@wildo-ai/zod-decorators 1.1.11 → 1.1.13

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 (78) hide show
  1. package/dist/esm/commons.d.ts.map +1 -1
  2. package/dist/esm/commons.js.map +1 -1
  3. package/dist/esm/currencies.shared.schemas.d.ts.map +1 -1
  4. package/dist/esm/currencies.shared.schemas.js.map +1 -1
  5. package/dist/esm/files/agent-file-formats.d.ts.map +1 -1
  6. package/dist/esm/files/agent-file-formats.js.map +1 -1
  7. package/dist/esm/files/extension-nature-map.d.ts.map +1 -1
  8. package/dist/esm/files/extension-nature-map.js.map +1 -1
  9. package/dist/esm/files/file-extensions.enum.d.ts.map +1 -1
  10. package/dist/esm/files/file-extensions.enum.js.map +1 -1
  11. package/dist/esm/files/mime-registry.d.ts.map +1 -1
  12. package/dist/esm/files/mime-registry.js.map +1 -1
  13. package/dist/esm/files/unified-registry.d.ts.map +1 -1
  14. package/dist/esm/files/unified-registry.js.map +1 -1
  15. package/dist/esm/index.d.ts +2 -0
  16. package/dist/esm/index.d.ts.map +1 -1
  17. package/dist/esm/index.js +2 -0
  18. package/dist/esm/index.js.map +1 -1
  19. package/dist/esm/install-bigint-json-encoding.d.ts +1 -1
  20. package/dist/esm/install-bigint-json-encoding.d.ts.map +1 -1
  21. package/dist/esm/install-bigint-json-encoding.js +1 -1
  22. package/dist/esm/install-bigint-json-encoding.js.map +1 -1
  23. package/dist/esm/testing/decorated-test-schemas.d.ts +7 -7
  24. package/dist/esm/testing/decorated-test-schemas.d.ts.map +1 -1
  25. package/dist/esm/testing/decorated-test-schemas.js.map +1 -1
  26. package/dist/esm/testing.d.ts.map +1 -1
  27. package/dist/esm/testing.js.map +1 -1
  28. package/dist/esm/zod-address.d.ts.map +1 -1
  29. package/dist/esm/zod-address.js.map +1 -1
  30. package/dist/esm/zod-bigdecimal.d.ts +4 -4
  31. package/dist/esm/zod-bigdecimal.d.ts.map +1 -1
  32. package/dist/esm/zod-bigdecimal.js.map +1 -1
  33. package/dist/esm/zod-code.d.ts +4 -4
  34. package/dist/esm/zod-code.d.ts.map +1 -1
  35. package/dist/esm/zod-code.js.map +1 -1
  36. package/dist/esm/zod-color.d.ts.map +1 -1
  37. package/dist/esm/zod-color.js.map +1 -1
  38. package/dist/esm/zod-decorators.types.d.ts +116 -9
  39. package/dist/esm/zod-decorators.types.d.ts.map +1 -1
  40. package/dist/esm/zod-decorators.types.js +40 -1
  41. package/dist/esm/zod-decorators.types.js.map +1 -1
  42. package/dist/esm/zod-document-sequence.d.ts.map +1 -1
  43. package/dist/esm/zod-document-sequence.js.map +1 -1
  44. package/dist/esm/zod-duration.d.ts +6 -6
  45. package/dist/esm/zod-duration.d.ts.map +1 -1
  46. package/dist/esm/zod-duration.js.map +1 -1
  47. package/dist/esm/zod-file.d.ts +5 -4
  48. package/dist/esm/zod-file.d.ts.map +1 -1
  49. package/dist/esm/zod-file.js +1 -1
  50. package/dist/esm/zod-file.js.map +1 -1
  51. package/dist/esm/zod-money.d.ts +4 -4
  52. package/dist/esm/zod-money.d.ts.map +1 -1
  53. package/dist/esm/zod-money.js.map +1 -1
  54. package/dist/esm/zod-phone.d.ts +4 -4
  55. package/dist/esm/zod-phone.d.ts.map +1 -1
  56. package/dist/esm/zod-phone.js.map +1 -1
  57. package/dist/esm/zod-range.d.ts.map +1 -1
  58. package/dist/esm/zod-range.js.map +1 -1
  59. package/dist/esm/zod-utils.shared.d.ts +47 -12
  60. package/dist/esm/zod-utils.shared.d.ts.map +1 -1
  61. package/dist/esm/zod-utils.shared.js +143 -40
  62. package/dist/esm/zod-utils.shared.js.map +1 -1
  63. package/dist/esm/zod-versioned.d.ts.map +1 -1
  64. package/dist/esm/zod-versioned.js.map +1 -1
  65. package/dist/esm/zod-wire-date.d.ts +107 -0
  66. package/dist/esm/zod-wire-date.d.ts.map +1 -0
  67. package/dist/esm/zod-wire-date.js +162 -0
  68. package/dist/esm/zod-wire-date.js.map +1 -0
  69. package/dist/esm/zod-year-month.d.ts +76 -0
  70. package/dist/esm/zod-year-month.d.ts.map +1 -0
  71. package/dist/esm/zod-year-month.js +115 -0
  72. package/dist/esm/zod-year-month.js.map +1 -0
  73. package/dist/esm/zod.module-override.d.ts +139 -156
  74. package/dist/esm/zod.module-override.d.ts.map +1 -1
  75. package/dist/esm/zod.module-override.js +176 -6
  76. package/dist/esm/zod.module-override.js.map +1 -1
  77. package/dist/tsconfig.build.tsbuildinfo +1 -1
  78. package/package.json +5 -5
