@mulmoclaude/core 0.25.0 → 0.25.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/assets/helps/collection-skills.md +51 -0
  2. package/assets/helps/custom-view.md +37 -3
  3. package/assets/helps/error-recovery.md +30 -0
  4. package/dist/collection/core/project.d.ts +3 -0
  5. package/dist/collection/core/schema.d.ts +13 -1
  6. package/dist/collection/core/schemaZ.d.ts +16 -0
  7. package/dist/collection/index.cjs +4 -1
  8. package/dist/collection/index.cjs.map +1 -1
  9. package/dist/collection/index.d.ts +1 -0
  10. package/dist/collection/index.js +3 -2
  11. package/dist/collection/index.js.map +1 -1
  12. package/dist/collection/registry/server/index.cjs +2 -2
  13. package/dist/collection/registry/server/index.js +2 -2
  14. package/dist/collection/server/csvStore.d.ts +8 -1
  15. package/dist/collection/server/discoveredCollection.d.ts +4 -0
  16. package/dist/collection/server/discovery.d.ts +1 -0
  17. package/dist/collection/server/index.cjs +20 -17
  18. package/dist/collection/server/index.d.ts +1 -0
  19. package/dist/collection/server/index.js +4 -4
  20. package/dist/collection/server/queryRunner.d.ts +4 -0
  21. package/dist/collection/server/spawn.d.ts +2 -1
  22. package/dist/collection/server/sqliteStore.d.ts +14 -0
  23. package/dist/collection/server/store.d.ts +43 -8
  24. package/dist/collection/server/storePage.d.ts +32 -0
  25. package/dist/collection-watchers/index.cjs +124 -38
  26. package/dist/collection-watchers/index.cjs.map +1 -1
  27. package/dist/collection-watchers/index.d.ts +1 -1
  28. package/dist/collection-watchers/index.js +124 -39
  29. package/dist/collection-watchers/index.js.map +1 -1
  30. package/dist/collection-watchers/reconciler.d.ts +13 -11
  31. package/dist/collection-watchers/watcher.d.ts +4 -3
  32. package/dist/{discovery-3K_Wtkvn.js → discovery-QUi_7aNG.js} +882 -7
  33. package/dist/discovery-QUi_7aNG.js.map +1 -0
  34. package/dist/{discovery-BZ2ms1tE.cjs → discovery-cwtfyDxl.cjs} +994 -4
  35. package/dist/discovery-cwtfyDxl.cjs.map +1 -0
  36. package/dist/feeds/index.cjs +2 -2
  37. package/dist/feeds/index.js +2 -2
  38. package/dist/feeds/server/index.cjs +26 -14
  39. package/dist/feeds/server/index.cjs.map +1 -1
  40. package/dist/feeds/server/index.js +26 -14
  41. package/dist/feeds/server/index.js.map +1 -1
  42. package/dist/google/index.cjs +9 -11
  43. package/dist/google/index.cjs.map +1 -1
  44. package/dist/google/index.js +9 -11
  45. package/dist/google/index.js.map +1 -1
  46. package/dist/{ids-BR4U1dJD.cjs → ids-Dl9Ij63K.cjs} +14 -1
  47. package/dist/ids-Dl9Ij63K.cjs.map +1 -0
  48. package/dist/{ids-D4e10971.js → ids-P2T89Gou.js} +9 -2
  49. package/dist/ids-P2T89Gou.js.map +1 -0
  50. package/dist/{ingestTypes-BsGOjR2l.js → ingestTypes-C-IQB4nw.js} +2 -2
  51. package/dist/{ingestTypes-BsGOjR2l.js.map → ingestTypes-C-IQB4nw.js.map} +1 -1
  52. package/dist/{ingestTypes-CygUXsjf.cjs → ingestTypes-DZpAC3N_.cjs} +2 -2
  53. package/dist/{ingestTypes-CygUXsjf.cjs.map → ingestTypes-DZpAC3N_.cjs.map} +1 -1
  54. package/dist/project-BWI5w_BT.cjs +17 -0
  55. package/dist/project-BWI5w_BT.cjs.map +1 -0
  56. package/dist/project-bU98ycsy.js +12 -0
  57. package/dist/project-bU98ycsy.js.map +1 -0
  58. package/dist/remote-view/index.cjs +5 -3
  59. package/dist/remote-view/index.cjs.map +1 -1
  60. package/dist/remote-view/index.d.ts +3 -1
  61. package/dist/remote-view/index.js +5 -3
  62. package/dist/remote-view/index.js.map +1 -1
  63. package/dist/{server-DZH7OjOD.js → server-BI3wnTwl.js} +227 -686
  64. package/dist/server-BI3wnTwl.js.map +1 -0
  65. package/dist/{server-DLRz-U7F.cjs → server-CKdWENZ-.cjs} +237 -775
  66. package/dist/server-CKdWENZ-.cjs.map +1 -0
  67. package/package.json +1 -1
  68. package/dist/discovery-3K_Wtkvn.js.map +0 -1
  69. package/dist/discovery-BZ2ms1tE.cjs.map +0 -1
  70. package/dist/ids-BR4U1dJD.cjs.map +0 -1
  71. package/dist/ids-D4e10971.js.map +0 -1
  72. package/dist/server-DLRz-U7F.cjs.map +0 -1
  73. package/dist/server-DZH7OjOD.js.map +0 -1
@@ -1,528 +1,13 @@
1
1
  const require_rolldown_runtime = require("./rolldown-runtime-D6vf50IK.cjs");
2
- const require_ids = require("./ids-BR4U1dJD.cjs");
2
+ const require_ids = require("./ids-Dl9Ij63K.cjs");
3
3
  const require_promptSafety = require("./promptSafety-Co4cYQ_Z.cjs");
4
- const require_discovery = require("./discovery-BZ2ms1tE.cjs");
4
+ const require_discovery = require("./discovery-cwtfyDxl.cjs");
5
5
  const require_skill_bridge_index = require("./skill-bridge/index.cjs");
6
6
  let node_path = require("node:path");
7
7
  node_path = require_rolldown_runtime.__toESM(node_path, 1);
8
8
  let node_fs_promises = require("node:fs/promises");
9
9
  let node_crypto = require("node:crypto");
10
- let node_os = require("node:os");
11
- let iconv_lite = require("iconv-lite");
12
- iconv_lite = require_rolldown_runtime.__toESM(iconv_lite, 1);
13
10
  let zod = require("zod");
