@harkenapp/sdk-react-native 0.0.1-alpha.1 → 0.0.1-alpha.2

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 (278) hide show
  1. package/README.md +44 -7
  2. package/app.plugin.cjs +12 -17
  3. package/dist/__mocks__/async-storage.d.ts +16 -0
  4. package/dist/__mocks__/async-storage.d.ts.map +1 -0
  5. package/dist/__mocks__/async-storage.js +39 -0
  6. package/dist/__mocks__/async-storage.js.map +1 -0
  7. package/dist/__mocks__/expo-document-picker.d.ts +26 -0
  8. package/dist/__mocks__/expo-document-picker.d.ts.map +1 -0
  9. package/dist/__mocks__/expo-document-picker.js +25 -0
  10. package/dist/__mocks__/expo-document-picker.js.map +1 -0
  11. package/dist/__mocks__/expo-file-system.d.ts +42 -0
  12. package/dist/__mocks__/expo-file-system.d.ts.map +1 -0
  13. package/dist/__mocks__/expo-file-system.js +37 -0
  14. package/dist/__mocks__/expo-file-system.js.map +1 -0
  15. package/dist/__mocks__/expo-image-picker.d.ts +30 -0
  16. package/dist/__mocks__/expo-image-picker.d.ts.map +1 -0
  17. package/dist/__mocks__/expo-image-picker.js +30 -0
  18. package/dist/__mocks__/expo-image-picker.js.map +1 -0
  19. package/dist/__mocks__/expo-secure-store.d.ts +15 -0
  20. package/dist/__mocks__/expo-secure-store.d.ts.map +1 -0
  21. package/dist/__mocks__/expo-secure-store.js +30 -0
  22. package/dist/__mocks__/expo-secure-store.js.map +1 -0
  23. package/dist/__mocks__/react-native.d.ts +73 -0
  24. package/dist/__mocks__/react-native.d.ts.map +1 -0
  25. package/dist/__mocks__/react-native.js +45 -0
  26. package/dist/__mocks__/react-native.js.map +1 -0
  27. package/dist/api/client.d.ts +8 -8
  28. package/dist/api/client.d.ts.map +1 -1
  29. package/dist/api/client.js +17 -19
  30. package/dist/api/client.js.map +1 -1
  31. package/dist/api/client.test.d.ts +2 -0
  32. package/dist/api/client.test.d.ts.map +1 -0
  33. package/dist/api/client.test.js +417 -0
  34. package/dist/api/client.test.js.map +1 -0
  35. package/dist/api/errors.d.ts +3 -3
  36. package/dist/api/errors.d.ts.map +1 -1
  37. package/dist/api/errors.js +3 -3
  38. package/dist/api/errors.js.map +1 -1
  39. package/dist/api/errors.test.d.ts +2 -0
  40. package/dist/api/errors.test.d.ts.map +1 -0
  41. package/dist/api/errors.test.js +155 -0
  42. package/dist/api/errors.test.js.map +1 -0
  43. package/dist/api/index.d.ts +6 -6
  44. package/dist/api/index.d.ts.map +1 -1
  45. package/dist/api/index.js.map +1 -1
  46. package/dist/api/retry.d.ts +1 -1
  47. package/dist/api/retry.d.ts.map +1 -1
  48. package/dist/api/retry.js.map +1 -1
  49. package/dist/api/retry.test.d.ts +2 -0
  50. package/dist/api/retry.test.d.ts.map +1 -0
  51. package/dist/api/retry.test.js +193 -0
  52. package/dist/api/retry.test.js.map +1 -0
  53. package/dist/attachments/FeedbackSheet.d.ts +36 -13
  54. package/dist/attachments/FeedbackSheet.d.ts.map +1 -1
  55. package/dist/attachments/FeedbackSheet.js +50 -30
  56. package/dist/attachments/FeedbackSheet.js.map +1 -1
  57. package/dist/attachments/index.d.ts +2 -2
  58. package/dist/components/AttachmentGrid.d.ts +12 -4
  59. package/dist/components/AttachmentGrid.d.ts.map +1 -1
  60. package/dist/components/AttachmentGrid.js +44 -34
  61. package/dist/components/AttachmentGrid.js.map +1 -1
  62. package/dist/components/AttachmentPicker.d.ts +3 -3
  63. package/dist/components/AttachmentPicker.d.ts.map +1 -1
  64. package/dist/components/AttachmentPicker.js +34 -36
  65. package/dist/components/AttachmentPicker.js.map +1 -1
  66. package/dist/components/AttachmentPreview.d.ts +10 -4
  67. package/dist/components/AttachmentPreview.d.ts.map +1 -1
  68. package/dist/components/AttachmentPreview.js +48 -34
  69. package/dist/components/AttachmentPreview.js.map +1 -1
  70. package/dist/components/CategorySelector.d.ts +3 -3
  71. package/dist/components/CategorySelector.d.ts.map +1 -1
  72. package/dist/components/CategorySelector.js +21 -27
  73. package/dist/components/CategorySelector.js.map +1 -1
  74. package/dist/components/FeedbackForm.d.ts +3 -3
  75. package/dist/components/FeedbackForm.d.ts.map +1 -1
  76. package/dist/components/FeedbackForm.js +7 -8
  77. package/dist/components/FeedbackForm.js.map +1 -1
  78. package/dist/components/FeedbackSheet.d.ts +34 -11
  79. package/dist/components/FeedbackSheet.d.ts.map +1 -1
  80. package/dist/components/FeedbackSheet.js +46 -28
  81. package/dist/components/FeedbackSheet.js.map +1 -1
  82. package/dist/components/ThemedButton.d.ts +16 -5
  83. package/dist/components/ThemedButton.d.ts.map +1 -1
  84. package/dist/components/ThemedButton.js +38 -29
  85. package/dist/components/ThemedButton.js.map +1 -1
  86. package/dist/components/ThemedText.d.ts +3 -3
  87. package/dist/components/ThemedText.d.ts.map +1 -1
  88. package/dist/components/ThemedText.js +1 -1
  89. package/dist/components/ThemedText.js.map +1 -1
  90. package/dist/components/ThemedTextInput.d.ts +11 -2
  91. package/dist/components/ThemedTextInput.d.ts.map +1 -1
  92. package/dist/components/ThemedTextInput.js +19 -9
  93. package/dist/components/ThemedTextInput.js.map +1 -1
  94. package/dist/components/UploadStatusOverlay.d.ts +11 -3
  95. package/dist/components/UploadStatusOverlay.d.ts.map +1 -1
  96. package/dist/components/UploadStatusOverlay.js +59 -76
  97. package/dist/components/UploadStatusOverlay.js.map +1 -1
  98. package/dist/components/index.d.ts +18 -18
  99. package/dist/components/index.d.ts.map +1 -1
  100. package/dist/components/index.js.map +1 -1
  101. package/dist/context/HarkenContext.d.ts +20 -15
  102. package/dist/context/HarkenContext.d.ts.map +1 -1
  103. package/dist/context/HarkenContext.js +20 -17
  104. package/dist/context/HarkenContext.js.map +1 -1
  105. package/dist/context/index.d.ts +2 -2
  106. package/dist/domain/index.d.ts +2 -2
  107. package/dist/domain/index.d.ts.map +1 -1
  108. package/dist/domain/index.js.map +1 -1
  109. package/dist/hooks/index.d.ts +5 -5
  110. package/dist/hooks/useAnonymousId.js +1 -1
  111. package/dist/hooks/useAnonymousId.test.d.ts +2 -0
  112. package/dist/hooks/useAnonymousId.test.d.ts.map +1 -0
  113. package/dist/hooks/useAnonymousId.test.js +154 -0
  114. package/dist/hooks/useAnonymousId.test.js.map +1 -0
  115. package/dist/hooks/useAttachmentPicker.d.ts +3 -3
  116. package/dist/hooks/useAttachmentPicker.js +7 -7
  117. package/dist/hooks/useAttachmentStatus.d.ts +1 -1
  118. package/dist/hooks/useAttachmentStatus.d.ts.map +1 -1
  119. package/dist/hooks/useAttachmentStatus.js.map +1 -1
  120. package/dist/hooks/useAttachmentUpload.d.ts +2 -2
  121. package/dist/hooks/useAttachmentUpload.d.ts.map +1 -1
  122. package/dist/hooks/useAttachmentUpload.js +5 -5
  123. package/dist/hooks/useAttachmentUpload.js.map +1 -1
  124. package/dist/hooks/useAttachmentUpload.test.d.ts +2 -0
  125. package/dist/hooks/useAttachmentUpload.test.d.ts.map +1 -0
  126. package/dist/hooks/useAttachmentUpload.test.js +542 -0
  127. package/dist/hooks/useAttachmentUpload.test.js.map +1 -0
  128. package/dist/hooks/useFeedback.d.ts +4 -4
  129. package/dist/hooks/useFeedback.d.ts.map +1 -1
  130. package/dist/hooks/useFeedback.js +3 -5
  131. package/dist/hooks/useFeedback.js.map +1 -1
  132. package/dist/hooks/useFeedback.test.d.ts +2 -0
  133. package/dist/hooks/useFeedback.test.d.ts.map +1 -0
  134. package/dist/hooks/useFeedback.test.js +299 -0
  135. package/dist/hooks/useFeedback.test.js.map +1 -0
  136. package/dist/hooks/useHarkenContext.d.ts +1 -1
  137. package/dist/hooks/useHarkenContext.js +1 -1
  138. package/dist/hooks/useHarkenTheme.d.ts +27 -3
  139. package/dist/hooks/useHarkenTheme.d.ts.map +1 -1
  140. package/dist/hooks/useHarkenTheme.js +26 -2
  141. package/dist/hooks/useHarkenTheme.js.map +1 -1
  142. package/dist/index.d.ts +28 -28
  143. package/dist/index.d.ts.map +1 -1
  144. package/dist/index.js.map +1 -1
  145. package/dist/services/index.d.ts +3 -3
  146. package/dist/services/index.d.ts.map +1 -1
  147. package/dist/services/index.js.map +1 -1
  148. package/dist/services/uploadQueueService.d.ts +2 -2
  149. package/dist/services/uploadQueueService.d.ts.map +1 -1
  150. package/dist/services/uploadQueueService.js +16 -17
  151. package/dist/services/uploadQueueService.js.map +1 -1
  152. package/dist/services/uploadQueueService.test.d.ts +2 -0
  153. package/dist/services/uploadQueueService.test.d.ts.map +1 -0
  154. package/dist/services/uploadQueueService.test.js +426 -0
  155. package/dist/services/uploadQueueService.test.js.map +1 -0
  156. package/dist/services/uploadQueueStorage.d.ts +1 -1
  157. package/dist/services/uploadQueueStorage.d.ts.map +1 -1
  158. package/dist/services/uploadQueueStorage.js +4 -4
  159. package/dist/services/uploadQueueStorage.js.map +1 -1
  160. package/dist/services/uploadQueueStorage.test.d.ts +2 -0
  161. package/dist/services/uploadQueueStorage.test.d.ts.map +1 -0
  162. package/dist/services/uploadQueueStorage.test.js +200 -0
  163. package/dist/services/uploadQueueStorage.test.js.map +1 -0
  164. package/dist/storage/IdentityStore.d.ts +1 -1
  165. package/dist/storage/IdentityStore.d.ts.map +1 -1
  166. package/dist/storage/IdentityStore.js.map +1 -1
  167. package/dist/storage/IdentityStore.test.d.ts +2 -0
  168. package/dist/storage/IdentityStore.test.d.ts.map +1 -0
  169. package/dist/storage/IdentityStore.test.js +176 -0
  170. package/dist/storage/IdentityStore.test.js.map +1 -0
  171. package/dist/storage/SecureStoreAdapter.d.ts +1 -1
  172. package/dist/storage/SecureStoreAdapter.test.d.ts +2 -0
  173. package/dist/storage/SecureStoreAdapter.test.d.ts.map +1 -0
  174. package/dist/storage/SecureStoreAdapter.test.js +114 -0
  175. package/dist/storage/SecureStoreAdapter.test.js.map +1 -0
  176. package/dist/storage/defaultStorage.d.ts +1 -1
  177. package/dist/storage/defaultStorage.js +4 -4
  178. package/dist/storage/defaultStorage.test.d.ts +2 -0
  179. package/dist/storage/defaultStorage.test.d.ts.map +1 -0
  180. package/dist/storage/defaultStorage.test.js +159 -0
  181. package/dist/storage/defaultStorage.test.js.map +1 -0
  182. package/dist/storage/index.d.ts +5 -5
  183. package/dist/storage/types.js +1 -1
  184. package/dist/theme/defaults.d.ts +14 -3
  185. package/dist/theme/defaults.d.ts.map +1 -1
  186. package/dist/theme/defaults.js +58 -43
  187. package/dist/theme/defaults.js.map +1 -1
  188. package/dist/theme/index.d.ts +3 -2
  189. package/dist/theme/index.d.ts.map +1 -1
  190. package/dist/theme/index.js +4 -1
  191. package/dist/theme/index.js.map +1 -1
  192. package/dist/theme/resolver.d.ts +16 -0
  193. package/dist/theme/resolver.d.ts.map +1 -0
  194. package/dist/theme/resolver.js +375 -0
  195. package/dist/theme/resolver.js.map +1 -0
  196. package/dist/theme/resolver.test.d.ts +2 -0
  197. package/dist/theme/resolver.test.d.ts.map +1 -0
  198. package/dist/theme/resolver.test.js +344 -0
  199. package/dist/theme/resolver.test.js.map +1 -0
  200. package/dist/theme/types.d.ts +378 -5
  201. package/dist/theme/types.d.ts.map +1 -1
  202. package/dist/types/config.d.ts +4 -4
  203. package/dist/types/index.d.ts +2 -2
  204. package/dist/utils/index.d.ts +1 -1
  205. package/dist/utils/uuid.d.ts.map +1 -1
  206. package/dist/utils/uuid.js +4 -5
  207. package/dist/utils/uuid.js.map +1 -1
  208. package/dist/utils/uuid.test.d.ts +2 -0
  209. package/dist/utils/uuid.test.d.ts.map +1 -0
  210. package/dist/utils/uuid.test.js +78 -0
  211. package/dist/utils/uuid.test.js.map +1 -0
  212. package/package.json +21 -13
  213. package/src/@types/expo-file-system-legacy.d.ts +3 -3
  214. package/src/__mocks__/async-storage.ts +46 -0
  215. package/src/__mocks__/expo-document-picker.ts +41 -0
  216. package/src/__mocks__/expo-file-system.ts +62 -0
  217. package/src/__mocks__/expo-image-picker.ts +48 -0
  218. package/src/__mocks__/expo-secure-store.ts +29 -0
  219. package/src/__mocks__/react-native.ts +46 -0
  220. package/src/api/client.test.ts +515 -0
  221. package/src/api/client.ts +45 -64
  222. package/src/api/errors.test.ts +193 -0
  223. package/src/api/errors.ts +7 -11
  224. package/src/api/index.ts +6 -10
  225. package/src/api/retry.test.ts +251 -0
  226. package/src/api/retry.ts +3 -6
  227. package/src/attachments/FeedbackSheet.tsx +100 -80
  228. package/src/attachments/index.ts +2 -2
  229. package/src/components/AttachmentGrid.tsx +54 -45
  230. package/src/components/AttachmentPicker.tsx +43 -54
  231. package/src/components/AttachmentPreview.tsx +51 -47
  232. package/src/components/CategorySelector.tsx +29 -35
  233. package/src/components/FeedbackForm.tsx +23 -35
  234. package/src/components/FeedbackSheet.tsx +89 -68
  235. package/src/components/ThemedButton.tsx +49 -47
  236. package/src/components/ThemedText.tsx +7 -10
  237. package/src/components/ThemedTextInput.tsx +23 -13
  238. package/src/components/UploadStatusOverlay.tsx +66 -89
  239. package/src/components/index.ts +18 -21
  240. package/src/context/HarkenContext.tsx +29 -28
  241. package/src/context/index.ts +2 -2
  242. package/src/domain/index.ts +2 -5
  243. package/src/domain/upload-queue.ts +5 -5
  244. package/src/hooks/index.ts +5 -5
  245. package/src/hooks/useAnonymousId.test.ts +189 -0
  246. package/src/hooks/useAnonymousId.ts +3 -3
  247. package/src/hooks/useAttachmentPicker.ts +12 -12
  248. package/src/hooks/useAttachmentStatus.ts +12 -16
  249. package/src/hooks/useAttachmentUpload.test.ts +632 -0
  250. package/src/hooks/useAttachmentUpload.ts +45 -54
  251. package/src/hooks/useFeedback.test.ts +376 -0
  252. package/src/hooks/useFeedback.ts +12 -14
  253. package/src/hooks/useHarkenContext.ts +4 -4
  254. package/src/hooks/useHarkenTheme.ts +30 -6
  255. package/src/index.ts +28 -52
  256. package/src/services/index.ts +3 -9
  257. package/src/services/uploadQueueService.test.ts +489 -0
  258. package/src/services/uploadQueueService.ts +40 -56
  259. package/src/services/uploadQueueStorage.test.ts +243 -0
  260. package/src/services/uploadQueueStorage.ts +7 -9
  261. package/src/storage/IdentityStore.test.ts +173 -0
  262. package/src/storage/IdentityStore.ts +4 -5
  263. package/src/storage/SecureStoreAdapter.test.ts +147 -0
  264. package/src/storage/SecureStoreAdapter.ts +1 -1
  265. package/src/storage/defaultStorage.test.ts +159 -0
  266. package/src/storage/defaultStorage.ts +6 -6
  267. package/src/storage/index.ts +5 -5
  268. package/src/storage/types.ts +1 -1
  269. package/src/theme/defaults.ts +75 -46
  270. package/src/theme/index.ts +15 -2
  271. package/src/theme/resolver.test.ts +411 -0
  272. package/src/theme/resolver.ts +446 -0
  273. package/src/theme/types.ts +453 -15
  274. package/src/types/config.ts +4 -4
  275. package/src/types/index.ts +2 -2
  276. package/src/utils/index.ts +1 -1
  277. package/src/utils/uuid.test.ts +85 -0
  278. package/src/utils/uuid.ts +4 -7
