@onreza/sqlx-js 0.4.0 → 0.6.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 (38) hide show
  1. package/README.md +139 -48
  2. package/ROADMAP.md +1 -3
  3. package/dist/bin/sqlx-js-diagnostics.d.ts +2 -0
  4. package/dist/bin/sqlx-js-diagnostics.js +96 -0
  5. package/dist/bin/sqlx-js.js +337 -89
  6. package/dist/src/artifacts.d.ts +9 -0
  7. package/dist/src/artifacts.js +26 -0
  8. package/dist/src/cache.d.ts +17 -0
  9. package/dist/src/cache.js +83 -1
  10. package/dist/src/codegen.js +14 -2
  11. package/dist/src/commands/doctor.d.ts +16 -0
  12. package/dist/src/commands/doctor.js +196 -0
  13. package/dist/src/commands/init.js +93 -13
  14. package/dist/src/commands/migrate.d.ts +2 -101
  15. package/dist/src/commands/migrate.js +11 -566
  16. package/dist/src/commands/pgschema.d.ts +6 -0
  17. package/dist/src/commands/pgschema.js +30 -0
  18. package/dist/src/commands/prepare.d.ts +41 -6
  19. package/dist/src/commands/prepare.js +440 -63
  20. package/dist/src/commands/schema.js +1 -1
  21. package/dist/src/commands/watch.d.ts +1 -0
  22. package/dist/src/commands/watch.js +23 -9
  23. package/dist/src/config.d.ts +22 -0
  24. package/dist/src/config.js +149 -11
  25. package/dist/src/index.d.ts +4 -2
  26. package/dist/src/index.js +2 -1
  27. package/dist/src/migration-core.d.ts +157 -0
  28. package/dist/src/migration-core.js +578 -0
  29. package/dist/src/postgres-runtime.d.ts +3 -0
  30. package/dist/src/postgres-runtime.js +66 -29
  31. package/dist/src/runtime.d.ts +36 -3
  32. package/dist/src/runtime.js +92 -23
  33. package/dist/src/scan/scanner.d.ts +10 -3
  34. package/dist/src/scan/scanner.js +83 -32
  35. package/dist/src/type-inspection.d.ts +1 -0
  36. package/dist/src/type-inspection.js +20 -0
  37. package/dist/src/typed.d.ts +10 -0
  38. package/package.json +11 -6
@@ -2,13 +2,8 @@
2
2
  import { existsSync, readFileSync } from "node:fs";
3
3
  import { dirname, join, resolve } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
