@ai-matrx/entity-data 0.3.44 → 0.4.1

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.
package/dist/index.cjs CHANGED
@@ -3,10 +3,6 @@ var __defProp = Object.defineProperty;
3
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
5
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __export = (target, all) => {
7
- for (var name in all)
8
- __defProp(target, name, { get: all[name], enumerable: true });
9
- };
10
6
  var __copyProps = (to, from, except, desc) => {
11
7
  if (from && typeof from === "object" || typeof from === "function") {
12
8
  for (let key of __getOwnPropNames(from))
@@ -15,721 +11,11 @@ var __copyProps = (to, from, except, desc) => {
15
11
  }
16
12
  return to;
17
13
  };
14
+ var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
18
15
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
16
 
20
17
  // src/index.ts
21
18
  var src_exports = {};
22
- __export(src_exports, {
23
- CUSTOM_PREFIX: () => CUSTOM_PREFIX,
24
- DEFAULT_BATCH_MS: () => DEFAULT_BATCH_MS,
25
- DEFAULT_MAX_ORG_TOPICS: () => DEFAULT_MAX_ORG_TOPICS,
26
- DEFAULT_MAX_WAIT_MS: () => DEFAULT_MAX_WAIT_MS,
27
- ENTITY_DOOR_SCHEMA: () => ENTITY_DOOR_SCHEMA,
28
- ENTITY_NOTICE_EVENT: () => ENTITY_NOTICE_EVENT,
29
- ENTITY_REFUSALS: () => ENTITY_REFUSALS,
30
- OP_ID_HEADER: () => OP_ID_HEADER,
31
- createEntityClient: () => createEntityClient,
32
- createEntityDataPort: () => createEntityDataPort,
33
- createEntityRealtime: () => createEntityRealtime,
34
- createSeedPort: () => createSeedPort,
35
- entityNoticeKey: () => entityNoticeKey,
36
- entityOrgChannel: () => entityOrgChannel,
37
- entityOrgChannelSpec: () => entityOrgChannelSpec,
38
- entityOrgTopic: () => entityOrgTopic,
39
- mapEntityError: () => mapEntityError,
40
- orderOrganizationTopics: () => orderOrganizationTopics,
41
- readEntityRealtimeKnobs: () => readEntityRealtimeKnobs,
42
- refusal: () => refusal,
43
- toColumn: () => toColumn,
44
- toColumns: () => toColumns,
45
- toRefs: () => toRefs,
46
- toRow: () => toRow,
47
- toWire: () => toWire,
48
- toWireValue: () => toWireValue,
49
- toWireValues: () => toWireValues,
50
- wireColumn: () => wireColumn
51
- });
52
19
  module.exports = __toCommonJS(src_exports);
53
-
54
- // src/core/client.ts
55
- var import_core2 = require("@ai-matrx/records/core");
56
-
57
- // src/core/errors.ts
58
- var import_core = require("@ai-matrx/records/core");
59
- var ENTITY_REFUSALS = {
60
- /** conflict_version: somebody else's write won. */
61
- MX001: "stale_write",
62
- /** organization_required: the call needs an organization and carried none. */
63
- MX002: "organization_required",
64
- /** forbidden. */
65
- MX003: "door",
66
- /** not_found (also: not visible to this person). */
67
- MX004: "not_found",
68
- /** validation_refused (detail.reason required / bad_value / ambiguous_word / constraint; `not_supported` is read apart below). */
69
- MX005: "invalid_argument",
70
- /** system_column: a write named a column the store owns. */
71
- MX008: "invalid_argument",
72
- /** unknown_identifier: a column that is not there or not usable here. */
73
- MX012: "invalid_argument",
74
- /** cursor_invalid: the page cursor belongs to another list; the engine restarts the page. */
75
- MX013: "invalid_argument",
76
- /** view_not_offered. */
77
- MX015: "invalid_argument",
78
- /** invalid_argument: a parameter the door refuses by name. */
79
- MX016: "invalid_argument"
80
- };
81
- function mapEntityError(error, where) {
82
- const mapped = (0, import_core.mapPgError)(error, where);
83
- const state = mapped.sqlstate;
84
- if (state && /^MX\d{3}$/.test(state)) {
85
- const code = ENTITY_REFUSALS[state];
86
- if (code) {
87
- const reason = mapped.detail?.reason;
88
- const known = { ...mapped, code: state === "MX005" && reason === "not_supported" ? "not_supported" : code };
89
- delete known.unmapped;
90
- delete known.diagnostic;
91
- return known;
92
- }
93
- }
94
- return mapped;
95
- }
96
- function refusal(code, message, hint) {
97
- return { ok: false, error: { code, message, ...hint ? { hint } : {} } };
98
- }
99
-
100
- // src/core/client.ts
101
- var OP_ID_HEADER = "x-matrx-op-id";
102
- var ENTITY_DOOR_SCHEMA = "platform";
103
- function createEntityClient(options) {
104
- const source = (0, import_core2.asRecordsDataSource)(options.dataSource ?? options.supabase);
105
- let warnedHeaderless = false;
106
- function withHeaders(pending, headers) {
107
- if (!headers) return;
108
- const builder = pending;
109
- if (typeof builder.setHeader === "function") {
110
- for (const [name, value] of Object.entries(headers)) builder.setHeader(name, value);
111
- return;
112
- }
113
- if (!warnedHeaderless) {
114
- warnedHeaderless = true;
115
- console.warn(
116
- `[@ai-matrx/entity-data] 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.`
117
- );
118
- }
119
- }
120
- async function call(fn, args, schema = ENTITY_DOOR_SCHEMA, headers) {
121
- try {
122
- const pending = source.rpc(fn, args, { schema });
123
- withHeaders(pending, headers);
124
- const answer = await pending;
125
- if (answer.error) return { ok: false, error: mapEntityError(answer.error, `${schema}.${fn}`) };
126
- return { ok: true, data: answer.data };
127
- } catch (thrown) {
128
- return {
129
- ok: false,
130
- error: mapEntityError({ code: "", message: thrown instanceof Error ? thrown.message : String(thrown) }, `${schema}.${fn}`)
131
- };
132
- }
133
- }
134
- return {
135
- list(request) {
136
- const args = {
137
- p_entity_type: request.token,
138
- p_scope: request.scope ?? null,
139
- p_filter: request.filter ?? {},
140
- p_search: request.search ?? null,
141
- p_sort: request.sort ?? null,
142
- p_archived: request.archived ?? "active",
143
- p_page_size: request.pageSize ?? null,
144
- p_after: request.after ?? null,
145
- p_with_total: request.withTotal ?? true
146
- };
147
- return call("entity_list_scoped", args);
148
- },
149
- get(request) {
150
- return call("entity_get", { p_entity_type: request.token, p_ids: [...request.ids] });
151
- },
152
- columns(request) {
153
- return call("entity_columns", { p_organization_id: request.organizationId ?? null, p_token: request.token });
154
- },
155
- insert(request) {
156
- return call(
157
- "entity_insert",
158
- { p_entity_type: request.token, p_organization_id: request.organizationId, p_rows: request.rows.map((r) => ({ ...r })) },
159
- ENTITY_DOOR_SCHEMA,
160
- opHeader(request.opId)
161
- );
162
- },
163
- update(request) {
164
- return call(
165
- "entity_update",
166
- { p_entity_type: request.token, p_id: request.id, p_base_version: request.baseVersion, p_patch: request.patch },
167
- ENTITY_DOOR_SCHEMA,
168
- opHeader(request.opId)
169
- );
170
- },
171
- archive(request) {
172
- return call("entity_archive", { p_entity_type: request.token, p_ids: [...request.ids] }, ENTITY_DOOR_SCHEMA, opHeader(request.opId));
173
- },
174
- unarchive(request) {
175
- return call("entity_unarchive", { p_entity_type: request.token, p_ids: [...request.ids] }, ENTITY_DOOR_SCHEMA, opHeader(request.opId));
176
- },
177
- knob(request) {
178
- return call("knob_resolve", { p_feature: "entity_data", p_key: request.key, p_organization_id: request.organizationId ?? null });
179
- },
180
- async memberOrganizations() {
181
- const answer = await call("my_orgs", {}, "iam");
182
- if (!answer.ok) return answer;
183
- const rows = Array.isArray(answer.data) ? answer.data : [];
184
- const ids = rows.map((row) => typeof row === "string" ? row : row && typeof row === "object" ? Object.values(row)[0] : null).filter((id) => typeof id === "string");
185
- return { ok: true, data: ids };
186
- }
187
- };
188
- }
189
- function opHeader(opId) {
190
- return opId ? { [OP_ID_HEADER]: opId } : void 0;
191
- }
192
-
193
- // src/core/wire.ts
194
- var IDENTITY = {
195
- _id: "id",
196
- _version: "version",
197
- _organization_id: "organization_id"
198
- };
199
- var CUSTOM_PREFIX = "_custom.";
200
- function wireColumn(column, customKeys) {
201
- const identity = IDENTITY[column];
202
- if (identity) return identity;
203
- if (column.startsWith(CUSTOM_PREFIX)) return column;
204
- return customKeys.has(column) ? `${CUSTOM_PREFIX}${column}` : column;
205
- }
206
- function predicate(clause, customKeys) {
207
- const column = wireColumn(clause.column, customKeys);
208
- const v = clause.value;
209
- switch (clause.op) {
210
- case "eq":
211
- return v === null || v === void 0 ? { column, op: "empty" } : { column, op: "eq", value: v };
212
- case "neq":
213
- return v === null || v === void 0 ? { column, op: "empty", negated: true } : { column, op: "neq", value: v };
214
- case "lt":
215
- case "lte":
216
- case "gt":
217
- case "gte":
218
- return { column, op: clause.op, value: v };
219
- case "in":
220
- return { column, op: "in", values: Array.isArray(v) ? v : [] };
221
- case "empty":
222
- return v === false ? { column, op: "empty", negated: true } : { column, op: "empty" };
223
- case "contains":
224
- return { column, op: "contains", value: v };
225
- default:
226
- return { column, op: String(clause.op), value: v };
227
- }
228
- }
229
- function toWire(query, customKeys = /* @__PURE__ */ new Set()) {
230
- const q = query ?? {};
231
- const out = {};
232
- const scope = {};
233
- if (q.lane) scope.kind = q.lane;
234
- if (q.org && q.org !== "all") scope.organization_id = q.org;
235
- if (scope.kind || scope.organization_id) out.scope = scope;
236
- if (q.where && q.where.length > 0) out.filter = { all: q.where.map((c) => predicate(c, customKeys)) };
237
- const search = q.search?.trim();
238
- if (search) out.search = search;
239
- if (q.sort && q.sort.length > 0) {
240
- out.sort = q.sort.map((s) => ({
241
- column: wireColumn(s.column, customKeys),
242
- dir: s.direction === "desc" ? "desc" : "asc",
243
- nulls: "last"
244
- }));
245
- }
246
- if (typeof q.pageSize === "number" && Number.isFinite(q.pageSize)) out.pageSize = Math.max(1, Math.floor(q.pageSize));
247
- return out;
248
- }
249
-
250
- // src/core/realtime.ts
251
- var import_realtime = require("@ai-matrx/realtime");
252
- var import_records = require("@ai-matrx/records");
253
- var entityOrgChannel = (0, import_realtime.defineChannelNamespace)({
254
- namespace: "entity-org",
255
- foreignTopic: "mx:entity-org",
256
- parts: ["organizationId", "token"],
257
- 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."
258
- });
259
- var ENTITY_NOTICE_EVENT = "entity_bump";
260
- var DEFAULT_BATCH_MS = 150;
261
- var DEFAULT_MAX_WAIT_MS = 1e3;
262
- var DEFAULT_MAX_ORG_TOPICS = 20;
263
- function entityNoticeKey(notice) {
264
- return notice.id ?? (notice.at ? `${notice.organization_id ?? ""}:${notice.entity_type ?? ""}:${notice.at}` : void 0);
265
- }
266
- function entityOrgTopic(organizationId, token) {
267
- return entityOrgChannel.topic({ organizationId, token });
268
- }
269
- function entityOrgChannelSpec(organizationId, token, onNotice, onBackfill) {
270
- return {
271
- topic: entityOrgTopic(organizationId, token),
272
- // Database Broadcast is authorized by RLS on realtime.messages; the manager awaits setAuth() first.
273
- private: true,
274
- // The sender is Postgres: no Matrx envelope. Our own write is known by the op id it carried.
275
- wire: { mode: "raw", isOwnMessage: (payload) => (0, import_records.isOwnOp)(payload?.op_id) },
276
- eventKey: (_source, payload) => entityNoticeKey(payload ?? {}),
277
- broadcast: [
278
- {
279
- event: ENTITY_NOTICE_EVENT,
280
- onMessage: (message) => {
281
- const notice = message.data ?? {};
282
- if ((0, import_records.isOwnOp)(notice.op_id)) return;
283
- onNotice(notice);
284
- }
285
- }
286
- ],
287
- // Realtime has no replay: a channel back from a gap re-reads.
288
- onBackfill: () => onBackfill()
289
- };
290
- }
291
- function orderOrganizationTopics(input) {
292
- const members = input.members ? new Set(input.members) : null;
293
- const out = [];
294
- const push = (org) => {
295
- if (!org || out.includes(org)) return;
296
- if (members && !members.has(org)) return;
297
- out.push(org);
298
- };
299
- push(input.active);
300
- push(input.filter);
301
- 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));
302
- for (const [org] of held) push(org);
303
- for (const org of [...input.members ?? []].sort()) push(org);
304
- return out.slice(0, Math.max(0, input.max));
305
- }
306
- function createEntityRealtime(options = {}) {
307
- const batchMs = options.batchMs ?? DEFAULT_BATCH_MS;
308
- const maxWaitMs = options.maxWaitMs ?? DEFAULT_MAX_WAIT_MS;
309
- const channels = /* @__PURE__ */ new Map();
310
- const open = (organizationId, token) => {
311
- const channel = { listeners: /* @__PURE__ */ new Set(), stop: null };
312
- const spec = entityOrgChannelSpec(
313
- organizationId,
314
- token,
315
- (notice) => {
316
- for (const listener of channel.listeners) listener.notice(notice);
317
- },
318
- () => {
319
- for (const listener of channel.listeners) listener.backfill();
320
- }
321
- );
322
- if (options.manager) {
323
- const handle = options.manager.open(spec);
324
- channel.stop = () => handle.close();
325
- } else {
326
- channel.stop = (0, import_realtime.subscribeToRealtimeManager)(() => spec);
327
- }
328
- return channel;
329
- };
330
- return {
331
- subscribe({ token, organizationIds }, sink) {
332
- let timer = null;
333
- let firstAt = 0;
334
- let pending = /* @__PURE__ */ new Set();
335
- let wholeList = false;
336
- const flush = () => {
337
- if (timer !== null) clearTimeout(timer);
338
- timer = null;
339
- const ids = [...pending];
340
- const all = wholeList;
341
- pending = /* @__PURE__ */ new Set();
342
- wholeList = false;
343
- if (all) sink.rows(null);
344
- else if (ids.length > 0) sink.rows(ids);
345
- };
346
- const nudge = () => {
347
- const now = Date.now();
348
- if (timer === null) firstAt = now;
349
- else clearTimeout(timer);
350
- const wait = Math.max(0, Math.min(batchMs, firstAt + maxWaitMs - now));
351
- timer = setTimeout(flush, wait);
352
- };
353
- const listener = {
354
- notice(notice) {
355
- if (notice.entity_type && notice.entity_type !== token) return;
356
- if (notice.ids == null) wholeList = true;
357
- else for (const id of notice.ids) pending.add(id);
358
- nudge();
359
- },
360
- backfill() {
361
- wholeList = true;
362
- flush();
363
- }
364
- };
365
- const keys = [...new Set(organizationIds)].map((org) => {
366
- const key = entityOrgTopic(org, token);
367
- const channel = channels.get(key) ?? open(org, token);
368
- channels.set(key, channel);
369
- channel.listeners.add(listener);
370
- return key;
371
- });
372
- return () => {
373
- if (timer !== null) clearTimeout(timer);
374
- for (const key of keys) {
375
- const channel = channels.get(key);
376
- if (!channel) continue;
377
- channel.listeners.delete(listener);
378
- if (channel.listeners.size > 0) continue;
379
- channel.stop?.();
380
- channels.delete(key);
381
- }
382
- };
383
- }
384
- };
385
- }
386
- async function readEntityRealtimeKnobs(client, organizationId) {
387
- const read = async (key, fallback) => {
388
- const answer = await client.knob({ key, organizationId: organizationId ?? null });
389
- const value = answer.ok ? Number(answer.data) : Number.NaN;
390
- if (!Number.isFinite(value)) {
391
- console.warn(`[@ai-matrx/entity-data] knob entity_data.${key} did not resolve; using its starting value ${fallback}.`);
392
- return fallback;
393
- }
394
- return value;
395
- };
396
- const [batchMs, maxWaitMs, maxOrgTopics] = await Promise.all([
397
- read("realtime_batch_ms", DEFAULT_BATCH_MS),
398
- read("realtime_max_wait_ms", DEFAULT_MAX_WAIT_MS),
399
- read("realtime_max_org_topics", DEFAULT_MAX_ORG_TOPICS)
400
- ]);
401
- return { batchMs, maxWaitMs, maxOrgTopics };
402
- }
403
-
404
- // src/port/port.ts
405
- var import_uuid = require("@ai-matrx/kit/uuid");
406
- var import_records2 = require("@ai-matrx/records");
407
-
408
- // src/port/rows.ts
409
- var IDENTITY_COLUMNS = /* @__PURE__ */ new Set(["id", "version", "organization_id"]);
410
- var PRESENTATION_KEYS = /* @__PURE__ */ new Set(["_state", "_k"]);
411
- function isRefList(value) {
412
- return Array.isArray(value) && value.length > 0 && value.every((item) => item !== null && typeof item === "object" && typeof item.token === "string" && typeof item.id === "string");
413
- }
414
- function toRefs(value) {
415
- if (!isRefList(value)) return [];
416
- return value.map((item) => ({ token: item.token, id: item.id, label: typeof item.label === "string" ? item.label : null }));
417
- }
418
- function toRow(wire) {
419
- const row = {
420
- _id: wire.id,
421
- _version: wire.version,
422
- _organization_id: wire.organization_id,
423
- _custom: wire._custom && typeof wire._custom === "object" ? { ...wire._custom } : {}
424
- };
425
- for (const [key, value] of Object.entries(wire)) {
426
- if (IDENTITY_COLUMNS.has(key) || PRESENTATION_KEYS.has(key) || key === "_custom") continue;
427
- row[key] = isRefList(value) ? toRefs(value) : value === void 0 ? null : value;
428
- }
429
- return row;
430
- }
431
- function toColumn(wire) {
432
- const column = {
433
- key: wire.key,
434
- label: wire.label || wire.key,
435
- type: wire.type,
436
- origin: wire.origin === "custom" ? "custom" : "platform",
437
- required: Boolean(wire.required),
438
- readOnly: Boolean(wire.readOnly)
439
- };
440
- if (wire.choices && wire.choices.length > 0) column.choices = [...wire.choices];
441
- if (wire.linksTo && wire.linksTo.length > 0) column.linksTo = [...wire.linksTo];
442
- return column;
443
- }
444
- function toColumns(wire) {
445
- return wire.filter((c) => !IDENTITY_COLUMNS.has(c.key)).map(toColumn);
446
- }
447
-
448
- // src/port/port.ts
449
- function toWireValue(value) {
450
- if (isRef(value)) return value.id;
451
- if (Array.isArray(value) && value.length > 0 && value.every(isRef)) {
452
- return value.length === 1 ? value[0].id : value.map((ref) => ref.id);
453
- }
454
- return value;
455
- }
456
- function isRef(value) {
457
- return value !== null && typeof value === "object" && !Array.isArray(value) && typeof value.token === "string" && typeof value.id === "string";
458
- }
459
- function toWireValues(values) {
460
- const out = {};
461
- for (const [key, value] of Object.entries(values)) {
462
- if (value === void 0) continue;
463
- if (key === "_custom" && value && typeof value === "object" && !Array.isArray(value)) {
464
- out._custom = Object.fromEntries(Object.entries(value).filter(([, v]) => v !== void 0).map(([k, v]) => [k, toWireValue(v)]));
465
- continue;
466
- }
467
- out[key] = Array.isArray(value) && value.length === 0 && key !== "_custom" ? null : toWireValue(value);
468
- }
469
- return out;
470
- }
471
- function createEntityDataPort(options) {
472
- const client = options.client ?? createEntityClient({ dataSource: options.dataSource });
473
- const activeOrganization = () => (options.activeOrganizationId ? options.activeOrganizationId() : options.organization) ?? null;
474
- const gets = /* @__PURE__ */ new Map();
475
- const aliases = options.aliases;
476
- const shapes = /* @__PURE__ */ new Map();
477
- let warnedNotLive = false;
478
- const held = /* @__PURE__ */ new Map();
479
- const replans = /* @__PURE__ */ new Map();
480
- let memberships = null;
481
- let maxOrgTopics = null;
482
- function hold(alias, rows) {
483
- const byId = held.get(alias) ?? /* @__PURE__ */ new Map();
484
- held.set(alias, byId);
485
- const before = new Set(byId.values());
486
- for (const r of rows) {
487
- if (typeof r.id === "string" && typeof r.organization_id === "string") byId.set(r.id, r.organization_id);
488
- }
489
- if ([...byId.values()].some((org) => !before.has(org))) for (const replan of replans.get(alias) ?? []) replan();
490
- }
491
- function heldCounts(alias) {
492
- const counts = /* @__PURE__ */ new Map();
493
- for (const org of held.get(alias)?.values() ?? []) counts.set(org, (counts.get(org) ?? 0) + 1);
494
- return counts;
495
- }
496
- function membershipsOnce() {
497
- memberships ??= client.memberOrganizations().then((answer) => {
498
- if (answer.ok) return answer.data;
499
- memberships = null;
500
- console.warn(`[@ai-matrx/entity-data] Could not read your organizations for live updates: ${answer.error.message}`);
501
- return null;
502
- });
503
- return memberships;
504
- }
505
- function maxOrgTopicsOnce() {
506
- maxOrgTopics ??= client.knob({ key: "realtime_max_org_topics", organizationId: activeOrganization() }).then((answer) => {
507
- const value = answer.ok ? Number(answer.data) : Number.NaN;
508
- if (Number.isFinite(value) && value >= 0) return value;
509
- console.warn(`[@ai-matrx/entity-data] knob entity_data.realtime_max_org_topics did not resolve; joining at most ${DEFAULT_MAX_ORG_TOPICS} organizations.`);
510
- return DEFAULT_MAX_ORG_TOPICS;
511
- });
512
- return maxOrgTopics;
513
- }
514
- const tokenBindings = /* @__PURE__ */ new Map();
515
- function bindingOf(alias) {
516
- if (Object.prototype.hasOwnProperty.call(aliases, alias)) return aliases[alias];
517
- if (!options.tokensAreAliases || !/^[a-z][a-z0-9_]*$/.test(alias)) return null;
518
- let bound = tokenBindings.get(alias);
519
- if (!bound) {
520
- bound = { kind: "entity", token: alias };
521
- tokenBindings.set(alias, bound);
522
- }
523
- return bound;
524
- }
525
- function noSuchAlias(alias) {
526
- return refusal("not_found", `This Applet has no data source called "${alias}".`, "Use one of the aliases sources() lists.");
527
- }
528
- function shapeOf(alias, binding) {
529
- let held2 = shapes.get(alias);
530
- if (!held2) {
531
- held2 = client.columns({ token: binding.token, organizationId: binding.organizationId ?? null }).then((answer) => {
532
- if (!answer.ok) shapes.delete(alias);
533
- return answer;
534
- });
535
- shapes.set(alias, held2);
536
- }
537
- return held2;
538
- }
539
- function getCoalesced(alias, token, id) {
540
- return new Promise((resolve) => {
541
- let batch = gets.get(alias);
542
- if (!batch) {
543
- batch = /* @__PURE__ */ new Map();
544
- gets.set(alias, batch);
545
- const asked = batch;
546
- queueMicrotask(() => {
547
- gets.delete(alias);
548
- void flushGets(alias, token, asked);
549
- });
550
- }
551
- const waiting = batch.get(id) ?? [];
552
- waiting.push(resolve);
553
- batch.set(id, waiting);
554
- });
555
- }
556
- async function flushGets(alias, token, asked) {
557
- const answer = await client.get({ token, ids: [...asked.keys()] });
558
- if (answer.ok) hold(alias, answer.data.rows);
559
- const byId = new Map(answer.ok ? answer.data.rows.map((r) => [r.id, r]) : []);
560
- for (const [id, waiting] of asked) {
561
- const found = byId.get(id);
562
- const wire = found && found["_state"]?.archived === true ? void 0 : found;
563
- const result = answer.ok ? { ok: true, data: wire ? toRow(wire) : null } : answer;
564
- for (const resolve of waiting) resolve(result);
565
- }
566
- }
567
- function readBack(alias, answer, what) {
568
- if (!answer.ok) return answer;
569
- hold(alias, answer.data.rows);
570
- const wire = answer.data.rows[0];
571
- if (!wire) {
572
- return refusal(
573
- "not_found",
574
- `${what} was saved, but you cannot open it now, so it is not shown here.`,
575
- "The row is in the store; who may open it decides whether it appears in this list."
576
- );
577
- }
578
- return { ok: true, data: toRow(wire) };
579
- }
580
- function done(answer) {
581
- return answer.ok ? { ok: true, data: void 0 } : answer;
582
- }
583
- return {
584
- async sources() {
585
- const out = [];
586
- for (const [alias, binding] of Object.entries(aliases)) {
587
- const shape = await shapeOf(alias, binding);
588
- if (!shape.ok) return shape;
589
- out.push({
590
- alias,
591
- label: binding.label ?? shape.data.label ?? alias,
592
- kind: "entity",
593
- canCreate: Boolean(shape.data.can_create),
594
- canUpdate: Boolean(shape.data.can_update)
595
- });
596
- }
597
- return { ok: true, data: out };
598
- },
599
- async describe(alias) {
600
- const binding = bindingOf(alias);
601
- if (!binding) return noSuchAlias(alias);
602
- const shape = await shapeOf(alias, binding);
603
- if (!shape.ok) return shape;
604
- return {
605
- ok: true,
606
- data: {
607
- token: binding.token,
608
- label: binding.label ?? shape.data.label ?? binding.token,
609
- canCreate: Boolean(shape.data.can_create),
610
- canUpdate: Boolean(shape.data.can_update)
611
- }
612
- };
613
- },
614
- async columns(alias) {
615
- const binding = bindingOf(alias);
616
- if (!binding) return noSuchAlias(alias);
617
- const shape = await shapeOf(alias, binding);
618
- return shape.ok ? { ok: true, data: toColumns(shape.data.columns) } : shape;
619
- },
620
- async list(alias, q = {}, cursor) {
621
- const binding = bindingOf(alias);
622
- if (!binding) return noSuchAlias(alias);
623
- let customKeys = /* @__PURE__ */ new Set();
624
- if (q.where && q.where.length > 0 || q.sort && q.sort.length > 0) {
625
- const shape = await shapeOf(alias, binding);
626
- if (!shape.ok) return shape;
627
- customKeys = new Set(shape.data.columns.filter((c) => c.origin === "custom").map((c) => c.key));
628
- }
629
- const wire = toWire(q, customKeys);
630
- if (binding.organizationId) wire.scope = { ...wire.scope, organization_id: binding.organizationId };
631
- const answer = await client.list({ token: binding.token, ...wire, after: cursor ?? null });
632
- if (!answer.ok) return answer;
633
- const { data } = answer;
634
- if (data.sort_ignored.length > 0) {
635
- console.warn(`[@ai-matrx/entity-data] "${alias}" sorts by one column for now; ignored: ${data.sort_ignored.join(", ")}.`);
636
- }
637
- hold(alias, data.rows);
638
- const page = {
639
- rows: data.rows.map(toRow),
640
- // A count that stopped at the store's cap is a floor, not a count: unknown beats a wrong number.
641
- total: data.total === null || data.total === void 0 || data.total_is_capped ? null : data.total,
642
- cursor: data.has_more ? data.next_after : null
643
- };
644
- return { ok: true, data: page };
645
- },
646
- async get(alias, id) {
647
- const binding = bindingOf(alias);
648
- if (!binding) return noSuchAlias(alias);
649
- if (!(0, import_uuid.isUuidShape)(id)) return refusal("invalid_argument", `"${String(id)}" is not a row id.`);
650
- return getCoalesced(alias, binding.token, id);
651
- },
652
- async create(alias, values) {
653
- const binding = bindingOf(alias);
654
- if (!binding) return noSuchAlias(alias);
655
- const organizationId = binding.organizationId ?? activeOrganization();
656
- if (!organizationId) {
657
- return refusal(
658
- "organization_required",
659
- "Choose the organization this new record belongs to.",
660
- "Pick an organization, then add the row again. Nothing was saved."
661
- );
662
- }
663
- const answer = await client.insert({ token: binding.token, organizationId, rows: [toWireValues(values)], opId: (0, import_records2.mintOpId)() });
664
- return readBack(alias, answer, "The new row");
665
- },
666
- async update(alias, id, patch, baseVersion) {
667
- const binding = bindingOf(alias);
668
- if (!binding) return noSuchAlias(alias);
669
- if (!(0, import_uuid.isUuidShape)(id)) return refusal("invalid_argument", `"${String(id)}" is not a row id.`);
670
- const answer = await client.update({ token: binding.token, id, baseVersion, patch: toWireValues(patch), opId: (0, import_records2.mintOpId)() });
671
- return readBack(alias, answer, "The change");
672
- },
673
- async archive(alias, id) {
674
- const binding = bindingOf(alias);
675
- if (!binding) return noSuchAlias(alias);
676
- if (!(0, import_uuid.isUuidShape)(id)) return refusal("invalid_argument", `"${String(id)}" is not a row id.`);
677
- return done(await client.archive({ token: binding.token, ids: [id], opId: (0, import_records2.mintOpId)() }));
678
- },
679
- async restore(alias, id) {
680
- const binding = bindingOf(alias);
681
- if (!binding) return noSuchAlias(alias);
682
- if (!(0, import_uuid.isUuidShape)(id)) return refusal("invalid_argument", `"${String(id)}" is not a row id.`);
683
- return done(await client.unarchive({ token: binding.token, ids: [id], opId: (0, import_records2.mintOpId)() }));
684
- },
685
- subscribe(event, listener) {
686
- const alias = event.startsWith("rows:") ? event.slice(5) : null;
687
- const binding = alias ? bindingOf(alias) : null;
688
- if (!alias || !binding) {
689
- throw new Error(`DataPort.subscribe: "${event}" is not an event this Applet's port carries. Use rows:<alias>.`);
690
- }
691
- const realtime = options.realtime;
692
- if (!realtime) {
693
- if (!warnedNotLive) {
694
- warnedNotLive = true;
695
- console.warn("[@ai-matrx/entity-data] This host bound no realtime layer, so other people's changes appear on the next read.");
696
- }
697
- return () => {
698
- };
699
- }
700
- let stopped = false;
701
- let joined = [];
702
- let stopLive = null;
703
- const sink = { rows: (ids) => listener({ ids: ids === null ? null : [...ids] }) };
704
- const plan = async () => {
705
- const organizationIds = binding.organizationId ? [binding.organizationId] : orderOrganizationTopics({
706
- active: activeOrganization(),
707
- held: heldCounts(alias),
708
- members: await membershipsOnce(),
709
- max: await maxOrgTopicsOnce()
710
- });
711
- if (stopped) return;
712
- if (organizationIds.length === joined.length && organizationIds.every((org, i) => org === joined[i])) return;
713
- const previous = stopLive;
714
- joined = organizationIds;
715
- stopLive = realtime.subscribe({ token: binding.token, organizationIds }, sink);
716
- previous?.();
717
- };
718
- const replan = () => void plan();
719
- const set = replans.get(alias) ?? /* @__PURE__ */ new Set();
720
- replans.set(alias, set);
721
- set.add(replan);
722
- replan();
723
- return () => {
724
- stopped = true;
725
- set.delete(replan);
726
- stopLive?.();
727
- stopLive = null;
728
- };
729
- }
730
- };
731
- }
732
- function createSeedPort(dataSource) {
733
- return createEntityDataPort({ client: createEntityClient({ dataSource }), aliases: {}, tokensAreAliases: true, activeOrganizationId: () => null });
734
- }
20
+ __reExport(src_exports, require("@ai-matrx/records/entity"), module.exports);
735
21
  //# sourceMappingURL=index.cjs.map