@metaobjectsdev/codegen-ts 0.8.1-rc.1 → 0.9.0-rc.1
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/dist/column-mapper.d.ts.map +1 -1
- package/dist/column-mapper.js +123 -46
- package/dist/column-mapper.js.map +1 -1
- package/dist/generators/callable-file.d.ts +8 -0
- package/dist/generators/callable-file.d.ts.map +1 -0
- package/dist/generators/callable-file.js +32 -0
- package/dist/generators/callable-file.js.map +1 -0
- package/dist/generators/docs-data-builder.d.ts.map +1 -1
- package/dist/generators/docs-data-builder.js +5 -2
- package/dist/generators/docs-data-builder.js.map +1 -1
- package/dist/generators/extractor-file.d.ts +9 -0
- package/dist/generators/extractor-file.d.ts.map +1 -0
- package/dist/generators/extractor-file.js +45 -0
- package/dist/generators/extractor-file.js.map +1 -0
- package/dist/generators/index.d.ts +3 -0
- package/dist/generators/index.d.ts.map +1 -1
- package/dist/generators/index.js +3 -0
- package/dist/generators/index.js.map +1 -1
- package/dist/generators/render-helper-file.d.ts +9 -0
- package/dist/generators/render-helper-file.d.ts.map +1 -0
- package/dist/generators/render-helper-file.js +58 -0
- package/dist/generators/render-helper-file.js.map +1 -0
- package/dist/payload-codegen.d.ts.map +1 -1
- package/dist/payload-codegen.js +42 -8
- package/dist/payload-codegen.js.map +1 -1
- package/dist/projection/extract-view-spec.d.ts.map +1 -1
- package/dist/projection/extract-view-spec.js +11 -3
- package/dist/projection/extract-view-spec.js.map +1 -1
- package/dist/render-engine/framework-provider.d.ts +6 -5
- package/dist/render-engine/framework-provider.d.ts.map +1 -1
- package/dist/render-engine/framework-provider.js +53 -11
- package/dist/render-engine/framework-provider.js.map +1 -1
- package/dist/templates/callable-file.d.ts +8 -0
- package/dist/templates/callable-file.d.ts.map +1 -0
- package/dist/templates/callable-file.js +98 -0
- package/dist/templates/callable-file.js.map +1 -0
- package/dist/templates/extract-delegate-emitter.d.ts +42 -0
- package/dist/templates/extract-delegate-emitter.d.ts.map +1 -0
- package/dist/templates/extract-delegate-emitter.js +339 -0
- package/dist/templates/extract-delegate-emitter.js.map +1 -0
- package/dist/templates/{recover-schema-emitter.d.ts → extract-schema-emitter.d.ts} +2 -2
- package/dist/templates/extract-schema-emitter.d.ts.map +1 -0
- package/dist/templates/{recover-schema-emitter.js → extract-schema-emitter.js} +37 -20
- package/dist/templates/extract-schema-emitter.js.map +1 -0
- package/dist/templates/extractor.d.ts +9 -0
- package/dist/templates/extractor.d.ts.map +1 -0
- package/dist/templates/extractor.js +296 -0
- package/dist/templates/extractor.js.map +1 -0
- package/dist/templates/field-meta.d.ts.map +1 -1
- package/dist/templates/field-meta.js +2 -1
- package/dist/templates/field-meta.js.map +1 -1
- package/dist/templates/filter-type.d.ts.map +1 -1
- package/dist/templates/filter-type.js +8 -5
- package/dist/templates/filter-type.js.map +1 -1
- package/dist/templates/fr010-field-mapping.d.ts +22 -6
- package/dist/templates/fr010-field-mapping.d.ts.map +1 -1
- package/dist/templates/fr010-field-mapping.js +66 -21
- package/dist/templates/fr010-field-mapping.js.map +1 -1
- package/dist/templates/inferred-types.d.ts +15 -1
- package/dist/templates/inferred-types.d.ts.map +1 -1
- package/dist/templates/inferred-types.js +30 -17
- package/dist/templates/inferred-types.js.map +1 -1
- package/dist/templates/output-parser.d.ts.map +1 -1
- package/dist/templates/output-parser.js +98 -34
- package/dist/templates/output-parser.js.map +1 -1
- package/dist/templates/output-prompt.js +2 -2
- package/dist/templates/render-helper.d.ts +14 -0
- package/dist/templates/render-helper.d.ts.map +1 -0
- package/dist/templates/render-helper.js +180 -0
- package/dist/templates/render-helper.js.map +1 -0
- package/dist/templates/zod-validators.d.ts.map +1 -1
- package/dist/templates/zod-validators.js +59 -3
- package/dist/templates/zod-validators.js.map +1 -1
- package/package.json +10 -4
- package/src/column-mapper.ts +128 -45
- package/src/generators/callable-file.ts +44 -0
- package/src/generators/docs-data-builder.ts +5 -1
- package/src/generators/extractor-file.ts +57 -0
- package/src/generators/index.ts +3 -0
- package/src/generators/render-helper-file.ts +74 -0
- package/src/payload-codegen.ts +52 -7
- package/src/projection/extract-view-spec.ts +11 -3
- package/src/render-engine/framework-provider.ts +53 -16
- package/src/templates/callable-file.ts +122 -0
- package/src/templates/extract-delegate-emitter.ts +370 -0
- package/src/templates/{recover-schema-emitter.ts → extract-schema-emitter.ts} +39 -19
- package/src/templates/extractor.ts +333 -0
- package/src/templates/field-meta.ts +2 -0
- package/src/templates/filter-type.ts +7 -5
- package/src/templates/fr010-field-mapping.ts +71 -18
- package/src/templates/inferred-types.ts +32 -18
- package/src/templates/output-parser.ts +108 -35
- package/src/templates/output-prompt.ts +2 -2
- package/src/templates/render-helper.ts +244 -0
- package/src/templates/zod-validators.ts +51 -4
- package/dist/templates/recover-schema-emitter.d.ts.map +0 -1
- package/dist/templates/recover-schema-emitter.js.map +0 -1
|
@@ -21,10 +21,17 @@ import {
|
|
|
21
21
|
} from "@metaobjectsdev/metadata";
|
|
22
22
|
import {
|
|
23
23
|
schemaLiteral,
|
|
24
|
-
mirrorInterface,
|
|
25
24
|
mirrorInitializer,
|
|
26
|
-
} from "./
|
|
27
|
-
import {
|
|
25
|
+
} from "./extract-schema-emitter.js";
|
|
26
|
+
import { extractMapHelpersUsed } from "./fr010-field-mapping.js";
|
|
27
|
+
import {
|
|
28
|
+
nestedMirrorInterfaces,
|
|
29
|
+
nestedMappers,
|
|
30
|
+
rootMapperName,
|
|
31
|
+
delegateHelpers,
|
|
32
|
+
usedHelpers,
|
|
33
|
+
hasNested,
|
|
34
|
+
} from "./extract-delegate-emitter.js";
|
|
28
35
|
|
|
29
36
|
const SCALAR_ZOD: Record<string, string> = {
|
|
30
37
|
string: "z.string()",
|
|
@@ -110,12 +117,12 @@ export function renderOutputParser(root: MetaData, templateName: string): string
|
|
|
110
117
|
const parseName = `parse${templateName}`;
|
|
111
118
|
const safeParseName = `safeParse${templateName}`;
|
|
112
119
|
|
|
113
|
-
// FR-010: emit the tolerant
|
|
120
|
+
// FR-010: emit the tolerant extract() API alongside the strict Zod parser when the
|
|
114
121
|
// template targets json/xml. The @payloadRef already resolved to a value-object above,
|
|
115
|
-
// so a
|
|
122
|
+
// so a ExtractSchema can always be baked. text-format outputs get no extract.
|
|
116
123
|
const format = (tmpl.ownAttr(TEMPLATE_ATTR_FORMAT) as string | undefined) ?? "text";
|
|
117
124
|
const lc = format.toLowerCase();
|
|
118
|
-
const
|
|
125
|
+
const emitExtractLenient = lc === "json" || lc === "xml";
|
|
119
126
|
|
|
120
127
|
const strictBody = `const ${schemaName} = ${schema};
|
|
121
128
|
|
|
@@ -153,66 +160,132 @@ export function ${safeParseName}(
|
|
|
153
160
|
}
|
|
154
161
|
`;
|
|
155
162
|
|
|
156
|
-
if (!
|
|
163
|
+
if (!emitExtractLenient) {
|
|
157
164
|
return `import { z } from "zod";\n\n${strictBody}`;
|
|
158
165
|
}
|
|
159
166
|
|
|
160
|
-
// ---- FR-010 tolerant
|
|
161
|
-
const
|
|
162
|
-
const
|
|
163
|
-
const
|
|
164
|
-
const
|
|
167
|
+
// ---- FR-010 tolerant extract block (json/xml only) ----
|
|
168
|
+
const extractedName = `${templateName}Extracted`;
|
|
169
|
+
const extractLenientFnName = `extractLenient${templateName}`;
|
|
170
|
+
const tryExtractLenientName = `tryExtractLenient${templateName}`;
|
|
171
|
+
const extractLenientWithName = `extractLenient${templateName}WithLoader`;
|
|
172
|
+
const schemaConstName = `${templateName}ExtractSchema`;
|
|
173
|
+
const payloadFqnConst = `${templateName.toUpperCase()}_PAYLOAD_NAME`;
|
|
174
|
+
const formatEnum = format.toLowerCase() === "xml" ? "Format.XML" : "Format.JSON";
|
|
165
175
|
const schemaLit = schemaLiteral(vo, format, payloadRef);
|
|
166
|
-
const mirrorDecl = mirrorInterface(vo, recoveredName);
|
|
167
176
|
const initializer = mirrorInitializer(vo);
|
|
168
|
-
const mapHelpers =
|
|
177
|
+
const mapHelpers = extractMapHelpersUsed(vo);
|
|
178
|
+
|
|
179
|
+
// The nullable mirror is shared by BOTH extract paths. Use the nested-aware emitter so the
|
|
180
|
+
// payload mirror's nested-object / array-of-object components are typed (not `unknown`), and
|
|
181
|
+
// so a mirror interface is emitted for every reachable nested value-object. The payload mirror
|
|
182
|
+
// keeps the canonical `<Template>Extracted` name (instead of `<PayloadVO>Extracted`) so the
|
|
183
|
+
// existing self-contained extract<Name>() initializer continues to satisfy it.
|
|
184
|
+
const mirrorDecls = nestedMirrorInterfaces(vo, root, extractedName);
|
|
185
|
+
const payloadHasNested = hasNested(vo, root);
|
|
169
186
|
|
|
170
|
-
// Render-package imports the
|
|
187
|
+
// Render-package imports the extract block needs. Only pull in the names the emitted
|
|
171
188
|
// source actually references, so the file has no unused imports (tsc noUnusedLocals-safe).
|
|
172
|
-
const renderImports = ["
|
|
189
|
+
const renderImports = ["extract", "extractSchema", "Format"];
|
|
173
190
|
if (schemaLit.includes("scalar(")) renderImports.push("scalar");
|
|
174
191
|
if (schemaLit.includes("enumField(")) renderImports.push("enumField");
|
|
175
192
|
if (schemaLit.includes("FieldKind.")) renderImports.push("FieldKind");
|
|
176
|
-
renderImports.push("type
|
|
193
|
+
renderImports.push("type ExtractSchema", "type ExtractOptions", "type ExtractionResult");
|
|
177
194
|
renderImports.push(...mapHelpers);
|
|
178
195
|
|
|
179
|
-
const
|
|
180
|
-
const ${schemaConstName}:
|
|
196
|
+
const selfContained = `/** Baked extract descriptor for the ${templateName} output. */
|
|
197
|
+
const ${schemaConstName}: ExtractSchema = ${schemaLit};
|
|
181
198
|
|
|
182
|
-
${
|
|
199
|
+
${mirrorDecls}
|
|
183
200
|
|
|
184
201
|
/**
|
|
185
|
-
*
|
|
186
|
-
* nullable mirror (\`${
|
|
187
|
-
* plus the per-field
|
|
202
|
+
* Self-contained tolerant best-effort extraction of a dirty LLM response; never throws.
|
|
203
|
+
* Returns a nullable mirror (\`${extractedName}\`) with fields null where lost/malformed,
|
|
204
|
+
* plus the per-field extraction report. Does NOT populate nested-object / array-of-object
|
|
205
|
+
* components (those stay null — the historical FR-010 gap). For full nested extraction, use
|
|
206
|
+
* \`${extractLenientWithName}(root, text)\`, which delegates to the runtime extract.
|
|
188
207
|
*/
|
|
189
|
-
export function ${
|
|
208
|
+
export function ${extractLenientFnName}(
|
|
190
209
|
text: string,
|
|
191
|
-
opts?:
|
|
192
|
-
):
|
|
193
|
-
const outcome =
|
|
210
|
+
opts?: ExtractOptions,
|
|
211
|
+
): ExtractionResult<${extractedName}> {
|
|
212
|
+
const outcome = extract(text, ${schemaConstName}, opts);
|
|
194
213
|
const d = outcome.data;
|
|
195
|
-
const data: ${
|
|
214
|
+
const data: ${extractedName} = ${initializer};
|
|
196
215
|
return { data, report: outcome.report };
|
|
197
216
|
}
|
|
198
217
|
|
|
199
218
|
/**
|
|
200
|
-
*
|
|
201
|
-
* field was lost. On success, \`result\` carries the
|
|
219
|
+
* Extraction as a bool gate: \`true\` when the response was non-empty and no required
|
|
220
|
+
* field was lost. On success, \`result\` carries the extracted mirror + report.
|
|
202
221
|
*/
|
|
203
|
-
export function ${
|
|
222
|
+
export function ${tryExtractLenientName}(
|
|
204
223
|
text: string,
|
|
205
|
-
): { ok: boolean; result:
|
|
206
|
-
const result = ${
|
|
224
|
+
): { ok: boolean; result: ExtractionResult<${extractedName}> } {
|
|
225
|
+
const result = ${extractLenientFnName}(text);
|
|
207
226
|
const ok = !result.report.isEmpty() && !result.report.hasLostRequired();
|
|
208
227
|
return { ok, result };
|
|
209
228
|
}
|
|
210
229
|
`;
|
|
211
230
|
|
|
231
|
+
// ---- Runtime-delegating extract (closes the nested gap) ----
|
|
232
|
+
// Resolves this payload's MetaObject from a loaded MetaRoot by its baked simple name and
|
|
233
|
+
// delegates to extractObject() in @metaobjectsdev/runtime-ts, which assembles the FULL nested
|
|
234
|
+
// object graph reflection-free. The assembled ValueObject graph is then mapped into the typed
|
|
235
|
+
// nullable mirror graph by the generated from<VO>Extracted mappers. Codegen-wrapping-runtime
|
|
236
|
+
// (a generated DAO calling the dynamic-metadata runtime) — mirrors the Java/Kotlin pilots.
|
|
237
|
+
//
|
|
238
|
+
// The baked PAYLOAD_NAME is the resolved payload VO's SIMPLE name (root.findObject matches on
|
|
239
|
+
// the object's `name`, not its FQN). The root mapper is named for the TEMPLATE (so it returns
|
|
240
|
+
// the canonically-named `<Template>Extracted` mirror); nested mappers use their VO names.
|
|
241
|
+
const payloadName = vo.name;
|
|
242
|
+
const rootMapper = rootMapperName(templateName);
|
|
243
|
+
const delegating = `
|
|
244
|
+
/** Payload value-object name this parser extracts — resolved against a loaded MetaRoot at runtime. */
|
|
245
|
+
export const ${payloadFqnConst} = ${JSON.stringify(payloadName)};
|
|
246
|
+
|
|
247
|
+
${nestedMappers(vo, root, rootMapper, extractedName)}
|
|
248
|
+
|
|
249
|
+
${delegateHelpers(usedHelpers(vo, root))}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Runtime-delegating tolerant extraction; never throws. Unlike \`${extractLenientFnName}(text)\`, this FULLY
|
|
253
|
+
* populates nested-object and array-of-object components by delegating to the metadata-driven
|
|
254
|
+
* runtime \`extractObject\` (which assembles the whole graph reflection-free via the Phase A object
|
|
255
|
+
* model), then maps the assembled graph into the typed \`${extractedName}\` mirror.
|
|
256
|
+
*
|
|
257
|
+
* @param root a loaded MetaRoot (e.g. \`(await new MetaDataLoader().load(...)).root\`) that declares
|
|
258
|
+
* the \`${payloadRef}\` value-object.
|
|
259
|
+
*/
|
|
260
|
+
export function ${extractLenientWithName}(
|
|
261
|
+
root: MetaRoot,
|
|
262
|
+
text: string,
|
|
263
|
+
opts?: Partial<ExtractOptions> | null,
|
|
264
|
+
): ExtractionResult<${extractedName}> {
|
|
265
|
+
const mo = root.findObject(${payloadFqnConst});
|
|
266
|
+
if (mo === undefined) {
|
|
267
|
+
throw new Error(\`${extractLenientWithName}: payload "\${${payloadFqnConst}}" not found in the supplied MetaRoot\`);
|
|
268
|
+
}
|
|
269
|
+
const outcome = extractObject(mo, text, ${formatEnum}, opts);
|
|
270
|
+
return { data: ${rootMapper}(outcome.data), report: outcome.report };
|
|
271
|
+
}
|
|
272
|
+
`;
|
|
273
|
+
|
|
274
|
+
// The delegating overload needs runtime-ts (extractObject) + the MetaRoot type from metadata.
|
|
275
|
+
// It is always emitted (the gap-closing path), regardless of whether THIS payload has nested
|
|
276
|
+
// fields — a flat payload still benefits from the loader-driven path and keeps the API uniform.
|
|
277
|
+
void payloadHasNested;
|
|
278
|
+
const metadataImport = `import type { MetaRoot } from "@metaobjectsdev/metadata";\n`;
|
|
279
|
+
const runtimeImport = `import { extractObject } from "@metaobjectsdev/runtime-ts";\n`;
|
|
280
|
+
|
|
212
281
|
return (
|
|
213
282
|
`import { z } from "zod";\n` +
|
|
214
|
-
`import {\n ${renderImports.join(",\n ")},\n} from "@metaobjectsdev/render";\n
|
|
283
|
+
`import {\n ${renderImports.join(",\n ")},\n} from "@metaobjectsdev/render";\n` +
|
|
284
|
+
metadataImport +
|
|
285
|
+
runtimeImport +
|
|
286
|
+
`\n` +
|
|
215
287
|
`${strictBody}\n` +
|
|
216
|
-
`${
|
|
288
|
+
`${selfContained}\n` +
|
|
289
|
+
`${delegating}`
|
|
217
290
|
);
|
|
218
291
|
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// @payloadRef resolves to a value-object, emits a `<TemplateName>.prompt.ts` file
|
|
6
6
|
// exporting `render<TemplateName>Format(overrides?)` backed by the render engine's
|
|
7
7
|
// renderOutputFormat(). The baked OutputFormatSpec's rootName is the payload name,
|
|
8
|
-
// so the prompt fragment and the
|
|
8
|
+
// so the prompt fragment and the extract() codegen agree on the root name.
|
|
9
9
|
//
|
|
10
10
|
// Mirrors the C# OutputPromptGenerator + OutputFormatSpecEmitter (split into a
|
|
11
11
|
// generator factory + this pure renderer, matching the TS output-parser shape).
|
|
@@ -59,7 +59,7 @@ export function renderOutputPrompt(root: MetaData, templateName: string): string
|
|
|
59
59
|
throw new Error(`template "${templateName}" @payloadRef "${payloadRef}" not found in metadata root`);
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
// rootName == payload name so the prompt fragment and
|
|
62
|
+
// rootName == payload name so the prompt fragment and extract() agree.
|
|
63
63
|
const spec = specLiteral(vo, tmpl, payloadRef);
|
|
64
64
|
const specName = `${templateName}FormatSpec`;
|
|
65
65
|
const fnName = `render${templateName}Format`;
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
// server/typescript/packages/codegen-ts/src/templates/render-helper.ts
|
|
2
|
+
//
|
|
3
|
+
// Per-template.output render helper: emits a typed `render<Name>(payload, provider)`
|
|
4
|
+
// that wraps the EXISTING render() engine, AND enforces the mustache↔payload-VO
|
|
5
|
+
// drift check (the EXISTING verify() engine) at BUILD time.
|
|
6
|
+
//
|
|
7
|
+
// Two shapes, keyed off @kind:
|
|
8
|
+
// • document (default) → renders @textRef in @format → one string.
|
|
9
|
+
// • email → renders @subjectRef + @htmlBodyRef (+ optional
|
|
10
|
+
// @textBodyRef) → a structured EmailDocument.
|
|
11
|
+
//
|
|
12
|
+
// The headline is the BUILD-TIME drift gate: BEFORE emitting, every referenced
|
|
13
|
+
// mustache is resolved via the codegen-time provider and verify()'d against the
|
|
14
|
+
// payload field tree. If a referenced text is unresolvable OR carries any
|
|
15
|
+
// NON-warning verify error (e.g. `{{field}}` not on the payload VO), this THROWS
|
|
16
|
+
// and FAILS codegen — naming the template, the ref, the error code, and the
|
|
17
|
+
// offending field. This is what makes the build fail when a mustache references
|
|
18
|
+
// a field the payload VO doesn't declare.
|
|
19
|
+
//
|
|
20
|
+
// Reuse, not reimplementation:
|
|
21
|
+
// • render() — the runtime helper delegates to it (emitted call).
|
|
22
|
+
// • verify() — run here at build time for the drift gate.
|
|
23
|
+
// • derivePayloadFieldTree (replicated minimally below — codegen-ts must NOT
|
|
24
|
+
// depend on the cli package; the walk is the same as cli's payload-field-tree).
|
|
25
|
+
// • PayloadField — the verify() field-tree shape; serialized into the emitted
|
|
26
|
+
// `verify:` literal so render()'s runtime drift check runs too.
|
|
27
|
+
|
|
28
|
+
import {
|
|
29
|
+
type MetaData,
|
|
30
|
+
TYPE_OBJECT,
|
|
31
|
+
TYPE_FIELD,
|
|
32
|
+
TYPE_TEMPLATE,
|
|
33
|
+
FIELD_SUBTYPE_OBJECT,
|
|
34
|
+
FIELD_ATTR_OBJECT_REF,
|
|
35
|
+
TEMPLATE_SUBTYPE_OUTPUT,
|
|
36
|
+
TEMPLATE_ATTR_PAYLOAD_REF,
|
|
37
|
+
TEMPLATE_ATTR_TEXT_REF,
|
|
38
|
+
TEMPLATE_ATTR_FORMAT,
|
|
39
|
+
TEMPLATE_ATTR_MAX_CHARS,
|
|
40
|
+
TEMPLATE_ATTR_KIND,
|
|
41
|
+
TEMPLATE_KIND_EMAIL,
|
|
42
|
+
TEMPLATE_KIND_DEFAULT,
|
|
43
|
+
TEMPLATE_ATTR_SUBJECT_REF,
|
|
44
|
+
TEMPLATE_ATTR_HTML_BODY_REF,
|
|
45
|
+
TEMPLATE_ATTR_TEXT_BODY_REF,
|
|
46
|
+
} from "@metaobjectsdev/metadata";
|
|
47
|
+
import {
|
|
48
|
+
verify,
|
|
49
|
+
ERR_REQUIRED_SLOT_UNUSED,
|
|
50
|
+
type Provider,
|
|
51
|
+
type PayloadField,
|
|
52
|
+
type VerifyError,
|
|
53
|
+
} from "@metaobjectsdev/render";
|
|
54
|
+
|
|
55
|
+
function findObject(root: MetaData, name: string): MetaData | undefined {
|
|
56
|
+
return root.ownChildren().find((c) => c.type === TYPE_OBJECT && c.name === name);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function findTemplate(root: MetaData, name: string): MetaData | undefined {
|
|
60
|
+
return root.ownChildren().find((c) => c.type === TYPE_TEMPLATE && c.name === name);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Walk an `object.value` view-object into a render `PayloadField[]`. Object-ref
|
|
65
|
+
* fields recurse into their referenced view-object; a `seen` set guards a
|
|
66
|
+
* (pathological) reference cycle. Replicates cli's `derivePayloadFieldTree` —
|
|
67
|
+
* codegen-ts must not depend on the cli package (wrong layer / would cycle), so
|
|
68
|
+
* the same small walk lives here against the metadata constants codegen-ts
|
|
69
|
+
* already imports.
|
|
70
|
+
*/
|
|
71
|
+
function derivePayloadFieldTree(
|
|
72
|
+
root: MetaData,
|
|
73
|
+
voName: string,
|
|
74
|
+
seen: ReadonlySet<string> = new Set(),
|
|
75
|
+
): PayloadField[] {
|
|
76
|
+
if (seen.has(voName)) return [];
|
|
77
|
+
const vo = findObject(root, voName);
|
|
78
|
+
if (!vo) return [];
|
|
79
|
+
const nextSeen = new Set(seen).add(voName);
|
|
80
|
+
const fields: PayloadField[] = [];
|
|
81
|
+
for (const f of vo.children().filter((c) => c.type === TYPE_FIELD)) {
|
|
82
|
+
if (f.subType === FIELD_SUBTYPE_OBJECT) {
|
|
83
|
+
const ref = f.ownAttr(FIELD_ATTR_OBJECT_REF);
|
|
84
|
+
if (typeof ref === "string") {
|
|
85
|
+
fields.push({ name: f.name, fields: derivePayloadFieldTree(root, ref, nextSeen) });
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
fields.push({ name: f.name });
|
|
90
|
+
}
|
|
91
|
+
return fields;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Serialize a PayloadField[] as a stable, deterministic TS array literal so the
|
|
95
|
+
* emitted render() call runs the same runtime drift check the build gate ran. */
|
|
96
|
+
function fieldTreeLiteral(fields: PayloadField[]): string {
|
|
97
|
+
// JSON.stringify is deterministic for this shape (no functions/cycles by
|
|
98
|
+
// construction) and produces valid TS object/array literal syntax.
|
|
99
|
+
return JSON.stringify(fields);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Run the build-time drift gate for one referenced mustache. Throws (fails
|
|
103
|
+
* codegen) when the ref is unresolvable OR verify() reports a non-warning error.
|
|
104
|
+
* Warnings (ERR_REQUIRED_SLOT_UNUSED) are tolerated. */
|
|
105
|
+
function gateRef(
|
|
106
|
+
templateName: string,
|
|
107
|
+
ref: string,
|
|
108
|
+
provider: Provider,
|
|
109
|
+
fields: PayloadField[],
|
|
110
|
+
): void {
|
|
111
|
+
const text = provider.resolve(ref);
|
|
112
|
+
if (text === undefined) {
|
|
113
|
+
throw new Error(
|
|
114
|
+
`render-helper drift: template "${templateName}" ref "${ref}" — unresolved (provider returned no text)`,
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
const errors: VerifyError[] = verify(text, fields, { provider }).filter(
|
|
118
|
+
(e) => e.code !== ERR_REQUIRED_SLOT_UNUSED,
|
|
119
|
+
);
|
|
120
|
+
if (errors.length > 0) {
|
|
121
|
+
const e = errors[0]!;
|
|
122
|
+
throw new Error(
|
|
123
|
+
`render-helper drift: template "${templateName}" ref "${ref}" — ${e.code}: {{${e.path}}} not on payload VO`,
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Render the per-template render-helper source for one `template.output`, AND
|
|
130
|
+
* run the build-time drift gate first. Throws if: the template isn't found /
|
|
131
|
+
* isn't a template.output; @payloadRef is missing or doesn't resolve to an
|
|
132
|
+
* object.value; a required ref for the kind is missing; or ANY referenced
|
|
133
|
+
* mustache drifts from the payload VO (the headline build gate).
|
|
134
|
+
*
|
|
135
|
+
* @param provider the codegen-time provider (e.g. projectProvider(projectRoot))
|
|
136
|
+
* used to resolve + verify each referenced mustache at build time.
|
|
137
|
+
*/
|
|
138
|
+
export function renderRenderHelper(
|
|
139
|
+
root: MetaData,
|
|
140
|
+
templateName: string,
|
|
141
|
+
provider: Provider,
|
|
142
|
+
): string {
|
|
143
|
+
const tmpl = findTemplate(root, templateName);
|
|
144
|
+
if (!tmpl) {
|
|
145
|
+
throw new Error(`template "${templateName}" not found in metadata root`);
|
|
146
|
+
}
|
|
147
|
+
if (tmpl.subType !== TEMPLATE_SUBTYPE_OUTPUT) {
|
|
148
|
+
throw new Error(
|
|
149
|
+
`template "${templateName}" is not a template.output (got subtype "${tmpl.subType}")`,
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
const payloadRef = tmpl.ownAttr(TEMPLATE_ATTR_PAYLOAD_REF);
|
|
153
|
+
if (typeof payloadRef !== "string") {
|
|
154
|
+
throw new Error(`template "${templateName}" missing @payloadRef`);
|
|
155
|
+
}
|
|
156
|
+
const vo = findObject(root, payloadRef);
|
|
157
|
+
if (!vo) {
|
|
158
|
+
throw new Error(
|
|
159
|
+
`template "${templateName}" @payloadRef "${payloadRef}" not found in metadata root`,
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const fields = derivePayloadFieldTree(root, payloadRef);
|
|
164
|
+
const ft = fieldTreeLiteral(fields);
|
|
165
|
+
const fnName = `render${templateName}`;
|
|
166
|
+
|
|
167
|
+
const kind = ((tmpl.ownAttr(TEMPLATE_ATTR_KIND) as string | undefined) ?? TEMPLATE_KIND_DEFAULT)
|
|
168
|
+
.toLowerCase();
|
|
169
|
+
|
|
170
|
+
if (kind === TEMPLATE_KIND_EMAIL) {
|
|
171
|
+
const subjectRef = tmpl.ownAttr(TEMPLATE_ATTR_SUBJECT_REF);
|
|
172
|
+
const htmlBodyRef = tmpl.ownAttr(TEMPLATE_ATTR_HTML_BODY_REF);
|
|
173
|
+
const textBodyRef = tmpl.ownAttr(TEMPLATE_ATTR_TEXT_BODY_REF);
|
|
174
|
+
if (typeof subjectRef !== "string") {
|
|
175
|
+
throw new Error(`template "${templateName}" (email) missing @subjectRef`);
|
|
176
|
+
}
|
|
177
|
+
if (typeof htmlBodyRef !== "string") {
|
|
178
|
+
throw new Error(`template "${templateName}" (email) missing @htmlBodyRef`);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// BUILD-TIME drift gate — every email part-ref is resolved + verified.
|
|
182
|
+
gateRef(templateName, subjectRef, provider, fields);
|
|
183
|
+
gateRef(templateName, htmlBodyRef, provider, fields);
|
|
184
|
+
if (typeof textBodyRef === "string") {
|
|
185
|
+
gateRef(templateName, textBodyRef, provider, fields);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const textBodyLine =
|
|
189
|
+
typeof textBodyRef === "string"
|
|
190
|
+
? `\n textBody: render({ ref: ${JSON.stringify(textBodyRef)}, payload, format: "text", provider, verify: ${ft} }),`
|
|
191
|
+
: "";
|
|
192
|
+
|
|
193
|
+
return `import { render } from "@metaobjectsdev/render";
|
|
194
|
+
import type { Provider, EmailDocument } from "@metaobjectsdev/render";
|
|
195
|
+
import type { ${payloadRef} } from "./payloads.js";
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Render the ${templateName} email (subject + html body${typeof textBodyRef === "string" ? " + text body" : ""}) from a
|
|
199
|
+
* typed ${payloadRef} payload. Wraps the render() engine; the payload field tree is
|
|
200
|
+
* baked in so render()'s runtime drift check matches the build-time gate.
|
|
201
|
+
*/
|
|
202
|
+
export function ${fnName}(payload: ${payloadRef}, provider: Provider): EmailDocument {
|
|
203
|
+
return {
|
|
204
|
+
subject: render({ ref: ${JSON.stringify(subjectRef)}, payload, format: "text", provider, verify: ${ft} }),
|
|
205
|
+
htmlBody: render({ ref: ${JSON.stringify(htmlBodyRef)}, payload, format: "html", provider, verify: ${ft} }),${textBodyLine}
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
`;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// --- document kind ---
|
|
212
|
+
const textRef = tmpl.ownAttr(TEMPLATE_ATTR_TEXT_REF);
|
|
213
|
+
if (typeof textRef !== "string") {
|
|
214
|
+
throw new Error(`template "${templateName}" (document) missing @textRef`);
|
|
215
|
+
}
|
|
216
|
+
const format = ((tmpl.ownAttr(TEMPLATE_ATTR_FORMAT) as string | undefined) ?? "text").toLowerCase();
|
|
217
|
+
const maxCharsAttr = tmpl.ownAttr(TEMPLATE_ATTR_MAX_CHARS);
|
|
218
|
+
const maxChars =
|
|
219
|
+
typeof maxCharsAttr === "number"
|
|
220
|
+
? maxCharsAttr
|
|
221
|
+
: typeof maxCharsAttr === "string" && maxCharsAttr.trim() !== ""
|
|
222
|
+
? Number(maxCharsAttr)
|
|
223
|
+
: undefined;
|
|
224
|
+
|
|
225
|
+
// BUILD-TIME drift gate.
|
|
226
|
+
gateRef(templateName, textRef, provider, fields);
|
|
227
|
+
|
|
228
|
+
const maxCharsArg =
|
|
229
|
+
maxChars !== undefined && Number.isFinite(maxChars) ? `, maxChars: ${maxChars}` : "";
|
|
230
|
+
|
|
231
|
+
return `import { render } from "@metaobjectsdev/render";
|
|
232
|
+
import type { Provider } from "@metaobjectsdev/render";
|
|
233
|
+
import type { ${payloadRef} } from "./payloads.js";
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Render the ${templateName} document from a typed ${payloadRef} payload. Wraps the
|
|
237
|
+
* render() engine; the payload field tree is baked in so render()'s runtime drift
|
|
238
|
+
* check matches the build-time gate enforced when this file was generated.
|
|
239
|
+
*/
|
|
240
|
+
export function ${fnName}(payload: ${payloadRef}, provider: Provider): string {
|
|
241
|
+
return render({ ref: ${JSON.stringify(textRef)}, payload, format: ${JSON.stringify(format)}, provider, verify: ${ft}${maxCharsArg} });
|
|
242
|
+
}
|
|
243
|
+
`;
|
|
244
|
+
}
|
|
@@ -15,11 +15,13 @@ import {
|
|
|
15
15
|
FIELD_SUBTYPE_STRING, FIELD_SUBTYPE_INT, FIELD_SUBTYPE_LONG, FIELD_SUBTYPE_CURRENCY,
|
|
16
16
|
FIELD_SUBTYPE_BOOLEAN, FIELD_SUBTYPE_DOUBLE, FIELD_SUBTYPE_FLOAT,
|
|
17
17
|
FIELD_SUBTYPE_DATE, FIELD_SUBTYPE_TIME, FIELD_SUBTYPE_TIMESTAMP,
|
|
18
|
-
FIELD_SUBTYPE_ENUM, FIELD_SUBTYPE_OBJECT,
|
|
18
|
+
FIELD_SUBTYPE_ENUM, FIELD_SUBTYPE_OBJECT, FIELD_SUBTYPE_UUID,
|
|
19
19
|
VALIDATOR_SUBTYPE_REQUIRED, VALIDATOR_SUBTYPE_LENGTH, VALIDATOR_SUBTYPE_REGEX,
|
|
20
|
+
VALIDATOR_SUBTYPE_NUMERIC, VALIDATOR_SUBTYPE_ARRAY,
|
|
20
21
|
IDENTITY_ATTR_FIELDS, IDENTITY_ATTR_GENERATION,
|
|
21
22
|
FIELD_ATTR_REQUIRED, FIELD_ATTR_MAX_LENGTH, FIELD_ATTR_DEFAULT,
|
|
22
|
-
FIELD_ATTR_AUTO_SET, FIELD_ATTR_OBJECT_REF,
|
|
23
|
+
FIELD_ATTR_AUTO_SET, FIELD_ATTR_OBJECT_REF, FIELD_ATTR_READ_ONLY,
|
|
24
|
+
AUTO_SET_ON_CREATE, AUTO_SET_ON_UPDATE,
|
|
23
25
|
VALIDATOR_ATTR_MAX, VALIDATOR_ATTR_MIN, VALIDATOR_ATTR_PATTERN,
|
|
24
26
|
GENERATION_INCREMENT, GENERATION_UUID,
|
|
25
27
|
} from "@metaobjectsdev/metadata";
|
|
@@ -57,6 +59,10 @@ export function renderInsertSchemaOnly(obj: MetaObject): Code {
|
|
|
57
59
|
const insertFieldLines: Code[] = [];
|
|
58
60
|
for (const child of obj.fields()) {
|
|
59
61
|
if (autoGenPkFields.has(child.name)) continue;
|
|
62
|
+
// FR-013: @readOnly fields are populated by DB / replication / external
|
|
63
|
+
// owner; the application has no path to write them. Exclude from the
|
|
64
|
+
// create-shape schema entirely.
|
|
65
|
+
if (child.ownAttr(FIELD_ATTR_READ_ONLY) === true) continue;
|
|
60
66
|
|
|
61
67
|
const autoSet = child.ownAttr(FIELD_ATTR_AUTO_SET);
|
|
62
68
|
|
|
@@ -88,6 +94,11 @@ export function renderZodValidators(obj: MetaObject): Code {
|
|
|
88
94
|
const updateFieldLines: Code[] = [];
|
|
89
95
|
for (const child of obj.fields()) {
|
|
90
96
|
if (autoGenPkFields.has(child.name)) continue;
|
|
97
|
+
// FR-013: @readOnly fields appear in neither InsertSchema nor UpdateSchema.
|
|
98
|
+
// The DB / trigger / replication owns the write path; the app must not
|
|
99
|
+
// pass these values in POST/PATCH bodies (routesFile enforces the same
|
|
100
|
+
// contract at the boundary with a 400 response).
|
|
101
|
+
if (child.ownAttr(FIELD_ATTR_READ_ONLY) === true) continue;
|
|
91
102
|
|
|
92
103
|
const autoSet = child.ownAttr(FIELD_ATTR_AUTO_SET);
|
|
93
104
|
|
|
@@ -180,6 +191,7 @@ function zodFieldExpr(field: MetaField): Code {
|
|
|
180
191
|
break;
|
|
181
192
|
}
|
|
182
193
|
case FIELD_SUBTYPE_STRING:
|
|
194
|
+
case FIELD_SUBTYPE_UUID:
|
|
183
195
|
default:
|
|
184
196
|
baseStr = "z.string()";
|
|
185
197
|
break;
|
|
@@ -200,12 +212,28 @@ function fieldWillBeOptional(field: MetaField): boolean {
|
|
|
200
212
|
return !isRequired || hasDefault;
|
|
201
213
|
}
|
|
202
214
|
|
|
203
|
-
/**
|
|
215
|
+
/** Numeric field subtypes whose Zod base is `z.number()` — value bounds apply. */
|
|
216
|
+
const NUMERIC_FIELD_SUBTYPES = new Set<string>([
|
|
217
|
+
FIELD_SUBTYPE_INT, FIELD_SUBTYPE_LONG, FIELD_SUBTYPE_CURRENCY,
|
|
218
|
+
FIELD_SUBTYPE_DOUBLE, FIELD_SUBTYPE_FLOAT,
|
|
219
|
+
]);
|
|
220
|
+
|
|
221
|
+
/** Append .min/.max/.regex/.optional() based on field-level validators + required state.
|
|
222
|
+
*
|
|
223
|
+
* Bound semantics by field shape:
|
|
224
|
+
* - string (scalar) → .min/.max = character count (validator.length + @maxLength)
|
|
225
|
+
* - numeric (scalar) → .min/.max = numeric value (validator.numeric)
|
|
226
|
+
* - array (any element) → .min/.max = element count (validator.array)
|
|
227
|
+
*/
|
|
204
228
|
function appendValidatorChain(base: Code, field: MetaField): Code {
|
|
205
229
|
let isRequired = field.ownAttr(FIELD_ATTR_REQUIRED) === true;
|
|
206
230
|
let maxLen: number | undefined = field.ownAttr(FIELD_ATTR_MAX_LENGTH) as number | undefined;
|
|
207
231
|
let minLen: number | undefined;
|
|
208
232
|
let pattern: string | undefined;
|
|
233
|
+
let numMin: number | undefined;
|
|
234
|
+
let numMax: number | undefined;
|
|
235
|
+
let arrMin: number | undefined;
|
|
236
|
+
let arrMax: number | undefined;
|
|
209
237
|
for (const child of field.validators()) {
|
|
210
238
|
if (child.subType === VALIDATOR_SUBTYPE_REQUIRED) isRequired = true;
|
|
211
239
|
if (child.subType === VALIDATOR_SUBTYPE_LENGTH) {
|
|
@@ -218,14 +246,33 @@ function appendValidatorChain(base: Code, field: MetaField): Code {
|
|
|
218
246
|
const p = child.ownAttr(VALIDATOR_ATTR_PATTERN);
|
|
219
247
|
if (typeof p === "string") pattern = p;
|
|
220
248
|
}
|
|
249
|
+
if (child.subType === VALIDATOR_SUBTYPE_NUMERIC) {
|
|
250
|
+
const max = child.ownAttr(VALIDATOR_ATTR_MAX);
|
|
251
|
+
const min = child.ownAttr(VALIDATOR_ATTR_MIN);
|
|
252
|
+
if (typeof max === "number") numMax = max;
|
|
253
|
+
if (typeof min === "number") numMin = min;
|
|
254
|
+
}
|
|
255
|
+
if (child.subType === VALIDATOR_SUBTYPE_ARRAY) {
|
|
256
|
+
const max = child.ownAttr(VALIDATOR_ATTR_MAX);
|
|
257
|
+
const min = child.ownAttr(VALIDATOR_ATTR_MIN);
|
|
258
|
+
if (typeof max === "number") arrMax = max;
|
|
259
|
+
if (typeof min === "number") arrMin = min;
|
|
260
|
+
}
|
|
221
261
|
}
|
|
222
262
|
|
|
223
263
|
let chain: Code = base;
|
|
224
|
-
|
|
264
|
+
// Array element-count bounds apply to the z.array(...) wrapper regardless of element type.
|
|
265
|
+
if (field.isArray) {
|
|
266
|
+
if (arrMin !== undefined) chain = code`${chain}.min(${arrMin})`;
|
|
267
|
+
if (arrMax !== undefined) chain = code`${chain}.max(${arrMax})`;
|
|
268
|
+
} else if (field.subType === FIELD_SUBTYPE_STRING) {
|
|
225
269
|
if (minLen !== undefined) chain = code`${chain}.min(${minLen})`;
|
|
226
270
|
else if (isRequired) chain = code`${chain}.min(1)`;
|
|
227
271
|
if (maxLen !== undefined) chain = code`${chain}.max(${maxLen})`;
|
|
228
272
|
if (pattern !== undefined) chain = code`${chain}.regex(new RegExp(${JSON.stringify(pattern)}))`;
|
|
273
|
+
} else if (NUMERIC_FIELD_SUBTYPES.has(field.subType)) {
|
|
274
|
+
if (numMin !== undefined) chain = code`${chain}.min(${numMin})`;
|
|
275
|
+
if (numMax !== undefined) chain = code`${chain}.max(${numMax})`;
|
|
229
276
|
}
|
|
230
277
|
|
|
231
278
|
// Fields with DB-level defaults are optional in the InsertSchema: the caller
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"recover-schema-emitter.d.ts","sourceRoot":"","sources":["../../src/templates/recover-schema-emitter.ts"],"names":[],"mappings":"AAcA,OAAO,EACL,KAAK,QAAQ,EAId,MAAM,0BAA0B,CAAC;AAclC,yDAAyD;AACzD,wBAAgB,aAAa,CAAC,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAIpF;AA+BD,0DAA0D;AAC1D,wBAAgB,eAAe,CAAC,EAAE,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,GAAG,MAAM,CAc3E;AAED,8FAA8F;AAC9F,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,QAAQ,GAAG,MAAM,CAGtD"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"recover-schema-emitter.js","sourceRoot":"","sources":["../../src/templates/recover-schema-emitter.ts"],"names":[],"mappings":"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"}
|