@ai-matrx/data 0.22.33 → 0.24.0

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 (74) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/README.md +34 -0
  3. package/bin/direct-db.mjs +80 -0
  4. package/bin/emit.mjs +380 -0
  5. package/bin/generate.mjs +225 -0
  6. package/bin/matrx-data.mjs +49 -0
  7. package/description/admin.json.gz +0 -0
  8. package/description/agent.json.gz +0 -0
  9. package/description/ai.json.gz +0 -0
  10. package/description/api.json.gz +0 -0
  11. package/description/app.json.gz +0 -0
  12. package/description/assignment.json.gz +0 -0
  13. package/description/audit.json.gz +0 -0
  14. package/description/batch.json.gz +0 -0
  15. package/description/billing.json.gz +0 -0
  16. package/description/browser.json.gz +0 -0
  17. package/description/canvas.json.gz +0 -0
  18. package/description/chat.json.gz +0 -0
  19. package/description/code.json.gz +0 -0
  20. package/description/commerce.json.gz +0 -0
  21. package/description/communication.json.gz +0 -0
  22. package/description/content.json.gz +0 -0
  23. package/description/content_ir.json.gz +0 -0
  24. package/description/context.json.gz +0 -0
  25. package/description/crm.json.gz +0 -0
  26. package/description/custom.json.gz +0 -0
  27. package/description/dictionary.json.gz +0 -0
  28. package/description/docproc.json.gz +0 -0
  29. package/description/education.json.gz +0 -0
  30. package/description/esign.json.gz +0 -0
  31. package/description/extend.json.gz +0 -0
  32. package/description/files.json.gz +0 -0
  33. package/description/growth.json.gz +0 -0
  34. package/description/hindsight.json.gz +0 -0
  35. package/description/history.json.gz +0 -0
  36. package/description/iam.json.gz +0 -0
  37. package/description/index.json.gz +0 -0
  38. package/description/interview.json.gz +0 -0
  39. package/description/knowledge.json.gz +0 -0
  40. package/description/legal.json.gz +0 -0
  41. package/description/mandate.json.gz +0 -0
  42. package/description/marketing.json.gz +0 -0
  43. package/description/media.json.gz +0 -0
  44. package/description/meta.json.gz +0 -0
  45. package/description/ops.json.gz +0 -0
  46. package/description/pdf.json.gz +0 -0
  47. package/description/plan.json.gz +0 -0
  48. package/description/platform.json.gz +0 -0
  49. package/description/podcast.json.gz +0 -0
  50. package/description/projects.json.gz +0 -0
  51. package/description/public.json.gz +0 -0
  52. package/description/rag.json.gz +0 -0
  53. package/description/research.json.gz +0 -0
  54. package/description/runtime.json.gz +0 -0
  55. package/description/scheduler.json.gz +0 -0
  56. package/description/scraper.json.gz +0 -0
  57. package/description/seo.json.gz +0 -0
  58. package/description/skill.json.gz +0 -0
  59. package/description/social.json.gz +0 -0
  60. package/description/tool.json.gz +0 -0
  61. package/description/transcripts.json.gz +0 -0
  62. package/description/ui.json.gz +0 -0
  63. package/description/users.json.gz +0 -0
  64. package/description/web.json.gz +0 -0
  65. package/description/work.json.gz +0 -0
  66. package/description/workbench.json.gz +0 -0
  67. package/description/workflow.json.gz +0 -0
  68. package/dist/db.cjs +274 -19
  69. package/dist/db.cjs.map +1 -1
  70. package/dist/db.d.cts +270 -3
  71. package/dist/db.d.ts +270 -3
  72. package/dist/db.js +270 -15
  73. package/dist/db.js.map +1 -1
  74. package/package.json +9 -4
