@breeztech/breez-sdk-spark 0.14.0 → 0.15.1

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 (37) hide show
  1. package/breez-sdk-spark.tgz +0 -0
  2. package/bundler/breez_sdk_spark_wasm.d.ts +114 -40
  3. package/bundler/breez_sdk_spark_wasm.js +1 -1
  4. package/bundler/breez_sdk_spark_wasm_bg.js +118 -104
  5. package/bundler/breez_sdk_spark_wasm_bg.wasm +0 -0
  6. package/bundler/breez_sdk_spark_wasm_bg.wasm.d.ts +12 -11
  7. package/deno/breez_sdk_spark_wasm.d.ts +114 -40
  8. package/deno/breez_sdk_spark_wasm.js +118 -104
  9. package/deno/breez_sdk_spark_wasm_bg.wasm +0 -0
  10. package/deno/breez_sdk_spark_wasm_bg.wasm.d.ts +12 -11
  11. package/nodejs/breez_sdk_spark_wasm.d.ts +114 -40
  12. package/nodejs/breez_sdk_spark_wasm.js +121 -106
  13. package/nodejs/breez_sdk_spark_wasm_bg.wasm +0 -0
  14. package/nodejs/breez_sdk_spark_wasm_bg.wasm.d.ts +12 -11
  15. package/nodejs/index.mjs +3 -2
  16. package/nodejs/mysql-session-manager/index.cjs +26 -8
  17. package/nodejs/mysql-session-manager/migrations.cjs +40 -3
  18. package/nodejs/mysql-storage/index.cjs +67 -48
  19. package/nodejs/mysql-storage/migrations.cjs +220 -85
  20. package/nodejs/mysql-token-store/index.cjs +133 -68
  21. package/nodejs/mysql-token-store/migrations.cjs +309 -80
  22. package/nodejs/mysql-tree-store/index.cjs +76 -41
  23. package/nodejs/mysql-tree-store/migrations.cjs +254 -71
  24. package/nodejs/postgres-session-manager/index.cjs +27 -9
  25. package/nodejs/postgres-session-manager/migrations.cjs +45 -6
  26. package/nodejs/postgres-storage/index.cjs +81 -62
  27. package/nodejs/postgres-storage/migrations.cjs +207 -79
  28. package/nodejs/postgres-token-store/index.cjs +111 -67
  29. package/nodejs/postgres-token-store/migrations.cjs +153 -61
  30. package/nodejs/postgres-tree-store/index.cjs +60 -42
  31. package/nodejs/postgres-tree-store/migrations.cjs +130 -46
  32. package/package.json +1 -1
  33. package/ssr/index.js +14 -9
  34. package/web/breez_sdk_spark_wasm.d.ts +126 -51
  35. package/web/breez_sdk_spark_wasm.js +118 -104
  36. package/web/breez_sdk_spark_wasm_bg.wasm +0 -0
  37. package/web/breez_sdk_spark_wasm_bg.wasm.d.ts +12 -11
@@ -4,17 +4,19 @@
4
4
 
5
5
  const { TokenStoreError } = require("./errors.cjs");
6
6
 
7
- const TOKEN_MIGRATIONS_TABLE = "token_schema_migrations";
7
+ const TOKEN_MIGRATIONS_TABLE = "brz_token_schema_migrations";
8
8
  const MIGRATION_LOCK_NAME = "breez_mysql_token_store_migration_lock";
9
9
  const MIGRATION_LOCK_TIMEOUT = 60;
10
10
 
11
11
  /**
12
12
  * Runs a single migration step. Plain strings are run as-is; tagged objects
13
- * (`{ op: 'dropPrimaryKey', table }`) are guarded against partial-apply replay
14
- * by checking `information_schema` first. MySQL DDL implicitly commits, so
15
- * if the migration crashes between two DDL statements the version row never
16
- * gets recorded and on retry, an unguarded DROP PRIMARY KEY would fail
17
- * (`ER_CANT_DROP_FIELD_OR_KEY`) because the PK is already gone.
13
+ * (`{ op: 'dropPrimaryKey', table }`, `{ op: 'dropForeignKey', table, name }`)
14
+ * are guarded against partial-apply replay (and against the `Disabled`
15
+ * foreign-key mode where the FK was never created) by checking
16
+ * `information_schema` first. MySQL DDL implicitly commits, so if the
17
+ * migration crashes between two DDL statements the version row never gets
18
+ * recorded — and on retry, an unguarded DROP would fail because the
19
+ * constraint is already gone.
18
20
  */