@@ -0,0 +1,147 @@
1
+ import { describe, it, expect, vi } from "vitest";
2
+ import { createSecureStoreAdapter, createMemoryStorage } from "./SecureStoreAdapter";
3
+
4
+ describe("createSecureStoreAdapter", () => {
5
+ describe("when SecureStore is available", () => {
6
+ it("maps getItem to getItemAsync", async () => {
7
+ const mockSecureStore = {
8
+ getItemAsync: vi.fn().mockResolvedValue("stored-value"),
9
+ setItemAsync: vi.fn(),
10
+ deleteItemAsync: vi.fn(),
11
+ };
12
+
13
+ const adapter = createSecureStoreAdapter(mockSecureStore);
14
+ const result = await adapter.getItem("test-key");
15
+
16
+ expect(mockSecureStore.getItemAsync).toHaveBeenCalledWith("test-key");
17
+ expect(result).toBe("stored-value");
18
+ });
19
+
20
+ it("maps setItem to setItemAsync", async () => {
21
+ const mockSecureStore = {
22
+ getItemAsync: vi.fn(),
23
+ setItemAsync: vi.fn().mockResolvedValue(undefined),
24
+ deleteItemAsync: vi.fn(),
25
+ };
26
+
27
+ const adapter = createSecureStoreAdapter(mockSecureStore);
28
+ await adapter.setItem("test-key", "test-value");
29
+
30
+ expect(mockSecureStore.setItemAsync).toHaveBeenCalledWith("test-key", "test-value");
31
+ });
32
+
33
+ it("maps deleteItem to deleteItemAsync", async () => {
34
+ const mockSecureStore = {
35
+ getItemAsync: vi.fn(),
36
+ setItemAsync: vi.fn(),
37
+ deleteItemAsync: vi.fn().mockResolvedValue(undefined),
38
+ };
39
+
40
+ const adapter = createSecureStoreAdapter(mockSecureStore);
41
+ await adapter.deleteItem("test-key");
42
+
43
+ expect(mockSecureStore.deleteItemAsync).toHaveBeenCalledWith("test-key");
44
+ });
45
+
46
+ it("returns null when key not found", async () => {
47
+ const mockSecureStore = {
48
+ getItemAsync: vi.fn().mockResolvedValue(null),
49
+ setItemAsync: vi.fn(),
50
+ deleteItemAsync: vi.fn(),
51
+ };
52
+
53
+ const adapter = createSecureStoreAdapter(mockSecureStore);
54
+ const result = await adapter.getItem("nonexistent");
55
+
56
+ expect(result).toBeNull();
57
+ });
58
+ });
59
+
60
+ describe("error handling", () => {
61
+ it("propagates getItemAsync errors", async () => {
62
+ const mockSecureStore = {
63
+ getItemAsync: vi.fn().mockRejectedValue(new Error("Storage error")),
64
+ setItemAsync: vi.fn(),
65
+ deleteItemAsync: vi.fn(),
66
+ };
67
+
68
+ const adapter = createSecureStoreAdapter(mockSecureStore);
69
+
70
+ await expect(adapter.getItem("test-key")).rejects.toThrow("Storage error");
71
+ });
72
+
73
+ it("propagates setItemAsync errors", async () => {
74
+ const mockSecureStore = {
75
+ getItemAsync: vi.fn(),
76
+ setItemAsync: vi.fn().mockRejectedValue(new Error("Write error")),
77
+ deleteItemAsync: vi.fn(),
78
+ };
79
+
80
+ const adapter = createSecureStoreAdapter(mockSecureStore);
81
+
82
+ await expect(adapter.setItem("key", "value")).rejects.toThrow("Write error");
83
+ });
84
+
85
+ it("propagates deleteItemAsync errors", async () => {
86
+ const mockSecureStore = {
87
+ getItemAsync: vi.fn(),
88
+ setItemAsync: vi.fn(),
89
+ deleteItemAsync: vi.fn().mockRejectedValue(new Error("Delete error")),
90
+ };
91
+
92
+ const adapter = createSecureStoreAdapter(mockSecureStore);
93
+
94
+ await expect(adapter.deleteItem("key")).rejects.toThrow("Delete error");
95
+ });
96
+ });
97
+ });
98
+
99
+ describe("createMemoryStorage", () => {
100
+ it("stores and retrieves values", async () => {
101
+ const storage = createMemoryStorage();
102
+
103
+ await storage.setItem("key1", "value1");
104
+ const result = await storage.getItem("key1");
105
+
106
+ expect(result).toBe("value1");
107
+ });
108
+
109
+ it("returns null for non-existent keys", async () => {
110
+ const storage = createMemoryStorage();
111
+
112
+ const result = await storage.getItem("nonexistent");
113
+
114
+ expect(result).toBeNull();
115
+ });
116
+
117
+ it("deletes values", async () => {
118
+ const storage = createMemoryStorage();
119
+
120
+ await storage.setItem("key", "value");
121
+ await storage.deleteItem("key");
122
+ const result = await storage.getItem("key");
123
+
124
+ expect(result).toBeNull();
125
+ });
126
+
127
+ it("overwrites existing values", async () => {
128
+ const storage = createMemoryStorage();
129
+
130
+ await storage.setItem("key", "first");
131
+ await storage.setItem("key", "second");
132
+ const result = await storage.getItem("key");
133
+
134
+ expect(result).toBe("second");
135
+ });
136
+
137
+ it("isolates storage between instances", async () => {
138
+ const storage1 = createMemoryStorage();
139
+ const storage2 = createMemoryStorage();
140
+
141
+ await storage1.setItem("key", "value1");
142
+ await storage2.setItem("key", "value2");
143
+
144
+ expect(await storage1.getItem("key")).toBe("value1");
145
+ expect(await storage2.getItem("key")).toBe("value2");
146
+ });
147
+ });
@@ -1,4 +1,4 @@
1
- import type { SecureStorage } from './types';
1
+ import type { SecureStorage } from "./types";
2
2
 
