@spacesops/wdk-react-native-core 1.0.0-beta.40
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/README.md +551 -0
- package/dist/__mocks__/react.d.ts +40 -0
- package/dist/__mocks__/react.js +33 -0
- package/dist/__mocks__/react.js.map +1 -0
- package/dist/__mocks__/secureStorage.d.ts +26 -0
- package/dist/__mocks__/secureStorage.js +80 -0
- package/dist/__mocks__/secureStorage.js.map +1 -0
- package/dist/__tests__/hooks/useBalance.test.d.ts +6 -0
- package/dist/__tests__/hooks/useBalance.test.js +217 -0
- package/dist/__tests__/hooks/useBalance.test.js.map +1 -0
- package/dist/__tests__/hooks/useWallet.test.d.ts +6 -0
- package/dist/__tests__/hooks/useWallet.test.js +105 -0
- package/dist/__tests__/hooks/useWallet.test.js.map +1 -0
- package/dist/__tests__/hooks/useWalletManager.test.d.ts +6 -0
- package/dist/__tests__/hooks/useWalletManager.test.js +193 -0
- package/dist/__tests__/hooks/useWalletManager.test.js.map +1 -0
- package/dist/__tests__/hooks/useWdkApp.test.d.ts +6 -0
- package/dist/__tests__/hooks/useWdkApp.test.js +55 -0
- package/dist/__tests__/hooks/useWdkApp.test.js.map +1 -0
- package/dist/__tests__/hooks/useWorklet.test.d.ts +6 -0
- package/dist/__tests__/hooks/useWorklet.test.js +168 -0
- package/dist/__tests__/hooks/useWorklet.test.js.map +1 -0
- package/dist/__tests__/provider/WdkAppProvider.test.d.ts +6 -0
- package/dist/__tests__/provider/WdkAppProvider.test.js +57 -0
- package/dist/__tests__/provider/WdkAppProvider.test.js.map +1 -0
- package/dist/__tests__/services/accountService.test.d.ts +6 -0
- package/dist/__tests__/services/accountService.test.js +216 -0
- package/dist/__tests__/services/accountService.test.js.map +1 -0
- package/dist/__tests__/services/addressService.test.d.ts +6 -0
- package/dist/__tests__/services/addressService.test.js +246 -0
- package/dist/__tests__/services/addressService.test.js.map +1 -0
- package/dist/__tests__/services/balanceService.test.d.ts +6 -0
- package/dist/__tests__/services/balanceService.test.js +333 -0
- package/dist/__tests__/services/balanceService.test.js.map +1 -0
- package/dist/__tests__/services/walletSetupService.test.d.ts +6 -0
- package/dist/__tests__/services/walletSetupService.test.js +304 -0
- package/dist/__tests__/services/walletSetupService.test.js.map +1 -0
- package/dist/__tests__/services/walletSwitchingService.test.d.ts +6 -0
- package/dist/__tests__/services/walletSwitchingService.test.js +209 -0
- package/dist/__tests__/services/walletSwitchingService.test.js.map +1 -0
- package/dist/__tests__/services/workletLifecycleService.test.d.ts +6 -0
- package/dist/__tests__/services/workletLifecycleService.test.js +451 -0
- package/dist/__tests__/services/workletLifecycleService.test.js.map +1 -0
- package/dist/__tests__/setup.d.ts +7 -0
- package/dist/__tests__/setup.js +49 -0
- package/dist/__tests__/setup.js.map +1 -0
- package/dist/__tests__/storage/mmkvStorage.test.d.ts +6 -0
- package/dist/__tests__/storage/mmkvStorage.test.js +213 -0
- package/dist/__tests__/storage/mmkvStorage.test.js.map +1 -0
- package/dist/__tests__/store/walletStore.test.d.ts +4 -0
- package/dist/__tests__/store/walletStore.test.js +86 -0
- package/dist/__tests__/store/walletStore.test.js.map +1 -0
- package/dist/__tests__/store/workletStore.test.d.ts +4 -0
- package/dist/__tests__/store/workletStore.test.js +255 -0
- package/dist/__tests__/store/workletStore.test.js.map +1 -0
- package/dist/__tests__/types/hrpc.test.d.ts +4 -0
- package/dist/__tests__/types/hrpc.test.js +99 -0
- package/dist/__tests__/types/hrpc.test.js.map +1 -0
- package/dist/__tests__/utils/balanceUtils.test.d.ts +4 -0
- package/dist/__tests__/utils/balanceUtils.test.js +133 -0
- package/dist/__tests__/utils/balanceUtils.test.js.map +1 -0
- package/dist/__tests__/utils/errorHandling.test.d.ts +6 -0
- package/dist/__tests__/utils/errorHandling.test.js +119 -0
- package/dist/__tests__/utils/errorHandling.test.js.map +1 -0
- package/dist/__tests__/utils/errorUtils.test.d.ts +4 -0
- package/dist/__tests__/utils/errorUtils.test.js +133 -0
- package/dist/__tests__/utils/errorUtils.test.js.map +1 -0
- package/dist/__tests__/utils/initializationState.test.d.ts +6 -0
- package/dist/__tests__/utils/initializationState.test.js +243 -0
- package/dist/__tests__/utils/initializationState.test.js.map +1 -0
- package/dist/__tests__/utils/jsonUtils.test.d.ts +4 -0
- package/dist/__tests__/utils/jsonUtils.test.js +118 -0
- package/dist/__tests__/utils/jsonUtils.test.js.map +1 -0
- package/dist/__tests__/utils/mmkvKeyManager.test.d.ts +6 -0
- package/dist/__tests__/utils/mmkvKeyManager.test.js +158 -0
- package/dist/__tests__/utils/mmkvKeyManager.test.js.map +1 -0
- package/dist/__tests__/utils/mnemonicUtils.test.d.ts +4 -0
- package/dist/__tests__/utils/mnemonicUtils.test.js +40 -0
- package/dist/__tests__/utils/mnemonicUtils.test.js.map +1 -0
- package/dist/__tests__/utils/raceConditions.test.d.ts +6 -0
- package/dist/__tests__/utils/raceConditions.test.js +239 -0
- package/dist/__tests__/utils/raceConditions.test.js.map +1 -0
- package/dist/__tests__/utils/result.test.d.ts +4 -0
- package/dist/__tests__/utils/result.test.js +66 -0
- package/dist/__tests__/utils/result.test.js.map +1 -0
- package/dist/__tests__/utils/schemas.test.d.ts +4 -0
- package/dist/__tests__/utils/schemas.test.js +66 -0
- package/dist/__tests__/utils/schemas.test.js.map +1 -0
- package/dist/__tests__/utils/storeHelpers.test.d.ts +4 -0
- package/dist/__tests__/utils/storeHelpers.test.js +227 -0
- package/dist/__tests__/utils/storeHelpers.test.js.map +1 -0
- package/dist/__tests__/utils/typeGuards.test.d.ts +4 -0
- package/dist/__tests__/utils/typeGuards.test.js +175 -0
- package/dist/__tests__/utils/typeGuards.test.js.map +1 -0
- package/dist/__tests__/utils/validation.test.d.ts +4 -0
- package/dist/__tests__/utils/validation.test.js +126 -0
- package/dist/__tests__/utils/validation.test.js.map +1 -0
- package/dist/__tests__/utils/walletUtils.test.d.ts +4 -0
- package/dist/__tests__/utils/walletUtils.test.js +156 -0
- package/dist/__tests__/utils/walletUtils.test.js.map +1 -0
- package/dist/hooks/useBalance.d.ts +170 -0
- package/dist/hooks/useBalance.js +442 -0
- package/dist/hooks/useBalance.js.map +1 -0
- package/dist/hooks/useWallet.d.ts +43 -0
- package/dist/hooks/useWallet.js +193 -0
- package/dist/hooks/useWallet.js.map +1 -0
- package/dist/hooks/useWalletManager.d.ts +100 -0
- package/dist/hooks/useWalletManager.js +526 -0
- package/dist/hooks/useWalletManager.js.map +1 -0
- package/dist/hooks/useWdkApp.d.ts +66 -0
- package/dist/hooks/useWdkApp.js +74 -0
- package/dist/hooks/useWdkApp.js.map +1 -0
- package/dist/hooks/useWorklet.d.ts +66 -0
- package/dist/hooks/useWorklet.js +42 -0
- package/dist/hooks/useWorklet.js.map +1 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.js +36 -0
- package/dist/index.js.map +1 -0
- package/dist/provider/WdkAppProvider.d.ts +112 -0
- package/dist/provider/WdkAppProvider.js +558 -0
- package/dist/provider/WdkAppProvider.js.map +1 -0
- package/dist/services/accountService.d.ts +52 -0
- package/dist/services/accountService.js +119 -0
- package/dist/services/accountService.js.map +1 -0
- package/dist/services/addressService.d.ts +22 -0
- package/dist/services/addressService.js +110 -0
- package/dist/services/addressService.js.map +1 -0
- package/dist/services/balanceService.d.ts +114 -0
- package/dist/services/balanceService.js +208 -0
- package/dist/services/balanceService.js.map +1 -0
- package/dist/services/walletSetupService.d.ts +217 -0
- package/dist/services/walletSetupService.js +535 -0
- package/dist/services/walletSetupService.js.map +1 -0
- package/dist/services/walletSwitchingService.d.ts +48 -0
- package/dist/services/walletSwitchingService.js +143 -0
- package/dist/services/walletSwitchingService.js.map +1 -0
- package/dist/services/workletLifecycleService.d.ts +117 -0
- package/dist/services/workletLifecycleService.js +433 -0
- package/dist/services/workletLifecycleService.js.map +1 -0
- package/dist/storage/mmkvStorage.d.ts +44 -0
- package/dist/storage/mmkvStorage.js +348 -0
- package/dist/storage/mmkvStorage.js.map +1 -0
- package/dist/store/walletStore.d.ts +130 -0
- package/dist/store/walletStore.js +204 -0
- package/dist/store/walletStore.js.map +1 -0
- package/dist/store/workletStore.d.ts +121 -0
- package/dist/store/workletStore.js +256 -0
- package/dist/store/workletStore.js.map +1 -0
- package/dist/types/hrpc.d.ts +60 -0
- package/dist/types/hrpc.js +26 -0
- package/dist/types/hrpc.js.map +1 -0
- package/dist/types.d.ts +168 -0
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/balanceUtils.d.ts +30 -0
- package/dist/utils/balanceUtils.js +86 -0
- package/dist/utils/balanceUtils.js.map +1 -0
- package/dist/utils/constants.d.ts +66 -0
- package/dist/utils/constants.js +73 -0
- package/dist/utils/constants.js.map +1 -0
- package/dist/utils/errorHandling.d.ts +47 -0
- package/dist/utils/errorHandling.js +54 -0
- package/dist/utils/errorHandling.js.map +1 -0
- package/dist/utils/errorUtils.d.ts +89 -0
- package/dist/utils/errorUtils.js +264 -0
- package/dist/utils/errorUtils.js.map +1 -0
- package/dist/utils/initializationState.d.ts +129 -0
- package/dist/utils/initializationState.js +220 -0
- package/dist/utils/initializationState.js.map +1 -0
- package/dist/utils/jsonUtils.d.ts +21 -0
- package/dist/utils/jsonUtils.js +67 -0
- package/dist/utils/jsonUtils.js.map +1 -0
- package/dist/utils/logger.d.ts +27 -0
- package/dist/utils/logger.js +50 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/mmkvKeyManager.d.ts +57 -0
- package/dist/utils/mmkvKeyManager.js +214 -0
- package/dist/utils/mmkvKeyManager.js.map +1 -0
- package/dist/utils/mnemonicUtils.d.ts +17 -0
- package/dist/utils/mnemonicUtils.js +28 -0
- package/dist/utils/mnemonicUtils.js.map +1 -0
- package/dist/utils/operationMutex.d.ts +69 -0
- package/dist/utils/operationMutex.js +150 -0
- package/dist/utils/operationMutex.js.map +1 -0
- package/dist/utils/result.d.ts +34 -0
- package/dist/utils/result.js +43 -0
- package/dist/utils/result.js.map +1 -0
- package/dist/utils/schemas.d.ts +365 -0
- package/dist/utils/schemas.js +152 -0
- package/dist/utils/schemas.js.map +1 -0
- package/dist/utils/storeHelpers.d.ts +112 -0
- package/dist/utils/storeHelpers.js +198 -0
- package/dist/utils/storeHelpers.js.map +1 -0
- package/dist/utils/typeGuards.d.ts +70 -0
- package/dist/utils/typeGuards.js +95 -0
- package/dist/utils/typeGuards.js.map +1 -0
- package/dist/utils/validation.d.ts +53 -0
- package/dist/utils/validation.js +152 -0
- package/dist/utils/validation.js.map +1 -0
- package/dist/utils/walletStateHelpers.d.ts +62 -0
- package/dist/utils/walletStateHelpers.js +81 -0
- package/dist/utils/walletStateHelpers.js.map +1 -0
- package/dist/utils/walletUtils.d.ts +38 -0
- package/dist/utils/walletUtils.js +87 -0
- package/dist/utils/walletUtils.js.map +1 -0
- package/package.json +79 -0
- package/src/__mocks__/react.ts +34 -0
- package/src/__mocks__/secureStorage.ts +87 -0
- package/src/__tests__/hooks/useBalance.test.ts +286 -0
- package/src/__tests__/hooks/useWallet.test.ts +122 -0
- package/src/__tests__/hooks/useWalletManager.test.ts +271 -0
- package/src/__tests__/hooks/useWdkApp.test.tsx +59 -0
- package/src/__tests__/hooks/useWorklet.test.ts +195 -0
- package/src/__tests__/provider/WdkAppProvider.test.tsx +67 -0
- package/src/__tests__/services/accountService.test.ts +337 -0
- package/src/__tests__/services/addressService.test.ts +312 -0
- package/src/__tests__/services/balanceService.test.ts +411 -0
- package/src/__tests__/services/walletSetupService.test.ts +463 -0
- package/src/__tests__/services/walletSwitchingService.test.ts +258 -0
- package/src/__tests__/services/workletLifecycleService.test.ts +514 -0
- package/src/__tests__/setup.ts +54 -0
- package/src/__tests__/storage/mmkvStorage.test.ts +325 -0
- package/src/__tests__/store/walletStore.test.ts +101 -0
- package/src/__tests__/store/workletStore.test.ts +329 -0
- package/src/__tests__/types/hrpc.test.ts +125 -0
- package/src/__tests__/utils/balanceUtils.test.ts +151 -0
- package/src/__tests__/utils/errorHandling.test.ts +147 -0
- package/src/__tests__/utils/errorUtils.test.ts +170 -0
- package/src/__tests__/utils/initializationState.test.ts +337 -0
- package/src/__tests__/utils/jsonUtils.test.ts +131 -0
- package/src/__tests__/utils/mmkvKeyManager.test.ts +205 -0
- package/src/__tests__/utils/mnemonicUtils.test.ts +48 -0
- package/src/__tests__/utils/raceConditions.test.ts +292 -0
- package/src/__tests__/utils/result.test.ts +73 -0
- package/src/__tests__/utils/schemas.test.ts +82 -0
- package/src/__tests__/utils/storeHelpers.test.ts +323 -0
- package/src/__tests__/utils/typeGuards.test.ts +209 -0
- package/src/__tests__/utils/validation.test.ts +152 -0
- package/src/__tests__/utils/walletUtils.test.ts +195 -0
- package/src/hooks/useBalance.ts +597 -0
- package/src/hooks/useWallet.ts +288 -0
- package/src/hooks/useWalletManager.ts +654 -0
- package/src/hooks/useWdkApp.ts +78 -0
- package/src/hooks/useWorklet.ts +109 -0
- package/src/index.ts +112 -0
- package/src/provider/WdkAppProvider.tsx +764 -0
- package/src/services/accountService.ts +134 -0
- package/src/services/addressService.ts +125 -0
- package/src/services/balanceService.ts +291 -0
- package/src/services/walletSetupService.ts +695 -0
- package/src/services/walletSwitchingService.ts +157 -0
- package/src/services/workletLifecycleService.ts +561 -0
- package/src/storage/mmkvStorage.ts +417 -0
- package/src/store/walletStore.ts +301 -0
- package/src/store/workletStore.ts +340 -0
- package/src/types/hrpc.ts +78 -0
- package/src/types.ts +188 -0
- package/src/utils/balanceUtils.ts +96 -0
- package/src/utils/constants.ts +84 -0
- package/src/utils/errorHandling.md +163 -0
- package/src/utils/errorHandling.ts +61 -0
- package/src/utils/errorUtils.ts +300 -0
- package/src/utils/initializationState.ts +242 -0
- package/src/utils/jsonUtils.ts +75 -0
- package/src/utils/logger.ts +54 -0
- package/src/utils/mmkvKeyManager.ts +256 -0
- package/src/utils/mnemonicUtils.ts +35 -0
- package/src/utils/operationMutex.ts +181 -0
- package/src/utils/result.ts +57 -0
- package/src/utils/schemas.ts +190 -0
- package/src/utils/storeHelpers.ts +236 -0
- package/src/utils/typeGuards.ts +129 -0
- package/src/utils/validation.ts +174 -0
- package/src/utils/walletStateHelpers.ts +104 -0
- package/src/utils/walletUtils.ts +107 -0
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
import { createMMKV, type MMKV } from 'react-native-mmkv'
|
|
2
|
+
import { getMMKVKey } from '../utils/mmkvKeyManager'
|
|
3
|
+
import { logWarn } from '../utils/logger'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Storage adapter interface for Zustand persistence
|
|
7
|
+
*/
|
|
8
|
+
export interface StorageAdapter {
|
|
9
|
+
getItem: (name: string) => string | null
|
|
10
|
+
setItem: (name: string, value: string) => void
|
|
11
|
+
removeItem: (name: string) => void
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Maximum number of storage instances to cache before evicting least recently used
|
|
16
|
+
* This prevents unbounded memory growth while maintaining performance
|
|
17
|
+
*/
|
|
18
|
+
const MAX_STORAGE_CACHE_SIZE = 10
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Cache for MMKV storage instances to avoid recreating them
|
|
22
|
+
* Uses LRU (Least Recently Used) eviction policy to limit memory usage
|
|
23
|
+
*/
|
|
24
|
+
const storageCache = new Map<string, MMKV>()
|
|
25
|
+
const storageAccessOrder = new Map<string, number>()
|
|
26
|
+
let storageAccessCounter = 0
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Evict least recently used storage instance from cache when limit is reached
|
|
30
|
+
*/
|
|
31
|
+
function evictLRUStorage(): void {
|
|
32
|
+
if (storageCache.size < MAX_STORAGE_CACHE_SIZE) {
|
|
33
|
+
return
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Find the least recently used storage
|
|
37
|
+
let oldestKey: string | null = null
|
|
38
|
+
let oldestAccess = Infinity
|
|
39
|
+
|
|
40
|
+
for (const [key, accessTime] of storageAccessOrder.entries()) {
|
|
41
|
+
if (accessTime < oldestAccess) {
|
|
42
|
+
oldestAccess = accessTime
|
|
43
|
+
oldestKey = key
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Remove the least recently used storage
|
|
48
|
+
if (oldestKey !== null) {
|
|
49
|
+
storageCache.delete(oldestKey)
|
|
50
|
+
storageAccessOrder.delete(oldestKey)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Create MMKV storage instance for the wallet
|
|
56
|
+
*
|
|
57
|
+
* SECURITY NOTE: MMKV stores files in the app's document directory, which is app-scoped.
|
|
58
|
+
* Two different apps will NOT share data because each app has its own isolated document directory.
|
|
59
|
+
*
|
|
60
|
+
* This is for non-sensitive data persistence (wallet metadata, balances, addresses).
|
|
61
|
+
* For sensitive data (encrypted seeds, keys), use SecureStorage from wdk-react-native-secure-storage.
|
|
62
|
+
*
|
|
63
|
+
* The encryption key is derived from a device/app identifier to ensure each app instance
|
|
64
|
+
* has a unique encryption key. If no account identifier is provided, a default app-scoped
|
|
65
|
+
* identifier is used.
|
|
66
|
+
*
|
|
67
|
+
* @param accountIdentifier - Optional account identifier for per-account encryption keys.
|
|
68
|
+
* If not provided, uses a default app-scoped identifier.
|
|
69
|
+
* @returns Promise that resolves to MMKV storage instance
|
|
70
|
+
*/
|
|
71
|
+
export async function createMMKVStorage(accountIdentifier?: string): Promise<MMKV> {
|
|
72
|
+
// Use account identifier if provided, otherwise use a default app-scoped identifier
|
|
73
|
+
// This ensures each app instance has a unique key while allowing per-account keys
|
|
74
|
+
const identifier = accountIdentifier || 'wdk-app-default'
|
|
75
|
+
|
|
76
|
+
// Check cache first
|
|
77
|
+
const cachedStorage = storageCache.get(identifier)
|
|
78
|
+
if (cachedStorage !== undefined) {
|
|
79
|
+
// Update access time for LRU tracking
|
|
80
|
+
storageAccessCounter++
|
|
81
|
+
storageAccessOrder.set(identifier, storageAccessCounter)
|
|
82
|
+
return cachedStorage
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Evict LRU storage if cache is full
|
|
86
|
+
evictLRUStorage()
|
|
87
|
+
|
|
88
|
+
// Derive encryption key asynchronously
|
|
89
|
+
const encryptionKey = await getMMKVKey(identifier)
|
|
90
|
+
|
|
91
|
+
const storage = createMMKV({
|
|
92
|
+
id: 'wallet-storage',
|
|
93
|
+
encryptionKey,
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
// Cache the storage instance
|
|
97
|
+
storageAccessCounter++
|
|
98
|
+
storageCache.set(identifier, storage)
|
|
99
|
+
storageAccessOrder.set(identifier, storageAccessCounter)
|
|
100
|
+
|
|
101
|
+
return storage
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Maximum number of storage adapters to cache before evicting least recently used
|
|
106
|
+
* This prevents unbounded memory growth while maintaining performance
|
|
107
|
+
*/
|
|
108
|
+
const MAX_ADAPTER_CACHE_SIZE = 10
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Storage adapter cache - stores initialized adapters by identifier
|
|
112
|
+
* Uses LRU (Least Recently Used) eviction policy to limit memory usage
|
|
113
|
+
*/
|
|
114
|
+
const adapterCache = new Map<string, StorageAdapter>()
|
|
115
|
+
const adapterAccessOrder = new Map<string, number>()
|
|
116
|
+
let adapterAccessCounter = 0
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Evict least recently used adapter from cache when limit is reached
|
|
120
|
+
*/
|
|
121
|
+
function evictLRUAdapter(): void {
|
|
122
|
+
if (adapterCache.size < MAX_ADAPTER_CACHE_SIZE) {
|
|
123
|
+
return
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Find the least recently used adapter
|
|
127
|
+
let oldestKey: string | null = null
|
|
128
|
+
let oldestAccess = Infinity
|
|
129
|
+
|
|
130
|
+
for (const [key, accessTime] of adapterAccessOrder.entries()) {
|
|
131
|
+
if (accessTime < oldestAccess) {
|
|
132
|
+
oldestAccess = accessTime
|
|
133
|
+
oldestKey = key
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Remove the least recently used adapter
|
|
138
|
+
if (oldestKey !== null) {
|
|
139
|
+
adapterCache.delete(oldestKey)
|
|
140
|
+
adapterAccessOrder.delete(oldestKey)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Pending operations queue for storage adapters during initialization
|
|
146
|
+
*/
|
|
147
|
+
interface PendingOperation {
|
|
148
|
+
type: 'get' | 'set' | 'remove'
|
|
149
|
+
key: string
|
|
150
|
+
value?: string
|
|
151
|
+
resolve: (value: string | null) => void
|
|
152
|
+
reject: (error: Error) => void
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Storage initialization state
|
|
157
|
+
*/
|
|
158
|
+
enum StorageInitState {
|
|
159
|
+
NOT_STARTED = 'NOT_STARTED',
|
|
160
|
+
INITIALIZING = 'INITIALIZING',
|
|
161
|
+
READY = 'READY',
|
|
162
|
+
ERROR = 'ERROR',
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Storage adapter factory for Zustand persistence
|
|
167
|
+
* This allows Zustand stores to use MMKV for persistence
|
|
168
|
+
*
|
|
169
|
+
* Uses lazy async initialization with synchronous API.
|
|
170
|
+
* Storage is initialized asynchronously when first accessed, then cached.
|
|
171
|
+
* Operations are queued during initialization to prevent race conditions.
|
|
172
|
+
*
|
|
173
|
+
* IMPORTANT: The synchronous API (getItem) may return null during initialization.
|
|
174
|
+
* This is expected behavior - Zustand will handle missing data gracefully on first load.
|
|
175
|
+
* Subsequent operations will be queued and processed once storage is ready.
|
|
176
|
+
*
|
|
177
|
+
* @param accountIdentifier - Optional account identifier for per-account encryption keys.
|
|
178
|
+
* If not provided, uses a default app-scoped identifier.
|
|
179
|
+
* @returns StorageAdapter with lazy async initialization
|
|
180
|
+
*/
|
|
181
|
+
export function createMMKVStorageAdapter(accountIdentifier?: string): StorageAdapter {
|
|
182
|
+
const identifier = accountIdentifier || 'wdk-app-default'
|
|
183
|
+
|
|
184
|
+
// Return cached adapter if available
|
|
185
|
+
const cachedAdapter = adapterCache.get(identifier)
|
|
186
|
+
if (cachedAdapter !== undefined) {
|
|
187
|
+
// Update access time for LRU tracking
|
|
188
|
+
adapterAccessCounter++
|
|
189
|
+
adapterAccessOrder.set(identifier, adapterAccessCounter)
|
|
190
|
+
return cachedAdapter
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Evict LRU adapter if cache is full
|
|
194
|
+
evictLRUAdapter()
|
|
195
|
+
|
|
196
|
+
let storageInstance: MMKV | null = null
|
|
197
|
+
let storagePromise: Promise<MMKV> | null = null
|
|
198
|
+
let initState: StorageInitState = StorageInitState.NOT_STARTED
|
|
199
|
+
let initError: Error | null = null
|
|
200
|
+
const pendingOperations: PendingOperation[] = []
|
|
201
|
+
|
|
202
|
+
// Process queued operations after storage is ready
|
|
203
|
+
const processPendingOperations = (storage: MMKV): void => {
|
|
204
|
+
// Process all pending operations in order
|
|
205
|
+
const operations = [...pendingOperations]
|
|
206
|
+
pendingOperations.length = 0 // Clear the array
|
|
207
|
+
|
|
208
|
+
for (const op of operations) {
|
|
209
|
+
try {
|
|
210
|
+
switch (op.type) {
|
|
211
|
+
case 'get':
|
|
212
|
+
const value = storage.getString(op.key)
|
|
213
|
+
op.resolve(value ?? null)
|
|
214
|
+
break
|
|
215
|
+
case 'set':
|
|
216
|
+
if (op.value !== undefined) {
|
|
217
|
+
storage.set(op.key, op.value)
|
|
218
|
+
op.resolve(null)
|
|
219
|
+
}
|
|
220
|
+
break
|
|
221
|
+
case 'remove':
|
|
222
|
+
;(storage as MMKVWithDelete).delete(op.key)
|
|
223
|
+
op.resolve(null)
|
|
224
|
+
break
|
|
225
|
+
}
|
|
226
|
+
} catch (error) {
|
|
227
|
+
op.reject(error instanceof Error ? error : new Error(String(error)))
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Initialize storage asynchronously
|
|
233
|
+
const ensureStorage = async (): Promise<MMKV> => {
|
|
234
|
+
// Return immediately if already ready
|
|
235
|
+
if (storageInstance && initState === StorageInitState.READY) {
|
|
236
|
+
return storageInstance
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Return existing promise if initializing
|
|
240
|
+
if (storagePromise && initState === StorageInitState.INITIALIZING) {
|
|
241
|
+
return storagePromise
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// If we're in error state, throw the error
|
|
245
|
+
if (initState === StorageInitState.ERROR && initError) {
|
|
246
|
+
throw initError
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Start initialization
|
|
250
|
+
if (initState === StorageInitState.NOT_STARTED) {
|
|
251
|
+
initState = StorageInitState.INITIALIZING
|
|
252
|
+
initError = null
|
|
253
|
+
|
|
254
|
+
storagePromise = createMMKVStorage(identifier)
|
|
255
|
+
.then((storage) => {
|
|
256
|
+
storageInstance = storage
|
|
257
|
+
initState = StorageInitState.READY
|
|
258
|
+
initError = null
|
|
259
|
+
|
|
260
|
+
// Process all pending operations
|
|
261
|
+
processPendingOperations(storage)
|
|
262
|
+
|
|
263
|
+
return storage
|
|
264
|
+
})
|
|
265
|
+
.catch((error) => {
|
|
266
|
+
const err = error instanceof Error ? error : new Error(String(error))
|
|
267
|
+
initState = StorageInitState.ERROR
|
|
268
|
+
initError = err
|
|
269
|
+
storagePromise = null
|
|
270
|
+
|
|
271
|
+
// Reject all pending operations
|
|
272
|
+
const operations = [...pendingOperations]
|
|
273
|
+
pendingOperations.length = 0
|
|
274
|
+
for (const op of operations) {
|
|
275
|
+
op.reject(err)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
throw err
|
|
279
|
+
})
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// At this point, storagePromise should be set
|
|
283
|
+
if (!storagePromise) {
|
|
284
|
+
const err = new Error('Failed to initialize storage: promise not created')
|
|
285
|
+
initState = StorageInitState.ERROR
|
|
286
|
+
initError = err
|
|
287
|
+
throw err
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return storagePromise
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Start initialization immediately in the background
|
|
294
|
+
// This helps reduce the window where operations need to be queued
|
|
295
|
+
ensureStorage().catch((error) => {
|
|
296
|
+
// Error is stored in initError and will be thrown on next access
|
|
297
|
+
// This prevents unhandled promise rejections
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
const adapter: StorageAdapter = {
|
|
301
|
+
getItem: (name: string): string | null => {
|
|
302
|
+
// If storage is ready, read directly
|
|
303
|
+
if (storageInstance && initState === StorageInitState.READY) {
|
|
304
|
+
try {
|
|
305
|
+
const value = storageInstance.getString(name)
|
|
306
|
+
return value ?? null
|
|
307
|
+
} catch (error) {
|
|
308
|
+
// If read fails, return null (Zustand will handle gracefully)
|
|
309
|
+
return null
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// If we're in error state, return null (Zustand will handle gracefully)
|
|
314
|
+
// The error will be thrown on next write operation
|
|
315
|
+
if (initState === StorageInitState.ERROR) {
|
|
316
|
+
return null
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// LIMITATION: During initialization, we cannot return values synchronously
|
|
320
|
+
// because storage initialization is async. Zustand's StorageAdapter interface
|
|
321
|
+
// requires synchronous getItem, so we must return null during initialization.
|
|
322
|
+
//
|
|
323
|
+
// This is expected behavior:
|
|
324
|
+
// - On first load during initialization: returns null, Zustand uses default state
|
|
325
|
+
// - After initialization: subsequent getItem calls will work correctly
|
|
326
|
+
// - Zustand handles null gracefully and will rehydrate on next read after init
|
|
327
|
+
//
|
|
328
|
+
// We don't queue get operations because we can't fulfill them synchronously.
|
|
329
|
+
// Instead, we ensure storage is initializing and return null immediately.
|
|
330
|
+
ensureStorage().catch(() => {
|
|
331
|
+
// Errors are handled in ensureStorage and stored in initError
|
|
332
|
+
})
|
|
333
|
+
|
|
334
|
+
// Return null during initialization - Zustand handles this gracefully
|
|
335
|
+
// The actual value will be available on next read after initialization completes
|
|
336
|
+
return null
|
|
337
|
+
},
|
|
338
|
+
setItem: (name: string, value: string): void => {
|
|
339
|
+
// If storage is ready, write directly
|
|
340
|
+
if (storageInstance && initState === StorageInitState.READY) {
|
|
341
|
+
try {
|
|
342
|
+
storageInstance.set(name, value)
|
|
343
|
+
return
|
|
344
|
+
} catch (error) {
|
|
345
|
+
// If write fails, queue it to retry later
|
|
346
|
+
// This handles edge cases where storage becomes unavailable
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Queue the write operation
|
|
351
|
+
pendingOperations.push({
|
|
352
|
+
type: 'set',
|
|
353
|
+
key: name,
|
|
354
|
+
value,
|
|
355
|
+
resolve: () => {
|
|
356
|
+
// Operation completed successfully
|
|
357
|
+
},
|
|
358
|
+
reject: (error) => {
|
|
359
|
+
// Log error but don't throw - this is non-critical data
|
|
360
|
+
// In production, you might want to log this to error tracking
|
|
361
|
+
logWarn('[MMKVStorageAdapter] Failed to set item:', name, error)
|
|
362
|
+
},
|
|
363
|
+
})
|
|
364
|
+
|
|
365
|
+
// Ensure storage is initializing
|
|
366
|
+
ensureStorage().catch((error) => {
|
|
367
|
+
// Error is stored and will be thrown when processing pending operations
|
|
368
|
+
// This ensures queued operations fail appropriately
|
|
369
|
+
})
|
|
370
|
+
},
|
|
371
|
+
removeItem: (name: string): void => {
|
|
372
|
+
// If storage is ready, delete directly
|
|
373
|
+
if (storageInstance && initState === StorageInitState.READY) {
|
|
374
|
+
try {
|
|
375
|
+
;(storageInstance as MMKVWithDelete).delete(name)
|
|
376
|
+
return
|
|
377
|
+
} catch (error) {
|
|
378
|
+
// If delete fails, queue it to retry later
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Queue the delete operation
|
|
383
|
+
pendingOperations.push({
|
|
384
|
+
type: 'remove',
|
|
385
|
+
key: name,
|
|
386
|
+
resolve: () => {
|
|
387
|
+
// Operation completed successfully
|
|
388
|
+
},
|
|
389
|
+
reject: (error) => {
|
|
390
|
+
// Log error but don't throw - this is non-critical data
|
|
391
|
+
logWarn('[MMKVStorageAdapter] Failed to remove item:', name, error)
|
|
392
|
+
},
|
|
393
|
+
})
|
|
394
|
+
|
|
395
|
+
// Ensure storage is initializing
|
|
396
|
+
ensureStorage().catch((error) => {
|
|
397
|
+
// Error is stored and will be thrown when processing pending operations
|
|
398
|
+
})
|
|
399
|
+
},
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Cache the adapter
|
|
403
|
+
adapterAccessCounter++
|
|
404
|
+
adapterCache.set(identifier, adapter)
|
|
405
|
+
adapterAccessOrder.set(identifier, adapterAccessCounter)
|
|
406
|
+
|
|
407
|
+
return adapter
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Extended MMKV type that includes the delete method
|
|
412
|
+
* MMKV has a delete method but TypeScript types may not include it
|
|
413
|
+
*/
|
|
414
|
+
interface MMKVWithDelete extends MMKV {
|
|
415
|
+
delete(key: string): void
|
|
416
|
+
}
|
|
417
|
+
|