@ai-matrx/records 0.73.3 → 0.73.4

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.
@@ -3,6 +3,52 @@ import { T as TemplateSpec } from '../types-F7PcHWG_.cjs';
3
3
  import { P as PgError } from '../field-CugcsP2y.cjs';
4
4
  import '../types-DT_Sk6qp.cjs';
5
5
 
6
+ type Json$1 = Record<string, unknown>;
7
+ /** One presented column, exactly as `platform.drill_describe(...).api.columns[]` says it. */
8
+ interface MemoryEntityColumn {
9
+ api_name: string;
10
+ name: string;
11
+ /** "text" | "number" | "choice" | "date" | "datetime" | "checkbox" | … */
12
+ type: string;
13
+ writable?: boolean;
14
+ choices?: string[];
15
+ /** A column shown as the words another table holds (`replaces`: the id column itself). */
16
+ lookup?: {
17
+ via: string;
18
+ token: string;
19
+ column: string;
20
+ replaces: boolean;
21
+ };
22
+ }
23
+ interface MemoryEntitySpec {
24
+ token: string;
25
+ label: string;
26
+ columns: MemoryEntityColumn[];
27
+ /** Rows as the door returns them: `id` plus api_name → value, lookups and choices in words. */
28
+ rows: Json$1[];
29
+ }
30
+ interface MemoryEntity extends MemoryEntitySpec {
31
+ rows: Json$1[];
32
+ }
33
+ type Answer = {
34
+ data: unknown;
35
+ error: null;
36
+ } | {
37
+ data: null;
38
+ error: {
39
+ code: string;
40
+ message: string;
41
+ details: null;
42
+ hint: string | null;
43
+ };
44
+ };
45
+ /** What `drill_describe` answers for a memory entity (dimensions from its choices, count + sums). */
46
+ declare function memoryEntityDescribe(e: MemoryEntity): Json$1;
47
+ /** What `drill_rows` answers: one page, rows in words. */
48
+ declare function memoryEntityRows(e: MemoryEntity, question: Json$1): Json$1;
49
+ /** What `entity_row_write` does: a choice by its word (any case), refused in the door's words otherwise. */
50
+ declare function memoryEntityWrite(e: MemoryEntity, recordId: string, columns: Json$1): Answer;
51
+
6
52
  type Json = Record<string, unknown>;
7
53
  /** The ids the store's kernel tables carry everywhere (custom.table_kernel_id() and its siblings). */