3
3
  /**
4
4
  * Adapter for expo-secure-store.
@@ -0,0 +1,159 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
+
3
+ // We need to test the module in isolation, so we use dynamic imports
4
+ // and reset the module state between tests
5
+
6
+ describe("defaultStorage", () => {
7
+ beforeEach(() => {
8
+ vi.resetModules();
9
+ });
10
+
11
+ afterEach(() => {
12
+ vi.restoreAllMocks();
13
+ });
14
+
15
+ describe("getDefaultStorage", () => {
16
+ it("uses SecureStore when available", async () => {
17
+ // Mock expo-secure-store to be available
18
+ vi.doMock("expo-secure-store", () => ({
19
+ getItemAsync: vi.fn().mockResolvedValue("secure-value"),
20
+ setItemAsync: vi.fn().mockResolvedValue(undefined),
21
+ deleteItemAsync: vi.fn().mockResolvedValue(undefined),
22
+ }));
23
+
24
+ const { getDefaultStorage } = await import("./defaultStorage");
25
+ const storage = await getDefaultStorage();
26
+
27
+ // The storage should use SecureStore
28
+ const result = await storage.getItem("test-key");
29
+
30
+ // Since we mocked getItemAsync to return "secure-value"
31
+ expect(result).toBe("secure-value");
32
+ });
33
+
34
+ it("falls back to memory storage when SecureStore unavailable", async () => {
35
+ // Mock expo-secure-store to throw (simulating it not being installed)
36
+ vi.doMock("expo-secure-store", () => {
37
+ throw new Error("Cannot find module 'expo-secure-store'");
38
+ });
39
+
40
+ // Suppress console.warn for this test
41
+ const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
42
+
43
+ const { getDefaultStorage } = await import("./defaultStorage");
44
+ const storage = await getDefaultStorage();
45
+
46
+ // Should fall back to memory storage and warn
47
+ expect(warnSpy).toHaveBeenCalledWith(
48
+ expect.stringContaining("expo-secure-store not available")
49
+ );
50
+
51
+ // Memory storage should work
52
+ await storage.setItem("key", "value");
53
+ const result = await storage.getItem("key");
54
+ expect(result).toBe("value");
55
+ });
56
+
57
+ it("caches the storage instance", async () => {
58
+ vi.doMock("expo-secure-store", () => ({
59
+ getItemAsync: vi.fn().mockResolvedValue(null),
60
+ setItemAsync: vi.fn().mockResolvedValue(undefined),
61
+ deleteItemAsync: vi.fn().mockResolvedValue(undefined),
62
+ }));
63
+
64
+ const { getDefaultStorage } = await import("./defaultStorage");
65
+
66
+ const storage1 = await getDefaultStorage();
67
+ const storage2 = await getDefaultStorage();
68
+
69
+ // Should return the same instance
70
+ expect(storage1).toBe(storage2);
71
+ });
72
+ });
73
+
74
+ describe("createDefaultStorage", () => {
75
+ it("returns a lazy wrapper that initializes on first use", async () => {
76
+ const mockGetItemAsync = vi.fn().mockResolvedValue("lazy-value");
77
+
78
+ vi.doMock("expo-secure-store", () => ({
79
+ getItemAsync: mockGetItemAsync,
80
+ setItemAsync: vi.fn().mockResolvedValue(undefined),
81
+ deleteItemAsync: vi.fn().mockResolvedValue(undefined),
82
+ }));
83
+
84
+ const { createDefaultStorage } = await import("./defaultStorage");
85
+ const storage = createDefaultStorage();
86
+
87
+ // SecureStore shouldn't be called yet (lazy)
88
+ expect(mockGetItemAsync).not.toHaveBeenCalled();
89
+
90
+ // First use triggers initialization
91
+ const result = await storage.getItem("test-key");
92
+
93
+ expect(mockGetItemAsync).toHaveBeenCalledWith("test-key");
94
+ expect(result).toBe("lazy-value");
95
+ });
96
+
97
+ it("caches initialization across method calls", async () => {
98
+ let initCount = 0;
99
+
100
+ vi.doMock("expo-secure-store", () => {
101
+ initCount++;
102
+ return {
103
+ getItemAsync: vi.fn().mockResolvedValue(null),
104
+ setItemAsync: vi.fn().mockResolvedValue(undefined),
105
+ deleteItemAsync: vi.fn().mockResolvedValue(undefined),
106
+ };
107
+ });
108
+
109
+ const { createDefaultStorage } = await import("./defaultStorage");
110
+ const storage = createDefaultStorage();
111
+
112
+ // Multiple operations should only initialize once
113
+ await storage.getItem("key1");
114
+ await storage.setItem("key2", "value");
115
+ await storage.deleteItem("key3");
116
+
117
+ // Module should only be imported once
118
+ expect(initCount).toBe(1);
119
+ });
120
+
121
+ it("handles concurrent initialization correctly", async () => {
122
+ vi.doMock("expo-secure-store", () => ({
123
+ getItemAsync: vi.fn().mockResolvedValue("concurrent-value"),
124
+ setItemAsync: vi.fn().mockResolvedValue(undefined),
125
+ deleteItemAsync: vi.fn().mockResolvedValue(undefined),
126
+ }));
127
+
128
+ const { createDefaultStorage } = await import("./defaultStorage");
129
+ const storage = createDefaultStorage();
130
+
131
+ // Concurrent calls should all succeed
132
+ const [result1, result2, result3] = await Promise.all([
133
+ storage.getItem("key1"),
134
+ storage.getItem("key2"),
135
+ storage.getItem("key3"),
136
+ ]);
137
+
138
+ expect(result1).toBe("concurrent-value");
139
+ expect(result2).toBe("concurrent-value");
140
+ expect(result3).toBe("concurrent-value");
141
+ });
142
+ });
143
+
144
+ describe("error handling", () => {
145
+ it("does not crash when SecureStore throws during operations", async () => {
146
+ vi.doMock("expo-secure-store", () => ({
147
+ getItemAsync: vi.fn().mockRejectedValue(new Error("SecureStore error")),
148
+ setItemAsync: vi.fn().mockRejectedValue(new Error("SecureStore error")),
149
+ deleteItemAsync: vi.fn().mockRejectedValue(new Error("SecureStore error")),
150
+ }));
151
+
152
+ const { createDefaultStorage } = await import("./defaultStorage");
153
+ const storage = createDefaultStorage();
154
+
155
+ // Errors from SecureStore should propagate (not swallowed)
156
+ await expect(storage.getItem("key")).rejects.toThrow("SecureStore error");
157
+ });
158
+ });
159
+ });
@@ -1,5 +1,5 @@
1
- import type { SecureStorage } from './types';
2
- import { createMemoryStorage } from './SecureStoreAdapter';
1
+ import type { SecureStorage } from "./types";
2
+ import { createMemoryStorage } from "./SecureStoreAdapter";
3
3
 
4
4
  /**
5
5
  * Cached default storage instance.
@@ -31,7 +31,7 @@ export async function getDefaultStorage(): Promise<SecureStorage> {
31
31
 
32
32
  try {
33
33
  // Dynamically import expo-secure-store
34
- const SecureStore = await import('expo-secure-store');
34
+ const SecureStore = await import("expo-secure-store");
35
35
 
36
36
  defaultStorageInstance = {
37
37
  async getItem(key: string): Promise<string | null> {
@@ -49,9 +49,9 @@ export async function getDefaultStorage(): Promise<SecureStorage> {
49
49
  } catch {
50
50
  // expo-secure-store not available, fall back to memory storage
51
51
  console.warn(
52
- '[Harken] expo-secure-store not available. Using in-memory storage. ' +
53
- 'Anonymous IDs will not persist across app restarts. ' +
54
- 'Install expo-secure-store for persistent storage.'
52
+ "[Harken] expo-secure-store not available. Using in-memory storage. " +
53
+ "Anonymous IDs will not persist across app restarts. " +
54
+ "Install expo-secure-store for persistent storage."
55
55
  );
56
56
  }
57
57
  }
@@ -1,5 +1,5 @@
1
- export type { SecureStorage } from './types';
2
- export { STORAGE_KEYS } from './types';
3
- export { createSecureStoreAdapter, createMemoryStorage } from './SecureStoreAdapter';
4
- export { createDefaultStorage } from './defaultStorage';
5
- export { IdentityStore } from './IdentityStore';
1
+ export type { SecureStorage } from "./types";
2
+ export { STORAGE_KEYS } from "./types";
3
+ export { createSecureStoreAdapter, createMemoryStorage } from "./SecureStoreAdapter";
4
+ export { createDefaultStorage } from "./defaultStorage";
5
+ export { IdentityStore } from "./IdentityStore";
@@ -30,5 +30,5 @@ export interface SecureStorage {
30
30
  * @internal
31
31
  */
