@metaobjectsdev/codegen-ts 0.14.2 → 0.15.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/dist/column-mapper.d.ts.map +1 -1
- package/dist/column-mapper.js +36 -16
- package/dist/column-mapper.js.map +1 -1
- package/dist/generators/api-model.js +21 -13
- package/dist/generators/api-model.js.map +1 -1
- package/dist/generators/docs-data-builder.d.ts.map +1 -1
- package/dist/generators/docs-data-builder.js +27 -22
- package/dist/generators/docs-data-builder.js.map +1 -1
- package/dist/generators/docs-file.d.ts.map +1 -1
- package/dist/generators/docs-file.js +2 -1
- package/dist/generators/docs-file.js.map +1 -1
- package/dist/generators/extractor-file.d.ts.map +1 -1
- package/dist/generators/extractor-file.js +4 -2
- package/dist/generators/extractor-file.js.map +1 -1
- package/dist/generators/output-prompt-file.d.ts.map +1 -1
- package/dist/generators/output-prompt-file.js +5 -3
- package/dist/generators/output-prompt-file.js.map +1 -1
- package/dist/generators/render-helper-file.d.ts.map +1 -1
- package/dist/generators/render-helper-file.js +5 -3
- 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 +2 -1
- package/dist/generators/routes-file-hono.js.map +1 -1
- package/dist/generators/routes-file.d.ts.map +1 -1
- package/dist/generators/routes-file.js +3 -1
- package/dist/generators/routes-file.js.map +1 -1
- package/dist/generators/template-doc-builder.d.ts.map +1 -1
- package/dist/generators/template-doc-builder.js +14 -11
- package/dist/generators/template-doc-builder.js.map +1 -1
- package/dist/generators/trace-helper-file.d.ts.map +1 -1
- package/dist/generators/trace-helper-file.js +11 -5
- package/dist/generators/trace-helper-file.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/naming.d.ts +10 -0
- package/dist/naming.d.ts.map +1 -1
- package/dist/naming.js +47 -0
- package/dist/naming.js.map +1 -1
- package/dist/payload-codegen.d.ts.map +1 -1
- package/dist/payload-codegen.js +12 -9
- 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 +26 -3
- package/dist/projection/extract-view-spec.js.map +1 -1
- package/dist/projection/projection-detector.d.ts.map +1 -1
- package/dist/projection/projection-detector.js +4 -0
- package/dist/projection/projection-detector.js.map +1 -1
- package/dist/relation-resolver.d.ts.map +1 -1
- package/dist/relation-resolver.js +20 -12
- package/dist/relation-resolver.js.map +1 -1
- package/dist/source-detect.d.ts.map +1 -1
- package/dist/source-detect.js +4 -1
- package/dist/source-detect.js.map +1 -1
- package/dist/template-codegen/template-data.js +1 -1
- package/dist/template-codegen/template-data.js.map +1 -1
- package/dist/templates/callable-file.d.ts.map +1 -1
- package/dist/templates/callable-file.js +8 -4
- package/dist/templates/callable-file.js.map +1 -1
- package/dist/templates/entity-constants.d.ts.map +1 -1
- package/dist/templates/entity-constants.js +15 -11
- 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 +7 -2
- package/dist/templates/entity-file.js.map +1 -1
- package/dist/templates/extract-delegate-emitter.d.ts.map +1 -1
- package/dist/templates/extract-delegate-emitter.js +2 -1
- package/dist/templates/extract-delegate-emitter.js.map +1 -1
- package/dist/templates/extractor.d.ts.map +1 -1
- package/dist/templates/extractor.js +8 -4
- package/dist/templates/extractor.js.map +1 -1
- package/dist/templates/field-meta.d.ts.map +1 -1
- package/dist/templates/field-meta.js +22 -4
- package/dist/templates/field-meta.js.map +1 -1
- package/dist/templates/find-templates.d.ts.map +1 -1
- package/dist/templates/find-templates.js +3 -0
- package/dist/templates/find-templates.js.map +1 -1
- package/dist/templates/fr010-field-mapping.d.ts.map +1 -1
- package/dist/templates/fr010-field-mapping.js +5 -2
- package/dist/templates/fr010-field-mapping.js.map +1 -1
- package/dist/templates/inferred-types.d.ts.map +1 -1
- package/dist/templates/inferred-types.js +18 -14
- package/dist/templates/inferred-types.js.map +1 -1
- package/dist/templates/output-format-spec-emitter.js +7 -3
- 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 +9 -5
- 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 +8 -4
- package/dist/templates/output-prompt.js.map +1 -1
- package/dist/templates/projection-decl.d.ts.map +1 -1
- package/dist/templates/projection-decl.js +4 -1
- 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 +12 -1
- package/dist/templates/queries-file.js.map +1 -1
- package/dist/templates/queries.d.ts +19 -1
- package/dist/templates/queries.d.ts.map +1 -1
- package/dist/templates/queries.js +49 -7
- package/dist/templates/queries.js.map +1 -1
- package/dist/templates/render-helper.d.ts.map +1 -1
- package/dist/templates/render-helper.js +17 -10
- package/dist/templates/render-helper.js.map +1 -1
- package/dist/templates/routes-file.js +2 -1
- package/dist/templates/routes-file.js.map +1 -1
- package/dist/templates/tph-discriminator.d.ts.map +1 -1
- package/dist/templates/tph-discriminator.js +7 -0
- package/dist/templates/tph-discriminator.js.map +1 -1
- package/dist/templates/zod-validators.d.ts.map +1 -1
- package/dist/templates/zod-validators.js +55 -15
- package/dist/templates/zod-validators.js.map +1 -1
- package/package.json +6 -6
- package/src/column-mapper.ts +37 -16
- package/src/generators/api-model.ts +21 -13
- package/src/generators/docs-data-builder.ts +27 -22
- package/src/generators/docs-file.ts +2 -1
- package/src/generators/extractor-file.ts +4 -2
- package/src/generators/output-prompt-file.ts +5 -3
- package/src/generators/render-helper-file.ts +5 -3
- package/src/generators/routes-file-hono.ts +2 -1
- package/src/generators/routes-file.ts +2 -1
- package/src/generators/template-doc-builder.ts +14 -11
- package/src/generators/trace-helper-file.ts +11 -5
- package/src/index.ts +2 -0
- package/src/naming.ts +51 -0
- package/src/payload-codegen.ts +12 -9
- package/src/projection/extract-view-spec.ts +26 -3
- package/src/projection/projection-detector.ts +4 -0
- package/src/reference/queries.ts +9 -0
- package/src/reference/routes.ts +2 -1
- package/src/relation-resolver.ts +20 -12
- package/src/source-detect.ts +4 -1
- package/src/template-codegen/template-data.ts +1 -1
- package/src/templates/callable-file.ts +8 -4
- package/src/templates/entity-constants.ts +15 -11
- package/src/templates/entity-file.ts +7 -2
- package/src/templates/extract-delegate-emitter.ts +2 -1
- package/src/templates/extractor.ts +8 -4
- package/src/templates/field-meta.ts +25 -3
- package/src/templates/find-templates.ts +3 -0
- package/src/templates/fr010-field-mapping.ts +6 -1
- package/src/templates/inferred-types.ts +19 -13
- package/src/templates/output-format-spec-emitter.ts +7 -3
- package/src/templates/output-parser.ts +9 -5
- package/src/templates/output-prompt.ts +8 -4
- package/src/templates/projection-decl.ts +4 -1
- package/src/templates/queries-file.ts +14 -0
- package/src/templates/queries.ts +68 -8
- package/src/templates/render-helper.ts +17 -10
- package/src/templates/routes-file.ts +2 -1
- package/src/templates/tph-discriminator.ts +7 -0
- package/src/templates/zod-validators.ts +56 -14
package/src/column-mapper.ts
CHANGED
|
@@ -18,6 +18,8 @@ import {
|
|
|
18
18
|
FIELD_SUBTYPE_MAP,
|
|
19
19
|
FIELD_SUBTYPE_ENUM,
|
|
20
20
|
FIELD_SUBTYPE_UUID,
|
|
21
|
+
FIELD_SUBTYPE_URI,
|
|
22
|
+
FIELD_SUBTYPE_INET,
|
|
21
23
|
VALIDATOR_SUBTYPE_REQUIRED,
|
|
22
24
|
VALIDATOR_SUBTYPE_LENGTH,
|
|
23
25
|
FIELD_ATTR_MAX_LENGTH,
|
|
@@ -34,7 +36,7 @@ import {
|
|
|
34
36
|
FIELD_ATTR_DB_COLUMN_TYPE,
|
|
35
37
|
DB_COLUMN_TYPE_UUID,
|
|
36
38
|
DB_COLUMN_TYPE_JSONB,
|
|
37
|
-
|
|
39
|
+
FIELD_ATTR_LOCAL_TIME,
|
|
38
40
|
} from "@metaobjectsdev/metadata";
|
|
39
41
|
import { columnNameFromField } from "./naming.js";
|
|
40
42
|
import { enumValues } from "./enum-meta.js";
|
|
@@ -160,19 +162,22 @@ export interface ColumnSpec {
|
|
|
160
162
|
* migrate-ts/src/expected-schema.ts (override checked first, wins over the
|
|
161
163
|
* subtype default), so codegen and DDL agree.
|
|
162
164
|
*
|
|
163
|
-
* Postgres-only: uuid/jsonb
|
|
164
|
-
*
|
|
165
|
-
*
|
|
165
|
+
* Postgres-only: uuid/jsonb are Postgres physical column types with no native
|
|
166
|
+
* SQLite analogue, so on SQLite the attribute is ignored (the caller falls
|
|
167
|
+
* through to the subtype default). The native TS binding is keyed on
|
|
166
168
|
* field.subType elsewhere and is unaffected — a `field.string @dbColumnType:uuid`
|
|
167
169
|
* field stays a TS `string`; only the Drizzle column function changes.
|
|
168
170
|
*
|
|
171
|
+
* ADR-0036 Wave 2: the `timestamp_with_tz` value is retired — `field.timestamp`
|
|
172
|
+
* is now instant/tz-aware BY DEFAULT (handled in the subtype switch below), and
|
|
173
|
+
* the naive opt-out is the `@localTime` boolean, not a physical override.
|
|
174
|
+
*
|
|
169
175
|
* The loader has already validated the (subtype × value) pairing, so an
|
|
170
176
|
* unrecognized value never reaches here; an unknown value returns undefined
|
|
171
177
|
* (fall through to subtype default).
|
|
172
178
|
*/
|
|
173
179
|
function pgColumnTypeOverride(
|
|
174
180
|
field: MetaField,
|
|
175
|
-
timestampMode: "date" | "string" = "string",
|
|
176
181
|
): { fnName: string; fnOptions?: Record<string, unknown> } | undefined {
|
|
177
182
|
const dbColumnType = field.attr(FIELD_ATTR_DB_COLUMN_TYPE);
|
|
178
183
|
if (typeof dbColumnType !== "string") return undefined;
|
|
@@ -181,12 +186,6 @@ function pgColumnTypeOverride(
|
|
|
181
186
|
return { fnName: "uuid" };
|
|
182
187
|
case DB_COLUMN_TYPE_JSONB:
|
|
183
188
|
return { fnName: "jsonb" };
|
|
184
|
-
case DB_COLUMN_TYPE_TIMESTAMP_WITH_TZ:
|
|
185
|
-
// Drizzle pg-core: timestamp(col, { withTimezone: true }) → timestamptz.
|
|
186
|
-
// mode defaults to "string" (ISO-8601 wire contract, matching the generated
|
|
187
|
-
// Zod); a consumer can opt into "date" (drizzle's native mode) via
|
|
188
|
-
// codegen.timestampMode when its hand-written code works with JS Dates.
|
|
189
|
-
return { fnName: "timestamp", fnOptions: { mode: timestampMode, withTimezone: true } };
|
|
190
189
|
default:
|
|
191
190
|
return undefined;
|
|
192
191
|
}
|
|
@@ -199,7 +198,8 @@ function getMaxLength(field: MetaField): number | undefined {
|
|
|
199
198
|
if (typeof lenAttr === "number") return lenAttr;
|
|
200
199
|
for (const child of field.validators()) {
|
|
201
200
|
if (child.subType === VALIDATOR_SUBTYPE_LENGTH) {
|
|
202
|
-
|
|
201
|
+
// ADR-0039: resolving — a validator may inherit @max via extends.
|
|
202
|
+
const max = child.attr(VALIDATOR_ATTR_MAX);
|
|
203
203
|
if (typeof max === "number") return max;
|
|
204
204
|
}
|
|
205
205
|
}
|
|
@@ -231,7 +231,7 @@ export function mapColumnType(
|
|
|
231
231
|
const dbName = field.column ?? columnNameFromField(field.name, strategy);
|
|
232
232
|
const importModule = dialect === "sqlite" ? "drizzle-orm/sqlite-core" : "drizzle-orm/pg-core";
|
|
233
233
|
const subType = field.subType;
|
|
234
|
-
const isArray = field.
|
|
234
|
+
const isArray = field.resolvedIsArray();
|
|
235
235
|
|
|
236
236
|
let fnName: string;
|
|
237
237
|
let fnOptions: Record<string, unknown> | undefined;
|
|
@@ -279,7 +279,9 @@ export function mapColumnType(
|
|
|
279
279
|
case FIELD_SUBTYPE_STRING:
|
|
280
280
|
case FIELD_SUBTYPE_ENUM:
|
|
281
281
|
case FIELD_SUBTYPE_UUID:
|
|
282
|
-
|
|
282
|
+
case FIELD_SUBTYPE_URI:
|
|
283
|
+
case FIELD_SUBTYPE_INET:
|
|
284
|
+
// SQLite has no native uuid/inet type; store as TEXT (string native binding).
|
|
283
285
|
fnName = "text";
|
|
284
286
|
break;
|
|
285
287
|
default:
|
|
@@ -291,7 +293,7 @@ export function mapColumnType(
|
|
|
291
293
|
// A physical @dbColumnType override wins over the subtype default (Postgres
|
|
292
294
|
// only; SQLite has no native analogue and falls through above). Resolved
|
|
293
295
|
// first so the override-precedence matches migrate-ts's expected-schema.
|
|
294
|
-
const override = pgColumnTypeOverride(field
|
|
296
|
+
const override = pgColumnTypeOverride(field);
|
|
295
297
|
if (override !== undefined) {
|
|
296
298
|
// Override fully determines the physical type; skip the subtype switch.
|
|
297
299
|
fnName = override.fnName;
|
|
@@ -322,6 +324,11 @@ export function mapColumnType(
|
|
|
322
324
|
fnName = "time";
|
|
323
325
|
break;
|
|
324
326
|
case FIELD_SUBTYPE_TIMESTAMP:
|
|
327
|
+
// ADR-0036 Wave 2: field.timestamp is instant / tz-aware BY DEFAULT →
|
|
328
|
+
// timestamp({ withTimezone: true }) = timestamptz. A naive wall-clock
|
|
329
|
+
// value opts out with @localTime:true → timestamp({ withTimezone:
|
|
330
|
+
// false }) = `timestamp without time zone`.
|
|
331
|
+
//
|
|
325
332
|
// mode:"string" so the column round-trips ISO-8601 strings — the
|
|
326
333
|
// generated Zod schema validates timestamp fields as z.string() and
|
|
327
334
|
// the cross-port wire format carries timestamps as JSON strings.
|
|
@@ -330,12 +337,26 @@ export function mapColumnType(
|
|
|
330
337
|
// inconsistent with the string-typed schema + wire contract and
|
|
331
338
|
// throws on a string write. See SP-B api-contract-generated lane.
|
|
332
339
|
fnName = "timestamp";
|
|
333
|
-
fnOptions = {
|
|
340
|
+
fnOptions = {
|
|
341
|
+
mode: timestampMode,
|
|
342
|
+
withTimezone: field.attr(FIELD_ATTR_LOCAL_TIME) !== true,
|
|
343
|
+
};
|
|
334
344
|
break;
|
|
335
345
|
case FIELD_SUBTYPE_UUID:
|
|
336
346
|
// Postgres native uuid column; native TS binding stays `string`.
|
|
337
347
|
fnName = "uuid";
|
|
338
348
|
break;
|
|
349
|
+
case FIELD_SUBTYPE_URI:
|
|
350
|
+
// ADR-0036/0037 Wave 3: Postgres has no uri type → text. Native TS
|
|
351
|
+
// binding stays `string` (validated as a URL via Zod .url()).
|
|
352
|
+
fnName = "text";
|
|
353
|
+
break;
|
|
354
|
+
case FIELD_SUBTYPE_INET:
|
|
355
|
+
// ADR-0036/0037 Wave 3: Postgres-native `inet` column (Drizzle's
|
|
356
|
+
// pg-core `inet()` infers as `string`). Native TS binding stays
|
|
357
|
+
// `string` (validated as an IP via Zod .ip()).
|
|
358
|
+
fnName = "inet";
|
|
359
|
+
break;
|
|
339
360
|
case FIELD_SUBTYPE_DECIMAL: {
|
|
340
361
|
// Drizzle pg `numeric` infers as a TS `string` (precision-exact); the
|
|
341
362
|
// native TS binding for field.decimal is `string` to match. Read the
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
// render handle promptRender() emits into a single
|
|
42
42
|
// aggregated `prompts.ts` (payload-codegen generateRenderHandle).
|
|
43
43
|
// ONLY for TOP-LEVEL template.prompt nodes (matching
|
|
44
|
-
// prompt-render-file.ts's
|
|
44
|
+
// prompt-render-file.ts's top-level template collection).
|
|
45
45
|
//
|
|
46
46
|
// DEFERRALS (tracked follow-ups — NOT documented by this builder yet, stated here
|
|
47
47
|
// so the gap is known + intentional):
|
|
@@ -353,7 +353,8 @@ function isQueryable(obj: MetaObject): boolean {
|
|
|
353
353
|
* generators which always emit. So REST symbols are gated separately from the
|
|
354
354
|
* other queryable kinds. */
|
|
355
355
|
function emitsRoutes(obj: MetaObject): boolean {
|
|
356
|
-
|
|
356
|
+
// ADR-0039: resolving — a concrete entity may inherit @emitRoutes via extends.
|
|
357
|
+
return obj.attr(CODEGEN_ATTR_EMIT_ROUTES) !== false;
|
|
357
358
|
}
|
|
358
359
|
|
|
359
360
|
function buildEntityUnit(
|
|
@@ -750,13 +751,16 @@ function callableSymbol(
|
|
|
750
751
|
* undefined for a zero-arg proc. Mirrors the callable template's resolution
|
|
751
752
|
* (the source child's SOURCE_ATTR_PARAMETER_REF). */
|
|
752
753
|
function callableArgsRef(obj: MetaObject, root: MetaRoot): string | undefined {
|
|
753
|
-
|
|
754
|
+
// ADR-0039: resolving — a callable entity's source (and its @parameterRef) may
|
|
755
|
+
// be inherited via extends.
|
|
756
|
+
for (const child of obj.children()) {
|
|
754
757
|
if (child.type !== TYPE_SOURCE) continue;
|
|
755
|
-
const ref = child.
|
|
758
|
+
const ref = child.attr(SOURCE_ATTR_PARAMETER_REF);
|
|
756
759
|
if (typeof ref === "string" && ref !== "") {
|
|
757
760
|
// Only count it when it resolves to a value object (the template's guard).
|
|
761
|
+
// ADR-0039: resolving — root has no super (children()==ownChildren()).
|
|
758
762
|
const vo = root
|
|
759
|
-
.
|
|
763
|
+
.children()
|
|
760
764
|
.find((c) => c.subType === OBJECT_SUBTYPE_VALUE && refMatchesObject(c, ref));
|
|
761
765
|
if (vo !== undefined) return ref;
|
|
762
766
|
}
|
|
@@ -806,8 +810,9 @@ function restHonoSymbols(obj: MetaObject, layout: OutputLayout): ApiSymbol[] {
|
|
|
806
810
|
// ---------------------------------------------------------------------------
|
|
807
811
|
|
|
808
812
|
function templateOutputs(root: MetaRoot): MetaData[] {
|
|
813
|
+
// ADR-0039: resolving — root has no super (children()==ownChildren()).
|
|
809
814
|
return root
|
|
810
|
-
.
|
|
815
|
+
.children()
|
|
811
816
|
.filter((c) => c.type === TYPE_TEMPLATE && c.subType === TEMPLATE_SUBTYPE_OUTPUT);
|
|
812
817
|
}
|
|
813
818
|
|
|
@@ -819,10 +824,11 @@ function buildTemplateUnit(tmpl: MetaData, root: MetaRoot, _layout: OutputLayout
|
|
|
819
824
|
const extractorMod = templateModulePath(`${name}.extractor`);
|
|
820
825
|
const renderMod = templateModulePath(`${name}.render`);
|
|
821
826
|
|
|
822
|
-
|
|
827
|
+
// ADR-0039: resolving — a template may inherit @payloadRef/@format/@kind via extends.
|
|
828
|
+
const payloadRef = tmpl.attr(TEMPLATE_ATTR_PAYLOAD_REF);
|
|
823
829
|
const payload = typeof payloadRef === "string" ? payloadRef : undefined;
|
|
824
|
-
const format = ((tmpl.
|
|
825
|
-
const kind = ((tmpl.
|
|
830
|
+
const format = ((tmpl.attr(TEMPLATE_ATTR_FORMAT) as string | undefined) ?? "text").toLowerCase();
|
|
831
|
+
const kind = ((tmpl.attr(TEMPLATE_ATTR_KIND) as string | undefined) ?? TEMPLATE_KIND_DEFAULT).toLowerCase();
|
|
826
832
|
|
|
827
833
|
// --- extractor: only json/xml output-parsers expose the extract API (matches
|
|
828
834
|
// extractor-file.ts's `if (format !== "json" && format !== "xml") continue`). ---
|
|
@@ -893,13 +899,14 @@ function buildTemplateUnit(tmpl: MetaData, root: MetaRoot, _layout: OutputLayout
|
|
|
893
899
|
// template.prompt nodes — the prompt-render handle.
|
|
894
900
|
// ---------------------------------------------------------------------------
|
|
895
901
|
|
|
896
|
-
/** TOP-LEVEL template.prompt nodes — matching the promptRender generator's
|
|
897
|
-
* collection (
|
|
902
|
+
/** TOP-LEVEL template.prompt nodes — matching the promptRender generator's
|
|
903
|
+
* top-level collection (root children filtered to TYPE_TEMPLATE +
|
|
898
904
|
* TEMPLATE_SUBTYPE_PROMPT). A prompt nested INSIDE an entity is not collected by
|
|
899
905
|
* the generator, so the builder must not document it either (no over-doc). */
|
|
900
906
|
function templatePrompts(root: MetaRoot): MetaData[] {
|
|
907
|
+
// ADR-0039: resolving — root has no super (children()==ownChildren()).
|
|
901
908
|
return root
|
|
902
|
-
.
|
|
909
|
+
.children()
|
|
903
910
|
.filter((c) => c.type === TYPE_TEMPLATE && c.subType === TEMPLATE_SUBTYPE_PROMPT);
|
|
904
911
|
}
|
|
905
912
|
|
|
@@ -919,7 +926,8 @@ function buildPromptUnit(tmpl: MetaData, root: MetaRoot): ApiUnitDoc {
|
|
|
919
926
|
// promptRender writes the aggregated handles to `outFile` (default "prompts.ts").
|
|
920
927
|
const promptsMod = templateModulePath("prompts");
|
|
921
928
|
|
|
922
|
-
|
|
929
|
+
// ADR-0039: resolving — a template may inherit @payloadRef via extends.
|
|
930
|
+
const payloadRef = tmpl.attr(TEMPLATE_ATTR_PAYLOAD_REF);
|
|
923
931
|
const payload = typeof payloadRef === "string" ? payloadRef : undefined;
|
|
924
932
|
|
|
925
933
|
if (payload) {
|
|
@@ -108,20 +108,21 @@ function collectValidatorParts(field: MetaField): ValidatorParts {
|
|
|
108
108
|
const regexParts: string[] = [];
|
|
109
109
|
const lengthParts: string[] = [];
|
|
110
110
|
const numericParts: string[] = [];
|
|
111
|
+
// ADR-0039: resolving — a validator may inherit @min/@max/@pattern via extends.
|
|
111
112
|
for (const v of field.validators()) {
|
|
112
113
|
if (v.subType === VALIDATOR_SUBTYPE_REGEX) {
|
|
113
|
-
const pattern = v.
|
|
114
|
+
const pattern = v.attr(VALIDATOR_ATTR_PATTERN);
|
|
114
115
|
if (typeof pattern === "string" && pattern.length > 0) {
|
|
115
116
|
regexParts.push(`pattern \`${pattern}\``);
|
|
116
117
|
}
|
|
117
118
|
} else if (v.subType === VALIDATOR_SUBTYPE_LENGTH) {
|
|
118
|
-
const min = v.
|
|
119
|
-
const max = v.
|
|
119
|
+
const min = v.attr(VALIDATOR_ATTR_MIN);
|
|
120
|
+
const max = v.attr(VALIDATOR_ATTR_MAX);
|
|
120
121
|
if (typeof min === "number") lengthParts.push(`minLength: ${min}`);
|
|
121
122
|
if (typeof max === "number" && typeof maxLenAttr !== "number") lengthParts.push(`maxLength: ${max}`);
|
|
122
123
|
} else if (v.subType === VALIDATOR_SUBTYPE_NUMERIC) {
|
|
123
|
-
const min = v.
|
|
124
|
-
const max = v.
|
|
124
|
+
const min = v.attr(VALIDATOR_ATTR_MIN);
|
|
125
|
+
const max = v.attr(VALIDATOR_ATTR_MAX);
|
|
125
126
|
if (typeof min === "number") numericParts.push(`min: ${min}`);
|
|
126
127
|
if (typeof max === "number") numericParts.push(`max: ${max}`);
|
|
127
128
|
}
|
|
@@ -148,7 +149,7 @@ export function neutralTypeStr(field: MetaField): string {
|
|
|
148
149
|
} else {
|
|
149
150
|
base = field.subType;
|
|
150
151
|
}
|
|
151
|
-
if (field.
|
|
152
|
+
if (field.resolvedIsArray()) base = `${base}[]`;
|
|
152
153
|
return base;
|
|
153
154
|
}
|
|
154
155
|
|
|
@@ -160,7 +161,7 @@ function neutralTypeCell(field: MetaField): string {
|
|
|
160
161
|
|
|
161
162
|
/** Neutral PHYSICAL type cell for the Storage table. Metadata-driven, no DDL
|
|
162
163
|
* re-derivation (ADR-0020): if the field declares a `@dbColumnType` physical
|
|
163
|
-
* override (e.g. `uuid`, `jsonb
|
|
164
|
+
* override (e.g. `uuid`, `jsonb`) show it UPPERCASED;
|
|
164
165
|
* otherwise fall back to the same neutral LOGICAL type the Constraints table
|
|
165
166
|
* uses. Deliberately does NOT derive ANSI/ORM SQL so it can't drift vs the
|
|
166
167
|
* migrate engine or re-introduce language-specific DDL. Wrapped in backticks. */
|
|
@@ -192,7 +193,7 @@ function buildConstraintRow(
|
|
|
192
193
|
if (isPk) rules.push("primary key");
|
|
193
194
|
if (field.attr(FIELD_ATTR_UNIQUE) === true) rules.push("unique");
|
|
194
195
|
|
|
195
|
-
if (field.subType === FIELD_SUBTYPE_ENUM && !field.
|
|
196
|
+
if (field.subType === FIELD_SUBTYPE_ENUM && !field.resolvedIsArray()) {
|
|
196
197
|
const values = enumValues(field);
|
|
197
198
|
if (values !== undefined && values.length > 0) {
|
|
198
199
|
const list = values.map((v) => `\`${v}\``).join(", ");
|
|
@@ -289,7 +290,7 @@ function buildFieldRow(
|
|
|
289
290
|
const rules: string[] = [];
|
|
290
291
|
if (field.attr(FIELD_ATTR_UNIQUE) === true) rules.push("unique");
|
|
291
292
|
|
|
292
|
-
if (field.subType === FIELD_SUBTYPE_ENUM && !field.
|
|
293
|
+
if (field.subType === FIELD_SUBTYPE_ENUM && !field.resolvedIsArray()) {
|
|
293
294
|
const values = enumValues(field);
|
|
294
295
|
if (values !== undefined && values.length > 0) {
|
|
295
296
|
const list = values.map((v) => `\`${v}\``).join(", ");
|
|
@@ -344,7 +345,7 @@ function buildFieldDetail(
|
|
|
344
345
|
const columnName = field.column;
|
|
345
346
|
const dbColumnType = field.attr(FIELD_ATTR_DB_COLUMN_TYPE);
|
|
346
347
|
const isUnique = field.attr(FIELD_ATTR_UNIQUE) === true;
|
|
347
|
-
const isEnum = field.subType === FIELD_SUBTYPE_ENUM && !field.
|
|
348
|
+
const isEnum = field.subType === FIELD_SUBTYPE_ENUM && !field.resolvedIsArray();
|
|
348
349
|
const enumVals = isEnum ? enumValues(field) : undefined;
|
|
349
350
|
const validators = field.validators();
|
|
350
351
|
const hasValidatorChildren = validators.some(
|
|
@@ -423,22 +424,23 @@ function buildFieldDetail(
|
|
|
423
424
|
// Validators — one bullet per validator subtype (regex / length / numeric),
|
|
424
425
|
// rendered in declaration order so authors can rely on the order they
|
|
425
426
|
// wrote.
|
|
427
|
+
// ADR-0039: resolving — a validator may inherit @min/@max/@pattern via extends.
|
|
426
428
|
for (const v of validators) {
|
|
427
429
|
if (v.subType === VALIDATOR_SUBTYPE_REGEX) {
|
|
428
|
-
const pattern = v.
|
|
430
|
+
const pattern = v.attr(VALIDATOR_ATTR_PATTERN);
|
|
429
431
|
if (typeof pattern === "string" && pattern.length > 0) {
|
|
430
432
|
bullets.push(`**Validator (regex):** pattern \`${pattern}\``);
|
|
431
433
|
}
|
|
432
434
|
} else if (v.subType === VALIDATOR_SUBTYPE_LENGTH) {
|
|
433
|
-
const min = v.
|
|
434
|
-
const max = v.
|
|
435
|
+
const min = v.attr(VALIDATOR_ATTR_MIN);
|
|
436
|
+
const max = v.attr(VALIDATOR_ATTR_MAX);
|
|
435
437
|
const fragments: string[] = [];
|
|
436
438
|
if (typeof min === "number") fragments.push(`min ${min}`);
|
|
437
439
|
if (typeof max === "number") fragments.push(`max ${max}`);
|
|
438
440
|
if (fragments.length > 0) bullets.push(`**Validator (length):** ${fragments.join(", ")}`);
|
|
439
441
|
} else if (v.subType === VALIDATOR_SUBTYPE_NUMERIC) {
|
|
440
|
-
const min = v.
|
|
441
|
-
const max = v.
|
|
442
|
+
const min = v.attr(VALIDATOR_ATTR_MIN);
|
|
443
|
+
const max = v.attr(VALIDATOR_ATTR_MAX);
|
|
442
444
|
const fragments: string[] = [];
|
|
443
445
|
if (typeof min === "number") fragments.push(`min ${min}`);
|
|
444
446
|
if (typeof max === "number") fragments.push(`max ${max}`);
|
|
@@ -524,9 +526,10 @@ function describeIdentity(id: MetaIdentity): string {
|
|
|
524
526
|
}
|
|
525
527
|
|
|
526
528
|
function relationshipBullet(r: ReturnType<MetaObject["relationships"]>[number]): string {
|
|
527
|
-
|
|
529
|
+
// ADR-0039: resolving — a relationship may inherit its attrs via extends.
|
|
530
|
+
const cardinality = r.attr(RELATIONSHIP_ATTR_CARDINALITY);
|
|
528
531
|
const card = typeof cardinality === "string" ? cardinality : "?";
|
|
529
|
-
const targetRaw = r.
|
|
532
|
+
const targetRaw = r.attr(RELATIONSHIP_ATTR_OBJECT_REF);
|
|
530
533
|
const target = typeof targetRaw === "string" ? stripPackage(targetRaw) : "?";
|
|
531
534
|
const subtype = r.subType;
|
|
532
535
|
let label: string;
|
|
@@ -542,14 +545,14 @@ function relationshipBullet(r: ReturnType<MetaObject["relationships"]>[number]):
|
|
|
542
545
|
// symmetric (undirected) → "symmetric self-join"
|
|
543
546
|
// @sourceRefField set (directed) → "directed self-join via `<field>`"
|
|
544
547
|
// The junction/disambiguator are DECLARED facts (ADR-0020 — no re-derivation).
|
|
545
|
-
const throughRaw = r.
|
|
548
|
+
const throughRaw = r.attr(RELATIONSHIP_ATTR_THROUGH);
|
|
546
549
|
if (typeof throughRaw === "string" && throughRaw.length > 0) {
|
|
547
550
|
const through = stripPackage(throughRaw);
|
|
548
551
|
const noteParts = [`${label}, through \`${through}\``];
|
|
549
|
-
if (r.
|
|
552
|
+
if (r.attr(RELATIONSHIP_ATTR_SYMMETRIC) === true) {
|
|
550
553
|
noteParts.push("symmetric self-join");
|
|
551
554
|
} else {
|
|
552
|
-
const srcRef = r.
|
|
555
|
+
const srcRef = r.attr(RELATIONSHIP_ATTR_SOURCE_REF_FIELD);
|
|
553
556
|
if (typeof srcRef === "string" && srcRef.length > 0) {
|
|
554
557
|
noteParts.push(`directed self-join via \`${srcRef}\``);
|
|
555
558
|
}
|
|
@@ -664,9 +667,11 @@ export function buildEntityDocData(
|
|
|
664
667
|
|
|
665
668
|
// ---- UsedBy
|
|
666
669
|
const usedByMatches: UsedByDoc[] = [];
|
|
667
|
-
|
|
670
|
+
// ADR-0039: resolving — root has no super (children()==ownChildren()), and a
|
|
671
|
+
// template may inherit @payloadRef via extends; resolve rather than work-by-accident.
|
|
672
|
+
for (const child of root.children()) {
|
|
668
673
|
if (child.type !== TYPE_TEMPLATE) continue;
|
|
669
|
-
const ref = child.
|
|
674
|
+
const ref = child.attr(TEMPLATE_ATTR_PAYLOAD_REF);
|
|
670
675
|
if (typeof ref !== "string") continue;
|
|
671
676
|
if (stripPackage(ref) !== entity.name) continue;
|
|
672
677
|
// Link to the template's own doc page. The href is derived from the SAME
|
|
@@ -135,7 +135,8 @@ export const docsFile = function docsFile(opts?: DocsFileOpts): Generator {
|
|
|
135
135
|
// ALSO emit one NEUTRAL render-contract page per `template.output` node —
|
|
136
136
|
// a sibling artifact, distinct from the entity page. Raw node name → file
|
|
137
137
|
// (`<name>.md`), agreeing with the entity Used-by back-link target.
|
|
138
|
-
|
|
138
|
+
// ADR-0039: resolving — root has no super (children()==ownChildren()).
|
|
139
|
+
for (const child of ctx.loadedRoot.children()) {
|
|
139
140
|
if (child.type !== TYPE_TEMPLATE || child.subType !== TEMPLATE_SUBTYPE_OUTPUT) continue;
|
|
140
141
|
const node = docPageNode(child);
|
|
141
142
|
templateNodes.push(node);
|
|
@@ -35,13 +35,15 @@ export const extractor = function extractor(opts?: ExtractorOpts): Generator {
|
|
|
35
35
|
const generator: Generator = {
|
|
36
36
|
name: "extractor",
|
|
37
37
|
generate: oncePerRun((_entities, ctx) => {
|
|
38
|
+
// ADR-0039: resolving — root has no super (children()==ownChildren()).
|
|
38
39
|
const outputs = ctx.loadedRoot
|
|
39
|
-
.
|
|
40
|
+
.children()
|
|
40
41
|
.filter((c) => c.type === TYPE_TEMPLATE && c.subType === TEMPLATE_SUBTYPE_OUTPUT);
|
|
41
42
|
const files: EmittedFile[] = [];
|
|
42
43
|
for (const t of outputs) {
|
|
43
44
|
// The extract tier requires the extract API, which only json/xml output-parsers emit.
|
|
44
|
-
|
|
45
|
+
// ADR-0039: resolving — a template may inherit @format via extends.
|
|
46
|
+
const format = ((t.attr(TEMPLATE_ATTR_FORMAT) as string | undefined) ?? "text").toLowerCase();
|
|
45
47
|
if (format !== "json" && format !== "xml") continue;
|
|
46
48
|
files.push({
|
|
47
49
|
path: `${dirPrefix}${t.name}.extractor.ts`,
|
|
@@ -40,17 +40,19 @@ export const outputPrompt = function outputPrompt(opts?: OutputPromptOpts): Gene
|
|
|
40
40
|
name: "output-prompt",
|
|
41
41
|
generate: oncePerRun((_entities, ctx) => {
|
|
42
42
|
const root = ctx.loadedRoot;
|
|
43
|
+
// ADR-0039: resolving — root has no super (children()==ownChildren()).
|
|
43
44
|
const outputs = root
|
|
44
|
-
.
|
|
45
|
+
.children()
|
|
45
46
|
.filter((c) => c.type === TYPE_TEMPLATE && c.subType === TEMPLATE_SUBTYPE_OUTPUT);
|
|
46
47
|
const files: EmittedFile[] = [];
|
|
47
48
|
for (const t of outputs) {
|
|
48
49
|
// Only json/xml outputs get a renderable prompt fragment.
|
|
49
50
|
if (!templateSupportsPrompt(t)) continue;
|
|
50
51
|
// @payloadRef must resolve to a value-object (same contract as the parser).
|
|
51
|
-
|
|
52
|
+
// ADR-0039: resolving — a template may inherit its @* refs/format/kind via extends.
|
|
53
|
+
const payloadRef = t.attr(TEMPLATE_ATTR_PAYLOAD_REF);
|
|
52
54
|
if (typeof payloadRef !== "string") continue;
|
|
53
|
-
const vo = root.
|
|
55
|
+
const vo = root.children().find((c) => c.type === TYPE_OBJECT && refMatchesObject(c, payloadRef));
|
|
54
56
|
if (!vo) continue;
|
|
55
57
|
files.push({
|
|
56
58
|
path: `${dirPrefix}${t.name}.prompt.ts`,
|
|
@@ -48,15 +48,17 @@ export const renderHelper = function renderHelper(opts?: RenderHelperOpts): Gene
|
|
|
48
48
|
// framework defaults; used to resolve + verify each referenced mustache so
|
|
49
49
|
// the build-time drift gate runs against the same texts render() will see.
|
|
50
50
|
const provider = projectProvider(ctx.projectRoot);
|
|
51
|
+
// ADR-0039: resolving — root has no super (children()==ownChildren()).
|
|
51
52
|
const outputs = root
|
|
52
|
-
.
|
|
53
|
+
.children()
|
|
53
54
|
.filter((c) => c.type === TYPE_TEMPLATE && c.subType === TEMPLATE_SUBTYPE_OUTPUT);
|
|
54
55
|
const files: EmittedFile[] = [];
|
|
55
56
|
for (const t of outputs) {
|
|
56
57
|
// @payloadRef must resolve to a value-object (same contract as the parser).
|
|
57
|
-
|
|
58
|
+
// ADR-0039: resolving — a template may inherit its @* refs/format/kind via extends.
|
|
59
|
+
const payloadRef = t.attr(TEMPLATE_ATTR_PAYLOAD_REF);
|
|
58
60
|
if (typeof payloadRef !== "string") continue;
|
|
59
|
-
const vo = root.
|
|
61
|
+
const vo = root.children().find((c) => c.type === TYPE_OBJECT && refMatchesObject(c, payloadRef));
|
|
60
62
|
if (!vo) continue;
|
|
61
63
|
files.push({
|
|
62
64
|
// renderRenderHelper THROWS (fails codegen) on a mustache↔VO drift —
|
|
@@ -30,7 +30,8 @@ export const routesFileHono = function routesFileHono(opts?: RoutesFileHonoOpts)
|
|
|
30
30
|
// Marks this as the Hono routes generator so the runner can aggregate
|
|
31
31
|
// `ctx.config.includeHonoRoutes` and api-docs auto-documents the Hono surface.
|
|
32
32
|
emitsHonoRoutes: true,
|
|
33
|
-
|
|
33
|
+
// ADR-0039: resolving — a concrete entity may inherit @emitRoutes via extends.
|
|
34
|
+
filter: (e: MetaObject) => e.attr(CODEGEN_ATTR_EMIT_ROUTES) !== false && userFilter(e),
|
|
34
35
|
generate: perEntity(async (entity, ctx) => {
|
|
35
36
|
if (!ctx.renderContext) {
|
|
36
37
|
throw new Error("routes-file-hono: renderContext is required (provided by runGen)");
|
|
@@ -24,7 +24,8 @@ export const routesFile = function routesFile(opts?: RoutesFileOpts): Generator
|
|
|
24
24
|
name: "routes-file",
|
|
25
25
|
// Always set: AND-composes metadata opt-out with optional user filter.
|
|
26
26
|
filter: (e: MetaObject) =>
|
|
27
|
-
|
|
27
|
+
// ADR-0039: resolving — a concrete entity may inherit its @emit* opt-out flag via extends.
|
|
28
|
+
e.attr(CODEGEN_ATTR_EMIT_ROUTES) !== false && !isTphSubtype(e) && userFilter(e),
|
|
28
29
|
generate: perEntity(async (entity, ctx) => {
|
|
29
30
|
if (!ctx.renderContext) {
|
|
30
31
|
throw new Error("routes-file: renderContext is required (provided by runGen)");
|
|
@@ -107,28 +107,29 @@ export function buildTemplateDocData(
|
|
|
107
107
|
): TemplateDocData {
|
|
108
108
|
const layout = opts?.layout ?? "flat";
|
|
109
109
|
const root = opts?.loadedRoot;
|
|
110
|
-
|
|
110
|
+
// ADR-0039: resolving — a template may inherit its @kind/@*Ref/@format/etc via extends.
|
|
111
|
+
const kindRaw = ((template.attr(TEMPLATE_ATTR_KIND) as string | undefined) ??
|
|
111
112
|
TEMPLATE_KIND_DEFAULT).toLowerCase();
|
|
112
113
|
const isEmail = kindRaw === TEMPLATE_KIND_EMAIL;
|
|
113
114
|
const kind: "document" | "email" = isEmail ? TEMPLATE_KIND_EMAIL : TEMPLATE_KIND_DOCUMENT;
|
|
114
115
|
|
|
115
|
-
const payloadRefRaw = template.
|
|
116
|
+
const payloadRefRaw = template.attr(TEMPLATE_ATTR_PAYLOAD_REF);
|
|
116
117
|
const payloadName =
|
|
117
118
|
typeof payloadRefRaw === "string" && payloadRefRaw.length > 0
|
|
118
119
|
? stripPackage(payloadRefRaw)
|
|
119
120
|
: "unknown";
|
|
120
121
|
|
|
121
|
-
const requiredTags = attrStringList(template.
|
|
122
|
-
const maxChars = readMaxChars(template.
|
|
122
|
+
const requiredTags = attrStringList(template.attr(TEMPLATE_ATTR_REQUIRED_TAGS));
|
|
123
|
+
const maxChars = readMaxChars(template.attr(TEMPLATE_ATTR_MAX_CHARS));
|
|
123
124
|
|
|
124
125
|
let format = "";
|
|
125
126
|
let parts: TemplateOutputPart[] | undefined;
|
|
126
127
|
const sourceRefs: string[] = [];
|
|
127
128
|
|
|
128
129
|
if (isEmail) {
|
|
129
|
-
const subjectRef = template.
|
|
130
|
-
const htmlBodyRef = template.
|
|
131
|
-
const textBodyRef = template.
|
|
130
|
+
const subjectRef = template.attr(TEMPLATE_ATTR_SUBJECT_REF);
|
|
131
|
+
const htmlBodyRef = template.attr(TEMPLATE_ATTR_HTML_BODY_REF);
|
|
132
|
+
const textBodyRef = template.attr(TEMPLATE_ATTR_TEXT_BODY_REF);
|
|
132
133
|
parts = [];
|
|
133
134
|
if (typeof subjectRef === "string") {
|
|
134
135
|
parts.push({ label: "Subject", ref: subjectRef, format: "text", escaped: false });
|
|
@@ -143,8 +144,8 @@ export function buildTemplateDocData(
|
|
|
143
144
|
sourceRefs.push(textBodyRef);
|
|
144
145
|
}
|
|
145
146
|
} else {
|
|
146
|
-
format = ((template.
|
|
147
|
-
const textRef = template.
|
|
147
|
+
format = ((template.attr(TEMPLATE_ATTR_FORMAT) as string | undefined) ?? "text").toLowerCase();
|
|
148
|
+
const textRef = template.attr(TEMPLATE_ATTR_TEXT_REF);
|
|
148
149
|
if (typeof textRef === "string") sourceRefs.push(textRef);
|
|
149
150
|
}
|
|
150
151
|
|
|
@@ -285,7 +286,8 @@ function makePartialHrefResolver(
|
|
|
285
286
|
): (ref: string) => string | undefined {
|
|
286
287
|
// Map each documented source ref → the template node that documents it.
|
|
287
288
|
const refToTemplate = new Map<string, MetaData>();
|
|
288
|
-
|
|
289
|
+
// ADR-0039: resolving — root has no super (children()==ownChildren()).
|
|
290
|
+
for (const child of root.children()) {
|
|
289
291
|
if (child.type !== TYPE_TEMPLATE || child.subType !== TEMPLATE_SUBTYPE_OUTPUT) continue;
|
|
290
292
|
for (const attr of [
|
|
291
293
|
TEMPLATE_ATTR_TEXT_REF,
|
|
@@ -293,7 +295,8 @@ function makePartialHrefResolver(
|
|
|
293
295
|
TEMPLATE_ATTR_HTML_BODY_REF,
|
|
294
296
|
TEMPLATE_ATTR_TEXT_BODY_REF,
|
|
295
297
|
]) {
|
|
296
|
-
|
|
298
|
+
// ADR-0039: resolving — a template may inherit these refs via extends.
|
|
299
|
+
const v = child.attr(attr);
|
|
297
300
|
if (typeof v === "string" && v.length > 0 && !refToTemplate.has(v)) {
|
|
298
301
|
refToTemplate.set(v, child);
|
|
299
302
|
}
|
|
@@ -72,13 +72,17 @@ export const traceHelperFile = function traceHelperFile(opts?: TraceHelperOpts):
|
|
|
72
72
|
if (!extendsBase(entity)) return [];
|
|
73
73
|
|
|
74
74
|
// Find the nested template.prompt.
|
|
75
|
-
|
|
75
|
+
// ADR-0039: resolving — a concrete trace entity may inherit its
|
|
76
|
+
// template.prompt from an abstract base (it extendsBase); own-only would
|
|
77
|
+
// miss the inherited prompt.
|
|
78
|
+
const prompt = entity.children().find(
|
|
76
79
|
(c) => c.type === TYPE_TEMPLATE && c.subType === TEMPLATE_SUBTYPE_PROMPT,
|
|
77
80
|
);
|
|
78
81
|
if (prompt === undefined) return [];
|
|
79
82
|
|
|
80
|
-
|
|
81
|
-
const
|
|
83
|
+
// ADR-0039: resolving — a prompt may inherit @payloadRef/@responseRef via extends.
|
|
84
|
+
const payloadRef = prompt.attr(TEMPLATE_ATTR_PAYLOAD_REF);
|
|
85
|
+
const responseRef = prompt.attr(TEMPLATE_ATTR_RESPONSE_REF);
|
|
82
86
|
|
|
83
87
|
// @responseRef types the result; @payloadRef types the request. Both gate
|
|
84
88
|
// the helper: the entity must declare voRequest/voResponse field.object
|
|
@@ -111,7 +115,8 @@ export const traceHelperFile = function traceHelperFile(opts?: TraceHelperOpts):
|
|
|
111
115
|
|
|
112
116
|
// Derive the parse format from the prompt's @format attr.
|
|
113
117
|
// "xml" → Format.XML; absent or any other value → Format.JSON.
|
|
114
|
-
|
|
118
|
+
// ADR-0039: resolving — a prompt may inherit @format via extends.
|
|
119
|
+
const promptFormat = prompt.attr(TEMPLATE_ATTR_FORMAT);
|
|
115
120
|
const formatLiteral = typeof promptFormat === "string" && promptFormat.toLowerCase() === "xml"
|
|
116
121
|
? "Format.XML"
|
|
117
122
|
: "Format.JSON";
|
|
@@ -124,7 +129,8 @@ export const traceHelperFile = function traceHelperFile(opts?: TraceHelperOpts):
|
|
|
124
129
|
|
|
125
130
|
// A renderable prompt (carries @textRef) gets an additional call<Entity> helper
|
|
126
131
|
// that renders the prompt text, calls the LLM, then parses + persists a trace row.
|
|
127
|
-
|
|
132
|
+
// ADR-0039: resolving — a prompt may inherit @textRef via extends.
|
|
133
|
+
const textRef = prompt.attr(TEMPLATE_ATTR_TEXT_REF);
|
|
128
134
|
const renderable = typeof textRef === "string";
|
|
129
135
|
// Same @format attr, two intentionally different shapes: extract() takes the
|
|
130
136
|
// Format enum (formatLiteral, above → Format.XML/Format.JSON), render() takes the
|