@zincapp/znvault-migrate 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/README.md +74 -0
  2. package/dist/adapters/engine-adapter.d.ts +91 -0
  3. package/dist/adapters/engine-adapter.d.ts.map +1 -0
  4. package/dist/adapters/engine-adapter.js +1 -0
  5. package/dist/adapters/engine-adapter.js.map +1 -0
  6. package/dist/adapters/mysql/connection.d.ts +30 -0
  7. package/dist/adapters/mysql/connection.d.ts.map +1 -0
  8. package/dist/adapters/mysql/connection.js +132 -0
  9. package/dist/adapters/mysql/connection.js.map +1 -0
  10. package/dist/adapters/mysql/lock.d.ts +41 -0
  11. package/dist/adapters/mysql/lock.d.ts.map +1 -0
  12. package/dist/adapters/mysql/lock.js +77 -0
  13. package/dist/adapters/mysql/lock.js.map +1 -0
  14. package/dist/adapters/mysql/mysql-adapter.d.ts +11 -0
  15. package/dist/adapters/mysql/mysql-adapter.d.ts.map +1 -0
  16. package/dist/adapters/mysql/mysql-adapter.js +130 -0
  17. package/dist/adapters/mysql/mysql-adapter.js.map +1 -0
  18. package/dist/adapters/mysql/mysql-conn.d.ts +22 -0
  19. package/dist/adapters/mysql/mysql-conn.d.ts.map +1 -0
  20. package/dist/adapters/mysql/mysql-conn.js +1 -0
  21. package/dist/adapters/mysql/mysql-conn.js.map +1 -0
  22. package/dist/adapters/mysql/scaffolding.d.ts +49 -0
  23. package/dist/adapters/mysql/scaffolding.d.ts.map +1 -0
  24. package/dist/adapters/mysql/scaffolding.js +70 -0
  25. package/dist/adapters/mysql/scaffolding.js.map +1 -0
  26. package/dist/adapters/mysql/schema-migrations-repo.d.ts +42 -0
  27. package/dist/adapters/mysql/schema-migrations-repo.d.ts.map +1 -0
  28. package/dist/adapters/mysql/schema-migrations-repo.js +77 -0
  29. package/dist/adapters/mysql/schema-migrations-repo.js.map +1 -0
  30. package/dist/adapters/mysql/sql-splitter.d.ts +15 -0
  31. package/dist/adapters/mysql/sql-splitter.d.ts.map +1 -0
  32. package/dist/adapters/mysql/sql-splitter.js +160 -0
  33. package/dist/adapters/mysql/sql-splitter.js.map +1 -0
  34. package/dist/config.d.ts +35 -0
  35. package/dist/config.d.ts.map +1 -0
  36. package/dist/config.js +36 -0
  37. package/dist/config.js.map +1 -0
  38. package/dist/core/baseline-marker.d.ts +2 -0
  39. package/dist/core/baseline-marker.d.ts.map +1 -0
  40. package/dist/core/baseline-marker.js +10 -0
  41. package/dist/core/baseline-marker.js.map +1 -0
  42. package/dist/core/checksum.d.ts +18 -0
  43. package/dist/core/checksum.d.ts.map +1 -0
  44. package/dist/core/checksum.js +30 -0
  45. package/dist/core/checksum.js.map +1 -0
  46. package/dist/core/migration-files.d.ts +8 -0
  47. package/dist/core/migration-files.d.ts.map +1 -0
  48. package/dist/core/migration-files.js +32 -0
  49. package/dist/core/migration-files.js.map +1 -0
  50. package/dist/core/planner.d.ts +36 -0
  51. package/dist/core/planner.d.ts.map +1 -0
  52. package/dist/core/planner.js +81 -0
  53. package/dist/core/planner.js.map +1 -0
  54. package/dist/core/runner.d.ts +159 -0
  55. package/dist/core/runner.d.ts.map +1 -0
  56. package/dist/core/runner.js +301 -0
  57. package/dist/core/runner.js.map +1 -0
  58. package/dist/core/types.d.ts +24 -0
  59. package/dist/core/types.d.ts.map +1 -0
  60. package/dist/core/types.js +1 -0
  61. package/dist/core/types.js.map +1 -0
  62. package/dist/index.d.ts +13 -0
  63. package/dist/index.d.ts.map +1 -0
  64. package/dist/index.js +13 -0
  65. package/dist/index.js.map +1 -0
  66. package/dist/lease/dynamic-secrets-client.d.ts +43 -0
  67. package/dist/lease/dynamic-secrets-client.d.ts.map +1 -0
  68. package/dist/lease/dynamic-secrets-client.js +110 -0
  69. package/dist/lease/dynamic-secrets-client.js.map +1 -0
  70. package/dist/lease/run-migrations.d.ts +169 -0
  71. package/dist/lease/run-migrations.d.ts.map +1 -0
  72. package/dist/lease/run-migrations.js +302 -0
  73. package/dist/lease/run-migrations.js.map +1 -0
  74. package/package.json +65 -0
