@metaobjectsdev/codegen-ts 0.21.6 → 0.22.0-rc.2

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 (33) hide show
  1. package/dist/projection/build-projection-views.d.ts.map +1 -1
  2. package/dist/projection/build-projection-views.js +6 -4
  3. package/dist/projection/build-projection-views.js.map +1 -1
  4. package/dist/projection/extract-view-spec.d.ts.map +1 -1
  5. package/dist/projection/extract-view-spec.js +3 -3
  6. package/dist/projection/extract-view-spec.js.map +1 -1
  7. package/dist/projection/projection-detector.d.ts.map +1 -1
  8. package/dist/projection/projection-detector.js +7 -3
  9. package/dist/projection/projection-detector.js.map +1 -1
  10. package/dist/runner.d.ts.map +1 -1
  11. package/dist/runner.js +5 -2
  12. package/dist/runner.js.map +1 -1
  13. package/dist/source-detect.d.ts.map +1 -1
  14. package/dist/source-detect.js +17 -15
  15. package/dist/source-detect.js.map +1 -1
  16. package/dist/templates/callable-file.d.ts.map +1 -1
  17. package/dist/templates/callable-file.js +4 -2
  18. package/dist/templates/callable-file.js.map +1 -1
  19. package/dist/templates/projection-decl.js +2 -2
  20. package/dist/templates/projection-decl.js.map +1 -1
  21. package/dist/templates/zod-validators.d.ts +2 -0
  22. package/dist/templates/zod-validators.d.ts.map +1 -1
  23. package/dist/templates/zod-validators.js +69 -12
  24. package/dist/templates/zod-validators.js.map +1 -1
  25. package/package.json +6 -6
  26. package/src/projection/build-projection-views.ts +10 -9
  27. package/src/projection/extract-view-spec.ts +4 -5
  28. package/src/projection/projection-detector.ts +7 -7
  29. package/src/runner.ts +5 -2
  30. package/src/source-detect.ts +18 -11
  31. package/src/templates/callable-file.ts +5 -2
  32. package/src/templates/projection-decl.ts +2 -2
  33. package/src/templates/zod-validators.ts +75 -12
@@ -159,6 +159,44 @@ function primaryKeyFieldNames(obj: MetaObject): Set<string> {
159
159
  return new Set(primaryIdentityFieldNames(obj));
160
160
  }
161
161
 
