@wildo-ai/zod-decorators 1.1.9 → 1.1.11

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 (45) hide show
  1. package/dist/esm/index.d.ts +1 -0
  2. package/dist/esm/index.d.ts.map +1 -1
  3. package/dist/esm/index.js +1 -0
  4. package/dist/esm/index.js.map +1 -1
  5. package/dist/esm/testing/decorated-test-schemas.d.ts +1 -1
  6. package/dist/esm/zod-bigdecimal.d.ts +16 -7
  7. package/dist/esm/zod-bigdecimal.d.ts.map +1 -1
  8. package/dist/esm/zod-bigdecimal.js.map +1 -1
  9. package/dist/esm/zod-code.d.ts +17 -8
  10. package/dist/esm/zod-code.d.ts.map +1 -1
  11. package/dist/esm/zod-code.js.map +1 -1
  12. package/dist/esm/zod-decorators.types.d.ts +339 -39
  13. package/dist/esm/zod-decorators.types.d.ts.map +1 -1
  14. package/dist/esm/zod-decorators.types.js +155 -13
  15. package/dist/esm/zod-decorators.types.js.map +1 -1
  16. package/dist/esm/zod-document-sequence.d.ts +119 -0
  17. package/dist/esm/zod-document-sequence.d.ts.map +1 -0
  18. package/dist/esm/zod-document-sequence.js +212 -0
  19. package/dist/esm/zod-document-sequence.js.map +1 -0
  20. package/dist/esm/zod-duration.d.ts +22 -13
  21. package/dist/esm/zod-duration.d.ts.map +1 -1
  22. package/dist/esm/zod-duration.js.map +1 -1
  23. package/dist/esm/zod-file.d.ts +0 -4
  24. package/dist/esm/zod-file.d.ts.map +1 -1
  25. package/dist/esm/zod-file.js +9 -20
  26. package/dist/esm/zod-file.js.map +1 -1
  27. package/dist/esm/zod-money.d.ts +22 -13
  28. package/dist/esm/zod-money.d.ts.map +1 -1
  29. package/dist/esm/zod-money.js.map +1 -1
  30. package/dist/esm/zod-phone.d.ts +22 -14
  31. package/dist/esm/zod-phone.d.ts.map +1 -1
  32. package/dist/esm/zod-phone.js.map +1 -1
  33. package/dist/esm/zod-range.d.ts.map +1 -1
  34. package/dist/esm/zod-range.js +5 -1
  35. package/dist/esm/zod-range.js.map +1 -1
  36. package/dist/esm/zod-utils.shared.d.ts +108 -2
  37. package/dist/esm/zod-utils.shared.d.ts.map +1 -1
  38. package/dist/esm/zod-utils.shared.js +228 -10
  39. package/dist/esm/zod-utils.shared.js.map +1 -1
  40. package/dist/esm/zod.module-override.d.ts +179 -53
  41. package/dist/esm/zod.module-override.d.ts.map +1 -1
  42. package/dist/esm/zod.module-override.js +473 -56
  43. package/dist/esm/zod.module-override.js.map +1 -1
  44. package/dist/tsconfig.build.tsbuildinfo +1 -0
  45. package/package.json +1 -1
@@ -163,11 +163,74 @@ function withoutNestedObjectDefaults(schema) {
163
163
  const rebuilt = schema.clone({ ...def, shape: nextShape });
164
164
  return copySchemaMetadata(schema, rebuilt);
165
165
  }
