@ai-matrx/records 0.87.31 → 0.92.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 (107) hide show
  1. package/CHANGELOG.md +98 -0
  2. package/README.md +115 -0
  3. package/dist/{client-E0VQ0NPO.d.ts → client-DE9Mq6YX.d.cts} +8 -5
  4. package/dist/{client-C3ZZNfGH.d.cts → client-vJAb7g9r.d.ts} +8 -5
  5. package/dist/core/index.cjs +25 -7
  6. package/dist/core/index.cjs.map +1 -1
  7. package/dist/core/index.d.cts +23 -12
  8. package/dist/core/index.d.ts +23 -12
  9. package/dist/core/index.js +25 -7
  10. package/dist/core/index.js.map +1 -1
  11. package/dist/data/index.cjs +19 -7
  12. package/dist/data/index.cjs.map +1 -1
  13. package/dist/data/index.d.cts +12 -142
  14. package/dist/data/index.d.ts +12 -142
  15. package/dist/data/index.js +19 -7
  16. package/dist/data/index.js.map +1 -1
  17. package/dist/{declareTable-BK3zufr7.d.cts → declareTable-Btg5bql1.d.cts} +4 -4
  18. package/dist/{declareTable-B12XybRp.d.ts → declareTable-JKt2q34d.d.ts} +4 -4
  19. package/dist/entity/index.cjs +984 -0
  20. package/dist/entity/index.cjs.map +1 -0
  21. package/dist/entity/index.d.cts +36 -0
  22. package/dist/entity/index.d.ts +36 -0
  23. package/dist/entity/index.js +964 -0
  24. package/dist/entity/index.js.map +1 -0
  25. package/dist/entity/react.cjs +1024 -0
  26. package/dist/entity/react.cjs.map +1 -0
  27. package/dist/entity/react.d.cts +511 -0
  28. package/dist/entity/react.d.ts +511 -0
  29. package/dist/entity/react.js +1015 -0
  30. package/dist/entity/react.js.map +1 -0
  31. package/dist/{field-BanTVGK2.d.cts → field-BJBnTyJ0.d.cts} +1 -1
  32. package/dist/{field-BPABOJ2Y.d.ts → field-Ti5iAxFr.d.ts} +1 -1
  33. package/dist/forms/index.cjs +984 -0
  34. package/dist/forms/index.cjs.map +1 -0
  35. package/dist/forms/index.d.cts +333 -0
  36. package/dist/forms/index.d.ts +333 -0
  37. package/dist/forms/index.js +961 -0
  38. package/dist/forms/index.js.map +1 -0
  39. package/dist/index.cjs +10 -1
  40. package/dist/index.cjs.map +1 -1
  41. package/dist/index.d.cts +11 -8
  42. package/dist/index.d.ts +11 -8
  43. package/dist/index.js +10 -1
  44. package/dist/index.js.map +1 -1
  45. package/dist/memory/index.d.cts +7 -6
  46. package/dist/memory/index.d.ts +7 -6
  47. package/dist/port-CPVBfDxS.d.cts +301 -0
  48. package/dist/port-D2i2o7Lg.d.ts +301 -0
  49. package/dist/portal-Db4CYyHe.d.ts +331 -0
  50. package/dist/portal-DfeY2Boy.d.cts +331 -0
  51. package/dist/{ports-TMWX-t9l.d.ts → ports-BWtxAnXh.d.ts} +2 -2
  52. package/dist/{ports-D8-iuY1P.d.cts → ports-DSU2RsR6.d.cts} +2 -2
  53. package/dist/react/index.cjs +55 -523
  54. package/dist/react/index.cjs.map +1 -1
  55. package/dist/react/index.d.cts +14 -1
  56. package/dist/react/index.d.ts +14 -1
  57. package/dist/react/index.js +52 -527
  58. package/dist/react/index.js.map +1 -1
  59. package/dist/realtime/index.d.cts +2 -2
  60. package/dist/realtime/index.d.ts +2 -2
  61. package/dist/{record-DpB2QgAZ.d.cts → record-BQi92d4-.d.cts} +7 -1
  62. package/dist/{record-DpB2QgAZ.d.ts → record-BQi92d4-.d.ts} +7 -1
  63. package/dist/relation-DMwkmzgN.d.cts +230 -0
  64. package/dist/relation-DlcqV_W5.d.ts +230 -0
  65. package/dist/rule-B8kR0l01.d.ts +250 -0
  66. package/dist/rule-BMViCLmW.d.cts +250 -0
  67. package/dist/search/index.cjs +283 -0
  68. package/dist/search/index.cjs.map +1 -0
  69. package/dist/search/index.d.cts +44 -0
  70. package/dist/search/index.d.ts +44 -0
  71. package/dist/search/index.js +260 -0
  72. package/dist/search/index.js.map +1 -0
  73. package/dist/setup/index.cjs +6854 -0
  74. package/dist/setup/index.cjs.map +1 -0
  75. package/dist/setup/index.d.cts +87 -0
  76. package/dist/setup/index.d.ts +87 -0
  77. package/dist/setup/index.js +6829 -0
  78. package/dist/setup/index.js.map +1 -0
  79. package/dist/{subscription-D7KQBHXN.d.ts → subscription-5CLN3Jug.d.ts} +3 -249
  80. package/dist/{subscription-BD9DnJxX.d.cts → subscription-D-VEzvyy.d.cts} +3 -249
  81. package/dist/{table-kind-BZu8mtND.d.cts → table-kind-CVp43g6h.d.ts} +3 -558
  82. package/dist/{table-kind-xKxTHtrV.d.ts → table-kind-Cm6PXkWx.d.cts} +3 -558
  83. package/dist/templates/index.d.cts +10 -9
  84. package/dist/templates/index.d.ts +10 -9
  85. package/dist/typed-table/index.cjs +19 -7
  86. package/dist/typed-table/index.cjs.map +1 -1
  87. package/dist/typed-table/index.d.cts +10 -7
  88. package/dist/typed-table/index.d.ts +10 -7
  89. package/dist/typed-table/index.js +19 -7
  90. package/dist/typed-table/index.js.map +1 -1
  91. package/dist/{types-CLP5H4Wk.d.cts → types-9Q5SHRDJ.d.cts} +2 -2
  92. package/dist/{types-DmPkWQic.d.ts → types-CfYy-vdV.d.ts} +2 -2
  93. package/dist/{types-C2uspRtf.d.cts → types-Cs2MyE3u.d.cts} +2 -2
  94. package/dist/{types-Dv61jStB.d.ts → types-DF-dxB_l.d.ts} +2 -2
  95. package/dist/types-DTEGaxso.d.cts +139 -0
  96. package/dist/types-Dji5SpFS.d.ts +139 -0
  97. package/dist/use-cases/index.d.cts +5 -5
  98. package/dist/use-cases/index.d.ts +5 -5
  99. package/dist/{validate-CWVToe1T.d.ts → validate-BydKd-Le.d.ts} +1 -1
  100. package/dist/{validate-DEB_mn4Y.d.cts → validate-CqJakTKk.d.cts} +1 -1
  101. package/dist/versions/index.cjs +375 -0
  102. package/dist/versions/index.cjs.map +1 -0
  103. package/dist/versions/index.d.cts +160 -0
  104. package/dist/versions/index.d.ts +160 -0
  105. package/dist/versions/index.js +353 -0
  106. package/dist/versions/index.js.map +1 -0
  107. package/package.json +62 -2