- import { runPrepare } from "../src/commands/prepare.js";
6
- import { runWatch } from "../src/commands/watch.js";
7
- import { migrateArchiveList, migrateArchiveRestore, migrateCheck, migrateDev, migrateRun, migrateInfo, migrateRevert, migrateAdd, migrateSquash, migrateVerify, } from "../src/commands/migrate.js";
8
- import { applyShadowMigrations, runSchemaCheck, runSchemaDump } from "../src/commands/schema.js";
9
- import { runInit } from "../src/commands/init.js";
10
- import { runPgschemaCommand, runPgschemaInstall } from "../src/commands/pgschema.js";
11
- import { loadConfig } from "../src/config.js";
5
+ import { parseArgs } from "node:util";
6
+ import { assertSupportedRuntime, loadConfig, loadRootEnv } from "../src/config.js";
12
7
  function packageVersion() {
13
8
  const here = dirname(fileURLToPath(import.meta.url));
14
9
  for (const path of [join(here, "../package.json"), join(here, "../../package.json")]) {
@@ -21,16 +16,20 @@ function packageVersion() {
21
16
  throw new Error("sqlx-js: cannot locate package.json for version");
22
17
  }
23
18
  const VERSION = packageVersion();
24
- function help() {
25
- console.error(`sqlx-js — compile-time-checked SQL for TypeScript + Postgres (v${VERSION})
19
+ const HELP = {
20
+ root: `sqlx-js — compile-time-checked SQL for TypeScript + Postgres (v${VERSION})
26
21
 
27
22
  usage:
28
23
  sqlx-js init [--root <dir>] [--schema-provider builtin|pgschema]
29
- sqlx-js db install | check | plan | apply [--root <dir>] [-- <pgschema args>]
30
- sqlx-js prepare [--check | --watch] [--root <dir>] [--dts <path>] [--no-prune] [--shadow-url <url>]
31
- sqlx-js migrate dev [--shadow-admin-url <url> | --shadow-url <url>] [--lock-timeout <ms>] | verify [--shadow-admin-url <url> | --shadow-url <url>] [--lock-timeout <ms>] | run [--dry-run] [--json] [--lock-timeout <ms>] | info [--json] | check [--json] | revert [--dry-run] [--json] [--shadow-admin-url <url> | --shadow-url <url>] [--lock-timeout <ms>] | add <name> | squash <name> [--shadow-admin-url <url> | --shadow-url <url>] [--replace] [--pg-dump <path>] [--lock-timeout <ms>] | archive list | archive restore <name> [--force]
32
- sqlx-js schema dump | check [--schema <path>] [--manifest <path>] [--no-manifest] [--shadow-url <url>]
24
+ sqlx-js doctor [--root <dir>] [--dts <path>] [--json]
25
+ sqlx-js db install | check [--root <dir>]
26
+ sqlx-js db plan | apply [--root <dir>] [-- <pgschema args>]
27
+ sqlx-js prepare [--check | --verify | --watch] [--json] [--strict-inference] [--root <dir>] [--dts <path>] [--no-prune] [--shadow-url <url>]
28
+ sqlx-js migrate dev [--shadow-admin-url <url> | --shadow-url <url>] [--lock-timeout <ms>] [--strict-inference] | verify [--shadow-admin-url <url> | --shadow-url <url>] [--lock-timeout <ms>] [--strict-inference] | run [--dry-run] [--json] [--lock-timeout <ms>] | info [--json] | check [--json] | revert [--dry-run] [--json] [--shadow-admin-url <url> | --shadow-url <url>] [--lock-timeout <ms>] | add <name> | squash <name> [--shadow-admin-url <url> | --shadow-url <url>] [--replace] [--pg-dump <path>] [--lock-timeout <ms>] | archive list | archive restore <name> [--force]
29
+ sqlx-js schema dump [--schema <path>] [--manifest <path>] [--no-manifest] [--shadow-url <url>]
30
+ sqlx-js schema check [--schema <path>] [--shadow-url <url>]
33
31
  sqlx-js --version
32
+ sqlx-js-diagnostics github|unix < prepare-diagnostics.json
34
33
 
35
34
  env:
36
35
  DATABASE_URL=postgres://... (supports sslmode, cert paths, application_name, connect_timeout, statement_timeout)
@@ -41,11 +40,13 @@ flags:
41
40
  --root <dir> scan root (default: cwd)
42
41
  --dts <path> declarations output (default: <root>/sqlx-js-env.d.ts)
43
42
  --check offline mode: verify scanned queries exist in cache, no DB
43
+ --verify prepare against DB/shadow and compare committed generated artifacts
44
44
  --watch re-prepare on file change (persistent PG connection)
45
45
  --no-prune keep orphaned cache entries (default: remove)
46
46
  --migrations <dir> migrations directory (default: <root>/migrations)
47
47
  --dry-run validate and print migrate run/revert plan without applying migrations
48
- --json machine-readable output for migrate info/check and migrate run/revert --dry-run
48
+ --json machine-readable output for prepare and migration inspection/dry-run commands
49
+ --strict-inference fail when query inference degrades or emits unknown types
49
50
  --force allow archive restore to overwrite existing migration files
50
51
  --lock-timeout <ms> advisory-lock acquisition timeout for migrate run/revert/dev/verify/squash
51
52
  --shadow-url <url> use an existing disposable shadow DB instead of auto-creating one
@@ -56,52 +57,250 @@ flags:
56
57
  --manifest <path> LLM schema manifest path (default: <root>/.sqlx-js/schema/schema.md)
57
58
  --no-manifest skip writing the LLM schema manifest during schema dump
58
59
  --schema-provider <name> init schema workflow: builtin (default) or pgschema
59
- `);
60
- process.exit(2);
60
+ `,
61
+ init: `usage: sqlx-js init [--root <dir>] [--schema-provider builtin|pgschema]`,
62
+ doctor: `usage: sqlx-js doctor [--root <dir>] [--dts <path>] [--json]`,
63
+ db: `usage: sqlx-js db install | check [--root <dir>] | plan | apply [--root <dir>] [-- <pgschema args>]`,
64
+ prepare: `usage: sqlx-js prepare [--check | --verify | --watch] [--json] [--strict-inference] [--root <dir>] [--dts <path>] [--no-prune] [--shadow-url <url>]`,
65
+ migrate: `usage: sqlx-js migrate dev [--shadow-admin-url <url> | --shadow-url <url>] [--lock-timeout <ms>] [--strict-inference] | verify [--shadow-admin-url <url> | --shadow-url <url>] [--lock-timeout <ms>] [--strict-inference] | run [--dry-run] [--json] [--lock-timeout <ms>] | info [--json] | check [--json] | revert [--dry-run] [--json] [--shadow-admin-url <url> | --shadow-url <url>] [--lock-timeout <ms>] | add <name> | squash <name> [--shadow-admin-url <url> | --shadow-url <url>] [--replace] [--pg-dump <path>] [--lock-timeout <ms>] | archive list | archive restore <name> [--force]`,
66
+ schema: `usage: sqlx-js schema dump [--schema <path>] [--manifest <path>] [--no-manifest] [--shadow-url <url>] | check [--schema <path>] [--shadow-url <url>]`,
67
+ };
68
+ function printHelp(scope, error = false) {
69
+ (error ? console.error : console.log)(HELP[scope]);
61
70
  }
62
- const passthroughIndex = process.argv.indexOf("--");
63
- const cliArgv = passthroughIndex >= 0 ? process.argv.slice(0, passthroughIndex) : process.argv;
64
- const passthroughArgs = passthroughIndex >= 0 ? process.argv.slice(passthroughIndex + 1) : [];
65
- function arg(name, def) {
66
- const eq = `${name}=`;
67
- for (let i = 0; i < cliArgv.length; i++) {
68
- const a = cliArgv[i];
69
- if (a === name)
70
- return cliArgv[i + 1] ?? def;
71
- if (a.startsWith(eq))
72
- return a.slice(eq.length);
73
- }
74
- return def;
71
+ function exitHelp(scope) {
72
+ printHelp(scope);
73
+ process.exit(0);
75
74
  }
76
- function flag(name) {
77
- for (const a of cliArgv) {
78
- if (a === name)
79
- return true;
80
- }
81
- return false;
75
+ function usageError(message, scope = "root") {
76
+ console.error(`sqlx-js: ${message}`);
77
+ printHelp(scope, true);
78
+ process.exit(2);
82
79
  }
83
- const cmd = process.argv[2];
80
+ const rawArgv = process.argv.slice(2);
81
+ const passthroughIndex = rawArgv.indexOf("--");
82
+ const cliArgv = passthroughIndex >= 0 ? rawArgv.slice(0, passthroughIndex) : rawArgv;
83
+ const passthroughArgs = passthroughIndex >= 0 ? rawArgv.slice(passthroughIndex + 1) : [];
84
+ const cmd = cliArgv[0];
85
+ const scopes = new Set(["init", "doctor", "db", "prepare", "migrate", "schema"]);
84
86
  if (cmd === "--version" || cmd === "-v") {
85
87
  console.log(VERSION);
86
88
  process.exit(0);
87
89
  }
88
- if (cmd === "--help" || cmd === "-h" || !cmd) {
89
- help();
90
+ if (!cmd || cmd === "--help" || cmd === "-h")
91
+ exitHelp("root");
92
+ if (!scopes.has(cmd))
93
+ usageError(`unknown command ${JSON.stringify(cmd)}`);
94
+ const scope = cmd;
95
+ if (cliArgv.includes("--help") || cliArgv.includes("-h"))
96
+ exitHelp(scope);
97
+ if (passthroughIndex >= 0 && cmd !== "db")
98
+ usageError("arguments after -- are only supported by sqlx-js db", scope);
99
+ const ROOT_OPTIONS = {
100
+ root: { type: "string" },
101
+ help: { type: "boolean", short: "h" },
102
+ };
103
+ function optionsFor(command, subcommand) {
104
+ if (command === "init")
105
+ return { ...ROOT_OPTIONS, "schema-provider": { type: "string" } };
106
+ if (command === "doctor")
107
+ return { ...ROOT_OPTIONS, dts: { type: "string" }, json: { type: "boolean" } };
108
+ if (command === "db")
109
+ return ROOT_OPTIONS;
110
+ if (command === "prepare") {
111
+ return {
112
+ ...ROOT_OPTIONS,
113
+ dts: { type: "string" },
114
+ check: { type: "boolean" },
115
+ verify: { type: "boolean" },
116
+ watch: { type: "boolean" },
117
+ json: { type: "boolean" },
118
+ "no-prune": { type: "boolean" },
119
+ "shadow-url": { type: "string" },
120
+ migrations: { type: "string" },
121
+ "strict-inference": { type: "boolean" },
122
+ };
123
+ }
124
+ if (command === "schema") {
125
+ const common = {
126
+ ...ROOT_OPTIONS,
127
+ schema: { type: "string" },
128
+ "shadow-url": { type: "string" },
129
+ migrations: { type: "string" },
130
+ };
131
+ return subcommand === "dump"
132
+ ? { ...common, manifest: { type: "string" }, "no-manifest": { type: "boolean" } }
133
+ : common;
134
+ }
135
+ const common = { ...ROOT_OPTIONS, migrations: { type: "string" } };
136
+ if (subcommand === "run") {
137
+ return { ...common, "dry-run": { type: "boolean" }, json: { type: "boolean" }, "lock-timeout": { type: "string" } };
138
+ }
139
+ if (subcommand === "info" || subcommand === "check")
140
+ return { ...common, json: { type: "boolean" } };
141
+ if (subcommand === "dev") {
142
+ return {
143
+ ...common,
144
+ "shadow-admin-url": { type: "string" },
145
+ "shadow-url": { type: "string" },
146
+ "lock-timeout": { type: "string" },
147
+ "no-prune": { type: "boolean" },
148
+ "strict-inference": { type: "boolean" },
149
+ };
150
+ }
151
+ if (subcommand === "verify") {
152
+ return {
153
+ ...common,
154
+ "shadow-admin-url": { type: "string" },
155
+ "shadow-url": { type: "string" },
156
+ "lock-timeout": { type: "string" },
157
+ "strict-inference": { type: "boolean" },
158
+ };
159
+ }
160
+ if (subcommand === "revert") {
161
+ return {
162
+ ...common,
163
+ "dry-run": { type: "boolean" },
164
+ json: { type: "boolean" },
165
+ "shadow-admin-url": { type: "string" },
166
+ "shadow-url": { type: "string" },
167
+ "lock-timeout": { type: "string" },
168
+ };
169
+ }
170
+ if (subcommand === "squash") {
171
+ return {
172
+ ...common,
173
+ "shadow-admin-url": { type: "string" },
174
+ "shadow-url": { type: "string" },
175
+ "lock-timeout": { type: "string" },
176
+ replace: { type: "boolean" },
177
+ "pg-dump": { type: "string" },
178
+ };
179
+ }
180
+ if (subcommand === "archive")
181
+ return { ...common, force: { type: "boolean" } };
182
+ return common;
183
+ }
184
+ const commandArgv = cliArgv.slice(1);
185
+ const subcommand = commandArgv[0]?.startsWith("-") ? undefined : commandArgv[0];
186
+ let parsed;
187
+ try {
188
+ parsed = parseArgs({
189
+ args: commandArgv,
190
+ options: optionsFor(cmd, subcommand),
191
+ strict: true,
192
+ allowPositionals: true,
193
+ });
90
194
  }
195
+ catch (error) {
196
+ usageError(error.message, scope);
197
+ }
198
+ const values = parsed.values;
199
+ const positionals = parsed.positionals;
200
+ function arg(name, def) {
201
+ const value = values[name.replace(/^--/, "")];
202
+ return typeof value === "string" ? value : def;
203
+ }
204
+ function flag(name) {
205
+ return values[name.replace(/^--/, "")] === true;
206
+ }
207
+ function requirePositionals(min, max, label) {
208
+ if (positionals.length < min || positionals.length > max) {
209
+ usageError(`${label}: expected ${min === max ? min : `${min} to ${max}`} positional argument(s)`, scope);
210
+ }
211
+ }
212
+ function validateInvocation() {
213
+ if (cmd === "init" || cmd === "doctor" || cmd === "prepare") {
214
+ requirePositionals(0, 0, cmd);
215
+ return;
216
+ }
217
+ if (cmd === "db") {
218
+ requirePositionals(1, 1, "db");
219
+ const sub = positionals[0];
220
+ if (sub !== "install" && sub !== "check" && sub !== "plan" && sub !== "apply") {
221
+ usageError(`unknown db command ${JSON.stringify(sub)}`, "db");
222
+ }
223
+ if (passthroughArgs.length > 0 && sub !== "plan" && sub !== "apply") {
224
+ usageError(`db ${sub} does not accept arguments after --`, "db");
225
+ }
226
+ return;
227
+ }
228
+ if (cmd === "schema") {
229
+ requirePositionals(1, 1, "schema");
230
+ const sub = positionals[0];
231
+ if (sub !== "dump" && sub !== "check")
232
+ usageError(`unknown schema command ${JSON.stringify(sub)}`, "schema");
233
+ return;
234
+ }
235
+ const sub = positionals[0];
236
+ if (!sub)
237
+ usageError("migrate command is required", "migrate");
238
+ if (["dev", "verify", "run", "info", "check", "revert"].includes(sub)) {
239
+ requirePositionals(1, 1, `migrate ${sub}`);
240
+ return;
241
+ }
242
+ if (sub === "add" || sub === "squash") {
243
+ requirePositionals(2, 2, `migrate ${sub}`);
244
+ return;
245
+ }
246
+ if (sub === "archive") {
247
+ const action = positionals[1];
248
+ if (action === "list") {
249
+ requirePositionals(2, 2, "migrate archive list");
250
+ if (flag("--force"))
251
+ usageError("--force is only supported by migrate archive restore", "migrate");
252
+ }
253
+ else if (action === "restore")
254
+ requirePositionals(3, 3, "migrate archive restore");
255
+ else
256
+ usageError(`unknown migrate archive command ${JSON.stringify(action)}`, "migrate");
257
+ return;
258
+ }
259
+ usageError(`unknown migrate command ${JSON.stringify(sub)}`, "migrate");
260
+ }
261
+ validateInvocation();
91
262
  const root = resolve(arg("--root", process.cwd()));
263
+ if (cmd !== "doctor") {
264
+ try {
265
+ assertSupportedRuntime();
266
+ }
267
+ catch (e) {
268
+ console.error(e.message);
269
+ process.exit(2);
270
+ }
271
+ }
272
+ let envError;
273
+ const needsEnvironment = cmd === "doctor" ||
274
+ cmd === "schema" ||
275
+ (cmd === "db" && (positionals[0] === "plan" || positionals[0] === "apply")) ||
276
+ (cmd === "prepare" && !flag("--check")) ||
277
+ (cmd === "migrate" && !["add", "check", "archive"].includes(positionals[0]));
278
+ if (needsEnvironment) {
279
+ try {
280
+ loadRootEnv(root);
281
+ }
282
+ catch (e) {
283
+ envError = e.message;
284
+ if (cmd !== "doctor") {
285
+ console.error(envError);
286
+ process.exit(2);
287
+ }
288
+ }
289
+ }
92
290
  const databaseUrl = process.env.DATABASE_URL ?? "";
93
291
  const shadowUrlArg = arg("--shadow-url");
94
292
  const shadowUrl = shadowUrlArg ?? process.env.SHADOW_DATABASE_URL;
95
293
  const shadowAdminUrl = arg("--shadow-admin-url") ?? process.env.SHADOW_ADMIN_DATABASE_URL;
96
294
  const cacheDir = join(root, ".sqlx-js");
97
295
  const dtsArg = arg("--dts");
98
- const dtsPath = dtsArg ? resolve(dtsArg) : join(root, "sqlx-js-env.d.ts");
296
+ const dtsPath = dtsArg ? resolve(root, dtsArg) : join(root, "sqlx-js-env.d.ts");
99
297
  const migrationsDir = join(root, arg("--migrations", "migrations"));
100
298
  const schemaArg = arg("--schema");
101
- const schemaPath = schemaArg ? resolve(schemaArg) : join(root, ".sqlx-js/schema/schema.json");
299
+ const schemaPath = schemaArg ? resolve(root, schemaArg) : join(root, ".sqlx-js/schema/schema.json");
102
300
  const manifestArg = arg("--manifest");
103
- const manifestPath = manifestArg ? resolve(manifestArg) : join(root, ".sqlx-js/schema/schema.md");
301
+ const manifestPath = manifestArg ? resolve(root, manifestArg) : join(root, ".sqlx-js/schema/schema.md");
104
302
  if (cmd === "init") {
303
+ const { runInit } = await import("../src/commands/init.js");
105
304
  const provider = arg("--schema-provider", "builtin");
106
305
  if (provider !== "builtin" && provider !== "pgschema") {
107
306
  console.error("--schema-provider must be builtin or pgschema");
@@ -109,8 +308,13 @@ if (cmd === "init") {
109
308
  }
110
309
  runInit({ root, schemaProvider: provider });
111
310
  }
311
+ else if (cmd === "doctor") {
312
+ const { runDoctor } = await import("../src/commands/doctor.js");
313
+ await runDoctor({ root, databaseUrl, cacheDir, dtsPath, json: flag("--json"), envError });
314
+ }
112
315
  else if (cmd === "db") {
113
- const sub = process.argv[3];
316
+ const { runPgschemaCommand, runPgschemaInstall } = await import("../src/commands/pgschema.js");
317
+ const sub = positionals[0];
114
318
  if (sub === "install") {
115
319
  try {
116
320
  await runPgschemaInstall({ root });
@@ -121,8 +325,6 @@ else if (cmd === "db") {
121
325
  }
122
326
  }
123
327
  else {
124
- if (sub !== "check" && sub !== "plan" && sub !== "apply")
125
- help();
126
328
  try {
127
329
  runPgschemaCommand({
128
330
  root,
@@ -139,29 +341,64 @@ else if (cmd === "db") {
139
341
  }
140
342
  }
141
343
  else if (cmd === "prepare") {
142
- if (flag("--check") && shadowUrlArg) {
143
- console.error("--shadow-url cannot be used with prepare --check; use live prepare or schema check --shadow-url");
144
- process.exit(2);
344
+ const { PrepareFatalError, runPrepare } = await import("../src/commands/prepare.js");
345
+ const prepareCheck = flag("--check");
346
+ const prepareVerify = flag("--verify");
347
+ const prepareWatch = flag("--watch");
348
+ const prepareJson = flag("--json");
349
+ const prepareMode = prepareVerify ? "verify" : prepareCheck ? "check" : "prepare";
350
+ const failPrepare = (message, phase, exitCode = 2, location = {}) => {
351
+ if (prepareJson) {
352
+ console.log(JSON.stringify({
353
+ formatVersion: 1,
354
+ ok: false,
355
+ mode: prepareMode,
356
+ sites: 0,
357
+ entries: 0,
358
+ failures: 1,
359
+ pruned: 0,
360
+ functions: 0,
361
+ diagnostics: [{ severity: "error", phase, message, ...location }],
362
+ }, null, 2));
363
+ }
364
+ else {
365
+ console.error(message);
366
+ }
367
+ process.exit(exitCode);
368
+ };
369
+ if ([prepareCheck, prepareVerify, prepareWatch].filter(Boolean).length > 1) {
370
+ failPrepare("--check, --verify, and --watch are mutually exclusive", "config");
371
+ }
372
+ if (prepareCheck && shadowUrlArg) {
373
+ failPrepare("--shadow-url cannot be used with prepare --check; use live prepare or schema check --shadow-url", "config");
374
+ }
375
+ if (prepareWatch && prepareJson) {
376
+ failPrepare("--watch and --json are mutually exclusive", "config");
377
+ }
378
+ if ((prepareCheck || prepareVerify) && flag("--no-prune")) {
379
+ failPrepare("--no-prune is only supported by prepare and prepare --watch", "config");
145
380
  }
146
- const prepareShadowUrl = flag("--check") ? undefined : shadowUrl;
381
+ const prepareShadowUrl = prepareCheck ? undefined : shadowUrl;
147
382
  const prepareDatabaseUrl = prepareShadowUrl ?? databaseUrl;
148
- if (!flag("--check") && !prepareDatabaseUrl) {
149
- console.error("DATABASE_URL is required for prepare (use --check for offline)");
150
- process.exit(2);
383
+ if (!prepareCheck && !prepareDatabaseUrl) {
384
+ failPrepare("DATABASE_URL is required for prepare (use --check for offline)", "connect");
151
385
  }
152
386
  const opts = {
153
387
  root,
154
388
  databaseUrl: prepareDatabaseUrl,
155
389
  cacheDir,
156
390
  dtsPath,
157
- check: flag("--check"),
391
+ check: prepareCheck,
392
+ verify: prepareVerify,
393
+ json: prepareJson,
158
394
  prune: !flag("--no-prune"),
395
+ strictInference: flag("--strict-inference"),
159
396
  };
160
- if (flag("--watch")) {
161
- if (flag("--check")) {
162
- console.error("--watch and --check are mutually exclusive");
163
- process.exit(2);
164
- }
397
+ const applyShadowMigrations = prepareShadowUrl
398
+ ? (await import("../src/commands/schema.js")).applyShadowMigrations
399
+ : undefined;
400
+ if (prepareWatch) {
401
+ const { runWatch } = await import("../src/commands/watch.js");
165
402
  await runWatch({
166
403
  ...opts,
167
404
  ...(prepareShadowUrl
@@ -175,13 +412,33 @@ else if (cmd === "prepare") {
175
412
  });
176
413
  }
177
414
  else {
178
- if (prepareShadowUrl)
179
- await applyShadowMigrations(prepareShadowUrl, migrationsDir);
180
- await runPrepare(opts);
415
+ if (prepareShadowUrl) {
416
+ try {
417
+ await applyShadowMigrations(prepareShadowUrl, migrationsDir, prepareJson ? () => { } : console.log);
418
+ }
419
+ catch (e) {
420
+ failPrepare(e.message, "shadow", 1);
421
+ }
422
+ }
423
+ try {
424
+ await runPrepare(opts);
425
+ }
426
+ catch (e) {
427
+ const message = e.message;
428
+ const phase = e instanceof PrepareFatalError
429
+ ? e.phase
430
+ : prepareVerify
431
+ ? "verify"
432
+ : "scan";
433
+ failPrepare(message, phase, 1, e instanceof PrepareFatalError
434
+ ? { file: e.file, line: e.line, column: e.column }
435
+ : {});
436
+ }
181
437
  }
182
438
  }
183
439
  else if (cmd === "schema") {
184
- const sub = process.argv[3];
440
+ const { runSchemaCheck, runSchemaDump } = await import("../src/commands/schema.js");
441
+ const sub = positionals[0];
185
442
  const schemaDatabaseUrl = shadowUrl ?? databaseUrl;
186
443
  if (!schemaDatabaseUrl) {
187
444
  console.error("DATABASE_URL is required for schema commands (or pass --shadow-url)");
@@ -199,19 +456,21 @@ else if (cmd === "schema") {
199
456
  await runSchemaDump(opts);
200
457
  else if (sub === "check")
201
458
  await runSchemaCheck(opts);
202
- else
203
- help();
204
459
  }
205
460
  else if (cmd === "migrate") {
206
- const sub = process.argv[3];
461
+ const { migrateArchiveList, migrateArchiveRestore, migrateCheck, migrateDev, migrateRun, migrateInfo, migrateRevert, migrateAdd, migrateSquash, migrateVerify, } = await import("../src/commands/migrate.js");
462
+ const sub = positionals[0];
207
463
  const revertDryRun = sub === "revert" && flag("--dry-run");
208
464
  const workflowShadowOnly = ((sub === "dev" || sub === "verify" || sub === "squash") && !!shadowUrl) || (revertDryRun && !!shadowUrl);
209
- if (!databaseUrl && sub !== "add" && sub !== "check" && sub !== "squash" && sub !== "archive" && !workflowShadowOnly) {
465
+ if (!databaseUrl && sub !== "add" && sub !== "check" && sub !== "archive" && !workflowShadowOnly) {
210
466
  console.error("DATABASE_URL is required");
211
467
  process.exit(2);
212
468
  }
213
469
  const tRaw = arg("--lock-timeout");
214
470
  const lockTimeoutMs = tRaw ? Number(tRaw) : undefined;
471
+ if (lockTimeoutMs !== undefined && !Number.isFinite(lockTimeoutMs)) {
472
+ usageError("--lock-timeout must be a finite number of milliseconds", "migrate");
473
+ }
215
474
  if (sub === "dev") {
216
475
  await migrateDev({
217
476
  root,
@@ -223,6 +482,7 @@ else if (cmd === "migrate") {
223
482
  shadowUrl,
224
483
  shadowAdminUrl,
225
484
  lockTimeoutMs,
485
+ strictInference: flag("--strict-inference"),
226
486
  });
227
487
  }
228
488
  else if (sub === "verify") {
@@ -235,15 +495,18 @@ else if (cmd === "migrate") {
235
495
  shadowUrl,
236
496
  shadowAdminUrl,
237
497
  lockTimeoutMs,
498
+ strictInference: flag("--strict-inference"),
238
499
  });
239
500
  }
240
501
  else if (sub === "run") {
241
502
  await migrateRun({ databaseUrl, migrationsDir, lockTimeoutMs, dryRun: flag("--dry-run"), json: flag("--json") });
242
503
  }
243
- else if (sub === "info")
504
+ else if (sub === "info") {
244
505
  await migrateInfo({ databaseUrl, migrationsDir, json: flag("--json") });
245
- else if (sub === "check")
506
+ }
507
+ else if (sub === "check") {
246
508
  migrateCheck({ migrationsDir, json: flag("--json") });
509
+ }
247
510
  else if (sub === "revert") {
248
511
  await migrateRevert({
249
512
  databaseUrl,
@@ -256,19 +519,11 @@ else if (cmd === "migrate") {
256
519
  });
257
520
  }
258
521
  else if (sub === "add") {
259
- const name = process.argv[4];
260
- if (!name) {
261
- console.error("migrate add: name required");
262
- process.exit(2);
263
- }
522
+ const name = positionals[1];
264
523
  migrateAdd({ databaseUrl, migrationsDir, name });
265
524
  }
266
525
  else if (sub === "squash") {
267
- const name = process.argv[4];
268
- if (!name) {
269
- console.error("migrate squash: name required");
270
- process.exit(2);
271
- }
526
+ const name = positionals[1];
272
527
  await migrateSquash({
273
528
  databaseUrl,
274
529
  migrationsDir,
@@ -281,23 +536,16 @@ else if (cmd === "migrate") {
281
536
  });
282
537
  }
283
538
  else if (sub === "archive") {
284
- const action = process.argv[4];
285
- if (action === "list")
539
+ const action = positionals[1];
540
+ if (action === "list") {
286
541
  migrateArchiveList({ migrationsDir });
542
+ }
287
543
  else if (action === "restore") {
288
- const name = process.argv[5];
289
- if (!name) {
290
- console.error("migrate archive restore: name required");
291
- process.exit(2);
292
- }
544
+ const name = positionals[2];
293
545
  migrateArchiveRestore({ migrationsDir, name, force: flag("--force") });
294
546
  }
295
- else
296
- help();
297
547
  }
298
- else
299
- help();
300
548
  }
301
549
  else {
302
- help();
550
+ usageError(`unknown command ${JSON.stringify(cmd)}`);
303
551
  }
@@ -0,0 +1,9 @@
1
+ export type ArtifactSet = {
2
+ cacheDir: string;
3
+ dtsPath: string;
4
+ };
5
+ export type ArtifactComparison = {
6
+ ok: boolean;
7
+ changed: string[];
8
+ };
9
+ export declare function compareArtifacts(expected: ArtifactSet, actual: ArtifactSet): ArtifactComparison;
@@ -0,0 +1,26 @@
1
+ import { existsSync, readFileSync, readdirSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { CACHE_MANIFEST_FILE } from "./cache.js";
4
+ function readGeneratedFiles(set) {
5
+ const files = new Map();
6
+ if (existsSync(set.cacheDir)) {
7
+ for (const name of readdirSync(set.cacheDir).sort()) {
8
+ if (name !== CACHE_MANIFEST_FILE && !/^[0-9a-f]{16}\.json$/.test(name))
9
+ continue;
10
+ files.set(`cache/${name}`, readFileSync(join(set.cacheDir, name), "utf8"));
11
+ }
12
+ const functions = join(set.cacheDir, "functions/functions.json");
13
+ if (existsSync(functions))
14
+ files.set("cache/functions/functions.json", readFileSync(functions, "utf8"));
15
+ }
16
+ if (existsSync(set.dtsPath))
17
+ files.set("sqlx-js-env.d.ts", readFileSync(set.dtsPath, "utf8"));
18
+ return files;
19
+ }
20
+ export function compareArtifacts(expected, actual) {
21
+ const left = readGeneratedFiles(expected);
22
+ const right = readGeneratedFiles(actual);
23
+ const names = new Set([...left.keys(), ...right.keys()]);
24
+ const changed = [...names].filter((name) => left.get(name) !== right.get(name)).sort();
25
+ return { ok: changed.length === 0, changed };
26
+ }
@@ -1,3 +1,11 @@
1
+ export declare const CACHE_FORMAT_VERSION = 2;
2
+ export declare const GENERATOR_REVISION = 3;
3
+ export declare const CACHE_MANIFEST_FILE = "cache-manifest.json";
4
+ export type CacheManifest = {
5
+ cacheFormat: typeof CACHE_FORMAT_VERSION;
6
+ generatorRevision: typeof GENERATOR_REVISION;
7
+ configHash: string;
8
+ };
1
9
  export type CacheColumn = {
2
10
  name: string;
3
11
  typeOid: number;
@@ -19,6 +27,7 @@ export type CacheEntry = {
19
27
  reason: string;
20
28
  };
21
29
  };
30
+ export declare function portableCacheOid(oid: number): number;
22
31
  export declare function fingerprint(query: string): string;
23
32
  export declare function effectiveNullable(c: CacheColumn): boolean;
24
33
  export declare class Cache {
@@ -28,6 +37,10 @@ export declare class Cache {
28
37
  has(fp: string): boolean;
29
38
  read(fp: string): CacheEntry | null;
30
39
  write(fp: string, entry: CacheEntry): void;
40
+ replaceAll(entries: Iterable<{
41
+ fp: string;
42
+ entry: CacheEntry;
43
+ }>, prune?: boolean): string[];
31
44
  list(): {
32
45
  fp: string;
33
46
  entry: CacheEntry;
@@ -35,3 +48,7 @@ export declare class Cache {
35
48
  remove(fp: string): void;
36
49
  prune(keep: Iterable<string>): string[];
37
50
  }
51
+ export declare function cacheManifestPath(cacheDir: string): string;
52
+ export declare function writeCacheManifest(cacheDir: string, configHash: string): void;
53
+ export declare function readCacheManifest(cacheDir: string): CacheManifest | null;
54
+ export declare function assertCacheManifest(cacheDir: string, configHash: string): CacheManifest;