162
+ /** PK field names the CALLER must supply on insert: an ASSIGNED primary key —
163
+ * a natural key or an externally-issued id, i.e. a primary identity carrying no
164
+ * @generation — that also has no @default to fill it.
165
+ *
166
+ * These are insert-REQUIRED regardless of @required, and that is a typing
167
+ * necessity rather than a policy choice: the Drizzle column for such a PK is
168
+ * `text("id").primaryKey()` with no default, so its inferred insert type
169
+ * requires the value. Deriving the PK's optionality from @required like any
170
+ * other field emitted `id: z.string().optional()`, and the generated
171
+ * `create<Entity>` — which pipes `InsertSchema.parse(data)` straight into
172
+ * `.values()` — then failed to compile (TS2769). It was also a semantic hole:
173
+ * the schema accepted a create payload with no primary key at all.
174
+ *
175
+ * Excluded, because each is already insert-optional for a real reason:
176
+ * • @generation increment|uuid PKs — omitted from the schema entirely
177
+ * (autoGenPkFieldNames), the caller never supplies them;
178
+ * • a PK carrying @default — the column gets that default, so omitting is
179
+ * legal and forcing it required would make callers repeat the default.
180
+ * Mirrors primaryKeyFieldNames' reasoning for the UpdateSchema: a PK column is
181
+ * never NULL, so PK optionality can never be read off @required alone. */
182
+ function assignedPkFieldNames(obj: MetaObject): Set<string> {
183
+ const autoGen = autoGenPkFieldNames(obj);
184
+ const out = new Set<string>();
185
+ for (const name of primaryIdentityFieldNames(obj)) {
186
+ if (autoGen.has(name)) continue;
187
+ out.add(name);
188
+ }
189
+ return out;
190
+ }
191
+
192
+ /** True when this field is an assigned PK with no @default — see
193
+ * assignedPkFieldNames. Kept as one predicate so the emitters and the
194
+ * documented-shape function (insertSchemaFields) cannot drift. */
195
+ function isInsertRequiredPk(field: MetaField, assignedPk: Set<string>): boolean {
196
+ if (!assignedPk.has(field.name)) return false;
197
+ return field.attr(FIELD_ATTR_DEFAULT) === undefined;
198
+ }
199
+
162
200
  /**
163
201
  * Emit ONLY the `<Name>InsertSchema`. Used by the value-object file emitter
164
202
  * for metaobjects with no writable source.rdb — those have no PATCH/update
@@ -171,6 +209,7 @@ function primaryKeyFieldNames(obj: MetaObject): Set<string> {
171
209
  export function renderInsertSchemaOnly(obj: MetaObject, ctx?: RenderContext): Code {
172
210
  const z = imp("z@zod");
173
211
  const autoGenPkFields = autoGenPkFieldNames(obj);
212
+ const assignedPkFields = assignedPkFieldNames(obj);
174
213
  const tphPin = tphDiscriminatorPin(obj);
175
214
 
176
215
  const insertFieldLines: Code[] = [];
@@ -204,7 +243,9 @@ export function renderInsertSchemaOnly(obj: MetaObject, ctx?: RenderContext): Co
204
243
  : code` ${child.name}: z.string().optional().transform(() => new Date().toISOString())`,
205
244
  );
206
245
  } else {
207
- insertFieldLines.push(code` ${child.name}: ${zodFieldExpr(child, obj, ctx)}`);
246
+ insertFieldLines.push(
247
+ code` ${child.name}: ${zodFieldExpr(child, obj, ctx, isInsertRequiredPk(child, assignedPkFields))}`,
248
+ );
208
249
  }
209
250
  }
210
251
 
@@ -246,11 +287,14 @@ export interface SchemaFieldShape {
246
287
  * • @readOnly fields are omitted (DB / replication owns the write path);
247
288
  * • a TPH subtype's @discriminator field is a pinned `z.literal(value)`;
248
289
  * • @autoSet fields are present but optional (server fills them);
290
+ * • an ASSIGNED primary key with no @default is required (see
291
+ * assignedPkFieldNames — its Drizzle column has no default);
249
292
  * • every other field's optionality is `fieldWillBeOptional` (not required, or
250
293
  * carries a @default).
251
294
  */
