@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
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Database Migration Manager for Breez SDK PostgreSQL Token Store
3
3
  *
4
- * Uses a token_schema_migrations table + pg_advisory_xact_lock to safely run
4
+ * Uses a brz_token_schema_migrations table + pg_advisory_xact_lock to safely run
5
5
  * migrations from concurrent processes.
6
6
  */
7
7
 
@@ -39,9 +39,11 @@ class TokenStoreMigrationManager {
39
39
  // Transaction-level advisory lock — automatically released on COMMIT/ROLLBACK
40
40
  await client.query(`SELECT pg_advisory_xact_lock(${MIGRATION_LOCK_ID})`);
41
41
 
42
+ await this._applySchemaRenames(client);
43
+
42
44
  // Create the migrations tracking table if needed
43
45
  await client.query(`
44
- CREATE TABLE IF NOT EXISTS token_schema_migrations (
46
+ CREATE TABLE IF NOT EXISTS brz_token_schema_migrations (
45
47
  version INTEGER PRIMARY KEY,
46
48
  applied_at TIMESTAMPTZ DEFAULT NOW()
47
49
  )
@@ -49,7 +51,7 @@ class TokenStoreMigrationManager {
49
51
 
50
52
  // Get current version
51
53
  const versionResult = await client.query(
52
- "SELECT COALESCE(MAX(version), 0) AS version FROM token_schema_migrations"
54
+ "SELECT COALESCE(MAX(version), 0) AS version FROM brz_token_schema_migrations"
53
55
  );
54
56
  const currentVersion = versionResult.rows[0].version;
55
57
 
@@ -76,7 +78,7 @@ class TokenStoreMigrationManager {
76
78
  }
77
79
 
78
80
  await client.query(
79
- "INSERT INTO token_schema_migrations (version) VALUES ($1)",
81
+ "INSERT INTO brz_token_schema_migrations (version) VALUES ($1)",
80
82
  [version]
81
83
  );
82
84
  }
@@ -94,6 +96,96 @@ class TokenStoreMigrationManager {
94
96
  }
95
97
  }
96
98
 
99
+ /**
100
+ * Pre-prefix rename. Canary-gated on the legacy `token_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 = 'token_schema_migrations'
110
+ ) AS exists`
111
+ );
112
+ if (!canary.rows[0].exists) {
113
+ return;
114
+ }
115
+
116
+ const tableRenames = [
117
+ ["token_metadata", "brz_token_metadata"],
118
+ ["token_reservations", "brz_token_reservations"],
119
+ ["token_outputs", "brz_token_outputs"],
120
+ ["token_spent_outputs", "brz_token_spent_outputs"],
121
+ ["token_swap_status", "brz_token_swap_status"],
122
+ ];
123
+ for (const [oldName, newName] of tableRenames) {
124
+ await client.query(`ALTER TABLE IF EXISTS ${oldName} RENAME TO ${newName}`);
125
+ }
126
+
127
+ const indexRenames = [
128
+ ["idx_token_metadata_user_issuer_pk", "brz_idx_token_metadata_user_issuer_pk"],
129
+ ["idx_token_outputs_user_identifier", "brz_idx_token_outputs_user_identifier"],
130
+ ["idx_token_outputs_user_reservation", "brz_idx_token_outputs_user_reservation"],
131
+ // Pre-multi-tenant indexes (dropped by the multi-tenant migration).
132
+ ["idx_token_metadata_issuer_pk", "brz_idx_token_metadata_issuer_pk"],
133
+ ["idx_token_outputs_identifier", "brz_idx_token_outputs_identifier"],
134
+ ["idx_token_outputs_reservation", "brz_idx_token_outputs_reservation"],
135
+ ];
136
+ for (const [oldName, newName] of indexRenames) {
137
+ await client.query(`ALTER INDEX IF EXISTS ${oldName} RENAME TO ${newName}`);
138
+ }
139
+
140
+ const constraintRenames = [
141
+ ["brz_token_metadata", "token_metadata_pkey", "brz_token_metadata_pkey"],
142
+ ["brz_token_reservations", "token_reservations_pkey", "brz_token_reservations_pkey"],
143
+ ["brz_token_outputs", "token_outputs_pkey", "brz_token_outputs_pkey"],
144
+ [
145
+ "brz_token_outputs",
146
+ "token_outputs_user_id_token_identifier_fkey",
147
+ "brz_token_outputs_user_id_token_identifier_fkey",
148
+ ],
149
+ [
150
+ "brz_token_outputs",
151
+ "token_outputs_user_id_reservation_id_fkey",
152
+ "brz_token_outputs_user_id_reservation_id_fkey",
153
+ ],
154
+ // Pre-multi-tenant FKs (single-column). Rename so the post-tenant
155
+ // migration's `DROP CONSTRAINT IF EXISTS brz_*_fkey` finds them.
156
+ [
157
+ "brz_token_outputs",
158
+ "token_outputs_token_identifier_fkey",
159
+ "brz_token_outputs_token_identifier_fkey",
160
+ ],
161
+ [
162
+ "brz_token_outputs",
163
+ "token_outputs_reservation_id_fkey",
164
+ "brz_token_outputs_reservation_id_fkey",
165
+ ],
166
+ ["brz_token_spent_outputs", "token_spent_outputs_pkey", "brz_token_spent_outputs_pkey"],
167
+ ["brz_token_swap_status", "token_swap_status_pkey", "brz_token_swap_status_pkey"],
168
+ ];
169
+ for (const [table, oldName, newName] of constraintRenames) {
170
+ await client.query(
171
+ `DO $$ BEGIN
172
+ IF EXISTS (
173
+ SELECT 1 FROM information_schema.table_constraints
174
+ WHERE table_schema = current_schema()
175
+ AND table_name = '${table}'
176
+ AND constraint_name = '${oldName}'
177
+ ) THEN
178
+ ALTER TABLE ${table} RENAME CONSTRAINT ${oldName} TO ${newName};
179
+ END IF;
180
+ END $$`
181
+ );
182
+ }
183
+
184
+ await client.query(
185
+ `ALTER TABLE IF EXISTS token_schema_migrations RENAME TO brz_token_schema_migrations`
186
+ );
187
+ }
188
+
97
189
  _log(level, message) {
98
190
  if (this.logger && typeof this.logger.log === "function") {
99
191
  this.logger.log({ line: message, level });
@@ -118,7 +210,7 @@ class TokenStoreMigrationManager {
118
210
  {
119
211
  name: "Create token store tables with race condition protection",
120
212
  sql: [
121
- `CREATE TABLE IF NOT EXISTS token_metadata (
213
+ `CREATE TABLE IF NOT EXISTS brz_token_metadata (
122
214
  identifier TEXT PRIMARY KEY,
123
215
  issuer_public_key TEXT NOT NULL,
124
216
  name TEXT NOT NULL,
@@ -129,18 +221,18 @@ class TokenStoreMigrationManager {
129
221
  creation_entity_public_key TEXT
130
222
  )`,
131
223
 
132
- `CREATE INDEX IF NOT EXISTS idx_token_metadata_issuer_pk
133
- ON token_metadata (issuer_public_key)`,
224
+ `CREATE INDEX IF NOT EXISTS brz_idx_token_metadata_issuer_pk
225
+ ON brz_token_metadata (issuer_public_key)`,
134
226
 
135
- `CREATE TABLE IF NOT EXISTS token_reservations (
227
+ `CREATE TABLE IF NOT EXISTS brz_token_reservations (
136
228
  id TEXT PRIMARY KEY,
137
229
  purpose TEXT NOT NULL,
138
230
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
139
231
  )`,
140
232
 
141
- `CREATE TABLE IF NOT EXISTS token_outputs (
233
+ `CREATE TABLE IF NOT EXISTS brz_token_outputs (
142
234
  id TEXT PRIMARY KEY,
143
- token_identifier TEXT NOT NULL REFERENCES token_metadata(identifier),
235
+ token_identifier TEXT NOT NULL REFERENCES brz_token_metadata(identifier),
144
236
  owner_public_key TEXT NOT NULL,
145
237
  revocation_commitment TEXT NOT NULL,
146
238
  withdraw_bond_sats BIGINT NOT NULL,
@@ -149,32 +241,32 @@ class TokenStoreMigrationManager {
149
241
  token_amount TEXT NOT NULL,
150
242
  prev_tx_hash TEXT NOT NULL,
151
243
  prev_tx_vout INTEGER NOT NULL,
152
- reservation_id TEXT REFERENCES token_reservations(id) ON DELETE SET NULL,
244
+ reservation_id TEXT REFERENCES brz_token_reservations(id) ON DELETE SET NULL,
153
245
  added_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
154
246
  )`,
155
247
 
156
- `CREATE INDEX IF NOT EXISTS idx_token_outputs_identifier
157
- ON token_outputs (token_identifier)`,
248
+ `CREATE INDEX IF NOT EXISTS brz_idx_token_outputs_identifier
249
+ ON brz_token_outputs (token_identifier)`,
158
250
 
159
- `CREATE INDEX IF NOT EXISTS idx_token_outputs_reservation
160
- ON token_outputs (reservation_id) WHERE reservation_id IS NOT NULL`,
251
+ `CREATE INDEX IF NOT EXISTS brz_idx_token_outputs_reservation
252
+ ON brz_token_outputs (reservation_id) WHERE reservation_id IS NOT NULL`,
161
253
 
162
- `CREATE TABLE IF NOT EXISTS token_spent_outputs (
254
+ `CREATE TABLE IF NOT EXISTS brz_token_spent_outputs (
163
255
  output_id TEXT PRIMARY KEY,
164
256
  spent_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
165
257
  )`,
166
258
 
167
- `CREATE TABLE IF NOT EXISTS token_swap_status (
259
+ `CREATE TABLE IF NOT EXISTS brz_token_swap_status (
168
260
  id INTEGER PRIMARY KEY DEFAULT 1 CHECK (id = 1),
169
261
  last_completed_at TIMESTAMPTZ
170
262
  )`,
171
263
 
172
- `INSERT INTO token_swap_status (id) VALUES (1) ON CONFLICT DO NOTHING`,
264
+ `INSERT INTO brz_token_swap_status (id) VALUES (1) ON CONFLICT DO NOTHING`,
173
265
  ],
174
266
  },
175
267
  {
176
268
  // Mirrors Rust migration 2 in spark-postgres/src/token_store.rs.
177
- // Adds user_id to every token-store table (including token_metadata
269
+ // Adds user_id to every token-store table (including brz_token_metadata
178
270
  // per-tenant to avoid 0-balance leakage for tokens a tenant never
179
271
  // owned), backfills with the connecting tenant, and rewrites primary
180
272
  // keys / FKs / indexes to lead with user_id. Composite FKs use NO
@@ -185,62 +277,62 @@ class TokenStoreMigrationManager {
185
277
  // Drop dependent FKs FIRST so we can rebuild parent PKs they
186
278
  // reference. Inline `REFERENCES` clauses get auto-named
187
279
  // `<table>_<column>_fkey`.
188
- `ALTER TABLE token_outputs
189
- DROP CONSTRAINT IF EXISTS token_outputs_reservation_id_fkey`,
190
- `ALTER TABLE token_outputs
191
- DROP CONSTRAINT IF EXISTS token_outputs_token_identifier_fkey`,
192
-
193
- // token_metadata: per-tenant scoping (privacy — see header).
194
- `ALTER TABLE token_metadata ADD COLUMN user_id BYTEA`,
195
- `UPDATE token_metadata SET user_id = ${idLit}`,
196
- `ALTER TABLE token_metadata
280
+ `ALTER TABLE brz_token_outputs
281
+ DROP CONSTRAINT IF EXISTS brz_token_outputs_reservation_id_fkey`,
282
+ `ALTER TABLE brz_token_outputs
283
+ DROP CONSTRAINT IF EXISTS brz_token_outputs_token_identifier_fkey`,
284
+
285
+ // brz_token_metadata: per-tenant scoping (privacy — see header).
286
+ `ALTER TABLE brz_token_metadata ADD COLUMN user_id BYTEA`,
287
+ `UPDATE brz_token_metadata SET user_id = ${idLit}`,
288
+ `ALTER TABLE brz_token_metadata
197
289
  ALTER COLUMN user_id SET NOT NULL,
198
- DROP CONSTRAINT IF EXISTS token_metadata_pkey,
290
+ DROP CONSTRAINT IF EXISTS brz_token_metadata_pkey,
199
291
  ADD PRIMARY KEY (user_id, identifier)`,
200
- `DROP INDEX IF EXISTS idx_token_metadata_issuer_pk`,
201
- `CREATE INDEX idx_token_metadata_user_issuer_pk
202
- ON token_metadata (user_id, issuer_public_key)`,
203
-
204
- // token_reservations: scope by user_id.
205
- `ALTER TABLE token_reservations ADD COLUMN user_id BYTEA`,
206
- `UPDATE token_reservations SET user_id = ${idLit}`,
207
- `ALTER TABLE token_reservations
292
+ `DROP INDEX IF EXISTS brz_idx_token_metadata_issuer_pk`,
293
+ `CREATE INDEX brz_idx_token_metadata_user_issuer_pk
294
+ ON brz_token_metadata (user_id, issuer_public_key)`,
295
+
296
+ // brz_token_reservations: scope by user_id.
297
+ `ALTER TABLE brz_token_reservations ADD COLUMN user_id BYTEA`,
298
+ `UPDATE brz_token_reservations SET user_id = ${idLit}`,
299
+ `ALTER TABLE brz_token_reservations
208
300
  ALTER COLUMN user_id SET NOT NULL,
209
- DROP CONSTRAINT IF EXISTS token_reservations_pkey,
301
+ DROP CONSTRAINT IF EXISTS brz_token_reservations_pkey,
210
302
  ADD PRIMARY KEY (user_id, id)`,
211
303
 
212
- // token_outputs: scope by user_id, rekey, re-add composite FKs.
213
- `ALTER TABLE token_outputs ADD COLUMN user_id BYTEA`,
214
- `UPDATE token_outputs SET user_id = ${idLit}`,
215
- `ALTER TABLE token_outputs
304
+ // brz_token_outputs: scope by user_id, rekey, re-add composite FKs.
305
+ `ALTER TABLE brz_token_outputs ADD COLUMN user_id BYTEA`,
306
+ `UPDATE brz_token_outputs SET user_id = ${idLit}`,
307
+ `ALTER TABLE brz_token_outputs
216
308
  ALTER COLUMN user_id SET NOT NULL,
217
- DROP CONSTRAINT IF EXISTS token_outputs_pkey,
309
+ DROP CONSTRAINT IF EXISTS brz_token_outputs_pkey,
218
310
  ADD PRIMARY KEY (user_id, id),
219
311
  ADD FOREIGN KEY (user_id, token_identifier)
220
- REFERENCES token_metadata(user_id, identifier),
312
+ REFERENCES brz_token_metadata(user_id, identifier),
221
313
  ADD FOREIGN KEY (user_id, reservation_id)
222
- REFERENCES token_reservations(user_id, id)`,
223
- `DROP INDEX IF EXISTS idx_token_outputs_identifier`,
224
- `DROP INDEX IF EXISTS idx_token_outputs_reservation`,
225
- `CREATE INDEX idx_token_outputs_user_identifier
226
- ON token_outputs (user_id, token_identifier)`,
227
- `CREATE INDEX idx_token_outputs_user_reservation
228
- ON token_outputs (user_id, reservation_id)
314
+ REFERENCES brz_token_reservations(user_id, id)`,
315
+ `DROP INDEX IF EXISTS brz_idx_token_outputs_identifier`,
316
+ `DROP INDEX IF EXISTS brz_idx_token_outputs_reservation`,
317
+ `CREATE INDEX brz_idx_token_outputs_user_identifier
318
+ ON brz_token_outputs (user_id, token_identifier)`,
319
+ `CREATE INDEX brz_idx_token_outputs_user_reservation
320
+ ON brz_token_outputs (user_id, reservation_id)
229
321
  WHERE reservation_id IS NOT NULL`,
230
322
 
231
- // token_spent_outputs: scope by user_id.
232
- `ALTER TABLE token_spent_outputs ADD COLUMN user_id BYTEA`,
233
- `UPDATE token_spent_outputs SET user_id = ${idLit}`,
234
- `ALTER TABLE token_spent_outputs
323
+ // brz_token_spent_outputs: scope by user_id.
324
+ `ALTER TABLE brz_token_spent_outputs ADD COLUMN user_id BYTEA`,
325
+ `UPDATE brz_token_spent_outputs SET user_id = ${idLit}`,
326
+ `ALTER TABLE brz_token_spent_outputs
235
327
  ALTER COLUMN user_id SET NOT NULL,
236
- DROP CONSTRAINT IF EXISTS token_spent_outputs_pkey,
328
+ DROP CONSTRAINT IF EXISTS brz_token_spent_outputs_pkey,
237
329
  ADD PRIMARY KEY (user_id, output_id)`,
238
330
 
239
- // token_swap_status: drop the singleton id, rekey by user_id.
240
- `ALTER TABLE token_swap_status DROP COLUMN id CASCADE`,
241
- `ALTER TABLE token_swap_status ADD COLUMN user_id BYTEA`,
242
- `UPDATE token_swap_status SET user_id = ${idLit}`,
243
- `ALTER TABLE token_swap_status
331
+ // brz_token_swap_status: drop the singleton id, rekey by user_id.
332
+ `ALTER TABLE brz_token_swap_status DROP COLUMN id CASCADE`,
333
+ `ALTER TABLE brz_token_swap_status ADD COLUMN user_id BYTEA`,
334
+ `UPDATE brz_token_swap_status SET user_id = ${idLit}`,
335
+ `ALTER TABLE brz_token_swap_status
244
336
  ALTER COLUMN user_id SET NOT NULL,
245
337
  ADD PRIMARY KEY (user_id)`,
246
338
  ],
@@ -62,7 +62,7 @@ class PostgresTreeStore {
62
62
  * identifying the tenant. All reads and writes are scoped by this.
63
63
  * @param {object} [logger]
64
64
  */
65
- constructor(pool, identity, logger = null) {
65
+ constructor(pool, identity, logger = null, runMigration = true) {
66
66
  if (!identity || identity.length !== 33) {
67
67
  throw new TreeStoreError(
68
68
  "tenant identity (33-byte secp256k1 pubkey) is required"
@@ -72,6 +72,7 @@ class PostgresTreeStore {
72
72
  this.identity = Buffer.from(identity);
73
73
  this.lockKey = _identityLockKey(TREE_STORE_LOCK_PREFIX, identity);
74
74
  this.logger = logger;
75
+ this.runMigration = runMigration;
75
76
  }
76
77
 
77
78
  /**
@@ -79,8 +80,10 @@ class PostgresTreeStore {
79
80
  */
80
81
  async initialize() {
81
82
  try {
82
- const migrationManager = new TreeStoreMigrationManager(this.logger);
83
- await migrationManager.migrate(this.pool, this.identity);
83
+ if (this.runMigration) {
84
+ const migrationManager = new TreeStoreMigrationManager(this.logger);
85
+ await migrationManager.migrate(this.pool, this.identity);
86
+ }
84
87
  return this;
85
88
  } catch (error) {
86
89
  throw new TreeStoreError(
@@ -194,8 +197,8 @@ class PostgresTreeStore {
194
197
  const result = await this.pool.query(
195
198
  `
196
199
  SELECT COALESCE(SUM((l.data->>'value')::bigint), 0)::bigint AS balance
197
- FROM tree_leaves l
198
- LEFT JOIN tree_reservations r
200
+ FROM brz_tree_leaves l
201
+ LEFT JOIN brz_tree_reservations r
199
202
  ON l.reservation_id = r.id AND l.user_id = r.user_id
200
203
  WHERE l.user_id = $1
201
204
  AND (
@@ -220,8 +223,8 @@ class PostgresTreeStore {
220
223
  `
221
224
  SELECT l.id, l.status, l.is_missing_from_operators, l.data,
222
225
  l.reservation_id, r.purpose
223
- FROM tree_leaves l
224
- LEFT JOIN tree_reservations r
226
+ FROM brz_tree_leaves l
227
+ LEFT JOIN brz_tree_reservations r
225
228
  ON l.reservation_id = r.id AND l.user_id = r.user_id
226
229
  WHERE l.user_id = $1
227
230
  `,
@@ -292,12 +295,12 @@ class PostgresTreeStore {
292
295
  `
293
296
  SELECT
294
297
  EXISTS(
295
- SELECT 1 FROM tree_reservations
298
+ SELECT 1 FROM brz_tree_reservations
296
299
  WHERE user_id = $1 AND purpose = 'Swap'
297
300
  ) AS has_active_swap,
298
301
  COALESCE(
299
302
  (SELECT last_completed_at >= $2
300
- FROM tree_swap_status WHERE user_id = $1),
303
+ FROM brz_tree_swap_status WHERE user_id = $1),
301
304
  FALSE
302
305
  ) AS swap_completed_during_refresh
303
306
  `,
@@ -314,7 +317,7 @@ class PostgresTreeStore {
314
317
  await this._cleanupSpentMarkers(client, refreshTimestamp);
315
318
 
316
319
  const spentResult = await client.query(
317
- "SELECT leaf_id FROM tree_spent_leaves WHERE user_id = $1 AND spent_at >= $2",
320
+ "SELECT leaf_id FROM brz_tree_spent_leaves WHERE user_id = $1 AND spent_at >= $2",
318
321
  [this.identity, refreshTimestamp]
319
322
  );
320
323
  const spentIds = new Set(spentResult.rows.map((r) => r.leaf_id));
@@ -324,7 +327,7 @@ class PostgresTreeStore {
324
327
  // _cleanupStaleReservations (which now NULLs reservation_id explicitly,
325
328
  // since the composite FK uses NO ACTION).
326
329
  await client.query(
327
- "DELETE FROM tree_leaves WHERE user_id = $1 AND reservation_id IS NULL AND added_at < $2",
330
+ "DELETE FROM brz_tree_leaves WHERE user_id = $1 AND reservation_id IS NULL AND added_at < $2",
328
331
  [this.identity, refreshTimestamp]
329
332
  );
330
333
 
@@ -358,7 +361,7 @@ class PostgresTreeStore {
358
361
  try {
359
362
  await this._withTransaction(async (client) => {
360
363
  const res = await client.query(
361
- "SELECT id FROM tree_reservations WHERE user_id = $1 AND id = $2",
364
+ "SELECT id FROM brz_tree_reservations WHERE user_id = $1 AND id = $2",
362
365
  [this.identity, id]
363
366
  );
364
367
 
@@ -367,12 +370,12 @@ class PostgresTreeStore {
367
370
  }
368
371
 
369
372
  await client.query(
370
- "DELETE FROM tree_leaves WHERE user_id = $1 AND reservation_id = $2",
373
+ "DELETE FROM brz_tree_leaves WHERE user_id = $1 AND reservation_id = $2",
371
374
  [this.identity, id]
372
375
  );
373
376
 
374
377
  await client.query(
375
- "DELETE FROM tree_reservations WHERE user_id = $1 AND id = $2",
378
+ "DELETE FROM brz_tree_reservations WHERE user_id = $1 AND id = $2",
376
379
  [this.identity, id]
377
380
  );
378
381
 
@@ -398,12 +401,12 @@ class PostgresTreeStore {
398
401
  try {
399
402
  // _withWriteTransaction acquires the advisory lock so this serializes
400
403
  // against `setLeaves`. Without it, a concurrent setLeaves could read
401
- // tree_spent_leaves before our marker commits and re-insert the
404
+ // brz_tree_spent_leaves before our marker commits and re-insert the
402
405
  // just-spent leaf as Available.
403
406
  await this._withWriteTransaction(async (client) => {
404
407
  // Check if reservation exists and get purpose
405
408
  const res = await client.query(
406
- "SELECT id, purpose FROM tree_reservations WHERE user_id = $1 AND id = $2",
409
+ "SELECT id, purpose FROM brz_tree_reservations WHERE user_id = $1 AND id = $2",
407
410
  [this.identity, id]
408
411
  );
409
412
 
@@ -412,17 +415,17 @@ class PostgresTreeStore {
412
415
  if (res.rows.length > 0) {
413
416
  isSwap = res.rows[0].purpose === "Swap";
414
417
  const leafResult = await client.query(
415
- "SELECT id FROM tree_leaves WHERE user_id = $1 AND reservation_id = $2",
418
+ "SELECT id FROM brz_tree_leaves WHERE user_id = $1 AND reservation_id = $2",
416
419
  [this.identity, id]
417
420
  );
418
421
  reservedLeafIds = leafResult.rows.map((r) => r.id);
419
422
  await this._batchInsertSpentLeaves(client, reservedLeafIds);
420
423
  await client.query(
421
- "DELETE FROM tree_leaves WHERE user_id = $1 AND reservation_id = $2",
424
+ "DELETE FROM brz_tree_leaves WHERE user_id = $1 AND reservation_id = $2",
422
425
  [this.identity, id]
423
426
  );
424
427
  await client.query(
425
- "DELETE FROM tree_reservations WHERE user_id = $1 AND id = $2",
428
+ "DELETE FROM brz_tree_reservations WHERE user_id = $1 AND id = $2",
426
429
  [this.identity, id]
427
430
  );
428
431
  }
@@ -436,7 +439,7 @@ class PostgresTreeStore {
436
439
  // that joined after migration 3 (and thus has no row) gets one created.
437
440
  if (isSwap && newLeaves && newLeaves.length > 0) {
438
441
  await client.query(
439
- `INSERT INTO tree_swap_status (user_id, last_completed_at)
442
+ `INSERT INTO brz_tree_swap_status (user_id, last_completed_at)
440
443
  VALUES ($1, NOW())
441
444
  ON CONFLICT (user_id) DO UPDATE
442
445
  SET last_completed_at = EXCLUDED.last_completed_at`,
@@ -472,7 +475,7 @@ class PostgresTreeStore {
472
475
  const totalResult = await client.query(
473
476
  `
474
477
  SELECT COALESCE(SUM((data->>'value')::bigint), 0)::bigint AS total
475
- FROM tree_leaves
478
+ FROM brz_tree_leaves
476
479
  WHERE user_id = $1
477
480
  AND status = 'Available'
478
481
  AND is_missing_from_operators = FALSE
@@ -490,7 +493,7 @@ class PostgresTreeStore {
490
493
  const slimResult = await client.query(
491
494
  `
492
495
  SELECT id, (data->>'value')::bigint AS value
493
- FROM tree_leaves
496
+ FROM brz_tree_leaves
494
497
  WHERE user_id = $1
495
498
  AND status = 'Available'
496
499
  AND is_missing_from_operators = FALSE
@@ -498,7 +501,7 @@ class PostgresTreeStore {
498
501
  AND (
499
502
  (data->>'value')::bigint <= $2
500
503
  OR id = (
501
- SELECT id FROM tree_leaves
504
+ SELECT id FROM brz_tree_leaves
502
505
  WHERE user_id = $1
503
506
  AND status = 'Available'
504
507
  AND is_missing_from_operators = FALSE
@@ -609,7 +612,7 @@ class PostgresTreeStore {
609
612
  async _fetchFullLeavesByIds(client, ids) {
610
613
  if (!ids || ids.length === 0) return [];
611
614
  const result = await client.query(
612
- "SELECT data FROM tree_leaves WHERE user_id = $2 AND id = ANY($1)",
615
+ "SELECT data FROM brz_tree_leaves WHERE user_id = $2 AND id = ANY($1)",
613
616
  [ids, this.identity]
614
617
  );
615
618
  return result.rows.map((r) => r.data);
@@ -643,7 +646,7 @@ class PostgresTreeStore {
643
646
  return await this._withTransaction(async (client) => {
644
647
  // Check if reservation exists
645
648
  const res = await client.query(
646
- "SELECT id FROM tree_reservations WHERE user_id = $1 AND id = $2",
649
+ "SELECT id FROM brz_tree_reservations WHERE user_id = $1 AND id = $2",
647
650
  [this.identity, reservationId]
648
651
  );
649
652
 
@@ -653,14 +656,14 @@ class PostgresTreeStore {
653
656
 
654
657
  // Get old reserved leaf IDs and mark as spent
655
658
  const oldLeavesResult = await client.query(
656
- "SELECT id FROM tree_leaves WHERE user_id = $1 AND reservation_id = $2",
659
+ "SELECT id FROM brz_tree_leaves WHERE user_id = $1 AND reservation_id = $2",
657
660
  [this.identity, reservationId]
658
661
  );
659
662
  const oldLeafIds = oldLeavesResult.rows.map((r) => r.id);
660
663
 
661
664
  await this._batchInsertSpentLeaves(client, oldLeafIds);
662
665
  await client.query(
663
- "DELETE FROM tree_leaves WHERE user_id = $1 AND reservation_id = $2",
666
+ "DELETE FROM brz_tree_leaves WHERE user_id = $1 AND reservation_id = $2",
664
667
  [this.identity, reservationId]
665
668
  );
666
669
 
@@ -676,7 +679,7 @@ class PostgresTreeStore {
676
679
 
677
680
  // Clear pending change amount
678
681
  await client.query(
679
- "UPDATE tree_reservations SET pending_change_amount = 0 WHERE user_id = $1 AND id = $2",
682
+ "UPDATE brz_tree_reservations SET pending_change_amount = 0 WHERE user_id = $1 AND id = $2",
680
683
  [this.identity, reservationId]
681
684
  );
682
685
 
@@ -859,7 +862,7 @@ class PostgresTreeStore {
859
862
  */
860
863
  async _calculatePendingBalance(client) {
861
864
  const result = await client.query(
862
- "SELECT COALESCE(SUM(pending_change_amount), 0)::BIGINT AS pending FROM tree_reservations WHERE user_id = $1",
865
+ "SELECT COALESCE(SUM(pending_change_amount), 0)::BIGINT AS pending FROM brz_tree_reservations WHERE user_id = $1",
863
866
  [this.identity]
864
867
  );
865
868
  return Number(result.rows[0].pending);
@@ -870,7 +873,7 @@ class PostgresTreeStore {
870
873
  */
871
874
  async _createReservation(client, reservationId, leaves, purpose, pendingChange) {
872
875
  await client.query(
873
- "INSERT INTO tree_reservations (user_id, id, purpose, pending_change_amount) VALUES ($1, $2, $3, $4)",
876
+ "INSERT INTO brz_tree_reservations (user_id, id, purpose, pending_change_amount) VALUES ($1, $2, $3, $4)",
874
877
  [this.identity, reservationId, purpose, pendingChange]
875
878
  );
876
879
 
@@ -879,7 +882,7 @@ class PostgresTreeStore {
879
882
  }
880
883
 
881
884
  /**
882
- * Batch upsert leaves into tree_leaves table.
885
+ * Batch upsert leaves into brz_tree_leaves table.
883
886
  */
884
887
  async _batchUpsertLeaves(client, leaves, isMissingFromOperators, skipIds) {
885
888
  if (!leaves || leaves.length === 0) return;
@@ -896,7 +899,7 @@ class PostgresTreeStore {
896
899
  const dataValues = filtered.map((l) => JSON.stringify(l));
897
900
 
898
901
  await client.query(
899
- `INSERT INTO tree_leaves (user_id, id, status, is_missing_from_operators, data, added_at)
902
+ `INSERT INTO brz_tree_leaves (user_id, id, status, is_missing_from_operators, data, added_at)
900
903
  SELECT $5, id, status, missing, data::jsonb, NOW()
901
904
  FROM UNNEST($1::text[], $2::text[], $3::bool[], $4::text[])
902
905
  AS t(id, status, missing, data)
@@ -916,7 +919,7 @@ class PostgresTreeStore {
916
919
  if (leafIds.length === 0) return;
917
920
 
918
921
  await client.query(
919
- "UPDATE tree_leaves SET reservation_id = $1 WHERE user_id = $3 AND id = ANY($2)",
922
+ "UPDATE brz_tree_leaves SET reservation_id = $1 WHERE user_id = $3 AND id = ANY($2)",
920
923
  [reservationId, leafIds, this.identity]
921
924
  );
922
925
  }
@@ -928,7 +931,7 @@ class PostgresTreeStore {
928
931
  if (leafIds.length === 0) return;
929
932
 
930
933
  await client.query(
931
- `INSERT INTO tree_spent_leaves (user_id, leaf_id)
934
+ `INSERT INTO brz_tree_spent_leaves (user_id, leaf_id)
932
935
  SELECT $2, leaf_id FROM UNNEST($1::text[]) AS t(leaf_id)
933
936
  ON CONFLICT DO NOTHING`,
934
937
  [leafIds, this.identity]
@@ -942,7 +945,7 @@ class PostgresTreeStore {
942
945
  if (leafIds.length === 0) return;
943
946
 
944
947
  await client.query(
945
- "DELETE FROM tree_spent_leaves WHERE user_id = $2 AND leaf_id = ANY($1)",
948
+ "DELETE FROM brz_tree_spent_leaves WHERE user_id = $2 AND leaf_id = ANY($1)",
946
949
  [leafIds, this.identity]
947
950
  );
948
951
  }
@@ -955,17 +958,17 @@ class PostgresTreeStore {
955
958
  */
956
959
  async _cleanupStaleReservations(client) {
957
960
  await client.query(
958
- `UPDATE tree_leaves SET reservation_id = NULL
961
+ `UPDATE brz_tree_leaves SET reservation_id = NULL
959
962
  WHERE user_id = $2
960
963
  AND reservation_id IN (
961
- SELECT id FROM tree_reservations
964
+ SELECT id FROM brz_tree_reservations
962
965
  WHERE user_id = $2
963
966
  AND created_at < NOW() - make_interval(secs => $1)
964
967
  )`,
965
968
  [RESERVATION_TIMEOUT_SECS, this.identity]
966
969
  );
967
970
  await client.query(
968
- `DELETE FROM tree_reservations
971
+ `DELETE FROM brz_tree_reservations
969
972
  WHERE user_id = $2
970
973
  AND created_at < NOW() - make_interval(secs => $1)`,
971
974
  [RESERVATION_TIMEOUT_SECS, this.identity]
@@ -980,7 +983,7 @@ class PostgresTreeStore {
980
983
  const cleanupCutoff = new Date(refreshTimestamp.getTime() - thresholdMs);
981
984
 
982
985
  await client.query(
983
- "DELETE FROM tree_spent_leaves WHERE user_id = $2 AND spent_at < $1",
986
+ "DELETE FROM brz_tree_spent_leaves WHERE user_id = $2 AND spent_at < $1",
984
987
  [cleanupCutoff, this.identity]
985
988
  );
986
989
  }
@@ -1005,7 +1008,12 @@ async function createPostgresTreeStore(config, identity, logger = null) {
1005
1008
  connectionTimeoutMillis: config.createTimeoutSecs * 1000,
1006
1009
  idleTimeoutMillis: config.recycleTimeoutSecs * 1000,
1007
1010
  });
1008
- return createPostgresTreeStoreWithPool(pool, identity, logger);
1011
+ return createPostgresTreeStoreWithPool(
1012
+ pool,
1013
+ identity,
1014
+ logger,
1015
+ config.runMigration !== false
1016
+ );
1009
1017
  }
1010
1018
 
1011
1019
  /**
@@ -1016,8 +1024,18 @@ async function createPostgresTreeStore(config, identity, logger = null) {
1016
1024
  * @param {object} [logger] - Optional logger
1017
1025
  * @returns {Promise<PostgresTreeStore>}
1018
1026
  */
1019
- async function createPostgresTreeStoreWithPool(pool, identity, logger = null) {
1020
- const store = new PostgresTreeStore(pool, identity, logger);
1027
+ async function createPostgresTreeStoreWithPool(
1028
+ pool,
1029
+ identity,
1030
+ logger = null,
1031
+ runMigration = true
1032
+ ) {
1033
+ const store = new PostgresTreeStore(
1034
+ pool,
1035
+ identity,
1036
+ logger,
1037
+ runMigration
1038
+ );
1021
1039
  await store.initialize();
1022
1040
  return store;
1023
1041
  }