@schemic/core 0.1.0-alpha.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 (45) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +212 -0
  3. package/lib/authoring.d.ts +89 -0
  4. package/lib/authoring.js +187 -0
  5. package/lib/authoring.js.map +1 -0
  6. package/lib/chunk-C4D6JWSE.js +54 -0
  7. package/lib/chunk-C4D6JWSE.js.map +1 -0
  8. package/lib/chunk-T23RNU7G.js +304 -0
  9. package/lib/chunk-T23RNU7G.js.map +1 -0
  10. package/lib/config-TIiKDd9t.d.ts +97 -0
  11. package/lib/config.d.ts +1 -0
  12. package/lib/config.js +8 -0
  13. package/lib/config.js.map +1 -0
  14. package/lib/driver-Dh5hLKHm.d.ts +736 -0
  15. package/lib/driver.d.ts +150 -0
  16. package/lib/driver.js +47 -0
  17. package/lib/driver.js.map +1 -0
  18. package/lib/index.d.ts +84 -0
  19. package/lib/index.js +794 -0
  20. package/lib/index.js.map +1 -0
  21. package/lib/testing.d.ts +29 -0
  22. package/lib/testing.js +111 -0
  23. package/lib/testing.js.map +1 -0
  24. package/package.json +93 -0
  25. package/src/authoring.ts +304 -0
  26. package/src/cli-kit/config.ts +179 -0
  27. package/src/cli-kit/diff.ts +230 -0
  28. package/src/cli-kit/filter.ts +159 -0
  29. package/src/cli-kit/merge.ts +380 -0
  30. package/src/cli-kit/meta.ts +123 -0
  31. package/src/cli-kit/pager.ts +42 -0
  32. package/src/cli-kit/schema.ts +186 -0
  33. package/src/cli-kit/style.ts +24 -0
  34. package/src/config.ts +51 -0
  35. package/src/connection.ts +78 -0
  36. package/src/driver/driver.ts +300 -0
  37. package/src/driver/index.ts +31 -0
  38. package/src/driver/portable-ir.ts +51 -0
  39. package/src/driver/portable.ts +124 -0
  40. package/src/driver/sdk.ts +66 -0
  41. package/src/index.ts +145 -0
  42. package/src/kind/index.ts +28 -0
  43. package/src/kind/plan.ts +390 -0
  44. package/src/kind/registry.ts +225 -0
  45. package/src/testing.ts +181 -0
