@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.
- package/breez-sdk-spark.tgz +0 -0
- package/bundler/breez_sdk_spark_wasm.d.ts +114 -40
- package/bundler/breez_sdk_spark_wasm.js +1 -1
- package/bundler/breez_sdk_spark_wasm_bg.js +118 -104
- package/bundler/breez_sdk_spark_wasm_bg.wasm +0 -0
- package/bundler/breez_sdk_spark_wasm_bg.wasm.d.ts +12 -11
- package/deno/breez_sdk_spark_wasm.d.ts +114 -40
- package/deno/breez_sdk_spark_wasm.js +118 -104
- package/deno/breez_sdk_spark_wasm_bg.wasm +0 -0
- package/deno/breez_sdk_spark_wasm_bg.wasm.d.ts +12 -11
- package/nodejs/breez_sdk_spark_wasm.d.ts +114 -40
- package/nodejs/breez_sdk_spark_wasm.js +121 -106
- package/nodejs/breez_sdk_spark_wasm_bg.wasm +0 -0
- package/nodejs/breez_sdk_spark_wasm_bg.wasm.d.ts +12 -11
- package/nodejs/index.mjs +3 -2
- package/nodejs/mysql-session-manager/index.cjs +26 -8
- package/nodejs/mysql-session-manager/migrations.cjs +40 -3
- package/nodejs/mysql-storage/index.cjs +67 -48
- package/nodejs/mysql-storage/migrations.cjs +220 -85
- package/nodejs/mysql-token-store/index.cjs +133 -68
- package/nodejs/mysql-token-store/migrations.cjs +309 -80
- package/nodejs/mysql-tree-store/index.cjs +76 -41
- package/nodejs/mysql-tree-store/migrations.cjs +254 -71
- package/nodejs/postgres-session-manager/index.cjs +27 -9
- package/nodejs/postgres-session-manager/migrations.cjs +45 -6
- package/nodejs/postgres-storage/index.cjs +81 -62
- package/nodejs/postgres-storage/migrations.cjs +207 -79
- package/nodejs/postgres-token-store/index.cjs +111 -67
- package/nodejs/postgres-token-store/migrations.cjs +153 -61
- package/nodejs/postgres-tree-store/index.cjs +60 -42
- package/nodejs/postgres-tree-store/migrations.cjs +130 -46
- package/package.json +1 -1
- package/ssr/index.js +14 -9
- package/web/breez_sdk_spark_wasm.d.ts +126 -51
- package/web/breez_sdk_spark_wasm.js +118 -104
- package/web/breez_sdk_spark_wasm_bg.wasm +0 -0
- 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 = "
|
|
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 }`
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
139
|
-
ON
|
|
140
|
-
`CREATE INDEX
|
|
141
|
-
`CREATE INDEX
|
|
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
|
|
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
|
|
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
|
|
300
|
+
`ALTER TABLE brz_tree_leaves
|
|
160
301
|
ADD COLUMN value BIGINT NOT NULL DEFAULT 0`,
|
|
161
|
-
`UPDATE
|
|
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
|
|
165
|
-
ON
|
|
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
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
//
|
|
181
|
-
`ALTER TABLE
|
|
182
|
-
`UPDATE
|
|
183
|
-
`ALTER TABLE
|
|
184
|
-
`ALTER TABLE
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
`
|
|
190
|
-
`DROP
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
`
|
|
203
|
-
`
|
|
204
|
-
`
|
|
205
|
-
`
|
|
206
|
-
|
|
207
|
-
|
|
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: "
|
|
210
|
-
`ALTER TABLE
|
|
211
|
-
`ALTER TABLE
|
|
212
|
-
`UPDATE
|
|
213
|
-
`ALTER TABLE
|
|
214
|
-
`ALTER TABLE
|
|
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
|
|
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
|
-
|
|
56
|
-
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
155
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
150
|
+
name: "Create brz_sessions table",
|
|
112
151
|
sql: [
|
|
113
|
-
`CREATE TABLE IF NOT EXISTS
|
|
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,
|