@metaobjectsdev/codegen-ts 0.21.1 → 0.21.3
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 +9 -0
- package/dist/column-mapper.js.map +1 -1
- package/dist/metaobjects-config.d.ts +15 -0
- package/dist/metaobjects-config.d.ts.map +1 -1
- package/dist/metaobjects-config.js +6 -2
- package/dist/metaobjects-config.js.map +1 -1
- package/dist/render-context.d.ts +4 -0
- package/dist/render-context.d.ts.map +1 -1
- package/dist/render-context.js +5 -1
- package/dist/render-context.js.map +1 -1
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +6 -1
- package/dist/runner.js.map +1 -1
- package/dist/templates/drizzle-schema.js +10 -4
- package/dist/templates/drizzle-schema.js.map +1 -1
- package/dist/templates/entity-file.js +1 -1
- package/dist/templates/entity-file.js.map +1 -1
- package/dist/templates/field-meta.d.ts +10 -1
- package/dist/templates/field-meta.d.ts.map +1 -1
- package/dist/templates/field-meta.js +22 -3
- package/dist/templates/field-meta.js.map +1 -1
- package/dist/templates/filter-allowlist.d.ts +9 -1
- package/dist/templates/filter-allowlist.d.ts.map +1 -1
- package/dist/templates/filter-allowlist.js +14 -2
- package/dist/templates/filter-allowlist.js.map +1 -1
- package/dist/templates/projection-decl.js +1 -1
- package/dist/templates/projection-decl.js.map +1 -1
- package/dist/templates/value-object-file.js +1 -1
- package/dist/templates/value-object-file.js.map +1 -1
- package/dist/templates/view-decl.d.ts.map +1 -1
- package/dist/templates/view-decl.js +3 -1
- package/dist/templates/view-decl.js.map +1 -1
- package/dist/templates/zod-validators.d.ts.map +1 -1
- package/dist/templates/zod-validators.js +44 -6
- package/dist/templates/zod-validators.js.map +1 -1
- package/package.json +6 -6
- package/src/column-mapper.ts +9 -0
- package/src/metaobjects-config.ts +21 -2
- package/src/reference/entity.ts +1 -1
- package/src/render-context.ts +9 -1
- package/src/runner.ts +8 -1
- package/src/templates/drizzle-schema.ts +10 -4
- package/src/templates/entity-file.ts +1 -1
- package/src/templates/field-meta.ts +22 -3
- package/src/templates/filter-allowlist.ts +15 -2
- package/src/templates/projection-decl.ts +1 -1
- package/src/templates/value-object-file.ts +1 -1
- package/src/templates/view-decl.ts +3 -1
- package/src/templates/zod-validators.ts +44 -5
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
opsForSubType,
|
|
17
17
|
} from "@metaobjectsdev/metadata";
|
|
18
18
|
import { sortableFields } from "./filter-shared.js";
|
|
19
|
+
import type { RenderContext } from "../render-context.js";
|
|
19
20
|
|
|
20
21
|
const NUMBER_SUBTYPES = new Set<string>([
|
|
21
22
|
FIELD_SUBTYPE_INT,
|
|
@@ -52,8 +53,15 @@ function filterableFields(entity: MetaObject, exclude?: string): MetaField[] {
|
|
|
52
53
|
* `exclude` (FR-017): drop a field from the allowlist. Used by per-subtype TPH
|
|
53
54
|
* allowlists to omit the discriminator — it's pinned by the per-subtype route
|
|
54
55
|
* path, so a client must not filter on it.
|
|
56
|
+
*
|
|
57
|
+
* `ctx` supplies `timestampMode`: under `"date"` a `field.timestamp` column binds a JS
|
|
58
|
+
* `Date`, so its rule carries `dateValues: true` and runtime-ts's filter parser coerces
|
|
59
|
+
* the query-string value with `new Date(...)` rather than binding a string against a
|
|
60
|
+
* Date-typed column (which threw at request time). `ctx.timestampMode` is already
|
|
61
|
+
* normalized to `"string"` for sqlite/D1 at both config choke points, so no dialect
|
|
62
|
+
* branching is needed here. Absent `ctx`, or in the default mode, output is unchanged.
|
|
55
63
|
*/
|
|
56
|
-
export function renderFilterAllowlist(entity: MetaObject, exclude?: string): Code {
|
|
64
|
+
export function renderFilterAllowlist(entity: MetaObject, exclude?: string, ctx?: RenderContext): Code {
|
|
57
65
|
const fields = filterableFields(entity, exclude);
|
|
58
66
|
if (fields.length === 0) {
|
|
59
67
|
return code`
|
|
@@ -66,7 +74,12 @@ export const ${entity.name}FilterAllowlist = {} as const satisfies FilterAllowli
|
|
|
66
74
|
.map((f) => {
|
|
67
75
|
const ops = opsForSubType(f.subType).map((o) => JSON.stringify(o)).join(", ");
|
|
68
76
|
const sub = filterSubTypeFor(f.subType);
|
|
69
|
-
|
|
77
|
+
// Only field.timestamp is governed by timestampMode — Drizzle types
|
|
78
|
+
// field.date / field.time as strings under every dialect.
|
|
79
|
+
const dateValues = ctx?.timestampMode === "date" && f.subType === FIELD_SUBTYPE_TIMESTAMP
|
|
80
|
+
? ", dateValues: true as const"
|
|
81
|
+
: "";
|
|
82
|
+
return ` ${f.name}: { ops: [${ops}] as const, subType: ${JSON.stringify(sub)} as const, leadingWildcard: false${dateValues} }`;
|
|
70
83
|
})
|
|
71
84
|
.join(",\n");
|
|
72
85
|
return code`
|
|
@@ -176,7 +176,7 @@ ${constFieldLines.join("\n")}
|
|
|
176
176
|
} as const;
|
|
177
177
|
`,
|
|
178
178
|
...(allowlists
|
|
179
|
-
? [renderFilterAllowlist(projection), renderSortAllowlist(projection)]
|
|
179
|
+
? [renderFilterAllowlist(projection, undefined, ctx), renderSortAllowlist(projection)]
|
|
180
180
|
: []),
|
|
181
181
|
renderFilterType(projection),
|
|
182
182
|
];
|
|
@@ -40,7 +40,7 @@ export function renderValueObjectFile(obj: MetaObject, apiPrefix = "", ctx?: Ren
|
|
|
40
40
|
// not filter on it). Included fields are the subtype's own + inherited base
|
|
41
41
|
// filterable fields. Drives the per-subtype REST routes' filter layer.
|
|
42
42
|
const discField = tphSubtype ? tphDiscriminatorPin(obj)?.fieldName : undefined;
|
|
43
|
-
const tphFilterAllowlist = tphSubtype ? renderFilterAllowlist(obj, discField) : null;
|
|
43
|
+
const tphFilterAllowlist = tphSubtype ? renderFilterAllowlist(obj, discField, ctx) : null;
|
|
44
44
|
const tphSortAllowlist = tphSubtype ? renderSortAllowlist(obj, discField) : null;
|
|
45
45
|
// FR-017 Tier 3: the per-subtype CLIENT filter type, discriminator-excluded —
|
|
46
46
|
// kept in lockstep with the per-subtype allowlist above so a typed
|
|
@@ -130,7 +130,9 @@ export function renderViewReadZodObject(fields: readonly MetaField[], opts: View
|
|
|
130
130
|
return code` ${f.name}: ${base}${nullable}`;
|
|
131
131
|
}
|
|
132
132
|
// #204 — an array passthrough reads as `T[]`; zodTypeFor returns the ELEMENT type.
|
|
133
|
-
|
|
133
|
+
// CRITICAL 3: thread timestampMode through so a FIELD_SUBTYPE_TIMESTAMP column
|
|
134
|
+
// agrees with the Drizzle view column's mode (both sourced from `opts` above).
|
|
135
|
+
const inner = code`${z}.${zodTypeFor(f, timestampMode).replace(/^z\./, "")}`;
|
|
134
136
|
const zbase = f.resolvedIsArray() ? code`${z}.array(${inner})` : inner;
|
|
135
137
|
return code` ${f.name}: ${zbase}${nullable}`;
|
|
136
138
|
});
|
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
VALIDATOR_ATTR_MAX, VALIDATOR_ATTR_MIN, VALIDATOR_ATTR_PATTERN,
|
|
29
29
|
GENERATION_INCREMENT, GENERATION_UUID,
|
|
30
30
|
OBJECT_ATTR_DISCRIMINATOR, OBJECT_ATTR_DISCRIMINATOR_VALUE,
|
|
31
|
+
OBJECT_SUBTYPE_VALUE,
|
|
31
32
|
} from "@metaobjectsdev/metadata";
|
|
32
33
|
import { enumValues, zodEnumExpr } from "../enum-meta.js";
|
|
33
34
|
import { ZOD_INET_EXPR } from "./net-regex.js";
|
|
@@ -193,7 +194,13 @@ export function renderInsertSchemaOnly(obj: MetaObject, ctx?: RenderContext): Co
|
|
|
193
194
|
|
|
194
195
|
if (autoSet === AUTO_SET_ON_CREATE || autoSet === AUTO_SET_ON_UPDATE) {
|
|
195
196
|
insertFieldLines.push(
|
|
196
|
-
|
|
197
|
+
ctx?.timestampMode === "date"
|
|
198
|
+
// CRITICAL 1: these schemas validate raw JSON request bodies (wire
|
|
199
|
+
// timestamps are ISO strings), never a live `Date` — z.coerce.date()
|
|
200
|
+
// (not z.date()) so a string round-trips; z.date() rejects every JSON
|
|
201
|
+
// wire value outright (verified: z.date().safeParse(isoString) is false).
|
|
202
|
+
? code` ${child.name}: z.coerce.date().optional().transform(() => new Date())`
|
|
203
|
+
: code` ${child.name}: z.string().optional().transform(() => new Date().toISOString())`,
|
|
197
204
|
);
|
|
198
205
|
} else {
|
|
199
206
|
insertFieldLines.push(code` ${child.name}: ${zodFieldExpr(child, obj, ctx)}`);
|
|
@@ -338,7 +345,13 @@ export function renderZodValidators(obj: MetaObject, ctx?: RenderContext): Code
|
|
|
338
345
|
// Insert schema: @autoSet fields use transform (always override client input).
|
|
339
346
|
if (autoSet === AUTO_SET_ON_CREATE || autoSet === AUTO_SET_ON_UPDATE) {
|
|
340
347
|
insertFieldLines.push(
|
|
341
|
-
|
|
348
|
+
ctx?.timestampMode === "date"
|
|
349
|
+
// CRITICAL 1: these schemas validate raw JSON request bodies (wire
|
|
350
|
+
// timestamps are ISO strings), never a live `Date` — z.coerce.date()
|
|
351
|
+
// (not z.date()) so a string round-trips; z.date() rejects every JSON
|
|
352
|
+
// wire value outright (verified: z.date().safeParse(isoString) is false).
|
|
353
|
+
? code` ${child.name}: z.coerce.date().optional().transform(() => new Date())`
|
|
354
|
+
: code` ${child.name}: z.string().optional().transform(() => new Date().toISOString())`,
|
|
342
355
|
);
|
|
343
356
|
// Preserving schema: the @autoSet column is validated verbatim (its natural
|
|
344
357
|
// field expr) so an import/restore keeps the caller's original timestamp.
|
|
@@ -354,7 +367,13 @@ export function renderZodValidators(obj: MetaObject, ctx?: RenderContext): Code
|
|
|
354
367
|
// Omit: creation timestamps cannot be changed after creation
|
|
355
368
|
} else if (autoSet === AUTO_SET_ON_UPDATE) {
|
|
356
369
|
updateFieldLines.push(
|
|
357
|
-
|
|
370
|
+
ctx?.timestampMode === "date"
|
|
371
|
+
// CRITICAL 1: these schemas validate raw JSON request bodies (wire
|
|
372
|
+
// timestamps are ISO strings), never a live `Date` — z.coerce.date()
|
|
373
|
+
// (not z.date()) so a string round-trips; z.date() rejects every JSON
|
|
374
|
+
// wire value outright (verified: z.date().safeParse(isoString) is false).
|
|
375
|
+
? code` ${child.name}: z.coerce.date().optional().transform(() => new Date())`
|
|
376
|
+
: code` ${child.name}: z.string().optional().transform(() => new Date().toISOString())`,
|
|
358
377
|
);
|
|
359
378
|
} else {
|
|
360
379
|
// All non-autoSet fields are optional in the update schema (PATCH semantics).
|
|
@@ -511,9 +530,29 @@ function zodFieldExpr(field: MetaField, owner?: MetaObject, ctx?: RenderContext)
|
|
|
511
530
|
break;
|
|
512
531
|
case FIELD_SUBTYPE_DATE:
|
|
513
532
|
case FIELD_SUBTYPE_TIME:
|
|
514
|
-
|
|
515
|
-
|
|
533
|
+
baseStr = "z.string()"; // calendar date / time-of-day — always ISO-string-shaped, not governed by timestampMode
|
|
534
|
+
break;
|
|
535
|
+
case FIELD_SUBTYPE_TIMESTAMP: {
|
|
536
|
+
// Must agree with column-mapper.ts's mapColumnType, which already honors
|
|
537
|
+
// ctx.timestampMode for the Drizzle column itself — z.string() here regardless would
|
|
538
|
+
// disagree with a "date"-mode column (Date-typed) and fail to typecheck downstream
|
|
539
|
+
// (reported against an adopting project).
|
|
540
|
+
//
|
|
541
|
+
// IMPORTANT 5: a VO-hosted (object.value) timestamp is jsonb storage —
|
|
542
|
+
// inherently ISO-string JSON, never a live `Date` — so it stays z.string()
|
|
543
|
+
// regardless of ctx.timestampMode; that matches the VO structural interface
|
|
544
|
+
// (inferred-types.ts's SCALAR_TS_BY_SUBTYPE, deliberately NOT mode-aware —
|
|
545
|
+
// the two are documented as lock-step). An entity/TPH-subtype field (a real
|
|
546
|
+
// DB column) honors the mode.
|
|
547
|
+
const voHosted = owner?.subType === OBJECT_SUBTYPE_VALUE;
|
|
548
|
+
// CRITICAL 1: z.coerce.date() (not z.date()) — the ONE expression correct
|
|
549
|
+
// for both readers of zodFieldExpr's output: the TPH read schema parses DB
|
|
550
|
+
// rows (already a `Date` under pg date mode — z.coerce.date() passes a
|
|
551
|
+
// Date through unchanged) while insert/update/preserving parse wire JSON
|
|
552
|
+
// (an ISO string — z.coerce.date() parses it; z.date() would reject it).
|
|
553
|
+
baseStr = ctx?.timestampMode === "date" && !voHosted ? "z.coerce.date()" : "z.string()";
|
|
516
554
|
break;
|
|
555
|
+
}
|
|
517
556
|
case FIELD_SUBTYPE_ENUM: {
|
|
518
557
|
const values = enumValues(field);
|
|
519
558
|
if (values === undefined) {
|