@metaobjectsdev/codegen-ts 0.24.3 → 0.24.5

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.
Files changed (65) hide show
  1. package/dist/client-directive.d.ts +18 -0
  2. package/dist/client-directive.d.ts.map +1 -0
  3. package/dist/client-directive.js +37 -0
  4. package/dist/client-directive.js.map +1 -0
  5. package/dist/generators/routes-file-hono.d.ts.map +1 -1
  6. package/dist/generators/routes-file-hono.js +11 -6
  7. package/dist/generators/routes-file-hono.js.map +1 -1
  8. package/dist/index.d.ts +3 -1
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +5 -1
  11. package/dist/index.js.map +1 -1
  12. package/dist/metaobjects-config.d.ts +29 -0
  13. package/dist/metaobjects-config.d.ts.map +1 -1
  14. package/dist/metaobjects-config.js +1 -0
  15. package/dist/metaobjects-config.js.map +1 -1
  16. package/dist/naming.d.ts +17 -0
  17. package/dist/naming.d.ts.map +1 -1
  18. package/dist/naming.js +21 -0
  19. package/dist/naming.js.map +1 -1
  20. package/dist/payload-codegen.d.ts.map +1 -1
  21. package/dist/payload-codegen.js +4 -3
  22. package/dist/payload-codegen.js.map +1 -1
  23. package/dist/prompt-generator-gate.d.ts +27 -0
  24. package/dist/prompt-generator-gate.d.ts.map +1 -0
  25. package/dist/prompt-generator-gate.js +62 -0
  26. package/dist/prompt-generator-gate.js.map +1 -0
  27. package/dist/reference-templates.d.ts +22 -5
  28. package/dist/reference-templates.d.ts.map +1 -1
  29. package/dist/reference-templates.js +55 -21
  30. package/dist/reference-templates.js.map +1 -1
  31. package/dist/render-context.d.ts +9 -1
  32. package/dist/render-context.d.ts.map +1 -1
  33. package/dist/render-context.js +1 -0
  34. package/dist/render-context.js.map +1 -1
  35. package/dist/runner.d.ts.map +1 -1
  36. package/dist/runner.js +6 -0
  37. package/dist/runner.js.map +1 -1
  38. package/dist/templates/output-parser.d.ts.map +1 -1
  39. package/dist/templates/output-parser.js +54 -8
  40. package/dist/templates/output-parser.js.map +1 -1
  41. package/dist/templates/render-helper.d.ts.map +1 -1
  42. package/dist/templates/render-helper.js +3 -1
  43. package/dist/templates/render-helper.js.map +1 -1
  44. package/dist/templates/requirement-test.d.ts.map +1 -1
  45. package/dist/templates/requirement-test.js +35 -19
  46. package/dist/templates/requirement-test.js.map +1 -1
  47. package/package.json +6 -6
  48. package/src/client-directive.ts +36 -0
  49. package/src/generators/routes-file-hono.ts +11 -9
  50. package/src/index.ts +6 -1
  51. package/src/metaobjects-config.ts +30 -0
  52. package/src/naming.ts +23 -0
  53. package/src/payload-codegen.ts +4 -3
  54. package/src/prompt-generator-gate.ts +73 -0
  55. package/src/reference/barrel.ts +3 -0
  56. package/src/reference/entity.ts +7 -0
  57. package/src/reference/queries.ts +5 -0
  58. package/src/reference/routes-hono.ts +103 -0
  59. package/src/reference/routes.ts +5 -0
  60. package/src/reference-templates.ts +61 -21
  61. package/src/render-context.ts +10 -1
  62. package/src/runner.ts +7 -0
  63. package/src/templates/output-parser.ts +54 -7
  64. package/src/templates/render-helper.ts +3 -1
  65. package/src/templates/requirement-test.ts +36 -16
@@ -15,11 +15,15 @@ import {
15
15
  TYPE_TEMPLATE,
16
16
  TEMPLATE_SUBTYPE_PROMPT,
17
17
  FIELD_SUBTYPE_OBJECT,
18
+ FIELD_SUBTYPE_ENUM,
18
19
  FIELD_ATTR_OBJECT_REF,
19
20
  TEMPLATE_ATTR_RESPONSE_REF,
20
21
  RESPONSE_FORMAT_XML,
21
22
  resolveObjectRef,
22
23
  } from "@metaobjectsdev/metadata";
