@onreza/sqlx-js 0.5.0 → 0.7.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.
@@ -4,79 +4,12 @@ import { tmpdir } from "node:os";
4
4
  import { basename, join } from "node:path";
5
5
  import { createHash, randomBytes } from "node:crypto";
6
6
  import { PgClient, parseDatabaseUrl, decodeText } from "../pg/wire.js";
7
- import { openSession, prepareOnce, verifyPrepareArtifacts } from "./prepare.js";
7
+ import { DEFAULT_MIGRATE_LOCK_KEY, SQUASH_PREFIX, acquireMigrateLock, applyPending, buildMigrationPlan, effectiveSquashReplacements, ensureTable, inspectMigrationPlan, inspectMigrations, listApplied, parseSquashMetadata, planPending, readMigrations, releaseMigrateLock, resetMigrationSession, } from "../migration-core.js";
8
8
  import { introspectConnected, schemaSnapshotEqual, } from "../schema-snapshot.js";
9
- const SQUASH_PREFIX = "-- sqlx-js-squash:";
10
- export const DEFAULT_MIGRATE_LOCK_KEY = 18750938867203960n;
9
+ export * from "../migration-core.js";
11
10
  const FILE_RE = /^(\d+)_(.+)\.up\.sql$/;
12
11
  const DOWN_FILE_RE = /^(\d+)_(.+)\.down\.sql$/;
13
12
  const SAFE_NAME_RE = /^[a-zA-Z0-9][a-zA-Z0-9_-]*$/;
14
- const MIGRATIONS_TABLE = "_sqlx_js_migrations";
15
- function readMigrations(dir) {
16
- if (!existsSync(dir))
17
- return [];
18
- const out = [];
19
- for (const f of readdirSync(dir).sort()) {
20
- const m = FILE_RE.exec(f);
21
- if (!m)
22
- continue;
23
- const version = parseInt(m[1], 10);
24
- const name = m[2];
25
- if (!SAFE_NAME_RE.test(name)) {
26
- throw new Error(`sqlx-js.migrate: unsafe migration filename ${f} — name must match /^[a-zA-Z0-9][a-zA-Z0-9_-]*$/`);
27
- }
28
- const upPath = join(dir, f);
29
- const downPath = join(dir, `${m[1]}_${name}.down.sql`);
30
- const upSql = readFileSync(upPath, "utf8");
31
- out.push({
32
- version,
33
- name,
34
- upPath,
35
- downPath: existsSync(downPath) ? downPath : null,
36
- upSql,
37
- upHash: createHash("sha256").update(upSql).digest("hex"),
38
- squash: parseSquashMetadata(upSql),
39
- });
40
- }
41
- return out;
42
- }
43
- function parseSquashMetadata(sql) {
44
- const line = sql.split(/\r?\n/).find((l) => l.startsWith(SQUASH_PREFIX));
45
- if (!line)
46
- return null;
47
- const raw = line.slice(SQUASH_PREFIX.length).trim();
48
- let parsed;
49
- try {
50
- parsed = JSON.parse(raw);
51
- }
52
- catch (e) {
53
- throw new Error(`sqlx-js.migrate: invalid squash metadata JSON: ${e.message}`);
54
- }
55
- if (!parsed || typeof parsed !== "object") {
56
- throw new Error("sqlx-js.migrate: invalid squash metadata");
57
- }
58
- const obj = parsed;
59
- if (obj.format !== 1 || !Array.isArray(obj.replaces) || obj.replaces.length === 0) {
60
- throw new Error("sqlx-js.migrate: invalid squash metadata");
61
- }
62
- const replaces = [];
63
- for (const r of obj.replaces) {
64
- if (!r || typeof r !== "object")
65
- throw new Error("sqlx-js.migrate: invalid squash replacement metadata");
66
- const item = r;
67
- if (typeof item.version !== "number" || !Number.isSafeInteger(item.version) || item.version <= 0) {
68
- throw new Error("sqlx-js.migrate: invalid squash replacement version");
69
- }
70
- if (typeof item.name !== "string" || !SAFE_NAME_RE.test(item.name)) {
71
- throw new Error("sqlx-js.migrate: invalid squash replacement name");
72
- }
73
- if (typeof item.upHash !== "string" || !/^[a-f0-9]{64}$/.test(item.upHash)) {
74
- throw new Error("sqlx-js.migrate: invalid squash replacement hash");
75
- }
76
- replaces.push({ version: item.version, name: item.name, upHash: item.upHash });
77
- }
78
- return { format: 1, replaces };
79
- }
80
13
  function quoteIdent(ident) {
81
14
  return `"${ident.replace(/"/g, '""')}"`;
82
15
  }