@@ -116,8 +116,9 @@ export function unwrapSchema(schema) {
116
116
  * - nested PLAIN object fields, recursively, through `optional` / `nullable` wrappers only. An object
117
117
  * nested inside a union, discriminated union, record, intersection, pipe/transform, `readonly` or `lazy`
118
118
  * is NOT peeled and keeps re-materialising its defaults — narrower than it could be, never wrong;
119
- * - an object carrying its own refinement is NOT peeled: a `.refine()` / `.superRefine()` may read a
120
- * defaulted key, and would see `undefined` (or throw) once that default stopped applying;
119
+ * - an object carrying its own refinement is NOT peeled, and neither is one under a wrapper or a default
120
+ * that carries one: a `.refine()` / `.superRefine()` may read a defaulted key, and would see `undefined`
121
+ * (or throw) once that default stopped applying (#3342);
121
122
  * - a nested key WITH a default becomes optional without it; a nested key WITHOUT one keeps its declared
122
123
  * optionality — a replaced nested object must still carry what it requires;
123
124
  * - ARRAYS are left alone: an array element is sent whole, and its defaults describe a new element;
@@ -127,6 +128,8 @@ export function unwrapSchema(schema) {
127
128
  * never mutated, so the source schema is untouched.
128
129
  */
129
130
  function withoutNestedObjectDefaults(schema) {
131
+ if (ownChecks(schema).length > 0)
132
+ return schema;
130
133
  const type = getZodType(schema);
131
134
  if (type === 'optional' || type === 'nullable') {
132
135
  const inner = getZodDefSchemaProperty(schema, 'innerType');
@@ -140,16 +143,13 @@ function withoutNestedObjectDefaults(schema) {
140
143
  if (type !== 'object')
141
144
  return schema;
142
145
  const def = schema._zod.def;
143
- if (Array.isArray(def.checks) && def.checks.length > 0)
144
- return schema;
145
146
  const shape = schema.shape;
146
147
  const nextShape = {};
147
148
  let changed = false;
148
149
  for (const [key, field] of Object.entries(shape)) {
149
150
  let next;
150
151
  if (isZodDefault(field)) {
151
- const peeled = getZodDefSchemaProperty(field, 'innerType');
152
- next = peeled ? copySchemaMetadata(field, withoutNestedObjectDefaults(peeled).optional()) : field;
152
+ next = withoutDefault(field);
153
153
  }
154
154
  else {
155
155
  next = withoutNestedObjectDefaults(field);
@@ -163,6 +163,37 @@ function withoutNestedObjectDefaults(schema) {
163
163
  const rebuilt = schema.clone({ ...def, shape: nextShape });
164
164
  return copySchemaMetadata(schema, rebuilt);
165
165
  }
166
+ /** The checks declared on this schema itself, which a rebuilt wrapper does not inherit. */
167
+ function ownChecks(schema) {
168
+ return getZodDefProperty(schema, 'checks') ?? [];
169
+ }
170
+ /**
171
+ * `check`, run only on what the request sent (#3342). A check declared on a `.default()` always had a value to read, so
172
+ * moved onto the optional that replaces the default it would meet `undefined` on every omitted field, and it has nothing
173
+ * to say about a field the request did not send. Zod's own `when` is what keeps its abort rule beside this.
174
+ */
175
+ function checkOnWhatWasSent(check) {
176
+ const { when } = check._zod.def;
177
+ const sent = z.check((payload) => (payload.value === undefined ? undefined : check._zod.check(payload)));
178
+ if (when)
179
+ sent._zod.def.when = (payload) => payload.value !== undefined && when(payload);
180
+ return sent;
181
+ }
182
+ /**
183
+ * A `.default()` field for a PATCH: optional, with nothing to re-materialise, and still held to the checks declared on the
184
+ * default (#3342). Those checks are the wrapper's own, so they are carried over by hand. With a check present, the fields
185
+ * under it keep their defaults, so it reads the value it was written against: the same rule as a refined object. The
186
+ * inner schema stays shared and is never mutated.
187
+ */
188
+ function withoutDefault(defaulted) {
189
+ const inner = getZodDefSchemaProperty(defaulted, 'innerType');
190
+ if (!inner)
191
+ return defaulted;
192
+ const checks = ownChecks(defaulted);
193
+ if (checks.length === 0)
194
+ return copySchemaMetadata(defaulted, withoutNestedObjectDefaults(inner).optional());
195
+ return copySchemaMetadata(defaulted, inner.optional().check(...checks.map(checkOnWhatWasSent)));
196
+ }
166
197
  /**
167
198
  * What a BULK update form validates (#2581): `schema` with every child of a child-writable nested object made optional,
168
199
  * at every level ({@link isChildWritableObject}).
@@ -186,6 +217,8 @@ export function withOptionalNestedChildren(schema) {
186
217
  }
187
218
  /** `field` with, if it is a child-writable object, each of its children optional (and so on down). */
188
219
  function withOptionalChildrenOf(field) {
220
+ if (ownChecks(field).length > 0)
221
+ return field;
189
222
  const type = getZodType(field);
190
223
  if (type === 'optional' || type === 'nullable') {
191
224
  const inner = getZodDefSchemaProperty(field, 'innerType');
@@ -267,10 +300,11 @@ function copySchemaMetadata(source, target) {
267
300
  * optional and does not already accept null.
268
301
  * - Default-wrapped fields: the outer `ZodDefault` is peeled, the inner schema
269
302
  * (keeping `.int()` / `.min()` / refinements) is made optional, and any
270
- * decorator metadata attached to the default wrapper (e.g.
303
+ * decorator metadata or description on the default wrapper (e.g.
271
304
  * `.default(0).isSummaryField()`) is carried onto the resulting optional so
272
- * `getMetadataRecursive` is unchanged. The inner schema is SHARED, never
273
- * mutated, so the source schema is left intact.
305
+ * `getMetadataRecursive` is unchanged. A check declared ON the default still holds, on what the
306
+ * request sent (#3342). The inner schema is SHARED, never mutated, so the source schema is left
307
+ * intact.
274
308
  *
275
309
  * Defaults remain correct for CREATE (a new row gets them) — this transform is
276
310
  * for the UPDATE DTO only.
@@ -281,23 +315,11 @@ export function toPatchOptionalField(fieldSchema) {
281
315
  const nestedPatched = withoutNestedObjectDefaults(fieldSchema);
282
316
  return admitsNullClear ? nestedPatched.nullable().optional() : nestedPatched.optional();
283
317
  }
284
- const rawInnerType = getZodDefSchemaProperty(fieldSchema, 'innerType');
285
- if (!rawInnerType)
318
+ if (!getZodDefSchemaProperty(fieldSchema, 'innerType'))
286
319
  return fieldSchema.optional();
287
- const innerType = withoutNestedObjectDefaults(rawInnerType);
288
320
  // A defaulted field is never null-clearable (see `fieldAdmitsAddedNullClear`), so the peeled
289
321
  // inner is made optional WITHOUT a nullable wrapper.
290
- const optionalInner = innerType.optional();
291
- const defaultWrapperMetadata = fieldSchema[SCHEMA_METADATA];
292
- if (defaultWrapperMetadata) {
293
- const optionalRecord = optionalInner;
294
- const existing = optionalRecord[SCHEMA_METADATA] ?? {};
295
- optionalRecord[SCHEMA_METADATA] = {
296
- ...existing,
297
- ...defaultWrapperMetadata,
298
- };
299
- }
300
- return optionalInner;
322
+ return withoutDefault(fieldSchema);
301
323
  }
302
324
  /**
303
325
  * The value an edit control should contribute to form state, once a "the user emptied this field"
@@ -608,6 +630,9 @@ export function withArrayElement(arraySchema, element) {
608
630
  return arraySchema;
609
631
  }
610
632
  const rebuilt = arraySchema.clone({ ...def, element });
633
+ const decoratorMetadata = arraySchema[SCHEMA_METADATA];
634
+ if (decoratorMetadata !== undefined)
635
+ rebuilt[SCHEMA_METADATA] = decoratorMetadata;
611
636
  const registered = z.globalRegistry.get(arraySchema);
612
637
  if (registered !== undefined && !z.globalRegistry.has(rebuilt)) {
613
638
  const { id: _id, ...carried } = registered;
@@ -1270,6 +1295,37 @@ export function getUnionObjectMembers(schema) {
1270
1295
  const options = (getZodDefArrayProperty(schema, 'options') ?? []);
1271
1296
  return options.filter((option) => isZodTypeAny(option) && isZodObject(option));
1272
1297
  }
1298
+ /**
1299
+ * The value(s) ONE member of a discriminated union holds for its discriminator, read through every wrapper (#2076).
1300
+ *
1301
+ * A member's discriminator is a literal (`z.literal('recurring')`, whose values Zod 4 keeps in an ARRAY under `values`)
1302
+ * or, for the base values a schema family groups into one member, an enum. It is no longer always BARE: the member that
1303
+ * holds the value the base field declares as its `.default()` carries `literal.optional().default(value)`, the one shape
1304
+ * a Zod 4 discriminated union can route an omitted key by. A reader of the bare `def.values` would miss that member, so
1305
+ * a reader of a DECLARED union's member values goes through here. {@link getUnionDiscriminatorKey}'s inference over a
1306
+ * PLAIN union deliberately does not: it asks whether a key discriminates at all, and only a bare literal proves that.
1307
+ *
1308
+ * Non-string literal values (a numeric discriminator) are kept as they are; an enum contributes its string values.
1309
+ */
1310
+ export function getDiscriminatorMemberValues(memberDiscriminator) {
1311
+ if (!memberDiscriminator)
1312
+ return [];
1313
+ const unwrapped = unwrapSchema(memberDiscriminator);
1314
+ if (getZodType(unwrapped) === 'literal') {
1315
+ return (getZodDefArrayProperty(unwrapped, 'values') ?? [getZodDefProperty(unwrapped, 'value')])
1316
+ .filter((value) => value !== undefined && value !== null);
1317
+ }
1318
+ return getEnumValues(unwrapped);
1319
+ }
1320
+ /**
1321
+ * Whether a member of a declared discriminated union is the one Zod routes a value WITHOUT the discriminator to (#2076):
1322
+ * its discriminator accepts the key's absence, which Zod 4 reads from the member field's `_zod.values` containing
1323
+ * `undefined` (an `.optional()` under the declared `.default()`).
1324
+ */
1325
+ function memberTakesOmittedDiscriminator(memberDiscriminator) {
1326
+ const values = memberDiscriminator?._zod?.values;
1327
+ return values instanceof Set && values.has(undefined);
1328
+ }
1273
1329
  /**
1274
1330
  * Resolve the ONE variant of a discriminated union that a given value belongs to.
1275
1331
  *
@@ -1282,7 +1338,8 @@ export function getUnionObjectMembers(schema) {
1282
1338
  * The discriminator is {@link getUnionDiscriminatorKey}'s: declared, or carried structurally by a plain
1283
1339
  * `z.union` (#1022 — a versioned record's items). For such a plain union a value carrying NO discriminator
1284
1340
  * belongs to the one strict member that omits the key, when there is one; for a declared discriminated union
1285
- * it belongs to none.
1341
+ * it belongs to the member Zod itself routes it to — the one whose discriminator takes the declared default
1342
+ * (#2076) — and to none when no member does.
1286
1343
  *
1287
1344
  * Returns `null` when the schema is not a discriminated union, when the value carries no
1288
1345
  * discriminator its union can place, or when no variant matches it — all three are "this caller cannot
@@ -1297,8 +1354,9 @@ export function resolveDiscriminatedUnionVariant(schema, value) {
1297
1354
  const discriminatorValue = value[discriminator];
1298
1355
  const options = (getZodDefArrayProperty(schema, 'options') ?? []);
1299
1356
  if (discriminatorValue === undefined) {
1300
- if (getZodDefStringProperty(schema, 'discriminator'))
1301
- return null;
1357
+ if (getZodDefStringProperty(schema, 'discriminator')) {
1358
+ return options.find((option) => isZodObject(option) && memberTakesOmittedDiscriminator(getZodObjectShape(option)[discriminator])) ?? null;
1359
+ }
1302
1360
  return options.find((option) => isZodObject(option) && getZodObjectShape(option)[discriminator] === undefined) ?? null;
1303
1361
  }
1304
1362
  for (const option of options) {
@@ -1309,12 +1367,11 @@ export function resolveDiscriminatedUnionVariant(schema, value) {
1309
1367
  if (!discriminatorSchema)
1310
1368
  continue;
1311
1369
  /*
1312
- * A variant's discriminator is a LITERAL (`z.literal(ICPArchetype.LEGAL_ENTITY)`), whose value
1313
- * Zod 4 stores as an array under `values` — the same shape `getEnumValues` reads for the
1314
- * literal case. Comparing against that is what makes the match exact rather than structural.
1370
+ * A variant's discriminator is a LITERAL (`z.literal(ICPArchetype.LEGAL_ENTITY)`), or the enum of a family's base
1371
+ * values, possibly under the declared default's wrappers (#2076). Comparing the exact values
1372
+ * `getDiscriminatorMemberValues` reads is what makes the match exact rather than structural.
1315
1373
  */
1316
- const literalValues = getZodDefArrayProperty(discriminatorSchema, 'values') ?? [];
1317
- if (literalValues.includes(discriminatorValue))
1374
+ if (getDiscriminatorMemberValues(discriminatorSchema).includes(discriminatorValue))
1318
1375
  return option;
1319
1376
  }
1320
1377
  return null;
@@ -1896,17 +1953,32 @@ export function getObjectLikeFieldSchema(schema, fieldName) {
1896
1953
  * - a **plain object** (`FieldSchemaType.OBJECT`). A value type stored as an object (money, an address, a duration, a
1897
1954
  * file, a versioned object) is ONE value, written whole: `price.amount` alone would leave every row's own currency
1898
1955
  * 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.
1956
+ * - with **no refinement** (`.refine()`, `.superRefine()`, `.check()`) on the object or on any wrapper around it. A
1957
+ * refinement judges the children TOGETHER (`start` before `end`), and a write of one child never runs it, so such an
1958
+ * object is written whole. A check on the optional, nullable or default around the object counts too (#3342): the form,
1959
+ * the write units and the server all ask this, so a wrapper's check cannot be required by one and skipped by another.
1901
1960
  *
1902
1961
  * 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.
1962
+ * Wrappers (optional, nullable, default, readonly, catch, and a pipe's input) are looked through, as `unwrapSchema` does.
1904
1963
  */
1905
1964
  export function isChildWritableObject(schema) {
1906
1965
  if (getSchemaType(schema) !== FieldSchemaType.OBJECT || getObjectShape(schema) === null)
1907
1966
  return false;
1908
- const checks = unwrapSchema(schema)._zod?.def?.checks;
1909
- return !(Array.isArray(checks) && checks.length > 0);
1967
+ // The wrappers `unwrapSchema` looks through, so every layer between the field and its object is asked.
1968
+ let current = schema;
1969
+ while (current) {
1970
+ if (ownChecks(current).length > 0)
1971
+ return false;
1972
+ const type = getZodType(current);
1973
+ if (type === 'pipe')
1974
+ current = getZodDefSchemaProperty(current, 'in');
1975
+ else if (type === 'optional' || type === 'nullable' || type === 'default' || type === 'readonly' || type === 'catch') {
1976
+ current = getZodDefSchemaProperty(current, 'innerType');
1977
+ }
1978
+ else
1979
+ return true;
1980
+ }
1981
+ return true;
1910
1982
  }
1911
1983
  /**
1912
1984
  * The schema of ONE CHILD of a nested object, addressed by a dotted path (`contact.city`, `settings.theme.mode`).
@@ -2416,6 +2488,13 @@ export function getBackendOnlyParams(schema) {
2416
2488
  return {}; // Default params when decorator is used without params
2417
2489
  return value;
2418
2490
  }
2491
+ /**
2492
+ * Whether a backend-only field declares that its value is the subject's own data, disclosed in their access export
2493
+ * (`isBackendOnly({ disclosedToSubject: true })`, #2958). `false` for a field that is not backend-only.
2494
+ */
2495
+ export function isBackendOnlyFieldDisclosedToSubject(schema) {
2496
+ return getBackendOnlyParams(schema)?.disclosedToSubject === true;
2497
+ }
2419
2498
  /**
2420
2499
  * Check if a schema field has the isWriteOnlySecret decorator.
2421
2500
  */
@@ -2548,6 +2627,14 @@ export function isDeciderField(schema) {
2548
2627
  const metadata = getMetadataRecursive(schema);
2549
2628
  return metadata?.[ZodDecorator.IS_DECIDER] === true;
2550
2629
  }
2630
+ /**
2631
+ * Are this field's values roles the writer grants (`.isRoleGrant()`, #3248)? Reads through wrapper types, so the marker
2632
+ * is seen whether it was declared before or after `.optional()` / `.default()`.
2633
+ */
2634
+ export function isRoleGrantField(schema) {
2635
+ const metadata = getMetadataRecursive(schema);
2636
+ return metadata?.[ZodDecorator.IS_ROLE_GRANT] === true;
2637
+ }
2551
2638
  /** The names of a row schema's decider fields (`.isDecider()`), across every variant of a polymorphic row. */
2552
2639
  export function deciderFieldNames(rowSchema) {
2553
2640
  if (!rowSchema)
@@ -2795,8 +2882,7 @@ export function isPrimaryKeyField(schema) {
2795
2882
  *
2796
2883
  * A business key is the **declaration-side identity** used by the data-seeding
2797
2884
  * subsystem to upsert / upgrade / sync seeded rows independently from the
2798
- * random `_id` (`isPrimaryKey`). See `.cursor/plans/seeding-first-class.md`
2799
- * §3.3 for the semantic contract.
2885
+ * random `_id` (`isPrimaryKey`).
2800
2886
  */
2801
2887
  export function isBusinessKeyField(schema) {
2802
2888
  const metadata = getMetadataRecursive(schema);
@@ -2816,8 +2902,7 @@ export function getDocumentSequenceParams(schema) {
2816
2902
  * list of field names, or `undefined` if no composite key is declared.
2817
2903
  *
2818
2904
  * The composite key is the schema-level analog of `isBusinessKey` — used
2819
- * when identity is `(orgId, templateKey)` rather than a single field. See
2820
- * `.cursor/plans/seeding-first-class.md` §3.3.
2905
+ * when identity is `(orgId, templateKey)` rather than a single field.
2821
2906
  */
2822
2907
  export function getBusinessKeySet(schema) {
2823
2908
  return getBusinessKeySetParams(schema)?.fields;
@@ -2836,6 +2921,24 @@ export function getBusinessKeySetParams(schema) {
2836
2921
  const params = metadata?.[ZodDecorator.OBJECT_BUSINESS_KEY_SET];
2837
2922
  return params?.fields ? params : undefined;
2838
2923
  }
2924
+ /**
2925
+ * Every NON-unique compound index an object schema declares with `compoundIndex([...])` (#3142), in declaration order,
2926
+ * or `[]`. Each entry's `fields` are in index order. The ONE reader both storage adapters ask (the MongoDB convertor
2927
+ * and the PostgreSQL planner), so they cannot disagree about which indexes a resource has.
2928
+ *
2929
+ * Only the indexes whose fields this object still HAS: Zod's `.omit()`, `.pick()` and `.extend()` copy the outer
2930
+ * metadata wholesale, so an object derived from one that declared an index would otherwise keep an index naming a field
2931
+ * it dropped, and its storage would refuse to build. An object that lost a field lost the index over it.
2932
+ */
2933
+ export function getCompoundIndexes(schema) {
2934
+ const declared = getMetadataRecursive(schema)?.[ZodDecorator.OBJECT_COMPOUND_INDEX];
2935
+ if (!Array.isArray(declared))
2936
+ return [];
2937
+ const shape = getZodObjectShape(schema);
2938
+ return declared
2939
+ .filter((index) => index.fields.every((field) => Object.hasOwn(shape, field)))
2940
+ .map((index) => ({ fields: [...index.fields] }));
2941
+ }
2839
2942
  /**
2840
2943
  * The target set an object declares with `isResourceReference({ targetSet })` (#2471): the object names one row of a
2841
2944
  * resource whose type is chosen at run time from that set. `undefined` when the object is not a resource reference.