@ai-matrx/data 0.25.0 → 0.26.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.26.1 — generate --only
4
+
5
+ - `matrx-data generate --only a,b,schema.c` keeps only the named tables, views and functions (a package
6
+ that calls 29 functions of `public` generates 13 KB, not the schema's 517 KB); an unknown name fails.
7
+
8
+ Consumer action: none.
9
+
10
+ ## 0.26.0 — callAll: every row of a set-returning function
11
+
12
+ - **`callAll(db, fn, args, { order })`** — the complete read of a set-returning database function:
13
+ `rpc(…, { count: "exact" })` paged by `range` over the named `order` (`"id"` or
14
+ `{ column, ascending, nullsFirst }`; end on a key), past the 1,000-row cap; a `DatabaseError` keeps the
15
+ database's own error (code kept); a short read throws `IncompleteReadError`; an empty order is refused.
16
+ - `SupabaseLike.rpc` takes PostgREST's optional third argument (`{ count }`).
17
+
18
+ Consumer action: none (additive). Packages that paged `.rpc(...).order().range()` by hand move to `callAll`.
19
+
3
20
  ## 0.25.0 — created_by stamped; listAll keeps the database's error
4
21
 
5
22
  - **A signed-in `insert` stamps `created_by`** with the session's person when the table has a
package/bin/generate.mjs CHANGED
@@ -195,17 +195,31 @@ function readDescriptionFile(dir, name) {
195
195
  throw new Error(`${name} not found in ${dir} (neither ${name} nor ${name}.gz)`);
196
196
  }
197
197
 
198
- export function generate({ from = BUNDLED_DESCRIPTION, out, schemas, check, importFrom, flagsIndex = process.argv.includes("--flags-index") }) {
198
+ /** Keep only the named tables/views/functions (`--only a,b,schema.c`); an unknown name is an error. */
199
+ function pick(desc, only) {
200
+ if (!only?.length) return desc;
201
+ const want = new Set(only.filter((n) => !n.includes(".") || n.startsWith(`${desc.schema}.`)).map((n) => n.slice(n.indexOf(".") + 1)));
202
+ const keep = (rec) => Object.fromEntries(Object.entries(rec ?? {}).filter(([n]) => want.has(n)));
203
+ const out = { ...desc, tables: keep(desc.tables), views: keep(desc.views), functions: keep(desc.functions) };
204
+ return out;
205
+ }
206
+
207
+ export function generate({ from = BUNDLED_DESCRIPTION, out, schemas, only, check, importFrom, flagsIndex = process.argv.includes("--flags-index") }) {
199
208
  const index = JSON.parse(readDescriptionFile(from, "index.json"));
200
209
  const names = schemas?.length ? schemas : Object.keys(index.schemas).sort();
201
210
  const files = {};
202
211
  const descs = [];
203
212
  for (const s of names) {
204
213
  if (!index.schemas[s]) throw new Error(`schema "${s}" is not in the description (${Object.keys(index.schemas).length} schemas)`);
205
- const desc = JSON.parse(readDescriptionFile(from, `${s}.json`));
214
+ const desc = pick(JSON.parse(readDescriptionFile(from, `${s}.json`)), only);
206
215
  descs.push(desc);
207
216
  files[`${s}.ts`] = renderSchema(desc, index.schemas[s].sha256, importFrom);
208
217
  }
218
+ if (only?.length) {
219
+ const found = new Set(descs.flatMap((d) => [...Object.keys(d.tables), ...Object.keys(d.views), ...Object.keys(d.functions)].flatMap((n) => [n, `${d.schema}.${n}`])));
220
+ const missing = only.filter((n) => !found.has(n));
221
+ if (missing.length) throw new Error(`--only names nothing in the description: ${missing.join(", ")}`);
222
+ }
209
223
  if (flagsIndex) {
210
224
  for (const d of descs) files[`${d.schema}.flags.ts`] = renderSchemaFlags(d, importFrom);
211
225
  files["flags-index.ts"] = renderFlagLoaders(descs.map((d) => d.schema), importFrom);
@@ -3,7 +3,7 @@
3
3
  * matrx-data — @ai-matrx/data's build-time CLI.
4
4
  *
5
5
  * matrx-data emit [--out <dir>] [--check] live database → the database description (read-only)
6
- * matrx-data generate --out <dir> [--from <dir>] [--schemas a,b] [--flags-index] [--check]
6
+ * matrx-data generate --out <dir> [--from <dir>] [--schemas a,b] [--only t1,fn2] [--flags-index] [--check]
7
7
  * description → typed doors in THIS app (--from defaults to the description
8
8
  * shipped inside this package version)
9
9
  *
@@ -35,6 +35,7 @@ async function main() {
35
35
  ...(from ? { from: path.resolve(from) } : {}),
36
36
  out: path.resolve(out),
37
37
  schemas: flag("--schemas")?.split(",").map((s) => s.trim()).filter(Boolean),
38
+ only: flag("--only")?.split(",").map((s) => s.trim()).filter(Boolean),
38
39
  check: rest.includes("--check"),
39
40
  importFrom: flag("--import-from") ?? "@ai-matrx/data/db",
40
41
  });
package/dist/db.cjs CHANGED
@@ -36,6 +36,7 @@ __export(db_exports, {
36
36
  assertDoor: () => assertDoor,
37
37
  assertWriteLanded: () => assertWriteLanded,
38
38
  call: () => call,
39
+ callAll: () => callAll,
39
40
  changesOf: () => changesOf,
40
41
  count: () => count,
41
42
  createActiveOrgCookie: () => createActiveOrgCookie,
@@ -1203,6 +1204,27 @@ async function call(db, fn, args) {
1203
1204
  }
1204
1205
  return check(door, await db.client.schema(fn.schema).rpc(fn.name, args));
1205
1206
  }
1207
+ async function callAll(db, fn, args, opts) {
1208
+ const door = `callAll ${fn.schema}.${fn.name}`;
1209
+ const role = db.role === "service" ? "service_role" : "authenticated";
1210
+ if (!fn.roles.includes(role) && !(role === "authenticated" && fn.roles.includes("anon"))) {
1211
+ throw new DoorRefusedError(door, `not executable by ${role} (executable by: ${fn.roles.join(", ") || "nobody"})`);
1212
+ }
1213
+ if (!opts.order.length) throw new DoorRefusedError(door, "a paged read needs an order (end on a key) \u2014 pages over no order can skip or repeat rows");
1214
+ return readAllRows(
1215
+ ({ from: lo, to: hi }) => {
1216
+ let q = db.client.schema(fn.schema).rpc(fn.name, args, { count: "exact" });
1217
+ for (const o of opts.order) {
1218
+ q = typeof o === "string" ? q.order(o) : q.order(o.column, { ascending: o.ascending ?? true, ...o.nullsFirst !== void 0 ? { nullsFirst: o.nullsFirst } : {} });
1219
+ }
1220
+ return Promise.resolve(q.range(lo, hi)).then((res) => {
1221
+ if (res.error) throw new DatabaseError(door, res.error);
1222
+ return res;
1223
+ });
1224
+ },
1225
+ { label: door, ...opts.pageSize !== void 0 ? { pageSize: opts.pageSize } : {}, ...opts.maxRows !== void 0 ? { maxRows: opts.maxRows } : {} }
1226
+ );
1227
+ }
1206
1228
  function tableByName(index, name) {
1207
1229
  const t = index[name];
1208
1230
  if (!t) throw new DoorRefusedError(`tableByName ${name}`, "no such table in the database description (schema-qualify it: schema.table)");