32
32
  export const STORAGE_KEYS = {
33
- ANON_ID: 'harken_anon_id',
33
+ ANON_ID: "harken_anon_id",
34
34
  } as const;
@@ -3,33 +3,36 @@ import type {
3
3
  HarkenTypography,
4
4
  HarkenSpacing,
5
5
  HarkenRadii,
6
+ HarkenSizing,
7
+ HarkenOpacity,
6
8
  HarkenTheme,
7
- } from './types';
9
+ } from "./types";
8
10
 
9
11
  /**
10
12
  * Default light mode colors.
11
13
  * Neutral, accessible palette with no hard-coded branding.
12
14
  */
13
15
  export const lightColors: HarkenColors = {
14
- primary: '#2563EB', // Blue 600
15
- primaryPressed: '#1D4ED8', // Blue 700
16
- background: '#FFFFFF',
17
- backgroundSecondary: '#F9FAFB', // Gray 50
18
- text: '#111827', // Gray 900
19
- textSecondary: '#6B7280', // Gray 500
20
- textPlaceholder: '#9CA3AF', // Gray 400
21
- textOnPrimary: '#FFFFFF',
22
- border: '#E5E7EB', // Gray 200
23
- borderFocused: '#2563EB', // Blue 600
24
- error: '#DC2626', // Red 600
25
- success: '#16A34A', // Green 600
26
- warning: '#D97706', // Amber 600
27
- info: '#2563EB', // Blue 600
28
- overlay: 'rgba(0, 0, 0, 0.3)',
29
- overlayDark: 'rgba(0, 0, 0, 0.6)',
30
- accent1: '#2563EB', // Blue 600 (camera)
31
- accent2: '#16A34A', // Green 600 (library)
32
- accent3: '#D97706', // Amber 600 (files)
16
+ primary: "#2563EB", // Blue 600
17
+ primaryPressed: "#1D4ED8", // Blue 700
18
+ background: "#FFFFFF",
19
+ surface: "#F9FAFB", // Gray 50 - container/modal surface
20
+ backgroundSecondary: "#F9FAFB", // Gray 50 - alias for surface (backwards compat)
21
+ text: "#111827", // Gray 900
22
+ textSecondary: "#6B7280", // Gray 500
23
+ textPlaceholder: "#9CA3AF", // Gray 400
24
+ textOnPrimary: "#FFFFFF",
25
+ border: "#E5E7EB", // Gray 200
26
+ borderFocused: "#2563EB", // Blue 600
27
+ error: "#DC2626", // Red 600
28
+ success: "#16A34A", // Green 600
29
+ warning: "#D97706", // Amber 600
30
+ info: "#2563EB", // Blue 600
31
+ overlay: "rgba(0, 0, 0, 0.3)",
32
+ overlayDark: "rgba(0, 0, 0, 0.6)",
33
+ accent1: "#2563EB", // Blue 600 (camera)
34
+ accent2: "#16A34A", // Green 600 (library)
35
+ accent3: "#D97706", // Amber 600 (files)
33
36
  };
