@fieldia/core 0.23.0 → 0.25.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 (221) hide show
  1. package/README.md +211 -3
  2. package/package.json +1 -1
  3. package/schema/page.schema.json +1051 -325
  4. package/src/index.d.mts +9 -2
  5. package/src/index.d.ts +9 -2
  6. package/src/index.js +31 -1
  7. package/src/index.js.map +1 -1
  8. package/src/index.mjs +12 -2
  9. package/src/index.mjs.map +1 -1
  10. package/src/lib/format/actions.d.mts +31 -1
  11. package/src/lib/format/actions.d.ts +31 -1
  12. package/src/lib/format/actions.js +1 -0
  13. package/src/lib/format/actions.js.map +1 -1
  14. package/src/lib/format/actions.mjs +1 -0
  15. package/src/lib/format/actions.mjs.map +1 -1
  16. package/src/lib/format/autofill-group.d.mts +12 -0
  17. package/src/lib/format/autofill-group.d.ts +12 -0
  18. package/src/lib/format/autofill-group.js +23 -0
  19. package/src/lib/format/autofill-group.js.map +1 -0
  20. package/src/lib/format/autofill-group.mjs +19 -0
  21. package/src/lib/format/autofill-group.mjs.map +1 -0
  22. package/src/lib/format/autofill.d.mts +14 -0
  23. package/src/lib/format/autofill.d.ts +14 -0
  24. package/src/lib/format/autofill.js +54 -0
  25. package/src/lib/format/autofill.js.map +1 -0
  26. package/src/lib/format/autofill.mjs +48 -0
  27. package/src/lib/format/autofill.mjs.map +1 -0
  28. package/src/lib/format/bulk-edit.d.mts +18 -0
  29. package/src/lib/format/bulk-edit.d.ts +18 -0
  30. package/src/lib/format/bulk-edit.js +26 -0
  31. package/src/lib/format/bulk-edit.js.map +1 -0
  32. package/src/lib/format/bulk-edit.mjs +21 -0
  33. package/src/lib/format/bulk-edit.mjs.map +1 -0
  34. package/src/lib/format/duplicate-kinds.d.mts +10 -0
  35. package/src/lib/format/duplicate-kinds.d.ts +10 -0
  36. package/src/lib/format/duplicate-kinds.js +14 -0
  37. package/src/lib/format/duplicate-kinds.js.map +1 -0
  38. package/src/lib/format/duplicate-kinds.mjs +10 -0
  39. package/src/lib/format/duplicate-kinds.mjs.map +1 -0
  40. package/src/lib/format/duplicates.d.mts +22 -0
  41. package/src/lib/format/duplicates.d.ts +22 -0
  42. package/src/lib/format/duplicates.js +24 -0
  43. package/src/lib/format/duplicates.js.map +1 -0
  44. package/src/lib/format/duplicates.mjs +21 -0
  45. package/src/lib/format/duplicates.mjs.map +1 -0
  46. package/src/lib/format/field.d.mts +291 -34
  47. package/src/lib/format/field.d.ts +291 -34
  48. package/src/lib/format/field.js +19 -4
  49. package/src/lib/format/field.js.map +1 -1
  50. package/src/lib/format/field.mjs +17 -2
  51. package/src/lib/format/field.mjs.map +1 -1
  52. package/src/lib/format/import-fields.d.mts +18 -0
  53. package/src/lib/format/import-fields.d.ts +18 -0
  54. package/src/lib/format/import-fields.js +26 -0
  55. package/src/lib/format/import-fields.js.map +1 -0
  56. package/src/lib/format/import-fields.mjs +21 -0
  57. package/src/lib/format/import-fields.mjs.map +1 -0
  58. package/src/lib/format/layout.d.mts +7357 -3127
  59. package/src/lib/format/layout.d.ts +7357 -3127
  60. package/src/lib/format/layout.js +108 -32
  61. package/src/lib/format/layout.js.map +1 -1
  62. package/src/lib/format/layout.mjs +107 -32
  63. package/src/lib/format/layout.mjs.map +1 -1
  64. package/src/lib/format/layout.types.d.mts +5 -2
  65. package/src/lib/format/layout.types.d.ts +5 -2
  66. package/src/lib/format/page.d.mts +1080 -106
  67. package/src/lib/format/page.d.ts +1080 -106
  68. package/src/lib/format/page.js +25 -0
  69. package/src/lib/format/page.js.map +1 -1
  70. package/src/lib/format/page.mjs +26 -1
  71. package/src/lib/format/page.mjs.map +1 -1
  72. package/src/lib/format/phone.d.mts +27 -0
  73. package/src/lib/format/phone.d.ts +27 -0
  74. package/src/lib/format/phone.js +43 -0
  75. package/src/lib/format/phone.js.map +1 -0
  76. package/src/lib/format/phone.mjs +39 -0
  77. package/src/lib/format/phone.mjs.map +1 -0
  78. package/src/lib/format/references.d.mts +39 -0
  79. package/src/lib/format/references.d.ts +39 -0
  80. package/src/lib/format/references.js +263 -20
  81. package/src/lib/format/references.js.map +1 -1
  82. package/src/lib/format/references.mjs +263 -20
  83. package/src/lib/format/references.mjs.map +1 -1
  84. package/src/lib/format/slots-options.d.mts +26 -0
  85. package/src/lib/format/slots-options.d.ts +26 -0
  86. package/src/lib/format/slots-options.js +35 -0
  87. package/src/lib/format/slots-options.js.map +1 -0
  88. package/src/lib/format/slots-options.mjs +31 -0
  89. package/src/lib/format/slots-options.mjs.map +1 -0
  90. package/src/lib/format/translate.js +8 -4
  91. package/src/lib/format/translate.js.map +1 -1
  92. package/src/lib/format/translate.mjs +8 -4
  93. package/src/lib/format/translate.mjs.map +1 -1
  94. package/src/lib/format/upgrade.d.mts +8 -3
  95. package/src/lib/format/upgrade.d.ts +8 -3
  96. package/src/lib/format/upgrade.js +8 -3
  97. package/src/lib/format/upgrade.js.map +1 -1
  98. package/src/lib/format/upgrade.mjs +8 -3
  99. package/src/lib/format/upgrade.mjs.map +1 -1
  100. package/src/lib/format/version.d.mts +2 -2
  101. package/src/lib/format/version.d.ts +2 -2
  102. package/src/lib/format/version.js +2 -2
  103. package/src/lib/format/version.js.map +1 -1
  104. package/src/lib/format/version.mjs +2 -2
  105. package/src/lib/format/version.mjs.map +1 -1
  106. package/src/lib/format/zoned.d.mts +8 -0
  107. package/src/lib/format/zoned.d.ts +8 -0
  108. package/src/lib/format/zoned.js +36 -0
  109. package/src/lib/format/zoned.js.map +1 -0
  110. package/src/lib/format/zoned.mjs +33 -0
  111. package/src/lib/format/zoned.mjs.map +1 -0
  112. package/src/lib/record/check.d.mts +6 -1
  113. package/src/lib/record/check.d.ts +6 -1
  114. package/src/lib/record/check.js +12 -4
  115. package/src/lib/record/check.js.map +1 -1
  116. package/src/lib/record/check.mjs +12 -4
  117. package/src/lib/record/check.mjs.map +1 -1
  118. package/src/lib/record/data-source.d.mts +309 -3
  119. package/src/lib/record/data-source.d.ts +309 -3
  120. package/src/lib/record/data-source.js +16 -0
  121. package/src/lib/record/data-source.js.map +1 -1
  122. package/src/lib/record/data-source.mjs +15 -0
  123. package/src/lib/record/data-source.mjs.map +1 -1
  124. package/src/lib/record/form.d.mts +116 -25
  125. package/src/lib/record/form.d.ts +116 -25
  126. package/src/lib/record/form.js +457 -78
  127. package/src/lib/record/form.js.map +1 -1
  128. package/src/lib/record/form.mjs +457 -78
  129. package/src/lib/record/form.mjs.map +1 -1
  130. package/src/lib/record/input-rules.d.mts +51 -9
  131. package/src/lib/record/input-rules.d.ts +51 -9
  132. package/src/lib/record/input-rules.js +119 -20
  133. package/src/lib/record/input-rules.js.map +1 -1
  134. package/src/lib/record/input-rules.mjs +118 -19
  135. package/src/lib/record/input-rules.mjs.map +1 -1
  136. package/src/lib/record/inputs.js +4 -2
  137. package/src/lib/record/inputs.js.map +1 -1
  138. package/src/lib/record/inputs.mjs +4 -2
  139. package/src/lib/record/inputs.mjs.map +1 -1
  140. package/src/lib/record/limits.d.mts +1 -1
  141. package/src/lib/record/limits.d.ts +1 -1
  142. package/src/lib/record/limits.js +4 -2
  143. package/src/lib/record/limits.js.map +1 -1
  144. package/src/lib/record/limits.mjs +4 -2
  145. package/src/lib/record/limits.mjs.map +1 -1
  146. package/src/lib/record/locales/ar.js +3 -2
  147. package/src/lib/record/locales/ar.js.map +1 -1
  148. package/src/lib/record/locales/ar.mjs +3 -2
  149. package/src/lib/record/locales/ar.mjs.map +1 -1
  150. package/src/lib/record/locales/de.js +3 -2
  151. package/src/lib/record/locales/de.js.map +1 -1
  152. package/src/lib/record/locales/de.mjs +3 -2
  153. package/src/lib/record/locales/de.mjs.map +1 -1
  154. package/src/lib/record/locales/fr.js +3 -2
  155. package/src/lib/record/locales/fr.js.map +1 -1
  156. package/src/lib/record/locales/fr.mjs +3 -2
  157. package/src/lib/record/locales/fr.mjs.map +1 -1
  158. package/src/lib/record/memory-data-source.d.mts +20 -2
  159. package/src/lib/record/memory-data-source.d.ts +20 -2
  160. package/src/lib/record/memory-data-source.js +153 -35
  161. package/src/lib/record/memory-data-source.js.map +1 -1
  162. package/src/lib/record/memory-data-source.mjs +154 -36
  163. package/src/lib/record/memory-data-source.mjs.map +1 -1
  164. package/src/lib/record/memory-duplicates.d.mts +19 -0
  165. package/src/lib/record/memory-duplicates.d.ts +19 -0
  166. package/src/lib/record/memory-duplicates.js +78 -0
  167. package/src/lib/record/memory-duplicates.js.map +1 -0
  168. package/src/lib/record/memory-duplicates.mjs +74 -0
  169. package/src/lib/record/memory-duplicates.mjs.map +1 -0
  170. package/src/lib/record/memory-schedules.d.mts +45 -0
  171. package/src/lib/record/memory-schedules.d.ts +45 -0
  172. package/src/lib/record/memory-schedules.js +81 -0
  173. package/src/lib/record/memory-schedules.js.map +1 -0
  174. package/src/lib/record/memory-schedules.mjs +78 -0
  175. package/src/lib/record/memory-schedules.mjs.map +1 -0
  176. package/src/lib/record/messages.d.mts +3 -1
  177. package/src/lib/record/messages.d.ts +3 -1
  178. package/src/lib/record/messages.js +3 -2
  179. package/src/lib/record/messages.js.map +1 -1
  180. package/src/lib/record/messages.mjs +3 -2
  181. package/src/lib/record/messages.mjs.map +1 -1
  182. package/src/lib/record/periods.d.mts +47 -0
  183. package/src/lib/record/periods.d.ts +47 -0
  184. package/src/lib/record/periods.js +86 -0
  185. package/src/lib/record/periods.js.map +1 -0
  186. package/src/lib/record/periods.mjs +79 -0
  187. package/src/lib/record/periods.mjs.map +1 -0
  188. package/src/lib/record/rules.js +5 -6
  189. package/src/lib/record/rules.js.map +1 -1
  190. package/src/lib/record/rules.mjs +5 -6
  191. package/src/lib/record/rules.mjs.map +1 -1
  192. package/src/lib/record/run.d.mts +75 -9
  193. package/src/lib/record/run.d.ts +75 -9
  194. package/src/lib/record/run.js +169 -26
  195. package/src/lib/record/run.js.map +1 -1
  196. package/src/lib/record/run.mjs +170 -27
  197. package/src/lib/record/run.mjs.map +1 -1
  198. package/src/lib/record/search.d.mts +13 -3
  199. package/src/lib/record/search.d.ts +13 -3
  200. package/src/lib/record/search.js +11 -4
  201. package/src/lib/record/search.js.map +1 -1
  202. package/src/lib/record/search.mjs +11 -4
  203. package/src/lib/record/search.mjs.map +1 -1
  204. package/src/lib/record/waits.d.mts +32 -0
  205. package/src/lib/record/waits.d.ts +32 -0
  206. package/src/lib/record/waits.js +89 -0
  207. package/src/lib/record/waits.js.map +1 -0
  208. package/src/lib/record/waits.mjs +85 -0
  209. package/src/lib/record/waits.mjs.map +1 -0
  210. package/src/lib/record/zones.d.mts +57 -0
  211. package/src/lib/record/zones.d.ts +57 -0
  212. package/src/lib/record/zones.js +147 -0
  213. package/src/lib/record/zones.js.map +1 -0
  214. package/src/lib/record/zones.mjs +132 -0
  215. package/src/lib/record/zones.mjs.map +1 -0
  216. package/src/runtime.d.mts +19 -9
  217. package/src/runtime.d.ts +19 -9
  218. package/src/runtime.js +37 -2
  219. package/src/runtime.js.map +1 -1
  220. package/src/runtime.mjs +9 -2
  221. 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.mjs';
