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,104 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { error, hasErrors } from '../diagnostics.js';
|
|
4
|
+
import { buildIr } from '../ir/build-ir.js';
|
|
5
|
+
import { resolveImports } from '../resolver/import-resolver.js';
|
|
6
|
+
import { createFsLoader } from '../resolver/module-loader.js';
|
|
7
|
+
import { validateIr } from '../validation/validate.js';
|
|
8
|
+
import { createDefaultRegistry } from '../generators/index.js';
|
|
9
|
+
import { toHeaderComment } from '../generators/support/header.js';
|
|
10
|
+
/**
|
|
11
|
+
* Run the front end of the pipeline: resolve imports, build the IR, and run
|
|
12
|
+
* semantic validation. Generators are intentionally not involved here — the CLI
|
|
13
|
+
* and {@link generate} depend on this, keeping analysis and code emission
|
|
14
|
+
* separable and independently testable.
|
|
15
|
+
*/
|
|
16
|
+
export const compile = (entryPath, options = {}) => {
|
|
17
|
+
const loader = options.loader ?? createFsLoader();
|
|
18
|
+
const { documents, diagnostics: importDiagnostics } = resolveImports(entryPath, loader);
|
|
19
|
+
const { ir, diagnostics: buildDiagnostics } = buildIr(documents);
|
|
20
|
+
const validationDiagnostics = validateIr(ir);
|
|
21
|
+
return {
|
|
22
|
+
ir,
|
|
23
|
+
documents,
|
|
24
|
+
diagnostics: [...importDiagnostics, ...buildDiagnostics, ...validationDiagnostics],
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Run every `generate:` target declared across the entry contract and its
|
|
29
|
+
* (transitive) imports — the one-command flow: each contract file states where
|
|
30
|
+
* its own outputs go, and `fbc generate <root>` materializes all of them.
|
|
31
|
+
* Output dirs resolve relative to the declaring yml file.
|
|
32
|
+
*/
|
|
33
|
+
export const generateAll = (entryPath, options = {}) => {
|
|
34
|
+
const { documents, diagnostics } = compile(entryPath, options);
|
|
35
|
+
if (hasErrors(diagnostics)) {
|
|
36
|
+
return { targets: [], diagnostics, ok: false };
|
|
37
|
+
}
|
|
38
|
+
const collected = [...diagnostics];
|
|
39
|
+
const targets = [];
|
|
40
|
+
let ok = true;
|
|
41
|
+
for (const doc of documents) {
|
|
42
|
+
for (const output of doc.outputs ?? []) {
|
|
43
|
+
const outDir = join(dirname(doc.filePath), output.out);
|
|
44
|
+
const result = generate(doc.filePath, {
|
|
45
|
+
outDir,
|
|
46
|
+
generators: output.generators,
|
|
47
|
+
registry: options.registry,
|
|
48
|
+
write: options.write,
|
|
49
|
+
...(options.context ? { context: options.context } : {}),
|
|
50
|
+
...(options.loader ? { loader: options.loader } : {}),
|
|
51
|
+
});
|
|
52
|
+
// Per-target compiles revisit shared imports; keep only new diagnostics.
|
|
53
|
+
for (const diagnostic of result.diagnostics) {
|
|
54
|
+
if (!collected.some(d => d.code === diagnostic.code && d.message === diagnostic.message)) {
|
|
55
|
+
collected.push(diagnostic);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (!result.ok)
|
|
59
|
+
ok = false;
|
|
60
|
+
targets.push({ source: doc.filePath, outDir, generators: output.generators, files: result.files });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return { targets, diagnostics: collected, ok };
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* Compile then emit code. Generation is skipped when compilation produced any
|
|
67
|
+
* error, so generators never see an invalid IR. The CLI is a thin wrapper over
|
|
68
|
+
* this function.
|
|
69
|
+
*/
|
|
70
|
+
export const generate = (entryPath, options) => {
|
|
71
|
+
const registry = options.registry ?? createDefaultRegistry();
|
|
72
|
+
const { ir, diagnostics } = compile(entryPath, options);
|
|
73
|
+
const collected = [...diagnostics];
|
|
74
|
+
if (hasErrors(collected)) {
|
|
75
|
+
return { ir, diagnostics: collected, files: [], ok: false };
|
|
76
|
+
}
|
|
77
|
+
const names = options.generators && options.generators.length > 0 ? options.generators : registry.names();
|
|
78
|
+
// CLI/options header wins; otherwise fall back to the contract's `header:`.
|
|
79
|
+
const context = { ...options.context };
|
|
80
|
+
if (context.header === undefined && ir.header !== undefined) {
|
|
81
|
+
context.header = toHeaderComment(ir.header);
|
|
82
|
+
}
|
|
83
|
+
const files = [];
|
|
84
|
+
for (const name of names) {
|
|
85
|
+
const generator = registry.get(name);
|
|
86
|
+
if (!generator) {
|
|
87
|
+
collected.push(error('UNKNOWN_GENERATOR', `Unknown generator "${name}"`));
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
for (const file of generator.generate(ir, context)) {
|
|
91
|
+
files.push({ path: join(options.outDir, file.path), content: file.content });
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (hasErrors(collected)) {
|
|
95
|
+
return { ir, diagnostics: collected, files: [], ok: false };
|
|
96
|
+
}
|
|
97
|
+
if (options.write) {
|
|
98
|
+
for (const file of files) {
|
|
99
|
+
mkdirSync(dirname(file.path), { recursive: true });
|
|
100
|
+
writeFileSync(file.path, file.content, 'utf8');
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return { ir, diagnostics: collected, files, ok: true };
|
|
104
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/_internal/compiler/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AACnG,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { compile, generate } from './compiler.js';
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export type DiagnosticSeverity = 'error' | 'warning';
|
|
2
|
+
/**
|
|
3
|
+
* A single problem found while compiling a contract. Diagnostics are collected
|
|
4
|
+
* across the pipeline (import resolution, IR build, semantic validation) rather
|
|
5
|
+
* than thrown, so the CLI can report every issue at once.
|
|
6
|
+
*/
|
|
7
|
+
export interface Diagnostic {
|
|
8
|
+
severity: DiagnosticSeverity;
|
|
9
|
+
/** Stable machine-readable identifier, e.g. `UNRESOLVED_TYPE`. */
|
|
10
|
+
code: string;
|
|
11
|
+
message: string;
|
|
12
|
+
/** Absolute path of the source file the problem originates from. */
|
|
13
|
+
file?: string;
|
|
14
|
+
/** Logical location inside the contract, e.g. `models.User.fields.id`. */
|
|
15
|
+
path?: string;
|
|
16
|
+
}
|
|
17
|
+
export declare const error: (code: string, message: string, extra?: Partial<Diagnostic>) => Diagnostic;
|
|
18
|
+
export declare const warning: (code: string, message: string, extra?: Partial<Diagnostic>) => Diagnostic;
|
|
19
|
+
export declare const hasErrors: (diagnostics: Diagnostic[]) => boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Thrown for unrecoverable problems (e.g. a YAML syntax error) where producing
|
|
22
|
+
* a partial result is not meaningful. Recoverable, semantic problems are
|
|
23
|
+
* reported as {@link Diagnostic}s instead.
|
|
24
|
+
*/
|
|
25
|
+
export declare class ContractError extends Error {
|
|
26
|
+
readonly diagnostics: Diagnostic[];
|
|
27
|
+
constructor(message: string, diagnostics?: Diagnostic[]);
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=diagnostics.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diagnostics.d.ts","sourceRoot":"","sources":["../../../src/lib/_internal/diagnostics.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,kBAAkB,GAAG,OAAO,GAAG,SAAS,CAAA;AAEpD;;;;GAIG;AACH,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,kBAAkB,CAAA;IAC5B,kEAAkE;IAClE,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,oEAAoE;IACpE,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,0EAA0E;IAC1E,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAED,eAAO,MAAM,KAAK,GAAI,MAAM,MAAM,EAAE,SAAS,MAAM,EAAE,QAAO,OAAO,CAAC,UAAU,CAAM,KAAG,UAKrF,CAAA;AAEF,eAAO,MAAM,OAAO,GAAI,MAAM,MAAM,EAAE,SAAS,MAAM,EAAE,QAAO,OAAO,CAAC,UAAU,CAAM,KAAG,UAKvF,CAAA;AAEF,eAAO,MAAM,SAAS,GAAI,aAAa,UAAU,EAAE,KAAG,OACW,CAAA;AAEjE;;;;GAIG;AACH,qBAAa,aAAc,SAAQ,KAAK;IACtC,QAAQ,CAAC,WAAW,EAAE,UAAU,EAAE,CAAA;gBAEtB,OAAO,EAAE,MAAM,EAAE,WAAW,GAAE,UAAU,EAAO;CAK5D"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export const error = (code, message, extra = {}) => ({
|
|
2
|
+
severity: 'error',
|
|
3
|
+
code,
|
|
4
|
+
message,
|
|
5
|
+
...extra,
|
|
6
|
+
});
|
|
7
|
+
export const warning = (code, message, extra = {}) => ({
|
|
8
|
+
severity: 'warning',
|
|
9
|
+
code,
|
|
10
|
+
message,
|
|
11
|
+
...extra,
|
|
12
|
+
});
|
|
13
|
+
export const hasErrors = (diagnostics) => diagnostics.some(diagnostic => diagnostic.severity === 'error');
|
|
14
|
+
/**
|
|
15
|
+
* Thrown for unrecoverable problems (e.g. a YAML syntax error) where producing
|
|
16
|
+
* a partial result is not meaningful. Recoverable, semantic problems are
|
|
17
|
+
* reported as {@link Diagnostic}s instead.
|
|
18
|
+
*/
|
|
19
|
+
export class ContractError extends Error {
|
|
20
|
+
diagnostics;
|
|
21
|
+
constructor(message, diagnostics = []) {
|
|
22
|
+
super(message);
|
|
23
|
+
this.name = 'ContractError';
|
|
24
|
+
this.diagnostics = diagnostics;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Diagnostic } from './diagnostics.js';
|
|
2
|
+
export declare const formatDiagnostic: (diagnostic: Diagnostic) => string;
|
|
3
|
+
export declare const formatDiagnostics: (diagnostics: Diagnostic[]) => string;
|
|
4
|
+
export declare const countBySeverity: (diagnostics: Diagnostic[]) => {
|
|
5
|
+
errors: number;
|
|
6
|
+
warnings: number;
|
|
7
|
+
};
|
|
8
|
+
//# sourceMappingURL=format-diagnostics.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format-diagnostics.d.ts","sourceRoot":"","sources":["../../../src/lib/_internal/format-diagnostics.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAE7C,eAAO,MAAM,gBAAgB,GAAI,YAAY,UAAU,KAAG,MAIzD,CAAA;AAED,eAAO,MAAM,iBAAiB,GAAI,aAAa,UAAU,EAAE,KAAG,MACU,CAAA;AAExE,eAAO,MAAM,eAAe,GAAI,aAAa,UAAU,EAAE,KAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAG5F,CAAA"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export const formatDiagnostic = (diagnostic) => {
|
|
2
|
+
const location = [diagnostic.file, diagnostic.path].filter(Boolean).join(' ');
|
|
3
|
+
const suffix = location ? ` (${location})` : '';
|
|
4
|
+
return `${diagnostic.severity} [${diagnostic.code}] ${diagnostic.message}${suffix}`;
|
|
5
|
+
};
|
|
6
|
+
export const formatDiagnostics = (diagnostics) => diagnostics.map(diagnostic => formatDiagnostic(diagnostic)).join('\n');
|
|
7
|
+
export const countBySeverity = (diagnostics) => ({
|
|
8
|
+
errors: diagnostics.filter(diagnostic => diagnostic.severity === 'error').length,
|
|
9
|
+
warnings: diagnostics.filter(diagnostic => diagnostic.severity === 'warning').length,
|
|
10
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Generator } from '../generator.js';
|
|
2
|
+
/**
|
|
3
|
+
* Generates class-validator DTO classes for API endpoint requests, mirroring the
|
|
4
|
+
* hand-written NestJS DTOs (decorators derived from field type +
|
|
5
|
+
* constraints). Complements the Zod request schemas for a NestJS ValidationPipe.
|
|
6
|
+
*/
|
|
7
|
+
export declare const createApiDtoGenerator: () => Generator;
|
|
8
|
+
//# sourceMappingURL=api-dto-generator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-dto-generator.d.ts","sourceRoot":"","sources":["../../../../../src/lib/_internal/generators/api/api-dto-generator.ts"],"names":[],"mappings":"AAGA,OAAO,EAAiB,SAAS,EAAoB,MAAM,iBAAiB,CAAA;AAiG5E;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,QAAO,SAgBvC,CAAA"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { findModel } from '../../ir/ir.js';
|
|
2
|
+
import { pascalCase, singleQuote } from '../support/naming.js';
|
|
3
|
+
import { isRelation, relationFkName } from '../support/relations.js';
|
|
4
|
+
import { headerBlocks } from '../support/header.js';
|
|
5
|
+
const SCALAR_TS = {
|
|
6
|
+
string: 'string',
|
|
7
|
+
int: 'number',
|
|
8
|
+
int64: 'number',
|
|
9
|
+
float: 'number',
|
|
10
|
+
boolean: 'boolean',
|
|
11
|
+
timestamp: 'string',
|
|
12
|
+
date: 'string',
|
|
13
|
+
json: 'unknown',
|
|
14
|
+
id: 'string',
|
|
15
|
+
};
|
|
16
|
+
const payloadFields = (ir, payload) => payload.model ? (findModel(ir, payload.model)?.fields ?? []) : payload.fields;
|
|
17
|
+
const propType = (ir, field) => {
|
|
18
|
+
let base;
|
|
19
|
+
if (isRelation(field)) {
|
|
20
|
+
base = 'string';
|
|
21
|
+
}
|
|
22
|
+
else if (field.type.kind === 'scalar') {
|
|
23
|
+
base = SCALAR_TS[field.type.name];
|
|
24
|
+
}
|
|
25
|
+
else if (field.type.kind === 'enum') {
|
|
26
|
+
const enumName = field.type.name;
|
|
27
|
+
const irEnum = ir.enums.find(e => e.name === enumName);
|
|
28
|
+
base = irEnum ? irEnum.values.map(singleQuote).join(' | ') : 'string';
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
base = 'unknown';
|
|
32
|
+
}
|
|
33
|
+
return field.list ? `${base}[]` : base;
|
|
34
|
+
};
|
|
35
|
+
/** Emit class-validator decorators for a field, tracking which need importing. */
|
|
36
|
+
const decoratorsFor = (ir, field, used) => {
|
|
37
|
+
const c = field.constraints;
|
|
38
|
+
const each = field.list ? '{ each: true }' : '';
|
|
39
|
+
const add = (name, args = '') => {
|
|
40
|
+
used.add(name);
|
|
41
|
+
return ` @${name}(${args})`;
|
|
42
|
+
};
|
|
43
|
+
const out = [];
|
|
44
|
+
if (field.optional)
|
|
45
|
+
out.push(add('IsOptional'));
|
|
46
|
+
if (field.list)
|
|
47
|
+
out.push(add('IsArray'));
|
|
48
|
+
if (isRelation(field) || (field.type.kind === 'scalar' && (field.type.name === 'string' || field.type.name === 'id'))) {
|
|
49
|
+
out.push(add('IsString', each));
|
|
50
|
+
}
|
|
51
|
+
else if (field.type.kind === 'scalar' && (field.type.name === 'int' || field.type.name === 'int64')) {
|
|
52
|
+
out.push(add('IsInt', each));
|
|
53
|
+
}
|
|
54
|
+
else if (field.type.kind === 'scalar' && field.type.name === 'float') {
|
|
55
|
+
out.push(add('IsNumber', each ? `{}, ${each}` : ''));
|
|
56
|
+
}
|
|
57
|
+
else if (field.type.kind === 'scalar' && field.type.name === 'boolean') {
|
|
58
|
+
out.push(add('IsBoolean', each));
|
|
59
|
+
}
|
|
60
|
+
else if (field.type.kind === 'scalar' && (field.type.name === 'timestamp' || field.type.name === 'date')) {
|
|
61
|
+
out.push(add('IsDateString', each));
|
|
62
|
+
}
|
|
63
|
+
else if (field.type.kind === 'enum') {
|
|
64
|
+
const enumName = field.type.name;
|
|
65
|
+
const irEnum = ir.enums.find(e => e.name === enumName);
|
|
66
|
+
const values = irEnum ? `[${irEnum.values.map(singleQuote).join(', ')}]` : '[]';
|
|
67
|
+
out.push(add('IsIn', each ? `${values}, ${each}` : values));
|
|
68
|
+
}
|
|
69
|
+
if (field.list) {
|
|
70
|
+
if (c.nonempty)
|
|
71
|
+
out.push(add('ArrayNotEmpty'));
|
|
72
|
+
if (c.minLength !== undefined)
|
|
73
|
+
out.push(add('ArrayMinSize', String(c.minLength)));
|
|
74
|
+
if (c.maxLength !== undefined)
|
|
75
|
+
out.push(add('ArrayMaxSize', String(c.maxLength)));
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
if (c.nonempty)
|
|
79
|
+
out.push(add('IsNotEmpty'));
|
|
80
|
+
if (c.minLength !== undefined)
|
|
81
|
+
out.push(add('MinLength', String(c.minLength)));
|
|
82
|
+
if (c.maxLength !== undefined)
|
|
83
|
+
out.push(add('MaxLength', String(c.maxLength)));
|
|
84
|
+
if (c.min !== undefined)
|
|
85
|
+
out.push(add('Min', String(c.min)));
|
|
86
|
+
if (c.max !== undefined)
|
|
87
|
+
out.push(add('Max', String(c.max)));
|
|
88
|
+
if (c.email)
|
|
89
|
+
out.push(add('IsEmail'));
|
|
90
|
+
if (c.url)
|
|
91
|
+
out.push(add('IsUrl'));
|
|
92
|
+
if (c.pattern)
|
|
93
|
+
out.push(add('Matches', `/${c.pattern}/`));
|
|
94
|
+
}
|
|
95
|
+
return out;
|
|
96
|
+
};
|
|
97
|
+
const renderDto = (ir, api, used) => {
|
|
98
|
+
const fields = payloadFields(ir, api.request);
|
|
99
|
+
const className = `${pascalCase(api.name)}Dto`;
|
|
100
|
+
if (api.request.isVoid || fields.length === 0) {
|
|
101
|
+
return `export class ${className} {}`;
|
|
102
|
+
}
|
|
103
|
+
const members = fields
|
|
104
|
+
.map(field => {
|
|
105
|
+
const name = isRelation(field) ? relationFkName(field) : field.name;
|
|
106
|
+
const decorators = decoratorsFor(ir, field, used).join('\n');
|
|
107
|
+
const mark = field.optional ? '?' : '!';
|
|
108
|
+
return `${decorators}\n ${name}${mark}: ${propType(ir, field)}`;
|
|
109
|
+
})
|
|
110
|
+
.join('\n\n');
|
|
111
|
+
return `export class ${className} {\n${members}\n}`;
|
|
112
|
+
};
|
|
113
|
+
/**
|
|
114
|
+
* Generates class-validator DTO classes for API endpoint requests, mirroring the
|
|
115
|
+
* hand-written NestJS DTOs (decorators derived from field type +
|
|
116
|
+
* constraints). Complements the Zod request schemas for a NestJS ValidationPipe.
|
|
117
|
+
*/
|
|
118
|
+
export const createApiDtoGenerator = () => ({
|
|
119
|
+
name: 'api-dto',
|
|
120
|
+
description: 'class-validator DTO classes for API requests',
|
|
121
|
+
generate(ir, context) {
|
|
122
|
+
if (ir.apis.length === 0) {
|
|
123
|
+
return [];
|
|
124
|
+
}
|
|
125
|
+
const used = new Set();
|
|
126
|
+
const dtoBlocks = ir.apis.map(api => renderDto(ir, api, used));
|
|
127
|
+
const blocks = [...headerBlocks(context)];
|
|
128
|
+
if (used.size > 0) {
|
|
129
|
+
blocks.push(`import { ${[...used].sort().join(', ')} } from 'class-validator'`);
|
|
130
|
+
}
|
|
131
|
+
blocks.push(...dtoBlocks);
|
|
132
|
+
return [{ path: 'api-dtos.ts', content: `${blocks.join('\n\n')}\n` }];
|
|
133
|
+
},
|
|
134
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Generator } from '../generator.js';
|
|
2
|
+
/**
|
|
3
|
+
* Generates request/response TypeScript types for each API endpoint. A payload
|
|
4
|
+
* can reference an existing model or declare inline fields; a void response maps
|
|
5
|
+
* to `void`. This mirrors the shared producer/consumer contract pattern.
|
|
6
|
+
*/
|
|
7
|
+
export declare const createApiTypesGenerator: () => Generator;
|
|
8
|
+
//# sourceMappingURL=api-types-generator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-types-generator.d.ts","sourceRoot":"","sources":["../../../../../src/lib/_internal/generators/api/api-types-generator.ts"],"names":[],"mappings":"AAGA,OAAO,EAAiB,SAAS,EAAoB,MAAM,iBAAiB,CAAA;AA6E5E;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,QAAO,SAwBzC,CAAA"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { pascalCase } from '../support/naming.js';
|
|
2
|
+
import { isRelation, relationFkName, relationFkType } from '../support/relations.js';
|
|
3
|
+
import { headerBlocks } from '../support/header.js';
|
|
4
|
+
const SCALAR_TS = {
|
|
5
|
+
string: 'string',
|
|
6
|
+
int: 'number',
|
|
7
|
+
int64: 'number',
|
|
8
|
+
float: 'number',
|
|
9
|
+
boolean: 'boolean',
|
|
10
|
+
timestamp: 'string',
|
|
11
|
+
date: 'string',
|
|
12
|
+
json: 'Json',
|
|
13
|
+
id: 'string',
|
|
14
|
+
};
|
|
15
|
+
const renderFieldTsType = (ir, field) => {
|
|
16
|
+
let base;
|
|
17
|
+
if (isRelation(field)) {
|
|
18
|
+
base = SCALAR_TS[relationFkType(ir, field)];
|
|
19
|
+
}
|
|
20
|
+
else if (field.type.kind === 'scalar') {
|
|
21
|
+
base = SCALAR_TS[field.type.name];
|
|
22
|
+
}
|
|
23
|
+
else if (field.type.kind === 'enum' || field.type.kind === 'model') {
|
|
24
|
+
base = field.type.name;
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
base = 'unknown';
|
|
28
|
+
}
|
|
29
|
+
return field.list ? `${base}[]` : base;
|
|
30
|
+
};
|
|
31
|
+
const renderField = (ir, field) => {
|
|
32
|
+
const name = isRelation(field) ? relationFkName(field) : field.name;
|
|
33
|
+
const optional = field.optional ? '?' : '';
|
|
34
|
+
const doc = field.description ? ` /** ${field.description} */\n` : '';
|
|
35
|
+
return `${doc} ${name}${optional}: ${renderFieldTsType(ir, field)}`;
|
|
36
|
+
};
|
|
37
|
+
const renderPayloadType = (ir, payload, typeName) => {
|
|
38
|
+
if (payload.isVoid) {
|
|
39
|
+
return `export type ${typeName} = void`;
|
|
40
|
+
}
|
|
41
|
+
if (payload.model) {
|
|
42
|
+
return `export type ${typeName} = ${payload.model}`;
|
|
43
|
+
}
|
|
44
|
+
if (payload.fields.length === 0) {
|
|
45
|
+
return `export type ${typeName} = Record<string, never>`;
|
|
46
|
+
}
|
|
47
|
+
const fields = payload.fields.map(field => renderField(ir, field)).join('\n');
|
|
48
|
+
return `export interface ${typeName} {\n${fields}\n}`;
|
|
49
|
+
};
|
|
50
|
+
const collectImports = (ir) => {
|
|
51
|
+
const names = new Set();
|
|
52
|
+
const add = (payload) => {
|
|
53
|
+
if (payload.model)
|
|
54
|
+
names.add(payload.model);
|
|
55
|
+
for (const field of payload.fields) {
|
|
56
|
+
if (field.type.kind === 'enum' || (field.type.kind === 'model' && !isRelation(field))) {
|
|
57
|
+
names.add(field.type.name);
|
|
58
|
+
}
|
|
59
|
+
if (field.type.kind === 'scalar' && field.type.name === 'json') {
|
|
60
|
+
names.add('Json');
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
for (const api of ir.apis) {
|
|
65
|
+
add(api.request);
|
|
66
|
+
add(api.response);
|
|
67
|
+
}
|
|
68
|
+
return [...names].sort();
|
|
69
|
+
};
|
|
70
|
+
const apiComment = (api) => {
|
|
71
|
+
const bits = [`kind: ${api.kind}`];
|
|
72
|
+
if (api.method)
|
|
73
|
+
bits.push(`method: ${api.method}`);
|
|
74
|
+
if (api.description)
|
|
75
|
+
bits.unshift(api.description);
|
|
76
|
+
return `// ${api.name} — ${bits.join(' | ')}`;
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* Generates request/response TypeScript types for each API endpoint. A payload
|
|
80
|
+
* can reference an existing model or declare inline fields; a void response maps
|
|
81
|
+
* to `void`. This mirrors the shared producer/consumer contract pattern.
|
|
82
|
+
*/
|
|
83
|
+
export const createApiTypesGenerator = () => ({
|
|
84
|
+
name: 'api-types',
|
|
85
|
+
description: 'API request/response TypeScript types',
|
|
86
|
+
generate(ir, context) {
|
|
87
|
+
if (ir.apis.length === 0) {
|
|
88
|
+
return [];
|
|
89
|
+
}
|
|
90
|
+
const blocks = [...headerBlocks(context)];
|
|
91
|
+
const imports = collectImports(ir);
|
|
92
|
+
if (imports.length > 0) {
|
|
93
|
+
blocks.push(`import type { ${imports.join(', ')} } from './types.js'`);
|
|
94
|
+
}
|
|
95
|
+
for (const api of ir.apis) {
|
|
96
|
+
const pascal = pascalCase(api.name);
|
|
97
|
+
blocks.push([
|
|
98
|
+
apiComment(api),
|
|
99
|
+
renderPayloadType(ir, api.request, `${pascal}Request`),
|
|
100
|
+
renderPayloadType(ir, api.response, `${pascal}Response`),
|
|
101
|
+
].join('\n'));
|
|
102
|
+
}
|
|
103
|
+
return [{ path: 'api-types.ts', content: `${blocks.join('\n\n')}\n` }];
|
|
104
|
+
},
|
|
105
|
+
});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Generator } from '../generator.js';
|
|
2
|
+
/**
|
|
3
|
+
* Generates Zod schemas that validate each endpoint's *request*. Model-backed
|
|
4
|
+
* requests are expanded to the model's fields so the schema is self-contained.
|
|
5
|
+
*/
|
|
6
|
+
export declare const createApiValidationGenerator: () => Generator;
|
|
7
|
+
//# sourceMappingURL=api-validation-generator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-validation-generator.d.ts","sourceRoot":"","sources":["../../../../../src/lib/_internal/generators/api/api-validation-generator.ts"],"names":[],"mappings":"AAIA,OAAO,EAAiB,SAAS,EAAoB,MAAM,iBAAiB,CAAA;AAqD5E;;;GAGG;AACH,eAAO,MAAM,4BAA4B,QAAO,SAa9C,CAAA"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { findModel } from '../../ir/ir.js';
|
|
2
|
+
import { pascalCase, singleQuote } from '../support/naming.js';
|
|
3
|
+
import { isRelation, relationFkName, relationFkType } from '../support/relations.js';
|
|
4
|
+
import { zodConstraints } from '../support/constraints.js';
|
|
5
|
+
import { headerBlocks } from '../support/header.js';
|
|
6
|
+
const SCALAR_ZOD = {
|
|
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
|
+
/** Resolve a payload to its concrete field list (inline fields or a referenced model). */
|
|
18
|
+
const payloadFields = (ir, payload) => {
|
|
19
|
+
if (payload.model) {
|
|
20
|
+
return findModel(ir, payload.model)?.fields ?? [];
|
|
21
|
+
}
|
|
22
|
+
return payload.fields;
|
|
23
|
+
};
|
|
24
|
+
const fieldSchema = (ir, field) => {
|
|
25
|
+
const name = isRelation(field) ? relationFkName(field) : field.name;
|
|
26
|
+
let schema;
|
|
27
|
+
if (isRelation(field)) {
|
|
28
|
+
schema = SCALAR_ZOD[relationFkType(ir, field)];
|
|
29
|
+
}
|
|
30
|
+
else if (field.type.kind === 'scalar') {
|
|
31
|
+
schema = SCALAR_ZOD[field.type.name];
|
|
32
|
+
}
|
|
33
|
+
else if (field.type.kind === 'enum') {
|
|
34
|
+
const enumName = field.type.name;
|
|
35
|
+
const irEnum = ir.enums.find(e => e.name === enumName);
|
|
36
|
+
schema = irEnum ? `z.enum([${irEnum.values.map(singleQuote).join(', ')}])` : 'z.string()';
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
schema = 'z.unknown()';
|
|
40
|
+
}
|
|
41
|
+
const cons = zodConstraints(field);
|
|
42
|
+
schema = field.list ? `z.array(${schema})${cons}` : `${schema}${cons}`;
|
|
43
|
+
if (field.optional)
|
|
44
|
+
schema = `${schema}.optional()`;
|
|
45
|
+
return ` ${name}: ${schema},`;
|
|
46
|
+
};
|
|
47
|
+
const renderRequestSchema = (ir, api) => {
|
|
48
|
+
const name = `${pascalCase(api.name)}RequestSchema`;
|
|
49
|
+
const fields = payloadFields(ir, api.request);
|
|
50
|
+
if (api.request.isVoid || fields.length === 0) {
|
|
51
|
+
return `export const ${name} = z.object({})`;
|
|
52
|
+
}
|
|
53
|
+
const body = fields.map(field => fieldSchema(ir, field)).join('\n');
|
|
54
|
+
return `export const ${name} = z.object({\n${body}\n})`;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Generates Zod schemas that validate each endpoint's *request*. Model-backed
|
|
58
|
+
* requests are expanded to the model's fields so the schema is self-contained.
|
|
59
|
+
*/
|
|
60
|
+
export const createApiValidationGenerator = () => ({
|
|
61
|
+
name: 'api-validation',
|
|
62
|
+
description: 'API request-validation Zod schemas',
|
|
63
|
+
generate(ir, context) {
|
|
64
|
+
if (ir.apis.length === 0) {
|
|
65
|
+
return [];
|
|
66
|
+
}
|
|
67
|
+
const blocks = [...headerBlocks(context), "import { z } from 'zod'"];
|
|
68
|
+
for (const api of ir.apis) {
|
|
69
|
+
blocks.push(renderRequestSchema(ir, api));
|
|
70
|
+
}
|
|
71
|
+
return [{ path: 'api-validation.ts', content: `${blocks.join('\n\n')}\n` }];
|
|
72
|
+
},
|
|
73
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Generator } from '../generator.js';
|
|
2
|
+
/**
|
|
3
|
+
* Generates Cloud Task / Pub/Sub payload contracts: a generic envelope type per
|
|
4
|
+
* declared envelope (`RetryTaskPayload<T> = T & { … }`), plus per-task
|
|
5
|
+
* `<Name>TaskData` + `<Name>TaskPayload` and delivery constants (max attempts,
|
|
6
|
+
* timeout, topic) — the shared payload contracts an app otherwise hand-writes.
|
|
7
|
+
*/
|
|
8
|
+
export declare const createTaskPayloadGenerator: () => Generator;
|
|
9
|
+
//# sourceMappingURL=task-payload-generator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"task-payload-generator.d.ts","sourceRoot":"","sources":["../../../../../src/lib/_internal/generators/api/task-payload-generator.ts"],"names":[],"mappings":"AAGA,OAAO,EAAiB,SAAS,EAAoB,MAAM,iBAAiB,CAAA;AAuE5E;;;;;GAKG;AACH,eAAO,MAAM,0BAA0B,QAAO,SAqB5C,CAAA"}
|