@ai-matrx/records 0.93.1 → 0.93.3

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 (42) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/dist/{client-D4mLGnph.d.ts → client-DM3dRewB.d.ts} +1 -1
  3. package/dist/{client-BPcc-V2O.d.cts → client-icpxhaRE.d.cts} +1 -1
  4. package/dist/core/index.d.cts +3 -3
  5. package/dist/core/index.d.ts +3 -3
  6. package/dist/data/index.d.cts +2 -2
  7. package/dist/data/index.d.ts +2 -2
  8. package/dist/{declareTable-BSHX1Abl.d.cts → declareTable-BtpsSJCr.d.cts} +1 -1
  9. package/dist/{declareTable-Br_4v6L7.d.ts → declareTable-MN4WuhWK.d.ts} +1 -1
  10. package/dist/first-page/index.cjs +627 -0
  11. package/dist/first-page/index.cjs.map +1 -0
  12. package/dist/first-page/index.d.cts +1298 -0
  13. package/dist/first-page/index.d.ts +1298 -0
  14. package/dist/first-page/index.js +604 -0
  15. package/dist/first-page/index.js.map +1 -0
  16. package/dist/index.d.cts +1 -1
  17. package/dist/index.d.ts +1 -1
  18. package/dist/memory/index.d.cts +2 -2
  19. package/dist/memory/index.d.ts +2 -2
  20. package/dist/questions/index.cjs +7 -2
  21. package/dist/questions/index.cjs.map +1 -1
  22. package/dist/questions/index.d.cts +2 -2
  23. package/dist/questions/index.d.ts +2 -2
  24. package/dist/questions/index.js +7 -2
  25. package/dist/questions/index.js.map +1 -1
  26. package/dist/setup/index.d.cts +2 -2
  27. package/dist/setup/index.d.ts +2 -2
  28. package/dist/{subscription-BGgwsJ8c.d.ts → subscription-DucBLG-Y.d.ts} +1 -1
  29. package/dist/{subscription-DVM2vL4e.d.cts → subscription-ycCVo9e2.d.cts} +1 -1
  30. package/dist/templates/index.d.cts +3 -3
  31. package/dist/templates/index.d.ts +3 -3
  32. package/dist/typed-table/index.d.cts +3 -3
  33. package/dist/typed-table/index.d.ts +3 -3
  34. package/dist/{types-C3ywxkho.d.ts → types-Cl0b_Hy8.d.ts} +1 -1
  35. package/dist/{types-reNR-zMe.d.cts → types-SezYoQ5_.d.cts} +1 -1
  36. package/dist/views/index.cjs +102 -0
  37. package/dist/views/index.cjs.map +1 -1
  38. package/dist/views/index.d.cts +46 -3
  39. package/dist/views/index.d.ts +46 -3
  40. package/dist/views/index.js +102 -0
  41. package/dist/views/index.js.map +1 -1
  42. package/package.json +12 -2