@@ -0,0 +1,225 @@
1
+ /**
2
+ * generate.mjs — `matrx-data generate`: the database description → typed doors,
3
+ * written INSIDE the app that uses them (Prisma's model: the package ships the
4
+ * runtime and this generator, never the generated tables, so a new column never
5
+ * waits on a package release).
6
+ *
7
+ * One file per schema (`<schema>.ts`), each self-contained (no file imports
8
+ * another), so importing one schema loads no other; every table and function is
9
+ * its own top-level `const` (flags + names only, no row data), so a bundler keeps
10
+ * exactly the descriptors used. Types: Row (client-excluded columns removed),
11
+ * Insert (defaulted / nullable / org-filled columns optional; generated and
12
+ * client-read-only columns removed), Update (Partial of Insert). Functions: one
13
+ * descriptor per name; overloads become a union of named-argument shapes.
14
+ *
15
+ * matrx-data generate --from <description dir> --out <dir> [--schemas a,b] [--check]
16
+ * [--flags-index] also write flags-index.ts for tableByName()
17
+ */
18
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
19
+ import path from "node:path";
20
+ import { fileURLToPath } from "node:url";
21
+ import { gunzipSync } from "node:zlib";
22
+
23
+ const RESERVED = new Set(
24
+ "break case catch class const continue debugger default delete do else enum export extends false finally for function if import in instanceof new null return super switch this throw true try typeof var void while with yield let static implements interface package private protected public await".split(
25
+ " ",
26
+ ),
27
+ );
28
+
29
+ const camel = (s) => s.replace(/^_+/, "").replace(/_+([a-z0-9])/g, (_, c) => c.toUpperCase());
30
+ const pascal = (s) => {
31
+ const c = camel(s);
32
+ return c.charAt(0).toUpperCase() + c.slice(1);
33
+ };
34
+ const ident = (s) => {
35
+ let c = camel(s) || "_";
36
+ if (/^[0-9]/.test(c)) c = `_${c}`;
37
+ return RESERVED.has(c) ? `${c}Table` : c;
38
+ };
39
+ const key = (s) => (/^[A-Za-z_$][\w$]*$/.test(s) ? s : JSON.stringify(s));
40
+
41
+ const NUMBER = new Set(["smallint", "integer", "bigint", "real", "double precision", "numeric", "oid", "int2", "int4", "int8", "float4", "float8"]);
42
+ const STRING_PREFIX = /^(?:text|uuid|character|char|varchar|citext|name|date|time|timestamp|interval|inet|cidr|macaddr|tsvector|tsquery|bytea|money|xml|bit|ltree|regclass|regproc|regtype|point|line|box|path|polygon|circle|int4range|int8range|tsrange|tstzrange|daterange|numrange|halfvec|vector|sparsevec)/;
43
+
44
+ function tsType(t, enums) {
45
+ let base;
46
+ if (t.enum) base = (enums[t.enum] ?? []).map((v) => JSON.stringify(v)).join(" | ") || "string";
47
+ else if (NUMBER.has(t.type)) base = "number";
48
+ else if (t.type === "boolean") base = "boolean";
49
+ else if (t.type === "json" || t.type === "jsonb") base = "Json";
50
+ else if (t.type === "void") base = "undefined";
51
+ else if (STRING_PREFIX.test(t.type)) base = "string";
52
+ else base = "unknown";
53
+ return t.array ? (base.includes("|") ? `(${base})[]` : `${base}[]`) : base;
54
+ }
55
+
56
+ function tableTypes(P, t, enums) {
57
+ const reg = t.registry ?? null;
58
+ const excluded = new Set(reg?.clientExcludedColumns ?? []);
59
+ const readOnly = new Set(reg?.clientReadOnlyColumns ?? []);
60
+ const rowLines = [];
61
+ const insLines = [];
62
+ for (const c of t.columns) {
63
+ const ty = tsType(c, enums);
64
+ if (!excluded.has(c.name)) rowLines.push(` ${key(c.name)}: ${ty}${c.nullable ? " | null" : ""};`);
65
+ if (t.kind !== "table" || c.generated || readOnly.has(c.name) || excluded.has(c.name)) continue;
66
+ const orgFilled = c.name === "organization_id" && (t.org.rule === "binding" || t.org.rule === "parent");
67
+ const optional = c.nullable || c.default || c.identity || orgFilled;
68
+ insLines.push(` ${key(c.name)}${optional ? "?" : ""}: ${ty}${c.nullable ? " | null" : ""};`);
69
+ }
70
+ let out = `export interface ${P}Row {\n${rowLines.join("\n")}\n}\n`;
71
+ if (t.kind === "table") {
72
+ out += `export interface ${P}Insert {\n${insLines.join("\n")}\n}\nexport type ${P}Update = Partial<${P}Insert>;\n`;
73
+ } else {
74
+ out += `export type ${P}Insert = never;\nexport type ${P}Update = never;\n`;
75
+ }
76
+ return out;
77
+ }
78
+
79
+ function tableDescriptor(schema, name, t) {
80
+ const reg = t.registry ?? null;
81
+ const archive = t.lifecycle.archived_at ? "archived_at" : t.lifecycle.is_archived ? "is_archived" : null;
82
+ const flags = {
83
+ schema,
84
+ name,
85
+ kind: t.kind,
86
+ type: reg?.type ?? null,
87
+ pk: t.primaryKey,
88
+ org: t.org,
89
+ versioned: !!(reg ? reg.versioned && t.lifecycle.version : t.lifecycle.version),
90
+ softDelete: !!(reg ? reg.softDelete && t.lifecycle.deleted_at : t.lifecycle.deleted_at),
91
+ archive,
92
+ readOnly: !!reg?.clientReadOnly,
93
+ deletesRefused: !!reg?.clientDeletesRefused,
94
+ readOnlyColumns: reg?.clientReadOnlyColumns ?? [],
95
+ listScope: reg?.listScope ?? null,
96
+ realtime: (t.realtime ?? []).includes("supabase_realtime"),
97
+ };
98
+ return flags;
99
+ }
100
+
101
+ function fnTypes(P, overloads, enums) {
102
+ const shapes = [];
103
+ let result = "unknown";
104
+ for (const f of overloads) {
105
+ const inputs = f.args.filter((a) => a.mode === "in" || a.mode === "inout" || a.mode === "variadic");
106
+ if (inputs.some((a) => !a.name)) {
107
+ shapes.push("never /* positional arguments: PostgREST calls by name */");
108
+ continue;
109
+ }
110
+ shapes.push(inputs.length ? `{ ${inputs.map((a) => `${key(a.name)}${a.optional ? "?" : ""}: ${tsType(a, enums)}`).join("; ")} }` : "Record<string, never>");
111
+ const outs = f.args.filter((a) => a.mode === "table" || a.mode === "out" || a.mode === "inout");
112
+ let r;
113
+ if (outs.length && (f.returns.type === "record" || f.args.some((a) => a.mode === "table"))) {
114
+ r = `{ ${outs.map((a) => `${key(a.name || "value")}: ${tsType(a, enums)} | null`).join("; ")} }`;
115
+ } else r = tsType(f.returns, enums);
116
+ if (f.returns.set) r = r.startsWith("{") || !r.includes("|") ? `${r}[]` : `(${r})[]`;
117
+ result = r;
118
+ }
119
+ const args = [...new Set(shapes)];
120
+ return `export type ${P}Args = ${args.join(" | ")};\nexport type ${P}Result = ${result};\n`;
121
+ }
122
+
123
+ const HEADER = (schema, src, importFrom) => `/**
124
+ * GENERATED by \`matrx-data generate\` from the database description — never edit by hand.
125
+ * schema: ${schema} · description ${src.slice(0, 12)}
126
+ * Regenerate: matrx-data generate (see @ai-matrx/data README).
127
+ */
128
+ /* eslint-disable */
129
+ import type { FunctionDescriptor, TableDescriptor } from "${importFrom}";
130
+
131
+ type Json = string | number | boolean | null | { [key: string]: Json | undefined } | Json[];
132
+ `;
133
+
134
+ export function renderSchema(desc, schemaSha, importFrom) {
135
+ const { schema, tables, views, functions, enums } = desc;
136
+ const used = new Set();
137
+ const take = (base) => {
138
+ let n = base;
139
+ while (used.has(n)) n = `${n}_`;
140
+ used.add(n);
141
+ return n;
142
+ };
143
+ let out = HEADER(schema, schemaSha, importFrom);
144
+ const rels = [...Object.entries(tables), ...Object.entries(views)].sort(([a], [b]) => a.localeCompare(b));
145
+ for (const [name, t] of rels) {
146
+ const id = take(ident(name));
147
+ const P = pascal(id);
148
+ out += `\n${tableTypes(P, t, enums)}export const ${id} = ${JSON.stringify(tableDescriptor(schema, name, t))} as unknown as TableDescriptor<${P}Row, ${P}Insert, ${P}Update>;\n`;
149
+ }
150
+ for (const [name, overloads] of Object.entries(functions).sort(([a], [b]) => a.localeCompare(b))) {
151
+ let id = ident(name);
152
+ if (used.has(id)) id = `${id}Fn`;
153
+ id = take(id);
154
+ const P = pascal(id);
155
+ const roles = [...new Set(overloads.flatMap((f) => f.roles))].sort();
156
+ const fd = { schema, name, roles, registered: overloads.some((f) => f.registered) };
157
+ out += `\n${fnTypes(P, overloads, enums)}export const ${id} = ${JSON.stringify(fd)} as unknown as FunctionDescriptor<${P}Args, ${P}Result>;\n`;
158
+ }
159
+ return out;
160
+ }
161
+
162
+ /** One schema's flags (`<schema>.flags.ts`) — loaded only when a runtime name names that schema. */
163
+ export function renderSchemaFlags(desc, importFrom) {
164
+ const index = {};
165
+ for (const [name, t] of Object.entries({ ...desc.tables, ...desc.views })) index[`${desc.schema}.${name}`] = tableDescriptor(desc.schema, name, t);
166
+ return `/** GENERATED by \`matrx-data generate --flags-index\` — ${desc.schema}'s table flags for tableByName(). Never edit by hand. */
167
+ /* eslint-disable */
168
+ import type { FlagsIndex } from "${importFrom}";
169
+
170
+ export const flags: FlagsIndex = ${JSON.stringify(index)} as unknown as FlagsIndex;
171
+ `;
172
+ }
173
+
174
+ /** Per-schema loaders: `loadTableByName(flagLoaders, "schema.table")` imports only that schema's flags. */
175
+ export function renderFlagLoaders(schemas, importFrom) {
176
+ return `/** GENERATED by \`matrx-data generate --flags-index\` — never edit by hand. */
177
+ /* eslint-disable */
178
+ import type { FlagLoaders } from "${importFrom}";
179
+
180
+ export const flagLoaders: FlagLoaders = {
181
+ ${schemas.map((s) => ` ${key(s)}: () => import("./${s}.flags").then((m) => m.flags),`).join("\n")}
182
+ };
183
+ `;
184
+ }
185
+
186
+ /** The description ships inside @ai-matrx/data (gzipped); `--from` points at a fresher one. */
187
+ export const BUNDLED_DESCRIPTION = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "description");
188
+
189
+ function readDescriptionFile(dir, name) {
190
+ const plain = path.join(dir, name);
191
+ if (existsSync(plain)) return readFileSync(plain, "utf8");
192
+ const gz = `${plain}.gz`;
193
+ if (existsSync(gz)) return gunzipSync(readFileSync(gz)).toString("utf8");
194
+ throw new Error(`${name} not found in ${dir} (neither ${name} nor ${name}.gz)`);
195
+ }
196
+
197
+ export function generate({ from = BUNDLED_DESCRIPTION, out, schemas, check, importFrom, flagsIndex = process.argv.includes("--flags-index") }) {
198
+ const index = JSON.parse(readDescriptionFile(from, "index.json"));
199
+ const names = schemas?.length ? schemas : Object.keys(index.schemas).sort();
200
+ const files = {};
201
+ const descs = [];
202
+ for (const s of names) {
203
+ if (!index.schemas[s]) throw new Error(`schema "${s}" is not in the description (${Object.keys(index.schemas).length} schemas)`);
204
+ const desc = JSON.parse(readDescriptionFile(from, `${s}.json`));
205
+ descs.push(desc);
206
+ files[`${s}.ts`] = renderSchema(desc, index.schemas[s].sha256, importFrom);
207
+ }
208
+ if (flagsIndex) {
209
+ for (const d of descs) files[`${d.schema}.flags.ts`] = renderSchemaFlags(d, importFrom);
210
+ files["flags-index.ts"] = renderFlagLoaders(descs.map((d) => d.schema), importFrom);
211
+ }
212
+ if (check) {
213
+ const stale = Object.entries(files).filter(([n, t]) => !existsSync(path.join(out, n)) || readFileSync(path.join(out, n), "utf8") !== t).map(([n]) => n);
214
+ if (stale.length) {
215
+ console.error(`matrx-data generate --check: stale against the description: ${stale.join(", ")}`);
216
+ return 1;
217
+ }
218
+ console.log(`matrx-data generate --check: ${Object.keys(files).length} file(s) current (contract ${index.contractSha256.slice(0, 12)})`);
219
+ return 0;
220
+ }
221
+ mkdirSync(out, { recursive: true });
222
+ for (const [n, t] of Object.entries(files)) writeFileSync(path.join(out, n), t);
223
+ console.log(`matrx-data generate: ${Object.keys(files).join(", ")} → ${out} (contract ${index.contractSha256.slice(0, 12)})`);
224
+ return 0;
225
+ }
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * matrx-data — @ai-matrx/data's build-time CLI.
4
+ *
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]
7
+ * description → typed doors in THIS app (--from defaults to the description
8
+ * shipped inside this package version)
9
+ *
10
+ * The package ships the runtime and this CLI; each app generates its own doors
11
+ * locally (Prisma's model), so a new column never waits on a package release.
12
+ */
13
+ import path from "node:path";
14
+
15
+ const [command, ...rest] = process.argv.slice(2);
16
+ const flag = (name) => {
17
+ const i = rest.indexOf(name);
18
+ return i >= 0 ? rest[i + 1] : undefined;
19
+ };
20
+
21
+ async function main() {
22
+ if (command === "emit") {
23
+ const { emit } = await import("./emit.mjs");
24
+ return emit({ out: path.resolve(flag("--out") ?? "db-contract"), check: rest.includes("--check") });
25
+ }
26
+ if (command === "generate") {
27
+ const { generate } = await import("./generate.mjs");
28
+ const from = flag("--from");
29
+ const out = flag("--out");
30
+ if (!out) {
31
+ console.error("matrx-data generate: --out <dir> is required (--from defaults to the description shipped in this package)");
32
+ return 2;
33
+ }
34
+ return generate({
35
+ ...(from ? { from: path.resolve(from) } : {}),
36
+ out: path.resolve(out),
37
+ schemas: flag("--schemas")?.split(",").map((s) => s.trim()).filter(Boolean),
38
+ check: rest.includes("--check"),
39
+ importFrom: flag("--import-from") ?? "@ai-matrx/data/db",
40
+ });
41
+ }
42
+ console.error("usage: matrx-data emit [--out <dir>] [--check] | matrx-data generate --from <dir> --out <dir> [--schemas a,b] [--check]");
43
+ return 2;
44
+ }
45
+
46
+ main().then((code) => process.exit(code ?? 0), (err) => {
47
+ console.error(`matrx-data ${command ?? ""}: ${err?.message ?? err}`);
48
+ process.exit(1);
49
+ });
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file