@ai-matrx/records 0.86.2 → 0.87.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.
@@ -15285,40 +15285,6 @@ interface RecordsSeed {
15285
15285
  answers: RecordsSeedAnswer[];
15286
15286
  }
15287
15287
 
15288
- /**
15289
- * THE CLIENT OPERATION LEDGER — how a browser recognises the echo of its own write.
15290
- *
15291
- * Every private-channel notice the record store broadcasts reaches EVERY browser watching that
15292
- * Table, including the one that caused it. Until now the writer had no way to tell its own
15293
- * change from a colleague's: the notice carried ids and nothing about who moved them, so
15294
- * `recordsRealtimePort.ts` declared `acceptEchoFromSelf: true` and the writer re-read a page it
15295
- * had already updated. It was named honestly rather than faked, and this is the other half.
15296
- *
15297
- * THE SHAPE. Every write through `RecordsClient` mints a uuid, sends it to the store as the
15298
- * platform envelope key `_op_id`, and remembers it here. The store lifts that key out before
15299
- * storage — it is never written onto the record — carries it on the change-feed row, and puts
15300
- * it in the notice as `op_id`. A notice whose `op_id` is in this ledger is this browser's own
15301
- * echo and is dropped.
15302
- *
15303
- * WHY A TIME WINDOW RATHER THAN A DELETE-ON-MATCH. One write can produce MORE THAN ONE notice:
15304
- * a record write that also moves a column produces a `record` notice and a `field` notice, and
15305
- * a statement touching two Tables produces one per Table. Forgetting an op the first time it
15306
- * came back would make the second notice look like somebody else's and cost the re-read this
15307
- * exists to avoid. So an op is remembered for a window and expires on its own.
15308
- *
15309
- * 🚨 THE WINDOW IS A CORRECTNESS BOUNDARY, NOT A TUNING KNOB, AND IT FAILS IN THE SAFE
15310
- * DIRECTION BY CONSTRUCTION. Too SHORT and a late echo is treated as a stranger's change: one
15311
- * extra read of a page that is already correct — invisible. Too LONG and a stranger's change
15312
- * that happened to reuse our uuid would be dropped — which cannot happen, because it is a v4
15313
- * uuid nobody else has ever seen. The only real cost of a long window is memory, so it is
15314
- * bounded by COUNT as well as by time.
15315
- *
15316
- * WHAT THIS IS NOT. It is not a replacement for the store's optimistic concurrency, and it
15317
- * never decides what is true — a dropped echo means "I already know about this", and every
15318
- * path that could be wrong about that (a reconnect, a tab wake, a notice with no `op_id` at
15319
- * all) re-reads through the door as it always did.
15320
- */
15321
-
15322
15288
  /**
15323
15289
  * Mint the id for one client operation and remember it as ours.
15324
15290
  *
@@ -15997,9 +15963,9 @@ declare const f: {
15997
15963
  member: <const O extends FieldOptions = {}>(opts?: O) => AppField<string, Req<O>, false, Uniq<O>>;
15998
15964
  /** A file. */
15999
15965
  attachment: <const O extends FieldOptions = {}>(opts?: O) => AppField<string, Req<O>, false, Uniq<O>>;
16000
- /** A platform thing (`note`, `agent` …) of one of `kinds`. */
15966
+ /** A platform thing (`note`, `agent` …) of one of `kinds`: `{ token, id }`, the store's own shape. */
16001
15967
  entityReference: <const O extends FieldOptions = {}>(kinds: readonly string[], opts?: O) => AppField<{
16002
- type: string;
15968
+ token: string;
16003
15969
  id: Uuid;
16004
15970
  }, Req<O>, false, Uniq<O>>;
16005
15971
  /** Computed by the store from `formulaText` (`{Visit fee} - {Deposit taken}`); read-only. */
@@ -15285,40 +15285,6 @@ interface RecordsSeed {
15285
15285
  answers: RecordsSeedAnswer[];
15286
15286
  }
15287
15287
 
