@metaobjectsdev/codegen-ts 0.24.4 → 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.
- 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/generators/routes-file-hono.d.ts.map +1 -1
- package/dist/generators/routes-file-hono.js +11 -6
- package/dist/generators/routes-file-hono.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/metaobjects-config.d.ts +29 -0
- package/dist/metaobjects-config.d.ts.map +1 -1
- package/dist/metaobjects-config.js +1 -0
- package/dist/metaobjects-config.js.map +1 -1
- 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/payload-codegen.d.ts.map +1 -1
- package/dist/payload-codegen.js +4 -3
- package/dist/payload-codegen.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 +9 -1
- package/dist/render-context.d.ts.map +1 -1
- package/dist/render-context.js +1 -0
- package/dist/render-context.js.map +1 -1
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +6 -0
- package/dist/runner.js.map +1 -1
- 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/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 +35 -19
- package/dist/templates/requirement-test.js.map +1 -1
- package/package.json +6 -6
- package/src/client-directive.ts +36 -0
- package/src/generators/routes-file-hono.ts +11 -9
- package/src/index.ts +6 -1
- package/src/metaobjects-config.ts +30 -0
- package/src/naming.ts +23 -0
- package/src/payload-codegen.ts +4 -3
- package/src/prompt-generator-gate.ts +73 -0
- package/src/reference/barrel.ts +3 -0
- package/src/reference/entity.ts +7 -0
- package/src/reference/queries.ts +5 -0
- package/src/reference/routes-hono.ts +103 -0
- package/src/reference/routes.ts +5 -0
- package/src/reference-templates.ts +61 -21
- package/src/render-context.ts +10 -1
- package/src/runner.ts +7 -0
- package/src/templates/output-parser.ts +54 -7
- package/src/templates/render-helper.ts +3 -1
- 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
|
-
|
|
141
|
-
|
|
142
|
-
const
|
|
143
|
-
const
|
|
144
|
-
const
|
|
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
|
-
|
|
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; `
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
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
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
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
|
-
|
|
42
|
-
|
|
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
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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` +
|