@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,301 @@
|
|
|
1
|
+
// server/typescript/packages/codegen-ts/src/generators/trace-helper-file.ts
|
|
2
|
+
//
|
|
3
|
+
// Stock generator that emits one <Entity>.trace.ts helper file for each concrete
|
|
4
|
+
// entity that (a) extends LlmCallBase (directly or transitively) and (b) nests a
|
|
5
|
+
// template.prompt with @payloadRef and/or @responseRef.
|
|
6
|
+
//
|
|
7
|
+
// The emitted helper exports an async function `record<Entity>(om, responseMo, input)`
|
|
8
|
+
// that EXTRACTS the typed response VO itself and persists ONE row = base envelope +
|
|
9
|
+
// raw I/O (via buildLlmCallRow) PLUS the typed voRequest/voResponse columns. The
|
|
10
|
+
// helper is typed against the generated payload interfaces (request + response VOs)
|
|
11
|
+
// so call-sites get compile-time checks.
|
|
12
|
+
//
|
|
13
|
+
// NOTE: ObjectManager does not expose its loaded metadata root, so the caller must
|
|
14
|
+
// pass the resolved `responseMo: MetaObject` explicitly. The generated helper
|
|
15
|
+
// documents this in its JSDoc.
|
|
16
|
+
//
|
|
17
|
+
// Consumer wiring (metaobjects.config.ts):
|
|
18
|
+
// generators: [..., entityFile(), queriesFile(), traceHelperFile(), barrel()]
|
|
19
|
+
|
|
20
|
+
import {
|
|
21
|
+
TYPE_OBJECT,
|
|
22
|
+
TYPE_TEMPLATE,
|
|
23
|
+
TEMPLATE_SUBTYPE_PROMPT,
|
|
24
|
+
TEMPLATE_ATTR_PAYLOAD_REF,
|
|
25
|
+
TEMPLATE_ATTR_RESPONSE_REF,
|
|
26
|
+
TEMPLATE_ATTR_FORMAT,
|
|
27
|
+
TEMPLATE_ATTR_TEXT_REF,
|
|
28
|
+
} from "@metaobjectsdev/metadata";
|
|
29
|
+
import type { MetaObject } from "@metaobjectsdev/metadata";
|
|
30
|
+
import {
|
|
31
|
+
type EmittedFile,
|
|
32
|
+
type Generator,
|
|
33
|
+
type GeneratorFactory,
|
|
34
|
+
perEntity,
|
|
35
|
+
} from "../generator.js";
|
|
36
|
+
import { generatePayloadInterfacesBatch } from "../payload-codegen.js";
|
|
37
|
+
import { GENERATED_HEADER } from "../constants.js";
|
|
38
|
+
import { tphDiscriminatorPin } from "../templates/zod-validators.js";
|
|
39
|
+
|
|
40
|
+
/** Short name of the shipped abstract base every trace entity extends. */
|
|
41
|
+
const LLM_CALL_BASE = "LlmCallBase";
|
|
42
|
+
|
|
43
|
+
export interface TraceHelperOpts {
|
|
44
|
+
/** Output directory prefix relative to the target's outDir. Default: "" (root). */
|
|
45
|
+
outDir?: string;
|
|
46
|
+
/** Optional named output target (registry key). Defaults to "default". */
|
|
47
|
+
target?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Walk the super chain looking for a node named LLM_CALL_BASE. */
|
|
51
|
+
function extendsBase(obj: MetaObject): boolean {
|
|
52
|
+
let cur = obj.superResolved;
|
|
53
|
+
while (cur !== undefined) {
|
|
54
|
+
if (cur.name === LLM_CALL_BASE) return true;
|
|
55
|
+
cur = cur.superResolved;
|
|
56
|
+
}
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Capitalise the first character. */
|
|
61
|
+
function pascal(s: string): string {
|
|
62
|
+
return s.length > 0 ? s[0]!.toUpperCase() + s.slice(1) : s;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export const traceHelperFile = function traceHelperFile(opts?: TraceHelperOpts): Generator {
|
|
66
|
+
const dirPrefix = opts?.outDir ? `${opts.outDir.replace(/\/$/, "")}/` : "";
|
|
67
|
+
const generator: Generator = {
|
|
68
|
+
name: "trace-helper",
|
|
69
|
+
generate: perEntity((entity, ctx) => {
|
|
70
|
+
// Only concrete entities derived from LlmCallBase.
|
|
71
|
+
if (entity.isAbstract) return [];
|
|
72
|
+
if (!extendsBase(entity)) return [];
|
|
73
|
+
|
|
74
|
+
// Find the nested template.prompt.
|
|
75
|
+
const prompt = entity.ownChildren().find(
|
|
76
|
+
(c) => c.type === TYPE_TEMPLATE && c.subType === TEMPLATE_SUBTYPE_PROMPT,
|
|
77
|
+
);
|
|
78
|
+
if (prompt === undefined) return [];
|
|
79
|
+
|
|
80
|
+
const payloadRef = prompt.ownAttr(TEMPLATE_ATTR_PAYLOAD_REF);
|
|
81
|
+
const responseRef = prompt.ownAttr(TEMPLATE_ATTR_RESPONSE_REF);
|
|
82
|
+
|
|
83
|
+
// @responseRef types the result; @payloadRef types the request. Both gate
|
|
84
|
+
// the helper: the entity must declare voRequest/voResponse field.object
|
|
85
|
+
// columns (authored) for these writes to land, and the prompt's refs name
|
|
86
|
+
// the VOs to render/extract into.
|
|
87
|
+
if (typeof responseRef !== "string") return [];
|
|
88
|
+
if (typeof payloadRef !== "string") return [];
|
|
89
|
+
|
|
90
|
+
const entityName = entity.name;
|
|
91
|
+
const fnName = `record${pascal(entityName)}`;
|
|
92
|
+
|
|
93
|
+
// STI: a trace entity that is a TPH subtype stamps its declared
|
|
94
|
+
// discriminator value as callType and drops callType from the caller input
|
|
95
|
+
// (industry-standard STI — the discriminator is framework-managed).
|
|
96
|
+
const tphPin = tphDiscriminatorPin(entity);
|
|
97
|
+
const sti = tphPin !== undefined;
|
|
98
|
+
const callTypeValue = sti ? tphPin.value : entityName;
|
|
99
|
+
|
|
100
|
+
// Emitted record<Entity> fragments. The caller never supplies the derived
|
|
101
|
+
// status/errorDetail (the helper computes them from extraction), and an STI
|
|
102
|
+
// subtype additionally drops the framework-managed callType discriminator.
|
|
103
|
+
const recordInputOmit = sti
|
|
104
|
+
? `"llmRequest" | "status" | "errorDetail" | "callType"`
|
|
105
|
+
: `"llmRequest" | "status" | "errorDetail"`;
|
|
106
|
+
// The object spread passed to buildLlmCallRow: STI subtypes stamp their
|
|
107
|
+
// discriminator value, all helpers fold in the derived status/errorDetail.
|
|
108
|
+
const recordBuildArg = sti
|
|
109
|
+
? `{ ...input, callType: ${JSON.stringify(callTypeValue)}, status, errorDetail }`
|
|
110
|
+
: `{ ...input, status, errorDetail }`;
|
|
111
|
+
|
|
112
|
+
// Derive the parse format from the prompt's @format attr.
|
|
113
|
+
// "xml" → Format.XML; absent or any other value → Format.JSON.
|
|
114
|
+
const promptFormat = prompt.ownAttr(TEMPLATE_ATTR_FORMAT);
|
|
115
|
+
const formatLiteral = typeof promptFormat === "string" && promptFormat.toLowerCase() === "xml"
|
|
116
|
+
? "Format.XML"
|
|
117
|
+
: "Format.JSON";
|
|
118
|
+
|
|
119
|
+
// Collect VO names for interface emission (dedupe via batch emitter).
|
|
120
|
+
// Both refs are guaranteed strings by the guards above.
|
|
121
|
+
const interfaces = generatePayloadInterfacesBatch(ctx.loadedRoot, [payloadRef, responseRef]);
|
|
122
|
+
|
|
123
|
+
const requestType = payloadRef;
|
|
124
|
+
|
|
125
|
+
// A renderable prompt (carries @textRef) gets an additional call<Entity> helper
|
|
126
|
+
// that renders the prompt text, calls the LLM, then parses + persists a trace row.
|
|
127
|
+
const textRef = prompt.ownAttr(TEMPLATE_ATTR_TEXT_REF);
|
|
128
|
+
const renderable = typeof textRef === "string";
|
|
129
|
+
// Same @format attr, two intentionally different shapes: extract() takes the
|
|
130
|
+
// Format enum (formatLiteral, above → Format.XML/Format.JSON), render() takes the
|
|
131
|
+
// raw format string (renderFormat, here → e.g. "json"/"xml", default "text").
|
|
132
|
+
const renderFormat = typeof promptFormat === "string" ? promptFormat : "text";
|
|
133
|
+
|
|
134
|
+
// Build the import block — all imports MUST stay at the top of the emitted file.
|
|
135
|
+
// `extract` + `render` both live in @metaobjectsdev/render: import them together
|
|
136
|
+
// (one de-duplicated statement) when the prompt is renderable, else import only
|
|
137
|
+
// `extract`.
|
|
138
|
+
const importLines: string[] = [
|
|
139
|
+
`import type { ObjectManager } from "@metaobjectsdev/runtime-ts";`,
|
|
140
|
+
`import {`,
|
|
141
|
+
` LlmCallDbRecorder,`,
|
|
142
|
+
` buildLlmCallRow,`,
|
|
143
|
+
` persistLlmCallRow,`,
|
|
144
|
+
` extractSchemaFor,`,
|
|
145
|
+
` Format,`,
|
|
146
|
+
` type LlmCallInput,`,
|
|
147
|
+
` type LlmCallRow,`,
|
|
148
|
+
`} from "@metaobjectsdev/runtime-ts";`,
|
|
149
|
+
renderable
|
|
150
|
+
? `import { extract, render, type Provider } from "@metaobjectsdev/render";`
|
|
151
|
+
: `import { extract } from "@metaobjectsdev/render";`,
|
|
152
|
+
`import type { MetaObject } from "@metaobjectsdev/metadata";`,
|
|
153
|
+
];
|
|
154
|
+
if (renderable) {
|
|
155
|
+
importLines.push(
|
|
156
|
+
`import {`,
|
|
157
|
+
` runLlmCall,`,
|
|
158
|
+
` type RunLlmCallInput,`,
|
|
159
|
+
` type RunLlmCallDeps,`,
|
|
160
|
+
` type LlmClient,`,
|
|
161
|
+
` type LlmRequest,`,
|
|
162
|
+
` type CostFn,`,
|
|
163
|
+
` type Clock,`,
|
|
164
|
+
` type IdGen,`,
|
|
165
|
+
`} from "@metaobjectsdev/ai-runtime";`,
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const lines: string[] = [
|
|
170
|
+
`// ${GENERATED_HEADER} — DO NOT EDIT.`,
|
|
171
|
+
``,
|
|
172
|
+
...importLines,
|
|
173
|
+
``,
|
|
174
|
+
`// ---- Payload interfaces (inlined) ------------------------------------------`,
|
|
175
|
+
``,
|
|
176
|
+
interfaces.trimEnd(),
|
|
177
|
+
``,
|
|
178
|
+
`// ---- Typed result -----------------------------------------------------------`,
|
|
179
|
+
``,
|
|
180
|
+
`export interface ${entityName}TraceResult {`,
|
|
181
|
+
` status: "ok" | "error";`,
|
|
182
|
+
` errorDetail: string | null;`,
|
|
183
|
+
` /** Parsed response VO, or null when extraction reported a lost-required field. */`,
|
|
184
|
+
` /** Note: voResponse is the plain extracted record typed as the response shape (structural, not an instance). */`,
|
|
185
|
+
` voResponse: ${responseRef} | null;`,
|
|
186
|
+
`}`,
|
|
187
|
+
``,
|
|
188
|
+
`// ---- Record helper ----------------------------------------------------------`,
|
|
189
|
+
``,
|
|
190
|
+
`/**`,
|
|
191
|
+
` * Record a single ${entityName} LLM call: extract the response VO and persist a`,
|
|
192
|
+
` * trace row via ObjectManager regardless of whether extraction succeeded.`,
|
|
193
|
+
` *`,
|
|
194
|
+
` * @param om - ObjectManager wired to the application database.`,
|
|
195
|
+
` * @param responseMo - MetaObject for \`${responseRef}\` (resolve via the loaded`,
|
|
196
|
+
` * metadata root: \`root.findObject("${responseRef}")\`).`,
|
|
197
|
+
` * Passed explicitly because ObjectManager does not expose`,
|
|
198
|
+
` * its loaded metadata root.`,
|
|
199
|
+
` * @param input - LLM call inputs; type \`llmRequest\` as \`${requestType}\`.`,
|
|
200
|
+
` * @param opts - Optional \`redact\` hook applied to the row before persist`,
|
|
201
|
+
` * (scrub PII/secrets on the typed path, same as the generic`,
|
|
202
|
+
` * recordLlmCall/callLlm helpers).`,
|
|
203
|
+
` */`,
|
|
204
|
+
`export async function ${fnName}(`,
|
|
205
|
+
` om: ObjectManager,`,
|
|
206
|
+
` responseMo: MetaObject,`,
|
|
207
|
+
` input: Omit<LlmCallInput, ${recordInputOmit}> & { llmRequest: ${requestType} },`,
|
|
208
|
+
` opts?: { redact?: (row: LlmCallRow) => LlmCallRow },`,
|
|
209
|
+
`): Promise<${entityName}TraceResult> {`,
|
|
210
|
+
` const schema = extractSchemaFor(responseMo, ${formatLiteral});`,
|
|
211
|
+
` const outcome = extract(input.llmResponseText, schema);`,
|
|
212
|
+
` const failed = outcome.report.hasLostRequired();`,
|
|
213
|
+
` const status = failed ? ("error" as const) : ("ok" as const);`,
|
|
214
|
+
' const errorDetail = failed ? `lost required: ${outcome.report.lostRequired().join(", ")}` : null;',
|
|
215
|
+
` const base = buildLlmCallRow(${recordBuildArg});`,
|
|
216
|
+
` const row = { ...base, voRequest: input.llmRequest, voResponse: failed ? null : outcome.data };`,
|
|
217
|
+
` await persistLlmCallRow(new LlmCallDbRecorder(om, "${entityName}"), row, opts?.redact ? { redact: opts.redact } : undefined);`,
|
|
218
|
+
` return { status, errorDetail, voResponse: failed ? null : (outcome.data as ${responseRef}) };`,
|
|
219
|
+
`}`,
|
|
220
|
+
``,
|
|
221
|
+
];
|
|
222
|
+
|
|
223
|
+
if (renderable) {
|
|
224
|
+
const callFn = `call${pascal(entityName)}`;
|
|
225
|
+
lines.push(
|
|
226
|
+
``,
|
|
227
|
+
`// ---- Call helper (GENERATE -> CALL -> record) -------------------------------`,
|
|
228
|
+
``,
|
|
229
|
+
`export interface ${entityName}CallDeps {`,
|
|
230
|
+
` om: ObjectManager;`,
|
|
231
|
+
` responseMo: MetaObject;`,
|
|
232
|
+
` client: LlmClient;`,
|
|
233
|
+
` /** Prompt-TEXT resolver for render() (NOT the LLM client). */`,
|
|
234
|
+
` provider: Provider;`,
|
|
235
|
+
` model: string;`,
|
|
236
|
+
` system?: string;`,
|
|
237
|
+
` params?: Record<string, unknown>;`,
|
|
238
|
+
` cost?: CostFn;`,
|
|
239
|
+
` clock?: Clock;`,
|
|
240
|
+
` ids?: IdGen;`,
|
|
241
|
+
` traceId?: string;`,
|
|
242
|
+
` parentSpanId?: string;`,
|
|
243
|
+
` sessionId?: string;`,
|
|
244
|
+
` /** Optional row-redaction hook applied before persist (scrub PII/secrets). */`,
|
|
245
|
+
` redact?: (row: LlmCallRow) => LlmCallRow;`,
|
|
246
|
+
`}`,
|
|
247
|
+
``,
|
|
248
|
+
`/**`,
|
|
249
|
+
` * Render the ${entityName} prompt, call the LLM, then parse + persist a trace`,
|
|
250
|
+
` * row (finally-style: a call/parse failure still writes a row).`,
|
|
251
|
+
` */`,
|
|
252
|
+
`export async function ${callFn}(`,
|
|
253
|
+
` payload: ${requestType},`,
|
|
254
|
+
` deps: ${entityName}CallDeps,`,
|
|
255
|
+
`): Promise<${entityName}TraceResult> {`,
|
|
256
|
+
` const prompt = render({ ref: ${JSON.stringify(textRef)}, payload, format: ${JSON.stringify(renderFormat)}, provider: deps.provider });`,
|
|
257
|
+
// Build request/input/deps conditionally so that an absent optional (T |
|
|
258
|
+
// undefined) is never assigned to an optional T? property — required for
|
|
259
|
+
// exactOptionalPropertyTypes-strict consumer projects.
|
|
260
|
+
` const request: LlmRequest = { prompt, model: deps.model };`,
|
|
261
|
+
` if (deps.system !== undefined) request.system = deps.system;`,
|
|
262
|
+
` if (deps.params !== undefined) request.params = deps.params;`,
|
|
263
|
+
` const runInput: RunLlmCallInput = { callType: ${JSON.stringify(callTypeValue)}, request };`,
|
|
264
|
+
` if (deps.traceId !== undefined) runInput.traceId = deps.traceId;`,
|
|
265
|
+
` if (deps.parentSpanId !== undefined) runInput.parentSpanId = deps.parentSpanId;`,
|
|
266
|
+
` if (deps.sessionId !== undefined) runInput.sessionId = deps.sessionId;`,
|
|
267
|
+
` const runDeps: RunLlmCallDeps = { client: deps.client };`,
|
|
268
|
+
` if (deps.cost !== undefined) runDeps.cost = deps.cost;`,
|
|
269
|
+
` if (deps.clock !== undefined) runDeps.clock = deps.clock;`,
|
|
270
|
+
` if (deps.ids !== undefined) runDeps.ids = deps.ids;`,
|
|
271
|
+
` const { input: recInput, completion } = await runLlmCall(runInput, runDeps);`,
|
|
272
|
+
` let voResponse: ${responseRef} | null = null;`,
|
|
273
|
+
` let status = recInput.status;`,
|
|
274
|
+
` let errorDetail = recInput.errorDetail;`,
|
|
275
|
+
` if (completion !== undefined) {`,
|
|
276
|
+
` const outcome = extract(completion.body, extractSchemaFor(deps.responseMo, ${formatLiteral}));`,
|
|
277
|
+
` if (outcome.report.hasLostRequired()) {`,
|
|
278
|
+
` status = "error";`,
|
|
279
|
+
' errorDetail = `lost required: ${outcome.report.lostRequired().join(", ")}`;',
|
|
280
|
+
` } else {`,
|
|
281
|
+
` voResponse = outcome.data as ${responseRef};`,
|
|
282
|
+
` }`,
|
|
283
|
+
` }`,
|
|
284
|
+
` const row = { ...buildLlmCallRow({ ...recInput, status, errorDetail }), voRequest: payload, voResponse };`,
|
|
285
|
+
` await persistLlmCallRow(new LlmCallDbRecorder(deps.om, ${JSON.stringify(entityName)}), row, deps.redact ? { redact: deps.redact } : undefined);`,
|
|
286
|
+
` return { status, errorDetail, voResponse };`,
|
|
287
|
+
`}`,
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return [{
|
|
292
|
+
path: `${dirPrefix}${entityName}.trace.ts`,
|
|
293
|
+
content: lines.join("\n"),
|
|
294
|
+
}];
|
|
295
|
+
}),
|
|
296
|
+
};
|
|
297
|
+
if (opts?.target) {
|
|
298
|
+
generator.target = opts.target;
|
|
299
|
+
}
|
|
300
|
+
return generator;
|
|
301
|
+
} as GeneratorFactory<TraceHelperOpts>;
|
package/src/index.ts
CHANGED
|
@@ -9,8 +9,17 @@ export type { RunGenOpts, RunGenResult } from "./runner.js";
|
|
|
9
9
|
export type { Generator, GenContext, EmittedFile, GeneratorFactory } from "./generator.js";
|
|
10
10
|
export { perEntity, oncePerRun } from "./generator.js";
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
export {
|
|
12
|
+
// ADR-0021 D3 — stable-name generator registry + discoverability surface.
|
|
13
|
+
export {
|
|
14
|
+
generatorRegistry,
|
|
15
|
+
listGenerators,
|
|
16
|
+
getGenerator,
|
|
17
|
+
} from "./generator-registry.js";
|
|
18
|
+
export type { GeneratorRegistryEntry, GeneratorTier } from "./generator-registry.js";
|
|
19
|
+
|
|
20
|
+
export type { MetaobjectsGenConfig, NormalizedMetaobjectsGenConfig, ResolvedGenConfig, Dialect, ExtStyle, ColumnNamingStrategy, MetaDataTypeProvider, GeneratorSpec, DocsConfig, ResolvedDocsConfig, DocsSurface, ApiSurface } from "./metaobjects-config.js";
|
|
21
|
+
export { defineConfig, normalizeConfig, resolveGenerators, resolveDocsConfig } from "./metaobjects-config.js";
|
|
22
|
+
export { apiLabel } from "./generators/api-label.js";
|
|
14
23
|
|
|
15
24
|
export type { ColumnSpec, DefaultExpr } from "./column-mapper.js";
|
|
16
25
|
export { mapColumnType } from "./column-mapper.js";
|
|
@@ -34,7 +43,7 @@ export type {
|
|
|
34
43
|
export { decideAndWrite, GitMissingError } from "./overwrite-policy.js";
|
|
35
44
|
|
|
36
45
|
export { CodegenError } from "./errors.js";
|
|
37
|
-
export { GENERATED_HEADER, EXTRA_SUFFIX, DEFAULT_OUT_DIR } from "./constants.js";
|
|
46
|
+
export { GENERATED_HEADER, EXTRA_SUFFIX, DEFAULT_OUT_DIR, CODEGEN_ATTR_EMIT_TANSTACK, CODEGEN_ATTR_EMIT_GRID, CODEGEN_ATTR_EMIT_FORM, CODEGEN_ATTR_EMIT_ROUTES } from "./constants.js";
|
|
38
47
|
|
|
39
48
|
export { formatTs } from "./format.js";
|
|
40
49
|
|
|
@@ -42,9 +51,49 @@ export { pluralize, columnNameFromField, tableNameFromEntity, viewNameFromProjec
|
|
|
42
51
|
|
|
43
52
|
export { packageToPath, entityOutputPath, crossEntitySpecifier, barrelEntrySpecifier, relativeModuleSpecifier, entityModuleSpecifier, siblingSpecifier, barrelModuleSpecifier } from "./import-path.js";
|
|
44
53
|
export type { OutputLayout, ResolvedTarget } from "./import-path.js";
|
|
54
|
+
export {
|
|
55
|
+
docPageOutputPath,
|
|
56
|
+
docPageHref,
|
|
57
|
+
docPageNode,
|
|
58
|
+
effectivePackage,
|
|
59
|
+
assertNoDuplicateDocPaths,
|
|
60
|
+
} from "./docs-paths.js";
|
|
61
|
+
export type { DocPageNode, DocPagePlacement } from "./docs-paths.js";
|
|
45
62
|
|
|
46
63
|
export { isProjection, isWriteThrough } from "./projection/projection-detector.js";
|
|
47
64
|
export { isAbstract, emitsInstanceArtifacts, emitsWriteArtifacts } from "./instance-artifacts.js";
|
|
65
|
+
// FR-017 TPH helpers — used by the per-framework codegen packages (tanstack,
|
|
66
|
+
// react) to dispatch polymorphic/per-subtype emission and skip subtype files.
|
|
67
|
+
export { isTphDiscriminatorBase, tphConcreteSubtypes, collectTphSubtypeFields, tphPlan, tphRouteSegment } from "./templates/tph-discriminator.js";
|
|
68
|
+
export type { TphPlan, TphSubtypePlan } from "./templates/tph-discriminator.js";
|
|
69
|
+
export { isTphSubtype, tphDiscriminatorPin } from "./templates/zod-validators.js";
|
|
70
|
+
|
|
71
|
+
// Built-in template render functions — the composition seam for adopters who
|
|
72
|
+
// want to call a built-in template, then post-process / append to its output
|
|
73
|
+
// from their own Generator (added to `generators: [...]`) WITHOUT forking the
|
|
74
|
+
// template. Mirrors the `renderZodValidators` export. Each is also reachable via
|
|
75
|
+
// a dedicated subpath (e.g. `@metaobjectsdev/codegen-ts/templates/entity-file`).
|
|
76
|
+
export { renderEntityFile } from "./templates/entity-file.js";
|
|
77
|
+
export type { RenderEntityFileOpts } from "./templates/entity-file.js";
|
|
78
|
+
export { renderZodValidators } from "./templates/zod-validators.js";
|
|
79
|
+
export { renderDrizzleSchema } from "./templates/drizzle-schema.js";
|
|
80
|
+
export {
|
|
81
|
+
renderInferredTypes,
|
|
82
|
+
renderEnumTypeAliases,
|
|
83
|
+
renderValueObjectInterface,
|
|
84
|
+
enumUnionAliasName,
|
|
85
|
+
enumUnionString,
|
|
86
|
+
} from "./templates/inferred-types.js";
|
|
87
|
+
export { renderBarrel } from "./templates/barrel.js";
|
|
88
|
+
export type { BarrelEntry } from "./templates/barrel.js";
|
|
89
|
+
export { renderFilterType } from "./templates/filter-type.js";
|
|
90
|
+
export { renderFilterAllowlist, renderSortAllowlist } from "./templates/filter-allowlist.js";
|
|
91
|
+
export { renderEntityConstants, resourcePath } from "./templates/entity-constants.js";
|
|
92
|
+
export { renderQueriesFile } from "./templates/queries-file.js";
|
|
93
|
+
export { renderRoutesFile } from "./templates/routes-file.js";
|
|
94
|
+
export { renderValueObjectFile } from "./templates/value-object-file.js";
|
|
95
|
+
export { renderProjectionDecl } from "./templates/projection-decl.js";
|
|
96
|
+
export type { ProjectionDeclOpts } from "./templates/projection-decl.js";
|
|
48
97
|
export { extractViewSpec } from "./projection/extract-view-spec.js";
|
|
49
98
|
export type { ExtractContext } from "./projection/extract-view-spec.js";
|
|
50
99
|
export { emitViewDdl } from "./projection/view-ddl-emit.js";
|
|
@@ -74,6 +123,8 @@ export type {
|
|
|
74
123
|
IdentityDoc,
|
|
75
124
|
RelationshipDoc,
|
|
76
125
|
UsedByDoc,
|
|
77
|
-
|
|
126
|
+
ConstraintRow,
|
|
78
127
|
} from "./generators/docs-data.js";
|
|
79
128
|
export { buildEntityDocData } from "./generators/docs-data-builder.js";
|
|
129
|
+
export type { TemplateDocData, TemplateOutputPart } from "./generators/template-doc-data.js";
|
|
130
|
+
export { buildTemplateDocData } from "./generators/template-doc-builder.js";
|
|
@@ -2,6 +2,19 @@ import { DEFAULT_COLUMN_NAMING_STRATEGY, type ColumnNamingStrategy, type MetaDat
|
|
|
2
2
|
import type { Generator } from "./generator.js";
|
|
3
3
|
import type { ExtStyle } from "./render-context.js";
|
|
4
4
|
import type { OutputLayout, ResolvedTarget } from "./import-path.js";
|
|
5
|
+
import { generatorRegistry } from "./generator-registry.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A config `generators` entry. Either a typed generator (the primary, fully
|
|
9
|
+
* typed form — `entityFile()`) OR a STABLE-NAME STRING resolved via the
|
|
10
|
+
* {@link generatorRegistry} (e.g. `"entity"`). The string form is the
|
|
11
|
+
* cross-port-consistent selection mechanism (matches C#/Python
|
|
12
|
+
* `--generators entity,routes`); it always uses the generator's DEFAULT
|
|
13
|
+
* options. Adopters needing options use the factory form.
|
|
14
|
+
*
|
|
15
|
+
* ADR-0021 #1 (TS parity).
|
|
16
|
+
*/
|
|
17
|
+
export type GeneratorSpec = Generator | string;
|
|
5
18
|
|
|
6
19
|
export type Dialect = "sqlite" | "postgres";
|
|
7
20
|
/** Re-exported from metadata so codegen-ts consumers see one canonical type. */
|
|
@@ -29,10 +42,30 @@ export interface ResolvedGenConfig {
|
|
|
29
42
|
dialect: Dialect;
|
|
30
43
|
/** "flat" (default) — all files in outDir; "package" — files placed in a sub-path derived from each entity's metadata package. */
|
|
31
44
|
outputLayout?: OutputLayout;
|
|
45
|
+
/** Whether the OPT-IN Hono routes generator (routesFileHono) is active in the
|
|
46
|
+
* run — aggregated by the runner from the suite's `emitsHonoRoutes` markers.
|
|
47
|
+
* api-docs reads this to AUTO-DETECT whether to document the Hono CRUD surface
|
|
48
|
+
* (it otherwise mirrors the default Fastify-only suite). Undefined ⇒ false. */
|
|
49
|
+
includeHonoRoutes?: boolean;
|
|
50
|
+
/**
|
|
51
|
+
* FR-019 / ADR-0026: the module specifier from which an externally-PROVIDED
|
|
52
|
+
* shared enum (`@provided: true` on an abstract package-level `field.enum`) is
|
|
53
|
+
* imported. metaobjects emits NO type for a provided enum — consuming entity
|
|
54
|
+
* files `import { <EnumName> } from "<providedEnumModule>"`. The per-port
|
|
55
|
+
* namespace/module is codegen config, never a metadata attr (ADR-0001). A model
|
|
56
|
+
* that references a provided enum without this set is a codegen-time error.
|
|
57
|
+
*/
|
|
58
|
+
providedEnumModule?: string;
|
|
32
59
|
}
|
|
33
60
|
|
|
34
61
|
export interface MetaobjectsGenConfig extends ResolvedGenConfig {
|
|
35
|
-
|
|
62
|
+
/**
|
|
63
|
+
* Generators to run. Each entry is either a typed generator factory result
|
|
64
|
+
* (`entityFile()`) or a stable-name string (`"entity"`) resolved via the
|
|
65
|
+
* registry. Mixed arrays are allowed (`["entity", routesFile()]`). String
|
|
66
|
+
* entries use the generator's default options. ADR-0021 #1.
|
|
67
|
+
*/
|
|
68
|
+
generators: GeneratorSpec[];
|
|
36
69
|
/** How field names map to DB column names when @dbColumn is omitted. Defaults to "snake_case". */
|
|
37
70
|
columnNamingStrategy?: ColumnNamingStrategy;
|
|
38
71
|
/** Path prefix applied to generated route registrations + hook fetch URLs. Defaults to "". */
|
|
@@ -46,6 +79,8 @@ export interface MetaobjectsGenConfig extends ResolvedGenConfig {
|
|
|
46
79
|
* governs the shape, mirroring the cross-port `emitAbstractShapes` option.
|
|
47
80
|
*/
|
|
48
81
|
emitAbstractShapes?: boolean;
|
|
82
|
+
/** Docs-output config consumed by the `meta docs` door. See {@link DocsConfig}. */
|
|
83
|
+
docs?: DocsConfig;
|
|
49
84
|
/** Named output destinations. Generators reference one via `target`. */
|
|
50
85
|
targets?: Record<string, TargetConfig>;
|
|
51
86
|
/** importBase for the default target (top-level outDir). */
|
|
@@ -63,7 +98,9 @@ export interface MetaobjectsGenConfig extends ResolvedGenConfig {
|
|
|
63
98
|
* `targets` is Omitted from the base so it can narrow from the user-facing
|
|
64
99
|
* TargetConfig to the fully-resolved ResolvedTarget (incompatible under
|
|
65
100
|
* exactOptionalPropertyTypes otherwise). */
|
|
66
|
-
export interface NormalizedMetaobjectsGenConfig extends Omit<MetaobjectsGenConfig, "targets"> {
|
|
101
|
+
export interface NormalizedMetaobjectsGenConfig extends Omit<MetaobjectsGenConfig, "targets" | "generators"> {
|
|
102
|
+
/** Fully resolved — every string spec has been mapped to its factory result. */
|
|
103
|
+
generators: Generator[];
|
|
67
104
|
columnNamingStrategy: ColumnNamingStrategy;
|
|
68
105
|
apiPrefix: string;
|
|
69
106
|
emitAbstractShapes: boolean;
|
|
@@ -71,6 +108,50 @@ export interface NormalizedMetaobjectsGenConfig extends Omit<MetaobjectsGenConfi
|
|
|
71
108
|
targets: Record<string, ResolvedTarget>;
|
|
72
109
|
}
|
|
73
110
|
|
|
111
|
+
export type DocsSurface = "model" | "api";
|
|
112
|
+
|
|
113
|
+
export interface ApiSurface {
|
|
114
|
+
lang: string;
|
|
115
|
+
subDir: string;
|
|
116
|
+
baseUrl?: string;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** The single docs-output config: where ALL doc surfaces go, how pages are laid
|
|
120
|
+
* out, and which surfaces to emit. Read by the `meta docs` door (and, when the
|
|
121
|
+
* api surface fans out, by each port's docs command). */
|
|
122
|
+
export interface DocsConfig {
|
|
123
|
+
outDir?: string;
|
|
124
|
+
layout?: OutputLayout;
|
|
125
|
+
baseUrl?: string;
|
|
126
|
+
surfaces?: DocsSurface[];
|
|
127
|
+
apiSurfaces?: ApiSurface[];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export interface ResolvedDocsConfig {
|
|
131
|
+
outDir: string;
|
|
132
|
+
layout: OutputLayout;
|
|
133
|
+
baseUrl: string;
|
|
134
|
+
surfaces: DocsSurface[];
|
|
135
|
+
apiSurfaces: ApiSurface[];
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Merge the config `docs:` block with CLI overrides over documented defaults.
|
|
139
|
+
* `fallbackLayout` is the project's `outputLayout` so docs default to the same
|
|
140
|
+
* page placement as codegen when `docs.layout` is unset. */
|
|
141
|
+
export function resolveDocsConfig(
|
|
142
|
+
block: DocsConfig | undefined,
|
|
143
|
+
cli: Partial<ResolvedDocsConfig>,
|
|
144
|
+
fallbackLayout: OutputLayout,
|
|
145
|
+
): ResolvedDocsConfig {
|
|
146
|
+
return {
|
|
147
|
+
outDir: cli.outDir ?? block?.outDir ?? "./docs",
|
|
148
|
+
layout: cli.layout ?? block?.layout ?? fallbackLayout,
|
|
149
|
+
baseUrl: cli.baseUrl ?? block?.baseUrl ?? "",
|
|
150
|
+
surfaces: cli.surfaces ?? block?.surfaces ?? ["model", "api"],
|
|
151
|
+
apiSurfaces: cli.apiSurfaces ?? block?.apiSurfaces ?? [{ lang: "ts", subDir: "api" }],
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
74
155
|
/** Identity passthrough; exists for IDE type-inference + autocomplete. */
|
|
75
156
|
export function defineConfig(config: MetaobjectsGenConfig): MetaobjectsGenConfig {
|
|
76
157
|
return config;
|
|
@@ -102,10 +183,44 @@ export function resolveTargets(config: MetaobjectsGenConfig): Record<string, Res
|
|
|
102
183
|
return out;
|
|
103
184
|
}
|
|
104
185
|
|
|
186
|
+
/**
|
|
187
|
+
* Materialize the config `generators` array: pass typed generators through
|
|
188
|
+
* untouched and resolve each stable-name string via the {@link generatorRegistry}
|
|
189
|
+
* to its factory result (default options). ADR-0021 #1.
|
|
190
|
+
*
|
|
191
|
+
* Errors:
|
|
192
|
+
* - a NEUTRAL name (`docs`, `mermaid-er`) is owned by `meta docs` (ADR-0021 D1)
|
|
193
|
+
* and is not selectable in the gen suite.
|
|
194
|
+
* - an UNKNOWN name throws listing the available NATIVE names.
|
|
195
|
+
*/
|
|
196
|
+
export function resolveGenerators(specs: readonly GeneratorSpec[]): Generator[] {
|
|
197
|
+
return specs.map((spec) => {
|
|
198
|
+
if (typeof spec !== "string") return spec;
|
|
199
|
+
const entry = generatorRegistry[spec];
|
|
200
|
+
if (entry === undefined) {
|
|
201
|
+
const native = Object.values(generatorRegistry)
|
|
202
|
+
.filter((e) => e.tier === "native")
|
|
203
|
+
.map((e) => e.name)
|
|
204
|
+
.sort();
|
|
205
|
+
throw new Error(
|
|
206
|
+
`unknown generator "${spec}". Available native generators: ${native.join(", ")}.`,
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
if (entry.tier !== "native") {
|
|
210
|
+
throw new Error(
|
|
211
|
+
`generator "${spec}" is neutral (owned by 'meta docs'); ` +
|
|
212
|
+
`not selectable in the gen suite.`,
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
return entry.factory();
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
105
219
|
/** Apply defaults to a MetaobjectsGenConfig, returning a NormalizedMetaobjectsGenConfig. */
|
|
106
220
|
export function normalizeConfig(config: MetaobjectsGenConfig): NormalizedMetaobjectsGenConfig {
|
|
107
221
|
return {
|
|
108
222
|
...config,
|
|
223
|
+
generators: resolveGenerators(config.generators),
|
|
109
224
|
columnNamingStrategy: config.columnNamingStrategy ?? DEFAULT_COLUMN_NAMING_STRATEGY,
|
|
110
225
|
apiPrefix: config.apiPrefix ?? "",
|
|
111
226
|
emitAbstractShapes: config.emitAbstractShapes ?? true,
|
package/src/naming.ts
CHANGED
|
@@ -63,5 +63,53 @@ export function variableNameFromEntity(entityName: string): string {
|
|
|
63
63
|
return pluralize(toCamelCase(entityName.charAt(0).toLowerCase() + entityName.slice(1)));
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
// Generated CRUD-helper symbol names (single source of truth).
|
|
68
|
+
//
|
|
69
|
+
// The queries generator (templates/queries.ts) emits one exported async function
|
|
70
|
+
// per CRUD verb whose NAME is derived purely from the entity name. These helpers
|
|
71
|
+
// are the canonical spelling of those names so anything that needs to REFER to a
|
|
72
|
+
// generated symbol (e.g. the api-docs ApiModel builder) derives the exact same
|
|
73
|
+
// string the generator emits — no drift, no invented names. The queries template
|
|
74
|
+
// itself uses these so the two can never disagree.
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
|
|
77
|
+
/** Generated read-by-PK helper name: `find<Entity>ById`. */
|
|
78
|
+
export function findByIdFnName(entityName: string): string {
|
|
79
|
+
return `find${entityName}ById`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Generated list helper name: `list<Plural>` (PascalCase plural). */
|
|
83
|
+
export function listFnName(entityName: string): string {
|
|
84
|
+
return `list${pluralize(entityName)}`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Generated create helper name: `create<Entity>`. */
|
|
88
|
+
export function createFnName(entityName: string): string {
|
|
89
|
+
return `create${entityName}`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Generated update helper name: `update<Entity>`. */
|
|
93
|
+
export function updateFnName(entityName: string): string {
|
|
94
|
+
return `update${entityName}`;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Generated delete-by-PK helper name: `delete<Entity>ById`. */
|
|
98
|
+
export function deleteByIdFnName(entityName: string): string {
|
|
99
|
+
return `delete${entityName}ById`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Generated Fastify route-registrar name: camelCase `<entity>Routes`. The routes
|
|
104
|
+
* generator (templates/routes-file.ts) emits a single exported
|
|
105
|
+
* `export async function <entity>Routes(fastify)` that mounts the entity's CRUD
|
|
106
|
+
* verb set — this is the symbol an adopter imports to wire the endpoints. Kept
|
|
107
|
+
* here as the single source of truth so the routes template and the api-docs
|
|
108
|
+
* ApiModel builder derive the exact same spelling (no drift).
|
|
109
|
+
*/
|
|
110
|
+
export function routesHandlerName(entityName: string): string {
|
|
111
|
+
return `${entityName.charAt(0).toLowerCase()}${entityName.slice(1)}Routes`;
|
|
112
|
+
}
|
|
113
|
+
|
|
66
114
|
// Re-exported here for callers that import from codegen-ts's naming module.
|
|
67
115
|
export { toKebabCase };
|
package/src/payload-codegen.ts
CHANGED
|
@@ -23,6 +23,8 @@ import {
|
|
|
23
23
|
TEMPLATE_ATTR_PAYLOAD_REF,
|
|
24
24
|
TEMPLATE_ATTR_TEXT_REF,
|
|
25
25
|
TEMPLATE_ATTR_FORMAT,
|
|
26
|
+
refMatchesObject,
|
|
27
|
+
stripPackage,
|
|
26
28
|
} from "@metaobjectsdev/metadata";
|
|
27
29
|
import { enumValues } from "./enum-meta.js";
|
|
28
30
|
import { enumUnionAliasName, enumUnionString } from "./templates/inferred-types.js";
|
|
@@ -45,7 +47,9 @@ const SCALAR_TS: Record<string, string> = {
|
|
|
45
47
|
};
|
|
46
48
|
|
|
47
49
|
function findObject(root: MetaData, name: string): MetaData | undefined {
|
|
48
|
-
|
|
50
|
+
// FR-032 — @payloadRef/@responseRef are FQN after the desugar/sweep; match on
|
|
51
|
+
// the effective FQN resolution key (with bare back-compat).
|
|
52
|
+
return root.ownChildren().find((c) => c.type === TYPE_OBJECT && refMatchesObject(c, name));
|
|
49
53
|
}
|
|
50
54
|
|
|
51
55
|
/**
|
|
@@ -168,14 +172,21 @@ export function generateRenderHandle(root: MetaData, templateName: string): stri
|
|
|
168
172
|
const tmpl = root.ownChildren().find((c) => c.type === TYPE_TEMPLATE && c.name === templateName);
|
|
169
173
|
if (!tmpl) throw new Error(`template "${templateName}" not found`);
|
|
170
174
|
const payloadRef = tmpl.ownAttr(TEMPLATE_ATTR_PAYLOAD_REF);
|
|
175
|
+
// FR-032 — @payloadRef is an FQN after the desugar/sweep; the generated TS
|
|
176
|
+
// TYPE NAME is the resolved value-object's bare name (an FQN like
|
|
177
|
+
// `acme::ai::Payload` is not a valid TS identifier). Fall back to the last
|
|
178
|
+
// `::`-segment when the VO is not in this root (defensive).
|
|
179
|
+
const payloadType =
|
|
180
|
+
(typeof payloadRef === "string" ? findObject(root, payloadRef)?.name : undefined) ??
|
|
181
|
+
(typeof payloadRef === "string" ? stripPackage(payloadRef) : String(payloadRef));
|
|
171
182
|
const textRef = tmpl.ownAttr(TEMPLATE_ATTR_TEXT_REF);
|
|
172
183
|
const format = (tmpl.ownAttr(TEMPLATE_ATTR_FORMAT) as string | undefined) ?? "text";
|
|
173
184
|
const fn = `render${pascal(templateName)}`;
|
|
174
185
|
return [
|
|
175
186
|
`import { render, type Provider } from "@metaobjectsdev/render";`,
|
|
176
|
-
`import type { ${
|
|
187
|
+
`import type { ${payloadType} } from "./payloads.js";`,
|
|
177
188
|
``,
|
|
178
|
-
`export function ${fn}(payload: ${
|
|
189
|
+
`export function ${fn}(payload: ${payloadType}, provider: Provider): string {`,
|
|
179
190
|
` return render({ ref: ${JSON.stringify(textRef)}, payload, format: ${JSON.stringify(format)}, provider });`,
|
|
180
191
|
`}`,
|
|
181
192
|
``,
|