15288
- /**
15289
- * THE CLIENT OPERATION LEDGER — how a browser recognises the echo of its own write.
15290
- *
15291
- * Every private-channel notice the record store broadcasts reaches EVERY browser watching that
15292
- * Table, including the one that caused it. Until now the writer had no way to tell its own
15293
- * change from a colleague's: the notice carried ids and nothing about who moved them, so
15294
- * `recordsRealtimePort.ts` declared `acceptEchoFromSelf: true` and the writer re-read a page it
15295
- * had already updated. It was named honestly rather than faked, and this is the other half.
15296
- *
15297
- * THE SHAPE. Every write through `RecordsClient` mints a uuid, sends it to the store as the
15298
- * platform envelope key `_op_id`, and remembers it here. The store lifts that key out before
15299
- * storage — it is never written onto the record — carries it on the change-feed row, and puts
15300
- * it in the notice as `op_id`. A notice whose `op_id` is in this ledger is this browser's own
15301
- * echo and is dropped.
15302
- *
15303
- * WHY A TIME WINDOW RATHER THAN A DELETE-ON-MATCH. One write can produce MORE THAN ONE notice:
15304
- * a record write that also moves a column produces a `record` notice and a `field` notice, and
15305
- * a statement touching two Tables produces one per Table. Forgetting an op the first time it
15306
- * came back would make the second notice look like somebody else's and cost the re-read this
15307
- * exists to avoid. So an op is remembered for a window and expires on its own.
15308
- *
15309
- * 🚨 THE WINDOW IS A CORRECTNESS BOUNDARY, NOT A TUNING KNOB, AND IT FAILS IN THE SAFE
15310
- * DIRECTION BY CONSTRUCTION. Too SHORT and a late echo is treated as a stranger's change: one
15311
- * extra read of a page that is already correct — invisible. Too LONG and a stranger's change
15312
- * that happened to reuse our uuid would be dropped — which cannot happen, because it is a v4
15313
- * uuid nobody else has ever seen. The only real cost of a long window is memory, so it is
15314
- * bounded by COUNT as well as by time.
15315
- *
15316
- * WHAT THIS IS NOT. It is not a replacement for the store's optimistic concurrency, and it
15317
- * never decides what is true — a dropped echo means "I already know about this", and every
15318
- * path that could be wrong about that (a reconnect, a tab wake, a notice with no `op_id` at
15319
- * all) re-reads through the door as it always did.
15320
- */
15321
-
15322
15288
  /**
15323
15289
  * Mint the id for one client operation and remember it as ours.
15324
15290
  *
@@ -15997,9 +15963,9 @@ declare const f: {
15997
15963
  member: <const O extends FieldOptions = {}>(opts?: O) => AppField<string, Req<O>, false, Uniq<O>>;
15998
15964
  /** A file. */
15999
15965
  attachment: <const O extends FieldOptions = {}>(opts?: O) => AppField<string, Req<O>, false, Uniq<O>>;
16000
- /** A platform thing (`note`, `agent` …) of one of `kinds`. */
15966
+ /** A platform thing (`note`, `agent` …) of one of `kinds`: `{ token, id }`, the store's own shape. */
16001
15967
  entityReference: <const O extends FieldOptions = {}>(kinds: readonly string[], opts?: O) => AppField<{
16002
- type: string;
15968
+ token: string;
16003
15969
  id: Uuid;
16004
15970
  }, Req<O>, false, Uniq<O>>;
16005
15971
  /** Computed by the store from `formulaText` (`{Visit fee} - {Deposit taken}`); read-only. */
@@ -70,7 +70,8 @@ function isWritingDoor(door) {
70
70
  }
71
71
 
72
72
  // src/core/recordChanges.ts