@@ -0,0 +1,160 @@
1
+ import { ReadRow, Uuid, RecordChange, RecordsResult, RecordChangeResult, RecordsError, RestoreResult, RecordsErrorCode } from '@ai-matrx/records';
2
+ import { RecordsClient } from '@ai-matrx/records/core';
3
+
4
+ /** The version a row carries itself, when the host's read gave it one. */
5
+ declare function ownVersion(row: ReadRow): number | null;
6
+
7
+ /** THE CONFLICT, on a card, a badge and the floating notice — the store said somebody else won. */
8
+ declare const STALE_MOVE_LABEL = "Changed by someone else";
9
+ /** THE VERSION COULD NOT BE READ, so nothing was written — never shown as a conflict. */
10
+ declare const VERSION_UNREAD_LABEL = "Could not check for changes";
11
+ /** The refusal for "the version the person saw is not known". */
12
+ declare function versionUnread(): RecordsError;
13
+ /** Is this refusal "the version could not be read" (never "somebody else changed it")? */
14
+ declare function isVersionUnread(error: RecordsError | null | undefined): boolean;
15
+ /** Is this refusal "somebody else changed it first"? */
16
+ declare function isChangedElsewhere(error: RecordsError | null | undefined): boolean;
17
+
18
+ interface RecordVersions {
19
+ /** The version the person saw this record at; waits for the read of freshly drawn rows. `null` = unread. */
20
+ seen: (id: Uuid) => Promise<number | null>;
21
+ /** The same, for many records at once. */
22
+ seenMany: (ids: readonly Uuid[]) => Promise<Map<Uuid, number | null>>;
23
+ /** A write this screen made moved the record on (a version only goes up). */
24
+ wrote: (id: Uuid, version: number) => void;
25
+ /**
26
+ * Read these records' versions again now: the person pressed Reload, or the store refused a write
27
+ * because somebody else won (`PT409`) — the rows are re-read too, so the next write is against what
28
+ * the person then sees. A change that moved only the version (who sees the row) never re-reads by
29
+ * content, so without this the row stayed refused until the screen was opened again (verifier V10).
30
+ */
31
+ refresh: (ids: readonly Uuid[]) => void;
32
+ }
33
+ /**
34
+ * THE VERSIONS AT LOAD for the rows a screen drew. Stable across re-renders (one handle); re-reads only
35
+ * the rows whose content changed. `mayRead` leaves out rows the screen will never write (a viewer's).
36
+ */
37
+ declare function useRecordVersions(client: Pick<RecordsClient, "recordHeaders">, rows: readonly ReadRow[], mayRead?: (row: ReadRow) => boolean): RecordVersions;
38
+ /** One record's version, read now — for a screen that shows something else first (a preview, a proposal). */
39
+ declare function readVersionNow(client: Pick<RecordsClient, "recordHeaders">, id: Uuid): Promise<number | null>;
40
+ /** ONE record's update, against the version the person saw (`add`: words that also become choices). */
41
+ declare function updateRecordAt(client: Pick<RecordsClient, "recordUpdate" | "recordUpdateAddingChoices">, args: {
42
+ record_id: Uuid;
43
+ patch: Record<string, unknown>;
44
+ version: number | null;
45
+ add?: Record<string, string[]> | undefined;
46
+ }): Promise<RecordsResult<number>>;
47
+ /** One change of a batch: an update NAMES the version it was made against (`null` = unread). */
48
+ type VersionedChange = Extract<RecordChange, {
49
+ op: "insert";
50
+ }> | Extract<RecordChange, {
51
+ op: "archive";
52
+ }> | {
53
+ op: "update";
54
+ record_id: Uuid;
55
+ patch: Record<string, unknown>;
56
+ expected_version: number | null;
57
+ };
58
+ /** MANY CHANGES TO ONE TABLE, ONE TRANSACTION. Any update with an unread version refuses the batch. */
59
+ declare function changeRecordsAt(client: Pick<RecordsClient, "recordChangeMany">, args: {
60
+ table_id: Uuid;
61
+ changes: readonly VersionedChange[];
62
+ }): Promise<RecordsResult<RecordChangeResult[]>>;
63
+ /** A pipeline card to another stage, against the version the board drew it at. */
64
+ declare function movePipelineCardAt(client: Pick<RecordsClient, "pipelineMove">, args: {
65
+ record_id: Uuid;
66
+ to: string;
67
+ also?: Record<string, unknown> | undefined;
68
+ version: number | null;
69
+ }): ReturnType<RecordsClient["pipelineMove"]>;
70
+ /** Many pipeline cards, one call; every card names its version or the batch is not sent. */
71
+ declare function movePipelineCardsAt(client: Pick<RecordsClient, "pipelineMoveMany">, args: {
72
+ moves: ReadonlyArray<{
73
+ record_id: Uuid;
74
+ stage: string;
75
+ version: number | null;
76
+ }>;
77
+ }): ReturnType<RecordsClient["pipelineMoveMany"]>;
78
+ /**
79
+ * PUT A VERSION BACK (one column, or the whole record), only while the record is still at the version
80
+ * the person saw the preview at. The restore doors take no expected version, so the check is ONE
81
+ * header read just before — a colleague's change since the preview is refused as a conflict; only a
82
+ * write landing inside that one round trip can still slip past (reported: needs a store-side version).
83
+ */
84
+ declare function restoreAt(client: Pick<RecordsClient, "recordHeaders" | "valueRestore" | "restoreVersion">, args: {
85
+ record_id: Uuid;
86
+ version: number;
87
+ field_key?: string | null | undefined;
88
+ seenVersion: number | null;
89
+ }): Promise<RecordsResult<RestoreResult>>;
90
+ /**
91
+ * A TABLE'S OWN VERSION, read now. `custom.record_headers` answers RECORDS only (it never returns a
92
+ * Table's row: measured on the clone 2026-10-02, 0 rows for a Table id), so a Table's version is the
93
+ * newest of its revisions (`custom.io_revisions`, which does answer a Table — v10 for Cedar Ridge PT's
94
+ * Patient Visits). The history can fall behind the record; a write on it goes through
95
+ * `updateOnTheWinningVersion`, which resends once on the winning version when the column it changes
96
+ * is still what the screen drew.
97
+ */
98
+ declare function readTableVersionNow(client: Pick<RecordsClient, "revisions" | "recordHeaders">, tableId: Uuid): Promise<number | null>;
99
+ /**
100
+ * A TABLE'S OWN RECORD (its name, its row label): the version of the table document this screen drew,
101
+ * read again whenever that document arrives with new content (never per render).
102
+ */
103
+ declare function useTableVersion(client: Pick<RecordsClient, "revisions" | "recordHeaders">, tableId: Uuid | null | undefined, table: unknown): () => Promise<number | null>;
104
+
105
+ type Answer = {
106
+ ok: boolean;
107
+ };
108
+ declare function askOnce<T extends Answer>(client: object, key: string, ask: () => Promise<T>, freshMs: number): Promise<T>;
109
+ /** Tests only: forget everything kept for every client (tests that share one data source across cases). */
110
+ declare function forgetAllAsked(client: object): void;
111
+ /** Forget every kept answer whose key starts with `prefix` (a write changed what it answers). */
112
+ declare function forgetAsked(client: object, prefix: string): void;
113
+ /**
114
+ * How long a column's choices are shared between askers: one page open, including the records
115
+ * client being rebuilt when the active organization resolves (~2 s in) and every effect that re-runs
116
+ * after it. 2 s ended just before the second fire (lane DEDUPE-READS: Spaces measured one at hydration
117
+ * and one ~2 s later). A write on this page ends it at once (`structureEpoch`); another person's edit
118
+ * shows within this window at most.
119
+ */
120
+ declare const FIELD_OPTIONS_FRESH_MS = 10000;
121
+ /** A column's choices, asked once for every asker in the same moment (`custom.field_options`). */
122
+ declare function fieldOptionsOnce(client: Pick<RecordsClient, "fieldOptions">, fieldId: string): ReturnType<RecordsClient["fieldOptions"]>;
123
+ /** How long a page of badges is shared between askers: the one page open, ended by any explicit refresh. */
124
+ declare const ENRICH_CELLS_FRESH_MS = 5000;
125
+
126
+ /**
127
+ * A refusal in this package's own words.
128
+ *
129
+ * @param code what KIND of refusal — picks the notice's title and its fallback remedy.
130
+ * @param sentence what happened, for a person.
131
+ * @param remedy what to do now, when this call site knows better than the code's default.
132
+ * @param diagnostic an engineer's note (a thrown message, a status) — rendered out of sight.
133
+ */
134
+ declare function refusal(code: RecordsErrorCode, sentence: string, remedy?: string | null, diagnostic?: string | null): RecordsError;
135
+ /**
136
+ * A refusal made from something THROWN in the browser. The thrown text is an
137
+ * engineer's (a parser's "Unexpected token } in JSON at position 14", a
138
+ * library's stack) and goes out of sight; the person reads `sentence`.
139
+ */
140
+ declare function refusalFromThrown(thrown: unknown, sentence: string, remedy?: string | null, code?: RecordsErrorCode): RecordsError;
141
+ /**
142
+ * The store's refusal when there is one, otherwise this package's own sentence —
143
+ * for the call sites whose answer MAY carry an error (`res.error?.message ?? "…"`
144
+ * was the old shape, and it printed the store's message raw).
145
+ */
146
+ declare function refusalOr(error: RecordsError | null | undefined, code: RecordsErrorCode, sentence: string): RecordsError;
147
+ /**
148
+ * Whatever a host callback threw, as a refusal: the store's own `RecordsError`
149
+ * when that is what arrived (it is drawn with its own words and remedy), and
150
+ * otherwise `refusalFromThrown` — the thrown text out of sight, `sentence` in view.
151
+ */
152
+ declare function asRefusal(thrown: unknown, sentence: string, remedy?: string | null, code?: RecordsErrorCode): RecordsError;
153
+
154
+ /** Two values the way a person would compare them: empty is empty, a list is its contents. */
155
+ declare function sameStoredValue(left: unknown, right: unknown): boolean;
156
+ declare function updateOnTheWinningVersion(client: Pick<RecordsClient, "recordUpdate" | "recordUpdateAddingChoices">, recordId: Uuid, patch: Record<string, unknown>, expectedVersion: number | null,
157
+ /** What this screen started from, per patched column — or `always` to resend regardless. */
158
+ base: Record<string, unknown> | "always"): Promise<RecordsResult<number>>;
159
+
160
+ export { ENRICH_CELLS_FRESH_MS, FIELD_OPTIONS_FRESH_MS, type RecordVersions, STALE_MOVE_LABEL, VERSION_UNREAD_LABEL, type VersionedChange, asRefusal, askOnce, changeRecordsAt, fieldOptionsOnce, forgetAllAsked, forgetAsked, isChangedElsewhere, isVersionUnread, movePipelineCardAt, movePipelineCardsAt, ownVersion, readTableVersionNow, readVersionNow, refusal, refusalFromThrown, refusalOr, restoreAt, sameStoredValue, updateOnTheWinningVersion, updateRecordAt, useRecordVersions, useTableVersion, versionUnread };
@@ -0,0 +1,353 @@
1
+ "use client";
2
+
3
+ // src/versions/recordVersions.ts
4
+ import { useCallback, useEffect, useMemo, useRef } from "react";
5
+
6
+ // src/versions/askOnce.ts
7
+ import { onTableStructureChanged } from "@ai-matrx/records/core";
8
+ var structureEpoch = 0;
9
+ onTableStructureChanged(() => {
10
+ structureEpoch += 1;
11
+ });
12
+ var asked = /* @__PURE__ */ new WeakMap();
13
+ var byPerson = /* @__PURE__ */ new WeakMap();
14
+ function heldFor(client) {
15
+ const config = client.config;
16
+ const source = config?.dataSource;
17
+ if (source && typeof source === "object" && config?.actor) {
18
+ const who = [config.actor.actor ?? "-", config.actor.user_id ?? "-", config.actor.on_behalf_of ?? "-"].join("|");
19
+ let persons = byPerson.get(source);
20
+ if (!persons) {
21
+ persons = /* @__PURE__ */ new Map();
22
+ byPerson.set(source, persons);
23
+ }
24
+ let held2 = persons.get(who);
25
+ if (!held2) {
26
+ held2 = /* @__PURE__ */ new Map();
27
+ persons.set(who, held2);
28
+ }
29
+ return held2;
30
+ }
31
+ let held = asked.get(client);
32
+ if (!held) {
33
+ held = /* @__PURE__ */ new Map();
34
+ asked.set(client, held);
35
+ }
36
+ return held;
37
+ }
38
+ function askOnce(client, key, ask, freshMs) {
39
+ const held = heldFor(client);
40
+ const now = Date.now();
41
+ const kept = held.get(key);
42
+ if (kept && kept.epoch === structureEpoch && now - kept.at <= freshMs) return kept.answer;
43
+ const answer = ask().then(
44
+ (result) => {
45
+ if (!result.ok) held.delete(key);
46
+ return result;
47
+ },
48
+ (thrown) => {
49
+ held.delete(key);
50
+ throw thrown;
51
+ }
52
+ );
53
+ held.set(key, { at: now, epoch: structureEpoch, answer });
54
+ return answer;
55
+ }
56
+ function forgetAllAsked(client) {
57
+ heldFor(client).clear();
58
+ }
59
+ function forgetAsked(client, prefix) {
60
+ const held = heldFor(client);
61
+ for (const key of [...held.keys()]) if (key.startsWith(prefix)) held.delete(key);
62
+ }
63
+ var FIELD_OPTIONS_FRESH_MS = 1e4;
64
+ function fieldOptionsOnce(client, fieldId) {
65
+ return askOnce(client, `field_options:${fieldId}`, () => client.fieldOptions({ field_id: fieldId }), FIELD_OPTIONS_FRESH_MS);
66
+ }
67
+ var ENRICH_CELLS_FRESH_MS = 5e3;
68
+
69
+ // src/versions/refusals.ts
70
+ function refusal(code, sentence, remedy, diagnostic) {
71
+ return {
72
+ code,
73
+ message: sentence,
74
+ ours: true,
75
+ ...remedy ? { hint: remedy } : {},
76
+ ...diagnostic ? { diagnostic } : {}
77
+ };
78
+ }
79
+ function refusalFromThrown(thrown, sentence, remedy, code = "internal") {
80
+ const said = thrown instanceof Error ? thrown.message : String(thrown ?? "");
81
+ return refusal(code, sentence, remedy, said ? `Thrown: ${said}` : null);
82
+ }
83
+ function refusalOr(error, code, sentence) {
84
+ return error ?? refusal(code, sentence);
85
+ }
86
+ function asRefusal(thrown, sentence, remedy, code = "internal") {
87
+ const t = thrown;
88
+ if (t && typeof t === "object" && typeof t.code === "string" && typeof t.message === "string" && !(thrown instanceof Error)) {
89
+ return t;
90
+ }
91
+ return refusalFromThrown(thrown, sentence, remedy, code);
92
+ }
93
+
94
+ // src/versions/rowVersion.ts
95
+ function ownVersion(row) {
96
+ const v = row.version;
97
+ return typeof v === "number" ? v : null;
98
+ }
99
+
100
+ // src/versions/recordVersions.ts
101
+ var STALE_MOVE_LABEL = "Changed by someone else";
102
+ var VERSION_UNREAD_LABEL = "Could not check for changes";
103
+ var UNREAD_SENTENCE = "Nothing was saved: this record's latest changes could not be checked.";
104
+ var UNREAD_REMEDY = "Reload, then try again.";
105
+ var SEEN_MOVED_SENTENCE = "Nothing was saved: someone else changed this record since you opened it.";
106
+ function versionUnread() {
107
+ return { ...refusal("stale_read", UNREAD_SENTENCE, UNREAD_REMEDY), detail: { version_unread: true } };
108
+ }
109
+ function isVersionUnread(error) {
110
+ const detail = error?.detail;
111
+ return Boolean(detail && detail.version_unread === true);
112
+ }
113
+ function isChangedElsewhere(error) {
114
+ return error?.code === "stale_write";
115
+ }
116
+ function signatureOf(row) {
117
+ const own = ownVersion(row);
118
+ if (own !== null) return `v${own}`;
119
+ try {
120
+ return JSON.stringify(row.document ?? null);
121
+ } catch {
122
+ return String(Math.random());
123
+ }
124
+ }
125
+ var HEADERS_SHARED_MS = 1e4;
126
+ function useRecordVersions(client, rows, mayRead) {
127
+ const versions = useRef(/* @__PURE__ */ new Map());
128
+ const drawn = useRef(/* @__PURE__ */ new Map());
129
+ const unread = useRef(/* @__PURE__ */ new Set());
130
+ const pending = useRef(/* @__PURE__ */ new Map());
131
+ const generation = useRef(0);
132
+ const mayReadRef = useRef(mayRead);
133
+ mayReadRef.current = mayRead;
134
+ const rowsRef = useRef(rows);
135
+ rowsRef.current = rows;
136
+ const key = useMemo(() => rows.map((row) => `${row.id}${signatureOf(row)}`).join(""), [rows]);
137
+ const raise = (id, version) => {
138
+ const held = versions.current.get(id);
139
+ if (held === void 0 || version > held) versions.current.set(id, version);
140
+ unread.current.delete(id);
141
+ };
142
+ const readHeaders = useCallback(
143
+ (toRead, shared = false) => {
144
+ if (toRead.length === 0) return;
145
+ generation.current += 1;
146
+ const gen = generation.current;
147
+ const markUnread = (id) => {
148
+ versions.current.delete(id);
149
+ unread.current.add(id);
150
+ };
151
+ const asking = shared ? askOnce(client, `record_headers:${toRead.join(",")}`, () => client.recordHeaders({ ids: toRead }), HEADERS_SHARED_MS) : client.recordHeaders({ ids: toRead });
152
+ const read = asking.then(
153
+ (res) => {
154
+ const found = /* @__PURE__ */ new Set();
155
+ if (res.ok) {
156
+ for (const header of res.data ?? []) {
157
+ found.add(header.id);
158
+ if (pending.current.get(header.id)?.gen === gen) raise(header.id, header.version);
159
+ }
160
+ }
161
+ for (const id of toRead) if (!found.has(id) && pending.current.get(id)?.gen === gen) markUnread(id);
162
+ },
163
+ () => {
164
+ for (const id of toRead) if (pending.current.get(id)?.gen === gen) markUnread(id);
165
+ }
166
+ ).finally(() => {
167
+ for (const id of toRead) if (pending.current.get(id)?.gen === gen) pending.current.delete(id);
168
+ });
169
+ for (const id of toRead) pending.current.set(id, { gen, read });
170
+ },
171
+ // eslint-disable-next-line react-hooks/exhaustive-deps
172
+ [client]
173
+ );
174
+ useEffect(() => {
175
+ const toRead = [];
176
+ for (const row of rowsRef.current) {
177
+ if (mayReadRef.current && !mayReadRef.current(row)) continue;
178
+ const sig = signatureOf(row);
179
+ if (drawn.current.get(row.id) === sig) continue;
180
+ drawn.current.set(row.id, sig);
181
+ const own = ownVersion(row);
182
+ if (own !== null) raise(row.id, own);
183
+ else toRead.push(row.id);
184
+ }
185
+ readHeaders(toRead, true);
186
+ }, [readHeaders, key]);
187
+ const refresh = useCallback((ids) => readHeaders([...ids]), [readHeaders]);
188
+ const seen = useCallback(async (id) => {
189
+ for (let guard = 0; guard < 5; guard += 1) {
190
+ const waiting = pending.current.get(id);
191
+ if (!waiting) break;
192
+ await waiting.read;
193
+ }
194
+ if (unread.current.has(id)) return null;
195
+ return versions.current.get(id) ?? null;
196
+ }, []);
197
+ const seenMany = useCallback(
198
+ async (ids) => {
199
+ const out = /* @__PURE__ */ new Map();
200
+ for (const id of ids) out.set(id, await seen(id));
201
+ return out;
202
+ },
203
+ [seen]
204
+ );
205
+ const wrote = useCallback(
206
+ (id, version) => {
207
+ raise(id, version);
208
+ readHeaders([id]);
209
+ },
210
+ [readHeaders]
211
+ );
212
+ return useMemo(() => ({ seen, seenMany, wrote, refresh }), [seen, seenMany, wrote, refresh]);
213
+ }
214
+ async function readVersionNow(client, id) {
215
+ try {
216
+ const res = await client.recordHeaders({ ids: [id] });
217
+ if (!res.ok) return null;
218
+ return (res.data ?? []).find((h) => h.id === id)?.version ?? null;
219
+ } catch {
220
+ return null;
221
+ }
222
+ }
223
+ async function updateRecordAt(client, args) {
224
+ if (args.version === null) return { ok: false, error: versionUnread() };
225
+ return args.add ? client.recordUpdateAddingChoices({ record_id: args.record_id, patch: args.patch, add: args.add, expectedVersion: args.version }) : client.recordUpdate({ record_id: args.record_id, patch: args.patch, expectedVersion: args.version });
226
+ }
227
+ async function changeRecordsAt(client, args) {
228
+ const sent = [];
229
+ for (const change of args.changes) {
230
+ if (change.op !== "update") {
231
+ sent.push(change);
232
+ continue;
233
+ }
234
+ if (change.expected_version === null) return { ok: false, error: versionUnread() };
235
+ sent.push({ op: "update", record_id: change.record_id, patch: change.patch, expected_version: change.expected_version });
236
+ }
237
+ return client.recordChangeMany({ table_id: args.table_id, changes: sent });
238
+ }
239
+ async function movePipelineCardAt(client, args) {
240
+ if (args.version === null) return { ok: false, error: versionUnread() };
241
+ return client.pipelineMove({
242
+ record_id: args.record_id,
243
+ to: args.to,
244
+ ...args.also ? { also: args.also } : {},
245
+ expected_version: args.version
246
+ });
247
+ }
248
+ async function movePipelineCardsAt(client, args) {
249
+ if (args.moves.some((m) => m.version === null)) return { ok: false, error: versionUnread() };
250
+ return client.pipelineMoveMany({
251
+ moves: args.moves.map((m) => ({ record_id: m.record_id, stage: m.stage, expected_version: m.version }))
252
+ });
253
+ }
254
+ async function restoreAt(client, args) {
255
+ if (args.seenVersion === null) return { ok: false, error: versionUnread() };
256
+ const now = await readVersionNow(client, args.record_id);
257
+ if (now === null) return { ok: false, error: versionUnread() };
258
+ if (now !== args.seenVersion) {
259
+ return {
260
+ ok: false,
261
+ error: { ...refusal("stale_write", SEEN_MOVED_SENTENCE, UNREAD_REMEDY), detail: { expected_version: args.seenVersion, current_version: now } }
262
+ };
263
+ }
264
+ return args.field_key ? client.valueRestore({ record_id: args.record_id, field_key: args.field_key, version: args.version }) : client.restoreVersion({ record_id: args.record_id, version: args.version });
265
+ }
266
+ async function readTableVersionNow(client, tableId) {
267
+ const header = await readVersionNow(client, tableId);
268
+ if (header !== null) return header;
269
+ try {
270
+ const res = await client.revisions({ record_id: tableId });
271
+ if (!res.ok) return null;
272
+ return (res.data ?? []).reduce((best, r) => best === null || r.version > best ? r.version : best, null);
273
+ } catch {
274
+ return null;
275
+ }
276
+ }
277
+ function useTableVersion(client, tableId, table) {
278
+ const key = useMemo(() => {
279
+ try {
280
+ return `${tableId ?? ""}${JSON.stringify(table ?? null)}`;
281
+ } catch {
282
+ return String(tableId ?? "");
283
+ }
284
+ }, [tableId, table]);
285
+ const read = useRef(null);
286
+ const clientRef = useRef(client);
287
+ clientRef.current = client;
288
+ const keyRef = useRef(key);
289
+ keyRef.current = key;
290
+ return useCallback(async () => {
291
+ if (!tableId) return null;
292
+ if (read.current && read.current.key === keyRef.current) return read.current.version;
293
+ const version = readTableVersionNow(clientRef.current, tableId);
294
+ read.current = { key: keyRef.current, version };
295
+ return version;
296
+ }, [tableId]);
297
+ }
298
+
299
+ // src/versions/writeOnTheWinningVersion.ts
300
+ import { asWriteConflict } from "@ai-matrx/records/core";
301
+ function sameStoredValue(left, right) {
302
+ if (left === right) return true;
303
+ const empty = (v) => v === null || v === void 0 || v === "";
304
+ if (empty(left) || empty(right)) return empty(left) && empty(right);
305
+ if (typeof left === "object" || typeof right === "object") {
306
+ try {
307
+ return JSON.stringify(left) === JSON.stringify(right);
308
+ } catch {
309
+ return false;
310
+ }
311
+ }
312
+ return false;
313
+ }
314
+ async function updateOnTheWinningVersion(client, recordId, patch, expectedVersion, base) {
315
+ const send = (expected) => updateRecordAt(client, { record_id: recordId, patch, version: expected });
316
+ const first = await send(expectedVersion);
317
+ if (first.ok) return first;
318
+ const conflict = asWriteConflict(first.error, recordId);
319
+ if (!conflict) return first;
320
+ const untouched = base === "always" || Object.keys(patch).every((key) => sameStoredValue(conflict.contested_fields[key], base[key]));
321
+ if (!untouched) return first;
322
+ return send(conflict.current_version);
323
+ }
324
+ export {
325
+ ENRICH_CELLS_FRESH_MS,
326
+ FIELD_OPTIONS_FRESH_MS,
327
+ STALE_MOVE_LABEL,
328
+ VERSION_UNREAD_LABEL,
329
+ asRefusal,
330
+ askOnce,
331
+ changeRecordsAt,
332
+ fieldOptionsOnce,
333
+ forgetAllAsked,
334
+ forgetAsked,
335
+ isChangedElsewhere,
336
+ isVersionUnread,
337
+ movePipelineCardAt,
338
+ movePipelineCardsAt,
339
+ ownVersion,
340
+ readTableVersionNow,
341
+ readVersionNow,
342
+ refusal,
343
+ refusalFromThrown,
344
+ refusalOr,
345
+ restoreAt,
346
+ sameStoredValue,
347
+ updateOnTheWinningVersion,
348
+ updateRecordAt,
349
+ useRecordVersions,
350
+ useTableVersion,
351
+ versionUnread
352
+ };
353
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/versions/recordVersions.ts","../../src/versions/askOnce.ts","../../src/versions/refusals.ts","../../src/versions/rowVersion.ts","../../src/versions/writeOnTheWinningVersion.ts"],"sourcesContent":["\"use client\";\n\n// src/versions/recordVersions.ts — @ai-matrx/records/versions (moved whole from records-ui 2026-10-08) · lane VIEWS-AND-FIELDS, sublane VW (2026-10-02)\n//\n// ONE RULE, ONE PLACE: EVERY UPDATE OF AN EXISTING RECORD CARRIES THE VERSION THE PERSON SAW.\n//\n// The store's `record_update`, `record_update_adding_choices`, `record_change_many` and\n// `pipeline_move` do a hard compare-and-set when handed a version, and a write with NO version is\n// last-write-wins (proven on the nightly clone, 2026-10-02: a `record_change_many` update with no\n// `expected_version` overwrote a colleague's change). So a screen that forgets the version — or\n// reads a fresh one just before writing — silently overwrites a colleague.\n//\n// This module is the ONLY file in the package that may call those doors (census guard:\n// `every-update-carries-the-version-it-saw.test.tsx`). It holds:\n//\n// · `useRecordVersions(rows)` — THE VERSIONS AT LOAD. Seeded from the row's own `version` when the\n// host's read carries one, else ONE `custom.record_headers` read for the rows whose CONTENT is new\n// (keyed by record id and by what the row holds — never by the array's identity, so a parent that\n// rebuilds the row array every render never re-reads a version without re-drawing the record:\n// verifier D1, a Rule view sorted in the browser). Rows re-read with new content (a reload, a\n// realtime notice) are read again. A version only ever goes up; a write this screen made raises it.\n// · `updateRecordAt`, `changeRecordsAt`, `movePipelineCardAt`, `movePipelineCardsAt`, `restoreAt` —\n// the write doors, each REFUSING a missing version with its own words (`VERSION_UNREAD_LABEL`),\n// never sending it as \"no version\".\n//\n// WHAT THIS CANNOT CLOSE (reported, store-side): the read doors answer documents, never versions, so\n// the version is a SECOND read just after the rows. A colleague's write landing between the two is\n// drawn old and versioned new. Only a read door that answers `version` per row closes that gap.\n\nimport { useCallback, useEffect, useMemo, useRef } from \"react\";\nimport type { RecordChange, RecordChangeResult, RecordsError, RecordsResult, ReadRow, RestoreResult, Uuid } from \"@ai-matrx/records\";\nimport type { RecordsClient } from \"@ai-matrx/records/core\";\nimport { askOnce } from \"./askOnce\";\nimport { refusal } from \"./refusals\";\n\n/** THE CONFLICT, on a card, a badge and the floating notice — the store said somebody else won. */\nexport const STALE_MOVE_LABEL = \"Changed by someone else\";\n/** THE VERSION COULD NOT BE READ, so nothing was written — never shown as a conflict. */\nexport const VERSION_UNREAD_LABEL = \"Could not check for changes\";\n\nconst UNREAD_SENTENCE = \"Nothing was saved: this record's latest changes could not be checked.\";\nconst UNREAD_REMEDY = \"Reload, then try again.\";\nconst SEEN_MOVED_SENTENCE = \"Nothing was saved: someone else changed this record since you opened it.\";\n\n/** The refusal for \"the version the person saw is not known\". */\nexport function versionUnread(): RecordsError {\n return { ...refusal(\"stale_read\", UNREAD_SENTENCE, UNREAD_REMEDY), detail: { version_unread: true } };\n}\n\n/** Is this refusal \"the version could not be read\" (never \"somebody else changed it\")? */\nexport function isVersionUnread(error: RecordsError | null | undefined): boolean {\n const detail = error?.detail as { version_unread?: unknown } | undefined;\n return Boolean(detail && detail.version_unread === true);\n}\n\n/** Is this refusal \"somebody else changed it first\"? */\nexport function isChangedElsewhere(error: RecordsError | null | undefined): boolean {\n return error?.code === \"stale_write\";\n}\n\nexport { ownVersion } from \"./rowVersion\";\nimport { ownVersion } from \"./rowVersion\";\n\n/** What a row holds, as one string — a re-read with the same content is not a new drawing. */\nfunction signatureOf(row: ReadRow): string {\n const own = ownVersion(row);\n if (own !== null) return `v${own}`;\n try {\n return JSON.stringify(row.document ?? null);\n } catch {\n return String(Math.random());\n }\n}\n\nexport interface RecordVersions {\n /** The version the person saw this record at; waits for the read of freshly drawn rows. `null` = unread. */\n seen: (id: Uuid) => Promise<number | null>;\n /** The same, for many records at once. */\n seenMany: (ids: readonly Uuid[]) => Promise<Map<Uuid, number | null>>;\n /** A write this screen made moved the record on (a version only goes up). */\n wrote: (id: Uuid, version: number) => void;\n /**\n * Read these records' versions again now: the person pressed Reload, or the store refused a write\n * because somebody else won (`PT409`) — the rows are re-read too, so the next write is against what\n * the person then sees. A change that moved only the version (who sees the row) never re-reads by\n * content, so without this the row stayed refused until the screen was opened again (verifier V10).\n */\n refresh: (ids: readonly Uuid[]) => void;\n}\n\n/**\n * How long the versions read at load are shared between the screens that ask the same ids — one page\n * open, including the records client rebuilt when the active organization resolves (~1.5-2 s in, after\n * a load that itself takes a while). Only the load's read is shared: a read after a write or a Reload\n * always asks, so this window never holds a version a write changed. 3 s ended inside that rebuild on a\n * loaded runner (the seeded-grid test, 2026-10-08).\n */\nconst HEADERS_SHARED_MS = 10_000;\n\n/**\n * THE VERSIONS AT LOAD for the rows a screen drew. Stable across re-renders (one handle); re-reads only\n * the rows whose content changed. `mayRead` leaves out rows the screen will never write (a viewer's).\n */\nexport function useRecordVersions(\n client: Pick<RecordsClient, \"recordHeaders\">,\n rows: readonly ReadRow[],\n mayRead?: (row: ReadRow) => boolean,\n): RecordVersions {\n const versions = useRef(new Map<Uuid, number>());\n const drawn = useRef(new Map<Uuid, string>());\n const unread = useRef(new Set<Uuid>());\n const pending = useRef(new Map<Uuid, { gen: number; read: Promise<void> }>());\n const generation = useRef(0);\n const mayReadRef = useRef(mayRead);\n mayReadRef.current = mayRead;\n const rowsRef = useRef(rows);\n rowsRef.current = rows;\n\n // THE KEY IS WHAT THE ROWS HOLD, NEVER THE ARRAY: a new array with the same rows reads nothing.\n const key = useMemo(() => rows.map((row) => `${row.id}\\u0001${signatureOf(row)}`).join(\"\\u0002\"), [rows]);\n\n const raise = (id: Uuid, version: number) => {\n const held = versions.current.get(id);\n if (held === undefined || version > held) versions.current.set(id, version);\n unread.current.delete(id);\n };\n\n const readHeaders = useCallback(\n (toRead: Uuid[], shared = false) => {\n if (toRead.length === 0) return;\n generation.current += 1;\n const gen = generation.current;\n const markUnread = (id: Uuid) => {\n versions.current.delete(id);\n unread.current.add(id);\n };\n // The versions AT LOAD are one question for every screen that draws these rows (the grid, its\n // editing layer, the move layer) and for a records client rebuilt a moment later: asked once. A\n // read after a write or a Reload (`shared` off) always asks.\n const asking = shared\n ? askOnce(client, `record_headers:${toRead.join(\",\")}`, () => client.recordHeaders({ ids: toRead }), HEADERS_SHARED_MS)\n : client.recordHeaders({ ids: toRead });\n const read = asking\n .then(\n (res) => {\n const found = new Set<Uuid>();\n if (res.ok) {\n for (const header of res.data ?? []) {\n found.add(header.id);\n if (pending.current.get(header.id)?.gen === gen) raise(header.id, header.version);\n }\n }\n // A FAILED OR SILENT READ IS AN UNREAD VERSION: said as its own refusal, never a guess.\n for (const id of toRead) if (!found.has(id) && pending.current.get(id)?.gen === gen) markUnread(id);\n },\n () => {\n for (const id of toRead) if (pending.current.get(id)?.gen === gen) markUnread(id);\n },\n )\n .finally(() => {\n for (const id of toRead) if (pending.current.get(id)?.gen === gen) pending.current.delete(id);\n });\n // A read already asked still answers for the rows it was asked for; a newer read of the same\n // record supersedes it by generation.\n for (const id of toRead) pending.current.set(id, { gen, read });\n },\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [client],\n );\n\n useEffect(() => {\n const toRead: Uuid[] = [];\n for (const row of rowsRef.current) {\n if (mayReadRef.current && !mayReadRef.current(row)) continue;\n const sig = signatureOf(row);\n if (drawn.current.get(row.id) === sig) continue;\n drawn.current.set(row.id, sig);\n const own = ownVersion(row);\n if (own !== null) raise(row.id, own);\n else toRead.push(row.id);\n }\n readHeaders(toRead, true);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [readHeaders, key]);\n\n /** Read these records' versions again now — the person pressed Reload. */\n const refresh = useCallback((ids: readonly Uuid[]) => readHeaders([...ids]), [readHeaders]);\n\n const seen = useCallback(async (id: Uuid): Promise<number | null> => {\n for (let guard = 0; guard < 5; guard += 1) {\n const waiting = pending.current.get(id);\n if (!waiting) break;\n await waiting.read;\n }\n if (unread.current.has(id)) return null;\n return versions.current.get(id) ?? null;\n }, []);\n\n const seenMany = useCallback(\n async (ids: readonly Uuid[]) => {\n const out = new Map<Uuid, number | null>();\n for (const id of ids) out.set(id, await seen(id));\n return out;\n },\n [seen],\n );\n\n /*\n * A WRITE THIS SCREEN MADE: its version raises the record's, and the record's header is read once\n * more. `record_update` answers the version from BEFORE the store's own automations ran (a stage\n * rule that notifies moves v3 to v4 with nothing on screen changing — verifier V10), so the next\n * write waits for the header, never for a conflict with the store itself.\n */\n const wrote = useCallback(\n (id: Uuid, version: number) => {\n raise(id, version);\n readHeaders([id]);\n },\n [readHeaders],\n );\n\n return useMemo(() => ({ seen, seenMany, wrote, refresh }), [seen, seenMany, wrote, refresh]);\n}\n\n/** One record's version, read now — for a screen that shows something else first (a preview, a proposal). */\nexport async function readVersionNow(client: Pick<RecordsClient, \"recordHeaders\">, id: Uuid): Promise<number | null> {\n try {\n const res = await client.recordHeaders({ ids: [id] });\n if (!res.ok) return null;\n return (res.data ?? []).find((h) => h.id === id)?.version ?? null;\n } catch {\n return null;\n }\n}\n\n/* ═══ THE WRITE DOORS ════════════════════════════════════════════════════════════════════════ */\n\n/** ONE record's update, against the version the person saw (`add`: words that also become choices). */\nexport async function updateRecordAt(\n client: Pick<RecordsClient, \"recordUpdate\" | \"recordUpdateAddingChoices\">,\n args: { record_id: Uuid; patch: Record<string, unknown>; version: number | null; add?: Record<string, string[]> | undefined },\n): Promise<RecordsResult<number>> {\n if (args.version === null) return { ok: false, error: versionUnread() };\n return args.add\n ? client.recordUpdateAddingChoices({ record_id: args.record_id, patch: args.patch, add: args.add, expectedVersion: args.version })\n : client.recordUpdate({ record_id: args.record_id, patch: args.patch, expectedVersion: args.version });\n}\n\n/** One change of a batch: an update NAMES the version it was made against (`null` = unread). */\nexport type VersionedChange =\n | Extract<RecordChange, { op: \"insert\" }>\n | Extract<RecordChange, { op: \"archive\" }>\n | { op: \"update\"; record_id: Uuid; patch: Record<string, unknown>; expected_version: number | null };\n\n/** MANY CHANGES TO ONE TABLE, ONE TRANSACTION. Any update with an unread version refuses the batch. */\nexport async function changeRecordsAt(\n client: Pick<RecordsClient, \"recordChangeMany\">,\n args: { table_id: Uuid; changes: readonly VersionedChange[] },\n): Promise<RecordsResult<RecordChangeResult[]>> {\n const sent: RecordChange[] = [];\n for (const change of args.changes) {\n if (change.op !== \"update\") {\n sent.push(change);\n continue;\n }\n if (change.expected_version === null) return { ok: false, error: versionUnread() };\n sent.push({ op: \"update\", record_id: change.record_id, patch: change.patch as never, expected_version: change.expected_version });\n }\n return client.recordChangeMany({ table_id: args.table_id, changes: sent });\n}\n\n/** A pipeline card to another stage, against the version the board drew it at. */\nexport async function movePipelineCardAt(\n client: Pick<RecordsClient, \"pipelineMove\">,\n args: { record_id: Uuid; to: string; also?: Record<string, unknown> | undefined; version: number | null },\n): ReturnType<RecordsClient[\"pipelineMove\"]> {\n if (args.version === null) return { ok: false, error: versionUnread() };\n return client.pipelineMove({\n record_id: args.record_id,\n to: args.to,\n ...(args.also ? { also: args.also } : {}),\n expected_version: args.version,\n });\n}\n\n/** Many pipeline cards, one call; every card names its version or the batch is not sent. */\nexport async function movePipelineCardsAt(\n client: Pick<RecordsClient, \"pipelineMoveMany\">,\n args: { moves: ReadonlyArray<{ record_id: Uuid; stage: string; version: number | null }> },\n): ReturnType<RecordsClient[\"pipelineMoveMany\"]> {\n if (args.moves.some((m) => m.version === null)) return { ok: false, error: versionUnread() };\n return client.pipelineMoveMany({\n moves: args.moves.map((m) => ({ record_id: m.record_id, stage: m.stage, expected_version: m.version as number })),\n });\n}\n\n/**\n * PUT A VERSION BACK (one column, or the whole record), only while the record is still at the version\n * the person saw the preview at. The restore doors take no expected version, so the check is ONE\n * header read just before — a colleague's change since the preview is refused as a conflict; only a\n * write landing inside that one round trip can still slip past (reported: needs a store-side version).\n */\nexport async function restoreAt(\n client: Pick<RecordsClient, \"recordHeaders\" | \"valueRestore\" | \"restoreVersion\">,\n args: { record_id: Uuid; version: number; field_key?: string | null | undefined; seenVersion: number | null },\n): Promise<RecordsResult<RestoreResult>> {\n if (args.seenVersion === null) return { ok: false, error: versionUnread() };\n const now = await readVersionNow(client, args.record_id);\n if (now === null) return { ok: false, error: versionUnread() };\n if (now !== args.seenVersion) {\n return {\n ok: false,\n error: { ...refusal(\"stale_write\", SEEN_MOVED_SENTENCE, UNREAD_REMEDY), detail: { expected_version: args.seenVersion, current_version: now } },\n };\n }\n return args.field_key\n ? client.valueRestore({ record_id: args.record_id, field_key: args.field_key, version: args.version })\n : client.restoreVersion({ record_id: args.record_id, version: args.version });\n}\n\n/**\n * A TABLE'S OWN VERSION, read now. `custom.record_headers` answers RECORDS only (it never returns a\n * Table's row: measured on the clone 2026-10-02, 0 rows for a Table id), so a Table's version is the\n * newest of its revisions (`custom.io_revisions`, which does answer a Table — v10 for Cedar Ridge PT's\n * Patient Visits). The history can fall behind the record; a write on it goes through\n * `updateOnTheWinningVersion`, which resends once on the winning version when the column it changes\n * is still what the screen drew.\n */\nexport async function readTableVersionNow(client: Pick<RecordsClient, \"revisions\" | \"recordHeaders\">, tableId: Uuid): Promise<number | null> {\n // `record_headers` FIRST: once it answers Tables too (sublane FD's migration, on the clone only as of\n // 2026-10-02) its version is the record's own and never lags. Until then it answers no row for a\n // Table, and the revisions stand in.\n const header = await readVersionNow(client, tableId);\n if (header !== null) return header;\n try {\n const res = await client.revisions({ record_id: tableId });\n if (!res.ok) return null;\n return (res.data ?? []).reduce<number | null>((best, r) => (best === null || r.version > best ? r.version : best), null);\n } catch {\n return null;\n }\n}\n\n/**\n * A TABLE'S OWN RECORD (its name, its row label): the version of the table document this screen drew,\n * read again whenever that document arrives with new content (never per render).\n */\nexport function useTableVersion(\n client: Pick<RecordsClient, \"revisions\" | \"recordHeaders\">,\n tableId: Uuid | null | undefined,\n table: unknown,\n): () => Promise<number | null> {\n const key = useMemo(() => {\n try {\n return `${tableId ?? \"\"}\\u0001${JSON.stringify(table ?? null)}`;\n } catch {\n return String(tableId ?? \"\");\n }\n }, [tableId, table]);\n // READ WHEN A WRITE ASKS, NEVER ON LOAD (lane TABLE-ACTIONS T5.3): two grid controls each read\n // the table's version on every table open — 2 × `record_headers` + 2 × `io_revisions` for a\n // number used only when somebody renames the title column. The first ask after the drawn table\n // changed reads it once; later asks share that read. A write goes through\n // `updateOnTheWinningVersion`, which compares the column it changes with what the screen drew, so\n // a version read at press time loses nothing the load-time read protected.\n const read = useRef<{ key: string; version: Promise<number | null> } | null>(null);\n const clientRef = useRef(client);\n clientRef.current = client;\n const keyRef = useRef(key);\n keyRef.current = key;\n return useCallback(async () => {\n if (!tableId) return null;\n if (read.current && read.current.key === keyRef.current) return read.current.version;\n const version = readTableVersionNow(clientRef.current, tableId);\n read.current = { key: keyRef.current, version };\n return version;\n }, [tableId]);\n}\n","// src/versions/askOnce.ts — @ai-matrx/records/versions (moved whole from records-ui 2026-10-08)\n//\n// ONE QUESTION IN FLIGHT, ONE ANSWER FOR EVERY ASKER (lane TABLE-ACTIONS T5.3, PROOF-BOOK 1.18).\n//\n// A table open asked `custom.field_options` 13 times for 2 choice columns (the colours, the label\n// lookup, the paste planner and each re-run of their effects each asked on their own) and the\n// header menus' value counts twice per column. Every asker of the same question, on the same\n// client, within `freshMs` of the last answer, gets that one answer. A refusal is never kept: the\n// next asker asks again. A write that changes the answer calls `forgetAsked` with its prefix.\n\nimport { onTableStructureChanged, type RecordsClient } from \"@ai-matrx/records/core\";\n\ntype Answer = { ok: boolean };\n\ntype Held = Map<string, { at: number; epoch: number; answer: Promise<unknown> }>;\n\n/**\n * A Table's shape moved (a column or its choices were written by anybody on this page): everything\n * kept before that moment is stale, whatever its age. This is what lets the freshness window cover a\n * whole page open (lane DEDUPE-READS) instead of two seconds.\n */\nlet structureEpoch = 0;\nonTableStructureChanged(() => {\n structureEpoch += 1;\n});\n\nconst asked = new WeakMap<object, Held>();\n/** Per data source (the host's one supabase client survives a records client's rebuild) and per person. */\nconst byPerson = new WeakMap<object, Map<string, Held>>();\n\n/**\n * WHO IS ASKING, NOT WHICH OBJECT (lane SEED-ENTITY). The records client is REBUILT when the active\n * organization resolves (~1.5 s into a page); its reads re-fire from effects keyed on the client. The\n * questions kept here are semantic (a table, a column, a set of ids — never the organization), and the\n * answer is the same person's, so a rebuilt client over the SAME data source for the SAME person\n * shares them. A client with no data source in its config (a test double) keeps its own.\n */\nfunction heldFor(client: object): Held {\n const config = (client as { config?: { dataSource?: unknown; actor?: { actor?: string; user_id?: string | null; on_behalf_of?: string | null } } }).config;\n const source = config?.dataSource;\n if (source && typeof source === \"object\" && config?.actor) {\n const who = [config.actor.actor ?? \"-\", config.actor.user_id ?? \"-\", config.actor.on_behalf_of ?? \"-\"].join(\"|\");\n let persons = byPerson.get(source);\n if (!persons) {\n persons = new Map();\n byPerson.set(source, persons);\n }\n let held = persons.get(who);\n if (!held) {\n held = new Map();\n persons.set(who, held);\n }\n return held;\n }\n let held = asked.get(client);\n if (!held) {\n held = new Map();\n asked.set(client, held);\n }\n return held;\n}\n\nexport function askOnce<T extends Answer>(client: object, key: string, ask: () => Promise<T>, freshMs: number): Promise<T> {\n const held = heldFor(client);\n const now = Date.now();\n const kept = held.get(key);\n if (kept && kept.epoch === structureEpoch && now - kept.at <= freshMs) return kept.answer as Promise<T>;\n const answer = ask().then(\n (result) => {\n if (!result.ok) held.delete(key);\n return result;\n },\n (thrown: unknown) => {\n held.delete(key);\n throw thrown;\n },\n );\n held.set(key, { at: now, epoch: structureEpoch, answer });\n return answer;\n}\n\n/** Tests only: forget everything kept for every client (tests that share one data source across cases). */\nexport function forgetAllAsked(client: object): void {\n heldFor(client).clear();\n}\n\n/** Forget every kept answer whose key starts with `prefix` (a write changed what it answers). */\nexport function forgetAsked(client: object, prefix: string): void {\n const held = heldFor(client);\n for (const key of [...held.keys()]) if (key.startsWith(prefix)) held.delete(key);\n}\n\n/**\n * How long a column's choices are shared between askers: one page open, including the records\n * client being rebuilt when the active organization resolves (~2 s in) and every effect that re-runs\n * after it. 2 s ended just before the second fire (lane DEDUPE-READS: Spaces measured one at hydration\n * and one ~2 s later). A write on this page ends it at once (`structureEpoch`); another person's edit\n * shows within this window at most.\n */\nexport const FIELD_OPTIONS_FRESH_MS = 10_000;\n\n/** A column's choices, asked once for every asker in the same moment (`custom.field_options`). */\nexport function fieldOptionsOnce(\n client: Pick<RecordsClient, \"fieldOptions\">,\n fieldId: string,\n): ReturnType<RecordsClient[\"fieldOptions\"]> {\n return askOnce(client, `field_options:${fieldId}`, () => client.fieldOptions({ field_id: fieldId }), FIELD_OPTIONS_FRESH_MS);\n}\n\n/** How long a page of badges is shared between askers: the one page open, ended by any explicit refresh. */\nexport const ENRICH_CELLS_FRESH_MS = 5_000;\n","// src/versions/refusals.ts — @ai-matrx/records/versions (moved whole from records-ui 2026-10-08)\n//\n// THE ONE BUILDER FOR A REFUSAL THIS PACKAGE WRITES ITSELF (lane REFUSAL-SWEEP,\n// 2026-09-23).\n//\n// `RefusalNotice` takes a `RecordsError`, and the store's own refusals arrive as\n// one. But twenty-six places in this package refused something IN THE BROWSER —\n// an answer not filled in, a file the browser could not read, a spreadsheet that\n// could not be written, a picker that could not list its choices — and each of\n// them held a bare string and printed it in red: no heading, no remedy, and in\n// five of them the store's `error.message` verbatim, straight past the\n// machine-identity filter every other screen goes through.\n//\n// So a refusal this package writes is built HERE, as the same object the store\n// hands back, and drawn by the same `RefusalNotice`. A person cannot tell which\n// half of the system said no and has no reason to care; `plainWords.ts` still\n// judges the sentence by shape, and the remedy is the one THIS call site knows\n// (the hint wins over the code's generic remedy — see `refusalForAPerson`).\n\nimport type { RecordsError, RecordsErrorCode } from \"@ai-matrx/records\";\n\n/**\n * A refusal in this package's own words.\n *\n * @param code what KIND of refusal — picks the notice's title and its fallback remedy.\n * @param sentence what happened, for a person.\n * @param remedy what to do now, when this call site knows better than the code's default.\n * @param diagnostic an engineer's note (a thrown message, a status) — rendered out of sight.\n */\nexport function refusal(\n code: RecordsErrorCode,\n sentence: string,\n remedy?: string | null,\n diagnostic?: string | null,\n): RecordsError {\n return {\n code,\n message: sentence,\n ours: true,\n ...(remedy ? { hint: remedy } : {}),\n ...(diagnostic ? { diagnostic } : {}),\n } as RecordsError;\n}\n\n/**\n * A refusal made from something THROWN in the browser. The thrown text is an\n * engineer's (a parser's \"Unexpected token } in JSON at position 14\", a\n * library's stack) and goes out of sight; the person reads `sentence`.\n */\nexport function refusalFromThrown(\n thrown: unknown,\n sentence: string,\n remedy?: string | null,\n code: RecordsErrorCode = \"internal\",\n): RecordsError {\n const said = thrown instanceof Error ? thrown.message : String(thrown ?? \"\");\n return refusal(code, sentence, remedy, said ? `Thrown: ${said}` : null);\n}\n\n/**\n * The store's refusal when there is one, otherwise this package's own sentence —\n * for the call sites whose answer MAY carry an error (`res.error?.message ?? \"…\"`\n * was the old shape, and it printed the store's message raw).\n */\nexport function refusalOr(error: RecordsError | null | undefined, code: RecordsErrorCode, sentence: string): RecordsError {\n return error ?? refusal(code, sentence);\n}\n\n/**\n * Whatever a host callback threw, as a refusal: the store's own `RecordsError`\n * when that is what arrived (it is drawn with its own words and remedy), and\n * otherwise `refusalFromThrown` — the thrown text out of sight, `sentence` in view.\n */\nexport function asRefusal(\n thrown: unknown,\n sentence: string,\n remedy?: string | null,\n code: RecordsErrorCode = \"internal\",\n): RecordsError {\n const t = thrown as Partial<RecordsError> | null | undefined;\n if (t && typeof t === \"object\" && typeof t.code === \"string\" && typeof t.message === \"string\" && !(thrown instanceof Error)) {\n return t as RecordsError;\n }\n return refusalFromThrown(thrown, sentence, remedy, code);\n}\n","// src/versions/rowVersion.ts — @ai-matrx/records/versions · pure (server-safe)\nimport type { ReadRow } from \"@ai-matrx/records\";\n\n/** The version a row carries itself, when the host's read gave it one. */\nexport function ownVersion(row: ReadRow): number | null {\n const v = (row as ReadRow & { version?: unknown }).version;\n return typeof v === \"number\" ? v : null;\n}\n","// src/versions/writeOnTheWinningVersion.ts — @ai-matrx/records/versions (moved whole from records-ui 2026-10-08) · lane RECORDS-UI-FIX (2026-09-23)\n//\n// ONE ANSWER TO \"THE STORE SAID SOMEBODY ELSE WON\" — for every screen that writes a\n// record it read earlier (the record form, a saved view; the grid's cell session\n// applies the same rule inside its own queue).\n//\n// 🚨 WHY IT EXISTS. A screen reads the version it writes against from the record's\n// HISTORY, and the history can be short of the record: a write that changes nothing\n// still moves `version` and records no history row (reproduced on the dev clone,\n// 2026-09-23 — `custom.record_update` of an identical value: version 1 → 2, history\n// still 1; 68 live records were already in that state). So a person who is the only\n// writer is told \"Someone else changed this record while you were working on it\",\n// and every retry says it again.\n//\n// THE RULE. The refusal carries each patched column's CURRENT value\n// (`contested_fields`). When every one of them is still what this screen started\n// from, whatever \"won\" did not touch what this person is changing, so the patch is\n// sent once more on the version the refusal names. Anything else is a real conflict\n// and is handed back for the person to decide. A saved view passes `always` — it is\n// the person's own arrangement, and their latest press is the one that stands.\n\nimport type { RecordsResult, Uuid } from \"@ai-matrx/records\";\nimport { asWriteConflict, type RecordsClient } from \"@ai-matrx/records/core\";\nimport { updateRecordAt } from \"./recordVersions\";\n\n/** Two values the way a person would compare them: empty is empty, a list is its contents. */\nexport function sameStoredValue(left: unknown, right: unknown): boolean {\n if (left === right) return true;\n const empty = (v: unknown) => v === null || v === undefined || v === \"\";\n if (empty(left) || empty(right)) return empty(left) && empty(right);\n if (typeof left === \"object\" || typeof right === \"object\") {\n try {\n return JSON.stringify(left) === JSON.stringify(right);\n } catch {\n return false;\n }\n }\n return false;\n}\n\nexport async function updateOnTheWinningVersion(\n client: Pick<RecordsClient, \"recordUpdate\" | \"recordUpdateAddingChoices\">,\n recordId: Uuid,\n patch: Record<string, unknown>,\n expectedVersion: number | null,\n /** What this screen started from, per patched column — or `always` to resend regardless. */\n base: Record<string, unknown> | \"always\",\n): Promise<RecordsResult<number>> {\n // THE ONE WRITE DOOR (`recordVersions.ts`): an unread version (`null`) is refused in its own words,\n // never sent as \"no version\" — which the store takes as last-write-wins (lane VW, verifier D6).\n const send = (expected: number | null) => updateRecordAt(client, { record_id: recordId, patch, version: expected });\n const first = await send(expectedVersion);\n if (first.ok) return first;\n const conflict = asWriteConflict(first.error, recordId);\n if (!conflict) return first;\n const untouched =\n base === \"always\" ||\n Object.keys(patch).every((key) => sameStoredValue(conflict.contested_fields[key], base[key]));\n if (!untouched) return first;\n return send(conflict.current_version);\n}\n"],"mappings":"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","names":["held"]}