8
54
  declare const MEMORY_KERNEL: {
@@ -78,6 +124,8 @@ interface MemoryWorld {
78
124
  recordTable: Map<string, MemoryTable>;
79
125
  /** Table id → the field key that is its pipeline stage. */
80
126
  stageField: Map<string, string>;
127
+ /** Standard sources (`{ kind: "entity", token }`) this world answers — `withEntities`. */
128
+ entities?: Map<string, MemoryEntity>;
81
129
  }
82
130
  interface TemplateWorldOptions {
83
131
  /** "YYYY-MM-DD" the template's relative dates resolve against. Default: today (UTC). */
@@ -95,6 +143,9 @@ declare function memoryDataSource(world: MemoryWorld): RecordsDataSource & {
95
143
  readonly world: MemoryWorld;
96
144
  };
97
145
 
146
+ /** Add standard sources (`{ kind: "entity", token }`) to a memory world, in the live doors' shapes. */
147
+ declare function withEntities(world: MemoryWorld, specs: MemoryEntitySpec[]): MemoryWorld;
148
+
98
149
  interface TemplatePreview {
99
150
  /** Mount this: `<RecordsMount config={preview.config}>` / `<RecordsProvider config={preview.config}>`. */
100
151
  config: RecordsConfig;
@@ -122,5 +173,12 @@ interface TemplatePreview {
122
173
  }
123
174
  /** A template, installed into memory and ready to mount. Pure for a given spec and day. */
124
175
  declare function templatePreview(spec: TemplateSpec, options?: TemplateWorldOptions): TemplatePreview;
176
+ /** A memory store holding only standard sources — mount it like a template preview. */
177
+ declare function entityStore(specs: MemoryEntitySpec[], options?: {
178
+ today?: string;
179
+ }): {
180
+ config: RecordsConfig;
181
+ world: MemoryWorld;
182
+ };
125
183
 
126
- export { MEMORY_KERNEL, type MemoryDashboard, type MemoryField, type MemoryForm, type MemoryOption, type MemoryTable, type MemoryView, type MemoryWorld, PREVIEW_REFUSAL, type TemplatePreview, type TemplateWorldOptions, memoryDataSource, stableId, templatePreview, templateWorld };
184
+ export { MEMORY_KERNEL, type MemoryDashboard, type MemoryEntity, type MemoryEntityColumn, type MemoryEntitySpec, type MemoryField, type MemoryForm, type MemoryOption, type MemoryTable, type MemoryView, type MemoryWorld, PREVIEW_REFUSAL, type TemplatePreview, type TemplateWorldOptions, entityStore, memoryDataSource, memoryEntityDescribe, memoryEntityRows, memoryEntityWrite, stableId, templatePreview, templateWorld, withEntities };
@@ -3,6 +3,52 @@ import { T as TemplateSpec } from '../types-DL7FEBKg.js';
3
3
  import { P as PgError } from '../field-CugcsP2y.js';
4
4
  import '../types-DZgTYEVh.js';
5
5
 
6
+ type Json$1 = Record<string, unknown>;
7
+ /** One presented column, exactly as `platform.drill_describe(...).api.columns[]` says it. */
8
+ interface MemoryEntityColumn {
9
+ api_name: string;
10
+ name: string;
11
+ /** "text" | "number" | "choice" | "date" | "datetime" | "checkbox" | … */
12
+ type: string;
13
+ writable?: boolean;
14
+ choices?: string[];
15
+ /** A column shown as the words another table holds (`replaces`: the id column itself). */
16
+ lookup?: {
17
+ via: string;
18
+ token: string;
19
+ column: string;
20
+ replaces: boolean;
21
+ };
22
+ }
23
+ interface MemoryEntitySpec {
24
+ token: string;
25
+ label: string;
26
+ columns: MemoryEntityColumn[];
27
+ /** Rows as the door returns them: `id` plus api_name → value, lookups and choices in words. */
28
+ rows: Json$1[];
29
+ }
30
+ interface MemoryEntity extends MemoryEntitySpec {
31
+ rows: Json$1[];
32
+ }
33
+ type Answer = {
34
+ data: unknown;
35
+ error: null;
36
+ } | {
37
+ data: null;
38
+ error: {
39
+ code: string;
40
+ message: string;
41
+ details: null;
42
+ hint: string | null;
43
+ };
44
+ };
45
+ /** What `drill_describe` answers for a memory entity (dimensions from its choices, count + sums). */
46
+ declare function memoryEntityDescribe(e: MemoryEntity): Json$1;
47
+ /** What `drill_rows` answers: one page, rows in words. */
48
+ declare function memoryEntityRows(e: MemoryEntity, question: Json$1): Json$1;
49
+ /** What `entity_row_write` does: a choice by its word (any case), refused in the door's words otherwise. */
50
+ declare function memoryEntityWrite(e: MemoryEntity, recordId: string, columns: Json$1): Answer;
51
+
6
52
  type Json = Record<string, unknown>;
7
53
  /** The ids the store's kernel tables carry everywhere (custom.table_kernel_id() and its siblings). */
8
54
  declare const MEMORY_KERNEL: {
@@ -78,6 +124,8 @@ interface MemoryWorld {
78
124
  recordTable: Map<string, MemoryTable>;
79
125
  /** Table id → the field key that is its pipeline stage. */
80
126
  stageField: Map<string, string>;
127
+ /** Standard sources (`{ kind: "entity", token }`) this world answers — `withEntities`. */
128
+ entities?: Map<string, MemoryEntity>;
81
129
  }
82
130
  interface TemplateWorldOptions {
83
131
  /** "YYYY-MM-DD" the template's relative dates resolve against. Default: today (UTC). */
@@ -95,6 +143,9 @@ declare function memoryDataSource(world: MemoryWorld): RecordsDataSource & {
95
143
  readonly world: MemoryWorld;
96
144
  };
97
145
 
146
+ /** Add standard sources (`{ kind: "entity", token }`) to a memory world, in the live doors' shapes. */
147
+ declare function withEntities(world: MemoryWorld, specs: MemoryEntitySpec[]): MemoryWorld;
148
+
98
149
  interface TemplatePreview {
99
150
  /** Mount this: `<RecordsMount config={preview.config}>` / `<RecordsProvider config={preview.config}>`. */
100
151
  config: RecordsConfig;
@@ -122,5 +173,12 @@ interface TemplatePreview {
122
173
  }
123
174
  /** A template, installed into memory and ready to mount. Pure for a given spec and day. */
124
175
  declare function templatePreview(spec: TemplateSpec, options?: TemplateWorldOptions): TemplatePreview;
176
+ /** A memory store holding only standard sources — mount it like a template preview. */
177
+ declare function entityStore(specs: MemoryEntitySpec[], options?: {
178
+ today?: string;
179
+ }): {
180
+ config: RecordsConfig;
181
+ world: MemoryWorld;
182
+ };
125
183
 
126
- export { MEMORY_KERNEL, type MemoryDashboard, type MemoryField, type MemoryForm, type MemoryOption, type MemoryTable, type MemoryView, type MemoryWorld, PREVIEW_REFUSAL, type TemplatePreview, type TemplateWorldOptions, memoryDataSource, stableId, templatePreview, templateWorld };
184
+ export { MEMORY_KERNEL, type MemoryDashboard, type MemoryEntity, type MemoryEntityColumn, type MemoryEntitySpec, type MemoryField, type MemoryForm, type MemoryOption, type MemoryTable, type MemoryView, type MemoryWorld, PREVIEW_REFUSAL, type TemplatePreview, type TemplateWorldOptions, entityStore, memoryDataSource, memoryEntityDescribe, memoryEntityRows, memoryEntityWrite, stableId, templatePreview, templateWorld, withEntities };
@@ -1,3 +1,112 @@
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 memoryEntityWrite(e, recordId, columns) {
70
+ const row = e.rows.find((r) => r["id"] === recordId);
71
+ if (!row) return refuse(`There is no ${e.label} you can open with that id.`, "02000");
72
+ const next = {};
73
+ for (const [k, v] of Object.entries(columns ?? {})) {
74
+ const c = e.columns.find((x) => x.api_name === k);
75
+ if (!c) return refuse(`This table has no column called "${k}".`);
76
+ if (c.lookup && !c.lookup.replaces) return refuse(`"${c.name}" is shown from another table, so it is changed there. Nothing was written.`);
77
+ if (!c.writable) return refuse(`"${c.name}" is not changed through this door.`, "42501");
78
+ if (c.type === "choice" && v !== null && v !== "") {
79
+ const word = (c.choices ?? []).find((w) => w === v) ?? (c.choices ?? []).find((w) => w.toLowerCase() === String(v).toLowerCase());
80
+ if (!word) return refuse(`"${String(v)}" is not one of the choices of "${c.name}".`, "22023", "The columns call lists its choices.");
81
+ next[k] = word;
82
+ } else next[k] = v === "" ? null : v;
83
+ }
84
+ const changed = Object.entries(next).some(([k, v]) => row[k] !== v);
85
+ Object.assign(row, next);
86
+ return ok({ id: recordId, organization_id: row["organization_id"] ?? null, changed, row });
87
+ }
88
+ function memoryEntityDoors(entities) {
89
+ const of = (args) => {
90
+ const src = args["p_source"];
91
+ return src && src["kind"] === "entity" ? entities.get(String(src["token"])) : void 0;
92
+ };
93
+ return {
94
+ drill_describe: (args) => {
95
+ const e = of(args);
96
+ return e ? ok(memoryEntityDescribe(e)) : null;
97
+ },
98
+ drill_rows: (args) => {
99
+ const e = of(args);
100
+ return e ? ok(memoryEntityRows(e, args["p_question"] ?? {})) : null;
101
+ },
102
+ entity_row_write: (args) => {
103
+ const e = entities.get(String(args["p_token"] ?? ""));
104
+ if (!e) return null;
105
+ return memoryEntityWrite(e, String(args["p_record_id"] ?? ""), args["p_columns"] ?? {});
106
+ }
107
+ };
108
+ }
109
+
1
110
  // src/templates/payloads.ts
2
111
  var ACTOR = "user";
3
112
  var COMPUTED = /* @__PURE__ */ new Set(["lookup", "rollup", "formula"]);
@@ -2226,10 +2335,10 @@ var PREVIEW_REFUSAL = {
2226
2335
  hint: null
2227
2336
  };
2228
2337
  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) {
2338
+ function ok2(data) {
2230
2339
  return { data, error: null };
2231
2340
  }
2232
- function refuse(message, code = "PGRST202") {
2341
+ function refuse2(message, code = "PGRST202") {
2233
2342
  return { data: null, error: { code, message, details: null, hint: null } };
2234
2343
  }
2235
2344
  function stamp(world) {
@@ -2553,55 +2662,55 @@ function memoryDataSource(world) {
2553
2662
  return typeof v === "string" ? v : v === void 0 || v === null ? null : String(v);
2554
2663
  };
2555
2664
  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),
2665
+ table_kernel_id: () => ok2(MEMORY_KERNEL.table),
2666
+ field_kernel_id: () => ok2(MEMORY_KERNEL.field),
2667
+ person_kernel_id: () => ok2(MEMORY_KERNEL.person),
2668
+ file_kernel_id: () => ok2(MEMORY_KERNEL.file),
2669
+ rule_kernel_id: () => ok2(MEMORY_KERNEL.rule),
2670
+ store_is_open: () => ok2(true),
2562
2671
  read_records: (args) => {
2563
2672
  const limit = Number(args["p_limit"] ?? 50);
2564
2673
  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));
2674
+ if (args["p_table_id"] === MEMORY_KERNEL.table) return ok2(world.tables.slice(offset, offset + limit).map(tableRow));
2566
2675
  const t = tableOf(args);
2567
- if (!t) return ok([]);
2568
- return ok(t.order.slice(offset, offset + limit).map((id) => row(t, id)));
2676
+ if (!t) return ok2([]);
2677
+ return ok2(t.order.slice(offset, offset + limit).map((id) => row(t, id)));
2569
2678
  },
2570
2679
  read_records_by_ids: (args) => {
2571
2680
  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));
2681
+ if (args["p_table_id"] === MEMORY_KERNEL.table) return ok2(world.tables.filter((t2) => ids.includes(t2.id)).map(tableRow));
2573
2682
  const t = tableOf(args);
2574
- if (!t) return ok([]);
2575
- return ok(ids.filter((id) => t.rows.has(id)).map((id) => row(t, id)));
2683
+ if (!t) return ok2([]);
2684
+ return ok2(ids.filter((id) => t.rows.has(id)).map((id) => row(t, id)));
2576
2685
  },
2577
2686
  read_records_matching: (args) => {
2578
2687
  const t = tableOf(args);
2579
- if (!t) return ok([]);
2688
+ if (!t) return ok2([]);
2580
2689
  const limit = Number(args["p_limit"] ?? 50);
2581
2690
  const offset = Number(args["p_offset"] ?? 0);
2582
- return ok(matching(world, t, args).slice(offset, offset + limit).map((id) => row(t, id)));
2691
+ return ok2(matching(world, t, args).slice(offset, offset + limit).map((id) => row(t, id)));
2583
2692
  },
2584
2693
  read_records_page: (args) => {
2585
2694
  const t = tableOf(args);
2586
2695
  const limit = Number(args["p_limit"] ?? 50);
2587
2696
  const offset = Number(args["p_offset"] ?? 0);
2588
- if (!t) return ok({ rows: [], total: 0, limit, offset, sort_ignored: [] });
2697
+ if (!t) return ok2({ rows: [], total: 0, limit, offset, sort_ignored: [] });
2589
2698
  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: [] });
2699
+ return ok2({ rows: ids.slice(offset, offset + limit).map((id) => row(t, id)), total: ids.length, limit, offset, sort_ignored: [] });
2591
2700
  },
2592
2701
  read_record: (args) => {
2593
2702
  const id = String(args["p_record_id"] ?? "");
2594
2703
  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));
