Skip to content

Commit

Permalink
v0.7.0
Browse files Browse the repository at this point in the history
  • Loading branch information
shuritch committed Nov 7, 2023
1 parent f76e5b8 commit ac4779f
Show file tree
Hide file tree
Showing 15 changed files with 163 additions and 87 deletions.
8 changes: 5 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@

## [Unreleased][unreleased]

## [0.7.0][] - 2023-11-07
## [0.7.0][] - 2023-11-08

- Calculated fields
- Calculated fields **[experemental]**
- Readme & tests for module
- Fixed README example errors

## [0.6.0][] - 2023-11-06

Expand Down Expand Up @@ -92,7 +93,8 @@
- Default exotic types: Any, Undefined, JSON
- Custom Errors

[unreleased]: https://github.com/astrohelm/metaforge/compare/v0.6.0...HEAD
[unreleased]: https://github.com/astrohelm/metaforge/compare/v0.7.0...HEAD
[0.7.0]: https://github.com/astrohelm/metaforge/compare/v0.6.0...v0.7.0
[0.6.0]: https://github.com/astrohelm/metaforge/compare/v0.5.0...v0.6.0
[0.5.0]: https://github.com/astrohelm/metaforge/compare/v0.4.0...v0.5.0
[0.4.0]: https://github.com/astrohelm/metaforge/compare/v0.3.0...v0.4.0
Expand Down
7 changes: 3 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<h1 align="center">MetaForge v0.6.0 🕵️</h1>
<h1 align="center">MetaForge v0.7.0 🕵️</h1>

## Describe your data structures by subset of JavaScript and:

