@metaobjectsdev/codegen-ts 0.24.4 → 0.25.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 +37 -12
- package/dist/api-surface.d.ts +29 -0
- package/dist/api-surface.d.ts.map +1 -1
- package/dist/api-surface.js +47 -0
- package/dist/api-surface.js.map +1 -1
- package/dist/client-directive.d.ts +18 -0
- package/dist/client-directive.d.ts.map +1 -0
- package/dist/client-directive.js +37 -0
- package/dist/client-directive.js.map +1 -0
- package/dist/column-mapper.d.ts +12 -0
- package/dist/column-mapper.d.ts.map +1 -1
- package/dist/column-mapper.js +1 -0
- package/dist/column-mapper.js.map +1 -1
- package/dist/constants.d.ts +24 -11
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +54 -19
- package/dist/constants.js.map +1 -1
- package/dist/db-emitting.d.ts +6 -0
- package/dist/db-emitting.d.ts.map +1 -0
- package/dist/db-emitting.js +26 -0
- package/dist/db-emitting.js.map +1 -0
- package/dist/generator-registry.d.ts.map +1 -1
- package/dist/generator-registry.js +7 -1
- package/dist/generator-registry.js.map +1 -1
- package/dist/generator.d.ts +5 -0
- package/dist/generator.d.ts.map +1 -1
- package/dist/generator.js.map +1 -1
- package/dist/generators/agent-docs-file.d.ts +19 -0
- package/dist/generators/agent-docs-file.d.ts.map +1 -0
- package/dist/generators/agent-docs-file.js +257 -0
- package/dist/generators/agent-docs-file.js.map +1 -0
- package/dist/generators/agent-requirements-page.d.ts +22 -0
- package/dist/generators/agent-requirements-page.d.ts.map +1 -0
- package/dist/generators/agent-requirements-page.js +175 -0
- package/dist/generators/agent-requirements-page.js.map +1 -0
- package/dist/generators/agent-schema-input.d.ts +69 -0
- package/dist/generators/agent-schema-input.d.ts.map +1 -0
- package/dist/generators/agent-schema-input.js +23 -0
- package/dist/generators/agent-schema-input.js.map +1 -0
- package/dist/generators/agent-schema-page.d.ts +21 -0
- package/dist/generators/agent-schema-page.d.ts.map +1 -0
- package/dist/generators/agent-schema-page.js +241 -0
- package/dist/generators/agent-schema-page.js.map +1 -0
- package/dist/generators/agent-ui-page.d.ts +22 -0
- package/dist/generators/agent-ui-page.d.ts.map +1 -0
- package/dist/generators/agent-ui-page.js +253 -0
- package/dist/generators/agent-ui-page.js.map +1 -0
- package/dist/generators/api-docs-file.d.ts.map +1 -1
- package/dist/generators/api-docs-file.js +13 -6
- package/dist/generators/api-docs-file.js.map +1 -1
- package/dist/generators/api-model.d.ts +6 -0
- package/dist/generators/api-model.d.ts.map +1 -1
- package/dist/generators/api-model.js +54 -35
- package/dist/generators/api-model.js.map +1 -1
- package/dist/generators/callable-file.js +1 -1
- package/dist/generators/callable-file.js.map +1 -1
- package/dist/generators/docs-data-builder.d.ts.map +1 -1
- package/dist/generators/docs-data-builder.js +47 -2
- package/dist/generators/docs-data-builder.js.map +1 -1
- package/dist/generators/docs-data.d.ts +6 -2
- package/dist/generators/docs-data.d.ts.map +1 -1
- package/dist/generators/index.d.ts +3 -0
- package/dist/generators/index.d.ts.map +1 -1
- package/dist/generators/index.js +4 -0
- package/dist/generators/index.js.map +1 -1
- package/dist/generators/names-file.d.ts +17 -0
- package/dist/generators/names-file.d.ts.map +1 -0
- package/dist/generators/names-file.js +95 -0
- package/dist/generators/names-file.js.map +1 -0
- package/dist/generators/requirements-markdown.d.ts +18 -1
- package/dist/generators/requirements-markdown.d.ts.map +1 -1
- package/dist/generators/requirements-markdown.js +11 -9
- package/dist/generators/requirements-markdown.js.map +1 -1
- package/dist/generators/routes-file-hono.d.ts +17 -2
- package/dist/generators/routes-file-hono.d.ts.map +1 -1
- package/dist/generators/routes-file-hono.js +15 -12
- package/dist/generators/routes-file-hono.js.map +1 -1
- package/dist/generators/routes-file.d.ts +17 -2
- package/dist/generators/routes-file.d.ts.map +1 -1
- package/dist/generators/routes-file.js +10 -8
- package/dist/generators/routes-file.js.map +1 -1
- package/dist/generators/template-generator.d.ts.map +1 -1
- package/dist/generators/template-generator.js +20 -6
- package/dist/generators/template-generator.js.map +1 -1
- package/dist/index.d.ts +21 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +40 -4
- package/dist/index.js.map +1 -1
- package/dist/metaobjects-config.d.ts +67 -1
- package/dist/metaobjects-config.d.ts.map +1 -1
- package/dist/metaobjects-config.js +10 -1
- package/dist/metaobjects-config.js.map +1 -1
- package/dist/names.d.ts +275 -0
- package/dist/names.d.ts.map +1 -0
- package/dist/names.js +434 -0
- package/dist/names.js.map +1 -0
- package/dist/naming.d.ts +17 -0
- package/dist/naming.d.ts.map +1 -1
- package/dist/naming.js +21 -0
- package/dist/naming.js.map +1 -1
- package/dist/overwrite-policy.d.ts.map +1 -1
- package/dist/overwrite-policy.js +15 -3
- package/dist/overwrite-policy.js.map +1 -1
- package/dist/payload-codegen.d.ts.map +1 -1
- package/dist/payload-codegen.js +4 -3
- package/dist/payload-codegen.js.map +1 -1
- package/dist/projection/extract-view-spec.d.ts +25 -1
- package/dist/projection/extract-view-spec.d.ts.map +1 -1
- package/dist/projection/extract-view-spec.js +79 -6
- package/dist/projection/extract-view-spec.js.map +1 -1
- package/dist/prompt-generator-gate.d.ts +27 -0
- package/dist/prompt-generator-gate.d.ts.map +1 -0
- package/dist/prompt-generator-gate.js +62 -0
- package/dist/prompt-generator-gate.js.map +1 -0
- package/dist/reference-templates.d.ts +22 -5
- package/dist/reference-templates.d.ts.map +1 -1
- package/dist/reference-templates.js +55 -21
- package/dist/reference-templates.js.map +1 -1
- package/dist/render-context.d.ts +20 -1
- package/dist/render-context.d.ts.map +1 -1
- package/dist/render-context.js +2 -0
- package/dist/render-context.js.map +1 -1
- package/dist/retired-codegen-attrs.d.ts +32 -0
- package/dist/retired-codegen-attrs.d.ts.map +1 -0
- package/dist/retired-codegen-attrs.js +47 -0
- package/dist/retired-codegen-attrs.js.map +1 -0
- package/dist/routes-expose.d.ts +63 -0
- package/dist/routes-expose.d.ts.map +1 -0
- package/dist/routes-expose.js +57 -0
- package/dist/routes-expose.js.map +1 -0
- package/dist/runner.d.ts +18 -0
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +115 -7
- package/dist/runner.js.map +1 -1
- package/dist/templates/callable-file.d.ts +7 -2
- package/dist/templates/callable-file.d.ts.map +1 -1
- package/dist/templates/callable-file.js +54 -9
- package/dist/templates/callable-file.js.map +1 -1
- package/dist/templates/drizzle-schema.d.ts.map +1 -1
- package/dist/templates/drizzle-schema.js +236 -65
- package/dist/templates/drizzle-schema.js.map +1 -1
- package/dist/templates/entity-constants.d.ts +15 -6
- package/dist/templates/entity-constants.d.ts.map +1 -1
- package/dist/templates/entity-constants.js +85 -161
- 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 +38 -4
- package/dist/templates/entity-file.js.map +1 -1
- package/dist/templates/entity-meta-file.d.ts +15 -1
- package/dist/templates/entity-meta-file.d.ts.map +1 -1
- package/dist/templates/entity-meta-file.js +12 -2
- package/dist/templates/entity-meta-file.js.map +1 -1
- package/dist/templates/entity-ui-descriptor.d.ts +112 -0
- package/dist/templates/entity-ui-descriptor.d.ts.map +1 -0
- package/dist/templates/entity-ui-descriptor.js +238 -0
- package/dist/templates/entity-ui-descriptor.js.map +1 -0
- package/dist/templates/field-meta.d.ts +46 -9
- package/dist/templates/field-meta.d.ts.map +1 -1
- package/dist/templates/field-meta.js +95 -21
- package/dist/templates/field-meta.js.map +1 -1
- package/dist/templates/names-decl.d.ts +38 -0
- package/dist/templates/names-decl.d.ts.map +1 -0
- package/dist/templates/names-decl.js +104 -0
- package/dist/templates/names-decl.js.map +1 -0
- package/dist/templates/output-parser.d.ts.map +1 -1
- package/dist/templates/output-parser.js +54 -8
- package/dist/templates/output-parser.js.map +1 -1
- package/dist/templates/projection-decl.d.ts +21 -0
- package/dist/templates/projection-decl.d.ts.map +1 -1
- package/dist/templates/projection-decl.js +53 -29
- package/dist/templates/projection-decl.js.map +1 -1
- package/dist/templates/queries-file.d.ts.map +1 -1
- package/dist/templates/queries-file.js +17 -2
- package/dist/templates/queries-file.js.map +1 -1
- package/dist/templates/render-helper.d.ts.map +1 -1
- package/dist/templates/render-helper.js +3 -1
- package/dist/templates/render-helper.js.map +1 -1
- package/dist/templates/requirement-test.d.ts.map +1 -1
- package/dist/templates/requirement-test.js +48 -21
- package/dist/templates/requirement-test.js.map +1 -1
- package/dist/templates/routes-file-hono.d.ts +2 -1
- package/dist/templates/routes-file-hono.d.ts.map +1 -1
- package/dist/templates/routes-file-hono.js +11 -2
- package/dist/templates/routes-file-hono.js.map +1 -1
- package/dist/templates/routes-file.d.ts +2 -1
- package/dist/templates/routes-file.d.ts.map +1 -1
- package/dist/templates/routes-file.js +54 -21
- package/dist/templates/routes-file.js.map +1 -1
- package/dist/templates/value-object-file.d.ts.map +1 -1
- package/dist/templates/value-object-file.js +15 -1
- package/dist/templates/value-object-file.js.map +1 -1
- package/dist/templates/view-decl.d.ts +37 -1
- package/dist/templates/view-decl.d.ts.map +1 -1
- package/dist/templates/view-decl.js +27 -3
- package/dist/templates/view-decl.js.map +1 -1
- package/dist/templates/zod-validators.d.ts +21 -0
- package/dist/templates/zod-validators.d.ts.map +1 -1
- package/dist/templates/zod-validators.js +40 -5
- package/dist/templates/zod-validators.js.map +1 -1
- package/dist/view-context.d.ts +24 -0
- package/dist/view-context.d.ts.map +1 -0
- package/dist/view-context.js +81 -0
- package/dist/view-context.js.map +1 -0
- package/package.json +6 -6
- package/src/api-surface.ts +53 -0
- package/src/client-directive.ts +36 -0
- package/src/column-mapper.ts +13 -0
- package/src/constants.ts +91 -16
- package/src/db-emitting.ts +31 -0
- package/src/generator-registry.ts +7 -0
- package/src/generator.ts +5 -0
- package/src/generators/agent-docs-file.ts +297 -0
- package/src/generators/agent-requirements-page.ts +199 -0
- package/src/generators/agent-schema-input.ts +88 -0
- package/src/generators/agent-schema-page.ts +297 -0
- package/src/generators/agent-ui-page.ts +290 -0
- package/src/generators/api-docs-file.ts +13 -6
- package/src/generators/api-model.ts +63 -34
- package/src/generators/callable-file.ts +1 -1
- package/src/generators/docs-data-builder.ts +46 -2
- package/src/generators/docs-data.ts +6 -2
- package/src/generators/index.ts +10 -0
- package/src/generators/names-file.ts +98 -0
- package/src/generators/requirements-markdown.ts +36 -2
- package/src/generators/routes-file-hono.ts +26 -15
- package/src/generators/routes-file.ts +20 -7
- package/src/generators/template-generator.ts +20 -6
- package/src/index.ts +63 -5
- package/src/metaobjects-config.ts +78 -2
- package/src/names.ts +630 -0
- package/src/naming.ts +23 -0
- package/src/overwrite-policy.ts +15 -3
- package/src/payload-codegen.ts +4 -3
- package/src/projection/extract-view-spec.ts +85 -6
- package/src/prompt-generator-gate.ts +73 -0
- package/src/reference/barrel.ts +3 -0
- package/src/reference/entity.ts +28 -2
- package/src/reference/names.ts +125 -0
- package/src/reference/queries.ts +14 -2
- package/src/reference/routes-hono.ts +113 -0
- package/src/reference/routes.ts +30 -9
- package/src/reference-templates.ts +61 -21
- package/src/render-context.ts +22 -1
- package/src/retired-codegen-attrs.ts +52 -0
- package/src/routes-expose.ts +86 -0
- package/src/runner.ts +129 -11
- package/src/templates/callable-file.ts +60 -8
- package/src/templates/drizzle-schema.ts +248 -60
- package/src/templates/entity-constants.ts +95 -184
- package/src/templates/entity-file.ts +46 -4
- package/src/templates/entity-meta-file.ts +16 -3
- package/src/templates/entity-ui-descriptor.ts +305 -0
- package/src/templates/field-meta.ts +100 -19
- package/src/templates/names-decl.ts +165 -0
- package/src/templates/output-parser.ts +54 -7
- package/src/templates/projection-decl.ts +72 -31
- package/src/templates/queries-file.ts +17 -2
- package/src/templates/render-helper.ts +3 -1
- package/src/templates/requirement-test.ts +49 -18
- package/src/templates/routes-file-hono.ts +14 -2
- package/src/templates/routes-file.ts +69 -24
- package/src/templates/value-object-file.ts +15 -1
- package/src/templates/view-decl.ts +53 -3
- package/src/templates/zod-validators.ts +41 -5
- package/src/view-context.ts +88 -0
|
@@ -23,6 +23,7 @@ import { renderEntityConstants } from "./entity-constants.js";
|
|
|
23
23
|
import { renderFilterAllowlist, renderSortAllowlist } from "./filter-allowlist.js";
|
|
24
24
|
import { renderFilterType } from "./filter-type.js";
|
|
25
25
|
import { GENERATED_HEADER } from "../constants.js";
|
|
26
|
+
import { namesRef, namesConstArg } from "../names.js";
|
|
26
27
|
|
|
27
28
|
export function renderValueObjectFile(obj: MetaObject, apiPrefix = "", ctx?: RenderContext): string {
|
|
28
29
|
const enumAliases = renderEnumTypeAliases(obj, ctx);
|
|
@@ -34,7 +35,20 @@ export function renderValueObjectFile(obj: MetaObject, apiPrefix = "", ctx?: Ren
|
|
|
34
35
|
// FR-017 Tier 3: a TPH subtype also emits its field-metadata constants object
|
|
35
36
|
// (the `<Sub>` const), so the React form generator can render per-field
|
|
36
37
|
// labels / rules / inputs the same way it does for ordinary entities.
|
|
37
|
-
|
|
38
|
+
//
|
|
39
|
+
// §A6/§B2 — a TPH subtype declares no source.rdb of its own; it INHERITS the
|
|
40
|
+
// discriminator base's single shared table (`children()` is resolving — ADR-0039).
|
|
41
|
+
// `resolveObjectNames` therefore returns a DEFINED result for it (confirmed
|
|
42
|
+
// empirically: `name` is the BASE's physical table name, `fields` covers both the
|
|
43
|
+
// base's inherited columns and the subtype's own), and `namesFile()` really does emit
|
|
44
|
+
// a `<Sub>.names.ts` for it — this is not a case where the literal is correct and must
|
|
45
|
+
// stay. Same `namesRef` pair every other §A6 site builds; `ctx` is guaranteed present
|
|
46
|
+
// at every real call site (entity-file.ts and its reference/entity.ts mirror both
|
|
47
|
+
// always pass it) even though the parameter stays optional for a bare unit-test call.
|
|
48
|
+
const tphNames = tphSubtype && ctx !== undefined ? namesRef(obj, ctx) : undefined;
|
|
49
|
+
const tphConstants = tphSubtype
|
|
50
|
+
? renderEntityConstants(obj, apiPrefix, namesConstArg(tphNames))
|
|
51
|
+
: null;
|
|
38
52
|
// FR-017 Tier 3: per-subtype filter + sort allowlists, excluding the
|
|
39
53
|
// discriminator (it's pinned by the per-subtype route path, so a client must
|
|
40
54
|
// not filter on it). Included fields are the subtype's own + inherited base
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
import type { ColumnNamingStrategy } from "../metaobjects-config.js";
|
|
15
15
|
import { mapColumnType } from "../column-mapper.js";
|
|
16
16
|
import { zodTypeFor } from "./field-meta.js";
|
|
17
|
+
import { columnExpr, type ObjectNames } from "../names.js";
|
|
17
18
|
|
|
18
19
|
export interface ViewDeclOpts {
|
|
19
20
|
readonly dialect: "postgres" | "sqlite";
|
|
@@ -38,6 +39,31 @@ export interface ViewDeclOpts {
|
|
|
38
39
|
* UpdateSchema, all of which already treat the PK as non-null.
|
|
39
40
|
*/
|
|
40
41
|
readonly pkFieldNames: ReadonlySet<string>;
|
|
42
|
+
/**
|
|
43
|
+
* §A6 — the resolved names artifact supplying this declaration's COLUMN constants,
|
|
44
|
+
* plus the ts-poet symbol reference (`imp()`'d import of `<Object>Names`), when the
|
|
45
|
+
* names generator is in the run. Absent ⇒ every column stays a literal.
|
|
46
|
+
*
|
|
47
|
+
* Deliberately says nothing about the view's OWN physical name: the two answers come
|
|
48
|
+
* apart on a write-through entity, whose artifact holds the TABLE's name (its primary
|
|
49
|
+
* source) while this declaration binds the REPLICA view. Conflating them cost the
|
|
50
|
+
* write-through read model its column constants — it had to omit `names` entirely to
|
|
51
|
+
* stop the artifact's view name being emitted, and lost every
|
|
52
|
+
* `.fields.<f>.column` reference with it. The view's name is now the caller's to pass
|
|
53
|
+
* (see the `viewName` parameter), which is the only place that knows.
|
|
54
|
+
*/
|
|
55
|
+
readonly names?: { readonly resolved: ObjectNames; readonly symbol: Code } | undefined;
|
|
56
|
+
/**
|
|
57
|
+
* The view's `@schema` — a bare name, or an EXPRESSION for it (a names constant).
|
|
58
|
+
*
|
|
59
|
+
* Passed in rather than derived from `names`, and for the same reason `viewName` is: a
|
|
60
|
+
* write-through entity's REPLICA view is a different source node from the one
|
|
61
|
+
* `<Entity>Names` resolves, so the artifact's `schema` there is the WRITE TABLE's and
|
|
62
|
+
* would be a confidently wrong answer. A caller whose artifact genuinely names this view
|
|
63
|
+
* passes the constant expression; the replica-view caller passes its own literal, which
|
|
64
|
+
* is consistent with that view's NAME already being a known literal for the same reason.
|
|
65
|
+
*/
|
|
66
|
+
readonly schema?: string | Code | undefined;
|
|
41
67
|
}
|
|
42
68
|
|
|
43
69
|
/**
|
|
@@ -85,7 +111,10 @@ function viewColumnLine(f: MetaField, opts: ViewDeclOpts): Code {
|
|
|
85
111
|
dollarType = code`.$type<Record<string, ${imp(`${vo.name}@${vo.module}`)}>>()`;
|
|
86
112
|
}
|
|
87
113
|
}
|
|
88
|
-
|
|
114
|
+
// A6 — reference the constant whenever the artifact is in the run AND carries this
|
|
115
|
+
// field. A lookup MISS is normal, not a divergence — see ViewDeclOpts.names.
|
|
116
|
+
const dbNameExpr = columnExpr(opts.names, f.name, spec.dbName);
|
|
117
|
+
return code` ${f.name}: ${colSym}(${dbNameExpr}${optsArg})${dollarType}${viewModifiers}`;
|
|
89
118
|
}
|
|
90
119
|
|
|
91
120
|
/**
|
|
@@ -95,7 +124,13 @@ function viewColumnLine(f: MetaField, opts: ViewDeclOpts): Code {
|
|
|
95
124
|
*/
|
|
96
125
|
export function renderExistingViewDecl(
|
|
97
126
|
fields: readonly MetaField[],
|
|
98
|
-
|
|
127
|
+
/**
|
|
128
|
+
* The view's own physical name: a plain string is emitted as a literal, a `Code` is
|
|
129
|
+
* emitted verbatim (a caller whose names artifact genuinely names THIS view passes
|
|
130
|
+
* `physicalNameExpr(names, viewName)`). See `ViewDeclOpts.names` for why this is not
|
|
131
|
+
* derived from that artifact here.
|
|
132
|
+
*/
|
|
133
|
+
viewName: string | Code,
|
|
99
134
|
viewVar: string,
|
|
100
135
|
opts: ViewDeclOpts,
|
|
101
136
|
): Code {
|
|
@@ -103,11 +138,26 @@ export function renderExistingViewDecl(
|
|
|
103
138
|
const viewModule = opts.dialect === "postgres" ? "drizzle-orm/pg-core" : "drizzle-orm/sqlite-core";
|
|
104
139
|
const viewSym = imp(`${viewFn}@${viewModule}`);
|
|
105
140
|
const viewColumnLines = fields.map((f) => viewColumnLine(f, opts));
|
|
141
|
+
const viewNameExpr = typeof viewName === "string" ? code`${JSON.stringify(viewName)}` : viewName;
|
|
142
|
+
|
|
143
|
+
// @schema — a view lands in a schema exactly as a table does, and migrate qualifies the
|
|
144
|
+
// `CREATE VIEW` it emits. Fixing the table binding and leaving this one would be the more
|
|
145
|
+
// dangerous half-job: the two would then disagree with each OTHER as well as with the
|
|
146
|
+
// database. Drizzle spells it with the same shape as the table case, `pgSchema(s).view(...)`
|
|
147
|
+
// — PgSchema carries `view` alongside `table`. sqlite is excluded for the same reason as
|
|
148
|
+
// there: no schema concept, and migrate refuses a non-default @schema on that dialect.
|
|
149
|
+
const viewSchemaExpr: Code | undefined =
|
|
150
|
+
opts.dialect !== "postgres" || opts.schema === undefined ? undefined
|
|
151
|
+
: typeof opts.schema === "string" ? code`${JSON.stringify(opts.schema)}`
|
|
152
|
+
: opts.schema;
|
|
153
|
+
const viewCall: Code = viewSchemaExpr === undefined
|
|
154
|
+
? code`${viewSym}`
|
|
155
|
+
: code`${imp(`pgSchema@${viewModule}`)}(${viewSchemaExpr}).view`;
|
|
106
156
|
return code`
|
|
107
157
|
// View declaration — Drizzle uses this for typed SELECT queries.
|
|
108
158
|
// The SQL view is created/managed by migrate-ts; .existing() tells Drizzle
|
|
109
159
|
// not to attempt DDL for this declaration.
|
|
110
|
-
export const ${viewVar} = ${
|
|
160
|
+
export const ${viewVar} = ${viewCall}(${viewNameExpr}, {
|
|
111
161
|
${joinCode(viewColumnLines, { on: ",\n" })}
|
|
112
162
|
}).existing();
|
|
113
163
|
`;
|
|
@@ -51,19 +51,55 @@ import { isRequired } from "../column-mapper.js";
|
|
|
51
51
|
* when the object is not a TPH subtype.
|
|
52
52
|
*/
|
|
53
53
|
export function tphDiscriminatorPin(obj: MetaObject): { fieldName: string; value: string } | undefined {
|
|
54
|
-
// ADR-0039: own —
|
|
55
|
-
//
|
|
56
|
-
// walking superResolved reading each level's OWN @discriminator.
|
|
54
|
+
// ADR-0039: own — a subtype declares its OWN @discriminatorValue (must not inherit
|
|
55
|
+
// one); the base level is found by the super-resolution walk in `tphDiscriminatorLevel`.
|
|
57
56
|
const value = obj.ownAttr(OBJECT_ATTR_DISCRIMINATOR_VALUE);
|
|
58
57
|
if (typeof value !== "string" || value === "") return undefined;
|
|
58
|
+
const level = tphDiscriminatorLevel(obj);
|
|
59
|
+
if (level === undefined) return undefined;
|
|
60
|
+
return { fieldName: level.fieldName, value };
|
|
61
|
+
}
|
|
59
62
|
|
|
60
|
-
|
|
63
|
+
/**
|
|
64
|
+
* The discriminator BASE an object extends — the nearest ancestor declaring
|
|
65
|
+
* `@discriminator` — or undefined when there is none.
|
|
66
|
+
*
|
|
67
|
+
* The base is NOT the topmost ancestor: a multi-level hierarchy can extend further up
|
|
68
|
+
* past the level that owns the discriminator, and a route path composed against the
|
|
69
|
+
* wrong level names an endpoint nothing mounts. One walk, read by `tphDiscriminatorPin`
|
|
70
|
+
* and by `restPath` (api-surface.ts), so the pin and the address agree on which object
|
|
71
|
+
* is the base.
|
|
72
|
+
*/
|
|
73
|
+
export function tphDiscriminatorBase(obj: MetaObject): MetaObject | undefined {
|
|
74
|
+
return tphDiscriminatorLevel(obj)?.base;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* True when this object itself DECLARES `@discriminator` — the level of a TPH hierarchy
|
|
79
|
+
* that owns the discriminator, whether or not any concrete subtype extends it yet.
|
|
80
|
+
*
|
|
81
|
+
* ADR-0039: own — `@discriminator` identifies a hierarchy LEVEL, so it is read own: a
|
|
82
|
+
* subtype inheriting the attr through `extends` must not be mistaken for a base. This is
|
|
83
|
+
* the one predicate the form generator's filter (`hasGeneratedForm`) and the `agent/ui.md`
|
|
84
|
+
* page both ask, so "is this a discriminator base?" has one spelling.
|
|
85
|
+
*/
|
|
86
|
+
export function declaresTphDiscriminator(obj: MetaObject): boolean {
|
|
87
|
+
return typeof obj.ownAttr(OBJECT_ATTR_DISCRIMINATOR) === "string";
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* The ONE super-resolution walk behind `tphDiscriminatorBase` and `tphDiscriminatorPin`:
|
|
92
|
+
* the nearest ancestor declaring a non-empty `@discriminator`, with the field name it
|
|
93
|
+
* declares. Returning both from one walk is what keeps the pin's field name and the
|
|
94
|
+
* base's identity from ever being read at two different levels.
|
|
95
|
+
*/
|
|
96
|
+
function tphDiscriminatorLevel(obj: MetaObject): { base: MetaObject; fieldName: string } | undefined {
|
|
61
97
|
let cursor = obj.superResolved;
|
|
62
98
|
while (cursor !== undefined) {
|
|
63
99
|
// ADR-0039: own — super-resolution walk; read each level's OWN @discriminator.
|
|
64
100
|
const fieldName = cursor.ownAttr(OBJECT_ATTR_DISCRIMINATOR);
|
|
65
101
|
if (typeof fieldName === "string" && fieldName !== "") {
|
|
66
|
-
return {
|
|
102
|
+
return { base: cursor as MetaObject, fieldName };
|
|
67
103
|
}
|
|
68
104
|
cursor = cursor.superResolved;
|
|
69
105
|
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// Selecting a field's view by the SURFACE that renders it (#356).
|
|
2
|
+
//
|
|
3
|
+
// A field may legally declare more than one `view.*` child and every one of them
|
|
4
|
+
// survives the load, so an emitter reading `field.views()[0]` let DECLARATION
|
|
5
|
+
// ORDER decide its output — and because several emitters read that same list,
|
|
6
|
+
// one declaration drove three unrelated surfaces at once. Declaring a
|
|
7
|
+
// `view.text` so a grid cell rendered as text silently degraded the generated
|
|
8
|
+
// FORM to an `<input>`, and swapping two lines of JSON with no semantic change
|
|
9
|
+
// flipped the form's `<select>` back. That is not a modelling mistake; it is the
|
|
10
|
+
// only possible outcome when one declaration serves three readers.
|
|
11
|
+
//
|
|
12
|
+
// The selector is the view's `name` — a reserved structural key already legal on
|
|
13
|
+
// every node — so this needs no new attribute, no provider and no
|
|
14
|
+
// `metamodelVersion` move (ADR-0037 step 0: the vocabulary existed, only the
|
|
15
|
+
// read was wrong). The same shape as #353's resolution.
|
|
16
|
+
//
|
|
17
|
+
// Two rules, and the second is the load-bearing one:
|
|
18
|
+
//
|
|
19
|
+
// 1. A field declaring ONE view keeps its exact current behaviour: that view
|
|
20
|
+
// applies to every surface, whatever it is named (or unnamed). Existing
|
|
21
|
+
// models are untouched by construction — a `name` is how a view is
|
|
22
|
+
// ADDRESSED by `extends` (ADR-0029 `Customer.priceCents.display`), and
|
|
23
|
+
// re-reading those names as surface names would break addressing.
|
|
24
|
+
//
|
|
25
|
+
// 2. A field declaring SEVERAL views must name one for the surface being
|
|
26
|
+
// rendered. No match is a hard error, not a fallback: falling back to
|
|
27
|
+
// `views()[0]` reinstates the positional read for exactly the multi-view
|
|
28
|
+
// case this exists to fix, and falling back to the inferred default turns a
|
|
29
|
+
// `name` typo ("forms") into a silently degraded control. A field cannot
|
|
30
|
+
// declare two views with the SAME name — the loader merges same-(type,
|
|
31
|
+
// name) siblings into one node — so a match is never ambiguous.
|
|
32
|
+
|
|
33
|
+
import type { MetaField, MetaView } from "@metaobjectsdev/metadata";
|
|
34
|
+
import { CodegenError } from "./errors.js";
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The surface a generated artifact renders, and therefore the `name` its
|
|
38
|
+
* emitter looks for among a field's views.
|
|
39
|
+
*
|
|
40
|
+
* These are the only two surfaces the packaged generators render. An owned
|
|
41
|
+
* generator (FR-040) targeting a third may pass its own name — `viewForContext`
|
|
42
|
+
* takes any string — but it then owns telling its authors what to name.
|
|
43
|
+
*/
|
|
44
|
+
export const VIEW_CONTEXT_FORM = "form";
|
|
45
|
+
/** Both grid tiers (TanStack and Angular) render the same surface, so both ask for this. */
|
|
46
|
+
export const VIEW_CONTEXT_GRID = "grid";
|
|
47
|
+
|
|
48
|
+
/** Render `view.dropdown name="form"` / `view.text (no name)` for a diagnostic. */
|
|
49
|
+
function describe(view: MetaView): string {
|
|
50
|
+
return view.name.length > 0
|
|
51
|
+
? `view.${view.subType} name="${view.name}"`
|
|
52
|
+
: `view.${view.subType} (no name)`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** `Entity.field`, or just the field name when the field has no parent object. */
|
|
56
|
+
function fieldPath(field: MetaField): string {
|
|
57
|
+
const owner = field.parent?.name;
|
|
58
|
+
return owner !== undefined && owner.length > 0 ? `${owner}.${field.name}` : field.name;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* The view a field declares for `context`, or `undefined` when it declares none
|
|
63
|
+
* at all (the caller's existing "no view" path — an inferred default — applies).
|
|
64
|
+
*
|
|
65
|
+
* Uses the RESOLVING accessor (ADR-0039): a view inherited through `extends` is
|
|
66
|
+
* as much the field's view as one declared on it.
|
|
67
|
+
*
|
|
68
|
+
* @throws CodegenError when the field declares several views and none is named
|
|
69
|
+
* for `context`. The remedy is in the message: name one of them.
|
|
70
|
+
*/
|
|
71
|
+
export function viewForContext(field: MetaField, context: string): MetaView | undefined {
|
|
72
|
+
const declared = field.views();
|
|
73
|
+
// One view (or none) is unambiguous: it applies to every surface, exactly as
|
|
74
|
+
// it did before this selector existed.
|
|
75
|
+
if (declared.length <= 1) return declared[0];
|
|
76
|
+
|
|
77
|
+
const match = declared.find((v) => v.name === context);
|
|
78
|
+
if (match !== undefined) return match;
|
|
79
|
+
|
|
80
|
+
throw new CodegenError(
|
|
81
|
+
`Field "${fieldPath(field)}" declares ${declared.length} views ` +
|
|
82
|
+
`(${declared.map(describe).join(", ")}) and none is named "${context}", so codegen ` +
|
|
83
|
+
`cannot tell which one renders the ${context}. Name one of them "${context}" — a ` +
|
|
84
|
+
`view's \`name\` selects the surface it renders ("${VIEW_CONTEXT_FORM}" or ` +
|
|
85
|
+
`"${VIEW_CONTEXT_GRID}"). A field declaring a SINGLE view needs no name: that view ` +
|
|
86
|
+
`applies to every surface.`,
|
|
87
|
+
);
|
|
88
|
+
}
|