@breeztech/breez-sdk-spark 0.15.0 → 0.16.1-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 (51) hide show
  1. package/breez-sdk-spark.tgz +0 -0
  2. package/bundler/breez_sdk_spark_wasm.d.ts +511 -215
  3. package/bundler/breez_sdk_spark_wasm.js +1 -1
  4. package/bundler/breez_sdk_spark_wasm_bg.js +567 -414
  5. package/bundler/breez_sdk_spark_wasm_bg.wasm +0 -0
  6. package/bundler/breez_sdk_spark_wasm_bg.wasm.d.ts +55 -46
  7. package/bundler/storage/index.js +205 -15
  8. package/deno/breez_sdk_spark_wasm.d.ts +511 -215
  9. package/deno/breez_sdk_spark_wasm.js +567 -414
  10. package/deno/breez_sdk_spark_wasm_bg.wasm +0 -0
  11. package/deno/breez_sdk_spark_wasm_bg.wasm.d.ts +55 -46
  12. package/nodejs/breez_sdk_spark_wasm.d.ts +511 -215
  13. package/nodejs/breez_sdk_spark_wasm.js +578 -421
  14. package/nodejs/breez_sdk_spark_wasm_bg.wasm +0 -0
  15. package/nodejs/breez_sdk_spark_wasm_bg.wasm.d.ts +55 -46
  16. package/nodejs/index.js +10 -10
  17. package/nodejs/index.mjs +12 -8
  18. package/nodejs/mysql-session-store/errors.cjs +13 -0
  19. package/nodejs/{mysql-session-manager → mysql-session-store}/index.cjs +24 -21
  20. package/nodejs/{mysql-session-manager → mysql-session-store}/migrations.cjs +17 -11
  21. package/nodejs/mysql-session-store/package.json +9 -0
  22. package/nodejs/mysql-storage/index.cjs +229 -111
  23. package/nodejs/mysql-storage/migrations.cjs +37 -2
  24. package/nodejs/mysql-token-store/index.cjs +99 -79
  25. package/nodejs/mysql-token-store/migrations.cjs +59 -2
  26. package/nodejs/mysql-tree-store/index.cjs +15 -9
  27. package/nodejs/mysql-tree-store/migrations.cjs +16 -2
  28. package/nodejs/package.json +2 -2
  29. package/nodejs/postgres-session-store/errors.cjs +13 -0
  30. package/nodejs/{postgres-session-manager → postgres-session-store}/index.cjs +23 -23
  31. package/nodejs/{postgres-session-manager → postgres-session-store}/migrations.cjs +14 -14
  32. package/nodejs/postgres-session-store/package.json +9 -0
  33. package/nodejs/postgres-storage/index.cjs +174 -107
  34. package/nodejs/postgres-storage/migrations.cjs +24 -0
  35. package/nodejs/postgres-token-store/index.cjs +89 -64
  36. package/nodejs/postgres-token-store/migrations.cjs +44 -0
  37. package/nodejs/storage/index.cjs +167 -113
  38. package/nodejs/storage/migrations.cjs +23 -0
  39. package/package.json +6 -1
  40. package/ssr/index.js +52 -28
  41. package/web/breez_sdk_spark_wasm.d.ts +566 -261
  42. package/web/breez_sdk_spark_wasm.js +567 -414
  43. package/web/breez_sdk_spark_wasm_bg.wasm +0 -0
  44. package/web/breez_sdk_spark_wasm_bg.wasm.d.ts +55 -46
  45. package/web/passkey-prf-provider/index.d.ts +203 -0
  46. package/web/passkey-prf-provider/index.js +733 -0
  47. package/web/storage/index.js +205 -15
  48. package/nodejs/mysql-session-manager/errors.cjs +0 -13
  49. package/nodejs/mysql-session-manager/package.json +0 -9
  50. package/nodejs/postgres-session-manager/errors.cjs +0 -13
  51. package/nodejs/postgres-session-manager/package.json +0 -9
