@fieldia/core 0.22.0 → 0.24.0

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 (177) hide show
  1. package/README.md +135 -3
  2. package/package.json +1 -1
  3. package/schema/page.schema.json +159 -4
  4. package/src/index.d.mts +6 -2
  5. package/src/index.d.ts +6 -2
  6. package/src/index.js +18 -1
  7. package/src/index.js.map +1 -1
  8. package/src/index.mjs +8 -2
  9. package/src/index.mjs.map +1 -1
  10. package/src/lib/format/autofill-group.d.mts +12 -0
  11. package/src/lib/format/autofill-group.d.ts +12 -0
  12. package/src/lib/format/autofill-group.js +23 -0
  13. package/src/lib/format/autofill-group.js.map +1 -0
  14. package/src/lib/format/autofill-group.mjs +19 -0
  15. package/src/lib/format/autofill-group.mjs.map +1 -0
  16. package/src/lib/format/autofill.d.mts +14 -0
  17. package/src/lib/format/autofill.d.ts +14 -0
  18. package/src/lib/format/autofill.js +54 -0
  19. package/src/lib/format/autofill.js.map +1 -0
  20. package/src/lib/format/autofill.mjs +48 -0
  21. package/src/lib/format/autofill.mjs.map +1 -0
  22. package/src/lib/format/bulk-edit.d.mts +18 -0
  23. package/src/lib/format/bulk-edit.d.ts +18 -0
  24. package/src/lib/format/bulk-edit.js +26 -0
  25. package/src/lib/format/bulk-edit.js.map +1 -0
  26. package/src/lib/format/bulk-edit.mjs +21 -0
  27. package/src/lib/format/bulk-edit.mjs.map +1 -0
  28. package/src/lib/format/field.d.mts +55 -34
  29. package/src/lib/format/field.d.ts +55 -34
  30. package/src/lib/format/field.js +16 -2
  31. package/src/lib/format/field.js.map +1 -1
  32. package/src/lib/format/field.mjs +15 -1
  33. package/src/lib/format/field.mjs.map +1 -1
  34. package/src/lib/format/layout.d.mts +365 -66
  35. package/src/lib/format/layout.d.ts +365 -66
  36. package/src/lib/format/layout.js +22 -4
  37. package/src/lib/format/layout.js.map +1 -1
  38. package/src/lib/format/layout.mjs +20 -3
  39. package/src/lib/format/layout.mjs.map +1 -1
  40. package/src/lib/format/page.d.mts +169 -29
  41. package/src/lib/format/page.d.ts +169 -29
  42. package/src/lib/format/page.js +11 -0
  43. package/src/lib/format/page.js.map +1 -1
  44. package/src/lib/format/page.mjs +12 -1
  45. package/src/lib/format/page.mjs.map +1 -1
  46. package/src/lib/format/phone.d.mts +27 -0
  47. package/src/lib/format/phone.d.ts +27 -0
  48. package/src/lib/format/phone.js +43 -0
  49. package/src/lib/format/phone.js.map +1 -0
  50. package/src/lib/format/phone.mjs +39 -0
  51. package/src/lib/format/phone.mjs.map +1 -0
  52. package/src/lib/format/references.d.mts +10 -0
  53. package/src/lib/format/references.d.ts +10 -0
  54. package/src/lib/format/references.js +96 -1
  55. package/src/lib/format/references.js.map +1 -1
  56. package/src/lib/format/references.mjs +96 -1
  57. package/src/lib/format/references.mjs.map +1 -1
  58. package/src/lib/format/slots-options.d.mts +26 -0
  59. package/src/lib/format/slots-options.d.ts +26 -0
  60. package/src/lib/format/slots-options.js +35 -0
  61. package/src/lib/format/slots-options.js.map +1 -0
  62. package/src/lib/format/slots-options.mjs +31 -0
  63. package/src/lib/format/slots-options.mjs.map +1 -0
  64. package/src/lib/format/upgrade.d.mts +4 -1
  65. package/src/lib/format/upgrade.d.ts +4 -1
  66. package/src/lib/format/upgrade.js +4 -1
  67. package/src/lib/format/upgrade.js.map +1 -1
  68. package/src/lib/format/upgrade.mjs +4 -1
  69. package/src/lib/format/upgrade.mjs.map +1 -1
  70. package/src/lib/format/version.d.mts +2 -2
  71. package/src/lib/format/version.d.ts +2 -2
  72. package/src/lib/format/version.js +2 -2
  73. package/src/lib/format/version.js.map +1 -1
  74. package/src/lib/format/version.mjs +2 -2
  75. package/src/lib/format/version.mjs.map +1 -1
  76. package/src/lib/format/zoned.d.mts +8 -0
  77. package/src/lib/format/zoned.d.ts +8 -0
  78. package/src/lib/format/zoned.js +36 -0
  79. package/src/lib/format/zoned.js.map +1 -0
  80. package/src/lib/format/zoned.mjs +33 -0
  81. package/src/lib/format/zoned.mjs.map +1 -0
  82. package/src/lib/record/check.d.mts +6 -1
  83. package/src/lib/record/check.d.ts +6 -1
  84. package/src/lib/record/check.js +12 -4
  85. package/src/lib/record/check.js.map +1 -1
  86. package/src/lib/record/check.mjs +12 -4
  87. package/src/lib/record/check.mjs.map +1 -1
  88. package/src/lib/record/data-source.d.mts +188 -3
  89. package/src/lib/record/data-source.d.ts +188 -3
  90. package/src/lib/record/data-source.js.map +1 -1
  91. package/src/lib/record/data-source.mjs.map +1 -1
  92. package/src/lib/record/form.d.mts +72 -25
  93. package/src/lib/record/form.d.ts +72 -25
  94. package/src/lib/record/form.js +404 -78
  95. package/src/lib/record/form.js.map +1 -1
  96. package/src/lib/record/form.mjs +404 -78
  97. package/src/lib/record/form.mjs.map +1 -1
  98. package/src/lib/record/input-rules.d.mts +51 -9
  99. package/src/lib/record/input-rules.d.ts +51 -9
  100. package/src/lib/record/input-rules.js +119 -20
  101. package/src/lib/record/input-rules.js.map +1 -1
  102. package/src/lib/record/input-rules.mjs +118 -19
  103. package/src/lib/record/input-rules.mjs.map +1 -1
  104. package/src/lib/record/inputs.js +4 -2
  105. package/src/lib/record/inputs.js.map +1 -1
  106. package/src/lib/record/inputs.mjs +4 -2
  107. package/src/lib/record/inputs.mjs.map +1 -1
  108. package/src/lib/record/limits.d.mts +1 -1
  109. package/src/lib/record/limits.d.ts +1 -1
  110. package/src/lib/record/limits.js +4 -2
  111. package/src/lib/record/limits.js.map +1 -1
  112. package/src/lib/record/limits.mjs +4 -2
  113. package/src/lib/record/limits.mjs.map +1 -1
  114. package/src/lib/record/locales/ar.js +3 -2
  115. package/src/lib/record/locales/ar.js.map +1 -1
  116. package/src/lib/record/locales/ar.mjs +3 -2
  117. package/src/lib/record/locales/ar.mjs.map +1 -1
  118. package/src/lib/record/locales/de.js +3 -2
  119. package/src/lib/record/locales/de.js.map +1 -1
  120. package/src/lib/record/locales/de.mjs +3 -2
  121. package/src/lib/record/locales/de.mjs.map +1 -1
  122. package/src/lib/record/locales/fr.js +3 -2
  123. package/src/lib/record/locales/fr.js.map +1 -1
  124. package/src/lib/record/locales/fr.mjs +3 -2
  125. package/src/lib/record/locales/fr.mjs.map +1 -1
  126. package/src/lib/record/memory-data-source.d.mts +13 -2
  127. package/src/lib/record/memory-data-source.d.ts +13 -2
  128. package/src/lib/record/memory-data-source.js +92 -35
  129. package/src/lib/record/memory-data-source.js.map +1 -1
  130. package/src/lib/record/memory-data-source.mjs +93 -36
  131. package/src/lib/record/memory-data-source.mjs.map +1 -1
  132. package/src/lib/record/memory-schedules.d.mts +45 -0
  133. package/src/lib/record/memory-schedules.d.ts +45 -0
  134. package/src/lib/record/memory-schedules.js +81 -0
  135. package/src/lib/record/memory-schedules.js.map +1 -0
  136. package/src/lib/record/memory-schedules.mjs +78 -0
  137. package/src/lib/record/memory-schedules.mjs.map +1 -0
  138. package/src/lib/record/messages.d.mts +3 -1
  139. package/src/lib/record/messages.d.ts +3 -1
  140. package/src/lib/record/messages.js +3 -2
  141. package/src/lib/record/messages.js.map +1 -1
  142. package/src/lib/record/messages.mjs +3 -2
  143. package/src/lib/record/messages.mjs.map +1 -1
  144. package/src/lib/record/periods.d.mts +47 -0
  145. package/src/lib/record/periods.d.ts +47 -0
  146. package/src/lib/record/periods.js +86 -0
  147. package/src/lib/record/periods.js.map +1 -0
  148. package/src/lib/record/periods.mjs +79 -0
  149. package/src/lib/record/periods.mjs.map +1 -0
  150. package/src/lib/record/rules.js +5 -6
  151. package/src/lib/record/rules.js.map +1 -1
  152. package/src/lib/record/rules.mjs +5 -6
  153. package/src/lib/record/rules.mjs.map +1 -1
  154. package/src/lib/record/run.d.mts +44 -8
  155. package/src/lib/record/run.d.ts +44 -8
  156. package/src/lib/record/run.js +155 -26
  157. package/src/lib/record/run.js.map +1 -1
  158. package/src/lib/record/run.mjs +156 -27
  159. package/src/lib/record/run.mjs.map +1 -1
  160. package/src/lib/record/search.d.mts +13 -3
  161. package/src/lib/record/search.d.ts +13 -3
  162. package/src/lib/record/search.js +11 -4
  163. package/src/lib/record/search.js.map +1 -1
  164. package/src/lib/record/search.mjs +11 -4
  165. package/src/lib/record/search.mjs.map +1 -1
  166. package/src/lib/record/zones.d.mts +57 -0
  167. package/src/lib/record/zones.d.ts +57 -0
  168. package/src/lib/record/zones.js +147 -0
  169. package/src/lib/record/zones.js.map +1 -0
  170. package/src/lib/record/zones.mjs +132 -0
  171. package/src/lib/record/zones.mjs.map +1 -0
  172. package/src/runtime.d.mts +12 -5
  173. package/src/runtime.d.ts +12 -5
  174. package/src/runtime.js +32 -2
  175. package/src/runtime.js.map +1 -1
  176. package/src/runtime.mjs +7 -1
  177. package/src/runtime.mjs.map +1 -1