@@ -0,0 +1,1298 @@
1
+ import { RecordsClient, RecordsSeed } from '@ai-matrx/records/core';
2
+
3
+ declare const ACTOR_VOCABULARY: readonly ["user", "agent", "system"];
4
+ declare const STORAGE_MODES: readonly ["light", "heavy"];
5
+ /** FLD-11's parity floor, each with the behaviour it is MADE OF (FLD-1). */
6
+ declare const PARITY_FIELD_TYPES: readonly [{
7
+ readonly parity_type: "attachment";
8
+ readonly behavior: "relation";
9
+ readonly made_of: "REC-31: a File record reached through a relation, target the kernel `File` Table";
10
+ }, {
11
+ readonly parity_type: "checkbox";
12
+ readonly behavior: "boolean";
13
+ readonly made_of: "a ticked or unticked answer, stored as a REAL boolean — and a record that never answered holds neither, so filters and group-by see three states, not two";
14
+ }, {
15
+ readonly parity_type: "currency";
16
+ readonly behavior: "range";
17
+ readonly made_of: "FLD-N-1: a number whose UNIT is the currency and whose format is currency — on the Field, never in presentation";
18
+ }, {
19
+ readonly parity_type: "datetime";
20
+ readonly behavior: "range";
21
+ readonly made_of: "a range of kind date or datetime — the dated modifier says the VALUES are dated, which is a different question";
22
+ }, {
23
+ readonly parity_type: "email";
24
+ readonly behavior: "text";
25
+ readonly made_of: "text whose format is email";
26
+ }, {
27
+ readonly parity_type: "formula";
28
+ readonly behavior: "formula";
29
+ readonly made_of: "FLD-9: a declared computation, on read or on write, evaluated by custom.rule_eval";
30
+ }, {
31
+ readonly parity_type: "lookup";
32
+ readonly behavior: "formula";
33
+ readonly made_of: "a Value read THROUGH a relation — config.via names the relation field, config.pick the Value on the far side";
34
+ }, {
35
+ readonly parity_type: "member";
36
+ readonly behavior: "relation";
37
+ readonly made_of: "a person: a relation whose target is the kernel `Person` Table";
38
+ }, {
39
+ readonly parity_type: "multi_select";
40
+ readonly behavior: "list";
41
+ readonly made_of: "many choices from the same Table — the multi modifier, never a second behaviour";
42
+ }, {
43
+ readonly parity_type: "percent";
44
+ readonly behavior: "range";
45
+ readonly made_of: "a number whose unit is % and whose format is percent";
46
+ }, {
47
+ readonly parity_type: "phone";
48
+ readonly behavior: "text";
49
+ readonly made_of: "text whose format is phone";
50
+ }, {
51
+ readonly parity_type: "rollup";
52
+ readonly behavior: "formula";
53
+ readonly made_of: "an aggregate ALONG a relation — config.via, config.of, config.agg; far-side ids are taken DISTINCT, so nothing is counted twice";
54
+ }, {
55
+ readonly parity_type: "select";
56
+ readonly behavior: "list";
57
+ readonly made_of: "one choice from a Table shown as a list (FLD-5/FLD-6)";
58
+ }, {
59
+ readonly parity_type: "url";
60
+ readonly behavior: "text";
61
+ readonly made_of: "text whose format is url, with the pattern Rule that makes the format enforceable";
62
+ }];
63
+ /** The node kinds `custom.rule_eval` evaluates, and the lane that owns each. */
64
+ declare const RULE_NODE_KINDS: readonly [{
65
+ readonly node: "actor_at_least";
66
+ readonly evaluated_by: "PIPELINES";
67
+ }, {
68
+ readonly node: "add";
69
+ readonly evaluated_by: "W1-RULE";
70
+ }, {
71
+ readonly node: "and";
72
+ readonly evaluated_by: "W1-RULE";
73
+ }, {
74
+ readonly node: "concat";
75
+ readonly evaluated_by: "W1-RULE";
76
+ }, {
77
+ readonly node: "const";
78
+ readonly evaluated_by: "W1-RULE";
79
+ }, {
80
+ readonly node: "div";
81
+ readonly evaluated_by: "W1-RULE";
82
+ }, {
83
+ readonly node: "eq";
84
+ readonly evaluated_by: "W1-RULE";
85
+ }, {
86
+ readonly node: "field";
87
+ readonly evaluated_by: "W1-RULE";
88
+ }, {
89
+ readonly node: "fx.abs";
90
+ readonly evaluated_by: "GRID-PRIMITIVES G3 (custom.formula_eval)";
91
+ }, {
92
+ readonly node: "fx.add";
93
+ readonly evaluated_by: "GRID-PRIMITIVES G3 (custom.formula_eval)";
94
+ }, {
95
+ readonly node: "fx.and";
96
+ readonly evaluated_by: "GRID-PRIMITIVES G3 (custom.formula_eval)";
97
+ }, {
98
+ readonly node: "fx.arraycompact";
99
+ readonly evaluated_by: "GRID-PRIMITIVES G3 (custom.formula_eval)";
100
+ }, {
101
+ readonly node: "fx.arrayjoin";
102
+ readonly evaluated_by: "GRID-PRIMITIVES G3 (custom.formula_eval)";
103
+ }, {
104
+ readonly node: "fx.autonumber";
105
+ readonly evaluated_by: "GRID-PRIMITIVES G3 (custom.formula_eval)";
106
+ }, {
107
+ readonly node: "fx.average";
108
+ readonly evaluated_by: "GRID-PRIMITIVES G3 (custom.formula_eval)";
109
+ }, {
110
+ readonly node: "fx.blank";
111
+ readonly evaluated_by: "GRID-PRIMITIVES G3 (custom.formula_eval)";
112
+ }, {
113
+ readonly node: "fx.concatenate";
114
+ readonly evaluated_by: "GRID-PRIMITIVES G3 (custom.formula_eval)";
115
+ }, {
116
+ readonly node: "fx.contains";
117
+ readonly evaluated_by: "GRID-PRIMITIVES G3 (custom.formula_eval)";
118
+ }, {
119
+ readonly node: "fx.created_time";
120
+ readonly evaluated_by: "GRID-PRIMITIVES G3 (custom.formula_eval)";
121
+ }, {
122
+ readonly node: "fx.dateadd";
123
+ readonly evaluated_by: "GRID-PRIMITIVES G3 (custom.formula_eval)";
124
+ }, {
125
+ readonly node: "fx.datediff";
126
+ readonly evaluated_by: "GRID-PRIMITIVES G3 (custom.formula_eval)";
127
+ }, {
128
+ readonly node: "fx.datetime_format";
129
+ readonly evaluated_by: "GRID-PRIMITIVES G3 (custom.formula_eval)";
130
+ }, {
131
+ readonly node: "fx.day";
132
+ readonly evaluated_by: "GRID-PRIMITIVES G3 (custom.formula_eval)";
133
+ }, {
134
+ readonly node: "fx.div";
135
+ readonly evaluated_by: "GRID-PRIMITIVES G3 (custom.formula_eval)";
136
+ }, {
137
+ readonly node: "fx.eq";
138
+ readonly evaluated_by: "GRID-PRIMITIVES G3 (custom.formula_eval)";
139
+ }, {
140
+ readonly node: "fx.find";
141
+ readonly evaluated_by: "GRID-PRIMITIVES G3 (custom.formula_eval)";
142
+ }, {
143
+ readonly node: "fx.gt";
144
+ readonly evaluated_by: "GRID-PRIMITIVES G3 (custom.formula_eval)";
145
+ }, {
146
+ readonly node: "fx.gte";
147
+ readonly evaluated_by: "GRID-PRIMITIVES G3 (custom.formula_eval)";
148
+ }, {
149
+ readonly node: "fx.if";
150
+ readonly evaluated_by: "GRID-PRIMITIVES G3 (custom.formula_eval)";
151
+ }, {
152
+ readonly node: "fx.isblank";
153
+ readonly evaluated_by: "GRID-PRIMITIVES G3 (custom.formula_eval)";
154
+ }, {
155
+ readonly node: "fx.left";
156
+ readonly evaluated_by: "GRID-PRIMITIVES G3 (custom.formula_eval)";
157
+ }, {
158
+ readonly node: "fx.len";
159
+ readonly evaluated_by: "GRID-PRIMITIVES G3 (custom.formula_eval)";
160
+ }, {
161
+ readonly node: "fx.lower";
162
+ readonly evaluated_by: "GRID-PRIMITIVES G3 (custom.formula_eval)";
163
+ }, {
164
+ readonly node: "fx.lt";
165
+ readonly evaluated_by: "GRID-PRIMITIVES G3 (custom.formula_eval)";
166
+ }, {
167
+ readonly node: "fx.lte";
168
+ readonly evaluated_by: "GRID-PRIMITIVES G3 (custom.formula_eval)";
169
+ }, {
170
+ readonly node: "fx.max";
171
+ readonly evaluated_by: "GRID-PRIMITIVES G3 (custom.formula_eval)";
172
+ }, {
173
+ readonly node: "fx.min";
174
+ readonly evaluated_by: "GRID-PRIMITIVES G3 (custom.formula_eval)";
175
+ }, {
176
+ readonly node: "fx.mod";
177
+ readonly evaluated_by: "GRID-PRIMITIVES G3 (custom.formula_eval)";
178
+ }, {
179
+ readonly node: "fx.modified_time";
180
+ readonly evaluated_by: "GRID-PRIMITIVES G3 (custom.formula_eval)";
181
+ }, {
182
+ readonly node: "fx.month";
183
+ readonly evaluated_by: "GRID-PRIMITIVES G3 (custom.formula_eval)";
184
+ }, {
185
+ readonly node: "fx.mul";
186
+ readonly evaluated_by: "GRID-PRIMITIVES G3 (custom.formula_eval)";
187
+ }, {
188
+ readonly node: "fx.ne";
189
+ readonly evaluated_by: "GRID-PRIMITIVES G3 (custom.formula_eval)";
190
+ }, {
191
+ readonly node: "fx.neg";
192
+ readonly evaluated_by: "GRID-PRIMITIVES G3 (custom.formula_eval)";
193
+ }, {
194
+ readonly node: "fx.not";
195
+ readonly evaluated_by: "GRID-PRIMITIVES G3 (custom.formula_eval)";
196
+ }, {
197
+ readonly node: "fx.now";
198
+ readonly evaluated_by: "GRID-PRIMITIVES G3 (custom.formula_eval)";
199
+ }, {
200
+ readonly node: "fx.or";
201
+ readonly evaluated_by: "GRID-PRIMITIVES G3 (custom.formula_eval)";
202
+ }, {
203
+ readonly node: "fx.regex_match";
204
+ readonly evaluated_by: "GRID-PRIMITIVES G3 (custom.formula_eval)";
205
+ }, {
206
+ readonly node: "fx.right";
207
+ readonly evaluated_by: "GRID-PRIMITIVES G3 (custom.formula_eval)";
208
+ }, {
209
+ readonly node: "fx.round";
210
+ readonly evaluated_by: "GRID-PRIMITIVES G3 (custom.formula_eval)";
211
+ }, {
212
+ readonly node: "fx.sub";
213
+ readonly evaluated_by: "GRID-PRIMITIVES G3 (custom.formula_eval)";
214
+ }, {
215
+ readonly node: "fx.substitute";
216
+ readonly evaluated_by: "GRID-PRIMITIVES G3 (custom.formula_eval)";
217
+ }, {
218
+ readonly node: "fx.sum";
219
+ readonly evaluated_by: "GRID-PRIMITIVES G3 (custom.formula_eval)";
220
+ }, {
221
+ readonly node: "fx.switch";
222
+ readonly evaluated_by: "GRID-PRIMITIVES G3 (custom.formula_eval)";
223
+ }, {
224
+ readonly node: "fx.today";
225
+ readonly evaluated_by: "GRID-PRIMITIVES G3 (custom.formula_eval)";
226
+ }, {
227
+ readonly node: "fx.trim";
228
+ readonly evaluated_by: "GRID-PRIMITIVES G3 (custom.formula_eval)";
229
+ }, {
230
+ readonly node: "fx.upper";
231
+ readonly evaluated_by: "GRID-PRIMITIVES G3 (custom.formula_eval)";
232
+ }, {
233
+ readonly node: "fx.workday";
234
+ readonly evaluated_by: "GRID-PRIMITIVES G3 (custom.formula_eval)";
235
+ }, {
236
+ readonly node: "fx.year";
237
+ readonly evaluated_by: "GRID-PRIMITIVES G3 (custom.formula_eval)";
238
+ }, {
239
+ readonly node: "gt";
240
+ readonly evaluated_by: "W1-RULE";
241
+ }, {
242
+ readonly node: "gte";
243
+ readonly evaluated_by: "W1-RULE";
244
+ }, {
245
+ readonly node: "length";
246
+ readonly evaluated_by: "W1-RULE";
247
+ }, {
248
+ readonly node: "lt";
249
+ readonly evaluated_by: "W1-RULE";
250
+ }, {
251
+ readonly node: "lte";
252
+ readonly evaluated_by: "W1-RULE";
253
+ }, {
254
+ readonly node: "matches";
255
+ readonly evaluated_by: "W1-RULE";
256
+ }, {
257
+ readonly node: "merge_field";
258
+ readonly evaluated_by: "W5-MERGE-B";
259
+ }, {
260
+ readonly node: "mul";
261
+ readonly evaluated_by: "W1-RULE";
262
+ }, {
263
+ readonly node: "ne";
264
+ readonly evaluated_by: "W1-RULE";
265
+ }, {
266
+ readonly node: "not";
267
+ readonly evaluated_by: "W1-RULE";
268
+ }, {
269
+ readonly node: "or";
270
+ readonly evaluated_by: "W1-RULE";
271
+ }, {
272
+ readonly node: "parent_field";
273
+ readonly evaluated_by: "W1-RULE-APPLY";
274
+ }, {
275
+ readonly node: "present";
276
+ readonly evaluated_by: "W1-RULE";
277
+ }, {
278
+ readonly node: "previous";
279
+ readonly evaluated_by: "PIPELINES";
280
+ }, {
281
+ readonly node: "sibling_count";
282
+ readonly evaluated_by: "STAGE-RULES";
283
+ }, {
284
+ readonly node: "stage_count";
285
+ readonly evaluated_by: "PIPELINES";
286
+ }, {
287
+ readonly node: "sub";
288
+ readonly evaluated_by: "W1-RULE";
289
+ }, {
290
+ readonly node: "within";
291
+ readonly evaluated_by: "CHAIR-FILTER";
292
+ }];
293
+
294
+ /**
295
+ * VAL-8. Who wrote a value. `user`, `agent`, `system` — enforced by every write
296
+ * door, and the three retired words (`human`, `ai`, `code`) are translated by
297
+ * the store's own `custom.actor_word`, never by a caller.
298
+ */
299
+ type Actor = (typeof ACTOR_VOCABULARY)[number];
300
+ /**
301
+ * REC-4. `heavy` is indexed storage with Rules on write; `light` is
302
+ * whole-document storage with Rules on read. Per Table, one store.
303
+ */
304
+ type StorageMode = (typeof STORAGE_MODES)[number];
305
+ /** FLD-1. The five behaviours a Field can have. One, exactly, and closed. */
306
+ type FieldBehavior = (typeof PARITY_FIELD_TYPES)[number]["behavior"];
307
+ /** The node kinds `custom.rule_eval` evaluates. */
308
+ type RuleNodeKind = (typeof RULE_NODE_KINDS)[number]["node"];
309
+ /**
310
+ * REC-32. The seven table types, closed. The parenthesised names are the
311
+ * retired ones the doctrine records; never coin an eighth.
312
+ */
313
+ declare const TABLE_TYPES: readonly ["entity", "detail", "reference", "ledger", "restricted", "system", "deprecated"];
314
+ type TableType = (typeof TABLE_TYPES)[number];
315
+ /** REL-2. What happens to the far side, separate from flavour. */
316
+ declare const ON_DELETE: readonly ["cascade", "set_null", "restrict"];
317
+ type OnDelete = (typeof ON_DELETE)[number];
318
+ /** FLD-7. Where a Field's values come from. */
319
+ declare const FIELD_SOURCES: readonly ["manual", "formula", "agent", "synced"];
320
+ type FieldSource = (typeof FIELD_SOURCES)[number];
321
+ /** FLD-9. A Formula declares whether it computes on read or on write. */
322
+ declare const COMPUTE_ON: readonly ["read", "write"];
323
+ type ComputeOn = (typeof COMPUTE_ON)[number];
324
+ /**
325
+ * AGT-7 / FLD-12. A Field's sensitivity sets a ceiling a merge field can never
326
+ * widen — and these are THE STORE'S four words, checked against its own guard.
327
+ *
328
+ * This list used to read `standard · sensitive · secret`, which the store has
329
+ * never accepted: writing a Field with `sensitivity: "standard"` is refused
330
+ * with "the field <name> has to say how sensitive its values are, and it says
331
+ * standard". The knob `custom/field_sensitivity_levels` keys off these same
332
+ * four words to decide which level may read and edit each one, so a package
333
+ * carrying three different words could never have agreed with it. Corrected
334
+ * against the live store, 2026-09-18.
335
+ */
336
+ declare const FIELD_SENSITIVITIES: readonly ["public", "internal", "confidential", "restricted"];
337
+ type FieldSensitivity = (typeof FIELD_SENSITIVITIES)[number];
338
+ /**
339
+ * FLD-12. WHETHER AN AGENT MAY SEE A FIELD'S VALUES, and these are the STORE'S
340
+ * four words — not the merge field's delivery tiers.
341
+ *
342
+ * `Field.context_policy` used to be typed as `Delivery` here, which is a
343
+ * different question with a different vocabulary: writing a Field with
344
+ * `context_policy: "inline"` is refused with "the field <name> has to say
345
+ * whether an agent may see its values, and it says inline". Delivery is about
346
+ * HOW MUCH of a value a merge field carries into a turn; this is about WHETHER
347
+ * the value reaches an agent at all, and conflating them let a screen offer a
348
+ * word the store has never accepted. Corrected against the live store,
349
+ * 2026-09-18.
350
+ */
351
+ declare const CONTEXT_POLICIES: readonly ["include", "summarize", "exclude", "on_request"];
352
+ type ContextPolicy = (typeof CONTEXT_POLICIES)[number];
353
+ /** REC-1. How a Table shows itself. */
354
+ declare const TABLE_DISPLAYS: readonly ["list", "grid", "board", "calendar", "timeline"];
355
+ type TableDisplay = (typeof TABLE_DISPLAYS)[number];
356
+
357
+ /**
358
+ * The structural shape of a PostgREST failure as supabase-js resolves it: a
359
+ * PLAIN OBJECT parsed from the response body, NOT an `Error`. Reading
360
+ * `.message` off `instanceof Error` discards the real database message on every
361
+ * genuine failure.
362
+ */
363
+ interface PgError {
364
+ code?: string | null;
365
+ message: string;
366
+ details?: string | null;
367
+ hint?: string | null;
368
+ }
369
+
370
+ /** A `uuid` as the store writes it. */
371
+ type Uuid = string;
372
+ /** An ISO-8601 timestamp, as jsonb carries it. */
373
+ type Timestamp = string;
374
+ /**
375
+ * DOOR-SPEED. One key of a page's order, as `custom.read_records_page` takes it. `as` is the
376
+ * column's own type — `number` sorts numerically (text that is not a number sorts last),
377
+ * `date` sorts ISO dates and instants, `text` (the default) sorts by lower-cased text. A choice
378
+ * column always sorts by the WORD a person sees; the store knows which columns are choices.
379
+ * NULLS LAST both ways, record id as the tie-breaker.
380
+ */
381
+ interface RecordPageSort {
382
+ field: string;
383
+ direction?: "asc" | "desc";
384
+ as?: "text" | "number" | "date";
385
+ }
386
+
387
+ /**
388
+ * A rule expression as `custom.rule_eval` ACTUALLY evaluates it — read off the
389
+ * door's own body, not invented here. A leaf is the node's name carrying its
390
+ * payload (`{"const": 3}`, `{"field": "<uuid>"}`); an operator is
391
+ * `{"op": "eq", "args": [...]}`.
392
+ *
393
+ * `field` / `parent_field` name a Field BY ID (REC-17 — the door refuses
394
+ * `field_name`, `field_key` and `field_label` by name), and `merge_field` is
395
+ * the resolver's seam, storable today and refused `0A000` until W5-MERGE-B.
396
+ *
397
+ * `p_values` is keyed by the Field's KEY: the door resolves the id to a key
398
+ * (`custom.rule_field_key`) and then reads `p_values -> key`.
399
+ */
400
+ type RuleExpression = {
401
+ const: unknown;
402
+ } | {
403
+ field: Uuid;
404
+ } | {
405
+ parent_field: Uuid;
406
+ } | {
407
+ merge_field: string;
408
+ } | {
409
+ op: RuleNodeKind;
410
+ args: RuleExpression[];
411
+ };
412
+
413
+ /** The measures the store offers. Asked of `custom.agg_operations()` at run time too. */
414
+ type AggregateOperation = "count" | "sum" | "avg" | "min" | "max"
415
+ /** GRID-PRIMITIVES G1: the summary bar's other four. Blank = no value, JSON null or "". */
416
+ | "median" | "filled" | "empty" | "unique"
417
+ /**
418
+ * Notion's calculation row (Spaces N6): the share of the rows asked with no value / a value, as a
419
+ * percent 0..100 (NULL over no rows); and the RANGE, largest minus smallest (a date column: the days
420
+ * between its earliest and latest moment).
421
+ */
422
+ | "percent_empty" | "percent_filled" | "range";
423
+ /**
424
+ * A HALF-OPEN MOMENT WINDOW: `from` is inclusive, `to` is exclusive. Half-open
425
+ * is the only shape that makes "September" and "October" meet exactly once —
426
+ * an inclusive `to` either double-counts the boundary moment or drops it,
427
+ * depending on the reader, and both of those are a chart telling a lie.
428
+ */
429
+ interface AggregateWindow {
430
+ from?: string | null;
431
+ to?: string | null;
432
+ }
433
+ /**
434
+ * ONE FILTER VALUE, as `custom.record_aggregate` and `custom.dashboard_run`
435
+ * both read it: a SCALAR is an equality, and an OBJECT is a window. It is not
436
+ * `unknown` on purpose — a filter value the store cannot read is a refusal a
437
+ * caller should get from the compiler, not from the database.
438
+ */
439
+ type AggregateFilter = string | number | boolean | null | AggregateWindow;
440
+ /**
441
+ * 🚨 THE STORE'S ONE FILTER SHAPE, AND THE ONLY NAME FOR IT.
442
+ *
443
+ * An object of Field key -> value. A scalar is an equality, `null` is UNSET
444
+ * (the third state of a tick box), an object is a half-open time window.
445
+ *
446
+ * It is not the aggregate's private shape and never was. `platform.saved_view`
447
+ * has carried exactly this as `definition -> 'filters'` across five live
448
+ * surface keys since DOOR-18; `custom.view_declare` writes it;
449
+ * `custom.agg_view_admits` reads it to decide whether a change belongs to a
450
+ * saved view; `custom.record_aggregate` takes it as `p_filter` to COMPUTE a
451
+ * dashboard number; and `custom.read_records_matching` takes it to OPEN the
452
+ * rows behind that number. One `custom.record_filter_sql` in the database turns
453
+ * it into SQL for the last two, so the number and the rows cannot disagree.
454
+ *
455
+ * Invent a second one and a chart can compute a total whose drill-through
456
+ * lands somewhere else, which is the only way a drill-through can lie.
457
+ */
458
+ type RecordFilter = Record<string, AggregateFilter>;
459
+ /**
460
+ * S2-PRIME FILTER-GROUPS — A READ DOOR'S QUESTION, IN EITHER OF ITS TWO SHAPES.
461
+ *
462
+ * The flat `RecordFilter` above (field key → scalar / `null` / window, AND only) is what
463
+ * dashboards, drill-throughs and the notifier carry. A `RuleExpression` — `{op, args}`, ALL / ANY /
464
+ * NOT groups nested to any depth, fields by id — is what the condition builder, a view's own
465
+ * filter and every membership Rule speak. `custom.record_filter_sql(org, table, filter)` turns
466
+ * EITHER into the one WHERE clause the grid (`list`), the aggregate and the board all use, so the
467
+ * three can never disagree about which records a view holds.
468
+ */
469
+ type StoreFilter = RecordFilter | RuleExpression;
470
+ /** One measure. `count` counts rows and needs no Field; the rest read one. */
471
+ interface AggregateMeasure {
472
+ op: AggregateOperation;
473
+ /** The Field's key. Required for every operation except `count`. */
474
+ key?: string;
475
+ }
476
+
477
+ /** FLD-3. A constraint attached to a Field. A Rule, never a behaviour. */
478
+ interface FieldRule {
479
+ /**
480
+ * `unique` (FLD-3 / B1): no two live records of the Table hold the same value, compared
481
+ * case- and outer-space-blind. The store enforces it under an advisory lock
482
+ * (`custom._unique_rule_holds`); it is also what lets `recordUpsert` find "the" record by a key.
483
+ * The mirror never predicts it — it needs a query.
484
+ */
485
+ kind: "min" | "max" | "length" | "pattern" | "equals_field" | "differs_from_field" | "unique";
486
+ /**
487
+ * The rule's own number or word: the least / the most for `min` / `max`, the
488
+ * LONGEST for `length`, the pattern for `pattern`, the other column's key for
489
+ * `equals_field` / `differs_from_field`. A `length` rule that only says how
490
+ * SHORT a value may be carries `min` and no `value`.
491
+ */
492
+ value?: string | number;
493
+ /**
494
+ * STORE-RULE-GAPS (the older grid's `patternHint`). On a `pattern` rule only:
495
+ * how a person writes it, e.g. `"949-555-0142"`. The store refuses an example
496
+ * its own pattern would refuse, and a refused value is told "Enter it like
497
+ * 949-555-0142." (the hint, and `detail.example`).
498
+ */
499
+ example?: string;
500
+ /**
501
+ * STORE-RULE-GAPS (the older grid's `minLength`). On a `length` rule only: the
502
+ * fewest characters. A blank is `required`'s question, never "too short".
503
+ */
504
+ min?: number;
505
+ /** FLD-10. Empty means "wherever the Field applies". */
506
+ applies_to_types?: string[];
507
+ }
508
+ /**
509
+ * `custom.field` — the ONE definitions table (FLD-8), organization-scoped, so
510
+ * two organizations add different fields to the same standard table.
511
+ */
512
+ /**
513
+ * WHICH OF THE OTHER RECORD'S COLUMNS A RELATION SHOWS (REL-DISP, 2026-09-21).
514
+ *
515
+ * A Table has ONE stored default reference column — its `title_field` — and until
516
+ * this existed every relation pointing at that Table could display that column and
517
+ * nothing else. A Jobs board could read "Maria Chen" but never "Maria Chen —
518
+ * Rincon", and two columns could not be joined into one string at all.
519
+ *
520
+ * ABSENT MEANS THE TABLE'S OWN COLUMN. A field with no `display` resolves exactly
521
+ * as it did before, so nothing changes for a column nobody has given a spec to.
522
+ *
523
+ * The store judges it once, in `custom._display_spec_for`: every column named must
524
+ * exist on the target Table, and the refusal names the one that does not. It is
525
+ * resolved by ONE body, `custom._words_for`, which `custom.record_words`,
526
+ * `custom._card_words`, `custom.relation_words` and `custom.relation_words_many`
527
+ * are all one-liners onto — so the thirteen store-side readers inherit it too.
528
+ *
529
+ * A display column that is itself a relation resolves ONE hop and never a second.
530
+ */
531
+ interface RelationDisplay {
532
+ /** The target's columns, in the order they are shown. */
533
+ columns: string[];
534
+ /** What goes between them. Defaults to a single space. */
535
+ separator: string;
536
+ }
537
+ interface Field {
538
+ id: Uuid;
539
+ organization_id: Uuid;
540
+ /** Set when this Field extends a standard entity rather than a custom Table. */
541
+ entity_definition_id: Uuid | null;
542
+ /** The token of the Table this Field belongs to. */
543
+ table_token: string | null;
544
+ key: string;
545
+ label: string;
546
+ name: string | null;
547
+ /** FLD-1. Exactly one of five. */
548
+ type: FieldBehavior;
549
+ /** FLD-2. The shape of the value, never a second behaviour. */
550
+ multi: boolean;
551
+ /** FLD-2. The values carry their own dates — the world clock, not History. */
552
+ dated: boolean;
553
+ /** FLD-3. Any number of attached validation Rules. */
554
+ rules: FieldRule[];
555
+ config: Record<string, unknown>;
556
+ required: boolean;
557
+ default: unknown;
558
+ sort: number | null;
559
+ /** The Table this relation points at. */
560
+ relation_target: Uuid | null;
561
+ relation_max: number | null;
562
+ on_target_delete: OnDelete | null;
563
+ /** REL-9. The reverse is visible from both ends and is NOT a second Field. */
564
+ inverse_key: string | null;
565
+ /** REL-DISP. Which of the target's columns this one shows. Null = the Table's own. */
566
+ display: RelationDisplay | null;
567
+ source: FieldSource;
568
+ source_config: Record<string, unknown>;
569
+ /** FLD-9. A Formula declares whether it computes on read or on write. */
570
+ compute_on: ComputeOn | null;
571
+ /** The unit, on the Field, because it changes the meaning. Never presentation. */
572
+ unit: string | null;
573
+ format: string | null;
574
+ /** AGT-7. A ceiling a merge field's delivery can never widen. */
575
+ sensitivity: FieldSensitivity | null;
576
+ /** FLD-12. Whether an agent may see this Field's values: include · summarize · exclude · on_request. */
577
+ context_policy: ContextPolicy | null;
578
+ /** AGT-6. How often a `source = agent` Field is refilled. */
579
+ review_interval_days: number | null;
580
+ /**
581
+ * §D says this is on the wrong object and moves to the merge field. It is
582
+ * still carried here because the live column is, and the client reads what
583
+ * the store holds — never what the plan wishes it held.
584
+ */
585
+ depends_on: Uuid[] | null;
586
+ /** FLD-10. Which record types this Field applies to. Empty means all of them. */
587
+ applies_to_types: string[];
588
+ /** FLD-5 / FLD-6. A list field's choices are the records of THIS Table. */
589
+ options_table_id: Uuid | null;
590
+ created_by: Uuid | null;
591
+ updated_by: Uuid | null;
592
+ created_at: Timestamp;
593
+ updated_at: Timestamp;
594
+ version: number;
595
+ metadata: Record<string, unknown>;
596
+ visibility: string;
597
+ }
598
+
599
+ /**
600
+ * REC-1. A Table declares its fields, exactly one Home, and the properties
601
+ * `display`, `ordered`, `heavy` | `light`, `retention` and `detail`.
602
+ */
603
+ interface Table {
604
+ id: Uuid;
605
+ organization_id: Uuid;
606
+ /**
607
+ * The access ladder's Table "Level": organization · public · confidential · private. Absent is
608
+ * the organization's own. Confidential and Private records have no "Shown to".
609
+ */
610
+ level?: string | null;
611
+ /** The token this Table is addressed by. */
612
+ slug: string;
613
+ name: string;
614
+ label_singular: string | null;
615
+ label_plural: string | null;
616
+ icon: string | null;
617
+ color: string | null;
618
+ /** REC-32. One of the seven, closed. */
619
+ type: TableType;
620
+ /** REC-11. A detail Table inherits only: no direct shares, never a Home. */
621
+ detail: boolean;
622
+ parent_token: string | null;
623
+ /** AGT-1 / AGT-2. On by default for a custom table; an organization turns it off. */
624
+ agent_writable: boolean;
625
+ display: TableDisplay | null;
626
+ ordered: boolean;
627
+ /** REC-4. `heavy` is indexed with Rules on write; `light` is the whole document with Rules on read. */
628
+ weight: StorageMode;
629
+ /** REC-23. How long a soft delete stays reversible. */
630
+ retention_days: number | null;
631
+ /** REC-2. Every Table has one; without it a record cannot be a chip. */
632
+ title_field: string | null;
633
+ /** The field ids this Table declares, in declaration order. */
634
+ fields: Uuid[];
635
+ default_sort: string | null;
636
+ row_order: string | null;
637
+ /** REC-14. Where this Table lives. Exactly one primary Home. */
638
+ home_id: Uuid | null;
639
+ /** REC-27. One of the nine, defined in code and not editable by Migration. */
640
+ is_kernel: boolean;
641
+ created_by: Uuid | null;
642
+ updated_by: Uuid | null;
643
+ created_at: Timestamp;
644
+ updated_at: Timestamp;
645
+ version: number;
646
+ metadata: Record<string, unknown>;
647
+ /** The old lane word; read `shown_to` and `published_to_web` (access ladder), which the store answers now. */
648
+ visibility: string;
649
+ /** Access ladder "Shown to" (which lists show it; never a lock). Absent on a store that does not answer it. */
650
+ shown_to?: string | null;
651
+ /** Access ladder "Published to the web". Absent on a store that does not answer it. */
652
+ published_to_web?: boolean;
653
+ /**
654
+ * Lane 10 FD. Part of the business's day-one data (Patients, Therapists, Services) — a mark, not
655
+ * storage. Absent is false. Only admin on the Table changes it (custom._table_shape_guard).
656
+ */
657
+ foundation?: boolean;
658
+ /**
659
+ * Present only on what `tableFind({ …, keptFor })` answers for an ARCHIVED table of that slug and
660
+ * kept_for (the store's `archived_at`); such an answer carries only `id`, `organization_id` and this.
661
+ */
662
+ archived_at?: Timestamp | null;
663
+ }
664
+
665
+ /**
666
+ * Does this column hold NUMBERS the store keeps — a number, money or percent column, or the record number
667
+ * (`config.system: autonumber`, worked out once on save and kept)?
668
+ */
669
+ declare function holdsNumbers(field: Field): boolean;
670
+ /** How the store should compare a column's values when it sorts by it. The record number sorts 1, 2 … 10. */
671
+ declare function sortAsFor(field: Field): NonNullable<RecordPageSort["as"]>;
672
+ /** One column sort, by Field key — the grid's header sort and the table's default sort share it. */
673
+ interface ColumnSort {
674
+ /** The Field KEY. */
675
+ id: string;
676
+ direction: "asc" | "desc";
677
+ }
678
+ /** A header sort in the store's page-order shape. A sort on a column that is gone is no sort. */
679
+ declare function pageSortFor(fields: readonly Field[], sort: ColumnSort | null | undefined): RecordPageSort[];
680
+ /**
681
+ * THE TABLE'S OWN SORT — or `null`. The store writes `default_sort` as `[{field, direction}]` with `field` a Field
682
+ * KEY; older rows hold a bare key or `"key desc"`, so all three shapes are read. A sort naming a column the table
683
+ * no longer has is DROPPED, never guessed at. Only the first sort key is used.
684
+ */
685
+ declare function defaultSortOf(table: Pick<Table, "default_sort"> | null | undefined, fields: readonly Field[]): ColumnSort | null;
686
+ /** The parts of a grid grouping the reads depend on (records-ui's `GridGrouping` is one). */
687
+ interface GroupingShape {
688
+ field: string;
689
+ then?: readonly unknown[] | undefined;
690
+ across?: unknown;
691
+ trail?: readonly unknown[] | undefined;
692
+ }
693
+ /**
694
+ * A grouping the merged grid keeps as ROWS under group headers: one level, no pivot, no drill trail. Anything else
695
+ * opens the drill, which reads other doors.
696
+ */
697
+ declare function isPlainGrouping<G extends GroupingShape>(grouping: G | undefined): grouping is G;
698
+ /** The page is asked in group order first, then in the sort the person chose. */
699
+ declare function groupedPageSort(fields: readonly Field[], groupKey: string | null, sort: RecordPageSort[]): RecordPageSort[];
700
+ /** Every summary the store answers (`custom.record_aggregate`); a chosen summary outside it is never sent. */
701
+ declare const SUMMARY_OPERATIONS: readonly AggregateOperation[];
702
+ /** Every group's count and each chosen summary: `record_aggregate`'s measures when grouped by the grouping column. */
703
+ declare function groupFactMeasures(chosen: Record<string, string>): AggregateMeasure[];
704
+ /** The summary bar's measures over the whole table: none when nothing is chosen. */
705
+ declare function summaryBarMeasures(chosen: Record<string, string>): AggregateMeasure[];
706
+ /** The keys of the choice columns, in the table's field order: each has a header filter list with counts. */
707
+ declare function choiceFieldKeys(fields: readonly Field[]): string[];
708
+ /** How many links a grid cell draws of a reverse column (the chips; the rest is "+N"). */
709
+ declare const REVERSE_CELL_CHIPS = 3;
710
+ /**
711
+ * THE VIEW'S OWN QUESTION, as the grid asks it: its Rule, else its flat filter (an empty flat filter is none).
712
+ * The grid's column filters are laid over this by records-ui's `storeQuestion`; with none, this is the question.
713
+ */
714
+ declare function viewFilterOf(shown: {
715
+ where?: StoreFilter | null | undefined;
716
+ filters?: StoreFilter | null | undefined;
717
+ } | null, _fields?: readonly Field[]): StoreFilter | undefined;
718
+
719
+ /** Who a question is asked for (the access lanes, applied to counting). */
720
+ type DrillLane = "mine" | "organization" | "platform";
721
+ type DrillGrain = "hour" | "day" | "week" | "month" | "quarter" | "year";
722
+ type DrillMeasureOp = "count" | "count_distinct" | "sum" | "avg" | "min" | "max" | "median" | "percentile" | "rate" | "filled" | "empty" | "ratio" | "sum_of" | "span";
723
+ /**
724
+ * The operations a question may ask ad hoc (`{op, of}`); a ratio, a percentile and a run rate are
725
+ * declared, never ad hoc (each needs more than a column: its parts, its p, its per).
726
+ */
727
+ type DrillAdHocOp = Exclude<DrillMeasureOp, "ratio" | "percentile" | "rate" | "sum_of">;
728
+ /**
729
+ * A threshold on the groups of an answer (`having`). Judged on the current window, and applied
730
+ * BEFORE the cut into Other: a group that misses any threshold is added into Other with the groups
731
+ * past the limit, so the answer still adds up to its total; `distinct_groups` counts the groups that
732
+ * met every threshold and the total row says how many did ("3 of 41 groups meet the rule …").
733
+ * Exactly one of `value` / `share_of_total` / `times_median`.
734
+ */
735
+ interface DrillHaving {
736
+ /** A Measure the question shows (a screen shows the number it filters on). */
737
+ measure: string;
738
+ /** At least / more than; at most / less than (lane DRILL-FLIP-FIXES: "groups where cost ≤ 5"). */
739
+ op: ">=" | ">" | "<=" | "<";
740
+ /** The group's value against a number. */
741
+ value?: number;
742
+ /** The group's value against this percent (0–100] of the window's total; additive Measures only. */
743
+ share_of_total?: number;
744
+ /** The group's value against this multiple of the median group's value. */
745
+ times_median?: number;
746
+ /** With `times_median`: the median of the groups above zero only. */
747
+ median_nonzero?: boolean;
748
+ /**
749
+ * The address of the drill setting that holds the line (organizations and people decide):
750
+ * `drill.finding.<definition>.<finding>.<knob>` inside a finding. When present the setting's value
751
+ * is the threshold and the number written beside it is its default. Resolved by the door through
752
+ * platform.drill_knob; clients read the same value from platform.knob_snapshot.
753
+ */
754
+ knob?: string;
755
+ }
756
+ /** A question — what a Saved view stores and the explorer's URL encodes. */
757
+ interface DrillQuestion {
758
+ /** Dimension keys, outermost first; a time as `key:grain` ("created_at:week"). At most four. */
759
+ by?: string[];
760
+ /** One Dimension drawn as the columns of a pivot. */
761
+ across?: string;
762
+ /** Measure keys, or an ad hoc `{op, of}` over a column the definition reads. */
763
+ show?: Array<string | {
764
+ op: DrillAdHocOp;
765
+ of?: string;
766
+ }>;
767
+ /**
768
+ * The store's own filter shape: a scalar is an equality, `null` is "not set", a list is any of
769
+ * them, `{from, to}` is a half-open range; `key:grain` = one period by its label.
770
+ */
771
+ where?: Record<string, unknown>;
772
+ window?: {
773
+ key?: string;
774
+ preset?: string;
775
+ from?: string;
776
+ to?: string;
777
+ };
778
+ compare?: "previous_period" | "same_period_last_year" | {
779
+ against: "previous_period" | "same_period_last_year" | "range";
780
+ period?: DrillGrain;
781
+ from?: string;
782
+ to?: string;
783
+ baseline?: {
784
+ from: string;
785
+ to: string;
786
+ };
787
+ to_date?: boolean;
788
+ };
789
+ sort?: {
790
+ key: string;
791
+ direction?: "asc" | "desc";
792
+ };
793
+ /** Groups per level before Other; the knob's value when absent, never above the page ceiling. */
794
+ limit?: number;
795
+ lane?: DrillLane;
796
+ /** The drill path a screen's default click follows. */
797
+ path?: string;
798
+ /** Thresholds on the groups, applied before the cut into Other (see DrillHaving). */
799
+ having?: DrillHaving[];
800
+ /** Records only: the page's start. */
801
+ offset?: number;
802
+ /** Records only: which columns a record shows (a definer definition's records: of its declared columns). */
803
+ columns?: string[];
804
+ }
805
+
806
+ /**
807
+ * One client's memory of its own operations. `mint()` names a write and remembers it; `isOwn(opId)`
808
+ * answers whether a notice is that write's echo.
809
+ *
810
+ * OWN-WRITE DETECTION IS PER CLIENT INSTANCE. Two setups in ONE process (a server holding two
811
+ * identities, a worker beside a UI) must hear each other: a realm-wide ledger made A's write "ours" to
812
+ * B as well, and B dropped it as an echo (2026-10-08, the README proof). `createRecords` gives every
813
+ * setup its own ledger and hands it to its client, its entity port and its live-changes port.
814
+ */
815
+ interface OpLedger {
816
+ mint(): Uuid;
817
+ isOwn(opId: string | null | undefined): boolean;
818
+ /** Test seam: forget everything. */
819
+ clear(): void;
820
+ /** Test seam: how many operations are currently remembered. */
821
+ readonly size: number;
822
+ }
823
+
824
+ /**
825
+ * The data seam: a structural subset of `SupabaseClient`. The package never
826
+ * opens a database connection of its own and never holds a key — the HOST
827
+ * supplies a client that already carries the operating person's session, which
828
+ * is what makes DOOR-5 and AGT-N-4 true (the store sees the person, not us).
829
+ */
830
+ interface RecordsDataSource {
831
+ /**
832
+ * Call a door. The name is always a door the live catalogue has; `/core`
833
+ * refuses `door_absent` before it ever reaches here for one that does not.
834
+ */
835
+ rpc(fn: string, args: Record<string, unknown>,
836
+ /**
837
+ * THE SCHEMA THE DOOR LIVES IN, always supplied: `custom` for the store's
838
+ * own doors, `iam` for the trust doors (the external principal and the
839
+ * publish binding, which are the PLATFORM's). A host binding supabase-js
840
+ * routes this to `client.schema(options.schema).rpc(fn, args)`; a host that
841
+ * ignores it calls its own default PostgREST profile — `public` — and is
842
+ * refused by name, which is the failure we want rather than a silent answer
843
+ * from the wrong system. (Before 2026-09-18 this was described as "only ever
844
+ * `iam`" and the store's doors went unqualified; the extension's first real
845
+ * turn proved that asks `public.store_is_open` and finds nothing.)
846
+ */
847
+ options?: {
848
+ schema?: string;
849
+ }): PromiseLike<{
850
+ data: unknown;
851
+ error: PgError | null;
852
+ }>;
853
+ /**
854
+ * OPTIONAL. True for the real supabase adapters: the client may open a table page with ONE
855
+ * `custom.table_page_bundle` call instead of a dozen shape doors one after another (PAGE-BUNDLE).
856
+ * Leave it off for a test or replay source that does not model the bundle door.
857
+ */
858
+ opensInOneCall?: boolean;
859
+ /** Reads of `custom.*` go through the schema-qualified builder, never a bare `from`. */
860
+ schema(name: string): {
861
+ from(table: string): RecordsTableQuery;
862
+ };
863
+ }
864
+ /** The read surface the list and query paths use. A real supabase-js client satisfies it. */
865
+ interface RecordsTableQuery {
866
+ select(columns: string, options?: {
867
+ count?: "exact" | "planned" | "estimated";
868
+ head?: boolean;
869
+ }): RecordsFilter;
870
+ }
871
+ interface RecordsFilter extends PromiseLike<RecordsResponse> {
872
+ eq(column: string, value: unknown): RecordsFilter;
873
+ neq(column: string, value: unknown): RecordsFilter;
874
+ in(column: string, values: readonly unknown[]): RecordsFilter;
875
+ is(column: string, value: null | boolean): RecordsFilter;
876
+ gte(column: string, value: unknown): RecordsFilter;
877
+ lte(column: string, value: unknown): RecordsFilter;
878
+ order(column: string, options?: {
879
+ ascending?: boolean;
880
+ }): RecordsFilter;
881
+ range(from: number, to: number): RecordsFilter;
882
+ limit(count: number): RecordsFilter;
883
+ }
884
+ interface RecordsResponse {
885
+ data: unknown;
886
+ error: PgError | null;
887
+ count?: number | null;
888
+ }
889
+ /**
890
+ * WHO IS ACTING. AGT-N-4: an agent carries the exact authority of the person
891
+ * operating it — there is no agent identity and there are no service roles. So
892
+ * the actor word and the person are two separate facts, and `on_behalf_of` is
893
+ * meaningful only when the actor is `agent`.
894
+ */
895
+ interface RecordsActor {
896
+ actor: Actor;
897
+ /** The signed-in person. The store reads this from the session, not from us; this is for the client's own mirror. */
898
+ user_id?: Uuid;
899
+ /** Only an agent acts on behalf of a person (the store refuses it otherwise). */
900
+ on_behalf_of?: Uuid;
901
+ }
902
+ /**
903
+ * OPTIONAL. A live-updates seam. The default degradation is stated rather than
904
+ * silent: with no port bound, `useRecords` reports `live: false` and the screen
905
+ * can say "not live" instead of quietly going stale.
906
+ *
907
+ * A host binds this with `@ai-matrx/data/react`'s channel hygiene — the package
908
+ * deliberately does NOT depend on it, so the headless core stays free of a
909
+ * realtime client and a host that has one keeps using the one it already has.
910
+ */
911
+ interface RecordsRealtimePort {
912
+ /**
913
+ * Watch one organization's records of one table. Returns an unsubscribe.
914
+ *
915
+ * THE HOST NUDGES THROUGH THIS SINK AND REACHES AROUND NOTHING. Until 0.43.0 the callback
916
+ * was `(recordId: Uuid) => void` and a host that also wanted to say "the SHAPE moved"
917
+ * imported `tableShapeChanged` from `@ai-matrx/records/react` and called it directly —
918
+ * which worked, and which meant the port contract did not describe half of what a port
919
+ * actually does. A column appearing is not a row changing, and the package owns the
920
+ * difference.
921
+ *
922
+ * NOTHING THAT ARRIVES HERE IS DATA. A nudge has not passed the read door, so the hook
923
+ * re-reads — by id set when the nudge names ids, whole-page when it cannot.
924
+ */
925
+ subscribeRecords(args: {
926
+ organization_id: Uuid;
927
+ table_id: Uuid;
928
+ }, sink: RecordsChangeSink): () => void;
929
+ }
930
+ /** What a realtime port may tell the package. Two calls, because a column is not a row. */
931
+ interface RecordsChangeSink {
932
+ /**
933
+ * THESE RECORDS MOVED. `null` means "I cannot tell you which" — a reconnect, a tab wake, or
934
+ * a change above the store's own id cap — and is the one case that costs a whole-page read.
935
+ * An EMPTY array is not the same thing and is not a nudge at all.
936
+ */
937
+ records(ids: readonly Uuid[] | null): void;
938
+ /**
939
+ * THIS TABLE'S SHAPE MOVED — a column added, retired or changed. Re-reading the rows would
940
+ * redraw the same table without the new column in it, which is a screen that is out of date
941
+ * while looking healthy.
942
+ */
943
+ shape(): void;
944
+ }
945
+ /**
946
+ * OPTIONAL. Where a refusal that nobody asked about goes — the loud funnel.
947
+ * Default degradation: `console.error` with the store's own words, never
948
+ * silence.
949
+ */
950
+ type RecordsErrorSink = (error: {
951
+ code: string;
952
+ message: string;
953
+ hint?: string;
954
+ where: string;
955
+ }) => void;
956
+ /**
957
+ * OPTIONAL. The merge-field resolver's server half (`W5-MERGE-B`). While it is
958
+ * not bound, `useMergeField` answers `pending` WITH THE REASON — never a blank,
959
+ * never a literal placeholder left in the text (DYN-14).
960
+ */
961
+ interface RecordsResolverPort {
962
+ resolve(args: {
963
+ organization_id: Uuid;
964
+ merge_field_keys: string[];
965
+ /** The live selection the `state` source reads (DYNAMIC-VALUES §C). */
966
+ state?: Record<string, unknown>;
967
+ }): Promise<unknown>;
968
+ }
969
+ /** The one config a host binds. */
970
+ interface RecordsConfig {
971
+ /** REQUIRED. */
972
+ dataSource: RecordsDataSource;
973
+ /** REQUIRED. */
974
+ actor: RecordsActor;
975
+ /**
976
+ * REQUIRED. REC-29: organizations are hard walls, and the store is keyed
977
+ * `(organization_id, id)`. There is no "current organization" this package
978
+ * discovers for itself — the host says which one, every time.
979
+ *
980
+ * `null` is the ALL-ORGANIZATIONS mode (access belongs to the person, not the selected
981
+ * organization): a host whose page lists across every organization the person belongs to
982
+ * mounts with none. Doors that answer for the person (`custom.data_home*`) take it; a door that
983
+ * needs one is sent NULL and refuses in its own words, never a guessed organization.
984
+ */
985
+ organizationId: Uuid | null;
986
+ /**
987
+ * EVERY organization the person belongs to (active-org-is-never-a-list-filter.md).
988
+ * `organizationId` above is where NEW things are written and which org a write
989
+ * runs in; it never decides what a READ shows. With this set, `tableList` /
990
+ * `tableFacts` span all of these organizations (or one, when the page passes
991
+ * its `organization_id` filter), and `tableRead` / `recordRead` /
992
+ * `recordValuesVersioned` open a record in ITS OWN organization. Absent ⇒ the
993
+ * client reads only `organizationId`, exactly as before.
994
+ */
995
+ organizationIds?: readonly Uuid[];
996
+ realtime?: RecordsRealtimePort;
997
+ /**
998
+ * This client's own-write memory (`createOpLedger()`). Hand the SAME ledger to the live-changes port
999
+ * that listens for this client. Absent ⇒ the realm's default ledger (one client per browser tab).
1000
+ */
1001
+ opLedger?: OpLedger;
1002
+ onError?: RecordsErrorSink;
1003
+ resolver?: RecordsResolverPort;
1004
+ /**
1005
+ * The schema the record store lives in. Exists so a rehearsal branch and
1006
+ * production can differ without a fork; it is `custom` everywhere today.
1007
+ */
1008
+ schema?: string;
1009
+ /**
1010
+ * Doors the generated catalogue (`STORE_DOORS`) does not list YET but this store is known to have —
1011
+ * the nightly clone after a rehearsal, a test store. A screen reading an owed door asks
1012
+ * `doorReady(config, name)` first, so production (which lacks the door until the chair applies it)
1013
+ * is never asked and logs nothing. Leave it out in a shipped host: the regenerated catalogue is
1014
+ * the switch.
1015
+ */
1016
+ knownDoors?: readonly string[];
1017
+ }
1018
+
1019
+ type WhereOp = "eq" | "neq" | "lt" | "lte" | "gt" | "gte" | "contains" | "in" | "empty";
1020
+ interface WhereClause {
1021
+ column: string;
1022
+ op: WhereOp;
1023
+ value?: unknown;
1024
+ }
1025
+ type Lane = "all" | "mine" | "team" | "orgs" | "shared" | "public" | "system";
1026
+ /** §4. A question of one source. Every clause must hold. */
1027
+ interface Query {
1028
+ where?: WhereClause[];
1029
+ search?: string;
1030
+ sort?: {
1031
+ column: string;
1032
+ direction?: "asc" | "desc";
1033
+ }[];
1034
+ pageSize?: number;
1035
+ /** Default: the type's "Shown to by default" knob. A custom table has one lane: what row security shows. */
1036
+ lane?: Lane;
1037
+ /** A filter, never a lock. Default "all". */
1038
+ org?: Uuid | "all";
1039
+ }
1040
+
1041
+ /** A view's look as the store keeps it: one JSON object under `definition.presentation`. */
1042
+ type ViewPresentationDocument = Record<string, unknown>;
1043
+ /**
1044
+ * HOW A VIEW'S LOOK IS READ AND WRITTEN — a PORT. The store keeps the look as one JSON object; what it
1045
+ * means (colors, formats, frozen columns) belongs to the screens that draw it, so the screens hand their
1046
+ * codec in (`@ai-matrx/records-ui` binds its grid presentation). Left out, the look stays the plain
1047
+ * document the store keeps — a headless host reads and writes it untouched.
1048
+ */
1049
+ interface ViewPresentationCodec<P> {
1050
+ parse(raw: unknown): P;
1051
+ isEmpty(presentation: P): boolean;
1052
+ /** The look as the JSON object the store keeps. */
1053
+ toDocument(presentation: P): Record<string, unknown>;
1054
+ }
1055
+
1056
+ /** The ways one saved view can be looked at (SCR-6; the timeline, lane VIEWS-AND-FIELDS TL). */
1057
+ declare const VIEW_LAYOUTS: readonly ["grid", "kanban", "calendar", "gallery", "timeline", "list"];
1058
+ type ViewLayout = (typeof VIEW_LAYOUTS)[number];
1059
+ /** One sort clause. The Field's key and a direction; nothing else is a sort. */
1060
+ interface ViewSort {
1061
+ field: string;
1062
+ direction: "asc" | "desc";
1063
+ }
1064
+ /** How a saved view is ordered: by hand, by its sort, or it never said (the same as sorted). */
1065
+ type ViewOrder = "manual" | "sorted" | null;
1066
+ /**
1067
+ * THE DECLARATIVE SPEC. An agent writes this whole object; a person tweaks it.
1068
+ * Every builder component in this package takes one of these as a prop.
1069
+ */
1070
+ interface SavedViewSpec<P = ViewPresentationDocument> {
1071
+ name: string;
1072
+ /** The Table this view looks at. */
1073
+ subject: Uuid;
1074
+ layout: ViewLayout;
1075
+ /** Kanban: the Field whose value becomes a column. */
1076
+ groupField?: string | null;
1077
+ /**
1078
+ * Kanban: a number Field to TOTAL under each column heading. It lives on the VIEW for
1079
+ * the same reason the colours do — two people read one board two ways and neither
1080
+ * reading is a fact about the records.
1081
+ */
1082
+ measure?: string | null;
1083
+ /**
1084
+ * Kanban: the Field whose values cut the board into SWIMLANES across the columns — a
1085
+ * dispatcher's board by Status, one lane per Technician (S1-PRIME VIEW-KEYS). Never the
1086
+ * column Field itself; the store refuses that.
1087
+ */
1088
+ swimlaneField?: string | null;
1089
+ /** Kanban: the columns this view keeps shut to a strip, by the column's stored value. */
1090
+ collapsedColumns?: string[];
1091
+ /** Calendar: the Field that places a record in the month. */
1092
+ dateField?: string | null;
1093
+ /** Timeline: the Field a record's bar starts on (`start_field`). */
1094
+ startField?: string | null;
1095
+ /** Calendar and timeline: the Field a record ends on (`end_field`). */
1096
+ endField?: string | null;
1097
+ /** Gallery: the Field shown large on the card. */
1098
+ imageField?: string | null;
1099
+ sorts?: ViewSort[];
1100
+ /**
1101
+ * HOW THE VIEW IS ORDERED (ORDER-FIX): `"manual"` — the positions a person set by hand ARE the
1102
+ * view's sort, and it carries no `sorts`; `"sorted"` or null — by `sorts`. READ ONLY: the store
1103
+ * writes "manual" only when the rows are placed (`custom.view_record_order_set`) and turns it
1104
+ * off itself when a sort is saved, so no patch ever sends it (`viewSettingsPatch` does not).
1105
+ */
1106
+ order?: ViewOrder;
1107
+ /**
1108
+ * The membership Rule. The STORE decides who is in this view; this is a
1109
+ * reference to its Rule record, never a predicate this package evaluates.
1110
+ */
1111
+ ruleId?: Uuid | null;
1112
+ /**
1113
+ * THE FLAT FILTER a digest or a notify rule wrote the view with — the one shape
1114
+ * `custom.record_filter_sql` compiles, by Field KEY. A view saved in the bar has
1115
+ * none; a view saved by `DigestScheduler` ("where Status is Open") does, and the
1116
+ * grid and the board show exactly the records it names.
1117
+ */
1118
+ filters?: RecordFilter;
1119
+ /**
1120
+ * S2-PRIME — THE VIEW'S OWN QUESTION, as the condition builder writes it: the store's Rule
1121
+ * shape `{op, args}`, ALL / ANY / NOT groups nested to any depth, Fields by id. Kept in the
1122
+ * definition as `where` and asked of the store unchanged — the grid's list door, the board's
1123
+ * headings and the dashboard's numbers compile it into the one WHERE clause
1124
+ * (`custom.record_filter_sql`). Never stored under `filters`: that key is the flat map the
1125
+ * digests and the notifier read, and they would refuse it by name.
1126
+ */
1127
+ where?: RuleExpression | null;
1128
+ isDefault?: boolean;
1129
+ /**
1130
+ * HOW THIS VIEW LOOKS — its colors, its per-column formats and its frozen
1131
+ * column (`presentation.ts`).
1132
+ *
1133
+ * It lives on the VIEW and not on the Fields because two people read one
1134
+ * table two ways and neither reading is a fact about the records; it lives on
1135
+ * a RECORD, not a new column, because that is what makes it something an
1136
+ * agent writes with the same `records` tool as everything else.
1137
+ */
1138
+ presentation?: P;
1139
+ }
1140
+ /**
1141
+ * A saved view as it comes back out of the store, with its row's own id.
1142
+ *
1143
+ * It carries no version: `custom.view_declare` MERGES one key at a time, so two
1144
+ * tabs pressing two different controls on one view both stand, and one control
1145
+ * pressed in both keeps the latest — how Airtable and Notion keep a view change.
1146
+ */
1147
+ interface SavedView<P = ViewPresentationDocument> extends SavedViewSpec<P> {
1148
+ id: Uuid;
1149
+ /** Where the view came from when it was moved in (the mover, the S0 repair), else null. */
1150
+ movedFrom: Record<string, unknown> | null;
1151
+ }
1152
+
1153
+ /** The table page's own page size (100 unless embedded). */
1154
+ declare const TABLE_PAGE_SIZE = 100;
1155
+ /** How many rows an embedded table reads at a time: its first page, and each "Show more". */
1156
+ declare const EMBEDDED_PAGE_SIZE = 50;
1157
+ interface FirstGridPageArgs {
1158
+ p_table_id: Uuid;
1159
+ p_filter: StoreFilter | Record<string, never>;
1160
+ p_search: null;
1161
+ p_sort: RecordPageSort[];
1162
+ p_view_id: null;
1163
+ p_limit: number;
1164
+ p_offset: 0;
1165
+ }
1166
+ /** The request for the plain opening, from what the screen itself reads — or null. Pure. */
1167
+ declare function firstGridPageArgs<P = ViewPresentationDocument>(input: {
1168
+ tableId: Uuid;
1169
+ table: Pick<Table, "default_sort"> | null;
1170
+ fields: readonly Field[];
1171
+ /** The view as THIS person sees it (the saved view with her look over it), or null for none. */
1172
+ shown: SavedView<P> | null;
1173
+ pageSize?: number;
1174
+ /**
1175
+ * The host draws the MERGED grid, which keeps a plain grouping as rows under group headers: the page is then
1176
+ * read in group order. The classic grid opens the drill for a grouped view, so a grouped view stays unseeded.
1177
+ */
1178
+ merged?: boolean;
1179
+ }): FirstGridPageArgs | null;
1180
+ type OpeningOptions<P> = {
1181
+ pageSize?: number;
1182
+ merged?: boolean;
1183
+ view?: SavedViewSpec<P> | undefined;
1184
+ codec?: ViewPresentationCodec<P>;
1185
+ };
1186
+ /**
1187
+ * Read what the screen reads (through `client`, e.g. one served from `custom.table_page_bundle`) and build the first
1188
+ * page's request. Null when anything it needs is not answered.
1189
+ */
1190
+ declare function firstGridPageRequest<P = ViewPresentationDocument>(client: Pick<RecordsClient, "tableRead" | "fields" | "views" | "viewLookRead">, tableId: Uuid, options?: Omit<OpeningOptions<P>, "view">): Promise<FirstGridPageArgs | null>;
1191
+ /**
1192
+ * THE PERSON'S SERVER-ROWS KNOBS, FROM THE TABLE'S BUNDLE: `custom.table_page_bundle` carries a `server_rows` part
1193
+ * (whether the page may wait for the server's rows, and for how long) resolved for the person asking. Null when the
1194
+ * seed holds no such answer (an older store, a refused part): the host treats that as off.
1195
+ */
1196
+ declare function serverRowsOf(seed: RecordsSeed | null | undefined): {
1197
+ on: boolean;
1198
+ capMs: number | null;
1199
+ } | null;
1200
+ /**
1201
+ * A TABLE PAGE'S SEED, ASKED BY THE SERVER AS THE PERSON.
1202
+ *
1203
+ * Asks what the table page opens on — the table's bundle, the grid's first page when it can be predicted, and on a
1204
+ * record page the record's bundle — through a recording data source, and answers every door answer it got as a
1205
+ * `RecordsSeed`. The host hands that to `<RecordsSeedProvider>` (`@ai-matrx/records/react`) above the table page.
1206
+ * `dataSource` is the person's own (a server Supabase client carrying her session). Never throws.
1207
+ */
1208
+ declare function askTablePageSeed<P = ViewPresentationDocument>(input: {
1209
+ dataSource: unknown;
1210
+ organizationId: Uuid;
1211
+ tableId: Uuid;
1212
+ actor: RecordsConfig["actor"];
1213
+ /** A record page: its record's bundle (`custom.record_page_bundle`) is asked beside the table's. */
1214
+ recordId?: Uuid | null;
1215
+ /**
1216
+ * Ask the grid's first page (default true). A function decides once the table's bundle has been answered — it is
1217
+ * handed everything asked so far, so a host can read the person's `server_rows` knob (`serverRowsOf`) first.
1218
+ */
1219
+ rows?: boolean | ((asked: RecordsSeed) => boolean);
1220
+ /** The table is drawn EMBEDDED: its page size. */
1221
+ embedded?: boolean;
1222
+ pageSize?: number;
1223
+ /** The host draws the merged grid: a view grouped by one field is seeded too (group counts, summary bar, link words). */
1224
+ merged?: boolean;
1225
+ /** THE VIEW THE HOST HANDS THE GRID, for a table with no saved view behind it. */
1226
+ view?: SavedViewSpec<P> | undefined;
1227
+ /** The host's view codec (records-ui binds its grid look). Left out: the plain document. */
1228
+ codec?: ViewPresentationCodec<P>;
1229
+ }): Promise<RecordsSeed>;
1230
+ type Base = {
1231
+ dataSource: unknown;
1232
+ organizationId: Uuid;
1233
+ actor: RecordsConfig["actor"];
1234
+ };
1235
+ /** A standard source's first read: its page size and the viewer's search (an empty search is none). */
1236
+ declare function entityTableQuery(options?: {
1237
+ pageSize?: number | undefined;
1238
+ search?: string | null | undefined;
1239
+ }): Query;
1240
+ /** The block's own props that decide a chart's question. */
1241
+ interface EntityChartQuestionInput {
1242
+ /** The Dimension to group by. Absent: one total. */
1243
+ by?: string | undefined;
1244
+ /** What to count: the rows (default) or a number column summed. */
1245
+ measure?: {
1246
+ op: "count";
1247
+ } | {
1248
+ op: "sum" | "avg" | "min" | "max";
1249
+ of: string;
1250
+ } | undefined;
1251
+ where?: Record<string, unknown> | undefined;
1252
+ organization?: string | undefined;
1253
+ }
1254
+ /** The chart's `platform.drill_ask` question. */
1255
+ declare function entityChartQuestion(input: EntityChartQuestionInput): DrillQuestion & {
1256
+ organization?: string;
1257
+ };
1258
+ /** Several seeds as one: the later answer wins on the same door and arguments; `at` is the oldest. */
1259
+ declare function mergeSeeds(...seeds: Array<RecordsSeed | null | undefined>): RecordsSeed;
1260
+ /**
1261
+ * AN ENTITY SOURCE'S BLOCK (a table, board or card of "task", "project", "crm_deal"): its columns and first page,
1262
+ * asked through the entity engine's own port with the question the block's `useEntityTable` sends. Never throws.
1263
+ */
1264
+ declare function askEntityBlockSeed(input: Base & {
1265
+ token: string;
1266
+ question?: {
1267
+ pageSize?: number | undefined;
1268
+ search?: string | null | undefined;
1269
+ };
1270
+ }): Promise<RecordsSeed>;
1271
+ /** AN ENTITY SOURCE'S CHART: `platform.drill_ask` with the question the block sends. */
1272
+ declare function askChartSeed(input: Base & {
1273
+ source: {
1274
+ kind: "entity";
1275
+ token: string;
1276
+ };
1277
+ question?: EntityChartQuestionInput;
1278
+ }): Promise<RecordsSeed>;
1279
+ /** One aggregate question of a tile: `client.recordAggregate`'s arguments but the table. */
1280
+ type TileSpec = Omit<Parameters<RecordsClient["recordAggregate"]>[0], "table_id">;
1281
+ /** A TABLE'S TILE: `custom.record_aggregate` over `tableId` for each `spec`. Read back with `useSeededAggregate`. */
1282
+ declare function askTileSeed(input: Base & {
1283
+ tableId: Uuid;
1284
+ spec: TileSpec | TileSpec[];
1285
+ }): Promise<RecordsSeed>;
1286
+ /**
1287
+ * THE FIRST PAGE WITH A HOST'S VIEW CODEC BOUND ONCE (records-ui binds its grid look): the host re-exports these
1288
+ * under the same names — bindings of the one definition here, never a second body.
1289
+ */
1290
+ declare function bindFirstPageCodec<P>(codec: ViewPresentationCodec<P>): {
1291
+ firstGridPageRequest: (client: Pick<RecordsClient, "tableRead" | "fields" | "views" | "viewLookRead">, tableId: Uuid, options?: {
1292
+ pageSize?: number;
1293
+ merged?: boolean;
1294
+ }) => Promise<FirstGridPageArgs | null>;
1295
+ askTablePageSeed: (input: Omit<Parameters<typeof askTablePageSeed<P>>[0], "codec">) => Promise<RecordsSeed>;
1296
+ };
1297
+
1298
+ export { type ColumnSort, EMBEDDED_PAGE_SIZE, type EntityChartQuestionInput, type FirstGridPageArgs, type GroupingShape, REVERSE_CELL_CHIPS, SUMMARY_OPERATIONS, TABLE_PAGE_SIZE, type TileSpec, askChartSeed, askEntityBlockSeed, askTablePageSeed, askTileSeed, bindFirstPageCodec, choiceFieldKeys, defaultSortOf, entityChartQuestion, entityTableQuery, firstGridPageArgs, firstGridPageRequest, groupFactMeasures, groupedPageSort, holdsNumbers, isPlainGrouping, mergeSeeds, pageSortFor, serverRowsOf, sortAsFor, summaryBarMeasures, viewFilterOf };