@metaobjectsdev/codegen-ts 0.9.0 → 0.10.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 +1 -1
- package/dist/column-mapper.d.ts.map +1 -1
- package/dist/column-mapper.js +24 -8
- package/dist/column-mapper.js.map +1 -1
- package/dist/constants.d.ts +8 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +16 -0
- package/dist/constants.js.map +1 -1
- package/dist/docs-paths.d.ts +58 -0
- package/dist/docs-paths.d.ts.map +1 -0
- package/dist/docs-paths.js +89 -0
- package/dist/docs-paths.js.map +1 -0
- package/dist/enum-import.d.ts +14 -0
- package/dist/enum-import.d.ts.map +1 -0
- package/dist/enum-import.js +35 -0
- package/dist/enum-import.js.map +1 -0
- package/dist/enum-shared.d.ts +32 -0
- package/dist/enum-shared.d.ts.map +1 -0
- package/dist/enum-shared.js +83 -0
- package/dist/enum-shared.js.map +1 -0
- package/dist/generator-registry.d.ts +22 -0
- package/dist/generator-registry.d.ts.map +1 -0
- package/dist/generator-registry.js +161 -0
- package/dist/generator-registry.js.map +1 -0
- package/dist/generator.d.ts +6 -0
- package/dist/generator.d.ts.map +1 -1
- package/dist/generator.js.map +1 -1
- package/dist/generators/api-doc-render.d.ts +17 -0
- package/dist/generators/api-doc-render.d.ts.map +1 -0
- package/dist/generators/api-doc-render.js +431 -0
- package/dist/generators/api-doc-render.js.map +1 -0
- package/dist/generators/api-docs-file.d.ts +21 -0
- package/dist/generators/api-docs-file.d.ts.map +1 -0
- package/dist/generators/api-docs-file.js +112 -0
- package/dist/generators/api-docs-file.js.map +1 -0
- package/dist/generators/api-field-shape.d.ts +39 -0
- package/dist/generators/api-field-shape.d.ts.map +1 -0
- package/dist/generators/api-field-shape.js +92 -0
- package/dist/generators/api-field-shape.js.map +1 -0
- package/dist/generators/api-label.d.ts +3 -0
- package/dist/generators/api-label.d.ts.map +1 -0
- package/dist/generators/api-label.js +8 -0
- package/dist/generators/api-label.js.map +1 -0
- package/dist/generators/api-model.d.ts +122 -0
- package/dist/generators/api-model.d.ts.map +1 -0
- package/dist/generators/api-model.js +809 -0
- package/dist/generators/api-model.js.map +1 -0
- package/dist/generators/docs-data-builder.d.ts +26 -4
- package/dist/generators/docs-data-builder.d.ts.map +1 -1
- package/dist/generators/docs-data-builder.js +436 -164
- package/dist/generators/docs-data-builder.js.map +1 -1
- package/dist/generators/docs-data.d.ts +136 -27
- package/dist/generators/docs-data.d.ts.map +1 -1
- package/dist/generators/docs-data.js +1 -1
- package/dist/generators/docs-data.js.map +1 -1
- package/dist/generators/docs-file.d.ts +19 -0
- package/dist/generators/docs-file.d.ts.map +1 -1
- package/dist/generators/docs-file.js +154 -27
- package/dist/generators/docs-file.js.map +1 -1
- package/dist/generators/entity-file.d.ts.map +1 -1
- package/dist/generators/entity-file.js +29 -14
- package/dist/generators/entity-file.js.map +1 -1
- package/dist/generators/extractor-file.d.ts.map +1 -1
- package/dist/generators/extractor-file.js +2 -1
- package/dist/generators/extractor-file.js.map +1 -1
- package/dist/generators/field-anchor.d.ts +7 -0
- package/dist/generators/field-anchor.d.ts.map +1 -0
- package/dist/generators/field-anchor.js +23 -0
- package/dist/generators/field-anchor.js.map +1 -0
- package/dist/generators/index.d.ts +8 -1
- package/dist/generators/index.d.ts.map +1 -1
- package/dist/generators/index.js +6 -0
- package/dist/generators/index.js.map +1 -1
- package/dist/generators/mermaid-er.d.ts +14 -0
- package/dist/generators/mermaid-er.d.ts.map +1 -1
- package/dist/generators/mermaid-er.js +14 -0
- package/dist/generators/mermaid-er.js.map +1 -1
- package/dist/generators/output-parser-file.d.ts.map +1 -1
- package/dist/generators/output-parser-file.js +3 -4
- package/dist/generators/output-parser-file.js.map +1 -1
- package/dist/generators/output-prompt-file.d.ts.map +1 -1
- package/dist/generators/output-prompt-file.js +2 -2
- package/dist/generators/output-prompt-file.js.map +1 -1
- package/dist/generators/prompt-render-file.d.ts.map +1 -1
- package/dist/generators/prompt-render-file.js +3 -4
- package/dist/generators/prompt-render-file.js.map +1 -1
- package/dist/generators/queries-file.d.ts.map +1 -1
- package/dist/generators/queries-file.js +8 -3
- package/dist/generators/queries-file.js.map +1 -1
- package/dist/generators/render-helper-file.d.ts.map +1 -1
- package/dist/generators/render-helper-file.js +2 -2
- package/dist/generators/render-helper-file.js.map +1 -1
- package/dist/generators/routes-file-hono.d.ts.map +1 -1
- package/dist/generators/routes-file-hono.js +5 -1
- package/dist/generators/routes-file-hono.js.map +1 -1
- package/dist/generators/routes-file.d.ts +3 -0
- package/dist/generators/routes-file.d.ts.map +1 -1
- package/dist/generators/routes-file.js +6 -1
- package/dist/generators/routes-file.js.map +1 -1
- package/dist/generators/template-doc-builder.d.ts +19 -0
- package/dist/generators/template-doc-builder.d.ts.map +1 -0
- package/dist/generators/template-doc-builder.js +220 -0
- package/dist/generators/template-doc-builder.js.map +1 -0
- package/dist/generators/template-doc-data.d.ts +62 -0
- package/dist/generators/template-doc-data.d.ts.map +1 -0
- package/dist/generators/template-doc-data.js +16 -0
- package/dist/generators/template-doc-data.js.map +1 -0
- package/dist/generators/template-payload-tree.d.ts +15 -0
- package/dist/generators/template-payload-tree.d.ts.map +1 -0
- package/dist/generators/template-payload-tree.js +61 -0
- package/dist/generators/template-payload-tree.js.map +1 -0
- package/dist/generators/template-source-annotate.d.ts +74 -0
- package/dist/generators/template-source-annotate.d.ts.map +1 -0
- package/dist/generators/template-source-annotate.js +184 -0
- package/dist/generators/template-source-annotate.js.map +1 -0
- package/dist/generators/template-source-render.d.ts +24 -0
- package/dist/generators/template-source-render.d.ts.map +1 -0
- package/dist/generators/template-source-render.js +175 -0
- package/dist/generators/template-source-render.js.map +1 -0
- package/dist/generators/trace-helper-file.d.ts +9 -0
- package/dist/generators/trace-helper-file.d.ts.map +1 -0
- package/dist/generators/trace-helper-file.js +196 -0
- package/dist/generators/trace-helper-file.js.map +1 -0
- package/dist/index.d.ts +29 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +28 -2
- package/dist/index.js.map +1 -1
- package/dist/metaobjects-config.d.ts +75 -2
- package/dist/metaobjects-config.d.ts.map +1 -1
- package/dist/metaobjects-config.js +43 -0
- package/dist/metaobjects-config.js.map +1 -1
- package/dist/naming.d.ts +19 -0
- package/dist/naming.d.ts.map +1 -1
- package/dist/naming.js +41 -0
- package/dist/naming.js.map +1 -1
- package/dist/payload-codegen.d.ts.map +1 -1
- package/dist/payload-codegen.js +12 -4
- 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 +51 -25
- package/dist/projection/extract-view-spec.js.map +1 -1
- package/dist/relation-resolver.d.ts +16 -0
- package/dist/relation-resolver.d.ts.map +1 -1
- package/dist/relation-resolver.js +82 -1
- package/dist/relation-resolver.js.map +1 -1
- package/dist/render-context.d.ts +4 -0
- package/dist/render-context.d.ts.map +1 -1
- package/dist/render-context.js.map +1 -1
- package/dist/render-engine/embedded-templates.generated.d.ts +2 -0
- package/dist/render-engine/embedded-templates.generated.d.ts.map +1 -0
- package/dist/render-engine/embedded-templates.generated.js +15 -0
- package/dist/render-engine/embedded-templates.generated.js.map +1 -0
- package/dist/render-engine/framework-provider.d.ts.map +1 -1
- package/dist/render-engine/framework-provider.js +26 -13
- package/dist/render-engine/framework-provider.js.map +1 -1
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +17 -0
- package/dist/runner.js.map +1 -1
- package/dist/templates/docs-file.d.ts +2 -6
- package/dist/templates/docs-file.d.ts.map +1 -1
- package/dist/templates/docs-file.js +2 -5
- package/dist/templates/docs-file.js.map +1 -1
- package/dist/templates/drizzle-schema.d.ts.map +1 -1
- package/dist/templates/drizzle-schema.js +30 -2
- package/dist/templates/drizzle-schema.js.map +1 -1
- package/dist/templates/entity-constants.d.ts +7 -0
- package/dist/templates/entity-constants.d.ts.map +1 -1
- package/dist/templates/entity-constants.js +1 -1
- package/dist/templates/entity-constants.js.map +1 -1
- package/dist/templates/entity-file.d.ts.map +1 -1
- package/dist/templates/entity-file.js +16 -5
- package/dist/templates/entity-file.js.map +1 -1
- package/dist/templates/enums-file.d.ts +11 -0
- package/dist/templates/enums-file.d.ts.map +1 -0
- package/dist/templates/enums-file.js +44 -0
- package/dist/templates/enums-file.js.map +1 -0
- package/dist/templates/extract-delegate-emitter.d.ts.map +1 -1
- package/dist/templates/extract-delegate-emitter.js +5 -7
- package/dist/templates/extract-delegate-emitter.js.map +1 -1
- package/dist/templates/extract-schema-emitter.d.ts.map +1 -1
- package/dist/templates/extract-schema-emitter.js +5 -1
- package/dist/templates/extract-schema-emitter.js.map +1 -1
- package/dist/templates/extractor.d.ts.map +1 -1
- package/dist/templates/extractor.js +56 -39
- package/dist/templates/extractor.js.map +1 -1
- package/dist/templates/field-meta.d.ts.map +1 -1
- package/dist/templates/field-meta.js +1 -5
- package/dist/templates/field-meta.js.map +1 -1
- package/dist/templates/filter-allowlist.d.ts +7 -2
- package/dist/templates/filter-allowlist.d.ts.map +1 -1
- package/dist/templates/filter-allowlist.js +17 -9
- package/dist/templates/filter-allowlist.js.map +1 -1
- package/dist/templates/filter-type.d.ts +7 -1
- package/dist/templates/filter-type.d.ts.map +1 -1
- package/dist/templates/filter-type.js +9 -5
- package/dist/templates/filter-type.js.map +1 -1
- package/dist/templates/find-templates.d.ts +4 -0
- package/dist/templates/find-templates.d.ts.map +1 -0
- package/dist/templates/find-templates.js +15 -0
- package/dist/templates/find-templates.js.map +1 -0
- package/dist/templates/fr010-field-mapping.d.ts +2 -0
- package/dist/templates/fr010-field-mapping.d.ts.map +1 -1
- package/dist/templates/fr010-field-mapping.js +10 -6
- package/dist/templates/fr010-field-mapping.js.map +1 -1
- package/dist/templates/inferred-types.d.ts +44 -7
- package/dist/templates/inferred-types.d.ts.map +1 -1
- package/dist/templates/inferred-types.js +107 -16
- package/dist/templates/inferred-types.js.map +1 -1
- package/dist/templates/mermaid-er.d.ts +35 -2
- package/dist/templates/mermaid-er.d.ts.map +1 -1
- package/dist/templates/mermaid-er.js +174 -7
- package/dist/templates/mermaid-er.js.map +1 -1
- package/dist/templates/output-parser.d.ts.map +1 -1
- package/dist/templates/output-parser.js +30 -79
- package/dist/templates/output-parser.js.map +1 -1
- package/dist/templates/output-prompt.d.ts.map +1 -1
- package/dist/templates/output-prompt.js +2 -2
- package/dist/templates/output-prompt.js.map +1 -1
- package/dist/templates/queries-file.d.ts.map +1 -1
- package/dist/templates/queries-file.js +112 -4
- package/dist/templates/queries-file.js.map +1 -1
- package/dist/templates/queries.d.ts +5 -0
- package/dist/templates/queries.d.ts.map +1 -1
- package/dist/templates/queries.js +7 -7
- package/dist/templates/queries.js.map +1 -1
- package/dist/templates/recover-schema-emitter.d.ts +8 -0
- package/dist/templates/recover-schema-emitter.d.ts.map +1 -0
- package/dist/templates/recover-schema-emitter.js +64 -0
- package/dist/templates/recover-schema-emitter.js.map +1 -0
- package/dist/templates/relations-block.js +10 -0
- package/dist/templates/relations-block.js.map +1 -1
- package/dist/templates/render-helper.d.ts.map +1 -1
- package/dist/templates/render-helper.js +4 -4
- package/dist/templates/render-helper.js.map +1 -1
- package/dist/templates/routes-file.d.ts.map +1 -1
- package/dist/templates/routes-file.js +183 -6
- package/dist/templates/routes-file.js.map +1 -1
- package/dist/templates/tph-discriminator.d.ts +56 -0
- package/dist/templates/tph-discriminator.d.ts.map +1 -0
- package/dist/templates/tph-discriminator.js +180 -0
- package/dist/templates/tph-discriminator.js.map +1 -0
- package/dist/templates/value-object-file.d.ts +2 -1
- package/dist/templates/value-object-file.d.ts.map +1 -1
- package/dist/templates/value-object-file.js +32 -4
- package/dist/templates/value-object-file.js.map +1 -1
- package/dist/templates/zod-validators.d.ts +64 -1
- package/dist/templates/zod-validators.d.ts.map +1 -1
- package/dist/templates/zod-validators.js +181 -8
- package/dist/templates/zod-validators.js.map +1 -1
- package/package.json +103 -34
- package/src/column-mapper.ts +25 -8
- package/src/constants.ts +18 -0
- package/src/docs-paths.ts +128 -0
- package/src/enum-import.ts +43 -0
- package/src/enum-shared.ts +95 -0
- package/src/generator-registry.ts +204 -0
- package/src/generator.ts +6 -0
- package/src/generators/api-doc-render.ts +572 -0
- package/src/generators/api-docs-file.ts +146 -0
- package/src/generators/api-field-shape.ts +114 -0
- package/src/generators/api-label.ts +7 -0
- package/src/generators/api-model.ts +1067 -0
- package/src/generators/docs-data-builder.ts +479 -185
- package/src/generators/docs-data.ts +139 -28
- package/src/generators/docs-file.ts +205 -39
- package/src/generators/entity-file.ts +31 -15
- package/src/generators/extractor-file.ts +2 -1
- package/src/generators/field-anchor.ts +24 -0
- package/src/generators/index.ts +8 -1
- package/src/generators/mermaid-er.ts +14 -0
- package/src/generators/output-parser-file.ts +3 -4
- package/src/generators/output-prompt-file.ts +2 -1
- package/src/generators/prompt-render-file.ts +3 -4
- package/src/generators/queries-file.ts +9 -3
- package/src/generators/render-helper-file.ts +2 -1
- package/src/generators/routes-file-hono.ts +5 -1
- package/src/generators/routes-file.ts +7 -1
- package/src/generators/template-doc-builder.ts +306 -0
- package/src/generators/template-doc-data.ts +85 -0
- package/src/generators/template-payload-tree.ts +71 -0
- package/src/generators/template-source-annotate.ts +290 -0
- package/src/generators/template-source-render.ts +203 -0
- package/src/generators/trace-helper-file.ts +301 -0
- package/src/index.ts +55 -4
- package/src/metaobjects-config.ts +117 -2
- package/src/naming.ts +48 -0
- package/src/payload-codegen.ts +14 -3
- package/src/projection/extract-view-spec.ts +49 -30
- package/src/relation-resolver.ts +103 -1
- package/src/render-context.ts +4 -0
- package/src/render-engine/embedded-templates.generated.ts +14 -0
- package/src/render-engine/framework-provider.ts +25 -11
- package/src/runner.ts +21 -0
- package/src/templates/docs-file.ts +2 -9
- package/src/templates/drizzle-schema.ts +31 -1
- package/src/templates/entity-constants.ts +1 -1
- package/src/templates/entity-file.ts +16 -5
- package/src/templates/enums-file.ts +50 -0
- package/src/templates/extract-delegate-emitter.ts +5 -6
- package/src/templates/extractor.ts +68 -38
- package/src/templates/field-meta.ts +0 -6
- package/src/templates/filter-allowlist.ts +17 -10
- package/src/templates/filter-type.ts +8 -6
- package/src/templates/find-templates.ts +15 -0
- package/src/templates/fr010-field-mapping.ts +10 -8
- package/src/templates/inferred-types.ts +108 -18
- package/src/templates/mermaid-er.ts +176 -8
- package/src/templates/output-parser.ts +30 -79
- package/src/templates/output-prompt.ts +2 -1
- package/src/templates/queries-file.ts +132 -3
- package/src/templates/queries.ts +15 -7
- package/src/templates/relations-block.ts +17 -0
- package/src/templates/render-helper.ts +4 -3
- package/src/templates/routes-file.ts +233 -6
- package/src/templates/tph-discriminator.ts +232 -0
- package/src/templates/value-object-file.ts +38 -4
- package/src/templates/zod-validators.ts +204 -7
- package/templates/api/agent-api.md.mustache +30 -0
- package/templates/api/entity-api.md.mustache +69 -0
- package/templates/api/index.md.mustache +21 -0
- package/templates/docs/entity-page.md.mustache +33 -21
- package/templates/docs/template-page.md.mustache +56 -0
- package/src/templates/extract-schema-emitter.ts +0 -111
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// downstream (e.g. to LLM tool_use input_schema) lost the nested object shape.
|
|
11
11
|
|
|
12
12
|
import { code, joinCode, imp, type Code } from "ts-poet";
|
|
13
|
-
import { MetaObject, MetaField } from "@metaobjectsdev/metadata";
|
|
13
|
+
import { MetaObject, MetaField, stripPackage } from "@metaobjectsdev/metadata";
|
|
14
14
|
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,
|
|
@@ -24,9 +24,82 @@ import {
|
|
|
24
24
|
AUTO_SET_ON_CREATE, AUTO_SET_ON_UPDATE,
|
|
25
25
|
VALIDATOR_ATTR_MAX, VALIDATOR_ATTR_MIN, VALIDATOR_ATTR_PATTERN,
|
|
26
26
|
GENERATION_INCREMENT, GENERATION_UUID,
|
|
27
|
+
OBJECT_ATTR_DISCRIMINATOR, OBJECT_ATTR_DISCRIMINATOR_VALUE,
|
|
27
28
|
} from "@metaobjectsdev/metadata";
|
|
28
29
|
import { enumValues, zodEnumExpr } from "../enum-meta.js";
|
|
29
30
|
import { renderDocsFor } from "./jsdoc.js";
|
|
31
|
+
import { sharedEnumForField } from "../enum-shared.js";
|
|
32
|
+
import { sharedEnumImportSpecifier } from "../enum-import.js";
|
|
33
|
+
import { sharedEnumZodConstName } from "./enums-file.js";
|
|
34
|
+
import type { RenderContext } from "../render-context.js";
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* FR-017 Tier 1 — when this object is a TPH subtype (@discriminatorValue set
|
|
38
|
+
* and an ancestor carries @discriminator), return the discriminator-field-name
|
|
39
|
+
* → pinned-literal-value pair. Subtypes emit `<field>: z.literal("<value>")`
|
|
40
|
+
* instead of the inherited field's normal type expression. Returns undefined
|
|
41
|
+
* when the object is not a TPH subtype.
|
|
42
|
+
*/
|
|
43
|
+
export function tphDiscriminatorPin(obj: MetaObject): { fieldName: string; value: string } | undefined {
|
|
44
|
+
const value = obj.ownAttr(OBJECT_ATTR_DISCRIMINATOR_VALUE);
|
|
45
|
+
if (typeof value !== "string" || value === "") return undefined;
|
|
46
|
+
|
|
47
|
+
// Walk the extends chain to find the root carrying @discriminator.
|
|
48
|
+
let cursor = obj.superResolved;
|
|
49
|
+
while (cursor !== undefined) {
|
|
50
|
+
const fieldName = cursor.ownAttr(OBJECT_ATTR_DISCRIMINATOR);
|
|
51
|
+
if (typeof fieldName === "string" && fieldName !== "") {
|
|
52
|
+
return { fieldName, value };
|
|
53
|
+
}
|
|
54
|
+
cursor = cursor.superResolved;
|
|
55
|
+
}
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** True when this object is a TPH subtype — it declares @discriminatorValue
|
|
60
|
+
* and an ancestor carries @discriminator. */
|
|
61
|
+
export function isTphSubtype(obj: MetaObject): boolean {
|
|
62
|
+
return tphDiscriminatorPin(obj) !== undefined;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* FR-017 Tier 2 — the per-subtype FULL read schema `<Sub>Schema`. Unlike the
|
|
67
|
+
* insert schema, this includes every effective field (PK included) so a raw DB
|
|
68
|
+
* row parses through it. The discriminator field is pinned to its literal value
|
|
69
|
+
* (`type: z.literal("Bridge")`) so the schema rejects a row of another subtype.
|
|
70
|
+
*
|
|
71
|
+
* This is the schema parse<Base>(row) dispatches to (see tph-discriminator.ts).
|
|
72
|
+
* Non-required columns are `.nullable()`-tolerant: a nullable TPH column read
|
|
73
|
+
* back from the DB arrives as `null`, not `undefined`, so the read schema must
|
|
74
|
+
* accept null (the insert schema, by contrast, makes them `.optional()`).
|
|
75
|
+
*/
|
|
76
|
+
export function renderTphSubtypeReadSchema(obj: MetaObject): Code {
|
|
77
|
+
const z = imp("z@zod");
|
|
78
|
+
const tphPin = tphDiscriminatorPin(obj);
|
|
79
|
+
|
|
80
|
+
const fieldLines: Code[] = [];
|
|
81
|
+
for (const child of obj.fields()) {
|
|
82
|
+
if (tphPin !== undefined && child.name === tphPin.fieldName) {
|
|
83
|
+
fieldLines.push(code` ${child.name}: z.literal(${JSON.stringify(tphPin.value)})`);
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
const expr = zodFieldExpr(child);
|
|
87
|
+
// zodFieldExpr already appends `.optional()` for non-required fields; add
|
|
88
|
+
// `.nullable()` on top so a NULL column value (the TPH default for any
|
|
89
|
+
// subtype-only column) parses cleanly.
|
|
90
|
+
fieldLines.push(
|
|
91
|
+
fieldWillBeOptional(child) ? code` ${child.name}: ${expr}.nullable()` : code` ${child.name}: ${expr}`,
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const docs = renderDocsFor(obj);
|
|
96
|
+
const docsPrefix = docs ? `${docs}\n` : "";
|
|
97
|
+
return code`
|
|
98
|
+
${docsPrefix}export const ${obj.name}Schema = ${z}.object({
|
|
99
|
+
${joinCode(fieldLines, { on: ",\n" })}
|
|
100
|
+
});
|
|
101
|
+
`;
|
|
102
|
+
}
|
|
30
103
|
|
|
31
104
|
/** Auto-generated PK field names that should be omitted from InsertSchema. */
|
|
32
105
|
function autoGenPkFieldNames(obj: MetaObject): Set<string> {
|
|
@@ -55,6 +128,7 @@ function autoGenPkFieldNames(obj: MetaObject): Set<string> {
|
|
|
55
128
|
export function renderInsertSchemaOnly(obj: MetaObject): Code {
|
|
56
129
|
const z = imp("z@zod");
|
|
57
130
|
const autoGenPkFields = autoGenPkFieldNames(obj);
|
|
131
|
+
const tphPin = tphDiscriminatorPin(obj);
|
|
58
132
|
|
|
59
133
|
const insertFieldLines: Code[] = [];
|
|
60
134
|
for (const child of obj.fields()) {
|
|
@@ -64,6 +138,14 @@ export function renderInsertSchemaOnly(obj: MetaObject): Code {
|
|
|
64
138
|
// create-shape schema entirely.
|
|
65
139
|
if (child.ownAttr(FIELD_ATTR_READ_ONLY) === true) continue;
|
|
66
140
|
|
|
141
|
+
// FR-017 Tier 1: TPH subtype pins its discriminator field to z.literal(...).
|
|
142
|
+
if (tphPin !== undefined && child.name === tphPin.fieldName) {
|
|
143
|
+
insertFieldLines.push(
|
|
144
|
+
code` ${child.name}: z.literal(${JSON.stringify(tphPin.value)})`,
|
|
145
|
+
);
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
|
|
67
149
|
const autoSet = child.ownAttr(FIELD_ATTR_AUTO_SET);
|
|
68
150
|
|
|
69
151
|
if (autoSet === AUTO_SET_ON_CREATE || autoSet === AUTO_SET_ON_UPDATE) {
|
|
@@ -86,9 +168,90 @@ ${joinCode(insertFieldLines, { on: ",\n" })}
|
|
|
86
168
|
`;
|
|
87
169
|
}
|
|
88
170
|
|
|
89
|
-
|
|
171
|
+
/** One documented field in an Insert/Update schema's accepted shape. */
|
|
172
|
+
export interface SchemaFieldShape {
|
|
173
|
+
/** The field name (the schema property key). */
|
|
174
|
+
name: string;
|
|
175
|
+
/** Whether the property is optional in the schema (`.optional()` / omitted-OK). */
|
|
176
|
+
optional: boolean;
|
|
177
|
+
/** For the @discriminator field on a TPH subtype's InsertSchema: the pinned
|
|
178
|
+
* literal value (`z.literal("Bridge")`). Undefined otherwise. */
|
|
179
|
+
pinnedLiteral?: string;
|
|
180
|
+
/** True for @autoSet timestamp fields the schema fills server-side
|
|
181
|
+
* (`z.string().optional().transform(...)`). */
|
|
182
|
+
autoSet?: boolean;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* The field SET (name + optionality) the `<Name>InsertSchema` accepts — derived
|
|
187
|
+
* by the SAME iteration + skip rules `renderInsertSchemaOnly` /
|
|
188
|
+
* `renderZodValidators` use to EMIT that schema, so a documented create-payload
|
|
189
|
+
* shape can never drift from the real schema:
|
|
190
|
+
* • auto-generated PK fields are omitted (caller doesn't provide them);
|
|
191
|
+
* • @readOnly fields are omitted (DB / replication owns the write path);
|
|
192
|
+
* • a TPH subtype's @discriminator field is a pinned `z.literal(value)`;
|
|
193
|
+
* • @autoSet fields are present but optional (server fills them);
|
|
194
|
+
* • every other field's optionality is `fieldWillBeOptional` (not required, or
|
|
195
|
+
* carries a @default).
|
|
196
|
+
*/
|
|
197
|
+
export function insertSchemaFields(obj: MetaObject): SchemaFieldShape[] {
|
|
198
|
+
const autoGenPkFields = autoGenPkFieldNames(obj);
|
|
199
|
+
const tphPin = tphDiscriminatorPin(obj);
|
|
200
|
+
const out: SchemaFieldShape[] = [];
|
|
201
|
+
for (const child of obj.fields()) {
|
|
202
|
+
if (autoGenPkFields.has(child.name)) continue;
|
|
203
|
+
if (child.ownAttr(FIELD_ATTR_READ_ONLY) === true) continue;
|
|
204
|
+
if (tphPin !== undefined && child.name === tphPin.fieldName) {
|
|
205
|
+
out.push({ name: child.name, optional: false, pinnedLiteral: tphPin.value });
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
const autoSet = child.ownAttr(FIELD_ATTR_AUTO_SET);
|
|
209
|
+
if (autoSet === AUTO_SET_ON_CREATE || autoSet === AUTO_SET_ON_UPDATE) {
|
|
210
|
+
out.push({ name: child.name, optional: true, autoSet: true });
|
|
211
|
+
} else {
|
|
212
|
+
out.push({ name: child.name, optional: fieldWillBeOptional(child) });
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return out;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* The field SET the `<Name>UpdateSchema` accepts — same iteration + skip rules
|
|
220
|
+
* as `insertSchemaFields`, but mirroring the UpdateSchema branch of
|
|
221
|
+
* `renderZodValidators`:
|
|
222
|
+
* • a TPH subtype's @discriminator field is OMITTED (clients can't change subtype);
|
|
223
|
+
* • @autoSet onCreate fields are OMITTED (creation timestamps are immutable);
|
|
224
|
+
* • @autoSet onUpdate fields are present + optional (server fills them);
|
|
225
|
+
* • every other field is optional (PATCH semantics).
|
|
226
|
+
*/
|
|
227
|
+
export function updateSchemaFields(obj: MetaObject): SchemaFieldShape[] {
|
|
228
|
+
const autoGenPkFields = autoGenPkFieldNames(obj);
|
|
229
|
+
const tphPin = tphDiscriminatorPin(obj);
|
|
230
|
+
const out: SchemaFieldShape[] = [];
|
|
231
|
+
for (const child of obj.fields()) {
|
|
232
|
+
if (autoGenPkFields.has(child.name)) continue;
|
|
233
|
+
if (child.ownAttr(FIELD_ATTR_READ_ONLY) === true) continue;
|
|
234
|
+
// TPH subtype discriminator: omitted from the update schema entirely.
|
|
235
|
+
if (tphPin !== undefined && child.name === tphPin.fieldName) continue;
|
|
236
|
+
const autoSet = child.ownAttr(FIELD_ATTR_AUTO_SET);
|
|
237
|
+
if (autoSet === AUTO_SET_ON_CREATE) {
|
|
238
|
+
// Omitted: creation timestamps cannot change after creation.
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
if (autoSet === AUTO_SET_ON_UPDATE) {
|
|
242
|
+
out.push({ name: child.name, optional: true, autoSet: true });
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
// All non-autoSet fields are optional in the update schema (PATCH semantics).
|
|
246
|
+
out.push({ name: child.name, optional: true });
|
|
247
|
+
}
|
|
248
|
+
return out;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export function renderZodValidators(obj: MetaObject, ctx?: RenderContext): Code {
|
|
90
252
|
const z = imp("z@zod");
|
|
91
253
|
const autoGenPkFields = autoGenPkFieldNames(obj);
|
|
254
|
+
const tphPin = tphDiscriminatorPin(obj);
|
|
92
255
|
|
|
93
256
|
const insertFieldLines: Code[] = [];
|
|
94
257
|
const updateFieldLines: Code[] = [];
|
|
@@ -100,6 +263,17 @@ export function renderZodValidators(obj: MetaObject): Code {
|
|
|
100
263
|
// contract at the boundary with a 400 response).
|
|
101
264
|
if (child.ownAttr(FIELD_ATTR_READ_ONLY) === true) continue;
|
|
102
265
|
|
|
266
|
+
// FR-017 Tier 1: TPH subtype pins its discriminator field to z.literal(...).
|
|
267
|
+
// The discriminator is implicit on subtype rows (controlled by URL / insert
|
|
268
|
+
// path) — the app never writes it via the body and never updates it.
|
|
269
|
+
// Insert: pinned literal. Update: omitted entirely (clients can't change subtype).
|
|
270
|
+
if (tphPin !== undefined && child.name === tphPin.fieldName) {
|
|
271
|
+
insertFieldLines.push(
|
|
272
|
+
code` ${child.name}: z.literal(${JSON.stringify(tphPin.value)})`,
|
|
273
|
+
);
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
|
|
103
277
|
const autoSet = child.ownAttr(FIELD_ATTR_AUTO_SET);
|
|
104
278
|
|
|
105
279
|
// Insert schema: @autoSet fields use transform (always override client input).
|
|
@@ -108,7 +282,7 @@ export function renderZodValidators(obj: MetaObject): Code {
|
|
|
108
282
|
code` ${child.name}: z.string().optional().transform(() => new Date().toISOString())`,
|
|
109
283
|
);
|
|
110
284
|
} else {
|
|
111
|
-
insertFieldLines.push(code` ${child.name}: ${zodFieldExpr(child)}`);
|
|
285
|
+
insertFieldLines.push(code` ${child.name}: ${zodFieldExpr(child, obj, ctx)}`);
|
|
112
286
|
}
|
|
113
287
|
|
|
114
288
|
// Update schema: @autoSet onCreate → omit entirely; onUpdate → transform
|
|
@@ -122,7 +296,7 @@ export function renderZodValidators(obj: MetaObject): Code {
|
|
|
122
296
|
// All non-autoSet fields are optional in the update schema (PATCH semantics).
|
|
123
297
|
// zodFieldExpr already appends .optional() when the field is non-required
|
|
124
298
|
// OR has a default; only append once more when it didn't.
|
|
125
|
-
const baseExpr = zodFieldExpr(child);
|
|
299
|
+
const baseExpr = zodFieldExpr(child, obj, ctx);
|
|
126
300
|
updateFieldLines.push(
|
|
127
301
|
fieldWillBeOptional(child) ? code` ${child.name}: ${baseExpr}` : code` ${child.name}: ${baseExpr}.optional()`,
|
|
128
302
|
);
|
|
@@ -146,7 +320,7 @@ ${joinCode(updateFieldLines, { on: ",\n" })}
|
|
|
146
320
|
`;
|
|
147
321
|
}
|
|
148
322
|
|
|
149
|
-
function zodFieldExpr(field: MetaField): Code {
|
|
323
|
+
function zodFieldExpr(field: MetaField, owner?: MetaObject, ctx?: RenderContext): Code {
|
|
150
324
|
// FIELD_SUBTYPE_OBJECT: emit z.array(<Ref>InsertSchema) / <Ref>InsertSchema
|
|
151
325
|
// via an imp() so ts-poet hoists the cross-module import. Without this the
|
|
152
326
|
// field used to collapse to z.string() / z.array(z.string()) and downstream
|
|
@@ -154,7 +328,10 @@ function zodFieldExpr(field: MetaField): Code {
|
|
|
154
328
|
if (field.subType === FIELD_SUBTYPE_OBJECT) {
|
|
155
329
|
const ref = field.ownAttr(FIELD_ATTR_OBJECT_REF);
|
|
156
330
|
if (typeof ref === "string" && ref.length > 0) {
|
|
157
|
-
|
|
331
|
+
// @objectRef may be authored fully-qualified or bare — the referenced
|
|
332
|
+
// <Ref>InsertSchema + its sibling module use the BARE short name.
|
|
333
|
+
const refBase = stripPackage(ref);
|
|
334
|
+
const refImp = imp(`${refBase}InsertSchema@./${refBase}.js`);
|
|
158
335
|
let base: Code = code`${refImp}`;
|
|
159
336
|
if (field.isArray) base = code`z.array(${base})`;
|
|
160
337
|
return appendValidatorChain(base, field);
|
|
@@ -187,7 +364,27 @@ function zodFieldExpr(field: MetaField): Code {
|
|
|
187
364
|
break;
|
|
188
365
|
case FIELD_SUBTYPE_ENUM: {
|
|
189
366
|
const values = enumValues(field);
|
|
190
|
-
|
|
367
|
+
if (values === undefined) {
|
|
368
|
+
baseStr = "z.string()";
|
|
369
|
+
break;
|
|
370
|
+
}
|
|
371
|
+
// FR-019: a field extending a MATERIALIZED root-level abstract enum uses the
|
|
372
|
+
// shared `<E>Enum` Zod const (imported from ./enums) instead of inlining
|
|
373
|
+
// z.enum([...]). A @provided enum keeps inline z.enum([...]) — validation
|
|
374
|
+
// stays metaobjects-owned (the @values SSOT); only the TS type is external.
|
|
375
|
+
// Inline enums (and bare-ctx unit-test calls) keep inlining as before.
|
|
376
|
+
if (ctx !== undefined) {
|
|
377
|
+
const shared = sharedEnumForField(field);
|
|
378
|
+
if (shared !== undefined && !shared.provided) {
|
|
379
|
+
const constName = sharedEnumZodConstName(shared.name);
|
|
380
|
+
const spec = sharedEnumImportSpecifier(ctx, owner?.package);
|
|
381
|
+
const sharedConst = imp(`${constName}@${spec}`);
|
|
382
|
+
let base: Code = code`${sharedConst}`;
|
|
383
|
+
if (field.isArray) base = code`z.array(${base})`;
|
|
384
|
+
return appendValidatorChain(base, field);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
baseStr = zodEnumExpr(values);
|
|
191
388
|
break;
|
|
192
389
|
}
|
|
193
390
|
case FIELD_SUBTYPE_STRING:
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{{{generatedMarker}}}
|
|
2
|
+
|
|
3
|
+
# {{title}}
|
|
4
|
+
|
|
5
|
+
Generated API reference for {{project}}; call these exactly as written. {{importNote}}
|
|
6
|
+
{{#hasSetup}}
|
|
7
|
+
|
|
8
|
+
## Setup
|
|
9
|
+
{{#setup}}
|
|
10
|
+
- `{{handle}}` — {{{note}}} `{{{snippetInline}}}`
|
|
11
|
+
{{/setup}}
|
|
12
|
+
{{/hasSetup}}
|
|
13
|
+
{{#units}}
|
|
14
|
+
|
|
15
|
+
## {{node}}
|
|
16
|
+
{{#groups}}
|
|
17
|
+
|
|
18
|
+
`{{importHeader}}`
|
|
19
|
+
{{#symbols}}
|
|
20
|
+
- `{{signature}}` — {{usage}}{{#throwsMarker}} {{throwsMarker}}{{/throwsMarker}}
|
|
21
|
+
{{/symbols}}
|
|
22
|
+
{{/groups}}
|
|
23
|
+
{{#example}}
|
|
24
|
+
|
|
25
|
+
Example:
|
|
26
|
+
```ts
|
|
27
|
+
{{{example}}}
|
|
28
|
+
```
|
|
29
|
+
{{/example}}
|
|
30
|
+
{{/units}}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{{{generatedMarker}}}
|
|
2
|
+
|
|
3
|
+
# {{node}} API
|
|
4
|
+
{{#modelPageHref}}
|
|
5
|
+
|
|
6
|
+
**Model / metadata:** [{{node}}]({{modelPageHref}})
|
|
7
|
+
{{/modelPageHref}}
|
|
8
|
+
|
|
9
|
+
> Import paths are relative to your generated-output directory.
|
|
10
|
+
{{#hasSetup}}
|
|
11
|
+
|
|
12
|
+
## Setup
|
|
13
|
+
|
|
14
|
+
Obtain the runtime handles the calls below need:
|
|
15
|
+
{{#setup}}
|
|
16
|
+
|
|
17
|
+
- `{{handle}}` — {{{note}}}
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
{{{snippet}}}
|
|
21
|
+
```
|
|
22
|
+
{{/setup}}
|
|
23
|
+
{{/hasSetup}}
|
|
24
|
+
{{#unitExample}}
|
|
25
|
+
|
|
26
|
+
## Example
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
{{{unitExample}}}
|
|
30
|
+
```
|
|
31
|
+
{{/unitExample}}
|
|
32
|
+
{{#sections}}
|
|
33
|
+
|
|
34
|
+
## {{heading}}
|
|
35
|
+
{{#symbols}}
|
|
36
|
+
|
|
37
|
+
### `{{signature}}`
|
|
38
|
+
|
|
39
|
+
{{usage}}
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
{{importLine}}
|
|
43
|
+
```
|
|
44
|
+
{{#hasFields}}
|
|
45
|
+
|
|
46
|
+
{{fieldsCaption}}:
|
|
47
|
+
|
|
48
|
+
| Field | Type | Required | Notes |
|
|
49
|
+
|---|---|---|---|
|
|
50
|
+
{{#fieldRows}}
|
|
51
|
+
| `{{field}}` | `{{{type}}}` | {{required}} | {{notes}} |
|
|
52
|
+
{{/fieldRows}}
|
|
53
|
+
{{/hasFields}}
|
|
54
|
+
{{#mountNote}}
|
|
55
|
+
|
|
56
|
+
Mount: {{{mountNote}}}
|
|
57
|
+
{{/mountNote}}
|
|
58
|
+
{{#throws}}
|
|
59
|
+
|
|
60
|
+
Throws: {{throws}}
|
|
61
|
+
{{/throws}}
|
|
62
|
+
{{#example}}
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
{{{example}}}
|
|
66
|
+
```
|
|
67
|
+
{{/example}}
|
|
68
|
+
{{/symbols}}
|
|
69
|
+
{{/sections}}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{{{generatedMarker}}}
|
|
2
|
+
|
|
3
|
+
# {{title}}
|
|
4
|
+
|
|
5
|
+
{{intro}}
|
|
6
|
+
{{#hasEntities}}
|
|
7
|
+
|
|
8
|
+
## Entities
|
|
9
|
+
|
|
10
|
+
{{#entities}}
|
|
11
|
+
- [{{node}}]({{href}}) — {{summary}} ({{symbolCount}} symbol{{^one}}s{{/one}})
|
|
12
|
+
{{/entities}}
|
|
13
|
+
{{/hasEntities}}
|
|
14
|
+
{{#hasTemplates}}
|
|
15
|
+
|
|
16
|
+
## Templates
|
|
17
|
+
|
|
18
|
+
{{#templates}}
|
|
19
|
+
- [{{node}}]({{href}}) — {{summary}} ({{symbolCount}} symbol{{^one}}s{{/one}})
|
|
20
|
+
{{/templates}}
|
|
21
|
+
{{/hasTemplates}}
|
|
@@ -1,21 +1,20 @@
|
|
|
1
1
|
{{{generatedMarker}}}
|
|
2
2
|
|
|
3
3
|
# {{entity.name}}
|
|
4
|
+
{{#summaryLead}}
|
|
5
|
+
|
|
6
|
+
{{{.}}}
|
|
7
|
+
{{/summaryLead}}
|
|
4
8
|
{{#descriptionQuote}}
|
|
5
9
|
|
|
6
10
|
{{{.}}}
|
|
7
11
|
{{/descriptionQuote}}
|
|
12
|
+
{{#apiRefs.0}}
|
|
8
13
|
|
|
9
|
-
{{{
|
|
10
|
-
{{
|
|
11
|
-
|
|
12
|
-
## Storage
|
|
14
|
+
**API reference:** {{/apiRefs.0}}{{#apiRefs}}[{{label}}]({{href}}){{^last}} · {{/last}}{{/apiRefs}}{{#apiRefs.0}}
|
|
15
|
+
{{/apiRefs.0}}
|
|
13
16
|
|
|
14
|
-
{{{
|
|
15
|
-
{{#storage.rows}}
|
|
16
|
-
{{{rowLine}}}
|
|
17
|
-
{{/storage.rows}}
|
|
18
|
-
{{/hasStorage}}
|
|
17
|
+
{{{preambleHeader}}}
|
|
19
18
|
{{#hasIdentities}}
|
|
20
19
|
|
|
21
20
|
## Identity
|
|
@@ -24,6 +23,31 @@
|
|
|
24
23
|
- {{{bullet}}}
|
|
25
24
|
{{/identities}}
|
|
26
25
|
{{/hasIdentities}}
|
|
26
|
+
{{#hasNeighborhoodEr}}
|
|
27
|
+
|
|
28
|
+
## In context
|
|
29
|
+
|
|
30
|
+
{{{neighborhoodErBlock}}}
|
|
31
|
+
{{/hasNeighborhoodEr}}
|
|
32
|
+
{{#fields.hasFields}}
|
|
33
|
+
|
|
34
|
+
## Fields
|
|
35
|
+
|
|
36
|
+
| Field | Type | Required | Column | Rules |
|
|
37
|
+
|---|---|---|---|---|
|
|
38
|
+
{{#fields.rows}}
|
|
39
|
+
| {{{fieldCell}}} | {{{typeCell}}} | {{requiredCell}} | {{{storageCell}}} | {{{rulesCell}}} |
|
|
40
|
+
{{/fields.rows}}
|
|
41
|
+
{{/fields.hasFields}}
|
|
42
|
+
{{#fieldDetails.hasDetails}}
|
|
43
|
+
|
|
44
|
+
## Field details
|
|
45
|
+
|
|
46
|
+
{{#fieldDetails.rows}}
|
|
47
|
+
{{{block}}}
|
|
48
|
+
|
|
49
|
+
{{/fieldDetails.rows}}
|
|
50
|
+
{{/fieldDetails.hasDetails}}
|
|
27
51
|
{{#hasRelationships}}
|
|
28
52
|
|
|
29
53
|
## Relationships
|
|
@@ -32,12 +56,6 @@
|
|
|
32
56
|
- {{{bullet}}}
|
|
33
57
|
{{/relationships}}
|
|
34
58
|
{{/hasRelationships}}
|
|
35
|
-
|
|
36
|
-
## Validation
|
|
37
|
-
|
|
38
|
-
- `{{validation.insertSchema}}` (Zod) — for creating new {{validation.lower}}s.
|
|
39
|
-
- `{{validation.updateSchema}}` (Zod) — for partial updates.
|
|
40
|
-
- See `{{validation.entityFile}}` for the exported schemas.
|
|
41
59
|
{{#hasUsedBy}}
|
|
42
60
|
|
|
43
61
|
## Used by
|
|
@@ -46,9 +64,3 @@
|
|
|
46
64
|
- {{{bullet}}}
|
|
47
65
|
{{/usedBy}}
|
|
48
66
|
{{/hasUsedBy}}
|
|
49
|
-
|
|
50
|
-
## Generated code
|
|
51
|
-
|
|
52
|
-
{{#generated}}
|
|
53
|
-
- `{{filename}}` — {{{description}}}
|
|
54
|
-
{{/generated}}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{{{generatedMarker}}}
|
|
2
|
+
|
|
3
|
+
# {{name}}
|
|
4
|
+
{{#descriptionQuote}}
|
|
5
|
+
|
|
6
|
+
{{{.}}}
|
|
7
|
+
{{/descriptionQuote}}
|
|
8
|
+
|
|
9
|
+
**Kind:** {{kind}}
|
|
10
|
+
|
|
11
|
+
## Output
|
|
12
|
+
{{^isEmail}}
|
|
13
|
+
|
|
14
|
+
- Format: `{{format}}`
|
|
15
|
+
{{/isEmail}}
|
|
16
|
+
{{#isEmail}}
|
|
17
|
+
|
|
18
|
+
Multipart email — rendered as the following parts:
|
|
19
|
+
|
|
20
|
+
| Part | Source | Format | Escaping |
|
|
21
|
+
|---|---|---|---|
|
|
22
|
+
{{#parts}}
|
|
23
|
+
| {{label}} | `{{ref}}` | `{{format}}` | {{#escaped}}escaped{{/escaped}}{{^escaped}}raw{{/escaped}} |
|
|
24
|
+
{{/parts}}
|
|
25
|
+
{{/isEmail}}
|
|
26
|
+
|
|
27
|
+
## Input
|
|
28
|
+
|
|
29
|
+
- Payload: [`{{payload.name}}`]({{payload.link}})
|
|
30
|
+
{{#hasRequiredTags}}
|
|
31
|
+
- Required fields:{{#requiredTags}} `{{.}}`{{/requiredTags}}
|
|
32
|
+
{{/hasRequiredTags}}
|
|
33
|
+
|
|
34
|
+
## Render contract
|
|
35
|
+
|
|
36
|
+
- Every field referenced by the template is validated against the payload at generation time; an unknown field fails generation.
|
|
37
|
+
{{#maxChars}}
|
|
38
|
+
- Maximum length: {{.}} characters (rendering longer output fails).
|
|
39
|
+
{{/maxChars}}
|
|
40
|
+
{{#hasRequiredTags}}
|
|
41
|
+
- Required tags must be present:{{#requiredTags}} `{{.}}`{{/requiredTags}}
|
|
42
|
+
{{/hasRequiredTags}}
|
|
43
|
+
|
|
44
|
+
## Source
|
|
45
|
+
|
|
46
|
+
{{#sourceRefs}}
|
|
47
|
+
- `{{.}}`
|
|
48
|
+
{{/sourceRefs}}
|
|
49
|
+
{{#templateSourceSection}}
|
|
50
|
+
|
|
51
|
+
{{{.}}}
|
|
52
|
+
{{/templateSourceSection}}
|
|
53
|
+
|
|
54
|
+
## Capability
|
|
55
|
+
|
|
56
|
+
{{capability}}
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
// server/typescript/packages/codegen-ts/src/templates/extract-schema-emitter.ts
|
|
2
|
-
//
|
|
3
|
-
// Turns a payload value-object into TS source fragments for the FR-010 extract codegen:
|
|
4
|
-
// • schemaLiteral — a `extractSchema(Format.JSON, "root", [ … ])` baked descriptor
|
|
5
|
-
// built from FieldSpec factories (scalar / enumField).
|
|
6
|
-
// • mirrorInterface — an all-nullable mirror interface `<Payload>Extracted` (each
|
|
7
|
-
// component `T | null`); extract returns this nullable twin rather
|
|
8
|
-
// than the strict payload (same reasoning as the Java/C#/Kotlin ports).
|
|
9
|
-
// • mirrorInitializer — `{ prop: asString(d, "prop"), … }` building the mirror from the
|
|
10
|
-
// forgiving outcome map `d`.
|
|
11
|
-
//
|
|
12
|
-
// Mirrors the C# ExtractSchemaEmitter (adapted to TS syntax + the `| null` nullable mirror).
|
|
13
|
-
// Bounded scope: scalar / enum / scalar-array. Nested object + array-of-enum deferred.
|
|
14
|
-
|
|
15
|
-
import {
|
|
16
|
-
type MetaData,
|
|
17
|
-
FIELD_SUBTYPE_ENUM,
|
|
18
|
-
FIELD_SUBTYPE_OBJECT,
|
|
19
|
-
FIELD_ATTR_ENUM_ALIAS,
|
|
20
|
-
} from "@metaobjectsdev/metadata";
|
|
21
|
-
import {
|
|
22
|
-
fields,
|
|
23
|
-
isRequired,
|
|
24
|
-
isArray,
|
|
25
|
-
scalarKind,
|
|
26
|
-
mirrorType,
|
|
27
|
-
extractMapCall,
|
|
28
|
-
enumValues,
|
|
29
|
-
coerceDefault,
|
|
30
|
-
defaultValue,
|
|
31
|
-
resolveNormalize,
|
|
32
|
-
jsonStringLiteral,
|
|
33
|
-
stringArrayLiteral,
|
|
34
|
-
propertiesMapLiteral,
|
|
35
|
-
} from "./fr010-field-mapping.js";
|
|
36
|
-
import { NORMALIZE_DEFAULT } from "@metaobjectsdev/metadata";
|
|
37
|
-
|
|
38
|
-
/** Emit `extractSchema(Format.X, "rootName", [ … ])`. */
|
|
39
|
-
export function schemaLiteral(vo: MetaData, format: string, rootName: string): string {
|
|
40
|
-
const formatEnum = format.toLowerCase() === "xml" ? "Format.XML" : "Format.JSON";
|
|
41
|
-
const specs = fields(vo).map((f) => fieldSpecLiteral(f, vo));
|
|
42
|
-
return `extractSchema(${formatEnum}, ${jsonStringLiteral(rootName)}, [${specs.join(", ")}])`;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function fieldSpecLiteral(field: MetaData, owner: MetaData): string {
|
|
46
|
-
const name = jsonStringLiteral(field.name);
|
|
47
|
-
const required = isRequired(field);
|
|
48
|
-
|
|
49
|
-
if (field.subType === FIELD_SUBTYPE_ENUM) {
|
|
50
|
-
const valuesLit = stringArrayLiteral(enumValues(field));
|
|
51
|
-
const aliasLit = propertiesMapLiteral(field.ownAttr(FIELD_ATTR_ENUM_ALIAS));
|
|
52
|
-
// FR-011: extended enumField signature is (name, required, values, aliases,
|
|
53
|
-
// coerceDefault?, normalize="strip", defaultValue?). Resolve the three new args
|
|
54
|
-
// (field → object → "strip" for normalize) and emit only what's needed: keep the
|
|
55
|
-
// back-compat 4-arg form when nothing is set, else emit the positional tail up to
|
|
56
|
-
// the last meaningful arg.
|
|
57
|
-
const cd = coerceDefault(field);
|
|
58
|
-
const dv = defaultValue(field);
|
|
59
|
-
const normalize = resolveNormalize(field, owner);
|
|
60
|
-
// enumField() sets array:false; enum-array is a bounded deferral (parity with Java/C#).
|
|
61
|
-
if (cd == null && dv == null && normalize === NORMALIZE_DEFAULT) {
|
|
62
|
-
return `enumField(${name}, ${required}, ${valuesLit}, ${aliasLit})`;
|
|
63
|
-
}
|
|
64
|
-
const cdLit = cd == null ? "null" : jsonStringLiteral(cd);
|
|
65
|
-
const normLit = jsonStringLiteral(normalize);
|
|
66
|
-
if (dv == null) {
|
|
67
|
-
return `enumField(${name}, ${required}, ${valuesLit}, ${aliasLit}, ${cdLit}, ${normLit})`;
|
|
68
|
-
}
|
|
69
|
-
return `enumField(${name}, ${required}, ${valuesLit}, ${aliasLit}, ${cdLit}, ${normLit}, ${jsonStringLiteral(dv)})`;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
if (field.subType === FIELD_SUBTYPE_OBJECT) {
|
|
73
|
-
// FR-010: nested extract deferred — treat as an opaque required/optional string slot.
|
|
74
|
-
return `scalar(${name}, FieldKind.STRING, ${required}) /* FR-010: nested extract deferred */`;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const kind = scalarKind(field.subType) ?? "STRING";
|
|
78
|
-
// Scalar-array: the scalar() factory only builds singular specs (array:false), so emit a
|
|
79
|
-
// FieldSpec object literal with array:true. Tier-2 win over the Roslyn proof: the emitted
|
|
80
|
-
// extract() actually populates the array at runtime (ExtractMap.asStringList).
|
|
81
|
-
if (isArray(field)) {
|
|
82
|
-
return (
|
|
83
|
-
`{ name: ${name}, kind: FieldKind.${kind}, required: ${required}, array: true, ` +
|
|
84
|
-
`enumValues: null, enumAlias: null, min: null, max: null, nested: null }`
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
|
-
return `scalar(${name}, FieldKind.${kind}, ${required})`;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/** Emit the all-nullable mirror interface declaration. */
|
|
91
|
-
export function mirrorInterface(vo: MetaData, interfaceName: string): string {
|
|
92
|
-
const base = interfaceName.endsWith("Extracted")
|
|
93
|
-
? interfaceName.slice(0, -"Extracted".length)
|
|
94
|
-
: interfaceName;
|
|
95
|
-
const lines: string[] = [];
|
|
96
|
-
lines.push(
|
|
97
|
-
`/** Best-effort extracted twin of \`${base}\` — every field nullable (null where lost/malformed). */`,
|
|
98
|
-
);
|
|
99
|
-
lines.push(`export interface ${interfaceName} {`);
|
|
100
|
-
for (const f of fields(vo)) {
|
|
101
|
-
lines.push(` ${f.name}: ${mirrorType(f)};`);
|
|
102
|
-
}
|
|
103
|
-
lines.push("}");
|
|
104
|
-
return lines.join("\n");
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/** Emit `{ prop: asString(d, "prop"), … }` building the mirror from the forgiving map `d`. */
|
|
108
|
-
export function mirrorInitializer(vo: MetaData): string {
|
|
109
|
-
const assigns = fields(vo).map((f) => `${f.name}: ${extractMapCall(f)}`);
|
|
110
|
-
return `{ ${assigns.join(", ")} }`;
|
|
111
|
-
}
|