@breeztech/breez-sdk-spark 0.13.10-dev → 0.13.12-dev1

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 (49) hide show
  1. package/breez-sdk-spark.tgz +0 -0
  2. package/bundler/breez_sdk_spark_wasm.d.ts +157 -0
  3. package/bundler/breez_sdk_spark_wasm.js +1 -1
  4. package/bundler/breez_sdk_spark_wasm_bg.js +419 -41
  5. package/bundler/breez_sdk_spark_wasm_bg.wasm +0 -0
  6. package/bundler/breez_sdk_spark_wasm_bg.wasm.d.ts +27 -7
  7. package/deno/breez_sdk_spark_wasm.d.ts +157 -0
  8. package/deno/breez_sdk_spark_wasm.js +419 -41
  9. package/deno/breez_sdk_spark_wasm_bg.wasm +0 -0
  10. package/deno/breez_sdk_spark_wasm_bg.wasm.d.ts +27 -7
  11. package/nodejs/breez_sdk_spark_wasm.d.ts +157 -0
  12. package/nodejs/breez_sdk_spark_wasm.js +428 -41
  13. package/nodejs/breez_sdk_spark_wasm_bg.wasm +0 -0
  14. package/nodejs/breez_sdk_spark_wasm_bg.wasm.d.ts +27 -7
  15. package/nodejs/index.js +56 -0
  16. package/nodejs/index.mjs +9 -0
  17. package/nodejs/mysql-session-manager/errors.cjs +13 -0
  18. package/nodejs/mysql-session-manager/index.cjs +144 -0
  19. package/nodejs/mysql-session-manager/migrations.cjs +102 -0
  20. package/nodejs/mysql-session-manager/package.json +9 -0
  21. package/nodejs/mysql-storage/errors.cjs +19 -0
  22. package/nodejs/mysql-storage/index.cjs +1367 -0
  23. package/nodejs/mysql-storage/migrations.cjs +387 -0
  24. package/nodejs/mysql-storage/package.json +9 -0
  25. package/nodejs/mysql-token-store/errors.cjs +9 -0
  26. package/nodejs/mysql-token-store/index.cjs +980 -0
  27. package/nodejs/mysql-token-store/migrations.cjs +255 -0
  28. package/nodejs/mysql-token-store/package.json +9 -0
  29. package/nodejs/mysql-tree-store/errors.cjs +9 -0
  30. package/nodejs/mysql-tree-store/index.cjs +941 -0
  31. package/nodejs/mysql-tree-store/migrations.cjs +221 -0
  32. package/nodejs/mysql-tree-store/package.json +9 -0
  33. package/nodejs/package.json +5 -0
  34. package/nodejs/postgres-session-manager/errors.cjs +13 -0
  35. package/nodejs/postgres-session-manager/index.cjs +165 -0
  36. package/nodejs/postgres-session-manager/migrations.cjs +126 -0
  37. package/nodejs/postgres-session-manager/package.json +9 -0
  38. package/nodejs/postgres-storage/index.cjs +147 -92
  39. package/nodejs/postgres-storage/migrations.cjs +85 -4
  40. package/nodejs/postgres-token-store/index.cjs +178 -102
  41. package/nodejs/postgres-token-store/migrations.cjs +92 -3
  42. package/nodejs/postgres-tree-store/index.cjs +168 -83
  43. package/nodejs/postgres-tree-store/migrations.cjs +80 -3
  44. package/package.json +1 -1
  45. package/ssr/index.js +53 -0
  46. package/web/breez_sdk_spark_wasm.d.ts +184 -7
  47. package/web/breez_sdk_spark_wasm.js +419 -41
  48. package/web/breez_sdk_spark_wasm_bg.wasm +0 -0
  49. package/web/breez_sdk_spark_wasm_bg.wasm.d.ts +27 -7