3
3
  import type { FileValue, RecordId, RelatedRecord, Values } from './values.mjs';
4
4
  import type { RunStop } from './run.mjs';
5
+ import type { DateInterval } from './periods.mjs';
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,208 @@ 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
+ * Make many records at once, each with its own values: a list's Import of
104
+ * a file's rows, a thousand at a time. Answer for each record asked for,
105
+ * in its order: the id it was made with, or why it was not made, in the
106
+ * person's words. Without it, the list saves each in turn (`save` with no
107
+ * id).
108
+ */
109
+ createMany?(request: CreateManyRequest): Promise<CreateManyResult>;
110
+ /**
111
+ * Take a list's Import on the server, in place of the browser: a file too
112
+ * big for one, or a server that imports its own way. Asked once the person
113
+ * has matched the file's columns to the page's fields, before a row is
114
+ * checked: answer `{ message }` when the server takes the file (the list
115
+ * says it: "Your file is being imported: you will get an email"), or
116
+ * `false` to let the browser import it after all, as it does without this.
117
+ */
118
+ importRecords?(request: ImportRequest): Promise<{
119
+ message: string;
120
+ } | false>;
121
+ /**
122
+ * Records like the one typed, for a page's possible duplicates (`duplicates`): asked a moment after a person stops
123
+ * typing in a field it names, as a saved record is opened, and as a record is saved — with the fields it names that
124
+ * hold a value. Answer with the records that have the same phone, email or id, or a name close enough, each with
125
+ * the fields it matched on; never the record itself. Without it, nothing is asked and nothing shows.
126
+ */
127
+ duplicates?(request: DuplicatesRequest): Promise<DuplicatesResult>;
128
+ /**
129
+ * Merge records into one, as a person chose on a possible duplicate's Merge: `keepId` kept, holding the values
130
+ * picked for each field, and `mergeIds` gone. Moving what was theirs — messages, activities, the records that link
131
+ * to them — to the one kept is the source's work; the page says it will happen. Refuse with `saveRefused` (a `rule`,
132
+ * with why) and nothing changes. Without it, possible duplicates offer no Merge.
133
+ */
134
+ merge?(request: MergeRequest): Promise<MergeResult | void>;
135
+ }
136
+ /** Many records made at once, each with its own values: a list's Import. */
137
+ export interface CreateManyRequest {
138
+ model: string;
139
+ /** Each record's values, in the file's order: what its row fills in, and the page's defaults for the rest. Links as `{ id, label }`. */
140
+ records: Values[];
141
+ /** The fields' definitions, as a save gets them. */
142
+ fields: Fields;
143
+ }
144
+ export interface CreateManyResult {
145
+ /**
146
+ * One answer for each record asked for, in the same order: the id it was made with, or why it was not made — and,
147
+ * where the source has it, the problem as a save would throw it (a field's own words). A record left unanswered is
148
+ * taken as not made.
149
+ */
150
+ records: ({
151
+ id: RecordId;
152
+ } | {
153
+ message: string;
154
+ problem?: SaveProblem;
155
+ })[];
156
+ }
157
+ /** A file a list's Import hands to the server, its columns matched to the page's fields. */
158
+ export interface ImportRequest {
159
+ model: string;
160
+ /** The file as the person picked it: a browser's File fits. */
161
+ file: UploadFile;
162
+ /** Each column of the file, in its order — its header's words, and the field it fills, null where it is left out. */
163
+ columns: {
164
+ header: string;
165
+ field: string | null;
166
+ }[];
167
+ /** How many rows it holds under its header. */
168
+ rows: number;
169
+ /** The fields' definitions, as a save gets them. */
170
+ fields: Fields;
171
+ /** The page's language and the order it writes dates in, so a server reads the cells as the page would. */
172
+ locale: string;
173
+ dateOrder: 'dmy' | 'mdy' | 'ymd';
174
+ }
175
+ /** Records like the one typed: what `duplicates` is asked. */
176
+ export interface DuplicatesRequest {
177
+ model: string;
178
+ /** The record typed, never its own duplicate; null while it is new. */
179
+ recordId: RecordId | null;
180
+ /** Every value of the form, as typed: a phone kept as E.164 where the page reads phones the world over. */
181
+ values: Values;
182
+ /** The fields to compare it by, by name: those the page names (`duplicates.fields`) that hold a value now. */
183
+ fields: string[];
184
+ }
185
+ /** A record like the one typed. */
186
+ export interface DuplicateMatch {
187
+ id: RecordId;
188
+ /** Its name, as people know it: "Nile Traders". */
189
+ name: string;
190
+ /**
191
+ * What it matched on: the names of the fields asked that it holds the same (`phone`, `email`), or a name close to
192
+ * the one typed (the name's field). Each is said under that field's box; words of the source's own ("the same
193
+ * address") are said as they are.
194
+ */
195
+ why: string[];
196
+ /** How alike, from 0 to 1, where the source tells: the likest first. */
197
+ score?: number;
198
+ }
199
+ export interface DuplicatesResult {
200
+ matches: DuplicateMatch[];
201
+ }
202
+ /** Records merged into one: what `merge` is asked. */
203
+ export interface MergeRequest {
204
+ model: string;
205
+ /** The record kept. */
206
+ keepId: RecordId;
207
+ /** The records merged into it, then gone. */
208
+ mergeIds: RecordId[];
209
+ /** The kept record's values, as picked on the merge's screen for each field the records held apart. */
210
+ values: Values;
211
+ /** The page's fields, so the source knows each link's model, as a save does. */
212
+ fields: Fields;
213
+ }
214
+ /** What a merge may tell: words of the source's own about it, said once it is done ("4 messages moved"). */
215
+ export interface MergeResult {
216
+ message?: string;
217
+ }
218
+ /** The same value written in a field of each record: a list's bulk edit. */
219
+ export interface WriteManyRequest {
220
+ model: string;
221
+ ids: RecordId[];
222
+ /** The fields written and their value, the same in each record: `{ state: 'active' }`. */
223
+ values: Values;
224
+ /** Those fields' definitions, as a save gets them. */
225
+ fields: Fields;
226
+ /** 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. */
227
+ versions?: Record<string, RecordVersion>;
228
+ }
229
+ export interface WriteManyResult {
230
+ /** 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). */
231
+ failed: {
232
+ id: RecordId;
233
+ message: string;
234
+ problem?: SaveProblem;
235
+ }[];
236
+ }
237
+ /** A list's records to put in a file: those ticked, or every one its search finds. */
238
+ export interface ExportRequest {
239
+ model: string;
240
+ format: 'xlsx' | 'csv';
241
+ /** The list's columns, in its order, each with its header's words. */
242
+ columns: {
243
+ field: string;
244
+ label: string;
245
+ }[];
246
+ /** The records ticked, by id; left out, every record `filter` finds. */
247
+ ids?: RecordId[];
248
+ /** The list's search, as `list` gets it. */
249
+ filter: ResolvedFilter[];
250
+ /** The list's order now. */
251
+ sort: SortOrder[];
252
+ /** How many records go. */
253
+ count: number;
254
+ /** The file's name, as the list would give it: "Customers 2026-10-16.xlsx". */
255
+ name: string;
256
+ /** The page's language, for values written as people there read them. */
257
+ locale: string;
258
+ }
259
+ /** A file for the browser to save: its bytes, or where to fetch them. */
260
+ export interface ExportedFile {
261
+ name: string;
262
+ /** Its media type, when `data` does not say it. */
263
+ type?: string;
264
+ /** Its bytes: a Blob, an ArrayBuffer, a typed array, or text. */
265
+ data?: ExportBytes | ArrayBuffer | ArrayBufferView | string;
266
+ /** Where the browser fetches it, in place of `data`. */
267
+ url?: string;
268
+ }
269
+ /** A Blob, as far as an export reads one: core names no DOM types. */
270
+ export interface ExportBytes {
271
+ readonly size: number;
272
+ readonly type: string;
273
+ arrayBuffer(): Promise<ArrayBuffer>;
71
274
  }
