react-native-nitro-storage 0.1.1 → 0.1.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 (52) hide show
  1. package/README.md +43 -22
  2. package/android/src/main/cpp/AndroidStorageAdapterCpp.cpp +10 -0
  3. package/android/src/main/cpp/AndroidStorageAdapterCpp.hpp +13 -0
  4. package/android/src/main/java/com/nitrostorage/AndroidStorageAdapter.kt +10 -0
  5. package/cpp/bindings/HybridStorage.cpp +50 -0
  6. package/cpp/bindings/HybridStorage.hpp +4 -0
  7. package/cpp/core/NativeStorageAdapter.hpp +3 -0
  8. package/ios/IOSStorageAdapterCpp.hpp +3 -0
  9. package/ios/IOSStorageAdapterCpp.mm +13 -0
  10. package/lib/commonjs/Storage.nitro.js +0 -7
  11. package/lib/commonjs/Storage.nitro.js.map +1 -1
  12. package/lib/commonjs/Storage.types.js +13 -0
  13. package/lib/commonjs/Storage.types.js.map +1 -0
  14. package/lib/commonjs/index.js +69 -8
  15. package/lib/commonjs/index.js.map +1 -1
  16. package/lib/commonjs/index.web.js +268 -0
  17. package/lib/commonjs/index.web.js.map +1 -0
  18. package/lib/commonjs/package.json +1 -0
  19. package/lib/module/Storage.nitro.js +1 -6
  20. package/lib/module/Storage.nitro.js.map +1 -1
  21. package/lib/module/Storage.types.js +9 -0
  22. package/lib/module/Storage.types.js.map +1 -0
  23. package/lib/module/index.js +66 -8
  24. package/lib/module/index.js.map +1 -1
  25. package/lib/module/index.web.js +257 -0
  26. package/lib/module/index.web.js.map +1 -0
  27. package/lib/typescript/Storage.nitro.d.ts +5 -6
  28. package/lib/typescript/Storage.nitro.d.ts.map +1 -1
  29. package/lib/typescript/Storage.types.d.ts +6 -0
  30. package/lib/typescript/Storage.types.d.ts.map +1 -0
  31. package/lib/typescript/index.d.ts +12 -3
  32. package/lib/typescript/index.d.ts.map +1 -1
  33. package/lib/typescript/index.web.d.ts +50 -0
  34. package/lib/typescript/index.web.d.ts.map +1 -0
  35. package/nitrogen/generated/android/NitroStorage+autolinking.cmake +1 -1
  36. package/nitrogen/generated/android/NitroStorage+autolinking.gradle +1 -1
  37. package/nitrogen/generated/android/NitroStorageOnLoad.cpp +1 -1
  38. package/nitrogen/generated/android/NitroStorageOnLoad.hpp +1 -1
  39. package/nitrogen/generated/android/kotlin/com/margelo/nitro/com/nitrostorage/NitroStorageOnLoad.kt +1 -1
  40. package/nitrogen/generated/ios/NitroStorage+autolinking.rb +2 -2
  41. package/nitrogen/generated/ios/NitroStorage-Swift-Cxx-Bridge.cpp +1 -1
  42. package/nitrogen/generated/ios/NitroStorage-Swift-Cxx-Bridge.hpp +1 -1
  43. package/nitrogen/generated/ios/NitroStorage-Swift-Cxx-Umbrella.hpp +1 -1
  44. package/nitrogen/generated/ios/NitroStorageAutolinking.mm +1 -1
  45. package/nitrogen/generated/ios/NitroStorageAutolinking.swift +1 -1
  46. package/nitrogen/generated/shared/c++/HybridStorageSpec.cpp +5 -1
  47. package/nitrogen/generated/shared/c++/HybridStorageSpec.hpp +6 -1
  48. package/package.json +6 -25
  49. package/src/Storage.nitro.ts +6 -7
  50. package/src/Storage.types.ts +5 -0
  51. package/src/index.ts +88 -14
  52. package/src/index.web.ts +341 -0
