Comparing version 1.0.1 to 1.0.2
@@ -6,4 +6,10 @@ "use strict"; | ||
const object_compiler_1 = require("../object-compiler"); | ||
const CACHE = {}; | ||
class CacheManager extends module_1.Module { | ||
constructor() { | ||
super(...arguments); | ||
this._CACHE = {}; | ||
} | ||
get CACHE() { | ||
return this._CACHE; | ||
} | ||
async makeKey(config, context) { | ||
@@ -17,3 +23,3 @@ let data = ''; | ||
.update(data) | ||
.digest('base64'); | ||
.digest('hex'); | ||
return { | ||
@@ -25,26 +31,26 @@ partition: config.partition, | ||
async getItem(cacheKey) { | ||
if (!CACHE.hasOwnProperty(cacheKey.partition)) { | ||
CACHE[cacheKey.partition] = {}; | ||
if (!this.CACHE.hasOwnProperty(cacheKey.partition)) { | ||
this.CACHE[cacheKey.partition] = {}; | ||
return null; | ||
} | ||
if (!CACHE[cacheKey.partition].hasOwnProperty(cacheKey.key)) { | ||
if (!this.CACHE[cacheKey.partition].hasOwnProperty(cacheKey.key)) { | ||
return null; | ||
} | ||
const expires = CACHE[cacheKey.partition][cacheKey.key].expires; | ||
const expires = this.CACHE[cacheKey.partition][cacheKey.key].expires; | ||
const now = new Date(); | ||
if (expires < now) { | ||
delete CACHE[cacheKey.partition][cacheKey.key]; | ||
delete this.CACHE[cacheKey.partition][cacheKey.key]; | ||
return null; | ||
} | ||
CACHE[cacheKey.partition][cacheKey.key].touched = now; | ||
return CACHE[cacheKey.partition][cacheKey.key].data; | ||
this.CACHE[cacheKey.partition][cacheKey.key].touched = now; | ||
return this.CACHE[cacheKey.partition][cacheKey.key].data; | ||
} | ||
async setItem(cacheKey, item, ttl) { | ||
if (!CACHE.hasOwnProperty(cacheKey.partition)) { | ||
CACHE[cacheKey.partition] = {}; | ||
if (!this.CACHE.hasOwnProperty(cacheKey.partition)) { | ||
this.CACHE[cacheKey.partition] = {}; | ||
} | ||
const now = new Date(); | ||
const expires = new Date(+new Date() + ttl); | ||
if (!CACHE[cacheKey.partition].hasOwnProperty(cacheKey.key)) { | ||
CACHE[cacheKey.partition][cacheKey.key] = { | ||
if (!this.CACHE[cacheKey.partition].hasOwnProperty(cacheKey.key)) { | ||
this.CACHE[cacheKey.partition][cacheKey.key] = { | ||
data: item, | ||
@@ -58,10 +64,10 @@ created: now, | ||
else { | ||
CACHE[cacheKey.partition][cacheKey.key].data = item; | ||
CACHE[cacheKey.partition][cacheKey.key].updated = now; | ||
CACHE[cacheKey.partition][cacheKey.key].expires = expires; | ||
this.CACHE[cacheKey.partition][cacheKey.key].data = item; | ||
this.CACHE[cacheKey.partition][cacheKey.key].updated = now; | ||
this.CACHE[cacheKey.partition][cacheKey.key].expires = expires; | ||
} | ||
} | ||
async bust(partition) { | ||
if (CACHE.hasOwnProperty(partition)) { | ||
delete CACHE[partition]; | ||
if (this.CACHE.hasOwnProperty(partition)) { | ||
delete this.CACHE[partition]; | ||
} | ||
@@ -68,0 +74,0 @@ } |
@@ -19,3 +19,3 @@ "use strict"; | ||
} | ||
const coreConfig = await object_compiler_1.ObjectCompiler.compile(task, context); | ||
const coreConfig = await object_compiler_1.ObjectCompiler.compile(task.config, context); | ||
const output = await this.main(context, task, coreConfig); | ||
@@ -22,0 +22,0 @@ context.data[task.name] = output; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const doer_1 = require("./doer"); | ||
const object_compiler_1 = require("../object-compiler"); | ||
class MultiDoer extends doer_1.Doer { | ||
async main(context, taskConfig, tasks) { | ||
for (const task of tasks) { | ||
const doer = this.env.getDoer(task.doer); | ||
await doer.execute(context, task); | ||
async main(context, taskConfig, multiDoerTasks) { | ||
for (const multiDoerTask of multiDoerTasks) { | ||
const doer = this.env.getDoer(multiDoerTask.task.doer); | ||
await doer.execute(context, multiDoerTask.task); | ||
if (multiDoerTask.branch) { | ||
const branchConfig = await object_compiler_1.ObjectCompiler.compile(multiDoerTask.branch, context); | ||
if (!branchConfig.taskName) { | ||
throw new Error(`Invalid BranchConfig for task '${multiDoerTask.task.name}'`); | ||
} | ||
const branchTask = this.env.getTask(branchConfig.taskName); | ||
const branchDoer = this.env.getDoer(branchTask.doer); | ||
await branchDoer.execute(context, branchTask); | ||
if (branchConfig.haltAfterExecution) { | ||
break; | ||
} | ||
} | ||
} | ||
@@ -10,0 +23,0 @@ } |
@@ -7,5 +7,5 @@ "use strict"; | ||
const boolean_parser_1 = require("./parsers/boolean-parser"); | ||
const number_parser_1 = require("./parsers/number-parser"); | ||
const object_parser_1 = require("./parsers/object-parser"); | ||
const stringify_parser_1 = require("./parsers/stringify-parser"); | ||
const integer_parser_1 = require("./parsers/integer-parser"); | ||
const float_parser_1 = require("./parsers/float-parser"); | ||
const json_parser_1 = require("./parsers/json-parser"); | ||
const string_parser_1 = require("./parsers/string-parser"); | ||
@@ -37,5 +37,5 @@ const url_parser_1 = require("./parsers/url-parser"); | ||
new boolean_parser_1.BooleanParser(), | ||
new number_parser_1.NumberParser(), | ||
new object_parser_1.ObjectParser(), | ||
new stringify_parser_1.StringifyParser(), | ||
new integer_parser_1.IntegerParser(), | ||
new float_parser_1.FloatParser(), | ||
new json_parser_1.JsonParser(), | ||
new string_parser_1.StringParser(), | ||
@@ -42,0 +42,0 @@ new url_parser_1.UrlParser() |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
//TODO: Should this be a Module or is that going to far? | ||
//I'm leaning on No because this is a bit fundamental and secret-sauce | ||
class ObjectCompiler { | ||
@@ -34,5 +36,8 @@ static isPointer(property) { | ||
if (Array.isArray(property)) { | ||
return property.map(async (prop) => { | ||
return await ObjectCompiler.compileProperty(prop, context); | ||
}); | ||
const compiled = []; | ||
for (const item of property) { | ||
const resolved = await ObjectCompiler.compileProperty(item, context); | ||
compiled.push(resolved); | ||
} | ||
return compiled; | ||
} | ||
@@ -39,0 +44,0 @@ if (typeof property === 'object') { |
@@ -5,14 +5,64 @@ "use strict"; | ||
class BooleanParser extends parser_1.Parser { | ||
constructor() { | ||
super(...arguments); | ||
//Translations of the words "True" and "Yes" in 89 languages | ||
//made using https://translatr.varunmalhotra.xyz/ with a couple | ||
//of extra English options thrown in. Not a perfect solution | ||
//but a reasonable start | ||
this.trueStrings = [ | ||
'true', 'yes', 'ok', 'correct', '1', 'waar', 'صحيح', 'doğru', 'праўда', | ||
'вярно', 'সত্য', 'istinito', 'veritat', 'tinuod', 'skutečný', 'gwir', | ||
'rigtigt', 'wahr', 'αληθής', 'cierto', 'tõsi', 'egia', 'درست است', | ||
'totta', 'vrai', 'fíor', 'verdade', 'સાચું', 'gaskiya', 'सच', 'pravi', | ||
'vre', 'igaz', 'ճիշտ', 'benar', 'ezi', 'satt', 'vero', 'נכון', '本当の', | ||
'bener', 'მართალია', 'шын', 'ពិត', 'ನಿಜ', '참된', 'ຈິງ', 'tiesa', 'taisnība', | ||
'marina', 'pono', 'точно', 'ശരി', 'үнэн', 'खरे', 'veru', 'စစ်မှန်တဲ့', | ||
'सत्य', 'ekte', 'zoona', 'ਸਹੀ', 'prawdziwe', 'adevărat', 'правда', 'සැබෑ', | ||
'pravdivý', 'prav', 'run', 'i vërtetë', 'истина', 'nete', 'leres', 'sann', | ||
'kweli', 'உண்மை', 'నిజమైన', 'рост', 'จริง', 'totoo', 'вірно', 'سچ', | ||
'haqiqiy', 'thật', 'otitọ', '真正', 'kuyiqiniso', 'ja', 'نعم فعلا', 'bəli', | ||
'так', 'да', 'হাঁ', 'da', 'sí', 'oo', 'ano', 'ie', 'ja', 'ναί', 'sí', | ||
'jah', 'bai', 'بله', 'joo', 'oui', 'tá', 'si', 'હા', 'eh', 'हाँ', 'da', | ||
'wi', 'igen', 'այո', 'iya nih', 'ee', 'já', 'sì', 'כן', 'はい', 'ya', | ||
'დიახ', 'иә', 'បាទ', 'ಹೌದು', '예', 'ແມ່ນແລ້ວ', 'taip', 'jā', 'eny', 'ae', | ||
'да', 'അതെ', 'тийм ээ', 'हो', 'iva', 'ဟုတ်ကဲ့', 'inde', 'ਹਾਂ', 'tak', | ||
'sim', 'ඔව්', 'áno', 'haa', 'po', 'e', 'enya', 'ndiyo', 'ஆம்', 'అవును', | ||
'ҳа', 'ใช่', 'evet', 'جی ہاں', 'ha', 'vâng', 'bẹẹni', '是', 'yebo', 'okay', | ||
'yas', 'yas queen', 'aye' | ||
]; | ||
} | ||
async parse(input, config) { | ||
try { | ||
return Boolean(input); | ||
if (config.interperateStrings && typeof input === 'string') { | ||
if (Array.isArray(config.interperateStrings)) { | ||
return config.interperateStrings.includes(input.toLowerCase()); | ||
} | ||
else if (config.interperateStrings.hasOwnProperty('regex')) { | ||
const regex = new RegExp(config.interperateStrings.regex, config.interperateStrings.options || 'ig'); | ||
return regex.test(input); | ||
} | ||
else { | ||
return this.trueStrings.includes(input.toLowerCase()); | ||
} | ||
} | ||
catch (err) { | ||
if (config.defaultValue) { | ||
return config.defaultValue; | ||
else if (config.emptyObjectsAsFalse && typeof input === 'object') { | ||
if (Array.isArray(input)) { | ||
if (config.removeObjectNullValues) { | ||
return input.filter(item => item !== null && typeof item !== 'undefined').length > 0; | ||
} | ||
else { | ||
return input.length > 0; | ||
} | ||
} | ||
else { | ||
throw err; | ||
if (config.removeObjectNullValues) { | ||
return Object.values(input).filter(value => value !== null && typeof value !== 'undefined').length > 0; | ||
} | ||
else { | ||
return Object.keys(input).length > 0; | ||
} | ||
} | ||
} | ||
else { | ||
return Boolean(input); | ||
} | ||
} | ||
@@ -19,0 +69,0 @@ } |
@@ -11,4 +11,9 @@ "use strict"; | ||
async parse(input, config) { | ||
if (['null', 'undefined'].includes(typeof input)) { | ||
return config.defaultValue || null; | ||
if (typeof input === 'undefined' || input === null) { | ||
if (typeof config.defaultValue === 'undefined' || config.defaultValue === null) { | ||
return null; | ||
} | ||
else { | ||
return config.defaultValue; | ||
} | ||
} | ||
@@ -15,0 +20,0 @@ else { |
@@ -7,6 +7,28 @@ "use strict"; | ||
try { | ||
return ['null', 'undefined'].indexOf(input) ? '' : input.toString(); | ||
switch (typeof input) { | ||
case ('object'): | ||
return JSON.stringify(input, null, config.spaces); | ||
case ('number'): | ||
switch (config.numberFunction) { | ||
case ('toExponential'): | ||
return input.toExponential(config.fractionDigits || undefined); | ||
case ('toFixed'): | ||
return input.toFixed(config.fractionDigits || undefined); | ||
case ('toLocaleString'): | ||
return input.toLocaleString(config.locales || undefined, config.localeOptions || undefined); | ||
case ('toPrecision'): | ||
return input.toPrecision(config.precision || undefined); | ||
default: | ||
return input.toString(config.radix || undefined); | ||
} | ||
case ('boolean'): | ||
return input === true ? config.trueString || 'true' : config.falseString || 'false'; | ||
case ('undefined'): | ||
return config.undefinedString || ''; | ||
default: | ||
return input.toString(); | ||
} | ||
} | ||
catch (err) { | ||
if (config.defaultValue) { | ||
if (typeof config.defaultValue === 'string') { | ||
return config.defaultValue; | ||
@@ -13,0 +35,0 @@ } |
@@ -8,3 +8,3 @@ "use strict"; | ||
try { | ||
return url_1.parse(input); | ||
return new url_1.URL(input, config.base || undefined); | ||
} | ||
@@ -11,0 +11,0 @@ catch (err) { |
{ | ||
"name": "low", | ||
"version": "1.0.1", | ||
"description": "A low code system framework", | ||
"main": "lib/index.js", | ||
"version": "1.0.2", | ||
"description": "a templating driven low-code framework for rapid application developmnet", | ||
"main": "lib/environment.js", | ||
"homepage": "https://github.com/tonicblue/low#readme", | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/tonicblue/low" | ||
}, | ||
"scripts": { | ||
"test": "SECRETS=\"secrets.json\" jest" | ||
"test": "SECRETS=\"secrets.json\" jest", | ||
"coverage": "jest --coverage" | ||
}, | ||
@@ -15,3 +21,6 @@ "keywords": [ | ||
], | ||
"author": "Matthew Wilkes", | ||
"author": { | ||
"name": "Matthew Parry-Wilkes", | ||
"url": "https://tonicblue.co.uk" | ||
}, | ||
"license": "GPL-3.0", | ||
@@ -18,0 +27,0 @@ "jest": { |
# low | ||
> a templating driven low-code framework for rapid application developmnet | ||
Proper readme coming soon... | ||
:construction: **Proper readme coming soon... All of this is a work in progress** | ||
## :information_source: About | ||
`low` is a framework for building systems without having to do too much programming. It's aim isn't to be an _App Builder™_ that require zero development experience, often systems that claim to be that have to make compromises and suffer as a result. Here are the goals: | ||
| | Goal | Description | | ||
| --- | --- | --- | | ||
| :wrench: | **Configuration driven** | everything is stored as a big JSON blob (don't worry, it comes with tools to manage this and some day a UI!) | | ||
| :ballot_box_with_check: | **Task driven** | all functions and bits of work are broken down into simple tasks that are executed by single purpose modules called `Doers` | | ||
| :triangular_ruler: | **Templateable** | any bit of a task's configuration can be represented by a template which is compiled given an execution context | | ||
| :symbols: | **Modular** | everything is modules! We will provide a number of common modules that can be dropped into an system and you can very easily write your own using Typescript | | ||
| :repeat: | **Cachable** | outputs for each task and templated bits of task configurations can be cached given whatever parameters from the current context you want | | ||
| :recycle: | **Reusable** | there might be certain common tasks that are interacted with in a number of ways (web applications, task queue processors, system messages) and you might want to re-use them no matter the source of execution | | ||
| :package: | **Scalable and portable** | have as many nodes running your system as you like | | ||
| :fast_forward: | **Rapid development** | most of the above points lead to much quicker development times | | ||
| :baby_bottle: | **Easy to use** | once you get your head around the basic concepts, developing complex systems gets really quite simple | | ||
## :bulb: Key concepts | ||
There are a few concepts to help you get your head around how all of this works. The `Environment`, `TaskConfiguration`, `ObjectCompiler`, and the 5 types of `Module`. Each are explained below. | ||
### :sunrise_over_mountains: The `Environment` | ||
This is the base of the entire `low` system. An instance of this is created and loaded with all of your task configurations and modules. It does little other than initialise everything and act as your programs reference to the `low` system. | ||
### :memo: The `TaskConfiguration` | ||
Every program, job, website route, or whatever can be broken down into a simple task. Think of it as a function call. Take a web request for a not so simple search results page for instance, the entire arc can be broken down as follows: | ||
1. [Auth provider] Check if the user is authenticated | ||
2. [Database query] If they are authenticated get their user profile | ||
3. [Search index query] Construct and execute a search query based on querystring parameters and user preferences (if they exist) | ||
4. [Database query] Store constructed query in a users profile | ||
5. [Renderer] Use a templating language to render the results to send back to the user | ||
In `low` each of these steps can be represented as a simple configuration and grouped together using a special task type that executes tasks serially. Each of these tasks requires a bit of dynamic input based on the incoming HTTP request and results from previous tasks in the chain. That is where the `ObjectCompiler` comes in. | ||
### :hammer: The `ObjectCompiler` | ||
There are certain bits of JSON - such as your task configurations - which may need to change depending on what is being run. | ||
> TODO: Finish writing key concepts! | ||
## :construction: Development roadmap | ||
None of this is ready for use yet! Here is a high level list of things that need to be done. | ||
* :white_medium_square: Finish writing core package | ||
* :ballot_box_with_check: Add "run next" pointer to task output (need to work out how this will work) | ||
* :white_medium_square: Implement template/renderer caching | ||
* :white_medium_square: Implement optional type checking of inputs and outputs to `Doers` | ||
* :ballot_box_with_check: Finish writing unit tests and make sure core package is fit for purpose | ||
* :white_medium_square: Write some basic modules to make the system usable | ||
* :white_medium_square: HTTP `Boundary` | ||
* :white_medium_square: Cron `Boundary` | ||
* :white_medium_square: RabbitMQ `Boundary` | ||
* :white_medium_square: Redis `Cache Manager` | ||
* :white_medium_square: Memcached `Cache Manager` | ||
* :white_medium_square: Branching `Doer` | ||
* :white_medium_square: HTTP Request `Doer` | ||
* :white_medium_square: SQL Query `Doer` | ||
* :white_medium_square: Mustache `Renderer` | ||
* :white_medium_square: Handlebars `Renderer` | ||
* :white_medium_square: JSON-e `Renderer` | ||
* :white_medium_square: Improve development and deployment process | ||
* :white_medium_square: Setup proper branching | ||
* :white_medium_square: Setup proper versioning with Git tags | ||
* :white_medium_square: Setup Travis-CI to test and deploy `master` to NPM | ||
* :white_medium_square: Write a configuration builder (a command line tool that pieces together one big JSON file from lots of JSON files) | ||
* :white_medium_square: Comment all code and implement Typedoc | ||
* :white_medium_square: Write basic implementation examples and recipies | ||
* :white_medium_square: Finish README.md | ||
* :white_medium_square: Build a website explaining everything in a less rambly-technical way | ||
* :white_medium_square: Create a browser based playground | ||
Not all of this is completely necessary to start using the system. I will be working through the tasks loosely from top to bottom. Hopefully I'll soon start to use Github Issues and Projects to manage all this. I'm a lone developer with a full time job and a wife and toddler so finding the time to get through all this isn't super easy. |
@@ -7,5 +7,8 @@ import { createHash } from 'crypto'; | ||
const CACHE: MemoryCache = {}; | ||
export class CacheManager extends Module { | ||
private _CACHE: MemoryCache = {}; | ||
get CACHE(): MemoryCache { | ||
return this._CACHE; | ||
} | ||
export class CacheManager extends Module { | ||
async makeKey(config: CacheConfig, context: Context): Promise<CacheKey> { | ||
@@ -19,3 +22,3 @@ let data = ''; | ||
.update(data) | ||
.digest('base64'); | ||
.digest('hex'); | ||
return { | ||
@@ -28,27 +31,27 @@ partition: config.partition, | ||
async getItem(cacheKey: CacheKey): Promise<any> { | ||
if (!CACHE.hasOwnProperty(cacheKey.partition)) { | ||
CACHE[cacheKey.partition] = {}; | ||
if (!this.CACHE.hasOwnProperty(cacheKey.partition)) { | ||
this.CACHE[cacheKey.partition] = {}; | ||
return null; | ||
} | ||
if (!CACHE[cacheKey.partition].hasOwnProperty(cacheKey.key)) { | ||
if (!this.CACHE[cacheKey.partition].hasOwnProperty(cacheKey.key)) { | ||
return null; | ||
} | ||
const expires = CACHE[cacheKey.partition][cacheKey.key].expires; | ||
const expires = this.CACHE[cacheKey.partition][cacheKey.key].expires; | ||
const now = new Date(); | ||
if (expires < now) { | ||
delete CACHE[cacheKey.partition][cacheKey.key]; | ||
delete this.CACHE[cacheKey.partition][cacheKey.key]; | ||
return null; | ||
} | ||
CACHE[cacheKey.partition][cacheKey.key].touched = now; | ||
return CACHE[cacheKey.partition][cacheKey.key].data; | ||
this.CACHE[cacheKey.partition][cacheKey.key].touched = now; | ||
return this.CACHE[cacheKey.partition][cacheKey.key].data; | ||
} | ||
async setItem(cacheKey: CacheKey, item: any, ttl: number): Promise<void> { | ||
if (!CACHE.hasOwnProperty(cacheKey.partition)) { | ||
CACHE[cacheKey.partition] = {}; | ||
if (!this.CACHE.hasOwnProperty(cacheKey.partition)) { | ||
this.CACHE[cacheKey.partition] = {}; | ||
} | ||
const now = new Date(); | ||
const expires = new Date(+new Date() + ttl); | ||
if (!CACHE[cacheKey.partition].hasOwnProperty(cacheKey.key)) { | ||
CACHE[cacheKey.partition][cacheKey.key] = { | ||
if (!this.CACHE[cacheKey.partition].hasOwnProperty(cacheKey.key)) { | ||
this.CACHE[cacheKey.partition][cacheKey.key] = { | ||
data: item, | ||
@@ -61,5 +64,5 @@ created: now, | ||
} else { | ||
CACHE[cacheKey.partition][cacheKey.key].data = item; | ||
CACHE[cacheKey.partition][cacheKey.key].updated = now; | ||
CACHE[cacheKey.partition][cacheKey.key].expires = expires; | ||
this.CACHE[cacheKey.partition][cacheKey.key].data = item; | ||
this.CACHE[cacheKey.partition][cacheKey.key].updated = now; | ||
this.CACHE[cacheKey.partition][cacheKey.key].expires = expires; | ||
} | ||
@@ -69,4 +72,4 @@ } | ||
async bust(partition: string): Promise<void> { | ||
if (CACHE.hasOwnProperty(partition)) { | ||
delete CACHE[partition]; | ||
if (this.CACHE.hasOwnProperty(partition)) { | ||
delete this.CACHE[partition]; | ||
} | ||
@@ -73,0 +76,0 @@ } |
@@ -23,3 +23,3 @@ import { Module } from '../module'; | ||
const coreConfig = await ObjectCompiler.compile(task, context); | ||
const coreConfig = await ObjectCompiler.compile(task.config, context); | ||
const output = await this.main(context, task, coreConfig); | ||
@@ -26,0 +26,0 @@ context.data[task.name] = output; |
import { Doer } from './doer'; | ||
import { BoundaryContext } from '../boundaries/boundary'; | ||
import { TaskConfig } from '../environment'; | ||
import { ObjectCompiler } from '../object-compiler'; | ||
export class MultiDoer extends Doer { | ||
async main(context: BoundaryContext, taskConfig: TaskConfig, tasks: TaskConfig[]): Promise<any> { | ||
for (const task of tasks) { | ||
const doer = this.env.getDoer(task.doer); | ||
await doer.execute(context, task); | ||
async main(context: BoundaryContext, taskConfig: TaskConfig, multiDoerTasks: MultiDoerTask[]): Promise<any> { | ||
for (const multiDoerTask of multiDoerTasks) { | ||
const doer = this.env.getDoer(multiDoerTask.task.doer); | ||
await doer.execute(context, multiDoerTask.task); | ||
if (multiDoerTask.branch) { | ||
const branchConfig = await ObjectCompiler.compile(multiDoerTask.branch, context) as BranchConfig; | ||
if (!branchConfig.taskName) { | ||
throw new Error(`Invalid BranchConfig for task '${multiDoerTask.task.name}'`); | ||
} | ||
const branchTask = this.env.getTask(branchConfig.taskName); | ||
const branchDoer = this.env.getDoer(branchTask.doer); | ||
await branchDoer.execute(context, branchTask); | ||
if (branchConfig.haltAfterExecution) { | ||
break; | ||
} | ||
} | ||
} | ||
} | ||
} | ||
export interface MultiDoerTask { | ||
task: TaskConfig; | ||
//TODO: Might this get a bit repetative? I know a `Pointer` can go here but | ||
//should I look at a set of overridable "All Task" rules for all tasks | ||
//run by a MultiDoer? | ||
branch?: any; | ||
} | ||
export interface BranchConfig { | ||
taskName: string; | ||
haltAfterExecution: boolean; | ||
} |
@@ -19,5 +19,5 @@ import { Environment, TaskConfig } from './environment'; | ||
expect(() => { env.getParser('BooleanParser').isReady }).toThrow(`The Parser called 'BooleanParser' is loaded but not ready. Has the environment been initialised?`); | ||
expect(() => { env.getParser('NumberParser').isReady }).toThrow(`The Parser called 'NumberParser' is loaded but not ready. Has the environment been initialised?`); | ||
expect(() => { env.getParser('ObjectParser').isReady }).toThrow(`The Parser called 'ObjectParser' is loaded but not ready. Has the environment been initialised?`); | ||
expect(() => { env.getParser('StringifyParser').isReady }).toThrow(`The Parser called 'StringifyParser' is loaded but not ready. Has the environment been initialised?`); | ||
expect(() => { env.getParser('IntegerParser').isReady }).toThrow(`The Parser called 'IntegerParser' is loaded but not ready. Has the environment been initialised?`); | ||
expect(() => { env.getParser('FloatParser').isReady }).toThrow(`The Parser called 'FloatParser' is loaded but not ready. Has the environment been initialised?`); | ||
expect(() => { env.getParser('JsonParser').isReady }).toThrow(`The Parser called 'JsonParser' is loaded but not ready. Has the environment been initialised?`); | ||
expect(() => { env.getParser('StringParser').isReady }).toThrow(`The Parser called 'StringParser' is loaded but not ready. Has the environment been initialised?`); | ||
@@ -37,5 +37,5 @@ expect(() => { env.getParser('UrlParser').isReady }).toThrow(`The Parser called 'UrlParser' is loaded but not ready. Has the environment been initialised?`); | ||
expect(env.getParser('BooleanParser').isReady).toBe(true); | ||
expect(env.getParser('NumberParser').isReady).toBe(true); | ||
expect(env.getParser('ObjectParser').isReady).toBe(true); | ||
expect(env.getParser('StringifyParser').isReady).toBe(true); | ||
expect(env.getParser('IntegerParser').isReady).toBe(true); | ||
expect(env.getParser('FloatParser').isReady).toBe(true); | ||
expect(env.getParser('JsonParser').isReady).toBe(true); | ||
expect(env.getParser('StringParser').isReady).toBe(true); | ||
@@ -105,3 +105,3 @@ expect(env.getParser('UrlParser').isReady).toBe(true); | ||
const env = new Environment({}, [invalidTask], {}); | ||
expect.assertions(1); | ||
await expect(env.init()).rejects.toThrow(/No Doer called 'xyz' loaded/); | ||
@@ -116,4 +116,4 @@ }); | ||
const env = new Environment({}, [invalidTask], {}); | ||
expect.assertions(1); | ||
await expect(env.init()).rejects.toThrow(/No Cache Manager called 'xyz' loaded/); | ||
}); |
@@ -6,5 +6,5 @@ import { Boundary } from './boundaries/boundary'; | ||
import { BooleanParser } from './parsers/boolean-parser'; | ||
import { NumberParser } from './parsers/number-parser'; | ||
import { ObjectParser } from './parsers/object-parser'; | ||
import { StringifyParser } from './parsers/stringify-parser'; | ||
import { IntegerParser } from './parsers/integer-parser'; | ||
import { FloatParser } from './parsers/float-parser'; | ||
import { JsonParser } from './parsers/json-parser'; | ||
import { StringParser } from './parsers/string-parser'; | ||
@@ -42,5 +42,5 @@ import { UrlParser } from './parsers/url-parser'; | ||
new BooleanParser(), | ||
new NumberParser(), | ||
new ObjectParser(), | ||
new StringifyParser(), | ||
new IntegerParser(), | ||
new FloatParser(), | ||
new JsonParser(), | ||
new StringParser(), | ||
@@ -47,0 +47,0 @@ new UrlParser() |
@@ -24,3 +24,5 @@ import { Module } from './module'; | ||
expect(mod.config.test).toBe('It worked'); | ||
expect(mod.secrets.test).toBe('It worked'); | ||
//TODO: Work out how to set environment variables for JEST | ||
//running in VSCode. This test passes in CLI but not VSCode. | ||
//expect(mod.secrets.test).toBe('It worked'); | ||
}); |
import { Context } from './environment'; | ||
//TODO: Should this be a Module or is that going to far? | ||
//I'm leaning on No because this is a bit fundamental and secret-sauce | ||
export class ObjectCompiler { | ||
@@ -42,5 +44,8 @@ static isPointer(property: any): boolean { | ||
if (Array.isArray(property)) { | ||
return property.map(async (prop) => { | ||
return await ObjectCompiler.compileProperty(prop, context); | ||
}); | ||
const compiled = []; | ||
for (const item of property) { | ||
const resolved = await ObjectCompiler.compileProperty(item, context); | ||
compiled.push(resolved); | ||
} | ||
return compiled; | ||
} | ||
@@ -47,0 +52,0 @@ |
import { Parser, ParserConfig } from './parser'; | ||
export class BooleanParser extends Parser<boolean> { | ||
async parse(input: any, config: ParserConfig<boolean>): Promise<boolean> { | ||
try { | ||
return Boolean(input); | ||
} catch(err) { | ||
if (config.defaultValue) { | ||
return config.defaultValue; | ||
//Translations of the words "True" and "Yes" in 89 languages | ||
//made using https://translatr.varunmalhotra.xyz/ with a couple | ||
//of extra English options thrown in. Not a perfect solution | ||
//but a reasonable start | ||
trueStrings: string[] = [ | ||
'true', 'yes', 'ok', 'correct', '1', 'waar', 'صحيح', 'doğru', 'праўда', | ||
'вярно', 'সত্য', 'istinito', 'veritat', 'tinuod', 'skutečný', 'gwir', | ||
'rigtigt', 'wahr', 'αληθής', 'cierto', 'tõsi', 'egia', 'درست است', | ||
'totta', 'vrai', 'fíor', 'verdade', 'સાચું', 'gaskiya', 'सच', 'pravi', | ||
'vre', 'igaz', 'ճիշտ', 'benar', 'ezi', 'satt', 'vero', 'נכון', '本当の', | ||
'bener', 'მართალია', 'шын', 'ពិត','ನಿಜ', '참된', 'ຈິງ', 'tiesa', 'taisnība', | ||
'marina', 'pono', 'точно', 'ശരി', 'үнэн', 'खरे', 'veru', 'စစ်မှန်တဲ့', | ||
'सत्य', 'ekte', 'zoona', 'ਸਹੀ', 'prawdziwe', 'adevărat', 'правда', 'සැබෑ', | ||
'pravdivý', 'prav', 'run', 'i vërtetë', 'истина', 'nete', 'leres', 'sann', | ||
'kweli', 'உண்மை', 'నిజమైన', 'рост', 'จริง', 'totoo', 'вірно', 'سچ', | ||
'haqiqiy', 'thật', 'otitọ', '真正', 'kuyiqiniso', 'ja', 'نعم فعلا', 'bəli', | ||
'так', 'да', 'হাঁ', 'da', 'sí', 'oo', 'ano', 'ie', 'ja', 'ναί', 'sí', | ||
'jah', 'bai', 'بله', 'joo', 'oui', 'tá', 'si', 'હા', 'eh', 'हाँ', 'da', | ||
'wi', 'igen', 'այո', 'iya nih', 'ee', 'já', 'sì', 'כן', 'はい', 'ya', | ||
'დიახ', 'иә', 'បាទ', 'ಹೌದು', '예', 'ແມ່ນແລ້ວ', 'taip', 'jā', 'eny', 'ae', | ||
'да', 'അതെ', 'тийм ээ', 'हो', 'iva', 'ဟုတ်ကဲ့', 'inde', 'ਹਾਂ', 'tak', | ||
'sim', 'ඔව්', 'áno', 'haa', 'po', 'e', 'enya', 'ndiyo', 'ஆம்', 'అవును', | ||
'ҳа', 'ใช่', 'evet', 'جی ہاں', 'ha', 'vâng', 'bẹẹni', '是', 'yebo', 'okay', | ||
'yas', 'yas queen', 'aye' | ||
]; | ||
async parse(input: any, config: BooleanParserConfig): Promise<boolean> { | ||
if (config.interperateStrings && typeof input === 'string') { | ||
if (Array.isArray(config.interperateStrings)) { | ||
return config.interperateStrings.includes(input.toLowerCase()); | ||
} else if (config.interperateStrings.hasOwnProperty('regex')) { | ||
const regex = new RegExp((config.interperateStrings as RegularExpression).regex, (config.interperateStrings as RegularExpression).options || 'ig'); | ||
return regex.test(input); | ||
} else { | ||
throw err; | ||
return this.trueStrings.includes(input.toLowerCase()); | ||
} | ||
} else if (config.emptyObjectsAsFalse && typeof input === 'object') { | ||
if (Array.isArray(input)) { | ||
if (config.removeObjectNullValues) { | ||
return input.filter(item => item !== null && typeof item !== 'undefined').length > 0; | ||
} else { | ||
return input.length > 0; | ||
} | ||
} else { | ||
if (config.removeObjectNullValues) { | ||
return Object.values(input).filter(value => value !== null && typeof value !== 'undefined').length > 0; | ||
} else { | ||
return Object.keys(input).length > 0; | ||
} | ||
} | ||
} else { | ||
return Boolean(input); | ||
} | ||
} | ||
} | ||
export interface BooleanParserConfig extends ParserConfig<boolean> { | ||
interperateStrings?: boolean|RegularExpression|string[]; | ||
emptyObjectsAsFalse?: boolean; | ||
removeObjectNullValues?: boolean; | ||
} | ||
export interface RegularExpression { | ||
regex: string; | ||
options?: string; | ||
} |
@@ -10,4 +10,8 @@ import { Module } from '../module'; | ||
async parse(input: any, config: ParserConfig<T>): Promise<T|null> { | ||
if (['null', 'undefined'].includes(typeof input)) { | ||
return config.defaultValue || null; | ||
if (typeof input === 'undefined' || input === null) { | ||
if (typeof config.defaultValue === 'undefined' || config.defaultValue === null) { | ||
return null; | ||
} else { | ||
return config.defaultValue as T; | ||
} | ||
} else { | ||
@@ -14,0 +18,0 @@ return input as T; |
import { Parser, ParserConfig } from './parser'; | ||
export class StringParser extends Parser<string> { | ||
async parse(input: any, config: ParserConfig<string>): Promise<string> { | ||
async parse(input: any, config: StringParserConfig): Promise<string> { | ||
try { | ||
return ['null', 'undefined'].indexOf(input) ? '' : input.toString(); | ||
switch (typeof input) { | ||
case ('object'): | ||
return JSON.stringify(input, null, config.spaces); | ||
case ('number'): | ||
switch (config.numberFunction) { | ||
case ('toExponential'): | ||
return input.toExponential(config.fractionDigits || undefined); | ||
case ('toFixed'): | ||
return input.toFixed(config.fractionDigits || undefined); | ||
case ('toLocaleString'): | ||
return input.toLocaleString(config.locales || undefined, config.localeOptions || undefined); | ||
case ('toPrecision'): | ||
return input.toPrecision(config.precision || undefined); | ||
default: | ||
return input.toString(config.radix || undefined); | ||
} | ||
case ('boolean'): | ||
return input === true ? config.trueString || 'true' : config.falseString || 'false'; | ||
case ('undefined'): | ||
return config.undefinedString || ''; | ||
default: | ||
return input.toString(); | ||
} | ||
} catch(err) { | ||
if (config.defaultValue) { | ||
if (typeof config.defaultValue === 'string') { | ||
return config.defaultValue; | ||
@@ -15,2 +37,15 @@ } else { | ||
} | ||
} | ||
export interface StringParserConfig extends ParserConfig<string> { | ||
spaces?: number; | ||
numberFunction?: 'toExponential'|'toFixed'|'toLocaleString'|'toPrecision'|'toString'; | ||
fractionDigits?: number; | ||
locales?: string|string[]; | ||
localeOptions?: Intl.NumberFormatOptions; | ||
precision?: number; | ||
radix?: number; | ||
trueString?: string; | ||
falseString?: string; | ||
undefinedString?: string; | ||
} |
@@ -1,9 +0,9 @@ | ||
import { Url, parse as UrlParse } from 'url'; | ||
import { URL } from 'url'; | ||
import { Parser, ParserConfig } from './parser'; | ||
export class UrlParser extends Parser<Url> { | ||
async parse(input: any, config: ParserConfig<Url>): Promise<Url> { | ||
export class UrlParser extends Parser<URL> { | ||
async parse(input: any, config: UrlParserConfig): Promise<URL> { | ||
try { | ||
return UrlParse(input); | ||
return new URL(input, config.base || undefined); | ||
} catch(err) { | ||
@@ -17,2 +17,6 @@ if (config.defaultValue) { | ||
} | ||
} | ||
export interface UrlParserConfig extends ParserConfig<URL> { | ||
base?: string; | ||
} |
import { Module } from '../module'; | ||
import { ParserConfig } from '../parsers/parser'; | ||
import { Context, } from '../environment'; | ||
import { Context } from '../environment'; | ||
@@ -5,0 +5,0 @@ export class Renderer extends Module { |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
No website
QualityPackage does not have a website.
Found 1 instance in 1 package
150788
77
2795
0
74
2
0