@ai-matrx/records 0.73.3 → 0.73.5

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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog — @ai-matrx/records
2
2
 
3
+ ## 0.73.5
4
+
5
+ Automatic changed-only republish (docs/metadata drift since the last tag — see
6
+ `git diff npm/records/v0.73.4..npm/records/v0.73.5 -- apps/shared/records`).
7
+ No source changes intended and no consumer action required.
8
+
9
+ ## 0.73.4
10
+
11
+ Automatic changed-only republish (docs/metadata drift since the last tag — see
12
+ `git diff npm/records/v0.73.3..npm/records/v0.73.4 -- apps/shared/records`).
13
+ No source changes intended and no consumer action required.
14
+
3
15
  ## 0.73.3
4
16
 
5
17
  Automatic changed-only republish (docs/metadata drift since the last tag — see
@@ -22,13 +22,187 @@ var memory_exports = {};
22
22
  __export(memory_exports, {
23
23
  MEMORY_KERNEL: () => MEMORY_KERNEL,
24
24
  PREVIEW_REFUSAL: () => PREVIEW_REFUSAL,
25
+ entityStore: () => entityStore,
25
26
  memoryDataSource: () => memoryDataSource,
27
+ memoryEntityAsk: () => memoryEntityAsk,
28
+ memoryEntityDescribe: () => memoryEntityDescribe,
29
+ memoryEntityRows: () => memoryEntityRows,
30
+ memoryEntityWrite: () => memoryEntityWrite,
26
31
  stableId: () => stableId,
27
32
  templatePreview: () => templatePreview,
28
- templateWorld: () => templateWorld
33
+ templateWorld: () => templateWorld,
34
+ withEntities: () => withEntities
29
35
  });
30
36
  module.exports = __toCommonJS(memory_exports);
31
37
 
38
+ // src/memory/entity.ts
39
+ var ok = (data) => ({ data, error: null });
40
+ var refuse = (message, code = "22023", hint = null) => ({
41
+ data: null,
42
+ error: { code, message, details: null, hint }
43
+ });
44
+ function memoryEntityDescribe(e) {
45
+ const dimensions = e.columns.filter((c) => c.type === "choice" && Array.isArray(c.choices)).map((c) => ({
46
+ key: c.api_name,
47
+ label: c.name,
48
+ from: c.api_name,
49
+ kind: "choice",
50
+ cardinality: "low",
51
+ choices: (c.choices ?? []).map((w) => ({ value: w, label: w }))
52
+ }));
53
+ const measures = [
54
+ { key: "count", label: `Number of ${e.label.toLowerCase()}`, op: "count", unit: "count", additive: true },
55
+ ...e.columns.filter((c) => c.type === "number").map((c) => ({
56
+ key: `sum_${c.api_name}`,
57
+ label: `Total ${c.name.toLowerCase()}`,
58
+ op: "sum",
59
+ of: c.api_name,
60
+ additive: true
61
+ }))
62
+ ];
63
+ return {
64
+ key: e.token,
65
+ label: e.label,
66
+ grain: `one row per ${e.label.toLowerCase()}`,
67
+ lanes: ["organization", "platform"],
68
+ dimensions,
69
+ measures,
70
+ paths: [],
71
+ inferred: [],
72
+ detail: { columns: e.columns.map((c) => c.api_name) },
73
+ source: { kind: "entity", token: e.token },
74
+ mode: "invoker",
75
+ api: { label: e.label, reach: "read_write", columns: e.columns },
76
+ calendar: { time_zone: "UTC", week_start: "monday" }
77
+ };
78
+ }
79
+ function matches(row, where) {
80
+ for (const [k, want] of Object.entries(where ?? {})) {
81
+ const have = row[k] ?? null;
82
+ if (Array.isArray(want)) {
83
+ if (!want.includes(have)) return false;
84
+ } else if (want === null) {
85
+ if (have !== null) return false;
86
+ } else if (typeof want === "object") continue;
87
+ else if (String(have).toLowerCase() !== String(want).toLowerCase()) return false;
88
+ }
89
+ return true;
90
+ }
91
+ function memoryEntityRows(e, question2) {
92
+ const limit = Number(question2["limit"] ?? 50);
93
+ const offset = Number(question2["offset"] ?? 0);
94
+ const rows = e.rows.filter((r) => matches(r, question2["where"]));
95
+ const page = rows.slice(offset, offset + limit);
96
+ return {
97
+ rows: page,
98
+ total: rows.length,
99
+ limit,
100
+ offset,
101
+ as_of: null,
102
+ columns: e.columns.map((c) => c.api_name),
103
+ ...offset + page.length < rows.length ? { next_offset: offset + page.length } : {}
104
+ };
105
+ }
106
+ function memoryEntityAsk(e, question2) {
107
+ const by = (question2["by"] ?? []).map((k) => k.split(":")[0]);
108
+ const shown = question2["show"] ?? ["count"];
109
+ const measures = (shown.length ? shown : ["count"]).map((m) => typeof m === "string" ? m === "count" ? { key: "count", op: "count", of: null } : { key: m, op: m.split("_")[0], of: m.slice(m.indexOf("_") + 1) } : { key: m.op === "count" ? "count" : `${m.op}_${m.of ?? ""}`, op: m.op, of: m.of ?? null });
110
+ const rows = e.rows.filter((r) => matches(r, question2["where"]));
111
+ const measure = (list) => {
112
+ const out2 = {};
113
+ for (const m of measures) {
114
+ if (m.op === "count") {
115
+ out2[m.key] = list.length;
116
+ continue;
117
+ }
118
+ const nums = list.map((r) => r[m.of ?? ""]).filter((v) => typeof v === "number");
119
+ const sum = nums.reduce((a, b) => a + b, 0);
120
+ out2[m.key] = !nums.length ? null : m.op === "sum" ? sum : m.op === "avg" ? sum / nums.length : m.op === "min" ? Math.min(...nums) : m.op === "max" ? Math.max(...nums) : null;
121
+ }
122
+ return out2;
123
+ };
124
+ const row = (kind, groups, list) => ({
125
+ kind,
126
+ groups,
127
+ measures: measure(list),
128
+ row_count: list.length,
129
+ prior_groups: null,
130
+ prior_measures: null,
131
+ prior_row_count: null,
132
+ delta: null,
133
+ compare: null,
134
+ distinct_groups: null,
135
+ labels: {},
136
+ says: null,
137
+ as_of: null
138
+ });
139
+ const out = [];
140
+ if (by.length) {
141
+ const groups = /* @__PURE__ */ new Map();
142
+ for (const r of rows) {
143
+ const g = {};
144
+ for (const k of by) {
145
+ const v = r[k];
146
+ g[k] = v === void 0 || v === "" ? null : v;
147
+ }
148
+ const key = JSON.stringify(g);
149
+ const hit = groups.get(key) ?? { groups: g, rows: [] };
150
+ hit.rows.push(r);
151
+ groups.set(key, hit);
152
+ }
153
+ const first = measures[0].key;
154
+ const answered = [...groups.values()].map((g) => row("group", g.groups, g.rows));
155
+ answered.sort((a, b) => Number(b["measures"][first] ?? 0) - Number(a["measures"][first] ?? 0));
156
+ out.push(...answered.slice(0, Number(question2["limit"] ?? 50)));
157
+ }
158
+ out.push(row("total", {}, rows));
159
+ return out;
160
+ }
161
+ function memoryEntityWrite(e, recordId, columns) {
162
+ const row = e.rows.find((r) => r["id"] === recordId);
163
+ if (!row) return refuse(`There is no ${e.label} you can open with that id.`, "02000");
164
+ const next = {};
165
+ for (const [k, v] of Object.entries(columns ?? {})) {
166
+ const c = e.columns.find((x) => x.api_name === k);
167
+ if (!c) return refuse(`This table has no column called "${k}".`);
168
+ if (c.lookup && !c.lookup.replaces) return refuse(`"${c.name}" is shown from another table, so it is changed there. Nothing was written.`);
169
+ if (!c.writable) return refuse(`"${c.name}" is not changed through this door.`, "42501");
170
+ if (c.type === "choice" && v !== null && v !== "") {
171
+ const word = (c.choices ?? []).find((w) => w === v) ?? (c.choices ?? []).find((w) => w.toLowerCase() === String(v).toLowerCase());
172
+ if (!word) return refuse(`"${String(v)}" is not one of the choices of "${c.name}".`, "22023", "The columns call lists its choices.");
173
+ next[k] = word;
174
+ } else next[k] = v === "" ? null : v;
175
+ }
176
+ const changed = Object.entries(next).some(([k, v]) => row[k] !== v);
177
+ Object.assign(row, next);
178
+ return ok({ id: recordId, organization_id: row["organization_id"] ?? null, changed, row });
179
+ }
180
+ function memoryEntityDoors(entities) {
181
+ const of = (args) => {
182
+ const src = args["p_source"];
183
+ return src && src["kind"] === "entity" ? entities.get(String(src["token"])) : void 0;
184
+ };
185
+ return {
186
+ drill_describe: (args) => {
187
+ const e = of(args);
188
+ return e ? ok(memoryEntityDescribe(e)) : null;
189
+ },
190
+ drill_ask: (args) => {
191
+ const e = of(args);
192
+ return e ? ok(memoryEntityAsk(e, args["p_question"] ?? {})) : null;
193
+ },
194
+ drill_rows: (args) => {
195
+ const e = of(args);
196
+ return e ? ok(memoryEntityRows(e, args["p_question"] ?? {})) : null;
197
+ },
198
+ entity_row_write: (args) => {
199
+ const e = entities.get(String(args["p_token"] ?? ""));
200
+ if (!e) return null;
201
+ return memoryEntityWrite(e, String(args["p_record_id"] ?? ""), args["p_columns"] ?? {});
202
+ }
203
+ };
204
+ }
205
+
32
206
  // src/templates/payloads.ts
33
207
  var ACTOR = "user";
34
208
  var COMPUTED = /* @__PURE__ */ new Set(["lookup", "rollup", "formula"]);
@@ -2257,10 +2431,10 @@ var PREVIEW_REFUSAL = {
2257
2431
  hint: null
2258
2432
  };
2259
2433
  var WRITE_DOOR = /(^|_)(write|writes|declare|delete|update|upsert|archive|restore|set|move|save|submit|insert|merge|retype|undo|retire|assign|claim|clear|snooze|unsnooze|publish|invite|revoke|issue|pin|duplicate|designate|bind|unbind|change|start|complete|decide|request|hold|release|expire|forget|begin|finish|rows|take|nudge|import|mute|sign|render|instantiate|ensure)(_|$)/;
2260
- function ok(data) {
2434
+ function ok2(data) {
2261
2435
  return { data, error: null };
2262
2436
  }
2263
- function refuse(message, code = "PGRST202") {
2437
+ function refuse2(message, code = "PGRST202") {
2264
2438
  return { data: null, error: { code, message, details: null, hint: null } };
2265
2439
  }
2266
2440
  function stamp(world) {
@@ -2445,16 +2619,11 @@ function groupValues(doc, stored, key) {
2445
2619
  const out = list.map((v) => v === void 0 || v === null || v === "" ? null : String(v));
2446
2620
  return out.length ? out : [null];
2447
2621
  }
2448
- function runBlock(world, subject, raw, canvas, index) {
2449
- const table = world.tableById.get(String(raw["table_id"] ?? subject));
2450
- const title = String(raw["title"] ?? `Block ${index + 1}`);
2451
- const base = { ...raw, title, table_id: table?.id ?? subject, ms: 0 };
2452
- if (!table) return { ...base, rows: [], refused: "This block's table is not in this template." };
2453
- if (raw["kind"] === "stuck") return { ...base, rows: [] };
2454
- const measures = (raw["measures"] ?? [{ op: "count" }]).map((m) => ({ op: String(m["op"] ?? "count"), key: m["key"] }));
2455
- const groupBy = (raw["group_by"] ?? []).slice();
2456
- const bucket = raw["bucket"] ?? null;
2457
- const ids = table.order.filter((id) => admits(world, table, id, raw["filter"]) && admits(world, table, id, canvas));
2622
+ function aggregateRows(world, table, ask) {
2623
+ const { groupBy, measures, bucket } = ask;
2624
+ const search = (ask.search ?? "").trim().toLowerCase();
2625
+ let ids = table.order.filter((id) => ask.filters.every((f) => admits(world, table, id, f)));
2626
+ if (search) ids = ids.filter((id) => JSON.stringify(readDocument(world, table, id)).toLowerCase().includes(search));
2458
2627
  const groups = /* @__PURE__ */ new Map();
2459
2628
  for (const id of ids) {
2460
2629
  const doc = readDocument(world, table, id);
@@ -2496,8 +2665,25 @@ function runBlock(world, subject, raw, canvas, index) {
2496
2665
  const k = `${bucket.key}_${bucket.by ?? "month"}`;
2497
2666
  rows.sort((a, b) => String(a.groups[k]).localeCompare(String(b.groups[k])));
2498
2667
  } else rows.sort((a, b) => (b.measures[primary] ?? 0) - (a.measures[primary] ?? 0));
2499
- const limit = Number(raw["limit"] ?? 0);
2668
+ const limit = Number(ask.limit ?? 0);
2500
2669
  if (limit > 0) rows = rows.slice(0, limit);
2670
+ return rows;
2671
+ }
2672
+ function measuresOf(raw) {
2673
+ const list = Array.isArray(raw) ? raw : [];
2674
+ const out = list.map((m) => ({ op: String(m["op"] ?? "count"), key: m["key"] }));
2675
+ return out.length ? out : [{ op: "count" }];
2676
+ }
2677
+ function runBlock(world, subject, raw, canvas, index) {
2678
+ const table = world.tableById.get(String(raw["table_id"] ?? subject));
2679
+ const title = String(raw["title"] ?? `Block ${index + 1}`);
2680
+ const base = { ...raw, title, table_id: table?.id ?? subject, ms: 0 };
2681
+ if (!table) return { ...base, rows: [], refused: "This block's table is not in this template." };
2682
+ if (raw["kind"] === "stuck") return { ...base, rows: [] };
2683
+ const measures = measuresOf(raw["measures"]);
2684
+ const groupBy = (raw["group_by"] ?? []).slice();
2685
+ const bucket = raw["bucket"] ?? null;
2686
+ const rows = aggregateRows(world, table, { groupBy, measures, bucket, filters: [raw["filter"], canvas], limit: Number(raw["limit"] ?? 0) });
2501
2687
  return { ...base, measures, group_by: groupBy, rows };
2502
2688
  }
2503
2689
  function bookingAvailability(world, raw) {
@@ -2584,55 +2770,55 @@ function memoryDataSource(world) {
2584
2770
  return typeof v === "string" ? v : v === void 0 || v === null ? null : String(v);
2585
2771
  };
2586
2772
  const doors = {
2587
- table_kernel_id: () => ok(MEMORY_KERNEL.table),
2588
- field_kernel_id: () => ok(MEMORY_KERNEL.field),
2589
- person_kernel_id: () => ok(MEMORY_KERNEL.person),
2590
- file_kernel_id: () => ok(MEMORY_KERNEL.file),
2591
- rule_kernel_id: () => ok(MEMORY_KERNEL.rule),
2592
- store_is_open: () => ok(true),
2773
+ table_kernel_id: () => ok2(MEMORY_KERNEL.table),
2774
+ field_kernel_id: () => ok2(MEMORY_KERNEL.field),
2775
+ person_kernel_id: () => ok2(MEMORY_KERNEL.person),
2776
+ file_kernel_id: () => ok2(MEMORY_KERNEL.file),
2777
+ rule_kernel_id: () => ok2(MEMORY_KERNEL.rule),
2778
+ store_is_open: () => ok2(true),
2593
2779
  read_records: (args) => {
2594
2780
  const limit = Number(args["p_limit"] ?? 50);
2595
2781
  const offset = Number(args["p_offset"] ?? 0);
2596
- if (args["p_table_id"] === MEMORY_KERNEL.table) return ok(world.tables.slice(offset, offset + limit).map(tableRow));
2782
+ if (args["p_table_id"] === MEMORY_KERNEL.table) return ok2(world.tables.slice(offset, offset + limit).map(tableRow));
2597
2783
  const t = tableOf(args);
2598
- if (!t) return ok([]);
2599
- return ok(t.order.slice(offset, offset + limit).map((id) => row(t, id)));
2784
+ if (!t) return ok2([]);
2785
+ return ok2(t.order.slice(offset, offset + limit).map((id) => row(t, id)));
2600
2786
  },
2601
2787
  read_records_by_ids: (args) => {
2602
2788
  const ids = args["p_record_ids"] ?? [];
2603
- if (args["p_table_id"] === MEMORY_KERNEL.table) return ok(world.tables.filter((t2) => ids.includes(t2.id)).map(tableRow));
2789
+ if (args["p_table_id"] === MEMORY_KERNEL.table) return ok2(world.tables.filter((t2) => ids.includes(t2.id)).map(tableRow));
2604
2790
  const t = tableOf(args);
2605
- if (!t) return ok([]);
2606
- return ok(ids.filter((id) => t.rows.has(id)).map((id) => row(t, id)));
2791
+ if (!t) return ok2([]);
2792
+ return ok2(ids.filter((id) => t.rows.has(id)).map((id) => row(t, id)));
2607
2793
  },
2608
2794
  read_records_matching: (args) => {
2609
2795
  const t = tableOf(args);
2610
- if (!t) return ok([]);
2796
+ if (!t) return ok2([]);
2611
2797
  const limit = Number(args["p_limit"] ?? 50);
2612
2798
  const offset = Number(args["p_offset"] ?? 0);
2613
- return ok(matching(world, t, args).slice(offset, offset + limit).map((id) => row(t, id)));
2799
+ return ok2(matching(world, t, args).slice(offset, offset + limit).map((id) => row(t, id)));
2614
2800
  },
2615
2801
  read_records_page: (args) => {
2616
2802
  const t = tableOf(args);
2617
2803
  const limit = Number(args["p_limit"] ?? 50);
2618
2804
  const offset = Number(args["p_offset"] ?? 0);
2619
- if (!t) return ok({ rows: [], total: 0, limit, offset, sort_ignored: [] });
2805
+ if (!t) return ok2({ rows: [], total: 0, limit, offset, sort_ignored: [] });
2620
2806
  const ids = matching(world, t, args);
2621
- return ok({ rows: ids.slice(offset, offset + limit).map((id) => row(t, id)), total: ids.length, limit, offset, sort_ignored: [] });
2807
+ return ok2({ rows: ids.slice(offset, offset + limit).map((id) => row(t, id)), total: ids.length, limit, offset, sort_ignored: [] });
2622
2808
  },
2623
2809
  read_record: (args) => {
2624
2810
  const id = String(args["p_record_id"] ?? "");
2625
2811
  const t = world.recordTable.get(id);
2626
- if (!t) return refuse("That record is not in this template.", "P0002");
2627
- return ok(readDocument(world, t, id));
2812
+ if (!t) return refuse2("That record is not in this template.", "P0002");
2813
+ return ok2(readDocument(world, t, id));
2628
2814
  },
2629
- record_headers: (args) => ok(
2815
+ record_headers: (args) => ok2(
2630
2816
  (args["p_ids"] ?? []).filter((id) => world.recordTable.has(id)).map((id) => ({ id, mine: false, version: 1, table_id: world.recordTable.get(id).id, deleted_at: null, ...stamp(world) }))
2631
2817
  ),
2632
2818
  applicable_fields: (args) => {
2633
2819
  const t = tableOf(args);
2634
- if (!t) return ok([]);
2635
- return ok(
2820
+ if (!t) return ok2([]);
2821
+ return ok2(
2636
2822
  t.fields.map((f) => ({
2637
2823
  id: f.id,
2638
2824
  data: f.data,
@@ -2651,9 +2837,9 @@ function memoryDataSource(world) {
2651
2837
  },
2652
2838
  field_options: (args) => {
2653
2839
  const hit = fieldById(args["p_field_id"]);
2654
- if (!hit) return ok([]);
2840
+ if (!hit) return ok2([]);
2655
2841
  const opts = hit.t.options.get(String(hit.f.data["key"])) ?? [];
2656
- return ok(
2842
+ return ok2(
2657
2843
  opts.map((o, i) => ({
2658
2844
  id: o.id,
2659
2845
  organization_id: world.organization_id,
@@ -2669,46 +2855,46 @@ function memoryDataSource(world) {
2669
2855
  }))
2670
2856
  );
2671
2857
  },
2672
- my_levels: (args) => ok((args["p_ids"] ?? []).map((id) => ({ id, level: VIEWER }))),
2673
- views: (args) => ok(
2858
+ my_levels: (args) => ok2((args["p_ids"] ?? []).map((id) => ({ id, level: VIEWER }))),
2859
+ views: (args) => ok2(
2674
2860
  world.views.filter((v) => !args["p_table_id"] || v.table_id === args["p_table_id"]).map((v) => ({ view_id: v.view_id, name: v.name, table_id: v.table_id, filters: v.filters, definition: v.definition, created_at: `${world.today}T09:00:00Z` }))
2675
2861
  ),
2676
2862
  view_look_read: (args) => {
2677
2863
  const out = {};
2678
2864
  for (const [key, look] of looks) if (key.startsWith(`${String(args["p_table_id"])}:`)) out[key.split(":")[1]] = look;
2679
- return ok(out);
2865
+ return ok2(out);
2680
2866
  },
2681
2867
  view_look_set: (args) => {
2682
2868
  const key = `${String(args["p_table_id"])}:${String(args["p_view_id"])}`;
2683
2869
  const look = args["p_look"] ?? null;
2684
2870
  if (look) looks.set(key, { look, updated_at: (/* @__PURE__ */ new Date()).toISOString() });
2685
2871
  else looks.delete(key);
2686
- return ok(look);
2872
+ return ok2(look);
2687
2873
  },
2688
- grid_layout: (args) => ok({
2874
+ grid_layout: (args) => ok2({
2689
2875
  layout: { mode: "auto", wrap: false, row_height: "normal", fit_max_columns: 8, freeze_first_column: false },
2690
2876
  source: { mode: "platform", wrap: "platform", row_height: "platform", fit_max_columns: "platform", freeze_first_column: "platform" },
2691
2877
  refused: [],
2692
2878
  view_id: args["p_view_id"] ?? null
2693
2879
  }),
2694
- table_decorations: () => ok({ rows: {}, cells: {}, rules: [], stale: [], colors: ["slate", "green", "amber", "red", "blue", "violet", "teal"], columns: {}, version: 1, rule_ops: [] }),
2695
- row_actions: () => ok({ stale: [], actions: [] }),
2696
- table_stage_field: (args) => ok(world.stageField.get(String(args["p_table_id"])) ?? null),
2880
+ table_decorations: () => ok2({ rows: {}, cells: {}, rules: [], stale: [], colors: ["slate", "green", "amber", "red", "blue", "violet", "teal"], columns: {}, version: 1, rule_ops: [] }),
2881
+ row_actions: () => ok2({ stale: [], actions: [] }),
2882
+ table_stage_field: (args) => ok2(world.stageField.get(String(args["p_table_id"])) ?? null),
2697
2883
  pipeline_read: (args) => {
2698
2884
  const t = tableOf(args);
2699
2885
  const stage = t ? world.stageField.get(t.id) : void 0;
2700
2886
  const field = stage ? t.fields.find((f) => f.data["key"] === stage) : void 0;
2701
- if (!t || !stage || !field) return ok({ is_pipeline: false, why: "This table has no stage column." });
2887
+ if (!t || !stage || !field) return ok2({ is_pipeline: false, why: "This table has no stage column." });
2702
2888
  const stages = (t.options.get(stage) ?? []).map((o) => ({ key: o.key, label: o.label, retired: false }));
2703
- return ok({ is_pipeline: true, table_id: t.id, stage_field: stage, field_id: field.id, stage_label: field.data["label"], stages, rules: [] });
2889
+ return ok2({ is_pipeline: true, table_id: t.id, stage_field: stage, field_id: field.id, stage_label: field.data["label"], stages, rules: [] });
2704
2890
  },
2705
2891
  pipeline_board: (args) => {
2706
2892
  const t = tableOf(args);
2707
2893
  const stage = t ? world.stageField.get(t.id) : void 0;
2708
- if (!t || !stage) return ok([]);
2894
+ if (!t || !stage) return ok2([]);
2709
2895
  const measure = typeof args["p_measure"] === "string" ? args["p_measure"] : null;
2710
2896
  const ids = t.order.filter((id) => admits(world, t, id, args["p_filter"]));
2711
- return ok(
2897
+ return ok2(
2712
2898
  (t.options.get(stage) ?? []).map((o, i) => {
2713
2899
  const inIt = ids.filter((id) => t.rows.get(id)[stage] === o.key);
2714
2900
  const total = measure ? inIt.reduce((sum, id) => sum + (typeof t.rows.get(id)[measure] === "number" ? t.rows.get(id)[measure] : 0), 0) : null;
@@ -2716,7 +2902,7 @@ function memoryDataSource(world) {
2716
2902
  })
2717
2903
  );
2718
2904
  },
2719
- relation_words_many: (args) => ok((args["p_record_ids"] ?? []).map((id) => ({ record_id: id, words: nameOf(id), archived: false }))),
2905
+ relation_words_many: (args) => ok2((args["p_record_ids"] ?? []).map((id) => ({ record_id: id, words: nameOf(id), archived: false }))),
2720
2906
  reverse_columns: (args) => {
2721
2907
  const target = String(args["p_table_id"] ?? "");
2722
2908
  const out = [];
@@ -2736,11 +2922,11 @@ function memoryDataSource(world) {
2736
2922
  });
2737
2923
  }
2738
2924
  }
2739
- return ok(out);
2925
+ return ok2(out);
2740
2926
  },
2741
2927
  reverse_links_many: (args) => {
2742
2928
  const hit = fieldById(args["p_field_id"]);
2743
- return ok(
2929
+ return ok2(
2744
2930
  (args["p_record_ids"] ?? []).map((rid) => {
2745
2931
  if (!hit) return { record_id: rid, total: 0, links: [] };
2746
2932
  const key = String(hit.f.data["key"]);
@@ -2754,28 +2940,28 @@ function memoryDataSource(world) {
2754
2940
  },
2755
2941
  // A template's own record has no history, comments, checklists or worked-out provenance yet:
2756
2942
  // these are the answers an installed template gives on its first day.
2757
- record_history: () => ok([]),
2758
- field_history: () => ok([]),
2759
- computed_provenance: () => ok([]),
2760
- comment_thread: (args) => ok({ record_id: args["p_record_id"] ?? null, comments: [], may_comment: false, may_resolve: false }),
2761
- checklist_runs: () => ok([]),
2762
- checklist_templates: () => ok([]),
2763
- doc_templates: () => ok([]),
2764
- doc_renders: () => ok([]),
2765
- doc_signatures: () => ok([]),
2766
- record_row_controls: () => ok({
2943
+ record_history: () => ok2([]),
2944
+ field_history: () => ok2([]),
2945
+ computed_provenance: () => ok2([]),
2946
+ comment_thread: (args) => ok2({ record_id: args["p_record_id"] ?? null, comments: [], may_comment: false, may_resolve: false }),
2947
+ checklist_runs: () => ok2([]),
2948
+ checklist_templates: () => ok2([]),
2949
+ doc_templates: () => ok2([]),
2950
+ doc_renders: () => ok2([]),
2951
+ doc_signatures: () => ok2([]),
2952
+ record_row_controls: () => ok2({
2767
2953
  shown_to: { value: null, effective: "everyone", type_default: null, table_default: null, allowed: ["everyone"], is_child: false, can_change: false },
2768
2954
  published_to_web: { available: false, value: false, table_default: null, can_change: false },
2769
2955
  indexed: { available: false, value: null, table_default: null, effective: false, can_change: false }
2770
2956
  }),
2771
- enrich_cells: () => ok([]),
2772
- enrichments: () => ok([]),
2773
- pipeline_pending: () => ok([]),
2774
- record_stage_pending: () => ok([]),
2775
- io_imports: () => ok([]),
2776
- relation_words: (args) => ok(nameOf(String(args["p_value"] ?? ""))),
2957
+ enrich_cells: () => ok2([]),
2958
+ enrichments: () => ok2([]),
2959
+ pipeline_pending: () => ok2([]),
2960
+ record_stage_pending: () => ok2([]),
2961
+ io_imports: () => ok2([]),
2962
+ relation_words: (args) => ok2(nameOf(String(args["p_value"] ?? ""))),
2777
2963
  // The read doors a form, a booking page and a dashboard are drawn from (lane CHAIR-GALLERY-3).
2778
- forms: (args) => ok(
2964
+ forms: (args) => ok2(
2779
2965
  world.forms.filter((f) => !f.booking && (!args["p_table_id"] || f.table_id === args["p_table_id"])).map((f) => ({
2780
2966
  form_id: f.form_id,
2781
2967
  table_id: f.table_id,
@@ -2798,7 +2984,7 @@ function memoryDataSource(world) {
2798
2984
  form_preview_asks: (args) => {
2799
2985
  const t = tableOf(args);
2800
2986
  const values = args["p_values"] ?? {};
2801
- return ok(
2987
+ return ok2(
2802
2988
  (args["p_questions"] ?? []).map((q) => {
2803
2989
  const when = q["show_if"] ?? q["showIf"] ?? null;
2804
2990
  const asked = !when || !t ? true : rule(t, values, values, when) !== false;
@@ -2806,7 +2992,7 @@ function memoryDataSource(world) {
2806
2992
  })
2807
2993
  );
2808
2994
  },
2809
- bookings: (args) => ok(
2995
+ bookings: (args) => ok2(
2810
2996
  world.forms.filter((f) => f.booking && (!args["p_table_id"] || f.table_id === args["p_table_id"])).map((f) => ({
2811
2997
  form_id: f.form_id,
2812
2998
  table_id: f.table_id,
@@ -2828,9 +3014,9 @@ function memoryDataSource(world) {
2828
3014
  booking_public: (args) => {
2829
3015
  const f = world.forms.find((x) => x.form_id === args["p_form_id"] && x.booking);
2830
3016
  const t = f ? world.tableById.get(f.table_id) : void 0;
2831
- if (!f || !t) return refuse("That booking page is not in this template.", "P0002");
3017
+ if (!f || !t) return refuse2("That booking page is not in this template.", "P0002");
2832
3018
  const availability = bookingAvailability(world, f.booking);
2833
- return ok({
3019
+ return ok2({
2834
3020
  form_id: f.form_id,
2835
3021
  organization_id: world.organization_id,
2836
3022
  table_id: t.id,
@@ -2844,7 +3030,7 @@ function memoryDataSource(world) {
2844
3030
  message: null
2845
3031
  });
2846
3032
  },
2847
- dashboards: (args) => ok(
3033
+ dashboards: (args) => ok2(
2848
3034
  world.dashboards.filter((d) => !args["p_table_id"] || d.table_id === args["p_table_id"]).map((d) => ({
2849
3035
  dashboard_id: d.dashboard_id,
2850
3036
  table_id: d.table_id,
@@ -2859,9 +3045,9 @@ function memoryDataSource(world) {
2859
3045
  ),
2860
3046
  dashboard_run: (args) => {
2861
3047
  const d = world.dashboards.find((x) => x.dashboard_id === args["p_dashboard_id"]);
2862
- if (!d) return refuse("That dashboard is not in this template.", "P0002");
3048
+ if (!d) return refuse2("That dashboard is not in this template.", "P0002");
2863
3049
  const canvas = args["p_filter"] ?? {};
2864
- return ok({
3050
+ return ok2({
2865
3051
  dashboard_id: d.dashboard_id,
2866
3052
  name: d.name,
2867
3053
  subject_table_id: d.table_id,
@@ -2873,16 +3059,34 @@ function memoryDataSource(world) {
2873
3059
  blocks: d.blocks.map((b, i) => runBlock(world, d.table_id, b, canvas, i))
2874
3060
  });
2875
3061
  },
2876
- dashboard_stuck: () => ok([])
3062
+ dashboard_stuck: () => ok2([]),
3063
+ // THE EIGHTH VERB, in memory: the same groups and measures as the live `custom.record_aggregate`
3064
+ // (and its row shape: no comparison asked ⇒ every prior_* null).
3065
+ record_aggregate: (args) => {
3066
+ const t = tableOf(args);
3067
+ if (!t) return refuse2("That table is not in this template.", "P0002");
3068
+ const rows = aggregateRows(world, t, {
3069
+ groupBy: (args["p_group_by"] ?? []).slice(),
3070
+ measures: measuresOf(args["p_measures"]),
3071
+ bucket: args["p_bucket"] ?? null,
3072
+ filters: [args["p_filter"]],
3073
+ ...typeof args["p_search"] === "string" ? { search: args["p_search"] } : {},
3074
+ limit: Number(args["p_limit"] ?? 200)
3075
+ });
3076
+ return ok2(rows.map((r) => ({ ...r, prior_groups: null, prior_measures: null, prior_row_count: null, delta: null, compare: null })));
3077
+ }
2877
3078
  };
2878
3079
  const source = {
2879
3080
  world,
2880
3081
  rpc(fn, args) {
2881
- const name = fn.replace(/^custom\./, "");
3082
+ const name = fn.replace(/^custom\./, "").replace(/^platform\./, "");
3083
+ const entityDoor = world.entities ? memoryEntityDoors(world.entities)[name] : void 0;
3084
+ const fromEntity = entityDoor ? entityDoor(args ?? {}) : null;
3085
+ if (fromEntity) return Promise.resolve(fromEntity);
2882
3086
  const door = doors[name];
2883
3087
  if (door) return Promise.resolve(door(args ?? {}));
2884
3088
  if (WRITE_DOOR.test(name)) return Promise.resolve({ data: null, error: PREVIEW_REFUSAL });
2885
- return Promise.resolve(refuse(`A template preview does not answer ${name}.`));
3089
+ return Promise.resolve(refuse2(`A template preview does not answer ${name}.`));
2886
3090
  },
2887
3091
  schema() {
2888
3092
  return {
@@ -2896,6 +3100,12 @@ function memoryDataSource(world) {
2896
3100
  }
2897
3101
 
2898
3102
  // src/memory/index.ts
3103
+ function withEntities(world, specs) {
3104
+ const entities = new Map(world.entities ?? []);
3105
+ for (const s of specs) entities.set(s.token, { ...s, rows: s.rows.map((r) => ({ ...r })) });
3106
+ world.entities = entities;
3107
+ return world;
3108
+ }
2899
3109
  function templatePreview(spec, options = {}) {
2900
3110
  const world = templateWorld(spec, options);
2901
3111
  const config = {
@@ -2911,4 +3121,25 @@ function templatePreview(spec, options = {}) {
2911
3121
  dashboards: world.dashboards.map((d) => ({ id: d.dashboard_id, token: d.token, name: d.name, tableId: d.table_id }))
2912
3122
  };
2913
3123
  }
3124
+ function entityStore(specs, options = {}) {
3125
+ const world = {
3126
+ organization_id: stableId("entity-store-organization"),
3127
+ user_id: stableId("entity-store-person"),
3128
+ timezone: "UTC",
3129
+ today: options.today ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
3130
+ businessName: "",
3131
+ tables: [],
3132
+ tableById: /* @__PURE__ */ new Map(),
3133
+ views: [],
3134
+ forms: [],
3135
+ dashboards: [],
3136
+ recordTable: /* @__PURE__ */ new Map(),
3137
+ stageField: /* @__PURE__ */ new Map()
3138
+ };
3139
+ withEntities(world, specs);
3140
+ return {
3141
+ world,
3142
+ config: { dataSource: memoryDataSource(world), actor: { actor: "user", user_id: world.user_id }, organizationId: world.organization_id }
3143
+ };
3144
+ }
2914
3145
  //# sourceMappingURL=index.cjs.map