@@ -1,15 +1,14 @@
1
- import type { HybridObject } from "react-native-nitro-modules";
2
-
3
- export enum StorageScope {
4
- Memory = 0,
5
- Disk = 1,
6
- Secure = 2,
7
- }
1
+ import { type HybridObject } from "react-native-nitro-modules";
2
+ import { StorageScope } from "./Storage.types";
8
3
 
9
4
  export interface Storage extends HybridObject<{ ios: "c++"; android: "c++" }> {
10
5
  set(key: string, value: string, scope: number): void;
11
6
  get(key: string, scope: number): string | undefined;
12
7
  remove(key: string, scope: number): void;
8
+ clear(scope: number): void;
9
+ setBatch(keys: string[], values: string[], scope: number): void;
10
+ getBatch(keys: string[], scope: number): (string | undefined)[];
11
+ removeBatch(keys: string[], scope: number): void;
13
12
  addOnChange(
14
13
  scope: number,
15
14
  callback: (key: string, value: string | undefined) => void
@@ -0,0 +1,5 @@
1
+ export enum StorageScope {
2
+ Memory = 0,
3
+ Disk = 1,
4
+ Secure = 2,
5
+ }
package/src/index.ts CHANGED
@@ -1,9 +1,9 @@
1
- import { useSyncExternalStore, useMemo } from "react";
1
+ import { useSyncExternalStore } from "react";
2
2
  import { NitroModules } from "react-native-nitro-modules";
3
- import type { Storage, StorageScope } from "./Storage.nitro";
4
- import { StorageScope as StorageScopeEnum } from "./Storage.nitro";
3
+ import type { Storage } from "./Storage.nitro";
4
+ import { StorageScope } from "./Storage.types";
5
5
 
6
- export { StorageScope } from "./Storage.nitro";
6
+ export { StorageScope } from "./Storage.types";
7
7
  export type { Storage } from "./Storage.nitro";
8
8
 
9
9
  let _storageModule: Storage | null = null;
@@ -23,11 +23,18 @@ function notifyMemoryListeners(key: string, value: any) {
23
23
  }
24
24
 
25
25
  export const storage = {
26
- clear: (scope: StorageScope.Memory) => {
27
- memoryStore.clear();
26
+ clear: (scope: StorageScope) => {
27
+ if (scope === StorageScope.Memory) {
28
+ memoryStore.clear();
29
+ notifyMemoryListeners("", undefined);
30
+ } else {
31
+ getStorageModule().clear(scope);
32
+ }
28
33
  },
29
34
  clearAll: () => {
30
- storage.clear(StorageScopeEnum.Memory);
35
+ storage.clear(StorageScope.Memory);
36
+ storage.clear(StorageScope.Disk);
37
+ storage.clear(StorageScope.Secure);
31
38
  },
32
39
  };
33
40
 
@@ -44,6 +51,9 @@ export interface StorageItem<T> {
44
51
  set: (value: T | ((prev: T) => T)) => void;
45
52
  delete: () => void;
46
53
  subscribe: (callback: () => void) => () => void;
54
+ serialize: (value: T) => string;
55
+ deserialize: (value: string) => T;
56
+ _triggerListeners: () => void;
47
57
  scope: StorageScope;
48
58
  key: string;
49
59
  }
@@ -61,7 +71,7 @@ export function createStorageItem<T = undefined>(
61
71
  ): StorageItem<T> {
62
72
  const serialize = config.serialize ?? defaultSerialize;
63
73
  const deserialize = config.deserialize ?? defaultDeserialize;
64
- const isMemory = config.scope === StorageScopeEnum.Memory;
74
+ const isMemory = config.scope === StorageScope.Memory;
65
75
 
66
76
  const listeners = new Set<() => void>();
67
77
  let unsubscribe: (() => void) | null = null;
@@ -70,7 +80,9 @@ export function createStorageItem<T = undefined>(
70
80
  if (!unsubscribe) {
71
81
  if (isMemory) {
72
82
  const listener = (key: string) => {
73
- if (key === config.key) {
83
+ if (key === "" || key === config.key) {
84
+ lastRaw = undefined;
85
+ lastValue = undefined;
74
86
  listeners.forEach((l) => l());
75
87
  }
76
88
  };
@@ -78,7 +90,9 @@ export function createStorageItem<T = undefined>(
78
90
  unsubscribe = () => memoryListeners.delete(listener);
79
91
  } else {
80
92
  unsubscribe = getStorageModule().addOnChange(config.scope, (key) => {
81
- if (key === config.key) {
93
+ if (key === "" || key === config.key) {
94
+ lastRaw = undefined;
95
+ lastValue = undefined;
82
96
  listeners.forEach((listener) => listener());
83
97
  }
84
98
  });
@@ -86,11 +100,11 @@ export function createStorageItem<T = undefined>(
86
100
  }
87
101
  };
88
102
 
89
- let lastRaw: string | any | undefined;
103
+ let lastRaw: string | undefined;
90
104
  let lastValue: T | undefined;
91
105
 
92
106
  const get = (): T => {
93
- let raw: string | any;
107
+ let raw: string | undefined;
94
108
 
95
109
  if (isMemory) {
96
110
  raw = memoryStore.get(config.key);
@@ -120,8 +134,8 @@ export function createStorageItem<T = undefined>(
120
134
  const set = (valueOrFn: T | ((prev: T) => T)): void => {
121
135
  const currentValue = get();
122
136
  const newValue =
123
- valueOrFn instanceof Function
124
- ? (valueOrFn as Function)(currentValue)
137
+ typeof valueOrFn === "function"
138
+ ? (valueOrFn as (prev: T) => T)(currentValue)
125
139
  : valueOrFn;
126
140
 
127
141
  if (isMemory) {
@@ -159,6 +173,13 @@ export function createStorageItem<T = undefined>(
159
173
  set,
160
174
  delete: deleteItem,
161
175
  subscribe,
176
+ serialize,
177
+ deserialize,
178
+ _triggerListeners: () => {
179
+ lastRaw = undefined;
180
+ lastValue = undefined;
181
+ listeners.forEach((l) => l());
182
+ },
162
183
  scope: config.scope,
163
184
  key: config.key,
164
185
  };
@@ -174,3 +195,56 @@ export function useStorage<T>(
174
195
  export function useSetStorage<T>(item: StorageItem<T>) {
175
196
  return item.set;
176
197
  }
198
+
199
+ export function getBatch(
200
+ items: StorageItem<any>[],
201
+ scope: StorageScope
202
+ ): any[] {
203
+ if (scope === StorageScope.Memory) {
204
+ return items.map((item) => item.get());
205
+ }
206
+
207
+ const keys = items.map((item) => item.key);
208
+ const rawValues = getStorageModule().getBatch(keys, scope);
209
+
210
+ return items.map((item, idx) => {
211
+ const raw = rawValues[idx];
212
+ if (raw === undefined) {
213
+ return item.get();
214
+ }
215
+ return item.deserialize(raw);
216
+ });
217
+ }
218
+
219
+ export function setBatch(
220
+ items: { item: StorageItem<any>; value: any }[],
221
+ scope: StorageScope
222
+ ): void {
223
+ if (scope === StorageScope.Memory) {
224
+ items.forEach(({ item, value }) => item.set(value));
225
+ return;
226
+ }
227
+
228
+ const keys = items.map((i) => i.item.key);
229
+ const values = items.map((i) => i.item.serialize(i.value));
230
+
231
+ getStorageModule().setBatch(keys, values, scope);
232
+
233
+ items.forEach(({ item }) => {
234
+ item._triggerListeners();
235
+ });
236
+ }
237
+
238
+ export function removeBatch(
239
+ items: StorageItem<any>[],
240
+ scope: StorageScope
241
+ ): void {
242
+ if (scope === StorageScope.Memory) {
243
+ items.forEach((item) => item.delete());
244
+ return;
245
+ }
246
+
247
+ const keys = items.map((item) => item.key);
248
+ getStorageModule().removeBatch(keys, scope);
249
+ items.forEach((item) => item.delete());
250
+ }
@@ -0,0 +1,341 @@
1
+ import { useSyncExternalStore } from "react";
2
+
3
+ export enum StorageScope {
4
+ Memory = 0,
5
+ Disk = 1,
6
+ Secure = 2,
7
+ }
8
+
9
+ export interface Storage {
10
+ name: string;
11
+ equals: (other: any) => boolean;
12
+ dispose: () => void;
13
+ set(key: string, value: string, scope: number): void;
14
+ get(key: string, scope: number): string | undefined;
15
+ remove(key: string, scope: number): void;
16
+ clear(scope: number): void;
17
+ setBatch(keys: string[], values: string[], scope: number): void;
18
+ getBatch(keys: string[], scope: number): (string | undefined)[];
19
+ removeBatch(keys: string[], scope: number): void;
20
+ addOnChange(
21
+ scope: number,
22
+ callback: (key: string, value: string | undefined) => void
23
+ ): () => void;
24
+ }
25
+
26
+ const diskListeners = new Map<string, Set<() => void>>();
27
+ const secureListeners = new Map<string, Set<() => void>>();
28
+
29
+ function notifyDiskListeners(key: string) {
30
+ diskListeners.get(key)?.forEach((cb) => cb());
31
+ }
32
+
33
+ function notifySecureListeners(key: string) {
34
+ secureListeners.get(key)?.forEach((cb) => cb());
35
+ }
36
+
37
+ const WebStorage: Storage = {
38
+ name: "Storage",
39
+ equals: (other) => other === WebStorage,
40
+ dispose: () => {},
41
+ set: (key: string, value: string, scope: number) => {
42
+ if (scope === StorageScope.Disk) {
43
+ localStorage?.setItem(key, value);
44
+ notifyDiskListeners(key);
45
+ } else if (scope === StorageScope.Secure) {
46
+ sessionStorage?.setItem(key, value);
47
+ notifySecureListeners(key);
48
+ }
49
+ },
50
+
51
+ get: (key: string, scope: number) => {
52
+ if (scope === StorageScope.Disk) {
53
+ return localStorage?.getItem(key) ?? undefined;
54
+ } else if (scope === StorageScope.Secure) {
55
+ return sessionStorage?.getItem(key) ?? undefined;
56
+ }
57
+ return undefined;
58
+ },
59
+ remove: (key: string, scope: number) => {
60
+ if (scope === StorageScope.Disk) {
61
+ localStorage?.removeItem(key);
62
+ notifyDiskListeners(key);
63
+ } else if (scope === StorageScope.Secure) {
64
+ sessionStorage?.removeItem(key);
65
+ notifySecureListeners(key);
66
+ }
67
+ },
68
+
69
+ clear: (scope: number) => {
70
+ if (scope === StorageScope.Disk) {
71
+ localStorage?.clear();
72
+ diskListeners.forEach((listeners) => {
73
+ listeners.forEach((cb) => cb());
74
+ });
75
+ } else if (scope === StorageScope.Secure) {
76
+ sessionStorage?.clear();
77
+ secureListeners.forEach((listeners) => {
78
+ listeners.forEach((cb) => cb());
79
+ });
80
+ }
81
+ },
82
+ setBatch: (keys: string[], values: string[], scope: number) => {
83
+ keys.forEach((key, i) => WebStorage.set(key, values[i], scope));
84
+ },
85
+ getBatch: (keys: string[], scope: number) => {
86
+ return keys.map((key) => WebStorage.get(key, scope));
87
+ },
88
+ removeBatch: (keys: string[], scope: number) => {
89
+ keys.forEach((key) => WebStorage.remove(key, scope));
90
+ },
91
+ addOnChange: (
92
+ _scope: number,
93
+ _callback: (key: string, value: string | undefined) => void
94
+ ) => {
95
+ return () => {};
96
+ },
97
+ };
98
+
99
+ const memoryStore = new Map<string, any>();
100
+ const memoryListeners = new Set<(key: string, value: any) => void>();
101
+
102
+ function notifyMemoryListeners(key: string, value: any) {
103
+ memoryListeners.forEach((listener) => listener(key, value));
104
+ }
105
+
106
+ export const storage = {
107
+ clear: (scope: StorageScope) => {
108
+ if (scope === StorageScope.Memory) {
109
+ memoryStore.clear();
110
+ notifyMemoryListeners("", undefined);
111
+ } else {
112
+ WebStorage.clear(scope);
113
+ }
114
+ },
115
+ clearAll: () => {
116
+ storage.clear(StorageScope.Memory);
117
+ storage.clear(StorageScope.Disk);
118
+ storage.clear(StorageScope.Secure);
119
+ },
120
+ };
121
+
122
+ export interface StorageItemConfig<T> {
123
+ key: string;
124
+ scope: StorageScope;
125
+ defaultValue?: T;
126
+ serialize?: (value: T) => string;
127
+ deserialize?: (value: string) => T;
128
+ }
129
+
130
+ export interface StorageItem<T> {
131
+ get: () => T;
132
+ set: (value: T | ((prev: T) => T)) => void;
133
+ delete: () => void;
134
+ subscribe: (callback: () => void) => () => void;
135
+ serialize: (value: T) => string;
136
+ deserialize: (value: string) => T;
137
+ _triggerListeners: () => void;
138
+ scope: StorageScope;
139
+ key: string;
140
+ }
141
+
142
+ function defaultSerialize<T>(value: T): string {
143
+ return JSON.stringify(value);
144
+ }
145
+
146
+ function defaultDeserialize<T>(value: string): T {
147
+ return JSON.parse(value) as T;
148
+ }
149
+
150
+ export function createStorageItem<T = undefined>(
151
+ config: StorageItemConfig<T>
152
+ ): StorageItem<T> {
153
+ const serialize = config.serialize ?? defaultSerialize;
154
+ const deserialize = config.deserialize ?? defaultDeserialize;
155
+ const isMemory = config.scope === StorageScope.Memory;
156
+
157
+ const listeners = new Set<() => void>();
158
+ let unsubscribe: (() => void) | null = null;
159
+ let lastRaw: string | undefined;
160
+ let lastValue: T | undefined;
161
+
162
+ const ensureSubscription = () => {
163
+ if (!unsubscribe) {
164
+ if (isMemory) {
165
+ const listener = (key: string) => {
166
+ if (key === config.key) {
167
+ lastRaw = undefined;
168
+ lastValue = undefined;
169
+ listeners.forEach((l) => l());
170
+ }
171
+ };
172
+ memoryListeners.add(listener);
173
+ unsubscribe = () => memoryListeners.delete(listener);
174
+ } else if (config.scope === StorageScope.Disk) {
175
+ const listener = () => {
176
+ lastRaw = undefined;
177
+ lastValue = undefined;
178
+ listeners.forEach((l) => l());
179
+ };
180
+ if (!diskListeners.has(config.key)) {
181
+ diskListeners.set(config.key, new Set());
182
+ }
183
+ diskListeners.get(config.key)!.add(listener);
184
+ unsubscribe = () => diskListeners.get(config.key)?.delete(listener);
185
+ } else if (config.scope === StorageScope.Secure) {
186
+ const listener = () => {
187
+ lastRaw = undefined;
188
+ lastValue = undefined;
189
+ listeners.forEach((l) => l());
190
+ };
191
+ if (!secureListeners.has(config.key)) {
192
+ secureListeners.set(config.key, new Set());
193
+ }
194
+ secureListeners.get(config.key)!.add(listener);
195
+ unsubscribe = () => secureListeners.get(config.key)?.delete(listener);
196
+ }
197
+ }
198
+ };
199
+
200
+ const get = (): T => {
201
+ let raw: string | undefined;
202
+ if (isMemory) {
203
+ raw = memoryStore.get(config.key);
204
+ } else {
205
+ raw = WebStorage.get(config.key, config.scope);
206
+ }
207
+
208
+ if (raw === lastRaw && lastValue !== undefined) {
209
+ return lastValue;
210
+ }
211
+
212
+ lastRaw = raw;
213
+
214
+ if (raw === undefined) {
215
+ lastValue = config.defaultValue as T;
216
+ } else {
217
+ lastValue = isMemory ? (raw as T) : deserialize(raw);
218
+ }
219
+
220
+ return lastValue;
221
+ };
222
+
223
+ const set = (valueOrFn: T | ((prev: T) => T)): void => {
224
+ const currentValue = get();
225
+ const newValue =
226
+ typeof valueOrFn === "function"
227
+ ? (valueOrFn as (prev: T) => T)(currentValue)
228
+ : valueOrFn;
229
+
230
+ lastRaw = undefined;
231
+
232
+ if (isMemory) {
233
+ memoryStore.set(config.key, newValue);
234
+ notifyMemoryListeners(config.key, newValue);
235
+ } else {
236
+ WebStorage.set(config.key, serialize(newValue), config.scope);
237
+ }
238
+ };
239
+
240
+ const deleteItem = (): void => {
241
+ lastRaw = undefined;
242
+
243
+ if (isMemory) {
244
+ memoryStore.delete(config.key);
245
+ notifyMemoryListeners(config.key, undefined);
246
+ } else {
247
+ WebStorage.remove(config.key, config.scope);
248
+ }
249
+ };
250
+
251
+ const subscribe = (callback: () => void): (() => void) => {
252
+ ensureSubscription();
253
+ listeners.add(callback);
254
+ return () => {
255
+ listeners.delete(callback);
256
+ if (listeners.size === 0 && unsubscribe) {
257
+ unsubscribe();
258
+ unsubscribe = null;
259
+ }
260
+ };
261
+ };
262
+
263
+ return {
264
+ get,
265
+ set,
266
+ delete: deleteItem,
267
+ subscribe,
268
+ serialize,
269
+ deserialize,
270
+ _triggerListeners: () => {
271
+ lastRaw = undefined;
272
+ lastValue = undefined;
273
+ listeners.forEach((l) => l());
274
+ },
275
+ scope: config.scope,
276
+ key: config.key,
277
+ };
278
+ }
279
+
280
+ export function useStorage<T>(
281
+ item: StorageItem<T>
282
+ ): [T, (value: T | ((prev: T) => T)) => void] {
283
+ const value = useSyncExternalStore(item.subscribe, item.get, item.get);
284
+ return [value, item.set];
285
+ }
286
+
287
+ export function useSetStorage<T>(item: StorageItem<T>) {
288
+ return item.set;
289
+ }
290
+
291
+ export function getBatch(
292
+ items: StorageItem<any>[],
293
+ scope: StorageScope
294
+ ): any[] {
295
+ if (scope === StorageScope.Memory) {
296
+ return items.map((item) => item.get());
297
+ }
298
+
299
+ const keys = items.map((item) => item.key);
300
+ const rawValues = WebStorage.getBatch(keys, scope);
301
+
302
+ return items.map((item, idx) => {
303
+ const raw = rawValues[idx];
304
+ if (raw === undefined) {
305
+ return item.get();
306
+ }
307
+ return item.deserialize(raw);
308
+ });
309
+ }
310
+
311
+ export function setBatch(
312
+ items: { item: StorageItem<any>; value: any }[],
313
+ scope: StorageScope
314
+ ): void {
315
+ if (scope === StorageScope.Memory) {
316
+ items.forEach(({ item, value }) => item.set(value));
317
+ return;
318
+ }
319
+
320
+ const keys = items.map((i) => i.item.key);
321
+ const values = items.map((i) => i.item.serialize(i.value));
322
+ WebStorage.setBatch(keys, values, scope);
323
+
324
+ items.forEach(({ item }) => {
325
+ item._triggerListeners();
326
+ });
327
+ }
328
+
329
+ export function removeBatch(
330
+ items: StorageItem<any>[],
331
+ scope: StorageScope
332
+ ): void {
333
+ if (scope === StorageScope.Memory) {
334
+ items.forEach((item) => item.delete());
335
+ return;
336
+ }
337
+
338
+ const keys = items.map((item) => item.key);
339
+ WebStorage.removeBatch(keys, scope);
340
+ items.forEach((item) => item.delete());
341
+ }