@@ -2,6 +2,7 @@ import type { FilterCondition, Fields, Option, PropertyDefinition } from '../for
2
2
  import type { JsonValue } from '../format/json';
3
3
  import type { FileValue, RecordId, RelatedRecord, Values } from './values';
4
4
  import type { RunStop } from './run';
5
+ import type { DateInterval } from './periods';
5
6
  /**
6
7
  * How Fieldia reaches data. Fieldia knows no backend: an app implements this
7
8
  * interface for its own (a Flectra adapter, a REST client, a survey store),
@@ -68,6 +69,93 @@ export interface DataSource {
68
69
  * size) and is shown as it is.
69
70
  */
70
71
  upload?(file: UploadFile, request: UploadRequest): Promise<UploadedFile>;
72
+ /**
73
+ * The times free to book, for a date and time drawn as booking slots
74
+ * (`"widget": "slots"`): each day asked — the days of a month, in the
75
+ * field's time zone — with its state (open, full, closed, a holiday) and
76
+ * the times a booking may start on it; asked again for the day of a time
77
+ * picked, so a time someone else took meanwhile is said, never booked.
78
+ * Without it, such a field is the plain date and time box.
79
+ */
80
+ slots?(request: SlotsRequest): Promise<SlotsResult>;
81
+ /**
82
+ * Change many records at once, the same value in each field given: a
83
+ * list's bulk edit, of the rows ticked or of every record the search
84
+ * finds. Answer with those not changed and why; every other one is taken
85
+ * as changed. Without it, the list saves each in turn (`save`, `partial`).
86
+ * Refuse a record this person may not change, or one changed since the
87
+ * version it was listed at (`versions`), as a save would.
88
+ */
89
+ writeMany?(request: WriteManyRequest): Promise<WriteManyResult>;
90
+ /**
91
+ * Write a list's export on the server, in place of the browser: for
92
+ * exports too big for a browser, or files the server words its own way.
93
+ * Answer with the file — its bytes (`data`: a Blob fits) or its address
94
+ * (`url`) — for the browser to save; with `{ message }` when the server
95
+ * hands it over its own way (an email on its way), the list saying it; or
96
+ * with `false` to let the list write it in the browser after all, as it
97
+ * does without this (a small export, say).
98
+ */
99
+ exportRecords?(request: ExportRequest): Promise<ExportedFile | {
100
+ message: string;
101
+ } | false>;
102
+ }
103
+ /** The same value written in a field of each record: a list's bulk edit. */
104
+ export interface WriteManyRequest {
105
+ model: string;
106
+ ids: RecordId[];
107
+ /** The fields written and their value, the same in each record: `{ state: 'active' }`. */
108
+ values: Values;
109
+ /** Those fields' definitions, as a save gets them. */
110
+ fields: Fields;
111
+ /** The version each record was listed at, by its id, where the list knew it: one changed since is refused, as a save over someone else's. */
112
+ versions?: Record<string, RecordVersion>;
113
+ }
114
+ export interface WriteManyResult {
115
+ /** Each record not changed, with why in the person's words — and, where the source has it, the problem as a save would throw it (a conflict's who and when). */
116
+ failed: {
117
+ id: RecordId;
118
+ message: string;
119
+ problem?: SaveProblem;
120
+ }[];
121
+ }
122
+ /** A list's records to put in a file: those ticked, or every one its search finds. */
123
+ export interface ExportRequest {
124
+ model: string;
125
+ format: 'xlsx' | 'csv';
126
+ /** The list's columns, in its order, each with its header's words. */
127
+ columns: {
128
+ field: string;
129
+ label: string;
130
+ }[];
131
+ /** The records ticked, by id; left out, every record `filter` finds. */
132
+ ids?: RecordId[];
133
+ /** The list's search, as `list` gets it. */
134
+ filter: ResolvedFilter[];
135
+ /** The list's order now. */
136
+ sort: SortOrder[];
137
+ /** How many records go. */
138
+ count: number;
139
+ /** The file's name, as the list would give it: "Customers 2026-10-16.xlsx". */
140
+ name: string;
141
+ /** The page's language, for values written as people there read them. */
142
+ locale: string;
143
+ }
144
+ /** A file for the browser to save: its bytes, or where to fetch them. */
145
+ export interface ExportedFile {
146
+ name: string;
147
+ /** Its media type, when `data` does not say it. */
148
+ type?: string;
149
+ /** Its bytes: a Blob, an ArrayBuffer, a typed array, or text. */
150
+ data?: ExportBytes | ArrayBuffer | ArrayBufferView | string;
151
+ /** Where the browser fetches it, in place of `data`. */
152
+ url?: string;
153
+ }
154
+ /** A Blob, as far as an export reads one: core names no DOM types. */
155
+ export interface ExportBytes {
156
+ readonly size: number;
157
+ readonly type: string;
158
+ arrayBuffer(): Promise<ArrayBuffer>;
71
159
  }
