generated from astrohelm/node-workspace
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #15 from astrohelm/dev
Metaforge v0.3.0
- Loading branch information
Showing
26 changed files
with
545 additions
and
277 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,28 +1,21 @@ | ||
'use strict'; | ||
|
||
const preprocess = require('./preprocess'); | ||
|
||
//? [Preprocess] Purpose: find, join and build prototype by plan | ||
module.exports = (types, tools, plan) => { | ||
const prototypes = preprocess(types, tools, plan); | ||
if (prototypes.length === 1) return prototypes[0]; | ||
if (!prototypes.length) return { test: () => [], ts: () => 'unknown', debug: 'Check warnings' }; | ||
const { condition = 'anyof' } = plan; | ||
// TODO: assign Meta information | ||
const { condition: conditionName = 'anyof' } = plan; | ||
|
||
return { | ||
prototypes, | ||
type: () => 'object', | ||
test: (sample, path = 'root') => { | ||
const handler = tools.conditions(condition, prototypes.length - 1); | ||
const errors = []; | ||
for (let i = 0; i < prototypes.length; ++i) { | ||
const result = prototypes[i].test(sample, path); | ||
const [toDo, err] = handler(result, i); | ||
if (err) { | ||
if (result.length) errors.push(...result); | ||
else errors.push(`[${path}] => ${err}: ${JSON.stringify(sample)}`); | ||
} | ||
if (toDo === 'skip' || toDo === 'break') break; | ||
if (toDo === 'continue') continue; | ||
} | ||
return errors; | ||
const createError = cause => new tools.Error({ cause, path, sample }); | ||
const condition = tools.getCondition(conditionName, prototypes.length - 1); | ||
return tools.runCondition(condition, { path, sample, createError, prototypes }); | ||
}, | ||
}; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,32 +1,30 @@ | ||
'use strict'; | ||
|
||
const path = 'PREPROCESS'; | ||
const { string } = require('astropack'); | ||
const { string: astropack } = require('astropack'); | ||
const { typeOf, isShorthand } = require('../schema/tools'); | ||
const ERR_SHORTHAND = 'Shorthand usage with non-scalar schema'; | ||
const ERR_MISSING_PROTO = 'Missing prototype'; | ||
const ERR_MISSING_SCHEMA = 'Missing or wrong schema at namespace'; | ||
|
||
module.exports = ({ types, namespace }, tools, schema) => { | ||
const [prototypes, { warn }] = [[], tools]; | ||
const signal = (cause, sample, sampleType) => warn({ sample, sampleType, path, cause }); | ||
for (const type of typeOf(schema)) { | ||
if (string.case.isFirstUpper(type)) { | ||
if (astropack.case.isFirstUpper(type)) { | ||
const prototype = namespace.get(type); | ||
if (prototype && prototype.test) prototypes.push(prototype); | ||
else signal('Missing or wrong schema at namespace', namespace, type); | ||
else signal(ERR_MISSING_SCHEMA, namespace, type); | ||
continue; | ||
} | ||
const Type = types.get(type); | ||
if (!Type) { | ||
signal('Missing prototype', schema, type); | ||
continue; | ||
} | ||
if (Type.kind !== 'scalar' && isShorthand(schema)) { | ||
signal('Shorthand usage with non-scalar schema', schema, type); | ||
if (!Type || ((Type.kind !== 'scalar' || type === 'enum') && isShorthand(schema))) { | ||
if (!Type) signal(ERR_MISSING_PROTO, schema, type); | ||
else signal(ERR_SHORTHAND, schema, type); | ||
continue; | ||
} | ||
const prototype = new Type(schema, tools); | ||
prototypes.push(prototype); | ||
} | ||
return prototypes; | ||
}; | ||
|
||
// sql, input, runtime check, core, rpc protocol |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
'use strict'; | ||
|
||
const SKIP = ['undefined', 'function', 'symbol']; | ||
const ParseStorage = require('./store'); | ||
|
||
const createParser = store => { | ||
const parser = sample => { | ||
const type = Array.isArray(sample) ? 'array' : typeof sample; | ||
if (SKIP.includes(type) || (type === 'object' && !sample)) return '?unknown'; | ||
for (const [, parse] of store[type]) { | ||
const result = parse(sample, parser); | ||
if (result) return result; | ||
} | ||
return '?unknown'; | ||
}; | ||
return parser; | ||
}; | ||
|
||
module.exports = { createParser, ParseStorage }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
'use strict'; | ||
|
||
const STANDARD = ['string', 'number', 'bigint', 'boolean', 'object', 'array']; | ||
function TypedParseStorage(name, prototype) { | ||
const handlers = new Map(); | ||
this.toJSON = () => handlers; | ||
this.toString = () => JSON.stringify(handlers); | ||
this[Symbol.iterator] = function* () { | ||
yield* handlers.entries(); | ||
yield [name, prototype]; | ||
}; | ||
|
||
return new Proxy(this, { | ||
ownKeys: () => handlers.keys(), | ||
has: (_, prop) => handlers.has(prop), | ||
set: (_, prop, value) => handlers.set(prop, value), | ||
deleteProperty: (_, prop) => handlers.delete(prop), | ||
get: (target, prop) => { | ||
if (prop === Symbol.iterator) return this[Symbol.iterator].bind(target); | ||
return handlers.get(prop); | ||
}, | ||
}); | ||
} | ||
|
||
module.exports = function ParseStorage(argTypes) { | ||
const types = new Map([...argTypes]); | ||
const store = STANDARD.reduce((acc, name) => { | ||
acc[name] = new TypedParseStorage(name, types.get(name).parse); | ||
types.delete(name); | ||
return acc; | ||
}, {}); | ||
|
||
for (const [name, Type] of types.entries()) { | ||
if (!Type.parse) continue; | ||
for (const type of Type.parse.targets) store[type][name] = Type.parse; | ||
} | ||
|
||
return types.clear(), Object.freeze(Object.assign(this, store)); | ||
}; |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
'use strict'; | ||
|
||
const astropack = require('astropack'); | ||
const struct = require('./struct'); | ||
|
||
const parse = (sample, parse) => { | ||
if (!Array.isArray(sample)) return null; | ||
const items = sample.reduce((acc, sample) => { | ||
const plan = parse(sample); | ||
for (const saved of acc) if (astropack.utils.equals(saved, plan)) return acc; | ||
return acc.push(plan), acc; | ||
}, []); | ||
if (items.length === 0) return { type: 'array', items: ['unknown'] }; | ||
if (items.length === 1) return { type: 'array', items: [items[0]] }; | ||
return { type: 'array', items }; | ||
}; | ||
|
||
module.exports = { | ||
array: struct(value => Array.isArray(value)), | ||
set: struct(value => value?.constructor?.name === 'Set'), | ||
}; | ||
module.exports.array.parse = Object.assign(parse, { targets: ['object'] }); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
'use strict'; | ||
|
||
const ERR_MISS = 'Data type missconfiguration, expected: '; | ||
const ERR_EMPTY = 'Empty list recived, but required'; | ||
module.exports = isInstance => ({ | ||
meta: { kind: 'struct', origin: 'default' }, | ||
construct(plan, tools) { | ||
const { Error, build, getCondition } = tools; | ||
this.required = plan.required ?? true; | ||
this.items = plan.items.map(v => build(v)); | ||
this.condition = plan.condition ?? 'anyof'; | ||
|
||
this.ts = () => 'object'; | ||
this.test = (sample, path) => { | ||
const createError = cause => new Error({ path, sample, plan, cause }); | ||
if (!isInstance(sample)) return [createError(ERR_MISS + this.type)]; | ||
const entries = [...sample]; | ||
if (!entries.length && this.required) return [createError(ERR_EMPTY)]; | ||
const errors = []; | ||
for (let i = 0; i < entries.length; ++i) { | ||
const condition = getCondition(this.condition, this.items.length - 1); | ||
const suboption = { path: `${path}[${i}]`, sample: entries[i] }; | ||
const createError = cause => new tools.Error({ cause, plan, ...suboption }); | ||
const option = { createError, prototypes: this.items, ...suboption }; | ||
const result = tools.runCondition(condition, option); | ||
if (result.length) errors.push(...result); | ||
} | ||
return errors; | ||
}; | ||
}, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
'use strict'; | ||
|
||
const defaultTest = () => []; | ||
const INVALID = 'Invalid prototype, missing construct method'; | ||
const TEST_FAIL = `Didn't pass test`; | ||
const RULE_FAIL = `Didn't pass rule: `; | ||
const { parseResult: parse, setDefault } = require('./utils'); | ||
|
||
//? [Pre-preprocess] Purpose: Prototype wrapper and tester | ||
module.exports = (name, proto, defaultMeta) => { | ||
if (!proto?.construct || typeof proto.construct !== 'function') throw new Error(INVALID); | ||
const meta = { type: name }; | ||
function Type(plan, tools) { | ||
Object.assign(this, meta); | ||
if (plan.meta) Object.assign(this, plan.meta); | ||
proto.construct.call(this, plan, tools), setDefault(this, plan, tools); | ||
|
||
const [test, rules] = [this.test ?? defaultTest, plan.rules ?? []]; | ||
this.test = (sample, path = 'root') => { | ||
const options = { sample, path, tools, plan }; | ||
const errors = parse(test(sample, path), options, TEST_FAIL); | ||
if (rules.length === 0) return errors; | ||
for (let i = 0; i < rules.length; ++i) { | ||
const result = parse(rules[i](sample), options, RULE_FAIL + i); | ||
if (result.length > 0) errors.push(...result); | ||
} | ||
return errors; | ||
}; | ||
return Object.freeze(this); | ||
} | ||
if (proto.meta) Object.assign(meta, proto.meta); | ||
if (defaultMeta) Object.assign(meta, defaultMeta); | ||
if (proto.parse && Array.isArray(proto.parse.targets)) Type.parse = proto.parse; | ||
return Object.assign(Type, meta); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
'use strict'; | ||
|
||
const parseResult = (res, options, cause) => { | ||
const { sample, path, plan, tools } = options; | ||
if (typeof res === 'object' && Array.isArray(res)) { | ||
if (!res.length || res[0] instanceof tools.Error) return res; | ||
return res.map(v => parseResult(v, options)).flat(2); | ||
} | ||
if (typeof res === 'boolean' && !res) return [new tools.Error({ sample, path, plan, cause })]; | ||
if (typeof res === 'string') return [new tools.Error({ sample, path, plan, cause: res })]; | ||
if (res instanceof tools.Error) return [res]; | ||
return []; | ||
}; | ||
|
||
const setDefault = (ctx, plan, tools) => { | ||
if (typeof plan.preprocess === 'function') ctx.preprocess = plan.preprocess; | ||
if (typeof plan.postprocess === 'function') ctx.postprocess = plan.postprocess; | ||
if (!('required' in ctx)) ctx.required = tools.isRequired(plan); | ||
if (!ctx.origin) ctx.origin = 'custom'; | ||
if (!ctx.type) ctx.type = 'unknown'; | ||
}; | ||
|
||
module.exports = { parseResult, setDefault }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,33 +1,33 @@ | ||
'use strict'; | ||
|
||
const REQUIRED = 'Value is required'; | ||
const any = type => ({ | ||
meta: { kind: 'scalar', subtype: 'exotic' }, | ||
const ERR_REQUIRED = 'Value is required'; | ||
const ERR_MISS = 'Not of expected type: object'; | ||
const META = { kind: 'scalar', origin: 'default' }; | ||
const any = { | ||
meta: META, | ||
construct(plan, tools) { | ||
const { isRequired, Error } = tools; | ||
[this.type, this.required] = [type, isRequired(plan)]; | ||
this.required = isRequired(plan); | ||
this.ts = () => this.type; | ||
this.test = (sample, path) => { | ||
if (!(!this.required && sample === undefined)) return []; | ||
return [new Error({ path, sample, plan, cause: REQUIRED })]; | ||
return [new Error({ path, sample, plan, cause: ERR_REQUIRED })]; | ||
}; | ||
}, | ||
}); | ||
}; | ||
|
||
const json = { | ||
meta: { kind: 'struct', subtype: 'exotic' }, | ||
meta: { kind: 'struct', origin: 'default' }, | ||
construct(plan, tools) { | ||
const { isRequired, Error } = tools; | ||
[this.type, this.required] = ['json', isRequired(plan)]; | ||
this.required = isRequired(plan); | ||
this.ts = () => 'object'; | ||
this.test = (sample, path) => { | ||
if (!this.required && sample === undefined) return []; | ||
if (typeof sample === 'object' && sample) return []; | ||
const err = cause => new Error({ path, sample, plan, cause }); | ||
if (this.required && sample === undefined) return [err(REQUIRED)]; | ||
return [err('Not of expected type: object')]; | ||
if (!this.required && sample === undefined) return []; | ||
return [new Error({ path, sample, plan, cause: ERR_MISS })]; | ||
}; | ||
}, | ||
}; | ||
|
||
module.exports = { any: any('any'), unknown: any('unknown'), json }; | ||
module.exports = { any, unknown: any, json }; |
Oops, something went wrong.