@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.
@@ -1,3 +1,171 @@
1
+ // src/memory/entity.ts
2
+ var ok = (data) => ({ data, error: null });
3
+ var refuse = (message, code = "22023", hint = null) => ({
4
+ data: null,
5
+ error: { code, message, details: null, hint }
6
+ });
7
+ function memoryEntityDescribe(e) {
8
+ const dimensions = e.columns.filter((c) => c.type === "choice" && Array.isArray(c.choices)).map((c) => ({
9
+ key: c.api_name,
10
+ label: c.name,
11
+ from: c.api_name,
12
+ kind: "choice",
13
+ cardinality: "low",
14
+ choices: (c.choices ?? []).map((w) => ({ value: w, label: w }))
15
+ }));
16
+ const measures = [
17
+ { key: "count", label: `Number of ${e.label.toLowerCase()}`, op: "count", unit: "count", additive: true },
18
+ ...e.columns.filter((c) => c.type === "number").map((c) => ({
19
+ key: `sum_${c.api_name}`,
20
+ label: `Total ${c.name.toLowerCase()}`,
21
+ op: "sum",
22
+ of: c.api_name,
23
+ additive: true
24
+ }))
25
+ ];
26
+ return {
27
+ key: e.token,
28
+ label: e.label,
29
+ grain: `one row per ${e.label.toLowerCase()}`,
30
+ lanes: ["organization", "platform"],
31
+ dimensions,
32
+ measures,
33
+ paths: [],
34
+ inferred: [],
35
+ detail: { columns: e.columns.map((c) => c.api_name) },
36
+ source: { kind: "entity", token: e.token },
37
+ mode: "invoker",
38
+ api: { label: e.label, reach: "read_write", columns: e.columns },
39
+ calendar: { time_zone: "UTC", week_start: "monday" }
40
+ };
41
+ }
42
+ function matches(row, where) {
43
+ for (const [k, want] of Object.entries(where ?? {})) {
44
+ const have = row[k] ?? null;
45
+ if (Array.isArray(want)) {
46
+ if (!want.includes(have)) return false;
47
+ } else if (want === null) {
48
+ if (have !== null) return false;
49
+ } else if (typeof want === "object") continue;
50
+ else if (String(have).toLowerCase() !== String(want).toLowerCase()) return false;
51
+ }
52
+ return true;
53
+ }
54
+ function memoryEntityRows(e, question2) {
55
+ const limit = Number(question2["limit"] ?? 50);
56
+ const offset = Number(question2["offset"] ?? 0);
57
+ const rows = e.rows.filter((r) => matches(r, question2["where"]));
58
+ const page = rows.slice(offset, offset + limit);
59
+ return {
60
+ rows: page,
61
+ total: rows.length,
62
+ limit,
63
+ offset,
64
+ as_of: null,
65
+ columns: e.columns.map((c) => c.api_name),
66
+ ...offset + page.length < rows.length ? { next_offset: offset + page.length } : {}
67
+ };
68
+ }
69
+ function memoryEntityAsk(e, question2) {
70
+ const by = (question2["by"] ?? []).map((k) => k.split(":")[0]);
71
+ const shown = question2["show"] ?? ["count"];
72
+ 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 });
73
+ const rows = e.rows.filter((r) => matches(r, question2["where"]));
74
+ const measure = (list) => {
75
+ const out2 = {};
76
+ for (const m of measures) {
77
+ if (m.op === "count") {
78
+ out2[m.key] = list.length;
79
+ continue;
80
+ }
81
+ const nums = list.map((r) => r[m.of ?? ""]).filter((v) => typeof v === "number");
82
+ const sum = nums.reduce((a, b) => a + b, 0);
83
+ 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;
84
+ }
85
+ return out2;
86
+ };
87
+ const row = (kind, groups, list) => ({
88
+ kind,
89
+ groups,
90
+ measures: measure(list),
91
+ row_count: list.length,
92
+ prior_groups: null,
93
+ prior_measures: null,
94
+ prior_row_count: null,
95
+ delta: null,
96
+ compare: null,
97
+ distinct_groups: null,
98
+ labels: {},
99
+ says: null,
100
+ as_of: null
101
+ });
102
+ const out = [];
103
+ if (by.length) {
104
+ const groups = /* @__PURE__ */ new Map();
105
+ for (const r of rows) {
106
+ const g = {};
107
+ for (const k of by) {
108
+ const v = r[k];
109
+ g[k] = v === void 0 || v === "" ? null : v;
110
+ }
111
+ const key = JSON.stringify(g);
112
+ const hit = groups.get(key) ?? { groups: g, rows: [] };
113
+ hit.rows.push(r);
114
+ groups.set(key, hit);
115
+ }
116
+ const first = measures[0].key;
117
+ const answered = [...groups.values()].map((g) => row("group", g.groups, g.rows));
118
+ answered.sort((a, b) => Number(b["measures"][first] ?? 0) - Number(a["measures"][first] ?? 0));
119
+ out.push(...answered.slice(0, Number(question2["limit"] ?? 50)));
120
+ }
121
+ out.push(row("total", {}, rows));
122
+ return out;
123
+ }
124
+ function memoryEntityWrite(e, recordId, columns) {
125
+ const row = e.rows.find((r) => r["id"] === recordId);
126
+ if (!row) return refuse(`There is no ${e.label} you can open with that id.`, "02000");
127
+ const next = {};
128
+ for (const [k, v] of Object.entries(columns ?? {})) {
129
+ const c = e.columns.find((x) => x.api_name === k);
130
+ if (!c) return refuse(`This table has no column called "${k}".`);
131
+ if (c.lookup && !c.lookup.replaces) return refuse(`"${c.name}" is shown from another table, so it is changed there. Nothing was written.`);
132
+ if (!c.writable) return refuse(`"${c.name}" is not changed through this door.`, "42501");
133
+ if (c.type === "choice" && v !== null && v !== "") {
134
+ const word = (c.choices ?? []).find((w) => w === v) ?? (c.choices ?? []).find((w) => w.toLowerCase() === String(v).toLowerCase());
135
+ if (!word) return refuse(`"${String(v)}" is not one of the choices of "${c.name}".`, "22023", "The columns call lists its choices.");
136
+ next[k] = word;
137
+ } else next[k] = v === "" ? null : v;
138
+ }
139
+ const changed = Object.entries(next).some(([k, v]) => row[k] !== v);
140
+ Object.assign(row, next);
141
+ return ok({ id: recordId, organization_id: row["organization_id"] ?? null, changed, row });
142
+ }
143
+ function memoryEntityDoors(entities) {
144
+ const of = (args) => {
145
+ const src = args["p_source"];
146
+ return src && src["kind"] === "entity" ? entities.get(String(src["token"])) : void 0;
147
+ };
148
+ return {
149
+ drill_describe: (args) => {
150
+ const e = of(args);
151
+ return e ? ok(memoryEntityDescribe(e)) : null;
152
+ },
153
+ drill_ask: (args) => {
154
+ const e = of(args);
155
+ return e ? ok(memoryEntityAsk(e, args["p_question"] ?? {})) : null;
156
+ },
157
+ drill_rows: (args) => {
158
+ const e = of(args);
159
+ return e ? ok(memoryEntityRows(e, args["p_question"] ?? {})) : null;
160
+ },
161
+ entity_row_write: (args) => {
162
+ const e = entities.get(String(args["p_token"] ?? ""));
163
+ if (!e) return null;
164
+ return memoryEntityWrite(e, String(args["p_record_id"] ?? ""), args["p_columns"] ?? {});
165
+ }
166
+ };
167
+ }
168
+
1
169
  // src/templates/payloads.ts