@@ -91,104 +24,6 @@ function maintenanceDatabaseUrl(databaseUrl) {
91
24
  function generatedShadowDatabaseName() {
92
25
  return `sqlx_js_shadow_${process.pid}_${Date.now().toString(36)}_${randomBytes(4).toString("hex")}`;
93
26
  }
94
- async function findMigrationStore(c) {
95
- const r = await c.simpleQuery(`
96
- SELECT n.nspname, cls.relname
97
- FROM pg_class cls
98
- JOIN pg_namespace n ON n.oid = cls.relnamespace
99
- WHERE cls.oid = to_regclass('${MIGRATIONS_TABLE}')
100
- `);
101
- const row = r.rows[0];
102
- if (!row)
103
- return null;
104
- const schema = decodeText(row[0]);
105
- const table = decodeText(row[1]);
106
- if (!schema || !table) {
107
- throw new Error(`sqlx-js.migrate: failed to resolve ${MIGRATIONS_TABLE} identifier`);
108
- }
109
- return { table: `${quoteIdent(schema)}.${quoteIdent(table)}` };
110
- }
111
- async function resolveMigrationStore(c) {
112
- const store = await findMigrationStore(c);
113
- if (!store) {
114
- throw new Error(`sqlx-js.migrate: failed to resolve ${MIGRATIONS_TABLE} in current search_path`);
115
- }
116
- return store;
117
- }
118
- export async function ensureTable(c) {
119
- const existing = await findMigrationStore(c);
120
- if (existing)
121
- return existing;
122
- await c.simpleQuery(`
123
- CREATE TABLE IF NOT EXISTS ${MIGRATIONS_TABLE} (
124
- version BIGINT PRIMARY KEY,
125
- name TEXT NOT NULL,
126
- up_hash TEXT NOT NULL,
127
- applied_at TIMESTAMPTZ NOT NULL DEFAULT now()
128
- )
129
- `);
130
- return resolveMigrationStore(c);
131
- }
132
- export async function listApplied(c, store) {
133
- const s = store ?? await resolveMigrationStore(c);
134
- const r = await c.simpleQuery(`SELECT version, name, up_hash FROM ${s.table} ORDER BY version`);
135
- const out = new Map();
136
- for (const row of r.rows) {
137
- out.set(Number(decodeText(row[0])), { name: decodeText(row[1]), hash: decodeText(row[2]) });
138
- }
139
- return out;
140
- }
141
- function appliedSquashSupersededVersions(all, applied, onEvent) {
142
- const superseded = new Set();
143
- const byVersion = new Map(all.map((m) => [m.version, m]));
144
- const visitedSquashes = new Set();
145
- const visitReplacements = (m) => {
146
- if (!m.squash || visitedSquashes.has(m.version))
147
- return true;
148
- visitedSquashes.add(m.version);
149
- for (const r of m.squash.replaces) {
150
- superseded.add(r.version);
151
- const current = byVersion.get(r.version);
152
- if (!current?.squash || current.version >= m.version)
153
- continue;
154
- if (current.name !== r.name || current.upHash !== r.upHash) {
155
- onEvent?.({ kind: "tampered", version: r.version, name: r.name, applied: r.upHash, current: current.upHash });
156
- return false;
157
- }
158
- if (!visitReplacements(current))
159
- return false;
160
- }
161
- return true;
162
- };
163
- for (const m of all) {
164
- if (!m.squash)
165
- continue;
166
- const a = applied.get(m.version);
167
- if (!a)
168
- continue;
169
- if (a.hash !== m.upHash) {
170
- onEvent?.({ kind: "tampered", version: m.version, name: m.name, applied: a.hash, current: m.upHash });
171
- return null;
172
- }
173
- if (!visitReplacements(m))
174
- return null;
175
- }
176
- return superseded;
177
- }
178
- function squashCoveredVersions(all) {
179
- const covered = new Set();
180
- for (const m of all) {
181
- if (!m.squash)
182
- continue;
183
- for (const r of m.squash.replaces) {
184
- if (r.version >= m.version) {
185
- throw new Error(`sqlx-js.migrate: squash replacement ${r.version}_${r.name} must be older than ${m.version}_${m.name}`);
186
- }
187
- covered.add(r.version);
188
- }
189
- }
190
- return covered;
191
- }
192
27
  function migrationCheckIssue(code, message, details = {}) {
193
28
  return { severity: "error", code, message, ...details };
194
29
  }
@@ -489,6 +324,7 @@ async function validateLatestDownForWorkflow(databaseUrl, migrationsDir) {
489
324
  }
490
325
  }
