Skip to content

Commit

Permalink
Merge pull request #19 from astrohelm/dev
Browse files Browse the repository at this point in the history
Version 0.6.0
  • Loading branch information
shuritch authored Nov 6, 2023
2 parents 67a7f0b + b0f3f19 commit f15ff4d
Show file tree
Hide file tree
Showing 16 changed files with 389 additions and 63 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
95 changes: 93 additions & 2 deletions modules/types/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,94 @@
# Metatype module / generate typescript:JSDOC from schema
# Metatype module

> WORK IN PROGRESS
Generate type annotation from schema;

> Warning: You will receive compressed version;
## Usage

By default module runs in mjs mode, that means that:

- It will export all schemas with $id field & root schema
- it will export as default root schema

> In cjs mode, it will export only root schema
```js
const plan = 'string';
const schema = new Schema(plan);
schema.dts('Example', { mode: 'mjs' });
// type Example = string;
// export type = { Example };
// export default Example;
```

## Example

### Input:

```js
{
"firstName": 'string',
"lastName": 'string',
"label": ["member", "guest", "vip"]
"age": '?number',
settings: { alertLevel: 'string', $id: 'Setting' }
}
```

### Output (mjs mode):

```ts
interface Settings {
alertLevel: string;
}

interface Example {
firstName: string;
lastName: string;
label: 'member' | 'guest' | 'vip';
age?: number;
settings: Settings;
}

export type { Example };
export default Example;
```

### Output (cjs mode):

```ts
interface Settings {
alertLevel: string;
}

interface Example {
firstName: string;
lastName: string;
label: 'member' | 'guest' | 'vip';
settings: Settings;
age?: number;
}

export = Example;
```

## Writing custom prototypes with Metatype

By default all custom types will recieve unknown type; If you want to have custom type, you may
create custom prototype with toTypescript field;

```js
function Date(plan, tools) {
this.toTypescript = (name, namespace) => {
// If you want to have your type in exports, you can add it name to exports;
const { definitions, exports } = namespace;
// If your type is complex, you can push your builded type / interface to definitions and return it name
const type = `type ${name} = Date`;
definitions.add(type);
//? You can return only name or value that can be assigned to type
return name; // Equal to:
return 'Date';
};
}
```
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};`;
};
};
Loading

0 comments on commit f15ff4d

Please sign in to comment.