14
- //#region src/collection/core/queryZ.ts
15
- /** Result-column aliases double as SQL identifiers and JSON keys — keep
16
- * them to a conservative identifier charset so neither side needs
17
- * escaping gymnastics. */
18
- var SAFE_ALIAS_PATTERN = /^[A-Za-z_]\w{0,63}$/;
19
- /** Hard ceiling on returned rows; `limit` clamps below it. A group-by on
20
- * a near-unique column would otherwise return one row per source row —
21
- * the exact materialization the aggregate path exists to avoid. */
22
- var MAX_QUERY_ROWS = 1e4;
23
- /** Default row cap when the query declares no `limit`. */
24
- var DEFAULT_QUERY_ROWS = 1e3;
25
- /** One aggregate column: `count` (rows; `column` optional to count
26
- * non-null cells) or `sum`/`avg`/`min`/`max` over a named CSV column. */
27
- var QueryAggregateZ = zod.z.object({
28
- op: zod.z.enum([
29
- "count",
30
- "sum",
31
- "avg",
32
- "min",
33
- "max"
34
- ]),
35
- column: zod.z.string().min(1).optional()
36
- }).refine((aggregate) => aggregate.op === "count" || aggregate.column !== void 0, {
37
- message: "`column` is required for every aggregate op except `count`",
38
- path: ["column"]
39
- });
40
- /** One filter condition. Same op vocabulary as the schema-level `where`
41
- * (`core/where.ts`) so authors learn one set; values may be typed
42
- * (number / boolean) since CSV columns are. `in` requires an array
43
- * value, every other op a scalar. */
44
- var QueryWhereZ = zod.z.object({
45
- field: zod.z.string().min(1),
46
- op: zod.z.enum([
47
- "eq",
48
- "ne",
49
- "in",
50
- "gt",
51
- "gte",
52
- "lt",
53
- "lte",
54
- "contains"
55
- ]),
56
- value: zod.z.union([
57
- zod.z.string(),
58
- zod.z.number(),
59
- zod.z.boolean(),
60
- zod.z.array(zod.z.union([
61
- zod.z.string(),
62
- zod.z.number(),
63
- zod.z.boolean()
64
- ])).min(1).max(100)
65
- ])
66
- }).refine((cond) => cond.op === "in" === Array.isArray(cond.value), {
67
- message: "`in` requires an array value (the allowed set); every other op requires a scalar value",
68
- path: ["value"]
69
- });
70
- var QueryOrderZ = zod.z.object({
71
- /** A `groupBy` column or an aggregate alias — membership enforced by
72
- * the whole-query refine below. */
73
- field: zod.z.string().min(1),
74
- dir: zod.z.enum(["asc", "desc"]).optional()
75
- });
76
- /** The whole query. At least one of `groupBy` / `aggregates` must be
77
- * present: bare `groupBy` is a DISTINCT listing, bare `aggregates` a
78
- * whole-file scalar row, together a grouped aggregation. */
79
- var CollectionQueryZ = zod.z.object({
80
- groupBy: zod.z.array(zod.z.string().min(1)).max(8).refine((columns) => new Set(columns.map((column) => column.toLowerCase())).size === columns.length, { message: "`groupBy` columns must be unique (case-insensitively — SQL identifiers ignore case)" }).optional(),
81
- aggregates: zod.z.record(zod.z.string().regex(SAFE_ALIAS_PATTERN, "aggregate aliases must be simple identifiers (letters/digits/underscore)"), QueryAggregateZ).optional(),
82
- where: zod.z.array(QueryWhereZ).max(16).optional(),
83
- orderBy: zod.z.array(QueryOrderZ).max(4).optional(),
84
- limit: zod.z.number().int().min(1).max(MAX_QUERY_ROWS).optional()
85
- }).refine((query) => (query.groupBy?.length ?? 0) > 0 || Object.keys(query.aggregates ?? {}).length > 0, {
86
- message: "declare at least one of `groupBy` (columns to bucket by) or `aggregates` (values to compute)",
87
- path: ["groupBy"]
88
- }).refine((query) => Object.keys(query.aggregates ?? {}).length <= 32, {
89
- message: `\`aggregates\` supports at most 32 entries`,
90
- path: ["aggregates"]
91
- }).refine((query) => {
92
- const groupLower = new Set((query.groupBy ?? []).map((column) => column.toLowerCase()));
93
- const seen = /* @__PURE__ */ new Set();
94
- return Object.keys(query.aggregates ?? {}).every((alias) => {
95
- const lower = alias.toLowerCase();
96
- if (groupLower.has(lower) || seen.has(lower)) return false;
97
- seen.add(lower);
98
- return true;
99
- });
100
- }, {
101
- message: "aggregate aliases must be unique and must not collide with `groupBy` column names (case-insensitively — SQL identifiers ignore case)",
102
- path: ["aggregates"]
103
- }).refine((query) => {
104
- const sortable = /* @__PURE__ */ new Set([...query.groupBy ?? [], ...Object.keys(query.aggregates ?? {})]);
105
- return (query.orderBy ?? []).every((order) => sortable.has(order.field));
106
- }, {
107
- message: "every `orderBy.field` must be a `groupBy` column or an aggregate alias",
108
- path: ["orderBy"]
109
- });
110
- //#endregion
111
- //#region src/collection/server/csvQuery.ts
112
- /** Double-quote a SQL identifier (CSV column name / result alias). */
113
- function quoteIdent(name) {
114
- return `"${name.replaceAll("\"", "\"\"")}"`;
115
- }
116
- /** Single-quote a SQL string literal (a `types={...}` struct key). */
117
- function quoteLiteral(value) {
118
- return `'${value.replaceAll("'", "''")}'`;
119
- }
120
- /** The `read_csv` argument list shared by every CSV query: the (prepared)
121
- * path plus a `types` pin forcing the key column to VARCHAR — without it
122
- * DuckDB's sniffer turns `001` into BIGINT 1, so leading zeros vanish
123
- * and distinct keys collapse. */
124
- function readCsvArgs(primaryKey) {
125
- return `?, types={${quoteLiteral(primaryKey)}: 'VARCHAR'}`;
126
- }
127
- /** One aggregate's SQL expression. `sum`/`avg` TRY_CAST to DOUBLE so a
128
- * column the sniffer kept as VARCHAR (mixed values) aggregates over its
129
- * numeric cells instead of erroring; non-numeric cells become NULL and
130
- * are skipped — standard BI tolerance. `min`/`max` stay native (they are
131
- * meaningful on strings and dates too). */
132
- function aggregateExpr(aggregate) {
133
- const { op, column } = aggregate;
134
- if (op === "count") return column === void 0 ? "count(*)" : `count(${quoteIdent(column)})`;
135
- if (op === "sum" || op === "avg") return `${op}(TRY_CAST(${quoteIdent(column ?? "")} AS DOUBLE))`;
136
- return `${op}(${quoteIdent(column ?? "")})`;
137
- }
138
- /** One where condition → SQL fragment + its bound parameters. String
139
- * equality compares against `CAST(col AS VARCHAR)` so a sniffer-typed
140
- * column still matches its textual value; numeric/boolean values compare
141
- * natively (DuckDB coerces the column side). */
142
- function whereFragment(cond) {
143
- const column = quoteIdent(cond.field);
144
- const asText = `CAST(${column} AS VARCHAR)`;
145
- if (cond.op === "in") {
146
- const values = cond.value;
147
- return {
148
- sql: `${values.every((value) => typeof value === "string") ? asText : column} IN (${values.map(() => "?").join(", ")})`,
149
- params: values
150
- };
151
- }
152
- if (cond.op === "contains") return {
153
- sql: `contains(${asText}, ?)`,
154
- params: [String(cond.value)]
155
- };
156
- const operator = {
157
- eq: "=",
158
- ne: "<>",
159
- gt: ">",
160
- gte: ">=",
161
- lt: "<",
162
- lte: "<="
163
- }[cond.op];
164
- return {
165
- sql: `${typeof cond.value === "string" && (cond.op === "eq" || cond.op === "ne") ? asText : column} ${operator} ?`,
166
- params: [cond.value]
167
- };
168
- }
169
- /** Compile a validated query against `fromSql` (a table-function call
170
- * whose FIRST placeholder is the source path — the executor binds it).
171
- * Returns the SQL and the where-value parameters that follow the path.
172
- * Callers MUST have run `CollectionQueryZ` first; this function trusts
173
- * the shape (aliases already charset-checked, orderBy membership already
174
- * enforced). */
175
- function compileQuery(query, fromSql) {
176
- const groupBy = query.groupBy ?? [];
177
- const aggregates = Object.entries(query.aggregates ?? {});
178
- const selectList = [...groupBy.map(quoteIdent), ...aggregates.map(([alias, aggregate]) => `${aggregateExpr(aggregate)} AS ${quoteIdent(alias)}`)];
179
- const where = (query.where ?? []).map(whereFragment);
180
- const clauses = [`SELECT ${selectList.join(", ")}`, `FROM ${fromSql}`];
181
- if (where.length > 0) clauses.push(`WHERE ${where.map((fragment) => fragment.sql).join(" AND ")}`);
182
- if (groupBy.length > 0) clauses.push(`GROUP BY ${groupBy.map(quoteIdent).join(", ")}`);
183
- const orderBy = (query.orderBy ?? []).map((order) => quoteIdent(order.field) + (order.dir === "desc" ? " DESC" : " ASC"));
184
- if (orderBy.length > 0) clauses.push(`ORDER BY ${orderBy.join(", ")}`);
185
- clauses.push(`LIMIT ${query.limit ?? 1e3}`);
186
- return {
187
- sql: clauses.join(" "),
188
- params: where.flatMap((fragment) => fragment.params)
189
- };
190
- }
191
- /** Compile against a CSV file (the dataSource store's engine). */
192
- function compileCsvQuery(query, primaryKey) {
193
- return compileQuery(query, `read_csv(${readCsvArgs(primaryKey)})`);
194
- }
195
- /** Compile against a JSONL file of ENRICHED records — the file-backed
196
- * collections' engine (see `jsonlQuery.ts`). No VARCHAR key pin needed:
197
- * enriched record ids are already strings. `sample_size=-1` makes the
198
- * schema inference scan EVERY line — with the default sample, a sparse
199
- * optional/derived field first appearing past the sample would not be
200
- * inferred as a column and the query would binder-error on it (Codex P2
201
- * on #2165). The full scan costs nothing extra here: aggregation reads
202
- * the whole file anyway. */
203
- function compileJsonlQuery(query) {
204
- return compileQuery(query, `read_json(?, format='newline_delimited', sample_size=-1)`);
205
- }
206
- //#endregion
207
- //#region src/collection/server/csvStore.ts
208
- /** `list()` row cap. Over-cap files are truncated with a warn — the v1
209
- * contract is "browse + per-record views", not full-table analytics. */
210
- var MAX_CSV_ROWS = 5e3;
211
- /** Record ids minted from non-safe key values: `id0x` + utf-8 hex. Raw key
212
- * values that themselves match this pattern are ALSO encoded, so the
213
- * encoded namespace never collides with a raw value (injective mapping). */
214
- var ENCODED_ID_PATTERN = /^id0x([0-9a-f]+)$/;
215
- /** A CSV key value → the record id it's addressed by. Safe values pass
216
- * through untouched; everything else (and anything shaped like an encoded
217
- * id) becomes `id0x<hex>`. Pure + exported for unit tests. */
218
- function encodeCsvRecordId(rawKey) {
219
- if (require_discovery.safeRecordId(rawKey) === rawKey && !ENCODED_ID_PATTERN.test(rawKey)) return rawKey;
220
- return `id0x${Buffer.from(rawKey, "utf-8").toString("hex")}`;
221
- }
222
- /** A record id → the CSV key value to look up. Inverse of
223
- * `encodeCsvRecordId` for encoded ids; anything else is already the raw
224
- * value. Pure + exported for unit tests. */
225
- function decodeCsvRecordId(itemId) {
226
- const match = ENCODED_ID_PATTERN.exec(itemId);
227
- if (!match) return itemId;
228
- return Buffer.from(match[1], "hex").toString("utf-8");
229
- }
230
- /** Normalize one DuckDB JS value into a JSON-safe record value: BigInt →
231
- * number (string beyond the safe range), DATE/TIMESTAMP → ISO string
232
- * (date-only when the clock is exactly UTC midnight, matching the `date`
233
- * field contract), exotic DuckDB values → their string form. Pure +
234
- * exported for unit tests. */
235
- function normalizeCsvValue(value) {
236
- if (typeof value === "bigint") return value <= BigInt(Number.MAX_SAFE_INTEGER) && value >= BigInt(-Number.MAX_SAFE_INTEGER) ? Number(value) : value.toString();
237
- if (value instanceof Date) {
238
- const iso = value.toISOString();
239
- return iso.endsWith("T00:00:00.000Z") ? iso.slice(0, 10) : iso;
240
- }
241
- if (value !== null && typeof value === "object") return String(value);
242
- return value;
243
- }
244
- /** One raw DuckDB row → a CollectionItem, or null when the key cell is
245
- * missing/empty (the row can't be addressed). The primaryKey field is
246
- * OVERWRITTEN with the (possibly encoded) record id so `item[primaryKey]`
247
- * and the record's address never drift — same invariant the file store's
248
- * write path enforces. Pure + exported for unit tests. */
249
- function csvRowToItem(row, primaryKey) {
250
- const normalized = Object.fromEntries(Object.entries(row).map(([key, value]) => [key, normalizeCsvValue(value)]));
251
- const rawKey = normalized[primaryKey];
252
- if (rawKey === null || rawKey === void 0 || String(rawKey) === "") return null;
253
- return {
254
- ...normalized,
255
- [primaryKey]: encodeCsvRecordId(String(rawKey))
256
- };
257
- }
258
- /** Dedupe by record id, LAST row wins (matches `csvRead`'s last-match
259
- * pick). Returns the surviving items in first-seen order. Pure +
260
- * exported for unit tests. */
261
- function dedupeByRecordId(items, primaryKey) {
262
- const byId = /* @__PURE__ */ new Map();
263
- for (const item of items) byId.set(String(item[primaryKey]), item);
264
- return {
265
- items: [...byId.values()],
266
- duplicates: items.length - byId.size
267
- };
268
- }
269
- /** True when a thrown DuckDB error is the `types` pin naming a column the
270
- * CSV doesn't have — the schema/file-mismatch case the caller downgrades
271
- * to "empty collection + warn" instead of a 500. */
272
- function isMissingKeyColumnError(err) {
273
- return String(err).includes("do not exist in the CSV");
274
- }
275
- /** Bytes sniffed for UTF-8 validity. The trailing 3 bytes of the sample
276
- * are dropped so a multibyte char split at the boundary can't produce a
277
- * false negative on a valid file. */
278
- var SNIFF_BYTES = 1024 * 1024;
279
- function isValidUtf8(buf) {
280
- try {
281
- new TextDecoder("utf-8", { fatal: true }).decode(buf);
282
- return true;
283
- } catch {
284
- return false;
285
- }
286
- }
287
- /** Detect the (best-effort) encoding of a non-UTF-8 buffer. BOMs decide
288
- * UTF-16; otherwise cp932 (the Shift_JIS superset — Excel-exported
289
- * Japanese CSVs are the primary non-UTF-8 case this feature serves). */
290
- function fallbackEncoding(buf) {
291
- if (buf.length >= 2 && buf[0] === 255 && buf[1] === 254) return "utf-16le";
292
- if (buf.length >= 2 && buf[0] === 254 && buf[1] === 255) return "utf-16be";
293
- return "cp932";
294
- }
295
- function cacheDir() {
296
- return node_path.default.join((0, node_os.tmpdir)(), "mulmoclaude-csv-utf8");
297
- }
298
- /** Read only the first `bytes` of a file — the encoding sniff must not
299
- * pull a multi-hundred-MB CSV into memory on the (common) UTF-8 path. */
300
- async function readHead(absPath, bytes) {
301
- const handle = await (0, node_fs_promises.open)(absPath, "r");
302
- try {
303
- const { size } = await handle.stat();
304
- const buf = Buffer.alloc(Math.min(bytes, size));
305
- await handle.read(buf, 0, buf.length, 0);
306
- return buf;
307
- } finally {
308
- await handle.close();
309
- }
310
- }
311
- /** Decode the whole file into a UTF-8 cache copy and return its path.
312
- * Cache key = (path, mtime, size), so a replaced CSV re-decodes and an
313
- * unchanged one never does. */
314
- async function pathExists$1(target) {
315
- try {
316
- await (0, node_fs_promises.stat)(target);
317
- return true;
318
- } catch {
319
- return false;
320
- }
321
- }
322
- /** Best-effort removal of older decode-cache entries for the same source
323
- * path — a frequently-replaced large CSV would otherwise accumulate one
324
- * full copy per (mtime, size) forever. Runs AFTER the current copy is
325
- * published; a concurrent reader holding an old fd is unaffected
326
- * (unlink-while-open is safe on POSIX). */
327
- async function evictSupersededCache(key, keepBasename) {
328
- try {
329
- const entries = await (0, node_fs_promises.readdir)(cacheDir());
330
- await Promise.all(entries.filter((name) => name.startsWith(`${key}-`) && name !== keepBasename).map((name) => (0, node_fs_promises.unlink)(node_path.default.join(cacheDir(), name)).catch(() => void 0)));
331
- } catch {}
332
- }
333
- /** Decode the whole file into a UTF-8 cache copy and return its path.
334
- * Cache key = (path, mtime, size), so a replaced CSV re-decodes and an
335
- * unchanged one never does; superseded copies are evicted. The cache
336
- * lives in the SHARED OS tmpdir, so the dir is 0700 and files 0600 —
337
- * decoded rows must not be readable by other local users. */
338
- async function decodeToCache(absPath, info) {
339
- const key = (0, node_crypto.createHash)("sha256").update(absPath).digest("hex").slice(0, 16);
340
- const cached = node_path.default.join(cacheDir(), `${key}-${Math.trunc(info.mtimeMs)}-${info.size}.csv`);
341
- if (!await pathExists$1(cached)) {
342
- const whole = await (0, node_fs_promises.readFile)(absPath);
343
- const encoding = fallbackEncoding(whole);
344
- const text = iconv_lite.default.decode(whole, encoding);
345
- await (0, node_fs_promises.mkdir)(cacheDir(), {
346
- recursive: true,
347
- mode: 448
348
- });
349
- const tmp = `${cached}.${(0, node_crypto.randomBytes)(4).toString("hex")}.tmp`;
350
- await (0, node_fs_promises.writeFile)(tmp, text, {
351
- encoding: "utf-8",
352
- mode: 384
353
- });
354
- await (0, node_fs_promises.rename)(tmp, cached);
355
- require_discovery.log.info("collections", "decoded non-UTF-8 dataSource file to cache", {
356
- path: absPath,
357
- encoding
358
- });
359
- await evictSupersededCache(key, node_path.default.basename(cached));
360
- }
361
- return cached;
362
- }
363
- /** Re-validate the dataSource file at READ time, mirroring the JSON
364
- * store's per-read defenses: realpath containment (a symlink swapped in
365
- * after discovery must not walk out of the workspace) and an lstat
366
- * regular-file check (a symlink leaf is refused outright, even one
367
- * pointing inside the workspace — same rule as `isRegularFile` on
368
- * record files). Returns the stat info, or null for "no readable file"
369
- * (ENOENT / refused), which callers render as an empty collection. */
370
- async function safeCsvStat(absPath, workspaceRoot) {
371
- if (!require_discovery.isContainedInRoot(absPath, workspaceRoot)) {
372
- require_discovery.log.warn("collections", "dataSource read refused: path escapes workspace", { path: absPath });
373
- return null;
374
- }
375
- let info;
376
- try {
377
- info = await (0, node_fs_promises.lstat)(absPath);
378
- } catch (err) {
379
- if (err.code === "ENOENT") return null;
380
- throw err;
381
- }
382
- if (!info.isFile()) {
383
- require_discovery.log.warn("collections", "dataSource read refused: not a regular file (symlink?)", { path: absPath });
384
- return null;
385
- }
386
- return info;
387
- }
388
- /** Return a path DuckDB can read as UTF-8: the original file when it
389
- * already is UTF-8 (the cheap, common case — only the head is sniffed),
390
- * else a decoded cache copy (see `decodeToCache`). Returns null when
391
- * there is no readable file (missing, symlink, or containment-refused —
392
- * see `safeCsvStat`), which callers render as an empty collection. */
393
- async function ensureUtf8CsvPath(absPath, workspaceRoot) {
394
- const info = await safeCsvStat(absPath, workspaceRoot);
395
- if (info === null) return null;
396
- const head = await readHead(absPath, SNIFF_BYTES);
397
- const sample = head.length === SNIFF_BYTES ? head.subarray(0, SNIFF_BYTES - 3) : head;
398
- if (!(head.length >= 2 && (head[0] === 255 && head[1] === 254 || head[0] === 254 && head[1] === 255)) && isValidUtf8(sample)) return absPath;
399
- return decodeToCache(absPath, info);
400
- }
401
- var instancePromise = null;
402
- /** Lazily create one shared in-memory DuckDB instance. The dynamic import
403
- * keeps the native module OUT of core's load path — a platform where the
404
- * prebuilt binding is missing degrades to a per-query error on dataSource
405
- * collections only, never a broken core. A failed init is retried on the
406
- * next call (the promise is reset). */
407
- async function duckDbInstance() {
408
- if (instancePromise === null) instancePromise = import("@duckdb/node-api").then((mod) => mod.DuckDBInstance.create(":memory:"));
409
- try {
410
- return await instancePromise;
411
- } catch (err) {
412
- instancePromise = null;
413
- throw new Error(`DuckDB is unavailable on this host (@duckdb/node-api failed to load: ${String(err)}) — dataSource collections cannot be read`);
414
- }
415
- }
416
- async function queryCsv(sql, params) {
417
- const connection = await (await duckDbInstance()).connect();
418
- try {
419
- return (await connection.runAndReadAll(sql, params)).getRowObjectsJS();
420
- } finally {
421
- connection.disconnectSync();
422
- }
423
- }
424
- /** Every row of the CSV as records — capped, deduped, id-encoded. The
425
- * key column is pinned to VARCHAR (see `readCsvArgs`). `workspaceRoot`
426
- * drives the per-read containment check; omitted, the configured host
427
- * root is used. */
428
- async function csvList(absPath, primaryKey, workspaceRoot) {
429
- const utf8Path = await ensureUtf8CsvPath(absPath, workspaceRoot ?? require_discovery.getWorkspaceRoot());
430
- if (utf8Path === null) return [];
431
- let rows;
432
- try {
433
- rows = await queryCsv(`SELECT * FROM read_csv(${readCsvArgs(primaryKey)}) LIMIT 5001`, [utf8Path]);
434
- } catch (err) {
435
- if (!isMissingKeyColumnError(err)) throw err;
436
- require_discovery.log.warn("collections", "dataSource CSV has no primaryKey column — every row is skipped", {
437
- path: absPath,
438
- primaryKey
439
- });
440
- return [];
441
- }
442
- if (rows.length > 5e3) {
443
- require_discovery.log.warn("collections", "dataSource CSV truncated to row cap", {
444
- path: absPath,
445
- cap: MAX_CSV_ROWS
446
- });
447
- rows.length = MAX_CSV_ROWS;
448
- }
449
- const items = rows.map((row) => csvRowToItem(row, primaryKey)).filter((item) => item !== null);
450
- const skipped = rows.length - items.length;
451
- if (skipped > 0) require_discovery.log.warn("collections", "dataSource CSV rows skipped (empty key cell)", {
452
- path: absPath,
453
- skipped
454
- });
455
- const deduped = dedupeByRecordId(items, primaryKey);
456
- if (deduped.duplicates > 0) require_discovery.log.warn("collections", "dataSource CSV has duplicate key values (last row wins)", {
457
- path: absPath,
458
- duplicates: deduped.duplicates
459
- });
460
- return deduped.items;
461
- }
462
- /** The scan-order ordinal column the last-match read adds. Underscore
463
- * prefix keeps it out of any plausible CSV header namespace; it is
464
- * stripped from the returned record either way. */
465
- var ROW_ORDINAL = "__mc_row";
466
- /** One record by id. The comparison value rides as a prepared-statement
467
- * parameter, and the LAST matching row is selected IN DuckDB (scan-order
468
- * ordinal + LIMIT 1) — a CSV with thousands of duplicate keys must not
469
- * materialize them all for one detail read. Consistent with csvList's
470
- * last-wins dedupe. */
471
- async function csvRead(absPath, primaryKey, itemId, workspaceRoot) {
472
- const utf8Path = await ensureUtf8CsvPath(absPath, workspaceRoot ?? require_discovery.getWorkspaceRoot());
473
- if (utf8Path === null) return null;
474
- const rawKey = decodeCsvRecordId(itemId);
475
- const last = (await queryCsv(`SELECT * FROM (SELECT *, row_number() OVER () AS ${quoteIdent(ROW_ORDINAL)} FROM read_csv(${readCsvArgs(primaryKey)})) WHERE CAST(${quoteIdent(primaryKey)} AS VARCHAR) = ? ORDER BY ${quoteIdent(ROW_ORDINAL)} DESC LIMIT 1`, [utf8Path, rawKey])).at(0);
476
- if (last === void 0) return null;
477
- const { [ROW_ORDINAL]: __ordinal, ...record } = last;
478
- return csvRowToItem(record, primaryKey);
479
- }
480
- /** Run a validated aggregation query (the structured DSL — see
481
- * `core/queryZ.ts`) over the WHOLE file: no row cap on the scan (a
482
- * capped aggregate would be a wrong number), only the result-row LIMIT
483
- * the compiler emits. Values are normalized like list/read rows so a
484
- * chart consumer gets plain JSON scalars. */
485
- async function csvRunQuery(absPath, primaryKey, query, workspaceRoot) {
486
- const utf8Path = await ensureUtf8CsvPath(absPath, workspaceRoot ?? require_discovery.getWorkspaceRoot());
487
- if (utf8Path === null) return [];
488
- const { sql, params } = compileCsvQuery(query, primaryKey);
489
- return (await queryCsv(sql, [utf8Path, ...params])).map((row) => Object.fromEntries(Object.entries(row).map(([key, value]) => [key, normalizeCsvValue(value)])));
490
- }
491
- //#endregion
492
- //#region src/collection/server/store.ts
493
- /** True when the collection accepts UI/tool writes. A `dataSource`
494
- * collection is read-only: updates happen by editing/replacing the
495
- * data file itself. Every write entry point checks this BEFORE calling
496
- * `writeItem`/`deleteItem` — server-enforced, not just UI-hidden. */
497
- function collectionWritable(collection) {
498
- return !require_ids.isReadOnlySchema(collection.schema);
499
- }
500
- /** The one-line refusal write paths surface (HTTP 405 / MCP error text). */
501
- function readOnlyRefusal(slug) {
502
- return `collection '${slug}' is read-only (backed by an external dataSource) — update the data file itself instead`;
503
- }
504
- /** Pick the store implementation for a discovered collection. A
505
- * `dataSource` schema whose `dataSourceFile` failed to resolve yields a
506
- * read-only EMPTY store rather than falling back to the (writable) file
507
- * store — a half-loaded read-only collection must never become writable. */
508
- function storeFor(collection, opts = {}) {
509
- if (require_ids.isReadOnlySchema(collection.schema)) {
510
- const file = collection.dataSourceFile;
511
- const key = collection.schema.primaryKey;
512
- return {
513
- capabilities: { writable: false },
514
- list: () => file === void 0 ? Promise.resolve([]) : csvList(file, key, opts.workspaceRoot),
515
- read: (itemId) => file === void 0 ? Promise.resolve(null) : csvRead(file, key, itemId, opts.workspaceRoot),
516
- query: (query) => file === void 0 ? Promise.resolve([]) : csvRunQuery(file, key, query, opts.workspaceRoot)
517
- };
518
- }
519
- return {
520
- capabilities: { writable: true },
521
- list: () => require_discovery.listItems(collection.dataDir, opts),
522
- read: (itemId) => require_discovery.readItem(collection.dataDir, itemId, opts)
523
- };
524
- }
525
- //#endregion
526
11
  //#region src/collection/server/jsonlQuery.ts