Expand All @@ -22,7 +22,7 @@ const userSchema = new Schema({
$meta: { name: 'user', description: 'schema for users testing' },
phone: { $type: 'union', types: ['number', 'string'] }, //? number or string
name: { $type: 'set', items: ['string', '?string'] }, //? set tuple
prase: (parent, root) => 'Hello ' + [...parent.name].join(' ') + ' !', // Calculated fields
prase: (sample, parent, root) => 'Hello ' + [...parent.name].join(' ') + ' !', // Calculated fields
mask: { $type: 'array', items: 'string' }, //? array
ip: {
$type: 'array',
Expand Down Expand Up @@ -65,14 +65,13 @@ systemSchema.pull('userSchema'); // Metadata: {..., name: 'user', description: '
- ### [About modules / plugins](./docs/modules.md#modules-or-another-words-plugins)
- [Writing custom modules](./docs/modules.md#writing-custom-modules)
- [Metatype](./modules/types/README.md) | generate type annotations from schema
- [Metacalc](./modules/calc/README.md) | schema preprocessing module
- [Handyman](./modules/handyman/README.md) | quality of life module
- [Metatest](./modules/test/README.md) | adds prototype testing
- ### [About prototypes](./docs/prototypes.md#readme-map)
- [How to build custom prototype](./docs/prototypes.md#writing-custom-prototypes)
- [Contracts](./docs/prototypes.md#schemas-contracts)

## Copyright & contributors
<h2 align="center">Copyright & contributors</h2>

<p align="center">
Copyright © 2023 <a href="https://github.com/astrohelm/metaforge/graphs/contributors">Astrohelm contributors</a>.
Expand Down
1 change: 0 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ function Schema(plan, options = {}) {
[this.forge, this.warnings] = [forge, warnings];
for (const [name, plugin] of modules.entries()) this.register(name, plugin);
return Object.assign(this, this.tools.build(plan));

function build(plan) {
const Type = forge.get(plan.$type);
if (Type) return new Type(plan);
Expand Down
2 changes: 1 addition & 1 deletion lib/forge.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ module.exports = function Forge(schema, custom = {}) {
return function ForgePrototype(plan) {
const meta = plan.$meta;
if (plan.$id) this.$id = plan.$id;
[this.$required, this.$type, this.$plan] = [plan.$required ?? true, name, plan];
[this.$required, this.$type] = [plan.$required ?? true, name];
if (meta && typeof meta === 'object' && !Array.isArray(meta)) Object.assign(this, meta);
[...before, ...chain, ...after].forEach(proto => proto.call(this, plan, schema.tools));
if (!this.$kind) this.$kind = 'unknown';
Expand Down
2 changes: 1 addition & 1 deletion lib/proto.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ function List(plan, { warn, build }) {
const PLANS_ERROR = 'Revievd plan without properties';
function Struct(plan, { build, warn }) {
[this.$kind, this.$properties, this.$patterns] = ['struct', new Map(), new Map()];
this.$isRecord = this.$type === 'record' || plan.isRecord;
this.$isRecord = this.$type === 'record' || (plan.isRecord ?? false);
this.$requires = [];
!plan.properties && warn({ plan, sample: plan.properties, cause: PLANS_ERROR });
for (const [key, value] of Object.entries(plan.properties ?? {})) {
Expand Down
34 changes: 0 additions & 34 deletions modules/calc/README.md

This file was deleted.

19 changes: 0 additions & 19 deletions modules/calc/index.js

This file was deleted.

14 changes: 0 additions & 14 deletions modules/calc/index.test.js

This file was deleted.

41 changes: 39 additions & 2 deletions modules/handyman/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Handyman module allow you to:
- pass schemas as namespace parameter
- pass schemas as plans
- pull sub schemas by $id
- calculate fields

## With module:

Expand Down Expand Up @@ -34,7 +35,7 @@ const schema = new Schema({
properties: {
test: { $type: 'string' },
test2: { $type: 'schema', schema: SubSchema },
test3: { $type: schema, schema: new Schema({ $type: 'string' }), $required: false };
test3: { $type: 'schema', schema: new Schema({ $type: 'string' }), $required: false };
test4: { $type: 'schema', schema: MyNamespaceSchema }
},
});
Expand Down Expand Up @@ -62,10 +63,46 @@ const schema = new Schema(
e: ['winter', 'spring'], //? Enum shorthand
f: { a: 'number', b: 'string' }, //? Object shorthand
g: { $type: 'array', items: 'string' }, //? Array items shorthand
h: 'MyExternalSchema',
h: 'MyExternalSchema', //? Namespace schema shorthand
},
{ namespace: { MyExternalSchema: new Schema('string') } },
);
```

> String shorthand is analog to <code>{ $type: type, required: type.includes('?') } </code>
## Calculated fields

Calculated fields supposed to do preprocessing of your schema;

> Warning: **experimental**. We do not support some types yet: Map, Set
### Example

```js
const schema = {
$id: 'user',
name: 'string',
phrase: (sample, schema, root) => 'Hello ' + schema.name + ' !',
};
const sample = { name: 'Alexander' };
new Schema(schema).calc(sample); // { ..., name: 'Alexander', phrase: 'Hello Alexander !'};
schema; // { $id: 'user', name: 'Alexander', phrase: 'Hello Alexander !'};
```

### Writing calculated fields

Calculated fields is a function that receives two arguments:

- root: root object <code>{ input: Sample }</code>
- parent: assigned target object

> Warning: your return value will be assigned to samples
### Additional

Method <code>schema.calc</code> receives mode as second parameter; This method allow to specify
return value as:

- Schema.calc(sample, true); // Returns copy of sample with assigned values
- Schema.calc(sample); // Returns sample object with assigned values
32 changes: 32 additions & 0 deletions modules/handyman/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
const RepairKit = require('./repairkit');
const MISSING_MODULE = 'Missing plugin: handyman';
const MODULES_ERROR = 'Received sub schema with different modules, may be incompatible: ';
const TRAVERSE_PATH = ['$properties', '$items'];
const entries = v => (v?.constructor?.name === 'Map' ? v.entries() : Object.entries(v));
const copy = sample => (Array.isArray(sample) ? [...sample] : { ...sample });

module.exports = (schema, options) => {
const namespace = options.namespace ? new Map(entries(options.namespace)) : new Map();
Expand All @@ -21,10 +23,40 @@ module.exports = (schema, options) => {
return null;
};

//TODO(sashapop10): Set problem
schema.calculate = (sample, mode) => {
let root = mode && typeof sample === 'object' ? copy(sample) : sample;
const calc = schema.$calc;
if (typeof calc === 'function') root = calc(root, root);
else if (calc) root = calc;
if (typeof root !== 'object') return root;
return traverse(root, schema);
function traverse(parent, schema) {
const data = mode ? copy(parent) : parent;
for (const key of TRAVERSE_PATH) {
if (schema.$type === 'set') continue;
const children = schema[key];
if (!children) continue;
const iterator = key === '$properties' ? [...children.keys()] : Object.keys(data);
for (const prop of iterator) {
const schema = children.get?.(prop) ?? children[prop] ?? children[0];
if (!schema) console.log(schema, prop, iterator);
const calc = schema.$calc;
let sample = data[prop];
if (typeof calc === 'function') sample = data[prop] = calc(sample, parent, root);
else if (calc) sample = data[prop] = calc;
if (typeof sample === 'object') data[prop] = traverse(sample, schema);
}
}
return data;
}
};

tools.build = plan => {
const fixed = repair(plan);
const builded = build(fixed);
const { $id } = builded;
if (fixed.$calc) builded.$calc = fixed.$calc;
if (fixed.$type !== 'schema') return builded;
const [a, b] = [[...modules.keys()], [...builded.modules.keys()]];
!a.every(key => b.includes(key)) && warn({ cause: MODULES_ERROR + $id, plan, sample: b });
Expand Down
70 changes: 68 additions & 2 deletions modules/handyman/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,76 @@ test('[Handyman] Shorthands', () => {
e: ['winter', 'spring'], //? Enum shorthand
f: { a: 'number', b: 'string' }, //? Object shorthand
g: { $type: 'array', items: 'string' }, //? Array items shorthand
h: 'MyExternalSchema',
h: 'MyExternalSchema', //? Namespace schema
j: () => 2 + 2, //? Calculated field
},
{ namespace: { MyExternalSchema: new Schema('string') } },
);

assert.strictEqual(schema.warnings.length, 0);
});

test('[Handyman] Calculated fields (basic)', () => {
const string = new Schema({ type: 'string', $calc: schema => 'Hello ' + schema });
assert.strictEqual(string.warnings.length, 0);
assert.strictEqual(string.calculate('Alexander'), 'Hello Alexander');
const enumerable = new Schema({ $type: 'enum', enum: ['Alexander', 'John'], $calc: 'John' });
assert.strictEqual(enumerable.warnings.length, 0);
assert.strictEqual(enumerable.calculate('Alexander'), 'John');
const union = new Schema({ $type: 'union', types: ['string', 'number'], $calc: 'John' });
assert.strictEqual(union.warnings.length, 0);
assert.strictEqual(union.calculate('Alexander'), 'John');
const object = new Schema({
name: 'string',
phrase: (_, parent) => 'Hello ' + parent.name + ' !',
phrase2: { $type: 'string', $calc: (_, schema) => 'Hello there ' + schema.name + ' !' },
phrase3: {
$type: 'schema',
schema: new Schema('string'),
$calc: (_, schema) => 'Hi ' + schema.name + ' !',
},
phrase4: new Schema({ $type: 'string', $calc: (_, schema) => 'Ni hao ' + schema.name + ' !' }),
});
assert.strictEqual(object.warnings.length, 0);
assert.deepStrictEqual(object.calculate({ name: 'Alexander' }), {
name: 'Alexander',
phrase: 'Hello Alexander !',
phrase2: 'Hello there Alexander !',
phrase3: 'Hi Alexander !',
phrase4: 'Ni hao Alexander !',
});
});

test('[Handyman] Calculated fields (extended)', () => {
const arr = new Schema({
type: 'array',
$items: 'string',
$calc: schema => (schema.unshift('Hello'), schema),
});

assert.strictEqual(arr.warnings.length, 0);
assert.deepStrictEqual(arr.calculate(['Alexander']), ['Hello', 'Alexander']);

const arr2 = new Schema({
$type: 'array',
items: { $type: 'number', $calc: schema => schema + 2 },
$calc: schema => (schema.unshift(2), schema),
});
assert.strictEqual(arr2.warnings.length, 0);
assert.deepStrictEqual(arr2.calculate([2]), [4, 4]);

const arr3 = new Schema({
$type: 'array',
items: {
name: 'string',
$calc: schema => {
schema.phrase = 'Hello ' + schema.name;
return schema;
},
},
});
assert.strictEqual(arr3.warnings.length, 0);
assert.deepStrictEqual(arr3.calculate([{ name: 'Alexander' }, { name: 'John' }]), [
{ name: 'Alexander', phrase: 'Hello Alexander' },
{ name: 'John', phrase: 'Hello John' },
]);
});
15 changes: 12 additions & 3 deletions modules/handyman/repairkit.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const SCHEMA_NOT_FOUND = 'Schema not found: ';
const TYPE_NOT_FOUND = 'Received unknown type: ';
const ARRAY_TYPE_NOT_FOUND = 'Cant parse type of received array: ';
module.exports = function RepairKit(schema, namespace) {
const shorthands = { string, object, array };
const shorthands = { string, object, array, function: func };
const { tools, forge, child } = schema;
return repair;

Expand Down Expand Up @@ -45,7 +45,8 @@ module.exports = function RepairKit(schema, namespace) {
}

function object(plan, warn) {
const { $required = true, $id, ...fields } = plan; //? Schema wrapper #2
if (typeof plan.$calc === 'function') return func(plan, warn);
const { $required = true, $meta, $id, ...fields } = plan; //? Schema wrapper #2
if (plan.constructor.name === 'Schema') return { $type: 'schema', schema: plan, $required };
if (!plan || plan.constructor.name !== 'Object') return unknown;
if ($id) return { $type: 'schema', $id, schema: child(fields), $required };
Expand All @@ -55,7 +56,15 @@ module.exports = function RepairKit(schema, namespace) {
return unknown;
}
const result = { $type: 'object', properties: { ...fields }, $required };
if (plan.$meta) result.$meta = plan.$meta;
if ($meta) result.$meta = $meta;
return result;
}

function func(plan, warn) {
if (typeof plan === 'function') return { $type: 'unknown', $calc: plan };
const { $calc, ...fields } = plan;
const type = repair(fields, warn);
if (type.$type === 'set' || type.$type === 'map') return type;
return { ...repair(fields, warn), $calc };
}
};
1 change: 0 additions & 1 deletion modules/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,5 @@ module.exports = new Map(
handyman: require('./handyman'),
metatype: require('./types'),
metatest: require('./test'),
metacalc: require('./calc'),
}),
);
2 changes: 1 addition & 1 deletion modules/types/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ module.exports = schema => {
if (namespace.exports.size === 1) {
const definitions = Array.from(namespace.definitions).join('');
if (mode === 'cjs') return definitions + `export = ${type}`;
return definitions + `export type ${name}=${type};export default ${type};`;
return definitions + `export type ${name}=${type};export default ${name};`;
}
namespace.definitions.add(`type ${name}=${type};`);
}
Expand Down
Loading

0 comments on commit ac4779f

Please sign in to comment.