2704
+ if (!t) return refuse2("That record is not in this template.", "P0002");
2705
+ return ok2(readDocument(world, t, id));
2597
2706
  },
2598
- record_headers: (args) => ok(
2707
+ record_headers: (args) => ok2(
2599
2708
  (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
2709
  ),
2601
2710
  applicable_fields: (args) => {
2602
2711
  const t = tableOf(args);
2603
- if (!t) return ok([]);
2604
- return ok(
2712
+ if (!t) return ok2([]);
2713
+ return ok2(
2605
2714
  t.fields.map((f) => ({
2606
2715
  id: f.id,
2607
2716
  data: f.data,
@@ -2620,9 +2729,9 @@ function memoryDataSource(world) {
2620
2729
  },
2621
2730
  field_options: (args) => {
2622
2731
  const hit = fieldById(args["p_field_id"]);
2623
- if (!hit) return ok([]);
2732
+ if (!hit) return ok2([]);
2624
2733
  const opts = hit.t.options.get(String(hit.f.data["key"])) ?? [];
2625
- return ok(
2734
+ return ok2(
2626
2735
  opts.map((o, i) => ({
2627
2736
  id: o.id,
2628
2737
  organization_id: world.organization_id,
@@ -2638,46 +2747,46 @@ function memoryDataSource(world) {
2638
2747
  }))
2639
2748
  );
2640
2749
  },
2641
- my_levels: (args) => ok((args["p_ids"] ?? []).map((id) => ({ id, level: VIEWER }))),
2642
- views: (args) => ok(
2750
+ my_levels: (args) => ok2((args["p_ids"] ?? []).map((id) => ({ id, level: VIEWER }))),
2751
+ views: (args) => ok2(
2643
2752
  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
2753
  ),
2645
2754
  view_look_read: (args) => {
2646
2755
  const out = {};
2647
2756
  for (const [key, look] of looks) if (key.startsWith(`${String(args["p_table_id"])}:`)) out[key.split(":")[1]] = look;
2648
- return ok(out);
2757
+ return ok2(out);
2649
2758
  },
2650
2759
  view_look_set: (args) => {
2651
2760
  const key = `${String(args["p_table_id"])}:${String(args["p_view_id"])}`;
2652
2761
  const look = args["p_look"] ?? null;
2653
2762
  if (look) looks.set(key, { look, updated_at: (/* @__PURE__ */ new Date()).toISOString() });
2654
2763
  else looks.delete(key);
2655
- return ok(look);
2764
+ return ok2(look);
2656
2765
  },
2657
- grid_layout: (args) => ok({
2766
+ grid_layout: (args) => ok2({
2658
2767
  layout: { mode: "auto", wrap: false, row_height: "normal", fit_max_columns: 8, freeze_first_column: false },
2659
2768
  source: { mode: "platform", wrap: "platform", row_height: "platform", fit_max_columns: "platform", freeze_first_column: "platform" },
2660
2769
  refused: [],
2661
2770
  view_id: args["p_view_id"] ?? null
2662
2771
  }),
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),
2772
+ table_decorations: () => ok2({ rows: {}, cells: {}, rules: [], stale: [], colors: ["slate", "green", "amber", "red", "blue", "violet", "teal"], columns: {}, version: 1, rule_ops: [] }),
2773
+ row_actions: () => ok2({ stale: [], actions: [] }),
2774
+ table_stage_field: (args) => ok2(world.stageField.get(String(args["p_table_id"])) ?? null),
2666
2775
  pipeline_read: (args) => {
2667
2776
  const t = tableOf(args);
2668
2777
  const stage = t ? world.stageField.get(t.id) : void 0;
2669
2778
  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." });
2779
+ if (!t || !stage || !field) return ok2({ is_pipeline: false, why: "This table has no stage column." });
2671
2780
  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: [] });
2781
+ return ok2({ is_pipeline: true, table_id: t.id, stage_field: stage, field_id: field.id, stage_label: field.data["label"], stages, rules: [] });
2673
2782
  },
2674
2783
  pipeline_board: (args) => {
2675
2784
  const t = tableOf(args);
2676
2785
  const stage = t ? world.stageField.get(t.id) : void 0;
2677
- if (!t || !stage) return ok([]);
2786
+ if (!t || !stage) return ok2([]);
2678
2787
  const measure = typeof args["p_measure"] === "string" ? args["p_measure"] : null;
2679
2788
  const ids = t.order.filter((id) => admits(world, t, id, args["p_filter"]));
2680
- return ok(
2789
+ return ok2(
2681
2790
  (t.options.get(stage) ?? []).map((o, i) => {
2682
2791
  const inIt = ids.filter((id) => t.rows.get(id)[stage] === o.key);
2683
2792
  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 +2794,7 @@ function memoryDataSource(world) {
2685
2794
  })
2686
2795
  );
2687
2796
  },
2688
- relation_words_many: (args) => ok((args["p_record_ids"] ?? []).map((id) => ({ record_id: id, words: nameOf(id), archived: false }))),
2797
+ relation_words_many: (args) => ok2((args["p_record_ids"] ?? []).map((id) => ({ record_id: id, words: nameOf(id), archived: false }))),
2689
2798
  reverse_columns: (args) => {
2690
2799
  const target = String(args["p_table_id"] ?? "");
2691
2800
  const out = [];
@@ -2705,11 +2814,11 @@ function memoryDataSource(world) {
2705
2814
  });
2706
2815
  }
2707
2816
  }
2708
- return ok(out);
2817
+ return ok2(out);
2709
2818
  },
2710
2819
  reverse_links_many: (args) => {
2711
2820
  const hit = fieldById(args["p_field_id"]);
2712
- return ok(
2821
+ return ok2(
2713
2822
  (args["p_record_ids"] ?? []).map((rid) => {
2714
2823
  if (!hit) return { record_id: rid, total: 0, links: [] };
2715
2824
  const key = String(hit.f.data["key"]);
@@ -2723,28 +2832,28 @@ function memoryDataSource(world) {
2723
2832
  },
2724
2833
  // A template's own record has no history, comments, checklists or worked-out provenance yet:
2725
2834
  // 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({
2835
+ record_history: () => ok2([]),
2836
+ field_history: () => ok2([]),
2837
+ computed_provenance: () => ok2([]),
2838
+ comment_thread: (args) => ok2({ record_id: args["p_record_id"] ?? null, comments: [], may_comment: false, may_resolve: false }),
2839
+ checklist_runs: () => ok2([]),
2840
+ checklist_templates: () => ok2([]),
2841
+ doc_templates: () => ok2([]),
2842
+ doc_renders: () => ok2([]),
2843
+ doc_signatures: () => ok2([]),
2844
+ record_row_controls: () => ok2({
2736
2845
  shown_to: { value: null, effective: "everyone", type_default: null, table_default: null, allowed: ["everyone"], is_child: false, can_change: false },
2737
2846
  published_to_web: { available: false, value: false, table_default: null, can_change: false },
2738
2847
  indexed: { available: false, value: null, table_default: null, effective: false, can_change: false }
2739
2848
  }),
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"] ?? ""))),
2849
+ enrich_cells: () => ok2([]),
2850
+ enrichments: () => ok2([]),
2851
+ pipeline_pending: () => ok2([]),
2852
+ record_stage_pending: () => ok2([]),
2853
+ io_imports: () => ok2([]),
2854
+ relation_words: (args) => ok2(nameOf(String(args["p_value"] ?? ""))),
2746
2855
  // The read doors a form, a booking page and a dashboard are drawn from (lane CHAIR-GALLERY-3).
2747
- forms: (args) => ok(
2856
+ forms: (args) => ok2(
2748
2857
  world.forms.filter((f) => !f.booking && (!args["p_table_id"] || f.table_id === args["p_table_id"])).map((f) => ({
2749
2858
  form_id: f.form_id,
2750
2859
  table_id: f.table_id,
@@ -2767,7 +2876,7 @@ function memoryDataSource(world) {
2767
2876
  form_preview_asks: (args) => {
2768
2877
  const t = tableOf(args);
2769
2878
  const values = args["p_values"] ?? {};
2770
- return ok(
2879
+ return ok2(
2771
2880
  (args["p_questions"] ?? []).map((q) => {
2772
2881
  const when = q["show_if"] ?? q["showIf"] ?? null;
2773
2882
  const asked = !when || !t ? true : rule(t, values, values, when) !== false;
@@ -2775,7 +2884,7 @@ function memoryDataSource(world) {
2775
2884
  })
2776
2885
  );
2777
2886
  },
2778
- bookings: (args) => ok(
2887
+ bookings: (args) => ok2(
2779
2888
  world.forms.filter((f) => f.booking && (!args["p_table_id"] || f.table_id === args["p_table_id"])).map((f) => ({
2780
2889
  form_id: f.form_id,
2781
2890
  table_id: f.table_id,
@@ -2797,9 +2906,9 @@ function memoryDataSource(world) {
2797
2906
  booking_public: (args) => {
2798
2907
  const f = world.forms.find((x) => x.form_id === args["p_form_id"] && x.booking);
2799
2908
  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");
2909
+ if (!f || !t) return refuse2("That booking page is not in this template.", "P0002");
2801
2910
  const availability = bookingAvailability(world, f.booking);
2802
- return ok({
2911
+ return ok2({
2803
2912
  form_id: f.form_id,
2804
2913
  organization_id: world.organization_id,
2805
2914
  table_id: t.id,
@@ -2813,7 +2922,7 @@ function memoryDataSource(world) {
2813
2922
  message: null
2814
2923
  });
2815
2924
  },
2816
- dashboards: (args) => ok(
2925
+ dashboards: (args) => ok2(
2817
2926
  world.dashboards.filter((d) => !args["p_table_id"] || d.table_id === args["p_table_id"]).map((d) => ({
2818
2927
  dashboard_id: d.dashboard_id,
2819
2928
  table_id: d.table_id,
@@ -2828,9 +2937,9 @@ function memoryDataSource(world) {
2828
2937
  ),
2829
2938
  dashboard_run: (args) => {
2830
2939
  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");
2940
+ if (!d) return refuse2("That dashboard is not in this template.", "P0002");
2832
2941
  const canvas = args["p_filter"] ?? {};
2833
- return ok({
2942
+ return ok2({
2834
2943
  dashboard_id: d.dashboard_id,
2835
2944
  name: d.name,
2836
2945
  subject_table_id: d.table_id,
@@ -2842,16 +2951,19 @@ function memoryDataSource(world) {
2842
2951
  blocks: d.blocks.map((b, i) => runBlock(world, d.table_id, b, canvas, i))
2843
2952
  });
2844
2953
  },
2845
- dashboard_stuck: () => ok([])
2954
+ dashboard_stuck: () => ok2([])
2846
2955
  };
2847
2956
  const source = {
2848
2957
  world,
2849
2958
  rpc(fn, args) {
2850
- const name = fn.replace(/^custom\./, "");
2959
+ const name = fn.replace(/^custom\./, "").replace(/^platform\./, "");
2960
+ const entityDoor = world.entities ? memoryEntityDoors(world.entities)[name] : void 0;
2961
+ const fromEntity = entityDoor ? entityDoor(args ?? {}) : null;
2962
+ if (fromEntity) return Promise.resolve(fromEntity);
2851
2963
  const door = doors[name];
2852
2964
  if (door) return Promise.resolve(door(args ?? {}));
2853
2965
  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}.`));
2966
+ return Promise.resolve(refuse2(`A template preview does not answer ${name}.`));
2855
2967
  },
2856
2968
  schema() {
2857
2969
  return {
@@ -2865,6 +2977,12 @@ function memoryDataSource(world) {
2865
2977
  }
2866
2978
 
2867
2979
  // src/memory/index.ts
2980
+ function withEntities(world, specs) {
2981
+ const entities = new Map(world.entities ?? []);
2982
+ for (const s of specs) entities.set(s.token, { ...s, rows: s.rows.map((r) => ({ ...r })) });
2983
+ world.entities = entities;
2984
+ return world;
2985
+ }
2868
2986
  function templatePreview(spec, options = {}) {
2869
2987
  const world = templateWorld(spec, options);
2870
2988
  const config = {
@@ -2880,12 +2998,38 @@ function templatePreview(spec, options = {}) {
2880
2998
  dashboards: world.dashboards.map((d) => ({ id: d.dashboard_id, token: d.token, name: d.name, tableId: d.table_id }))
2881
2999
  };
2882
3000
  }
3001
+ function entityStore(specs, options = {}) {
3002
+ const world = {
3003
+ organization_id: stableId("entity-store-organization"),
3004
+ user_id: stableId("entity-store-person"),
3005
+ timezone: "UTC",
3006
+ today: options.today ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
3007
+ businessName: "",
3008
+ tables: [],
3009
+ tableById: /* @__PURE__ */ new Map(),
3010
+ views: [],
3011
+ forms: [],
3012
+ dashboards: [],
3013
+ recordTable: /* @__PURE__ */ new Map(),
3014
+ stageField: /* @__PURE__ */ new Map()
3015
+ };
3016
+ withEntities(world, specs);
3017
+ return {
3018
+ world,
3019
+ config: { dataSource: memoryDataSource(world), actor: { actor: "user", user_id: world.user_id }, organizationId: world.organization_id }
3020
+ };
3021
+ }
2883
3022
  export {
2884
3023
  MEMORY_KERNEL,
2885
3024
  PREVIEW_REFUSAL,
3025
+ entityStore,
2886
3026
  memoryDataSource,
3027
+ memoryEntityDescribe,
3028
+ memoryEntityRows,
3029
+ memoryEntityWrite,
2887
3030
  stableId,
2888
3031
  templatePreview,
2889
- templateWorld
3032
+ templateWorld,
3033
+ withEntities
2890
3034
  };
2891
3035
  //# sourceMappingURL=index.js.map