34
37
 
35
38
  /**
@@ -37,25 +40,26 @@ export const lightColors: HarkenColors = {
37
40
  * Inverted palette optimized for dark backgrounds.
38
41
  */
39
42
  export const darkColors: HarkenColors = {
40
- primary: '#3B82F6', // Blue 500
41
- primaryPressed: '#2563EB', // Blue 600
42
- background: '#111827', // Gray 900
43
- backgroundSecondary: '#1F2937', // Gray 800
44
- text: '#F9FAFB', // Gray 50
45
- textSecondary: '#9CA3AF', // Gray 400
46
- textPlaceholder: '#6B7280', // Gray 500
47
- textOnPrimary: '#FFFFFF',
48
- border: '#374151', // Gray 700
49
- borderFocused: '#3B82F6', // Blue 500
50
- error: '#EF4444', // Red 500
51
- success: '#22C55E', // Green 500
52
- warning: '#F59E0B', // Amber 500
53
- info: '#3B82F6', // Blue 500
54
- overlay: 'rgba(0, 0, 0, 0.5)',
55
- overlayDark: 'rgba(0, 0, 0, 0.8)',
56
- accent1: '#3B82F6', // Blue 500 (camera)
57
- accent2: '#22C55E', // Green 500 (library)
58
- accent3: '#F59E0B', // Amber 500 (files)
43
+ primary: "#3B82F6", // Blue 500
44
+ primaryPressed: "#2563EB", // Blue 600
45
+ background: "#111827", // Gray 900
46
+ surface: "#1F2937", // Gray 800 - container/modal surface
47
+ backgroundSecondary: "#1F2937", // Gray 800 - alias for surface (backwards compat)
48
+ text: "#F9FAFB", // Gray 50
49
+ textSecondary: "#9CA3AF", // Gray 400
50
+ textPlaceholder: "#6B7280", // Gray 500
51
+ textOnPrimary: "#FFFFFF",
52
+ border: "#374151", // Gray 700
53
+ borderFocused: "#3B82F6", // Blue 500
54
+ error: "#EF4444", // Red 500
55
+ success: "#22C55E", // Green 500
56
+ warning: "#F59E0B", // Amber 500
57
+ info: "#3B82F6", // Blue 500
58
+ overlay: "rgba(0, 0, 0, 0.5)",
59
+ overlayDark: "rgba(0, 0, 0, 0.8)",
60
+ accent1: "#3B82F6", // Blue 500 (camera)
61
+ accent2: "#22C55E", // Green 500 (library)
62
+ accent3: "#F59E0B", // Amber 500 (files)
59
63
  };
