@ai-matrx/records 0.73.4 → 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,11 @@
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
+
3
9
  ## 0.73.4
4
10
 
5
11
  Automatic changed-only republish (docs/metadata drift since the last tag — see
@@ -24,6 +24,7 @@ __export(memory_exports, {
24
24
  PREVIEW_REFUSAL: () => PREVIEW_REFUSAL,
25
25
  entityStore: () => entityStore,
26
26
  memoryDataSource: () => memoryDataSource,
27
+ memoryEntityAsk: () => memoryEntityAsk,
27
28
  memoryEntityDescribe: () => memoryEntityDescribe,
28
29
  memoryEntityRows: () => memoryEntityRows,
29
30
  memoryEntityWrite: () => memoryEntityWrite,
@@ -102,6 +103,61 @@ function memoryEntityRows(e, question2) {
102
103
  ...offset + page.length < rows.length ? { next_offset: offset + page.length } : {}
103
104
  };
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
+ }
105
161
  function memoryEntityWrite(e, recordId, columns) {
106
162
  const row = e.rows.find((r) => r["id"] === recordId);
107
163
  if (!row) return refuse(`There is no ${e.label} you can open with that id.`, "02000");
@@ -131,6 +187,10 @@ function memoryEntityDoors(entities) {
131
187
  const e = of(args);
132
188
  return e ? ok(memoryEntityDescribe(e)) : null;
133
189
  },
190
+ drill_ask: (args) => {
191
+ const e = of(args);
192
+ return e ? ok(memoryEntityAsk(e, args["p_question"] ?? {})) : null;
193
+ },
134
194
  drill_rows: (args) => {
135
195
  const e = of(args);
136
196
  return e ? ok(memoryEntityRows(e, args["p_question"] ?? {})) : null;
@@ -2559,16 +2619,11 @@ function groupValues(doc, stored, key) {
2559
2619
  const out = list.map((v) => v === void 0 || v === null || v === "" ? null : String(v));
2560
2620
  return out.length ? out : [null];
2561
2621
  }
2562
- function runBlock(world, subject, raw, canvas, index) {
2563
- const table = world.tableById.get(String(raw["table_id"] ?? subject));
2564
- const title = String(raw["title"] ?? `Block ${index + 1}`);
2565
- const base = { ...raw, title, table_id: table?.id ?? subject, ms: 0 };
2566
- if (!table) return { ...base, rows: [], refused: "This block's table is not in this template." };
2567
- if (raw["kind"] === "stuck") return { ...base, rows: [] };
2568
- const measures = (raw["measures"] ?? [{ op: "count" }]).map((m) => ({ op: String(m["op"] ?? "count"), key: m["key"] }));
2569
- const groupBy = (raw["group_by"] ?? []).slice();
2570
- const bucket = raw["bucket"] ?? null;
2571
- 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));
2572
2627
  const groups = /* @__PURE__ */ new Map();
2573
2628
  for (const id of ids) {
2574
2629
  const doc = readDocument(world, table, id);
@@ -2610,8 +2665,25 @@ function runBlock(world, subject, raw, canvas, index) {
2610
2665
  const k = `${bucket.key}_${bucket.by ?? "month"}`;
2611
2666
  rows.sort((a, b) => String(a.groups[k]).localeCompare(String(b.groups[k])));
2612
2667
  } else rows.sort((a, b) => (b.measures[primary] ?? 0) - (a.measures[primary] ?? 0));
2613
- const limit = Number(raw["limit"] ?? 0);
2668
+ const limit = Number(ask.limit ?? 0);
2614
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) });
2615
2687
  return { ...base, measures, group_by: groupBy, rows };
2616
2688
  }
2617
2689
  function bookingAvailability(world, raw) {
@@ -2987,7 +3059,22 @@ function memoryDataSource(world) {
2987
3059
  blocks: d.blocks.map((b, i) => runBlock(world, d.table_id, b, canvas, i))
2988
3060
  });
2989
3061
  },
2990
- dashboard_stuck: () => ok2([])
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
+ }
2991
3078
  };
2992
3079
  const source = {
2993
3080
  world,