166
+ /**
167
+ * What a BULK update form validates (#2581): `schema` with every child of a child-writable nested object made optional,
168
+ * at every level ({@link isChildWritableObject}).
169
+ *
170
+ * A bulk update writes a nested object child by child, and only the children the person changed, so a child its
171
+ * object REQUIRES is not required of the form: requiring it would make the person fill every required sibling to change
172
+ * one, and those siblings would then be written to every selected row. The server validates each child written against
173
+ * its own declaration, and builds a whole object only for a row that has none (refusing when the children written do
174
+ * not make one).
175
+ *
176
+ * Only nested children change: a top-level field keeps its own optionality, since the action's request decides what it
177
+ * cannot run without. An object that is not child-writable (a value type, one with its own refinement) and an array are
178
+ * left exactly as declared: they are written whole. Rebuilt like {@link withoutNestedObjectDefaults} does, with Zod's
179
+ * own `clone(def)` and every rebuilt schema carrying its source's metadata, so labels, edit modes and descriptions
180
+ * survive and the source is untouched. A schema that is not an object (a union) is returned as it is.
181
+ */
182
+ export function withOptionalNestedChildren(schema) {
183
+ if (getZodType(schema) !== 'object')
184
+ return schema;
185
+ return rebuildObjectShape(schema, (field) => withOptionalChildrenOf(field));
186
+ }
187
+ /** `field` with, if it is a child-writable object, each of its children optional (and so on down). */
188
+ function withOptionalChildrenOf(field) {
189
+ const type = getZodType(field);
190
+ if (type === 'optional' || type === 'nullable') {
191
+ const inner = getZodDefSchemaProperty(field, 'innerType');
192
+ if (!inner)
193
+ return field;
194
+ const rebuiltInner = withOptionalChildrenOf(inner);
195
+ if (rebuiltInner === inner)
196
+ return field;
197
+ return copySchemaMetadata(field, type === 'optional' ? rebuiltInner.optional() : rebuiltInner.nullable());
198
+ }
199
+ if (type !== 'object' || !isChildWritableObject(field))
200
+ return field;
201
+ return rebuildObjectShape(field, (child) => {
202
+ const rebuiltChild = withOptionalChildrenOf(child);
203
+ if (rebuiltChild.safeParse(undefined).success)
204
+ return rebuiltChild;
205
+ return copySchemaMetadata(child, rebuiltChild.optional());
206
+ });
207
+ }
208
+ /** `schema` (a Zod object) with each field passed through `rebuild`; the same schema when nothing changed. */
209
+ function rebuildObjectShape(schema, rebuild) {
210
+ const def = schema._zod.def;
211
+ const shape = schema.shape;
212
+ const nextShape = {};
213
+ let changed = false;
214
+ for (const [key, field] of Object.entries(shape)) {
215
+ const next = rebuild(field);
216
+ if (next !== field)
217
+ changed = true;
218
+ nextShape[key] = next;
219
+ }
220
+ if (!changed)
221
+ return schema;
222
+ const rebuilt = schema.clone({ ...def, shape: nextShape });
223
+ return copySchemaMetadata(schema, rebuilt);
224
+ }
166
225
  /** Carry decorator metadata from a source wrapper onto a rebuilt one; the source is never mutated. */