60
64
 
61
65
  /**
@@ -63,22 +67,22 @@ export const darkColors: HarkenColors = {
63
67
  * Uses system font for maximum compatibility.
64
68
  */
65
69
  export const defaultTypography: HarkenTypography = {
66
- fontFamily: 'System',
70
+ fontFamily: "System",
67
71
  fontFamilyHeading: undefined, // Falls back to fontFamily
68
72
 
69
73
  titleSize: 20,
70
74
  titleLineHeight: 1.3,
71
- titleWeight: '600',
75
+ titleWeight: "600",
72
76
 
73
77
  bodySize: 16,
74
78
  bodyLineHeight: 1.5,
75
- bodyWeight: 'normal',
79
+ bodyWeight: "normal",
76
80
 
77
81
  labelSize: 14,
78
- labelWeight: '500',
82
+ labelWeight: "500",
79
83
 
80
84
  captionSize: 12,
81
- captionWeight: 'normal',
85
+ captionWeight: "normal",
82
86
  };
83
87
 
84
88
  /**
@@ -126,26 +130,51 @@ export const darkTheme: HarkenTheme = {
126
130
  radii: defaultRadii,
127
131
  };
128
132
 
133
+ /** Extended theme type that includes optional sizing and opacity */
134
+ type ExtendedHarkenTheme = HarkenTheme & {
135
+ sizing?: Partial<HarkenSizing>;
136
+ opacity?: Partial<HarkenOpacity>;
137
+ };
138
+
129
139
  /**
130
140
  * Creates a theme by merging overrides with a base theme.
141
+ *
142
+ * Note: For full resolved theme with component aliases, use `resolveTheme` instead.
143
+ * This function creates a raw HarkenTheme suitable for passing to resolveTheme.
131
144
  */
