@metaobjectsdev/codegen-ts 0.9.0 → 0.11.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/column-mapper.d.ts +12 -6
- package/dist/column-mapper.d.ts.map +1 -1
- package/dist/column-mapper.js +68 -28
- 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 +439 -167
- 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/import-path.d.ts +18 -0
- package/dist/import-path.d.ts.map +1 -1
- package/dist/import-path.js +21 -0
- package/dist/import-path.js.map +1 -1
- 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 +101 -2
- package/dist/metaobjects-config.d.ts.map +1 -1
- package/dist/metaobjects-config.js +46 -0
- package/dist/metaobjects-config.js.map +1 -1
- package/dist/naming.d.ts +39 -2
- package/dist/naming.d.ts.map +1 -1
- package/dist/naming.js +52 -3
- package/dist/naming.js.map +1 -1
- package/dist/payload-codegen.d.ts.map +1 -1
- package/dist/payload-codegen.js +14 -6
- package/dist/payload-codegen.js.map +1 -1
- package/dist/pk-resolver.d.ts.map +1 -1
- package/dist/pk-resolver.js +4 -2
- package/dist/pk-resolver.js.map +1 -1
- package/dist/projection/extract-view-spec.d.ts.map +1 -1
- package/dist/projection/extract-view-spec.js +52 -26
- 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 +25 -2
- package/dist/render-context.d.ts.map +1 -1
- package/dist/render-context.js +7 -0
- 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 +20 -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 +72 -23
- 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 +3 -3
- 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 +6 -8
- package/dist/templates/extract-delegate-emitter.js.map +1 -1
- package/dist/templates/extractor.d.ts.map +1 -1
- package/dist/templates/extractor.js +58 -41
- package/dist/templates/extractor.js.map +1 -1
- package/dist/templates/field-meta.d.ts.map +1 -1
- package/dist/templates/field-meta.js +2 -6
- 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 +18 -10
- package/dist/templates/filter-allowlist.js.map +1 -1
- package/dist/templates/filter-shared.js +2 -2
- package/dist/templates/filter-shared.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 +10 -6
- 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 +15 -11
- 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 +121 -19
- 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-format-spec-emitter.js +1 -1
- package/dist/templates/output-format-spec-emitter.js.map +1 -1
- package/dist/templates/output-parser.d.ts.map +1 -1
- package/dist/templates/output-parser.js +31 -80
- 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 +113 -5
- package/dist/templates/queries-file.js.map +1 -1
- package/dist/templates/queries.d.ts +7 -2
- package/dist/templates/queries.d.ts.map +1 -1
- package/dist/templates/queries.js +15 -15
- package/dist/templates/queries.js.map +1 -1
- package/dist/templates/relations-block.d.ts.map +1 -1
- package/dist/templates/relations-block.js +12 -3
- 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 +5 -5
- package/dist/templates/render-helper.js.map +1 -1
- package/dist/templates/routes-file-hono.d.ts.map +1 -1
- package/dist/templates/routes-file-hono.js +1 -2
- package/dist/templates/routes-file-hono.js.map +1 -1
- package/dist/templates/routes-file.d.ts.map +1 -1
- package/dist/templates/routes-file.js +184 -7
- 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 +33 -5
- package/dist/templates/value-object-file.js.map +1 -1
- package/dist/templates/zod-validators.d.ts +65 -2
- package/dist/templates/zod-validators.d.ts.map +1 -1
- package/dist/templates/zod-validators.js +202 -22
- package/dist/templates/zod-validators.js.map +1 -1
- package/package.json +103 -34
- package/src/column-mapper.ts +79 -32
- 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 +483 -189
- 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/import-path.ts +28 -0
- package/src/index.ts +55 -4
- package/src/metaobjects-config.ts +146 -2
- package/src/naming.ts +73 -3
- package/src/payload-codegen.ts +16 -5
- package/src/pk-resolver.ts +4 -2
- package/src/projection/extract-view-spec.ts +50 -31
- package/src/relation-resolver.ts +103 -1
- package/src/render-context.ts +32 -2
- package/src/render-engine/embedded-templates.generated.ts +14 -0
- package/src/render-engine/framework-provider.ts +25 -11
- package/src/runner.ts +24 -0
- package/src/templates/docs-file.ts +2 -9
- package/src/templates/drizzle-schema.ts +80 -28
- package/src/templates/entity-constants.ts +3 -3
- package/src/templates/entity-file.ts +16 -5
- package/src/templates/enums-file.ts +50 -0
- package/src/templates/extract-delegate-emitter.ts +6 -7
- package/src/templates/extractor.ts +70 -40
- package/src/templates/field-meta.ts +1 -7
- package/src/templates/filter-allowlist.ts +18 -11
- package/src/templates/filter-shared.ts +2 -2
- package/src/templates/filter-type.ts +9 -7
- package/src/templates/find-templates.ts +15 -0
- package/src/templates/fr010-field-mapping.ts +15 -13
- package/src/templates/inferred-types.ts +122 -21
- package/src/templates/mermaid-er.ts +176 -8
- package/src/templates/output-format-spec-emitter.ts +1 -1
- package/src/templates/output-parser.ts +31 -80
- package/src/templates/output-prompt.ts +2 -1
- package/src/templates/queries-file.ts +133 -4
- package/src/templates/queries.ts +21 -15
- package/src/templates/relations-block.ts +19 -3
- package/src/templates/render-helper.ts +5 -4
- package/src/templates/routes-file-hono.ts +1 -2
- package/src/templates/routes-file.ts +234 -7
- package/src/templates/tph-discriminator.ts +232 -0
- package/src/templates/value-object-file.ts +39 -5
- package/src/templates/zod-validators.ts +225 -21
- 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/dist/templates/extract-schema-emitter.d.ts +0 -8
- package/dist/templates/extract-schema-emitter.d.ts.map +0 -1
- package/dist/templates/extract-schema-emitter.js +0 -81
- package/dist/templates/extract-schema-emitter.js.map +0 -1
- package/src/templates/extract-schema-emitter.ts +0 -111
package/src/naming.ts
CHANGED
|
@@ -58,9 +58,79 @@ export function viewNameFromProjection(
|
|
|
58
58
|
return "v" + sep + applyColumnNamingStrategy(projectionName, strategy);
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
/**
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
/** Codegen control over how an entity name lowers to its collection (table)
|
|
62
|
+
* variable name. Both knobs are project-level codegen config (ADR-0001 —
|
|
63
|
+
* naming is a per-port codegen concern, NOT a metadata attribute), so they
|
|
64
|
+
* carry no cross-port conformance cost. */
|
|
65
|
+
export interface CollectionNameOptions {
|
|
66
|
+
/** Auto-pluralize the camelCase entity name. Default `true` (e.g.
|
|
67
|
+
* `AgentConfig` → `agentConfigs`). Set `false` to keep it singular
|
|
68
|
+
* (`agentConfig`). */
|
|
69
|
+
pluralize?: boolean;
|
|
70
|
+
/** Per-entity exact var-name overrides, keyed by the bare entity name. Wins
|
|
71
|
+
* over `pluralize` — the escape hatch for the handful of tables a global
|
|
72
|
+
* rule gets wrong (e.g. `{ AuditLog: "auditLog", LlmTierConfig: "llmTierConfig" }`). */
|
|
73
|
+
overrides?: Record<string, string>;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** PascalCase entity → camelCase Drizzle table variable. Auto-pluralizes by
|
|
77
|
+
* default; `opts` lets a project turn pluralization off globally and/or pin
|
|
78
|
+
* exact names per entity. With no `opts` the behavior is the historical
|
|
79
|
+
* always-pluralize (callers like the relation-resolver that only need the
|
|
80
|
+
* cosmetic query-API member name pass nothing). */
|
|
81
|
+
export function variableNameFromEntity(entityName: string, opts?: CollectionNameOptions): string {
|
|
82
|
+
const override = opts?.overrides?.[entityName];
|
|
83
|
+
if (override !== undefined && override.length > 0) return override;
|
|
84
|
+
const camel = toCamelCase(entityName.charAt(0).toLowerCase() + entityName.slice(1));
|
|
85
|
+
return opts?.pluralize === false ? camel : pluralize(camel);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
// Generated CRUD-helper symbol names (single source of truth).
|
|
90
|
+
//
|
|
91
|
+
// The queries generator (templates/queries.ts) emits one exported async function
|
|
92
|
+
// per CRUD verb whose NAME is derived purely from the entity name. These helpers
|
|
93
|
+
// are the canonical spelling of those names so anything that needs to REFER to a
|
|
94
|
+
// generated symbol (e.g. the api-docs ApiModel builder) derives the exact same
|
|
95
|
+
// string the generator emits — no drift, no invented names. The queries template
|
|
96
|
+
// itself uses these so the two can never disagree.
|
|
97
|
+
// ---------------------------------------------------------------------------
|
|
98
|
+
|
|
99
|
+
/** Generated read-by-PK helper name: `find<Entity>ById`. */
|
|
100
|
+
export function findByIdFnName(entityName: string): string {
|
|
101
|
+
return `find${entityName}ById`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Generated list helper name: `list<Plural>` (PascalCase plural). */
|
|
105
|
+
export function listFnName(entityName: string): string {
|
|
106
|
+
return `list${pluralize(entityName)}`;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Generated create helper name: `create<Entity>`. */
|
|
110
|
+
export function createFnName(entityName: string): string {
|
|
111
|
+
return `create${entityName}`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Generated update helper name: `update<Entity>`. */
|
|
115
|
+
export function updateFnName(entityName: string): string {
|
|
116
|
+
return `update${entityName}`;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Generated delete-by-PK helper name: `delete<Entity>ById`. */
|
|
120
|
+
export function deleteByIdFnName(entityName: string): string {
|
|
121
|
+
return `delete${entityName}ById`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Generated Fastify route-registrar name: camelCase `<entity>Routes`. The routes
|
|
126
|
+
* generator (templates/routes-file.ts) emits a single exported
|
|
127
|
+
* `export async function <entity>Routes(fastify)` that mounts the entity's CRUD
|
|
128
|
+
* verb set — this is the symbol an adopter imports to wire the endpoints. Kept
|
|
129
|
+
* here as the single source of truth so the routes template and the api-docs
|
|
130
|
+
* ApiModel builder derive the exact same spelling (no drift).
|
|
131
|
+
*/
|
|
132
|
+
export function routesHandlerName(entityName: string): string {
|
|
133
|
+
return `${entityName.charAt(0).toLowerCase()}${entityName.slice(1)}Routes`;
|
|
64
134
|
}
|
|
65
135
|
|
|
66
136
|
// Re-exported here for callers that import from codegen-ts's naming module.
|
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
|
/**
|
|
@@ -61,7 +65,7 @@ function fieldTsType(
|
|
|
61
65
|
ownerName: string,
|
|
62
66
|
): { type: string; refVo?: string; enumAlias?: { name: string; decl: string } } {
|
|
63
67
|
if (field.subType === FIELD_SUBTYPE_OBJECT) {
|
|
64
|
-
const ref = field.
|
|
68
|
+
const ref = field.attr(FIELD_ATTR_OBJECT_REF);
|
|
65
69
|
const refName = typeof ref === "string" ? ref : "unknown";
|
|
66
70
|
// isArray is a structural property on MetaData, not an attr.
|
|
67
71
|
const isArray = field.isArray;
|
|
@@ -92,7 +96,7 @@ function fieldTsType(
|
|
|
92
96
|
|
|
93
97
|
/** True iff the field's @required is explicitly set to true. */
|
|
94
98
|
function isFieldRequired(field: MetaData): boolean {
|
|
95
|
-
return field.
|
|
99
|
+
return field.attr(FIELD_ATTR_REQUIRED) === true;
|
|
96
100
|
}
|
|
97
101
|
|
|
98
102
|
function emitInterface(
|
|
@@ -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
|
``,
|
package/src/pk-resolver.ts
CHANGED
|
@@ -30,7 +30,9 @@ export function buildPkMap(root: MetaRoot): Map<string, PkInfo> {
|
|
|
30
30
|
// primaryIdentity() resolves the primary identity across the super-chain.
|
|
31
31
|
const primary = obj.primaryIdentity();
|
|
32
32
|
if (!primary) continue;
|
|
33
|
-
|
|
33
|
+
// attr() (effective) not ownAttr() — @fields/@generation can be inherited when the
|
|
34
|
+
// identity node-level `extends` a base identity without restating them (#56).
|
|
35
|
+
const fields = primary.attr(IDENTITY_ATTR_FIELDS);
|
|
34
36
|
if (!Array.isArray(fields) && typeof fields !== "string") continue;
|
|
35
37
|
const fieldsList = Array.isArray(fields) ? fields : [fields];
|
|
36
38
|
if (fieldsList.length === 0) continue;
|
|
@@ -38,7 +40,7 @@ export function buildPkMap(root: MetaRoot): Map<string, PkInfo> {
|
|
|
38
40
|
// findField() resolves the field across the super-chain (handles extends:).
|
|
39
41
|
const pkField = obj.findField(pkFieldName);
|
|
40
42
|
const fieldSubType = pkField?.subType ?? FIELD_SUBTYPE_LONG; // sane default
|
|
41
|
-
const generation = primary.
|
|
43
|
+
const generation = primary.attr(IDENTITY_ATTR_GENERATION);
|
|
42
44
|
const info: PkInfo = { fieldName: pkFieldName, fieldSubType };
|
|
43
45
|
if (typeof generation === "string") info.generation = generation;
|
|
44
46
|
result.set(obj.name, info);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
TYPE_FIELD,
|
|
3
|
+
TYPE_IDENTITY,
|
|
3
4
|
TYPE_ORIGIN,
|
|
4
5
|
TYPE_RELATIONSHIP,
|
|
5
6
|
MetaSource,
|
|
@@ -79,33 +80,62 @@ export function projectionViewName(
|
|
|
79
80
|
return viewName(projection, { columnNamingStrategy });
|
|
80
81
|
}
|
|
81
82
|
|
|
83
|
+
/**
|
|
84
|
+
* FR-024 (ADR-0029): the entity NAMED by a node's dotted extends ref — the
|
|
85
|
+
* owner part of `<owner>.<child>...` resolved as an object. Mirrors the
|
|
86
|
+
* loader's `_refNamedOwner`: the ref names the anchor, never the physical
|
|
87
|
+
* declaring ancestor of an inherited child.
|
|
88
|
+
*/
|
|
89
|
+
function refNamedOwner(node: MetaData, root: MetaRoot): MetaObject | undefined {
|
|
90
|
+
const ref = (node as { superRef?: string }).superRef;
|
|
91
|
+
if (ref === undefined) return undefined;
|
|
92
|
+
const lastSep = ref.lastIndexOf("::");
|
|
93
|
+
const tail = lastSep === -1 ? ref : ref.slice(lastSep + 2);
|
|
94
|
+
const dot = tail.indexOf(".");
|
|
95
|
+
if (dot <= 0) return undefined;
|
|
96
|
+
return root.findObject(tail.slice(0, dot)) ?? undefined;
|
|
97
|
+
}
|
|
98
|
+
|
|
82
99
|
function baseEntityFor(
|
|
83
100
|
projection: MetaObject,
|
|
84
101
|
root: MetaRoot,
|
|
85
102
|
): MetaObject {
|
|
86
|
-
//
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
103
|
+
// FR-024 base-anchor rules (mirror the loader's _deriveBaseEntity):
|
|
104
|
+
// 1) the extends-bound identity anchors the base entity;
|
|
105
|
+
// 2) else the single distinct entity targeted by extends-bound fields.
|
|
106
|
+
// The pre-FR-024 object-level `extends:` firehose is removed (B4b cutover).
|
|
107
|
+
//
|
|
108
|
+
// COUPLING NOTE: this intentionally derives the anchor ONLY from the ref's
|
|
109
|
+
// named owner (refNamedOwner), NOT the loader's `superResolved.parent`
|
|
110
|
+
// fallback for a non-dotted identity extends. That fallback is unreachable
|
|
111
|
+
// here because the loader gate (validate-identity-passthrough →
|
|
112
|
+
// ERR_PROJECTION_IDENTITY_NOT_EXTENDED) rejects any projection whose identity
|
|
113
|
+
// is not dotted-extends-bound before codegen runs. If that loader gate is
|
|
114
|
+
// ever loosened, this function must grow the same fallback.
|
|
115
|
+
for (const identity of projection
|
|
116
|
+
.ownChildren()
|
|
117
|
+
.filter((c) => c.type === TYPE_IDENTITY)) {
|
|
118
|
+
const named = refNamedOwner(identity, root);
|
|
119
|
+
if (named !== undefined) return named;
|
|
93
120
|
}
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
`Projection ${projection.name}: extends "${superName}" does not resolve to any entity.`,
|
|
99
|
-
);
|
|
121
|
+
const targets = new Set<MetaObject>();
|
|
122
|
+
for (const f of projection.ownChildren().filter((c) => c.type === TYPE_FIELD)) {
|
|
123
|
+
const named = refNamedOwner(f, root);
|
|
124
|
+
if (named !== undefined && named !== projection) targets.add(named);
|
|
100
125
|
}
|
|
101
|
-
return
|
|
126
|
+
if (targets.size === 1) return [...targets][0]!;
|
|
127
|
+
throw new Error(
|
|
128
|
+
`Projection ${projection.name}: cannot derive the base entity — declare an ` +
|
|
129
|
+
`extends-bound identity (identity.primary { name, extends: "<Entity>.<identity>" }) ` +
|
|
130
|
+
`to anchor the base (FR-024).`,
|
|
131
|
+
);
|
|
102
132
|
}
|
|
103
133
|
|
|
104
134
|
function sourceColumnNameFor(
|
|
105
135
|
entityField: MetaData,
|
|
106
136
|
ctx: ExtractContext,
|
|
107
137
|
): string {
|
|
108
|
-
const col = entityField.
|
|
138
|
+
const col = entityField.attr(FIELD_ATTR_COLUMN);
|
|
109
139
|
if (typeof col === "string" && col !== "") return col;
|
|
110
140
|
return columnNameFromField(entityField.name, ctx.columnNamingStrategy);
|
|
111
141
|
}
|
|
@@ -270,22 +300,11 @@ function buildSelectSpec(
|
|
|
270
300
|
): SelectSpec {
|
|
271
301
|
const columns: SelectColumn[] = [];
|
|
272
302
|
|
|
273
|
-
//
|
|
274
|
-
//
|
|
275
|
-
//
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
(c) => c.type === TYPE_FIELD && c.name === baseField.name,
|
|
279
|
-
);
|
|
280
|
-
if (overridden) continue;
|
|
281
|
-
columns.push({
|
|
282
|
-
kind: "passthrough",
|
|
283
|
-
fieldName: baseField.name,
|
|
284
|
-
dbColAlias: sourceColumnNameFor(baseField, ctx),
|
|
285
|
-
sourceAlias: joinTree.baseAlias,
|
|
286
|
-
sourceColumn: sourceColumnNameFor(baseField, ctx),
|
|
287
|
-
});
|
|
288
|
-
}
|
|
303
|
+
// FR-024 (ADR-0028): the projection's DECLARED field set IS the exposure —
|
|
304
|
+
// the inclusive list, fail-closed by construction. The pre-FR-024 loop that
|
|
305
|
+
// emitted every base-entity field as an implicit passthrough (the firehose)
|
|
306
|
+
// is removed with the B4b cutover: base columns are declared explicitly as
|
|
307
|
+
// extends-bound fields (`{ field.int: { name: id, extends: "Program.id" } }`).
|
|
289
308
|
|
|
290
309
|
// Fields explicitly declared on the projection.
|
|
291
310
|
for (const field of projection.ownChildren()) {
|
package/src/relation-resolver.ts
CHANGED
|
@@ -5,11 +5,14 @@
|
|
|
5
5
|
//
|
|
6
6
|
// Reads identity.reference declarations to determine the physical reference side.
|
|
7
7
|
|
|
8
|
-
import type { MetaRoot } from "@metaobjectsdev/metadata";
|
|
8
|
+
import type { MetaRoot, MetaObject, MetaRelationship } from "@metaobjectsdev/metadata";
|
|
9
9
|
import {
|
|
10
10
|
RELATIONSHIP_ATTR_CARDINALITY,
|
|
11
11
|
RELATIONSHIP_ATTR_OBJECT_REF,
|
|
12
|
+
RELATIONSHIP_ATTR_THROUGH,
|
|
12
13
|
CARDINALITY_ONE,
|
|
14
|
+
CARDINALITY_MANY,
|
|
15
|
+
deriveM2MFields,
|
|
13
16
|
stripPackage,
|
|
14
17
|
} from "@metaobjectsdev/metadata";
|
|
15
18
|
import { variableNameFromEntity } from "./naming.js";
|
|
@@ -26,6 +29,22 @@ export interface RelationEntry {
|
|
|
26
29
|
fkField?: string;
|
|
27
30
|
/** For cardinality 'one': the target entity's PK field (e.g., "id") */
|
|
28
31
|
targetPkField?: string;
|
|
32
|
+
/**
|
|
33
|
+
* FR-018 M:N navigation fields. Present only for a many-to-many navigation (a
|
|
34
|
+
* `@cardinality: "many"` relationship that declares `@through`). The Drizzle
|
|
35
|
+
* relations() block emits `many(<junction>)` for these; the routes file emits
|
|
36
|
+
* a `mountM2mRoute(...)` traversal. The junction FK fields are DERIVED from the
|
|
37
|
+
* junction entity's two `identity.reference` children (the SSOT), never
|
|
38
|
+
* restated on the relationship.
|
|
39
|
+
*/
|
|
40
|
+
/** The junction/through entity name (e.g., "PostTag"). M:N entries only. */
|
|
41
|
+
junctionEntity?: string;
|
|
42
|
+
/** Junction FK field holding the source-side key (logical field name, e.g. "postId"). M:N only. */
|
|
43
|
+
sourceJoinField?: string;
|
|
44
|
+
/** Junction FK field holding the target-side key (logical field name, e.g. "tagId"). M:N only. */
|
|
45
|
+
targetJoinField?: string;
|
|
46
|
+
/** Undirected self-join: union both junction FK columns at read time. M:N only. */
|
|
47
|
+
symmetric?: boolean;
|
|
29
48
|
}
|
|
30
49
|
|
|
31
50
|
/** Map from entity name → list of relations for that entity's relations() block */
|
|
@@ -51,6 +70,16 @@ export function buildRelationMap(root: MetaRoot): RelationMap {
|
|
|
51
70
|
|
|
52
71
|
for (const child of obj.relationships()) {
|
|
53
72
|
const cardinality = child.ownAttr(RELATIONSHIP_ATTR_CARDINALITY) as string | undefined;
|
|
73
|
+
|
|
74
|
+
// FR-018 M:N: `@cardinality: "many"` + `@through` — derive the junction FK
|
|
75
|
+
// columns from the junction's identity.reference children and register a
|
|
76
|
+
// many(junction) navigation on the source.
|
|
77
|
+
if (cardinality === CARDINALITY_MANY && child.ownAttr(RELATIONSHIP_ATTR_THROUGH) !== undefined) {
|
|
78
|
+
const m2m = buildM2mEntry(obj, child as MetaRelationship, root);
|
|
79
|
+
if (m2m) ensure(obj.name).push(m2m);
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
|
|
54
83
|
if (cardinality !== CARDINALITY_ONE) continue;
|
|
55
84
|
|
|
56
85
|
const targetEntityRaw = child.ownAttr(RELATIONSHIP_ATTR_OBJECT_REF) as string | undefined;
|
|
@@ -83,5 +112,78 @@ export function buildRelationMap(root: MetaRoot): RelationMap {
|
|
|
83
112
|
}
|
|
84
113
|
}
|
|
85
114
|
|
|
115
|
+
// FR-018: junction entities reached via @through need their two belongs-to
|
|
116
|
+
// one() sides so the through-table is navigable in the Drizzle relational
|
|
117
|
+
// query API (db.query.posts.findMany({ with: { tags: { with: { tag: true }}}})).
|
|
118
|
+
// A junction is any entity named by some M:N relationship's @through.
|
|
119
|
+
for (const junctionName of collectJunctionNames(root)) {
|
|
120
|
+
const junction = root.findObject(junctionName);
|
|
121
|
+
if (!junction) continue;
|
|
122
|
+
const entries = ensure(junctionName);
|
|
123
|
+
for (const ref of junction.referenceIdentities()) {
|
|
124
|
+
const targetRaw = ref.targetEntity;
|
|
125
|
+
const fkField = ref.fields[0];
|
|
126
|
+
if (!targetRaw || !fkField) continue;
|
|
127
|
+
const targetEntity = stripPackage(targetRaw);
|
|
128
|
+
// The relation member is named after the target entity (camel singular);
|
|
129
|
+
// multiple references to the same entity (self-join junction) are
|
|
130
|
+
// disambiguated by the FK field name.
|
|
131
|
+
const refName = ref.name && ref.name.length > 0
|
|
132
|
+
? ref.name
|
|
133
|
+
: variableNameFromEntity(targetEntity);
|
|
134
|
+
entries.push({
|
|
135
|
+
name: refName,
|
|
136
|
+
cardinality: "one",
|
|
137
|
+
targetEntity,
|
|
138
|
+
fkField,
|
|
139
|
+
targetPkField: "id",
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
86
144
|
return result;
|
|
87
145
|
}
|
|
146
|
+
|
|
147
|
+
/** Names of all entities that are the `@through` junction of some M:N relationship. */
|
|
148
|
+
function collectJunctionNames(root: MetaRoot): Set<string> {
|
|
149
|
+
const names = new Set<string>();
|
|
150
|
+
for (const obj of root.objects()) {
|
|
151
|
+
for (const rel of obj.relationships()) {
|
|
152
|
+
if (rel.ownAttr(RELATIONSHIP_ATTR_CARDINALITY) !== CARDINALITY_MANY) continue;
|
|
153
|
+
const through = rel.ownAttr(RELATIONSHIP_ATTR_THROUGH) as string | undefined;
|
|
154
|
+
if (through) names.add(stripPackage(through));
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return names;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Build the source-side M:N navigation entry: derive the junction FK fields from
|
|
162
|
+
* the junction's two identity.reference children (the SSOT), handling hetero /
|
|
163
|
+
* directed-self-join / symmetric. Returns null (skips the entry) if derivation
|
|
164
|
+
* fails — the loader validation pass surfaces the actionable error separately.
|
|
165
|
+
*/
|
|
166
|
+
function buildM2mEntry(
|
|
167
|
+
source: MetaObject,
|
|
168
|
+
rel: MetaRelationship,
|
|
169
|
+
root: MetaRoot,
|
|
170
|
+
): RelationEntry | null {
|
|
171
|
+
const targetRaw = rel.ownAttr(RELATIONSHIP_ATTR_OBJECT_REF) as string | undefined;
|
|
172
|
+
const throughRaw = rel.ownAttr(RELATIONSHIP_ATTR_THROUGH) as string | undefined;
|
|
173
|
+
if (!targetRaw || !throughRaw) return null;
|
|
174
|
+
let fields;
|
|
175
|
+
try {
|
|
176
|
+
fields = deriveM2MFields(rel, source, root);
|
|
177
|
+
} catch {
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
return {
|
|
181
|
+
name: rel.name,
|
|
182
|
+
cardinality: "many",
|
|
183
|
+
targetEntity: stripPackage(targetRaw),
|
|
184
|
+
junctionEntity: stripPackage(throughRaw),
|
|
185
|
+
sourceJoinField: fields.sourceField,
|
|
186
|
+
targetJoinField: fields.targetField,
|
|
187
|
+
symmetric: rel.symmetric,
|
|
188
|
+
};
|
|
189
|
+
}
|
package/src/render-context.ts
CHANGED
|
@@ -6,6 +6,7 @@ import type { PkInfo } from "./pk-resolver.js";
|
|
|
6
6
|
import type { RelationMap } from "./relation-resolver.js";
|
|
7
7
|
import type { ColumnNamingStrategy } from "./metaobjects-config.js";
|
|
8
8
|
import type { OutputLayout, ResolvedTarget } from "./import-path.js";
|
|
9
|
+
import { variableNameFromEntity } from "./naming.js";
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* How to format cross-entity import specifiers in generated files.
|
|
@@ -37,12 +38,26 @@ export interface RenderContext {
|
|
|
37
38
|
extStyle: ExtStyle;
|
|
38
39
|
/** Column naming strategy: how field names map to DB column names. Defaults to "snake_case". */
|
|
39
40
|
columnNamingStrategy: ColumnNamingStrategy;
|
|
41
|
+
/**
|
|
42
|
+
* Drizzle timestamp column mode. "string" (default) types timestamp columns as
|
|
43
|
+
* ISO-8601 strings (matching the generated Zod + cross-port wire contract);
|
|
44
|
+
* "date" uses drizzle's native JS-Date mode (for consumers whose hand-written
|
|
45
|
+
* code works with `Date`). Opt in via `codegen.timestampMode`.
|
|
46
|
+
*/
|
|
47
|
+
timestampMode: "date" | "string";
|
|
40
48
|
/** Path prefix applied to generated route registrations + hook fetch URLs. Defaults to "". */
|
|
41
49
|
apiPrefix: string;
|
|
42
50
|
/** Whether abstract entities emit their shape artifact (type-only interface / value-object file). Defaults to true. Instance/write artifacts are never emitted for abstract entities regardless. */
|
|
43
51
|
emitAbstractShapes: boolean;
|
|
44
52
|
/** Output layout mode: "flat" (default) — all files in outDir; "package" — sub-paths from entity metadata package. */
|
|
45
53
|
outputLayout: OutputLayout;
|
|
54
|
+
/**
|
|
55
|
+
* Resolve an entity name to its Drizzle collection (table) variable name,
|
|
56
|
+
* applying the project's pluralization config + per-entity overrides. Every
|
|
57
|
+
* template that emits or references a table var goes through this so the
|
|
58
|
+
* declaration and all references agree. Defaults to always-pluralize.
|
|
59
|
+
*/
|
|
60
|
+
collectionName: (entityName: string) => string;
|
|
46
61
|
/** The target THIS generator emits to (drives path layout + same-target imports). */
|
|
47
62
|
selfTarget: ResolvedTarget;
|
|
48
63
|
/** Where entity files live (drives cross-target entity imports). */
|
|
@@ -52,19 +67,28 @@ export interface RenderContext {
|
|
|
52
67
|
relationMap: RelationMap;
|
|
53
68
|
/** Entity name → its metadata package (undefined if the entity has no package). Built once per run. */
|
|
54
69
|
packageOf: Map<string, string | undefined>;
|
|
70
|
+
/** FR-019: module specifier to import externally-PROVIDED shared enums from
|
|
71
|
+
* (`@provided: true` declarations). Undefined when unset — referencing a
|
|
72
|
+
* provided enum without it is a codegen-time error. */
|
|
73
|
+
providedEnumModule?: string;
|
|
55
74
|
}
|
|
56
75
|
|
|
57
|
-
/** Optional shape — `extStyle`, `omImport`, `columnNamingStrategy`, `apiPrefix`, `outputLayout`, and `packageOf` default if omitted. `packageOf` defaults to an empty Map (correct for flat layout; `runGen` always provides the real map). */
|
|
58
|
-
export type RenderContextInput = Omit<RenderContext, "extStyle" | "omImport" | "columnNamingStrategy" | "apiPrefix" | "emitAbstractShapes" | "outputLayout" | "packageOf" | "selfTarget" | "entityModuleTarget"> & {
|
|
76
|
+
/** Optional shape — `extStyle`, `omImport`, `columnNamingStrategy`, `apiPrefix`, `outputLayout`, and `packageOf` default if omitted. `packageOf` defaults to an empty Map (correct for flat layout; `runGen` always provides the real map). `collectionName` is built from `pluralizeCollections` + `collectionNameOverrides` (both default to always-pluralize). */
|
|
77
|
+
export type RenderContextInput = Omit<RenderContext, "extStyle" | "omImport" | "columnNamingStrategy" | "timestampMode" | "apiPrefix" | "emitAbstractShapes" | "outputLayout" | "packageOf" | "selfTarget" | "entityModuleTarget" | "collectionName"> & {
|
|
59
78
|
extStyle?: ExtStyle;
|
|
60
79
|
omImport?: string;
|
|
61
80
|
columnNamingStrategy?: ColumnNamingStrategy;
|
|
81
|
+
timestampMode?: "date" | "string";
|
|
62
82
|
apiPrefix?: string;
|
|
63
83
|
emitAbstractShapes?: boolean;
|
|
64
84
|
outputLayout?: OutputLayout;
|
|
65
85
|
packageOf?: Map<string, string | undefined>;
|
|
66
86
|
selfTarget?: ResolvedTarget;
|
|
67
87
|
entityModuleTarget?: ResolvedTarget;
|
|
88
|
+
/** Auto-pluralize collection (table) variable names. Default true. */
|
|
89
|
+
pluralizeCollections?: boolean;
|
|
90
|
+
/** Per-entity exact collection-var-name overrides, keyed by bare entity name. */
|
|
91
|
+
collectionNameOverrides?: Record<string, string>;
|
|
68
92
|
};
|
|
69
93
|
|
|
70
94
|
/** Append the configured extension to a cross-entity module specifier. */
|
|
@@ -82,16 +106,22 @@ export function makeRenderContext(opts: RenderContextInput): RenderContext {
|
|
|
82
106
|
outputLayout,
|
|
83
107
|
dbImport: opts.dbImport,
|
|
84
108
|
};
|
|
109
|
+
const collectionNameOpts = {
|
|
110
|
+
pluralize: opts.pluralizeCollections ?? true,
|
|
111
|
+
overrides: opts.collectionNameOverrides ?? {},
|
|
112
|
+
};
|
|
85
113
|
return {
|
|
86
114
|
...opts,
|
|
87
115
|
extStyle: opts.extStyle ?? "none",
|
|
88
116
|
omImport: opts.omImport ?? "../index",
|
|
89
117
|
columnNamingStrategy: opts.columnNamingStrategy ?? "snake_case",
|
|
118
|
+
timestampMode: opts.timestampMode ?? "string",
|
|
90
119
|
apiPrefix: opts.apiPrefix ?? "",
|
|
91
120
|
emitAbstractShapes: opts.emitAbstractShapes ?? true,
|
|
92
121
|
outputLayout,
|
|
93
122
|
packageOf: opts.packageOf ?? new Map(),
|
|
94
123
|
selfTarget: defaultTarget,
|
|
95
124
|
entityModuleTarget: opts.entityModuleTarget ?? defaultTarget,
|
|
125
|
+
collectionName: (entityName: string) => variableNameFromEntity(entityName, collectionNameOpts),
|
|
96
126
|
};
|
|
97
127
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// @generated from templates/*/*.mustache — DO NOT EDIT.
|
|
2
|
+
// Regenerate: bun run scripts/generate-embedded-templates.ts (or scripts/sync-doc-templates.sh).
|
|
3
|
+
//
|
|
4
|
+
// Embedded framework doc templates so they resolve inside the
|
|
5
|
+
// `bun build --compile` standalone `meta` binary, where the on-disk
|
|
6
|
+
// `templates/` directory is unavailable. Keys are provider resolve refs
|
|
7
|
+
// (path under templates/ minus the .mustache suffix).
|
|
8
|
+
export const EMBEDDED_FRAMEWORK_TEMPLATES: Record<string, string> = {
|
|
9
|
+
"api/agent-api.md": "{{{generatedMarker}}}\n\n# {{title}}\n\nGenerated API reference for {{project}}; call these exactly as written. {{importNote}}\n{{#hasSetup}}\n\n## Setup\n{{#setup}}\n- `{{handle}}` — {{{note}}} `{{{snippetInline}}}`\n{{/setup}}\n{{/hasSetup}}\n{{#units}}\n\n## {{node}}\n{{#groups}}\n\n`{{importHeader}}`\n{{#symbols}}\n- `{{signature}}` — {{usage}}{{#throwsMarker}} {{throwsMarker}}{{/throwsMarker}}\n{{/symbols}}\n{{/groups}}\n{{#example}}\n\nExample:\n```ts\n{{{example}}}\n```\n{{/example}}\n{{/units}}\n",
|
|
10
|
+
"api/entity-api.md": "{{{generatedMarker}}}\n\n# {{node}} API\n{{#modelPageHref}}\n\n**Model / metadata:** [{{node}}]({{modelPageHref}})\n{{/modelPageHref}}\n\n> Import paths are relative to your generated-output directory.\n{{#hasSetup}}\n\n## Setup\n\nObtain the runtime handles the calls below need:\n{{#setup}}\n\n- `{{handle}}` — {{{note}}}\n\n```ts\n{{{snippet}}}\n```\n{{/setup}}\n{{/hasSetup}}\n{{#unitExample}}\n\n## Example\n\n```ts\n{{{unitExample}}}\n```\n{{/unitExample}}\n{{#sections}}\n\n## {{heading}}\n{{#symbols}}\n\n### `{{signature}}`\n\n{{usage}}\n\n```ts\n{{importLine}}\n```\n{{#hasFields}}\n\n{{fieldsCaption}}:\n\n| Field | Type | Required | Notes |\n|---|---|---|---|\n{{#fieldRows}}\n| `{{field}}` | `{{{type}}}` | {{required}} | {{notes}} |\n{{/fieldRows}}\n{{/hasFields}}\n{{#mountNote}}\n\nMount: {{{mountNote}}}\n{{/mountNote}}\n{{#throws}}\n\nThrows: {{throws}}\n{{/throws}}\n{{#example}}\n\n```ts\n{{{example}}}\n```\n{{/example}}\n{{/symbols}}\n{{/sections}}\n",
|
|
11
|
+
"api/index.md": "{{{generatedMarker}}}\n\n# {{title}}\n\n{{intro}}\n{{#hasEntities}}\n\n## Entities\n\n{{#entities}}\n- [{{node}}]({{href}}) — {{summary}} ({{symbolCount}} symbol{{^one}}s{{/one}})\n{{/entities}}\n{{/hasEntities}}\n{{#hasTemplates}}\n\n## Templates\n\n{{#templates}}\n- [{{node}}]({{href}}) — {{summary}} ({{symbolCount}} symbol{{^one}}s{{/one}})\n{{/templates}}\n{{/hasTemplates}}\n",
|
|
12
|
+
"docs/entity-page.md": "{{{generatedMarker}}}\n\n# {{entity.name}}\n{{#summaryLead}}\n\n{{{.}}}\n{{/summaryLead}}\n{{#descriptionQuote}}\n\n{{{.}}}\n{{/descriptionQuote}}\n{{#apiRefs.0}}\n\n**API reference:** {{/apiRefs.0}}{{#apiRefs}}[{{label}}]({{href}}){{^last}} · {{/last}}{{/apiRefs}}{{#apiRefs.0}}\n{{/apiRefs.0}}\n\n{{{preambleHeader}}}\n{{#hasIdentities}}\n\n## Identity\n\n{{#identities}}\n- {{{bullet}}}\n{{/identities}}\n{{/hasIdentities}}\n{{#hasNeighborhoodEr}}\n\n## In context\n\n{{{neighborhoodErBlock}}}\n{{/hasNeighborhoodEr}}\n{{#fields.hasFields}}\n\n## Fields\n\n| Field | Type | Required | Column | Rules |\n|---|---|---|---|---|\n{{#fields.rows}}\n| {{{fieldCell}}} | {{{typeCell}}} | {{requiredCell}} | {{{storageCell}}} | {{{rulesCell}}} |\n{{/fields.rows}}\n{{/fields.hasFields}}\n{{#fieldDetails.hasDetails}}\n\n## Field details\n\n{{#fieldDetails.rows}}\n{{{block}}}\n\n{{/fieldDetails.rows}}\n{{/fieldDetails.hasDetails}}\n{{#hasRelationships}}\n\n## Relationships\n\n{{#relationships}}\n- {{{bullet}}}\n{{/relationships}}\n{{/hasRelationships}}\n{{#hasUsedBy}}\n\n## Used by\n\n{{#usedBy}}\n- {{{bullet}}}\n{{/usedBy}}\n{{/hasUsedBy}}\n",
|
|
13
|
+
"docs/template-page.md": "{{{generatedMarker}}}\n\n# {{name}}\n{{#descriptionQuote}}\n\n{{{.}}}\n{{/descriptionQuote}}\n\n**Kind:** {{kind}}\n\n## Output\n{{^isEmail}}\n\n- Format: `{{format}}`\n{{/isEmail}}\n{{#isEmail}}\n\nMultipart email — rendered as the following parts:\n\n| Part | Source | Format | Escaping |\n|---|---|---|---|\n{{#parts}}\n| {{label}} | `{{ref}}` | `{{format}}` | {{#escaped}}escaped{{/escaped}}{{^escaped}}raw{{/escaped}} |\n{{/parts}}\n{{/isEmail}}\n\n## Input\n\n- Payload: [`{{payload.name}}`]({{payload.link}})\n{{#hasRequiredTags}}\n- Required fields:{{#requiredTags}} `{{.}}`{{/requiredTags}}\n{{/hasRequiredTags}}\n\n## Render contract\n\n- Every field referenced by the template is validated against the payload at generation time; an unknown field fails generation.\n{{#maxChars}}\n- Maximum length: {{.}} characters (rendering longer output fails).\n{{/maxChars}}\n{{#hasRequiredTags}}\n- Required tags must be present:{{#requiredTags}} `{{.}}`{{/requiredTags}}\n{{/hasRequiredTags}}\n\n## Source\n\n{{#sourceRefs}}\n- `{{.}}`\n{{/sourceRefs}}\n{{#templateSourceSection}}\n\n{{{.}}}\n{{/templateSourceSection}}\n\n## Capability\n\n{{capability}}\n",
|
|
14
|
+
};
|
|
@@ -16,6 +16,7 @@ import type { Provider } from "@metaobjectsdev/render";
|
|
|
16
16
|
import { existsSync, readFileSync } from "node:fs";
|
|
17
17
|
import { join, resolve, dirname } from "node:path";
|
|
18
18
|
import { fileURLToPath } from "node:url";
|
|
19
|
+
import { EMBEDDED_FRAMEWORK_TEMPLATES } from "./embedded-templates.generated.js";
|
|
19
20
|
|
|
20
21
|
/** Canonical shipped template — used to verify a candidate framework
|
|
21
22
|
* templates directory actually contains our defaults. Without this check a
|
|
@@ -31,8 +32,11 @@ const CANONICAL_TEMPLATE_REL = "docs/entity-page.md.mustache";
|
|
|
31
32
|
*
|
|
32
33
|
* Returns `undefined` when no on-disk templates dir can be found — e.g. inside
|
|
33
34
|
* the `bun build --compile` standalone binary, whose `import.meta.url` is a
|
|
34
|
-
* `/$bunfs/root` virtual path with no real `package.json` alongside it.
|
|
35
|
-
*
|
|
35
|
+
* `/$bunfs/root` virtual path with no real `package.json` alongside it. In that
|
|
36
|
+
* case `FrameworkTemplatesProvider.resolve` falls back to the bundled
|
|
37
|
+
* `EMBEDDED_FRAMEWORK_TEMPLATES` (embedded-templates.generated.ts), a plain
|
|
38
|
+
* string module generated from the canonical templates/docs/*.mustache and
|
|
39
|
+
* compiled into the binary. */
|
|
36
40
|
function findFrameworkTemplatesDir(start: string): string | undefined {
|
|
37
41
|
let dir = start;
|
|
38
42
|
while (true) {
|
|
@@ -81,23 +85,33 @@ export class FileSystemProvider implements Provider {
|
|
|
81
85
|
}
|
|
82
86
|
|
|
83
87
|
/** The framework defaults provider — resolves refs against codegen-ts's own
|
|
84
|
-
* on-disk `templates/` directory
|
|
88
|
+
* on-disk `templates/` directory, falling back to the bundled
|
|
89
|
+
* `EMBEDDED_FRAMEWORK_TEMPLATES` when no on-disk dir exists.
|
|
85
90
|
*
|
|
86
91
|
* Resolution is lazy: the directory is located on first `resolve()`, not at
|
|
87
92
|
* module import. This keeps merely importing this module side-effect-free,
|
|
88
93
|
* which matters for the `bun build --compile` standalone `meta` binary — its
|
|
89
94
|
* `import.meta.url` is a `/$bunfs/root` virtual path with no on-disk
|
|
90
|
-
* `templates/` dir
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
95
|
+
* `templates/` dir.
|
|
96
|
+
*
|
|
97
|
+
* ON-DISK FIRST, then embedded: the source/install layout (and adopter
|
|
98
|
+
* overrides chained ahead of this provider via `projectProvider`) always wins,
|
|
99
|
+
* so local edits to the shipped package `templates/` still take effect. Only
|
|
100
|
+
* when the on-disk dir is unresolved (the compiled binary) — or a ref the dir
|
|
101
|
+
* doesn't contain — do we consult the embedded map, which is generated from
|
|
102
|
+
* the same canonical templates and compiled into the binary. Unknown refs are
|
|
103
|
+
* `undefined` in the map, which is the correct miss. */
|
|
94
104
|
class FrameworkTemplatesProvider implements Provider {
|
|
95
105
|
resolve(ref: string): string | undefined {
|
|
106
|
+
// On-disk first: dev/install layout, plus shipped-package edits.
|
|
96
107
|
const dir = frameworkTemplatesDir();
|
|
97
|
-
if (dir
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
108
|
+
if (dir !== undefined) {
|
|
109
|
+
const path = join(dir, `${ref}.mustache`);
|
|
110
|
+
if (existsSync(path)) return readFileSync(path, "utf-8");
|
|
111
|
+
}
|
|
112
|
+
// Embedded fallback: the binary case (no on-disk dir) or a ref the on-disk
|
|
113
|
+
// dir doesn't carry. `undefined` for unknown refs is the correct miss.
|
|
114
|
+
return EMBEDDED_FRAMEWORK_TEMPLATES[ref];
|
|
101
115
|
}
|
|
102
116
|
}
|
|
103
117
|
|
package/src/runner.ts
CHANGED
|
@@ -20,6 +20,10 @@ import {
|
|
|
20
20
|
* from untrusted sources (e.g. MCP). Mirrors the guard in legacy generate.ts. */
|
|
21
21
|
const VALID_ENTITY_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
22
22
|
|
|
23
|
+
/** ADR-0025: doc generators whose single door is `meta docs`. If a `meta gen`
|
|
24
|
+
* config still lists one (by its stable `name`), the runner warns + skips it. */
|
|
25
|
+
const DEPRECATED_DOC_GENERATORS = new Set(["docs-file", "api-docs"]);
|
|
26
|
+
|
|
23
27
|
export interface RunGenOpts {
|
|
24
28
|
config: MetaobjectsGenConfig;
|
|
25
29
|
metadata: MetaData;
|
|
@@ -143,9 +147,24 @@ export async function runGen(opts: RunGenOpts): Promise<RunGenResult> {
|
|
|
143
147
|
root.objects().map((o) => [o.name, o.package]),
|
|
144
148
|
);
|
|
145
149
|
|
|
150
|
+
// Auto-detect: is the OPT-IN Hono routes generator in the active suite? If so,
|
|
151
|
+
// surface it on every generator's ctx.config so api-docs documents the Hono
|
|
152
|
+
// CRUD surface it actually emits (rather than silently omitting it).
|
|
153
|
+
const includeHonoRoutes = config.generators.some((g) => g.emitsHonoRoutes === true);
|
|
154
|
+
|
|
146
155
|
// 4. Run each generator with a per-target render context; collect with full path.
|
|
147
156
|
const emitted: { fullPath: string; content: string; generatedBy: string }[] = [];
|
|
148
157
|
for (const generator of config.generators) {
|
|
158
|
+
// ADR-0025: `meta docs` is the single docs door. A `meta gen` config that
|
|
159
|
+
// still lists a deprecated doc generator is warned + skipped, not run — the
|
|
160
|
+
// generator stays as `meta docs`'s internal engine.
|
|
161
|
+
if (DEPRECATED_DOC_GENERATORS.has(generator.name)) {
|
|
162
|
+
warnings.push(
|
|
163
|
+
`[${generator.name}] docs are produced by 'meta docs' (ADR-0025); ` +
|
|
164
|
+
`remove ${generator.name === "api-docs" ? "apiDocsFile()" : "docsFile()"} from generators. Skipped.`,
|
|
165
|
+
);
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
149
168
|
const selfTarget = targetOf(generator);
|
|
150
169
|
const renderContext = makeRenderContext({
|
|
151
170
|
dialect: config.dialect,
|
|
@@ -154,6 +173,9 @@ export async function runGen(opts: RunGenOpts): Promise<RunGenResult> {
|
|
|
154
173
|
dbImport: selfTarget.dbImport,
|
|
155
174
|
extStyle: config.extStyle,
|
|
156
175
|
columnNamingStrategy: config.columnNamingStrategy,
|
|
176
|
+
pluralizeCollections: config.pluralizeCollections,
|
|
177
|
+
collectionNameOverrides: config.collectionNameOverrides,
|
|
178
|
+
timestampMode: config.timestampMode,
|
|
157
179
|
apiPrefix: config.apiPrefix,
|
|
158
180
|
emitAbstractShapes: config.emitAbstractShapes,
|
|
159
181
|
outputLayout: selfTarget.outputLayout,
|
|
@@ -162,6 +184,7 @@ export async function runGen(opts: RunGenOpts): Promise<RunGenResult> {
|
|
|
162
184
|
packageOf,
|
|
163
185
|
selfTarget,
|
|
164
186
|
entityModuleTarget,
|
|
187
|
+
...(config.providedEnumModule !== undefined && { providedEnumModule: config.providedEnumModule }),
|
|
165
188
|
});
|
|
166
189
|
const ctx: GenContext = {
|
|
167
190
|
entities: safeEntities,
|
|
@@ -173,6 +196,7 @@ export async function runGen(opts: RunGenOpts): Promise<RunGenResult> {
|
|
|
173
196
|
dbImport: selfTarget.dbImport,
|
|
174
197
|
dialect: config.dialect,
|
|
175
198
|
outputLayout: selfTarget.outputLayout,
|
|
199
|
+
includeHonoRoutes,
|
|
176
200
|
},
|
|
177
201
|
renderContext,
|
|
178
202
|
...(projectRoot !== undefined && { projectRoot }),
|