@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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Database Migration Manager for Breez SDK PostgreSQL Token Store
|
|
3
3
|
*
|
|
4
|
-
* Uses a
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
133
|
-
ON
|
|
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
|
|
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
|
|
233
|
+
`CREATE TABLE IF NOT EXISTS brz_token_outputs (
|
|
142
234
|
id TEXT PRIMARY KEY,
|
|
143
|
-
token_identifier TEXT NOT NULL REFERENCES
|
|
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
|
|
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
|
|
157
|
-
ON
|
|
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
|
|
160
|
-
ON
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
189
|
-
DROP CONSTRAINT IF EXISTS
|
|
190
|
-
`ALTER TABLE
|
|
191
|
-
DROP CONSTRAINT IF EXISTS
|
|
192
|
-
|
|
193
|
-
//
|
|
194
|
-
`ALTER TABLE
|
|
195
|
-
`UPDATE
|
|
196
|
-
`ALTER TABLE
|
|
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
|
|
290
|
+
DROP CONSTRAINT IF EXISTS brz_token_metadata_pkey,
|
|
199
291
|
ADD PRIMARY KEY (user_id, identifier)`,
|
|
200
|
-
`DROP INDEX IF EXISTS
|
|
201
|
-
`CREATE INDEX
|
|
202
|
-
ON
|
|
203
|
-
|
|
204
|
-
//
|
|
205
|
-
`ALTER TABLE
|
|
206
|
-
`UPDATE
|
|
207
|
-
`ALTER TABLE
|
|
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
|
|
301
|
+
DROP CONSTRAINT IF EXISTS brz_token_reservations_pkey,
|
|
210
302
|
ADD PRIMARY KEY (user_id, id)`,
|
|
211
303
|
|
|
212
|
-
//
|
|
213
|
-
`ALTER TABLE
|
|
214
|
-
`UPDATE
|
|
215
|
-
`ALTER TABLE
|
|
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
|
|
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
|
|
312
|
+
REFERENCES brz_token_metadata(user_id, identifier),
|
|
221
313
|
ADD FOREIGN KEY (user_id, reservation_id)
|
|
222
|
-
REFERENCES
|
|
223
|
-
`DROP INDEX IF EXISTS
|
|
224
|
-
`DROP INDEX IF EXISTS
|
|
225
|
-
`CREATE INDEX
|
|
226
|
-
ON
|
|
227
|
-
`CREATE INDEX
|
|
228
|
-
ON
|
|
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
|
-
//
|
|
232
|
-
`ALTER TABLE
|
|
233
|
-
`UPDATE
|
|
234
|
-
`ALTER TABLE
|
|
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
|
|
328
|
+
DROP CONSTRAINT IF EXISTS brz_token_spent_outputs_pkey,
|
|
237
329
|
ADD PRIMARY KEY (user_id, output_id)`,
|
|
238
330
|
|
|
239
|
-
//
|
|
240
|
-
`ALTER TABLE
|
|
241
|
-
`ALTER TABLE
|
|
242
|
-
`UPDATE
|
|
243
|
-
`ALTER TABLE
|
|
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
|
-
|
|
83
|
-
|
|
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
|
|
198
|
-
LEFT JOIN
|
|
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
|
|
224
|
-
LEFT JOIN
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
//
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
1020
|
-
|
|
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
|
}
|