167
226
  function copySchemaMetadata(source, target) {
168
227
  const registered = z.globalRegistry.get(source);
169
- if (registered && !z.globalRegistry.has(target))
170
- z.globalRegistry.add(target, registered);
228
+ if (registered && !z.globalRegistry.has(target)) {
229
+ // Never the `id`: an id names ONE schema, and the rebuilt copy taking it would rename the source's export.
230
+ const { id: _id, ...inherited } = registered;
231
+ if (Object.keys(inherited).length > 0)
232
+ z.globalRegistry.add(target, inherited);
233
+ }
171
234
  const metadata = source[SCHEMA_METADATA];
172
235
  if (metadata) {
173
236
  const record = target;
@@ -779,7 +842,17 @@ export function getSchemaType(schema) {
779
842
  }
780
843
  }
781
844
  /*
782
- * A `z.date()` is a DAY unless it declares itself a MOMENT.
845
+ * Which CONTROL a `z.date()` gets: the day pair unless it declares itself a MOMENT. Three declarations, two
846
+ * classifications (#2180):
847
+ *
848
+ * - `z.date().isCalendarDate()` → `DATE`. A calendar day, held as UTC midnight and read by its UTC day, the
849
+ * same day for every reader. `isCalendarDateSchema` is what the formatters and the picker read for that.
850
+ * - bare `z.date()` → `DATE` as well. Edited and shown as a day, but the value is an INSTANT, read in the
851
+ * reader's zone: its picker shows the day it reads there, and a picked day is that day in that zone at the
852
+ * value's own time of day (its start, for an empty field) (#2454). Right for a timestamp shown by its day
853
+ * (`createdAt`); a field meaning a day should declare `isCalendarDate()`.
854
+ * - `z.date().dateTimeUI(...)` → `DATETIME`. A moment with a time of day. It cannot be combined with
855
+ * `isCalendarDate()`: applying the second one throws.
783
856
  *
784
857
  * In Wildo a datetime is Date-based — an ISO-8601 string is a wire format for one, never the
785
858
  * model — so `ZodDate` is the carrier for both "a day" (`dueDate`) and "a point in time"
@@ -1799,21 +1872,66 @@ function stripZodInternalKeys(shape) {
1799
1872
  * @returns the field's declared schema, or `undefined` when no variant declares it.
1800
1873
  */
1801
1874
  export function getObjectLikeFieldSchema(schema, fieldName) {
1875
+ // OWN keys only: a name like `constructor` or `__proto__` would otherwise answer with an Object.prototype member,
1876
+ // which is not a schema and makes every reader that inspects it throw.
1802
1877
  const directShape = getObjectShape(schema);
1803
1878
  if (directShape)
1804
- return directShape[fieldName];
1879
+ return Object.hasOwn(directShape, fieldName) ? directShape[fieldName] : undefined;
1805
1880
  const unwrapped = unwrapSchema(schema);
1806
1881
  if (!isZodUnion(unwrapped))
1807
1882
  return undefined;
1808
1883
  const options = getZodDefArrayProperty(unwrapped, 'options') ?? [];
1809
1884
  for (const option of options) {
1810
1885
  const optionShape = getObjectShape(option);
1811
- const fieldSchema = optionShape?.[fieldName];
1886
+ const fieldSchema = optionShape && Object.hasOwn(optionShape, fieldName) ? optionShape[fieldName] : undefined;
1812
1887
  if (fieldSchema)
1813
1888
  return fieldSchema;
1814
1889
  }
1815
1890
  return undefined;
1816
1891
  }
1892
+ /**
1893
+ * Whether a nested object's CHILDREN may be written one by one (`contact.city`), leaving each stored row's other
1894
+ * children as they are: what a bulk update does to part of a nested object (#2581).
1895
+ *
1896
+ * - a **plain object** (`FieldSchemaType.OBJECT`). A value type stored as an object (money, an address, a duration, a
1897
+ * file, a versioned object) is ONE value, written whole: `price.amount` alone would leave every row's own currency
1898
+ * under it.
1899
+ * - with **no refinement of its own** (`.refine()`, `.superRefine()`, `.check()`). A refinement judges the children
1900
+ * TOGETHER (`start` before `end`), and a write of one child never runs it, so such an object is written whole.
1901
+ *
1902
+ * Arrays are never child-writable: an index addresses a position, which is a different element on every row.
1903
+ * Wrappers (optional, nullable, default) are looked through.
1904
+ */
1905
+ export function isChildWritableObject(schema) {
1906
+ if (getSchemaType(schema) !== FieldSchemaType.OBJECT || getObjectShape(schema) === null)
1907
+ return false;
1908
+ const checks = unwrapSchema(schema)._zod?.def?.checks;
1909
+ return !(Array.isArray(checks) && checks.length > 0);
1910
+ }
1911
+ /**
1912
+ * The schema of ONE CHILD of a nested object, addressed by a dotted path (`contact.city`, `settings.theme.mode`).
1913
+ *
1914
+ * This is what a bulk update writes when it changes part of a nested object on many rows: each row keeps the children
1915
+ * it was not asked to change, which a whole-object write cannot do (it replaces the object on every row). So the path
1916
+ * walks only objects whose children can be written one by one ({@link isChildWritableObject}) at every level.
1917
+ *
1918
+ * @param schema - the object (or union of objects) whose top-level field the path starts from; wrappers are unwrapped.
1919
+ * @param path - `<field>.<child>[.<child>…]`. A top-level name is not a child path.
1920
+ * @returns the child's declared schema, or `undefined` when the path names no child of a child-writable object.
1921
+ */
1922
+ export function getNestedChildFieldSchema(schema, path) {
1923
+ const segments = path.split('.');
1924
+ if (segments.length < 2 || segments.some((segment) => segment.length === 0))
1925
+ return undefined;
1926
+ let current = getObjectLikeFieldSchema(schema, segments[0]);
1927
+ for (const segment of segments.slice(1)) {
1928
+ if (!current || !isChildWritableObject(current))
1929
+ return undefined;
1930
+ const shape = getObjectShape(current);
1931
+ current = shape && Object.hasOwn(shape, segment) ? shape[segment] : undefined;
1932
+ }
1933
+ return current;
1934
+ }
1817
1935
  export function getObjectShape(schema) {
1818
1936
  const unwrapped = unwrapSchema(schema);
1819
1937
  if (getZodType(unwrapped) !== 'object') {
@@ -2349,8 +2467,9 @@ export function getPortabilityProvenance(schema) {
2349
2467
  * or `undefined` when the field carries no declaration.
2350
2468
  *
2351
2469
  * Set via the fluent `.dataCategories(...)` method or `addDataCategories(schema, ...)`. Always an
2352
- * ARRAY, deduped and non-empty: the decorator chokepoint normalizes a single-value authoring form
2353
- * on the way in, so a caller never has to handle two shapes.
2470
+ * ARRAY, deduped and non-empty: the decorator chokepoint normalizes every authoring form on the way
2471
+ * in, so a caller never has to handle two shapes. The optional condition is read separately, by
2472
+ * {@link getPersonalDataCondition}.
2354
2473
  *
2355
2474
  * Reads through wrapper types (`getMetadataRecursive` walks outer → inner), so it observes the
2356
2475
  * declaration whether it was written before or after `.optional()` / `.nullable()` / `.nullish()` /
@@ -2364,9 +2483,30 @@ export function getPortabilityProvenance(schema) {
2364
2483
  * @wildo_source:part:start zod.decorators.data-categories facet:layer:shared facet:family:decorator facet:kind:api facet:audience:app-developer
2365
2484
  */
2366
2485
  export function getDataCategories(schema) {
2367
- const metadata = getMetadataRecursive(schema);
2368
- const value = metadata?.[ZodDecorator.DATA_CATEGORIES];
2369
- return Array.isArray(value) && value.length > 0 ? value : undefined;
2486
+ const categories = getMetadataRecursive(schema)?.[ZodDecorator.DATA_CATEGORIES]?.categories;
2487
+ return Array.isArray(categories) && categories.length > 0 ? categories : undefined;
2488
+ }
2489
+ /**
2490
+ * Under WHICH CONDITION is this field personal data at all? Returns the `PersonalDataCondition`
2491
+ * value declared through `.dataCategories(categories, { personalOnlyWhen })` (as an opaque string),
2492
+ * or `undefined` when the field is personal data on every row — or declares no category (#2119).
2493
+ *
2494
+ * Read it beside {@link getDataCategories}, never instead of it: a conditional field still HOLDS
2495
+ * those categories, and every reader deciding whether a field is personal (erasure, export, the
2496
+ * stance checks) keeps counting it. The condition matters only to a statement made about the data,
2497
+ * such as a privacy notice saying "where the customer is an individual".
2498
+ */
2499
+ export function getPersonalDataCondition(schema) {
2500
+ const condition = getMetadataRecursive(schema)?.[ZodDecorator.DATA_CATEGORIES]?.personalOnlyWhen;
2501
+ return typeof condition === 'string' && condition.length > 0 ? condition : undefined;
2502
+ }
2503
+ /**
2504
+ * Does this field name a party OTHER than the one its row is about, as declared through
2505
+ * `.dataCategories(categories, { aboutAnotherParty: true })` (#2535)? `false` for every field that does not say so.
2506
+ * WHOSE data the field is, not WHETHER it is personal: read it beside {@link getDataCategories}, never instead of it.
2507
+ */
2508
+ export function isPersonalDataAboutAnotherParty(schema) {
2509
+ return getMetadataRecursive(schema)?.[ZodDecorator.DATA_CATEGORIES]?.aboutAnotherParty === true;
2370
2510
  }
2371
2511
  /** @wildo_source:part:end zod.decorators.data-categories */
2372
2512
  /**
@@ -2391,6 +2531,48 @@ export function isAuditEvidenceField(schema) {
2391
2531
  const metadata = getMetadataRecursive(schema);
2392
2532
  return metadata?.[ZodDecorator.IS_AUDIT_EVIDENCE] === true;
2393
2533
  }
2534
+ /**
2535
+ * Does this row field hold what the most recent action said about itself (the reason given for a decision),
2536
+ * rather than state the next action starts from? Set via the fluent `.isLastActionRecord()`. Reads through
2537
+ * wrapper types, so the marker is seen whether it was declared before or after `.optional()` / `.default()`.
2538
+ */
2539
+ export function isLastActionRecordField(schema) {
2540
+ const metadata = getMetadataRecursive(schema);
2541
+ return metadata?.[ZodDecorator.IS_LAST_ACTION_RECORD] === true;
2542
+ }
2543
+ /**
2544
+ * Does this row field record which person decided something on the row (`.isDecider()`, #2906)? Reads through wrapper
2545
+ * types, so the marker is seen whether it was declared before or after `.optional()` / `.default()`.
2546
+ */
2547
+ export function isDeciderField(schema) {
2548
+ const metadata = getMetadataRecursive(schema);
2549
+ return metadata?.[ZodDecorator.IS_DECIDER] === true;
2550
+ }
2551
+ /** The names of a row schema's decider fields (`.isDecider()`), across every variant of a polymorphic row. */
2552
+ export function deciderFieldNames(rowSchema) {
2553
+ if (!rowSchema)
2554
+ return new Set();
2555
+ const names = new Set();
2556
+ for (const [fieldName, fieldSchema] of Object.entries(getMergedObjectOrUnionShape(rowSchema))) {
2557
+ if (isDeciderField(fieldSchema))
2558
+ names.add(fieldName);
2559
+ }
2560
+ return names;
2561
+ }
2562
+ /**
2563
+ * The names of a row schema's last-action-record fields (`.isLastActionRecord()`), across every variant of a
2564
+ * polymorphic (discriminated-union) row. What a form that starts from the row must leave out of its seed.
2565
+ */
2566
+ export function lastActionRecordFieldNames(rowSchema) {
2567
+ if (!rowSchema)
2568
+ return new Set();
2569
+ const names = new Set();
2570
+ for (const [fieldName, fieldSchema] of Object.entries(getMergedObjectOrUnionShape(rowSchema))) {
2571
+ if (isLastActionRecordField(fieldSchema))
2572
+ names.add(fieldName);
2573
+ }
2574
+ return names;
2575
+ }
2394
2576
  function getImpersonalizeParams(schema) {
2395
2577
  const metadata = getMetadataRecursive(schema);
2396
2578
  const value = metadata?.[ZodDecorator.IMPERSONALIZE_WITH];
@@ -2620,6 +2802,14 @@ export function isBusinessKeyField(schema) {
2620
2802
  const metadata = getMetadataRecursive(schema);
2621
2803
  return metadata?.[ZodDecorator.IS_BUSINESS_KEY] === true;
2622
2804
  }
2805
+ /**
2806
+ * The `documentSequence` parameters a field declares (#2509), through any wrapper, or `undefined`. What the backend's
2807
+ * allocator reads to number a row's document field.
2808
+ */
2809
+ export function getDocumentSequenceParams(schema) {
2810
+ const metadata = getMetadataRecursive(schema);
2811
+ return metadata?.[ZodDecorator.DOCUMENT_SEQUENCE];
2812
+ }
2623
2813
  /**
2624
2814
  * Read the composite business key declared on an object schema via
2625
2815
  * `businessKeySet([...])` (object-level decorator). Returns the ordered
@@ -2646,6 +2836,15 @@ export function getBusinessKeySetParams(schema) {
2646
2836
  const params = metadata?.[ZodDecorator.OBJECT_BUSINESS_KEY_SET];
2647
2837
  return params?.fields ? params : undefined;
2648
2838
  }
2839
+ /**
2840
+ * The target set an object declares with `isResourceReference({ targetSet })` (#2471): the object names one row of a
2841
+ * resource whose type is chosen at run time from that set. `undefined` when the object is not a resource reference.
2842
+ * Read through wrappers, so `WorkUnitRefSchema.optional()` answers like `WorkUnitRefSchema`.
2843
+ */
2844
+ export function getResourceReferenceTargetSet(schema) {
2845
+ const params = getMetadataRecursive(schema)?.[ZodDecorator.RESOURCE_REFERENCE];
2846
+ return typeof params?.targetSet === 'string' && params.targetSet.length > 0 ? params.targetSet : undefined;
2847
+ }
2649
2848
  /**
2650
2849
  * Convenience predicate: does the schema declare any business key
2651
2850
  * identity (single field-level marker OR composite object-level set)?
@@ -3555,6 +3754,17 @@ export function isEligibleForLayoutDefaultInlineEdit(schema) {
3555
3754
  return false;
3556
3755
  return true;
3557
3756
  }
3757
+ /**
3758
+ * Is this field editable in place because it DECLARES so (`inlineEditable`, true or with params)?
3759
+ *
3760
+ * Its own declaration, the counterpart of {@link isEligibleForLayoutDefaultInlineEdit}, with the one clause a
3761
+ * declaration cannot override: a field `.excludeFromUpdate()` strips is never offered (#2322). The write would
3762
+ * persist nothing while the control reported success, so the rule belongs to the engine rather than to every author
3763
+ * who combines the two decorators.
3764
+ */
3765
+ export function isInlineEditableByDeclaration(schema) {
3766
+ return hasInlineEditableDecorator(schema) && !isExcludedFromUpdate(schema);
3767
+ }
3558
3768
  /**
3559
3769
  * Get the resolved inline editable params from `{primitive}UI.display.inlineEditable`.
3560
3770
  * Handles both `true` shorthand and full object config.
@@ -4253,6 +4463,14 @@ export function getDateUI(schema) {
4253
4463
  export function hasDateUI(schema) {
4254
4464
  return hasDecorator(schema, ZodDecorator.DATE_UI);
4255
4465
  }
4466
+ /**
4467
+ * Whether a date field holds a CALENDAR DAY (`z.date().isCalendarDate()`) rather than an instant: stored as
4468
+ * UTC midnight of the day, shown and edited by that day whatever the reader's time zone. See
4469
+ * `ZodDecorator.IS_CALENDAR_DATE`.
4470
+ */
4471
+ export function isCalendarDateSchema(schema) {
4472
+ return schema !== undefined && hasDecorator(schema, ZodDecorator.IS_CALENDAR_DATE);
4473
+ }
4256
4474
  export function getDateTimeUI(schema) {
4257
4475
  return getCustomFieldMetadata(schema)[ZodDecorator.DATETIME_UI];
4258
4476
  }