72
160
  /**
73
161
  * Which state of a record a page opened, as its source keeps it: an etag, a
@@ -207,6 +295,15 @@ export interface SearchRequest {
207
295
  /** The field's filter, with every `valueFrom` already replaced by its value. */
208
296
  filter?: ResolvedFilter[];
209
297
  limit?: number;
298
+ /**
299
+ * Only the records named exactly `query`, in any case — not every one whose
300
+ * name holds it. A box asks so for a name typed when a search's first
301
+ * answers, cut at their limit, did not hold it ("Nile" after ten "Blue
302
+ * Nile"s), and to tell whether more than one record has that name. A source
303
+ * that does not know it searches as ever: the box looks for the name among
304
+ * what comes.
305
+ */
306
+ exact?: boolean;
210
307
  }
211
308
  export type ResolvedFilterCondition = Omit<FilterCondition, 'valueFrom' | 'value'> & {
212
309
  value: JsonValue;
@@ -258,7 +355,23 @@ export interface ListedRecord {
258
355
  /** What the person may do with it: its row's buttons and a bulk edit keep to it. */
259
356
  can?: RecordPermissions;
260
357
  }
261
- export interface GroupRequest {
358
+ /**
359
+ * How a date's records are grouped: by the day, week, month, quarter or year
360
+ * each falls in rather than by its exact value. Each group's `value` is then
361
+ * the first day of its span — "2026-03-01" for March 2026, "2026-03-09" for
362
+ * the week from 9 March — the page writing its words ("March 2026", "Week 11,
363
+ * 2026"); the groups in time order, those with no date last. `spanStart`
364
+ * counts a value's span as a source should.
365
+ */
366
+ export interface GroupInterval {
367
+ /** For a date or a date and time field: group by the span each record falls in. */
368
+ interval?: DateInterval;
369
+ /** With `interval: "week"`: the day a week starts on, 0 Sunday to 6 Saturday — the page's language's. */
370
+ weekStart?: number;
371
+ /** With `interval`, for a date and time field: the time zone its days are told in (IANA: "Africa/Cairo") — the page's. */
372
+ timeZone?: string;
373
+ }
374
+ export interface GroupRequest extends GroupInterval {
262
375
  model: string;
263
376
  field: string;
264
377
  filter: ResolvedFilter[];
@@ -271,14 +384,22 @@ export interface Group {
271
384
  }
272
385
  /** How a field is totalled: added up, averaged, its least or its most, or how many records hold a value in it. */
273
386
  export type TotalKind = 'sum' | 'avg' | 'min' | 'max' | 'count';
274
- export interface TotalsRequest {
387
+ export interface TotalsRequest extends GroupInterval {
275
388
  model: string;
276
389
  /** The list's search, as `list` gets it. For the ticked rows alone, add `{ field: 'id', op: 'in', value: ids }`. */
277
390
  filter: ResolvedFilter[];
278
391
  /** What to total, by field: `{ amount: ['sum'], hours: ['sum', 'avg'] }`. */
279
392
  totals: Record<string, TotalKind[]>;
280
- /** Total under each value of this field as well, grouped as `groups` groups it: a Group By heading's sums. */
393
+ /** Total under each value of this field as well, grouped as `groups` groups it (by `interval`, for a date): a Group By heading's sums. */
281
394
  groupBy?: string;
395
+ /**
396
+ * The field holding each amount's currency, by the money field, where each
397
+ * record keeps its own: `{ amount: 'currency_id' }`. Such an amount is
398
+ * totalled within one currency only: over records in more than one, its
399
+ * sum, average, least and most are null — no honest number — and the
400
+ * result names the currency of those it could total (`currencies`).
401
+ */
402
+ currencies?: Record<string, string>;
282
403
  }
283
404
  /**
284
405
  * Each field's totals, by field and then by kind: `totals.amount.sum`. The
@@ -293,9 +414,19 @@ export interface TotalsResult {
293
414
  totals: Totals;
294
415
  /** With `groupBy`: each value's totals, in the order `groups` gives them. */
295
416
  groups?: TotalsGroup[];
417
+ /**
418
+ * With `currencies` asked: the currency each money field's totals are in,
419
+ * by field, as its currency field holds it (a link as `{ id, label }`, a
420
+ * code as "EGP"); null where its records hold more than one, or none. A
421
+ * page writes an amount's total only in a currency it knows: the field's
422
+ * own, or the one named here.
423
+ */
424
+ currencies?: Record<string, JsonValue>;
296
425
  }
297
426
  export interface TotalsGroup extends Group {
298
427
  totals: Totals;
428
+ /** The currency of each money field's totals under this value, as `TotalsResult.currencies` says it. */
429
+ currencies?: Record<string, JsonValue>;
299
430
  }
300
431
  /**
301
432
  * A file to store: what an upload reads of a browser's `File`. Core names no
@@ -358,6 +489,23 @@ export interface SubmitRequest {
358
489
  pageId: string;
359
490
  /** The answers to the questions that were shown. Skipped steps are left out. */
360
491
  values: Values;
492
+ /** On a page with a bot check (`botCheck`): what it saw, for the app to decide on. The page never refuses a send for it. */
493
+ bot?: BotReport;
494
+ }
495
+ /**
496
+ * What a page's bot check saw as its answers were sent. A person never fills the hidden box and takes longer than a
497
+ * few seconds; a send that is either is likely a bot's — or a person's whose browser filled every box at once, which is
498
+ * why the app decides: keep it aside, ask the captcha's service, or refuse it.
499
+ */
500
+ export interface BotReport {
501
+ /** The hidden box was filled: no person sees it, reaches it with Tab or hears it read out. */
502
+ honeypot: boolean;
503
+ /** Sent sooner than the page's `minSeconds` after it was shown, or started again. */
504
+ tooFast: boolean;
505
+ /** Seconds from the page shown (or started again) to this send, to a tenth. */
506
+ seconds: number;
507
+ /** The captcha's token, on a page with one (`botCheck.captcha`), for the app to check with its service: null when none came. */
508
+ captcha?: string | null;
361
509
  }
362
510
  export interface SubmitResult {
363
511
  id?: RecordId;
@@ -444,3 +592,40 @@ export declare function saveRefused(problem: SaveProblem): Error & {
444
592
  };
445
593
  /** What a failed save tells: its own problem, a failed fetch as the network, anything else as it is. */
446
594
  export declare function saveProblemOf(error: unknown): SaveProblem;
595
+ /** What a booking's free times are asked with: the days, the zone they are in, and what the page knows of the booking. */
596
+ export interface SlotsRequest {
597
+ /** The page's model, for a record page; empty for a responses page. */
598
+ model: string;
599
+ /** The date and time field booked. */
600
+ field: string;
601
+ /** The record booked, so its own booking counts as free for it; null for a new one. */
602
+ recordId: RecordId | null;
603
+ /** The schedule the field's node names (`options.schedule`): a doctor's, a showroom's. The field's name unless it names one. */
604
+ schedule: string;
605
+ /** The first and the last day asked, both included, as 2026-10-01: days in `timeZone`. */
606
+ from: string;
607
+ to: string;
608
+ /** The zone the days are in, by its IANA name: the field's, its page's, or the browser's. */
609
+ timeZone: string;
610
+ /** The minutes a booking lasts, and those kept free between two, as the page says (`options.length`, `options.between`). */
611
+ length?: number;
612
+ between?: number;
613
+ /** Every value of the form: the doctor, the service, the car to drive. */
614
+ values: Values;
615
+ }
616
+ /** What a day is for booking: times free on it, all taken, not open, or a holiday. */
617
+ export type SlotDayState = 'open' | 'full' | 'closed' | 'holiday';
618
+ /** One day of a booking's: its state and the times a booking may start on it. */
619
+ export interface SlotDay {
620
+ state: SlotDayState;
621
+ /** The times free, as instants (ISO 8601, in UTC or with their offset: 2026-10-15T09:30:00+03:00), earliest first. */
622
+ free?: string[];
623
+ /** Words for the day, in the person's language: a holiday's name, why it is closed. */
624
+ note?: string;
625
+ }
626
+ /** The days asked, each by its date (2026-10-15): a day left out is closed. */
627
+ export interface SlotsResult {
628
+ days: Record<string, SlotDay>;
629
+ /** The minutes a booking lasts, when the source knows better than the page: the service's own length. */
630
+ length?: number;
631
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"data-source.js","sourceRoot":"","sources":["../../../../../../libs/core/src/lib/record/data-source.ts"],"names":[],"mappings":";;;AAyHA,oCAEC;AAGD,wCAGC;AA0UD,kCAEC;AAGD,sCAMC;AA9VD,6GAA6G;AAC7G,SAAgB,YAAY,CAAC,MAAc,EAAE,OAA6D,EAAE;IAC1G,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;AAC/H,CAAC;AAED,6HAA6H;AAC7H,SAAgB,cAAc,CAAC,MAA6B;IAC1D,MAAM,MAAM,GAAG,MAA+B,CAAC;IAC/C,OAAO,MAAM,CAAC,OAAO,KAAK,IAAI,IAAI,MAAM,CAAC,MAAM,KAAK,IAAI,IAAI,OAAO,MAAM,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAE,MAAuB,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAgB,EAAE,CAAC;AACzK,CAAC;AAwTD;;;;;GAKG;AACH,MAAa,aAAc,SAAQ,KAAK;IAC7B,QAAQ,CAAe;IACvB,OAAO,CAAc;IAC9B,YAAY,QAAsB,EAAE,OAAO,GAAG,sDAAsD;QAClG,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,eAAe,CAAC;QAC5B,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,OAAO,GAAG,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;IACzD,CAAC;CACF;AATD,sCASC;AAED,8EAA8E;AAC9E,SAAgB,WAAW,CAAC,OAAoB;IAC9C,OAAO,MAAM,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;AAChE,CAAC;AAED,wGAAwG;AACxG,SAAgB,aAAa,CAAC,KAAc;IAC1C,MAAM,OAAO,GAAI,KAA0C,EAAE,OAAO,CAAC;IACrE,IAAI,OAAO,IAAI,OAAO,OAAO,CAAC,IAAI,KAAK,QAAQ;QAAE,OAAO,OAAO,CAAC;IAChE,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACvE,IAAI,KAAK,YAAY,SAAS,IAAI,4BAA4B,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC;IAClH,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;AACpC,CAAC"}
1
+ {"version":3,"file":"data-source.js","sourceRoot":"","sources":["../../../../../../libs/core/src/lib/record/data-source.ts"],"names":[],"mappings":";;;AA6MA,oCAEC;AAGD,wCAGC;AAwYD,kCAEC;AAGD,sCAMC;AA5ZD,6GAA6G;AAC7G,SAAgB,YAAY,CAAC,MAAc,EAAE,OAA6D,EAAE;IAC1G,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;AAC/H,CAAC;AAED,6HAA6H;AAC7H,SAAgB,cAAc,CAAC,MAA6B;IAC1D,MAAM,MAAM,GAAG,MAA+B,CAAC;IAC/C,OAAO,MAAM,CAAC,OAAO,KAAK,IAAI,IAAI,MAAM,CAAC,MAAM,KAAK,IAAI,IAAI,OAAO,MAAM,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAE,MAAuB,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAgB,EAAE,CAAC;AACzK,CAAC;AAsXD;;;;;GAKG;AACH,MAAa,aAAc,SAAQ,KAAK;IAC7B,QAAQ,CAAe;IACvB,OAAO,CAAc;IAC9B,YAAY,QAAsB,EAAE,OAAO,GAAG,sDAAsD;QAClG,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,eAAe,CAAC;QAC5B,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,OAAO,GAAG,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;IACzD,CAAC;CACF;AATD,sCASC;AAED,8EAA8E;AAC9E,SAAgB,WAAW,CAAC,OAAoB;IAC9C,OAAO,MAAM,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;AAChE,CAAC;AAED,wGAAwG;AACxG,SAAgB,aAAa,CAAC,KAAc;IAC1C,MAAM,OAAO,GAAI,KAA0C,EAAE,OAAO,CAAC;IACrE,IAAI,OAAO,IAAI,OAAO,OAAO,CAAC,IAAI,KAAK,QAAQ;QAAE,OAAO,OAAO,CAAC;IAChE,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACvE,IAAI,KAAK,YAAY,SAAS,IAAI,4BAA4B,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC;IAClH,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;AACpC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"data-source.mjs","sourceRoot":"","sources":["../../../../../../libs/core/src/lib/record/data-source.ts"],"names":[],"mappings":"AAwHA,6GAA6G;AAC7G,MAAM,UAAU,YAAY,CAAC,MAAc,EAAE,OAA6D,EAAE;IAC1G,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;AAC/H,CAAC;AAED,6HAA6H;AAC7H,MAAM,UAAU,cAAc,CAAC,MAA6B;IAC1D,MAAM,MAAM,GAAG,MAA+B,CAAC;IAC/C,OAAO,MAAM,CAAC,OAAO,KAAK,IAAI,IAAI,MAAM,CAAC,MAAM,KAAK,IAAI,IAAI,OAAO,MAAM,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAE,MAAuB,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAgB,EAAE,CAAC;AACzK,CAAC;AAwTD;;;;;GAKG;AACH,MAAM,OAAO,aAAc,SAAQ,KAAK;IAC7B,QAAQ,CAAe;IACvB,OAAO,CAAc;IAC9B,YAAY,QAAsB,EAAE,OAAO,GAAG,sDAAsD;QAClG,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,eAAe,CAAC;QAC5B,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,OAAO,GAAG,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;IACzD,CAAC;CACF;AAED,8EAA8E;AAC9E,MAAM,UAAU,WAAW,CAAC,OAAoB;IAC9C,OAAO,MAAM,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;AAChE,CAAC;AAED,wGAAwG;AACxG,MAAM,UAAU,aAAa,CAAC,KAAc;IAC1C,MAAM,OAAO,GAAI,KAA0C,EAAE,OAAO,CAAC;IACrE,IAAI,OAAO,IAAI,OAAO,OAAO,CAAC,IAAI,KAAK,QAAQ;QAAE,OAAO,OAAO,CAAC;IAChE,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACvE,IAAI,KAAK,YAAY,SAAS,IAAI,4BAA4B,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC;IAClH,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;AACpC,CAAC"}
1
+ {"version":3,"file":"data-source.mjs","sourceRoot":"","sources":["../../../../../../libs/core/src/lib/record/data-source.ts"],"names":[],"mappings":"AA4MA,6GAA6G;AAC7G,MAAM,UAAU,YAAY,CAAC,MAAc,EAAE,OAA6D,EAAE;IAC1G,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;AAC/H,CAAC;AAED,6HAA6H;AAC7H,MAAM,UAAU,cAAc,CAAC,MAA6B;IAC1D,MAAM,MAAM,GAAG,MAA+B,CAAC;IAC/C,OAAO,MAAM,CAAC,OAAO,KAAK,IAAI,IAAI,MAAM,CAAC,MAAM,KAAK,IAAI,IAAI,OAAO,MAAM,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAE,MAAuB,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAgB,EAAE,CAAC;AACzK,CAAC;AAsXD;;;;;GAKG;AACH,MAAM,OAAO,aAAc,SAAQ,KAAK;IAC7B,QAAQ,CAAe;IACvB,OAAO,CAAc;IAC9B,YAAY,QAAsB,EAAE,OAAO,GAAG,sDAAsD;QAClG,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,eAAe,CAAC;QAC5B,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,OAAO,GAAG,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;IACzD,CAAC;CACF;AAED,8EAA8E;AAC9E,MAAM,UAAU,WAAW,CAAC,OAAoB;IAC9C,OAAO,MAAM,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;AAChE,CAAC;AAED,wGAAwG;AACxG,MAAM,UAAU,aAAa,CAAC,KAAc;IAC1C,MAAM,OAAO,GAAI,KAA0C,EAAE,OAAO,CAAC;IACrE,IAAI,OAAO,IAAI,OAAO,OAAO,CAAC,IAAI,KAAK,QAAQ;QAAE,OAAO,OAAO,CAAC;IAChE,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACvE,IAAI,KAAK,YAAY,SAAS,IAAI,4BAA4B,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC;IAClH,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;AACpC,CAAC"}
@@ -4,7 +4,7 @@ import type { Page } from '../format/page.mjs';
4
4
  import type { JsonValue } from '../format/json.mjs';
5
5
  import { type FieldTone, type LineState } from './cells.mjs';
6
6
  import { type Messages } from './messages.mjs';
7
- import { type DataSource, type RecordChanges, type RecordPermissions, type RecordVersion, type SaveProblem, type UploadFile } from './data-source.mjs';
7
+ import { type DataSource, type RecordChanges, type RecordPermissions, type RecordVersion, type SaveProblem, type BotReport, type SlotsResult, type UploadFile } from './data-source.mjs';
8
8
  import { type FileUpload, type UploadHow } from './uploads.mjs';
9
9
  import { type Scheduler } from './scheduler.mjs';
10
10
  import { type ActionHost, type ActionRequest, type ActionResult, type OnAction, type PostedMessage, type RunContext, type RunResult } from './run.mjs';
@@ -61,11 +61,12 @@ export interface FormState {
61
61
  /**
62
62
  * Fields holding a change of the person's that the last finished save of
63
63
  * this record did not send: made while it was on its way, while the run that
64
- * saved went on (its afterSave run, a copy being made), or since. Worked out
65
- * from the record as that save left it, so a change back to it is none; and
66
- * before the record's first save, only a file the store holds and no save
67
- * sent. Never a value a step, a rule, a compute or the app set: each change
68
- * is told by its origin. The next record starts with none.
64
+ * saved went on (its afterSave run, a copy being made), or since — and text
65
+ * they left since in a box of it that does not read (`unread`), which no save
66
+ * can send. Worked out from the record as that save left it, so a change back
67
+ * to it is none; and before the record's first save, only a file the store
68
+ * holds and no save sent. Never a value a step, a rule, a compute or the app
69
+ * set: each change is told by its origin. The next record starts with none.
69
70
  */
70
71
  readonly unsent: readonly string[];
71
72
  /**
@@ -80,6 +81,15 @@ export interface FormState {
80
81
  */
81
82
  readonly unread: Readonly<Record<string, UnreadText>>;
82
83
  }
84
+ /** How a search for a link's records is asked (`form.search`, `form.searchLine`). */
85
+ export interface SearchOptions {
86
+ /** The model searched: a reference's, or the model structured data names records of. */
87
+ model?: string;
88
+ /** Those records alone, for their names. */
89
+ ids?: RecordId[];
90
+ /** Only the records named exactly the query, in any case (`SearchRequest.exact`): a typed name read as a record. */
91
+ exact?: boolean;
92
+ }
83
93
  /** Text left in a box that does not read, and why, as `FormState.unread` keeps it. */
84
94
  export interface UnreadText {
85
95
  readonly text: string;
@@ -110,12 +120,15 @@ export interface FormConflict {
110
120
  export declare function unsavedWork(state: FormState): boolean;
111
121
  /**
112
122
  * Whether a way out would lose the person's work without their knowing: a
113
- * change of theirs the last save did not send (`unsent`), files still on
114
- * their way, changes Reload set aside — and, `how.changes`, any change not
115
- * saved. What `form.leave()` — the one check every way out goes through —
116
- * asks about, once the save on its way and the files on theirs settled.
117
- * Before a record's first save, a change not saved is not at risk: as in
118
- * 0.20, each way out decides.
123
+ * change of theirs the last save did not send, or text they left since that
124
+ * does not read ("12a" counts as "12" does: `unsent`), files still on
125
+ * their way, changes Reload set aside, the changes of a save refused for a
126
+ * conflict, waiting for Reload or Keep mine (only a source that keeps
127
+ * versions has one) — and, `how.changes`, any change not saved. What
128
+ * `form.leave()` — the one check every way out goes through — asks about,
129
+ * once the save on its way and the files on theirs settled. Before a
130
+ * record's first save, a change not saved is not at risk: as in 0.20, each
131
+ * way out decides.
119
132
  */
120
133
  export declare function workAtRisk(state: FormState, how?: LeaveHow): boolean;
121
134
  /**
@@ -169,14 +182,15 @@ export interface FormEvents {
169
182
  values: Readonly<Values>;
170
183
  by: ChangeBy;
171
184
  };
172
- /** A record was saved. */
185
+ /** A record was saved: its values as the save left it — what it sent and what came back; a change made meanwhile is not among them. */
173
186
  save: {
174
187
  recordId: RecordId | null;
175
188
  values: Readonly<Values>;
176
189
  };
177
- /** A page of responses sent its answers: those sent. */
190
+ /** A page of responses sent its answers: those sent, and on a page with a bot check (`botCheck`) what it saw. */
178
191
  send: {
179
192
  values: Values;
193
+ bot?: BotReport;
180
194
  };
181
195
  /** The app answered a call: with nothing (undefined), or an `ActionResult`. */
182
196
  action: {
@@ -341,6 +355,15 @@ export interface Form {
341
355
  * Resolves once every reading that waits (a name looked up) has ended.
342
356
  */
343
357
  settle(): Promise<void>;
358
+ /**
359
+ * Tell a public page's bot check (`page.botCheck`) what was seen: its hidden box filled (`honeypot`, the viewer's), or
360
+ * the captcha's token as the app's widget gives it (`captcha`; null once it expires). Each send carries them, with
361
+ * the time the answers took, as `bot` (`SubmitRequest`); a token goes once it is sent, as a captcha's is spent.
362
+ */
363
+ botCheck(seen: {
364
+ honeypot?: boolean;
365
+ captcha?: string | null;
366
+ }): void;
344
367
  /**
345
368
  * Whether a box's reading of what was typed still waits (a name being looked up): a check or a step forward made
346
369
  * now refuses its text with "still looking", so a caller that can wait for it (`settle`) checks again after.
@@ -390,6 +413,17 @@ export interface Form {
390
413
  * first, so a way out with nothing to wait for is not put off.
391
414
  */
392
415
  leaveNow(how?: LeaveHow): boolean;
416
+ /**
417
+ * The record shown now, for an answer asked for it that comes later: the
418
+ * function returned is true while that record is still the one shown, as it
419
+ * was — no other opened, it not loaded again, no new one started, the
420
+ * person's Discard not pressed, the form not closed. What writes on the
421
+ * form, or acts on its record, once a wait ends — a name looked up, a
422
+ * record made from a link, a line's dialog, a step's call to the source —
423
+ * asks it first, so an answer is laid only on the record it was asked for
424
+ * (docs/plans/wave1-contract.md, section 10, part 2).
425
+ */
426
+ recordShown(): () => boolean;
393
427
  /**
394
428
  * Post words in the record's conversation, as a `post` step does: through
395
429
  * the form's host when it has a way to, else told to the form's own
@@ -414,17 +448,12 @@ export interface Form {
414
448
  * Records a many2one, many2many or reference may point to. A reference needs
415
449
  * `options.model`, and so does structured data naming records of a model (a
416
450
  * json field, such as an analytic distribution's accounts); `options.ids`
417
- * finds those records alone, for their names.
451
+ * finds those records alone, for their names; `options.exact`, those named
452
+ * exactly the query (`SearchRequest.exact`).
418
453
  */
419
- search(field: string, query: string, limit?: number, options?: {
420
- model?: string;
421
- ids?: RecordId[];
422
- }): Promise<RelatedRecord[]>;
454
+ search(field: string, query: string, limit?: number, options?: SearchOptions): Promise<RelatedRecord[]>;
423
455
  /** The same, for a relation inside a one2many line, filtered by that line's values — or structured data of a line naming records of a model (`options.model`). */
424
- searchLine(field: string, key: string, subfield: string, query: string, limit?: number, options?: {
425
- model?: string;
426
- ids?: RecordId[];
427
- }): Promise<RelatedRecord[]>;
456
+ searchLine(field: string, key: string, subfield: string, query: string, limit?: number, options?: SearchOptions): Promise<RelatedRecord[]>;
428
457
  /**
429
458
  * The values of records a many2many links to, for a table of them: `fields`
430
459
  * of the records with these ids, in the order asked, read with the data
@@ -460,7 +489,9 @@ export interface Form {
460
489
  /**
461
490
  * Press a button: ask its `confirm` first, run its `steps`, then call its
462
491
  * `action`, when it names one, as a final `call`. A list's button passes
463
- * the records chosen in it, and every call of the run carries them.
492
+ * the records chosen in it, and every call of the run carries them. What is
493
+ * typed is read before its first step that reads the form or hands its
494
+ * values on, as a save reads it: text that does not read stops the run.
464
495
  */
465
496
  runAction(id: string, chosen?: {
466
497
  recordIds: RecordId[];
@@ -485,7 +516,7 @@ export interface Form {
485
516
  * carrying the lines. Hidden, or with no line chosen, it does not run.
486
517
  */
487
518
  runLinesAction(nodeId: string, buttonId: string, keys: readonly string[]): Promise<RunResult>;
488
- /** Run steps on this form, as a button would: `context.id` is what their calls carry (`run` when left out). */
519
+ /** Run steps on this form, as a button would — what is typed read first: `context.id` is what their calls carry (`run` when left out). */
489
520
  run(steps: readonly ActionStep[], context?: RunContext): Promise<RunResult>;
490
521
  /** A tab was shown: the viewer says so, and the page's `show` steps for it run. */
491
522
  shown(tab: string): void;
@@ -499,6 +530,22 @@ export interface Form {
499
530
  * travels inside the record as base64, as it always has.
500
531
  */
501
532
  canUpload(): boolean;
533
+ /** Today, as 2026-10-15, on the clock of the page's time zone — the browser's own where it names none — by the form's clock (`now`). */
534
+ today(): string;
535
+ /** Whether a date and time drawn as booking slots can ask for its free times: the data source has `slots`. */
536
+ canFindSlots(): boolean;
537
+ /**
538
+ * The free times of a date and time drawn as booking slots, asked of the data source's `slots` for the days `from`
539
+ * to `to` — in the field's time zone: its own, its page's, or the browser's — with the record, its values and the
540
+ * zone filled in. Rejects where the source has no `slots`.
541
+ */
542
+ findSlots(field: string, ask: {
543
+ from: string;
544
+ to: string;
545
+ schedule?: string;
546
+ length?: number;
547
+ between?: number;
548
+ }): Promise<SlotsResult>;
502
549
  /**
503
550
  * Store files picked for a file or picture field — or a field on a table's
504
551
  * line (`how.line`) — through the data source's `upload`, each as soon as
@@ -4,7 +4,7 @@ import type { Page } from '../format/page';
4
4
  import type { JsonValue } from '../format/json';
5
5
  import { type FieldTone, type LineState } from './cells';
6
6
  import { type Messages } from './messages';
7
- import { type DataSource, type RecordChanges, type RecordPermissions, type RecordVersion, type SaveProblem, type UploadFile } from './data-source';
7
+ import { type DataSource, type RecordChanges, type RecordPermissions, type RecordVersion, type SaveProblem, type BotReport, type SlotsResult, type UploadFile } from './data-source';
8
8
  import { type FileUpload, type UploadHow } from './uploads';
9
9
  import { type Scheduler } from './scheduler';
10
10
  import { type ActionHost, type ActionRequest, type ActionResult, type OnAction, type PostedMessage, type RunContext, type RunResult } from './run';
@@ -61,11 +61,12 @@ export interface FormState {
61
61
  /**
62
62
  * Fields holding a change of the person's that the last finished save of
63
63
  * this record did not send: made while it was on its way, while the run that
64
- * saved went on (its afterSave run, a copy being made), or since. Worked out
65
- * from the record as that save left it, so a change back to it is none; and
66
- * before the record's first save, only a file the store holds and no save
67
- * sent. Never a value a step, a rule, a compute or the app set: each change
68
- * is told by its origin. The next record starts with none.
64
+ * saved went on (its afterSave run, a copy being made), or since — and text
65
+ * they left since in a box of it that does not read (`unread`), which no save
66
+ * can send. Worked out from the record as that save left it, so a change back
67
+ * to it is none; and before the record's first save, only a file the store
68
+ * holds and no save sent. Never a value a step, a rule, a compute or the app
69
+ * set: each change is told by its origin. The next record starts with none.
69
70
  */
70
71
  readonly unsent: readonly string[];
71
72
  /**
@@ -80,6 +81,15 @@ export interface FormState {
80
81
  */
81
82
  readonly unread: Readonly<Record<string, UnreadText>>;
82
83
  }
84
+ /** How a search for a link's records is asked (`form.search`, `form.searchLine`). */
85
+ export interface SearchOptions {
86
+ /** The model searched: a reference's, or the model structured data names records of. */
87
+ model?: string;
88
+ /** Those records alone, for their names. */
89
+ ids?: RecordId[];
90
+ /** Only the records named exactly the query, in any case (`SearchRequest.exact`): a typed name read as a record. */
91
+ exact?: boolean;
92
+ }
83
93
  /** Text left in a box that does not read, and why, as `FormState.unread` keeps it. */
84
94
  export interface UnreadText {
85
95
  readonly text: string;
@@ -110,12 +120,15 @@ export interface FormConflict {
110
120
  export declare function unsavedWork(state: FormState): boolean;
111
121
  /**
112
122
  * Whether a way out would lose the person's work without their knowing: a
113
- * change of theirs the last save did not send (`unsent`), files still on
114
- * their way, changes Reload set aside — and, `how.changes`, any change not
115
- * saved. What `form.leave()` — the one check every way out goes through —
116
- * asks about, once the save on its way and the files on theirs settled.
117
- * Before a record's first save, a change not saved is not at risk: as in
118
- * 0.20, each way out decides.
123
+ * change of theirs the last save did not send, or text they left since that
124
+ * does not read ("12a" counts as "12" does: `unsent`), files still on
125
+ * their way, changes Reload set aside, the changes of a save refused for a
126
+ * conflict, waiting for Reload or Keep mine (only a source that keeps
127
+ * versions has one) — and, `how.changes`, any change not saved. What
128
+ * `form.leave()` — the one check every way out goes through — asks about,
129
+ * once the save on its way and the files on theirs settled. Before a
130
+ * record's first save, a change not saved is not at risk: as in 0.20, each
131
+ * way out decides.
119
132
  */
120
133
  export declare function workAtRisk(state: FormState, how?: LeaveHow): boolean;
121
134
  /**
@@ -169,14 +182,15 @@ export interface FormEvents {
169
182
  values: Readonly<Values>;
170
183
  by: ChangeBy;
171
184
  };
172
- /** A record was saved. */
185
+ /** A record was saved: its values as the save left it — what it sent and what came back; a change made meanwhile is not among them. */
173
186
  save: {
174
187
  recordId: RecordId | null;
175
188
  values: Readonly<Values>;
176
189
  };
177
- /** A page of responses sent its answers: those sent. */
190
+ /** A page of responses sent its answers: those sent, and on a page with a bot check (`botCheck`) what it saw. */
178
191
  send: {
179
192
  values: Values;
193
+ bot?: BotReport;
180
194
  };
181
195
  /** The app answered a call: with nothing (undefined), or an `ActionResult`. */
182
196
  action: {
@@ -341,6 +355,15 @@ export interface Form {
341
355
  * Resolves once every reading that waits (a name looked up) has ended.
342
356
  */
343
357
  settle(): Promise<void>;
358
+ /**
359
+ * Tell a public page's bot check (`page.botCheck`) what was seen: its hidden box filled (`honeypot`, the viewer's), or
360
+ * the captcha's token as the app's widget gives it (`captcha`; null once it expires). Each send carries them, with
361
+ * the time the answers took, as `bot` (`SubmitRequest`); a token goes once it is sent, as a captcha's is spent.
362
+ */
363
+ botCheck(seen: {
364
+ honeypot?: boolean;
365
+ captcha?: string | null;
366
+ }): void;
344
367
  /**
345
368
  * Whether a box's reading of what was typed still waits (a name being looked up): a check or a step forward made
346
369
  * now refuses its text with "still looking", so a caller that can wait for it (`settle`) checks again after.
@@ -390,6 +413,17 @@ export interface Form {
390
413
  * first, so a way out with nothing to wait for is not put off.
391
414
  */
392
415
  leaveNow(how?: LeaveHow): boolean;
416
+ /**
417
+ * The record shown now, for an answer asked for it that comes later: the
418
+ * function returned is true while that record is still the one shown, as it
419
+ * was — no other opened, it not loaded again, no new one started, the
420
+ * person's Discard not pressed, the form not closed. What writes on the
421
+ * form, or acts on its record, once a wait ends — a name looked up, a
422
+ * record made from a link, a line's dialog, a step's call to the source —
423
+ * asks it first, so an answer is laid only on the record it was asked for
424
+ * (docs/plans/wave1-contract.md, section 10, part 2).
425
+ */
426
+ recordShown(): () => boolean;
393
427
  /**
394
428
  * Post words in the record's conversation, as a `post` step does: through
395
429
  * the form's host when it has a way to, else told to the form's own
@@ -414,17 +448,12 @@ export interface Form {
414
448
  * Records a many2one, many2many or reference may point to. A reference needs
415
449
  * `options.model`, and so does structured data naming records of a model (a
416
450
  * json field, such as an analytic distribution's accounts); `options.ids`
417
- * finds those records alone, for their names.
451
+ * finds those records alone, for their names; `options.exact`, those named
452
+ * exactly the query (`SearchRequest.exact`).
418
453
  */
419
- search(field: string, query: string, limit?: number, options?: {
420
- model?: string;
421
- ids?: RecordId[];
422
- }): Promise<RelatedRecord[]>;
454
+ search(field: string, query: string, limit?: number, options?: SearchOptions): Promise<RelatedRecord[]>;
423
455
  /** The same, for a relation inside a one2many line, filtered by that line's values — or structured data of a line naming records of a model (`options.model`). */
424
- searchLine(field: string, key: string, subfield: string, query: string, limit?: number, options?: {
425
- model?: string;
426
- ids?: RecordId[];
427
- }): Promise<RelatedRecord[]>;
456
+ searchLine(field: string, key: string, subfield: string, query: string, limit?: number, options?: SearchOptions): Promise<RelatedRecord[]>;
428
457
  /**
429
458
  * The values of records a many2many links to, for a table of them: `fields`
430
459
  * of the records with these ids, in the order asked, read with the data
@@ -460,7 +489,9 @@ export interface Form {
460
489
  /**
461
490
  * Press a button: ask its `confirm` first, run its `steps`, then call its
462
491
  * `action`, when it names one, as a final `call`. A list's button passes
463
- * the records chosen in it, and every call of the run carries them.
492
+ * the records chosen in it, and every call of the run carries them. What is
493
+ * typed is read before its first step that reads the form or hands its
494
+ * values on, as a save reads it: text that does not read stops the run.
464
495
  */
465
496
  runAction(id: string, chosen?: {
466
497
  recordIds: RecordId[];
@@ -485,7 +516,7 @@ export interface Form {
485
516
  * carrying the lines. Hidden, or with no line chosen, it does not run.
486
517
  */
487
518
  runLinesAction(nodeId: string, buttonId: string, keys: readonly string[]): Promise<RunResult>;
488
- /** Run steps on this form, as a button would: `context.id` is what their calls carry (`run` when left out). */
519
+ /** Run steps on this form, as a button would — what is typed read first: `context.id` is what their calls carry (`run` when left out). */
489
520
  run(steps: readonly ActionStep[], context?: RunContext): Promise<RunResult>;
490
521
  /** A tab was shown: the viewer says so, and the page's `show` steps for it run. */
491
522
  shown(tab: string): void;
@@ -499,6 +530,22 @@ export interface Form {
499
530
  * travels inside the record as base64, as it always has.
500
531
  */
501
532
  canUpload(): boolean;
533
+ /** Today, as 2026-10-15, on the clock of the page's time zone — the browser's own where it names none — by the form's clock (`now`). */
534
+ today(): string;
535
+ /** Whether a date and time drawn as booking slots can ask for its free times: the data source has `slots`. */
536
+ canFindSlots(): boolean;
537
+ /**
538
+ * The free times of a date and time drawn as booking slots, asked of the data source's `slots` for the days `from`
539
+ * to `to` — in the field's time zone: its own, its page's, or the browser's — with the record, its values and the
540
+ * zone filled in. Rejects where the source has no `slots`.
541
+ */
542
+ findSlots(field: string, ask: {
543
+ from: string;
544
+ to: string;
545
+ schedule?: string;
546
+ length?: number;
547
+ between?: number;
548
+ }): Promise<SlotsResult>;
502
549
  /**
503
550
  * Store files picked for a file or picture field — or a field on a table's
504
551
  * line (`how.line`) — through the data source's `upload`, each as soon as