@wildo-ai/zod-decorators 1.1.10 → 1.1.12

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 (52) hide show
  1. package/dist/esm/index.d.ts +2 -0
  2. package/dist/esm/index.d.ts.map +1 -1
  3. package/dist/esm/index.js +2 -0
  4. package/dist/esm/index.js.map +1 -1
  5. package/dist/esm/install-bigint-json-encoding.d.ts +1 -1
  6. package/dist/esm/install-bigint-json-encoding.js +1 -1
  7. package/dist/esm/install-bigint-json-encoding.js.map +1 -1
  8. package/dist/esm/testing/decorated-test-schemas.d.ts +8 -8
  9. package/dist/esm/zod-bigdecimal.d.ts +16 -8
  10. package/dist/esm/zod-bigdecimal.d.ts.map +1 -1
  11. package/dist/esm/zod-bigdecimal.js.map +1 -1
  12. package/dist/esm/zod-code.d.ts +17 -9
  13. package/dist/esm/zod-code.d.ts.map +1 -1
  14. package/dist/esm/zod-code.js.map +1 -1
  15. package/dist/esm/zod-decorators.types.d.ts +446 -48
  16. package/dist/esm/zod-decorators.types.d.ts.map +1 -1
  17. package/dist/esm/zod-decorators.types.js +190 -14
  18. package/dist/esm/zod-decorators.types.js.map +1 -1
  19. package/dist/esm/zod-duration.d.ts +24 -16
  20. package/dist/esm/zod-duration.d.ts.map +1 -1
  21. package/dist/esm/zod-duration.js.map +1 -1
  22. package/dist/esm/zod-file.d.ts +5 -8
  23. package/dist/esm/zod-file.d.ts.map +1 -1
  24. package/dist/esm/zod-file.js +10 -21
  25. package/dist/esm/zod-file.js.map +1 -1
  26. package/dist/esm/zod-money.d.ts +22 -14
  27. package/dist/esm/zod-money.d.ts.map +1 -1
  28. package/dist/esm/zod-money.js.map +1 -1
  29. package/dist/esm/zod-phone.d.ts +22 -14
  30. package/dist/esm/zod-phone.d.ts.map +1 -1
  31. package/dist/esm/zod-phone.js.map +1 -1
  32. package/dist/esm/zod-range.d.ts.map +1 -1
  33. package/dist/esm/zod-range.js +5 -1
  34. package/dist/esm/zod-range.js.map +1 -1
  35. package/dist/esm/zod-utils.shared.d.ts +146 -11
  36. package/dist/esm/zod-utils.shared.d.ts.map +1 -1
  37. package/dist/esm/zod-utils.shared.js +358 -45
  38. package/dist/esm/zod-utils.shared.js.map +1 -1
  39. package/dist/esm/zod-wire-date.d.ts +107 -0
  40. package/dist/esm/zod-wire-date.d.ts.map +1 -0
  41. package/dist/esm/zod-wire-date.js +162 -0
  42. package/dist/esm/zod-wire-date.js.map +1 -0
  43. package/dist/esm/zod-year-month.d.ts +76 -0
  44. package/dist/esm/zod-year-month.d.ts.map +1 -0
  45. package/dist/esm/zod-year-month.js +115 -0
  46. package/dist/esm/zod-year-month.js.map +1 -0
  47. package/dist/esm/zod.module-override.d.ts +263 -159
  48. package/dist/esm/zod.module-override.d.ts.map +1 -1
  49. package/dist/esm/zod.module-override.js +608 -66
  50. package/dist/esm/zod.module-override.js.map +1 -1
  51. package/dist/tsconfig.build.tsbuildinfo +1 -1
  52. package/package.json +5 -5
@@ -3,7 +3,6 @@ import type { CountryCode as LibPhoneNumberCountryCode } from 'libphonenumber-js
3
3
  import { ViewportBreakpoint } from './commons';
4
4
  import { AvailableCurrency, CurrencyAllowed } from './currencies.shared.schemas';
5
5
  import { z } from 'zod';
6
- import { DecoratedPrimitiveZodType } from './zod.module-override';
7
6
  import { ZodBrandedColorInstance } from './zod-color';
8
7
  import type { DocumentSequenceParams } from './zod-document-sequence';
9
8
  /**
@@ -147,9 +146,14 @@ export declare enum AutocompleteModifier {
147
146
  PAGER = "pager",
148
147
  WEBAUTHN = "webauthn"
149
148
  }
149
+ /** @wildo_source:part:start zod.decorators.chrome-modes facet:layer:frontend facet:family:ui facet:surface:form facet:surface:display facet:audience:app-developer */
150
150
  /**
151
151
  * Chrome layout mode for display context (detail/read-only views).
152
152
  * Controls how the field wrapper (label, value, description) is arranged.
153
+ *
154
+ * Import from `@wildo-ai/zod-decorators`. Declared per field (`display.chromeMode` in its `…UI()`), per
155
+ * layout (`layout.defaultChromeMode.display`) or per section; a `<FieldTiles>` host sets TILE for the fields
156
+ * placed in it. Every mode may be given per breakpoint ({@link ResponsiveAware}).
153
157
  */