19
21
  async function runMigrationStep(conn, step) {
20
22
  if (typeof step === "string") {
@@ -34,12 +36,45 @@ async function runMigrationStep(conn, step) {
34
36
  }
35
37
  return;
36
38
  }
39
+ if (step.op === "dropForeignKey") {
40
+ const [rows] = await conn.query(
41
+ `SELECT COUNT(*) AS c FROM information_schema.table_constraints
42
+ WHERE table_schema = DATABASE()
43
+ AND table_name = ?
44
+ AND constraint_type = 'FOREIGN KEY'
45
+ AND constraint_name = ?`,
46
+ [step.table, step.name]
47
+ );
48
+ if (rows[0].c > 0) {
49
+ await conn.query(
50
+ `ALTER TABLE \`${step.table}\` DROP FOREIGN KEY \`${step.name}\``
51
+ );
52
+ }
53
+ return;
54
+ }
55
+ if (step.op === "addForeignKey") {
56
+ const [rows] = await conn.query(
57
+ `SELECT COUNT(*) AS c FROM information_schema.table_constraints
58
+ WHERE table_schema = DATABASE()
59
+ AND table_name = ?
60
+ AND constraint_type = 'FOREIGN KEY'
61
+ AND constraint_name = ?`,
62
+ [step.table, step.name]
63
+ );
64
+ if (rows[0].c === 0) {
65
+ await conn.query(
66
+ `ALTER TABLE \`${step.table}\` ADD CONSTRAINT \`${step.name}\` ${step.definition}`
67
+ );
68
+ }
69
+ return;
70
+ }
37
71
  throw new Error(`Unknown migration step op: ${JSON.stringify(step)}`);
38
72
  }
39
73
 
