firebase-contract 0.1.0-rc.9 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +140 -63
- package/dist/lib/_internal/compiler/compiler.d.ts.map +1 -1
- package/dist/lib/_internal/compiler/compiler.js +50 -21
- package/dist/lib/_internal/generators/api/api-dto-generator.d.ts.map +1 -1
- package/dist/lib/_internal/generators/api/api-dto-generator.js +1 -8
- package/dist/lib/_internal/generators/api/api-types-generator.d.ts.map +1 -1
- package/dist/lib/_internal/generators/api/api-types-generator.js +11 -5
- package/dist/lib/_internal/generators/api/task-payload-generator.js +3 -3
- package/dist/lib/_internal/generators/data-connect/graphql-generator.d.ts +0 -7
- package/dist/lib/_internal/generators/data-connect/graphql-generator.d.ts.map +1 -1
- package/dist/lib/_internal/generators/data-connect/graphql-generator.js +5 -4
- package/dist/lib/_internal/generators/data-connect/operations-generator.d.ts +0 -7
- package/dist/lib/_internal/generators/data-connect/operations-generator.d.ts.map +1 -1
- package/dist/lib/_internal/generators/data-connect/operations-generator.js +8 -7
- package/dist/lib/_internal/generators/firestore/firestore-projection-generator.d.ts.map +1 -1
- package/dist/lib/_internal/generators/firestore/firestore-projection-generator.js +7 -22
- package/dist/lib/_internal/generators/firestore/firestore-split-generator.d.ts +1 -1
- package/dist/lib/_internal/generators/firestore/firestore-split-generator.d.ts.map +1 -1
- package/dist/lib/_internal/generators/firestore/firestore-split-generator.js +69 -47
- package/dist/lib/_internal/generators/generator.d.ts +2 -0
- package/dist/lib/_internal/generators/generator.d.ts.map +1 -1
- package/dist/lib/_internal/generators/index.d.ts +2 -5
- package/dist/lib/_internal/generators/index.d.ts.map +1 -1
- package/dist/lib/_internal/generators/index.js +4 -15
- package/dist/lib/_internal/generators/support/jsdoc.d.ts +3 -0
- package/dist/lib/_internal/generators/support/jsdoc.d.ts.map +1 -0
- package/dist/lib/_internal/generators/support/jsdoc.js +9 -0
- package/dist/lib/_internal/generators/support/split-variant.d.ts +9 -0
- package/dist/lib/_internal/generators/support/split-variant.d.ts.map +1 -0
- package/dist/lib/_internal/generators/support/split-variant.js +13 -0
- package/dist/lib/_internal/generators/support/split.d.ts +1 -1
- package/dist/lib/_internal/generators/support/split.d.ts.map +1 -1
- package/dist/lib/_internal/generators/support/split.js +3 -2
- package/dist/lib/_internal/generators/typescript/render.d.ts +15 -0
- package/dist/lib/_internal/generators/typescript/render.d.ts.map +1 -0
- package/dist/lib/_internal/generators/typescript/render.js +92 -0
- package/dist/lib/_internal/generators/typescript/typescript-generator.d.ts +2 -1
- package/dist/lib/_internal/generators/typescript/typescript-generator.d.ts.map +1 -1
- package/dist/lib/_internal/generators/typescript/typescript-generator.js +12 -78
- package/dist/lib/_internal/generators/typescript/typescript-split-generator.d.ts +1 -1
- package/dist/lib/_internal/generators/typescript/typescript-split-generator.d.ts.map +1 -1
- package/dist/lib/_internal/generators/typescript/typescript-split-generator.js +4 -84
- package/dist/lib/_internal/generators/zod/render.d.ts +12 -0
- package/dist/lib/_internal/generators/zod/render.d.ts.map +1 -0
- package/dist/lib/_internal/generators/zod/render.js +59 -0
- package/dist/lib/_internal/generators/zod/zod-generator.d.ts +2 -1
- package/dist/lib/_internal/generators/zod/zod-generator.d.ts.map +1 -1
- package/dist/lib/_internal/generators/zod/zod-generator.js +11 -55
- package/dist/lib/_internal/generators/zod/zod-split-generator.d.ts +1 -1
- package/dist/lib/_internal/generators/zod/zod-split-generator.d.ts.map +1 -1
- package/dist/lib/_internal/generators/zod/zod-split-generator.js +4 -56
- package/dist/lib/_internal/ir/build-ir.d.ts.map +1 -1
- package/dist/lib/_internal/ir/build-ir.js +93 -8
- package/dist/lib/_internal/ir/ir.d.ts +18 -5
- package/dist/lib/_internal/ir/ir.d.ts.map +1 -1
- package/dist/lib/_internal/ir/ir.js +1 -0
- package/dist/lib/_internal/parser/parse.d.ts +5 -3
- package/dist/lib/_internal/parser/parse.d.ts.map +1 -1
- package/dist/lib/_internal/parser/parse.js +103 -14
- package/dist/lib/_internal/parser/raw-document.d.ts +18 -2
- package/dist/lib/_internal/parser/raw-document.d.ts.map +1 -1
- package/dist/lib/_internal/resolver/import-resolver.js +1 -1
- package/dist/lib/_internal/validation/rules.d.ts +39 -0
- package/dist/lib/_internal/validation/rules.d.ts.map +1 -1
- package/dist/lib/_internal/validation/rules.js +133 -1
- package/dist/lib/cli/commands/generate.d.ts.map +1 -1
- package/dist/lib/cli/commands/generate.js +6 -4
- package/dist/lib/cli/commands/index.d.ts.map +1 -1
- package/dist/lib/cli/commands/index.js +2 -0
- package/dist/lib/cli/commands/init.d.ts.map +1 -1
- package/dist/lib/cli/commands/init.js +2 -18
- package/dist/lib/cli/commands/where.d.ts +3 -0
- package/dist/lib/cli/commands/where.d.ts.map +1 -0
- package/dist/lib/cli/commands/where.js +57 -0
- package/dist/lib/cli/config.d.ts +3 -2
- package/dist/lib/cli/config.d.ts.map +1 -1
- package/dist/lib/cli/config.js +3 -0
- package/package.json +1 -1
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { constantCase, singleQuote } from '../support/naming.js';
|
|
2
|
+
import { isRelation, relationFkName, relationFkType } from '../support/relations.js';
|
|
3
|
+
/**
|
|
4
|
+
* Rendering shared by the bundled and split TypeScript layouts. Both layouts
|
|
5
|
+
* MUST go through these helpers so a contract setting (valueKeys, nullable,
|
|
6
|
+
* multi-line descriptions, …) means the same thing regardless of layout.
|
|
7
|
+
*/
|
|
8
|
+
export const SCALAR_TS = {
|
|
9
|
+
string: 'string',
|
|
10
|
+
int: 'number',
|
|
11
|
+
int64: 'number',
|
|
12
|
+
float: 'number',
|
|
13
|
+
boolean: 'boolean',
|
|
14
|
+
timestamp: 'string',
|
|
15
|
+
date: 'string',
|
|
16
|
+
json: 'Json',
|
|
17
|
+
id: 'string',
|
|
18
|
+
};
|
|
19
|
+
export const JSON_TYPE = [
|
|
20
|
+
'export type Json =',
|
|
21
|
+
' | string',
|
|
22
|
+
' | number',
|
|
23
|
+
' | boolean',
|
|
24
|
+
' | null',
|
|
25
|
+
' | Json[]',
|
|
26
|
+
' | { [key: string]: Json }',
|
|
27
|
+
].join('\n');
|
|
28
|
+
const renderRef = (ref) => {
|
|
29
|
+
switch (ref.kind) {
|
|
30
|
+
case 'scalar':
|
|
31
|
+
return SCALAR_TS[ref.name];
|
|
32
|
+
case 'enum':
|
|
33
|
+
case 'model':
|
|
34
|
+
return ref.name;
|
|
35
|
+
default:
|
|
36
|
+
return 'unknown';
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
export const docLines = (description, indent) => {
|
|
40
|
+
if (!description)
|
|
41
|
+
return [];
|
|
42
|
+
const lines = description.split('\n');
|
|
43
|
+
if (lines.length === 1)
|
|
44
|
+
return [`${indent}/** ${lines[0]} */`];
|
|
45
|
+
return [`${indent}/**`, ...lines.map(line => `${indent} * ${line}`.trimEnd()), `${indent} */`];
|
|
46
|
+
};
|
|
47
|
+
export const renderField = (field, ir) => {
|
|
48
|
+
const optional = field.optional ? '?' : '';
|
|
49
|
+
const doc = docLines(field.description, ' ');
|
|
50
|
+
let entry;
|
|
51
|
+
if (field.literal !== undefined) {
|
|
52
|
+
const type = field.list ? `${singleQuote(field.literal)}[]` : singleQuote(field.literal);
|
|
53
|
+
entry = ` ${field.name}${optional}: ${type}`;
|
|
54
|
+
}
|
|
55
|
+
else if (isRelation(field)) {
|
|
56
|
+
const base = SCALAR_TS[relationFkType(ir, field)];
|
|
57
|
+
entry = ` ${relationFkName(field)}${optional}: ${field.list ? `${base}[]` : base}`;
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
const base = renderRef(field.type);
|
|
61
|
+
const type = field.list ? `${base}[]` : base;
|
|
62
|
+
const nullable = field.nullable ? ' | null' : '';
|
|
63
|
+
entry = ` ${field.name}${optional}: ${type}${nullable}`;
|
|
64
|
+
}
|
|
65
|
+
return [...doc, entry].join('\n');
|
|
66
|
+
};
|
|
67
|
+
export const renderEnumConst = (irEnum) => {
|
|
68
|
+
const doc = docLines(irEnum.description, '');
|
|
69
|
+
const constName = constantCase(irEnum.name);
|
|
70
|
+
const entries = irEnum.values
|
|
71
|
+
.map(value => ` ${irEnum.valueKeys?.[value] ?? constantCase(value)}: ${singleQuote(value)},`)
|
|
72
|
+
.join('\n');
|
|
73
|
+
return [
|
|
74
|
+
...doc,
|
|
75
|
+
`export const ${constName} = Object.freeze({`,
|
|
76
|
+
entries,
|
|
77
|
+
`} as const)`,
|
|
78
|
+
`export type ${irEnum.name}Key = keyof typeof ${constName}`,
|
|
79
|
+
`export type ${irEnum.name} = (typeof ${constName})[${irEnum.name}Key]`,
|
|
80
|
+
].join('\n');
|
|
81
|
+
};
|
|
82
|
+
export const renderEnumUnion = (irEnum) => {
|
|
83
|
+
const doc = docLines(irEnum.description, '');
|
|
84
|
+
const union = irEnum.values.map(value => singleQuote(value)).join(' | ') || 'never';
|
|
85
|
+
return [...doc, `export type ${irEnum.name} = ${union}`].join('\n');
|
|
86
|
+
};
|
|
87
|
+
export const renderModel = (model, ir) => {
|
|
88
|
+
const doc = docLines(model.description, '');
|
|
89
|
+
const fields = model.fields.map(field => renderField(field, ir)).join('\n');
|
|
90
|
+
return [...doc, `export interface ${model.name} {`, fields, '}'].join('\n');
|
|
91
|
+
};
|
|
92
|
+
export const usesJson = (models) => models.some(model => model.fields.some(field => field.type.kind === 'scalar' && field.type.name === 'json'));
|
|
@@ -12,7 +12,8 @@ export interface TypeScriptGeneratorOptions {
|
|
|
12
12
|
* Generates TypeScript types: an interface per model and, per enum, either a
|
|
13
13
|
* frozen const object (default — runtime values + `Key`/value types) or a
|
|
14
14
|
* plain string-literal union. This is the highest-priority
|
|
15
|
-
* generator and the shape other TS-facing generators reference.
|
|
15
|
+
* generator and the shape other TS-facing generators reference. Rendering is
|
|
16
|
+
* shared with the split layout (./render.js) so both emit identical members.
|
|
16
17
|
*/
|
|
17
18
|
export declare const createTypeScriptGenerator: (options?: TypeScriptGeneratorOptions) => Generator;
|
|
18
19
|
//# sourceMappingURL=typescript-generator.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"typescript-generator.d.ts","sourceRoot":"","sources":["../../../../../src/lib/_internal/generators/typescript/typescript-generator.ts"],"names":[],"mappings":"AACA,OAAO,EAAiB,SAAS,EAAoB,MAAM,iBAAiB,CAAA;
|
|
1
|
+
{"version":3,"file":"typescript-generator.d.ts","sourceRoot":"","sources":["../../../../../src/lib/_internal/generators/typescript/typescript-generator.ts"],"names":[],"mappings":"AACA,OAAO,EAAiB,SAAS,EAAoB,MAAM,iBAAiB,CAAA;AAO5E,MAAM,WAAW,0BAA0B;IACzC;;;;;OAKG;IACH,SAAS,CAAC,EAAE,OAAO,GAAG,OAAO,CAAA;CAC9B;AAED;;;;;;GAMG;AACH,eAAO,MAAM,yBAAyB,GAAI,UAAS,0BAA+B,KAAG,SA4BpF,CAAA"}
|
|
@@ -1,100 +1,34 @@
|
|
|
1
1
|
import { headerBlocks } from '../support/header.js';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { withSplitVariant } from '../support/split-variant.js';
|
|
3
|
+
import { createTypeScriptSplitLayout } from './typescript-split-generator.js';
|
|
4
4
|
import { outputFile } from '../support/templates.js';
|
|
5
|
-
|
|
6
|
-
string: 'string',
|
|
7
|
-
int: 'number',
|
|
8
|
-
int64: 'number',
|
|
9
|
-
float: 'number',
|
|
10
|
-
boolean: 'boolean',
|
|
11
|
-
timestamp: 'string',
|
|
12
|
-
date: 'string',
|
|
13
|
-
json: 'Json',
|
|
14
|
-
id: 'string',
|
|
15
|
-
};
|
|
16
|
-
const JSON_TYPE = [
|
|
17
|
-
'export type Json =',
|
|
18
|
-
' | string',
|
|
19
|
-
' | number',
|
|
20
|
-
' | boolean',
|
|
21
|
-
' | null',
|
|
22
|
-
' | Json[]',
|
|
23
|
-
' | { [key: string]: Json }',
|
|
24
|
-
].join('\n');
|
|
25
|
-
const renderRef = (ref) => {
|
|
26
|
-
switch (ref.kind) {
|
|
27
|
-
case 'scalar':
|
|
28
|
-
return SCALAR_TS[ref.name];
|
|
29
|
-
case 'enum':
|
|
30
|
-
case 'model':
|
|
31
|
-
return ref.name;
|
|
32
|
-
default:
|
|
33
|
-
return 'unknown';
|
|
34
|
-
}
|
|
35
|
-
};
|
|
36
|
-
const renderFieldType = (field) => {
|
|
37
|
-
const base = renderRef(field.type);
|
|
38
|
-
return field.list ? `${base}[]` : base;
|
|
39
|
-
};
|
|
40
|
-
const renderField = (field, ir) => {
|
|
41
|
-
const optional = field.optional ? '?' : '';
|
|
42
|
-
const doc = field.description ? ` /** ${field.description} */\n` : '';
|
|
43
|
-
if (field.literal !== undefined) {
|
|
44
|
-
const type = field.list ? `${singleQuote(field.literal)}[]` : singleQuote(field.literal);
|
|
45
|
-
return `${doc} ${field.name}${optional}: ${type}`;
|
|
46
|
-
}
|
|
47
|
-
// Relation: expose the foreign-key id (matching the persisted/projected shape).
|
|
48
|
-
if (isRelation(field)) {
|
|
49
|
-
const base = SCALAR_TS[relationFkType(ir, field)];
|
|
50
|
-
const type = field.list ? `${base}[]` : base;
|
|
51
|
-
return `${doc} ${relationFkName(field)}${optional}: ${type}`;
|
|
52
|
-
}
|
|
53
|
-
return `${doc} ${field.name}${optional}: ${renderFieldType(field)}`;
|
|
54
|
-
};
|
|
55
|
-
const usesJson = (ir) => ir.models.some(model => model.fields.some(field => field.type.kind === 'scalar' && field.type.name === 'json'));
|
|
56
|
-
const renderEnumUnion = (irEnum) => {
|
|
57
|
-
const doc = irEnum.description ? `/** ${irEnum.description} */\n` : '';
|
|
58
|
-
const union = irEnum.values.map(value => singleQuote(value)).join(' | ') || 'never';
|
|
59
|
-
return `${doc}export type ${irEnum.name} = ${union}`;
|
|
60
|
-
};
|
|
61
|
-
const renderEnumConst = (irEnum) => {
|
|
62
|
-
const doc = irEnum.description ? `/** ${irEnum.description} */\n` : '';
|
|
63
|
-
const constName = constantCase(irEnum.name);
|
|
64
|
-
const entries = irEnum.values.map(value => ` ${constantCase(value)}: ${singleQuote(value)},`).join('\n');
|
|
65
|
-
return [
|
|
66
|
-
`${doc}export const ${constName} = Object.freeze({`,
|
|
67
|
-
entries,
|
|
68
|
-
`} as const)`,
|
|
69
|
-
`export type ${irEnum.name}Key = keyof typeof ${constName}`,
|
|
70
|
-
`export type ${irEnum.name} = (typeof ${constName})[${irEnum.name}Key]`,
|
|
71
|
-
].join('\n');
|
|
72
|
-
};
|
|
5
|
+
import { JSON_TYPE, renderEnumConst, renderEnumUnion, renderModel, usesJson } from './render.js';
|
|
73
6
|
/**
|
|
74
7
|
* Generates TypeScript types: an interface per model and, per enum, either a
|
|
75
8
|
* frozen const object (default — runtime values + `Key`/value types) or a
|
|
76
9
|
* plain string-literal union. This is the highest-priority
|
|
77
|
-
* generator and the shape other TS-facing generators reference.
|
|
10
|
+
* generator and the shape other TS-facing generators reference. Rendering is
|
|
11
|
+
* shared with the split layout (./render.js) so both emit identical members.
|
|
78
12
|
*/
|
|
79
13
|
export const createTypeScriptGenerator = (options = {}) => {
|
|
80
14
|
const enumStyle = options.enumStyle ?? 'const';
|
|
81
|
-
return {
|
|
15
|
+
return withSplitVariant({
|
|
82
16
|
name: 'typescript',
|
|
83
17
|
description: 'TypeScript interfaces and enum const/union types',
|
|
84
18
|
generate(ir, context) {
|
|
85
19
|
const blocks = [...headerBlocks(context)];
|
|
86
|
-
|
|
20
|
+
// Pinned value objects (models with `out`) are firestore-scoped: excluded here.
|
|
21
|
+
const models = ir.models.filter(model => model.out === undefined);
|
|
22
|
+
if (usesJson(models)) {
|
|
87
23
|
blocks.push(JSON_TYPE);
|
|
88
24
|
}
|
|
89
25
|
for (const irEnum of ir.enums) {
|
|
90
26
|
blocks.push(enumStyle === 'const' ? renderEnumConst(irEnum) : renderEnumUnion(irEnum));
|
|
91
27
|
}
|
|
92
|
-
for (const model of
|
|
93
|
-
|
|
94
|
-
const fields = model.fields.map(field => renderField(field, ir)).join('\n');
|
|
95
|
-
blocks.push(`${doc}export interface ${model.name} {\n${fields}\n}`);
|
|
28
|
+
for (const model of models) {
|
|
29
|
+
blocks.push(renderModel(model, ir));
|
|
96
30
|
}
|
|
97
31
|
return [{ path: outputFile(context, 'types.ts'), content: `${blocks.join('\n\n')}\n` }];
|
|
98
32
|
},
|
|
99
|
-
};
|
|
33
|
+
}, createTypeScriptSplitLayout());
|
|
100
34
|
};
|
|
@@ -6,5 +6,5 @@ import { Generator } from '../generator.js';
|
|
|
6
6
|
* leftovers (and the `Json` type) live in `types/_shared.ts`, and `types.ts`
|
|
7
7
|
* is a barrel re-exporting every file.
|
|
8
8
|
*/
|
|
9
|
-
export declare const
|
|
9
|
+
export declare const createTypeScriptSplitLayout: () => Generator;
|
|
10
10
|
//# sourceMappingURL=typescript-split-generator.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"typescript-split-generator.d.ts","sourceRoot":"","sources":["../../../../../src/lib/_internal/generators/typescript/typescript-split-generator.ts"],"names":[],"mappings":"AACA,OAAO,EAAiB,SAAS,EAAoB,MAAM,iBAAiB,CAAA;
|
|
1
|
+
{"version":3,"file":"typescript-split-generator.d.ts","sourceRoot":"","sources":["../../../../../src/lib/_internal/generators/typescript/typescript-split-generator.ts"],"names":[],"mappings":"AACA,OAAO,EAAiB,SAAS,EAAoB,MAAM,iBAAiB,CAAA;AAO5E;;;;;;GAMG;AACH,eAAO,MAAM,2BAA2B,QAAO,SA0E7C,CAAA"}
|
|
@@ -1,89 +1,9 @@
|
|
|
1
1
|
import { findEnum, findModel } from '../../ir/ir.js';
|
|
2
2
|
import { headerBlocks } from '../support/header.js';
|
|
3
|
-
import {
|
|
4
|
-
import { isRelation, relationFkName, relationFkType } from '../support/relations.js';
|
|
3
|
+
import { isRelation } from '../support/relations.js';
|
|
5
4
|
import { assignModuleNames, collectDeps, splitGroups } from '../support/split.js';
|
|
6
5
|
import { outputFile } from '../support/templates.js';
|
|
7
|
-
|
|
8
|
-
string: 'string',
|
|
9
|
-
int: 'number',
|
|
10
|
-
int64: 'number',
|
|
11
|
-
float: 'number',
|
|
12
|
-
boolean: 'boolean',
|
|
13
|
-
timestamp: 'string',
|
|
14
|
-
date: 'string',
|
|
15
|
-
json: 'Json',
|
|
16
|
-
id: 'string',
|
|
17
|
-
};
|
|
18
|
-
const JSON_TYPE = [
|
|
19
|
-
'export type Json =',
|
|
20
|
-
' | string',
|
|
21
|
-
' | number',
|
|
22
|
-
' | boolean',
|
|
23
|
-
' | null',
|
|
24
|
-
' | Json[]',
|
|
25
|
-
' | { [key: string]: Json }',
|
|
26
|
-
].join('\n');
|
|
27
|
-
const renderRef = (ref) => {
|
|
28
|
-
switch (ref.kind) {
|
|
29
|
-
case 'scalar':
|
|
30
|
-
return SCALAR_TS[ref.name];
|
|
31
|
-
case 'enum':
|
|
32
|
-
case 'model':
|
|
33
|
-
return ref.name;
|
|
34
|
-
default:
|
|
35
|
-
return 'unknown';
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
|
-
const docLines = (description, indent) => {
|
|
39
|
-
if (!description)
|
|
40
|
-
return [];
|
|
41
|
-
const lines = description.split('\n');
|
|
42
|
-
if (lines.length === 1)
|
|
43
|
-
return [`${indent}/** ${lines[0]} */`];
|
|
44
|
-
return [`${indent}/**`, ...lines.map(line => `${indent} * ${line}`.trimEnd()), `${indent} */`];
|
|
45
|
-
};
|
|
46
|
-
const renderField = (field, ir) => {
|
|
47
|
-
const optional = field.optional ? '?' : '';
|
|
48
|
-
const doc = docLines(field.description, ' ');
|
|
49
|
-
let entry;
|
|
50
|
-
if (field.literal !== undefined) {
|
|
51
|
-
const type = field.list ? `${singleQuote(field.literal)}[]` : singleQuote(field.literal);
|
|
52
|
-
entry = ` ${field.name}${optional}: ${type}`;
|
|
53
|
-
}
|
|
54
|
-
else if (isRelation(field)) {
|
|
55
|
-
const base = SCALAR_TS[relationFkType(ir, field)];
|
|
56
|
-
entry = ` ${relationFkName(field)}${optional}: ${field.list ? `${base}[]` : base}`;
|
|
57
|
-
}
|
|
58
|
-
else {
|
|
59
|
-
const base = renderRef(field.type);
|
|
60
|
-
const type = field.list ? `${base}[]` : base;
|
|
61
|
-
const nullable = field.nullable ? ' | null' : '';
|
|
62
|
-
entry = ` ${field.name}${optional}: ${type}${nullable}`;
|
|
63
|
-
}
|
|
64
|
-
return [...doc, entry].join('\n');
|
|
65
|
-
};
|
|
66
|
-
const renderEnumConst = (irEnum) => {
|
|
67
|
-
const doc = docLines(irEnum.description, '');
|
|
68
|
-
const constName = constantCase(irEnum.name);
|
|
69
|
-
const entries = irEnum.values
|
|
70
|
-
.map(value => ` ${irEnum.valueKeys?.[value] ?? constantCase(value)}: ${singleQuote(value)},`)
|
|
71
|
-
.join('\n');
|
|
72
|
-
return [
|
|
73
|
-
...doc,
|
|
74
|
-
`export const ${constName} = Object.freeze({`,
|
|
75
|
-
entries,
|
|
76
|
-
`} as const)`,
|
|
77
|
-
`export type ${irEnum.name}Key = keyof typeof ${constName}`,
|
|
78
|
-
`export type ${irEnum.name} = (typeof ${constName})[${irEnum.name}Key]`,
|
|
79
|
-
].join('\n');
|
|
80
|
-
};
|
|
81
|
-
const renderModel = (model, ir) => {
|
|
82
|
-
const doc = docLines(model.description, '');
|
|
83
|
-
const fields = model.fields.map(field => renderField(field, ir)).join('\n');
|
|
84
|
-
return [...doc, `export interface ${model.name} {`, fields, '}'].join('\n');
|
|
85
|
-
};
|
|
86
|
-
const usesJson = (models) => models.some(model => model.fields.some(field => field.type.kind === 'scalar' && field.type.name === 'json'));
|
|
6
|
+
import { JSON_TYPE, renderEnumConst, renderModel, usesJson } from './render.js';
|
|
87
7
|
/**
|
|
88
8
|
* Split variant of the TypeScript generator: one file per table at
|
|
89
9
|
* `types/<table-name>.ts` (kebab-case), with enums and embedded value objects
|
|
@@ -91,8 +11,8 @@ const usesJson = (models) => models.some(model => model.fields.some(field => fie
|
|
|
91
11
|
* leftovers (and the `Json` type) live in `types/_shared.ts`, and `types.ts`
|
|
92
12
|
* is a barrel re-exporting every file.
|
|
93
13
|
*/
|
|
94
|
-
export const
|
|
95
|
-
name: 'typescript
|
|
14
|
+
export const createTypeScriptSplitLayout = () => ({
|
|
15
|
+
name: 'typescript',
|
|
96
16
|
description: 'TypeScript interfaces, one file per table (types/<table>.ts + types.ts barrel)',
|
|
97
17
|
generate(ir, context) {
|
|
98
18
|
const groups = splitGroups(ir);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Ir, IrEnum, IrField, IrModel, ScalarType } from '../../ir/ir.js';
|
|
2
|
+
/**
|
|
3
|
+
* Rendering shared by the bundled and split Zod layouts. Both layouts MUST go
|
|
4
|
+
* through these helpers so a contract setting (nullable, constraints, …) means
|
|
5
|
+
* the same thing regardless of layout.
|
|
6
|
+
*/
|
|
7
|
+
export declare const SCALAR_ZOD: Record<ScalarType, string>;
|
|
8
|
+
export declare const schemaName: (name: string) => string;
|
|
9
|
+
export declare const renderFieldSchema: (field: IrField, ir: Ir) => string;
|
|
10
|
+
export declare const renderEnum: (irEnum: IrEnum) => string;
|
|
11
|
+
export declare const renderModel: (model: IrModel, ir: Ir) => string;
|
|
12
|
+
//# sourceMappingURL=render.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../../../../src/lib/_internal/generators/zod/render.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAa,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAKpF;;;;GAIG;AAEH,eAAO,MAAM,UAAU,EAAE,MAAM,CAAC,UAAU,EAAE,MAAM,CAUjD,CAAA;AAED,eAAO,MAAM,UAAU,GAAI,MAAM,MAAM,KAAG,MAAyB,CAAA;AAgBnE,eAAO,MAAM,iBAAiB,GAAI,OAAO,OAAO,EAAE,IAAI,EAAE,KAAG,MAa1D,CAAA;AAED,eAAO,MAAM,UAAU,GAAI,QAAQ,MAAM,KAAG,MAM3C,CAAA;AAED,eAAO,MAAM,WAAW,GAAI,OAAO,OAAO,EAAE,IAAI,EAAE,KAAG,MAMpD,CAAA"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { zodConstraints } from '../support/constraints.js';
|
|
2
|
+
import { singleQuote } from '../support/naming.js';
|
|
3
|
+
import { isRelation, relationFkName, relationFkType } from '../support/relations.js';
|
|
4
|
+
/**
|
|
5
|
+
* Rendering shared by the bundled and split Zod layouts. Both layouts MUST go
|
|
6
|
+
* through these helpers so a contract setting (nullable, constraints, …) means
|
|
7
|
+
* the same thing regardless of layout.
|
|
8
|
+
*/
|
|
9
|
+
export const SCALAR_ZOD = {
|
|
10
|
+
string: 'z.string()',
|
|
11
|
+
int: 'z.number().int()',
|
|
12
|
+
int64: 'z.number().int()',
|
|
13
|
+
float: 'z.number()',
|
|
14
|
+
boolean: 'z.boolean()',
|
|
15
|
+
timestamp: 'z.string().datetime()',
|
|
16
|
+
date: 'z.string()',
|
|
17
|
+
json: 'z.unknown()',
|
|
18
|
+
id: 'z.string()',
|
|
19
|
+
};
|
|
20
|
+
export const schemaName = (name) => `${name}Schema`;
|
|
21
|
+
const renderRef = (ref) => {
|
|
22
|
+
switch (ref.kind) {
|
|
23
|
+
case 'scalar':
|
|
24
|
+
return SCALAR_ZOD[ref.name];
|
|
25
|
+
case 'enum':
|
|
26
|
+
return schemaName(ref.name);
|
|
27
|
+
case 'model':
|
|
28
|
+
// Wrapped in z.lazy so declaration order and (cross-file) cycles never matter.
|
|
29
|
+
return `z.lazy(() => ${schemaName(ref.name)})`;
|
|
30
|
+
default:
|
|
31
|
+
return 'z.unknown()';
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
export const renderFieldSchema = (field, ir) => {
|
|
35
|
+
const isRel = isRelation(field);
|
|
36
|
+
const name = isRel ? relationFkName(field) : field.name;
|
|
37
|
+
const base = field.literal !== undefined
|
|
38
|
+
? `z.literal(${singleQuote(field.literal)})`
|
|
39
|
+
: isRel
|
|
40
|
+
? SCALAR_ZOD[relationFkType(ir, field)]
|
|
41
|
+
: renderRef(field.type);
|
|
42
|
+
const cons = zodConstraints(field);
|
|
43
|
+
let schema = field.list ? `z.array(${base})${cons}` : `${base}${cons}`;
|
|
44
|
+
if (field.nullable)
|
|
45
|
+
schema = `${schema}.nullable()`;
|
|
46
|
+
if (field.optional)
|
|
47
|
+
schema = `${schema}.optional()`;
|
|
48
|
+
return ` ${name}: ${schema},`;
|
|
49
|
+
};
|
|
50
|
+
export const renderEnum = (irEnum) => {
|
|
51
|
+
const values = irEnum.values.map(singleQuote).join(', ');
|
|
52
|
+
return (`export const ${schemaName(irEnum.name)} = z.enum([${values}])\n` +
|
|
53
|
+
`export type ${irEnum.name} = z.infer<typeof ${schemaName(irEnum.name)}>`);
|
|
54
|
+
};
|
|
55
|
+
export const renderModel = (model, ir) => {
|
|
56
|
+
const fields = model.fields.map(field => renderFieldSchema(field, ir)).join('\n');
|
|
57
|
+
return (`export const ${schemaName(model.name)} = z.object({\n${fields}\n})\n` +
|
|
58
|
+
`export type ${model.name} = z.infer<typeof ${schemaName(model.name)}>`);
|
|
59
|
+
};
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { Generator } from '../generator.js';
|
|
2
2
|
/**
|
|
3
3
|
* Generates Zod schemas and the types inferred from them. Model references use
|
|
4
|
-
* `z.lazy` so the output is order- and cycle-independent.
|
|
4
|
+
* `z.lazy` so the output is order- and cycle-independent. Rendering is shared
|
|
5
|
+
* with the split layout (./render.js) so both emit identical members.
|
|
5
6
|
*/
|
|
6
7
|
export declare const createZodGenerator: () => Generator;
|
|
7
8
|
//# sourceMappingURL=zod-generator.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"zod-generator.d.ts","sourceRoot":"","sources":["../../../../../src/lib/_internal/generators/zod/zod-generator.ts"],"names":[],"mappings":"AACA,OAAO,EAAiB,SAAS,EAAoB,MAAM,iBAAiB,CAAA;
|
|
1
|
+
{"version":3,"file":"zod-generator.d.ts","sourceRoot":"","sources":["../../../../../src/lib/_internal/generators/zod/zod-generator.ts"],"names":[],"mappings":"AACA,OAAO,EAAiB,SAAS,EAAoB,MAAM,iBAAiB,CAAA;AAO5E;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,QAAO,SAqBnC,CAAA"}
|
|
@@ -1,69 +1,25 @@
|
|
|
1
1
|
import { headerBlocks } from '../support/header.js';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { zodConstraints } from '../support/constraints.js';
|
|
2
|
+
import { withSplitVariant } from '../support/split-variant.js';
|
|
3
|
+
import { createZodSplitLayout } from './zod-split-generator.js';
|
|
5
4
|
import { outputFile } from '../support/templates.js';
|
|
6
|
-
|
|
7
|
-
string: 'z.string()',
|
|
8
|
-
int: 'z.number().int()',
|
|
9
|
-
int64: 'z.number().int()',
|
|
10
|
-
float: 'z.number()',
|
|
11
|
-
boolean: 'z.boolean()',
|
|
12
|
-
timestamp: 'z.string().datetime()',
|
|
13
|
-
date: 'z.string()',
|
|
14
|
-
json: 'z.unknown()',
|
|
15
|
-
id: 'z.string()',
|
|
16
|
-
};
|
|
17
|
-
const schemaName = (name) => `${name}Schema`;
|
|
18
|
-
const renderRef = (ref) => {
|
|
19
|
-
switch (ref.kind) {
|
|
20
|
-
case 'scalar':
|
|
21
|
-
return SCALAR_ZOD[ref.name];
|
|
22
|
-
case 'enum':
|
|
23
|
-
// Enums are declared before models, so a direct reference is safe.
|
|
24
|
-
return schemaName(ref.name);
|
|
25
|
-
case 'model':
|
|
26
|
-
// Model references are wrapped in z.lazy so declaration order and cycles
|
|
27
|
-
// never matter.
|
|
28
|
-
return `z.lazy(() => ${schemaName(ref.name)})`;
|
|
29
|
-
default:
|
|
30
|
-
return 'z.unknown()';
|
|
31
|
-
}
|
|
32
|
-
};
|
|
33
|
-
const renderFieldSchema = (field, ir) => {
|
|
34
|
-
const isRel = isRelation(field);
|
|
35
|
-
const name = isRel ? relationFkName(field) : field.name;
|
|
36
|
-
const base = field.literal !== undefined
|
|
37
|
-
? `z.literal(${singleQuote(field.literal)})`
|
|
38
|
-
: isRel
|
|
39
|
-
? SCALAR_ZOD[relationFkType(ir, field)]
|
|
40
|
-
: renderRef(field.type);
|
|
41
|
-
const cons = zodConstraints(field);
|
|
42
|
-
let schema = field.list ? `z.array(${base})${cons}` : `${base}${cons}`;
|
|
43
|
-
if (field.optional) {
|
|
44
|
-
schema = `${schema}.optional()`;
|
|
45
|
-
}
|
|
46
|
-
return ` ${name}: ${schema},`;
|
|
47
|
-
};
|
|
5
|
+
import { renderEnum, renderModel } from './render.js';
|
|
48
6
|
/**
|
|
49
7
|
* Generates Zod schemas and the types inferred from them. Model references use
|
|
50
|
-
* `z.lazy` so the output is order- and cycle-independent.
|
|
8
|
+
* `z.lazy` so the output is order- and cycle-independent. Rendering is shared
|
|
9
|
+
* with the split layout (./render.js) so both emit identical members.
|
|
51
10
|
*/
|
|
52
|
-
export const createZodGenerator = () => ({
|
|
11
|
+
export const createZodGenerator = () => withSplitVariant({
|
|
53
12
|
name: 'zod',
|
|
54
13
|
description: 'Zod validation schemas',
|
|
55
14
|
generate(ir, context) {
|
|
56
15
|
const blocks = [...headerBlocks(context), "import { z } from 'zod'"];
|
|
57
16
|
for (const irEnum of ir.enums) {
|
|
58
|
-
|
|
59
|
-
blocks.push(`export const ${schemaName(irEnum.name)} = z.enum([${values}])\n` +
|
|
60
|
-
`export type ${irEnum.name} = z.infer<typeof ${schemaName(irEnum.name)}>`);
|
|
17
|
+
blocks.push(renderEnum(irEnum));
|
|
61
18
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
blocks.push(
|
|
65
|
-
`export type ${model.name} = z.infer<typeof ${schemaName(model.name)}>`);
|
|
19
|
+
// Pinned value objects (models with `out`) are firestore-scoped: excluded here.
|
|
20
|
+
for (const model of ir.models.filter(model => model.out === undefined)) {
|
|
21
|
+
blocks.push(renderModel(model, ir));
|
|
66
22
|
}
|
|
67
23
|
return [{ path: outputFile(context, 'schemas.ts'), content: `${blocks.join('\n\n')}\n` }];
|
|
68
24
|
},
|
|
69
|
-
});
|
|
25
|
+
}, createZodSplitLayout());
|
|
@@ -5,5 +5,5 @@ import { Generator } from '../generator.js';
|
|
|
5
5
|
* co-located at their first referencing table, leftovers in
|
|
6
6
|
* `schemas/_shared.ts`, and a `schemas.ts` barrel.
|
|
7
7
|
*/
|
|
8
|
-
export declare const
|
|
8
|
+
export declare const createZodSplitLayout: () => Generator;
|
|
9
9
|
//# sourceMappingURL=zod-split-generator.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"zod-split-generator.d.ts","sourceRoot":"","sources":["../../../../../src/lib/_internal/generators/zod/zod-split-generator.ts"],"names":[],"mappings":"AACA,OAAO,EAAiB,SAAS,EAAoB,MAAM,iBAAiB,CAAA;
|
|
1
|
+
{"version":3,"file":"zod-split-generator.d.ts","sourceRoot":"","sources":["../../../../../src/lib/_internal/generators/zod/zod-split-generator.ts"],"names":[],"mappings":"AACA,OAAO,EAAiB,SAAS,EAAoB,MAAM,iBAAiB,CAAA;AAO5E;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,QAAO,SAmEtC,CAAA"}
|
|
@@ -1,69 +1,17 @@
|
|
|
1
1
|
import { findEnum, findModel } from '../../ir/ir.js';
|
|
2
|
-
import { zodConstraints } from '../support/constraints.js';
|
|
3
2
|
import { headerBlocks } from '../support/header.js';
|
|
4
|
-
import {
|
|
5
|
-
import { isRelation, relationFkName, relationFkType } from '../support/relations.js';
|
|
3
|
+
import { isRelation } from '../support/relations.js';
|
|
6
4
|
import { assignModuleNames, collectDeps, splitGroups } from '../support/split.js';
|
|
7
5
|
import { outputFile } from '../support/templates.js';
|
|
8
|
-
|
|
9
|
-
string: 'z.string()',
|
|
10
|
-
int: 'z.number().int()',
|
|
11
|
-
int64: 'z.number().int()',
|
|
12
|
-
float: 'z.number()',
|
|
13
|
-
boolean: 'z.boolean()',
|
|
14
|
-
timestamp: 'z.string().datetime()',
|
|
15
|
-
date: 'z.string()',
|
|
16
|
-
json: 'z.unknown()',
|
|
17
|
-
id: 'z.string()',
|
|
18
|
-
};
|
|
19
|
-
const schemaName = (name) => `${name}Schema`;
|
|
20
|
-
const renderRef = (ref) => {
|
|
21
|
-
switch (ref.kind) {
|
|
22
|
-
case 'scalar':
|
|
23
|
-
return SCALAR_ZOD[ref.name];
|
|
24
|
-
case 'enum':
|
|
25
|
-
return schemaName(ref.name);
|
|
26
|
-
case 'model':
|
|
27
|
-
// Wrapped in z.lazy so declaration order and (cross-file) cycles never matter.
|
|
28
|
-
return `z.lazy(() => ${schemaName(ref.name)})`;
|
|
29
|
-
default:
|
|
30
|
-
return 'z.unknown()';
|
|
31
|
-
}
|
|
32
|
-
};
|
|
33
|
-
const renderFieldSchema = (field, ir) => {
|
|
34
|
-
const isRel = isRelation(field);
|
|
35
|
-
const name = isRel ? relationFkName(field) : field.name;
|
|
36
|
-
const base = field.literal !== undefined
|
|
37
|
-
? `z.literal(${singleQuote(field.literal)})`
|
|
38
|
-
: isRel
|
|
39
|
-
? SCALAR_ZOD[relationFkType(ir, field)]
|
|
40
|
-
: renderRef(field.type);
|
|
41
|
-
const cons = zodConstraints(field);
|
|
42
|
-
let schema = field.list ? `z.array(${base})${cons}` : `${base}${cons}`;
|
|
43
|
-
if (field.nullable)
|
|
44
|
-
schema = `${schema}.nullable()`;
|
|
45
|
-
if (field.optional)
|
|
46
|
-
schema = `${schema}.optional()`;
|
|
47
|
-
return ` ${name}: ${schema},`;
|
|
48
|
-
};
|
|
49
|
-
const renderEnum = (irEnum) => {
|
|
50
|
-
const values = irEnum.values.map(singleQuote).join(', ');
|
|
51
|
-
return (`export const ${schemaName(irEnum.name)} = z.enum([${values}])\n` +
|
|
52
|
-
`export type ${irEnum.name} = z.infer<typeof ${schemaName(irEnum.name)}>`);
|
|
53
|
-
};
|
|
54
|
-
const renderModel = (model, ir) => {
|
|
55
|
-
const fields = model.fields.map(field => renderFieldSchema(field, ir)).join('\n');
|
|
56
|
-
return (`export const ${schemaName(model.name)} = z.object({\n${fields}\n})\n` +
|
|
57
|
-
`export type ${model.name} = z.infer<typeof ${schemaName(model.name)}>`);
|
|
58
|
-
};
|
|
6
|
+
import { renderEnum, renderModel, schemaName } from './render.js';
|
|
59
7
|
/**
|
|
60
8
|
* Split variant of the Zod generator: one file per table at
|
|
61
9
|
* `schemas/<table-name>.ts` (kebab-case) with enums and embedded value objects
|
|
62
10
|
* co-located at their first referencing table, leftovers in
|
|
63
11
|
* `schemas/_shared.ts`, and a `schemas.ts` barrel.
|
|
64
12
|
*/
|
|
65
|
-
export const
|
|
66
|
-
name: 'zod
|
|
13
|
+
export const createZodSplitLayout = () => ({
|
|
14
|
+
name: 'zod',
|
|
67
15
|
description: 'Zod schemas, one file per table (schemas/<table>.ts + schemas.ts barrel)',
|
|
68
16
|
generate(ir, context) {
|
|
69
17
|
const groups = splitGroups(ir);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build-ir.d.ts","sourceRoot":"","sources":["../../../../src/lib/_internal/ir/build-ir.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,
|
|
1
|
+
{"version":3,"file":"build-ir.d.ts","sourceRoot":"","sources":["../../../../src/lib/_internal/ir/build-ir.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAkB,MAAM,mBAAmB,CAAA;AAC9D,OAAO,EAGL,WAAW,EAIZ,MAAM,2BAA2B,CAAA;AAElC,OAAO,EAGL,EAAE,EAiBH,MAAM,SAAS,CAAA;AAEhB,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,EAAE,CAAA;IACN,WAAW,EAAE,UAAU,EAAE,CAAA;CAC1B;AAyPD;;;;;;;;GAQG;AACH,eAAO,MAAM,OAAO,GAAI,WAAW,WAAW,EAAE,KAAG,aAkSlD,CAAA"}
|