2
170
  var ACTOR = "user";
3
171
  var COMPUTED = /* @__PURE__ */ new Set(["lookup", "rollup", "formula"]);
@@ -2226,10 +2394,10 @@ var PREVIEW_REFUSAL = {
2226
2394
  hint: null
2227
2395
  };
2228
2396
  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)(_|$)/;
2229
- function ok(data) {
2397
+ function ok2(data) {
2230
2398
  return { data, error: null };
2231
2399
  }
2232
- function refuse(message, code = "PGRST202") {
2400
+ function refuse2(message, code = "PGRST202") {
2233
2401
  return { data: null, error: { code, message, details: null, hint: null } };
2234
2402
  }
2235
2403
  function stamp(world) {
@@ -2414,16 +2582,11 @@ function groupValues(doc, stored, key) {
2414
2582
  const out = list.map((v) => v === void 0 || v === null || v === "" ? null : String(v));
2415
2583
  return out.length ? out : [null];
2416
2584
  }
2417
- function runBlock(world, subject, raw, canvas, index) {
2418
- const table = world.tableById.get(String(raw["table_id"] ?? subject));
2419
- const title = String(raw["title"] ?? `Block ${index + 1}`);
2420
- const base = { ...raw, title, table_id: table?.id ?? subject, ms: 0 };
2421
- if (!table) return { ...base, rows: [], refused: "This block's table is not in this template." };
2422
- if (raw["kind"] === "stuck") return { ...base, rows: [] };
2423
- const measures = (raw["measures"] ?? [{ op: "count" }]).map((m) => ({ op: String(m["op"] ?? "count"), key: m["key"] }));
2424
- const groupBy = (raw["group_by"] ?? []).slice();
2425
- const bucket = raw["bucket"] ?? null;
2426
- const ids = table.order.filter((id) => admits(world, table, id, raw["filter"]) && admits(world, table, id, canvas));
2585
+ function aggregateRows(world, table, ask) {
2586
+ const { groupBy, measures, bucket } = ask;
2587
+ const search = (ask.search ?? "").trim().toLowerCase();
2588
+ let ids = table.order.filter((id) => ask.filters.every((f) => admits(world, table, id, f)));
2589
+ if (search) ids = ids.filter((id) => JSON.stringify(readDocument(world, table, id)).toLowerCase().includes(search));
2427
2590
  const groups = /* @__PURE__ */ new Map();
2428
2591
  for (const id of ids) {
2429
2592
  const doc = readDocument(world, table, id);
@@ -2465,8 +2628,25 @@ function runBlock(world, subject, raw, canvas, index) {
2465
2628
  const k = `${bucket.key}_${bucket.by ?? "month"}`;
2466
2629
  rows.sort((a, b) => String(a.groups[k]).localeCompare(String(b.groups[k])));
2467
2630
  } else rows.sort((a, b) => (b.measures[primary] ?? 0) - (a.measures[primary] ?? 0));
2468
- const limit = Number(raw["limit"] ?? 0);
2631
+ const limit = Number(ask.limit ?? 0);
2469
2632
  if (limit > 0) rows = rows.slice(0, limit);
2633
+ return rows;
2634
+ }
2635
+ function measuresOf(raw) {
2636
+ const list = Array.isArray(raw) ? raw : [];
2637
+ const out = list.map((m) => ({ op: String(m["op"] ?? "count"), key: m["key"] }));
2638
+ return out.length ? out : [{ op: "count" }];
2639
+ }
2640
+ function runBlock(world, subject, raw, canvas, index) {
2641
+ const table = world.tableById.get(String(raw["table_id"] ?? subject));
2642
+ const title = String(raw["title"] ?? `Block ${index + 1}`);
2643
+ const base = { ...raw, title, table_id: table?.id ?? subject, ms: 0 };
2644
+ if (!table) return { ...base, rows: [], refused: "This block's table is not in this template." };
2645
+ if (raw["kind"] === "stuck") return { ...base, rows: [] };
2646
+ const measures = measuresOf(raw["measures"]);
2647
+ const groupBy = (raw["group_by"] ?? []).slice();
2648
+ const bucket = raw["bucket"] ?? null;
2649
+ const rows = aggregateRows(world, table, { groupBy, measures, bucket, filters: [raw["filter"], canvas], limit: Number(raw["limit"] ?? 0) });
2470
2650
  return { ...base, measures, group_by: groupBy, rows };
2471
2651
  }
2472
2652
  function bookingAvailability(world, raw) {
@@ -2553,55 +2733,55 @@ function memoryDataSource(world) {
2553
2733
  return typeof v === "string" ? v : v === void 0 || v === null ? null : String(v);
2554
2734
  };
2555
2735
  const doors = {
2556
- table_kernel_id: () => ok(MEMORY_KERNEL.table),
2557
- field_kernel_id: () => ok(MEMORY_KERNEL.field),
2558
- person_kernel_id: () => ok(MEMORY_KERNEL.person),
2559
- file_kernel_id: () => ok(MEMORY_KERNEL.file),
2560
- rule_kernel_id: () => ok(MEMORY_KERNEL.rule),
2561
- store_is_open: () => ok(true),
2736
+ table_kernel_id: () => ok2(MEMORY_KERNEL.table),
2737
+ field_kernel_id: () => ok2(MEMORY_KERNEL.field),
2738
+ person_kernel_id: () => ok2(MEMORY_KERNEL.person),
2739
+ file_kernel_id: () => ok2(MEMORY_KERNEL.file),
2740
+ rule_kernel_id: () => ok2(MEMORY_KERNEL.rule),
2741
+ store_is_open: () => ok2(true),
2562
2742
  read_records: (args) => {
2563
2743
  const limit = Number(args["p_limit"] ?? 50);
2564
2744
  const offset = Number(args["p_offset"] ?? 0);
2565
- if (args["p_table_id"] === MEMORY_KERNEL.table) return ok(world.tables.slice(offset, offset + limit).map(tableRow));
2745
+ if (args["p_table_id"] === MEMORY_KERNEL.table) return ok2(world.tables.slice(offset, offset + limit).map(tableRow));
2566
2746
  const t = tableOf(args);
2567
- if (!t) return ok([]);
2568
- return ok(t.order.slice(offset, offset + limit).map((id) => row(t, id)));
2747
+ if (!t) return ok2([]);
2748
+ return ok2(t.order.slice(offset, offset + limit).map((id) => row(t, id)));
2569
2749
  },
2570
2750
  read_records_by_ids: (args) => {
2571
2751
  const ids = args["p_record_ids"] ?? [];
2572
- if (args["p_table_id"] === MEMORY_KERNEL.table) return ok(world.tables.filter((t2) => ids.includes(t2.id)).map(tableRow));
2752
+ if (args["p_table_id"] === MEMORY_KERNEL.table) return ok2(world.tables.filter((t2) => ids.includes(t2.id)).map(tableRow));
2573
2753
  const t = tableOf(args);
2574
- if (!t) return ok([]);
2575
- return ok(ids.filter((id) => t.rows.has(id)).map((id) => row(t, id)));
2754
+ if (!t) return ok2([]);
2755
+ return ok2(ids.filter((id) => t.rows.has(id)).map((id) => row(t, id)));
2576
2756
  },
2577
2757
  read_records_matching: (args) => {
2578
2758
  const t = tableOf(args);
2579
- if (!t) return ok([]);
2759
+ if (!t) return ok2([]);
2580
2760
  const limit = Number(args["p_limit"] ?? 50);
2581
2761
  const offset = Number(args["p_offset"] ?? 0);
2582
- return ok(matching(world, t, args).slice(offset, offset + limit).map((id) => row(t, id)));
2762
+ return ok2(matching(world, t, args).slice(offset, offset + limit).map((id) => row(t, id)));
2583
2763
  },
2584
2764
  read_records_page: (args) => {
2585
2765
  const t = tableOf(args);
2586
2766
  const limit = Number(args["p_limit"] ?? 50);
2587
2767
  const offset = Number(args["p_offset"] ?? 0);
2588
- if (!t) return ok({ rows: [], total: 0, limit, offset, sort_ignored: [] });
2768
+ if (!t) return ok2({ rows: [], total: 0, limit, offset, sort_ignored: [] });
2589
2769
  const ids = matching(world, t, args);
2590
- return ok({ rows: ids.slice(offset, offset + limit).map((id) => row(t, id)), total: ids.length, limit, offset, sort_ignored: [] });
2770
+ return ok2({ rows: ids.slice(offset, offset + limit).map((id) => row(t, id)), total: ids.length, limit, offset, sort_ignored: [] });
2591
2771
  },
2592
2772
  read_record: (args) => {
2593
2773
  const id = String(args["p_record_id"] ?? "");
2594
2774
  const t = world.recordTable.get(id);
2595
- if (!t) return refuse("That record is not in this template.", "P0002");
2596
- return ok(readDocument(world, t, id));
2775
+ if (!t) return refuse2("That record is not in this template.", "P0002");
2776
+ return ok2(readDocument(world, t, id));
2597
2777
  },
2598
- record_headers: (args) => ok(
2778
+ record_headers: (args) => ok2(
2599
2779
  (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) }))
2600
2780
  ),
2601
2781
  applicable_fields: (args) => {
2602
2782
  const t = tableOf(args);
2603
- if (!t) return ok([]);
2604
- return ok(
2783
+ if (!t) return ok2([]);
2784
+ return ok2(
2605
2785
  t.fields.map((f) => ({
2606
2786
  id: f.id,
2607
2787
  data: f.data,
@@ -2620,9 +2800,9 @@ function memoryDataSource(world) {
2620
2800
  },
2621
2801
  field_options: (args) => {
2622
2802
  const hit = fieldById(args["p_field_id"]);
2623
- if (!hit) return ok([]);
2803
+ if (!hit) return ok2([]);
2624
2804
  const opts = hit.t.options.get(String(hit.f.data["key"])) ?? [];
2625
- return ok(
2805
+ return ok2(
2626
2806
  opts.map((o, i) => ({
2627
2807
  id: o.id,
2628
2808
  organization_id: world.organization_id,
@@ -2638,46 +2818,46 @@ function memoryDataSource(world) {
2638
2818
  }))
2639
2819
  );
2640
2820
  },
2641
- my_levels: (args) => ok((args["p_ids"] ?? []).map((id) => ({ id, level: VIEWER }))),
2642
- views: (args) => ok(
2821
+ my_levels: (args) => ok2((args["p_ids"] ?? []).map((id) => ({ id, level: VIEWER }))),
2822
+ views: (args) => ok2(
2643
2823
  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` }))
2644
2824
  ),
2645
2825
  view_look_read: (args) => {
2646
2826
  const out = {};
2647
2827
  for (const [key, look] of looks) if (key.startsWith(`${String(args["p_table_id"])}:`)) out[key.split(":")[1]] = look;
2648
- return ok(out);
2828
+ return ok2(out);
2649
2829
  },
2650
2830
  view_look_set: (args) => {
2651
2831
  const key = `${String(args["p_table_id"])}:${String(args["p_view_id"])}`;
2652
2832
  const look = args["p_look"] ?? null;
2653
2833
  if (look) looks.set(key, { look, updated_at: (/* @__PURE__ */ new Date()).toISOString() });
2654
2834
  else looks.delete(key);
2655
- return ok(look);
2835
+ return ok2(look);
2656
2836
  },
2657
- grid_layout: (args) => ok({
2837
+ grid_layout: (args) => ok2({
2658
2838
  layout: { mode: "auto", wrap: false, row_height: "normal", fit_max_columns: 8, freeze_first_column: false },
2659
2839
  source: { mode: "platform", wrap: "platform", row_height: "platform", fit_max_columns: "platform", freeze_first_column: "platform" },
2660
2840
  refused: [],
2661
2841
  view_id: args["p_view_id"] ?? null
2662
2842
  }),
2663
- table_decorations: () => ok({ rows: {}, cells: {}, rules: [], stale: [], colors: ["slate", "green", "amber", "red", "blue", "violet", "teal"], columns: {}, version: 1, rule_ops: [] }),
2664
- row_actions: () => ok({ stale: [], actions: [] }),
2665
- table_stage_field: (args) => ok(world.stageField.get(String(args["p_table_id"])) ?? null),
2843
+ table_decorations: () => ok2({ rows: {}, cells: {}, rules: [], stale: [], colors: ["slate", "green", "amber", "red", "blue", "violet", "teal"], columns: {}, version: 1, rule_ops: [] }),
2844
+ row_actions: () => ok2({ stale: [], actions: [] }),
2845
+ table_stage_field: (args) => ok2(world.stageField.get(String(args["p_table_id"])) ?? null),
2666
2846
  pipeline_read: (args) => {
2667
2847
  const t = tableOf(args);
2668
2848
  const stage = t ? world.stageField.get(t.id) : void 0;
2669
2849
  const field = stage ? t.fields.find((f) => f.data["key"] === stage) : void 0;
2670
- if (!t || !stage || !field) return ok({ is_pipeline: false, why: "This table has no stage column." });
2850
+ if (!t || !stage || !field) return ok2({ is_pipeline: false, why: "This table has no stage column." });
2671
2851
  const stages = (t.options.get(stage) ?? []).map((o) => ({ key: o.key, label: o.label, retired: false }));
2672
- return ok({ is_pipeline: true, table_id: t.id, stage_field: stage, field_id: field.id, stage_label: field.data["label"], stages, rules: [] });
2852
+ return ok2({ is_pipeline: true, table_id: t.id, stage_field: stage, field_id: field.id, stage_label: field.data["label"], stages, rules: [] });
2673
2853
  },
2674
2854
  pipeline_board: (args) => {
2675
2855
  const t = tableOf(args);
2676
2856
  const stage = t ? world.stageField.get(t.id) : void 0;
2677
- if (!t || !stage) return ok([]);
2857
+ if (!t || !stage) return ok2([]);
2678
2858
  const measure = typeof args["p_measure"] === "string" ? args["p_measure"] : null;
2679
2859
  const ids = t.order.filter((id) => admits(world, t, id, args["p_filter"]));
2680
- return ok(
2860
+ return ok2(
2681
2861
  (t.options.get(stage) ?? []).map((o, i) => {
2682
2862
  const inIt = ids.filter((id) => t.rows.get(id)[stage] === o.key);
2683
2863
  const total = measure ? inIt.reduce((sum, id) => sum + (typeof t.rows.get(id)[measure] === "number" ? t.rows.get(id)[measure] : 0), 0) : null;
@@ -2685,7 +2865,7 @@ function memoryDataSource(world) {
2685
2865
  })
2686
2866
  );
2687
2867
  },
2688
- relation_words_many: (args) => ok((args["p_record_ids"] ?? []).map((id) => ({ record_id: id, words: nameOf(id), archived: false }))),
2868
+ relation_words_many: (args) => ok2((args["p_record_ids"] ?? []).map((id) => ({ record_id: id, words: nameOf(id), archived: false }))),
2689
2869
  reverse_columns: (args) => {
2690
2870
  const target = String(args["p_table_id"] ?? "");
2691
2871
  const out = [];
@@ -2705,11 +2885,11 @@ function memoryDataSource(world) {
2705
2885
  });
2706
2886
  }
2707
2887
  }
2708
- return ok(out);
2888
+ return ok2(out);
2709
2889
  },
2710
2890
  reverse_links_many: (args) => {
2711
2891
  const hit = fieldById(args["p_field_id"]);
2712
- return ok(
2892
+ return ok2(
2713
2893
  (args["p_record_ids"] ?? []).map((rid) => {
2714
2894
  if (!hit) return { record_id: rid, total: 0, links: [] };
2715
2895
  const key = String(hit.f.data["key"]);
@@ -2723,28 +2903,28 @@ function memoryDataSource(world) {
2723
2903
  },
2724
2904
  // A template's own record has no history, comments, checklists or worked-out provenance yet:
2725
2905
  // these are the answers an installed template gives on its first day.
2726
- record_history: () => ok([]),
2727
- field_history: () => ok([]),
2728
- computed_provenance: () => ok([]),
2729
- comment_thread: (args) => ok({ record_id: args["p_record_id"] ?? null, comments: [], may_comment: false, may_resolve: false }),
2730
- checklist_runs: () => ok([]),
2731
- checklist_templates: () => ok([]),
2732
- doc_templates: () => ok([]),
2733
- doc_renders: () => ok([]),
2734
- doc_signatures: () => ok([]),
2735
- record_row_controls: () => ok({
2906
+ record_history: () => ok2([]),
2907
+ field_history: () => ok2([]),
2908
+ computed_provenance: () => ok2([]),
2909
+ comment_thread: (args) => ok2({ record_id: args["p_record_id"] ?? null, comments: [], may_comment: false, may_resolve: false }),
2910
+ checklist_runs: () => ok2([]),
2911
+ checklist_templates: () => ok2([]),
2912
+ doc_templates: () => ok2([]),
2913
+ doc_renders: () => ok2([]),
2914
+ doc_signatures: () => ok2([]),
2915
+ record_row_controls: () => ok2({
2736
2916
  shown_to: { value: null, effective: "everyone", type_default: null, table_default: null, allowed: ["everyone"], is_child: false, can_change: false },
2737
2917
  published_to_web: { available: false, value: false, table_default: null, can_change: false },
2738
2918
  indexed: { available: false, value: null, table_default: null, effective: false, can_change: false }
2739
2919
  }),
2740
- enrich_cells: () => ok([]),
2741
- enrichments: () => ok([]),
2742
- pipeline_pending: () => ok([]),
2743
- record_stage_pending: () => ok([]),
2744
- io_imports: () => ok([]),
2745
- relation_words: (args) => ok(nameOf(String(args["p_value"] ?? ""))),
2920
+ enrich_cells: () => ok2([]),
2921
+ enrichments: () => ok2([]),
2922
+ pipeline_pending: () => ok2([]),
2923
+ record_stage_pending: () => ok2([]),
2924
+ io_imports: () => ok2([]),
2925
+ relation_words: (args) => ok2(nameOf(String(args["p_value"] ?? ""))),
2746
2926
  // The read doors a form, a booking page and a dashboard are drawn from (lane CHAIR-GALLERY-3).
2747
- forms: (args) => ok(
2927
+ forms: (args) => ok2(
2748
2928
  world.forms.filter((f) => !f.booking && (!args["p_table_id"] || f.table_id === args["p_table_id"])).map((f) => ({
2749
2929
  form_id: f.form_id,
2750
2930
  table_id: f.table_id,
@@ -2767,7 +2947,7 @@ function memoryDataSource(world) {
2767
2947
  form_preview_asks: (args) => {
2768
2948
  const t = tableOf(args);
2769
2949
  const values = args["p_values"] ?? {};
2770
- return ok(
2950
+ return ok2(
2771
2951
  (args["p_questions"] ?? []).map((q) => {
2772
2952
  const when = q["show_if"] ?? q["showIf"] ?? null;
2773
2953
  const asked = !when || !t ? true : rule(t, values, values, when) !== false;
@@ -2775,7 +2955,7 @@ function memoryDataSource(world) {
2775
2955
  })
2776
2956
  );
2777
2957
  },
2778
- bookings: (args) => ok(
2958
+ bookings: (args) => ok2(
2779
2959
  world.forms.filter((f) => f.booking && (!args["p_table_id"] || f.table_id === args["p_table_id"])).map((f) => ({
2780
2960
  form_id: f.form_id,
2781
2961
  table_id: f.table_id,
@@ -2797,9 +2977,9 @@ function memoryDataSource(world) {
2797
2977
  booking_public: (args) => {
2798
2978
  const f = world.forms.find((x) => x.form_id === args["p_form_id"] && x.booking);
2799
2979
  const t = f ? world.tableById.get(f.table_id) : void 0;
2800
- if (!f || !t) return refuse("That booking page is not in this template.", "P0002");
2980
+ if (!f || !t) return refuse2("That booking page is not in this template.", "P0002");
2801
2981
  const availability = bookingAvailability(world, f.booking);
2802
- return ok({
2982
+ return ok2({
2803
2983
  form_id: f.form_id,
2804
2984
  organization_id: world.organization_id,
2805
2985
  table_id: t.id,
@@ -2813,7 +2993,7 @@ function memoryDataSource(world) {
2813
2993
  message: null
2814
2994
  });
2815
2995
  },
2816
- dashboards: (args) => ok(
2996
+ dashboards: (args) => ok2(
2817
2997
  world.dashboards.filter((d) => !args["p_table_id"] || d.table_id === args["p_table_id"]).map((d) => ({
2818
2998
  dashboard_id: d.dashboard_id,
2819
2999
  table_id: d.table_id,
@@ -2828,9 +3008,9 @@ function memoryDataSource(world) {
2828
3008
  ),
2829
3009
  dashboard_run: (args) => {
2830
3010
  const d = world.dashboards.find((x) => x.dashboard_id === args["p_dashboard_id"]);
2831
- if (!d) return refuse("That dashboard is not in this template.", "P0002");
3011
+ if (!d) return refuse2("That dashboard is not in this template.", "P0002");
2832
3012
  const canvas = args["p_filter"] ?? {};
2833
- return ok({
3013
+ return ok2({
2834
3014
  dashboard_id: d.dashboard_id,
2835
3015
  name: d.name,
2836
3016
  subject_table_id: d.table_id,
@@ -2842,16 +3022,34 @@ function memoryDataSource(world) {
2842
3022
  blocks: d.blocks.map((b, i) => runBlock(world, d.table_id, b, canvas, i))
2843
3023
  });
2844
3024
  },
2845
- dashboard_stuck: () => ok([])
3025
+ dashboard_stuck: () => ok2([]),
3026
+ // THE EIGHTH VERB, in memory: the same groups and measures as the live `custom.record_aggregate`
3027
+ // (and its row shape: no comparison asked ⇒ every prior_* null).
3028
+ record_aggregate: (args) => {
3029
+ const t = tableOf(args);
3030
+ if (!t) return refuse2("That table is not in this template.", "P0002");
3031
+ const rows = aggregateRows(world, t, {
3032
+ groupBy: (args["p_group_by"] ?? []).slice(),
3033
+ measures: measuresOf(args["p_measures"]),
3034
+ bucket: args["p_bucket"] ?? null,
3035
+ filters: [args["p_filter"]],
3036
+ ...typeof args["p_search"] === "string" ? { search: args["p_search"] } : {},
3037
+ limit: Number(args["p_limit"] ?? 200)
3038
+ });
3039
+ return ok2(rows.map((r) => ({ ...r, prior_groups: null, prior_measures: null, prior_row_count: null, delta: null, compare: null })));
3040
+ }
2846
3041
  };
2847
3042
  const source = {
2848
3043
  world,
2849
3044
  rpc(fn, args) {
2850
- const name = fn.replace(/^custom\./, "");
3045
+ const name = fn.replace(/^custom\./, "").replace(/^platform\./, "");
3046
+ const entityDoor = world.entities ? memoryEntityDoors(world.entities)[name] : void 0;
3047
+ const fromEntity = entityDoor ? entityDoor(args ?? {}) : null;
3048
+ if (fromEntity) return Promise.resolve(fromEntity);
2851
3049
  const door = doors[name];
2852
3050
  if (door) return Promise.resolve(door(args ?? {}));
2853
3051
  if (WRITE_DOOR.test(name)) return Promise.resolve({ data: null, error: PREVIEW_REFUSAL });
2854
- return Promise.resolve(refuse(`A template preview does not answer ${name}.`));
3052
+ return Promise.resolve(refuse2(`A template preview does not answer ${name}.`));
2855
3053
  },
2856
3054
  schema() {
2857
3055
  return {
@@ -2865,6 +3063,12 @@ function memoryDataSource(world) {
2865
3063
  }
2866
3064
 
2867
3065
  // src/memory/index.ts
3066
+ function withEntities(world, specs) {
3067
+ const entities = new Map(world.entities ?? []);
3068
+ for (const s of specs) entities.set(s.token, { ...s, rows: s.rows.map((r) => ({ ...r })) });
3069
+ world.entities = entities;
3070
+ return world;
3071
+ }
2868
3072
  function templatePreview(spec, options = {}) {
2869
3073
  const world = templateWorld(spec, options);
2870
3074
  const config = {
@@ -2880,12 +3084,39 @@ function templatePreview(spec, options = {}) {
2880
3084
  dashboards: world.dashboards.map((d) => ({ id: d.dashboard_id, token: d.token, name: d.name, tableId: d.table_id }))
2881
3085
  };
2882
3086
  }
3087
+ function entityStore(specs, options = {}) {
3088
+ const world = {
3089
+ organization_id: stableId("entity-store-organization"),
3090
+ user_id: stableId("entity-store-person"),
3091
+ timezone: "UTC",
3092
+ today: options.today ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
3093
+ businessName: "",
3094
+ tables: [],
3095
+ tableById: /* @__PURE__ */ new Map(),
3096
+ views: [],
3097
+ forms: [],
3098
+ dashboards: [],
3099
+ recordTable: /* @__PURE__ */ new Map(),
3100
+ stageField: /* @__PURE__ */ new Map()
3101
+ };
3102
+ withEntities(world, specs);
3103
+ return {
3104
+ world,
3105
+ config: { dataSource: memoryDataSource(world), actor: { actor: "user", user_id: world.user_id }, organizationId: world.organization_id }
3106
+ };
3107
+ }
2883
3108
  export {
2884
3109
  MEMORY_KERNEL,
2885
3110
  PREVIEW_REFUSAL,
3111
+ entityStore,
2886
3112
  memoryDataSource,
3113
+ memoryEntityAsk,
3114
+ memoryEntityDescribe,
3115
+ memoryEntityRows,
3116
+ memoryEntityWrite,
2887
3117
  stableId,
2888
3118
  templatePreview,
2889
- templateWorld
3119
+ templateWorld,
3120
+ withEntities
2890
3121
  };
2891
3122
  //# sourceMappingURL=index.js.map