Skip to content

Commit

Permalink
v0.3.0
Browse files Browse the repository at this point in the history
  • Loading branch information
shuritch committed Oct 19, 2023
1 parent cd4c869 commit c477027
Show file tree
Hide file tree
Showing 24 changed files with 348 additions and 275 deletions.
13 changes: 8 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,22 @@

## [Unreleased][unreleased]

## [1.0.0][] - 2023-10-1\*
<!-- ## [1.0.0][] - 2023-10-1\*
- Typescript types generation [issue](https://github.com/astrohelm/astroplan/issues/5)
- FS utilities [issue](https://github.com/astrohelm/astroplan/issues/9)
- Plan generation from sample [issue](https://github.com/astrohelm/astroplan/issues/10)
- DOCS & Typings & JSDoc [issue](https://github.com/astrohelm/astroplan/issues/11) -->

## [0.3.0][] - 2023-10-18
## [0.3.0][] - 2023-10-19

- Plan generation from sample [issue](https://github.com/astrohelm/astroplan/issues/10)
- DOCS & Typings & JSDoc [issue](https://github.com/astrohelm/astroplan/issues/11)
- Schema child method, now you can update only schema or only options for new schemas
- Getter & Setter for metadata, now you can gather all metadata by <code>schema.meta</code>
- Metadata fetcher, now you can gather all metadata by <code>schema.meta</code>
- Fixed case when schema returns only methods without metadata, now it returns prototypes key with
all metadata
- Preprocess & postprocess for objects properties mutations
- New prototype <code>enum</code>
- Code cleanup & tests

## [0.2.0][] - 2023-10-18

Expand Down
20 changes: 6 additions & 14 deletions lib/builder/index.js
Original file line number Diff line number Diff line change
@@ -1,29 +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;
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 });
},
};
};
18 changes: 9 additions & 9 deletions lib/builder/preprocess.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
'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);
Expand Down
2 changes: 1 addition & 1 deletion lib/parser/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ module.exports = function ParseStorage(argTypes) {

for (const [name, Type] of types.entries()) {
if (!Type.parse) continue;
for (const type of Type.parse.targets) store[name][type] = Type.parse;
for (const type of Type.parse.targets) store[type][name] = Type.parse;
}

return types.clear(), Object.freeze(Object.assign(this, store));
Expand Down
58 changes: 0 additions & 58 deletions lib/proto/arrays.js

This file was deleted.

22 changes: 22 additions & 0 deletions lib/proto/arrays/index.js
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'] });
31 changes: 31 additions & 0 deletions lib/proto/arrays/struct.js
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;
};
},
});
50 changes: 0 additions & 50 deletions lib/proto/constructor.js

This file was deleted.

35 changes: 35 additions & 0 deletions lib/proto/constructor/index.js
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);
};
23 changes: 23 additions & 0 deletions lib/proto/constructor/utils.js
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 };
16 changes: 8 additions & 8 deletions lib/proto/exotic.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,31 @@
'use strict';

const REQUIRED = 'Value is required';
const ERR_REQUIRED = 'Value is required';
const ERR_MISS = 'Not of expected type: object';
const META = { kind: 'scalar', origin: 'default' };
const any = {
meta: { kind: 'scalar', subtype: 'exotic' },
meta: META,
construct(plan, tools) {
const { isRequired, Error } = tools;
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.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 })];
};
},
};
Expand Down
3 changes: 1 addition & 2 deletions lib/proto/index.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
'use strict';

const createType = require('./constructor');

const prototypes = Object.entries({
...require('./scalars'),
...require('./objects'),
...require('./objects/index.js'),
...require('./arrays'),
...require('./exotic'),
});
Expand Down
Loading

0 comments on commit c477027

Please sign in to comment.