Skip to content

Commit

Permalink
v0.6.0
Browse files Browse the repository at this point in the history
  • Loading branch information
shuritch committed Nov 6, 2023
1 parent 13ce439 commit 201d85a
Show file tree
Hide file tree
Showing 14 changed files with 291 additions and 60 deletions.
12 changes: 8 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@

## [Unreleased][unreleased]

<!--
- Calculated fields module
- Typescript + JSDOC generation module -->
<!-- - Calculated fields module -->

## [0.6.0][] - 2023-11-00

- Typescript generation module
- Documentation fixes

## [0.5.0][] - 2023-11-05

Expand Down Expand Up @@ -86,7 +89,8 @@
- Default exotic types: Any, Undefined, JSON
- Custom Errors
[unreleased]: https://github.com/astrohelm/metaforge/compare/v0.5.0...HEAD
[unreleased]: https://github.com/astrohelm/metaforge/compare/v0.6.0...HEAD
[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
[0.3.0]: https://github.com/astrohelm/metaforge/compare/v0.2.0...v0.3.0
Expand Down
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<h1 align="center">MetaForge v0.5.0 🕵️</h1>
<h1 align="center">MetaForge v0.6.0 🕵️</h1>

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

Expand All @@ -20,7 +20,7 @@ npm i metaforge --save
const userSchema = new Schema({
$id: 'userSchema',
$meta: { name: 'user', description: 'schema for users testing' },
phone: { $type: 'union', types: ['number', 'string'] }, //? anyof tyupe
phone: { $type: 'union', types: ['number', 'string'] }, //? number or string
name: { $type: 'set', items: ['string', '?string'] }, //? set tuple
mask: { $type: 'array', items: 'string' }, //? array
ip: {
Expand Down Expand Up @@ -50,7 +50,7 @@ const sample = [
//...
];

systemSchema.warnings; // inspect after build warnings
systemSchema.warnings; // Inspect warnings after build
systemSchema.test(sample); // Shema validation
systemSchema.toTypescript('system'); // Typescript generation
systemSchema.pull('userSchema').test(sample[0]); // Subschema validation
Expand All @@ -60,14 +60,14 @@ systemSchema.pull('userSchema'); // Metadata: {..., name: 'user', description: '

## Docs

- [About modules](./docs/modules.md#modules-or-another-words-plugins)
- ### [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
- [Handyman](./modules/handyman/README.md) | quality of life module
- [Metatest](./modules/test/README.md) | adds prototype testing
- [Metatype](./modules/types/README.md) | generate typescript:JSDOC from schema
- [Writing custom modules](./docs/prototypes.md#writing-custom-modules)
- [About prototypes](./docs/prototypes.md#readme-map)
- [Schemas contracts](./docs/prototypes.md#schemas-contracts)
- ### [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

Expand Down
22 changes: 21 additions & 1 deletion modules/handyman/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Handyman module allow you to:

- pass schema shorthands
- pass shorthands
- validate & secure plans
- pass schemas as namespace parameter
- pass schemas as plans
Expand Down Expand Up @@ -49,3 +49,23 @@ const schema = new Schema({
- tuple shorthand: <code>['string', 'number']</code>
- enum shorthand: <code>['winter', 'spring']</code>
- schema shorthand: <code>new Schema('?string')</code>

### Example:

```js
const schema = new Schema(
{
a: 'string', //? scalar shorthand
b: '?string', //? optional shorthand
c: ['string', 'string'], //? tuple
d: new Schema('?string'), //? Schema shorthand
e: ['winter', 'spring'], //? Enum shorthand
f: { a: 'number', b: 'string' }, //? Object shorthand
g: { $type: 'array', items: 'string' }, //? Array items shorthand
h: 'MyExternalSchema',
},
{ namespace: { MyExternalSchema: new Schema('string') } },
);
```

> String shorthand is analog to <code>{ $type: type, required: type.includes('?') } </code>
48 changes: 48 additions & 0 deletions modules/handyman/index.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
'use strict';

const [test, assert] = [require('node:test'), require('node:assert')];
const Schema = require('../..');

test('[Handyman] Schema with namespace', () => {
const namespace = { User: new Schema('string') };
const schema = new Schema(['User', 'User'], { namespace });
const sample = ['Alexander', 'Ivanov'];

assert.strictEqual(namespace.User.warnings.length + schema.warnings.length, 0);
assert.strictEqual(schema.test(sample).length, 0);
});

test('[Handyman] Pull schemas', () => {
const schema = new Schema({
$id: 'MySchema',
a: 'string',
b: { $id: 'MySubSchema', c: 'number' },
c: new Schema('?string'),
d: { $type: 'schema', schema: new Schema('number'), $id: 'MySubSchema2' },
e: { $type: 'schema', schema: new Schema({ $type: 'number', $id: 'MySubSchema3' }) },
});

assert.strictEqual(schema.warnings.length, 0);
assert.strictEqual(!!schema.pull('MySchema'), false);
assert.strictEqual(!!schema.pull('MySubSchema'), true);
assert.strictEqual(!!schema.pull('MySubSchema2'), true);
assert.strictEqual(!!schema.pull('MySubSchema3'), true);
});

test('[Handyman] Shorthands', () => {
const schema = new Schema(
{
a: 'string', //? scalar shorthand
b: '?string', //? optional shorthand
c: ['string', 'string'], //? tuple
d: new Schema('?string'), //? Schema shorthand
e: ['winter', 'spring'], //? Enum shorthand
f: { a: 'number', b: 'string' }, //? Object shorthand
g: { $type: 'array', items: 'string' }, //? Array items shorthand
h: 'MyExternalSchema',
},
{ namespace: { MyExternalSchema: new Schema('string') } },
);

assert.strictEqual(schema.warnings.length, 0);
});
2 changes: 1 addition & 1 deletion tests/basic.test.js → modules/test/basic.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use strict';

const [test, assert] = [require('node:test'), require('node:assert')];
const Schema = require('..');
const Schema = require('../..');

test('Schema without errors & warnings', () => {
const userSchema = new Schema({
Expand Down
4 changes: 2 additions & 2 deletions modules/test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ module.exports = (schema, options) => {
const Error = schema.tools.Error;

function TestWrapper(plan) {
if (plan.$type === 'schema') return this.test.bind(this);
if (plan.$type === 'schema') return this.test;
const planRules = plan?.$rules;
const rules = Array.isArray(planRules) ? planRules : [planRules];
const tests = rules.filter(test => typeof test === 'string' || typeof test === 'function');
typeof this.test === 'function' && tests.unshift(this.test.bind(this));
typeof this.test === 'function' && tests.unshift(this.test);
this.test = (sample, path = 'root', isPartial = false) => {
if (sample === undefined || sample === null) {
if (!this.$required) return [];
Expand Down
2 changes: 1 addition & 1 deletion tests/rules.test.js → modules/test/rules.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use strict';

const [test, assert] = [require('node:test'), require('node:assert')];
const Schema = require('..');
const Schema = require('../..');

test('Rules', () => {
const rule1 = sample => sample?.length > 5;
Expand Down
30 changes: 26 additions & 4 deletions modules/types/index.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,33 @@
'use strict';

const { nameFix } = require('./utils');
const types = require('./types');

module.exports = schema => {
function TypescriptWrapper() {
this.toTypescript = () => 'unknown';
}
for (const [name, proto] of types.entries()) schema.forge.attach(name, proto);
schema.forge.attach('before', TypescriptWrapper);
schema.forge.attach('before', { toTypescript: () => 'unknown' });
schema.forge.attach('after', function TypescriptWrapper() {
const compile = this.toTypescript;
this.toTypescript = (name, namespace) => compile(nameFix(name), namespace);
});

schema.dts = (name = 'MetaForge', options = {}) => {
const mode = options.mode ?? 'mjs';
if (name !== nameFix(name)) throw new Error('Invalid name format');
const namespace = { definitions: new Set(), exports: new Set() };
const type = schema.toTypescript(name, namespace);
if (type !== name) {
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};`;
}
namespace.definitions.add(`type ${name}=${type};`);
}
namespace.exports.add(name);
const definitions = Array.from(namespace.definitions).join('');
if (mode === 'cjs') return definitions + `export = ${name};`;
const exports = `export type{${Array.from(namespace.exports).join(',')}};`;
return definitions + exports + `export default ${name};`;
};
};
95 changes: 95 additions & 0 deletions modules/types/index.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/* eslint-disable quotes */
'use strict';

const [test, assert] = [require('node:test'), require('node:assert')];
const Schema = require('../../');

const generate = (type, name) => new Schema(type).dts(name);
const base = 'type MetaForge=';
const exp = 'export type{MetaForge};export default MetaForge;';
test('[DTS] Basic', () => {
assert.strictEqual(generate({ $type: 'string' }), base + 'string;' + exp);
assert.strictEqual(generate('number'), base + 'number;' + exp);
assert.strictEqual(generate('bigint'), base + 'bigint;' + exp);
assert.strictEqual(generate('boolean'), base + 'boolean;' + exp);
assert.strictEqual(generate('unknown'), base + 'unknown;' + exp);
assert.strictEqual(generate('?any'), base + '(any|null|undefined);' + exp);
});

test('[DTS] Enumerable', () => {
assert.strictEqual(generate(['hello', 'world']), base + "('hello'|'world');" + exp);
const data = ['hello', 'there', 'my', 'dear', 'world'];
const result = `type MetaForge='hello'|'there'|'my'|'dear'|'world';`;
assert.strictEqual(generate(data), result + exp);
});

test('[DTS] Union', () => {
assert.strictEqual(
generate({ $type: 'union', types: ['string', '?number'] }),
'type MetaForge=(string|(number|null|undefined));' + exp,
);
assert.strictEqual(
generate({ $type: 'union', types: [{ $type: 'union', types: ['string', '?number'] }] }),
'type MetaForge=((string|(number|null|undefined)));' + exp,
);
});

test('[DTS] Array', () => {
assert.strictEqual(
generate(['string', '?number']),
'type MetaForge=[string,(number|null|undefined)];' + exp,
);
assert.strictEqual(
generate({ $type: 'set', items: ['string', '?number'] }),
'type MetaForge=Set<string|(number|null|undefined)>;' + exp,
);
assert.strictEqual(
generate({ $type: 'array', items: { $type: 'union', types: ['string', '?number'] } }),
'type MetaForge=((string|(number|null|undefined)))[];' + exp,
);
assert.strictEqual(
generate({ $type: 'tuple', items: { $type: 'union', types: ['string', '?number'] } }),
'type MetaForge=[(string|(number|null|undefined))];' + exp,
);
const enumerable = ['hello', 'there', 'my', 'dear', 'world'];
const complex = ['?number', enumerable, { a: 'string', b: enumerable }];
let result = "type MetaForge_1='hello'|'there'|'my'|'dear'|'world';";
result += "type MetaForge_2_b='hello'|'there'|'my'|'dear'|'world';";
result += 'interface MetaForge_2{a:string;b:MetaForge_2_b;};';
result += 'type MetaForge=[(number|null|undefined),MetaForge_1,MetaForge_2];' + exp;
assert.strictEqual(generate(complex), result);
});

test('[DTS] Struct', () => {
const schema = { "'": 'string', '"': 'string', b: '?number', 'c+': { d: ['hello', 'world'] } };
let result = "interface MetaForge_c{d:('hello'|'world');};";
result += 'interface MetaForge{"\'":string;\'"\':string;';
result += "b?:(number|null|undefined);'c+':MetaForge_c;};";
result += exp;
assert.strictEqual(generate(schema), result);
});

test('[DTS] Schema', () => {
const schema = {
$id: 'MySchema',
a: 'string',
b: { $id: 'MySubSchema', c: 'number' },
c: new Schema('?string'),
d: { $type: 'schema', schema: new Schema('number'), $id: 'MySubSchema2' },
e: { $type: 'schema', schema: new Schema({ $type: 'number', $id: 'MySubSchema3' }) },
};
let r = 'interface MySubSchema{c:number;};type MySubSchema2=number;type MySubSchema3=number;';
r += `interface MetaForge{a:string;b:MySubSchema;c?:(string|null|undefined);`;
r += 'd:MySubSchema2;e:MySubSchema3;};';
r += 'export type{MySubSchema,MySubSchema2,MySubSchema3,MetaForge};export default MetaForge;';
assert.strictEqual(generate(schema), r);
});

test('[DTS] Modes', () => {
const schema = new Schema({ a: { $id: 'MySubSchema', c: 'number' } });
const result = 'interface MySubSchema{c:number;};interface MetaForge{a:MySubSchema;};';
const mjs = result + 'export type{MySubSchema,MetaForge};export default MetaForge;';
const cjs = result + 'export = MetaForge;';
assert.strictEqual(schema.dts('MetaForge'), mjs);
assert.strictEqual(schema.dts('MetaForge', { mode: 'cjs' }), cjs);
});
Loading

0 comments on commit 201d85a

Please sign in to comment.