@@ -3,13 +3,13 @@
3
3
  export const memory: WebAssembly.Memory;
4
4
  export const __wbg_bitcoinchainservicehandle_free: (a: number, b: number) => void;
5
5
  export const __wbg_breezsdk_free: (a: number, b: number) => void;
6
- export const __wbg_defaultsigner_free: (a: number, b: number) => void;
7
- export const __wbg_mysqlconnectionpool_free: (a: number, b: number) => void;
8
- export const __wbg_passkey_free: (a: number, b: number) => void;
9
- export const __wbg_postgresconnectionpool_free: (a: number, b: number) => void;
6
+ export const __wbg_externalsigners_free: (a: number, b: number) => void;
7
+ export const __wbg_passkeyclient_free: (a: number, b: number) => void;
8
+ export const __wbg_passkeylabels_free: (a: number, b: number) => void;
10
9
  export const __wbg_sdkbuilder_free: (a: number, b: number) => void;
11
10
  export const __wbg_tokenissuer_free: (a: number, b: number) => void;
12
11
  export const __wbg_wasmsdkcontext_free: (a: number, b: number) => void;
12
+ export const __wbg_wasmstorageconfig_free: (a: number, b: number) => void;
13
13
  export const bitcoinchainservicehandle_broadcastTransaction: (a: number, b: number, c: number) => any;
14
14
  export const bitcoinchainservicehandle_getAddressUtxos: (a: number, b: number, c: number) => any;
15
15
  export const bitcoinchainservicehandle_getTransactionHex: (a: number, b: number, c: number) => any;