24
+ import type { MetaField } from "@metaobjectsdev/metadata";
25
+ import { enumValues, zodEnumExpr } from "../enum-meta.js";
26
+ import { templateSymbolBase } from "../naming.js";
23
27
  import { responseShape } from "./find-inbound.js";
24
28
  import { isRequired } from "./fr010-field-mapping.js";
25
29
  import {
@@ -32,6 +36,29 @@ import {
32
36
  } from "./extract-delegate-emitter.js";
33
37
  import type { RenderContext } from "../render-context.js";
34
38
 
39
+ /**
40
+ * Wire-shape validators per field subtype, for a JSON reply from a model.
41
+ *
42
+ * This map used to hold only the nine entries above `uuid`, so EVERY other subtype fell
43
+ * through the `?? "z.unknown()"` below — enum, uuid, date, time, timestamp, decimal,
44
+ * currency, uri and inet. `z.unknown()` accepts anything, including `null` on a `@required`
45
+ * field, so a declared response payload of eleven fields validated three of them. It
46
+ * mapped `class`, `short` and `byte` — the three subtypes this project CUT as
47
+ * non-functional registration-only stubs — while missing `currency` and `uuid`, which are
48
+ * exactly where a hallucinated value costs money or corrupts an identifier.
49
+ *
50
+ * The inversion is what makes it serious: validation was strongest on the payload we
51
+ * control and absent on the reply we do not, in the tier whose entire name is
52
+ * parser-on-receipt. The tolerant extractor in this same generated file reads the live
53
+ * metadata and DOES reject a non-member; so did Python's `FieldSpec.enum_field`. Only this
54
+ * strict path — the one called `parse<Name>`, documented `@throws on validation failure`,
55
+ * and the first one an adopter reaches for — threw the domain away.
56
+ *
57
+ * Temporal and numeric subtypes stay STRING-shaped on the wire deliberately: these
58
+ * validate raw JSON, where a timestamp is an ISO string and a decimal is a string to
59
+ * avoid float loss (the same reasoning as `zod-validators.ts`'s `z.coerce.date()` note).
60
+ * Coercion into native types is the caller's job, after the shape is known good.
61
+ */
35
62
  const SCALAR_ZOD: Record<string, string> = {
36
63
  string: "z.string()",
37
64
  class: "z.string()",
@@ -42,6 +69,17 @@ const SCALAR_ZOD: Record<string, string> = {
42
69
  double: "z.number()",
43
70
  float: "z.number()",
44
71
  boolean: "z.boolean()",
72
+ uuid: "z.string().uuid()",
73
+ date: "z.string()",
74
+ time: "z.string()",
75
+ timestamp: "z.string()",
76
+ // A decimal crosses the wire as a string precisely so it does not become a float;
77
+ // accepting a number here would re-admit the loss the subtype exists to prevent.
78
+ decimal: "z.string()",
79
+ // Integer minor units — the wire contract every port shares. Never a float.
80
+ currency: "z.number().int()",
81
+ uri: "z.string()",
82
+ inet: "z.string()",
45
83
  };
46
84
 
47
85
  // ADR-0039: resolving — root has no super (children()==ownChildren()); a top-level object/template may itself extend, so resolve rather than work-by-accident.
@@ -84,6 +122,12 @@ function fieldZod(field: MetaData, root: MetaData, seen: ReadonlySet<string>, de
84
122
  const inner = findObject(root, refName, field.parent?.package ?? field.parent?.fileDefaultPackage ?? "");
85
123
  base = inner ? renderObjectSchema(inner, root, new Set(seen).add(refName), depth + 1) : "z.unknown()";
86
124
  }
125
+ } else if (field.subType === FIELD_SUBTYPE_ENUM) {
126
+ // The members are declared, resolving (so an `extends`-inherited @values is seen),
127
+ // and already reachable — the send-side type in prompts.ts is built from them. The
128
+ // one closed domain in an untrusted reply is the one worth checking hardest.
129
+ const values = enumValues(field as MetaField);
130
+ base = values !== undefined && values.length > 0 ? zodEnumExpr(values) : "z.unknown()";
87
131
  } else {
88
132
  base = SCALAR_ZOD[field.subType] ?? "z.unknown()";
89
133
  }
@@ -137,11 +181,14 @@ export function renderOutputParser(root: MetaData, templateName: string, ctx?: R
137
181
  const { vo, ref: payloadRef } = shape;
138
182
 
139
183
  const schema = renderObjectSchema(vo, root, new Set([payloadRef]), 0);
140
- const schemaName = `${templateName}Schema`;
141
- const dataName = `${templateName}Data`;
142
- const errorName = `${templateName}ValidationError`;
143
- const parseName = `parse${templateName}`;
144
- const safeParseName = `safeParse${templateName}`;
184
+ // One base for every symbol in this file, shared with promptRender()'s handle — the
185
+ // three template emitters used to spell the same node three ways (see naming.ts).
186
+ const base = templateSymbolBase(templateName);
187
+ const schemaName = `${base}Schema`;
188
+ const dataName = `${base}Data`;
189
+ const errorName = `${base}ValidationError`;
190
+ const parseName = `parse${base}`;
191
+ const safeParseName = `safeParse${base}`;
145
192
 
146
193
  // FR-010: emit the tolerant extract() API alongside the strict Zod parser.
147
194
  //
@@ -209,8 +256,8 @@ export function ${safeParseName}(
209
256
  // Unconditional since ADR-0052: a declared @responseRef IS the request for the
210
257
  // tolerant path, and @responseFormat is a closed json|xml set, so there is no
211
258
  // longer a third case to gate on.
212
- const extractedName = `${templateName}Extracted`;
213
- const extractLenientWithName = `extractLenient${templateName}WithLoader`;
259
+ const extractedName = `${templateSymbolBase(templateName)}Extracted`;
260
+ const extractLenientWithName = `extractLenient${templateSymbolBase(templateName)}WithLoader`;
214
261
  const payloadFqnConst = `${templateName.toUpperCase()}_PAYLOAD_NAME`;
215
262
  const formatEnum = format === RESPONSE_FORMAT_XML ? "Format.XML" : "Format.JSON";
216
263
 
@@ -52,6 +52,7 @@ import {
52
52
  type PayloadField,
53
53
  type VerifyError,
54
54
  } from "@metaobjectsdev/render";
55
+ import { templateSymbolBase } from "../naming.js";
55
56
 
56
57
  // ADR-0039: resolving — root has no super (children()==ownChildren()); a top-level object/template may itself extend, so resolve rather than work-by-accident.
57
58
  // ADR-0042: package-local — resolveObjectRef binds a bare @objectRef in `referrerPkg` first (else root-level), an FQN exactly.
@@ -175,7 +176,8 @@ export function renderRenderHelper(
175
176
 
176
177
  const fields = derivePayloadFieldTree(root, payloadRef, tmplPkg);
177
178
  const ft = fieldTreeLiteral(fields);
178
- const fnName = `render${templateName}`;
179
+ // Same base as promptRender()'s handle and outputParser()'s symbols (naming.ts).
180
+ const fnName = `render${templateSymbolBase(templateName)}`;
179
181
  // @payloadRef may arrive package-qualified (FQN) once resolved — a bare
180
182
  // `::`-free name is required everywhere it's emitted as a TS identifier /
181
183
  // import specifier below (mirrors the same stripPackage() call every other
@@ -10,6 +10,11 @@
10
10
  // claims the capability works, so an empty green test asserts the opposite of the
11
11
  // claim — the original defect recreated in a new place.
12
12
 
13
+ import {
14
+ REQUIREMENT_STATUSES,
15
+ REQUIREMENT_STATUSES_REQUIRING_LIVE_NODES,
16
+ REQUIREMENT_STATUS_RETIRED,
17
+ } from "@metaobjectsdev/metadata";
13
18
  import { GENERATED_HEADER } from "../constants.js";
14
19
  import type { RequirementView, ResolvedClaim } from "../requirement-walk.js";
15
20
 
@@ -27,21 +32,22 @@ export interface RequirementTestArgs {
27
32
  * Statuses whose stub is SKIPPED rather than failing.
28
33
  *
29
34
  * The rule is "does this entry claim the capability works right now?" — only `live`
30
- * and `partial` do. `planned` is intended-not-built; `abandoned` and `superseded`
31
- * describe a capability deliberately retired, whose `@implementedBy` is SUPPOSED to
32
- * dangle. Emitting a failing stub for any of the three reddens an application's suite
33
- * forever for something nobody intends to build, which is the noise an app silences
34
- * wholesale — taking the `live` stubs with it.
35
+ * and `partial` do. `planned` is intended-not-built; `retired` is built-then-
36
+ * deliberately-removed. Emitting a failing stub for either reddens an application's
37
+ * suite forever for something nobody intends to build, which is the noise an app
38
+ * silences wholesale taking the `live` stubs with it.
35
39
  *
36
- * (FR-038 §4 proposes retiring `abandoned`/`superseded` from the vocabulary entirely.
37
- * Until that breaking cut lands they are legal `@status` values, so the renderer has
38
- * to handle them.)
40
+ * This set is derived from the LOADER's enum, not restated from it. It used to be a
41
+ * literal naming `abandoned` and `superseded`, and when 0.24.0 retired both and
42
+ * 0.24.2 put `retired` in their place, the set was left behind: it skipped two
43
+ * statuses the loader had begun REFUSING and failed on the one that replaced them,
44
+ * so every retired entry emitted a permanently red stub — the exact noise this set
45
+ * exists to prevent. Deriving it means the next status move cannot leave it behind:
46
+ * a status that is neither live nor partial is skipped by construction.
39
47
  */
40
- const SKIPPED_STATUSES: ReadonlySet<string> = new Set([
41
- "planned",
42
- "abandoned",
43
- "superseded",
44
- ]);
48
+ const SKIPPED_STATUSES: ReadonlySet<string> = new Set(
49
+ REQUIREMENT_STATUSES.filter((s) => !REQUIREMENT_STATUSES_REQUIRING_LIVE_NODES.includes(s)),
50
+ );
45
51
 
46
52
  /**
47
53
  * Escape an author-supplied value for a double-quoted TS string literal.
@@ -112,12 +118,26 @@ export function renderRequirementTest(a: RequirementTestArgs): string {
112
118
  // A `live` or `partial` stub asserts FAILURE until someone writes the real
113
119
  // assertion over it. `expect.unreachable` names the requirement in the failure
114
120
  // message, so a red run says which claim is unproven rather than just "failed".
115
- const body = skipped
116
- ? ` // Intended, not built. Write the assertion when this becomes live.`
117
- : ` expect.unreachable(\n` +
121
+ // The two skipped statuses mean OPPOSITE things, so they must not share a body.
122
+ // `planned` is intended-not-built and the instruction is to write the assertion when
123
+ // it becomes live. `retired` is built-then-deliberately-removed, and telling a reader
124
+ // to write it "when this becomes live" instructs them to revive the capability —
125
+ // inverting the one guardrail 0.24.2 restored `retired` for. The repo's own harness
126
+ // generator already branches here; the shipped renderer now does too.
127
+ let body: string;
128
+ if (!skipped) {
129
+ body =
130
+ ` expect.unreachable(\n` +
118
131
  ` "unimplemented requirement stub: ${testName} — " +\n` +
119
132
  ` "replace this with an assertion that fails when: ${forStringLiteral(a.counterexample)}",\n` +
120
133
  ` );`;
134
+ } else if (a.view.status === REQUIREMENT_STATUS_RETIRED) {
135
+ body =
136
+ ` // Retired: this capability was deliberately removed and must not be rebuilt.\n` +
137
+ ` // If you assert anything here, assert that it STAYS removed.`;
138
+ } else {
139
+ body = ` // Intended, not built. Write the assertion when this becomes live.`;
140
+ }
121
141
 
122
142
  return (
123
143
  `// ${GENERATED_HEADER}.\n` +