@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,1024 @@
1
+ "use client";
2
+ "use strict";
3
+ "use client";
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
21
+
22
+ // src/entity/react/index.tsx
23
+ var react_exports = {};
24
+ __export(react_exports, {
25
+ ENTITY_PORT_KEY: () => ENTITY_PORT_KEY,
26
+ EntityDataProvider: () => EntityDataProvider,
27
+ useEntityColumns: () => useEntityColumns,
28
+ useEntityList: () => useEntityList,
29
+ useEntityRow: () => useEntityRow,
30
+ useEntityScope: () => useEntityScope,
31
+ useEntityType: () => useEntityType,
32
+ useEntityWrite: () => useEntityWrite,
33
+ useOptionalEntityScope: () => useOptionalEntityScope
34
+ });
35
+ module.exports = __toCommonJS(react_exports);
36
+ var import_react = require("react");
37
+ var import_react_query = require("@tanstack/react-query");
38
+ var import_seed = require("@ai-matrx/records/react");
39
+ var import_react2 = require("@ai-matrx/records/data/react");
40
+
41
+ // src/core/supabase.ts
42
+ function refuse(code, message, hint) {
43
+ return { data: null, error: { code, message, hint, details: "" } };
44
+ }
45
+ function supabaseDataSource(client) {
46
+ if (isBound(client)) return client;
47
+ const supabase = client;
48
+ const hasSchema = typeof supabase.schema === "function";
49
+ const bound = {
50
+ // PAGE-BUNDLE: the real adapter opens a table page with one `custom.table_page_bundle` call.
51
+ opensInOneCall: true,
52
+ rpc(fn, args, options) {
53
+ if (!hasSchema) {
54
+ return Promise.resolve(
55
+ refuse(
56
+ "data_source_unbound",
57
+ `The records client was given something that is not a supabase client: it has no schema() method, so the door ${fn} cannot be called in the schema it lives in.`,
58
+ "Pass a supabase-js v2 client (createClient(...)) to supabaseDataSource()."
59
+ )
60
+ );
61
+ }
62
+ const name = options?.schema;
63
+ if (!name) {
64
+ return Promise.resolve(
65
+ refuse(
66
+ "schema_unsaid",
67
+ `The door ${fn} was called without saying which schema it lives in. Every door of this store is custom.${fn} and every trust door is iam.${fn}; an unqualified call reaches PostgREST's default profile (public) and is refused there.`,
68
+ "This is a bug in the caller, not in your data: /core says the schema on every call."
69
+ )
70
+ );
71
+ }
72
+ return supabase.schema(name).rpc(fn, args);
73
+ },
74
+ schema(name) {
75
+ return supabase.schema(name);
76
+ }
77
+ };
78
+ Object.defineProperty(bound, BOUND, { value: true, enumerable: false });
79
+ return bound;
80
+ }
81
+ var BOUND = /* @__PURE__ */ Symbol.for("@ai-matrx/records/supabaseDataSource");
82
+ function isBound(source) {
83
+ return source[BOUND] === true;
84
+ }
85
+ function asRecordsDataSource(source) {
86
+ if (source === null || typeof source !== "object") return supabaseDataSource({});
87
+ if (isBound(source)) return source;
88
+ const candidate = source;
89
+ const looksLikeSupabaseJs = typeof candidate.schema === "function" && typeof candidate.from === "function" && candidate.auth !== null && typeof candidate.auth === "object";
90
+ if (looksLikeSupabaseJs || typeof candidate.rpc !== "function") return supabaseDataSource(source);
91
+ return source;
92
+ }
93
+
94
+ // src/core/pgError.ts
95
+ var BY_SQLSTATE = {
96
+ PT409: "stale_write",
97
+ "42501": "door",
98
+ "23514": "refused_by_rule",
99
+ "22004": "invalid_argument",
100
+ "22023": "invalid_argument",
101
+ // MX005 — a value the store cannot take as asked: since 2026-10-06 (AP-3 U3) a word that names more than one
102
+ // choice or linked record raises it (it used to borrow PT409 and read as somebody else's write winning).
103
+ MX005: "invalid_argument",
104
+ // MXD01 — the held-change door tried the proposed values before filing an
105
+ // approval request and the store refused them. Nothing is waiting for a
106
+ // person to decide; correct the value before asking again, which is the same
107
+ // caller-side condition as the other invalid-argument refusals.
108
+ MXD01: "invalid_argument",
109
+ // 22P02 — a value does not fit the type the store holds for it. The class is
110
+ // real and was hit live: an organization whose copy of a package table grew
111
+ // `status` as a uuid-valued Field was sent the text "open", and the store
112
+ // answered `invalid input syntax for type uuid: "open"`. It is the CALL that
113
+ // is wrong for the shape the store holds, which is what `invalid_argument`
114
+ // already means — the store's own sentence names the value and the type.
115
+ "22P02": "invalid_argument",
116
+ // 42703 — a column that is not there. The store raises it from
117
+ // `custom.entity_table`, which is asked for a column by NAME, so the class is
118
+ // exactly "the call named something the shape does not have": the same thing
119
+ // `invalid_argument` already means, and the store's own sentence names the
120
+ // column. Added 2026-09-20 (lane FORMS) because the package's live-door suite
121
+ // caught it raised by a door this package did not know about — which is the
122
+ // suite working, and is why the map is a census rather than a guess.
123
+ "42703": "invalid_argument",
124
+ // 22007 — a date or a time the store could not read as one. It is raised by
125
+ // `custom.dashboard_window_sql`, which is handed a window a caller wrote
126
+ // ("last 30 days", a literal date), so the class is again "the call named
127
+ // something the shape does not accept" and the store's own sentence names the
128
+ // text it could not read. Added 2026-09-20 (lane SCREEN-BREAK) after the
129
+ // live-door census caught it — the same way 42703 arrived, which is the
130
+ // census working rather than a guess.
131
+ "22007": "invalid_argument",
132
+ // 22003 — a number the store could not hold at the size it was handed. It is
133
+ // raised by `custom.context_resolve`, which is given a window or a depth a
134
+ // caller wrote, so the class is the same one 22P02, 42703 and 22007 already
135
+ // are: the CALL named a value the shape does not accept, and the store's own
136
+ // sentence names the number and the range. Added 2026-09-20 (lane GRID-TWO)
137
+ // when the live-door census caught the release gate red on it — which is the
138
+ // census working, and the fourth time it has been the thing that found a new
139
+ // refusal class rather than a person guessing one.
140
+ "22003": "invalid_argument",
141
+ // 42883 — `custom._template_call` (the template family, 2026-10-03) was handed a step that
142
+ // names a door the store does not have, or an argument that door does not take. The TEMPLATE
143
+ // named something the shape does not have — the same class 42703 is — and the store's own
144
+ // sentence names the door or the argument. Caught by the live-door census on records 0.68.1's
145
+ // tag, the eighth time the census found a class before a person did. (A bare 42883 on a door
146
+ // this package itself asks for is still `door_absent`, mapped before this funnel in
147
+ // `owedDoor`.)
148
+ "42883": "invalid_argument",
149
+ "02000": "not_found",
150
+ // P0002 — `custom.inbox_snooze` / `custom.inbox_clear` (lane S5-PRIME-2, 2026-09-24): "There is
151
+ // nothing in your inbox with that id". An invented id, another person's item and another
152
+ // organization's all answer it alike, so it reads exactly as 02000 does: not found.
153
+ "P0002": "not_found",
154
+ "23503": "missing_reference",
155
+ "23505": "already_exists",
156
+ "0A000": "not_supported",
157
+ "53400": "store_limit",
158
+ "22012": "store_limit",
159
+ // 54001 — `custom.record_delete` stops a cascade that nests past sixty-four
160
+ // levels of containment, because that is a loop somebody built rather than a
161
+ // hierarchy, and saying so beats recursing until the server runs out of
162
+ // stack. It is a LIMIT of the store, and the store's own sentence names what
163
+ // to do about it, so it reads as one.
164
+ "54001": "store_limit",
165
+ // 54000 — the grid primitives' organization ceilings (GRID-PRIMITIVES, 2026-09-23):
166
+ // `custom.action_run` on a selection past its knob, `custom.table_decorate` past the
167
+ // rule count, `custom.table_from_example` past `custom/example_rows_max`,
168
+ // `custom.table_webhook_declare` past `custom/table_webhooks_max`, and
169
+ // `custom.hub_changed_by`. Each names its knob in the hint and writes nothing, so it
170
+ // reads exactly like 54001: a limit of the store, in the store's own sentence.
171
+ "54000": "store_limit",
172
+ // VIS: `custom.has_visibility_at` refuses to answer from a snapshot older
173
+ // than a write this reader has already seen, rather than returning something
174
+ // they just lost access to. It is retryable on a fresher connection, which is
175
+ // a different instruction from every other refusal here.
176
+ // 55000 — `custom.table_move` (SC-1', 2026-09-24) refuses a move the table's present state
177
+ // does not allow: a destination that cannot take it, a table something else holds, contents
178
+ // that point at themselves in a circle. The store's own sentence names the reason and nothing
179
+ // moves, so it reads as a refusal on the merits. Caught by the live-door census on records
180
+ // 0.58.3's tag, the seventh time the census found a class before a person did.
181
+ "55000": "refused_by_rule",
182
+ // 42P17 — a worked-out column that would read itself round a circle (STORE-TAILS-3,
183
+ // 2026-09-25): `custom._field_reads_what_it_reads` refuses the declaration or edit that would
184
+ // close the circle, naming every column in it, and `custom.far_value` refuses a circle stored
185
+ // before that rule when a lookup or rollup is worked out. Refused on its merits, in the store's
186
+ // own sentence, nothing written — so it reads as 55000 does.
187
+ "42P17": "refused_by_rule",
188
+ // P0001 — `custom.context_scope_write` (2026-10-03) refuses a scope whose parent breaks the
189
+ // parent rule in the old trigger's own class and words (manager ruling H6). Refused on its
190
+ // merits, nothing written. Caught by the live-door census on records 0.65.0's tag.
191
+ P0001: "refused_by_rule",
192
+ "40001": "stale_read",
193
+ // 57014 — the store cancelled the statement at its own timeout. Like
194
+ // `stale_read` and unlike every other refusal here, the call was never
195
+ // refused on its merits: the same call on a smaller page, or once the
196
+ // organization's kernel is not being rewritten, can succeed. It gets its own
197
+ // code so a screen can offer "try again" honestly instead of showing a
198
+ // failure sentence the caller cannot act on.
199
+ "57014": "timed_out",
200
+ // PostgREST's own two: the store is not being SERVED, which is a different
201
+ // thing from the store refusing. `custom` absent from `pgrst.db_schemas`, or
202
+ // the grants not copied, land here.
203
+ PGRST202: "store_not_served",
204
+ PGRST205: "store_not_served",
205
+ PGRST301: "door",
206
+ // PT428 — NOT A FAILURE, and the release gate's live census is what found it
207
+ // missing: the store raises it and this package answered `internal`, which is
208
+ // the one classification that tells a screen nothing. A stage gate whose
209
+ // `on_fail` is `require_approval` files the request and leaves the record
210
+ // where it was; the transaction is rolled back to the pipeline's own block, so
211
+ // "nothing was written" is a fact rather than a promise. Landed by lane
212
+ // STAGE-RULES; mapped here by STAGE-RULES-2 the day after, when the census
213
+ // caught it — the sixth time that census has been the thing that found a
214
+ // refusal class rather than a person guessing one.
215
+ PT428: "needs_approval"
216
+ };
217
+ var MAPPED_SQLSTATES = Object.keys(BY_SQLSTATE);
218
+ function parseDetail(details) {
219
+ if (!details) return void 0;
220
+ const trimmed = details.trim();
221
+ if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return details;
222
+ try {
223
+ return JSON.parse(trimmed);
224
+ } catch {
225
+ return details;
226
+ }
227
+ }
228
+ function isNotASqlstate(code) {
229
+ if (!code) return true;
230
+ return /^[0-9]{3}$/.test(code);
231
+ }
232
+ function mapPgError(error, where) {
233
+ const raw = error.code ?? void 0;
234
+ const sqlstate = raw && raw !== "" ? raw : void 0;
235
+ const mapped = {
236
+ code: "internal",
237
+ message: error.message,
238
+ ...error.hint ? { hint: error.hint } : {},
239
+ ...sqlstate ? { sqlstate } : {}
240
+ };
241
+ if (isNotASqlstate(sqlstate)) {
242
+ mapped.code = "unreachable";
243
+ } else {
244
+ mapped.code = BY_SQLSTATE[sqlstate] ?? "internal";
245
+ }
246
+ const detail = parseDetail(error.details);
247
+ if (detail !== void 0) mapped.detail = detail;
248
+ if (mapped.code === "internal") {
249
+ mapped.unmapped = true;
250
+ mapped.diagnostic = `@ai-matrx/records did not recognise SQLSTATE ${sqlstate ?? "(none)"} from ${where}; the store's own message is the thing to act on.`;
251
+ } else if (mapped.code === "unreachable") {
252
+ mapped.unmapped = true;
253
+ mapped.diagnostic = `@ai-matrx/records never reached the store from ${where}: ${raw ? `the transport answered ${raw}` : "no response"} \u2014 ${error.message}`;
254
+ mapped.message = "We could not reach your data.";
255
+ delete mapped.hint;
256
+ }
257
+ return mapped;
258
+ }
259
+
260
+ // src/entity/core/errors.ts
261
+ var ENTITY_REFUSALS = {
262
+ /** conflict_version: somebody else's write won. */
263
+ MX001: "stale_write",
264
+ /** organization_required: the call needs an organization and carried none. */
265
+ MX002: "organization_required",
266
+ /** forbidden. */
267
+ MX003: "door",
268
+ /** not_found (also: not visible to this person). */
269
+ MX004: "not_found",
270
+ /** validation_refused (detail.reason required / bad_value / ambiguous_word / constraint; `not_supported` is read apart below). */
271
+ MX005: "invalid_argument",
272
+ /** system_column: a write named a column the store owns. */
273
+ MX008: "invalid_argument",
274
+ /** unknown_identifier: a column that is not there or not usable here. */
275
+ MX012: "invalid_argument",
276
+ /** cursor_invalid: the page cursor belongs to another list; the engine restarts the page. */
277
+ MX013: "invalid_argument",
278
+ /** view_not_offered. */
279
+ MX015: "invalid_argument",
280
+ /** invalid_argument: a parameter the door refuses by name. */
281
+ MX016: "invalid_argument"
282
+ };
283
+ function mapEntityError(error, where) {
284
+ const mapped = mapPgError(error, where);
285
+ const state = mapped.sqlstate;
286
+ if (state && /^MX\d{3}$/.test(state)) {
287
+ const code = ENTITY_REFUSALS[state];
288
+ if (code) {
289
+ const reason = mapped.detail?.reason;
290
+ const known = { ...mapped, code: state === "MX005" && reason === "not_supported" ? "not_supported" : code };
291
+ delete known.unmapped;
292
+ delete known.diagnostic;
293
+ return known;
294
+ }
295
+ }
296
+ return mapped;
297
+ }
298
+ function refusal(code, message, hint) {
299
+ return { ok: false, error: { code, message, ...hint ? { hint } : {} } };
300
+ }
301
+
302
+ // src/entity/core/client.ts
303
+ var OP_ID_HEADER = "x-matrx-op-id";
304
+ var ENTITY_DOOR_SCHEMA = "platform";
305
+ function createEntityClient(options) {
306
+ const source = asRecordsDataSource(options.dataSource ?? options.supabase);
307
+ let warnedHeaderless = false;
308
+ function withHeaders(pending, headers) {
309
+ if (!headers) return;
310
+ const builder = pending;
311
+ if (typeof builder.setHeader === "function") {
312
+ for (const [name, value] of Object.entries(headers)) builder.setHeader(name, value);
313
+ return;
314
+ }
315
+ if (!warnedHeaderless) {
316
+ warnedHeaderless = true;
317
+ console.warn(
318
+ `[@ai-matrx/records/entity] This data source cannot carry a per-call header (${Object.keys(headers).join(", ")}), so this browser re-reads the notice of its own writes once.`
319
+ );
320
+ }
321
+ }
322
+ async function call(fn, args, schema = ENTITY_DOOR_SCHEMA, headers) {
323
+ try {
324
+ const pending = source.rpc(fn, args, { schema });
325
+ withHeaders(pending, headers);
326
+ const answer = await pending;
327
+ if (answer.error) return { ok: false, error: mapEntityError(answer.error, `${schema}.${fn}`) };
328
+ return { ok: true, data: answer.data };
329
+ } catch (thrown) {
330
+ return {
331
+ ok: false,
332
+ error: mapEntityError({ code: "", message: thrown instanceof Error ? thrown.message : String(thrown) }, `${schema}.${fn}`)
333
+ };
334
+ }
335
+ }
336
+ return {
337
+ list(request) {
338
+ const args = {
339
+ p_entity_type: request.token,
340
+ p_scope: request.scope ?? null,
341
+ p_filter: request.filter ?? {},
342
+ p_search: request.search ?? null,
343
+ p_sort: request.sort ?? null,
344
+ p_archived: request.archived ?? "active",
345
+ p_page_size: request.pageSize ?? null,
346
+ p_after: request.after ?? null,
347
+ p_with_total: request.withTotal ?? true
348
+ };
349
+ return call("entity_list_scoped", args);
350
+ },
351
+ get(request) {
352
+ return call("entity_get", { p_entity_type: request.token, p_ids: [...request.ids] });
353
+ },
354
+ columns(request) {
355
+ return call("entity_columns", { p_organization_id: request.organizationId ?? null, p_token: request.token });
356
+ },
357
+ insert(request) {
358
+ return call(
359
+ "entity_insert",
360
+ { p_entity_type: request.token, p_organization_id: request.organizationId, p_rows: request.rows.map((r) => ({ ...r })) },
361
+ ENTITY_DOOR_SCHEMA,
362
+ opHeader(request.opId)
363
+ );
364
+ },
365
+ update(request) {
366
+ return call(
367
+ "entity_update",
368
+ { p_entity_type: request.token, p_id: request.id, p_base_version: request.baseVersion, p_patch: request.patch },
369
+ ENTITY_DOOR_SCHEMA,
370
+ opHeader(request.opId)
371
+ );
372
+ },
373
+ archive(request) {
374
+ return call("entity_archive", { p_entity_type: request.token, p_ids: [...request.ids] }, ENTITY_DOOR_SCHEMA, opHeader(request.opId));
375
+ },
376
+ unarchive(request) {
377
+ return call("entity_unarchive", { p_entity_type: request.token, p_ids: [...request.ids] }, ENTITY_DOOR_SCHEMA, opHeader(request.opId));
378
+ },
379
+ knob(request) {
380
+ return call("knob_resolve", { p_feature: "entity_data", p_key: request.key, p_organization_id: request.organizationId ?? null });
381
+ },
382
+ async memberOrganizations() {
383
+ const answer = await call("my_orgs", {}, "iam");
384
+ if (!answer.ok) return answer;
385
+ const rows = Array.isArray(answer.data) ? answer.data : [];
386
+ const ids = rows.map((row) => typeof row === "string" ? row : row && typeof row === "object" ? Object.values(row)[0] : null).filter((id) => typeof id === "string");
387
+ return { ok: true, data: ids };
388
+ }
389
+ };
390
+ }
391
+ function opHeader(opId) {
392
+ return opId ? { [OP_ID_HEADER]: opId } : void 0;
393
+ }
394
+
395
+ // src/entity/core/realtime.ts
396
+ var import_realtime = require("@ai-matrx/realtime");
397
+
398
+ // src/ops.ts
399
+ var import_global_slot = require("@ai-matrx/kit/global-slot");
400
+ var REMEMBER_MS = 3e4;
401
+ var REMEMBER_MAX = 500;
402
+ var ours = (0, import_global_slot.globalSlot)("ai-matrx.records.op-ledger", () => /* @__PURE__ */ new Map());
403
+ function trim(now) {
404
+ for (const [id, at] of ours) {
405
+ if (now - at <= REMEMBER_MS) break;
406
+ ours.delete(id);
407
+ }
408
+ while (ours.size > REMEMBER_MAX) {
409
+ const oldest = ours.keys().next();
410
+ if (oldest.done === true) break;
411
+ ours.delete(oldest.value);
412
+ }
413
+ }
414
+ function mintOpId() {
415
+ const id = crypto.randomUUID();
416
+ const now = Date.now();
417
+ ours.set(id, now);
418
+ trim(now);
419
+ return id;
420
+ }
421
+ function isOwnOp(opId) {
422
+ if (!opId) return false;
423
+ const at = ours.get(opId);
424
+ if (at === void 0) return false;
425
+ if (Date.now() - at > REMEMBER_MS) {
426
+ ours.delete(opId);
427
+ return false;
428
+ }
429
+ return true;
430
+ }
431
+
432
+ // src/entity/core/realtime.ts
433
+ var entityOrgChannel = (0, import_realtime.defineChannelNamespace)({
434
+ namespace: "entity-org",
435
+ foreignTopic: "mx:entity-org",
436
+ parts: ["organizationId", "token"],
437
+ description: "One organization's changes to one platform type (contacts, deals, projects, tasks, employees). Carries NOTICES written by platform.entity_org_notice_stmt \u2014 which ids moved, never a value. Admitted by the one RLS policy on realtime.messages to members of that organization."
438
+ });
439
+ var ENTITY_NOTICE_EVENT = "entity_bump";
440
+ var DEFAULT_BATCH_MS = 150;
441
+ var DEFAULT_MAX_WAIT_MS = 1e3;
442
+ var DEFAULT_MAX_ORG_TOPICS = 20;
443
+ function entityNoticeKey(notice) {
444
+ return notice.id ?? (notice.at ? `${notice.organization_id ?? ""}:${notice.entity_type ?? ""}:${notice.at}` : void 0);
445
+ }
446
+ function entityOrgTopic(organizationId, token) {
447
+ return entityOrgChannel.topic({ organizationId, token });
448
+ }
449
+ function entityOrgChannelSpec(organizationId, token, onNotice, onBackfill) {
450
+ return {
451
+ topic: entityOrgTopic(organizationId, token),
452
+ // Database Broadcast is authorized by RLS on realtime.messages; the manager awaits setAuth() first.
453
+ private: true,
454
+ // The sender is Postgres: no Matrx envelope. Our own write is known by the op id it carried.
455
+ wire: { mode: "raw", isOwnMessage: (payload) => isOwnOp(payload?.op_id) },
456
+ eventKey: (_source, payload) => entityNoticeKey(payload ?? {}),
457
+ broadcast: [
458
+ {
459
+ event: ENTITY_NOTICE_EVENT,
460
+ onMessage: (message) => {
461
+ const notice = message.data ?? {};
462
+ if (isOwnOp(notice.op_id)) return;
463
+ onNotice(notice);
464
+ }
465
+ }
466
+ ],
467
+ // Realtime has no replay: a channel back from a gap re-reads.
468
+ onBackfill: () => onBackfill()
469
+ };
470
+ }
471
+ function orderOrganizationTopics(input) {
472
+ const members = input.members ? new Set(input.members) : null;
473
+ const out = [];
474
+ const push = (org) => {
475
+ if (!org || out.includes(org)) return;
476
+ if (members && !members.has(org)) return;
477
+ out.push(org);
478
+ };
479
+ push(input.active);
480
+ push(input.filter);
481
+ const held = [...(input.held ?? /* @__PURE__ */ new Map()).entries()].sort((a, b) => b[1] - a[1] || (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0));
482
+ for (const [org] of held) push(org);
483
+ for (const org of [...input.members ?? []].sort()) push(org);
484
+ return out.slice(0, Math.max(0, input.max));
485
+ }
486
+ function createEntityRealtime(options = {}) {
487
+ const batchMs = options.batchMs ?? DEFAULT_BATCH_MS;
488
+ const maxWaitMs = options.maxWaitMs ?? DEFAULT_MAX_WAIT_MS;
489
+ const channels = /* @__PURE__ */ new Map();
490
+ const open = (organizationId, token) => {
491
+ const channel = { listeners: /* @__PURE__ */ new Set(), stop: null };
492
+ const spec = entityOrgChannelSpec(
493
+ organizationId,
494
+ token,
495
+ (notice) => {
496
+ for (const listener of channel.listeners) listener.notice(notice);
497
+ },
498
+ () => {
499
+ for (const listener of channel.listeners) listener.backfill();
500
+ }
501
+ );
502
+ if (options.manager) {
503
+ const handle = options.manager.open(spec);
504
+ channel.stop = () => handle.close();
505
+ } else {
506
+ channel.stop = (0, import_realtime.subscribeToRealtimeManager)(() => spec);
507
+ }
508
+ return channel;
509
+ };
510
+ return {
511
+ subscribe({ token, organizationIds }, sink) {
512
+ let timer = null;
513
+ let firstAt = 0;
514
+ let pending = /* @__PURE__ */ new Set();
515
+ let wholeList = false;
516
+ const flush = () => {
517
+ if (timer !== null) clearTimeout(timer);
518
+ timer = null;
519
+ const ids = [...pending];
520
+ const all = wholeList;
521
+ pending = /* @__PURE__ */ new Set();
522
+ wholeList = false;
523
+ if (all) sink.rows(null);
524
+ else if (ids.length > 0) sink.rows(ids);
525
+ };
526
+ const nudge = () => {
527
+ const now = Date.now();
528
+ if (timer === null) firstAt = now;
529
+ else clearTimeout(timer);
530
+ const wait = Math.max(0, Math.min(batchMs, firstAt + maxWaitMs - now));
531
+ timer = setTimeout(flush, wait);
532
+ };
533
+ const listener = {
534
+ notice(notice) {
535
+ if (notice.entity_type && notice.entity_type !== token) return;
536
+ if (notice.ids == null) wholeList = true;
537
+ else for (const id of notice.ids) pending.add(id);
538
+ nudge();
539
+ },
540
+ backfill() {
541
+ wholeList = true;
542
+ flush();
543
+ }
544
+ };
545
+ const keys = [...new Set(organizationIds)].map((org) => {
546
+ const key = entityOrgTopic(org, token);
547
+ const channel = channels.get(key) ?? open(org, token);
548
+ channels.set(key, channel);
549
+ channel.listeners.add(listener);
550
+ return key;
551
+ });
552
+ return () => {
553
+ if (timer !== null) clearTimeout(timer);
554
+ for (const key of keys) {
555
+ const channel = channels.get(key);
556
+ if (!channel) continue;
557
+ channel.listeners.delete(listener);
558
+ if (channel.listeners.size > 0) continue;
559
+ channel.stop?.();
560
+ channels.delete(key);
561
+ }
562
+ };
563
+ }
564
+ };
565
+ }
566
+
567
+ // src/entity/port/port.ts
568
+ var import_uuid = require("@ai-matrx/kit/uuid");
569
+
570
+ // src/entity/core/wire.ts
571
+ var IDENTITY = {
572
+ _id: "id",
573
+ _version: "version",
574
+ _organization_id: "organization_id"
575
+ };
576
+ var CUSTOM_PREFIX = "_custom.";
577
+ function wireColumn(column, customKeys) {
578
+ const identity = IDENTITY[column];
579
+ if (identity) return identity;
580
+ if (column.startsWith(CUSTOM_PREFIX)) return column;
581
+ return customKeys.has(column) ? `${CUSTOM_PREFIX}${column}` : column;
582
+ }
583
+ function predicate(clause, customKeys) {
584
+ const column = wireColumn(clause.column, customKeys);
585
+ const v = clause.value;
586
+ switch (clause.op) {
587
+ case "eq":
588
+ return v === null || v === void 0 ? { column, op: "empty" } : { column, op: "eq", value: v };
589
+ case "neq":
590
+ return v === null || v === void 0 ? { column, op: "empty", negated: true } : { column, op: "neq", value: v };
591
+ case "lt":
592
+ case "lte":
593
+ case "gt":
594
+ case "gte":
595
+ return { column, op: clause.op, value: v };
596
+ case "in":
597
+ return { column, op: "in", values: Array.isArray(v) ? v : [] };
598
+ case "empty":
599
+ return v === false ? { column, op: "empty", negated: true } : { column, op: "empty" };
600
+ case "contains":
601
+ return { column, op: "contains", value: v };
602
+ default:
603
+ return { column, op: String(clause.op), value: v };
604
+ }
605
+ }
606
+ function toWire(query, customKeys = /* @__PURE__ */ new Set()) {
607
+ const q = query ?? {};
608
+ const out = {};
609
+ const scope = {};
610
+ if (q.lane) scope.kind = q.lane;
611
+ if (q.org && q.org !== "all") scope.organization_id = q.org;
612
+ if (scope.kind || scope.organization_id) out.scope = scope;
613
+ if (q.where && q.where.length > 0) out.filter = { all: q.where.map((c) => predicate(c, customKeys)) };
614
+ const search = q.search?.trim();
615
+ if (search) out.search = search;
616
+ if (q.sort && q.sort.length > 0) {
617
+ out.sort = q.sort.map((s) => ({
618
+ column: wireColumn(s.column, customKeys),
619
+ dir: s.direction === "desc" ? "desc" : "asc",
620
+ nulls: "last"
621
+ }));
622
+ }
623
+ if (typeof q.pageSize === "number" && Number.isFinite(q.pageSize)) out.pageSize = Math.max(1, Math.floor(q.pageSize));
624
+ return out;
625
+ }
626
+
627
+ // src/entity/port/rows.ts
628
+ var IDENTITY_COLUMNS = /* @__PURE__ */ new Set(["id", "version", "organization_id"]);
629
+ var PRESENTATION_KEYS = /* @__PURE__ */ new Set(["_state", "_k"]);
630
+ function isRefList(value) {
631
+ return Array.isArray(value) && value.length > 0 && value.every((item) => item !== null && typeof item === "object" && typeof item.token === "string" && typeof item.id === "string");
632
+ }
633
+ function toRefs(value) {
634
+ if (!isRefList(value)) return [];
635
+ return value.map((item) => ({ token: item.token, id: item.id, label: typeof item.label === "string" ? item.label : null }));
636
+ }
637
+ function toRow(wire) {
638
+ const row = {
639
+ _id: wire.id,
640
+ _version: wire.version,
641
+ _organization_id: wire.organization_id,
642
+ _custom: wire._custom && typeof wire._custom === "object" ? { ...wire._custom } : {}
643
+ };
644
+ for (const [key, value] of Object.entries(wire)) {
645
+ if (IDENTITY_COLUMNS.has(key) || PRESENTATION_KEYS.has(key) || key === "_custom") continue;
646
+ row[key] = isRefList(value) ? toRefs(value) : value === void 0 ? null : value;
647
+ }
648
+ return row;
649
+ }
650
+ function toColumn(wire) {
651
+ const column = {
652
+ key: wire.key,
653
+ label: wire.label || wire.key,
654
+ type: wire.type,
655
+ origin: wire.origin === "custom" ? "custom" : "platform",
656
+ required: Boolean(wire.required),
657
+ readOnly: Boolean(wire.readOnly)
658
+ };
659
+ if (wire.choices && wire.choices.length > 0) column.choices = [...wire.choices];
660
+ if (wire.linksTo && wire.linksTo.length > 0) column.linksTo = [...wire.linksTo];
661
+ return column;
662
+ }
663
+ function toColumns(wire) {
664
+ return wire.filter((c) => !IDENTITY_COLUMNS.has(c.key)).map(toColumn);
665
+ }
666
+
667
+ // src/entity/port/port.ts
668
+ function toWireValue(value) {
669
+ if (isRef(value)) return value.id;
670
+ if (Array.isArray(value) && value.length > 0 && value.every(isRef)) {
671
+ return value.length === 1 ? value[0].id : value.map((ref) => ref.id);
672
+ }
673
+ return value;
674
+ }
675
+ function isRef(value) {
676
+ return value !== null && typeof value === "object" && !Array.isArray(value) && typeof value.token === "string" && typeof value.id === "string";
677
+ }
678
+ function toWireValues(values) {
679
+ const out = {};
680
+ for (const [key, value] of Object.entries(values)) {
681
+ if (value === void 0) continue;
682
+ if (key === "_custom" && value && typeof value === "object" && !Array.isArray(value)) {
683
+ out._custom = Object.fromEntries(Object.entries(value).filter(([, v]) => v !== void 0).map(([k, v]) => [k, toWireValue(v)]));
684
+ continue;
685
+ }
686
+ out[key] = Array.isArray(value) && value.length === 0 && key !== "_custom" ? null : toWireValue(value);
687
+ }
688
+ return out;
689
+ }
690
+ function createEntityDataPort(options) {
691
+ const client = options.client ?? createEntityClient({ dataSource: options.dataSource });
692
+ const activeOrganization = () => (options.activeOrganizationId ? options.activeOrganizationId() : options.organization) ?? null;
693
+ const gets = /* @__PURE__ */ new Map();
694
+ const aliases = options.aliases;
695
+ const shapes = /* @__PURE__ */ new Map();
696
+ let warnedNotLive = false;
697
+ const held = /* @__PURE__ */ new Map();
698
+ const replans = /* @__PURE__ */ new Map();
699
+ let memberships = null;
700
+ let maxOrgTopics = null;
701
+ function hold(alias, rows) {
702
+ const byId = held.get(alias) ?? /* @__PURE__ */ new Map();
703
+ held.set(alias, byId);
704
+ const before = new Set(byId.values());
705
+ for (const r of rows) {
706
+ if (typeof r.id === "string" && typeof r.organization_id === "string") byId.set(r.id, r.organization_id);
707
+ }
708
+ if ([...byId.values()].some((org) => !before.has(org))) for (const replan of replans.get(alias) ?? []) replan();
709
+ }
710
+ function heldCounts(alias) {
711
+ const counts = /* @__PURE__ */ new Map();
712
+ for (const org of held.get(alias)?.values() ?? []) counts.set(org, (counts.get(org) ?? 0) + 1);
713
+ return counts;
714
+ }
715
+ function membershipsOnce() {
716
+ memberships ??= client.memberOrganizations().then((answer) => {
717
+ if (answer.ok) return answer.data;
718
+ memberships = null;
719
+ console.warn(`[@ai-matrx/records/entity] Could not read your organizations for live updates: ${answer.error.message}`);
720
+ return null;
721
+ });
722
+ return memberships;
723
+ }
724
+ function maxOrgTopicsOnce() {
725
+ maxOrgTopics ??= client.knob({ key: "realtime_max_org_topics", organizationId: activeOrganization() }).then((answer) => {
726
+ const value = answer.ok ? Number(answer.data) : Number.NaN;
727
+ if (Number.isFinite(value) && value >= 0) return value;
728
+ console.warn(`[@ai-matrx/records/entity] knob entity_data.realtime_max_org_topics did not resolve; joining at most ${DEFAULT_MAX_ORG_TOPICS} organizations.`);
729
+ return DEFAULT_MAX_ORG_TOPICS;
730
+ });
731
+ return maxOrgTopics;
732
+ }
733
+ const tokenBindings = /* @__PURE__ */ new Map();
734
+ function bindingOf(alias) {
735
+ if (Object.prototype.hasOwnProperty.call(aliases, alias)) return aliases[alias];
736
+ if (!options.tokensAreAliases || !/^[a-z][a-z0-9_]*$/.test(alias)) return null;
737
+ let bound = tokenBindings.get(alias);
738
+ if (!bound) {
739
+ bound = { kind: "entity", token: alias };
740
+ tokenBindings.set(alias, bound);
741
+ }
742
+ return bound;
743
+ }
744
+ function noSuchAlias(alias) {
745
+ return refusal("not_found", `This Applet has no data source called "${alias}".`, "Use one of the aliases sources() lists.");
746
+ }
747
+ function shapeOf(alias, binding) {
748
+ let held2 = shapes.get(alias);
749
+ if (!held2) {
750
+ held2 = client.columns({ token: binding.token, organizationId: binding.organizationId ?? null }).then((answer) => {
751
+ if (!answer.ok) shapes.delete(alias);
752
+ return answer;
753
+ });
754
+ shapes.set(alias, held2);
755
+ }
756
+ return held2;
757
+ }
758
+ function getCoalesced(alias, token, id) {
759
+ return new Promise((resolve) => {
760
+ let batch = gets.get(alias);
761
+ if (!batch) {
762
+ batch = /* @__PURE__ */ new Map();
763
+ gets.set(alias, batch);
764
+ const asked = batch;
765
+ queueMicrotask(() => {
766
+ gets.delete(alias);
767
+ void flushGets(alias, token, asked);
768
+ });
769
+ }
770
+ const waiting = batch.get(id) ?? [];
771
+ waiting.push(resolve);
772
+ batch.set(id, waiting);
773
+ });
774
+ }
775
+ async function flushGets(alias, token, asked) {
776
+ const answer = await client.get({ token, ids: [...asked.keys()] });
777
+ if (answer.ok) hold(alias, answer.data.rows);
778
+ const byId = new Map(answer.ok ? answer.data.rows.map((r) => [r.id, r]) : []);
779
+ for (const [id, waiting] of asked) {
780
+ const found = byId.get(id);
781
+ const wire = found && found["_state"]?.archived === true ? void 0 : found;
782
+ const result = answer.ok ? { ok: true, data: wire ? toRow(wire) : null } : answer;
783
+ for (const resolve of waiting) resolve(result);
784
+ }
785
+ }
786
+ function readBack(alias, answer, what) {
787
+ if (!answer.ok) return answer;
788
+ hold(alias, answer.data.rows);
789
+ const wire = answer.data.rows[0];
790
+ if (!wire) {
791
+ return refusal(
792
+ "not_found",
793
+ `${what} was saved, but you cannot open it now, so it is not shown here.`,
794
+ "The row is in the store; who may open it decides whether it appears in this list."
795
+ );
796
+ }
797
+ return { ok: true, data: toRow(wire) };
798
+ }
799
+ function done(answer) {
800
+ return answer.ok ? { ok: true, data: void 0 } : answer;
801
+ }
802
+ return {
803
+ async sources() {
804
+ const out = [];
805
+ for (const [alias, binding] of Object.entries(aliases)) {
806
+ const shape = await shapeOf(alias, binding);
807
+ if (!shape.ok) return shape;
808
+ out.push({
809
+ alias,
810
+ label: binding.label ?? shape.data.label ?? alias,
811
+ kind: "entity",
812
+ canCreate: Boolean(shape.data.can_create),
813
+ canUpdate: Boolean(shape.data.can_update)
814
+ });
815
+ }
816
+ return { ok: true, data: out };
817
+ },
818
+ async describe(alias) {
819
+ const binding = bindingOf(alias);
820
+ if (!binding) return noSuchAlias(alias);
821
+ const shape = await shapeOf(alias, binding);
822
+ if (!shape.ok) return shape;
823
+ return {
824
+ ok: true,
825
+ data: {
826
+ token: binding.token,
827
+ label: binding.label ?? shape.data.label ?? binding.token,
828
+ canCreate: Boolean(shape.data.can_create),
829
+ canUpdate: Boolean(shape.data.can_update)
830
+ }
831
+ };
832
+ },
833
+ async columns(alias) {
834
+ const binding = bindingOf(alias);
835
+ if (!binding) return noSuchAlias(alias);
836
+ const shape = await shapeOf(alias, binding);
837
+ return shape.ok ? { ok: true, data: toColumns(shape.data.columns) } : shape;
838
+ },
839
+ async list(alias, q = {}, cursor) {
840
+ const binding = bindingOf(alias);
841
+ if (!binding) return noSuchAlias(alias);
842
+ let customKeys = /* @__PURE__ */ new Set();
843
+ if (q.where && q.where.length > 0 || q.sort && q.sort.length > 0) {
844
+ const shape = await shapeOf(alias, binding);
845
+ if (!shape.ok) return shape;
846
+ customKeys = new Set(shape.data.columns.filter((c) => c.origin === "custom").map((c) => c.key));
847
+ }
848
+ const wire = toWire(q, customKeys);
849
+ if (binding.organizationId) wire.scope = { ...wire.scope, organization_id: binding.organizationId };
850
+ const answer = await client.list({ token: binding.token, ...wire, after: cursor ?? null });
851
+ if (!answer.ok) return answer;
852
+ const { data } = answer;
853
+ if (data.sort_ignored.length > 0) {
854
+ console.warn(`[@ai-matrx/records/entity] "${alias}" sorts by one column for now; ignored: ${data.sort_ignored.join(", ")}.`);
855
+ }
856
+ hold(alias, data.rows);
857
+ const page = {
858
+ rows: data.rows.map(toRow),
859
+ // A count that stopped at the store's cap is a floor, not a count: unknown beats a wrong number.
860
+ total: data.total === null || data.total === void 0 || data.total_is_capped ? null : data.total,
861
+ cursor: data.has_more ? data.next_after : null
862
+ };
863
+ return { ok: true, data: page };
864
+ },
865
+ async get(alias, id) {
866
+ const binding = bindingOf(alias);
867
+ if (!binding) return noSuchAlias(alias);
868
+ if (!(0, import_uuid.isUuidShape)(id)) return refusal("invalid_argument", `"${String(id)}" is not a row id.`);
869
+ return getCoalesced(alias, binding.token, id);
870
+ },
871
+ async create(alias, values) {
872
+ const binding = bindingOf(alias);
873
+ if (!binding) return noSuchAlias(alias);
874
+ const organizationId = binding.organizationId ?? activeOrganization();
875
+ if (!organizationId) {
876
+ return refusal(
877
+ "organization_required",
878
+ "Choose the organization this new record belongs to.",
879
+ "Pick an organization, then add the row again. Nothing was saved."
880
+ );
881
+ }
882
+ const answer = await client.insert({ token: binding.token, organizationId, rows: [toWireValues(values)], opId: mintOpId() });
883
+ return readBack(alias, answer, "The new row");
884
+ },
885
+ async update(alias, id, patch, baseVersion) {
886
+ const binding = bindingOf(alias);
887
+ if (!binding) return noSuchAlias(alias);
888
+ if (!(0, import_uuid.isUuidShape)(id)) return refusal("invalid_argument", `"${String(id)}" is not a row id.`);
889
+ const answer = await client.update({ token: binding.token, id, baseVersion, patch: toWireValues(patch), opId: mintOpId() });
890
+ return readBack(alias, answer, "The change");
891
+ },
892
+ async archive(alias, id) {
893
+ const binding = bindingOf(alias);
894
+ if (!binding) return noSuchAlias(alias);
895
+ if (!(0, import_uuid.isUuidShape)(id)) return refusal("invalid_argument", `"${String(id)}" is not a row id.`);
896
+ return done(await client.archive({ token: binding.token, ids: [id], opId: mintOpId() }));
897
+ },
898
+ async restore(alias, id) {
899
+ const binding = bindingOf(alias);
900
+ if (!binding) return noSuchAlias(alias);
901
+ if (!(0, import_uuid.isUuidShape)(id)) return refusal("invalid_argument", `"${String(id)}" is not a row id.`);
902
+ return done(await client.unarchive({ token: binding.token, ids: [id], opId: mintOpId() }));
903
+ },
904
+ subscribe(event, listener) {
905
+ const alias = event.startsWith("rows:") ? event.slice(5) : null;
906
+ const binding = alias ? bindingOf(alias) : null;
907
+ if (!alias || !binding) {
908
+ throw new Error(`DataPort.subscribe: "${event}" is not an event this Applet's port carries. Use rows:<alias>.`);
909
+ }
910
+ const realtime = options.realtime;
911
+ if (!realtime) {
912
+ if (!warnedNotLive) {
913
+ warnedNotLive = true;
914
+ console.warn("[@ai-matrx/records/entity] This host bound no realtime layer, so other people's changes appear on the next read.");
915
+ }
916
+ return () => {
917
+ };
918
+ }
919
+ let stopped = false;
920
+ let joined = [];
921
+ let stopLive = null;
922
+ const sink = { rows: (ids) => listener({ ids: ids === null ? null : [...ids] }) };
923
+ const plan = async () => {
924
+ const organizationIds = binding.organizationId ? [binding.organizationId] : orderOrganizationTopics({
925
+ active: activeOrganization(),
926
+ held: heldCounts(alias),
927
+ members: await membershipsOnce(),
928
+ max: await maxOrgTopicsOnce()
929
+ });
930
+ if (stopped) return;
931
+ if (organizationIds.length === joined.length && organizationIds.every((org, i) => org === joined[i])) return;
932
+ const previous = stopLive;
933
+ joined = organizationIds;
934
+ stopLive = realtime.subscribe({ token: binding.token, organizationIds }, sink);
935
+ previous?.();
936
+ };
937
+ const replan = () => void plan();
938
+ const set = replans.get(alias) ?? /* @__PURE__ */ new Set();
939
+ replans.set(alias, set);
940
+ set.add(replan);
941
+ replan();
942
+ return () => {
943
+ stopped = true;
944
+ set.delete(replan);
945
+ stopLive?.();
946
+ stopLive = null;
947
+ };
948
+ }
949
+ };
950
+ }
951
+ function createSeedPort(dataSource) {
952
+ return createEntityDataPort({ client: createEntityClient({ dataSource }), aliases: {}, tokensAreAliases: true, activeOrganizationId: () => null });
953
+ }
954
+
955
+ // src/entity/react/index.tsx
956
+ var ENTITY_PORT_KEY = "entity-data";
957
+ var EntityScopeContext = (0, import_react.createContext)(null);
958
+ function EntityDataProvider({ dataSource, client, activeOrganizationId, realtime, portKey = ENTITY_PORT_KEY, children }) {
959
+ const active = (0, import_react.useRef)(activeOrganizationId);
960
+ active.current = activeOrganizationId;
961
+ const scope = (0, import_react.useMemo)(() => {
962
+ const bound = client ?? createEntityClient({ dataSource });
963
+ const live = realtime === false ? void 0 : realtime ?? createEntityRealtime();
964
+ const port = createEntityDataPort({
965
+ client: bound,
966
+ aliases: {},
967
+ tokensAreAliases: true,
968
+ activeOrganizationId: () => active.current,
969
+ ...live ? { realtime: live } : {}
970
+ });
971
+ return { port, portKey, events: (0, import_react2.hostEvents)(port) };
972
+ }, [client, dataSource, realtime, portKey]);
973
+ const above = (0, import_react.useContext)(import_react_query.QueryClientContext);
974
+ const [serverClient] = (0, import_react.useState)(() => typeof window === "undefined" && !above ? new import_react_query.QueryClient() : null);
975
+ const inner = (0, import_react.createElement)(EntityScopeContext.Provider, { value: scope }, children);
976
+ return serverClient ? (0, import_react.createElement)(import_react_query.QueryClientProvider, { client: serverClient }, inner) : inner;
977
+ }
978
+ function useOptionalEntityScope() {
979
+ return (0, import_react.useContext)(EntityScopeContext);
980
+ }
981
+ function useEntityScope() {
982
+ const scope = (0, import_react.useContext)(EntityScopeContext);
983
+ if (!scope) {
984
+ throw new Error(
985
+ "An entity hook was called outside <EntityDataProvider>. Mount it once, high, with the supabase client that carries this person's session and the active organization."
986
+ );
987
+ }
988
+ return scope;
989
+ }
990
+ function useEntityList(token, query = {}) {
991
+ const seeded = (0, import_seed.useSeededSource)(`entity:${token}:rows:${JSON.stringify(query)}`, (source) => createSeedPort(source).list(token, query));
992
+ return (0, import_react2.useDataRows)(token, query, useEntityScope(), seeded);
993
+ }
994
+ function useEntityColumns(token) {
995
+ const seeded = (0, import_seed.useSeededSource)(`entity:${token}:columns`, (source) => createSeedPort(source).columns(token));
996
+ return (0, import_react2.useDataColumns)(token, useEntityScope(), seeded);
997
+ }
998
+ function useEntityType(token) {
999
+ const scope = useEntityScope();
1000
+ const qc = (0, import_react2.useRecordsQueryClient)();
1001
+ const seeded = (0, import_seed.useSeededSource)(`entity:${token}:type`, (source) => createSeedPort(source).describe(token));
1002
+ const q = (0, import_react_query.useQuery)(
1003
+ {
1004
+ ...seeded ? { initialData: seeded.data, initialDataUpdatedAt: seeded.at } : {},
1005
+ queryKey: [...import_react2.dataKeys.alias(scope.portKey, token), "type"],
1006
+ queryFn: async () => {
1007
+ const answer = await scope.port.describe(token);
1008
+ if (!answer.ok) throw Object.assign(new Error(answer.error.message), { error: answer.error });
1009
+ return answer.data;
1010
+ },
1011
+ retry: false
1012
+ },
1013
+ qc
1014
+ );
1015
+ const failed = q.error;
1016
+ return { data: q.data, loading: q.isPending, error: failed ? failed.error ?? { code: "internal", message: failed.message } : null, refresh: () => void q.refetch() };
1017
+ }
1018
+ function useEntityRow(token, id) {
1019
+ return (0, import_react2.useDataRow)(token, id, useEntityScope());
1020
+ }
1021
+ function useEntityWrite(token) {
1022
+ return (0, import_react2.useDataMutations)(token, useEntityScope());
1023
+ }
1024
+ //# sourceMappingURL=react.cjs.map