132
145
  export function createTheme(
133
- baseTheme: HarkenTheme,
146
+ baseTheme: ExtendedHarkenTheme,
134
147
  overrides?: {
135
148
  colors?: Partial<HarkenColors>;
136
149
  typography?: Partial<HarkenTypography>;
137
150
  spacing?: Partial<HarkenSpacing>;
138
151
  radii?: Partial<HarkenRadii>;
152
+ sizing?: Partial<HarkenSizing>;
153
+ opacity?: Partial<HarkenOpacity>;
139
154
  }
140
- ): HarkenTheme {
155
+ ): ExtendedHarkenTheme {
141
156
  if (!overrides) {
142
157
  return baseTheme;
143
158
  }
144
159
 
160
+ // Merge sizing if either base or overrides has it
161
+ const baseSizing = baseTheme.sizing;
162
+ const overrideSizing = overrides.sizing;
163
+ const mergedSizing =
164
+ baseSizing || overrideSizing ? { ...baseSizing, ...overrideSizing } : undefined;
165
+
166
+ // Merge opacity if either base or overrides has it
167
+ const baseOpacity = baseTheme.opacity;
168
+ const overrideOpacity = overrides.opacity;
169
+ const mergedOpacity =
170
+ baseOpacity || overrideOpacity ? { ...baseOpacity, ...overrideOpacity } : undefined;
171
+
145
172
  return {
146
173
  colors: { ...baseTheme.colors, ...overrides.colors },
147
174
  typography: { ...baseTheme.typography, ...overrides.typography },
148
175
  spacing: { ...baseTheme.spacing, ...overrides.spacing },
149
176
  radii: { ...baseTheme.radii, ...overrides.radii },
177
+ ...(mergedSizing && { sizing: mergedSizing }),
178
+ ...(mergedOpacity && { opacity: mergedOpacity }),
150
179
  };
151
180
  }
@@ -4,11 +4,21 @@ export type {
4
4
  HarkenTypography,
5
5
  HarkenSpacing,
6
6
  HarkenRadii,
7
+ HarkenSizing,
8
+ HarkenOpacity,
7
9
  HarkenTheme,
8
10
  PartialHarkenTheme,
9
11
  TextWeight,
10
12
  ThemeMode,
11
- } from './types';
13
+ // Resolved theme types
14
+ ResolvedHarkenColors,
15
+ ResolvedHarkenSpacing,
16
+ ResolvedHarkenRadii,
17
+ ResolvedHarkenSizing,
18
+ ResolvedHarkenOpacity,
19
+ ResolvedHarkenTheme,
20
+ HarkenComponentTokens,
21
+ } from "./types";
12
22
 
13
23
  // Default theme exports
14
24
  export {
@@ -20,4 +30,7 @@ export {
20
30
  lightTheme,
21
31
  darkTheme,
22
32
  createTheme,
23
- } from './defaults';
33
+ } from "./defaults";
34
+
35
+ // Theme resolver
36
+ export { resolveTheme } from "./resolver";