@@ -17,18 +17,18 @@ export const bitcoinchainservicehandle_getTransactionStatus: (a: number, b: numb
17
17
  export const bitcoinchainservicehandle_recommendedFees: (a: number) => any;
18
18
  export const breezsdk_addContact: (a: number, b: any) => any;
19
19
  export const breezsdk_addEventListener: (a: number, b: any) => any;
20
+ export const breezsdk_authorizeLightningAddressTransfer: (a: number, b: any) => any;
20
21
  export const breezsdk_buyBitcoin: (a: number, b: any) => any;
21
- export const breezsdk_cancelLeafOptimization: (a: number) => any;
22
22
  export const breezsdk_checkLightningAddressAvailable: (a: number, b: any) => any;
23
23
  export const breezsdk_checkMessage: (a: number, b: any) => any;
24
24
  export const breezsdk_claimDeposit: (a: number, b: any) => any;
25
25
  export const breezsdk_claimHtlcPayment: (a: number, b: any) => any;
26
+ export const breezsdk_claimLightningAddressTransfer: (a: number, b: any) => any;
26
27
  export const breezsdk_deleteContact: (a: number, b: number, c: number) => any;
27
28
  export const breezsdk_deleteLightningAddress: (a: number) => any;
28
29
  export const breezsdk_disconnect: (a: number) => any;
29
30
  export const breezsdk_fetchConversionLimits: (a: number, b: any) => any;
30
31
  export const breezsdk_getInfo: (a: number, b: any) => any;
31
- export const breezsdk_getLeafOptimizationProgress: (a: number) => any;
32
32
  export const breezsdk_getLightningAddress: (a: number) => any;
33
33
  export const breezsdk_getPayment: (a: number, b: any) => any;
34
34
  export const breezsdk_getTokenIssuer: (a: number) => number;
@@ -43,6 +43,7 @@ export const breezsdk_listWebhooks: (a: number) => any;
43
43
  export const breezsdk_lnurlAuth: (a: number, b: any) => any;
44
44
  export const breezsdk_lnurlPay: (a: number, b: any) => any;
45
45
  export const breezsdk_lnurlWithdraw: (a: number, b: any) => any;
46
+ export const breezsdk_optimizeLeaves: (a: number, b: any) => any;
46
47
  export const breezsdk_parse: (a: number, b: number, c: number) => any;
47
48
  export const breezsdk_prepareLnurlPay: (a: number, b: any) => any;
48
49
  export const breezsdk_prepareSendPayment: (a: number, b: any) => any;
@@ -55,65 +56,71 @@ export const breezsdk_registerWebhook: (a: number, b: any) => any;
55
56
  export const breezsdk_removeEventListener: (a: number, b: number, c: number) => any;
56
57
  export const breezsdk_sendPayment: (a: number, b: any) => any;
57
58
  export const breezsdk_signMessage: (a: number, b: any) => any;
58
- export const breezsdk_startLeafOptimization: (a: number) => any;
59
59
  export const breezsdk_syncWallet: (a: number, b: any) => any;
60
60
  export const breezsdk_unregisterWebhook: (a: number, b: any) => any;
61
61
  export const breezsdk_updateContact: (a: number, b: any) => any;
62
62
  export const breezsdk_updateUserSettings: (a: number, b: any) => any;
63
63
  export const connect: (a: any) => any;
64
- export const connectWithSigner: (a: any, b: any, c: number, d: number) => any;
65
- export const createMysqlConnectionPool: (a: any) => [number, number, number];
66
- export const createPostgresConnectionPool: (a: any) => [number, number, number];
64
+ export const connectWithSigner: (a: any, b: any, c: any, d: number, e: number) => any;
65
+ export const createTurnkeySigner: (a: any) => any;
67
66
  export const defaultConfig: (a: any) => any;
68
- export const defaultExternalSigner: (a: number, b: number, c: number, d: number, e: any, f: number) => [number, number, number];
67
+ export const defaultExternalSigners: (a: number, b: number, c: number, d: number, e: any, f: number) => [number, number, number];
69
68
  export const defaultMysqlStorageConfig: (a: number, b: number) => any;
70
69
  export const defaultPostgresStorageConfig: (a: number, b: number) => any;
71
70
  export const defaultServerConfig: (a: any) => any;
72
- export const defaultsigner_aggregateFrost: (a: number, b: any) => any;
73
- export const defaultsigner_decryptEcies: (a: number, b: number, c: number, d: number, e: number) => any;
74
- export const defaultsigner_derivePublicKey: (a: number, b: number, c: number) => any;
75
- export const defaultsigner_encryptEcies: (a: number, b: number, c: number, d: number, e: number) => any;
76
- export const defaultsigner_encryptPrivateKeyForReceiver: (a: number, b: any, c: any) => any;
77
- export const defaultsigner_generateRandomSecret: (a: number) => any;
78
- export const defaultsigner_generateRandomSigningCommitment: (a: number) => any;
79
- export const defaultsigner_getPublicKeyForNode: (a: number, b: any) => any;
80
- export const defaultsigner_hmacSha256: (a: number, b: number, c: number, d: number, e: number) => any;
81
- export const defaultsigner_identityPublicKey: (a: number) => [number, number, number];
82
- export const defaultsigner_publicKeyFromSecret: (a: number, b: any) => any;
83
- export const defaultsigner_signEcdsa: (a: number, b: any, c: number, d: number) => any;
84
- export const defaultsigner_signEcdsaRecoverable: (a: number, b: any, c: number, d: number) => any;
85
- export const defaultsigner_signFrost: (a: number, b: any) => any;
86
- export const defaultsigner_signHashSchnorr: (a: number, b: number, c: number, d: number, e: number) => any;
87
- export const defaultsigner_splitSecretWithProofs: (a: number, b: any, c: number, d: number) => any;
88
- export const defaultsigner_staticDepositSecret: (a: number, b: number) => any;
89
- export const defaultsigner_staticDepositSecretEncrypted: (a: number, b: number) => any;
90
- export const defaultsigner_staticDepositSigningKey: (a: number, b: number) => any;
91
- export const defaultsigner_subtractSecrets: (a: number, b: any, c: any) => any;
71
+ export const defaultStorage: (a: number, b: number) => number;
72
+ export const externalbreezsignerhandle_decryptEcies: (a: number, b: number, c: number, d: number, e: number) => any;
73
+ export const externalbreezsignerhandle_derivePublicKey: (a: number, b: number, c: number) => any;
74
+ export const externalbreezsignerhandle_encryptEcies: (a: number, b: number, c: number, d: number, e: number) => any;
75
+ export const externalbreezsignerhandle_hmacSha256: (a: number, b: number, c: number, d: number, e: number) => any;
76
+ export const externalbreezsignerhandle_signEcdsa: (a: number, b: any, c: number, d: number) => any;
77
+ export const externalbreezsignerhandle_signEcdsaRecoverable: (a: number, b: any, c: number, d: number) => any;
78
+ export const externalbreezsignerhandle_signHashSchnorr: (a: number, b: number, c: number, d: number, e: number) => any;
79
+ export const externalsigners_breezSigner: (a: number) => number;
80
+ export const externalsigners_sparkSigner: (a: number) => number;
81
+ export const externalsparksignerhandle_getIdentityPublicKey: (a: number) => any;
82
+ export const externalsparksignerhandle_getPublicKeyForLeaf: (a: number, b: any) => any;
83
+ export const externalsparksignerhandle_getStaticDepositPublicKey: (a: number, b: number) => any;
84
+ export const externalsparksignerhandle_prepareClaim: (a: number, b: any) => any;
85
+ export const externalsparksignerhandle_prepareLightningReceive: (a: number, b: any) => any;
86
+ export const externalsparksignerhandle_prepareStaticDeposit: (a: number, b: any) => any;
87
+ export const externalsparksignerhandle_prepareStaticDepositClaim: (a: number, b: any) => any;
88
+ export const externalsparksignerhandle_prepareTokenTransaction: (a: number, b: any) => any;
89
+ export const externalsparksignerhandle_prepareTransfer: (a: number, b: any) => any;
90
+ export const externalsparksignerhandle_signAuthenticationChallenge: (a: number, b: number, c: number) => any;
91
+ export const externalsparksignerhandle_signFrost: (a: number, b: number, c: number) => any;
92
+ export const externalsparksignerhandle_signMessage: (a: number, b: number, c: number) => any;
93
+ export const externalsparksignerhandle_signSparkInvoice: (a: number, b: any) => any;
94
+ export const externalsparksignerhandle_signStaticDepositRefund: (a: number, b: any) => any;
95
+ export const externalsparksignerhandle_startStaticDepositRefund: (a: number, b: any) => any;
92
96
  export const getSparkStatus: () => any;
93
97
  export const initLogging: (a: any, b: number, c: number) => any;
98
+ export const mysqlStorage: (a: any) => number;
94
99
  export const newRestChainService: (a: number, b: number, c: any, d: any, e: number) => any;
95
100
  export const newSharedSdkContext: (a: any) => any;
96
- export const passkey_getWallet: (a: number, b: number, c: number) => any;
97
- export const passkey_isAvailable: (a: number) => any;
98
- export const passkey_listLabels: (a: number) => any;
99
- export const passkey_new: (a: any, b: number) => number;
100
- export const passkey_storeLabel: (a: number, b: number, c: number) => any;
101
+ export const passkeyclient_checkAvailability: (a: number) => any;
102
+ export const passkeyclient_labels: (a: number) => number;
103
+ export const passkeyclient_new: (a: any, b: number, c: number, d: number) => number;
104
+ export const passkeyclient_register: (a: number, b: any) => any;
105
+ export const passkeyclient_signIn: (a: number, b: any) => any;
106
+ export const passkeylabels_list: (a: number) => any;
107
+ export const passkeylabels_store: (a: number, b: number, c: number) => any;
108
+ export const postgresStorage: (a: any) => number;
101
109
  export const sdkbuilder_build: (a: number) => any;
102
110
  export const sdkbuilder_new: (a: any, b: any) => number;
103
- export const sdkbuilder_newWithSigner: (a: any, b: any) => number;
111
+ export const sdkbuilder_newWithSigner: (a: any, b: any, c: any) => number;
112
+ export const sdkbuilder_withAccountNumber: (a: number, b: number) => number;
104
113
  export const sdkbuilder_withChainService: (a: number, b: any) => number;
105
114
  export const sdkbuilder_withDefaultStorage: (a: number, b: number, c: number) => any;
106
115
  export const sdkbuilder_withFiatService: (a: number, b: any) => number;
107
- export const sdkbuilder_withKeySet: (a: number, b: any) => number;
108
116
  export const sdkbuilder_withLnurlClient: (a: number, b: any) => number;
109
117
  export const sdkbuilder_withMysqlBackend: (a: number, b: any) => [number, number, number];
110
- export const sdkbuilder_withMysqlConnectionPool: (a: number, b: number) => number;
111
118
  export const sdkbuilder_withPaymentObserver: (a: number, b: any) => number;
112
119
  export const sdkbuilder_withPostgresBackend: (a: number, b: any) => [number, number, number];
113
- export const sdkbuilder_withPostgresConnectionPool: (a: number, b: number) => number;
114
120
  export const sdkbuilder_withRestChainService: (a: number, b: number, c: number, d: any, e: number) => number;
115
121
  export const sdkbuilder_withSharedContext: (a: number, b: number) => number;
116
122
  export const sdkbuilder_withStorage: (a: number, b: any) => number;
123
+ export const sdkbuilder_withStorageBackend: (a: number, b: number) => number;
117
124
  export const tokenissuer_burnIssuerToken: (a: number, b: any) => any;
118
125
  export const tokenissuer_createIssuerToken: (a: number, b: any) => any;
119
126
  export const tokenissuer_freezeIssuerToken: (a: number, b: any) => any;
@@ -139,16 +146,18 @@ export const intounderlyingsink_close: (a: number) => any;
139
146
  export const intounderlyingsink_write: (a: number, b: any) => any;
140
147
  export const intounderlyingsource_cancel: (a: number) => void;
141
148
  export const intounderlyingsource_pull: (a: number, b: any) => any;
142
- export const wasm_bindgen__convert__closures_____invoke__h013a98c02f3b4b21: (a: number, b: number, c: any) => [number, number];
143
- export const wasm_bindgen__convert__closures_____invoke__h013a98c02f3b4b21_4: (a: number, b: number, c: any) => [number, number];
144
- export const wasm_bindgen__convert__closures_____invoke__h013a98c02f3b4b21_5: (a: number, b: number, c: any) => [number, number];
145
- export const wasm_bindgen__convert__closures_____invoke__h013a98c02f3b4b21_6: (a: number, b: number, c: any) => [number, number];
146
- export const wasm_bindgen__convert__closures_____invoke__h013a98c02f3b4b21_7: (a: number, b: number, c: any) => [number, number];
149
+ export const __wbg_externalbreezsignerhandle_free: (a: number, b: number) => void;
150
+ export const __wbg_externalsparksignerhandle_free: (a: number, b: number) => void;
151
+ export const wasm_bindgen__convert__closures_____invoke__h4bd7a023e323559c: (a: number, b: number, c: any) => [number, number];
152
+ export const wasm_bindgen__convert__closures_____invoke__h4bd7a023e323559c_4: (a: number, b: number, c: any) => [number, number];
153
+ export const wasm_bindgen__convert__closures_____invoke__h4bd7a023e323559c_5: (a: number, b: number, c: any) => [number, number];
154
+ export const wasm_bindgen__convert__closures_____invoke__h4bd7a023e323559c_6: (a: number, b: number, c: any) => [number, number];
155
+ export const wasm_bindgen__convert__closures_____invoke__h4bd7a023e323559c_7: (a: number, b: number, c: any) => [number, number];
147
156
  export const wasm_bindgen__convert__closures_____invoke__h41057d61edf43a32: (a: number, b: number, c: any, d: any) => void;
148
157
  export const wasm_bindgen__convert__closures_____invoke__h4819aba3eed2db57: (a: number, b: number, c: any) => void;
149
158
  export const wasm_bindgen__convert__closures_____invoke__h4819aba3eed2db57_2: (a: number, b: number, c: any) => void;
150
159
  export const wasm_bindgen__convert__closures_____invoke__h4819aba3eed2db57_3: (a: number, b: number, c: any) => void;
151
- export const wasm_bindgen__convert__closures_____invoke__h124479769cd429fd: (a: number, b: number) => void;
160
+ export const wasm_bindgen__convert__closures_____invoke__h484cd36e13f37bd7: (a: number, b: number) => void;
152
161
  export const __wbindgen_malloc: (a: number, b: number) => number;
153
162
  export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
154
163
  export const __wbindgen_free: (a: number, b: number, c: number) => void;
@@ -457,6 +457,94 @@ class MigrationManager {
457
457
  }
458
458
  }
