@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.
Files changed (275) hide show
  1. package/README.md +551 -0
  2. package/dist/__mocks__/react.d.ts +40 -0
  3. package/dist/__mocks__/react.js +33 -0
  4. package/dist/__mocks__/react.js.map +1 -0
  5. package/dist/__mocks__/secureStorage.d.ts +26 -0
  6. package/dist/__mocks__/secureStorage.js +80 -0
  7. package/dist/__mocks__/secureStorage.js.map +1 -0
  8. package/dist/__tests__/hooks/useBalance.test.d.ts +6 -0
  9. package/dist/__tests__/hooks/useBalance.test.js +217 -0
  10. package/dist/__tests__/hooks/useBalance.test.js.map +1 -0
  11. package/dist/__tests__/hooks/useWallet.test.d.ts +6 -0
  12. package/dist/__tests__/hooks/useWallet.test.js +105 -0
  13. package/dist/__tests__/hooks/useWallet.test.js.map +1 -0
  14. package/dist/__tests__/hooks/useWalletManager.test.d.ts +6 -0
  15. package/dist/__tests__/hooks/useWalletManager.test.js +193 -0
  16. package/dist/__tests__/hooks/useWalletManager.test.js.map +1 -0
  17. package/dist/__tests__/hooks/useWdkApp.test.d.ts +6 -0
  18. package/dist/__tests__/hooks/useWdkApp.test.js +55 -0
  19. package/dist/__tests__/hooks/useWdkApp.test.js.map +1 -0
  20. package/dist/__tests__/hooks/useWorklet.test.d.ts +6 -0
  21. package/dist/__tests__/hooks/useWorklet.test.js +168 -0
  22. package/dist/__tests__/hooks/useWorklet.test.js.map +1 -0
  23. package/dist/__tests__/provider/WdkAppProvider.test.d.ts +6 -0
  24. package/dist/__tests__/provider/WdkAppProvider.test.js +57 -0
  25. package/dist/__tests__/provider/WdkAppProvider.test.js.map +1 -0
  26. package/dist/__tests__/services/accountService.test.d.ts +6 -0
  27. package/dist/__tests__/services/accountService.test.js +216 -0
  28. package/dist/__tests__/services/accountService.test.js.map +1 -0
  29. package/dist/__tests__/services/addressService.test.d.ts +6 -0
  30. package/dist/__tests__/services/addressService.test.js +246 -0
  31. package/dist/__tests__/services/addressService.test.js.map +1 -0
  32. package/dist/__tests__/services/balanceService.test.d.ts +6 -0
  33. package/dist/__tests__/services/balanceService.test.js +333 -0
  34. package/dist/__tests__/services/balanceService.test.js.map +1 -0
  35. package/dist/__tests__/services/walletSetupService.test.d.ts +6 -0
  36. package/dist/__tests__/services/walletSetupService.test.js +304 -0
  37. package/dist/__tests__/services/walletSetupService.test.js.map +1 -0
  38. package/dist/__tests__/services/walletSwitchingService.test.d.ts +6 -0
  39. package/dist/__tests__/services/walletSwitchingService.test.js +209 -0
  40. package/dist/__tests__/services/walletSwitchingService.test.js.map +1 -0
  41. package/dist/__tests__/services/workletLifecycleService.test.d.ts +6 -0
  42. package/dist/__tests__/services/workletLifecycleService.test.js +451 -0
  43. package/dist/__tests__/services/workletLifecycleService.test.js.map +1 -0
  44. package/dist/__tests__/setup.d.ts +7 -0
  45. package/dist/__tests__/setup.js +49 -0
  46. package/dist/__tests__/setup.js.map +1 -0
  47. package/dist/__tests__/storage/mmkvStorage.test.d.ts +6 -0
  48. package/dist/__tests__/storage/mmkvStorage.test.js +213 -0
  49. package/dist/__tests__/storage/mmkvStorage.test.js.map +1 -0
  50. package/dist/__tests__/store/walletStore.test.d.ts +4 -0
  51. package/dist/__tests__/store/walletStore.test.js +86 -0
  52. package/dist/__tests__/store/walletStore.test.js.map +1 -0
  53. package/dist/__tests__/store/workletStore.test.d.ts +4 -0
  54. package/dist/__tests__/store/workletStore.test.js +255 -0
  55. package/dist/__tests__/store/workletStore.test.js.map +1 -0
  56. package/dist/__tests__/types/hrpc.test.d.ts +4 -0
  57. package/dist/__tests__/types/hrpc.test.js +99 -0
  58. package/dist/__tests__/types/hrpc.test.js.map +1 -0
  59. package/dist/__tests__/utils/balanceUtils.test.d.ts +4 -0
  60. package/dist/__tests__/utils/balanceUtils.test.js +133 -0
  61. package/dist/__tests__/utils/balanceUtils.test.js.map +1 -0
  62. package/dist/__tests__/utils/errorHandling.test.d.ts +6 -0
  63. package/dist/__tests__/utils/errorHandling.test.js +119 -0
  64. package/dist/__tests__/utils/errorHandling.test.js.map +1 -0
  65. package/dist/__tests__/utils/errorUtils.test.d.ts +4 -0
  66. package/dist/__tests__/utils/errorUtils.test.js +133 -0
  67. package/dist/__tests__/utils/errorUtils.test.js.map +1 -0
  68. package/dist/__tests__/utils/initializationState.test.d.ts +6 -0
  69. package/dist/__tests__/utils/initializationState.test.js +243 -0
  70. package/dist/__tests__/utils/initializationState.test.js.map +1 -0
  71. package/dist/__tests__/utils/jsonUtils.test.d.ts +4 -0
  72. package/dist/__tests__/utils/jsonUtils.test.js +118 -0
  73. package/dist/__tests__/utils/jsonUtils.test.js.map +1 -0
  74. package/dist/__tests__/utils/mmkvKeyManager.test.d.ts +6 -0
  75. package/dist/__tests__/utils/mmkvKeyManager.test.js +158 -0
  76. package/dist/__tests__/utils/mmkvKeyManager.test.js.map +1 -0
  77. package/dist/__tests__/utils/mnemonicUtils.test.d.ts +4 -0
  78. package/dist/__tests__/utils/mnemonicUtils.test.js +40 -0
  79. package/dist/__tests__/utils/mnemonicUtils.test.js.map +1 -0
  80. package/dist/__tests__/utils/raceConditions.test.d.ts +6 -0
  81. package/dist/__tests__/utils/raceConditions.test.js +239 -0
  82. package/dist/__tests__/utils/raceConditions.test.js.map +1 -0
  83. package/dist/__tests__/utils/result.test.d.ts +4 -0
  84. package/dist/__tests__/utils/result.test.js +66 -0
  85. package/dist/__tests__/utils/result.test.js.map +1 -0
  86. package/dist/__tests__/utils/schemas.test.d.ts +4 -0
  87. package/dist/__tests__/utils/schemas.test.js +66 -0
  88. package/dist/__tests__/utils/schemas.test.js.map +1 -0
  89. package/dist/__tests__/utils/storeHelpers.test.d.ts +4 -0
  90. package/dist/__tests__/utils/storeHelpers.test.js +227 -0
  91. package/dist/__tests__/utils/storeHelpers.test.js.map +1 -0
  92. package/dist/__tests__/utils/typeGuards.test.d.ts +4 -0
  93. package/dist/__tests__/utils/typeGuards.test.js +175 -0
  94. package/dist/__tests__/utils/typeGuards.test.js.map +1 -0
  95. package/dist/__tests__/utils/validation.test.d.ts +4 -0
  96. package/dist/__tests__/utils/validation.test.js +126 -0
  97. package/dist/__tests__/utils/validation.test.js.map +1 -0
  98. package/dist/__tests__/utils/walletUtils.test.d.ts +4 -0
  99. package/dist/__tests__/utils/walletUtils.test.js +156 -0
  100. package/dist/__tests__/utils/walletUtils.test.js.map +1 -0
  101. package/dist/hooks/useBalance.d.ts +170 -0
  102. package/dist/hooks/useBalance.js +442 -0
  103. package/dist/hooks/useBalance.js.map +1 -0
  104. package/dist/hooks/useWallet.d.ts +43 -0
  105. package/dist/hooks/useWallet.js +193 -0
  106. package/dist/hooks/useWallet.js.map +1 -0
  107. package/dist/hooks/useWalletManager.d.ts +100 -0
  108. package/dist/hooks/useWalletManager.js +526 -0
  109. package/dist/hooks/useWalletManager.js.map +1 -0
  110. package/dist/hooks/useWdkApp.d.ts +66 -0
  111. package/dist/hooks/useWdkApp.js +74 -0
  112. package/dist/hooks/useWdkApp.js.map +1 -0
  113. package/dist/hooks/useWorklet.d.ts +66 -0
  114. package/dist/hooks/useWorklet.js +42 -0
  115. package/dist/hooks/useWorklet.js.map +1 -0
  116. package/dist/index.d.ts +33 -0
  117. package/dist/index.js +36 -0
  118. package/dist/index.js.map +1 -0
  119. package/dist/provider/WdkAppProvider.d.ts +112 -0
  120. package/dist/provider/WdkAppProvider.js +558 -0
  121. package/dist/provider/WdkAppProvider.js.map +1 -0
  122. package/dist/services/accountService.d.ts +52 -0
  123. package/dist/services/accountService.js +119 -0
  124. package/dist/services/accountService.js.map +1 -0
  125. package/dist/services/addressService.d.ts +22 -0
  126. package/dist/services/addressService.js +110 -0
  127. package/dist/services/addressService.js.map +1 -0
  128. package/dist/services/balanceService.d.ts +114 -0
  129. package/dist/services/balanceService.js +208 -0
  130. package/dist/services/balanceService.js.map +1 -0
  131. package/dist/services/walletSetupService.d.ts +217 -0
  132. package/dist/services/walletSetupService.js +535 -0
  133. package/dist/services/walletSetupService.js.map +1 -0
  134. package/dist/services/walletSwitchingService.d.ts +48 -0
  135. package/dist/services/walletSwitchingService.js +143 -0
  136. package/dist/services/walletSwitchingService.js.map +1 -0
  137. package/dist/services/workletLifecycleService.d.ts +117 -0
  138. package/dist/services/workletLifecycleService.js +433 -0
  139. package/dist/services/workletLifecycleService.js.map +1 -0
  140. package/dist/storage/mmkvStorage.d.ts +44 -0
  141. package/dist/storage/mmkvStorage.js +348 -0
  142. package/dist/storage/mmkvStorage.js.map +1 -0
  143. package/dist/store/walletStore.d.ts +130 -0
  144. package/dist/store/walletStore.js +204 -0
  145. package/dist/store/walletStore.js.map +1 -0
  146. package/dist/store/workletStore.d.ts +121 -0
  147. package/dist/store/workletStore.js +256 -0
  148. package/dist/store/workletStore.js.map +1 -0
  149. package/dist/types/hrpc.d.ts +60 -0
  150. package/dist/types/hrpc.js +26 -0
  151. package/dist/types/hrpc.js.map +1 -0
  152. package/dist/types.d.ts +168 -0
  153. package/dist/types.js +7 -0
  154. package/dist/types.js.map +1 -0
  155. package/dist/utils/balanceUtils.d.ts +30 -0
  156. package/dist/utils/balanceUtils.js +86 -0
  157. package/dist/utils/balanceUtils.js.map +1 -0
  158. package/dist/utils/constants.d.ts +66 -0
  159. package/dist/utils/constants.js +73 -0
  160. package/dist/utils/constants.js.map +1 -0
  161. package/dist/utils/errorHandling.d.ts +47 -0
  162. package/dist/utils/errorHandling.js +54 -0
  163. package/dist/utils/errorHandling.js.map +1 -0
  164. package/dist/utils/errorUtils.d.ts +89 -0
  165. package/dist/utils/errorUtils.js +264 -0
  166. package/dist/utils/errorUtils.js.map +1 -0
  167. package/dist/utils/initializationState.d.ts +129 -0
  168. package/dist/utils/initializationState.js +220 -0
  169. package/dist/utils/initializationState.js.map +1 -0
  170. package/dist/utils/jsonUtils.d.ts +21 -0
  171. package/dist/utils/jsonUtils.js +67 -0
  172. package/dist/utils/jsonUtils.js.map +1 -0
  173. package/dist/utils/logger.d.ts +27 -0
  174. package/dist/utils/logger.js +50 -0
  175. package/dist/utils/logger.js.map +1 -0
  176. package/dist/utils/mmkvKeyManager.d.ts +57 -0
  177. package/dist/utils/mmkvKeyManager.js +214 -0
  178. package/dist/utils/mmkvKeyManager.js.map +1 -0
  179. package/dist/utils/mnemonicUtils.d.ts +17 -0
  180. package/dist/utils/mnemonicUtils.js +28 -0
  181. package/dist/utils/mnemonicUtils.js.map +1 -0
  182. package/dist/utils/operationMutex.d.ts +69 -0
  183. package/dist/utils/operationMutex.js +150 -0
  184. package/dist/utils/operationMutex.js.map +1 -0
  185. package/dist/utils/result.d.ts +34 -0
  186. package/dist/utils/result.js +43 -0
  187. package/dist/utils/result.js.map +1 -0
  188. package/dist/utils/schemas.d.ts +365 -0
  189. package/dist/utils/schemas.js +152 -0
  190. package/dist/utils/schemas.js.map +1 -0
  191. package/dist/utils/storeHelpers.d.ts +112 -0
  192. package/dist/utils/storeHelpers.js +198 -0
  193. package/dist/utils/storeHelpers.js.map +1 -0
  194. package/dist/utils/typeGuards.d.ts +70 -0
  195. package/dist/utils/typeGuards.js +95 -0
  196. package/dist/utils/typeGuards.js.map +1 -0
  197. package/dist/utils/validation.d.ts +53 -0
  198. package/dist/utils/validation.js +152 -0
  199. package/dist/utils/validation.js.map +1 -0
  200. package/dist/utils/walletStateHelpers.d.ts +62 -0
  201. package/dist/utils/walletStateHelpers.js +81 -0
  202. package/dist/utils/walletStateHelpers.js.map +1 -0
  203. package/dist/utils/walletUtils.d.ts +38 -0
  204. package/dist/utils/walletUtils.js +87 -0
  205. package/dist/utils/walletUtils.js.map +1 -0
  206. package/package.json +79 -0
  207. package/src/__mocks__/react.ts +34 -0
  208. package/src/__mocks__/secureStorage.ts +87 -0
  209. package/src/__tests__/hooks/useBalance.test.ts +286 -0
  210. package/src/__tests__/hooks/useWallet.test.ts +122 -0
  211. package/src/__tests__/hooks/useWalletManager.test.ts +271 -0
  212. package/src/__tests__/hooks/useWdkApp.test.tsx +59 -0
  213. package/src/__tests__/hooks/useWorklet.test.ts +195 -0
  214. package/src/__tests__/provider/WdkAppProvider.test.tsx +67 -0
  215. package/src/__tests__/services/accountService.test.ts +337 -0
  216. package/src/__tests__/services/addressService.test.ts +312 -0
  217. package/src/__tests__/services/balanceService.test.ts +411 -0
  218. package/src/__tests__/services/walletSetupService.test.ts +463 -0
  219. package/src/__tests__/services/walletSwitchingService.test.ts +258 -0
  220. package/src/__tests__/services/workletLifecycleService.test.ts +514 -0
  221. package/src/__tests__/setup.ts +54 -0
  222. package/src/__tests__/storage/mmkvStorage.test.ts +325 -0
  223. package/src/__tests__/store/walletStore.test.ts +101 -0
  224. package/src/__tests__/store/workletStore.test.ts +329 -0
  225. package/src/__tests__/types/hrpc.test.ts +125 -0
  226. package/src/__tests__/utils/balanceUtils.test.ts +151 -0
  227. package/src/__tests__/utils/errorHandling.test.ts +147 -0
  228. package/src/__tests__/utils/errorUtils.test.ts +170 -0
  229. package/src/__tests__/utils/initializationState.test.ts +337 -0
  230. package/src/__tests__/utils/jsonUtils.test.ts +131 -0
  231. package/src/__tests__/utils/mmkvKeyManager.test.ts +205 -0
  232. package/src/__tests__/utils/mnemonicUtils.test.ts +48 -0
  233. package/src/__tests__/utils/raceConditions.test.ts +292 -0
  234. package/src/__tests__/utils/result.test.ts +73 -0
  235. package/src/__tests__/utils/schemas.test.ts +82 -0
  236. package/src/__tests__/utils/storeHelpers.test.ts +323 -0
  237. package/src/__tests__/utils/typeGuards.test.ts +209 -0
  238. package/src/__tests__/utils/validation.test.ts +152 -0
  239. package/src/__tests__/utils/walletUtils.test.ts +195 -0
  240. package/src/hooks/useBalance.ts +597 -0
  241. package/src/hooks/useWallet.ts +288 -0
  242. package/src/hooks/useWalletManager.ts +654 -0
  243. package/src/hooks/useWdkApp.ts +78 -0
  244. package/src/hooks/useWorklet.ts +109 -0
  245. package/src/index.ts +112 -0
  246. package/src/provider/WdkAppProvider.tsx +764 -0
  247. package/src/services/accountService.ts +134 -0
  248. package/src/services/addressService.ts +125 -0
  249. package/src/services/balanceService.ts +291 -0
  250. package/src/services/walletSetupService.ts +695 -0
  251. package/src/services/walletSwitchingService.ts +157 -0
  252. package/src/services/workletLifecycleService.ts +561 -0
  253. package/src/storage/mmkvStorage.ts +417 -0
  254. package/src/store/walletStore.ts +301 -0
  255. package/src/store/workletStore.ts +340 -0
  256. package/src/types/hrpc.ts +78 -0
  257. package/src/types.ts +188 -0
  258. package/src/utils/balanceUtils.ts +96 -0
  259. package/src/utils/constants.ts +84 -0
  260. package/src/utils/errorHandling.md +163 -0
  261. package/src/utils/errorHandling.ts +61 -0
  262. package/src/utils/errorUtils.ts +300 -0
  263. package/src/utils/initializationState.ts +242 -0
  264. package/src/utils/jsonUtils.ts +75 -0
  265. package/src/utils/logger.ts +54 -0
  266. package/src/utils/mmkvKeyManager.ts +256 -0
  267. package/src/utils/mnemonicUtils.ts +35 -0
  268. package/src/utils/operationMutex.ts +181 -0
  269. package/src/utils/result.ts +57 -0
  270. package/src/utils/schemas.ts +190 -0
  271. package/src/utils/storeHelpers.ts +236 -0
  272. package/src/utils/typeGuards.ts +129 -0
  273. package/src/utils/validation.ts +174 -0
  274. package/src/utils/walletStateHelpers.ts +104 -0
  275. 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
+