40
74
  class MysqlTokenStoreMigrationManager {
41
- constructor(logger = null) {
75
+ constructor(logger = null, foreignKeyMode = "Enforced") {
42
76
  this.logger = logger;
77
+ this.foreignKeyMode = foreignKeyMode;
43
78
  }
44
79
 
45
80
  /**
@@ -67,6 +102,8 @@ class MysqlTokenStoreMigrationManager {
67
102
  }
68
103
 
69
104
  try {
105
+ await this._applySchemaRenames(conn);
106
+
70
107
  await conn.query("START TRANSACTION");
71
108
 
72
109
  await conn.query(`
@@ -123,15 +160,139 @@ class MysqlTokenStoreMigrationManager {
123
160
  * the bytes come from a typed secp256k1 pubkey (`[0-9a-f]{66}` after hex
124
161
  * encoding) — not user-controlled input.
125
162
  */
163
+ /**
164
+ * Pre-prefix rename. Canary-gated on the legacy `token_schema_migrations`
165
+ * table.
166
+ * @param {import('mysql2/promise').PoolConnection} conn
167
+ */
168
+ async _applySchemaRenames(conn) {
169
+ if (!(await _mysqlTableExists(conn, "token_schema_migrations"))) {
170
+ return;
171
+ }
172
+
173
+ const tableRenames = [
174
+ ["token_metadata", "brz_token_metadata"],
175
+ ["token_reservations", "brz_token_reservations"],
176
+ ["token_outputs", "brz_token_outputs"],
177
+ ["token_spent_outputs", "brz_token_spent_outputs"],
178
+ ["token_swap_status", "brz_token_swap_status"],
179
+ ];
180
+ for (const [oldName, newName] of tableRenames) {
181
+ if (
182
+ (await _mysqlTableExists(conn, oldName)) &&
183
+ !(await _mysqlTableExists(conn, newName))
184
+ ) {
185
+ await conn.query(`RENAME TABLE \`${oldName}\` TO \`${newName}\``);
186
+ }
187
+ }
188
+
189
+ const indexRenames = [
190
+ [
191
+ "brz_token_metadata",
192
+ "idx_token_metadata_user_issuer_pk",
193
+ "brz_idx_token_metadata_user_issuer_pk",
194
+ ],
195
+ [
196
+ "brz_token_outputs",
197
+ "idx_token_outputs_user_identifier",
198
+ "brz_idx_token_outputs_user_identifier",
199
+ ],
200
+ [
201
+ "brz_token_outputs",
202
+ "idx_token_outputs_user_reservation",
203
+ "brz_idx_token_outputs_user_reservation",
204
+ ],
205
+ // Pre-multi-tenant indexes (dropped by the multi-tenant migration).
206
+ [
207
+ "brz_token_metadata",
208
+ "idx_token_metadata_issuer_pk",
209
+ "brz_idx_token_metadata_issuer_pk",
210
+ ],
211
+ [
212
+ "brz_token_outputs",
213
+ "idx_token_outputs_identifier",
214
+ "brz_idx_token_outputs_identifier",
215
+ ],
216
+ [
217
+ "brz_token_outputs",
218
+ "idx_token_outputs_reservation",
219
+ "brz_idx_token_outputs_reservation",
220
+ ],
221
+ ];
222
+ for (const [table, oldName, newName] of indexRenames) {
223
+ if (
224
+ (await _mysqlIndexExists(conn, table, oldName)) &&
225
+ !(await _mysqlIndexExists(conn, table, newName))
226
+ ) {
227
+ await conn.query(
228
+ `ALTER TABLE \`${table}\` RENAME INDEX \`${oldName}\` TO \`${newName}\``
229
+ );
230
+ }
231
+ }
232
+
233
+ const fkRenames = [
234
+ {
235
+ table: "brz_token_outputs",
236
+ oldName: "fk_token_outputs_metadata_user",
237
+ newName: "brz_fk_token_outputs_metadata_user",
238
+ definition:
239
+ "FOREIGN KEY (user_id, token_identifier) REFERENCES `brz_token_metadata`(user_id, identifier)",
240
+ },
241
+ {
242
+ table: "brz_token_outputs",
243
+ oldName: "fk_token_outputs_reservation_user",
244
+ newName: "brz_fk_token_outputs_reservation_user",
245
+ definition:
246
+ "FOREIGN KEY (user_id, reservation_id) REFERENCES `brz_token_reservations`(user_id, id)",
247
+ },
248
+ // Pre-multi-tenant FKs (single-column). Rename so the post-tenant
249
+ // migration's drop-foreign-key steps find them.
250
+ {
251
+ table: "brz_token_outputs",
252
+ oldName: "fk_token_outputs_metadata",
253
+ newName: "brz_fk_token_outputs_metadata",
254
+ definition:
255
+ "FOREIGN KEY (token_identifier) REFERENCES `brz_token_metadata`(identifier)",
256
+ },
257
+ {
258
+ table: "brz_token_outputs",
259
+ oldName: "fk_token_outputs_reservation",
260
+ newName: "brz_fk_token_outputs_reservation",
261
+ definition:
262
+ "FOREIGN KEY (reservation_id) REFERENCES `brz_token_reservations`(id) ON DELETE SET NULL",
263
+ },
264
+ ];
265
+ for (const fk of fkRenames) {
266
+ if (await _mysqlForeignKeyExists(conn, fk.table, fk.newName)) {
267
+ continue;
268
+ }
269
+ if (!(await _mysqlForeignKeyExists(conn, fk.table, fk.oldName))) {
270
+ continue;
271
+ }
272
+ await conn.query(
273
+ `ALTER TABLE \`${fk.table}\`` +
274
+ ` DROP FOREIGN KEY \`${fk.oldName}\`,` +
275
+ ` ADD CONSTRAINT \`${fk.newName}\` ${fk.definition}`
276
+ );
277
+ }
278
+
279
+ if (
280
+ (await _mysqlTableExists(conn, "token_schema_migrations")) &&
281
+ !(await _mysqlTableExists(conn, TOKEN_MIGRATIONS_TABLE))
282
+ ) {
283
+ await conn.query(
284
+ `RENAME TABLE \`token_schema_migrations\` TO \`${TOKEN_MIGRATIONS_TABLE}\``
285
+ );
286
+ }
287
+ }
288
+
126
289
  _getMigrations(identity) {
127
290
  const idHex = Buffer.from(identity).toString("hex");
128
291
  const idLit = `UNHEX('${idHex}')`;
292
+ const foreignKeyModeEnforced = this.foreignKeyMode === "Enforced";
129
293
 
130
- return [
131
- {
132
- name: "Create token store tables",
133
- sql: [
134
- `CREATE TABLE IF NOT EXISTS token_metadata (
294
+ const initialSql = [
295
+ `CREATE TABLE IF NOT EXISTS brz_token_metadata (
135
296
  identifier VARCHAR(255) NOT NULL PRIMARY KEY,
136
297
  issuer_public_key VARCHAR(255) NOT NULL,
137
298
  name VARCHAR(255) NOT NULL,
@@ -141,14 +302,14 @@ class MysqlTokenStoreMigrationManager {
141
302
  is_freezable TINYINT(1) NOT NULL,
142
303
  creation_entity_public_key VARCHAR(255) NULL
143
304
  )`,
144
- `CREATE INDEX idx_token_metadata_issuer_pk
145
- ON token_metadata (issuer_public_key)`,
146
- `CREATE TABLE IF NOT EXISTS token_reservations (
305
+ `CREATE INDEX brz_idx_token_metadata_issuer_pk
306
+ ON brz_token_metadata (issuer_public_key)`,
307
+ `CREATE TABLE IF NOT EXISTS brz_token_reservations (
147
308
  id VARCHAR(255) NOT NULL PRIMARY KEY,
148
309
  purpose VARCHAR(64) NOT NULL,
149
310
  created_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6)
150
311
  )`,
151
- `CREATE TABLE IF NOT EXISTS token_outputs (
312
+ `CREATE TABLE IF NOT EXISTS brz_token_outputs (
152
313
  id VARCHAR(255) NOT NULL PRIMARY KEY,
153
314
  token_identifier VARCHAR(255) NOT NULL,
154
315
  owner_public_key VARCHAR(255) NOT NULL,
@@ -160,33 +321,52 @@ class MysqlTokenStoreMigrationManager {
160
321
  prev_tx_hash VARCHAR(255) NOT NULL,
161
322
  prev_tx_vout INT NOT NULL,
162
323
  reservation_id VARCHAR(255) NULL,
163
- added_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
164
- CONSTRAINT fk_token_outputs_metadata FOREIGN KEY (token_identifier)
165
- REFERENCES token_metadata(identifier),
166
- CONSTRAINT fk_token_outputs_reservation FOREIGN KEY (reservation_id)
167
- REFERENCES token_reservations(id) ON DELETE SET NULL
324
+ added_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6)
168
325
  )`,
169
- `CREATE INDEX idx_token_outputs_identifier
170
- ON token_outputs (token_identifier)`,
171
- `CREATE INDEX idx_token_outputs_reservation
172
- ON token_outputs (reservation_id)`,
173
- `CREATE TABLE IF NOT EXISTS token_spent_outputs (
326
+ `CREATE INDEX brz_idx_token_outputs_identifier
327
+ ON brz_token_outputs (token_identifier)`,
328
+ `CREATE INDEX brz_idx_token_outputs_reservation
329
+ ON brz_token_outputs (reservation_id)`,
330
+ `CREATE TABLE IF NOT EXISTS brz_token_spent_outputs (
174
331
  output_id VARCHAR(255) NOT NULL PRIMARY KEY,
175
332
  spent_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6)
176
333
  )`,
177
- `CREATE TABLE IF NOT EXISTS token_swap_status (
334
+ `CREATE TABLE IF NOT EXISTS brz_token_swap_status (
178
335
  id INT NOT NULL PRIMARY KEY DEFAULT 1,
179
336
  last_completed_at DATETIME(6) NULL,
180
337
  CHECK (id = 1)
181
338
  )`,
182
- `INSERT INTO token_swap_status (id) VALUES (1)
339
+ ];
340
+ if (foreignKeyModeEnforced) {
341
+ initialSql.push(
342
+ {
343
+ op: "addForeignKey",
344
+ table: "brz_token_outputs",
345
+ name: "brz_fk_token_outputs_metadata",
346
+ definition: `FOREIGN KEY (token_identifier) REFERENCES brz_token_metadata(identifier)`,
347
+ },
348
+ {
349
+ op: "addForeignKey",
350
+ table: "brz_token_outputs",
351
+ name: "brz_fk_token_outputs_reservation",
352
+ definition: `FOREIGN KEY (reservation_id) REFERENCES brz_token_reservations(id) ON DELETE SET NULL`,
353
+ }
354
+ );
355
+ }
356
+
357
+ return [
358
+ {
359
+ name: "Create token store tables",
360
+ sql: [
361
+ ...initialSql,
362
+ `INSERT INTO brz_token_swap_status (id) VALUES (1)
183
363
  ON DUPLICATE KEY UPDATE id = id`,
184
364
  ],
185
365
  },
186
366
  {
187
367
  // Mirrors Rust migration 2 in spark-mysql/src/token_store.rs and the
188
368
  // postgres equivalent. Adds user_id to every token-store table
189
- // (including token_metadata — per-tenant to avoid 0-balance leakage
369
+ // (including brz_token_metadata — per-tenant to avoid 0-balance leakage
190
370
  // for tokens a tenant never owned), backfills with the connecting
191
371
  // tenant, and rewrites primary keys / FKs / indexes to lead with
192
372
  // user_id. Composite FKs use NO ACTION because column-list SET NULL
@@ -194,62 +374,111 @@ class MysqlTokenStoreMigrationManager {
194
374
  name: "Multi-tenant scoping: add user_id and rewrite primary keys / FKs",
195
375
  sql: [
196
376
  // Drop dependent FKs FIRST so we can rewrite the parent PKs.
197
- `ALTER TABLE token_outputs DROP FOREIGN KEY fk_token_outputs_metadata`,
198
- `ALTER TABLE token_outputs DROP FOREIGN KEY fk_token_outputs_reservation`,
199
-
200
- // token_metadata: per-tenant scoping (privacy — see header).
201
- `ALTER TABLE token_metadata ADD COLUMN user_id VARBINARY(33) NULL`,
202
- `UPDATE token_metadata SET user_id = ${idLit} WHERE user_id IS NULL`,
203
- `ALTER TABLE token_metadata MODIFY COLUMN user_id VARBINARY(33) NOT NULL`,
204
- `ALTER TABLE token_metadata DROP PRIMARY KEY, ADD PRIMARY KEY (user_id, identifier)`,
205
- `DROP INDEX idx_token_metadata_issuer_pk ON token_metadata`,
206
- `CREATE INDEX idx_token_metadata_user_issuer_pk
207
- ON token_metadata (user_id, issuer_public_key)`,
208
-
209
- // token_reservations: scope by user_id.
210
- `ALTER TABLE token_reservations ADD COLUMN user_id VARBINARY(33) NULL`,
211
- `UPDATE token_reservations SET user_id = ${idLit} WHERE user_id IS NULL`,
212
- `ALTER TABLE token_reservations MODIFY COLUMN user_id VARBINARY(33) NOT NULL`,
213
- `ALTER TABLE token_reservations DROP PRIMARY KEY, ADD PRIMARY KEY (user_id, id)`,
214
-
215
- // token_outputs: scope by user_id, rekey, re-add composite FKs.
216
- `ALTER TABLE token_outputs ADD COLUMN user_id VARBINARY(33) NULL`,
217
- `UPDATE token_outputs SET user_id = ${idLit} WHERE user_id IS NULL`,
218
- `ALTER TABLE token_outputs MODIFY COLUMN user_id VARBINARY(33) NOT NULL`,
219
- `ALTER TABLE token_outputs DROP PRIMARY KEY, ADD PRIMARY KEY (user_id, id)`,
220
- `ALTER TABLE token_outputs
221
- ADD CONSTRAINT fk_token_outputs_metadata_user
222
- FOREIGN KEY (user_id, token_identifier)
223
- REFERENCES token_metadata(user_id, identifier)`,
224
- `ALTER TABLE token_outputs
225
- ADD CONSTRAINT fk_token_outputs_reservation_user
226
- FOREIGN KEY (user_id, reservation_id)
227
- REFERENCES token_reservations(user_id, id)`,
228
- `DROP INDEX idx_token_outputs_identifier ON token_outputs`,
229
- `DROP INDEX idx_token_outputs_reservation ON token_outputs`,
230
- `CREATE INDEX idx_token_outputs_user_identifier
231
- ON token_outputs (user_id, token_identifier)`,
232
- `CREATE INDEX idx_token_outputs_user_reservation
233
- ON token_outputs (user_id, reservation_id)`,
234
-
235
- // token_spent_outputs: scope by user_id.
236
- `ALTER TABLE token_spent_outputs ADD COLUMN user_id VARBINARY(33) NULL`,
237
- `UPDATE token_spent_outputs SET user_id = ${idLit} WHERE user_id IS NULL`,
238
- `ALTER TABLE token_spent_outputs MODIFY COLUMN user_id VARBINARY(33) NOT NULL`,
239
- `ALTER TABLE token_spent_outputs DROP PRIMARY KEY, ADD PRIMARY KEY (user_id, output_id)`,
240
-
241
- // token_swap_status was a singleton (PK id=1, CHECK id=1). Drop the
377
+ // Guarded so that databases created with `Disabled` foreign-key mode
378
+ // (where the FKs were never created) skip the DROP rather than
379
+ // erroring.
380
+ {
381
+ op: "dropForeignKey",
382
+ table: "brz_token_outputs",
383
+ name: "brz_fk_token_outputs_metadata",
384
+ },
385
+ {
386
+ op: "dropForeignKey",
387
+ table: "brz_token_outputs",
388
+ name: "brz_fk_token_outputs_reservation",
389
+ },
390
+
391
+ // brz_token_metadata: per-tenant scoping (privacy see header).
392
+ `ALTER TABLE brz_token_metadata ADD COLUMN user_id VARBINARY(33) NULL`,
393
+ `UPDATE brz_token_metadata SET user_id = ${idLit} WHERE user_id IS NULL`,
394
+ `ALTER TABLE brz_token_metadata MODIFY COLUMN user_id VARBINARY(33) NOT NULL`,
395
+ `ALTER TABLE brz_token_metadata DROP PRIMARY KEY, ADD PRIMARY KEY (user_id, identifier)`,
396
+ `DROP INDEX brz_idx_token_metadata_issuer_pk ON brz_token_metadata`,
397
+ `CREATE INDEX brz_idx_token_metadata_user_issuer_pk
398
+ ON brz_token_metadata (user_id, issuer_public_key)`,
399
+
400
+ // brz_token_reservations: scope by user_id.
401
+ `ALTER TABLE brz_token_reservations ADD COLUMN user_id VARBINARY(33) NULL`,
402
+ `UPDATE brz_token_reservations SET user_id = ${idLit} WHERE user_id IS NULL`,
403
+ `ALTER TABLE brz_token_reservations MODIFY COLUMN user_id VARBINARY(33) NOT NULL`,
404
+ `ALTER TABLE brz_token_reservations DROP PRIMARY KEY, ADD PRIMARY KEY (user_id, id)`,
405
+
406
+ // brz_token_outputs: scope by user_id, rekey, optionally re-add composite FKs.
407
+ `ALTER TABLE brz_token_outputs ADD COLUMN user_id VARBINARY(33) NULL`,
408
+ `UPDATE brz_token_outputs SET user_id = ${idLit} WHERE user_id IS NULL`,
409
+ `ALTER TABLE brz_token_outputs MODIFY COLUMN user_id VARBINARY(33) NOT NULL`,
410
+ `ALTER TABLE brz_token_outputs DROP PRIMARY KEY, ADD PRIMARY KEY (user_id, id)`,
411
+ ...(foreignKeyModeEnforced
412
+ ? [
413
+ {
414
+ op: "addForeignKey",
415
+ table: "brz_token_outputs",
416
+ name: "brz_fk_token_outputs_metadata_user",
417
+ definition: `FOREIGN KEY (user_id, token_identifier) REFERENCES brz_token_metadata(user_id, identifier)`,
418
+ },
419
+ {
420
+ op: "addForeignKey",
421
+ table: "brz_token_outputs",
422
+ name: "brz_fk_token_outputs_reservation_user",
423
+ definition: `FOREIGN KEY (user_id, reservation_id) REFERENCES brz_token_reservations(user_id, id)`,
424
+ },
425
+ ]
426
+ : []),
427
+ `DROP INDEX brz_idx_token_outputs_identifier ON brz_token_outputs`,
428
+ `DROP INDEX brz_idx_token_outputs_reservation ON brz_token_outputs`,
429
+ `CREATE INDEX brz_idx_token_outputs_user_identifier
430
+ ON brz_token_outputs (user_id, token_identifier)`,
431
+ `CREATE INDEX brz_idx_token_outputs_user_reservation
432
+ ON brz_token_outputs (user_id, reservation_id)`,
433
+
434
+ // brz_token_spent_outputs: scope by user_id.
435
+ `ALTER TABLE brz_token_spent_outputs ADD COLUMN user_id VARBINARY(33) NULL`,
436
+ `UPDATE brz_token_spent_outputs SET user_id = ${idLit} WHERE user_id IS NULL`,
437
+ `ALTER TABLE brz_token_spent_outputs MODIFY COLUMN user_id VARBINARY(33) NOT NULL`,
438
+ `ALTER TABLE brz_token_spent_outputs DROP PRIMARY KEY, ADD PRIMARY KEY (user_id, output_id)`,
439
+
440
+ // brz_token_swap_status was a singleton (PK id=1, CHECK id=1). Drop the
242
441
  // PK and the id column, then re-key by user_id.
243
- { op: "dropPrimaryKey", table: "token_swap_status" },
244
- `ALTER TABLE token_swap_status DROP COLUMN id`,
245
- `ALTER TABLE token_swap_status ADD COLUMN user_id VARBINARY(33) NULL`,
246
- `UPDATE token_swap_status SET user_id = ${idLit} WHERE user_id IS NULL`,
247
- `ALTER TABLE token_swap_status MODIFY COLUMN user_id VARBINARY(33) NOT NULL`,
248
- `ALTER TABLE token_swap_status ADD PRIMARY KEY (user_id)`,
442
+ { op: "dropPrimaryKey", table: "brz_token_swap_status" },
443
+ `ALTER TABLE brz_token_swap_status DROP COLUMN id`,
444
+ `ALTER TABLE brz_token_swap_status ADD COLUMN user_id VARBINARY(33) NULL`,
445
+ `UPDATE brz_token_swap_status SET user_id = ${idLit} WHERE user_id IS NULL`,
446
+ `ALTER TABLE brz_token_swap_status MODIFY COLUMN user_id VARBINARY(33) NOT NULL`,
447
+ `ALTER TABLE brz_token_swap_status ADD PRIMARY KEY (user_id)`,
249
448
  ],
250
449
  },
251
450
  ];
252
451
  }
253
452
  }
254
453
 
454
+ async function _mysqlTableExists(conn, tableName) {
455
+ const [rows] = await conn.query(
456
+ `SELECT COUNT(*) AS c FROM information_schema.tables
457
+ WHERE table_schema = DATABASE() AND table_name = ?`,
458
+ [tableName]
459
+ );
460
+ return Number(rows[0].c) > 0;
461
+ }
462
+
463
+ async function _mysqlIndexExists(conn, tableName, indexName) {
464
+ const [rows] = await conn.query(
465
+ `SELECT COUNT(*) AS c FROM information_schema.statistics
466
+ WHERE table_schema = DATABASE() AND table_name = ? AND index_name = ?`,
467
+ [tableName, indexName]
468
+ );
469
+ return Number(rows[0].c) > 0;
470
+ }
471
+
472
+ async function _mysqlForeignKeyExists(conn, tableName, constraintName) {
473
+ const [rows] = await conn.query(
474
+ `SELECT COUNT(*) AS c FROM information_schema.table_constraints
475
+ WHERE table_schema = DATABASE()
476
+ AND table_name = ?
477
+ AND constraint_type = 'FOREIGN KEY'
478
+ AND constraint_name = ?`,
479
+ [tableName, constraintName]
480
+ );
481
+ return Number(rows[0].c) > 0;
482
+ }
483
+
255
484
  module.exports = { MysqlTokenStoreMigrationManager };