73
- var listeners = /* @__PURE__ */ new Set();
73
+ import { globalSlot } from "@ai-matrx/kit/global-slot";
74
+ var listeners = globalSlot("ai-matrx.records.record-change-listeners", () => /* @__PURE__ */ new Set());
74
75
  function announceRecordChanged(recordId) {
75
76
  for (const listener of [...listeners]) {
76
77
  try {
@@ -80,7 +81,7 @@ function announceRecordChanged(recordId) {
80
81
  }
81
82
  }
82
83
  }
83
- var removed = /* @__PURE__ */ new Set();
84
+ var removed = globalSlot("ai-matrx.records.removed-records", () => /* @__PURE__ */ new Set());
84
85
  function markRecordRemoved(recordId) {
85
86
  removed.add(recordId);
86
87
  }
@@ -94,7 +95,7 @@ function onRecordChanged(listener) {
94
95
  listeners.add(listener);
95
96
  return () => listeners.delete(listener);
96
97
  }
97
- var tableListeners = /* @__PURE__ */ new Set();
98
+ var tableListeners = globalSlot("ai-matrx.records.table-structure-listeners", () => /* @__PURE__ */ new Set());
98
99
  function announceTableStructureChanged(tableId) {
99
100
  for (const listener of [...tableListeners]) {
100
101
  try {
@@ -496,9 +497,10 @@ function sourceArchivedOf(value) {
496
497
  }
497
498
 
498
499
  // src/ops.ts
500
+ import { globalSlot as globalSlot2 } from "@ai-matrx/kit/global-slot";
499
501
  var REMEMBER_MS = 3e4;
500
502
  var REMEMBER_MAX = 500;
501
- var ours = /* @__PURE__ */ new Map();
503
+ var ours = globalSlot2("ai-matrx.records.op-ledger", () => /* @__PURE__ */ new Map());
502
504
  function trim(now) {
503
505
  for (const [id, at] of ours) {
504
506
  if (now - at <= REMEMBER_MS) break;
@@ -10418,8 +10420,9 @@ function optionsTableOf(field2) {
10418
10420
  return typeof id === "string" && id ? id : null;
10419
10421
  }
10420
10422
  async function readTableShape(client, tableId) {
10421
- const fields = await client.fields({ table_id: tableId });
10423
+ const [fields, table] = await Promise.all([client.fields({ table_id: tableId }), client.tableRead({ table_id: tableId, organization_id: client.config.organizationId })]);
10422
10424
  if (!fields.ok) return fields;
10425
+ const titleField = table.ok ? table.data?.title_field ?? null : null;
10423
10426
  const choiceFields = fields.data.filter((f2) => optionsTableOf(f2) !== null);
10424
10427
  const options = await Promise.all(choiceFields.map((f2) => client.fieldOptions({ field_id: f2.id })));
10425
10428
  const choicesById = /* @__PURE__ */ new Map();
@@ -10443,8 +10446,10 @@ async function readTableShape(client, tableId) {
10443
10446
  type,
10444
10447
  origin: "custom",
10445
10448
  required: Boolean(field2.required),
10449
+ multi: field2.multi === true,
10446
10450
  readOnly: COMPUTED_TYPES.has(field2.type) || field2.source === "formula" || field2.compute_on !== null && field2.compute_on !== void 0
10447
10451
  };
10452
+ if (titleField !== null && (titleField === field2.key || titleField === field2.id)) column.title = true;
10448
10453
  const choices = choicesById.get(field2.id);
10449
10454
  if (choices) column.choices = choices;
10450
10455
  if (field2.type === "relation" && field2.relation_target) column.linksTo = [field2.relation_target];
@@ -10710,7 +10715,15 @@ function createTableDataPort({ client, aliases }) {
10710
10715
  const binding = bindingOf(alias);
10711
10716
  if (!binding) return noSuchAlias(alias);
10712
10717
  const shape = await shapeOf(alias, binding);
10713
- return shape.ok ? { ok: true, data: shape.data.columns } : shape;
10718
+ if (!shape.ok) return shape;
10719
+ return {
10720
+ ok: true,
10721
+ data: shape.data.columns.map((c) => {
10722
+ const target = c.type === "relation" && c.linksTo?.length === 1 ? c.linksTo[0] : null;
10723
+ const served = target ? Object.entries(aliases).find(([, b]) => b.tableId === target)?.[0] : void 0;
10724
+ return served ? { ...c, linksToSource: served } : c;
10725
+ })
10726
+ };
10714
10727
  },
10715
10728
  async list(alias, q = {}, cursor) {
10716
10729
  const binding = bindingOf(alias);
@@ -11557,7 +11570,7 @@ var f = {
11557
11570
  member: (opts) => field("member", opts),
11558
11571
  /** A file. */
11559
11572
  attachment: (opts) => field("attachment", opts),
11560
- /** A platform thing (`note`, `agent` …) of one of `kinds`. */
11573
+ /** A platform thing (`note`, `agent` …) of one of `kinds`: `{ token, id }`, the store's own shape. */
11561
11574
  entityReference: (kinds, opts) => field("entity_reference", opts, { allowedTypes: [...kinds] }),
11562
11575
  /** Computed by the store from `formulaText` (`{Visit fee} - {Deposit taken}`); read-only. */
11563
11576
  formula: (formulaText, opts) => field("formula", opts, { declaration: { formula_text: formulaText, ...opts?.declaration ?? {} } }, true),