@breeztech/breez-sdk-spark 0.7.19 → 0.8.0-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.
- package/breez-sdk-spark.tgz +0 -0
- package/bundler/breez_sdk_spark_wasm.d.ts +581 -532
- package/bundler/breez_sdk_spark_wasm_bg.js +74 -51
- package/bundler/breez_sdk_spark_wasm_bg.wasm +0 -0
- package/bundler/breez_sdk_spark_wasm_bg.wasm.d.ts +4 -2
- package/bundler/storage/index.js +550 -103
- package/deno/breez_sdk_spark_wasm.d.ts +581 -532
- package/deno/breez_sdk_spark_wasm.js +72 -50
- package/deno/breez_sdk_spark_wasm_bg.wasm +0 -0
- package/deno/breez_sdk_spark_wasm_bg.wasm.d.ts +4 -2
- package/nodejs/breez_sdk_spark_wasm.d.ts +581 -532
- package/nodejs/breez_sdk_spark_wasm.js +74 -51
- package/nodejs/breez_sdk_spark_wasm_bg.wasm +0 -0
- package/nodejs/breez_sdk_spark_wasm_bg.wasm.d.ts +4 -2
- package/nodejs/storage/index.cjs +175 -149
- package/nodejs/storage/migrations.cjs +27 -3
- package/package.json +1 -1
- package/web/breez_sdk_spark_wasm.d.ts +585 -534
- package/web/breez_sdk_spark_wasm.js +72 -50
- package/web/breez_sdk_spark_wasm_bg.wasm +0 -0
- package/web/breez_sdk_spark_wasm_bg.wasm.d.ts +4 -2
- package/web/storage/index.js +550 -103
package/nodejs/storage/index.cjs
CHANGED
|
@@ -27,6 +27,47 @@ try {
|
|
|
27
27
|
const { StorageError } = require("./errors.cjs");
|
|
28
28
|
const { MigrationManager } = require("./migrations.cjs");
|
|
29
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Base query for payment lookups.
|
|
32
|
+
* All columns are accessed by name in _rowToPayment.
|
|
33
|
+
* parent_payment_id is only used by getPaymentsByParentIds.
|
|
34
|
+
*/
|
|
35
|
+
const SELECT_PAYMENT_SQL = `
|
|
36
|
+
SELECT p.id,
|
|
37
|
+
p.payment_type,
|
|
38
|
+
p.status,
|
|
39
|
+
p.amount,
|
|
40
|
+
p.fees,
|
|
41
|
+
p.timestamp,
|
|
42
|
+
p.method,
|
|
43
|
+
p.withdraw_tx_id,
|
|
44
|
+
p.deposit_tx_id,
|
|
45
|
+
p.spark,
|
|
46
|
+
l.invoice AS lightning_invoice,
|
|
47
|
+
l.payment_hash AS lightning_payment_hash,
|
|
48
|
+
l.destination_pubkey AS lightning_destination_pubkey,
|
|
49
|
+
COALESCE(l.description, pm.lnurl_description) AS lightning_description,
|
|
50
|
+
l.preimage AS lightning_preimage,
|
|
51
|
+
pm.lnurl_pay_info,
|
|
52
|
+
pm.lnurl_withdraw_info,
|
|
53
|
+
pm.conversion_info,
|
|
54
|
+
t.metadata AS token_metadata,
|
|
55
|
+
t.tx_hash AS token_tx_hash,
|
|
56
|
+
t.tx_type AS token_tx_type,
|
|
57
|
+
t.invoice_details AS token_invoice_details,
|
|
58
|
+
s.invoice_details AS spark_invoice_details,
|
|
59
|
+
s.htlc_details AS spark_htlc_details,
|
|
60
|
+
lrm.nostr_zap_request AS lnurl_nostr_zap_request,
|
|
61
|
+
lrm.nostr_zap_receipt AS lnurl_nostr_zap_receipt,
|
|
62
|
+
lrm.sender_comment AS lnurl_sender_comment,
|
|
63
|
+
pm.parent_payment_id
|
|
64
|
+
FROM payments p
|
|
65
|
+
LEFT JOIN payment_details_lightning l ON p.id = l.payment_id
|
|
66
|
+
LEFT JOIN payment_details_token t ON p.id = t.payment_id
|
|
67
|
+
LEFT JOIN payment_details_spark s ON p.id = s.payment_id
|
|
68
|
+
LEFT JOIN payment_metadata pm ON p.id = pm.payment_id
|
|
69
|
+
LEFT JOIN lnurl_receive_metadata lrm ON l.payment_hash = lrm.payment_hash`;
|
|
70
|
+
|
|
30
71
|
class SqliteStorage {
|
|
31
72
|
constructor(dbPath, logger = null) {
|
|
32
73
|
this.dbPath = dbPath;
|
|
@@ -196,6 +237,14 @@ class SqliteStorage {
|
|
|
196
237
|
paymentDetailsClauses.push("t.tx_hash = ?");
|
|
197
238
|
params.push(paymentDetailsFilter.txHash);
|
|
198
239
|
}
|
|
240
|
+
// Filter by token transaction type
|
|
241
|
+
if (
|
|
242
|
+
paymentDetailsFilter.type === "token" &&
|
|
243
|
+
paymentDetailsFilter.txType !== undefined
|
|
244
|
+
) {
|
|
245
|
+
paymentDetailsClauses.push("t.tx_type = ?");
|
|
246
|
+
params.push(paymentDetailsFilter.txType);
|
|
247
|
+
}
|
|
199
248
|
|
|
200
249
|
if (paymentDetailsClauses.length > 0) {
|
|
201
250
|
allPaymentDetailsClauses.push(`(${paymentDetailsClauses.join(" AND ")})`);
|
|
@@ -221,50 +270,16 @@ class SqliteStorage {
|
|
|
221
270
|
}
|
|
222
271
|
}
|
|
223
272
|
|
|
273
|
+
// Exclude child payments (those with a parent_payment_id)
|
|
274
|
+
whereClauses.push("pm.parent_payment_id IS NULL");
|
|
275
|
+
|
|
224
276
|
// Build the WHERE clause
|
|
225
277
|
const whereSql =
|
|
226
278
|
whereClauses.length > 0 ? `WHERE ${whereClauses.join(" AND ")}` : "";
|
|
227
279
|
|
|
228
280
|
// Determine sort order
|
|
229
281
|
const orderDirection = request.sortAscending ? "ASC" : "DESC";
|
|
230
|
-
|
|
231
|
-
const query = `
|
|
232
|
-
SELECT p.id
|
|
233
|
-
, p.payment_type
|
|
234
|
-
, p.status
|
|
235
|
-
, p.amount
|
|
236
|
-
, p.fees
|
|
237
|
-
, p.timestamp
|
|
238
|
-
, p.method
|
|
239
|
-
, p.withdraw_tx_id
|
|
240
|
-
, p.deposit_tx_id
|
|
241
|
-
, p.spark
|
|
242
|
-
, l.invoice AS lightning_invoice
|
|
243
|
-
, l.payment_hash AS lightning_payment_hash
|
|
244
|
-
, l.destination_pubkey AS lightning_destination_pubkey
|
|
245
|
-
, COALESCE(l.description, pm.lnurl_description) AS lightning_description
|
|
246
|
-
, l.preimage AS lightning_preimage
|
|
247
|
-
, pm.lnurl_pay_info
|
|
248
|
-
, pm.lnurl_withdraw_info
|
|
249
|
-
, pm.conversion_info
|
|
250
|
-
, t.metadata AS token_metadata
|
|
251
|
-
, t.tx_hash AS token_tx_hash
|
|
252
|
-
, t.invoice_details AS token_invoice_details
|
|
253
|
-
, s.invoice_details AS spark_invoice_details
|
|
254
|
-
, s.htlc_details AS spark_htlc_details
|
|
255
|
-
, lrm.nostr_zap_request AS lnurl_nostr_zap_request
|
|
256
|
-
, lrm.nostr_zap_receipt AS lnurl_nostr_zap_receipt
|
|
257
|
-
, lrm.sender_comment AS lnurl_sender_comment
|
|
258
|
-
FROM payments p
|
|
259
|
-
LEFT JOIN payment_details_lightning l ON p.id = l.payment_id
|
|
260
|
-
LEFT JOIN payment_details_token t ON p.id = t.payment_id
|
|
261
|
-
LEFT JOIN payment_details_spark s ON p.id = s.payment_id
|
|
262
|
-
LEFT JOIN payment_metadata pm ON p.id = pm.payment_id
|
|
263
|
-
LEFT JOIN lnurl_receive_metadata lrm ON l.payment_hash = lrm.payment_hash
|
|
264
|
-
${whereSql}
|
|
265
|
-
ORDER BY p.timestamp ${orderDirection}
|
|
266
|
-
LIMIT ? OFFSET ?
|
|
267
|
-
`;
|
|
282
|
+
const query = `${SELECT_PAYMENT_SQL} ${whereSql} ORDER BY p.timestamp ${orderDirection} LIMIT ? OFFSET ?`;
|
|
268
283
|
|
|
269
284
|
params.push(actualLimit, actualOffset);
|
|
270
285
|
const stmt = this.db.prepare(query);
|
|
@@ -318,11 +333,12 @@ class SqliteStorage {
|
|
|
318
333
|
);
|
|
319
334
|
const tokenInsert = this.db.prepare(
|
|
320
335
|
`INSERT INTO payment_details_token
|
|
321
|
-
(payment_id, metadata, tx_hash, invoice_details)
|
|
322
|
-
VALUES (@id, @metadata, @txHash, @invoiceDetails)
|
|
336
|
+
(payment_id, metadata, tx_hash, tx_type, invoice_details)
|
|
337
|
+
VALUES (@id, @metadata, @txHash, @txType, @invoiceDetails)
|
|
323
338
|
ON CONFLICT(payment_id) DO UPDATE SET
|
|
324
339
|
metadata=excluded.metadata,
|
|
325
340
|
tx_hash=excluded.tx_hash,
|
|
341
|
+
tx_type=excluded.tx_type,
|
|
326
342
|
invoice_details=COALESCE(excluded.invoice_details, payment_details_token.invoice_details)`
|
|
327
343
|
);
|
|
328
344
|
const sparkInsert = this.db.prepare(
|
|
@@ -381,6 +397,7 @@ class SqliteStorage {
|
|
|
381
397
|
id: payment.id,
|
|
382
398
|
metadata: JSON.stringify(payment.details.metadata),
|
|
383
399
|
txHash: payment.details.txHash,
|
|
400
|
+
txType: payment.details.txType,
|
|
384
401
|
invoiceDetails: payment.details.invoiceDetails
|
|
385
402
|
? JSON.stringify(payment.details.invoiceDetails)
|
|
386
403
|
: null,
|
|
@@ -408,43 +425,9 @@ class SqliteStorage {
|
|
|
408
425
|
);
|
|
409
426
|
}
|
|
410
427
|
|
|
411
|
-
const stmt = this.db.prepare(
|
|
412
|
-
SELECT p.id
|
|
413
|
-
, p.payment_type
|
|
414
|
-
, p.status
|
|
415
|
-
, p.amount
|
|
416
|
-
, p.fees
|
|
417
|
-
, p.timestamp
|
|
418
|
-
, p.method
|
|
419
|
-
, p.withdraw_tx_id
|
|
420
|
-
, p.deposit_tx_id
|
|
421
|
-
, p.spark
|
|
422
|
-
, l.invoice AS lightning_invoice
|
|
423
|
-
, l.payment_hash AS lightning_payment_hash
|
|
424
|
-
, l.destination_pubkey AS lightning_destination_pubkey
|
|
425
|
-
, COALESCE(l.description, pm.lnurl_description) AS lightning_description
|
|
426
|
-
, l.preimage AS lightning_preimage
|
|
427
|
-
, pm.lnurl_pay_info
|
|
428
|
-
, pm.lnurl_withdraw_info
|
|
429
|
-
, pm.conversion_info
|
|
430
|
-
, t.metadata AS token_metadata
|
|
431
|
-
, t.tx_hash AS token_tx_hash
|
|
432
|
-
, t.invoice_details AS token_invoice_details
|
|
433
|
-
, s.invoice_details AS spark_invoice_details
|
|
434
|
-
, s.htlc_details AS spark_htlc_details
|
|
435
|
-
, lrm.nostr_zap_request AS lnurl_nostr_zap_request
|
|
436
|
-
, lrm.nostr_zap_receipt AS lnurl_nostr_zap_receipt
|
|
437
|
-
, lrm.sender_comment AS lnurl_sender_comment
|
|
438
|
-
FROM payments p
|
|
439
|
-
LEFT JOIN payment_details_lightning l ON p.id = l.payment_id
|
|
440
|
-
LEFT JOIN payment_details_token t ON p.id = t.payment_id
|
|
441
|
-
LEFT JOIN payment_details_spark s ON p.id = s.payment_id
|
|
442
|
-
LEFT JOIN payment_metadata pm ON p.id = pm.payment_id
|
|
443
|
-
LEFT JOIN lnurl_receive_metadata lrm ON l.payment_hash = lrm.payment_hash
|
|
444
|
-
WHERE p.id = ?
|
|
445
|
-
`);
|
|
446
|
-
|
|
428
|
+
const stmt = this.db.prepare(`${SELECT_PAYMENT_SQL} WHERE p.id = ?`);
|
|
447
429
|
const row = stmt.get(id);
|
|
430
|
+
|
|
448
431
|
if (!row) {
|
|
449
432
|
return Promise.reject(
|
|
450
433
|
new StorageError(`Payment with id '${id}' not found`)
|
|
@@ -472,43 +455,9 @@ class SqliteStorage {
|
|
|
472
455
|
);
|
|
473
456
|
}
|
|
474
457
|
|
|
475
|
-
const stmt = this.db.prepare(
|
|
476
|
-
SELECT p.id
|
|
477
|
-
, p.payment_type
|
|
478
|
-
, p.status
|
|
479
|
-
, p.amount
|
|
480
|
-
, p.fees
|
|
481
|
-
, p.timestamp
|
|
482
|
-
, p.method
|
|
483
|
-
, p.withdraw_tx_id
|
|
484
|
-
, p.deposit_tx_id
|
|
485
|
-
, p.spark
|
|
486
|
-
, l.invoice AS lightning_invoice
|
|
487
|
-
, l.payment_hash AS lightning_payment_hash
|
|
488
|
-
, l.destination_pubkey AS lightning_destination_pubkey
|
|
489
|
-
, COALESCE(l.description, pm.lnurl_description) AS lightning_description
|
|
490
|
-
, l.preimage AS lightning_preimage
|
|
491
|
-
, pm.lnurl_pay_info
|
|
492
|
-
, pm.lnurl_withdraw_info
|
|
493
|
-
, pm.conversion_info
|
|
494
|
-
, t.metadata AS token_metadata
|
|
495
|
-
, t.tx_hash AS token_tx_hash
|
|
496
|
-
, t.invoice_details AS token_invoice_details
|
|
497
|
-
, s.invoice_details AS spark_invoice_details
|
|
498
|
-
, s.htlc_details AS spark_htlc_details
|
|
499
|
-
, lrm.nostr_zap_request AS lnurl_nostr_zap_request
|
|
500
|
-
, lrm.nostr_zap_receipt AS lnurl_nostr_zap_receipt
|
|
501
|
-
, lrm.sender_comment AS lnurl_sender_comment
|
|
502
|
-
FROM payments p
|
|
503
|
-
LEFT JOIN payment_details_lightning l ON p.id = l.payment_id
|
|
504
|
-
LEFT JOIN payment_details_token t ON p.id = t.payment_id
|
|
505
|
-
LEFT JOIN payment_details_spark s ON p.id = s.payment_id
|
|
506
|
-
LEFT JOIN payment_metadata pm ON p.id = pm.payment_id
|
|
507
|
-
LEFT JOIN lnurl_receive_metadata lrm ON l.payment_hash = lrm.payment_hash
|
|
508
|
-
WHERE l.invoice = ?
|
|
509
|
-
`);
|
|
510
|
-
|
|
458
|
+
const stmt = this.db.prepare(`${SELECT_PAYMENT_SQL} WHERE l.invoice = ?`);
|
|
511
459
|
const row = stmt.get(invoice);
|
|
460
|
+
|
|
512
461
|
if (!row) {
|
|
513
462
|
return Promise.resolve(null);
|
|
514
463
|
}
|
|
@@ -526,11 +475,66 @@ class SqliteStorage {
|
|
|
526
475
|
}
|
|
527
476
|
}
|
|
528
477
|
|
|
529
|
-
|
|
478
|
+
/**
|
|
479
|
+
* Gets payments that have any of the specified parent payment IDs.
|
|
480
|
+
* @param {string[]} parentPaymentIds - Array of parent payment IDs
|
|
481
|
+
* @returns {Promise<Object>} Map of parentPaymentId -> array of RelatedPayment objects
|
|
482
|
+
*/
|
|
483
|
+
getPaymentsByParentIds(parentPaymentIds) {
|
|
484
|
+
try {
|
|
485
|
+
if (!parentPaymentIds || parentPaymentIds.length === 0) {
|
|
486
|
+
return Promise.resolve({});
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// Early exit if no related payments exist
|
|
490
|
+
const hasRelated = this.db
|
|
491
|
+
.prepare(
|
|
492
|
+
"SELECT EXISTS(SELECT 1 FROM payment_metadata WHERE parent_payment_id IS NOT NULL LIMIT 1)"
|
|
493
|
+
)
|
|
494
|
+
.pluck()
|
|
495
|
+
.get();
|
|
496
|
+
if (!hasRelated) {
|
|
497
|
+
return Promise.resolve({});
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
const placeholders = parentPaymentIds.map(() => "?").join(", ");
|
|
501
|
+
const query = `${SELECT_PAYMENT_SQL} WHERE pm.parent_payment_id IN (${placeholders}) ORDER BY p.timestamp ASC`;
|
|
502
|
+
|
|
503
|
+
const stmt = this.db.prepare(query);
|
|
504
|
+
const rows = stmt.all(...parentPaymentIds);
|
|
505
|
+
|
|
506
|
+
// Group payments by parent_payment_id
|
|
507
|
+
const result = {};
|
|
508
|
+
for (const row of rows) {
|
|
509
|
+
const parentId = row.parent_payment_id;
|
|
510
|
+
if (!result[parentId]) {
|
|
511
|
+
result[parentId] = [];
|
|
512
|
+
}
|
|
513
|
+
result[parentId].push(this._rowToPayment(row));
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
return Promise.resolve(result);
|
|
517
|
+
} catch (error) {
|
|
518
|
+
return Promise.reject(
|
|
519
|
+
new StorageError(
|
|
520
|
+
`Failed to get payments by parent ids: ${error.message}`,
|
|
521
|
+
error
|
|
522
|
+
)
|
|
523
|
+
);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
insertPaymentMetadata(paymentId, metadata) {
|
|
530
528
|
try {
|
|
531
529
|
const stmt = this.db.prepare(`
|
|
532
|
-
INSERT
|
|
530
|
+
INSERT INTO payment_metadata (payment_id, parent_payment_id, lnurl_pay_info, lnurl_withdraw_info, lnurl_description, conversion_info)
|
|
533
531
|
VALUES (?, ?, ?, ?, ?, ?)
|
|
532
|
+
ON CONFLICT(payment_id) DO UPDATE SET
|
|
533
|
+
parent_payment_id = COALESCE(excluded.parent_payment_id, parent_payment_id),
|
|
534
|
+
lnurl_pay_info = COALESCE(excluded.lnurl_pay_info, lnurl_pay_info),
|
|
535
|
+
lnurl_withdraw_info = COALESCE(excluded.lnurl_withdraw_info, lnurl_withdraw_info),
|
|
536
|
+
lnurl_description = COALESCE(excluded.lnurl_description, lnurl_description),
|
|
537
|
+
conversion_info = COALESCE(excluded.conversion_info, conversion_info)
|
|
534
538
|
`);
|
|
535
539
|
|
|
536
540
|
stmt.run(
|
|
@@ -755,6 +759,7 @@ class SqliteStorage {
|
|
|
755
759
|
type: "token",
|
|
756
760
|
metadata: JSON.parse(row.token_metadata),
|
|
757
761
|
txHash: row.token_tx_hash,
|
|
762
|
+
txType: row.token_tx_type,
|
|
758
763
|
invoiceDetails: row.token_invoice_details
|
|
759
764
|
? JSON.parse(row.token_invoice_details)
|
|
760
765
|
: null,
|
|
@@ -793,11 +798,14 @@ class SqliteStorage {
|
|
|
793
798
|
syncAddOutgoingChange(record) {
|
|
794
799
|
try {
|
|
795
800
|
const transaction = this.db.transaction(() => {
|
|
796
|
-
//
|
|
801
|
+
// Compute next revision as max(committed, max outgoing) + 1, without updating sync_revision
|
|
797
802
|
const revisionQuery = this.db.prepare(`
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
803
|
+
SELECT CAST(
|
|
804
|
+
MAX(
|
|
805
|
+
(SELECT revision FROM sync_revision),
|
|
806
|
+
COALESCE((SELECT MAX(revision) FROM sync_outgoing), 0)
|
|
807
|
+
) + 1
|
|
808
|
+
AS TEXT) AS revision
|
|
801
809
|
`);
|
|
802
810
|
const revision = BigInt(revisionQuery.get().revision);
|
|
803
811
|
|
|
@@ -836,7 +844,7 @@ class SqliteStorage {
|
|
|
836
844
|
}
|
|
837
845
|
}
|
|
838
846
|
|
|
839
|
-
syncCompleteOutgoingSync(record) {
|
|
847
|
+
syncCompleteOutgoingSync(record, localRevision) {
|
|
840
848
|
try {
|
|
841
849
|
const transaction = this.db.transaction(() => {
|
|
842
850
|
// Delete records that have been synced
|
|
@@ -848,7 +856,7 @@ class SqliteStorage {
|
|
|
848
856
|
deleteStmt.run(
|
|
849
857
|
record.id.type,
|
|
850
858
|
record.id.dataId,
|
|
851
|
-
|
|
859
|
+
localRevision.toString()
|
|
852
860
|
);
|
|
853
861
|
|
|
854
862
|
// Update or insert the sync state
|
|
@@ -871,6 +879,12 @@ class SqliteStorage {
|
|
|
871
879
|
Math.floor(Date.now() / 1000),
|
|
872
880
|
JSON.stringify(record.data)
|
|
873
881
|
);
|
|
882
|
+
|
|
883
|
+
// Update sync_revision to track the highest known revision
|
|
884
|
+
const updateRevisionStmt = this.db.prepare(`
|
|
885
|
+
UPDATE sync_revision SET revision = MAX(revision, CAST(? AS INTEGER))
|
|
886
|
+
`);
|
|
887
|
+
updateRevisionStmt.run(record.revision.toString());
|
|
874
888
|
});
|
|
875
889
|
|
|
876
890
|
transaction();
|
|
@@ -953,7 +967,7 @@ class SqliteStorage {
|
|
|
953
967
|
syncGetLastRevision() {
|
|
954
968
|
try {
|
|
955
969
|
const stmt = this.db.prepare(
|
|
956
|
-
`SELECT CAST(
|
|
970
|
+
`SELECT CAST(revision AS TEXT) as revision FROM sync_revision`
|
|
957
971
|
);
|
|
958
972
|
const row = stmt.get();
|
|
959
973
|
|
|
@@ -1031,9 +1045,9 @@ class SqliteStorage {
|
|
|
1031
1045
|
syncRebasePendingOutgoingRecords(revision) {
|
|
1032
1046
|
try {
|
|
1033
1047
|
const transaction = this.db.transaction(() => {
|
|
1034
|
-
// Get current revision
|
|
1048
|
+
// Get current committed revision from sync_revision table
|
|
1035
1049
|
const getLastRevisionStmt = this.db.prepare(`
|
|
1036
|
-
SELECT CAST(
|
|
1050
|
+
SELECT CAST(revision AS TEXT) as last_revision FROM sync_revision
|
|
1037
1051
|
`);
|
|
1038
1052
|
const revisionRow = getLastRevisionStmt.get();
|
|
1039
1053
|
const lastRevision = revisionRow
|
|
@@ -1044,17 +1058,20 @@ class SqliteStorage {
|
|
|
1044
1058
|
const diff =
|
|
1045
1059
|
revision > lastRevision ? revision - lastRevision : BigInt(0);
|
|
1046
1060
|
|
|
1047
|
-
if (diff
|
|
1048
|
-
|
|
1061
|
+
if (diff > BigInt(0)) {
|
|
1062
|
+
// Update all pending outgoing records
|
|
1063
|
+
const updateRecordsStmt = this.db.prepare(`
|
|
1064
|
+
UPDATE sync_outgoing
|
|
1065
|
+
SET revision = revision + CAST(? AS INTEGER)
|
|
1066
|
+
`);
|
|
1067
|
+
updateRecordsStmt.run(diff.toString());
|
|
1049
1068
|
}
|
|
1050
1069
|
|
|
1051
|
-
// Update
|
|
1052
|
-
const
|
|
1053
|
-
UPDATE
|
|
1054
|
-
SET revision = revision + CAST(? AS INTEGER)
|
|
1070
|
+
// Update sync_revision within the same transaction so retries are idempotent
|
|
1071
|
+
const updateRevisionStmt = this.db.prepare(`
|
|
1072
|
+
UPDATE sync_revision SET revision = MAX(revision, CAST(? AS INTEGER))
|
|
1055
1073
|
`);
|
|
1056
|
-
|
|
1057
|
-
updateRecordsStmt.run(diff.toString());
|
|
1074
|
+
updateRevisionStmt.run(revision.toString());
|
|
1058
1075
|
});
|
|
1059
1076
|
|
|
1060
1077
|
transaction();
|
|
@@ -1206,26 +1223,35 @@ class SqliteStorage {
|
|
|
1206
1223
|
|
|
1207
1224
|
syncUpdateRecordFromIncoming(record) {
|
|
1208
1225
|
try {
|
|
1209
|
-
const
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1226
|
+
const transaction = this.db.transaction(() => {
|
|
1227
|
+
const stmt = this.db.prepare(`
|
|
1228
|
+
INSERT OR REPLACE INTO sync_state (
|
|
1229
|
+
record_type,
|
|
1230
|
+
data_id,
|
|
1231
|
+
revision,
|
|
1232
|
+
schema_version,
|
|
1233
|
+
commit_time,
|
|
1234
|
+
data
|
|
1235
|
+
) VALUES (?, ?, CAST(? AS INTEGER), ?, ?, ?)
|
|
1236
|
+
`);
|
|
1219
1237
|
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1238
|
+
stmt.run(
|
|
1239
|
+
record.id.type,
|
|
1240
|
+
record.id.dataId,
|
|
1241
|
+
record.revision.toString(),
|
|
1242
|
+
record.schemaVersion,
|
|
1243
|
+
Math.floor(Date.now() / 1000),
|
|
1244
|
+
JSON.stringify(record.data)
|
|
1245
|
+
);
|
|
1228
1246
|
|
|
1247
|
+
// Update sync_revision to track the highest known revision
|
|
1248
|
+
const updateRevisionStmt = this.db.prepare(`
|
|
1249
|
+
UPDATE sync_revision SET revision = MAX(revision, CAST(? AS INTEGER))
|
|
1250
|
+
`);
|
|
1251
|
+
updateRevisionStmt.run(record.revision.toString());
|
|
1252
|
+
});
|
|
1253
|
+
|
|
1254
|
+
transaction();
|
|
1229
1255
|
return Promise.resolve();
|
|
1230
1256
|
} catch (error) {
|
|
1231
1257
|
return Promise.reject(
|
|
@@ -229,6 +229,8 @@ class MigrationManager {
|
|
|
229
229
|
{
|
|
230
230
|
name: "Create sync tables",
|
|
231
231
|
sql: [
|
|
232
|
+
// sync_revision: tracks the last committed revision (from server-acknowledged
|
|
233
|
+
// or server-received records). Does NOT include pending outgoing revisions.
|
|
232
234
|
`CREATE TABLE sync_revision (
|
|
233
235
|
revision INTEGER NOT NULL DEFAULT 0
|
|
234
236
|
)`,
|
|
@@ -285,13 +287,13 @@ class MigrationManager {
|
|
|
285
287
|
nostr_zap_request TEXT,
|
|
286
288
|
nostr_zap_receipt TEXT,
|
|
287
289
|
sender_comment TEXT
|
|
288
|
-
)
|
|
290
|
+
)`,
|
|
289
291
|
},
|
|
290
292
|
{
|
|
291
293
|
// Delete all unclaimed deposits to clear old claim_error JSON format.
|
|
292
294
|
// Deposits will be recovered on next sync.
|
|
293
295
|
name: "Clear unclaimed deposits for claim_error format change",
|
|
294
|
-
sql: `DELETE FROM unclaimed_deposits
|
|
296
|
+
sql: `DELETE FROM unclaimed_deposits`,
|
|
295
297
|
},
|
|
296
298
|
{
|
|
297
299
|
// Clear all sync tables due to BreezSigner signature change.
|
|
@@ -309,7 +311,7 @@ class MigrationManager {
|
|
|
309
311
|
},
|
|
310
312
|
{
|
|
311
313
|
name: "Add token conversion info to payment_metadata",
|
|
312
|
-
sql: `ALTER TABLE payment_metadata ADD COLUMN token_conversion_info TEXT
|
|
314
|
+
sql: `ALTER TABLE payment_metadata ADD COLUMN token_conversion_info TEXT`,
|
|
313
315
|
},
|
|
314
316
|
{
|
|
315
317
|
name: "Add parent payment id to payment_metadata",
|
|
@@ -321,6 +323,28 @@ class MigrationManager {
|
|
|
321
323
|
`ALTER TABLE payment_metadata DROP COLUMN token_conversion_info`,
|
|
322
324
|
`ALTER TABLE payment_metadata ADD COLUMN conversion_info TEXT`]
|
|
323
325
|
},
|
|
326
|
+
{
|
|
327
|
+
name: "Add tx_type column to payment_details_token",
|
|
328
|
+
sql: [
|
|
329
|
+
// Add tx_type column with a default value of 'transfer'.
|
|
330
|
+
// Delete sync cache to trigger token re-sync which will update all records with correct tx_type.
|
|
331
|
+
// Note: This intentionally couples to the CachedSyncInfo schema at migration time.
|
|
332
|
+
`ALTER TABLE payment_details_token ADD COLUMN tx_type TEXT NOT NULL DEFAULT 'transfer'`,
|
|
333
|
+
`UPDATE settings
|
|
334
|
+
SET value = json_set(value, '$.last_synced_final_token_payment_id', NULL)
|
|
335
|
+
WHERE key = 'sync_offset' AND json_valid(value) AND json_type(value, '$.last_synced_final_token_payment_id') IS NOT NULL`,
|
|
336
|
+
],
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
name: "Clear sync tables to force re-sync",
|
|
340
|
+
sql: [
|
|
341
|
+
`DELETE FROM sync_outgoing`,
|
|
342
|
+
`DELETE FROM sync_incoming`,
|
|
343
|
+
`DELETE FROM sync_state`,
|
|
344
|
+
`UPDATE sync_revision SET revision = 0`,
|
|
345
|
+
`DELETE FROM settings WHERE key = 'sync_initial_complete'`
|
|
346
|
+
]
|
|
347
|
+
},
|
|
324
348
|
];
|
|
325
349
|
}
|
|
326
350
|
}
|
package/package.json
CHANGED