459
459
  },
460
+ {
461
+ // Recovery migration for the missing "contacts" object store.
462
+ //
463
+ // The original "Create contacts store" migration above was INSERTED into
464
+ // the middle of this array (at index 12) instead of being appended, in
465
+ // SDK 0.11.0. That index was already occupied by the "Clear cached
466
+ // lightning address for LnurlInfo schema change" migration, which had
467
+ // shipped one release earlier in SDK 0.10.0 (dbVersion 13).
468
+ //
469
+ // Migration array indices map 1:1 to DB version transitions, so any
470
+ // database that ran 0.10.0 reached version 13 and, on upgrading to
471
+ // 0.11.0+ (dbVersion 14), ran only migrations[13] — permanently skipping
472
+ // the newly-inserted migrations[12]. Those databases never got a
473
+ // "contacts" object store, so every contact operation fails with
474
+ // NotFoundError.
475
+ //
476
+ // This migration is correctly appended and idempotently (re)creates the
477
+ // store, so affected databases recover on their next upgrade while
478
+ // unaffected databases treat it as a no-op. Keep the original migration
479
+ // in place; do not delete or reorder it.
480
+ name: "Create contacts store (recovery for skipped migration)",
481
+ upgrade: (db) => {
482
+ if (!db.objectStoreNames.contains("contacts")) {
483
+ const contactsStore = db.createObjectStore("contacts", { keyPath: "id" });
484
+ contactsStore.createIndex("name_identifier", ["name", "paymentIdentifier"], { unique: false });
485
+ contactsStore.createIndex("name", "name", { unique: false });
486
+ }
487
+ }
488
+ },
489
+ {
490
+ // Add deposit_vout to distinguish deposits sharing a funding tx. The
491
+ // new field is carried inside the JSON `details` blob. We can't safely
492
+ // backfill vout on the existing blobs: we never stored the original SSP
493
+ // output_index, and vout=0 is a valid output index — defaulting would
494
+ // silently mislabel. Instead we clear `details` on legacy deposit blobs
495
+ // so the read path returns `details: None` (matches what the SQL
496
+ // backends do by leaving the payments row but having no matching
497
+ // payment_details_deposit row). Reset the bitcoin sync offset so the
498
+ // resync re-fetches the SSP user_request and the upsert rewrites the
499
+ // blob with the proper vout.
500
+ name: "Clear legacy deposit details and reset sync offset for vout",
501
+ upgrade: (db, transaction) => {
502
+ if (db.objectStoreNames.contains("payments")) {
503
+ const paymentStore = transaction.objectStore("payments");
504
+ const cursorRequest = paymentStore.openCursor();
505
+ cursorRequest.onsuccess = (event) => {
506
+ const cursor = event.target.result;
507
+ if (!cursor) return;
508
+ const payment = cursor.value;
509
+ let details = null;
510
+ if (payment.details && typeof payment.details === "string") {
511
+ try {
512
+ details = JSON.parse(payment.details);
513
+ } catch (e) {
514
+ details = null;
515
+ }
516
+ } else {
517
+ details = payment.details;
518
+ }
519
+ if (details && details.type === "deposit") {
520
+ payment.details = null;
521
+ cursor.update(payment);
522
+ }
523
+ cursor.continue();
524
+ };
525
+ }
526
+ if (db.objectStoreNames.contains("settings")) {
527
+ const settingsStore = transaction.objectStore("settings");
528
+ const getRequest = settingsStore.get("sync_offset");
529
+
530
+ getRequest.onsuccess = () => {
531
+ const syncCache = getRequest.result;
532
+ if (syncCache && syncCache.value) {
533
+ try {
534
+ const syncInfo = JSON.parse(syncCache.value);
535
+ syncInfo.offset = 0;
536
+ settingsStore.put({
537
+ key: "sync_offset",
538
+ value: JSON.stringify(syncInfo),
539
+ });
540
+ } catch (e) {
541
+ // If parsing fails, just continue
542
+ }
543
+ }
544
+ };
545
+ }
546
+ },
547
+ },
460
548
  ];
