@bsv/wallet-toolbox 1.1.34 → 1.1.36

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 (124) hide show
  1. package/docs/client.md +215 -213
  2. package/docs/setup.md +2 -2
  3. package/docs/storage.md +140 -34
  4. package/docs/wallet.md +215 -213
  5. package/out/src/Setup.d.ts.map +1 -1
  6. package/out/src/Setup.js +2 -2
  7. package/out/src/Setup.js.map +1 -1
  8. package/out/src/sdk/WERR_errors.d.ts +1 -1
  9. package/out/src/sdk/WERR_errors.d.ts.map +1 -1
  10. package/out/src/sdk/WERR_errors.js +2 -2
  11. package/out/src/sdk/WERR_errors.js.map +1 -1
  12. package/out/src/sdk/WalletStorage.interfaces.d.ts +107 -0
  13. package/out/src/sdk/WalletStorage.interfaces.d.ts.map +1 -1
  14. package/out/src/sdk/index.d.ts +0 -2
  15. package/out/src/sdk/index.d.ts.map +1 -1
  16. package/out/src/sdk/index.js +0 -2
  17. package/out/src/sdk/index.js.map +1 -1
  18. package/out/src/services/__tests/verifyBeef.test.d.ts +2 -0
  19. package/out/src/services/__tests/verifyBeef.test.d.ts.map +1 -0
  20. package/out/src/services/__tests/verifyBeef.test.js +16 -0
  21. package/out/src/services/__tests/verifyBeef.test.js.map +1 -0
  22. package/out/src/services/providers/__tests/WhatsOnChain.test.js +11 -0
  23. package/out/src/services/providers/__tests/WhatsOnChain.test.js.map +1 -1
  24. package/out/src/storage/StorageReader.d.ts +1 -1
  25. package/out/src/storage/StorageReader.d.ts.map +1 -1
  26. package/out/src/storage/StorageReaderWriter.d.ts.map +1 -1
  27. package/out/src/storage/StorageReaderWriter.js +2 -1
  28. package/out/src/storage/StorageReaderWriter.js.map +1 -1
  29. package/out/src/storage/StorageSyncReader.d.ts +2 -18
  30. package/out/src/storage/StorageSyncReader.d.ts.map +1 -1
  31. package/out/src/storage/StorageSyncReader.js +1 -105
  32. package/out/src/storage/StorageSyncReader.js.map +1 -1
  33. package/out/src/storage/WalletStorageManager.d.ts +86 -10
  34. package/out/src/storage/WalletStorageManager.d.ts.map +1 -1
  35. package/out/src/storage/WalletStorageManager.js +239 -79
  36. package/out/src/storage/WalletStorageManager.js.map +1 -1
  37. package/out/src/storage/schema/KnexMigrations.d.ts.map +1 -1
  38. package/out/src/storage/schema/KnexMigrations.js +19 -0
  39. package/out/src/storage/schema/KnexMigrations.js.map +1 -1
  40. package/out/src/storage/schema/entities/SyncState.js +1 -1
  41. package/out/src/storage/schema/entities/User.d.ts +2 -2
  42. package/out/src/storage/schema/entities/User.d.ts.map +1 -1
  43. package/out/src/storage/schema/entities/User.js +1 -1
  44. package/out/src/storage/schema/entities/User.js.map +1 -1
  45. package/out/src/storage/schema/entities/__tests/SyncStateTests.test.js +2 -1
  46. package/out/src/storage/schema/entities/__tests/SyncStateTests.test.js.map +1 -1
  47. package/out/src/storage/schema/entities/__tests/usersTests.test.js +12 -6
  48. package/out/src/storage/schema/entities/__tests/usersTests.test.js.map +1 -1
  49. package/out/src/storage/schema/tables/User.d.ts +1 -1
  50. package/out/src/storage/schema/tables/User.d.ts.map +1 -1
  51. package/out/src/storage/sync/StorageMySQLDojoReader.d.ts +1 -1
  52. package/out/src/storage/sync/StorageMySQLDojoReader.d.ts.map +1 -1
  53. package/out/src/storage/sync/StorageMySQLDojoReader.js +4 -5
  54. package/out/src/storage/sync/StorageMySQLDojoReader.js.map +1 -1
  55. package/out/test/Wallet/sync/Wallet.updateWalletLegacyTestData.man.test.js +25 -13
  56. package/out/test/Wallet/sync/Wallet.updateWalletLegacyTestData.man.test.js.map +1 -1
  57. package/out/test/Wallet/sync/setActive.test.d.ts +2 -0
  58. package/out/test/Wallet/sync/setActive.test.d.ts.map +1 -0
  59. package/out/test/Wallet/sync/setActive.test.js +131 -0
  60. package/out/test/Wallet/sync/setActive.test.js.map +1 -0
  61. package/out/test/examples/{backup.test.d.ts → backup.man.test.d.ts} +1 -1
  62. package/out/test/examples/backup.man.test.d.ts.map +1 -0
  63. package/out/test/examples/{backup.test.js → backup.man.test.js} +1 -1
  64. package/out/test/examples/backup.man.test.js.map +1 -0
  65. package/out/test/examples/pushdrop.test.d.ts.map +1 -1
  66. package/out/test/examples/pushdrop.test.js +2 -6
  67. package/out/test/examples/pushdrop.test.js.map +1 -1
  68. package/out/test/storage/StorageMySQLDojoReader.man.test.js.map +1 -1
  69. package/out/test/storage/count.test.js +1 -0
  70. package/out/test/storage/count.test.js.map +1 -1
  71. package/out/test/storage/find.test.js +1 -0
  72. package/out/test/storage/find.test.js.map +1 -1
  73. package/out/test/storage/update.test.js +3 -1
  74. package/out/test/storage/update.test.js.map +1 -1
  75. package/out/test/storage/update2.test.js +5 -2
  76. package/out/test/storage/update2.test.js.map +1 -1
  77. package/out/test/utils/TestUtilsWalletStorage.d.ts +4 -0
  78. package/out/test/utils/TestUtilsWalletStorage.d.ts.map +1 -1
  79. package/out/test/utils/TestUtilsWalletStorage.js +10 -5
  80. package/out/test/utils/TestUtilsWalletStorage.js.map +1 -1
  81. package/out/test/wallet/sync/Wallet.sync.test.js +23 -8
  82. package/out/test/wallet/sync/Wallet.sync.test.js.map +1 -1
  83. package/out/tsconfig.all.tsbuildinfo +1 -1
  84. package/package.json +1 -1
  85. package/src/Setup.ts +3 -2
  86. package/src/sdk/WERR_errors.ts +3 -2
  87. package/src/sdk/WalletStorage.interfaces.ts +117 -0
  88. package/src/sdk/index.ts +0 -2
  89. package/src/services/__tests/verifyBeef.test.ts +18 -0
  90. package/src/services/providers/__tests/WhatsOnChain.test.ts +16 -1
  91. package/src/storage/StorageReader.ts +1 -1
  92. package/src/storage/StorageReaderWriter.ts +2 -1
  93. package/src/storage/StorageSyncReader.ts +2 -109
  94. package/src/storage/WalletStorageManager.ts +317 -87
  95. package/src/storage/schema/KnexMigrations.ts +23 -1
  96. package/src/storage/schema/entities/SyncState.ts +1 -1
  97. package/src/storage/schema/entities/User.ts +2 -2
  98. package/src/storage/schema/entities/__tests/SyncStateTests.test.ts +2 -1
  99. package/src/storage/schema/entities/__tests/usersTests.test.ts +12 -6
  100. package/src/storage/schema/tables/User.ts +1 -1
  101. package/src/storage/sync/StorageMySQLDojoReader.ts +8 -6
  102. package/test/Wallet/sync/Wallet.updateWalletLegacyTestData.man.test.ts +30 -13
  103. package/test/Wallet/sync/setActive.test.ts +147 -0
  104. package/test/examples/pushdrop.test.ts +3 -8
  105. package/test/storage/StorageMySQLDojoReader.man.test.ts +1 -1
  106. package/test/storage/count.test.ts +1 -0
  107. package/test/storage/find.test.ts +1 -0
  108. package/test/storage/update.test.ts +3 -1
  109. package/test/storage/update2.test.ts +5 -2
  110. package/test/utils/TestUtilsWalletStorage.ts +15 -5
  111. package/test/wallet/sync/Wallet.sync.test.ts +28 -8
  112. package/out/src/sdk/StorageSyncReader.d.ts +0 -121
  113. package/out/src/sdk/StorageSyncReader.d.ts.map +0 -1
  114. package/out/src/sdk/StorageSyncReader.js +0 -3
  115. package/out/src/sdk/StorageSyncReader.js.map +0 -1
  116. package/out/src/sdk/StorageSyncReaderWriter.d.ts +0 -89
  117. package/out/src/sdk/StorageSyncReaderWriter.d.ts.map +0 -1
  118. package/out/src/sdk/StorageSyncReaderWriter.js +0 -3
  119. package/out/src/sdk/StorageSyncReaderWriter.js.map +0 -1
  120. package/out/test/examples/backup.test.d.ts.map +0 -1
  121. package/out/test/examples/backup.test.js.map +0 -1
  122. package/src/sdk/StorageSyncReader.ts +0 -173
  123. package/src/sdk/StorageSyncReaderWriter.ts +0 -277
  124. /package/test/examples/{backup.test.ts → backup.man.test.ts} +0 -0