@@ -0,0 +1,387 @@
1
+ /**
2
+ * Database Migration Manager for Breez SDK MySQL Storage.
3
+ *
4
+ * Mirrors `postgres-storage/migrations.cjs` with SQL adapted for MySQL 8.0+:
5
+ * - JSONB → JSON
6
+ * - TIMESTAMPTZ NOT NULL DEFAULT NOW() → DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6)
7
+ * - TEXT PRIMARY KEY → VARCHAR(255) PRIMARY KEY
8
+ * - BOOLEAN → TINYINT(1)
9
+ * - ON CONFLICT DO NOTHING → INSERT … ON DUPLICATE KEY UPDATE <pk> = <pk>
10
+ * (avoid INSERT IGNORE: it silently swallows non-PK errors too)
11
+ * - pg_advisory_xact_lock → GET_LOCK/RELEASE_LOCK
12
+ * - reserved word `key` quoted with backticks
13
+ *
14
+ * Uses a schema_migrations table + GET_LOCK to safely run migrations from
15
+ * concurrent processes. Unlike pg's transaction-scoped advisory locks, MySQL
16
+ * named locks are session-scoped, so we explicitly RELEASE_LOCK after the
17
+ * commit (or on error in finally).
18
+ */
19
+
20
+ const { StorageError } = require("./errors.cjs");
21
+
22
+ /** Named lock used to serialize concurrent migration runs. */
23
+ const MIGRATION_LOCK_NAME = "breez_mysql_migration_lock";
24
+ /** Seconds to wait when acquiring the migration lock. */
25
+ const MIGRATION_LOCK_TIMEOUT = 60;
26
+
27
+ /**
28
+ * Runs a single migration step. Plain strings are run as-is; tagged objects
29
+ * (`{ op: 'dropPrimaryKey', table }`) are guarded against partial-apply replay
30
+ * by checking `information_schema` first. MySQL DDL implicitly commits, so
31
+ * if the migration crashes between two DDL statements the version row never
32
+ * gets recorded — and on retry, an unguarded DROP PRIMARY KEY would fail
33
+ * (`ER_CANT_DROP_FIELD_OR_KEY`) because the PK is already gone.
34
+ */
35
+ async function runMigrationStep(conn, step) {
36
+ if (typeof step === "string") {
37
+ await conn.query(step);
38
+ return;
39
+ }
40
+ if (step.op === "dropPrimaryKey") {
41
+ const [rows] = await conn.query(
42
+ `SELECT COUNT(*) AS c FROM information_schema.table_constraints
43
+ WHERE table_schema = DATABASE()
44
+ AND table_name = ?
45
+ AND constraint_type = 'PRIMARY KEY'`,
46
+ [step.table]
47
+ );
48
+ if (rows[0].c > 0) {
49
+ await conn.query(`ALTER TABLE \`${step.table}\` DROP PRIMARY KEY`);
50
+ }
51
+ return;
52
+ }
53
+ throw new Error(`Unknown migration step op: ${JSON.stringify(step)}`);
54
+ }
55
+
56
+ class MysqlMigrationManager {
57
+ constructor(logger = null) {
58
+ this.logger = logger;
59
+ }
60
+
61
+ /**
62
+ * Run all pending migrations. Holds a session-scoped GET_LOCK for the full
63
+ * sequence so concurrent processes serialize.
64
+ *
65
+ * @param {import('mysql2/promise').Pool} pool
66
+ * @param {Buffer|Uint8Array} identity - 33-byte secp256k1 compressed pubkey
67
+ * identifying the tenant. Used to backfill `user_id` columns in the
68
+ * multi-tenant migration so that pre-existing single-tenant data remains
69
+ * readable.
70
+ */
71
+ async migrate(pool, identity) {
72
+ const conn = await pool.getConnection();
73
+ try {
74
+ const [lockRows] = await conn.query(
75
+ "SELECT GET_LOCK(?, ?) AS acquired",
76
+ [MIGRATION_LOCK_NAME, MIGRATION_LOCK_TIMEOUT]
77
+ );
78
+ if (!lockRows || lockRows[0].acquired !== 1) {
79
+ throw new StorageError(
80
+ `Failed to acquire migration lock '${MIGRATION_LOCK_NAME}' within ${MIGRATION_LOCK_TIMEOUT}s`
81
+ );
82
+ }
83
+
84
+ try {
85
+ await conn.query("START TRANSACTION");
86
+
87
+ await conn.query(`
88
+ CREATE TABLE IF NOT EXISTS schema_migrations (
89
+ version INT PRIMARY KEY,
90
+ applied_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6)
91
+ )
92
+ `);
93
+
94
+ const [versionRows] = await conn.query(
95
+ "SELECT COALESCE(MAX(version), 0) AS version FROM schema_migrations"
96
+ );
97
+ const currentVersion = versionRows[0].version;
98
+
99
+ const migrations = this._getMigrations(identity);
100
+
101
+ if (currentVersion >= migrations.length) {
102
+ this._log("info", `Database is up to date (version ${currentVersion})`);
103
+ await conn.query("COMMIT");
104
+ return;
105
+ }
106
+
107
+ this._log(
108
+ "info",
109
+ `Migrating database from version ${currentVersion} to ${migrations.length}`
110
+ );
111
+
112
+ for (let i = currentVersion; i < migrations.length; i++) {
113
+ const migration = migrations[i];
114
+ const version = i + 1;
115
+ this._log("debug", `Running migration ${version}: ${migration.name}`);
116
+
117
+ for (const step of migration.sql) {
118
+ await runMigrationStep(conn, step);
119
+ }
120
+
121
+ await conn.query(
122
+ "INSERT INTO schema_migrations (version) VALUES (?)",
123
+ [version]
124
+ );
125
+ }
126
+
127
+ await conn.query("COMMIT");
128
+ this._log("info", "Database migration completed successfully");
129
+ } catch (error) {
130
+ await conn.query("ROLLBACK").catch(() => {});
131
+ throw new StorageError(
132
+ `Migration failed: ${error.message}`,
133
+ error
134
+ );
135
+ } finally {
136
+ // Release the named lock regardless of outcome.
137
+ await conn
138
+ .query("SELECT RELEASE_LOCK(?)", [MIGRATION_LOCK_NAME])
139
+ .catch(() => {});
140
+ }
141
+ } finally {
142
+ conn.release();
143
+ }
144
+ }
145
+
146
+ _log(level, message) {
147
+ if (this.logger && typeof this.logger.log === "function") {
148
+ this.logger.log({ line: message, level });
149
+ } else if (level === "error") {
150
+ // eslint-disable-next-line no-console
151
+ console.error(`[MysqlMigrationManager] ${message}`);
152
+ }
153
+ }
154
+
155
+ /**
156
+ * @param {Buffer|Uint8Array} identity - 33-byte tenant identity. Inlined as
157
+ * an `UNHEX('...')` literal in the multi-tenant scoping migration. Safe
158
+ * because the bytes come from a typed secp256k1 pubkey (character set
159
+ * `[0-9a-f]{66}` after hex encoding) — not user-controlled input.
160
+ */
161
+ _getMigrations(identity) {
162
+ const idHex = Buffer.from(identity).toString("hex");
163
+ const idLit = `UNHEX('${idHex}')`;
164
+
165
+ // Per-table backfill: ADD COLUMN nullable -> UPDATE -> SET NOT NULL +
166
+ // drop/recreate PK. Returns an array of statements. Tables with backticked
167
+ // names (e.g. `settings` uses `key`) need the caller to backtick `pkCols`.
168
+ const scopeTable = (table, pkCols) => [
169
+ `ALTER TABLE \`${table}\` ADD COLUMN user_id VARBINARY(33) NULL`,
170
+ `UPDATE \`${table}\` SET user_id = ${idLit} WHERE user_id IS NULL`,
171
+ `ALTER TABLE \`${table}\` MODIFY COLUMN user_id VARBINARY(33) NOT NULL`,
172
+ `ALTER TABLE \`${table}\` DROP PRIMARY KEY, ADD PRIMARY KEY (user_id, ${pkCols})`,
173
+ ];
174
+
175
+ return [
176
+ {
177
+ name: "Create all tables at final schema",
178
+ sql: [
179
+ // -- Core tables --
180
+ `CREATE TABLE IF NOT EXISTS payments (
181
+ id VARCHAR(255) NOT NULL PRIMARY KEY,
182
+ payment_type VARCHAR(64) NOT NULL,
183
+ status VARCHAR(64) NOT NULL,
184
+ amount VARCHAR(64) NOT NULL,
185
+ fees VARCHAR(64) NOT NULL,
186
+ timestamp BIGINT NOT NULL,
187
+ method VARCHAR(64) NULL,
188
+ withdraw_tx_id VARCHAR(255) NULL,
189
+ deposit_tx_id VARCHAR(255) NULL,
190
+ spark TINYINT(1) NULL
191
+ )`,
192
+
193
+ `CREATE TABLE IF NOT EXISTS settings (
194
+ \`key\` VARCHAR(255) NOT NULL PRIMARY KEY,
195
+ value LONGTEXT NOT NULL
196
+ )`,
197
+
198
+ `CREATE TABLE IF NOT EXISTS unclaimed_deposits (
199
+ txid VARCHAR(255) NOT NULL,
200
+ vout INT NOT NULL,
201
+ amount_sats BIGINT NULL,
202
+ claim_error JSON NULL,
203
+ refund_tx LONGTEXT NULL,
204
+ refund_tx_id VARCHAR(255) NULL,
205
+ PRIMARY KEY (txid, vout)
206
+ )`,
207
+
208
+ `CREATE TABLE IF NOT EXISTS payment_metadata (
209
+ payment_id VARCHAR(255) NOT NULL PRIMARY KEY,
210
+ parent_payment_id VARCHAR(255) NULL,
211
+ lnurl_pay_info JSON NULL,
212
+ lnurl_withdraw_info JSON NULL,
213
+ lnurl_description LONGTEXT NULL,
214
+ conversion_info JSON NULL
215
+ )`,
216
+
217
+ `CREATE TABLE IF NOT EXISTS payment_details_lightning (
218
+ payment_id VARCHAR(255) NOT NULL PRIMARY KEY,
219
+ invoice LONGTEXT NOT NULL,
220
+ payment_hash VARCHAR(255) NOT NULL,
221
+ destination_pubkey VARCHAR(255) NOT NULL,
222
+ description LONGTEXT NULL,
223
+ preimage VARCHAR(255) NULL,
224
+ htlc_status VARCHAR(64) NOT NULL,
225
+ htlc_expiry_time BIGINT NOT NULL
226
+ )`,
227
+
228
+ `CREATE TABLE IF NOT EXISTS payment_details_token (
229
+ payment_id VARCHAR(255) NOT NULL PRIMARY KEY,
230
+ metadata JSON NOT NULL,
231
+ tx_hash VARCHAR(255) NOT NULL,
232
+ tx_type VARCHAR(64) NOT NULL,
233
+ invoice_details JSON NULL
234
+ )`,
235
+
236
+ `CREATE TABLE IF NOT EXISTS payment_details_spark (
237
+ payment_id VARCHAR(255) NOT NULL PRIMARY KEY,
238
+ invoice_details JSON NULL,
239
+ htlc_details JSON NULL
240
+ )`,
241
+
242
+ `CREATE TABLE IF NOT EXISTS lnurl_receive_metadata (
243
+ payment_hash VARCHAR(255) NOT NULL PRIMARY KEY,
244
+ nostr_zap_request LONGTEXT NULL,
245
+ nostr_zap_receipt LONGTEXT NULL,
246
+ sender_comment LONGTEXT NULL
247
+ )`,
248
+
249
+ // -- Sync tables --
250
+ `CREATE TABLE IF NOT EXISTS sync_revision (
251
+ id INT NOT NULL PRIMARY KEY DEFAULT 1,
252
+ revision BIGINT NOT NULL DEFAULT 0,
253
+ CHECK (id = 1)
254
+ )`,
255
+ `INSERT INTO sync_revision (id, revision) VALUES (1, 0)
256
+ ON DUPLICATE KEY UPDATE id = id`,
257
+
258
+ `CREATE TABLE IF NOT EXISTS sync_outgoing (
259
+ record_type VARCHAR(255) NOT NULL,
260
+ data_id VARCHAR(255) NOT NULL,
261
+ schema_version VARCHAR(64) NOT NULL,
262
+ commit_time BIGINT NOT NULL,
263
+ updated_fields_json JSON NOT NULL,
264
+ revision BIGINT NOT NULL
265
+ )`,
266
+
267
+ `CREATE TABLE IF NOT EXISTS sync_state (
268
+ record_type VARCHAR(255) NOT NULL,
269
+ data_id VARCHAR(255) NOT NULL,
270
+ schema_version VARCHAR(64) NOT NULL,
271
+ commit_time BIGINT NOT NULL,
272
+ data JSON NOT NULL,
273
+ revision BIGINT NOT NULL,
274
+ PRIMARY KEY (record_type, data_id)
275
+ )`,
276
+
277
+ `CREATE TABLE IF NOT EXISTS sync_incoming (
278
+ record_type VARCHAR(255) NOT NULL,
279
+ data_id VARCHAR(255) NOT NULL,
280
+ schema_version VARCHAR(64) NOT NULL,
281
+ commit_time BIGINT NOT NULL,
282
+ data JSON NOT NULL,
283
+ revision BIGINT NOT NULL,
284
+ PRIMARY KEY (record_type, data_id, revision)
285
+ )`,
286
+
287
+ // -- Indexes --
288
+ `CREATE INDEX idx_payments_timestamp ON payments(timestamp)`,
289
+ `CREATE INDEX idx_payments_payment_type ON payments(payment_type)`,
290
+ `CREATE INDEX idx_payments_status ON payments(status)`,
291
+ `CREATE INDEX idx_payment_details_lightning_invoice
292
+ ON payment_details_lightning(invoice(255))`,
293
+ `CREATE INDEX idx_payment_details_lightning_payment_hash
294
+ ON payment_details_lightning(payment_hash)`,
295
+ `CREATE INDEX idx_payment_metadata_parent ON payment_metadata(parent_payment_id)`,
296
+ `CREATE INDEX idx_sync_outgoing_data_id_record_type
297
+ ON sync_outgoing(record_type, data_id)`,
298
+ `CREATE INDEX idx_sync_incoming_revision ON sync_incoming(revision)`,
299
+ ],
300
+ },
301
+ {
302
+ name: "Create contacts table",
303
+ sql: [
304
+ `CREATE TABLE IF NOT EXISTS contacts (
305
+ id VARCHAR(255) NOT NULL PRIMARY KEY,
306
+ name VARCHAR(255) NOT NULL,
307
+ payment_identifier VARCHAR(255) NOT NULL,
308
+ created_at BIGINT NOT NULL,
309
+ updated_at BIGINT NOT NULL
310
+ )`,
311
+ ],
312
+ },
313
+ {
314
+ name: "Add is_mature to unclaimed_deposits",
315
+ sql: [
316
+ `ALTER TABLE unclaimed_deposits ADD COLUMN is_mature TINYINT(1) NOT NULL DEFAULT 1`,
317
+ ],
318
+ },
319
+ {
320
+ name: "Add conversion_status to payment_metadata",
321
+ sql: [
322
+ `ALTER TABLE payment_metadata ADD COLUMN conversion_status VARCHAR(64) NULL`,
323
+ ],
324
+ },
325
+ {
326
+ name: "Multi-tenant scoping: add user_id and rewrite primary keys",
327
+ sql: [
328
+ // Per-user tables.
329
+ ...scopeTable("payments", "id"),
330
+ `DROP INDEX idx_payments_timestamp ON payments`,
331
+ `DROP INDEX idx_payments_payment_type ON payments`,
332
+ `DROP INDEX idx_payments_status ON payments`,
333
+ `CREATE INDEX idx_payments_user_timestamp ON payments(user_id, timestamp)`,
334
+ `CREATE INDEX idx_payments_user_payment_type ON payments(user_id, payment_type)`,
335
+ `CREATE INDEX idx_payments_user_status ON payments(user_id, status)`,
336
+
337
+ ...scopeTable("payment_metadata", "payment_id"),
338
+ `DROP INDEX idx_payment_metadata_parent ON payment_metadata`,
339
+ `CREATE INDEX idx_payment_metadata_user_parent
340
+ ON payment_metadata(user_id, parent_payment_id)`,
341
+
342
+ ...scopeTable("payment_details_lightning", "payment_id"),
343
+ `DROP INDEX idx_payment_details_lightning_invoice ON payment_details_lightning`,
344
+ `DROP INDEX idx_payment_details_lightning_payment_hash ON payment_details_lightning`,
345
+ `CREATE INDEX idx_payment_details_lightning_user_invoice
346
+ ON payment_details_lightning(user_id, invoice(255))`,
347
+ `CREATE INDEX idx_payment_details_lightning_user_payment_hash
348
+ ON payment_details_lightning(user_id, payment_hash)`,
349
+
350
+ ...scopeTable("payment_details_token", "payment_id"),
351
+ ...scopeTable("payment_details_spark", "payment_id"),
352
+ ...scopeTable("lnurl_receive_metadata", "payment_hash"),
353
+ ...scopeTable("unclaimed_deposits", "txid, vout"),
354
+ ...scopeTable("contacts", "id"),
355
+ ...scopeTable("settings", "`key`"),
356
+
357
+ // sync_revision was a singleton (PK id=1, CHECK id=1). Drop the PK
358
+ // and the id column, then re-key by user_id.
359
+ { op: "dropPrimaryKey", table: "sync_revision" },
360
+ `ALTER TABLE sync_revision DROP COLUMN id`,
361
+ `ALTER TABLE sync_revision ADD COLUMN user_id VARBINARY(33) NULL`,
362
+ `UPDATE sync_revision SET user_id = ${idLit} WHERE user_id IS NULL`,
363
+ `ALTER TABLE sync_revision MODIFY COLUMN user_id VARBINARY(33) NOT NULL`,
364
+ `ALTER TABLE sync_revision ADD PRIMARY KEY (user_id)`,
365
+
366
+ // sync_outgoing has no PK, only an index — just add user_id and
367
+ // rewrite the index.
368
+ `ALTER TABLE sync_outgoing ADD COLUMN user_id VARBINARY(33) NULL`,
369
+ `UPDATE sync_outgoing SET user_id = ${idLit} WHERE user_id IS NULL`,
370
+ `ALTER TABLE sync_outgoing MODIFY COLUMN user_id VARBINARY(33) NOT NULL`,
371
+ `DROP INDEX idx_sync_outgoing_data_id_record_type ON sync_outgoing`,
372
+ `CREATE INDEX idx_sync_outgoing_user_record_type_data_id
373
+ ON sync_outgoing(user_id, record_type, data_id)`,
374
+
375
+ ...scopeTable("sync_state", "record_type, data_id"),
376
+
377
+ ...scopeTable("sync_incoming", "record_type, data_id, revision"),
378
+ `DROP INDEX idx_sync_incoming_revision ON sync_incoming`,
379
+ `CREATE INDEX idx_sync_incoming_user_revision
380
+ ON sync_incoming(user_id, revision)`,
381
+ ],
382
+ },
383
+ ];
384
+ }
385
+ }
386
+
387
+ module.exports = { MysqlMigrationManager };
@@ -0,0 +1,9 @@
1
+ {
2
+ "dependencies": {
3
+ "mysql2": "^3.11.0"
4
+ },
5
+ "description": "Node.js MySQL storage implementation for Breez SDK WASM (CommonJS)",
6
+ "main": "index.cjs",
7
+ "name": "@breez-sdk/mysql-storage",
8
+ "version": "1.0.0"
9
+ }
@@ -0,0 +1,9 @@
1
+ class TokenStoreError extends Error {
2
+ constructor(message, cause = null) {
3
+ super(message);
4
+ this.name = 'TokenStoreError';
5
+ this.cause = cause;
6
+ }
7
+ }
8
+
9
+ module.exports = { TokenStoreError };