252
295
  export function insertSchemaFields(obj: MetaObject): SchemaFieldShape[] {
253
296
  const autoGenPkFields = autoGenPkFieldNames(obj);
297
+ const assignedPkFields = assignedPkFieldNames(obj);
254
298
  const tphPin = tphDiscriminatorPin(obj);
255
299
  const out: SchemaFieldShape[] = [];
256
300
  for (const child of obj.fields()) {
@@ -266,7 +310,10 @@ export function insertSchemaFields(obj: MetaObject): SchemaFieldShape[] {
266
310
  if (autoSet === AUTO_SET_ON_CREATE || autoSet === AUTO_SET_ON_UPDATE) {
267
311
  out.push({ name: child.name, optional: true, autoSet: true });
268
312
  } else {
269
- out.push({ name: child.name, optional: fieldWillBeOptional(child) });
313
+ out.push({
314
+ name: child.name,
315
+ optional: isInsertRequiredPk(child, assignedPkFields) ? false : fieldWillBeOptional(child),
316
+ });
270
317
  }
271
318
  }
272
319
  return out;
@@ -311,6 +358,7 @@ export function renderZodValidators(obj: MetaObject, ctx?: RenderContext): Code
311
358
  const z = imp("z@zod");
312
359
  const autoGenPkFields = autoGenPkFieldNames(obj);
313
360
  const pkFields = primaryKeyFieldNames(obj);
361
+ const assignedPkFields = assignedPkFieldNames(obj);
314
362
  const tphPin = tphDiscriminatorPin(obj);
315
363
 
316
364
  const insertFieldLines: Code[] = [];
@@ -358,7 +406,9 @@ export function renderZodValidators(obj: MetaObject, ctx?: RenderContext): Code
358
406
  // field expr) so an import/restore keeps the caller's original timestamp.
359
407
  preservingFieldLines.push(code` ${child.name}: ${zodFieldExpr(child, obj, ctx)}`);
360
408
  } else {
361
- const fieldLine = code` ${child.name}: ${zodFieldExpr(child, obj, ctx)}`;
409
+ // The preserving schema is an INSERT shape (import / restore / replication),
410
+ // so an assigned PK is required there for the same typing reason.
411
+ const fieldLine = code` ${child.name}: ${zodFieldExpr(child, obj, ctx, isInsertRequiredPk(child, assignedPkFields))}`;
362
412
  insertFieldLines.push(fieldLine);
363
413
  preservingFieldLines.push(fieldLine);
364
414
  }
@@ -452,7 +502,14 @@ function zodScalarFor(subType: string): string {
452
502
  return "z.string()"; // string/uuid/date/time/timestamp/decimal/enum on the wire
453
503
  }
454
504
 
455
- function zodFieldExpr(field: MetaField, owner?: MetaObject, ctx?: RenderContext): Code {
505
+ function zodFieldExpr(
506
+ field: MetaField,
507
+ owner?: MetaObject,
508
+ ctx?: RenderContext,
509
+ /** Suppress the trailing `.optional()` — see assignedPkFieldNames. Set ONLY
510
+ * by the insert-shape emitters; the update/read shapes must stay optional. */
511
+ forceRequired = false,
512
+ ): Code {
456
513
  // `@dbColumnType: jsonb` on a scalar (legal only on field.string) is the
457
514
  // sanctioned "open JSON bag" escape hatch — a genuinely untyped JSON column
458
515
  // with no value-object to reference. Its Drizzle column is a bare `jsonb()`,
@@ -465,7 +522,7 @@ function zodFieldExpr(field: MetaField, owner?: MetaObject, ctx?: RenderContext)
465
522
  if (field.attr(FIELD_ATTR_DB_COLUMN_TYPE) === DB_COLUMN_TYPE_JSONB) {
466
523
  let base: Code = code`z.unknown()`;
467
524
  if (field.resolvedIsArray()) base = code`z.array(${base})`;
468
- return appendValidatorChain(base, field);
525
+ return appendValidatorChain(base, field, forceRequired);
469
526
  }
470
527
 
471
528
  // FIELD_SUBTYPE_OBJECT: emit z.array(<Ref>InsertSchema) / <Ref>InsertSchema
@@ -490,13 +547,13 @@ function zodFieldExpr(field: MetaField, owner?: MetaObject, ctx?: RenderContext)
490
547
  const refImp = imp(`${refName}InsertSchema@${moduleSpec}`);
491
548
  let base: Code = code`${refImp}`;
492
549
  if (field.resolvedIsArray()) base = code`z.array(${base})`;
493
- return appendValidatorChain(base, field);
550
+ return appendValidatorChain(base, field, forceRequired);
494
551
  }
495
552
  // No resolvable @objectRef — fall through to z.unknown(); downstream code
496
553
  // can still pass a value through but loses validation.
497
554
  let base: Code = code`z.unknown()`;
498
555
  if (field.resolvedIsArray()) base = code`z.array(${base})`;
499
- return appendValidatorChain(base, field);
556
+ return appendValidatorChain(base, field, forceRequired);
500
557
  }
501
558
 
502
559
  // field.map → z.record(z.string(), V): value is a VO's InsertSchema (@objectRef)
@@ -509,10 +566,10 @@ function zodFieldExpr(field: MetaField, owner?: MetaObject, ctx?: RenderContext)
509
566
  ? valueObjectModuleSpecifier(refName, ctx.packageOf, owner.package, ctx.outputLayout, ctx.extStyle)
510
567
  : `./${refName}.js`;
511
568
  const refImp = imp(`${refName}InsertSchema@${moduleSpec}`);
512
- return appendValidatorChain(code`z.record(z.string(), ${refImp})`, field);
569
+ return appendValidatorChain(code`z.record(z.string(), ${refImp})`, field, forceRequired);
513
570
  }