461
549
  }
462
550
  }
@@ -480,7 +568,12 @@ class IndexedDBStorage {
480
568
  this.db = null;
481
569
  this.migrationManager = null;
482
570
  this.logger = logger;
483
- this.dbVersion = 15; // Current schema version
571
+ // IMPORTANT: the migrations array in MigrationManager is append-only. The
572
+ // migration at array index N is the upgrade step from DB version N to N+1,
573
+ // so existing databases depend on indices never shifting. Never insert,
574
+ // reorder, or delete a migration — only append. dbVersion MUST equal the
575
+ // number of migrations (enforced by the guard in initialize()).
576
+ this.dbVersion = 18; // Current schema version (= migration count)
484
577
  }
485
578
 
486
579
  /**
@@ -495,6 +588,17 @@ class IndexedDBStorage {
495
588
  throw new StorageError("IndexedDB is not available in this environment");
496
589
  }
497
590
 
591
+ // Guard: dbVersion must equal the migration count. If they drift apart, the
592
+ // upgrade loop either skips the trailing migration(s) or requests a version
593
+ // no migration fills in. Fail fast — this is a programming error.
594
+ const migrationCount = new MigrationManager(null, StorageError).migrations.length;
595
+ if (this.dbVersion !== migrationCount) {
596
+ throw new StorageError(
597
+ `dbVersion (${this.dbVersion}) must equal the migration count (${migrationCount}). ` +
598
+ `Migrations are append-only: append a new migration and bump dbVersion to match.`
599
+ );
600
+ }
601
+
498
602
  return new Promise((resolve, reject) => {
499
603
  const request = indexedDB.open(this.dbName, this.dbVersion);
500
604
 
@@ -802,31 +906,98 @@ class IndexedDBStorage {
802
906
  });
803
907
  }
804
908
 
805
- async insertPayment(payment) {
909
+ async applyPaymentUpdate(payment) {
806
910
  if (!this.db) {
807
911
  throw new StorageError("Database not initialized");
808
912
  }
809
913
 
810
914
  return new Promise((resolve, reject) => {
915
+ let shouldEmit = false;
916
+ let settled = false;
811
917
  const transaction = this.db.transaction("payments", "readwrite");
812
918
  const store = transaction.objectStore("payments");
813
919
 
814
- // Ensure details and method are serialized properly
815
- const paymentToStore = {
816
- ...payment,
817
- details: payment.details ? JSON.stringify(payment.details) : null,
818
- method: payment.method ? JSON.stringify(payment.method) : null,
920
+ const rejectOnce = (message, error) => {
921
+ if (settled) {
922
+ return;
923
+ }
924
+ settled = true;
925
+ reject(new StorageError(message, error));
819
926
  };
820
927
 
821
- const request = store.put(paymentToStore);
822
- request.onsuccess = () => resolve();
823
- request.onerror = () => {
824
- reject(
825
- new StorageError(
826
- `Failed to insert payment '${payment.id}': ${request.error?.message || "Unknown error"
928
+ transaction.oncomplete = () => {
929
+ if (settled) {
930
+ return;
931
+ }
932
+ settled = true;
933
+ resolve(shouldEmit);
934
+ };
935
+
936
+ transaction.onerror = () => {
937
+ rejectOnce(
938
+ `Failed to apply payment update '${payment.id}': ${transaction.error?.message || "Unknown error"
939
+ }`,
940
+ transaction.error
941
+ );
942
+ };
943
+
944
+ transaction.onabort = () => {
945
+ rejectOnce(
946
+ `Payment update transaction aborted for '${payment.id}': ${transaction.error?.message || "Unknown error"
947
+ }`,
948
+ transaction.error
949
+ );
950
+ };
951
+
952
+ const getRequest = store.get(payment.id);
953
+
954
+ getRequest.onsuccess = () => {
955
+ const storedPayment = getRequest.result;
956
+ const stored = storedPayment
957
+ ? this._normalizePaymentStatus(storedPayment.status)
958
+ : null;
959
+ const next = this._normalizePaymentStatus(payment.status);
960
+
961
+ let shouldPersist;
962
+ if (stored == null) {
963
+ shouldPersist = true;
964
+ shouldEmit = true;
965
+ } else if (stored === next) {
966
+ console.debug(
967
+ `Skipping redundant payment event: id=${payment.id} status=${next}`
968
+ );
969
+ shouldPersist = true;
970
+ shouldEmit = false;
971
+ } else if (this._isFinalPaymentStatus(stored)) {
972
+ console.warn(
973
+ `Skipping payment update (would replace terminal status): id=${payment.id} stored=${stored} new=${next}`
974
+ );
975
+ shouldPersist = false;
976
+ shouldEmit = false;
977
+ } else {
978
+ shouldPersist = true;
979
+ shouldEmit = true;
980
+ }
981
+
982
+ if (!shouldPersist) {
983
+ return;
984
+ }
985
+
986
+ const putRequest = store.put(this._paymentToStore(payment));
987
+ putRequest.onerror = () => {
988
+ rejectOnce(
989
+ `Failed to persist payment update '${payment.id}': ${putRequest.error?.message || "Unknown error"
827
990
  }`,
828
- request.error
829
- )
991
+ putRequest.error
992
+ );
993
+ };
994
+ };
995
+
996
+ getRequest.onerror = () => {
997
+ rejectOnce(
998
+ `Failed to read payment '${payment.id}' before update: ${getRequest.error?.message || "Unknown error"
999
+ }`,
1000
+ getRequest.error
830
1001
  );
831
1002
  };
832
1003
  });
@@ -2021,6 +2192,25 @@ class IndexedDBStorage {
2021
2192
 
2022
2193
  // ===== Private Helper Methods =====
2023
2194
 
2195
+ _paymentToStore(payment) {
2196
+ // Ensure details and method are serialized properly
2197
+ return {
2198
+ ...payment,
2199
+ details: payment.details ? JSON.stringify(payment.details) : null,
2200
+ method: payment.method ? JSON.stringify(payment.method) : null,
2201
+ };
2202
+ }
2203
+
2204
+ _normalizePaymentStatus(status) {
2205
+ return typeof status === "string" ? status.toLowerCase() : status;
2206
+ }
2207
+
2208
+ _isFinalPaymentStatus(status) {
2209
+ const normalized = this._normalizePaymentStatus(status);
2210
+ return normalized === "completed" || normalized === "failed";
2211
+ }
2212
+
2213
+
2024
2214
  _matchesFilters(payment, request) {
2025
2215
  // Filter by payment type
2026
2216
  if (request.typeFilter && request.typeFilter.length > 0) {