firebase-contract 0.1.0-rc.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 +560 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/lib/_internal/compiler/compiler.d.ts +71 -0
- package/dist/lib/_internal/compiler/compiler.d.ts.map +1 -0
- package/dist/lib/_internal/compiler/compiler.js +104 -0
- package/dist/lib/_internal/compiler/index.d.ts +3 -0
- package/dist/lib/_internal/compiler/index.d.ts.map +1 -0
- package/dist/lib/_internal/compiler/index.js +1 -0
- package/dist/lib/_internal/diagnostics.d.ts +29 -0
- package/dist/lib/_internal/diagnostics.d.ts.map +1 -0
- package/dist/lib/_internal/diagnostics.js +26 -0
- package/dist/lib/_internal/format-diagnostics.d.ts +8 -0
- package/dist/lib/_internal/format-diagnostics.d.ts.map +1 -0
- package/dist/lib/_internal/format-diagnostics.js +10 -0
- package/dist/lib/_internal/generators/api/api-dto-generator.d.ts +8 -0
- package/dist/lib/_internal/generators/api/api-dto-generator.d.ts.map +1 -0
- package/dist/lib/_internal/generators/api/api-dto-generator.js +134 -0
- package/dist/lib/_internal/generators/api/api-types-generator.d.ts +8 -0
- package/dist/lib/_internal/generators/api/api-types-generator.d.ts.map +1 -0
- package/dist/lib/_internal/generators/api/api-types-generator.js +105 -0
- package/dist/lib/_internal/generators/api/api-validation-generator.d.ts +7 -0
- package/dist/lib/_internal/generators/api/api-validation-generator.d.ts.map +1 -0
- package/dist/lib/_internal/generators/api/api-validation-generator.js +73 -0
- package/dist/lib/_internal/generators/api/task-payload-generator.d.ts +9 -0
- package/dist/lib/_internal/generators/api/task-payload-generator.d.ts.map +1 -0
- package/dist/lib/_internal/generators/api/task-payload-generator.js +96 -0
- package/dist/lib/_internal/generators/config/config-generator.d.ts +9 -0
- package/dist/lib/_internal/generators/config/config-generator.d.ts.map +1 -0
- package/dist/lib/_internal/generators/config/config-generator.js +71 -0
- package/dist/lib/_internal/generators/data-connect/adapter-generator.d.ts +8 -0
- package/dist/lib/_internal/generators/data-connect/adapter-generator.d.ts.map +1 -0
- package/dist/lib/_internal/generators/data-connect/adapter-generator.js +142 -0
- package/dist/lib/_internal/generators/data-connect/graphql-generator.d.ts +15 -0
- package/dist/lib/_internal/generators/data-connect/graphql-generator.d.ts.map +1 -0
- package/dist/lib/_internal/generators/data-connect/graphql-generator.js +209 -0
- package/dist/lib/_internal/generators/data-connect/operations-generator.d.ts +18 -0
- package/dist/lib/_internal/generators/data-connect/operations-generator.d.ts.map +1 -0
- package/dist/lib/_internal/generators/data-connect/operations-generator.js +539 -0
- package/dist/lib/_internal/generators/firestore/firestore-projection-generator.d.ts +11 -0
- package/dist/lib/_internal/generators/firestore/firestore-projection-generator.d.ts.map +1 -0
- package/dist/lib/_internal/generators/firestore/firestore-projection-generator.js +112 -0
- package/dist/lib/_internal/generators/firestore/firestore-split-generator.d.ts +11 -0
- package/dist/lib/_internal/generators/firestore/firestore-split-generator.d.ts.map +1 -0
- package/dist/lib/_internal/generators/firestore/firestore-split-generator.js +375 -0
- package/dist/lib/_internal/generators/firestore/firestore-type-generator.d.ts +12 -0
- package/dist/lib/_internal/generators/firestore/firestore-type-generator.d.ts.map +1 -0
- package/dist/lib/_internal/generators/firestore/firestore-type-generator.js +81 -0
- package/dist/lib/_internal/generators/generator.d.ts +23 -0
- package/dist/lib/_internal/generators/generator.d.ts.map +1 -0
- package/dist/lib/_internal/generators/generator.js +1 -0
- package/dist/lib/_internal/generators/id/id-codec-generator.d.ts +16 -0
- package/dist/lib/_internal/generators/id/id-codec-generator.d.ts.map +1 -0
- package/dist/lib/_internal/generators/id/id-codec-generator.js +140 -0
- package/dist/lib/_internal/generators/index.d.ts +31 -0
- package/dist/lib/_internal/generators/index.d.ts.map +1 -0
- package/dist/lib/_internal/generators/index.js +64 -0
- package/dist/lib/_internal/generators/registry.d.ts +15 -0
- package/dist/lib/_internal/generators/registry.d.ts.map +1 -0
- package/dist/lib/_internal/generators/registry.js +24 -0
- package/dist/lib/_internal/generators/sql/sql-migration-generator.d.ts +9 -0
- package/dist/lib/_internal/generators/sql/sql-migration-generator.d.ts.map +1 -0
- package/dist/lib/_internal/generators/sql/sql-migration-generator.js +44 -0
- package/dist/lib/_internal/generators/support/constraints.d.ts +8 -0
- package/dist/lib/_internal/generators/support/constraints.d.ts.map +1 -0
- package/dist/lib/_internal/generators/support/constraints.js +39 -0
- package/dist/lib/_internal/generators/support/header.d.ts +13 -0
- package/dist/lib/_internal/generators/support/header.d.ts.map +1 -0
- package/dist/lib/_internal/generators/support/header.js +17 -0
- package/dist/lib/_internal/generators/support/naming.d.ts +10 -0
- package/dist/lib/_internal/generators/support/naming.d.ts.map +1 -0
- package/dist/lib/_internal/generators/support/naming.js +30 -0
- package/dist/lib/_internal/generators/support/relations.d.ts +12 -0
- package/dist/lib/_internal/generators/support/relations.d.ts.map +1 -0
- package/dist/lib/_internal/generators/support/relations.js +11 -0
- package/dist/lib/_internal/generators/support/split.d.ts +34 -0
- package/dist/lib/_internal/generators/support/split.d.ts.map +1 -0
- package/dist/lib/_internal/generators/support/split.js +75 -0
- package/dist/lib/_internal/generators/typescript/typescript-generator.d.ts +18 -0
- package/dist/lib/_internal/generators/typescript/typescript-generator.d.ts.map +1 -0
- package/dist/lib/_internal/generators/typescript/typescript-generator.js +99 -0
- package/dist/lib/_internal/generators/typescript/typescript-split-generator.d.ts +10 -0
- package/dist/lib/_internal/generators/typescript/typescript-split-generator.d.ts.map +1 -0
- package/dist/lib/_internal/generators/typescript/typescript-split-generator.js +175 -0
- package/dist/lib/_internal/generators/union/union-generator.d.ts +8 -0
- package/dist/lib/_internal/generators/union/union-generator.d.ts.map +1 -0
- package/dist/lib/_internal/generators/union/union-generator.js +37 -0
- package/dist/lib/_internal/generators/zod/zod-generator.d.ts +7 -0
- package/dist/lib/_internal/generators/zod/zod-generator.d.ts.map +1 -0
- package/dist/lib/_internal/generators/zod/zod-generator.js +68 -0
- package/dist/lib/_internal/generators/zod/zod-split-generator.d.ts +9 -0
- package/dist/lib/_internal/generators/zod/zod-split-generator.d.ts.map +1 -0
- package/dist/lib/_internal/generators/zod/zod-split-generator.js +136 -0
- package/dist/lib/_internal/ir/build-ir.d.ts +18 -0
- package/dist/lib/_internal/ir/build-ir.d.ts.map +1 -0
- package/dist/lib/_internal/ir/build-ir.js +430 -0
- package/dist/lib/_internal/ir/ir.d.ts +393 -0
- package/dist/lib/_internal/ir/ir.d.ts.map +1 -0
- package/dist/lib/_internal/ir/ir.js +22 -0
- package/dist/lib/_internal/parser/parse.d.ts +9 -0
- package/dist/lib/_internal/parser/parse.d.ts.map +1 -0
- package/dist/lib/_internal/parser/parse.js +682 -0
- package/dist/lib/_internal/parser/raw-document.d.ts +230 -0
- package/dist/lib/_internal/parser/raw-document.d.ts.map +1 -0
- package/dist/lib/_internal/parser/raw-document.js +6 -0
- package/dist/lib/_internal/resolver/import-resolver.d.ts +25 -0
- package/dist/lib/_internal/resolver/import-resolver.d.ts.map +1 -0
- package/dist/lib/_internal/resolver/import-resolver.js +73 -0
- package/dist/lib/_internal/resolver/module-loader.d.ts +25 -0
- package/dist/lib/_internal/resolver/module-loader.d.ts.map +1 -0
- package/dist/lib/_internal/resolver/module-loader.js +62 -0
- package/dist/lib/_internal/runtime/data-connect-adapter.d.ts +25 -0
- package/dist/lib/_internal/runtime/data-connect-adapter.d.ts.map +1 -0
- package/dist/lib/_internal/runtime/data-connect-adapter.js +24 -0
- package/dist/lib/_internal/runtime/index.d.ts +3 -0
- package/dist/lib/_internal/runtime/index.d.ts.map +1 -0
- package/dist/lib/_internal/runtime/index.js +1 -0
- package/dist/lib/_internal/validation/rules.d.ts +32 -0
- package/dist/lib/_internal/validation/rules.d.ts.map +1 -0
- package/dist/lib/_internal/validation/rules.js +282 -0
- package/dist/lib/_internal/validation/validate.d.ts +9 -0
- package/dist/lib/_internal/validation/validate.d.ts.map +1 -0
- package/dist/lib/_internal/validation/validate.js +6 -0
- package/dist/lib/cli/commands/generate.d.ts +3 -0
- package/dist/lib/cli/commands/generate.d.ts.map +1 -0
- package/dist/lib/cli/commands/generate.js +66 -0
- package/dist/lib/cli/commands/index.d.ts +4 -0
- package/dist/lib/cli/commands/index.d.ts.map +1 -0
- package/dist/lib/cli/commands/index.js +11 -0
- package/dist/lib/cli/commands/init.d.ts +3 -0
- package/dist/lib/cli/commands/init.d.ts.map +1 -0
- package/dist/lib/cli/commands/init.js +72 -0
- package/dist/lib/cli/commands/inspect.d.ts +3 -0
- package/dist/lib/cli/commands/inspect.d.ts.map +1 -0
- package/dist/lib/cli/commands/inspect.js +23 -0
- package/dist/lib/cli/commands/validate.d.ts +3 -0
- package/dist/lib/cli/commands/validate.d.ts.map +1 -0
- package/dist/lib/cli/commands/validate.js +23 -0
- package/dist/lib/cli/config.d.ts +12 -0
- package/dist/lib/cli/config.d.ts.map +1 -0
- package/dist/lib/cli/config.js +20 -0
- package/dist/lib/cli/index.d.ts +3 -0
- package/dist/lib/cli/index.d.ts.map +1 -0
- package/dist/lib/cli/index.js +13 -0
- package/dist/lib/cli/version.d.ts +3 -0
- package/dist/lib/cli/version.d.ts.map +1 -0
- package/dist/lib/cli/version.js +15 -0
- package/dist/lib/modules/runtime.d.ts +2 -0
- package/dist/lib/modules/runtime.d.ts.map +1 -0
- package/dist/lib/modules/runtime.js +3 -0
- package/package.json +60 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { findEnum, findModel } from '../../ir/ir.js';
|
|
2
|
+
import { headerBlocks } from '../support/header.js';
|
|
3
|
+
import { constantCase, singleQuote } from '../support/naming.js';
|
|
4
|
+
import { isRelation, relationFkName, relationFkType } from '../support/relations.js';
|
|
5
|
+
import { collectDeps, kebabCase, splitGroups, tableNameOf } from '../support/split.js';
|
|
6
|
+
const SCALAR_TS = {
|
|
7
|
+
string: 'string',
|
|
8
|
+
int: 'number',
|
|
9
|
+
int64: 'number',
|
|
10
|
+
float: 'number',
|
|
11
|
+
boolean: 'boolean',
|
|
12
|
+
timestamp: 'string',
|
|
13
|
+
date: 'string',
|
|
14
|
+
json: 'Json',
|
|
15
|
+
id: 'string',
|
|
16
|
+
};
|
|
17
|
+
const JSON_TYPE = [
|
|
18
|
+
'export type Json =',
|
|
19
|
+
' | string',
|
|
20
|
+
' | number',
|
|
21
|
+
' | boolean',
|
|
22
|
+
' | null',
|
|
23
|
+
' | Json[]',
|
|
24
|
+
' | { [key: string]: Json }',
|
|
25
|
+
].join('\n');
|
|
26
|
+
const renderRef = (ref) => {
|
|
27
|
+
switch (ref.kind) {
|
|
28
|
+
case 'scalar':
|
|
29
|
+
return SCALAR_TS[ref.name];
|
|
30
|
+
case 'enum':
|
|
31
|
+
case 'model':
|
|
32
|
+
return ref.name;
|
|
33
|
+
default:
|
|
34
|
+
return 'unknown';
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
const docLines = (description, indent) => {
|
|
38
|
+
if (!description)
|
|
39
|
+
return [];
|
|
40
|
+
const lines = description.split('\n');
|
|
41
|
+
if (lines.length === 1)
|
|
42
|
+
return [`${indent}/** ${lines[0]} */`];
|
|
43
|
+
return [`${indent}/**`, ...lines.map(line => `${indent} * ${line}`.trimEnd()), `${indent} */`];
|
|
44
|
+
};
|
|
45
|
+
const renderField = (field, ir) => {
|
|
46
|
+
const optional = field.optional ? '?' : '';
|
|
47
|
+
const doc = docLines(field.description, ' ');
|
|
48
|
+
let entry;
|
|
49
|
+
if (field.literal !== undefined) {
|
|
50
|
+
const type = field.list ? `${singleQuote(field.literal)}[]` : singleQuote(field.literal);
|
|
51
|
+
entry = ` ${field.name}${optional}: ${type}`;
|
|
52
|
+
}
|
|
53
|
+
else if (isRelation(field)) {
|
|
54
|
+
const base = SCALAR_TS[relationFkType(ir, field)];
|
|
55
|
+
entry = ` ${relationFkName(field)}${optional}: ${field.list ? `${base}[]` : base}`;
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
const base = renderRef(field.type);
|
|
59
|
+
const type = field.list ? `${base}[]` : base;
|
|
60
|
+
const nullable = field.nullable ? ' | null' : '';
|
|
61
|
+
entry = ` ${field.name}${optional}: ${type}${nullable}`;
|
|
62
|
+
}
|
|
63
|
+
return [...doc, entry].join('\n');
|
|
64
|
+
};
|
|
65
|
+
const renderEnumConst = (irEnum) => {
|
|
66
|
+
const doc = docLines(irEnum.description, '');
|
|
67
|
+
const constName = constantCase(irEnum.name);
|
|
68
|
+
const entries = irEnum.values
|
|
69
|
+
.map(value => ` ${irEnum.valueKeys?.[value] ?? constantCase(value)}: ${singleQuote(value)},`)
|
|
70
|
+
.join('\n');
|
|
71
|
+
return [
|
|
72
|
+
...doc,
|
|
73
|
+
`export const ${constName} = Object.freeze({`,
|
|
74
|
+
entries,
|
|
75
|
+
`} as const)`,
|
|
76
|
+
`export type ${irEnum.name}Key = keyof typeof ${constName}`,
|
|
77
|
+
`export type ${irEnum.name} = (typeof ${constName})[${irEnum.name}Key]`,
|
|
78
|
+
].join('\n');
|
|
79
|
+
};
|
|
80
|
+
const renderModel = (model, ir) => {
|
|
81
|
+
const doc = docLines(model.description, '');
|
|
82
|
+
const fields = model.fields.map(field => renderField(field, ir)).join('\n');
|
|
83
|
+
return [...doc, `export interface ${model.name} {`, fields, '}'].join('\n');
|
|
84
|
+
};
|
|
85
|
+
const usesJson = (models) => models.some(model => model.fields.some(field => field.type.kind === 'scalar' && field.type.name === 'json'));
|
|
86
|
+
/**
|
|
87
|
+
* Split variant of the TypeScript generator: one file per table at
|
|
88
|
+
* `types/<table-name>.ts` (kebab-case), with enums and embedded value objects
|
|
89
|
+
* co-located in the file of the first table that references them. Shared
|
|
90
|
+
* leftovers (and the `Json` type) live in `types/_shared.ts`, and `types.ts`
|
|
91
|
+
* is a barrel re-exporting every file.
|
|
92
|
+
*/
|
|
93
|
+
export const createTypeScriptSplitGenerator = () => ({
|
|
94
|
+
name: 'typescript-split',
|
|
95
|
+
description: 'TypeScript interfaces, one file per table (types/<table>.ts + types.ts barrel)',
|
|
96
|
+
generate(ir, context) {
|
|
97
|
+
const groups = splitGroups(ir);
|
|
98
|
+
const files = [];
|
|
99
|
+
const fileOf = new Map(); // symbol -> module basename (no ext)
|
|
100
|
+
const sharedNeeded = usesJson(ir.models) || groups.leftoverEnums.length > 0 || groups.leftoverModels.length > 0;
|
|
101
|
+
if (usesJson(ir.models))
|
|
102
|
+
fileOf.set('Json', '_shared');
|
|
103
|
+
for (const name of groups.leftoverEnums)
|
|
104
|
+
fileOf.set(name, '_shared');
|
|
105
|
+
for (const name of groups.leftoverModels)
|
|
106
|
+
fileOf.set(name, '_shared');
|
|
107
|
+
for (const table of groups.tables) {
|
|
108
|
+
const moduleName = kebabCase(tableNameOf(table));
|
|
109
|
+
fileOf.set(table.name, moduleName);
|
|
110
|
+
for (const [name, home] of groups.enumHome)
|
|
111
|
+
if (home === table.name)
|
|
112
|
+
fileOf.set(name, moduleName);
|
|
113
|
+
for (const [name, home] of groups.modelHome)
|
|
114
|
+
if (home === table.name)
|
|
115
|
+
fileOf.set(name, moduleName);
|
|
116
|
+
}
|
|
117
|
+
const emit = (moduleName, enums, models, includeJson) => {
|
|
118
|
+
const blocks = [...headerBlocks(context)];
|
|
119
|
+
// Imports for symbols declared in sibling files.
|
|
120
|
+
const external = new Map();
|
|
121
|
+
const need = (symbol) => {
|
|
122
|
+
const home = fileOf.get(symbol);
|
|
123
|
+
if (home && home !== moduleName) {
|
|
124
|
+
const set = external.get(home) ?? new Set();
|
|
125
|
+
set.add(symbol);
|
|
126
|
+
external.set(home, set);
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
for (const model of models) {
|
|
130
|
+
for (const field of model.fields) {
|
|
131
|
+
if (field.type.kind === 'scalar' && field.type.name === 'json')
|
|
132
|
+
need('Json');
|
|
133
|
+
// Literal-pinned and relation fields render without referencing the type.
|
|
134
|
+
if (field.literal !== undefined || isRelation(field))
|
|
135
|
+
continue;
|
|
136
|
+
if (field.type.kind === 'enum' || field.type.kind === 'model')
|
|
137
|
+
need(field.type.name);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
const imports = [...external.entries()]
|
|
141
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
142
|
+
.map(([home, symbols]) => `import type { ${[...symbols].sort().join(', ')} } from './${home}'`);
|
|
143
|
+
if (imports.length > 0)
|
|
144
|
+
blocks.push(imports.join('\n'));
|
|
145
|
+
if (includeJson)
|
|
146
|
+
blocks.push(JSON_TYPE);
|
|
147
|
+
for (const irEnum of enums)
|
|
148
|
+
blocks.push(renderEnumConst(irEnum));
|
|
149
|
+
for (const model of models)
|
|
150
|
+
blocks.push(renderModel(model, ir));
|
|
151
|
+
files.push({ path: `types/${moduleName}.ts`, content: `${blocks.join('\n\n')}\n` });
|
|
152
|
+
};
|
|
153
|
+
if (sharedNeeded) {
|
|
154
|
+
const enums = groups.leftoverEnums.map(name => findEnum(ir, name)).filter((e) => Boolean(e));
|
|
155
|
+
const models = groups.leftoverModels.map(name => findModel(ir, name)).filter((m) => Boolean(m));
|
|
156
|
+
emit('_shared', enums, models, usesJson(ir.models));
|
|
157
|
+
}
|
|
158
|
+
for (const table of groups.tables) {
|
|
159
|
+
const moduleName = kebabCase(tableNameOf(table));
|
|
160
|
+
const deps = collectDeps(ir, [table]);
|
|
161
|
+
const enums = deps.enums
|
|
162
|
+
.filter(name => groups.enumHome.get(name) === table.name)
|
|
163
|
+
.map(name => findEnum(ir, name))
|
|
164
|
+
.filter((e) => Boolean(e));
|
|
165
|
+
const models = deps.models
|
|
166
|
+
.filter(name => groups.modelHome.get(name) === table.name)
|
|
167
|
+
.map(name => findModel(ir, name))
|
|
168
|
+
.filter((m) => Boolean(m));
|
|
169
|
+
emit(moduleName, enums, [...models, table], false);
|
|
170
|
+
}
|
|
171
|
+
const exports = files.map(file => `export * from './${file.path.replace(/^types\//, 'types/').replace(/\.ts$/, '')}'`);
|
|
172
|
+
files.push({ path: 'types.ts', content: `${[...headerBlocks(context), exports.join('\n')].join('\n\n')}\n` });
|
|
173
|
+
return files;
|
|
174
|
+
},
|
|
175
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Generator } from '../generator.js';
|
|
2
|
+
/**
|
|
3
|
+
* Generates Zod discriminated unions (and their TS union types) over model
|
|
4
|
+
* variants — e.g. a `z.discriminatedUnion('operationType', …)` of operation drafts.
|
|
5
|
+
* References the variant schemas/types from the zod and typescript outputs.
|
|
6
|
+
*/
|
|
7
|
+
export declare const createUnionGenerator: () => Generator;
|
|
8
|
+
//# sourceMappingURL=union-generator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"union-generator.d.ts","sourceRoot":"","sources":["../../../../../src/lib/_internal/generators/union/union-generator.ts"],"names":[],"mappings":"AAEA,OAAO,EAAiB,SAAS,EAAoB,MAAM,iBAAiB,CAAA;AAe5E;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,QAAO,SAqBtC,CAAA"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { singleQuote } from '../support/naming.js';
|
|
2
|
+
import { headerBlocks } from '../support/header.js';
|
|
3
|
+
const schemaName = (name) => `${name}Schema`;
|
|
4
|
+
const renderUnion = (union) => {
|
|
5
|
+
const variantSchemas = union.variants.map(schemaName).join(', ');
|
|
6
|
+
const variantTypes = union.variants.join(' | ') || 'never';
|
|
7
|
+
const doc = union.description ? `/** ${union.description} */\n` : '';
|
|
8
|
+
return [
|
|
9
|
+
`${doc}export const ${schemaName(union.name)} = z.discriminatedUnion(${singleQuote(union.discriminant)}, [${variantSchemas}])`,
|
|
10
|
+
`export type ${union.name} = ${variantTypes}`,
|
|
11
|
+
].join('\n');
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Generates Zod discriminated unions (and their TS union types) over model
|
|
15
|
+
* variants — e.g. a `z.discriminatedUnion('operationType', …)` of operation drafts.
|
|
16
|
+
* References the variant schemas/types from the zod and typescript outputs.
|
|
17
|
+
*/
|
|
18
|
+
export const createUnionGenerator = () => ({
|
|
19
|
+
name: 'unions',
|
|
20
|
+
description: 'Zod discriminated unions + TS union types',
|
|
21
|
+
generate(ir, context) {
|
|
22
|
+
if (ir.unions.length === 0) {
|
|
23
|
+
return [];
|
|
24
|
+
}
|
|
25
|
+
const variantNames = [...new Set(ir.unions.flatMap(union => union.variants))].sort();
|
|
26
|
+
const blocks = [...headerBlocks(context), "import { z } from 'zod'"];
|
|
27
|
+
if (variantNames.length > 0) {
|
|
28
|
+
// One import block — a blank line between relative imports trips import/order.
|
|
29
|
+
blocks.push(`import { ${variantNames.map(schemaName).join(', ')} } from './schemas.js'\n` +
|
|
30
|
+
`import type { ${variantNames.join(', ')} } from './types.js'`);
|
|
31
|
+
}
|
|
32
|
+
for (const union of ir.unions) {
|
|
33
|
+
blocks.push(renderUnion(union));
|
|
34
|
+
}
|
|
35
|
+
return [{ path: 'unions.ts', content: `${blocks.join('\n\n')}\n` }];
|
|
36
|
+
},
|
|
37
|
+
});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Generator } from '../generator.js';
|
|
2
|
+
/**
|
|
3
|
+
* Generates Zod schemas and the types inferred from them. Model references use
|
|
4
|
+
* `z.lazy` so the output is order- and cycle-independent.
|
|
5
|
+
*/
|
|
6
|
+
export declare const createZodGenerator: () => Generator;
|
|
7
|
+
//# sourceMappingURL=zod-generator.d.ts.map
|
|
@@ -0,0 +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;AAoD5E;;;GAGG;AACH,eAAO,MAAM,kBAAkB,QAAO,SAwBpC,CAAA"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { headerBlocks } from '../support/header.js';
|
|
2
|
+
import { singleQuote } from '../support/naming.js';
|
|
3
|
+
import { isRelation, relationFkName, relationFkType } from '../support/relations.js';
|
|
4
|
+
import { zodConstraints } from '../support/constraints.js';
|
|
5
|
+
const SCALAR_ZOD = {
|
|
6
|
+
string: 'z.string()',
|
|
7
|
+
int: 'z.number().int()',
|
|
8
|
+
int64: 'z.number().int()',
|
|
9
|
+
float: 'z.number()',
|
|
10
|
+
boolean: 'z.boolean()',
|
|
11
|
+
timestamp: 'z.string().datetime()',
|
|
12
|
+
date: 'z.string()',
|
|
13
|
+
json: 'z.unknown()',
|
|
14
|
+
id: 'z.string()',
|
|
15
|
+
};
|
|
16
|
+
const schemaName = (name) => `${name}Schema`;
|
|
17
|
+
const renderRef = (ref) => {
|
|
18
|
+
switch (ref.kind) {
|
|
19
|
+
case 'scalar':
|
|
20
|
+
return SCALAR_ZOD[ref.name];
|
|
21
|
+
case 'enum':
|
|
22
|
+
// Enums are declared before models, so a direct reference is safe.
|
|
23
|
+
return schemaName(ref.name);
|
|
24
|
+
case 'model':
|
|
25
|
+
// Model references are wrapped in z.lazy so declaration order and cycles
|
|
26
|
+
// never matter.
|
|
27
|
+
return `z.lazy(() => ${schemaName(ref.name)})`;
|
|
28
|
+
default:
|
|
29
|
+
return 'z.unknown()';
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
const renderFieldSchema = (field, ir) => {
|
|
33
|
+
const isRel = isRelation(field);
|
|
34
|
+
const name = isRel ? relationFkName(field) : field.name;
|
|
35
|
+
const base = field.literal !== undefined
|
|
36
|
+
? `z.literal(${singleQuote(field.literal)})`
|
|
37
|
+
: isRel
|
|
38
|
+
? SCALAR_ZOD[relationFkType(ir, field)]
|
|
39
|
+
: renderRef(field.type);
|
|
40
|
+
const cons = zodConstraints(field);
|
|
41
|
+
let schema = field.list ? `z.array(${base})${cons}` : `${base}${cons}`;
|
|
42
|
+
if (field.optional) {
|
|
43
|
+
schema = `${schema}.optional()`;
|
|
44
|
+
}
|
|
45
|
+
return ` ${name}: ${schema},`;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Generates Zod schemas and the types inferred from them. Model references use
|
|
49
|
+
* `z.lazy` so the output is order- and cycle-independent.
|
|
50
|
+
*/
|
|
51
|
+
export const createZodGenerator = () => ({
|
|
52
|
+
name: 'zod',
|
|
53
|
+
description: 'Zod validation schemas',
|
|
54
|
+
generate(ir, context) {
|
|
55
|
+
const blocks = [...headerBlocks(context), "import { z } from 'zod'"];
|
|
56
|
+
for (const irEnum of ir.enums) {
|
|
57
|
+
const values = irEnum.values.map(singleQuote).join(', ');
|
|
58
|
+
blocks.push(`export const ${schemaName(irEnum.name)} = z.enum([${values}])\n` +
|
|
59
|
+
`export type ${irEnum.name} = z.infer<typeof ${schemaName(irEnum.name)}>`);
|
|
60
|
+
}
|
|
61
|
+
for (const model of ir.models) {
|
|
62
|
+
const fields = model.fields.map(field => renderFieldSchema(field, ir)).join('\n');
|
|
63
|
+
blocks.push(`export const ${schemaName(model.name)} = z.object({\n${fields}\n})\n` +
|
|
64
|
+
`export type ${model.name} = z.infer<typeof ${schemaName(model.name)}>`);
|
|
65
|
+
}
|
|
66
|
+
return [{ path: 'schemas.ts', content: `${blocks.join('\n\n')}\n` }];
|
|
67
|
+
},
|
|
68
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Generator } from '../generator.js';
|
|
2
|
+
/**
|
|
3
|
+
* Split variant of the Zod generator: one file per table at
|
|
4
|
+
* `schemas/<table-name>.ts` (kebab-case) with enums and embedded value objects
|
|
5
|
+
* co-located at their first referencing table, leftovers in
|
|
6
|
+
* `schemas/_shared.ts`, and a `schemas.ts` barrel.
|
|
7
|
+
*/
|
|
8
|
+
export declare const createZodSplitGenerator: () => Generator;
|
|
9
|
+
//# sourceMappingURL=zod-split-generator.d.ts.map
|
|
@@ -0,0 +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;AAkE5E;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,QAAO,SAkEzC,CAAA"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { findEnum, findModel } from '../../ir/ir.js';
|
|
2
|
+
import { zodConstraints } from '../support/constraints.js';
|
|
3
|
+
import { headerBlocks } from '../support/header.js';
|
|
4
|
+
import { singleQuote } from '../support/naming.js';
|
|
5
|
+
import { isRelation, relationFkName, relationFkType } from '../support/relations.js';
|
|
6
|
+
import { collectDeps, kebabCase, splitGroups, tableNameOf } from '../support/split.js';
|
|
7
|
+
const SCALAR_ZOD = {
|
|
8
|
+
string: 'z.string()',
|
|
9
|
+
int: 'z.number().int()',
|
|
10
|
+
int64: 'z.number().int()',
|
|
11
|
+
float: 'z.number()',
|
|
12
|
+
boolean: 'z.boolean()',
|
|
13
|
+
timestamp: 'z.string().datetime()',
|
|
14
|
+
date: 'z.string()',
|
|
15
|
+
json: 'z.unknown()',
|
|
16
|
+
id: 'z.string()',
|
|
17
|
+
};
|
|
18
|
+
const schemaName = (name) => `${name}Schema`;
|
|
19
|
+
const renderRef = (ref) => {
|
|
20
|
+
switch (ref.kind) {
|
|
21
|
+
case 'scalar':
|
|
22
|
+
return SCALAR_ZOD[ref.name];
|
|
23
|
+
case 'enum':
|
|
24
|
+
return schemaName(ref.name);
|
|
25
|
+
case 'model':
|
|
26
|
+
// Wrapped in z.lazy so declaration order and (cross-file) cycles never matter.
|
|
27
|
+
return `z.lazy(() => ${schemaName(ref.name)})`;
|
|
28
|
+
default:
|
|
29
|
+
return 'z.unknown()';
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
const renderFieldSchema = (field, ir) => {
|
|
33
|
+
const isRel = isRelation(field);
|
|
34
|
+
const name = isRel ? relationFkName(field) : field.name;
|
|
35
|
+
const base = field.literal !== undefined
|
|
36
|
+
? `z.literal(${singleQuote(field.literal)})`
|
|
37
|
+
: isRel
|
|
38
|
+
? SCALAR_ZOD[relationFkType(ir, field)]
|
|
39
|
+
: renderRef(field.type);
|
|
40
|
+
const cons = zodConstraints(field);
|
|
41
|
+
let schema = field.list ? `z.array(${base})${cons}` : `${base}${cons}`;
|
|
42
|
+
if (field.nullable)
|
|
43
|
+
schema = `${schema}.nullable()`;
|
|
44
|
+
if (field.optional)
|
|
45
|
+
schema = `${schema}.optional()`;
|
|
46
|
+
return ` ${name}: ${schema},`;
|
|
47
|
+
};
|
|
48
|
+
const renderEnum = (irEnum) => {
|
|
49
|
+
const values = irEnum.values.map(singleQuote).join(', ');
|
|
50
|
+
return (`export const ${schemaName(irEnum.name)} = z.enum([${values}])\n` +
|
|
51
|
+
`export type ${irEnum.name} = z.infer<typeof ${schemaName(irEnum.name)}>`);
|
|
52
|
+
};
|
|
53
|
+
const renderModel = (model, ir) => {
|
|
54
|
+
const fields = model.fields.map(field => renderFieldSchema(field, ir)).join('\n');
|
|
55
|
+
return (`export const ${schemaName(model.name)} = z.object({\n${fields}\n})\n` +
|
|
56
|
+
`export type ${model.name} = z.infer<typeof ${schemaName(model.name)}>`);
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Split variant of the Zod generator: one file per table at
|
|
60
|
+
* `schemas/<table-name>.ts` (kebab-case) with enums and embedded value objects
|
|
61
|
+
* co-located at their first referencing table, leftovers in
|
|
62
|
+
* `schemas/_shared.ts`, and a `schemas.ts` barrel.
|
|
63
|
+
*/
|
|
64
|
+
export const createZodSplitGenerator = () => ({
|
|
65
|
+
name: 'zod-split',
|
|
66
|
+
description: 'Zod schemas, one file per table (schemas/<table>.ts + schemas.ts barrel)',
|
|
67
|
+
generate(ir, context) {
|
|
68
|
+
const groups = splitGroups(ir);
|
|
69
|
+
const files = [];
|
|
70
|
+
const fileOf = new Map();
|
|
71
|
+
for (const name of groups.leftoverEnums)
|
|
72
|
+
fileOf.set(name, '_shared');
|
|
73
|
+
for (const name of groups.leftoverModels)
|
|
74
|
+
fileOf.set(name, '_shared');
|
|
75
|
+
for (const table of groups.tables) {
|
|
76
|
+
const moduleName = kebabCase(tableNameOf(table));
|
|
77
|
+
fileOf.set(table.name, moduleName);
|
|
78
|
+
for (const [name, home] of groups.enumHome)
|
|
79
|
+
if (home === table.name)
|
|
80
|
+
fileOf.set(name, moduleName);
|
|
81
|
+
for (const [name, home] of groups.modelHome)
|
|
82
|
+
if (home === table.name)
|
|
83
|
+
fileOf.set(name, moduleName);
|
|
84
|
+
}
|
|
85
|
+
const emit = (moduleName, enums, models) => {
|
|
86
|
+
const blocks = [...headerBlocks(context), "import { z } from 'zod'"];
|
|
87
|
+
const external = new Map();
|
|
88
|
+
for (const model of models) {
|
|
89
|
+
for (const field of model.fields) {
|
|
90
|
+
// Literal-pinned and relation fields render without referencing the schema.
|
|
91
|
+
if (field.literal !== undefined || isRelation(field))
|
|
92
|
+
continue;
|
|
93
|
+
if (field.type.kind !== 'enum' && field.type.kind !== 'model')
|
|
94
|
+
continue;
|
|
95
|
+
const home = fileOf.get(field.type.name);
|
|
96
|
+
if (home && home !== moduleName) {
|
|
97
|
+
const set = external.get(home) ?? new Set();
|
|
98
|
+
set.add(schemaName(field.type.name));
|
|
99
|
+
external.set(home, set);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const imports = [...external.entries()]
|
|
104
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
105
|
+
.map(([home, symbols]) => `import { ${[...symbols].sort().join(', ')} } from './${home}'`);
|
|
106
|
+
if (imports.length > 0)
|
|
107
|
+
blocks.push(imports.join('\n'));
|
|
108
|
+
for (const irEnum of enums)
|
|
109
|
+
blocks.push(renderEnum(irEnum));
|
|
110
|
+
for (const model of models)
|
|
111
|
+
blocks.push(renderModel(model, ir));
|
|
112
|
+
files.push({ path: `schemas/${moduleName}.ts`, content: `${blocks.join('\n\n')}\n` });
|
|
113
|
+
};
|
|
114
|
+
if (groups.leftoverEnums.length > 0 || groups.leftoverModels.length > 0) {
|
|
115
|
+
const enums = groups.leftoverEnums.map(name => findEnum(ir, name)).filter((e) => Boolean(e));
|
|
116
|
+
const models = groups.leftoverModels.map(name => findModel(ir, name)).filter((m) => Boolean(m));
|
|
117
|
+
emit('_shared', enums, models);
|
|
118
|
+
}
|
|
119
|
+
for (const table of groups.tables) {
|
|
120
|
+
const moduleName = kebabCase(tableNameOf(table));
|
|
121
|
+
const deps = collectDeps(ir, [table]);
|
|
122
|
+
const enums = deps.enums
|
|
123
|
+
.filter(name => groups.enumHome.get(name) === table.name)
|
|
124
|
+
.map(name => findEnum(ir, name))
|
|
125
|
+
.filter((e) => Boolean(e));
|
|
126
|
+
const models = deps.models
|
|
127
|
+
.filter(name => groups.modelHome.get(name) === table.name)
|
|
128
|
+
.map(name => findModel(ir, name))
|
|
129
|
+
.filter((m) => Boolean(m));
|
|
130
|
+
emit(moduleName, enums, [...models, table]);
|
|
131
|
+
}
|
|
132
|
+
const exports = files.map(file => `export * from './${file.path.replace(/\.ts$/, '')}'`);
|
|
133
|
+
files.push({ path: 'schemas.ts', content: `${[...headerBlocks(context), exports.join('\n')].join('\n\n')}\n` });
|
|
134
|
+
return files;
|
|
135
|
+
},
|
|
136
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Diagnostic } from '../diagnostics.js';
|
|
2
|
+
import { RawContract } from '../parser/raw-document.js';
|
|
3
|
+
import { Ir } from './ir.js';
|
|
4
|
+
export interface BuildIrResult {
|
|
5
|
+
ir: Ir;
|
|
6
|
+
diagnostics: Diagnostic[];
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Merge every resolved document into one normalized {@link Ir}.
|
|
10
|
+
*
|
|
11
|
+
* Type names on fields are resolved against the full set of enums and models
|
|
12
|
+
* gathered from all documents, so imports work transparently. Names defined in
|
|
13
|
+
* more than one place are reported as `DUPLICATE_DEFINITION` and the first
|
|
14
|
+
* definition wins. Unknown type names are left as `unresolved` refs for the
|
|
15
|
+
* validation step to flag.
|
|
16
|
+
*/
|
|
17
|
+
export declare const buildIr: (documents: RawContract[]) => BuildIrResult;
|
|
18
|
+
//# sourceMappingURL=build-ir.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build-ir.d.ts","sourceRoot":"","sources":["../../../../src/lib/_internal/ir/build-ir.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAS,MAAM,mBAAmB,CAAA;AACrD,OAAO,EAGL,WAAW,EAIZ,MAAM,2BAA2B,CAAA;AAElC,OAAO,EAGL,EAAE,EAgBH,MAAM,SAAS,CAAA;AAEhB,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,EAAE,CAAA;IACN,WAAW,EAAE,UAAU,EAAE,CAAA;CAC1B;AAoLD;;;;;;;;GAQG;AACH,eAAO,MAAM,OAAO,GAAI,WAAW,WAAW,EAAE,KAAG,aAqQlD,CAAA"}
|