527
12
  /** SQL semantics for an aggregate-only query over ZERO rows: one scalar
528
13
  * row (`count` = 0, everything else NULL) — the same shape the CSV path
@@ -559,11 +44,11 @@ function absentReferencedColumns(rows, query) {
559
44
  /** Run a validated query over enriched collection rows. */
560
45
  async function runQueryOverRows(rows, query) {
561
46
  if (rows.length === 0) return emptyCollectionResult(query);
562
- await (0, node_fs_promises.mkdir)(cacheDir(), {
47
+ await (0, node_fs_promises.mkdir)(require_discovery.cacheDir(), {
563
48
  recursive: true,
564
49
  mode: 448
565
50
  });
566
- const jsonlPath = node_path.default.join(cacheDir(), `q-${(0, node_crypto.randomBytes)(8).toString("hex")}.jsonl`);
51
+ const jsonlPath = node_path.default.join(require_discovery.cacheDir(), `q-${(0, node_crypto.randomBytes)(8).toString("hex")}.jsonl`);
567
52
  try {
568
53
  const nullPads = Object.fromEntries(absentReferencedColumns(rows, query).map((column) => [column, null]));
569
54
  const handle = await (0, node_fs_promises.open)(jsonlPath, "wx", 384);
@@ -579,8 +64,8 @@ async function runQueryOverRows(rows, query) {
579
64
  } finally {
580
65
  await handle.close();
581
66
  }
582
- const { sql, params } = compileJsonlQuery(query);
583
- return (await queryCsv(sql, [jsonlPath, ...params])).map((row) => Object.fromEntries(Object.entries(row).map(([key, value]) => [key, normalizeCsvValue(value)])));
67
+ const { sql, params } = require_discovery.compileJsonlQuery(query);
68
+ return (await require_discovery.queryCsv(sql, [jsonlPath, ...params])).map((row) => Object.fromEntries(Object.entries(row).map(([key, value]) => [key, require_discovery.normalizeCsvValue(value)])));
584
69
  } finally {
585
70
  await (0, node_fs_promises.unlink)(jsonlPath).catch((err) => {
586
71
  if (err.code !== "ENOENT") require_discovery.log.warn("collections", "temp JSONL cleanup failed", {
@@ -591,6 +76,132 @@ async function runQueryOverRows(rows, query) {
591
76
  }
592
77
  }
593
78
  //#endregion
79
+ //#region src/collection/server/derive.ts
80
+ /** Slugs of every collection referenced by a `ref` field — top-level
81
+ * and one level into `table` sub-fields (nested tables are
82
+ * schema-rejected). Mirrors the client's `uniqueRefTargets`. */
83
+ function uniqueRefTargets(schema) {
84
+ const targets = /* @__PURE__ */ new Set();
85
+ const walk = (fields) => {
86
+ for (const field of Object.values(fields)) {
87
+ if (field.type === "ref" && typeof field.to === "string" && field.to.length > 0) targets.add(field.to);
88
+ if (field.type === "table" && field.of) walk(field.of);
89
+ }
90
+ };
91
+ walk(schema.fields);
92
+ return [...targets];
93
+ }
94
+ /** Slugs of every collection referenced by an `embed` field (top-level
95
+ * only, like the client). */
96
+ function uniqueEmbedTargets(schema) {
97
+ const targets = /* @__PURE__ */ new Set();
98
+ for (const field of Object.values(schema.fields)) if (field.type === "embed" && typeof field.to === "string" && field.to.length > 0) targets.add(field.to);
99
+ return [...targets];
100
+ }
101
+ /** Slugs of every SOURCE collection a `backlinks` or `rollup` field
102
+ * reverses over — loaded exactly like ref/embed targets (whole
103
+ * collection, once; the two field kinds share one load). */
104
+ function uniqueBacklinkSources(schema) {
105
+ const sources = /* @__PURE__ */ new Set();
106
+ for (const field of Object.values(schema.fields)) if ((field.type === "backlinks" || field.type === "rollup") && field.from.length > 0) sources.add(field.from);
107
+ return [...sources];
108
+ }
109
+ async function loadTarget(slug, opts) {
110
+ const target = await require_discovery.loadCollection(slug, opts);
111
+ if (!target) return null;
112
+ const items = await require_discovery.storeFor(target, { workspaceRoot: opts.workspaceRoot }).list();
113
+ const byId = {};
114
+ for (const item of items) {
115
+ const itemId = item[target.schema.primaryKey];
116
+ if (typeof itemId === "string" && itemId.length > 0) byId[itemId] = require_promptSafety.deriveAll(target.schema, item, {});
117
+ }
118
+ return {
119
+ schema: target.schema,
120
+ byId
121
+ };
122
+ }
123
+ /** Load every ref/embed target and backlink source collection once.
124
+ * Unknown / unloadable targets are simply absent — downstream derefs
125
+ * resolve to null (em-dash) and backlinks to an empty row set, the
126
+ * same fail-soft the UI renders. */
127
+ async function loadLinkedTargets(schema, opts) {
128
+ const slugs = [.../* @__PURE__ */ new Set([
129
+ ...uniqueRefTargets(schema),
130
+ ...uniqueEmbedTargets(schema),
131
+ ...uniqueBacklinkSources(schema)
132
+ ])];
133
+ const loaded = {};
134
+ for (const slug of slugs) {
135
+ const target = await loadTarget(slug, opts);
136
+ if (target) loaded[slug] = target;
137
+ }
138
+ return loaded;
139
+ }
140
+ function toRefRecords(linked) {
141
+ return Object.fromEntries(Object.entries(linked).map(([slug, target]) => [slug, target.byId]));
142
+ }
143
+ /** The matching source rows for one `backlinks` field, projected to the
144
+ * source primaryKey + `display` columns — so getItems on a
145
+ * heavily-referenced record stays a summary, not a dump of the source
146
+ * collection. Missing source ⇒ [] (fail-soft). The rows come from the
147
+ * DERIVED source records (`byId`), so `display`/`filter` on a derived
148
+ * source column (an invoice `total`) works. */
149
+ function projectBacklinks(field, schema, enriched, linked) {
150
+ const source = linked[field.from];
151
+ if (!source) return [];
152
+ return require_promptSafety.backlinkRows(field, String(enriched[schema.primaryKey] ?? ""), Object.values(source.byId)).map((row) => require_promptSafety.projectBacklinkRow(row, field.display, source.schema.primaryKey));
153
+ }
154
+ /** Project the computed (never-stored) field kinds onto one derived
155
+ * record: `toggle` → boolean off its enum, `embed` → the target record
156
+ * (fixed `id` or per-record `idField`), or null when missing,
157
+ * `backlinks` → the matching source rows (see `projectBacklinks`). */
158
+ function projectComputed(schema, enriched, linked) {
159
+ for (const [key, field] of Object.entries(schema.fields)) {
160
+ if (field.type === "toggle" && field.field) enriched[key] = String(enriched[field.field] ?? "") === field.onValue;
161
+ if (field.type === "embed" && field.to) {
162
+ const targetId = require_ids.embedTargetId(field, enriched);
163
+ enriched[key] = targetId && linked[field.to]?.byId[targetId] || null;
164
+ }
165
+ if (field.type === "backlinks") enriched[key] = projectBacklinks(field, schema, enriched, linked);
166
+ }
167
+ return enriched;
168
+ }
169
+ /** Resolve every rollup field onto a COPY of the record, BEFORE the
170
+ * formula pass — a `derived` formula may reference rollup values as
171
+ * plain identifiers (`played = homePlayed + awayPlayed`). Same reverse
172
+ * machinery as backlinks, collapsed to a number: an unresolvable source
173
+ * is null (a formula reading it fails soft to em-dash); an empty match
174
+ * set is a real 0. Returns `record` unchanged when the schema declares
175
+ * no rollups. */
176
+ function projectRollups(schema, record, linked) {
177
+ let out = record;
178
+ for (const [key, field] of Object.entries(schema.fields)) {
179
+ if (field.type !== "rollup") continue;
180
+ if (out === record) out = { ...record };
181
+ const source = linked[field.from];
182
+ const selfId = String(record[schema.primaryKey] ?? "");
183
+ out[key] = source ? require_promptSafety.rollupValue(field, selfId, Object.values(source.byId)) : null;
184
+ }
185
+ return out;
186
+ }
187
+ /** Enrich records with every host-computed field: derived formulas
188
+ * evaluated (cross-collection derefs included), toggles projected,
189
+ * embeds resolved. Loads each linked collection ONCE per call. Input
190
+ * records are not mutated. */
191
+ async function enrichItems(collection, items, opts = {}) {
192
+ const { schema } = collection;
193
+ const linked = await loadLinkedTargets(schema, opts);
194
+ const refRecords = toRefRecords(linked);
195
+ return items.map((item) => projectComputed(schema, require_promptSafety.deriveAll(schema, projectRollups(schema, item, linked), refRecords), linked));
196
+ }
197
+ //#endregion
198
+ //#region src/collection/server/queryRunner.ts
199
+ async function runCollectionQuery(collection, query, opts = {}) {
200
+ const store = require_discovery.storeFor(collection, { workspaceRoot: opts.workspaceRoot });
201
+ if (store.query) return store.query(query);
202
+ return runQueryOverRows(await enrichItems(collection, await store.list(), opts), query);
203
+ }
204
+ //#endregion
594
205
  //#region src/collection/core/recordZ.ts
595
206
  /** The emptiness rule shared by `required` and the "only check present
596
207
  * values" gate. NOT a truthiness check — `0` and `false` are filled. */
@@ -708,6 +319,7 @@ async function listRecordFilenames(dataDir, workspaceRoot) {
708
319
  }
709
320
  async function validateCollectionRecords(collection, opts = {}) {
710
321
  if (collection.schema.dataSource !== void 0) return [];
322
+ if (collection.schema.storage !== void 0) return validateStoreRecords(collection, opts);
711
323
  const workspaceRoot = opts.workspaceRoot ?? require_discovery.getWorkspaceRoot();
712
324
  const entries = await listRecordFilenames(collection.dataDir, workspaceRoot);
713
325
  const issues = [];
@@ -719,6 +331,34 @@ async function validateCollectionRecords(collection, opts = {}) {
719
331
  }
720
332
  return issues;
721
333
  }
334
+ /** Store-backed twin of the file scan: list every record through the
335
+ * collection's store and lint it with the same "strict" report-only tier.
336
+ * A row the store can't even parse is invisible here (the store skips
337
+ * it), so the read/parse classifications of the file scan don't apply —
338
+ * schema violations are what this catches. `file` carries the record id
339
+ * (there is no per-record filename). */
340
+ async function validateStoreRecords(collection, opts) {
341
+ let items;
342
+ try {
343
+ items = await require_discovery.storeFor(collection, { workspaceRoot: opts.workspaceRoot }).list();
344
+ } catch (err) {
345
+ return [{
346
+ file: "(store)",
347
+ problem: `records could not be read from the storage backend: ${err instanceof Error ? err.message : String(err)}`
348
+ }];
349
+ }
350
+ const issues = [];
351
+ for (const item of items) {
352
+ if (issues.length >= MAX_ISSUES) break;
353
+ const itemId = String(item[collection.schema.primaryKey] ?? "");
354
+ const problem = validateRecordObject(item, itemId, collection.schema, "strict");
355
+ if (problem) issues.push({
356
+ file: itemId,
357
+ problem
358
+ });
359
+ }
360
+ return issues;
361
+ }
722
362
  async function readRecordText(fullPath, name) {
723
363
  try {
724
364
  if (!(await (0, node_fs_promises.lstat)(fullPath)).isFile()) return {
@@ -796,7 +436,13 @@ async function applyMutateAction(collection, action, itemId, params, opts = {})
796
436
  status: "invalid-params",
797
437
  problem: paramProblem
798
438
  };
799
- const existing = await require_discovery.readItem(collection.dataDir, itemId, opts);
439
+ const store = require_discovery.storeFor(collection, opts);
440
+ if (!store.write) return {
441
+ ok: false,
442
+ status: "write-refused",
443
+ problem: "write refused (read-only collection)"
444
+ };
445
+ const existing = await store.read(itemId);
800
446
  if (!existing) return {
801
447
  ok: false,
802
448
  status: "not-found",
@@ -821,10 +467,7 @@ async function applyMutateAction(collection, action, itemId, params, opts = {})
821
467
  status: "invalid-record",
822
468
  problem: invalid
823
469
  };
824
- const result = await require_discovery.writeItem(collection.dataDir, itemId, merged, {
825
- workspaceRoot: opts.workspaceRoot,
826
- slug: collection.slug
827
- });
470
+ const result = await store.write(itemId, merged);
828
471
  if (result.kind !== "ok") return {
829
472
  ok: false,
830
473
  status: "write-refused",
@@ -882,14 +525,14 @@ async function countRecordFiles(dataDir, workspaceRoot) {
882
525
  }
883
526
  }
884
527
  /** A collection's record count for the ontology. File-backed: the cheap
885
- * readdir count. `dataSource`-backed: the row count via the CSV store
886
- * (subject to its row cap) — the dataDir is a phantom there, so the
887
- * readdir count would always misreport 0. Fail-soft to 0 like
888
- * `countRecordFiles` (an unreadable file / missing DuckDB must not
528
+ * readdir count. Every other backend (`dataSource` CSV, `storage`
529
+ * sqlite): the store's page total — the dataDir is a phantom there, so
530
+ * the readdir count would always misreport 0. Fail-soft to 0 like
531
+ * `countRecordFiles` (an unreadable file / missing engine must not
889
532
  * break the whole ontology). */
890
533
  async function countRecords(collection, workspaceRoot) {
891
- if (!collectionWritable(collection)) try {
892
- return (await storeFor(collection, { workspaceRoot }).list()).length;
534
+ if (require_ids.storageKindFor(collection.schema) !== "file") try {
535
+ return (await require_discovery.storeFor(collection, { workspaceRoot }).page({ limit: 0 })).total;
893
536
  } catch {
894
537
  return 0;
895
538
  }
@@ -916,125 +559,6 @@ async function buildWorkspaceOntology(opts = {}) {
916
559
  return Promise.all(collections.map((collection) => toOntologyEntry(collection, workspaceRoot)));
917
560
  }
918
561
  //#endregion
919
- //#region src/collection/server/derive.ts
920
- /** Slugs of every collection referenced by a `ref` field — top-level
921
- * and one level into `table` sub-fields (nested tables are
922
- * schema-rejected). Mirrors the client's `uniqueRefTargets`. */
923
- function uniqueRefTargets(schema) {
924
- const targets = /* @__PURE__ */ new Set();
925
- const walk = (fields) => {
926
- for (const field of Object.values(fields)) {
927
- if (field.type === "ref" && typeof field.to === "string" && field.to.length > 0) targets.add(field.to);
928
- if (field.type === "table" && field.of) walk(field.of);
929
- }
930
- };
931
- walk(schema.fields);
932
- return [...targets];
933
- }
934
- /** Slugs of every collection referenced by an `embed` field (top-level
935
- * only, like the client). */
936
- function uniqueEmbedTargets(schema) {
937
- const targets = /* @__PURE__ */ new Set();
938
- for (const field of Object.values(schema.fields)) if (field.type === "embed" && typeof field.to === "string" && field.to.length > 0) targets.add(field.to);
939
- return [...targets];
940
- }
941
- /** Slugs of every SOURCE collection a `backlinks` or `rollup` field
942
- * reverses over — loaded exactly like ref/embed targets (whole
943
- * collection, once; the two field kinds share one load). */
944
- function uniqueBacklinkSources(schema) {
945
- const sources = /* @__PURE__ */ new Set();
946
- for (const field of Object.values(schema.fields)) if ((field.type === "backlinks" || field.type === "rollup") && field.from.length > 0) sources.add(field.from);
947
- return [...sources];
948
- }
949
- async function loadTarget(slug, opts) {
950
- const target = await require_discovery.loadCollection(slug, opts);
951
- if (!target) return null;
952
- const items = await storeFor(target, { workspaceRoot: opts.workspaceRoot }).list();
953
- const byId = {};
954
- for (const item of items) {
955
- const itemId = item[target.schema.primaryKey];
956
- if (typeof itemId === "string" && itemId.length > 0) byId[itemId] = require_promptSafety.deriveAll(target.schema, item, {});
957
- }
958
- return {
959
- schema: target.schema,
960
- byId
961
- };
962
- }
963
- /** Load every ref/embed target and backlink source collection once.
964
- * Unknown / unloadable targets are simply absent — downstream derefs
965
- * resolve to null (em-dash) and backlinks to an empty row set, the
966
- * same fail-soft the UI renders. */
967
- async function loadLinkedTargets(schema, opts) {
968
- const slugs = [.../* @__PURE__ */ new Set([
969
- ...uniqueRefTargets(schema),
970
- ...uniqueEmbedTargets(schema),
971
- ...uniqueBacklinkSources(schema)
972
- ])];
973
- const loaded = {};
974
- for (const slug of slugs) {
975
- const target = await loadTarget(slug, opts);
976
- if (target) loaded[slug] = target;
977
- }
978
- return loaded;
979
- }
980
- function toRefRecords(linked) {
981
- return Object.fromEntries(Object.entries(linked).map(([slug, target]) => [slug, target.byId]));
982
- }
983
- /** The matching source rows for one `backlinks` field, projected to the
984
- * source primaryKey + `display` columns — so getItems on a
985
- * heavily-referenced record stays a summary, not a dump of the source
986
- * collection. Missing source ⇒ [] (fail-soft). The rows come from the
987
- * DERIVED source records (`byId`), so `display`/`filter` on a derived
988
- * source column (an invoice `total`) works. */
989
- function projectBacklinks(field, schema, enriched, linked) {
990
- const source = linked[field.from];
991
- if (!source) return [];
992
- return require_promptSafety.backlinkRows(field, String(enriched[schema.primaryKey] ?? ""), Object.values(source.byId)).map((row) => require_promptSafety.projectBacklinkRow(row, field.display, source.schema.primaryKey));
993
- }
994
- /** Project the computed (never-stored) field kinds onto one derived
995
- * record: `toggle` → boolean off its enum, `embed` → the target record
996
- * (fixed `id` or per-record `idField`), or null when missing,
997
- * `backlinks` → the matching source rows (see `projectBacklinks`). */
998
- function projectComputed(schema, enriched, linked) {
999
- for (const [key, field] of Object.entries(schema.fields)) {
1000
- if (field.type === "toggle" && field.field) enriched[key] = String(enriched[field.field] ?? "") === field.onValue;
1001
- if (field.type === "embed" && field.to) {
1002
- const targetId = require_ids.embedTargetId(field, enriched);
1003
- enriched[key] = targetId && linked[field.to]?.byId[targetId] || null;
1004
- }
1005
- if (field.type === "backlinks") enriched[key] = projectBacklinks(field, schema, enriched, linked);
1006
- }
1007
- return enriched;
1008
- }
1009
- /** Resolve every rollup field onto a COPY of the record, BEFORE the
1010
- * formula pass — a `derived` formula may reference rollup values as
1011
- * plain identifiers (`played = homePlayed + awayPlayed`). Same reverse
1012
- * machinery as backlinks, collapsed to a number: an unresolvable source
1013
- * is null (a formula reading it fails soft to em-dash); an empty match
1014
- * set is a real 0. Returns `record` unchanged when the schema declares
1015
- * no rollups. */
1016
- function projectRollups(schema, record, linked) {
1017
- let out = record;
1018
- for (const [key, field] of Object.entries(schema.fields)) {
1019
- if (field.type !== "rollup") continue;
1020
- if (out === record) out = { ...record };
1021
- const source = linked[field.from];
1022
- const selfId = String(record[schema.primaryKey] ?? "");
1023
- out[key] = source ? require_promptSafety.rollupValue(field, selfId, Object.values(source.byId)) : null;
1024
- }
1025
- return out;
1026
- }
1027
- /** Enrich records with every host-computed field: derived formulas
1028
- * evaluated (cross-collection derefs included), toggles projected,
1029
- * embeds resolved. Loads each linked collection ONCE per call. Input
1030
- * records are not mutated. */
1031
- async function enrichItems(collection, items, opts = {}) {
1032
- const { schema } = collection;
1033
- const linked = await loadLinkedTargets(schema, opts);
1034
- const refRecords = toRefRecords(linked);
1035
- return items.map((item) => projectComputed(schema, require_promptSafety.deriveAll(schema, projectRollups(schema, item, linked), refRecords), linked));
1036
- }
1037
- //#endregion
1038
562
  //#region src/collection/server/dynamicIcon.ts
1039
563
  /** Index `items` by their `primaryKey` value — the `recordsById` map a
1040
564
  * `valueFrom` reference (e.g. `_config.defaultCity`) resolves against.
@@ -1067,7 +591,7 @@ async function computeCollectionIcon(collection, opts = {}) {
1067
591
  try {
1068
592
  const source = await require_discovery.loadCollection(spec.source.collection, opts);
1069
593
  if (!source) return spec.fallback ?? schema.icon;
1070
- const ordered = sortByPrimaryKey(await storeFor(source, { workspaceRoot: opts.workspaceRoot }).list(), source.schema.primaryKey);
594
+ const ordered = sortByPrimaryKey(await require_discovery.storeFor(source, { workspaceRoot: opts.workspaceRoot }).list(), source.schema.primaryKey);
1071
595
  const orderBy = spec.source.orderBy ?? require_promptSafety.firstDateField(source.schema);
1072
596
  const recordsById = buildRecordsById(ordered, source.schema.primaryKey);
1073
597
  return require_promptSafety.resolveIcon(require_promptSafety.selectDynamicRecord(ordered, spec.source, orderBy, recordsById), spec, schema.icon, recordsById);
@@ -1255,7 +779,8 @@ function logSpawnSkip(slug, triggerField, every, sourceItem, sourceId) {
1255
779
  * predicate doesn't match, the trigger date is unparseable, or the
1256
780
  * successor already exists (create-if-absent). Never overwrites an
1257
781
  * existing successor — protects any edits the user made to it. */
1258
- async function maybeSpawnSuccessor(slug, schema, dataDir, sourceItem, sourceId, ioOpts = {}) {
782
+ async function maybeSpawnSuccessor(collection, sourceItem, sourceId, ioOpts = {}) {
783
+ const { slug, schema } = collection;
1259
784
  const { spawn } = schema;
1260
785
  if (!spawn || !schema.triggerField) return;
1261
786
  if (!matchesWhen(spawn.when, schema, sourceItem)) return;
@@ -1273,11 +798,15 @@ async function maybeSpawnSuccessor(slug, schema, dataDir, sourceItem, sourceId,
1273
798
  return;
1274
799
  }
1275
800
  try {
1276
- const result = await require_discovery.writeItem(dataDir, computed.id, computed.record, {
1277
- ...ioOpts,
1278
- refuseOverwrite: true,
1279
- slug
1280
- });
801
+ const { write } = require_discovery.storeFor(collection, ioOpts);
802
+ if (!write) {
803
+ require_discovery.log.warn("collections", "spawn skipped: collection store is read-only", {
804
+ slug,
805
+ sourceId
806
+ });
807
+ return;
808
+ }
809
+ const result = await write(computed.id, computed.record, { refuseOverwrite: true });
1281
810
  if (result.kind === "ok") require_discovery.log.info("collections", "spawned successor", {
1282
811
  slug,
1283
812
  sourceId,
@@ -1335,7 +864,8 @@ function deleteTargets(collection, workspaceRoot) {
1335
864
  return [
1336
865
  stagingSkillDir(workspaceRoot, collection.slug),
1337
866
  collection.skillDir,
1338
- collection.dataDir
867
+ collection.dataDir,
868
+ ...collection.storageFile !== void 0 ? [collection.storageFile] : []
1339
869
  ];
1340
870
  }
1341
871
  /** The records directory the delete recursively archives + removes
@@ -1367,6 +897,11 @@ function isDataDirSafe(dataDir, slug, workspaceRoot) {
1367
897
  * `dataSource` collection has no record files to copy (its rows live in
1368
898
  * the external data file, which the delete never touches). */
1369
899
  function restoreRecordsStep(schema) {
900
+ if (schema.storage !== void 0) return `2. Records: copy the archived database file
901
+ \`${node_path.default.basename(schema.storage.path)}\` (next to this document) back to
902
+ \`${schema.storage.path}\` (workspace-relative, \`cp\`). It holds every
903
+ record. If \`-wal\`/\`-journal\` sidecar files were archived alongside
904
+ it, copy them back too.`;
1370
905
  if (schema.dataPath === void 0) return `2. Records: nothing to copy. This is a \`dataSource\` collection —
1371
906
  its records are the rows of \`${schema.dataSource?.path}\`, which the
1372
907
  delete never touched.`;
@@ -1403,7 +938,7 @@ ${restoreRecordsStep(schema)}
1403
938
 
1404
939
  - slug: \`${slug}\`
1405
940
  - title: ${schema.title}
1406
- - dataPath: \`${schema.dataPath ?? `(dataSource) ${schema.dataSource?.path}`}\`
941
+ - dataPath: \`${schema.dataPath ?? (schema.storage !== void 0 ? `(storage) ${schema.storage.path}` : `(dataSource) ${schema.dataSource?.path}`)}\`
1407
942
  `;
1408
943
  }
1409
944
  /** Copy one skill copy + the records + RESTORE.md into `archiveDir`. */
@@ -1411,6 +946,18 @@ async function writeArchive(collection, archiveDir, workspaceRoot) {
1411
946
  const staging = stagingSkillDir(workspaceRoot, collection.slug);
1412
947
  await (0, node_fs_promises.cp)(await pathExists(staging) ? staging : collection.skillDir, node_path.default.join(archiveDir, "skill"), { recursive: true });
1413
948
  if (await pathExists(collection.dataDir)) await (0, node_fs_promises.cp)(collection.dataDir, node_path.default.join(archiveDir, "records"), { recursive: true });
949
+ if (collection.storageFile !== void 0 && await pathExists(collection.storageFile)) {
950
+ const checkpointed = await require_discovery.checkpointSqliteDatabase(collection.storageFile);
951
+ await (0, node_fs_promises.cp)(collection.storageFile, node_path.default.join(archiveDir, node_path.default.basename(collection.storageFile)));
952
+ if (!checkpointed) for (const suffix of [
953
+ "-wal",
954
+ "-journal",
955
+ "-shm"
956
+ ]) {
957
+ const sidecar = `${collection.storageFile}${suffix}`;
958
+ if (await pathExists(sidecar)) await (0, node_fs_promises.cp)(sidecar, node_path.default.join(archiveDir, node_path.default.basename(sidecar)));
959
+ }
960
+ }
1414
961
  await (0, node_fs_promises.writeFile)(node_path.default.join(archiveDir, "RESTORE.md"), buildRestoreDoc(collection), "utf-8");
1415
962
  }
1416
963
  /** Remove all three locations. `rm -rf`-style (force) so a missing dir
@@ -1430,6 +977,12 @@ async function removeLocations(collection, workspaceRoot) {
1430
977
  force: true
1431
978
  });
1432
979
  await (0, node_fs_promises.rmdir)(node_path.default.dirname(collection.dataDir)).catch(() => void 0);
980
+ if (collection.storageFile !== void 0) {
981
+ await (0, node_fs_promises.rm)(collection.storageFile, { force: true });
982
+ await (0, node_fs_promises.rm)(`${collection.storageFile}-wal`, { force: true });
983
+ await (0, node_fs_promises.rm)(`${collection.storageFile}-journal`, { force: true });
984
+ await (0, node_fs_promises.rm)(`${collection.storageFile}-shm`, { force: true });
985
+ }
1433
986
  }
1434
987
  async function deleteCollection(collection, opts = {}) {
1435
988
  const { slug } = collection;
@@ -1626,7 +1179,7 @@ async function recordIssuesWarning(collection, deps) {
1626
1179
  return `${issues.length} record file(s) have data problems and are missing from this result. Fix each (Read → correct → Write):\n${lines}`;
1627
1180
  }
1628
1181
  async function loadRequestedItems(collection, ids, deps) {
1629
- const store = storeFor(collection, { workspaceRoot: deps.workspaceRoot });
1182
+ const store = require_discovery.storeFor(collection, { workspaceRoot: deps.workspaceRoot });
1630
1183
  if (!ids) return {
1631
1184
  items: await store.list(),
1632
1185
  missing: []
@@ -1688,13 +1241,13 @@ function computedKeyProblem(record, schema) {
1688
1241
  * `toggle` value, and re-writing it would perpetuate a forged
1689
1242
  * host-computed value. A merge heals the record instead.
1690
1243
  *
1691
- * readItem THROWS on a malformed stored file (only ENOENT is null) —
1244
+ * the store read THROWS on a malformed stored file (only ENOENT is null) —
1692
1245
  * downgraded to a per-row rejection here, like loadRequestedItems'
1693
1246
  * `missing`, so one broken file can't abort the whole putItems batch. */
1694
- async function mergeWithExisting(collection, record, itemId, deps) {
1247
+ async function mergeWithExisting(collection, store, record, itemId) {
1695
1248
  let existing;
1696
1249
  try {
1697
- existing = await require_discovery.readItem(collection.dataDir, itemId, { workspaceRoot: deps.workspaceRoot });
1250
+ existing = await store.read(itemId);
1698
1251
  } catch {
1699
1252
  return `'${itemId}' has a malformed stored file — mode "merge" needs to read it; fix the file (Read → correct → Write) or replace it whole with "upsert"`;
1700
1253
  }
@@ -1708,7 +1261,7 @@ async function mergeWithExisting(collection, record, itemId, deps) {
1708
1261
  ...record
1709
1262
  };
1710
1263
  }
1711
- async function putOneItem(collection, record, mode, deps) {
1264
+ async function putOneItem(collection, store, write, record, mode, deps) {
1712
1265
  const { schema } = collection;
1713
1266
  const itemId = require_discovery.resolveCreateItemId(schema, record);
1714
1267
  const reject = (about, problem) => ({ rejected: {
@@ -1720,7 +1273,7 @@ async function putOneItem(collection, record, mode, deps) {
1720
1273
  if (computed) return reject(itemId, computed);
1721
1274
  let toWrite = record;
1722
1275
  if (mode === "merge") {
1723
- const merged = await mergeWithExisting(collection, record, itemId, deps);
1276
+ const merged = await mergeWithExisting(collection, store, record, itemId);
1724
1277
  if (typeof merged === "string") return reject(itemId, merged);
1725
1278
  toWrite = merged;
1726
1279
  }
@@ -1728,11 +1281,7 @@ async function putOneItem(collection, record, mode, deps) {
1728
1281
  const invalid = validateRecordObject(toWrite, itemId, schema);
1729
1282
  if (invalid) return reject(itemId, invalid);
1730
1283
  }
1731
- const result = await require_discovery.writeItem(collection.dataDir, itemId, toWrite, {
1732
- refuseOverwrite: mode === "create",
1733
- workspaceRoot: deps.workspaceRoot,
1734
- slug: collection.slug
1735
- });
1284
+ const result = await write(itemId, toWrite, { refuseOverwrite: mode === "create" });
1736
1285
  if (result.kind === "ok") return { written: result.itemId };
1737
1286
  if (result.kind === "invalid-id") return reject(itemId, `'${itemId}' is not a valid record id (letters/digits at the ends; -, _, or . inside; no '..' or path characters)`);
1738
1287
  if (result.kind === "conflict") return reject(itemId, `'${itemId}' already exists — mode "create" refuses overwrite; use "upsert" to update it`);
@@ -1740,25 +1289,14 @@ async function putOneItem(collection, record, mode, deps) {
1740
1289
  }
1741
1290
  /** Aggregation over a collection via the structured query DSL
1742
1291
  * (`core/queryZ.ts`) — the paved road for counts / sums / group-bys
1743
- * that a row listing can't answer honestly. Two engines behind one
1744
- * shape: a dataSource collection queries its CSV natively through the
1745
- * store (`store.query`, uncapped whole-file scan); a file-backed
1746
- * collection aggregates its ENRICHED records (computed fields
1747
- * `derived` / `rollup` / `toggle` — are real columns) through the same
1748
- * compiled SQL over a temp JSONL (`runQueryOverRows`). */
1292
+ * that a row listing can't answer honestly. The engine choice (native
1293
+ * store query vs enrich-then-JSONL fallback) lives in
1294
+ * `runCollectionQuery`, shared with the desktop custom view's `/query`
1295
+ * route so the two surfaces can never drift. */
1749
1296
  async function handleQueryItems(collection, queryArg, deps) {
1750
- const parsed = CollectionQueryZ.safeParse(queryArg);
1297
+ const parsed = require_discovery.CollectionQueryZ.safeParse(queryArg);
1751
1298
  if (!parsed.success) return `manageCollection: \`query\` rejected — fix and retry:\n${parsed.error.issues.slice(0, 20).map((issue) => `- ${issue.path.map(String).join(".") || "(root)"}: ${require_promptSafety.defangForPrompt(issue.message)}`).join("\n")}`;
1752
- const store = storeFor(collection, { workspaceRoot: deps.workspaceRoot });
1753
- if (store.query) {
1754
- const rows = await store.query(parsed.data);
1755
- return JSON.stringify({
1756
- collection: collection.slug,
1757
- count: rows.length,
1758
- rows
1759
- });
1760
- }
1761
- const rows = await runQueryOverRows(await enrichItems(collection, await store.list(), deps), parsed.data);
1299
+ const rows = await runCollectionQuery(collection, parsed.data, { workspaceRoot: deps.workspaceRoot });
1762
1300
  return JSON.stringify({
1763
1301
  collection: collection.slug,
1764
1302
  count: rows.length,
@@ -1766,11 +1304,13 @@ async function handleQueryItems(collection, queryArg, deps) {
1766
1304
  });
1767
1305
  }
1768
1306
  async function handlePutItems(collection, args, deps) {
1769
- if (!collectionWritable(collection)) return `manageCollection: ${readOnlyRefusal(collection.slug)} (its records are the rows of '${collection.schema.dataSource?.path}'; edit that file to change the data).`;
1307
+ const store = require_discovery.storeFor(collection, { workspaceRoot: deps.workspaceRoot });
1308
+ const { write } = store;
1309
+ if (!write) return `manageCollection: ${require_discovery.readOnlyRefusal(collection.slug)} (its records are the rows of '${collection.schema.dataSource?.path}'; edit that file to change the data).`;
1770
1310
  const written = [];
1771
1311
  const rejected = [];
1772
1312
  for (const record of args.items) {
1773
- const outcome = await putOneItem(collection, record, args.mode, deps);
1313
+ const outcome = await putOneItem(collection, store, write, record, args.mode, deps);
1774
1314
  if (outcome.written) written.push(outcome.written);
1775
1315
  if (outcome.rejected) rejected.push(outcome.rejected);
1776
1316
  }
@@ -1986,30 +1526,6 @@ function makeManageCollectionTool(deps = {}) {
1986
1526
  };
1987
1527
  }
1988
1528
  //#endregion
1989
- Object.defineProperty(exports, "CollectionQueryZ", {
1990
- enumerable: true,
1991
- get: function() {
1992
- return CollectionQueryZ;
1993
- }
1994
- });
1995
- Object.defineProperty(exports, "DEFAULT_QUERY_ROWS", {
1996
- enumerable: true,
1997
- get: function() {
1998
- return DEFAULT_QUERY_ROWS;
1999
- }
2000
- });
2001
- Object.defineProperty(exports, "MAX_CSV_ROWS", {
2002
- enumerable: true,
2003
- get: function() {
2004
- return MAX_CSV_ROWS;
2005
- }
2006
- });
2007
- Object.defineProperty(exports, "MAX_QUERY_ROWS", {
2008
- enumerable: true,
2009
- get: function() {
2010
- return MAX_QUERY_ROWS;
2011
- }
2012
- });
2013
1529
  Object.defineProperty(exports, "MAX_SCHEMA_ISSUES", {
2014
1530
  enumerable: true,
2015
1531
  get: function() {
@@ -2046,24 +1562,6 @@ Object.defineProperty(exports, "buildWorkspaceOntology", {
2046
1562
  return buildWorkspaceOntology;
2047
1563
  }
2048
1564
  });
2049
- Object.defineProperty(exports, "collectionWritable", {
2050
- enumerable: true,
2051
- get: function() {
2052
- return collectionWritable;
2053
- }
2054
- });
2055
- Object.defineProperty(exports, "compileCsvQuery", {
2056
- enumerable: true,
2057
- get: function() {
2058
- return compileCsvQuery;
2059
- }
2060
- });
2061
- Object.defineProperty(exports, "compileJsonlQuery", {
2062
- enumerable: true,
2063
- get: function() {
2064
- return compileJsonlQuery;
2065
- }
2066
- });
2067
1565
  Object.defineProperty(exports, "compileRecordZ", {
2068
1566
  enumerable: true,
2069
1567
  get: function() {
@@ -2082,30 +1580,12 @@ Object.defineProperty(exports, "computeSuccessor", {
2082
1580
  return computeSuccessor;
2083
1581
  }
2084
1582
  });
2085
- Object.defineProperty(exports, "csvRowToItem", {
2086
- enumerable: true,
2087
- get: function() {
2088
- return csvRowToItem;
2089
- }
2090
- });
2091
1583
  Object.defineProperty(exports, "daysInMonth", {
2092
1584
  enumerable: true,
2093
1585
  get: function() {
2094
1586
  return daysInMonth;
2095
1587
  }
2096
1588
  });
2097
- Object.defineProperty(exports, "decodeCsvRecordId", {
2098
- enumerable: true,
2099
- get: function() {
2100
- return decodeCsvRecordId;
2101
- }
2102
- });
2103
- Object.defineProperty(exports, "dedupeByRecordId", {
2104
- enumerable: true,
2105
- get: function() {
2106
- return dedupeByRecordId;
2107
- }
2108
- });
2109
1589
  Object.defineProperty(exports, "deleteCollection", {
2110
1590
  enumerable: true,
2111
1591
  get: function() {
@@ -2124,12 +1604,6 @@ Object.defineProperty(exports, "deleteCustomView", {
2124
1604
  return deleteCustomView;
2125
1605
  }
2126
1606
  });
2127
- Object.defineProperty(exports, "encodeCsvRecordId", {
2128
- enumerable: true,
2129
- get: function() {
2130
- return encodeCsvRecordId;
2131
- }
2132
- });
2133
1607
  Object.defineProperty(exports, "enrichItems", {
2134
1608
  enumerable: true,
2135
1609
  get: function() {
@@ -2172,24 +1646,12 @@ Object.defineProperty(exports, "maybeSpawnSuccessor", {
2172
1646
  return maybeSpawnSuccessor;
2173
1647
  }
2174
1648
  });
2175
- Object.defineProperty(exports, "normalizeCsvValue", {
2176
- enumerable: true,
2177
- get: function() {
2178
- return normalizeCsvValue;
2179
- }
2180
- });
2181
1649
  Object.defineProperty(exports, "parseCivil", {
2182
1650
  enumerable: true,
2183
1651
  get: function() {
2184
1652
  return parseCivil;
2185
1653
  }
2186
1654
  });
2187
- Object.defineProperty(exports, "readOnlyRefusal", {
2188
- enumerable: true,
2189
- get: function() {
2190
- return readOnlyRefusal;
2191
- }
2192
- });
2193
1655
  Object.defineProperty(exports, "recordFieldProblem", {
2194
1656
  enumerable: true,
2195
1657
  get: function() {
@@ -2202,22 +1664,22 @@ Object.defineProperty(exports, "resolveEvery", {
2202
1664
  return resolveEvery;
2203
1665
  }
2204
1666
  });
2205
- Object.defineProperty(exports, "runQueryOverRows", {
1667
+ Object.defineProperty(exports, "runCollectionQuery", {
2206
1668
  enumerable: true,
2207
1669
  get: function() {
2208
- return runQueryOverRows;
1670
+ return runCollectionQuery;
2209
1671
  }
2210
1672
  });
2211
- Object.defineProperty(exports, "schemaRelations", {
1673
+ Object.defineProperty(exports, "runQueryOverRows", {
2212
1674
  enumerable: true,
2213
1675
  get: function() {
2214
- return schemaRelations;
1676
+ return runQueryOverRows;
2215
1677
  }
2216
1678
  });
2217
- Object.defineProperty(exports, "storeFor", {
1679
+ Object.defineProperty(exports, "schemaRelations", {
2218
1680
  enumerable: true,
2219
1681
  get: function() {
2220
- return storeFor;
1682
+ return schemaRelations;
2221
1683
  }
2222
1684
  });
2223
1685
  Object.defineProperty(exports, "successorId", {
@@ -2239,4 +1701,4 @@ Object.defineProperty(exports, "validateRecordObject", {
2239
1701
  }
2240
1702
  });
2241
1703
 
2242
- //# sourceMappingURL=server-DLRz-U7F.cjs.map
1704
+ //# sourceMappingURL=server-CKdWENZ-.cjs.map