514
571
  const vt = field.attr(FIELD_ATTR_VALUE_TYPE);
515
- return appendValidatorChain(code`z.record(z.string(), ${zodScalarFor(typeof vt === "string" ? vt : "string")})`, field);
572
+ return appendValidatorChain(code`z.record(z.string(), ${zodScalarFor(typeof vt === "string" ? vt : "string")})`, field, forceRequired);
516
573
  }
517
574
 
518
575
  let baseStr: string;
@@ -573,7 +630,7 @@ function zodFieldExpr(field: MetaField, owner?: MetaObject, ctx?: RenderContext)
573
630
  const sharedConst = imp(`${constName}@${spec}`);
574
631
  let base: Code = code`${sharedConst}`;
575
632
  if (field.resolvedIsArray()) base = code`z.array(${base})`;
576
- return appendValidatorChain(base, field);
633
+ return appendValidatorChain(base, field, forceRequired);
577
634
  }
578
635
  }
579
636
  baseStr = zodEnumExpr(values);
@@ -612,7 +669,7 @@ function zodFieldExpr(field: MetaField, owner?: MetaObject, ctx?: RenderContext)
612
669
  }
613
670
 
614
671
  if (field.resolvedIsArray()) baseStr = `z.array(${baseStr})`;
615
- return appendValidatorChain(code`${baseStr}`, field);
672
+ return appendValidatorChain(code`${baseStr}`, field, forceRequired);
616
673
  }
617
674
 
618
675
  /** Mirrors the optional-or-not decision inside appendValidatorChain so the update-schema
@@ -635,7 +692,7 @@ const NUMERIC_FIELD_SUBTYPES = new Set<string>([
635
692
  * - numeric (scalar) → .min/.max = numeric value (validator.numeric)
636
693
  * - array (any element) → .min/.max = element count (validator.array)
637
694
  */
638
- function appendValidatorChain(base: Code, field: MetaField): Code {
695
+ function appendValidatorChain(base: Code, field: MetaField, forceRequired = false): Code {
639
696
  let isRequired = field.attr(FIELD_ATTR_REQUIRED) === true;
640
697
  let maxLen: number | undefined = field.attr(FIELD_ATTR_MAX_LENGTH) as number | undefined;
641
698
  let minLen: number | undefined;
@@ -709,6 +766,12 @@ function appendValidatorChain(base: Code, field: MetaField): Code {
709
766
  if (numMax !== undefined) chain = code`${chain}.max(${numMax})`;
710
767
  }
711
768
 
769
+ // An assigned PK is insert-REQUIRED whatever @required says — its Drizzle
770
+ // column has no default, so an optional value cannot typecheck into
771
+ // `.values()`. Only the caller knows this (the update/read shapes must stay
772
+ // optional), hence the explicit opt-in. See assignedPkFieldNames.
773
+ if (forceRequired) return chain;
774
+
712
775
  // Fields with DB-level defaults are optional in the InsertSchema: the caller
713
776
  // can omit them and the DB will fill in. Otherwise required-with-default
714
777
  // would force callers to repeat the default at every call site.