491
326
  async function prepareWorkflowArtifacts(opts, databaseUrl) {
327
+ const { openSession, prepareOnce } = await import("./prepare.js");
492
328
  const prepareOpts = {
493
329
  root: opts.root,
494
330
  databaseUrl,
@@ -496,6 +332,7 @@ async function prepareWorkflowArtifacts(opts, databaseUrl) {
496
332
  dtsPath: opts.dtsPath,
497
333
  check: false,
498
334
  prune: opts.prune,
335
+ strictInference: opts.strictInference,
499
336
  };
500
337
  const session = await openSession(prepareOpts);
501
338
  try {
@@ -512,6 +349,7 @@ async function prepareWorkflowArtifacts(opts, databaseUrl) {
512
349
  }
513
350
  }
514
351
  async function prepareInTemporaryArtifacts(opts, databaseUrl) {
352
+ const { verifyPrepareArtifacts } = await import("./prepare.js");
515
353
  const verification = await verifyPrepareArtifacts({
516
354
  root: opts.root,
517
355
  databaseUrl,
@@ -520,388 +358,10 @@ async function prepareInTemporaryArtifacts(opts, databaseUrl) {
520
358
  check: false,
521
359
  verify: true,
522
360
  prune: true,
361
+ strictInference: opts.strictInference,
523
362
  });
524
363
  return verification.ok;
525
364
  }
526
- function effectiveSquashReplacements(all) {
527
- const covered = squashCoveredVersions(all);
528
- return all
529
- .filter((m) => !covered.has(m.version))
530
- .map((m) => ({ version: m.version, name: m.name, upHash: m.upHash }));
531
- }
532
- function preflightSquashMigrations(all, applied, superseded, onEvent) {
533
- const byVersion = new Map(all.map((m) => [m.version, m]));
534
- for (const m of all) {
535
- if (!m.squash)
536
- continue;
537
- let present = 0;
538
- for (const r of m.squash.replaces) {
539
- if (r.version >= m.version) {
540
- onEvent?.({
541
- kind: "failed",
542
- version: m.version,
543
- name: m.name,
544
- error: `squash replacement ${r.version}_${r.name} must be older than ${m.version}_${m.name}`,
545
- });
546
- return "failed";
547
- }
548
- const current = byVersion.get(r.version);
549
- if (current && !superseded.has(r.version) && (current.name !== r.name || current.upHash !== r.upHash)) {
550
- onEvent?.({ kind: "tampered", version: r.version, name: r.name, applied: r.upHash, current: current.upHash });
551
- return "tampered";
552
- }
553
- const a = applied.get(r.version);
554
- if (!a)
555
- continue;
556
- present++;
557
- if (a.hash !== r.upHash || a.name !== r.name) {
558
- onEvent?.({ kind: "tampered", version: r.version, name: r.name, applied: a.hash, current: r.upHash });
559
- return "tampered";
560
- }
561
- }
562
- if (present > 0 && present !== m.squash.replaces.length) {
563
- onEvent?.({
564
- kind: "failed",
565
- version: m.version,
566
- name: m.name,
567
- error: `squash migration replaces ${m.squash.replaces.length} migration(s), but only ${present} matching row(s) are applied`,
568
- });
569
- return "failed";
570
- }
571
- }
572
- return "ok";
573
- }
574
- function planSquashAdoption(m, applied, onEvent) {
575
- if (!m.squash)
576
- return { kind: "none" };
577
- let present = 0;
578
- for (const r of m.squash.replaces) {
579
- if (r.version >= m.version) {
580
- onEvent?.({
581
- kind: "failed",
582
- version: m.version,
583
- name: m.name,
584
- error: `squash replacement ${r.version}_${r.name} must be older than ${m.version}_${m.name}`,
585
- });
586
- return { kind: "failed" };
587
- }
588
- const a = applied.get(r.version);
589
- if (!a)
590
- continue;
591
- present++;
592
- if (a.hash !== r.upHash || a.name !== r.name) {
593
- onEvent?.({ kind: "tampered", version: r.version, name: r.name, applied: a.hash, current: r.upHash });
594
- return { kind: "tampered" };
595
- }
596
- }
597
- if (present === 0)
598
- return { kind: "none" };
599
- if (present !== m.squash.replaces.length) {
600
- onEvent?.({
601
- kind: "failed",
602
- version: m.version,
603
- name: m.name,
604
- error: `squash migration replaces ${m.squash.replaces.length} migration(s), but only ${present} matching row(s) are applied`,
605
- });
606
- return { kind: "failed" };
607
- }
608
- return { kind: "adopt", replaced: m.squash.replaces.length };
609
- }
610
- function buildMigrationPlan(all, applied, onEvent) {
611
- const plannedApplied = new Map(applied);
612
- const superseded = appliedSquashSupersededVersions(all, plannedApplied, onEvent);
613
- if (!superseded)
614
- return { kind: "tampered" };
615
- const preflight = preflightSquashMigrations(all, plannedApplied, superseded, onEvent);
616
- if (preflight !== "ok")
617
- return { kind: preflight };
618
- const steps = [];
619
- for (const m of all) {
620
- if (superseded.has(m.version))
621
- continue;
622
- const a = plannedApplied.get(m.version);
623
- if (a) {
624
- if (a.hash !== m.upHash) {
625
- onEvent?.({ kind: "tampered", version: m.version, name: m.name, applied: a.hash, current: m.upHash });
626
- return { kind: "tampered" };
627
- }
628
- continue;
629
- }
630
- const adoption = planSquashAdoption(m, plannedApplied, onEvent);
631
- if (adoption.kind === "adopt") {
632
- steps.push({ kind: "adopt", migration: m, replaced: adoption.replaced });
633
- for (const r of m.squash.replaces)
634
- plannedApplied.delete(r.version);
635
- plannedApplied.set(m.version, { name: m.name, hash: m.upHash });
636
- continue;
637
- }
638
- if (adoption.kind === "tampered" || adoption.kind === "failed")
639
- return { kind: adoption.kind };
640
- steps.push({ kind: "apply", migration: m });
641
- plannedApplied.set(m.version, { name: m.name, hash: m.upHash });
642
- }
643
- return { kind: "ok", steps };
644
- }
645
- function publicPlanItem(step) {
646
- if (step.kind === "apply") {
647
- return { kind: "apply", version: step.migration.version, name: step.migration.name };
648
- }
649
- return {
650
- kind: "adopt",
651
- version: step.migration.version,
652
- name: step.migration.name,
653
- replaced: step.replaced,
654
- };
655
- }
656
- async function resetMigrationSession(c) {
657
- await c.simpleQuery("RESET ALL");
658
- }
659
- async function executeSquashAdoption(c, store, m, applied, replaced, onEvent) {
660
- if (!m.squash)
661
- return "failed";
662
- let committed = false;
663
- await c.simpleQuery("BEGIN");
664
- try {
665
- for (const r of m.squash.replaces) {
666
- await c.execParamsText(`DELETE FROM ${store.table} WHERE version = $1`, [String(r.version)]);
667
- }
668
- await c.execParamsText(`INSERT INTO ${store.table} (version, name, up_hash) VALUES ($1, $2, $3)`, [String(m.version), m.name, m.upHash]);
669
- await c.simpleQuery("COMMIT");
670
- committed = true;
671
- }
672
- catch (err) {
673
- let rollbackErr;
674
- if (!committed) {
675
- try {
676
- await c.simpleQuery("ROLLBACK");
677
- }
678
- catch (rb) {
679
- rollbackErr = rb.message;
680
- }
681
- }
682
- const message = rollbackErr
683
- ? `${err.message} (rollback also failed: ${rollbackErr})`
684
- : err.message;
685
- onEvent?.({ kind: "failed", version: m.version, name: m.name, error: message });
686
- return "failed";
687
- }
688
- try {
689
- await resetMigrationSession(c);
690
- }
691
- catch (err) {
692
- onEvent?.({ kind: "failed", version: m.version, name: m.name, error: `failed to reset migration session: ${err.message}` });
693
- return "failed";
694
- }
695
- for (const r of m.squash.replaces)
696
- applied.delete(r.version);
697
- applied.set(m.version, { name: m.name, hash: m.upHash });
698
- onEvent?.({ kind: "adopted", version: m.version, name: m.name, replaced });
699
- return "done";
700
- }
701
- export async function planPending(c, migrationsDir, onEvent) {
702
- const store = await findMigrationStore(c);
703
- const applied = store ? await listApplied(c, store) : new Map();
704
- const all = readMigrations(migrationsDir);
705
- const counts = { pending: 0, adoptable: 0, tampered: 0, failed: 0 };
706
- const plan = buildMigrationPlan(all, applied, onEvent);
707
- if (plan.kind === "tampered") {
708
- counts.tampered++;
709
- return { ...counts, steps: [] };
710
- }
711
- if (plan.kind === "failed") {
712
- counts.failed++;
713
- return { ...counts, steps: [] };
714
- }
715
- const steps = plan.steps.map(publicPlanItem);
716
- for (const step of steps) {
717
- if (step.kind === "apply") {
718
- counts.pending++;
719
- onEvent?.({ kind: "pending", version: step.version, name: step.name });
720
- }
721
- else {
722
- counts.adoptable++;
723
- onEvent?.({ kind: "adoptable", version: step.version, name: step.name, replaced: step.replaced });
724
- }
725
- }
726
- return { ...counts, steps };
727
- }
728
- export async function inspectMigrationPlan(c, migrationsDir) {
729
- const diagnostics = [];
730
- const result = await planPending(c, migrationsDir, (e) => {
731
- if (e.kind === "tampered" || e.kind === "failed")
732
- diagnostics.push(e);
733
- });
734
- return {
735
- ok: result.tampered === 0 && result.failed === 0,
736
- ...result,
737
- diagnostics,
738
- };
739
- }
740
- export async function inspectMigrations(c, migrationsDir) {
741
- const store = await findMigrationStore(c);
742
- const applied = store ? await listApplied(c, store) : new Map();
743
- const all = readMigrations(migrationsDir);
744
- const validation = [];
745
- const plan = buildMigrationPlan(all, applied, (e) => validation.push(e));
746
- const planned = new Map();
747
- if (plan.kind === "ok") {
748
- for (const step of plan.steps)
749
- planned.set(step.migration.version, step);
750
- }
751
- const validationByVersion = new Map(validation.map((e) => [e.version, e]));
752
- const superseded = appliedSquashSupersededVersions(all, applied) ?? new Set();
753
- const summary = {
754
- applied: 0,
755
- pending: 0,
756
- adoptable: 0,
757
- superseded: 0,
758
- tampered: 0,
759
- failed: 0,
760
- };
761
- const items = all.map((m) => {
762
- const validationEvent = validationByVersion.get(m.version);
763
- if (validationEvent?.kind === "tampered") {
764
- summary.tampered++;
765
- return {
766
- version: m.version,
767
- name: m.name,
768
- status: "tampered",
769
- detail: `hash mismatch: applied ${validationEvent.applied.slice(0, 16)} vs current ${validationEvent.current.slice(0, 16)}`,
770
- };
771
- }
772
- if (validationEvent?.kind === "failed") {
773
- summary.failed++;
774
- return { version: m.version, name: m.name, status: "failed", detail: validationEvent.error };
775
- }
776
- if (superseded.has(m.version)) {
777
- summary.superseded++;
778
- return { version: m.version, name: m.name, status: "superseded" };
779
- }
780
- const a = applied.get(m.version);
781
- if (a) {
782
- if (a.hash !== m.upHash) {
783
- summary.tampered++;
784
- return {
785
- version: m.version,
786
- name: m.name,
787
- status: "tampered",
788
- detail: `hash mismatch: applied ${a.hash.slice(0, 16)} vs current ${m.upHash.slice(0, 16)}`,
789
- };
790
- }
791
- summary.applied++;
792
- return { version: m.version, name: m.name, status: "applied" };
793
- }
794
- const plannedStep = planned.get(m.version);
795
- if (plannedStep?.kind === "adopt") {
796
- summary.adoptable++;
797
- return { version: m.version, name: m.name, status: "adoptable", detail: `${plannedStep.replaced} replaced` };
798
- }
799
- summary.pending++;
800
- return { version: m.version, name: m.name, status: "pending" };
801
- });
802
- return { historyTable: store?.table ?? null, summary, items };
803
- }
804
- export async function applyPending(c, migrationsDir, onEvent) {
805
- const store = await ensureTable(c);
806
- const applied = await listApplied(c, store);
807
- const all = readMigrations(migrationsDir);
808
- const counts = { applied: 0, tampered: 0, failed: 0 };
809
- const plan = buildMigrationPlan(all, applied, onEvent);
810
- if (plan.kind === "tampered") {
811
- counts.tampered++;
812
- return counts;
813
- }
814
- if (plan.kind === "failed") {
815
- counts.failed++;
816
- return counts;
817
- }
818
- for (const step of plan.steps) {
819
- const m = step.migration;
820
- if (step.kind === "adopt") {
821
- const adoption = await executeSquashAdoption(c, store, m, applied, step.replaced, onEvent);
822
- if (adoption === "failed") {
823
- counts.failed++;
824
- return counts;
825
- }
826
- counts.applied++;
827
- continue;
828
- }
829
- let committed = false;
830
- await c.simpleQuery("BEGIN");
831
- try {
832
- await c.simpleQuery(m.upSql);
833
- await c.execParamsText(`INSERT INTO ${store.table} (version, name, up_hash) VALUES ($1, $2, $3)`, [String(m.version), m.name, m.upHash]);
834
- await c.simpleQuery("COMMIT");
835
- committed = true;
836
- }
837
- catch (err) {
838
- let rollbackErr;
839
- if (!committed) {
840
- try {
841
- await c.simpleQuery("ROLLBACK");
842
- }
843
- catch (rb) {
844
- rollbackErr = rb.message;
845
- }
846
- }
847
- counts.failed++;
848
- const message = rollbackErr
849
- ? `${err.message} (rollback also failed: ${rollbackErr})`
850
- : err.message;
851
- onEvent?.({ kind: "failed", version: m.version, name: m.name, error: message });
852
- return counts;
853
- }
854
- try {
855
- await resetMigrationSession(c);
856
- }
857
- catch (err) {
858
- counts.failed++;
859
- onEvent?.({ kind: "failed", version: m.version, name: m.name, error: `failed to reset migration session: ${err.message}` });
860
- return counts;
861
- }
862
- counts.applied++;
863
- applied.set(m.version, { name: m.name, hash: m.upHash });
864
- onEvent?.({ kind: "applied", version: m.version, name: m.name });
865
- }
866
- return counts;
867
- }
868
- function lockKeyToString(lockKey) {
869
- if (typeof lockKey === "bigint")
870
- return lockKey.toString();
871
- if (!Number.isSafeInteger(lockKey)) {
872
- throw new Error(`sqlx-js.migrate: lockKey must be a safe integer or bigint, got ${lockKey}`);
873
- }
874
- return BigInt(lockKey).toString();
875
- }
876
- export async function acquireMigrateLock(c, lockKey = DEFAULT_MIGRATE_LOCK_KEY, timeoutMs) {
877
- if (timeoutMs !== undefined && !Number.isFinite(timeoutMs)) {
878
- throw new Error(`sqlx-js.migrate: lockTimeoutMs must be a finite number, got ${timeoutMs}`);
879
- }
880
- const key = lockKeyToString(lockKey);
881
- if (timeoutMs === undefined || timeoutMs <= 0) {
882
- await c.simpleQuery(`SELECT pg_advisory_lock(${key})`);
883
- return;
884
- }
885
- const start = Date.now();
886
- let delay = 50;
887
- while (true) {
888
- const r = await c.simpleQuery(`SELECT pg_try_advisory_lock(${key})`);
889
- const got = decodeText(r.rows[0]?.[0] ?? null) === "t";
890
- if (got)
891
- return;
892
- const elapsed = Date.now() - start;
893
- if (elapsed >= timeoutMs) {
894
- throw new Error(`sqlx-js.migrate: failed to acquire advisory lock ${key} within ${timeoutMs}ms`);
895
- }
896
- const remaining = timeoutMs - elapsed;
897
- await new Promise((resolve) => setTimeout(resolve, Math.min(delay, remaining)));
898
- delay = Math.min(delay * 2, 2000);
899
- }
900
- }
901
- export async function releaseMigrateLock(c, lockKey = DEFAULT_MIGRATE_LOCK_KEY) {
902
- const key = lockKeyToString(lockKey);
903
- await c.simpleQuery(`SELECT pg_advisory_unlock(${key})`);
904
- }
905
365
  function safeMigrationName(name) {
906
366
  const safe = name.replace(/[^a-zA-Z0-9_-]+/g, "_").replace(/^[^a-zA-Z0-9]+/, "");
907
367
  if (!safe)
@@ -1,5 +1,6 @@
1
1
  import { PgClient, type ConnConfig, type FieldDescription } from "../pg/wire.js";
2
2
  import { SchemaCache } from "../pg/schema.js";
3
+ import { type QueryCallSite } from "../scan/scanner.js";
3
4
  import { type SqlxJsConfig } from "../config.js";
4
5
  export type PrepareOptions = {
5
6
  root: string;
@@ -7,11 +8,13 @@ export type PrepareOptions = {
7
8
  cacheDir: string;
8
9
  dtsPath: string;
9
10
  check: boolean;
11
+ offline?: boolean;
10
12
  verify?: boolean;
11
13
  json?: boolean;
12
14
  prune?: boolean;
15
+ strictInference?: boolean;
13
16
  };
14
- export type PrepareDiagnosticPhase = "config" | "connect" | "shadow" | "scan" | "describe" | "introspect" | "analyze" | "param-map" | "cache" | "verify";
17
+ export type PrepareDiagnosticPhase = "config" | "connect" | "shadow" | "scan" | "describe" | "result-shape" | "introspect" | "analyze" | "param-map" | "inference" | "cache" | "verify";
15
18
  export declare class PrepareFatalError extends Error {
16
19
  readonly phase: PrepareDiagnosticPhase;
17
20
  readonly file?: string;
@@ -48,6 +51,11 @@ export type PrepareSession = {
48
51
  schema: SchemaCache;
49
52
  userCfg: SqlxJsConfig;
50
53
  };
54
+ export type PrepareIncrementalInput = {
55
+ sites?: QueryCallSite[];
56
+ reuseCacheFps?: ReadonlySet<string>;
57
+ reuseFunctions?: boolean;
58
+ };
51
59
  export declare function openSession(opts: PrepareOptions): Promise<PrepareSession>;
52
60
  type DescribeOutcome = {
53
61
  ok: true;
@@ -62,7 +70,7 @@ export declare function describeAll(cfg: ConnConfig, sessionClient: PgClient, qu
62
70
  fp: string;
63
71
  query: string;
64
72
  }[], concurrency: number): Promise<Map<string, DescribeOutcome>>;
65
- export declare function prepareOnce(opts: PrepareOptions, session: PrepareSession, log?: (msg: string) => void, err?: (msg: string) => void, concurrency?: number): Promise<PrepareResult>;
73
+ export declare function prepareOnce(opts: PrepareOptions, session: PrepareSession, log?: (msg: string) => void, err?: (msg: string) => void, concurrency?: number, input?: PrepareIncrementalInput): Promise<PrepareResult>;
66
74
  export declare function runPrepare(opts: PrepareOptions): Promise<void>;
67
75
  export declare function verifyPrepareArtifacts(opts: PrepareOptions, log?: (msg: string) => void, err?: (msg: string) => void): Promise<{
68
76
  ok: boolean;