Skip to content

Commit

Permalink
v0.8.0
Browse files Browse the repository at this point in the history
  • Loading branch information
shuritch committed Nov 8, 2023
1 parent 06459c1 commit 9fb6da1
Show file tree
Hide file tree
Showing 13 changed files with 161 additions and 103 deletions.
8 changes: 4 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@
## [1.0.0][] - 2023-11-00
- Release version
-->

## [0.9.0][] - 2023-11-00
<!-- ## [0.9.0][] - 2023-11-10
- Pre-release fixes
- Documentation enhancements
- Code quality improvements
-->
- Code quality improvements -->

## [0.8.0][] - 2023-11-09
## [0.8.0][] - 2023-11-08

- JSDOC generation for metatype module, [issue](https://github.com/astrohelm/metaforge/issues/11)
- Metatest optional default export
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<h1 align="center">MetaForge v0.7.0 🕵️</h1>
<h1 align="center">MetaForge v0.8.0 🕵️</h1>

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

Expand Down
5 changes: 4 additions & 1 deletion lib/forge.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ module.exports = function Forge(schema, custom = {}) {
const meta = plan.$meta;
if (plan.$id) this.$id = plan.$id;
[this.$required, this.$type] = [plan.$required ?? true, name];
if (meta && typeof meta === 'object' && !Array.isArray(meta)) Object.assign(this, meta);
if (meta && typeof meta === 'object' && !Array.isArray(meta)) {
this.$meta = meta;
Object.assign(this, meta);
}
[...before, ...chain, ...after].forEach(proto => proto.call(this, plan, schema.tools));
if (!this.$kind) this.$kind = 'unknown';
};
Expand Down
5 changes: 3 additions & 2 deletions modules/handyman/repairkit.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ module.exports = function RepairKit(schema, namespace) {

function object(plan, warn) {
if (typeof plan.$calc === 'function') return func(plan, warn);
const { $required = true, $meta, $id, ...fields } = plan; //? Schema wrapper #2
const { $required = true, $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 +55,8 @@ module.exports = function RepairKit(schema, namespace) {
warn({ cause: TYPE_NOT_FOUND + plan.$type, sample: plan.$type, plan });
return unknown;
}
const result = { $type: 'object', properties: { ...fields }, $required };
const { $meta, ...other } = fields;
const result = { $type: 'object', properties: { ...other }, $required };
if ($meta) result.$meta = $meta;
return result;
}
Expand Down
89 changes: 56 additions & 33 deletions modules/types/README.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,28 @@
# Metatype module

Generate type annotation from schema;

> Warning: You will receive compressed version;
Generate type annotation & jsdoc from schema;

## 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' });
schema.dts('Example'); // Equal to:
schema.dts('Example', { export: { type: 'mjs', mode: 'all' } });
// type Example = string;
// export type = { Example };
// export default Example;
schema.dts('Example', { export: { type: 'mjs', mode: 'no' } });
// type Example = string;
schema.dts('Example', { export: { type: 'mjs', mode: 'exports-only' } });
// type Example = string;
// export type = { Example };
schema.dts('Example', { export: { type: 'mjs', mode: 'default-only' } });
// type Example = string;
// export default Example;
schema.dts('Example', { export: { type: 'cjs' } });
// type Example = string;
// export = Example;
```

## Example
Expand All @@ -28,24 +31,39 @@ schema.dts('Example', { mode: 'mjs' });

```js
{
$meta: { '@name': 'User', '@description': 'User data' }
"firstName": 'string',
"lastName": 'string',
"lastName": { $type: '?string', $meta: { '@description': 'optional' } },
"label": ["member", "guest", "vip"]
"age": '?number',
settings: { alertLevel: 'string', $id: 'Setting' }
settings: {
$id: 'Setting',
alertLevel: 'string',
$meta: { '@description': 'User settings' }
}
}
```

### Output (mjs mode):
### Output:

```ts
/**
* @description User settings
*/
interface Settings {
alertLevel: string;
}

/**
* @name User
* @description User data
*/
interface Example {
firstName: string;
lastName: string;
/**
* @description optional
*/
lastName?: string;
label: 'member' | 'guest' | 'vip';
age?: number;
settings: Settings;
Expand All @@ -55,29 +73,13 @@ 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;

> If your prototype has children prototypes, it not be handled with jsdoc comments;
```js
function Date(plan, tools) {
this.toTypescript = (name, namespace) => {
Expand All @@ -89,6 +91,27 @@ function Date(plan, tools) {
//? You can return only name or value that can be assigned to type
return name; // Equal to:
return 'Date';
//? Returned value will be assigned to other type or will be exported if it was on top
};
}
```

## JSDOC

To have JSDOC comments in your type annotations you need to add <code>\$meta</code> field to your
schema; Also, your <code>\$meta</code> properties should start with <code>@</code>;

### Example

```js
({
reciever: 'number',
money: 'number',
$meta: {
'@version': 'v1',
'@name': 'Pay check',
'@description': 'Cash settlement',
'@example': '<caption>Check example</caption>\n{ money: 100, reciever: 2 }',
},
});
```
31 changes: 15 additions & 16 deletions modules/types/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict';

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

module.exports = schema => {
Expand All @@ -11,26 +11,25 @@ module.exports = schema => {
this.toTypescript = (name, namespace) => compile(nameFix(name), namespace);
});

// TODO: Default export removable
// TODO: JSDOC documentation
schema.dts = (name = 'MetaForge', options = {}) => {
const mode = options.mode ?? 'mjs';
// const defaultExport = options.defaultExport ?? true;
const [exportMode, exportType] = [options.export?.mode ?? 'all', options.export?.type ?? '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);
namespace.exports.add(name);
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 ${name};`;
}
namespace.definitions.add(`type ${name}=${type};`);
const meta = schema.$meta;
namespace.definitions.add(`${meta ? jsdoc(meta) : ''}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};`;
let result = Array.from(namespace.definitions).join('\n\n');
if (exportMode === 'no') return result;
if (exportMode !== 'default-only' && exportType === 'mjs') {
result += `\nexport type { ${Array.from(namespace.exports).join(', ')} };`;
}
if (exportMode !== 'exports-only') {
if (exportType === 'mjs') return result + `\nexport default ${name};`;
return result + `\nexport = ${name};`;
}
return result;
};
};
64 changes: 35 additions & 29 deletions modules/types/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,67 +5,66 @@ 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;';
const base = 'type MetaForge = ';
const exp = '\nexport type { MetaForge };\nexport 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);
assert.strictEqual(generate('?any'), base + '(any|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';`;
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,
'type MetaForge = (string|(number|undefined));' + exp,
);
assert.strictEqual(
generate({ $type: 'union', types: [{ $type: 'union', types: ['string', '?number'] }] }),
'type MetaForge=((string|(number|null|undefined)));' + exp,
'type MetaForge = ((string|(number|undefined)));' + exp,
);
});

test('[DTS] Array', () => {
assert.strictEqual(
generate(['string', '?number']),
'type MetaForge=[string,(number|null|undefined)];' + exp,
'type MetaForge = [string,(number|undefined)];' + exp,
);
assert.strictEqual(
generate({ $type: 'set', items: ['string', '?number'] }),
'type MetaForge=Set<string|(number|null|undefined)>;' + exp,
'type MetaForge = Set<string|(number|undefined)>;' + exp,
);
assert.strictEqual(
generate({ $type: 'array', items: { $type: 'union', types: ['string', '?number'] } }),
'type MetaForge=((string|(number|null|undefined)))[];' + exp,
'type MetaForge = ((string|(number|undefined)))[];' + exp,
);
assert.strictEqual(
generate({ $type: 'tuple', items: { $type: 'union', types: ['string', '?number'] } }),
'type MetaForge=[(string|(number|null|undefined))];' + exp,
'type MetaForge = [(string|(number|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;
let result = "type MetaForge_1 = 'hello'|'there'|'my'|'dear'|'world';\n\n";
result += "type MetaForge_2_b = 'hello'|'there'|'my'|'dear'|'world';\n\n";
result += 'interface MetaForge_2 {\n a: string;\n b: MetaForge_2_b;\n};\n\n';
result += 'type MetaForge = [(number|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;
let result = "interface MetaForge_c {\n d: ('hello'|'world');\n};\n\n";
result += 'interface MetaForge {\n "\'": string;\n \'"\': string;';
result += "\n b?: (number|undefined);\n 'c+': MetaForge_c;\n};" + exp;
assert.strictEqual(generate(schema), result);
});

Expand All @@ -78,18 +77,25 @@ test('[DTS] Schema', () => {
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;';
let r = 'interface MySubSchema {\n c: number;\n};\n\ntype MySubSchema2 = number;\n\n';
r += `type MySubSchema3 = number;\n\n`;
r += 'interface MetaForge {\n a: string;\n b: MySubSchema;';
r += `\n c?: (string|undefined);\n d: MySubSchema2;\n e: MySubSchema3;\n};\n`;
r += 'export type { MySubSchema, MySubSchema2, MySubSchema3, MetaForge };';
r += '\nexport 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);
test('[DTS] JSDoc', () => {
const schema = new Schema({
$id: 'User',
$meta: { '@name': 'user', '@description': 'About user' },
name: { $type: 'string', $meta: { '@description': 'User name' } },
age: '?number',
});
let result = '/**\n * @name user\n * @description About user\n */\n';
result += 'interface MetaForge {\n /**\n * @description User name\n */\n';
result += ' name: string;\n age?: (number|undefined);\n};\n\n';
result += 'export type { MetaForge };\nexport default MetaForge;';
assert.strictEqual(schema.dts(), result);
});
Loading

0 comments on commit 9fb6da1

Please sign in to comment.