@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.
Files changed (50) hide show
  1. package/dist/column-mapper.d.ts.map +1 -1
  2. package/dist/column-mapper.js +9 -0
  3. package/dist/column-mapper.js.map +1 -1
  4. package/dist/metaobjects-config.d.ts +15 -0
  5. package/dist/metaobjects-config.d.ts.map +1 -1
  6. package/dist/metaobjects-config.js +6 -2
  7. package/dist/metaobjects-config.js.map +1 -1
  8. package/dist/render-context.d.ts +4 -0
  9. package/dist/render-context.d.ts.map +1 -1
  10. package/dist/render-context.js +5 -1
  11. package/dist/render-context.js.map +1 -1
  12. package/dist/runner.d.ts.map +1 -1
  13. package/dist/runner.js +6 -1
  14. package/dist/runner.js.map +1 -1
  15. package/dist/templates/drizzle-schema.js +10 -4
  16. package/dist/templates/drizzle-schema.js.map +1 -1
  17. package/dist/templates/entity-file.js +1 -1
  18. package/dist/templates/entity-file.js.map +1 -1
  19. package/dist/templates/field-meta.d.ts +10 -1
  20. package/dist/templates/field-meta.d.ts.map +1 -1
  21. package/dist/templates/field-meta.js +22 -3
  22. package/dist/templates/field-meta.js.map +1 -1
  23. package/dist/templates/filter-allowlist.d.ts +9 -1
  24. package/dist/templates/filter-allowlist.d.ts.map +1 -1
  25. package/dist/templates/filter-allowlist.js +14 -2
  26. package/dist/templates/filter-allowlist.js.map +1 -1
  27. package/dist/templates/projection-decl.js +1 -1
  28. package/dist/templates/projection-decl.js.map +1 -1
  29. package/dist/templates/value-object-file.js +1 -1
  30. package/dist/templates/value-object-file.js.map +1 -1
  31. package/dist/templates/view-decl.d.ts.map +1 -1
  32. package/dist/templates/view-decl.js +3 -1
  33. package/dist/templates/view-decl.js.map +1 -1
  34. package/dist/templates/zod-validators.d.ts.map +1 -1
  35. package/dist/templates/zod-validators.js +44 -6
  36. package/dist/templates/zod-validators.js.map +1 -1
  37. package/package.json +6 -6
  38. package/src/column-mapper.ts +9 -0
  39. package/src/metaobjects-config.ts +21 -2
  40. package/src/reference/entity.ts +1 -1
  41. package/src/render-context.ts +9 -1
  42. package/src/runner.ts +8 -1
  43. package/src/templates/drizzle-schema.ts +10 -4
  44. package/src/templates/entity-file.ts +1 -1
  45. package/src/templates/field-meta.ts +22 -3
  46. package/src/templates/filter-allowlist.ts +15 -2
  47. package/src/templates/projection-decl.ts +1 -1
  48. package/src/templates/value-object-file.ts +1 -1
  49. package/src/templates/view-decl.ts +3 -1
  50. 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
- return ` ${f.name}: { ops: [${ops}] as const, subType: ${JSON.stringify(sub)} as const, leadingWildcard: false }`;
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
- const inner = code`${z}.${zodTypeFor(f).replace(/^z\./, "")}`;
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
- code` ${child.name}: z.string().optional().transform(() => new Date().toISOString())`,
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
- code` ${child.name}: z.string().optional().transform(() => new Date().toISOString())`,
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
- code` ${child.name}: z.string().optional().transform(() => new Date().toISOString())`,
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
- case FIELD_SUBTYPE_TIMESTAMP:
515
- baseStr = "z.string()";
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) {