package/lib/index.js ADDED
@@ -0,0 +1,794 @@
1
+ import {
2
+ array,
3
+ connectionEntry,
4
+ isConnectionEntry,
5
+ literal,
6
+ nullable,
7
+ option,
8
+ record,
9
+ scalar,
10
+ union
11
+ } from "./chunk-C4D6JWSE.js";
12
+ import {
13
+ KindRegistry,
14
+ buildKindDiff,
15
+ driverNames,
16
+ emitKinds,
17
+ getDriver,
18
+ introspectKinds,
19
+ lowerSchema,
20
+ orderObjects,
21
+ planKinds,
22
+ registerDriver,
23
+ snapshotKinds,
24
+ snapshotObjects
25
+ } from "./chunk-T23RNU7G.js";
26
+
27
+ // src/cli-kit/config.ts
28
+ import { existsSync, statSync } from "fs";
29
+ import { dirname, resolve } from "path";
30
+ import { createJiti } from "jiti";
31
+ var CONFIG_NAMES = [
32
+ "schemic.config.ts",
33
+ "schemic.config.mjs",
34
+ "schemic.config.js"
35
+ ];
36
+ var DEFAULT_MIGRATIONS = "./database/migrations";
37
+ function schemaIsFilePath(path) {
38
+ if (existsSync(path)) return statSync(path).isFile();
39
+ return /\.[mc]?[jt]s$/.test(path);
40
+ }
41
+ function loadDotEnv(dir) {
42
+ const proc = process;
43
+ if (typeof proc.loadEnvFile !== "function") return;
44
+ for (const name of [".env.local", ".env"]) {
45
+ const file = resolve(dir, name);
46
+ if (!existsSync(file)) continue;
47
+ try {
48
+ proc.loadEnvFile(file);
49
+ } catch {
50
+ }
51
+ }
52
+ }
53
+ function makeJiti() {
54
+ return createJiti(import.meta.url, {
55
+ interopDefault: true,
56
+ fsCache: false,
57
+ moduleCache: false
58
+ });
59
+ }
60
+ async function loadProject(opts) {
61
+ const cwd = opts?.cwd ?? process.cwd();
62
+ const path = opts?.config ? resolve(cwd, opts.config) : CONFIG_NAMES.map((n) => resolve(cwd, n)).find((p) => existsSync(p));
63
+ if (!path || !existsSync(path)) {
64
+ throw new Error("No schemic.config.ts found \u2014 run `schemic init` first.");
65
+ }
66
+ const root = dirname(path);
67
+ loadDotEnv(root);
68
+ const loaded = await makeJiti().import(path);
69
+ const config = loaded.default ?? loaded;
70
+ if (!config?.connections || Object.keys(config.connections).length === 0) {
71
+ throw new Error(`Invalid config at ${path}: expected a "connections" map.`);
72
+ }
73
+ return { config, root };
74
+ }
75
+ function resolveConnectionConfig(config, connection, conn, driver, root) {
76
+ const { schema, migrations, key, ...params } = conn;
77
+ const schemaPath = resolve(root, schema);
78
+ const migrationsDir = resolve(root, migrations ?? DEFAULT_MIGRATIONS);
79
+ return {
80
+ connection: key ? `${connection}:${key}` : connection,
81
+ driver,
82
+ root,
83
+ schemaPath,
84
+ schemaIsFile: schemaIsFilePath(schemaPath),
85
+ migrationsDir,
86
+ metaDir: resolve(migrationsDir, "meta"),
87
+ migrationsTable: config.migrationsTable ?? "_migrations",
88
+ params,
89
+ seed: config.seed
90
+ };
91
+ }
92
+ async function loadConfig(opts) {
93
+ const { config, root } = await loadProject(opts);
94
+ const names = Object.keys(config.connections);
95
+ const name = config.defaultConnection ?? (names.length === 1 ? names[0] : "default");
96
+ const entry = config.connections[name];
97
+ if (!entry) {
98
+ throw new Error(
99
+ `No connection named "${name}". Set "defaultConnection" or pass --connection. Known: ${names.join(", ")}.`
100
+ );
101
+ }
102
+ const ctx = { connections: {}, args: {}, env: process.env };
103
+ const resolved = await entry.resolve(ctx);
104
+ if (resolved.length !== 1) {
105
+ throw new Error(
106
+ `Connection "${name}" resolved to ${resolved.length} connections (a collection); pass --connection ${name}:<key>.`
107
+ );
108
+ }
109
+ return resolveConnectionConfig(config, name, resolved[0], entry.driver, root);
110
+ }
111
+
112
+ // src/cli-kit/style.ts
113
+ var colorEnabled = () => Boolean(process.stdout.isTTY) && !process.env.NO_COLOR;
114
+ var paint = (code, s) => colorEnabled() ? `\x1B[${code}m${s}\x1B[0m` : s;
115
+ var style = {
116
+ green: (s) => paint(32, s),
117
+ red: (s) => paint(31, s),
118
+ yellow: (s) => paint(33, s),
119
+ cyan: (s) => paint(36, s),
120
+ dim: (s) => paint(90, s),
121
+ bold: (s) => paint(1, s)
122
+ };
123
+ var ok = (s) => `${style.green("\u2713")} ${s}`;
124
+ var fail = (s) => `${style.red("\u2717")} ${s}`;
125
+ var plural = (n, word) => `${n} ${word}${n === 1 ? "" : "s"}`;
126
+
127
+ // src/cli-kit/diff.ts
128
+ function isEmptyDiff(diff) {
129
+ return diff.up.length === 0;
130
+ }
131
+ function tokenDiff(before, after) {
132
+ const a = before.split(" ");
133
+ const b = after.split(" ");
134
+ const m = a.length;
135
+ const n = b.length;
136
+ const dp = Array.from(
137
+ { length: m + 1 },
138
+ () => new Array(n + 1).fill(0)
139
+ );
140
+ for (let i2 = m - 1; i2 >= 0; i2--) {
141
+ for (let j2 = n - 1; j2 >= 0; j2--) {
142
+ dp[i2][j2] = a[i2] === b[j2] ? dp[i2 + 1][j2 + 1] + 1 : Math.max(dp[i2 + 1][j2], dp[i2][j2 + 1]);
143
+ }
144
+ }
145
+ const colored = colorEnabled();
146
+ const del = (t) => colored ? style.red(t) : `[-${t}-]`;
147
+ const ins = (t) => colored ? style.green(t) : `{+${t}+}`;
148
+ const eq = (t) => colored ? style.dim(t) : t;
149
+ const out = [];
150
+ let i = 0;
151
+ let j = 0;
152
+ while (i < m && j < n) {
153
+ if (a[i] === b[j]) {
154
+ out.push(eq(a[i]));
155
+ i++;
156
+ j++;
157
+ } else if (dp[i + 1][j] >= dp[i][j + 1]) {
158
+ out.push(del(a[i++]));
159
+ } else {
160
+ out.push(ins(b[j++]));
161
+ }
162
+ }
163
+ while (i < m) out.push(del(a[i++]));
164
+ while (j < n) out.push(ins(b[j++]));
165
+ return out.join(" ");
166
+ }
167
+ function renderItem(it, inline = false) {
168
+ if (it.op === "add") return style.green(` + ${it.ddl}`);
169
+ if (it.op === "remove") return style.red(` - ${it.ddl}`);
170
+ if (inline) return ` ${tokenDiff(it.before, it.after)}`;
171
+ return `${style.red(` - ${it.before}`)}
172
+ ${style.green(` + ${it.after}`)}`;
173
+ }
174
+ function groupByFile(items) {
175
+ const order = [];
176
+ const byKey = /* @__PURE__ */ new Map();
177
+ for (const it of items) {
178
+ const key = it.file ?? `\0${it.table}`;
179
+ let group = byKey.get(key);
180
+ if (!group) {
181
+ group = [];
182
+ byKey.set(key, group);
183
+ order.push(key);
184
+ }
185
+ group.push(it);
186
+ }
187
+ return order.map((key) => {
188
+ const group = byKey.get(key) ?? [];
189
+ return {
190
+ header: group.find((i) => i.file)?.file ?? group[0].table,
191
+ items: group
192
+ };
193
+ });
194
+ }
195
+ function formatItems(items, inline = false) {
196
+ return groupByFile(items).map(
197
+ (g) => [
198
+ style.bold(g.header),
199
+ ...g.items.map((it) => renderItem(it, inline))
200
+ ].join("\n")
201
+ ).join("\n\n");
202
+ }
203
+ function formatPatch(diff) {
204
+ const items = diff.items ?? [];
205
+ if (!items.length) return "";
206
+ const out = [];
207
+ for (const { header, items: group } of groupByFile(items)) {
208
+ const lines = [];
209
+ let dels = 0;
210
+ let adds = 0;
211
+ for (const it of group) {
212
+ if (it.op === "add") {
213
+ lines.push(`+${it.ddl}`);
214
+ adds++;
215
+ } else if (it.op === "remove") {
216
+ lines.push(`-${it.old}`);
217
+ dels++;
218
+ } else {
219
+ lines.push(`-${it.before}`, `+${it.after}`);
220
+ dels++;
221
+ adds++;
222
+ }
223
+ }
224
+ out.push(
225
+ `diff --git a/${header} b/${header}`,
226
+ `--- a/${header}`,
227
+ `+++ b/${header}`,
228
+ `@@ -${dels ? 1 : 0},${dels} +${adds ? 1 : 0},${adds} @@`,
229
+ ...lines
230
+ );
231
+ }
232
+ return `${out.join("\n")}
233
+ `;
234
+ }
235
+ function formatFull(diff, inline = false) {
236
+ const byKey = new Map((diff.items ?? []).map((it) => [it.key, it]));
237
+ const out = [];
238
+ let prev;
239
+ for (const f of diff.full ?? []) {
240
+ if (prev !== void 0 && f.table !== prev) out.push("");
241
+ const it = byKey.get(f.key);
242
+ if (it?.op === "change") out.push(renderItem(it, inline));
243
+ else if (it?.op === "add") out.push(style.green(` + ${f.ddl}`));
244
+ else out.push(style.dim(` ${f.ddl}`));
245
+ prev = f.table;
246
+ }
247
+ const removed = (diff.items ?? []).filter((it) => it.op === "remove");
248
+ if (removed.length) {
249
+ out.push("");
250
+ for (const it of removed) out.push(renderItem(it, inline));
251
+ }
252
+ return out.join("\n");
253
+ }
254
+ function formatDiff(diff, opts = {}) {
255
+ if (!diff.up.length) return "No changes.";
256
+ let out = opts.full ? formatFull(diff, opts.inline) : formatItems(diff.items ?? [], opts.inline);
257
+ if (opts.down) {
258
+ out += `
259
+
260
+ ${style.dim(" rollback (down):")}
261
+ ${diff.down.map((s) => style.dim(` ${s}`)).join("\n")}`;
262
+ }
263
+ return out;
264
+ }
265
+ function summarizeKinds(registry, items) {
266
+ const counts = /* @__PURE__ */ new Map();
267
+ for (const it of items) counts.set(it.kind, (counts.get(it.kind) ?? 0) + 1);
268
+ const parts = [];
269
+ for (const [kind, n] of counts) {
270
+ const d = registry.display(kind);
271
+ parts.push(`${n} ${n === 1 ? d.label : d.plural}`);
272
+ }
273
+ return parts.join(", ");
274
+ }
275
+
276
+ // src/cli-kit/filter.ts
277
+ function cat(v, defaultOn) {
278
+ if (v === void 0) return { on: defaultOn };
279
+ if (v === false) return { on: false };
280
+ if (v === true) return { on: true };
281
+ const names = new Set(
282
+ v.split(",").map((s) => s.trim()).filter(Boolean)
283
+ );
284
+ return names.size ? { on: true, names } : { on: true };
285
+ }
286
+ function parseFilter(o) {
287
+ return {
288
+ tables: cat(o.tables, true),
289
+ functions: cat(o.functions, true),
290
+ events: cat(o.events, true),
291
+ access: cat(o.access, false)
292
+ // DEFINE ACCESS is explicit everywhere — see module note.
293
+ };
294
+ }
295
+ function kindFlags(cmd) {
296
+ return cmd.option("--tables [names]", "only these tables (comma-separated)").option("--no-tables", "exclude all tables").option("--functions [names]", "only these functions").option("--no-functions", "exclude all functions").option("--events [names]", "only these events").option("--no-events", "exclude all events").option(
297
+ "--access [names]",
298
+ "include access (off by default; key not pulled)"
299
+ ).option("--no-access", "exclude all access (the default)");
300
+ }
301
+ var inCat = (c, name) => c.on && (!c.names || c.names.has(name));
302
+ var objKey = (o) => `${o.kind}:${o.name}`;
303
+ function category(kind) {
304
+ if (kind === "function") return "functions";
305
+ if (kind === "access") return "access";
306
+ return "tables";
307
+ }
308
+ function passesFilter(registry, o, f) {
309
+ const owner = registry.engine(o.kind)?.owner?.(o);
310
+ if (owner) {
311
+ if (!inCat(f.tables, owner.name)) return false;
312
+ return o.kind === "event" ? inCat(f.events, o.name) : true;
313
+ }
314
+ return inCat(f[category(o.kind)], o.name);
315
+ }
316
+ function filterKinds(registry, objects, f) {
317
+ return objects.filter((o) => passesFilter(registry, o, f));
318
+ }
319
+ function mergeStored(registry, prev, next, f) {
320
+ const merged = /* @__PURE__ */ new Map();
321
+ for (const o of snapshotObjects(prev.schema))
322
+ if (!passesFilter(registry, o, f)) merged.set(objKey(o), o);
323
+ for (const o of snapshotObjects(next.schema))
324
+ if (passesFilter(registry, o, f)) merged.set(objKey(o), o);
325
+ return {
326
+ version: 3,
327
+ driver: next.driver,
328
+ schema: snapshotKinds([...merged.values()]),
329
+ files: { ...prev.files ?? {}, ...next.files ?? {} }
330
+ };
331
+ }
332
+ function intersectKinds(registry, disk, live, f) {
333
+ const liveKeys = new Set(live.map(objKey));
334
+ return disk.filter(
335
+ (o) => liveKeys.has(objKey(o)) && passesFilter(registry, o, f)
336
+ );
337
+ }
338
+
339
+ // src/cli-kit/merge.ts
340
+ import { mkdirSync, rmSync, writeFileSync } from "fs";
341
+ import { dirname as dirname2 } from "path";
342
+ import { generateCode, parseModule } from "magicast";
343
+ function applyPull(plan) {
344
+ const touched = [];
345
+ for (const f of plan.files) {
346
+ if (f.action === "unchanged") continue;
347
+ if (f.action === "delete") {
348
+ rmSync(f.abs, { force: true });
349
+ } else {
350
+ mkdirSync(dirname2(f.abs), { recursive: true });
351
+ writeFileSync(f.abs, f.after);
352
+ }
353
+ touched.push(f.rel);
354
+ }
355
+ return touched;
356
+ }
357
+ var asMod = (mod) => mod;
358
+ function exportConstName(node) {
359
+ if (node.type !== "ExportNamedDeclaration") return null;
360
+ if (node.declaration?.type !== "VariableDeclaration") return null;
361
+ return node.declaration.declarations?.[0]?.id?.name ?? null;
362
+ }
363
+ function exportConstInit(node) {
364
+ return node.declaration?.declarations?.[0]?.init ?? null;
365
+ }
366
+ function defineCall(init) {
367
+ let n = init;
368
+ while (n) {
369
+ if (n.type === "CallExpression") {
370
+ if (n.callee?.type === "Identifier" && /^define/.test(n.callee.name ?? ""))
371
+ return n;
372
+ n = n.callee?.type === "MemberExpression" ? n.callee.object ?? null : n.callee ?? null;
373
+ } else if (n.type === "MemberExpression") {
374
+ n = n.object ?? null;
375
+ } else break;
376
+ }
377
+ return null;
378
+ }
379
+ function fieldsObject(init) {
380
+ const call = defineCall(init);
381
+ if (!call) return null;
382
+ let obj = call.arguments?.[1] ?? null;
383
+ if (obj?.type === "ArrowFunctionExpression") obj = obj.body ?? null;
384
+ return obj?.type === "ObjectExpression" ? obj : null;
385
+ }
386
+ function propKey(p) {
387
+ return p.key?.name ?? p.key?.value;
388
+ }
389
+ function hasLeadingComment(node) {
390
+ return Boolean(node.comments?.some((c) => c.leading));
391
+ }
392
+ function parseImportLine(line) {
393
+ const m = /import\s*\{([^}]*)\}\s*from\s*["']([^"']+)["']/.exec(line);
394
+ if (!m) return null;
395
+ return {
396
+ from: m[2],
397
+ names: m[1].split(",").map((s) => s.trim()).filter(Boolean)
398
+ };
399
+ }
400
+ function mergeUnits(existingSrc, units, opts) {
401
+ const mod = parseModule(existingSrc);
402
+ const body = asMod(mod).$ast.body;
403
+ const existing = /* @__PURE__ */ new Map();
404
+ for (const node of body) {
405
+ const name = exportConstName(node);
406
+ if (name) existing.set(name, node);
407
+ }
408
+ const localOnly = { fields: [], objects: [] };
409
+ const desiredNames = new Set(units.map((u) => u.exportName));
410
+ for (const unit of units) {
411
+ const unitBody = asMod(parseModule(unit.code)).$ast.body;
412
+ const desiredNode = unitBody.find(
413
+ (n) => exportConstName(n) === unit.exportName
414
+ );
415
+ if (!desiredNode) continue;
416
+ const prior = existing.get(unit.exportName);
417
+ if (prior) {
418
+ if (unit.kind === "table") {
419
+ const priorObj = fieldsObject(exportConstInit(prior));
420
+ const desiredObj = fieldsObject(exportConstInit(desiredNode));
421
+ if (priorObj?.properties && desiredObj?.properties) {
422
+ const desiredKeys = new Set(desiredObj.properties.map(propKey));
423
+ const localFields = priorObj.properties.filter(
424
+ (p) => !desiredKeys.has(propKey(p))
425
+ );
426
+ if (localFields.length) {
427
+ localOnly.fields.push({
428
+ exportName: unit.exportName,
429
+ fields: localFields.map((p) => propKey(p) ?? "?")
430
+ });
431
+ if (opts.keepLocalFields)
432
+ for (const p of localFields) desiredObj.properties.push(p);
433
+ }
434
+ }
435
+ }
436
+ if (!hasLeadingComment(desiredNode))
437
+ desiredNode.comments = prior.comments;
438
+ body[body.indexOf(prior)] = desiredNode;
439
+ } else {
440
+ body.push(desiredNode);
441
+ }
442
+ addImports(mod, unit.imports);
443
+ }
444
+ for (const [name, node] of existing) {
445
+ if (desiredNames.has(name)) continue;
446
+ localOnly.objects.push(name);
447
+ if (!opts.keepLocalObjects) body.splice(body.indexOf(node), 1);
448
+ }
449
+ return { content: ensureTrailingNewline(generateCode(mod).code), localOnly };
450
+ }
451
+ function addImports(mod, lines) {
452
+ const imports = asMod(mod).imports;
453
+ for (const line of lines) {
454
+ const parsed = parseImportLine(line);
455
+ if (!parsed) continue;
456
+ for (const name of parsed.names)
457
+ imports.$add({ from: parsed.from, imported: name, local: name });
458
+ }
459
+ }
460
+ function ensureTrailingNewline(s) {
461
+ return s.endsWith("\n") ? s : `${s}
462
+ `;
463
+ }
464
+ var splitLines = (s) => s === "" ? [] : s.replace(/\n$/, "").split("\n");
465
+ function lineOps(before, after) {
466
+ const a = splitLines(before);
467
+ const b = splitLines(after);
468
+ const m = a.length;
469
+ const n = b.length;
470
+ const dp = Array.from(
471
+ { length: m + 1 },
472
+ () => new Array(n + 1).fill(0)
473
+ );
474
+ for (let i2 = m - 1; i2 >= 0; i2--)
475
+ for (let j2 = n - 1; j2 >= 0; j2--)
476
+ dp[i2][j2] = a[i2] === b[j2] ? dp[i2 + 1][j2 + 1] + 1 : Math.max(dp[i2 + 1][j2], dp[i2][j2 + 1]);
477
+ const ops = [];
478
+ let i = 0;
479
+ let j = 0;
480
+ while (i < m && j < n) {
481
+ if (a[i] === b[j]) {
482
+ ops.push({ tag: " ", line: a[i] });
483
+ i++;
484
+ j++;
485
+ } else if (dp[i + 1][j] >= dp[i][j + 1]) {
486
+ ops.push({ tag: "-", line: a[i++] });
487
+ } else {
488
+ ops.push({ tag: "+", line: b[j++] });
489
+ }
490
+ }
491
+ while (i < m) ops.push({ tag: "-", line: a[i++] });
492
+ while (j < n) ops.push({ tag: "+", line: b[j++] });
493
+ return ops;
494
+ }
495
+ function lineDiff(before, after) {
496
+ const ops = lineOps(before, after);
497
+ const CONTEXT = 2;
498
+ const keep = new Array(ops.length).fill(false);
499
+ ops.forEach((op, idx) => {
500
+ if (op.tag === " ") return;
501
+ for (let k = Math.max(0, idx - CONTEXT); k <= Math.min(ops.length - 1, idx + CONTEXT); k++)
502
+ keep[k] = true;
503
+ });
504
+ const out = [];
505
+ let gap = false;
506
+ ops.forEach((op, idx) => {
507
+ if (!keep[idx]) {
508
+ if (!gap) out.push(style.dim(" \u2026"));
509
+ gap = true;
510
+ return;
511
+ }
512
+ gap = false;
513
+ if (op.tag === " ") out.push(style.dim(` ${op.line}`));
514
+ else if (op.tag === "-") out.push(style.red(`- ${op.line}`));
515
+ else out.push(style.green(`+ ${op.line}`));
516
+ });
517
+ return out.join("\n");
518
+ }
519
+ function unifiedDiff(before, after, label) {
520
+ const ops = lineOps(before, after);
521
+ if (!ops.some((o) => o.tag !== " ")) return "";
522
+ const oldLen = ops.filter((o) => o.tag !== "+").length;
523
+ const newLen = ops.filter((o) => o.tag !== "-").length;
524
+ const body = ops.map(
525
+ (o) => o.tag === " " ? ` ${o.line}` : `${o.tag}${o.line}`
526
+ );
527
+ return `${[
528
+ `diff --git a/${label} b/${label}`,
529
+ `--- a/${label}`,
530
+ `+++ b/${label}`,
531
+ `@@ -1,${oldLen} +1,${newLen} @@`,
532
+ ...body
533
+ ].join("\n")}
534
+ `;
535
+ }
536
+ function actionLabel(action) {
537
+ if (action === "create") return colorEnabled() ? style.green("new") : "new";
538
+ if (action === "update")
539
+ return colorEnabled() ? style.yellow("update") : "update";
540
+ if (action === "delete")
541
+ return colorEnabled() ? style.red("delete") : "delete";
542
+ return style.dim("unchanged");
543
+ }
544
+
545
+ // src/cli-kit/meta.ts
546
+ import { createHash } from "crypto";
547
+ import {
548
+ existsSync as existsSync2,
549
+ mkdirSync as mkdirSync2,
550
+ readdirSync,
551
+ readFileSync,
552
+ writeFileSync as writeFileSync2
553
+ } from "fs";
554
+ import { join } from "path";
555
+ var SNAPSHOT_FILE = "_snapshot.json";
556
+ var DEFAULT_MIGRATION_EXT = ".surql";
557
+ function emptyStored() {
558
+ return {
559
+ version: 3,
560
+ driver: "surrealdb",
561
+ schema: { kinds: {} },
562
+ files: {}
563
+ };
564
+ }
565
+ var EMPTY_STORED = emptyStored();
566
+ function readSnapshot(metaDir) {
567
+ const path = join(metaDir, SNAPSHOT_FILE);
568
+ if (!existsSync2(path)) return emptyStored();
569
+ const raw = JSON.parse(readFileSync(path, "utf8"));
570
+ if (raw.version === 3 && raw.driver && raw.schema)
571
+ return { files: {}, ...raw };
572
+ return emptyStored();
573
+ }
574
+ function writeSnapshot(metaDir, snapshot) {
575
+ mkdirSync2(metaDir, { recursive: true });
576
+ writeFileSync2(
577
+ join(metaDir, SNAPSHOT_FILE),
578
+ `${JSON.stringify(snapshot, null, 2)}
579
+ `
580
+ );
581
+ }
582
+ function listMigrations(migrationsDir, ext = DEFAULT_MIGRATION_EXT) {
583
+ if (!existsSync2(migrationsDir)) return [];
584
+ return readdirSync(migrationsDir).filter((f) => f.endsWith(ext)).sort().map((file) => ({ tag: file.slice(0, -ext.length), file }));
585
+ }
586
+ function timestamp(date) {
587
+ const p = (n) => String(n).padStart(2, "0");
588
+ return `${date.getUTCFullYear()}` + p(date.getUTCMonth() + 1) + p(date.getUTCDate()) + p(date.getUTCHours()) + p(date.getUTCMinutes()) + p(date.getUTCSeconds());
589
+ }
590
+ function checksum(content) {
591
+ return createHash("sha256").update(content).digest("hex").slice(0, 16);
592
+ }
593
+ function slug(name) {
594
+ return name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "") || "migration";
595
+ }
596
+
597
+ // src/cli-kit/pager.ts
598
+ import { execFileSync, spawn } from "child_process";
599
+ function gitConfig(key) {
600
+ try {
601
+ const v = execFileSync("git", ["config", "--get", key], {
602
+ encoding: "utf8",
603
+ stdio: ["ignore", "pipe", "ignore"]
604
+ }).trim();
605
+ return v || void 0;
606
+ } catch {
607
+ return void 0;
608
+ }
609
+ }
610
+ function resolvePager() {
611
+ return gitConfig("pager.diff") || gitConfig("core.pager") || process.env.GIT_PAGER || process.env.PAGER || void 0;
612
+ }
613
+ function pipeThroughPager(pager, text) {
614
+ return new Promise((resolve2, reject) => {
615
+ const child = spawn("sh", ["-c", pager], {
616
+ stdio: ["pipe", "inherit", "inherit"]
617
+ });
618
+ child.once("error", reject);
619
+ child.once("close", () => resolve2());
620
+ child.stdin.on("error", () => {
621
+ });
622
+ child.stdin.end(text);
623
+ });
624
+ }
625
+
626
+ // src/cli-kit/schema.ts
627
+ import { existsSync as existsSync3, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
628
+ import { join as join2 } from "path";
629
+ function isTableDef(v) {
630
+ if (!v || typeof v !== "object") return false;
631
+ const t = v;
632
+ return typeof t.name === "string" && typeof t.fields === "object" && t.fields !== null && typeof t.config === "object" && t.config !== null && typeof t.record === "function";
633
+ }
634
+ function isStandaloneDef(v) {
635
+ if (!v || typeof v !== "object") return false;
636
+ const d = v;
637
+ return (d.kind === "event" || d.kind === "function" || d.kind === "access") && typeof d.name === "string";
638
+ }
639
+ function tsFiles(dir) {
640
+ const out = [];
641
+ for (const entry of readdirSync2(dir).sort()) {
642
+ const p = join2(dir, entry);
643
+ if (statSync2(p).isDirectory()) out.push(...tsFiles(p));
644
+ else if (/\.(ts|mts|js|mjs)$/.test(entry) && !entry.endsWith(".d.ts"))
645
+ out.push(p);
646
+ }
647
+ return out;
648
+ }
649
+ function schemaFiles(path) {
650
+ return statSync2(path).isFile() ? [path] : tsFiles(path);
651
+ }
652
+ async function* tablesIn(jiti, file) {
653
+ const mod = await jiti.import(file);
654
+ for (const value of Object.values(mod)) if (isTableDef(value)) yield value;
655
+ }
656
+ async function loadDefs(schemaPath) {
657
+ if (!existsSync3(schemaPath)) {
658
+ throw new Error(`Schema path not found: ${schemaPath}`);
659
+ }
660
+ const jiti = makeJiti();
661
+ const tables = /* @__PURE__ */ new Map();
662
+ const defs = [];
663
+ const fileOf = /* @__PURE__ */ new Map();
664
+ for (const file of schemaFiles(schemaPath)) {
665
+ const mod = await jiti.import(file);
666
+ for (const value of Object.values(mod)) {
667
+ if (isTableDef(value)) {
668
+ tables.set(value.name, value);
669
+ fileOf.set(value, file);
670
+ } else if (isStandaloneDef(value)) {
671
+ defs.push(value);
672
+ fileOf.set(value, file);
673
+ }
674
+ }
675
+ }
676
+ const rank = (t) => t.config.relation ? 1 : 0;
677
+ const sorted = [...tables.values()].sort(
678
+ (a, b) => rank(a) - rank(b) || a.name.localeCompare(b.name)
679
+ );
680
+ return { tables: sorted, defs, fileOf };
681
+ }
682
+ async function loadSchemas(schemaPath) {
683
+ return (await loadDefs(schemaPath)).tables;
684
+ }
685
+ async function scanLocalEntities(schemaPath) {
686
+ if (!existsSync3(schemaPath)) return /* @__PURE__ */ new Map();
687
+ const jiti = makeJiti();
688
+ const out = /* @__PURE__ */ new Map();
689
+ for (const file of schemaFiles(schemaPath)) {
690
+ const exports = Object.entries(
691
+ await jiti.import(file)
692
+ );
693
+ const entities = [];
694
+ for (const [exportName, value] of exports) {
695
+ if (isTableDef(value))
696
+ entities.push({ exportName, name: value.name, kind: "table" });
697
+ else if (isStandaloneDef(value) && (value.kind === "function" || value.kind === "access"))
698
+ entities.push({ exportName, name: value.name, kind: "def" });
699
+ }
700
+ if (entities.length)
701
+ out.set(file, {
702
+ entities,
703
+ pureSchema: entities.length === exports.length
704
+ });
705
+ }
706
+ return out;
707
+ }
708
+ async function existingTables(schemaPath) {
709
+ if (!existsSync3(schemaPath)) return /* @__PURE__ */ new Map();
710
+ const jiti = makeJiti();
711
+ const out = /* @__PURE__ */ new Map();
712
+ for (const file of schemaFiles(schemaPath)) {
713
+ for await (const t of tablesIn(jiti, file)) out.set(t.name, file);
714
+ }
715
+ return out;
716
+ }
717
+ async function duplicateTables(schemaPath) {
718
+ if (!existsSync3(schemaPath)) return /* @__PURE__ */ new Map();
719
+ const jiti = makeJiti();
720
+ const seen = /* @__PURE__ */ new Map();
721
+ for (const file of schemaFiles(schemaPath)) {
722
+ for await (const t of tablesIn(jiti, file)) {
723
+ const files = seen.get(t.name);
724
+ if (files) files.push(file);
725
+ else seen.set(t.name, [file]);
726
+ }
727
+ }
728
+ return new Map([...seen].filter(([, files]) => files.length > 1));
729
+ }
730
+ export {
731
+ EMPTY_STORED,
732
+ KindRegistry,
733
+ actionLabel,
734
+ applyPull,
735
+ array,
736
+ buildKindDiff,
737
+ checksum,
738
+ colorEnabled,
739
+ connectionEntry,
740
+ driverNames,
741
+ duplicateTables,
742
+ emitKinds,
743
+ existingTables,
744
+ fail,
745
+ filterKinds,
746
+ formatDiff,
747
+ formatItems,
748
+ formatPatch,
749
+ getDriver,
750
+ inCat,
751
+ intersectKinds,
752
+ introspectKinds,
753
+ isConnectionEntry,
754
+ isEmptyDiff,
755
+ kindFlags,
756
+ lineDiff,
757
+ listMigrations,
758
+ literal,
759
+ loadConfig,
760
+ loadDefs,
761
+ loadProject,
762
+ loadSchemas,
763
+ lowerSchema,
764
+ makeJiti,
765
+ mergeStored,
766
+ mergeUnits,
767
+ nullable,
768
+ ok,
769
+ option,
770
+ orderObjects,
771
+ parseFilter,
772
+ passesFilter,
773
+ pipeThroughPager,
774
+ planKinds,
775
+ plural,
776
+ readSnapshot,
777
+ record,
778
+ registerDriver,
779
+ resolveConnectionConfig,
780
+ resolvePager,
781
+ scalar,
782
+ scanLocalEntities,
783
+ slug,
784
+ snapshotKinds,
785
+ snapshotObjects,
786
+ style,
787
+ summarizeKinds,
788
+ timestamp,
789
+ tokenDiff,
790
+ unifiedDiff,
791
+ union,
792
+ writeSnapshot
793
+ };
794
+ //# sourceMappingURL=index.js.map