@@ -0,0 +1,301 @@
1
+ import { readFileSync, existsSync } from 'node:fs';
2
+ import { isAbsolute, join } from 'node:path';
3
+ import { discover } from './migration-files.js';
4
+ import { readBaselineMarker } from './baseline-marker.js';
5
+ import { canonicalChecksumFile } from './checksum.js';
6
+ import { plan } from './planner.js';
7
+ /**
8
+ * Orchestrates migration discovery, planning, locking, and execution.
9
+ *
10
+ * Ports MigrationRunner.kt + MigrateMain.kt orchestration EXACTLY:
11
+ * - status(): read-only — ensureTable + plan; preflight(version-check only).
12
+ * - run(): write path — preflight(requireWritePrimary=true) → ensureTable (before lock,
13
+ * Kotlin parity) → acquire → seedIfVirgin → plan → reconcile → pending → release.
14
+ *
15
+ * `zn_*` helper procedures (0000_ files) are NOT created by this engine — see the
16
+ * doc comment on run() for details.
17
+ *
18
+ * Engine-agnostic core: the six MySQL-specific collaborators (preflight, lock
19
+ * acquire/isHeld/release, migration-file apply, reconcile, definer-object cleanup,
20
+ * schema_migrations repo) are delegated to an injected `EngineAdapter` operating
21
+ * on a live `Conn`. The runner's own control flow — ordering, guards, error
22
+ * strings — is unchanged from the MySQL-only source.
23
+ */
24
+ export class MigrationRunner {
25
+ adapter;
26
+ conn;
27
+ migrationsDir;
28
+ appliedBy;
29
+ integrityDirs;
30
+ scaffolding;
31
+ repo;
32
+ /**
33
+ * @param adapter The engine adapter — implements the six MySQL-specific
34
+ * collaborators (preflight, lock, migration-file apply,
35
+ * reconcile, definer-object cleanup, schema_migrations repo).
36
+ * @param conn The live connection the adapter's methods operate on.
37
+ * @param migrationsDir The CURRENT phase's migrations directory — the one whose
38
+ * files are classified and applied.
39
+ * @param appliedBy The applied-by identity recorded on each row.
40
+ * @param integrityDirs Additional directories that share this DB's
41
+ * schema_migrations history (the OTHER migration phase's dir,
42
+ * e.g. the pre/ dir when this runner is running post/). Used
43
+ * only to widen the orphan/checksum integrity lookup so a row
44
+ * applied by a sibling phase is not mistaken for a
45
+ * renamed/deleted file. Defaults to none (single-dir configs).
46
+ * @param scaffolding Optional migration-helper scaffolding config for THIS phase.
47
+ * When set, `run()` applies `<migrationsDir>/<filename>` at the
48
+ * start of the phase (after discover(), before seeding/reconcile/
49
+ * pending) and, unconditionally, drops every object the given
50
+ * `leaseUser` defined once the reconcile+pending work concludes
51
+ * (success or failure) — see the ordering note on run(). Undefined
52
+ * (the default) means no scaffolding: byte-identical to the
53
+ * pre-scaffolding runner.
54
+ */
55
+ constructor(adapter, conn, migrationsDir, appliedBy, integrityDirs = [], scaffolding) {
56
+ this.adapter = adapter;
57
+ this.conn = conn;
58
+ this.migrationsDir = migrationsDir;
59
+ this.appliedBy = appliedBy;
60
+ this.integrityDirs = integrityDirs;
61
+ this.scaffolding = scaffolding;
62
+ this.repo = adapter.schemaMigrationsRepo(conn);
63
+ }
64
+ /**
65
+ * Build the union of migration files across this phase's dir and any sibling
66
+ * integrity dirs (pre ∪ post), for the planner's integrity lookup. Missing dirs
67
+ * are skipped defensively (discover() throws on a non-existent path). If two dirs
68
+ * ever declare the same version prefix, the current phase's file wins the lookup.
69
+ */
70
+ allTrackedFiles(phaseFiles) {
71
+ const byVersion = new Map(phaseFiles.map((f) => [f.version, f]));
72
+ for (const dir of this.integrityDirs) {
73
+ if (dir === this.migrationsDir || !existsSync(dir))
74
+ continue;
75
+ for (const f of discover(dir)) {
76
+ if (!byVersion.has(f.version))
77
+ byVersion.set(f.version, f);
78
+ }
79
+ }
80
+ return [...byVersion.values()];
81
+ }
82
+ /**
83
+ * Read-only status: how many migrations are in each state.
84
+ * Does NOT acquire the lock or apply any helpers.
85
+ * Mirrors MigrateMain: preflight(requireWritePrimary=false) for 'status'.
86
+ */
87
+ async status() {
88
+ await this.adapter.preflight(this.conn, false); // version-check only; skip read-only check
89
+ await this.repo.ensureTable();
90
+ const phaseFiles = discover(this.migrationsDir);
91
+ const p = plan(phaseFiles, await this.repo.all(), canonicalChecksumFile, this.allTrackedFiles(phaseFiles));
92
+ return { applied: p.applied.length, reconcile: p.reconcile.length, pending: p.pending.length };
93
+ }
94
+ /**
95
+ * Apply all pending and reconcile migrations.
96
+ *
97
+ * Order (load-bearing — matches Kotlin MigrationRunner.run() exactly):
98
+ * 1. preflight(requireWritePrimary=true) — refuse read-only replicas.
99
+ * 2. ensureTable() BEFORE the lock (the only pre-lock mutation, per Kotlin parity).
100
+ * 3. acquire(db) — GET_LOCK.
101
+ * 3a. scaffolding (if configured) — apply THIS phase's helper objects (migration_utils.sql
102
+ * or equivalent) before any seeding/reconcile/pending work touches the DB.
103
+ * 4. seedBaselineIfVirgin — seed baselined rows when schema_migrations is empty.
104
+ * 5. plan() — classify remaining files.
105
+ * 6. reconcile — asserts-first; re-run body only when unmet or no asserts.
106
+ * 7. pending — claim(success=0) → exec → markSuccess(success=1).
107
+ * 7a. finally (nested, scoped to 6+7): scaffolding cleanup — if configured and the
108
+ * lock is still held, drop every object the lease user defined
109
+ * (dropDefinerObjects), unconditionally — runs whether 6/7 succeeded or threw.
110
+ * A cleanup failure is logged, never rethrown (must not mask a primary error).
111
+ * The lock-held check itself (lockHeld()) is also non-throwing — a dead
112
+ * connection can make the underlying IS_USED_LOCK query reject rather than
113
+ * resolve false, and this guard must not mask a primary error either.
114
+ * 8. finally: release lock; if release was not clean and no primary error, throw tripwire.
115
+ *
116
+ * The `zn_*` helper procedures (0000_ files) are NO LONGER created here. They are
117
+ * now provisioned ahead of the migration phase by vault's routines-apply step,
118
+ * owned by a persistent routines DB account — see the vault-side dynsec-routines-
119
+ * provisioning feature (docs/superpowers/specs/2026-07-01-dynsec-routines-
120
+ * provisioning-design.md). Root cause: DROP+CREATE'ing them here made the
121
+ * ephemeral migrate user their DEFINER, and MySQL 8.4 refuses `DROP USER` for an
122
+ * account referenced as a stored-routine DEFINER (ER 4006) — which broke lease
123
+ * revocation on every migration run. Migrations now only `CALL zn_*`; they never
124
+ * (re)create the procedures themselves.
125
+ */
126
+ async run() {
127
+ await this.adapter.preflight(this.conn, true); // refuse read-only replica
128
+ // ensureTable BEFORE the lock — the CREATE TABLE IF NOT EXISTS is the sole
129
+ // pre-lock mutation. This matches Kotlin to allow a fresh DB to get the table
130
+ // without contention risk (only one runner is expected in normal operation).
131
+ await this.repo.ensureTable();
132
+ await this.adapter.acquireLock(this.conn);
133
+ let primary = null;
134
+ try {
135
+ const files = discover(this.migrationsDir);
136
+ // NOTE: 0000_ helper files are discovered above (discover() does not filter
137
+ // them out) but are intentionally never applied here — plan() excludes them
138
+ // from all buckets (migration-planner.ts) and seedBaselineIfVirgin() skips
139
+ // them too, so they are inert as far as this engine is concerned. See the
140
+ // class doc comment on run() for why per-run creation was removed.
141
+ // Step 3a: Scaffolding — create THIS phase's migration-helper objects (dropped
142
+ // after reconcile+pending, unconditionally — see the finally block below).
143
+ // Applied before any seeding/reconcile/pending work so bodies can CALL them.
144
+ if (this.scaffolding && this.adapter.applyScaffolding) {
145
+ const scaffoldPath = isAbsolute(this.scaffolding.filename)
146
+ ? this.scaffolding.filename
147
+ : join(this.migrationsDir, this.scaffolding.filename);
148
+ await this.adapter.applyScaffolding(this.conn, scaffoldPath);
149
+ }
150
+ // Step 4: Seed baseline rows for a virgin DB (schema_migrations is empty).
151
+ const seeded = await this.seedBaselineIfVirgin(files);
152
+ // Step 5: Plan. Integrity lookup spans this dir ∪ the sibling phase dir(s)
153
+ // (they share one schema_migrations table); classification stays scoped to
154
+ // `files` (this phase only ever applies its own directory).
155
+ const p = plan(files, await this.repo.all(), canonicalChecksumFile, this.allTrackedFiles(files));
156
+ let reconciled = 0;
157
+ let applied = 0;
158
+ try {
159
+ // Step 6: Reconcile — asserts-first; re-run body only when asserts are absent
160
+ // or one failed (indicating the migration was only partially applied).
161
+ for (const f of p.reconcile) {
162
+ await this.requireLockHeld();
163
+ await this.reconcile(f.path);
164
+ reconciled++;
165
+ }
166
+ // Step 7: Pending — claim(success=0) → execute body → markSuccess(success=1).
167
+ // A throw in executeStatements leaves the row at success=0 for later reconcile.
168
+ for (const f of p.pending) {
169
+ await this.requireLockHeld();
170
+ const checksum = canonicalChecksumFile(f.path);
171
+ await this.repo.claim(f.version, checksum, this.appliedBy); // autocommit → durable
172
+ const start = Date.now();
173
+ await this.executeStatements(f.path); // throws → row stays success=0
174
+ await this.requireLockHeld();
175
+ await this.repo.markSuccess(f.version, Date.now() - start);
176
+ applied++;
177
+ }
178
+ }
179
+ finally {
180
+ // Step 7a: Scaffolding cleanup — drop every object the lease user defined,
181
+ // so the ephemeral migrate user is never left a DEFINER (ER-4006). Runs
182
+ // whether the reconcile/pending block above succeeded or threw (unconditional),
183
+ // but ONLY when scaffolding is configured AND the lock is still held (a lost
184
+ // lock means another runner may be concurrently active — do not touch the DB).
185
+ if (this.scaffolding && (await this.lockHeld())) {
186
+ try {
187
+ await this.adapter.dropOwnedObjects(this.conn, this.scaffolding.leaseUser);
188
+ }
189
+ catch (cleanupErr) {
190
+ // Log but do not rethrow — this must never mask a primary phase error.
191
+ // A failed/partial sweep surfaces later via the eventual DROP USER
192
+ // re-hitting ER-4006, which is visible and actionable.
193
+ console.warn(`scaffolding cleanup (dropDefinerObjects) failed for lease user '${this.scaffolding.leaseUser}': ${cleanupErr instanceof Error ? cleanupErr.message : String(cleanupErr)}`);
194
+ }
195
+ }
196
+ }
197
+ return { seeded, reconciled, applied, pendingRemaining: 0 };
198
+ }
199
+ catch (e) {
200
+ primary = e;
201
+ throw e;
202
+ }
203
+ finally {
204
+ // release() never throws — it must not mask the primary error.
205
+ // A non-clean release is the lost-lock tripwire: surface it only when
206
+ // there is no primary error to preserve.
207
+ const released = await this.adapter.releaseLock(this.conn);
208
+ if (!released && primary === null) {
209
+ // eslint-disable-next-line no-unsafe-finally
210
+ throw new Error('Lock was not held at release — a concurrent runner may have run. Re-run status and verify schema_migrations.');
211
+ }
212
+ }
213
+ }
214
+ /**
215
+ * Abort immediately if this session no longer holds the GET_LOCK.
216
+ * A killed session or proxy reconnect would silently drop the lock server-side.
217
+ */
218
+ async requireLockHeld() {
219
+ if (!(await this.adapter.lockHeld(this.conn))) {
220
+ throw new Error('Lost the migration lock mid-run (session killed or reconnected). Aborting to avoid concurrent DDL.');
221
+ }
222
+ }
223
+ /**
224
+ * Non-throwing counterpart to requireLockHeld() — used by cleanup paths (e.g.
225
+ * scaffolding's finally block) that must NOT touch the DB once the lock is lost,
226
+ * but also must not themselves throw while already unwinding another error.
227
+ *
228
+ * `lock.isHeld()` runs `SELECT IS_USED_LOCK(...)`, which can itself REJECT on a
229
+ * dead connection (killed session / proxy reconnect — exactly the scenario this
230
+ * guard exists for), not just resolve to false. Since callers await this from a
231
+ * `finally` block, an unswallowed rejection here would replace/mask whatever
232
+ * primary error is already in flight (a throwing `finally` overrides the pending
233
+ * exception). Treat any error the same as "lock not held": skip the guarded
234
+ * cleanup rather than risk touching a possibly-dead connection or masking the
235
+ * real error.
236
+ */
237
+ async lockHeld() {
238
+ try {
239
+ return await this.adapter.lockHeld(this.conn);
240
+ }
241
+ catch (e) {
242
+ console.warn(`lock.isHeld() failed while checking scaffolding cleanup eligibility (treating as lock-not-held): ${e instanceof Error ? e.message : String(e)}`);
243
+ return false;
244
+ }
245
+ }
246
+ /**
247
+ * Execute every SQL statement in a file (text protocol, one at a time).
248
+ * A failed CALL zn_assert_* signals SIGNAL '45000', which propagates as an
249
+ * Error and leaves any pending claim row at success=0.
250
+ *
251
+ * The split+per-statement-execute logic lives on the adapter (`applyMigrationFile`)
252
+ * — core only reads the file and hands the SQL text over.
253
+ */
254
+ async executeStatements(path) {
255
+ const sql = readFileSync(path, 'utf8');
256
+ await this.adapter.applyMigrationFile(this.conn, sql);
257
+ }
258
+ /**
259
+ * Reconcile a success=0 row per spec step 7a.
260
+ *
261
+ * Run the file's trailing CALL zn_assert_* postconditions FIRST:
262
+ * - If they exist and all pass → the migration is already fully applied;
263
+ * markSuccess WITHOUT re-running the body (avoids double-apply hazard for
264
+ * non-idempotent bodies).
265
+ * - If there are no asserts, OR any assert throws → re-run the ENTIRE
266
+ * (idempotent) body, then markSuccess.
267
+ *
268
+ * The asserts-first logic (and the requireLockHeld() guard before markSuccess)
269
+ * lives on the adapter (`reconcile`) — core only reads the file, derives the
270
+ * version, and delegates.
271
+ */
272
+ async reconcile(path) {
273
+ const fileSql = readFileSync(path, 'utf8');
274
+ const version = path.split('/').pop();
275
+ await this.adapter.reconcile(this.conn, version, fileSql, this.repo);
276
+ }
277
+ /**
278
+ * Seed baselined rows for a virgin DB (schema_migrations has no rows yet).
279
+ *
280
+ * Reads the BASELINE_MARKER from baseline/00-baseline-schema.sql and seeds
281
+ * a baselined row for every non-0000_ file whose prefix <= the marker.
282
+ * Returns the number of rows seeded.
283
+ */
284
+ async seedBaselineIfVirgin(files) {
285
+ if ((await this.repo.all()).length > 0)
286
+ return 0;
287
+ const marker = readBaselineMarker(join(this.migrationsDir, 'baseline', '00-baseline-schema.sql'));
288
+ if (!marker)
289
+ return 0; // no baseline file (unit/throwaway DB) → nothing to seed
290
+ let n = 0;
291
+ for (const f of files) {
292
+ if (f.version.startsWith('0000_'))
293
+ continue; // helpers are never seeded
294
+ if (f.prefix <= marker) {
295
+ await this.repo.seedBaseline(f.version, canonicalChecksumFile(f.path), this.appliedBy);
296
+ n++;
297
+ }
298
+ }
299
+ return n;
300
+ }
301
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runner.js","sourceRoot":"","sources":["../../src/core/runner.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ export interface MigrationFile {
2
+ version: string;
3
+ prefix: string;
4
+ path: string;
5
+ }
6
+ export interface MigrationRow {
7
+ version: string;
8
+ checksum: string;
9
+ checksumAlgo: string;
10
+ success: boolean;
11
+ baselined: boolean;
12
+ }
13
+ export interface MigrationPlan {
14
+ applied: MigrationFile[];
15
+ reconcile: MigrationFile[];
16
+ pending: MigrationFile[];
17
+ }
18
+ export interface RunResult {
19
+ seeded: number;
20
+ reconciled: number;
21
+ applied: number;
22
+ pendingRemaining: number;
23
+ }
24
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,aAAa;IAAG,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;CAAE;AAEjF,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,SAAS,EAAE,aAAa,EAAE,CAAC;IAC3B,OAAO,EAAE,aAAa,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,gBAAgB,EAAE,MAAM,CAAC;CAC1B"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,13 @@
1
+ export type { MigrationConfig } from './config.js';
2
+ export { validateMigrationConfig } from './config.js';
3
+ export { MigrationRunner } from './core/runner.js';
4
+ export type { MigrationFile, MigrationRow, RunResult, MigrationPlan } from './core/types.js';
5
+ export type { EngineAdapter, Conn, SchemaMigrationsRepo, EngineCredentials } from './adapters/engine-adapter.js';
6
+ export { mysqlAdapter } from './adapters/mysql/mysql-adapter.js';
7
+ export { ChecksumMismatchError, OrphanTrackedRowError } from './core/planner.js';
8
+ export { DuplicatePrefixError } from './core/migration-files.js';
9
+ export { runMigrations, defaultDeps } from './lease/run-migrations.js';
10
+ export type { RunMigrationsOpts, RunMigrationsDeps } from './lease/run-migrations.js';
11
+ export type { Lease } from './lease/dynamic-secrets-client.js';
12
+ export { makeDynamicSecretsClient } from './lease/dynamic-secrets-client.js';
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAEtD,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACnD,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAE7F,YAAY,EAAE,aAAa,EAAE,IAAI,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACjH,OAAO,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AAGjE,OAAO,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AACjF,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAIjE,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AACvE,YAAY,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AACtF,YAAY,EAAE,KAAK,EAAE,MAAM,mCAAmC,CAAC;AAC/D,OAAO,EAAE,wBAAwB,EAAE,MAAM,mCAAmC,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,13 @@
1
+ // Path: znvault-migrate/src/index.ts
2
+ // Public API surface for @zincapp/znvault-migrate.
3
+ // Consumed by znvault-plugin-payara (Phase 2) and znvault-cli (Phase 3).
4
+ export { validateMigrationConfig } from './config.js';
5
+ export { MigrationRunner } from './core/runner.js';
6
+ export { mysqlAdapter } from './adapters/mysql/mysql-adapter.js';
7
+ // Error classes surfaced by the core so consumers can catch/discriminate them.
8
+ export { ChecksumMismatchError, OrphanTrackedRowError } from './core/planner.js';
9
+ export { DuplicatePrefixError } from './core/migration-files.js';
10
+ // Lease layer (Task 7): mint a dynamic-secrets lease → open a connection via
11
+ // the selected engine adapter → run the migration engine → revoke the lease.
12
+ export { runMigrations, defaultDeps } from './lease/run-migrations.js';
13
+ export { makeDynamicSecretsClient } from './lease/dynamic-secrets-client.js';
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,qCAAqC;AACrC,mDAAmD;AACnD,yEAAyE;AAGzE,OAAO,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAEtD,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAInD,OAAO,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AAEjE,+EAA+E;AAC/E,OAAO,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AACjF,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAEjE,6EAA6E;AAC7E,6EAA6E;AAC7E,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAGvE,OAAO,EAAE,wBAAwB,EAAE,MAAM,mCAAmC,CAAC"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Dynamic-secrets REST client for ZN-Vault.
3
+ *
4
+ * These are raw async functions hitting the Vault REST API — NOT the znvault-cli
5
+ * command handlers (which call inquirer.prompt / process.exit and are unusable as
6
+ * lifecycle primitives).
7
+ *
8
+ * Key design (Codex F6.1): a 404 / 410 / non-ACTIVE-lease response on revokeCredential
9
+ * is treated as SUCCESS — the lease is already gone. This is critical because a
10
+ * timed-out-then-retried revoke hits a now-non-ACTIVE lease on the 2nd attempt and
11
+ * must not be retried into an error.
12
+ */
13
+ export interface Lease {
14
+ leaseId: string;
15
+ username: string;
16
+ password: string;
17
+ /** MySQL hostname returned by the Vault dynamic-secrets connection. */
18
+ host: string;
19
+ /** MySQL port returned by the Vault dynamic-secrets connection. */
20
+ port: number;
21
+ /** MySQL database name returned by the Vault dynamic-secrets connection (may be undefined if the connection doesn't pin a database). */
22
+ database?: string;
23
+ }
24
+ export interface VaultHttp {
25
+ post(path: string, body: unknown): Promise<{
26
+ status: number;
27
+ body: any;
28
+ }>;
29
+ }
30
+ /**
31
+ * Create a dynamic-secrets client bound to the given HTTP transport.
32
+ *
33
+ * @param http - The vault HTTP client (e.g. the plugin's existing VaultHttp).
34
+ */
35
+ export declare function makeDynamicSecretsClient(http: VaultHttp): {
36
+ issueCredential(roleId: string, opts: {
37
+ ttlSeconds: number;
38
+ }): Promise<Lease>;
39
+ revokeCredential(leaseId: string, opts: {
40
+ reason: string;
41
+ }): Promise<void>;
42
+ };
43
+ //# sourceMappingURL=dynamic-secrets-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dynamic-secrets-client.d.ts","sourceRoot":"","sources":["../../src/lease/dynamic-secrets-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,MAAM,WAAW,KAAK;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,uEAAuE;IACvE,IAAI,EAAE,MAAM,CAAC;IACb,mEAAmE;IACnE,IAAI,EAAE,MAAM,CAAC;IACb,wIAAwI;IACxI,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,GAAG,CAAA;KAAE,CAAC,CAAC;CAC3E;AAmCD;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,SAAS,GAAG;IACzD,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,UAAU,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;IAC9E,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5E,CAkEA"}
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Dynamic-secrets REST client for ZN-Vault.
3
+ *
4
+ * These are raw async functions hitting the Vault REST API — NOT the znvault-cli
5
+ * command handlers (which call inquirer.prompt / process.exit and are unusable as
6
+ * lifecycle primitives).
7
+ *
8
+ * Key design (Codex F6.1): a 404 / 410 / non-ACTIVE-lease response on revokeCredential
9
+ * is treated as SUCCESS — the lease is already gone. This is critical because a
10
+ * timed-out-then-retried revoke hits a now-non-ACTIVE lease on the 2nd attempt and
11
+ * must not be retried into an error.
12
+ */
13
+ /**
14
+ * Classify a revoke error as "already gone" (the lease no longer exists).
15
+ *
16
+ * A lease is already gone when:
17
+ * - The error carries HTTP status 404 (not found) or 410 (gone).
18
+ * - The error carries a response body indicating a non-ACTIVE or not-found lease state
19
+ * (e.g. status: 'EXPIRED', 'REVOKED', 'NOT_FOUND').
20
+ *
21
+ * Any other error (500, network failure, etc.) is NOT already-gone.
22
+ */
23
+ function isAlreadyGone(e) {
24
+ if (e == null || typeof e !== 'object')
25
+ return false;
26
+ const obj = e;
27
+ // 404 / 410 by HTTP status
28
+ const status = obj['status'];
29
+ if (status === 404 || status === 410)
30
+ return true;
31
+ // Response body carries a non-ACTIVE lease status
32
+ const body = obj['body'];
33
+ if (body != null && typeof body === 'object') {
34
+ const bodyStatus = body['status'];
35
+ if (typeof bodyStatus === 'string' &&
36
+ bodyStatus !== 'ACTIVE') {
37
+ return true;
38
+ }
39
+ }
40
+ return false;
41
+ }
42
+ /**
43
+ * Create a dynamic-secrets client bound to the given HTTP transport.
44
+ *
45
+ * @param http - The vault HTTP client (e.g. the plugin's existing VaultHttp).
46
+ */
47
+ export function makeDynamicSecretsClient(http) {
48
+ return {
49
+ /**
50
+ * Issue a new dynamic-secrets credential for the given role.
51
+ *
52
+ * POST /v1/dynamic-secrets/roles/:roleId/credentials
53
+ * → { leaseId, username, password, host, port, database? }
54
+ *
55
+ * CRITICAL: validates that host and port are present in the response.
56
+ * If missing, the just-minted lease is revoked (best-effort) before
57
+ * throwing so the orphaned credential is not left to expire passively.
58
+ * Mirrors the znvault-cli mysql broker pattern (spec F2).
59
+ */
60
+ async issueCredential(roleId, opts) {
61
+ const path = `/v1/dynamic-secrets/roles/${roleId}/credentials`;
62
+ const res = await http.post(path, { ttlSeconds: opts.ttlSeconds });
63
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
64
+ const body = res.body;
65
+ // Validate that the server returned host and port (spec F2 — no --host fallback).
66
+ if (!body.host || body.port === undefined) {
67
+ // Best-effort revoke of the just-minted partial lease before throwing.
68
+ // Wrap in try/catch so a revoke failure does not mask the validation error.
69
+ try {
70
+ const revokePath = `/v1/dynamic-secrets/leases/${body.leaseId}/revoke`;
71
+ await http.post(revokePath, { reason: 'incomplete credential' });
72
+ }
73
+ catch {
74
+ // Intentionally swallowed — cleanup job + TTL will expire the lease.
75
+ }
76
+ throw new Error(`Vault did not return host/port in the credential for role '${roleId}'. ` +
77
+ `Please upgrade vault to a version that returns host/port in dynamic-secret credentials.`);
78
+ }
79
+ return {
80
+ leaseId: body.leaseId,
81
+ username: body.username,
82
+ password: body.password,
83
+ host: body.host,
84
+ port: body.port,
85
+ database: body.database,
86
+ };
87
+ },
88
+ /**
89
+ * Revoke an existing dynamic-secrets credential lease.
90
+ *
91
+ * POST /v1/dynamic-secrets/leases/:leaseId/revoke
92
+ *
93
+ * CRITICAL (Codex F6.1): a 404 / 410 / non-ACTIVE-lease error is treated as
94
+ * SUCCESS — the lease is already gone. Resolve, do NOT throw.
95
+ */
96
+ async revokeCredential(leaseId, opts) {
97
+ const path = `/v1/dynamic-secrets/leases/${leaseId}/revoke`;
98
+ try {
99
+ await http.post(path, { reason: opts.reason });
100
+ }
101
+ catch (e) {
102
+ if (isAlreadyGone(e)) {
103
+ // Lease is already gone — treat as success per spec F6.1.
104
+ return;
105
+ }
106
+ throw e;
107
+ }
108
+ },
109
+ };
110
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dynamic-secrets-client.js","sourceRoot":"","sources":["../../src/lease/dynamic-secrets-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAkBH;;;;;;;;;GASG;AACH,SAAS,aAAa,CAAC,CAAU;IAC/B,IAAI,CAAC,IAAI,IAAI,IAAI,OAAO,CAAC,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IACrD,MAAM,GAAG,GAAG,CAA4B,CAAC;IAEzC,2BAA2B;IAC3B,MAAM,MAAM,GAAG,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC7B,IAAI,MAAM,KAAK,GAAG,IAAI,MAAM,KAAK,GAAG;QAAE,OAAO,IAAI,CAAC;IAElD,kDAAkD;IAClD,MAAM,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC;IACzB,IAAI,IAAI,IAAI,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC7C,MAAM,UAAU,GAAI,IAAgC,CAAC,QAAQ,CAAC,CAAC;QAC/D,IACE,OAAO,UAAU,KAAK,QAAQ;YAC9B,UAAU,KAAK,QAAQ,EACvB,CAAC;YACD,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,wBAAwB,CAAC,IAAe;IAItD,OAAO;QACL;;;;;;;;;;WAUG;QACH,KAAK,CAAC,eAAe,CAAC,MAAc,EAAE,IAA4B;YAChE,MAAM,IAAI,GAAG,6BAA6B,MAAM,cAAc,CAAC;YAC/D,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC;YACnE,8DAA8D;YAC9D,MAAM,IAAI,GAAG,GAAG,CAAC,IAAW,CAAC;YAE7B,kFAAkF;YAClF,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;gBAC1C,uEAAuE;gBACvE,4EAA4E;gBAC5E,IAAI,CAAC;oBACH,MAAM,UAAU,GAAG,8BAA8B,IAAI,CAAC,OAAiB,SAAS,CAAC;oBACjF,MAAM,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,MAAM,EAAE,uBAAuB,EAAE,CAAC,CAAC;gBACnE,CAAC;gBAAC,MAAM,CAAC;oBACP,qEAAqE;gBACvE,CAAC;gBACD,MAAM,IAAI,KAAK,CACb,8DAA8D,MAAM,KAAK;oBACzE,yFAAyF,CAC1F,CAAC;YACJ,CAAC;YAED,OAAO;gBACL,OAAO,EAAE,IAAI,CAAC,OAAiB;gBAC/B,QAAQ,EAAE,IAAI,CAAC,QAAkB;gBACjC,QAAQ,EAAE,IAAI,CAAC,QAAkB;gBACjC,IAAI,EAAE,IAAI,CAAC,IAAc;gBACzB,IAAI,EAAE,IAAI,CAAC,IAAc;gBACzB,QAAQ,EAAE,IAAI,CAAC,QAA8B;aAC9C,CAAC;QACJ,CAAC;QAED;;;;;;;WAOG;QACH,KAAK,CAAC,gBAAgB,CAAC,OAAe,EAAE,IAAwB;YAC9D,MAAM,IAAI,GAAG,8BAA8B,OAAO,SAAS,CAAC;YAC5D,IAAI,CAAC;gBACH,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;YACjD,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,IAAI,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC;oBACrB,0DAA0D;oBAC1D,OAAO;gBACT,CAAC;gBACD,MAAM,CAAC,CAAC;YACV,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC"}