154
158
  export declare const DisplayChromeMode: {
155
159
  /** Label above, value below, description below value (default). */
@@ -167,11 +171,32 @@ export declare const DisplayChromeMode: {
167
171
  readonly COMPACT: "compact";
168
172
  /** No chrome at all — bare value output. */
169
173
  readonly HIDDEN: "hidden";
174
+ /**
175
+ * A FACT TILE: the name small and quiet above, the value below, on a bounded tile
176
+ * (`SurfaceVariant.TILE`) — the shape of a record's facts laid out as a grid, which is how they are
177
+ * read: scanned by name, compared by value.
178
+ *
179
+ * Hosted by `<FieldTiles>`, which lays the tiles on a grid and gives its fields this chrome by
180
+ * default; a field that declares its own `chromeMode` (a description declaring HIDDEN) keeps it and
181
+ * takes a whole row of the grid in that chrome. Wide values — lists, objects, files, long text, a
182
+ * reference shown as its record's summary card — take a whole row. A tile shows no description line;
183
+ * a description the field declares rides in the tile's tooltip.
184
+ *
185
+ * Not the SETTINGS_ROW read chrome, which stays the shape of a SETTINGS pane: there the value sits
186
+ * where its control will appear. On a record the row placed a value up to 300px from its name, and a
187
+ * record is not a settings pane.
188
+ */
189
+ readonly TILE: "tile";
170
190
  };
171
191
  export type DisplayChromeMode = typeof DisplayChromeMode[keyof typeof DisplayChromeMode];
172
192
  /**
173
193
  * Chrome layout mode for edit context (forms).
174
- * Superset of DisplayChromeMode with form-specific modes (SETTINGS_ROW, RELAXED).
194
+ *
195
+ * NOT a superset of {@link DisplayChromeMode}, though most members share a name and a value: RELAXED
196
+ * exists only here (a form's generous onboarding spacing), and TILE exists only on the display side (a
197
+ * record's facts read as tiles; a form control is never a tile). A shared name is not always a shared
198
+ * geometry — edit INLINE puts the control BEFORE its label (a switch, a checkbox), display INLINE puts the
199
+ * name before its value — so read each member's own line.
175
200
  */
176
201
  export declare const EditChromeMode: {
177
202
  /** Label above, control below, description + error below control (default). */
@@ -199,6 +224,7 @@ export type ResponsiveChromeMode = EditChromeMode | Partial<Record<ViewportBreak
199
224
  * Used for chromeMode, forceHide, and other properties that may vary by viewport.
200
225
  */
201
226
  export type ResponsiveAware<T> = T | Partial<Record<ViewportBreakpoint, T>>;
227
+ /** @wildo_source:part:end zod.decorators.chrome-modes */
202
228
  export type FormFieldSchemaTypeComponents = {
203
229
  [K in FieldSchemaType]: K extends FieldSchemaType.STRING ? FormFieldComponent.INPUT | FormFieldComponent.TEXTAREA | FormFieldComponent.SELECT | FormFieldComponent.RADIO_GROUP | FormFieldComponent.DATE | FormFieldComponent.TIME | FormFieldComponent.DATETIME : K extends FieldSchemaType.NUMBER ? FormFieldComponent.INPUT | FormFieldComponent.SLIDER | FormFieldComponent.SELECT | FormFieldComponent.RADIO_GROUP : K extends FieldSchemaType.BIGINT ? FormFieldComponent.INPUT : K extends FieldSchemaType.BOOLEAN ? FormFieldComponent.SWITCH | FormFieldComponent.CHECKBOX | FormFieldComponent.SELECT | FormFieldComponent.RADIO_GROUP : K extends FieldSchemaType.ENUM ? FormFieldComponent.SELECT | FormFieldComponent.RADIO_GROUP : K extends FieldSchemaType.DATE ? FormFieldComponent.DATE | FormFieldComponent.DATETIME | FormFieldComponent.INPUT : K extends FieldSchemaType.TIME ? FormFieldComponent.TIME | FormFieldComponent.INPUT : K extends FieldSchemaType.DATETIME ? FormFieldComponent.DATETIME | FormFieldComponent.INPUT : K extends FieldSchemaType.RANGE_NUMBER ? FormFieldComponent.RANGE_SLIDER : K extends FieldSchemaType.RANGE_DATE ? FormFieldComponent.DATE_RANGE : K extends FieldSchemaType.RANGE_DATETIME ? FormFieldComponent.DATETIME_RANGE : K extends FieldSchemaType.RANGE_TIME ? FormFieldComponent.TIME_RANGE : K extends FieldSchemaType.RANGE_BIGDECIMAL ? FormFieldComponent.BIGDECIMAL_RANGE : K extends FieldSchemaType.RANGE_MONEY ? FormFieldComponent.MONEY_RANGE : K extends FieldSchemaType.MONEY ? FormFieldComponent.INPUT : K extends FieldSchemaType.DURATION ? FormFieldComponent.DURATION : K extends FieldSchemaType.PHONE ? FormFieldComponent.PHONE : K extends FieldSchemaType.ADDRESS ? FormFieldComponent.ADDRESS : K extends FieldSchemaType.BIGDECIMAL ? FormFieldComponent.INPUT : K extends FieldSchemaType.CODE ? FormFieldComponent.TEXTAREA : K extends FieldSchemaType.FILE ? FormFieldComponent.INPUT : K extends FieldSchemaType.ARRAY ? FormFieldComponent.CHECKBOX_GROUP | FormFieldComponent.SELECT : K extends FieldSchemaType.SET ? FormFieldComponent.INPUT | FormFieldComponent.CHECKBOX_GROUP | FormFieldComponent.SELECT : K extends FieldSchemaType.TUPLE ? FormFieldComponent.INPUT : K extends FieldSchemaType.UNKNOWN ? FormFieldComponent.INPUT : never;
204
230
  };
@@ -250,7 +276,8 @@ export type DefaultDisplayComponentForSchemaType<T extends FieldSchemaType> = Di
250
276
  * - DEFAULT: Standard section with border and padding
251
277
  * - CARD: Wrapped in a card container with shadow
252
278
  * - GHOST: Minimal container without visual styling
253
- * - COLLAPSIBLE: Expandable/collapsible section with trigger
279
+ * - COLLAPSIBLE: ONE section behind its own open/close trigger. `defaultFocus={false}` starts it
280
+ * closed — the mode for an aside a reader reveals on request
254
281
  *
255
282
  * **Container modes** (sections sharing the same mode are grouped together,
256
283
  * rendered at the position of the first occurrence):
@@ -265,13 +292,21 @@ export type DefaultDisplayComponentForSchemaType<T extends FieldSchemaType> = Di
265
292
  * - VERTICAL_TABS: Vertical tabs (side navigation)
266
293
  * - PILLS: Pill/segment navigation (compact)
267
294
  *
268
- * @category Expandable (toggle section visibility)
269
- * - ACCORDION: Each section independently collapsible
270
- * - EXPANDABLE: "Show more" button reveals additional sections
295
+ * @category Disclosure over a GROUP of sections
296
+ * - ACCORDION: one trigger per section; single-open unless `allowMultiple`
297
+ * - EXPANDABLE: truncation, not a toggle — the first `initialVisibleCount` (default 3) sections of
298
+ * the group show, and one "Show more" button reveals the rest. A group of `initialVisibleCount`
299
+ * sections or fewer never truncates, so EXPANDABLE over a single section renders it always open
300
+ * with no button: to hide one section until asked, use COLLAPSIBLE (#2197)
271
301
  *
272
302
  * @category Guided (sequential, with indicators)
273
- * - STEPPER: Step-by-step with step indicators
303
+ * - STEPPER: Step-by-step with step indicators — a WIZARD: the reader moves through the steps
274
304
  * - TIMELINE: Chronological progression (vertical timeline)
305
+ * - STAGES: the stages of ONE record's lifecycle, read-only. The stage the record is at comes from a
306
+ * field of the record (`stageBinding` on the group's first section), so a read page shows where the
307
+ * record stands: earlier stages reached, its own stage current, later ones not reached, each with its
308
+ * description, and no navigation buttons. Hosted by `DisplayLayout`, the host that holds a record
309
+ * (#1786). STEPPER is the wrong mode for this: its position is the READER's, not the record's.
275
310
  *
276
311
  * NOTE: Wizard is NOT included — it's a higher-level layout pattern
277
312
  * that contains sections, not a section rendering mode.
@@ -290,7 +325,8 @@ export declare enum SectionMode {
290
325
  ACCORDION = "accordion",
291
326
  EXPANDABLE = "expandable",
292
327
  STEPPER = "stepper",
293
- TIMELINE = "timeline"
328
+ TIMELINE = "timeline",
329
+ STAGES = "stages"
294
330
  }
295
331
  /** Solo modes — render in place, no grouping. Valid on `<Section appearance>`. */
296
332
  export type SoloSectionMode = SectionMode.DEFAULT | SectionMode.CARD | SectionMode.GHOST | SectionMode.COLLAPSIBLE;
@@ -474,6 +510,12 @@ export declare const StringDisplayMode: {
474
510
  * `edit.editMode: StringEditMode.ORGANIZATION_ROLE` so the field is picked the way it is read.
475
511
  */
476
512
  readonly ORGANIZATION_ROLE: "organization_role";
513
+ /**
514
+ * The value is a calendar month, `YYYY-MM` — render it as the month and the year in the reader's locale
515
+ * ("October 2026", "octobre 2026"), never the raw string. A value that is not a year-month shows as typed. Declared
516
+ * by `z_yearMonth()`, which pairs it with `edit.editMode: StringEditMode.YEAR_MONTH`.
517
+ */
518
+ readonly YEAR_MONTH: "year_month";
477
519
  };
478
520
  export type StringDisplayMode = typeof StringDisplayMode[keyof typeof StringDisplayMode];
479
521
  /**
@@ -499,11 +541,37 @@ export declare const StringEditMode: {
499
541
  * `/application-config` (`organizationRoles`). Each option reads as the role's word from the `roles` labels. Use it
500
542
  * for any string field that names ONE role a setting admits (a reader role, a routing role), so an administrator
501
543
  * picks a role that exists instead of typing a key exactly. Falls back to a text input when the backend publishes no
502
- * table. The server remains the control: it decides what holding the role grants.
544
+ * table. An operation's `requestFieldOffers` narrows the roles further (#2778), and `organizationRoleTable:
545
+ * ANOTHER_ORGANIZATION` (see {@link OrganizationRoleTable}) makes it a role held in another organization, offered from
546
+ * the operation's offer alone. The server remains the control: it decides what holding the role grants.
503
547
  */
504
548
  readonly ORGANIZATION_ROLE: "organization_role";
549
+ /**
550
+ * A MONTH-AND-YEAR picker: a list of the twelve months, named in the reader's locale, and a year. It writes the value
551
+ * as `YYYY-MM` and nothing else, so the user never types the format. Declared by `z_yearMonth()`.
552
+ */
553
+ readonly YEAR_MONTH: "year_month";
505
554
  };
506
555
  export type StringEditMode = typeof StringEditMode[keyof typeof StringEditMode];
556
+ /**
557
+ * WHOSE role table an `ORGANIZATION_ROLE` picker reads (#2778).
558
+ *
559
+ * - `CURRENT_ORGANIZATION` (the default): the role is one of the organization being administered, so the picker offers
560
+ * that organization's table (the application's organization roles as its type narrows them).
561
+ * - `ANOTHER_ORGANIZATION`: the role names a role in ANOTHER organization, such as the role a representing organization
562
+ * proposes to hold in the granting one. The current organization's table is then the wrong population, so the picker
563
+ * offers only the values its operation's request field offer gives (`requestFieldOffers`), and is a text input
564
+ * while there is no offer.
565
+ *
566
+ * A const object, not an enum, like every edit- and display-mode vocabulary it sits among (`StringEditMode` above):
567
+ * these values are written inside `stringUI({ edit })` literals, and one vocabulary of the family declared differently
568
+ * would be the one a reader trips on. Converting the family is one decision for all of them, not a per-member one.
569
+ */
570
+ export declare const OrganizationRoleTable: {
571
+ readonly CURRENT_ORGANIZATION: "current_organization";
572
+ readonly ANOTHER_ORGANIZATION: "another_organization";
573
+ };
574
+ export type OrganizationRoleTable = typeof OrganizationRoleTable[keyof typeof OrganizationRoleTable];
507
575
  /**
508
576
  * Number display modes for rendering number values
509
577
  */
@@ -560,10 +628,28 @@ export type BigDecimalEditMode = typeof BigDecimalEditMode[keyof typeof BigDecim
560
628
  * Enum display modes for rendering enum values
561
629
  */
562
630
  export declare const EnumDisplayMode: {
631
+ /** The value's word alone, in the voice of the text around it — for a value with no colour to carry. */
563
632
  readonly TEXT: "text";
633
+ /** A soft pill in the value's colour. A value with no icon of its own leads with a state dot. */
564
634
  readonly BADGE: "badge";
635
+ /**
636
+ * The value's icon alone, on a pill in its colour; the word is read to assistive technology and shown on
637
+ * hover. The compact form for a summary line (`summaryOverride: { displayMode: ICON }`) where the icon is
638
+ * already known to the reader. A value with no `icon` of its own shows a dot in its colour instead.
639
+ */
565
640
  readonly ICON: "icon";
641
+ /** The word on a small card of its own — for a value that is the subject of a surface, not one fact among several. */
566
642
  readonly CARD: "card";
643
+ /**
644
+ * A tone dot and the label, with no chip around them — the QUIET form of a coloured value.
645
+ *
646
+ * For the second coloured value beside a status: priority next to status, severity next to state.
647
+ * Two filled pills side by side compete for the same attention; one pill and one dot keep the
648
+ * status as the thing that is read first. The dot fills with the value's colour at full strength and
649
+ * the label takes the same tone, so the value still reads without the chip. A value whose colour is
650
+ * the neutral one renders its dot and label muted.
651
+ */
652
+ readonly DOT: "dot";
567
653
  };
568
654
  export type EnumDisplayMode = typeof EnumDisplayMode[keyof typeof EnumDisplayMode];
569
655
  /**
@@ -678,10 +764,9 @@ export interface EnumValueDisplayConfig {
678
764
  * values: { ...current?.values, active: { color: 'success' } },
679
765
  * }));
680
766
  *
681
- * schema.formComponent((current) => ({
767
+ * schema.stringUI((current) => ({
682
768
  * ...current,
683
- * component: 'custom-input',
684
- * hideLabel: true,
769
+ * edit: { ...current?.edit, forceHide: { description: true } },
685
770
  * }));
686
771
  * ```
687
772
  */
@@ -860,7 +945,7 @@ export declare enum RangeType {
860
945
  export type RangeTypeMapping = {
861
946
  [RangeType.NUMBER]: number;
862
947
  [RangeType.DATE]: Date;
863
- [RangeType.DATETIME]: string;
948
+ [RangeType.DATETIME]: Date;
864
949
  [RangeType.TIME]: string;
865
950
  [RangeType.BIGDECIMAL]: string;
866
951
  [RangeType.MONEY]: {
@@ -1373,10 +1458,15 @@ export interface FileMultipleFilePolicy {
1373
1458
  * <scope>/<scopeId>/ ← the tenant segments (organizations/<id>, user_self/<id>,
1374
1459
  * or application/<applicationId>)
1375
1460
  * [folderPath(ctx)/] ← dynamic folder path
1376
- * <fileName(ctx) | fileId> ← explicit name or UUID fallback
1461
+ * [<fileId>/]<fileName(ctx)> | <fileId> ← explicit name under the file's own id, or the id alone
1377
1462
  * ```
1378
1463
  *
1379
- * Only the last two lines are authored here. Everything above them is derived by the framework and
1464
+ * A name is display metadata, never the identity of the stored object (#2901): when a name is resolved, the file's id
1465
+ * is the level directly above it, so two files of one field never share a key, whatever the name says. A static
1466
+ * `fileName` once gave every file of the field one key, and a regenerated file was written over the bytes its
1467
+ * predecessor still pointed at.
1468
+ *
1469
+ * Only `folderPath` and the name are authored here. Everything above them is derived by the framework and
1380
1470
  * is identical on every path-based provider, because `<scope>/<scopeId>` is what makes one tenant's
1381
1471
  * blobs a PREFIX — the unit erasure, orphan sweeps and per-tenant listing operate on.
1382
1472
  *
@@ -1387,7 +1477,7 @@ export interface FileMultipleFilePolicy {
1387
1477
  * z_file({
1388
1478
  * filePathBinding: {
1389
1479
  * folderPath: (ctx) => `specs/icps/${ctx.relatedObject?.name}`,
1390
- * fileName: 'personna.md',
1480
+ * fileName: 'personna.md', // dead-plan-citation: an example stored file name
1391
1481
  * },
1392
1482
  * })
1393
1483
  * ```
@@ -1421,11 +1511,12 @@ export interface FilePathBinding {
1421
1511
  */
1422
1512
  folderPath?: string | ((context: FilePathBindingContext) => string);
1423
1513
  /**
1424
- * File name for single `z_file()` fields (non-multiple).
1425
- * When omitted, falls back to the fileId (UUID-based, legacy behavior).
1514
+ * File name for single `z_file()` fields (non-multiple): the stored object's last segment, placed under the file's own
1515
+ * id, so a static name is safe (#2901). When omitted, the key is the fileId alone.
1426
1516
  *
1427
1517
  * - `string` — static name; can include `{ext}` placeholder.
1428
1518
  * - `function` — receives binding context and returns the file name.
1519
+ * (dead-plan-citation: the examples below are stored file names.)
1429
1520
  *
1430
1521
  * @example 'personna.md'
1431
1522
  * @example 'avatar.{ext}'
@@ -1457,7 +1548,8 @@ export declare enum FileUploaderEntityType {
1457
1548
  USER = "user",
1458
1549
  ANONYMOUS_USER = "anonymous_user",
1459
1550
  ORGANIZATION_MACHINE = "organization_machine",
1460
- APPLICATION_MACHINE = "application_machine"
1551
+ APPLICATION_MACHINE = "application_machine",
1552
+ INBOUND_DELIVERY = "inbound_delivery"
1461
1553
  }
1462
1554
  export type FileUploaderOwnership = {
1463
1555
  uploadedBy: string;
@@ -2087,6 +2179,35 @@ export interface BackendOnlyFieldParams {
2087
2179
  * material is attributable.
2088
2180
  */
2089
2181
  isReturnedAtCreation?: boolean;
2182
+ /**
2183
+ * `true` declares that this backend-only value is the SUBJECT'S OWN DATA, so a person's ACCESS export (GDPR Art. 15)
2184
+ * carries it: `isBackendOnly({ disclosedToSubject: true })`. It changes nothing else: the field is still stripped from
2185
+ * every request and response DTO, every listing and every model or tool output.
2186
+ *
2187
+ * Why it exists (#2958). A subject export reads rows below the DTO projection, so it withholds every backend-only field
2188
+ * as a security floor: that is what keeps a password hash or a token hash out of a bundle. Many backend-only fields are
2189
+ * not secrets, though. They are kept off the API because a generic reader must not see them (who a pending decision is
2190
+ * routed to, when it was decided, which chat card a person was sent), and to the person they are about they are plain
2191
+ * personal data. Without this declaration such a row reached its subject with that substance withheld.
2192
+ *
2193
+ * Declare it only where the value is about the person who receives the export: never on a credential, a secret, a
2194
+ * token or a lease, never on a field holding other people's data (a list of everyone a row names). When the row can also
2195
+ * reach someone it is NOT about (a conversation's owner, for the id of the person who decided on it), the resource
2196
+ * withholds the field from that path as other people's data (`subjectExportOthersDataFields`).
2197
+ *
2198
+ * A portability export (Art. 20) still withholds it: a backend-only value is written by the system and never provided
2199
+ * by the subject through any request, so it is outside what Art. 20 covers.
2200
+ *
2201
+ * Refused, at the decorator chokepoint and in either order, beside `isPassword()`, `isOtp()`, `isWriteOnlySecret()`,
2202
+ * `isEphemeral()` and `isReturnedAtCreation`: each marks a secret. A field the application declares
2203
+ * `.dataCategories(CREDENTIALS)` is refused when its resource configuration is built.
2204
+ *
2205
+ * spec-cite: GDPR Art. 15(1), 15(3) (https://gdpr-info.eu/art-15-gdpr/); Art. 20(1) (https://gdpr-info.eu/art-20-gdpr/)
2206
+ *
2207
+ * assurance-control: WILDO.PRIVACY.SUBJECT_DATA_EXPORT — a backend-only value about the subject reaches their access
2208
+ * export only by this declaration, never by loosening the floor.
2209
+ */
2210
+ disclosedToSubject?: true;
2090
2211
  }
2091
2212
  /**
2092
2213
  * Parameters for the isWriteOnlySecret decorator.
@@ -2164,6 +2285,44 @@ export interface ImpersonalizeFieldParams {
2164
2285
  */
2165
2286
  maskValue?: string;
2166
2287
  }
2288
+ /**
2289
+ * The optional second argument of `.dataCategories(categories, options)` (#2119).
2290
+ *
2291
+ * Kept as an options bag, like `impersonalizeWith(treatment, options)`, so a second qualifier lands
2292
+ * here instead of as a new positional argument.
2293
+ */
2294
+ export interface DataCategoriesOptions {
2295
+ /**
2296
+ * The condition under which this field's value is personal data at all — a `PersonalDataCondition`
2297
+ * value carried as an opaque string (zod-decorators sits below saas-models and cannot reference
2298
+ * the enum). Absent means the field is personal data on every row.
2299
+ *
2300
+ * The case it exists for: a field naming the PARTY a row describes — a tenant organization, a
2301
+ * customer account — is a natural person's data when that party is an individual (a sole trader)
2302
+ * and not when it is a company. Declaring the categories with this condition lets a privacy
2303
+ * notice say "where the customer is an individual" instead of choosing between two false
2304
+ * sentences. Whether the value is a real member is checked by the saas-models boot guard.
2305
+ */
2306
+ personalOnlyWhen?: string;
2307
+ /**
2308
+ * `true` when the field names a party OTHER than the one the row is about: a copy of another organization's name kept
2309
+ * on a cost aggregate, a relationship, a membership or an access grant (#2535). Absent on a field naming the row's own
2310
+ * party, such as an organization's or a customer account's own name.
2311
+ *
2312
+ * WHOSE data a field is, which is a different question from WHEN it is personal (`personalOnlyWhen`), so it is
2313
+ * declared separately. Only one reader asks it: the `RETAIN_ONLY` contradiction guard, which admits a conditional copy
2314
+ * of another party's name on a resource stating "no personal data about its own subject", and still refuses the row's
2315
+ * own party's name there. Every other reader (erasure, export, the notice) counts the field exactly as before.
2316
+ */
2317
+ aboutAnotherParty?: boolean;
2318
+ }
2319
+ /**
2320
+ * What `.dataCategories()` STORES: the normalized form of either authoring shape. The categories
2321
+ * are always a deduped, non-empty array; the condition is present only when one was declared.
2322
+ */
2323
+ export interface DataCategoriesFieldParams extends DataCategoriesOptions {
2324
+ categories: readonly string[];
2325
+ }
2167
2326
  /**
2168
2327
  * Parameters passed to virtual field calculation functions.
2169
2328
  * The `utils` is typed as `any` in shared-lib but will be properly typed at runtime in backend-lib.
@@ -2188,16 +2347,56 @@ export interface VirtualFieldCalculationParams<TObject = any, TContext = any, TU
2188
2347
  */
2189
2348
  utils: any;
2190
2349
  }
2350
+ /**
2351
+ * Parameters passed to a virtual field's BATCHED calculation ({@link VirtualFieldParams.calculateMany}): the rows of
2352
+ * one list, in the order the list returns them.
2353
+ */
2354
+ export interface VirtualFieldBatchCalculationParams<TObject = any> {
2355
+ /** The rows to compute the field for, in list order. */
2356
+ currentObjects: readonly TObject[];
2357
+ /** The execution context of the request listing them. */
2358
+ executionContext: any;
2359
+ /** The same utility services as {@link VirtualFieldCalculationParams.utils}. */
2360
+ utils: any;
2361
+ }
2191
2362
  /**
2192
2363
  * Parameters for the isVirtual decorator.
2193
2364
  * Virtual fields are computed at runtime and not stored in the database.
2194
2365
  */
2195
2366
  export interface VirtualFieldParams<TObject = any, TResult = any> {
2196
2367
  /**
2197
- * The calculation function that computes this virtual field's value.
2198
- * Called at runtime by VirtualFieldsBackendService with injected utilities.
2368
+ * The calculation function that computes this virtual field's value for ONE row.
2369
+ * Called at runtime by VirtualFieldsBackendService with injected utilities: on a READ, and on a list row when the
2370
+ * field declares no {@link calculateMany}.
2199
2371
  */
2200
2372
  calculate: (params: VirtualFieldCalculationParams<TObject>) => TResult | Promise<TResult>;
2373
+ /**
2374
+ * The BATCHED form: computes the field for every row of a list in ONE call (#2631). Declare it when the field reads
2375
+ * another resource, so a page of N rows reads that resource once (one query over the rows' keys) instead of N times.
2376
+ *
2377
+ * - Return one value per row, in the same order: an array exactly as long as `currentObjects`. Any other length is
2378
+ * treated as a failure of the whole batch, like a throw.
2379
+ * - A failing batch gives every row `undefined` when the computation is fault-tolerant (the list default) and throws
2380
+ * otherwise, exactly as a failing `calculate` does for its one row.
2381
+ * - Used wherever several rows are computed together: a LIST or SEARCH page, and a relationship's children in a
2382
+ * resource context. One row (a READ) still goes through `calculate`, so the two must give the same value for the same row:
2383
+ * write the derivation once and call it from both.
2384
+ * - Ignored for a field that declares `needsObjectContext` or `needsUserContext`: those contexts are resolved per
2385
+ * row, so such a field is computed by `calculate` alone.
2386
+ *
2387
+ * @example
2388
+ * ```ts
2389
+ * tenantStatus: z.string().optional().isVirtual({
2390
+ * dependencies: ['organizationId'],
2391
+ * calculate: async ({ currentObject, utils }) => (await statusesOf(utils, [currentObject.organizationId])).get(currentObject.organizationId),
2392
+ * calculateMany: async ({ currentObjects, utils }) => {
2393
+ * const statuses = await statusesOf(utils, currentObjects.map((row) => row.organizationId));
2394
+ * return currentObjects.map((row) => statuses.get(row.organizationId));
2395
+ * },
2396
+ * }),
2397
+ * ```
2398
+ */
2399
+ calculateMany?: (params: VirtualFieldBatchCalculationParams<TObject>) => readonly TResult[] | Promise<readonly TResult[]>;
2201
2400
  /**
2202
2401
  * If true, the objectContext (parent/child relationships) will be passed to calculate().
2203
2402
  * Setting this to true has a performance cost as it requires fetching related resources.
@@ -2209,8 +2408,8 @@ export interface VirtualFieldParams<TObject = any, TResult = any> {
2209
2408
  */
2210
2409
  needsUserContext?: boolean;
2211
2410
  /**
2212
- * Optional list of field names this virtual field depends on.
2213
- * Used for documentation and potential future optimizations.
2411
+ * The stored fields this virtual field reads from its row. The repository adds them to the read projection, so
2412
+ * `calculate` / `calculateMany` receive them even on a SUMMARY row where they are not themselves summary fields.
2214
2413
  */
2215
2414
  dependencies?: string[];
2216
2415
  }
@@ -2266,8 +2465,14 @@ export interface UniqueFieldParams {
2266
2465
  */
2267
2466
  compoundWith?: string;
2268
2467
  /**
2269
- * Whether to allow null values without triggering uniqueness.
2270
- * MongoDB unique indexes exclude null by default when sparse is true.
2468
+ * Whether a row WITHOUT a value (the field absent or `null`) is left out of the uniqueness, so any number of such rows
2469
+ * may coexist. PostgreSQL plans it as a unique constraint whose NULLs are distinct. MongoDB builds it as a partial
2470
+ * filter that bounds the field from below in its own type (`{ field: { $gte: '' } }` for a string), which leaves out a
2471
+ * null as well as a missing value (#2582): MongoDB's own `sparse: true` would still index a stored null. The one
2472
+ * exception is a field whose type has no lowest value (an array, a free-form value), whose filter is
2473
+ * `{ $exists: true }` and so indexes a stored null.
2474
+ *
2475
+ * `false` makes rows without a value collide with each other on both stores (`NULLS NOT DISTINCT` on PostgreSQL).
2271
2476
  * @default true
2272
2477
  */
2273
2478
  sparse?: boolean;
@@ -2337,6 +2542,62 @@ export interface BusinessKeySetParams {
2337
2542
  */
2338
2543
  conflictMessageReference?: string;
2339
2544
  }
2545
+ /**
2546
+ * One NON-UNIQUE index over several fields, declared with the object-level `compoundIndex([...])` decorator (#3142).
2547
+ *
2548
+ * Declare one for a read that filters on some fields and sorts on another, when the table grows without bound: "one
2549
+ * organization's audit rows of one event type, newest first" is `['organizationId', 'eventType', 'createdAt']`. Order is
2550
+ * the index's identity and decides which reads it serves: the equality filters come first, the sort field last. A read
2551
+ * filtering on a leading PREFIX of the list is served as well, so `['organizationId', 'createdAt']` also serves "one
2552
+ * organization's rows". The index is walked in either direction, so newest-first needs no descending declaration.
2553
+ *
2554
+ * Why separate per-field `.isDBIndexed()` declarations are not the same thing: the database may combine two of them for
2555
+ * an equality filter, but it cannot use them to return a filtered set already in sort order, so it either sorts the whole
2556
+ * filtered set or walks the sort field's index and discards every row of the other tenants. Measured on a 580,000-row
2557
+ * PostgreSQL audit trail: 534 ms and 2.2 s through per-field indexes, 1 to 9 ms through the compound one.
2558
+ *
2559
+ * Uniqueness is NOT what this declares; a composite IDENTITY is `.businessKeySet([...])`. Every field must be declared
2560
+ * on the object (checked at decoration time) and STORED (not `.isVirtual()`), there must be at least two (one field is
2561
+ * `.isDBIndexed()`), and none may repeat. Several indexes are declared by calling the decorator once per index. An
2562
+ * object derived with `.omit()` / `.pick()` keeps only the indexes whose fields it still has. Both storage adapters
2563
+ * honour it: MongoDB builds the index (ended on `_id`, as every plain index is, so a paged read's total order is
2564
+ * served), and the PostgreSQL plan records it so `wildo db generate` writes the migration that creates it. Not yet
2565
+ * supported on the BASE schema of a polymorphic resource, which refuses outer-level metadata it cannot carry to its
2566
+ * variants: declare it on a resource whose main schema is a plain object.
2567
+ *
2568
+ * @example
2569
+ * // one tenant's trail, optionally of one event type, newest first
2570
+ * z.object({ organizationId: z.string().optional(), eventType: z.string(), createdAt: z.date() })
2571
+ * .compoundIndex(['organizationId', 'eventType', 'createdAt'])
2572
+ * .compoundIndex(['organizationId', 'createdAt'])
2573
+ */
2574
+ export interface CompoundIndexParams {
2575
+ /** The indexed fields, in index order: equality filters first, the sort field last. */
2576
+ fields: string[];
2577
+ }
2578
+ /**
2579
+ * Parameters for the object-level `isResourceReference({ targetSet })` decorator (#2471).
2580
+ *
2581
+ * The decorated object names ONE row of a resource whose type is not fixed: its `resourceType` member is the
2582
+ * resource's name and its `resourceId` member the row's id. Unlike `.isForeignKey()` it declares no relationship
2583
+ * and changes nothing about storage: the two members stay plain strings, and the referenced resource is only
2584
+ * known at run time. It is what lets a form offer the resource types first and then the rows of the chosen one.
2585
+ *
2586
+ * `targetSet` names WHICH resource types the reference may name. It is a member of a vocabulary owned by the
2587
+ * layer that knows the types (`ResourceReferenceTargetSet` in `@wildo-ai/saas-models`), and the backend publishes
2588
+ * each set's members on `/application-config` (`resourceReferenceTargets`), derived from what the application
2589
+ * declares. A string here because this package sits below that vocabulary, as `dataCategories` does.
2590
+ *
2591
+ * The server stays the control: a reference is accepted only where the service that owns the field admits it.
2592
+ *
2593
+ * @example
2594
+ * export const WorkUnitRefSchema = z.strictObject({ resourceType: z.string(), resourceId: z.string() })
2595
+ * .isResourceReference({ targetSet: ResourceReferenceTargetSet.WORK_UNIT_CASES });
2596
+ */
2597
+ export interface ResourceReferenceParams {
2598
+ /** The set of resource types the reference may name. */
2599
+ targetSet: string;
2600
+ }
2340
2601
  /** @wildo_source:part:end zod.decorators.shared-structural-params */
2341
2602
  /**
2342
2603
  * @wildo_source:part:start zod.decorators.shared-structural-params facet:layer:shared facet:family:decorator facet:family:param-surface
@@ -2355,7 +2616,7 @@ export interface BusinessKeySetParams {
2355
2616
  * enforced on both. Do NOT read the MongoDB index as the mechanism and conclude the declaration is
2356
2617
  * inert elsewhere.
2357
2618
  *
2358
- * Two consequences worth knowing before you declare one:
2619
+ * Consequences worth knowing before you declare one:
2359
2620
  * - the sweep is deliberately bounded per run, so a table receiving rows faster than they expire
2360
2621
  * stays above its target until the rate changes; it reports when it stops early;
2361
2622
  * - a resource CANNOT declare both a lifetime and retrieval-source fields (`.ragSource()`). That
@@ -2364,7 +2625,16 @@ export interface BusinessKeySetParams {
2364
2625
  * - a resource scoped to an ORGANIZATION cannot declare a lifetime at all. For the same reason
2365
2626
  * no legal hold could suspend the deletion, and a hold on an organization covers every row of
2366
2627
  * it. A lifetime is for application-level plumbing; tenant data binds a retention class
2367
- * (`retentionBinding`), whose sweep deletes through the service layer and skips held rows.
2628
+ * (`retentionBinding`), whose sweep deletes through the service layer and skips held rows;
2629
+ * - a resource that another resource's ENABLED `onParentDelete` edge points at cannot declare a lifetime, whatever the
2630
+ * strategy (DELETE, IMPERSONALIZE_AND_RETAIN, REFUSE_WHILE_CHILDREN_EXIST) or mode (IMMEDIATE, LAZY). It is refused at startup: an expired
2631
+ * row leaves with none of its lifecycle running, so its children would outlive it, keep their personal data, or be
2632
+ * walked past. Bind a retention class instead; its sweep deletes through the service layer, which runs the edges;
2633
+ * - the field must be a TOP-LEVEL field of the main schema (always an object) or of an inheritance level: a lifetime on
2634
+ * a nested field is refused, since every guard above reads top-level fields while MongoDB would expire rows by the
2635
+ * nested path natively. An inheritance variant MAY give its rows their own lifetime, on a field only it declares;
2636
+ * one field has one lifetime, so a field declared with a `.ttl()` in one schema and differently in another (another
2637
+ * window, or none) is refused (MongoDB keeps one TTL index per path).
2368
2638
  *
2369
2639
  * This is unrelated to `retentionPolicy`, which decides what an ERASURE does to a row that must
2370
2640
  * SURVIVE. This one decides whether the row lives at all.
@@ -2435,7 +2705,7 @@ export interface SummaryFieldParams {
2435
2705
  * // Field definition:
2436
2706
  * userId: z.string().isForeignKey().isSummaryField({ populate: true })
2437
2707
  *
2438
- * // LIST/SEARCH response will include:
2708
+ * // A LIST or SEARCH response will include:
2439
2709
  * {
2440
2710
  * _id: "123",
2441
2711
  * userId: "user-456",
@@ -2613,6 +2883,10 @@ export interface DraftableConfigParams {
2613
2883
  * Display has no `error` sub-element to hide.
2614
2884
  */
2615
2885
  export interface ForceHideDisplayConfig {
2886
+ /**
2887
+ * Removes the label everywhere, assistive technology included, exactly like the `hideLabel` prop. To hide it from
2888
+ * sight only, declare `labelPlacement: 'hidden'` where the field is placed instead (#3079).
2889
+ */
2616
2890
  label?: boolean;
2617
2891
  description?: boolean;
2618
2892
  }
@@ -2666,9 +2940,9 @@ export interface PrimitiveUIBaseDisplayParams {
2666
2940
  tooltip?: boolean;
2667
2941
  /**
2668
2942
  * When `true`, the field is omitted entirely from the DISPLAY (read) surface
2669
- * whenever its value is empty — null, undefined, an empty string, or an empty
2670
- * array. The whole row (label + chrome + em-dash placeholder) is removed, not
2671
- * just the value.
2943
+ * whenever its value is empty — null, undefined, a blank string, an empty array or
2944
+ * an empty plain object. `false` and `0` are answers, never empty, and so is a date.
2945
+ * The whole row (label + chrome + em-dash placeholder) is removed, not just the value.
2672
2946
  *
2673
2947
  * DISPLAY-ONLY: the edit form always renders the field so an empty value can
2674
2948
  * still be filled in. This is the field-level counterpart to the section-level
@@ -2676,9 +2950,21 @@ export interface PrimitiveUIBaseDisplayParams {
2676
2950
  * `AutoEmptyValue.HIDDEN` — use it to keep optional metadata (a job title, a
2677
2951
  * secondary contact) from cluttering a read view when unset.
2678
2952
  *
2953
+ * UNSET means `true` inside a `<FieldTiles>` host (a record's facts — an empty tile
2954
+ * is clutter there, and the generated read view uses the host when the layout
2955
+ * declares no `defaultChromeMode.display`) and `false` everywhere else. So a field
2956
+ * whose emptiness is itself worth seeing on a record ("this contact has no email")
2957
+ * declares `hideWhenEmpty: false` explicitly. A field the reader can edit IN PLACE
2958
+ * keeps its empty tile without saying so — hiding it would remove the only way to
2959
+ * fill it there. And the default reaches only the fields placed in the host: the
2960
+ * items of a list and the members of an object a tile shows are its content, not
2961
+ * facts of the grid, and follow their own decorators.
2962
+ *
2679
2963
  * @example
2680
2964
  * // Hide the "Title" row on the profile read view until the user sets one:
2681
2965
  * schemaShape.title.stringUI({ display: { hideWhenEmpty: true } })
2966
+ * // Keep an empty email visible as a tile — the gap is the information:
2967
+ * schemaShape.email.stringUI({ display: { hideWhenEmpty: false } })
2682
2968
  */
2683
2969
  hideWhenEmpty?: boolean;
2684
2970
  /**
@@ -2812,6 +3098,8 @@ export interface StringUIEditParams extends PrimitiveUIBaseEditParams {
2812
3098
  editMode?: StringEditMode;
2813
3099
  /** Number of rows for TEXTAREA editMode. */
2814
3100
  textareaRows?: number;
3101
+ /** ORGANIZATION_ROLE editMode: whose role table the picker reads. Default `CURRENT_ORGANIZATION`. See {@link OrganizationRoleTable}. */
3102
+ organizationRoleTable?: OrganizationRoleTable;
2815
3103
  }
2816
3104
  export interface StringUIParams {
2817
3105
  display?: StringUIDisplayParams;
@@ -2848,7 +3136,7 @@ export interface NumberUIParams {
2848
3136
  edit?: NumberUIEditParams;
2849
3137
  }
2850
3138
  export interface EnumUIDisplayParams extends PrimitiveUIBaseDisplayParams {
2851
- /** Display renderer. TEXT | BADGE | ICON | CARD. */
3139
+ /** Display renderer — one of {@link EnumDisplayMode}, whose members each say when they are the right one. */
2852
3140
  displayMode?: EnumDisplayMode;
2853
3141
  }
2854
3142
  export interface EnumUIEditParams extends PrimitiveUIBaseEditParams {
@@ -3103,12 +3391,15 @@ export interface ForeignKeyUIParams {
3103
3391
  edit?: ForeignKeyUIEditParams;
3104
3392
  }
3105
3393
  /**
3106
- * List marker treatment for a PRIMITIVE array's display items.
3394
+ * How each item of an array's display announces itself as one of several: bare lines, bullets, a rule
3395
+ * between object entries, or a wrapping row of chips. The members apply to different entries (primitive
3396
+ * lines, object entries, strings), which each member's own line states.
3107
3397
  *
3108
- * Its own enum rather than a new `DisplayDirection` member on purpose: direction answers "which
3109
- * axis do items flow on", marker style answers "does each item announce itself as one of several" —
3110
- * merging them would make `BULLETS × horizontal` an unrepresentable-but-meaningful combination and
3111
- * overload an enum that alignment code also reads.
3398
+ * Its own enum rather than a new `DisplayDirection` member on purpose: direction answers "which axis do
3399
+ * items flow on", list style answers "how is each item set apart" — merging them would make
3400
+ * `BULLETS × horizontal` an unrepresentable-but-meaningful combination and overload an enum that
3401
+ * alignment code also reads. The one member that decides the flow itself is CHIPS: chips always wrap on
3402
+ * a line, so it ignores `displayMode`.
3112
3403
  */
3113
3404
  export declare enum ArrayListStyle {
3114
3405
  /** Bare stacked values — the historical default. */
@@ -3124,7 +3415,17 @@ export declare enum ArrayListStyle {
3124
3415
  * bounded region, a card per entry is a third frame around content that has two. Unlike
3125
3416
  * {@link ArrayListStyle.BULLETS}, which is for primitive lines, this is for entries.
3126
3417
  */
3127
- SEPARATED = "separated"
3418
+ SEPARATED = "separated",
3419
+ /**
3420
+ * Each entry a small neutral chip, the chips wrapping on one line — the shape of TAGS, labels and
3421
+ * keywords: short, unordered, read as a set.
3422
+ *
3423
+ * Applies to STRING entries. A list of enum values already renders each value in its own display
3424
+ * mode (a badge, a dot), so a chip around it would be a frame around a frame; any other entry type
3425
+ * falls back to the horizontal flow. Without it, two tags under `SEPARATED` stacked one above the
3426
+ * other at the right edge of a settings row, 81px tall for two words.
3427
+ */
3428
+ CHIPS = "chips"
3128
3429
  }
3129
3430
  export interface ArrayUIDisplayParams extends PrimitiveUIBaseDisplayParams {
3130
3431
  /** Direction for rendering items in display context. */
@@ -3132,9 +3433,10 @@ export interface ArrayUIDisplayParams extends PrimitiveUIBaseDisplayParams {
3132
3433
  /** Max items shown in display (rest truncated). */
3133
3434
  maxItems?: number;
3134
3435
  /**
3135
- * Marker treatment for PRIMITIVE items (see {@link ArrayListStyle}). No effect on object arrays —
3136
- * their items are cards/rows, not list lines — and no effect on horizontal flow, where markers
3137
- * have no line to anchor to.
3436
+ * How each item announces itself as one of several (see {@link ArrayListStyle}, whose members each
3437
+ * state the entries they apply to): PLAIN and BULLETS mark primitive lines, SEPARATED rules object
3438
+ * entries, CHIPS renders string entries as a wrapping row of chips and so decides the flow itself.
3439
+ * The line markers have no effect on horizontal flow, where there is no line to anchor to.
3138
3440
  */
3139
3441
  listStyle?: ArrayListStyle;
3140
3442
  }
@@ -3549,6 +3851,11 @@ export declare enum ZodDecorator {
3549
3851
  * WHETHER each value is a real `PersonalDataCategory` member is checked by the resource factory's
3550
3852
  * boot guard, which owns the enum — this layer knows only that non-empty strings were declared.
3551
3853
  *
3854
+ * An optional second argument, `{ personalOnlyWhen }` ({@link DataCategoriesOptions}), says the
3855
+ * field is personal data only under a `PersonalDataCondition` — the field naming a customer
3856
+ * organization, which is a person's data only when that customer is a sole trader (#2119). The
3857
+ * field still counts as personal for every guard; only the privacy notice words it conditionally.
3858
+ *
3552
3859
  * ⚠️ Four adjacent axes, none of them this one. {@link ZodDecorator.IMPERSONALIZE_WITH} says what
3553
3860
  * happens to the value on ERASURE; {@link ZodDecorator.PORTABILITY_PROVENANCE} says where it CAME
3554
3861
  * FROM; `Compliance_DataClassification` is a SENSITIVITY ladder for flows-actors config; a
@@ -3594,6 +3901,48 @@ export declare enum ZodDecorator {
3594
3901
  * assurance-control: WILDO.SECRETS.DISCLOSURE_AUDIT
3595
3902
  */
3596
3903
  IS_AUDIT_EVIDENCE = "isAuditEvidence",
3904
+ /**
3905
+ * Last-action record: this field holds what the MOST RECENT action on the row said about ITSELF — the
3906
+ * reason a person gave for a decision, the justification typed for a reassignment — not state the next
3907
+ * action starts from.
3908
+ *
3909
+ * Authored fluently on the ROW schema: `decisionReason: z.string().optional().isLastActionRecord()`. A pure
3910
+ * marker. Read it back with `isLastActionRecordField(schema)`.
3911
+ *
3912
+ * What it changes: a form that starts from the row (an update-like verb's form starts from the record it
3913
+ * acts on, so "Set disclosure" opens on the current disclosure) never offers this field's value as the
3914
+ * starting value of the next action. Without it, a verb whose request names the same field opened with the
3915
+ * previous decision's words already typed in: "Suspend" prefilled with the reason an acceptance was given,
3916
+ * which a person who submits without reading records as the reason for the suspension (#2192).
3917
+ *
3918
+ * Mark a field when an action REPLACES it with words about that action. Do not mark a setting an action
3919
+ * edits from its current value (a disclosure level, a list of admitted agents): starting from the current
3920
+ * value is what makes such a form safe to submit.
3921
+ */
3922
+ IS_LAST_ACTION_RECORD = "isLastActionRecord",
3923
+ /**
3924
+ * Decider: this field records WHICH PERSON decided something on the row: who approved, accepted, placed or released
3925
+ * (`decidedByUserId`, `acceptedByUserId`, `placedByUserId`). A pure marker, read back with `isDeciderField(schema)`.
3926
+ *
3927
+ * What it changes: a decider field records a person's decision, so the declaration of a verb that writes it names it
3928
+ * (`approvalSeal.decides`, CAPTURED or NONE with a reason, #2906). The engine's source check, a test over the
3929
+ * engine's own resources, refuses a decider field no declaration names and flags a field NAMED like a decider that is
3930
+ * not marked. No check runs over an application's resources yet: there the marker documents the field.
3931
+ */
3932
+ IS_DECIDER = "isDecider",
3933
+ /**
3934
+ * Role grant: this field's values are roles the WRITER grants (a membership's `roles`, a machine credential's
3935
+ * `roles`, a unit assignment's `role`, an account's `appRoles`). A pure marker, read back with
3936
+ * `isRoleGrantField(schema)`.
3937
+ *
3938
+ * What it changes: a form offers only the roles the person filling it may grant (#3248), decided by the rule the
3939
+ * server's role-ceiling gate applies (`resolveRoleGrantCeiling` in `@wildo-ai/saas-models`), plus any role the
3940
+ * record already holds, so a stored role is never dropped from view. It grants nothing and refuses nothing: the
3941
+ * server gate on the write is the control, and the marker only keeps the form from offering what that gate refuses.
3942
+ * Mark a field that a form offers roles in, where a write of it is checked against the writer's ceiling. A role
3943
+ * grant typed as free text (an SSO connection's `roleMapping`) offers no roles, so marking it would narrow nothing.
3944
+ */
3945
+ IS_ROLE_GRANT = "isRoleGrant",
3597
3946
  /**
3598
3947
  * RAG source: this field's TEXT is a retrieval-corpus source. The framework chunks the value
3599
3948
  * (extracting text first for file fields), stores the chunks in the application's OWN datastore
@@ -3664,6 +4013,16 @@ export declare enum ZodDecorator {
3664
4013
  * (single field vs composite). Mixing is rejected at decoration time.
3665
4014
  */
3666
4015
  OBJECT_BUSINESS_KEY_SET = "businessKeySet",
4016
+ /**
4017
+ * Object-level: one NON-unique index over several fields, for reads that filter on the leading fields and sort on
4018
+ * the last (#3142). Accumulates: each call declares one more index. See {@link CompoundIndexParams}.
4019
+ */
4020
+ OBJECT_COMPOUND_INDEX = "compoundIndex",
4021
+ /**
4022
+ * Object-level: the object (`{ resourceType, resourceId }`) names one row of a resource whose type is chosen at
4023
+ * run time from a published target set. See {@link ResourceReferenceParams}.
4024
+ */
4025
+ RESOURCE_REFERENCE = "isResourceReference",
3667
4026
  TTL = "ttl",
3668
4027
  IS_PASSWORD = "isPassword",
3669
4028
  IS_OTP = "isOtp",
@@ -3678,6 +4037,23 @@ export declare enum ZodDecorator {
3678
4037
  NUMBER_UI = "numberUI",
3679
4038
  ENUM_UI = "enumUI",
3680
4039
  DATE_UI = "dateUI",
4040
+ /**
4041
+ * A `Date` field that holds a CALENDAR DAY — a due date, a birthday, an invoice date — rather than an
4042
+ * instant. Declared with `z.date().isCalendarDate()`; read with `isCalendarDateSchema()`.
4043
+ *
4044
+ * A JavaScript `Date` is an instant, and an instant falls on different days in different time zones: a due
4045
+ * date picked as 3 Oct in Paris, stored as that browser's midnight, read 2 Oct in New York. A calendar day
4046
+ * has no zone. So a calendar date is held as UTC MIDNIGHT of its day, every reader sees that same day, and
4047
+ * the picker writes and reads the day itself. "Tomorrow" still means tomorrow in the READER's zone.
4048
+ *
4049
+ * Undeclared, a `Date` stays an instant (`createdAt`, `expiresAt`, `completedAt`…), shown in the reader's
4050
+ * configured time zone. A date RANGE (`z_range('date')`) is a range of calendar days without declaring it.
4051
+ *
4052
+ * It cannot be combined with `dateTimeUI()`, which declares a moment with a time of day: applying the second of
4053
+ * the two throws. "Today" for a calendar day, in its relative words, its picker, a new item's default and a
4054
+ * preset filter window alike, is today in the reader's resolved time zone.
4055
+ */
4056
+ IS_CALENDAR_DATE = "isCalendarDate",
3681
4057
  DATETIME_UI = "dateTimeUI",
3682
4058
  TIME_UI = "timeUI",
3683
4059
  MONEY_UI = "moneyUI",
@@ -3732,10 +4108,15 @@ export type ZodDecorator_ParamsType = {
3732
4108
  /**
3733
4109
  * The field's personal-data categories — `PersonalDataCategory` values, typed as plain strings at
3734
4110
  * this layer (zod-decorators sits below saas-models and cannot import the enum). Accepts a single
3735
- * value (the common case) or an array; the chokepoint normalizes either to a deduped array.
4111
+ * value (the common case) or an array, or the full {@link DataCategoriesFieldParams} object when
4112
+ * the fluent method's `options` argument was given; the chokepoint normalizes all three to that
4113
+ * object.
3736
4114
  */
3737
- [ZodDecorator.DATA_CATEGORIES]: string | readonly string[];
4115
+ [ZodDecorator.DATA_CATEGORIES]: string | readonly string[] | DataCategoriesFieldParams;
3738
4116
  [ZodDecorator.IS_AUDIT_EVIDENCE]: undefined;
4117
+ [ZodDecorator.IS_LAST_ACTION_RECORD]: undefined;
4118
+ [ZodDecorator.IS_DECIDER]: undefined;
4119
+ [ZodDecorator.IS_ROLE_GRANT]: undefined;
3739
4120
  /**
3740
4121
  * RAG-source declaration. The config object is optional — `.ragSource()` with no argument is a
3741
4122
  * complete declaration — and the chokepoint normalizes an omitted config to `{}` so every reader
@@ -3751,6 +4132,8 @@ export type ZodDecorator_ParamsType = {
3751
4132
  [ZodDecorator.IS_BUSINESS_KEY]: undefined;
3752
4133
  [ZodDecorator.DOCUMENT_SEQUENCE]: DocumentSequenceParams;
3753
4134
  [ZodDecorator.OBJECT_BUSINESS_KEY_SET]: BusinessKeySetParams | DecoratorUpdater<BusinessKeySetParams>;
4135
+ [ZodDecorator.OBJECT_COMPOUND_INDEX]: CompoundIndexParams;
4136
+ [ZodDecorator.RESOURCE_REFERENCE]: ResourceReferenceParams;
3754
4137
  [ZodDecorator.TTL]: TtlFieldParams | DecoratorUpdater<TtlFieldParams>;
3755
4138
  [ZodDecorator.IS_PASSWORD]: undefined;
3756
4139
  [ZodDecorator.IS_OTP]: OtpFieldParams | DecoratorUpdater<OtpFieldParams> | undefined;
@@ -3765,6 +4148,7 @@ export type ZodDecorator_ParamsType = {
3765
4148
  [ZodDecorator.NUMBER_UI]: NumberUIParams | DecoratorUpdater<NumberUIParams> | undefined;
3766
4149
  [ZodDecorator.ENUM_UI]: EnumUIParams | DecoratorUpdater<EnumUIParams> | undefined;
3767
4150
  [ZodDecorator.DATE_UI]: DateUIParams | DecoratorUpdater<DateUIParams> | undefined;
4151
+ [ZodDecorator.IS_CALENDAR_DATE]: undefined;
3768
4152
  [ZodDecorator.DATETIME_UI]: DateTimeUIParams | DecoratorUpdater<DateTimeUIParams> | undefined;
3769
4153
  [ZodDecorator.TIME_UI]: TimeUIParams | DecoratorUpdater<TimeUIParams> | undefined;
3770
4154
  [ZodDecorator.MONEY_UI]: MoneyUIParams | DecoratorUpdater<MoneyUIParams> | undefined;
@@ -3811,12 +4195,17 @@ export type ZodDecorator_Types = {
3811
4195
  * rejects blanks. */
3812
4196
  [ZodDecorator.PORTABILITY_PROVENANCE]: string;
3813
4197
  /** Personal-data categories — `PersonalDataCategory` values carried as opaque `string`s
3814
- * (zod-decorators sits below saas-models). ALWAYS the normalized array, deduped and non-empty:
3815
- * the chokepoint widens a bare single-value authoring form on the way in, so every reader sees
3816
- * one shape and `getDataCategories` can answer "is this field categorized?" by presence alone. */
3817
- [ZodDecorator.DATA_CATEGORIES]: readonly string[];
4198
+ * (zod-decorators sits below saas-models), plus the optional `personalOnlyWhen` condition (#2119).
4199
+ * ALWAYS the normalized object, its `categories` deduped and non-empty: the chokepoint widens the
4200
+ * bare-value and array authoring forms on the way in, so every reader sees one shape and
4201
+ * `getDataCategories` can answer "is this field categorized?" by presence alone. */
4202
+ [ZodDecorator.DATA_CATEGORIES]: DataCategoriesFieldParams;
3818
4203
  /** Audit-evidence marker — stored as the bare `true` sentinel (parameterless, like IS_BUSINESS_KEY). */
3819
4204
  [ZodDecorator.IS_AUDIT_EVIDENCE]: true;
4205
+ /** Last-action-record marker — stored as the bare `true` sentinel (parameterless, like IS_AUDIT_EVIDENCE). */
4206
+ [ZodDecorator.IS_LAST_ACTION_RECORD]: true;
4207
+ [ZodDecorator.IS_DECIDER]: true;
4208
+ [ZodDecorator.IS_ROLE_GRANT]: true;
3820
4209
  /** RAG-source config — ALWAYS the normalized object ( `{}` when authored with no argument): the
3821
4210
  * chokepoint validates and normalizes on the way in, so every reader sees one shape and
3822
4211
  * `getRagSourceConfig` can answer "is this field a source?" by presence alone. */
@@ -3830,6 +4219,8 @@ export type ZodDecorator_Types = {
3830
4219
  [ZodDecorator.IS_BUSINESS_KEY]: true;
3831
4220
  [ZodDecorator.DOCUMENT_SEQUENCE]: DocumentSequenceParams;
3832
4221
  [ZodDecorator.OBJECT_BUSINESS_KEY_SET]: BusinessKeySetParams;
4222
+ [ZodDecorator.OBJECT_COMPOUND_INDEX]: CompoundIndexParams[];
4223
+ [ZodDecorator.RESOURCE_REFERENCE]: ResourceReferenceParams;
3833
4224
  [ZodDecorator.TTL]: TtlFieldParams;
3834
4225
  [ZodDecorator.IS_PASSWORD]: true;
3835
4226
  [ZodDecorator.IS_OTP]: OtpFieldParams | true;
@@ -3844,6 +4235,7 @@ export type ZodDecorator_Types = {
3844
4235
  [ZodDecorator.NUMBER_UI]: NumberUIParams;
3845
4236
  [ZodDecorator.ENUM_UI]: EnumUIParams;
3846
4237
  [ZodDecorator.DATE_UI]: DateUIParams;
4238
+ [ZodDecorator.IS_CALENDAR_DATE]: true;
3847
4239
  [ZodDecorator.DATETIME_UI]: DateTimeUIParams;
3848
4240
  [ZodDecorator.TIME_UI]: TimeUIParams;
3849
4241
  [ZodDecorator.MONEY_UI]: MoneyUIParams;
@@ -3890,6 +4282,9 @@ export interface CustomFieldMetadata {
3890
4282
  [ZodDecorator.PORTABILITY_PROVENANCE]?: ZodDecorator_Types[ZodDecorator.PORTABILITY_PROVENANCE];
3891
4283
  [ZodDecorator.DATA_CATEGORIES]?: ZodDecorator_Types[ZodDecorator.DATA_CATEGORIES];
3892
4284
  [ZodDecorator.IS_AUDIT_EVIDENCE]?: ZodDecorator_Types[ZodDecorator.IS_AUDIT_EVIDENCE];
4285
+ [ZodDecorator.IS_LAST_ACTION_RECORD]?: ZodDecorator_Types[ZodDecorator.IS_LAST_ACTION_RECORD];
4286
+ [ZodDecorator.IS_DECIDER]?: ZodDecorator_Types[ZodDecorator.IS_DECIDER];
4287
+ [ZodDecorator.IS_ROLE_GRANT]?: ZodDecorator_Types[ZodDecorator.IS_ROLE_GRANT];
3893
4288
  [ZodDecorator.RAG_SOURCE]?: ZodDecorator_Types[ZodDecorator.RAG_SOURCE];
3894
4289
  [ZodDecorator.IS_VIRTUAL]?: ZodDecorator_Types[ZodDecorator.IS_VIRTUAL];
3895
4290
  [ZodDecorator.IS_DISCRIMINATOR]?: ZodDecorator_Types[ZodDecorator.IS_DISCRIMINATOR];
@@ -3900,6 +4295,8 @@ export interface CustomFieldMetadata {
3900
4295
  [ZodDecorator.IS_BUSINESS_KEY]?: ZodDecorator_Types[ZodDecorator.IS_BUSINESS_KEY];
3901
4296
  [ZodDecorator.DOCUMENT_SEQUENCE]?: ZodDecorator_Types[ZodDecorator.DOCUMENT_SEQUENCE];
3902
4297
  [ZodDecorator.OBJECT_BUSINESS_KEY_SET]?: ZodDecorator_Types[ZodDecorator.OBJECT_BUSINESS_KEY_SET];
4298
+ [ZodDecorator.OBJECT_COMPOUND_INDEX]?: ZodDecorator_Types[ZodDecorator.OBJECT_COMPOUND_INDEX];
4299
+ [ZodDecorator.RESOURCE_REFERENCE]?: ZodDecorator_Types[ZodDecorator.RESOURCE_REFERENCE];
3903
4300
  [ZodDecorator.TTL]?: ZodDecorator_Types[ZodDecorator.TTL];
3904
4301
  [ZodDecorator.IS_PASSWORD]?: ZodDecorator_Types[ZodDecorator.IS_PASSWORD];
3905
4302
  [ZodDecorator.IS_OTP]?: ZodDecorator_Types[ZodDecorator.IS_OTP];
@@ -3914,6 +4311,7 @@ export interface CustomFieldMetadata {
3914
4311
  [ZodDecorator.NUMBER_UI]?: ZodDecorator_Types[ZodDecorator.NUMBER_UI];
3915
4312
  [ZodDecorator.ENUM_UI]?: ZodDecorator_Types[ZodDecorator.ENUM_UI];
3916
4313
  [ZodDecorator.DATE_UI]?: ZodDecorator_Types[ZodDecorator.DATE_UI];
4314
+ [ZodDecorator.IS_CALENDAR_DATE]?: ZodDecorator_Types[ZodDecorator.IS_CALENDAR_DATE];
3917
4315
  [ZodDecorator.DATETIME_UI]?: ZodDecorator_Types[ZodDecorator.DATETIME_UI];
3918
4316
  [ZodDecorator.TIME_UI]?: ZodDecorator_Types[ZodDecorator.TIME_UI];
3919
4317
  [ZodDecorator.MONEY_UI]?: ZodDecorator_Types[ZodDecorator.MONEY_UI];
@@ -3986,7 +4384,7 @@ export declare enum RequestDtoWriteSemantics {
3986
4384
  }
3987
4385
  /** @wildo_source:part:end zod.decorators.schema-metadata */
3988
4386
  type ZodenumInstance = ReturnType<typeof z.enum>;
3989
- export type PrimitiveZodType = z.ZodString | z.ZodNumber | z.ZodBoolean | z.ZodDate | z.ZodEnum<any> | ZodenumInstance | ZodBrandedColorInstance | DecoratedPrimitiveZodType<z.ZodString> | DecoratedPrimitiveZodType<z.ZodNumber> | DecoratedPrimitiveZodType<z.ZodBoolean> | DecoratedPrimitiveZodType<z.ZodDate> | DecoratedPrimitiveZodType<z.ZodEnum<any>> | DecoratedPrimitiveZodType<ZodenumInstance>;
4387
+ export type PrimitiveZodType = z.ZodString | z.ZodNumber | z.ZodBoolean | z.ZodDate | z.ZodEnum<any> | ZodenumInstance | ZodBrandedColorInstance;
3990
4388
  export type SerializableZodSchema = {
3991
4389
  type: 'string';
3992
4390
  optional?: boolean;