@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 { TreeStoreError } = require("./errors.cjs");
6
6
 
7
- const TREE_MIGRATIONS_TABLE = "tree_schema_migrations";
7
+ const TREE_MIGRATIONS_TABLE = "brz_tree_schema_migrations";
8
8
  const MIGRATION_LOCK_NAME = "breez_mysql_tree_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 MysqlTreeStoreMigrationManager {
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
  async migrate(pool, identity) {
@@ -56,6 +91,8 @@ class MysqlTreeStoreMigrationManager {
56
91
  }
57
92
 
58
93
  try {
94
+ await this._applySchemaRenames(conn);
95
+
59
96
  await conn.query("START TRANSACTION");
60
97
 
61
98
  await conn.query(`
@@ -106,116 +143,262 @@ class MysqlTreeStoreMigrationManager {
106
143
  }
107
144
  }
108
145
 
146
+ /**
147
+ * Pre-prefix rename. Canary-gated on the legacy `tree_schema_migrations`
148
+ * table.
149
+ * @param {import('mysql2/promise').PoolConnection} conn
150
+ */
151
+ async _applySchemaRenames(conn) {
152
+ if (!(await _mysqlTableExists(conn, "tree_schema_migrations"))) {
153
+ return;
154
+ }
155
+
156
+ const tableRenames = [
157
+ ["tree_reservations", "brz_tree_reservations"],
158
+ ["tree_leaves", "brz_tree_leaves"],
159
+ ["tree_spent_leaves", "brz_tree_spent_leaves"],
160
+ ["tree_swap_status", "brz_tree_swap_status"],
161
+ ];
162
+ for (const [oldName, newName] of tableRenames) {
163
+ if (
164
+ (await _mysqlTableExists(conn, oldName)) &&
165
+ !(await _mysqlTableExists(conn, newName))
166
+ ) {
167
+ await conn.query(`RENAME TABLE \`${oldName}\` TO \`${newName}\``);
168
+ }
169
+ }
170
+
171
+ const indexRenames = [
172
+ ["brz_tree_leaves", "idx_tree_leaves_user_available", "brz_idx_tree_leaves_user_available"],
173
+ [
174
+ "brz_tree_leaves",
175
+ "idx_tree_leaves_user_reservation",
176
+ "brz_idx_tree_leaves_user_reservation",
177
+ ],
178
+ ["brz_tree_leaves", "idx_tree_leaves_user_added_at", "brz_idx_tree_leaves_user_added_at"],
179
+ ["brz_tree_leaves", "idx_tree_leaves_user_slim", "brz_idx_tree_leaves_user_slim"],
180
+ // Pre-multi-tenant indexes (dropped by the multi-tenant migration).
181
+ ["brz_tree_leaves", "idx_tree_leaves_available", "brz_idx_tree_leaves_available"],
182
+ ["brz_tree_leaves", "idx_tree_leaves_reservation", "brz_idx_tree_leaves_reservation"],
183
+ ["brz_tree_leaves", "idx_tree_leaves_added_at", "brz_idx_tree_leaves_added_at"],
184
+ ["brz_tree_leaves", "idx_tree_leaves_slim", "brz_idx_tree_leaves_slim"],
185
+ ];
186
+ for (const [table, oldName, newName] of indexRenames) {
187
+ if (
188
+ (await _mysqlIndexExists(conn, table, oldName)) &&
189
+ !(await _mysqlIndexExists(conn, table, newName))
190
+ ) {
191
+ await conn.query(
192
+ `ALTER TABLE \`${table}\` RENAME INDEX \`${oldName}\` TO \`${newName}\``
193
+ );
194
+ }
195
+ }
196
+
197
+ // MySQL has no RENAME CONSTRAINT for foreign keys: drop the legacy FK
198
+ // and re-add it under the brz_ name.
199
+ const fkRenames = [
200
+ {
201
+ table: "brz_tree_leaves",
202
+ oldName: "fk_tree_leaves_reservation_user",
203
+ newName: "brz_fk_tree_leaves_reservation_user",
204
+ definition:
205
+ "FOREIGN KEY (user_id, reservation_id) REFERENCES `brz_tree_reservations`(user_id, id)",
206
+ },
207
+ // Pre-multi-tenant FK (single-column). Rename so the post-tenant
208
+ // migration's drop-foreign-key step finds it.
209
+ {
210
+ table: "brz_tree_leaves",
211
+ oldName: "fk_tree_leaves_reservation",
212
+ newName: "brz_fk_tree_leaves_reservation",
213
+ definition:
214
+ "FOREIGN KEY (reservation_id) REFERENCES `brz_tree_reservations`(id) ON DELETE SET NULL",
215
+ },
216
+ ];
217
+ for (const fk of fkRenames) {
218
+ if (await _mysqlForeignKeyExists(conn, fk.table, fk.newName)) {
219
+ continue;
220
+ }
221
+ if (!(await _mysqlForeignKeyExists(conn, fk.table, fk.oldName))) {
222
+ continue;
223
+ }
224
+ await conn.query(
225
+ `ALTER TABLE \`${fk.table}\`` +
226
+ ` DROP FOREIGN KEY \`${fk.oldName}\`,` +
227
+ ` ADD CONSTRAINT \`${fk.newName}\` ${fk.definition}`
228
+ );
229
+ }
230
+
231
+ if (
232
+ (await _mysqlTableExists(conn, "tree_schema_migrations")) &&
233
+ !(await _mysqlTableExists(conn, TREE_MIGRATIONS_TABLE))
234
+ ) {
235
+ await conn.query(
236
+ `RENAME TABLE \`tree_schema_migrations\` TO \`${TREE_MIGRATIONS_TABLE}\``
237
+ );
238
+ }
239
+ }
240
+
109
241
  _getMigrations(identity) {
110
242
  const idHex = Buffer.from(identity).toString("hex");
111
243
  const idLit = `UNHEX('${idHex}')`;
244
+ const foreignKeyModeEnforced = this.foreignKeyMode === "Enforced";
112
245
 
113
- return [
114
- {
115
- name: "Create tree store tables",
116
- sql: [
117
- `CREATE TABLE IF NOT EXISTS tree_reservations (
246
+ const initialSql = [
247
+ `CREATE TABLE IF NOT EXISTS brz_tree_reservations (
118
248
  id VARCHAR(255) NOT NULL PRIMARY KEY,
119
249
  purpose VARCHAR(64) NOT NULL,
120
250
  pending_change_amount BIGINT NOT NULL DEFAULT 0,
121
251
  created_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6)
122
252
  )`,
123
- `CREATE TABLE IF NOT EXISTS tree_leaves (
253
+ `CREATE TABLE IF NOT EXISTS brz_tree_leaves (
124
254
  id VARCHAR(255) NOT NULL PRIMARY KEY,
125
255
  status VARCHAR(64) NOT NULL,
126
256
  is_missing_from_operators TINYINT(1) NOT NULL DEFAULT 0,
127
257
  reservation_id VARCHAR(255) NULL,
128
258
  data JSON NOT NULL,
129
259
  created_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
130
- added_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
131
- CONSTRAINT fk_tree_leaves_reservation FOREIGN KEY (reservation_id)
132
- REFERENCES tree_reservations(id) ON DELETE SET NULL
260
+ added_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6)
133
261
  )`,
134
- `CREATE TABLE IF NOT EXISTS tree_spent_leaves (
262
+ `CREATE TABLE IF NOT EXISTS brz_tree_spent_leaves (
135
263
  leaf_id VARCHAR(255) NOT NULL PRIMARY KEY,
136
264
  spent_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6)
137
265
  )`,
138
- `CREATE INDEX idx_tree_leaves_available
139
- ON tree_leaves(status, is_missing_from_operators)`,
140
- `CREATE INDEX idx_tree_leaves_reservation ON tree_leaves(reservation_id)`,
141
- `CREATE INDEX idx_tree_leaves_added_at ON tree_leaves(added_at)`,
142
- ],
266
+ `CREATE INDEX brz_idx_tree_leaves_available
267
+ ON brz_tree_leaves(status, is_missing_from_operators)`,
268
+ `CREATE INDEX brz_idx_tree_leaves_reservation ON brz_tree_leaves(reservation_id)`,
269
+ `CREATE INDEX brz_idx_tree_leaves_added_at ON brz_tree_leaves(added_at)`,
270
+ ];
271
+ if (foreignKeyModeEnforced) {
272
+ initialSql.push({
273
+ op: "addForeignKey",
274
+ table: "brz_tree_leaves",
275
+ name: "brz_fk_tree_leaves_reservation",
276
+ definition: `FOREIGN KEY (reservation_id) REFERENCES brz_tree_reservations(id) ON DELETE SET NULL`,
277
+ });
278
+ }
279
+
280
+ return [
281
+ {
282
+ name: "Create tree store tables",
283
+ sql: initialSql,
143
284
  },
144
285
  {
145
286
  name: "Add swap status tracking",
146
287
  sql: [
147
- `CREATE TABLE IF NOT EXISTS tree_swap_status (
288
+ `CREATE TABLE IF NOT EXISTS brz_tree_swap_status (
148
289
  id INT NOT NULL PRIMARY KEY DEFAULT 1,
149
290
  last_completed_at DATETIME(6) NULL,
150
291
  CHECK (id = 1)
151
292
  )`,
152
- `INSERT INTO tree_swap_status (id) VALUES (1)
293
+ `INSERT INTO brz_tree_swap_status (id) VALUES (1)
153
294
  ON DUPLICATE KEY UPDATE id = id`,
154
295
  ],
155
296
  },
156
297
  {
157
298
  name: "Promote leaf value to BIGINT column with covering index",
158
299
  sql: [
159
- `ALTER TABLE tree_leaves
300
+ `ALTER TABLE brz_tree_leaves
160
301
  ADD COLUMN value BIGINT NOT NULL DEFAULT 0`,
161
- `UPDATE tree_leaves
302
+ `UPDATE brz_tree_leaves
162
303
  SET value = CAST(JSON_UNQUOTE(JSON_EXTRACT(data, '$.value')) AS UNSIGNED)
163
304
  WHERE value = 0`,
164
- `CREATE INDEX idx_tree_leaves_slim
165
- ON tree_leaves(status, is_missing_from_operators, reservation_id, value)`,
305
+ `CREATE INDEX brz_idx_tree_leaves_slim
306
+ ON brz_tree_leaves(status, is_missing_from_operators, reservation_id, value)`,
166
307
  ],
167
308
  },
168
309
  {
169
310
  name: "Multi-tenant scoping: add user_id and rewrite primary keys / FKs",
170
311
  sql: [
171
- // Drop the existing FK so we can rewrite the parent PK.
172
- `ALTER TABLE tree_leaves DROP FOREIGN KEY fk_tree_leaves_reservation`,
173
-
174
- // tree_reservations: scope by user_id.
175
- `ALTER TABLE tree_reservations ADD COLUMN user_id VARBINARY(33) NULL`,
176
- `UPDATE tree_reservations SET user_id = ${idLit} WHERE user_id IS NULL`,
177
- `ALTER TABLE tree_reservations MODIFY COLUMN user_id VARBINARY(33) NOT NULL`,
178
- `ALTER TABLE tree_reservations DROP PRIMARY KEY, ADD PRIMARY KEY (user_id, id)`,
179
-
180
- // tree_leaves: scope by user_id, rekey, re-add composite FK.
181
- `ALTER TABLE tree_leaves ADD COLUMN user_id VARBINARY(33) NULL`,
182
- `UPDATE tree_leaves SET user_id = ${idLit} WHERE user_id IS NULL`,
183
- `ALTER TABLE tree_leaves MODIFY COLUMN user_id VARBINARY(33) NOT NULL`,
184
- `ALTER TABLE tree_leaves DROP PRIMARY KEY, ADD PRIMARY KEY (user_id, id)`,
185
- `ALTER TABLE tree_leaves
186
- ADD CONSTRAINT fk_tree_leaves_reservation_user
187
- FOREIGN KEY (user_id, reservation_id)
188
- REFERENCES tree_reservations(user_id, id)`,
189
- `DROP INDEX idx_tree_leaves_available ON tree_leaves`,
190
- `DROP INDEX idx_tree_leaves_reservation ON tree_leaves`,
191
- `DROP INDEX idx_tree_leaves_added_at ON tree_leaves`,
192
- `DROP INDEX idx_tree_leaves_slim ON tree_leaves`,
193
- `CREATE INDEX idx_tree_leaves_user_available
194
- ON tree_leaves(user_id, status, is_missing_from_operators)`,
195
- `CREATE INDEX idx_tree_leaves_user_reservation
196
- ON tree_leaves(user_id, reservation_id)`,
197
- `CREATE INDEX idx_tree_leaves_user_added_at ON tree_leaves(user_id, added_at)`,
198
- `CREATE INDEX idx_tree_leaves_user_slim
199
- ON tree_leaves(user_id, status, is_missing_from_operators, reservation_id, value)`,
200
-
201
- // tree_spent_leaves: scope by user_id.
202
- `ALTER TABLE tree_spent_leaves ADD COLUMN user_id VARBINARY(33) NULL`,
203
- `UPDATE tree_spent_leaves SET user_id = ${idLit} WHERE user_id IS NULL`,
204
- `ALTER TABLE tree_spent_leaves MODIFY COLUMN user_id VARBINARY(33) NOT NULL`,
205
- `ALTER TABLE tree_spent_leaves DROP PRIMARY KEY, ADD PRIMARY KEY (user_id, leaf_id)`,
206
-
207
- // tree_swap_status was a singleton (PK id=1, CHECK id=1). Drop the PK
312
+ // Drop the existing FK so we can rewrite the parent PK. Guarded so
313
+ // that databases created with `Disabled` foreign-key mode (where the
314
+ // FK was never created) skip the DROP rather than erroring.
315
+ {
316
+ op: "dropForeignKey",
317
+ table: "brz_tree_leaves",
318
+ name: "brz_fk_tree_leaves_reservation",
319
+ },
320
+
321
+ // brz_tree_reservations: scope by user_id.
322
+ `ALTER TABLE brz_tree_reservations ADD COLUMN user_id VARBINARY(33) NULL`,
323
+ `UPDATE brz_tree_reservations SET user_id = ${idLit} WHERE user_id IS NULL`,
324
+ `ALTER TABLE brz_tree_reservations MODIFY COLUMN user_id VARBINARY(33) NOT NULL`,
325
+ `ALTER TABLE brz_tree_reservations DROP PRIMARY KEY, ADD PRIMARY KEY (user_id, id)`,
326
+
327
+ // brz_tree_leaves: scope by user_id, rekey, optionally re-add composite FK.
328
+ `ALTER TABLE brz_tree_leaves ADD COLUMN user_id VARBINARY(33) NULL`,
329
+ `UPDATE brz_tree_leaves SET user_id = ${idLit} WHERE user_id IS NULL`,
330
+ `ALTER TABLE brz_tree_leaves MODIFY COLUMN user_id VARBINARY(33) NOT NULL`,
331
+ `ALTER TABLE brz_tree_leaves DROP PRIMARY KEY, ADD PRIMARY KEY (user_id, id)`,
332
+ ...(foreignKeyModeEnforced
333
+ ? [
334
+ {
335
+ op: "addForeignKey",
336
+ table: "brz_tree_leaves",
337
+ name: "brz_fk_tree_leaves_reservation_user",
338
+ definition: `FOREIGN KEY (user_id, reservation_id) REFERENCES brz_tree_reservations(user_id, id)`,
339
+ },
340
+ ]
341
+ : []),
342
+ `DROP INDEX brz_idx_tree_leaves_available ON brz_tree_leaves`,
343
+ `DROP INDEX brz_idx_tree_leaves_reservation ON brz_tree_leaves`,
344
+ `DROP INDEX brz_idx_tree_leaves_added_at ON brz_tree_leaves`,
345
+ `DROP INDEX brz_idx_tree_leaves_slim ON brz_tree_leaves`,
346
+ `CREATE INDEX brz_idx_tree_leaves_user_available
347
+ ON brz_tree_leaves(user_id, status, is_missing_from_operators)`,
348
+ `CREATE INDEX brz_idx_tree_leaves_user_reservation
349
+ ON brz_tree_leaves(user_id, reservation_id)`,
350
+ `CREATE INDEX brz_idx_tree_leaves_user_added_at ON brz_tree_leaves(user_id, added_at)`,
351
+ `CREATE INDEX brz_idx_tree_leaves_user_slim
352
+ ON brz_tree_leaves(user_id, status, is_missing_from_operators, reservation_id, value)`,
353
+
354
+ // brz_tree_spent_leaves: scope by user_id.
355
+ `ALTER TABLE brz_tree_spent_leaves ADD COLUMN user_id VARBINARY(33) NULL`,
356
+ `UPDATE brz_tree_spent_leaves SET user_id = ${idLit} WHERE user_id IS NULL`,
357
+ `ALTER TABLE brz_tree_spent_leaves MODIFY COLUMN user_id VARBINARY(33) NOT NULL`,
358
+ `ALTER TABLE brz_tree_spent_leaves DROP PRIMARY KEY, ADD PRIMARY KEY (user_id, leaf_id)`,
359
+
360
+ // brz_tree_swap_status was a singleton (PK id=1, CHECK id=1). Drop the PK
208
361
  // and the id column, then re-key by user_id.
209
- { op: "dropPrimaryKey", table: "tree_swap_status" },
210
- `ALTER TABLE tree_swap_status DROP COLUMN id`,
211
- `ALTER TABLE tree_swap_status ADD COLUMN user_id VARBINARY(33) NULL`,
212
- `UPDATE tree_swap_status SET user_id = ${idLit} WHERE user_id IS NULL`,
213
- `ALTER TABLE tree_swap_status MODIFY COLUMN user_id VARBINARY(33) NOT NULL`,
214
- `ALTER TABLE tree_swap_status ADD PRIMARY KEY (user_id)`,
362
+ { op: "dropPrimaryKey", table: "brz_tree_swap_status" },
363
+ `ALTER TABLE brz_tree_swap_status DROP COLUMN id`,
364
+ `ALTER TABLE brz_tree_swap_status ADD COLUMN user_id VARBINARY(33) NULL`,
365
+ `UPDATE brz_tree_swap_status SET user_id = ${idLit} WHERE user_id IS NULL`,
366
+ `ALTER TABLE brz_tree_swap_status MODIFY COLUMN user_id VARBINARY(33) NOT NULL`,
367
+ `ALTER TABLE brz_tree_swap_status ADD PRIMARY KEY (user_id)`,
215
368
  ],
216
369
  },
217
370
  ];
218
371
  }
219
372
  }
220
373
 
374
+ async function _mysqlTableExists(conn, tableName) {
375
+ const [rows] = await conn.query(
376
+ `SELECT COUNT(*) AS c FROM information_schema.tables
377
+ WHERE table_schema = DATABASE() AND table_name = ?`,
378
+ [tableName]
379
+ );
380
+ return Number(rows[0].c) > 0;
381
+ }
382
+
383
+ async function _mysqlIndexExists(conn, tableName, indexName) {
384
+ const [rows] = await conn.query(
385
+ `SELECT COUNT(*) AS c FROM information_schema.statistics
386
+ WHERE table_schema = DATABASE() AND table_name = ? AND index_name = ?`,
387
+ [tableName, indexName]
388
+ );
389
+ return Number(rows[0].c) > 0;
390
+ }
391
+
392
+ async function _mysqlForeignKeyExists(conn, tableName, constraintName) {
393
+ const [rows] = await conn.query(
394
+ `SELECT COUNT(*) AS c FROM information_schema.table_constraints
395
+ WHERE table_schema = DATABASE()
396
+ AND table_name = ?
397
+ AND constraint_type = 'FOREIGN KEY'
398
+ AND constraint_name = ?`,
399
+ [tableName, constraintName]
400
+ );
401
+ return Number(rows[0].c) > 0;
402
+ }
403
+
221
404
  module.exports = { MysqlTreeStoreMigrationManager };
@@ -7,7 +7,7 @@
7
7
  * `setSession(serviceIdentityKey, session)` upserts a session.
8
8
  *
9
9
  * Tenant identity is bound at construction so multiple tenants can share
10
- * a single Postgres database without leaking sessions across tenants.
10
+ * a single Postgres database without leaking brz_sessions across tenants.
11
11
  */
12
12
 
13
13
  let pg;
@@ -39,7 +39,7 @@ class PostgresSessionManager {
39
39
  * identifying the tenant. All reads and writes are scoped by this.
40
40
  * @param {object} [logger]
41
41
  */
42
- constructor(pool, identity, logger = null) {
42
+ constructor(pool, identity, logger = null, runMigration = true) {
43
43
  if (!identity || identity.length !== 33) {
44
44
  throw new SessionManagerError(
45
45
  "tenant identity (33-byte secp256k1 pubkey) is required"
@@ -48,12 +48,15 @@ class PostgresSessionManager {
48
48
  this.pool = pool;
49
49
  this.identity = Buffer.from(identity);
50
50
  this.logger = logger;
51
+ this.runMigration = runMigration;
51
52
  }
52
53
 
53
54
  async initialize() {
54
55
  try {
55
- const migrationManager = new SessionManagerMigrationManager(this.logger);
56
- await migrationManager.migrate(this.pool);
56
+ if (this.runMigration) {
57
+ const migrationManager = new SessionManagerMigrationManager(this.logger);
58
+ await migrationManager.migrate(this.pool);
59
+ }
57
60
  return this;
58
61
  } catch (error) {
59
62
  throw new SessionManagerError(
@@ -81,7 +84,7 @@ class PostgresSessionManager {
81
84
  const serviceKey = _decodePubkey(serviceIdentityKey);
82
85
  try {
83
86
  const { rows } = await this.pool.query(
84
- `SELECT token, expiration FROM sessions
87
+ `SELECT token, expiration FROM brz_sessions
85
88
  WHERE user_id = $1 AND service_identity_key = $2`,
86
89
  [this.identity, serviceKey]
87
90
  );
@@ -110,7 +113,7 @@ class PostgresSessionManager {
110
113
  const serviceKey = _decodePubkey(serviceIdentityKey);
111
114
  try {
112
115
  await this.pool.query(
113
- `INSERT INTO sessions (user_id, service_identity_key, token, expiration)
116
+ `INSERT INTO brz_sessions (user_id, service_identity_key, token, expiration)
114
117
  VALUES ($1, $2, $3, $4)
115
118
  ON CONFLICT (user_id, service_identity_key)
116
119
  DO UPDATE SET token = EXCLUDED.token, expiration = EXCLUDED.expiration`,
@@ -142,7 +145,12 @@ function _decodePubkey(hex) {
142
145
  */
143
146
  async function createPostgresSessionManager(poolConfig, identity, logger = null) {
144
147
  const pool = new pg.Pool(poolConfig);
145
- const manager = new PostgresSessionManager(pool, identity, logger);
148
+ const manager = new PostgresSessionManager(
149
+ pool,
150
+ identity,
151
+ logger,
152
+ poolConfig.runMigration !== false
153
+ );
146
154
  await manager.initialize();
147
155
  return manager;
148
156
  }
@@ -151,8 +159,18 @@ async function createPostgresSessionManager(poolConfig, identity, logger = null)
151
159
  * Wraps an existing pool — useful when sharing the pool with the storage,
152
160
  * tree store, and token store implementations.
153
161
  */
154
- async function createPostgresSessionManagerWithPool(pool, identity, logger = null) {
155
- const manager = new PostgresSessionManager(pool, identity, logger);
162
+ async function createPostgresSessionManagerWithPool(
163
+ pool,
164
+ identity,
165
+ logger = null,
166
+ runMigration = true
167
+ ) {
168
+ const manager = new PostgresSessionManager(
169
+ pool,
170
+ identity,
171
+ logger,
172
+ runMigration
173
+ );
156
174
  await manager.initialize();
157
175
  return manager;
158
176
  }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Database Migration Manager for Breez SDK PostgreSQL Session Manager.
3
3
  *
4
- * Uses a session_schema_migrations table + pg_advisory_xact_lock to safely
4
+ * Uses a brz_session_schema_migrations table + pg_advisory_xact_lock to safely
5
5
  * run migrations from concurrent processes. Mirrors the schema produced by
6
6
  * the Rust `PostgresSessionManager`.
7
7
  */
@@ -32,15 +32,17 @@ class SessionManagerMigrationManager {
32
32
  await client.query("BEGIN");
33
33
  await client.query(`SELECT pg_advisory_xact_lock(${MIGRATION_LOCK_ID})`);
34
34
 
35
+ await this._applySchemaRenames(client);
36
+
35
37
  await client.query(`
36
- CREATE TABLE IF NOT EXISTS session_schema_migrations (
38
+ CREATE TABLE IF NOT EXISTS brz_session_schema_migrations (
37
39
  version INTEGER PRIMARY KEY,
38
40
  applied_at TIMESTAMPTZ DEFAULT NOW()
39
41
  )
40
42
  `);
41
43
 
42
44
  const versionResult = await client.query(
43
- "SELECT COALESCE(MAX(version), 0) AS version FROM session_schema_migrations"
45
+ "SELECT COALESCE(MAX(version), 0) AS version FROM brz_session_schema_migrations"
44
46
  );
45
47
  const currentVersion = versionResult.rows[0].version;
46
48
 
@@ -73,7 +75,7 @@ class SessionManagerMigrationManager {
73
75
  }
74
76
 
75
77
  await client.query(
76
- "INSERT INTO session_schema_migrations (version) VALUES ($1)",
78
+ "INSERT INTO brz_session_schema_migrations (version) VALUES ($1)",
77
79
  [version]
78
80
  );
79
81
  }
@@ -94,6 +96,43 @@ class SessionManagerMigrationManager {
94
96
  }
95
97
  }
96
98
 
99
+ /**
100
+ * Pre-prefix rename. Canary-gated on the legacy `session_schema_migrations`
101
+ * table.
102
+ * @param {import('pg').PoolClient} client
103
+ */
104
+ async _applySchemaRenames(client) {
105
+ const canary = await client.query(
106
+ `SELECT EXISTS (
107
+ SELECT 1 FROM information_schema.tables
108
+ WHERE table_schema = current_schema()
109
+ AND table_name = 'session_schema_migrations'
110
+ ) AS exists`
111
+ );
112
+ if (!canary.rows[0].exists) {
113
+ return;
114
+ }
115
+
116
+ // Rename data tables first, then their auto-named PK constraints, then
117
+ // the migrations tracking table (which doubles as the rename canary).
118
+ await client.query(`ALTER TABLE IF EXISTS sessions RENAME TO brz_sessions`);
119
+ await client.query(
120
+ `DO $$ BEGIN
121
+ IF EXISTS (
122
+ SELECT 1 FROM information_schema.table_constraints
123
+ WHERE table_schema = current_schema()
124
+ AND table_name = 'brz_sessions'
125
+ AND constraint_name = 'sessions_pkey'
126
+ ) THEN
127
+ ALTER TABLE brz_sessions RENAME CONSTRAINT sessions_pkey TO brz_sessions_pkey;
128
+ END IF;
129
+ END $$`
130
+ );
131
+ await client.query(
132
+ `ALTER TABLE IF EXISTS session_schema_migrations RENAME TO brz_session_schema_migrations`
133
+ );
134
+ }
135
+
97
136
  _log(level, message) {
98
137
  if (this.logger && typeof this.logger.log === "function") {
99
138
  this.logger.log({ line: message, level });
@@ -108,9 +147,9 @@ class SessionManagerMigrationManager {
108
147
  _getMigrations() {
109
148
  return [
110
149
  {
111
- name: "Create sessions table",
150
+ name: "Create brz_sessions table",
112
151
  sql: [
113
- `CREATE TABLE IF NOT EXISTS sessions (
152
+ `CREATE TABLE IF NOT EXISTS brz_sessions (
114
153
  user_id BYTEA NOT NULL,
115
154
  service_identity_key BYTEA NOT NULL,
116
155
  token TEXT NOT NULL,