@@ -25,6 +25,19 @@ import {
25
25
  TableUser,
26
26
  wait
27
27
  } from '../index.client'
28
+ import { WERR_INVALID_PARAMETER } from '../sdk'
29
+
30
+ class ManagedStorage {
31
+ isAvailable: boolean
32
+ isStorageProvider: boolean
33
+ settings?: TableSettings
34
+ user?: TableUser
35
+
36
+ constructor(public storage: sdk.WalletStorageProvider) {
37
+ this.isStorageProvider = storage.isStorageProvider()
38
+ this.isAvailable = false
39
+ }
40
+ }
28
41
 
29
42
  /**
30
43
  * The `WalletStorageManager` class delivers authentication checking storage access to the wallet.
@@ -42,11 +55,41 @@ import {
42
55
  * for these services.
43
56
  */
44
57
  export class WalletStorageManager implements sdk.WalletStorage {
45
- stores: sdk.WalletStorageProvider[] = []
58
+ /**
59
+ * All configured stores including current active, backups, and conflicting actives.
60
+ */
61
+ _stores: ManagedStorage[] = []
62
+ /**
63
+ * True if makeAvailable has been run and access to managed stores (active) is allowed
64
+ */
65
+ _isAvailable: boolean = false
66
+ /**
67
+ * The current active store which is only enabled if the store's user record activeStorage property matches its settings record storageIdentityKey property
68
+ */
69
+ _active?: ManagedStorage
70
+ /**
71
+ * Stores to which state is pushed by updateBackups.
72
+ */
73
+ _backups?: ManagedStorage[]
74
+ /**
75
+ * Stores whose user record activeStorage property disagrees with the active store's user record activeStorage property.
76
+ */
77
+ _conflictingActives?: ManagedStorage[]
78
+ /**
79
+ * identityKey is always valid, userId and isActive are valid only if _isAvailable
80
+ */
46
81
  _authId: sdk.AuthId
82
+ /**
83
+ * Configured services if any. If valid, shared with stores (which may ignore it).
84
+ */
47
85
  _services?: sdk.WalletServices
48
- _userIdentityKeyToId: Record<string, number> = {}
86
+ /**
87
+ * How many read access operations are pending
88
+ */
49
89
  _readerCount: number = 0
90
+ /**
91
+ * How many write access operations are pending
92
+ */
50
93
  _writerCount: number = 0
51
94
  /**
52
95
  * if true, allow only a single writer to proceed at a time.
@@ -69,9 +112,9 @@ export class WalletStorageManager implements sdk.WalletStorage {
69
112
  active?: sdk.WalletStorageProvider,
70
113
  backups?: sdk.WalletStorageProvider[]
71
114
  ) {
72
- this.stores = []
73
- if (active) this.stores.push(active)
74
- if (backups) this.stores = this.stores.concat(backups)
115
+ const stores = [...(backups || [])]
116
+ if (active) stores.unshift(active)
117
+ this._stores = stores.map(s => new ManagedStorage(s))
75
118
  this._authId = { identityKey }
76
119
  }
77
120
 
@@ -79,35 +122,153 @@ export class WalletStorageManager implements sdk.WalletStorage {
79
122
  return false
80
123
  }
81
124
 
82
- async getUserId(): Promise<number> {
83
- if (!this._authId.userId) await this.getAuth()
84
- return this._authId.userId!
125
+ isAvailable(): boolean {
126
+ return this._isAvailable
127
+ }
128
+
129
+ /**
130
+ * The active storage is "enabled" only if its `storageIdentityKey` matches the user's currently selected `activeStorage`,
131
+ * and only if there are no stores with conflicting `activeStorage` selections.
132
+ *
133
+ * A wallet may be created without including the user's currently selected active storage. This allows readonly access to their wallet data.
134
+ *
135
+ * In addition, if there are conflicting `activeStorage` selections among backup storage providers then the active remains disabled.
136
+ */
137
+ get isActiveEnabled(): boolean {
138
+ return (
139
+ this._active !== undefined &&
140
+ this._active.settings!.storageIdentityKey ===
141
+ this._active.user!.activeStorage &&
142
+ this._conflictingActives !== undefined &&
143
+ this._conflictingActives.length === 0
144
+ )
145
+ }
146
+
147
+ /**
148
+ * @returns true if at least one WalletStorageProvider has been added.
149
+ */
150
+ canMakeAvailable(): boolean {
151
+ return this._stores.length > 0
152
+ }
153
+
154
+ /**
155
+ * This async function must be called after construction and before
156
+ * any other async function can proceed.
157
+ *
158
+ * Runs through `_stores` validating all properties and partitioning across `_active`, `_backups`, `_conflictingActives`.
159
+ *
160
+ * @throws WERR_INVALID_PARAMETER if canMakeAvailable returns false.
161
+ *
162
+ * @returns {TableSettings} from the active storage.
163
+ */
164
+ async makeAvailable(): Promise<TableSettings> {
165
+ if (this._isAvailable) return this._active!.settings!
166
+
167
+ this._active = undefined
168
+ this._backups = []
169
+ this._conflictingActives = []
170
+
171
+ if (this._stores.length < 1)
172
+ throw new sdk.WERR_INVALID_PARAMETER(
173
+ 'active',
174
+ 'valid. Must add active storage provider to wallet.'
175
+ )
176
+
177
+ // Initial backups. conflictingActives will be removed.
178
+ const backups: ManagedStorage[] = []
179
+ let i = -1
180
+ for (const store of this._stores) {
181
+ i++
182
+ if (!store.isAvailable || !store.settings || !store.user) {
183
+ // Validate all ManagedStorage properties.
184
+ store.settings = await store.storage.makeAvailable()
185
+ const r = await store.storage.findOrInsertUser(this._authId.identityKey)
186
+ store.user = r.user
187
+ store.isAvailable = true
188
+ }
189
+ if (!this._active)
190
+ // _stores[0] becomes the default active store. It may be replaced if it is not the user's "enabled" activeStorage and that store is found among the remainder (backups).
191
+ this._active = store
192
+ else {
193
+ const ua = store.user!.activeStorage
194
+ const si = store.settings!.storageIdentityKey
195
+ if (ua === si && !this.isActiveEnabled) {
196
+ // This store's user record selects it as an enabled active storage...
197
+ // swap the current not-enabled active for this storeage.
198
+ backups.push(this._active!)
199
+ this._active = store
200
+ } else {
201
+ // This store is a backup: Its user record selects some other storage as active.
202
+ backups.push(store)
203
+ }
204
+ }
205
+ }
206
+
207
+ // Review backups, partition out conflicting actives.
208
+ const si = this._active!.settings?.storageIdentityKey
209
+ for (const store of backups) {
210
+ if (store.user!.activeStorage !== si) this._conflictingActives.push(store)
211
+ else this._backups.push(store)
212
+ }
213
+
214
+ this._isAvailable = true
215
+ this._authId.userId = this._active!.user!.userId
216
+ this._authId.isActive = this.isActiveEnabled
217
+
218
+ return this._active!.settings!
219
+ }
220
+
221
+ private verifyActive(): ManagedStorage {
222
+ if (!this._active || !this._isAvailable)
223
+ throw new sdk.WERR_INVALID_OPERATION(
224
+ 'An active WalletStorageProvider must be added to this WalletStorageManager and makeAvailable must be called.'
225
+ )
226
+ return this._active
85
227
  }
86
228
 
87
229
  async getAuth(mustBeActive?: boolean): Promise<sdk.AuthId> {
88
230
  if (!this.isAvailable()) await this.makeAvailable()
89
- const { user, isNew } = await this.getActive().findOrInsertUser(
90
- this._authId.identityKey
91
- )
92
- if (!user)
93
- throw new sdk.WERR_INVALID_PARAMETER('identityKey', 'exist on storage.')
94
- this._authId.userId = user.userId
95
- this._authId.isActive =
96
- user.activeStorage === undefined ||
97
- user.activeStorage === this.getSettings().storageIdentityKey
98
231
  if (mustBeActive && !this._authId.isActive) throw new sdk.WERR_NOT_ACTIVE()
99
232
  return this._authId
100
233
  }
101
234
 
235
+ async getUserId(): Promise<number> {
236
+ return (await this.getAuth()).userId!
237
+ }
238
+
102
239
  getActive(): sdk.WalletStorageProvider {
103
- if (this.stores.length < 1)
104
- throw new sdk.WERR_INVALID_OPERATION(
105
- 'An active WalletStorageProvider must be added to this WalletStorageManager'
106
- )
107
- return this.stores[0]
240
+ return this.verifyActive().storage
241
+ }
242
+
243
+ getActiveSettings(): TableSettings {
244
+ return this.verifyActive().settings!
245
+ }
246
+
247
+ getActiveUser(): TableUser {
248
+ return this.verifyActive().user!
249
+ }
250
+
251
+ getActiveStore(): string {
252
+ return this.verifyActive().settings!.storageIdentityKey
253
+ }
254
+
255
+ getBackupStores(): string[] {
256
+ this.verifyActive()
257
+ return this._backups!.map(b => b.settings!.storageIdentityKey)
258
+ }
259
+
260
+ getConflictingStores(): string[] {
261
+ this.verifyActive()
262
+ return this._conflictingActives!.map(b => b.settings!.storageIdentityKey)
263
+ }
264
+
265
+ getAllStores(): string[] {
266
+ this.verifyActive()
267
+ return this._stores.map(b => b.settings!.storageIdentityKey)
108
268
  }
109
269
 
110
270
  async getActiveForWriter(): Promise<sdk.WalletStorageWriter> {
271
+ if (!this.isAvailable()) await this.makeAvailable()
111
272
  while (
112
273
  this._storageProviderLocked ||
113
274
  this._syncLocked ||
@@ -121,6 +282,7 @@ export class WalletStorageManager implements sdk.WalletStorage {
121
282
  }
122
283
 
123
284
  async getActiveForReader(): Promise<sdk.WalletStorageReader> {
285
+ if (!this.isAvailable()) await this.makeAvailable()
124
286
  while (
125
287
  this._storageProviderLocked ||
126
288
  this._syncLocked ||
@@ -133,6 +295,7 @@ export class WalletStorageManager implements sdk.WalletStorage {
133
295
  }
134
296
 
135
297
  async getActiveForSync(): Promise<sdk.WalletStorageSync> {
298
+ if (!this.isAvailable()) await this.makeAvailable()
136
299
  // Wait for a current sync task to complete...
137
300
  while (this._syncLocked) {
138
301
  await wait(100)
@@ -152,6 +315,7 @@ export class WalletStorageManager implements sdk.WalletStorage {
152
315
  }
153
316
 
154
317
  async getActiveForStorageProvider(): Promise<StorageProvider> {
318
+ if (!this.isAvailable()) await this.makeAvailable()
155
319
  // Wait for a current storageProvider call to complete...
156
320
  while (this._storageProviderLocked) {
157
321
  await wait(100)
@@ -234,21 +398,19 @@ export class WalletStorageManager implements sdk.WalletStorage {
234
398
  return this.getActive().isStorageProvider()
235
399
  }
236
400
 
237
- isAvailable(): boolean {
238
- return this.getActive().isAvailable()
239
- }
240
-
241
401
  async addWalletStorageProvider(
242
402
  provider: sdk.WalletStorageProvider
243
403
  ): Promise<void> {
244
404
  await provider.makeAvailable()
245
405
  if (this._services) provider.setServices(this._services)
246
- this.stores.push(provider)
406
+ this._stores.push(new ManagedStorage(provider))
407
+ this._isAvailable = false
408
+ await this.makeAvailable()
247
409
  }
248
410
 
249
411
  setServices(v: sdk.WalletServices) {
250
412
  this._services = v
251
- for (const store of this.stores) store.setServices(v)
413
+ for (const store of this._stores) store.storage.setServices(v)
252
414
  }
253
415
  getServices(): sdk.WalletServices {
254
416
  if (!this._services)
@@ -260,13 +422,6 @@ export class WalletStorageManager implements sdk.WalletStorage {
260
422
  return this.getActive().getSettings()
261
423
  }
262
424
 
263
- async makeAvailable(): Promise<TableSettings> {
264
- return await this.runAsWriter(async writer => {
265
- writer.makeAvailable()
266
- return writer.getSettings()
267
- })
268
- }
269
-
270
425
  async migrate(
271
426
  storageName: string,
272
427
  storageIdentityKey: string
@@ -277,9 +432,9 @@ export class WalletStorageManager implements sdk.WalletStorage {
277
432
  }
278
433
 
279
434
  async destroy(): Promise<void> {
280
- if (this.stores.length < 1) return
435
+ if (this._stores.length < 1) return
281
436
  return await this.runAsWriter(async writer => {
282
- for (const store of this.stores) await store.destroy()
437
+ for (const store of this._stores) await store.storage.destroy()
283
438
  })
284
439
  }
285
440
 
@@ -415,21 +570,27 @@ export class WalletStorageManager implements sdk.WalletStorage {
415
570
 
416
571
  async syncFromReader(
417
572
  identityKey: string,
418
- reader: StorageSyncReader
419
- ): Promise<void> {
573
+ reader: sdk.WalletStorageSyncReader,
574
+ activeSync?: sdk.WalletStorageSync,
575
+ log: string = ''
576
+ ): Promise<{ inserts: number; updates: number; log: string }> {
420
577
  const auth = await this.getAuth()
421
578
  if (identityKey !== auth.identityKey) throw new sdk.WERR_UNAUTHORIZED()
422
579
 
423
580
  const readerSettings = await reader.makeAvailable()
424
581
 
425
- return await this.runAsSync(async sync => {
582
+ let inserts = 0,
583
+ updates = 0
584
+
585
+ log = await this.runAsSync(async sync => {
426
586
  const writer = sync
427
587
  const writerSettings = this.getSettings()
428
588
 
429
- let log = ''
430
- let inserts = 0,
431
- updates = 0
589
+ log += `syncFromReader from ${readerSettings.storageName} to ${writerSettings.storageName}\n`
590
+
591
+ let i = -1
432
592
  for (;;) {
593
+ i++
433
594
  const ss = await EntitySyncState.fromStorage(
434
595
  writer,
435
596
  identityKey,
@@ -440,43 +601,45 @@ export class WalletStorageManager implements sdk.WalletStorage {
440
601
  writerSettings.storageIdentityKey
441
602
  )
442
603
  const chunk = await reader.getSyncChunk(args)
604
+ if (chunk.user) {
605
+ // Merging state from a reader cannot update activeStorage
606
+ chunk.user.activeStorage = this._active!.user!.activeStorage
607
+ }
443
608
  const r = await writer.processSyncChunk(args, chunk)
444
609
  inserts += r.inserts
445
610
  updates += r.updates
446
- //log += `${r.maxUpdated_at} inserted ${r.inserts} updated ${r.updates}\n`
611
+ log += `chunk ${i} inserted ${r.inserts} updated ${r.updates} ${r.maxUpdated_at}\n`
447
612
  if (r.done) break
448
613
  }
449
- //console.log(log)
450
- console.log(`sync complete: ${inserts} inserts, ${updates} updates`)
451
- })
452
- }
453
-
454
- async updateBackups(activeSync?: sdk.WalletStorageSync) {
455
- const auth = await this.getAuth()
456
- return await this.runAsSync(async sync => {
457
- for (const backup of this.stores.slice(1)) {
458
- await this.syncToWriter(auth, backup, sync)
459
- }
614
+ log += `syncFromReader complete: ${inserts} inserts, ${updates} updates\n`
615
+ return log
460
616
  }, activeSync)
617
+
618
+ return { inserts, updates, log }
461
619
  }
462
620
 
463
621
  async syncToWriter(
464
622
  auth: sdk.AuthId,
465
623
  writer: sdk.WalletStorageProvider,
466
- activeSync?: sdk.WalletStorageSync
467
- ): Promise<{ inserts: number; updates: number }> {
624
+ activeSync?: sdk.WalletStorageSync,
625
+ log: string = ''
626
+ ): Promise<{ inserts: number; updates: number; log: string }> {
468
627
  const identityKey = auth.identityKey
469
628
 
470
629
  const writerSettings = await writer.makeAvailable()
471
630
 
472
- return await this.runAsSync(async sync => {
631
+ let inserts = 0,
632
+ updates = 0
633
+
634
+ log = await this.runAsSync(async sync => {
473
635
  const reader = sync
474
- const readerSettings = this.getSettings()
636
+ const readerSettings = reader.getSettings()
475
637
 
476
- let log = ''
477
- let inserts = 0,
478
- updates = 0
638
+ log += `syncToWriter from ${readerSettings.storageName} to ${writerSettings.storageName}\n`
639
+
640
+ let i = -1
479
641
  for (;;) {
642
+ i++
480
643
  const ss = await EntitySyncState.fromStorage(
481
644
  writer,
482
645
  identityKey,
@@ -490,50 +653,117 @@ export class WalletStorageManager implements sdk.WalletStorage {
490
653
  const r = await writer.processSyncChunk(args, chunk)
491
654
  inserts += r.inserts
492
655
  updates += r.updates
493
- log += `${r.maxUpdated_at} inserted ${r.inserts} updated ${r.updates}\n`
656
+ log += `chunk ${i} inserted ${r.inserts} updated ${r.updates} ${r.maxUpdated_at}\n`
494
657
  if (r.done) break
495
658
  }
496
- //console.log(log)
497
- //console.log(`sync complete: ${inserts} inserts, ${updates} updates`)
498
- return { inserts, updates }
659
+ log += `syncToWriter complete: ${inserts} inserts, ${updates} updates\n`
660
+ return log
661
+ }, activeSync)
662
+
663
+ return { inserts, updates, log }
664
+ }
665
+
666
+ async updateBackups(activeSync?: sdk.WalletStorageSync) {
667
+ const auth = await this.getAuth(true)
668
+ return await this.runAsSync(async sync => {
669
+ for (const backup of this._backups!) {
670
+ await this.syncToWriter(auth, backup.storage, sync)
671
+ }
499
672
  }, activeSync)
500
673
  }
501
674
 
502
675
  /**
503
676
  * Updates backups and switches to new active storage provider from among current backup providers.
504
677
  *
678
+ * Also resolves conflicting actives.
679
+ *
505
680
  * @param storageIdentityKey of current backup storage provider that is to become the new active provider.
506
681
  */
507
- async setActive(storageIdentityKey: string): Promise<void> {
508
- const newActiveIndex = this.stores.findIndex(
509
- s => s.getSettings().storageIdentityKey === storageIdentityKey
682
+ async setActive(storageIdentityKey: string): Promise<string> {
683
+ if (!this.isAvailable()) await this.makeAvailable()
684
+
685
+ // Confirm a valid storageIdentityKey: must match one of the _stores.
686
+ const newActiveIndex = this._stores.findIndex(
687
+ s => s.settings!.storageIdentityKey === storageIdentityKey
510
688
  )
511
689
  if (newActiveIndex < 0)
512
690
  throw new sdk.WERR_INVALID_PARAMETER(
513
691
  'storageIdentityKey',
514
- `registered with this "WalletStorageManager" as a backup data store.`
692
+ `registered with this "WalletStorageManager". ${storageIdentityKey} does not match any managed store.`
515
693
  )
516
- if (newActiveIndex === 0)
694
+
695
+ const identityKey = (await this.getAuth()).identityKey
696
+ const newActive = this._stores[newActiveIndex]
697
+
698
+ let log = `setActive to ${newActive.settings!.storageName}`
699
+
700
+ if (storageIdentityKey === this.getActiveStore() && this.isActiveEnabled)
517
701
  /** Setting the current active as the new active is a permitted no-op. */
518
- return
702
+ return log + ` unchanged\n`
519
703
 
520
- const auth = await this.getAuth()
521
- const newActive = this.stores[newActiveIndex]
522
- const newActiveStorageIdentityKey = (await newActive.makeAvailable())
523
- .storageIdentityKey
704
+ log += '\n'
524
705
 
525
- return await this.runAsSync(async sync => {
526
- await sync.setActive(auth, newActiveStorageIdentityKey)
527
- await this.updateBackups(sync)
528
- // swap stores...
529
- const oldActive = this.stores[0]
530
- this.stores[0] = this.stores[newActiveIndex]
531
- this.stores[newActiveIndex] = oldActive
532
- this._authId = {
533
- ...this._authId,
534
- userId: undefined,
535
- isActive: undefined
706
+ log += await this.runAsSync(async sync => {
707
+ let log = ''
708
+
709
+ if (this._conflictingActives!.length > 0) {
710
+ // Handle case where new active is current active to resolve conflicts.
711
+ // And where new active is one of the current conflict actives.
712
+ this._conflictingActives!.push(this._active!)
713
+ // Remove the new active from conflicting actives and
714
+ // set new active as the conflicting active that matches the target `storageIdentityKey`
715
+ this._conflictingActives = this._conflictingActives!.filter(ca => {
716
+ const isNewActive =
717
+ ca.settings!.storageIdentityKey === storageIdentityKey
718
+ return !isNewActive
719
+ })
720
+
721
+ // Merge state from conflicting actives into `newActive`.
722
+ for (const conflict of this._conflictingActives) {
723
+ const sfr = await this.syncToWriter(
724
+ { identityKey, userId: newActive.user!.userId, isActive: false },
725
+ newActive.storage,
726
+ conflict.storage
727
+ )
728
+ log += sfr.log
729
+ }
730
+ }
731
+
732
+ // If there were conflicting actives,
733
+ // Push state merged from all merged actives into newActive to all stores other than the now single active.
734
+ // Otherwise,
735
+ // Push state from current active to all other stores.
736
+ const backupSource =
737
+ this._conflictingActives!.length > 0 ? newActive : this._active!
738
+
739
+ for (const store of this._stores) {
740
+ // Update all store's user records to reflect new active store
741
+ await store.storage.setActive(
742
+ { identityKey, userId: store.user!.userId },
743
+ storageIdentityKey
744
+ )
745
+ // Update cached user.activeStorage of all stores
746
+ store.user!.activeStorage = storageIdentityKey
747
+
748
+ if (
749
+ store.settings!.storageIdentityKey !==
750
+ backupSource.settings!.storageIdentityKey
751
+ ) {
752
+ const stwr = await this.syncToWriter(
753
+ { identityKey, userId: store.user!.userId, isActive: false },
754
+ store.storage,
755
+ backupSource.storage
756
+ )
757
+ log += stwr.log
758
+ }
536
759
  }
760
+
761
+ this._isAvailable = false
762
+ await this.makeAvailable()
763
+
764
+ return log
537
765
  })
766
+
767
+ return log
538
768
  }
539
769
  }
@@ -1,6 +1,6 @@
1
1
  /* eslint-disable @typescript-eslint/no-unused-vars */
2
2
  import { Knex } from 'knex'
3
- import { sdk } from '../../index.all'
3
+ import { sdk, StorageKnex } from '../../index.all'
4
4
  import { DBType } from '../StorageReader'
5
5
 
6
6
  interface Migration {
@@ -91,6 +91,28 @@ export class KnexMigrations implements MigrationSource<string> {
91
91
  }
92
92
  }
93
93
 
94
+ migrations['2025-02-22-001 nonNULL activeStorage'] = {
95
+ async up(knex) {
96
+ const storage = new StorageKnex({
97
+ ...StorageKnex.defaultOptions(),
98
+ chain: <sdk.Chain>chain,
99
+ knex
100
+ })
101
+ const settings = await storage.makeAvailable()
102
+ await knex.raw(
103
+ `update users set activeStorage = '${settings.storageIdentityKey}' where activeStorage is NULL`
104
+ )
105
+ await knex.schema.alterTable('users', table => {
106
+ table.string('activeStorage').notNullable().alter()
107
+ })
108
+ },
109
+ async down(knex) {
110
+ await knex.schema.alterTable('users', table => {
111
+ table.string('activeStorage').nullable().alter()
112
+ })
113
+ }
114
+ }
115
+
94
116
  migrations['2025-01-21-001 add activeStorage to users'] = {
95
117
  async up(knex) {
96
118
  await knex.schema.alterTable('users', table => {
@@ -309,7 +309,7 @@ export class EntitySyncState extends EntityBase<TableSyncState> {
309
309
  ): sdk.RequestSyncChunkArgs {
310
310
  const a: sdk.RequestSyncChunkArgs = {
311
311
  identityKey: forIdentityKey,
312
- maxRoughSize: maxRoughSize || 20000000,
312
+ maxRoughSize: maxRoughSize || 10000000,
313
313
  maxItems: maxItems || 1000,
314
314
  offsets: [],
315
315
  since: this.when,
@@ -18,7 +18,7 @@ export class EntityUser extends EntityBase<TableUser> {
18
18
  created_at: now,
19
19
  updated_at: now,
20
20
  identityKey: '',
21
- activeStorage: undefined
21
+ activeStorage: ''
22
22
  }
23
23
  )
24
24
  }
@@ -54,7 +54,7 @@ export class EntityUser extends EntityBase<TableUser> {
54
54
  get activeStorage() {
55
55
  return this.api.activeStorage
56
56
  }
57
- set activeStorage(v: string | undefined) {
57
+ set activeStorage(v: string) {
58
58
  this.api.activeStorage = v
59
59
  }
60
60
 
@@ -146,7 +146,8 @@ describe('SyncState class method tests', () => {
146
146
  userId: 1,
147
147
  identityKey: 'testIdentityKey',
148
148
  created_at: new Date(), // Add required property
149
- updated_at: new Date() // Add required property
149
+ updated_at: new Date(), // Add required property
150
+ activeStorage: ''
150
151
  },
151
152
  provenTxs: [],
152
153
  provenTxReqs: [],