@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
package/dist/src/cache.js CHANGED
@@ -1,6 +1,13 @@
1
1
  import { createHash, randomBytes } from "node:crypto";
2
2
  import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, unlinkSync, renameSync } from "node:fs";
3
3
  import { join } from "node:path";
4
+ import { isBuiltinOid } from "./pg/oids.js";
5
+ export const CACHE_FORMAT_VERSION = 2;
6
+ export const GENERATOR_REVISION = 3;
7
+ export const CACHE_MANIFEST_FILE = "cache-manifest.json";
8
+ export function portableCacheOid(oid) {
9
+ return isBuiltinOid(oid) ? oid : 0;
10
+ }
4
11
  export function fingerprint(query) {
5
12
  const norm = normalizeForFingerprint(query);
6
13
  return createHash("sha256").update(norm).digest("hex").slice(0, 16);
@@ -202,11 +209,35 @@ export class Cache {
202
209
  throw err;
203
210
  }
204
211
  }
212
+ replaceAll(entries, prune = true) {
213
+ this.ensure();
214
+ const staged = [];
215
+ try {
216
+ for (const { fp, entry } of entries) {
217
+ const final = join(this.dir, `${fp}.json`);
218
+ const tmp = `${final}.tmp-${randomBytes(4).toString("hex")}`;
219
+ writeFileSync(tmp, JSON.stringify(entry, null, 2));
220
+ staged.push({ fp, tmp, final });
221
+ }
222
+ for (const item of staged)
223
+ renameSync(item.tmp, item.final);
224
+ }
225
+ catch (err) {
226
+ for (const item of staged) {
227
+ try {
228
+ unlinkSync(item.tmp);
229
+ }
230
+ catch { }
231
+ }
232
+ throw err;
233
+ }
234
+ return prune ? this.prune(staged.map((item) => item.fp)) : [];
235
+ }
205
236
  list() {
206
237
  if (!existsSync(this.dir))
207
238
  return [];
208
239
  return readdirSync(this.dir)
209
- .filter((f) => f.endsWith(".json") && !f.includes(".tmp-"))
240
+ .filter((f) => f !== CACHE_MANIFEST_FILE && f.endsWith(".json") && !f.includes(".tmp-"))
210
241
  .map((f) => {
211
242
  const fp = f.replace(/\.json$/, "");
212
243
  return { fp, entry: assertEntryShape(fp, parseEntryJson(join(this.dir, f))) };
@@ -229,3 +260,54 @@ export class Cache {
229
260
  return removed;
230
261
  }
231
262
  }
263
+ export function cacheManifestPath(cacheDir) {
264
+ return join(cacheDir, CACHE_MANIFEST_FILE);
265
+ }
266
+ export function writeCacheManifest(cacheDir, configHash) {
267
+ if (!existsSync(cacheDir))
268
+ mkdirSync(cacheDir, { recursive: true });
269
+ const path = cacheManifestPath(cacheDir);
270
+ const tmp = `${path}.tmp-${randomBytes(4).toString("hex")}`;
271
+ const manifest = {
272
+ cacheFormat: CACHE_FORMAT_VERSION,
273
+ generatorRevision: GENERATOR_REVISION,
274
+ configHash,
275
+ };
276
+ writeFileSync(tmp, JSON.stringify(manifest, null, 2) + "\n");
277
+ try {
278
+ renameSync(tmp, path);
279
+ }
280
+ catch (err) {
281
+ try {
282
+ unlinkSync(tmp);
283
+ }
284
+ catch { }
285
+ throw err;
286
+ }
287
+ }
288
+ export function readCacheManifest(cacheDir) {
289
+ const path = cacheManifestPath(cacheDir);
290
+ if (!existsSync(path))
291
+ return null;
292
+ const raw = parseEntryJson(path);
293
+ if (!raw || typeof raw !== "object") {
294
+ throw new Error(`sqlx-js: cache manifest is malformed: ${path}`);
295
+ }
296
+ const value = raw;
297
+ if (value.cacheFormat !== CACHE_FORMAT_VERSION ||
298
+ value.generatorRevision !== GENERATOR_REVISION ||
299
+ typeof value.configHash !== "string") {
300
+ throw new Error(`sqlx-js: cache manifest is stale: ${path}. Run \`sqlx-js prepare\`.`);
301
+ }
302
+ return value;
303
+ }
304
+ export function assertCacheManifest(cacheDir, configHash) {
305
+ const manifest = readCacheManifest(cacheDir);
306
+ if (!manifest) {
307
+ throw new Error(`sqlx-js: cache manifest is missing. Run \`sqlx-js prepare\` to regenerate the cache.`);
308
+ }
309
+ if (manifest.configHash !== configHash) {
310
+ throw new Error("sqlx-js: cache was generated with a different jsonbTypes/customTypes config. Run `sqlx-js prepare`.");
311
+ }
312
+ return manifest;
313
+ }
@@ -1,4 +1,5 @@
1
- import { writeFileSync, mkdirSync } from "node:fs";
1
+ import { writeFileSync, mkdirSync, renameSync, unlinkSync } from "node:fs";
2
+ import { randomBytes } from "node:crypto";
2
3
  import { dirname } from "node:path";
3
4
  import { effectiveNullable } from "./cache.js";
4
5
  function entrySignature(e) {
@@ -25,7 +26,18 @@ export function emitDts(outPath, entries, functions = []) {
25
26
  emitModule(lines, "@onreza/sqlx-js", entries, functions);
26
27
  lines.push("");
27
28
  lines.push("export {};");
28
- writeFileSync(outPath, lines.join("\n") + "\n");
29
+ const tmp = `${outPath}.tmp-${randomBytes(4).toString("hex")}`;
30
+ writeFileSync(tmp, lines.join("\n") + "\n");
31
+ try {
32
+ renameSync(tmp, outPath);
33
+ }
34
+ catch (err) {
35
+ try {
36
+ unlinkSync(tmp);
37
+ }
38
+ catch { }
39
+ throw err;
40
+ }
29
41
  }
30
42
  function emitModule(lines, moduleName, entries, functions) {
31
43
  lines.push(`declare module ${JSON.stringify(moduleName)} {`);
@@ -0,0 +1,16 @@
1
+ export type DoctorCheck = {
2
+ name: string;
3
+ status: "ok" | "warning" | "error";
4
+ message: string;
5
+ details?: Record<string, unknown>;
6
+ };
7
+ export type DoctorOptions = {
8
+ root: string;
9
+ databaseUrl: string;
10
+ cacheDir: string;
11
+ dtsPath: string;
12
+ json?: boolean;
13
+ envError?: string;
14
+ };
15
+ export declare function inspectDoctor(opts: DoctorOptions): Promise<DoctorCheck[]>;
16
+ export declare function runDoctor(opts: DoctorOptions): Promise<void>;
@@ -0,0 +1,196 @@
1
+ import { existsSync } from "node:fs";
2
+ import { join, resolve } from "node:path";
3
+ import ts from "typescript";
4
+ import { assertCacheManifest } from "../cache.js";
5
+ import { assertSupportedRuntime, envFilePath, loadConfigInfo, nativeTypeScriptEnabled, prepareConfigHash, runtimeVersion, } from "../config.js";
6
+ import { decodeText, parseDatabaseUrl, PgClient } from "../pg/wire.js";
7
+ import { probePgschema } from "./pgschema.js";
8
+ function decodeBoolean(value) {
9
+ const text = decodeText(value ?? null);
10
+ return text === "t" || text === "true" || text === "1";
11
+ }
12
+ function tsconfigIncludes(root, file) {
13
+ const path = join(root, "tsconfig.json");
14
+ if (!existsSync(path))
15
+ return { ok: false, message: `tsconfig.json not found at ${path}` };
16
+ const target = resolve(file);
17
+ const visited = new Set();
18
+ const visit = (configPath) => {
19
+ const resolvedConfig = resolve(configPath);
20
+ if (visited.has(resolvedConfig))
21
+ return { included: false };
22
+ visited.add(resolvedConfig);
23
+ const read = ts.readConfigFile(resolvedConfig, ts.sys.readFile);
24
+ if (read.error) {
25
+ return { included: false, error: ts.flattenDiagnosticMessageText(read.error.messageText, "\n") };
26
+ }
27
+ const parsed = ts.parseJsonConfigFileContent(read.config, ts.sys, resolve(resolvedConfig, ".."), undefined, resolvedConfig);
28
+ if (parsed.errors.length > 0) {
29
+ return {
30
+ included: false,
31
+ error: parsed.errors.map((error) => ts.flattenDiagnosticMessageText(error.messageText, "\n")).join("; "),
32
+ };
33
+ }
34
+ if (parsed.fileNames.some((name) => resolve(name) === target))
35
+ return { included: true };
36
+ for (const reference of parsed.projectReferences ?? []) {
37
+ const result = visit(ts.resolveProjectReferencePath(reference));
38
+ if (result.included || result.error)
39
+ return result;
40
+ }
41
+ return { included: false };
42
+ };
43
+ const result = visit(path);
44
+ if (result.error)
45
+ return { ok: false, message: result.error };
46
+ const included = result.included;
47
+ return included
48
+ ? { ok: true, message: `${file} is included by tsconfig.json` }
49
+ : { ok: false, message: `${file} is not included by tsconfig.json` };
50
+ }
51
+ export async function inspectDoctor(opts) {
52
+ const checks = [];
53
+ const runtime = runtimeVersion();
54
+ try {
55
+ assertSupportedRuntime();
56
+ checks.push({
57
+ name: "runtime",
58
+ status: "ok",
59
+ message: `${runtime.runtime} ${runtime.version} satisfies the supported baseline`,
60
+ });
61
+ }
62
+ catch (e) {
63
+ checks.push({ name: "runtime", status: "error", message: e.message });
64
+ }
65
+ let config = {};
66
+ let configLoaded = true;
67
+ try {
68
+ const info = await loadConfigInfo(opts.root);
69
+ config = info.config;
70
+ const nativeTs = nativeTypeScriptEnabled();
71
+ if (info.path && /\.m?ts$/.test(info.path) && nativeTs === false) {
72
+ checks.push({ name: "config", status: "error", message: "native TypeScript execution is disabled" });
73
+ }
74
+ else {
75
+ checks.push({
76
+ name: "config",
77
+ status: "ok",
78
+ message: info.path ? `loaded ${info.path}` : "no config file; defaults are active",
79
+ });
80
+ }
81
+ }
82
+ catch (e) {
83
+ configLoaded = false;
84
+ checks.push({ name: "config", status: "error", message: e.message });
85
+ }
86
+ const envPath = envFilePath(opts.root);
87
+ checks.push(opts.envError
88
+ ? { name: "env", status: "error", message: opts.envError }
89
+ : existsSync(envPath) && !opts.databaseUrl
90
+ ? { name: "env", status: "error", message: `${envPath} exists but DATABASE_URL is missing` }
91
+ : existsSync(envPath)
92
+ ? { name: "env", status: "ok", message: `loaded environment file ${envPath}` }
93
+ : opts.databaseUrl
94
+ ? { name: "env", status: "ok", message: "DATABASE_URL is provided by the process environment" }
95
+ : { name: "env", status: "error", message: `DATABASE_URL is missing and ${envPath} does not exist` });
96
+ if (!configLoaded) {
97
+ checks.push({ name: "cache", status: "warning", message: "cache config-hash check skipped because config failed to load" });
98
+ }
99
+ else {
100
+ try {
101
+ assertCacheManifest(opts.cacheDir, prepareConfigHash(config));
102
+ checks.push({ name: "cache", status: "ok", message: "cache manifest and config hash are current" });
103
+ }
104
+ catch (e) {
105
+ checks.push({ name: "cache", status: "error", message: e.message });
106
+ }
107
+ }
108
+ const tsconfig = tsconfigIncludes(opts.root, opts.dtsPath);
109
+ checks.push({
110
+ name: "tsconfig",
111
+ status: tsconfig.ok ? "ok" : "error",
112
+ message: tsconfig.message,
113
+ });
114
+ if (!opts.databaseUrl) {
115
+ checks.push({ name: "database", status: "error", message: "DATABASE_URL is required for the database check" });
116
+ }
117
+ else {
118
+ const client = new PgClient(parseDatabaseUrl(opts.databaseUrl));
119
+ try {
120
+ await client.connect();
121
+ await client.describe("SELECT 1");
122
+ const result = await client.simpleQueryAll(`
123
+ SELECT current_setting('server_version'), current_user, current_database(),
124
+ (SELECT (rolcreatedb OR rolsuper)::text FROM pg_roles WHERE rolname = current_user),
125
+ COALESCE(has_schema_privilege(current_user, current_schema(), 'USAGE'), false)::text
126
+ `);
127
+ const row = result.rows[0];
128
+ const canCreateDatabase = decodeBoolean(row[3]);
129
+ const hasSchemaUsage = decodeBoolean(row[4]);
130
+ const shadowDatabaseUrl = process.env.SHADOW_DATABASE_URL;
131
+ const shadowAdminDatabaseUrl = process.env.SHADOW_ADMIN_DATABASE_URL;
132
+ const hasShadowFallback = Boolean(shadowDatabaseUrl || shadowAdminDatabaseUrl);
133
+ const needsShadowCreate = config.schema?.provider !== "pgschema";
134
+ checks.push({
135
+ name: "database",
136
+ status: "ok",
137
+ message: `connected to ${decodeText(row[2])} as ${decodeText(row[1])}`,
138
+ details: {
139
+ serverVersion: decodeText(row[0]),
140
+ describe: true,
141
+ schemaUsage: hasSchemaUsage,
142
+ },
143
+ });
144
+ checks.push({
145
+ name: "permissions",
146
+ status: hasSchemaUsage && (!needsShadowCreate || canCreateDatabase || hasShadowFallback) ? "ok" : "warning",
147
+ message: !hasSchemaUsage
148
+ ? "current user lacks USAGE on the current schema"
149
+ : !needsShadowCreate
150
+ ? "current user can use the current schema; pgschema does not require shadow-database creation"
151
+ : canCreateDatabase
152
+ ? "current user can use the current schema and create shadow databases"
153
+ : hasShadowFallback
154
+ ? "current user cannot create databases; a shadow database fallback is configured"
155
+ : "current user cannot create shadow databases; configure SHADOW_ADMIN_DATABASE_URL or SHADOW_DATABASE_URL",
156
+ details: {
157
+ schemaUsage: hasSchemaUsage,
158
+ createDatabase: canCreateDatabase,
159
+ shadowDatabaseConfigured: Boolean(shadowDatabaseUrl),
160
+ shadowAdminConfigured: Boolean(shadowAdminDatabaseUrl),
161
+ },
162
+ });
163
+ }
164
+ catch (e) {
165
+ checks.push({ name: "database", status: "error", message: e.message });
166
+ }
167
+ finally {
168
+ await client.end().catch(() => { });
169
+ }
170
+ }
171
+ if (!configLoaded) {
172
+ checks.push({ name: "pgschema", status: "warning", message: "schema-provider check skipped because config failed to load" });
173
+ }
174
+ else if (config.schema?.provider === "pgschema") {
175
+ const probe = probePgschema(opts.root, config);
176
+ checks.push({ name: "pgschema", status: probe.ok ? "ok" : "error", message: probe.message });
177
+ }
178
+ else {
179
+ checks.push({ name: "pgschema", status: "ok", message: "built-in migration provider is active" });
180
+ }
181
+ return checks;
182
+ }
183
+ export async function runDoctor(opts) {
184
+ const checks = await inspectDoctor(opts);
185
+ const ok = checks.every((check) => check.status !== "error");
186
+ if (opts.json) {
187
+ console.log(JSON.stringify({ formatVersion: 1, ok, checks }, null, 2));
188
+ }
189
+ else {
190
+ for (const check of checks) {
191
+ console.log(`${check.status.padEnd(7)} ${check.name}: ${check.message}`);
192
+ }
193
+ }
194
+ if (!ok)
195
+ process.exitCode = 1;
196
+ }
@@ -1,21 +1,19 @@
1
- import { existsSync, mkdirSync, writeFileSync } from "node:fs";
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { dirname, join } from "node:path";
3
- const CONFIG_TEMPLATE = `import type { SqlxJsConfig } from "@onreza/sqlx-js";
3
+ const CONFIG_TEMPLATE = `import { defineConfig } from "@onreza/sqlx-js";
4
4
 
5
- const config: SqlxJsConfig = {
5
+ export default defineConfig({
6
6
  // Map jsonb columns/params to TypeScript types declared in a .d.ts, e.g.
7
7
  // "users.settings": "SqlxJsJson.UserSettings",
8
8
  jsonbTypes: {},
9
9
  // Map PostgreSQL type names to TypeScript types, e.g.
10
10
  // geometry: "GeoJSON.Geometry",
11
11
  customTypes: {},
12
- };
13
-
14
- export default config;
12
+ });
15
13
  `;
16
- const PGSCHEMA_CONFIG_TEMPLATE = `import type { SqlxJsConfig } from "@onreza/sqlx-js";
14
+ const PGSCHEMA_CONFIG_TEMPLATE = `import { defineConfig } from "@onreza/sqlx-js";
17
15
 
18
- const config: SqlxJsConfig = {
16
+ export default defineConfig({
19
17
  schema: {
20
18
  provider: "pgschema",
21
19
  file: "schema.sql",
@@ -23,9 +21,7 @@ const config: SqlxJsConfig = {
23
21
  },
24
22
  jsonbTypes: {},
25
23
  customTypes: {},
26
- };
27
-
28
- export default config;
24
+ });
29
25
  `;
30
26
  const ENV_TEMPLATE = `# Connection string used by sqlx-js prepare/migrate and the runtime.
31
27
  DATABASE_URL=postgres://user:password@localhost:5432/your_db
@@ -37,10 +33,23 @@ const PGSCHEMA_TEMPLATE = `-- Desired PostgreSQL schema managed by pgschema.
37
33
  -- sqlx-js db plan -- --output-json plan.json
38
34
  -- sqlx-js db apply -- --auto-approve
39
35
  `;
36
+ const DTS_TEMPLATE = `// AUTO-GENERATED by sqlx-js. Do not edit.
37
+ // Run \`sqlx-js prepare\` to regenerate.
38
+
39
+ declare module "@onreza/sqlx-js" {
40
+ interface KnownQueries {}
41
+ interface KnownFileQueries {}
42
+ interface KnownFunctions {}
43
+ }
44
+
45
+ export {};
46
+ `;
40
47
  export function runInit(opts) {
41
48
  const log = opts.log ?? console.log;
42
49
  const created = [];
50
+ const updated = [];
43
51
  const skipped = [];
52
+ const notes = [];
44
53
  const ensureFile = (rel, content) => {
45
54
  const full = join(opts.root, rel);
46
55
  if (existsSync(full)) {
@@ -67,10 +76,81 @@ export function runInit(opts) {
67
76
  else
68
77
  ensureDir("migrations");
69
78
  ensureFile(".env.example", ENV_TEMPLATE);
79
+ ensureFile("sqlx-js-env.d.ts", DTS_TEMPLATE);
80
+ const updateJson = (rel, mutate) => {
81
+ const full = join(opts.root, rel);
82
+ if (!existsSync(full))
83
+ return;
84
+ const source = readFileSync(full, "utf8");
85
+ let value;
86
+ try {
87
+ value = JSON.parse(source);
88
+ }
89
+ catch {
90
+ notes.push(`${rel}: manual update required because the file is not strict JSON`);
91
+ return;
92
+ }
93
+ if (!value || typeof value !== "object" || Array.isArray(value))
94
+ return;
95
+ if (!mutate(value))
96
+ return;
97
+ const indent = source.match(/\n([ \t]+)"/)?.[1] ?? " ";
98
+ writeFileSync(full, JSON.stringify(value, null, indent) + "\n");
99
+ updated.push(rel);
100
+ };
101
+ updateJson("tsconfig.json", (value) => {
102
+ const files = value.files;
103
+ if (files !== undefined && (!Array.isArray(files) || !files.every((item) => typeof item === "string"))) {
104
+ notes.push("tsconfig.json: manual update required because files is not an array of strings");
105
+ return false;
106
+ }
107
+ if (Array.isArray(files)) {
108
+ if (files.includes("sqlx-js-env.d.ts"))
109
+ return false;
110
+ files.push("sqlx-js-env.d.ts");
111
+ return true;
112
+ }
113
+ const include = value.include;
114
+ if (include !== undefined && (!Array.isArray(include) || !include.every((item) => typeof item === "string"))) {
115
+ notes.push("tsconfig.json: manual update required because include is not an array of strings");
116
+ return false;
117
+ }
118
+ if (Array.isArray(include)) {
119
+ if (include.includes("sqlx-js-env.d.ts"))
120
+ return false;
121
+ include.push("sqlx-js-env.d.ts");
122
+ return true;
123
+ }
124
+ return false;
125
+ });
126
+ updateJson("package.json", (value) => {
127
+ if (value.scripts !== undefined && (!value.scripts || typeof value.scripts !== "object" || Array.isArray(value.scripts))) {
128
+ notes.push("package.json: manual script update required because scripts is not an object");
129
+ return false;
130
+ }
131
+ const scripts = (value.scripts ??= {});
132
+ const defaults = {
133
+ "sqlx:prepare": "sqlx-js prepare",
134
+ "sqlx:check": "sqlx-js prepare --check",
135
+ "sqlx:verify": "sqlx-js prepare --verify --strict-inference",
136
+ };
137
+ let changed = false;
138
+ for (const [name, command] of Object.entries(defaults)) {
139
+ if (scripts[name] !== undefined)
140
+ continue;
141
+ scripts[name] = command;
142
+ changed = true;
143
+ }
144
+ return changed;
145
+ });
70
146
  for (const f of created)
71
147
  log(`created ${f}`);
148
+ for (const f of updated)
149
+ log(`updated ${f}`);
72
150
  for (const f of skipped)
73
151
  log(`exists ${f} (left unchanged)`);
152
+ for (const note of notes)
153
+ log(`manual ${note}`);
74
154
  log("");
75
155
  log("Next steps:");
76
156
  log(" 1. Set DATABASE_URL (see .env.example).");
@@ -82,7 +162,7 @@ export function runInit(opts) {
82
162
  }
83
163
  else {
84
164
  log(" 2. Add a migration: sqlx-js migrate add init");
85
- log(" 3. Make sure tsconfig.json \"include\" covers sqlx-js-env.d.ts.");
86
- log(" 4. Develop locally: sqlx-js migrate dev");
165
+ log(" 3. Check the project setup: sqlx-js doctor");
166
+ log(" 4. Develop locally: sqlx-js migrate dev --strict-inference");
87
167
  }
88
168
  }
@@ -1,4 +1,5 @@
1
1
  import { PgClient } from "../pg/wire.js";
2
+ export * from "../migration-core.js";
2
3
  export type MigrateOptions = {
3
4
  databaseUrl: string;
4
5
  migrationsDir: string;
@@ -12,91 +13,7 @@ export type MigrationWorkflowOptions = MigrateOptions & {
12
13
  shadowAdminUrl?: string;
13
14
  lockKey?: number | bigint;
14
15
  lockTimeoutMs?: number;
15
- };
16
- export type MigrationStore = {
17
- table: string;
18
- };
19
- export declare const DEFAULT_MIGRATE_LOCK_KEY = 18750938867203960n;
20
- export declare function ensureTable(c: PgClient): Promise<MigrationStore>;
21
- export declare function listApplied(c: PgClient, store?: MigrationStore): Promise<Map<number, {
22
- name: string;
23
- hash: string;
24
- }>>;
25
- export type ApplyOutcome = {
26
- kind: "applied";
27
- version: number;
28
- name: string;
29
- } | {
30
- kind: "adopted";
31
- version: number;
32
- name: string;
33
- replaced: number;
34
- } | {
35
- kind: "tampered";
36
- version: number;
37
- name: string;
38
- applied: string;
39
- current: string;
40
- } | {
41
- kind: "failed";
42
- version: number;
43
- name: string;
44
- error: string;
45
- };
46
- export type PlanOutcome = {
47
- kind: "pending";
48
- version: number;
49
- name: string;
50
- } | {
51
- kind: "adoptable";
52
- version: number;
53
- name: string;
54
- replaced: number;
55
- } | {
56
- kind: "tampered";
57
- version: number;
58
- name: string;
59
- applied: string;
60
- current: string;
61
- } | {
62
- kind: "failed";
63
- version: number;
64
- name: string;
65
- error: string;
66
- };
67
- export type MigrationPlanItem = {
68
- kind: "apply";
69
- version: number;
70
- name: string;
71
- } | {
72
- kind: "adopt";
73
- version: number;
74
- name: string;
75
- replaced: number;
76
- };
77
- export type MigrationPlanDiagnostic = Extract<PlanOutcome, {
78
- kind: "tampered" | "failed";
79
- }>;
80
- export type MigrationPlanSnapshot = {
81
- ok: boolean;
82
- pending: number;
83
- adoptable: number;
84
- tampered: number;
85
- failed: number;
86
- steps: MigrationPlanItem[];
87
- diagnostics: MigrationPlanDiagnostic[];
88
- };
89
- export type MigrationInfoStatus = "applied" | "pending" | "adoptable" | "superseded" | "tampered" | "failed";
90
- export type MigrationInfoItem = {
91
- version: number;
92
- name: string;
93
- status: MigrationInfoStatus;
94
- detail?: string;
95
- };
96
- export type MigrationInfoSnapshot = {
97
- historyTable: string | null;
98
- summary: Record<MigrationInfoStatus, number>;
99
- items: MigrationInfoItem[];
16
+ strictInference?: boolean;
100
17
  };
101
18
  export type MigrationArchive = {
102
19
  name: string;
@@ -151,22 +68,6 @@ export type RevertDryRunOutcome = {
151
68
  error: string;
152
69
  };
153
70
  export declare function checkMigrationFiles(migrationsDir: string): MigrationCheckReport;
154
- export declare function planPending(c: PgClient, migrationsDir: string, onEvent?: (e: PlanOutcome) => void): Promise<{
155
- pending: number;
156
- adoptable: number;
157
- tampered: number;
158
- failed: number;
159
- steps: MigrationPlanItem[];
160
- }>;
161
- export declare function inspectMigrationPlan(c: PgClient, migrationsDir: string): Promise<MigrationPlanSnapshot>;
162
- export declare function inspectMigrations(c: PgClient, migrationsDir: string): Promise<MigrationInfoSnapshot>;
163
- export declare function applyPending(c: PgClient, migrationsDir: string, onEvent?: (e: ApplyOutcome) => void): Promise<{
164
- applied: number;
165
- tampered: number;
166
- failed: number;
167
- }>;
168
- export declare function acquireMigrateLock(c: PgClient, lockKey?: number | bigint, timeoutMs?: number): Promise<void>;
169
- export declare function releaseMigrateLock(c: PgClient, lockKey?: number | bigint): Promise<void>;
170
71
  export declare function sanitizePgDumpSchema(sql: string): string;
171
72
  export declare function listMigrationArchives(migrationsDir: string): MigrationArchive[];
172
73
  export declare function restoreMigrationArchive(migrationsDir: string, archiveName: string, opts?: {