72
275
  /**
73
276
  * Which state of a record a page opened, as its source keeps it: an etag, a
@@ -207,6 +410,15 @@ export interface SearchRequest {
207
410
  /** The field's filter, with every `valueFrom` already replaced by its value. */
208
411
  filter?: ResolvedFilter[];
209
412
  limit?: number;
413
+ /**
414
+ * Only the records named exactly `query`, in any case — not every one whose
415
+ * name holds it. A box asks so for a name typed when a search's first
416
+ * answers, cut at their limit, did not hold it ("Nile" after ten "Blue
417
+ * Nile"s), and to tell whether more than one record has that name. A source
418
+ * that does not know it searches as ever: the box looks for the name among
419
+ * what comes.
420
+ */
421
+ exact?: boolean;
210
422
  }
211
423
  export type ResolvedFilterCondition = Omit<FilterCondition, 'valueFrom' | 'value'> & {
212
424
  value: JsonValue;
@@ -258,7 +470,23 @@ export interface ListedRecord {
258
470
  /** What the person may do with it: its row's buttons and a bulk edit keep to it. */
259
471
  can?: RecordPermissions;
260
472
  }
261
- export interface GroupRequest {
473
+ /**
474
+ * How a date's records are grouped: by the day, week, month, quarter or year
475
+ * each falls in rather than by its exact value. Each group's `value` is then
476
+ * the first day of its span — "2026-03-01" for March 2026, "2026-03-09" for
477
+ * the week from 9 March — the page writing its words ("March 2026", "Week 11,
478
+ * 2026"); the groups in time order, those with no date last. `spanStart`
479
+ * counts a value's span as a source should.
480
+ */
481
+ export interface GroupInterval {
482
+ /** For a date or a date and time field: group by the span each record falls in. */
483
+ interval?: DateInterval;
484
+ /** With `interval: "week"`: the day a week starts on, 0 Sunday to 6 Saturday — the page's language's. */
485
+ weekStart?: number;
486
+ /** With `interval`, for a date and time field: the time zone its days are told in (IANA: "Africa/Cairo") — the page's. */
487
+ timeZone?: string;
488
+ }
489
+ export interface GroupRequest extends GroupInterval {
262
490
  model: string;
263
491
  field: string;
264
492
  filter: ResolvedFilter[];
@@ -271,14 +499,22 @@ export interface Group {
271
499
  }
272
500
  /** How a field is totalled: added up, averaged, its least or its most, or how many records hold a value in it. */
273
501
  export type TotalKind = 'sum' | 'avg' | 'min' | 'max' | 'count';
274
- export interface TotalsRequest {
502
+ export interface TotalsRequest extends GroupInterval {
275
503
  model: string;
276
504
  /** The list's search, as `list` gets it. For the ticked rows alone, add `{ field: 'id', op: 'in', value: ids }`. */
277
505
  filter: ResolvedFilter[];
278
506
  /** What to total, by field: `{ amount: ['sum'], hours: ['sum', 'avg'] }`. */
279
507
  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. */
508
+ /** 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
509
  groupBy?: string;
510
+ /**
511
+ * The field holding each amount's currency, by the money field, where each
512
+ * record keeps its own: `{ amount: 'currency_id' }`. Such an amount is
513
+ * totalled within one currency only: over records in more than one, its
514
+ * sum, average, least and most are null — no honest number — and the
515
+ * result names the currency of those it could total (`currencies`).
516
+ */
517
+ currencies?: Record<string, string>;
282
518
  }
283
519
  /**
284
520
  * Each field's totals, by field and then by kind: `totals.amount.sum`. The
@@ -293,9 +529,19 @@ export interface TotalsResult {
293
529
  totals: Totals;
294
530
  /** With `groupBy`: each value's totals, in the order `groups` gives them. */
295
531
  groups?: TotalsGroup[];
532
+ /**
533
+ * With `currencies` asked: the currency each money field's totals are in,
534
+ * by field, as its currency field holds it (a link as `{ id, label }`, a
535
+ * code as "EGP"); null where its records hold more than one, or none. A
536
+ * page writes an amount's total only in a currency it knows: the field's
537
+ * own, or the one named here.
538
+ */
539
+ currencies?: Record<string, JsonValue>;
296
540
  }
297
541
  export interface TotalsGroup extends Group {
298
542
  totals: Totals;
543
+ /** The currency of each money field's totals under this value, as `TotalsResult.currencies` says it. */
544
+ currencies?: Record<string, JsonValue>;
299
545
  }
300
546
  /**
301
547
  * A file to store: what an upload reads of a browser's `File`. Core names no
@@ -358,6 +604,23 @@ export interface SubmitRequest {
358
604
  pageId: string;
359
605
  /** The answers to the questions that were shown. Skipped steps are left out. */
360
606
  values: Values;
607
+ /** 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. */
608
+ bot?: BotReport;
609
+ }
610
+ /**
611
+ * What a page's bot check saw as its answers were sent. A person never fills the hidden box and takes longer than a
612
+ * 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
613
+ * why the app decides: keep it aside, ask the captcha's service, or refuse it.
614
+ */
615
+ export interface BotReport {
616
+ /** The hidden box was filled: no person sees it, reaches it with Tab or hears it read out. */
617
+ honeypot: boolean;
618
+ /** Sent sooner than the page's `minSeconds` after it was shown, or started again. */
619
+ tooFast: boolean;
620
+ /** Seconds from the page shown (or started again) to this send, to a tenth. */
621
+ seconds: number;
622
+ /** The captcha's token, on a page with one (`botCheck.captcha`), for the app to check with its service: null when none came. */
623
+ captcha?: string | null;
361
624
  }
362
625
  export interface SubmitResult {
363
626
  id?: RecordId;
@@ -444,3 +707,46 @@ export declare function saveRefused(problem: SaveProblem): Error & {
444
707
  };
445
708
  /** What a failed save tells: its own problem, a failed fetch as the network, anything else as it is. */
446
709
  export declare function saveProblemOf(error: unknown): SaveProblem;
710
+ /** What a booking's free times are asked with: the days, the zone they are in, and what the page knows of the booking. */
711
+ export interface SlotsRequest {
712
+ /** The page's model, for a record page; empty for a responses page. */
713
+ model: string;
714
+ /** The date and time field booked. */
715
+ field: string;
716
+ /** The record booked, so its own booking counts as free for it; null for a new one. */
717
+ recordId: RecordId | null;
718
+ /** The schedule the field's node names (`options.schedule`): a doctor's, a showroom's. The field's name unless it names one. */
719
+ schedule: string;
720
+ /** The first and the last day asked, both included, as 2026-10-01: days in `timeZone`. */
721
+ from: string;
722
+ to: string;
723
+ /** The zone the days are in, by its IANA name: the field's, its page's, or the browser's. */
724
+ timeZone: string;
725
+ /** The minutes a booking lasts, and those kept free between two, as the page says (`options.length`, `options.between`). */
726
+ length?: number;
727
+ between?: number;
728
+ /** Every value of the form: the doctor, the service, the car to drive. */
729
+ values: Values;
730
+ }
731
+ /** What a day is for booking: times free on it, all taken, not open, or a holiday. */
732
+ export type SlotDayState = 'open' | 'full' | 'closed' | 'holiday';
733
+ /** One day of a booking's: its state and the times a booking may start on it. */
734
+ export interface SlotDay {
735
+ state: SlotDayState;
736
+ /** The times free, as instants (ISO 8601, in UTC or with their offset: 2026-10-15T09:30:00+03:00), earliest first. */
737
+ free?: string[];
738
+ /** Words for the day, in the person's language: a holiday's name, why it is closed. */
739
+ note?: string;
740
+ }
741
+ /** The days asked, each by its date (2026-10-15): a day left out is closed. */
742
+ export interface SlotsResult {
743
+ days: Record<string, SlotDay>;
744
+ /** The minutes a booking lasts, when the source knows better than the page: the service's own length. */
745
+ length?: number;
746
+ }
747
+ /**
748
+ * A new record's save, made from its values alone — as a list's Import saves a row where the source has no
749
+ * `createMany`, and a memory source makes each of `createMany`'s: every value in `values`, the links of a many2many
750
+ * as one `set`, the rest as changes.
751
+ */
752
+ export declare function newRecordSave(model: string, fields: Fields, values: Values): SaveRequest;