@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
|
@@ -0,0 +1,809 @@
|
|
|
1
|
+
// server/typescript/packages/codegen-ts/src/generators/api-model.ts
|
|
2
|
+
//
|
|
3
|
+
// ApiModel — an intermediate representation (IR) of the PUBLIC API surface an
|
|
4
|
+
// adopter's codegen produces from their metadata. The whole point is to be
|
|
5
|
+
// ACCURATE BY CONSTRUCTION: every symbol NAME here is derived by REUSING the
|
|
6
|
+
// real generators' own naming/signature logic (the same helpers the generators
|
|
7
|
+
// call when they emit code), never invented. The Task-4 accuracy gate runs the
|
|
8
|
+
// real generators and asserts each ApiModel symbol name actually appears in the
|
|
9
|
+
// generated output — so this builder must agree with them by construction.
|
|
10
|
+
//
|
|
11
|
+
// What it documents, per node:
|
|
12
|
+
// • ENTITY (object.entity, queryable):
|
|
13
|
+
// - model : the entity type/const (entity-file emits `<Name>`)
|
|
14
|
+
// - data-access : findById / list / create / update / deleteById helpers
|
|
15
|
+
// (templates/queries.ts — exact spellings via naming.ts)
|
|
16
|
+
// - rest : the 5 CRUD endpoints the routes generator mounts at the
|
|
17
|
+
// entity's $path (read-only set for projections)
|
|
18
|
+
// - validation : <Name>InsertSchema / <Name>UpdateSchema (zod-validators)
|
|
19
|
+
// • template.output:
|
|
20
|
+
// - extractor : extract<Name> / extractLenient<Name> (templates/extractor.ts)
|
|
21
|
+
// — ONLY when @format is json/xml (extractor generator gate)
|
|
22
|
+
// - render : render<Name> (templates/render-helper.ts) — document →
|
|
23
|
+
// string, email → EmailDocument (@kind gate)
|
|
24
|
+
// • ENTITY (additional, T5 — relationships / callable / Hono):
|
|
25
|
+
// - relation : the `<var>Relations` drizzle relations() export the
|
|
26
|
+
// entity file composes (relations-block.ts), one per entity
|
|
27
|
+
// that has relations; the per-navigation accessors (1:N
|
|
28
|
+
// one() / M:N many(junction)) ride in its field shape, named
|
|
29
|
+
// + cardinality-tagged + target-tagged. ONLY when the
|
|
30
|
+
// relation-resolver derives a relations() block for it.
|
|
31
|
+
// - callable : call<Entity> (templates/callable-file.ts) — ONLY when the
|
|
32
|
+
// entity is backed by a stored-proc / table-function source
|
|
33
|
+
// (isCallableEntity); the typed proc wrapper.
|
|
34
|
+
// - rest-hono : the Hono CRUD registrar register<Entity>Routes
|
|
35
|
+
// (templates/routes-file-hono.ts) — the OPT-IN Hono variant
|
|
36
|
+
// of the Fastify REST surface. Documented ONLY when the
|
|
37
|
+
// adopter wires routesFileHono() (ctx.includeHonoRoutes), and
|
|
38
|
+
// gated by the SAME @emitRoutes:false filter.
|
|
39
|
+
// • template.prompt (T5):
|
|
40
|
+
// - prompt : render<Name> (payload, provider): string — the prompt
|
|
41
|
+
// render handle promptRender() emits into a single
|
|
42
|
+
// aggregated `prompts.ts` (payload-codegen generateRenderHandle).
|
|
43
|
+
// ONLY for TOP-LEVEL template.prompt nodes (matching
|
|
44
|
+
// prompt-render-file.ts's `ctx.loadedRoot.ownChildren()`).
|
|
45
|
+
//
|
|
46
|
+
// DEFERRALS (tracked follow-ups — NOT documented by this builder yet, stated here
|
|
47
|
+
// so the gap is known + intentional):
|
|
48
|
+
// • TanStack / React generator surface — formFile / tanstackQuery / grid + hooks
|
|
49
|
+
// are framework ADD-ONS (a separate front-end-codegen effort); their emitted
|
|
50
|
+
// symbols are out of scope for the back-end public-API IR this builder models.
|
|
51
|
+
// • TPH BASE per-subtype write helpers (create<Sub> / update<Sub>ById /
|
|
52
|
+
// delete<Sub>ById scoped to the shared table) + the subtype REST subpaths —
|
|
53
|
+
// the prior deliberate deferral (see the TPH skip note below). Under-documented
|
|
54
|
+
// (allowed), never invented.
|
|
55
|
+
//
|
|
56
|
+
// SKIP rules honored (matching the real generators' filters):
|
|
57
|
+
// • object.value records have no primary identity → the queries generator skips
|
|
58
|
+
// them entirely (queries-file.ts `skipNonQueryable` = subType !== "value" &&
|
|
59
|
+
// !isTphSubtype), and they get no CRUD/routes/validation. So value objects
|
|
60
|
+
// contribute ONLY a model symbol here.
|
|
61
|
+
// • TPH subtypes (a @discriminatorValue under a @discriminator base) are ALSO
|
|
62
|
+
// skipped by the queries + routes generators (isTphSubtype, from
|
|
63
|
+
// templates/zod-validators.ts) — their query/route/validation surface lives
|
|
64
|
+
// in the discriminator BASE's polymorphic file, NOT their own. So a TPH
|
|
65
|
+
// subtype likewise contributes ONLY a model symbol here.
|
|
66
|
+
// The discriminator BASE itself stays queryable, but its data-access surface
|
|
67
|
+
// is REDUCED: the queries generator emits only the polymorphic reads
|
|
68
|
+
// find<Base>ById + list<Base>s on the base — create/update/delete are emitted
|
|
69
|
+
// PER CONCRETE SUBTYPE (create<Sub> …), since a base row can't be inserted
|
|
70
|
+
// without choosing a subtype. So the builder documents only those two reads
|
|
71
|
+
// (plus the base's validation schemas + base-path REST, which ARE emitted);
|
|
72
|
+
// documenting create<Base>/update<Base>/delete<Base>ById would be
|
|
73
|
+
// over-documentation (the Task-4 accuracy gate catches exactly that).
|
|
74
|
+
// DEFERRAL: the TPH BASE's per-subtype polymorphic write helpers (create<Sub>
|
|
75
|
+
// / update<Sub>ById / delete<Sub>ById scoped to the shared table) and the
|
|
76
|
+
// subtype REST subpaths are NOT YET documented by this builder — that fuller
|
|
77
|
+
// TPH modeling is a tracked follow-up (under-documentation, allowed).
|
|
78
|
+
// • @emitRoutes:false entities → the routes generator filters them out
|
|
79
|
+
// (routes-file.ts: ownAttr(CODEGEN_ATTR_EMIT_ROUTES) !== false), so they get
|
|
80
|
+
// NO REST symbols here. The queries + validator generators do NOT honor
|
|
81
|
+
// @emitRoutes, so data-access + validation symbols still apply.
|
|
82
|
+
import { OBJECT_SUBTYPE_VALUE, TYPE_TEMPLATE, TEMPLATE_SUBTYPE_OUTPUT, TEMPLATE_SUBTYPE_PROMPT, TEMPLATE_ATTR_PAYLOAD_REF, TEMPLATE_ATTR_FORMAT, TEMPLATE_ATTR_KIND, TEMPLATE_KIND_EMAIL, TEMPLATE_KIND_DEFAULT, TYPE_SOURCE, SOURCE_ATTR_PARAMETER_REF, refMatchesObject, } from "@metaobjectsdev/metadata";
|
|
83
|
+
import { findByIdFnName, listFnName, createFnName, updateFnName, deleteByIdFnName, routesHandlerName, variableNameFromEntity, } from "../naming.js";
|
|
84
|
+
import { getPkInfo } from "../templates/queries.js";
|
|
85
|
+
import { isTphSubtype } from "../templates/zod-validators.js";
|
|
86
|
+
import { isTphDiscriminatorBase } from "../templates/tph-discriminator.js";
|
|
87
|
+
import { isCallableEntity } from "../templates/callable-file.js";
|
|
88
|
+
import { CODEGEN_ATTR_EMIT_ROUTES } from "../constants.js";
|
|
89
|
+
import { resourcePath } from "../templates/entity-constants.js";
|
|
90
|
+
import { isProjection } from "../projection/projection-detector.js";
|
|
91
|
+
import { buildPkMap } from "../pk-resolver.js";
|
|
92
|
+
import { buildRelationMap } from "../relation-resolver.js";
|
|
93
|
+
import { effectivePackage } from "../docs-paths.js";
|
|
94
|
+
import { entityOutputPath } from "../import-path.js";
|
|
95
|
+
import { modelFieldShapes, createFieldShapes, updateFieldShapes, payloadFieldShapes, } from "./api-field-shape.js";
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
// Builder.
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
export function buildApiModel(root, ctx) {
|
|
100
|
+
const pkMap = ctx.pkMap ?? buildPkMap(root);
|
|
101
|
+
// getPkInfo wants a RenderContext; it only reads `.pkMap`, so a structural
|
|
102
|
+
// shim is sufficient (and avoids forcing callers to build a full context).
|
|
103
|
+
const pkCtx = { pkMap };
|
|
104
|
+
const layout = ctx.outputLayout ?? "flat";
|
|
105
|
+
const relationMap = ctx.relationMap ?? buildRelationMap(root);
|
|
106
|
+
const includeHono = ctx.includeHonoRoutes ?? false;
|
|
107
|
+
const units = [];
|
|
108
|
+
for (const obj of root.objects()) {
|
|
109
|
+
units.push(buildEntityUnit(obj, pkCtx, root, layout, relationMap, includeHono));
|
|
110
|
+
}
|
|
111
|
+
for (const tmpl of templateOutputs(root)) {
|
|
112
|
+
units.push(buildTemplateUnit(tmpl, root, layout));
|
|
113
|
+
}
|
|
114
|
+
for (const tmpl of templatePrompts(root)) {
|
|
115
|
+
units.push(buildPromptUnit(tmpl, root));
|
|
116
|
+
}
|
|
117
|
+
return { units };
|
|
118
|
+
}
|
|
119
|
+
// ---------------------------------------------------------------------------
|
|
120
|
+
// importPath derivation — the SINGLE place a documented symbol's import module
|
|
121
|
+
// is computed. It mirrors the EMITTING generator's own path logic exactly so a
|
|
122
|
+
// documented import can never drift from where the code actually lands:
|
|
123
|
+
//
|
|
124
|
+
// • entity / queries / routes files use
|
|
125
|
+
// entityOutputPath(layout, entity.package, "<Name>.<suffix>.ts")
|
|
126
|
+
// (queries-file.ts / entity-file.ts / routes-file.ts) — note they key off
|
|
127
|
+
// the entity's OWN bare `.package` (often undefined for objects, FR5d), so
|
|
128
|
+
// in package layout they only fold when the object actually carries a
|
|
129
|
+
// package. We pass the SAME `obj.package` here, not effectivePackage.
|
|
130
|
+
// • extractor / render-helper files emit a FLAT `<Name>.extractor.ts` /
|
|
131
|
+
// `<Name>.render.ts` regardless of layout (extractor-file.ts /
|
|
132
|
+
// render-helper-file.ts: `${t.name}.<suffix>.ts`, no package folding).
|
|
133
|
+
//
|
|
134
|
+
// The importPath is the emitted path WITHOUT the trailing `.ts`.
|
|
135
|
+
// ---------------------------------------------------------------------------
|
|
136
|
+
/** Extension-less module specifier for an entity-derived file
|
|
137
|
+
* (`<Name>` / `<Name>.queries` / `<Name>.routes`), folded by the SAME
|
|
138
|
+
* entityOutputPath logic the emitting generator uses. */
|
|
139
|
+
function entityModulePath(layout, obj, basename) {
|
|
140
|
+
return stripTs(entityOutputPath(layout, obj.package, `${basename}.ts`));
|
|
141
|
+
}
|
|
142
|
+
/** Extension-less module specifier for a template-derived file
|
|
143
|
+
* (`<Name>.extractor` / `<Name>.render`) — always flat (the generators do not
|
|
144
|
+
* fold these by package). */
|
|
145
|
+
function templateModulePath(basename) {
|
|
146
|
+
return basename;
|
|
147
|
+
}
|
|
148
|
+
function stripTs(path) {
|
|
149
|
+
return path.endsWith(".ts") ? path.slice(0, -3) : path;
|
|
150
|
+
}
|
|
151
|
+
// ---------------------------------------------------------------------------
|
|
152
|
+
// Entities.
|
|
153
|
+
// ---------------------------------------------------------------------------
|
|
154
|
+
/** Mirror of the queries generator's filter (queries-file.ts `skipNonQueryable`
|
|
155
|
+
* = `subType !== OBJECT_SUBTYPE_VALUE && !isTphSubtype`). A queryable entity is
|
|
156
|
+
* any non-value, non-TPH-subtype object:
|
|
157
|
+
* • Value objects have no primary identity → the queries/routes/validation
|
|
158
|
+
* generators emit no CRUD for them.
|
|
159
|
+
* • TPH subtypes (@discriminatorValue under a @discriminator base) emit no
|
|
160
|
+
* standalone queries/routes file — their surface lives in the discriminator
|
|
161
|
+
* BASE's polymorphic file (routes-file.ts:27 + queries-file.ts:21-22).
|
|
162
|
+
* Either way the object contributes only a model symbol here. (The TPH base's
|
|
163
|
+
* per-subtype polymorphic helpers + subpaths are a documented deferral — see
|
|
164
|
+
* the module header.) */
|
|
165
|
+
function isQueryable(obj) {
|
|
166
|
+
return obj.subType !== OBJECT_SUBTYPE_VALUE && !isTphSubtype(obj);
|
|
167
|
+
}
|
|
168
|
+
/** Whether the routes generator emits REST routes for this entity. It filters
|
|
169
|
+
* out @emitRoutes:false (routes-file.ts:27), unlike the queries + validator
|
|
170
|
+
* generators which always emit. So REST symbols are gated separately from the
|
|
171
|
+
* other queryable kinds. */
|
|
172
|
+
function emitsRoutes(obj) {
|
|
173
|
+
return obj.ownAttr(CODEGEN_ATTR_EMIT_ROUTES) !== false;
|
|
174
|
+
}
|
|
175
|
+
function buildEntityUnit(obj, ctx, root, layout, relationMap, includeHono) {
|
|
176
|
+
const name = obj.name;
|
|
177
|
+
const symbols = [];
|
|
178
|
+
// The entity MODEL + its zod schemas are emitted into `<Name>.ts` (entity-file
|
|
179
|
+
// composes drizzle-schema + inferred-types + zod-validators), so model AND
|
|
180
|
+
// validation share the entity module's importPath.
|
|
181
|
+
const entityMod = entityModulePath(layout, obj, name);
|
|
182
|
+
// --- model: the entity type/const the entity-file generator emits (bare name). ---
|
|
183
|
+
symbols.push({
|
|
184
|
+
name,
|
|
185
|
+
kind: "model",
|
|
186
|
+
importPath: entityMod,
|
|
187
|
+
signature: `interface ${name}`,
|
|
188
|
+
returns: name,
|
|
189
|
+
usage: `The typed shape of a ${name} row, generated from its metadata.`,
|
|
190
|
+
fields: modelFieldShapes(obj),
|
|
191
|
+
});
|
|
192
|
+
if (isQueryable(obj)) {
|
|
193
|
+
symbols.push(...dataAccessSymbols(obj, ctx, root, layout));
|
|
194
|
+
symbols.push(...validationSymbols(obj, entityMod));
|
|
195
|
+
// REST is additionally gated: @emitRoutes:false suppresses routes only.
|
|
196
|
+
if (emitsRoutes(obj)) {
|
|
197
|
+
symbols.push(...restSymbols(obj, layout));
|
|
198
|
+
// The OPT-IN Hono variant mounts the SAME CRUD verbs under the SAME
|
|
199
|
+
// @emitRoutes filter — documented only when the adopter wired it.
|
|
200
|
+
if (includeHono)
|
|
201
|
+
symbols.push(...restHonoSymbols(obj, layout));
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
// --- relation: the drizzle relations() export, when the resolver derives a
|
|
205
|
+
// relations() block for this entity (1:N belongs-to + inverse many, M:N
|
|
206
|
+
// @through). Independent of isQueryable — a relations() block is emitted by
|
|
207
|
+
// the entity file regardless. ---
|
|
208
|
+
const relationSym = relationSymbol(obj, entityMod, relationMap);
|
|
209
|
+
if (relationSym !== undefined)
|
|
210
|
+
symbols.push(relationSym);
|
|
211
|
+
// --- callable: call<Entity>, only when the entity is backed by a stored-proc /
|
|
212
|
+
// table-function source (matching the callable generator's isCallableEntity
|
|
213
|
+
// filter). ---
|
|
214
|
+
const callableSym = callableSymbol(obj, root, layout);
|
|
215
|
+
if (callableSym !== undefined)
|
|
216
|
+
symbols.push(callableSym);
|
|
217
|
+
const unit = {
|
|
218
|
+
node: name,
|
|
219
|
+
package: effectivePackage(obj),
|
|
220
|
+
nodeKind: "entity",
|
|
221
|
+
symbols,
|
|
222
|
+
};
|
|
223
|
+
// The worked example reads the row back by its REAL primary key (e.g.
|
|
224
|
+
// `created.code`), not a hard-coded `id` — reuse the same getPkInfo the
|
|
225
|
+
// data-access symbols were named from so the example is accurate-by-
|
|
226
|
+
// construction for any PK field name.
|
|
227
|
+
const pkName = getPkInfo(obj, ctx).fieldName;
|
|
228
|
+
const example = entityExample(name, pkName, symbols);
|
|
229
|
+
if (example !== undefined)
|
|
230
|
+
unit.example = example;
|
|
231
|
+
return unit;
|
|
232
|
+
}
|
|
233
|
+
/** The CRUD helpers templates/queries.ts emits, named via the SHARED naming
|
|
234
|
+
* helpers the template itself uses (so the names cannot drift). The PK field +
|
|
235
|
+
* TS type come from the real getPkInfo.
|
|
236
|
+
*
|
|
237
|
+
* TPH discriminator BASE divergence: when `obj` is a discriminator base, the
|
|
238
|
+
* queries generator does NOT emit standalone create<Base>/update<Base>/
|
|
239
|
+
* delete<Base>ById — a base row can't be inserted without choosing a concrete
|
|
240
|
+
* subtype, so write helpers are emitted PER CONCRETE SUBTYPE (create<Sub> …),
|
|
241
|
+
* not on the base. The base file emits only the polymorphic reads find<Base>ById
|
|
242
|
+
* + list<Base>s. Documenting create<Base>/update<Base>/delete<Base>ById would be
|
|
243
|
+
* OVER-documentation (the api-docs accuracy gate catches exactly this). The
|
|
244
|
+
* per-subtype write helpers themselves are a tracked deferral (module header),
|
|
245
|
+
* so we under-document (allowed) rather than invent names. */
|
|
246
|
+
function dataAccessSymbols(obj, ctx, root, layout) {
|
|
247
|
+
const name = obj.name;
|
|
248
|
+
const { fieldName: pk, tsType: pkType } = getPkInfo(obj, ctx);
|
|
249
|
+
// All CRUD helpers are emitted into `<Name>.queries.ts` (queries-file.ts).
|
|
250
|
+
const mod = entityModulePath(layout, obj, `${name}.queries`);
|
|
251
|
+
const find = findByIdFnName(name);
|
|
252
|
+
const list = listFnName(name);
|
|
253
|
+
const create = createFnName(name);
|
|
254
|
+
const update = updateFnName(name);
|
|
255
|
+
const del = deleteByIdFnName(name);
|
|
256
|
+
const reads = [
|
|
257
|
+
{
|
|
258
|
+
name: find,
|
|
259
|
+
kind: "data-access",
|
|
260
|
+
importPath: mod,
|
|
261
|
+
signature: `${find}(db: Db, ${pk}: ${pkType}): Promise<${name} | null>`,
|
|
262
|
+
params: [`db: Db`, `${pk}: ${pkType}`],
|
|
263
|
+
returns: `Promise<${name} | null>`,
|
|
264
|
+
usage: `Fetch a single ${name} by its primary key; null when not found.`,
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
name: list,
|
|
268
|
+
kind: "data-access",
|
|
269
|
+
importPath: mod,
|
|
270
|
+
signature: `${list}(db: Db, opts?: { limit?: number; offset?: number }): Promise<${name}[]>`,
|
|
271
|
+
params: [`db: Db`, `opts?: { limit?: number; offset?: number }`],
|
|
272
|
+
returns: `Promise<${name}[]>`,
|
|
273
|
+
usage: `List ${name} rows with optional limit/offset paging.`,
|
|
274
|
+
},
|
|
275
|
+
];
|
|
276
|
+
// A TPH discriminator base emits ONLY the polymorphic reads — the write
|
|
277
|
+
// helpers are per concrete subtype (create<Sub> …), not on the base.
|
|
278
|
+
if (isTphDiscriminatorBase(obj, root)) {
|
|
279
|
+
return reads;
|
|
280
|
+
}
|
|
281
|
+
// Create/update payload shapes — the EXACT InsertSchema/UpdateSchema field sets
|
|
282
|
+
// (api-field-shape reuses the zod emitter's own walk), so `data: unknown`'s
|
|
283
|
+
// real shape is documented + gate-verified against the emitted schema.
|
|
284
|
+
const createShape = createFieldShapes(obj);
|
|
285
|
+
const updateShape = updateFieldShapes(obj);
|
|
286
|
+
return [
|
|
287
|
+
...reads,
|
|
288
|
+
{
|
|
289
|
+
name: create,
|
|
290
|
+
kind: "data-access",
|
|
291
|
+
importPath: mod,
|
|
292
|
+
signature: `${create}(db: Db, data: unknown): Promise<${name}>`,
|
|
293
|
+
params: [`db: Db`, `data: unknown`],
|
|
294
|
+
returns: `Promise<${name}>`,
|
|
295
|
+
throws: `ZodError when data fails ${name}InsertSchema validation.`,
|
|
296
|
+
usage: `Validate (via ${name}InsertSchema) and insert a new ${name}.`,
|
|
297
|
+
fields: createShape,
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
name: update,
|
|
301
|
+
kind: "data-access",
|
|
302
|
+
importPath: mod,
|
|
303
|
+
signature: `${update}(db: Db, ${pk}: ${pkType}, data: unknown): Promise<${name} | null>`,
|
|
304
|
+
params: [`db: Db`, `${pk}: ${pkType}`, `data: unknown`],
|
|
305
|
+
returns: `Promise<${name} | null>`,
|
|
306
|
+
throws: `ZodError when data fails the partial ${name}InsertSchema validation.`,
|
|
307
|
+
usage: `Partially update an existing ${name} by primary key; null when not found.`,
|
|
308
|
+
fields: updateShape,
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
name: del,
|
|
312
|
+
kind: "data-access",
|
|
313
|
+
importPath: mod,
|
|
314
|
+
signature: `${del}(db: Db, ${pk}: ${pkType}): Promise<boolean>`,
|
|
315
|
+
params: [`db: Db`, `${pk}: ${pkType}`],
|
|
316
|
+
returns: `Promise<boolean>`,
|
|
317
|
+
usage: `Delete a ${name} by primary key; true when a row was removed.`,
|
|
318
|
+
},
|
|
319
|
+
];
|
|
320
|
+
}
|
|
321
|
+
/** The two zod schemas the validator generator emits per entity. The route +
|
|
322
|
+
* queries generators import these exact names (<Name>InsertSchema /
|
|
323
|
+
* <Name>UpdateSchema), so the spelling is verified against their usage. */
|
|
324
|
+
function validationSymbols(obj, entityMod) {
|
|
325
|
+
const name = obj.name;
|
|
326
|
+
// The zod schemas are composed INTO the entity file (entity-file.ts calls
|
|
327
|
+
// renderZodValidators), so they import from the same `<Name>` module. The
|
|
328
|
+
// documented field shapes ARE those schemas' accepted shapes.
|
|
329
|
+
return [
|
|
330
|
+
{
|
|
331
|
+
name: `${name}InsertSchema`,
|
|
332
|
+
kind: "validation",
|
|
333
|
+
importPath: entityMod,
|
|
334
|
+
signature: `${name}InsertSchema: ZodType`,
|
|
335
|
+
returns: `ZodType`,
|
|
336
|
+
usage: `Zod schema validating the body of a create<${name}> / POST request (auto-generated PKs excluded).`,
|
|
337
|
+
fields: createFieldShapes(obj),
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
name: `${name}UpdateSchema`,
|
|
341
|
+
kind: "validation",
|
|
342
|
+
importPath: entityMod,
|
|
343
|
+
signature: `${name}UpdateSchema: ZodType`,
|
|
344
|
+
returns: `ZodType`,
|
|
345
|
+
usage: `Zod schema validating the body of an update / PATCH request (all fields optional).`,
|
|
346
|
+
fields: updateFieldShapes(obj),
|
|
347
|
+
},
|
|
348
|
+
];
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* The REST endpoints the routes generator mounts for an entity. The routes
|
|
352
|
+
* generator does NOT emit one function per verb — it emits a single
|
|
353
|
+
* `<name>Routes(fastify)` handler that mounts the standard CRUD verb set at the
|
|
354
|
+
* entity's $path via mountCrudRoutes (or the read-only subset via
|
|
355
|
+
* mountReadOnlyCrudRoutes for a projection). We reuse resourcePath() — the same
|
|
356
|
+
* function entity-constants.ts uses to compute $path — so the documented paths
|
|
357
|
+
* match the generated routes exactly. The verb→path mapping mirrors the runtime
|
|
358
|
+
* mountCrudRoutes contract referenced in routes-file.ts's comments.
|
|
359
|
+
*/
|
|
360
|
+
function restSymbols(obj, layout) {
|
|
361
|
+
const name = obj.name;
|
|
362
|
+
const path = resourcePath(obj);
|
|
363
|
+
const readOnly = isProjection(obj);
|
|
364
|
+
// REST endpoints are not importable functions — to WIRE them an adopter
|
|
365
|
+
// imports the entity's route registrar (`<entity>Routes`) from the routes
|
|
366
|
+
// module the routes generator emits (`<Name>.routes.ts`) and mounts it:
|
|
367
|
+
// import { <registrar> } from "<routesMod>"; await <registrar>(fastify);
|
|
368
|
+
// Every endpoint of one entity shares that single registrar import.
|
|
369
|
+
const routesMod = entityModulePath(layout, obj, `${name}.routes`);
|
|
370
|
+
const registrar = routesHandlerName(name);
|
|
371
|
+
// The REST bodies/responses ARE the same gate-verified shapes: a GET returns
|
|
372
|
+
// the model shape, POST takes the create shape, PATCH takes the update shape.
|
|
373
|
+
const modelShape = modelFieldShapes(obj);
|
|
374
|
+
const createShape = createFieldShapes(obj);
|
|
375
|
+
const updateShape = updateFieldShapes(obj);
|
|
376
|
+
const ep = restEndpointFactory("rest", routesMod, registrar);
|
|
377
|
+
const symbols = [
|
|
378
|
+
ep("GET", path, `List ${name} (supports filter/sort/paging query params).`, modelShape),
|
|
379
|
+
ep("GET", `${path}/:id`, `Fetch a single ${name} by id (404 when not found).`, modelShape),
|
|
380
|
+
];
|
|
381
|
+
if (!readOnly) {
|
|
382
|
+
symbols.push(ep("POST", path, `Create a ${name} (body validated by ${name}InsertSchema).`, createShape), ep("PATCH", `${path}/:id`, `Partially update a ${name} by id (body validated by ${name}UpdateSchema).`, updateShape), ep("DELETE", `${path}/:id`, `Delete a ${name} by id.`));
|
|
383
|
+
}
|
|
384
|
+
return symbols;
|
|
385
|
+
}
|
|
386
|
+
/** Build a REST endpoint symbol factory bound to one route surface (kind +
|
|
387
|
+
* route module + registrar). The Fastify and Hono REST builders share this so a
|
|
388
|
+
* `METHOD /path` endpoint is shaped one way; only the surface-level kind/module/
|
|
389
|
+
* registrar and the per-endpoint description differ between them. */
|
|
390
|
+
function restEndpointFactory(kind, routesMod, registrar) {
|
|
391
|
+
return (method, p, desc, fields) => {
|
|
392
|
+
const sym = {
|
|
393
|
+
name: `${method} ${p}`,
|
|
394
|
+
kind,
|
|
395
|
+
importPath: routesMod,
|
|
396
|
+
registrar,
|
|
397
|
+
signature: `${method} ${p}`,
|
|
398
|
+
usage: desc,
|
|
399
|
+
};
|
|
400
|
+
if (fields !== undefined)
|
|
401
|
+
sym.fields = fields;
|
|
402
|
+
return sym;
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
// ---------------------------------------------------------------------------
|
|
406
|
+
// T5: relations / callable / Hono (entity-level), then prompt (template.prompt).
|
|
407
|
+
// ---------------------------------------------------------------------------
|
|
408
|
+
/**
|
|
409
|
+
* The drizzle relations() export the entity file composes for an entity that has
|
|
410
|
+
* relations. The relations-block generator emits
|
|
411
|
+
* `export const <var>Relations = relations(<var>, ({ one, many }) => ({ … }))`
|
|
412
|
+
* where `<var> = variableNameFromEntity(name)` (so `Post` → `postRelations`) and
|
|
413
|
+
* the body is one accessor per RelationEntry the resolver derived — `author:
|
|
414
|
+
* one(User, …)` for a 1:N belongs-to, `tags: many(PostTag)` for an M:N @through,
|
|
415
|
+
* and the inverse `posts: many(…)` registered on the target. We document the
|
|
416
|
+
* EXPORT (the importable symbol) and ride each navigation in the field shape
|
|
417
|
+
* (name → cardinality-tagged target), all derived from the SAME RelationMap the
|
|
418
|
+
* generator emits from — never invented. Returns undefined when the resolver
|
|
419
|
+
* derived no relations() block for this entity (the entity file emits none).
|
|
420
|
+
*/
|
|
421
|
+
function relationSymbol(obj, entityMod, relationMap) {
|
|
422
|
+
const entries = relationMap.get(obj.name);
|
|
423
|
+
if (entries === undefined || entries.length === 0)
|
|
424
|
+
return undefined;
|
|
425
|
+
const varName = variableNameFromEntity(obj.name);
|
|
426
|
+
const relationsExport = `${varName}Relations`;
|
|
427
|
+
// One field-shape row per navigation: name is the relation accessor key the
|
|
428
|
+
// block emits; type carries the cardinality + the entity you traverse to; note
|
|
429
|
+
// explains how to query it via the relational API.
|
|
430
|
+
const navFields = entries.map((e) => relationNavField(e));
|
|
431
|
+
return {
|
|
432
|
+
name: relationsExport,
|
|
433
|
+
kind: "relation",
|
|
434
|
+
importPath: entityMod,
|
|
435
|
+
signature: `const ${relationsExport}: Relations<"${tableName(varName)}", …>`,
|
|
436
|
+
returns: relationsExport,
|
|
437
|
+
usage: `Drizzle relations() for ${obj.name} — register it with your schema, then ` +
|
|
438
|
+
`traverse via the relational query API (db.query.${varName}.findMany({ with: { … } })).`,
|
|
439
|
+
fields: navFields,
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
/** Drizzle's relations() first arg is the table var; we only need a stable label
|
|
443
|
+
* here for the signature, so reuse the entity's table var name. */
|
|
444
|
+
function tableName(varName) {
|
|
445
|
+
return varName;
|
|
446
|
+
}
|
|
447
|
+
/** A field-shape row describing ONE relation navigation: accessor name + a
|
|
448
|
+
* cardinality-tagged target "type" + a how-to-traverse note. Mirrors the
|
|
449
|
+
* RelationEntry the resolver produced (1:N one() / M:N many(junction) / inverse
|
|
450
|
+
* many()), never restated. */
|
|
451
|
+
function relationNavField(e) {
|
|
452
|
+
if (e.cardinality === "one") {
|
|
453
|
+
return {
|
|
454
|
+
name: e.name,
|
|
455
|
+
type: `${e.targetEntity} (1:1 / N:1)`,
|
|
456
|
+
optional: true,
|
|
457
|
+
note: `belongs-to → ${e.targetEntity}${e.fkField ? ` via ${e.fkField}` : ""}`,
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
// many — either a M:N through a junction or a 1:N inverse.
|
|
461
|
+
if (e.junctionEntity !== undefined) {
|
|
462
|
+
return {
|
|
463
|
+
name: e.name,
|
|
464
|
+
type: `${e.targetEntity}[] (M:N via ${e.junctionEntity})`,
|
|
465
|
+
optional: true,
|
|
466
|
+
note: `many-to-many → ${e.targetEntity} through ${e.junctionEntity}`,
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
return {
|
|
470
|
+
name: e.name,
|
|
471
|
+
type: `${e.targetEntity}[] (1:N)`,
|
|
472
|
+
optional: true,
|
|
473
|
+
note: `has-many → ${e.targetEntity}`,
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* The callable wrapper `call<Entity>` the callable generator emits for an entity
|
|
478
|
+
* backed by a stored-proc / table-function source (isCallableEntity — the SAME
|
|
479
|
+
* filter the generator factory uses). The wrapper takes `(db, args: <argsVO>)`
|
|
480
|
+
* (or just `(db)` for a zero-arg proc) and returns `Promise<<Entity>[]>`, emitted
|
|
481
|
+
* into a FLAT-OR-PACKAGE-FOLDED `<Entity>.callable.ts` (entityOutputPath, same as
|
|
482
|
+
* the generator). Returns undefined for a non-callable entity (no file emitted).
|
|
483
|
+
*/
|
|
484
|
+
function callableSymbol(obj, root, layout) {
|
|
485
|
+
if (!isCallableEntity(obj))
|
|
486
|
+
return undefined;
|
|
487
|
+
const name = obj.name;
|
|
488
|
+
const fn = `call${name}`;
|
|
489
|
+
const mod = entityModulePath(layout, obj, `${name}.callable`);
|
|
490
|
+
// Resolve the @parameterRef args value-object name (same resolution the
|
|
491
|
+
// callable template uses) to type the `args` param — undefined ⇒ zero-arg proc.
|
|
492
|
+
const argsRef = callableArgsRef(obj, root);
|
|
493
|
+
const signature = argsRef
|
|
494
|
+
? `${fn}(db: NodePgDatabase, args: ${argsRef}): Promise<${name}[]>`
|
|
495
|
+
: `${fn}(db: NodePgDatabase): Promise<${name}[]>`;
|
|
496
|
+
const params = argsRef
|
|
497
|
+
? [`db: NodePgDatabase`, `args: ${argsRef}`]
|
|
498
|
+
: [`db: NodePgDatabase`];
|
|
499
|
+
return {
|
|
500
|
+
name: fn,
|
|
501
|
+
kind: "callable",
|
|
502
|
+
importPath: mod,
|
|
503
|
+
signature,
|
|
504
|
+
params,
|
|
505
|
+
returns: `${name}[]`,
|
|
506
|
+
usage: `Call the ${name} stored procedure / table function and parse each row into a typed ${name}.`,
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
/** The @parameterRef value-object name for a callable entity's source, or
|
|
510
|
+
* undefined for a zero-arg proc. Mirrors the callable template's resolution
|
|
511
|
+
* (the source child's SOURCE_ATTR_PARAMETER_REF). */
|
|
512
|
+
function callableArgsRef(obj, root) {
|
|
513
|
+
for (const child of obj.ownChildren()) {
|
|
514
|
+
if (child.type !== TYPE_SOURCE)
|
|
515
|
+
continue;
|
|
516
|
+
const ref = child.ownAttr(SOURCE_ATTR_PARAMETER_REF);
|
|
517
|
+
if (typeof ref === "string" && ref !== "") {
|
|
518
|
+
// Only count it when it resolves to a value object (the template's guard).
|
|
519
|
+
const vo = root
|
|
520
|
+
.ownChildren()
|
|
521
|
+
.find((c) => c.subType === OBJECT_SUBTYPE_VALUE && refMatchesObject(c, ref));
|
|
522
|
+
if (vo !== undefined)
|
|
523
|
+
return ref;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
return undefined;
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* The OPT-IN Hono CRUD registrar `register<Entity>Routes(app, deps)` the
|
|
530
|
+
* routesFileHono generator emits into `<Entity>.routes.hono.ts`. Parallels the
|
|
531
|
+
* Fastify restSymbols (same verb set, same resourcePath, read-only for
|
|
532
|
+
* projections) but carries the Hono registrar name + import module. Documented
|
|
533
|
+
* only when the adopter opts into the Hono variant (includeHonoRoutes).
|
|
534
|
+
*/
|
|
535
|
+
function restHonoSymbols(obj, layout) {
|
|
536
|
+
const name = obj.name;
|
|
537
|
+
const path = resourcePath(obj);
|
|
538
|
+
const readOnly = isProjection(obj);
|
|
539
|
+
const honoMod = entityModulePath(layout, obj, `${name}.routes.hono`);
|
|
540
|
+
const registrar = `register${name}Routes`;
|
|
541
|
+
const modelShape = modelFieldShapes(obj);
|
|
542
|
+
const createShape = createFieldShapes(obj);
|
|
543
|
+
const updateShape = updateFieldShapes(obj);
|
|
544
|
+
const ep = restEndpointFactory("rest-hono", honoMod, registrar);
|
|
545
|
+
const symbols = [
|
|
546
|
+
ep("GET", path, `[Hono] List ${name} (filter/sort/paging query params).`, modelShape),
|
|
547
|
+
ep("GET", `${path}/:id`, `[Hono] Fetch a single ${name} by id (404 when not found).`, modelShape),
|
|
548
|
+
];
|
|
549
|
+
if (!readOnly) {
|
|
550
|
+
symbols.push(ep("POST", path, `[Hono] Create a ${name} (body validated by ${name}InsertSchema).`, createShape), ep("PATCH", `${path}/:id`, `[Hono] Partially update a ${name} by id (body validated by ${name}UpdateSchema).`, updateShape), ep("DELETE", `${path}/:id`, `[Hono] Delete a ${name} by id.`));
|
|
551
|
+
}
|
|
552
|
+
return symbols;
|
|
553
|
+
}
|
|
554
|
+
// ---------------------------------------------------------------------------
|
|
555
|
+
// template.output nodes.
|
|
556
|
+
// ---------------------------------------------------------------------------
|
|
557
|
+
function templateOutputs(root) {
|
|
558
|
+
return root
|
|
559
|
+
.ownChildren()
|
|
560
|
+
.filter((c) => c.type === TYPE_TEMPLATE && c.subType === TEMPLATE_SUBTYPE_OUTPUT);
|
|
561
|
+
}
|
|
562
|
+
function buildTemplateUnit(tmpl, root, _layout) {
|
|
563
|
+
const name = tmpl.name;
|
|
564
|
+
const symbols = [];
|
|
565
|
+
// extractor + render-helper generators emit FLAT `<Name>.extractor.ts` /
|
|
566
|
+
// `<Name>.render.ts` (no package folding), so importPath ignores layout.
|
|
567
|
+
const extractorMod = templateModulePath(`${name}.extractor`);
|
|
568
|
+
const renderMod = templateModulePath(`${name}.render`);
|
|
569
|
+
const payloadRef = tmpl.ownAttr(TEMPLATE_ATTR_PAYLOAD_REF);
|
|
570
|
+
const payload = typeof payloadRef === "string" ? payloadRef : undefined;
|
|
571
|
+
const format = (tmpl.ownAttr(TEMPLATE_ATTR_FORMAT) ?? "text").toLowerCase();
|
|
572
|
+
const kind = (tmpl.ownAttr(TEMPLATE_ATTR_KIND) ?? TEMPLATE_KIND_DEFAULT).toLowerCase();
|
|
573
|
+
// --- extractor: only json/xml output-parsers expose the extract API (matches
|
|
574
|
+
// extractor-file.ts's `if (format !== "json" && format !== "xml") continue`). ---
|
|
575
|
+
if (payload && (format === "json" || format === "xml")) {
|
|
576
|
+
const extract = `extract${name}`;
|
|
577
|
+
const extractLenient = `extractLenient${name}`;
|
|
578
|
+
// The extractor's strict return IS the @payloadRef value-object's interface —
|
|
579
|
+
// document its field shape (same VO field walk the payload interface emitter
|
|
580
|
+
// uses) so an agent sees what `extract<Name>` yields, not just the type name.
|
|
581
|
+
const payloadShape = payloadFieldShapes(root, payload);
|
|
582
|
+
const extractSym = {
|
|
583
|
+
name: extract,
|
|
584
|
+
kind: "extractor",
|
|
585
|
+
importPath: extractorMod,
|
|
586
|
+
signature: `${extract}(root: MetaRoot, text: string): ${payload}`,
|
|
587
|
+
params: [`root: MetaRoot`, `text: string`],
|
|
588
|
+
returns: payload,
|
|
589
|
+
throws: `Error when a @required field is lost (the strict opt-in gate).`,
|
|
590
|
+
usage: `Parse dirty LLM ${format} text into a strict, fully-typed ${payload} graph.`,
|
|
591
|
+
};
|
|
592
|
+
if (payloadShape !== undefined)
|
|
593
|
+
extractSym.fields = payloadShape;
|
|
594
|
+
symbols.push(extractSym, {
|
|
595
|
+
name: extractLenient,
|
|
596
|
+
kind: "extractor",
|
|
597
|
+
importPath: extractorMod,
|
|
598
|
+
signature: `${extractLenient}(root: MetaRoot, text: string): ExtractionResult<${name}Extracted>`,
|
|
599
|
+
params: [`root: MetaRoot`, `text: string`],
|
|
600
|
+
returns: `ExtractionResult<${name}Extracted>`,
|
|
601
|
+
usage: `Never-throwing extract; inspect report for lost/defaulted fields.`,
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
// --- render: render<Name>; document → string, email → EmailDocument
|
|
605
|
+
// (matches render-helper.ts's @kind branch). Render is emitted for any
|
|
606
|
+
// @format (the helper wraps render() regardless), so it is NOT format-gated. ---
|
|
607
|
+
if (payload) {
|
|
608
|
+
const render = `render${name}`;
|
|
609
|
+
const isEmail = kind === TEMPLATE_KIND_EMAIL;
|
|
610
|
+
const returns = isEmail ? "EmailDocument" : "string";
|
|
611
|
+
symbols.push({
|
|
612
|
+
name: render,
|
|
613
|
+
kind: "render",
|
|
614
|
+
importPath: renderMod,
|
|
615
|
+
signature: `${render}(payload: ${payload}, provider: Provider): ${returns}`,
|
|
616
|
+
params: [`payload: ${payload}`, `provider: Provider`],
|
|
617
|
+
returns,
|
|
618
|
+
usage: isEmail
|
|
619
|
+
? `Render the ${name} email (subject + bodies) from a typed ${payload} payload.`
|
|
620
|
+
: `Render the ${name} document from a typed ${payload} payload.`,
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
const unit = {
|
|
624
|
+
node: name,
|
|
625
|
+
package: effectivePackage(tmpl),
|
|
626
|
+
nodeKind: "template",
|
|
627
|
+
symbols,
|
|
628
|
+
};
|
|
629
|
+
const example = templateExample(name, symbols);
|
|
630
|
+
if (example !== undefined)
|
|
631
|
+
unit.example = example;
|
|
632
|
+
return unit;
|
|
633
|
+
}
|
|
634
|
+
// ---------------------------------------------------------------------------
|
|
635
|
+
// template.prompt nodes — the prompt-render handle.
|
|
636
|
+
// ---------------------------------------------------------------------------
|
|
637
|
+
/** TOP-LEVEL template.prompt nodes — matching the promptRender generator's own
|
|
638
|
+
* collection (`ctx.loadedRoot.ownChildren()` filtered to TYPE_TEMPLATE +
|
|
639
|
+
* TEMPLATE_SUBTYPE_PROMPT). A prompt nested INSIDE an entity is not collected by
|
|
640
|
+
* the generator, so the builder must not document it either (no over-doc). */
|
|
641
|
+
function templatePrompts(root) {
|
|
642
|
+
return root
|
|
643
|
+
.ownChildren()
|
|
644
|
+
.filter((c) => c.type === TYPE_TEMPLATE && c.subType === TEMPLATE_SUBTYPE_PROMPT);
|
|
645
|
+
}
|
|
646
|
+
/**
|
|
647
|
+
* The render handle promptRender() emits per template.prompt — generateRenderHandle
|
|
648
|
+
* (payload-codegen.ts) produces
|
|
649
|
+
* `export function render<Name>(payload: <payloadRef>, provider: Provider): string`
|
|
650
|
+
* and promptRender aggregates every handle into a SINGLE file (default outFile
|
|
651
|
+
* "prompts.ts"), so the import module is the bare `prompts` (no package folding;
|
|
652
|
+
* the generator writes the outFile verbatim). The payload field shape is the
|
|
653
|
+
* @payloadRef VO interface (same walk the payload-interface emitter uses), so an
|
|
654
|
+
* agent sees what to pass.
|
|
655
|
+
*/
|
|
656
|
+
function buildPromptUnit(tmpl, root) {
|
|
657
|
+
const name = tmpl.name;
|
|
658
|
+
const symbols = [];
|
|
659
|
+
// promptRender writes the aggregated handles to `outFile` (default "prompts.ts").
|
|
660
|
+
const promptsMod = templateModulePath("prompts");
|
|
661
|
+
const payloadRef = tmpl.ownAttr(TEMPLATE_ATTR_PAYLOAD_REF);
|
|
662
|
+
const payload = typeof payloadRef === "string" ? payloadRef : undefined;
|
|
663
|
+
if (payload) {
|
|
664
|
+
const render = `render${name}`;
|
|
665
|
+
const sym = {
|
|
666
|
+
name: render,
|
|
667
|
+
kind: "prompt",
|
|
668
|
+
importPath: promptsMod,
|
|
669
|
+
signature: `${render}(payload: ${payload}, provider: Provider): string`,
|
|
670
|
+
params: [`payload: ${payload}`, `provider: Provider`],
|
|
671
|
+
returns: "string",
|
|
672
|
+
usage: `Render the ${name} prompt text from a typed ${payload} payload (ready to send to an LLM).`,
|
|
673
|
+
};
|
|
674
|
+
const payloadShape = payloadFieldShapes(root, payload);
|
|
675
|
+
if (payloadShape !== undefined)
|
|
676
|
+
sym.fields = payloadShape;
|
|
677
|
+
symbols.push(sym);
|
|
678
|
+
}
|
|
679
|
+
return {
|
|
680
|
+
node: name,
|
|
681
|
+
package: effectivePackage(tmpl),
|
|
682
|
+
nodeKind: "template",
|
|
683
|
+
symbols,
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
// ---------------------------------------------------------------------------
|
|
687
|
+
// Worked examples — composed from the symbols already documented above (their
|
|
688
|
+
// real names + importPaths) and the field SHAPES (T2) attached to the payload
|
|
689
|
+
// symbols, with VALUES derived from each field's TS type. Nothing is invented:
|
|
690
|
+
// a symbol that isn't in `symbols` is never called, and an import never names a
|
|
691
|
+
// module a documented symbol doesn't already point at.
|
|
692
|
+
// ---------------------------------------------------------------------------
|
|
693
|
+
/** A sample literal for a documented field, derived from its TS type STRING
|
|
694
|
+
* (T2's `FieldShape.type`) — never from the entity. Enum unions yield a real
|
|
695
|
+
* member; arrays an empty list; scalars a type-appropriate placeholder. */
|
|
696
|
+
function sampleValueForType(type) {
|
|
697
|
+
const t = type.trim();
|
|
698
|
+
// Enum / string-literal union (`"active" | "archived"`): use the first member
|
|
699
|
+
// verbatim so the example is a REAL accepted value.
|
|
700
|
+
const firstLiteral = t.match(/^"([^"]*)"/);
|
|
701
|
+
if (firstLiteral)
|
|
702
|
+
return `"${firstLiteral[1]}"`;
|
|
703
|
+
if (t.endsWith("[]"))
|
|
704
|
+
return "[]";
|
|
705
|
+
if (t === "number")
|
|
706
|
+
return "1";
|
|
707
|
+
if (t === "bigint")
|
|
708
|
+
return "1n";
|
|
709
|
+
if (t === "boolean")
|
|
710
|
+
return "true";
|
|
711
|
+
if (t === "Date")
|
|
712
|
+
return "new Date()";
|
|
713
|
+
if (t === "string")
|
|
714
|
+
return `"…"`;
|
|
715
|
+
// Unknown / object / nested type: a typed-object placeholder keeps the call
|
|
716
|
+
// shape intact without inventing a fake member set.
|
|
717
|
+
return "{}";
|
|
718
|
+
}
|
|
719
|
+
/** Build a `{ field: value; … }` object literal from a payload field shape,
|
|
720
|
+
* using only the REQUIRED fields plus the first optional (so an agent sees a
|
|
721
|
+
* minimal-but-real body) — values derived from each field's TS type. */
|
|
722
|
+
function objectLiteralFromFields(fields) {
|
|
723
|
+
if (fields === undefined || fields.length === 0)
|
|
724
|
+
return "{}";
|
|
725
|
+
const required = fields.filter((f) => !f.optional);
|
|
726
|
+
// If nothing is strictly required, show the first field so the body isn't `{}`.
|
|
727
|
+
const chosen = required.length > 0 ? required : fields.slice(0, 1);
|
|
728
|
+
const parts = chosen.map((f) => `${f.name}: ${sampleValueForType(f.type)}`);
|
|
729
|
+
return `{ ${parts.join(", ")} }`;
|
|
730
|
+
}
|
|
731
|
+
/** One `import { … } from "<mod>"` line per module, deduped, reusing each
|
|
732
|
+
* symbol's OWN importPath (so the example import can't drift from the docs). */
|
|
733
|
+
function importLines(picks) {
|
|
734
|
+
const order = [];
|
|
735
|
+
const byMod = new Map();
|
|
736
|
+
for (const p of picks) {
|
|
737
|
+
let names = byMod.get(p.importPath);
|
|
738
|
+
if (names === undefined) {
|
|
739
|
+
names = [];
|
|
740
|
+
byMod.set(p.importPath, names);
|
|
741
|
+
order.push(p.importPath);
|
|
742
|
+
}
|
|
743
|
+
if (!names.includes(p.name))
|
|
744
|
+
names.push(p.name);
|
|
745
|
+
}
|
|
746
|
+
return order.map((mod) => `import { ${byMod.get(mod).join(", ")} } from "${mod}";`);
|
|
747
|
+
}
|
|
748
|
+
/** A worked create→find→update→delete flow over an entity's documented CRUD
|
|
749
|
+
* helpers. Only emitted when the entity actually carries those data-access
|
|
750
|
+
* symbols (a value object / TPH subtype has only a model → no example).
|
|
751
|
+
*
|
|
752
|
+
* `pkName` is the entity's REAL primary-key field (from getPkInfo) — the
|
|
753
|
+
* find/update/delete calls read it back as `created.<pkName>`, so the example
|
|
754
|
+
* stays accurate-by-construction for an entity whose PK is not named `id`. */
|
|
755
|
+
function entityExample(name, pkName, symbols) {
|
|
756
|
+
const da = (fn) => symbols.find((s) => s.kind === "data-access" && s.name === fn);
|
|
757
|
+
const create = da(createFnName(name));
|
|
758
|
+
const find = da(findByIdFnName(name));
|
|
759
|
+
const update = da(updateFnName(name));
|
|
760
|
+
const del = da(deleteByIdFnName(name));
|
|
761
|
+
// Need at least create+find to have a meaningful worked flow.
|
|
762
|
+
if (create === undefined || find === undefined)
|
|
763
|
+
return undefined;
|
|
764
|
+
const createBody = objectLiteralFromFields(create.fields);
|
|
765
|
+
// The handle returned by create<Name> exposes the row's real PK accessor.
|
|
766
|
+
const createdPk = `created.${pkName}`;
|
|
767
|
+
const picks = [
|
|
768
|
+
{ name: create.name, importPath: create.importPath },
|
|
769
|
+
{ name: find.name, importPath: find.importPath },
|
|
770
|
+
];
|
|
771
|
+
const body = [
|
|
772
|
+
`const created = await ${create.name}(db, ${createBody});`,
|
|
773
|
+
`const found = await ${find.name}(db, ${createdPk});`,
|
|
774
|
+
];
|
|
775
|
+
if (update !== undefined) {
|
|
776
|
+
picks.push({ name: update.name, importPath: update.importPath });
|
|
777
|
+
body.push(`const updated = await ${update.name}(db, ${createdPk}, ${objectLiteralFromFields(update.fields)});`);
|
|
778
|
+
}
|
|
779
|
+
if (del !== undefined) {
|
|
780
|
+
picks.push({ name: del.name, importPath: del.importPath });
|
|
781
|
+
body.push(`const removed = await ${del.name}(db, ${createdPk});`);
|
|
782
|
+
}
|
|
783
|
+
return { imports: importLines(picks), body };
|
|
784
|
+
}
|
|
785
|
+
/** A worked extract / render example for a template unit, over whichever of the
|
|
786
|
+
* two surfaces the template actually exposes (extract is json/xml-gated). */
|
|
787
|
+
function templateExample(name, symbols) {
|
|
788
|
+
const extract = symbols.find((s) => s.kind === "extractor" && s.name === `extract${name}`);
|
|
789
|
+
const renderSym = symbols.find((s) => s.kind === "render" && s.name === `render${name}`);
|
|
790
|
+
if (extract === undefined && renderSym === undefined)
|
|
791
|
+
return undefined;
|
|
792
|
+
const picks = [];
|
|
793
|
+
const body = [];
|
|
794
|
+
if (extract !== undefined) {
|
|
795
|
+
picks.push({ name: extract.name, importPath: extract.importPath });
|
|
796
|
+
body.push(`const extracted = ${extract.name}(root, llmText);`);
|
|
797
|
+
}
|
|
798
|
+
if (renderSym !== undefined) {
|
|
799
|
+
picks.push({ name: renderSym.name, importPath: renderSym.importPath });
|
|
800
|
+
// Render's payload object literal comes from the @payloadRef VO shape the
|
|
801
|
+
// render symbol returns/consumes; reuse the extractor's documented payload
|
|
802
|
+
// fields when present so the example body is real.
|
|
803
|
+
const payloadFields = extract?.fields;
|
|
804
|
+
const payloadLit = objectLiteralFromFields(payloadFields);
|
|
805
|
+
body.push(`const output = ${renderSym.name}(${payloadLit}, provider);`);
|
|
806
|
+
}
|
|
807
|
+
return { imports: importLines(picks), body };
|
|
808
|
+
}
|
|
809
|
+
//# sourceMappingURL=api-model.js.map
|