react-native-nitro-storage 0.1.3 → 0.3.0
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 +320 -391
- package/android/src/main/cpp/AndroidStorageAdapterCpp.cpp +101 -0
- package/android/src/main/cpp/AndroidStorageAdapterCpp.hpp +6 -41
- package/android/src/main/java/com/nitrostorage/AndroidStorageAdapter.kt +125 -37
- package/app.plugin.js +9 -7
- package/cpp/bindings/HybridStorage.cpp +214 -19
- package/cpp/bindings/HybridStorage.hpp +1 -0
- package/cpp/core/NativeStorageAdapter.hpp +7 -0
- package/ios/IOSStorageAdapterCpp.hpp +6 -0
- package/ios/IOSStorageAdapterCpp.mm +90 -7
- package/lib/commonjs/index.js +537 -66
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.web.js +558 -130
- package/lib/commonjs/index.web.js.map +1 -1
- package/lib/commonjs/internal.js +102 -0
- package/lib/commonjs/internal.js.map +1 -0
- package/lib/module/index.js +528 -67
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +536 -122
- package/lib/module/index.web.js.map +1 -1
- package/lib/module/internal.js +92 -0
- package/lib/module/internal.js.map +1 -0
- package/lib/typescript/index.d.ts +42 -6
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/index.web.d.ts +45 -12
- package/lib/typescript/index.web.d.ts.map +1 -1
- package/lib/typescript/internal.d.ts +19 -0
- package/lib/typescript/internal.d.ts.map +1 -0
- package/lib/typescript/migration.d.ts +2 -3
- package/lib/typescript/migration.d.ts.map +1 -1
- package/nitrogen/generated/android/NitroStorage+autolinking.cmake +1 -1
- package/nitrogen/generated/android/NitroStorage+autolinking.gradle +1 -1
- package/nitrogen/generated/android/NitroStorageOnLoad.cpp +1 -1
- package/nitrogen/generated/android/NitroStorageOnLoad.hpp +1 -1
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/com/nitrostorage/NitroStorageOnLoad.kt +1 -1
- package/nitrogen/generated/ios/NitroStorage+autolinking.rb +1 -1
- package/nitrogen/generated/ios/NitroStorage-Swift-Cxx-Bridge.cpp +1 -1
- package/nitrogen/generated/ios/NitroStorage-Swift-Cxx-Bridge.hpp +1 -1
- package/nitrogen/generated/ios/NitroStorage-Swift-Cxx-Umbrella.hpp +1 -1
- package/nitrogen/generated/ios/NitroStorageAutolinking.mm +1 -1
- package/nitrogen/generated/ios/NitroStorageAutolinking.swift +5 -1
- package/nitrogen/generated/shared/c++/HybridStorageSpec.cpp +1 -1
- package/nitrogen/generated/shared/c++/HybridStorageSpec.hpp +1 -1
- package/package.json +19 -8
- package/src/index.ts +734 -74
- package/src/index.web.ts +732 -128
- package/src/internal.ts +134 -0
- package/src/migration.ts +2 -2
|
@@ -3,193 +3,471 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports
|
|
6
|
+
Object.defineProperty(exports, "StorageScope", {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
get: function () {
|
|
9
|
+
return _Storage.StorageScope;
|
|
10
|
+
}
|
|
11
|
+
});
|
|
7
12
|
exports.createStorageItem = createStorageItem;
|
|
8
13
|
exports.getBatch = getBatch;
|
|
14
|
+
Object.defineProperty(exports, "migrateFromMMKV", {
|
|
15
|
+
enumerable: true,
|
|
16
|
+
get: function () {
|
|
17
|
+
return _migration.migrateFromMMKV;
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
exports.migrateToLatest = migrateToLatest;
|
|
21
|
+
exports.registerMigration = registerMigration;
|
|
9
22
|
exports.removeBatch = removeBatch;
|
|
23
|
+
exports.runTransaction = runTransaction;
|
|
10
24
|
exports.setBatch = setBatch;
|
|
11
25
|
exports.storage = void 0;
|
|
12
26
|
exports.useSetStorage = useSetStorage;
|
|
13
27
|
exports.useStorage = useStorage;
|
|
28
|
+
exports.useStorageSelector = useStorageSelector;
|
|
14
29
|
var _react = require("react");
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
30
|
+
var _Storage = require("./Storage.types");
|
|
31
|
+
var _internal = require("./internal");
|
|
32
|
+
var _migration = require("./migration");
|
|
33
|
+
const registeredMigrations = new Map();
|
|
34
|
+
const runMicrotask = typeof queueMicrotask === "function" ? queueMicrotask : task => {
|
|
35
|
+
Promise.resolve().then(task);
|
|
36
|
+
};
|
|
37
|
+
const memoryStore = new Map();
|
|
38
|
+
const memoryListeners = new Map();
|
|
39
|
+
const webScopeListeners = new Map([[_Storage.StorageScope.Disk, new Map()], [_Storage.StorageScope.Secure, new Map()]]);
|
|
40
|
+
const scopedRawCache = new Map([[_Storage.StorageScope.Disk, new Map()], [_Storage.StorageScope.Secure, new Map()]]);
|
|
41
|
+
const pendingSecureWrites = new Map();
|
|
42
|
+
let secureFlushScheduled = false;
|
|
43
|
+
function getBrowserStorage(scope) {
|
|
44
|
+
if (scope === _Storage.StorageScope.Disk) {
|
|
45
|
+
return globalThis.localStorage;
|
|
46
|
+
}
|
|
47
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
48
|
+
return globalThis.sessionStorage;
|
|
49
|
+
}
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
function getScopedListeners(scope) {
|
|
53
|
+
return webScopeListeners.get(scope);
|
|
54
|
+
}
|
|
55
|
+
function getScopeRawCache(scope) {
|
|
56
|
+
return scopedRawCache.get(scope);
|
|
57
|
+
}
|
|
58
|
+
function cacheRawValue(scope, key, value) {
|
|
59
|
+
getScopeRawCache(scope).set(key, value);
|
|
60
|
+
}
|
|
61
|
+
function readCachedRawValue(scope, key) {
|
|
62
|
+
return getScopeRawCache(scope).get(key);
|
|
63
|
+
}
|
|
64
|
+
function hasCachedRawValue(scope, key) {
|
|
65
|
+
return getScopeRawCache(scope).has(key);
|
|
66
|
+
}
|
|
67
|
+
function clearScopeRawCache(scope) {
|
|
68
|
+
getScopeRawCache(scope).clear();
|
|
69
|
+
}
|
|
70
|
+
function notifyKeyListeners(registry, key) {
|
|
71
|
+
registry.get(key)?.forEach(listener => listener());
|
|
72
|
+
}
|
|
73
|
+
function notifyAllListeners(registry) {
|
|
74
|
+
registry.forEach(listeners => {
|
|
75
|
+
listeners.forEach(listener => listener());
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
function addKeyListener(registry, key, listener) {
|
|
79
|
+
let listeners = registry.get(key);
|
|
80
|
+
if (!listeners) {
|
|
81
|
+
listeners = new Set();
|
|
82
|
+
registry.set(key, listeners);
|
|
83
|
+
}
|
|
84
|
+
listeners.add(listener);
|
|
85
|
+
return () => {
|
|
86
|
+
const scopedListeners = registry.get(key);
|
|
87
|
+
if (!scopedListeners) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
scopedListeners.delete(listener);
|
|
91
|
+
if (scopedListeners.size === 0) {
|
|
92
|
+
registry.delete(key);
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function readPendingSecureWrite(key) {
|
|
97
|
+
return pendingSecureWrites.get(key)?.value;
|
|
98
|
+
}
|
|
99
|
+
function hasPendingSecureWrite(key) {
|
|
100
|
+
return pendingSecureWrites.has(key);
|
|
101
|
+
}
|
|
102
|
+
function clearPendingSecureWrite(key) {
|
|
103
|
+
pendingSecureWrites.delete(key);
|
|
104
|
+
}
|
|
105
|
+
function flushSecureWrites() {
|
|
106
|
+
secureFlushScheduled = false;
|
|
107
|
+
if (pendingSecureWrites.size === 0) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const writes = Array.from(pendingSecureWrites.values());
|
|
111
|
+
pendingSecureWrites.clear();
|
|
112
|
+
const keysToSet = [];
|
|
113
|
+
const valuesToSet = [];
|
|
114
|
+
const keysToRemove = [];
|
|
115
|
+
writes.forEach(({
|
|
116
|
+
key,
|
|
117
|
+
value
|
|
118
|
+
}) => {
|
|
119
|
+
if (value === undefined) {
|
|
120
|
+
keysToRemove.push(key);
|
|
121
|
+
} else {
|
|
122
|
+
keysToSet.push(key);
|
|
123
|
+
valuesToSet.push(value);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
if (keysToSet.length > 0) {
|
|
127
|
+
WebStorage.setBatch(keysToSet, valuesToSet, _Storage.StorageScope.Secure);
|
|
128
|
+
}
|
|
129
|
+
if (keysToRemove.length > 0) {
|
|
130
|
+
WebStorage.removeBatch(keysToRemove, _Storage.StorageScope.Secure);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
function scheduleSecureWrite(key, value) {
|
|
134
|
+
pendingSecureWrites.set(key, {
|
|
135
|
+
key,
|
|
136
|
+
value
|
|
137
|
+
});
|
|
138
|
+
if (secureFlushScheduled) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
secureFlushScheduled = true;
|
|
142
|
+
runMicrotask(flushSecureWrites);
|
|
28
143
|
}
|
|
29
144
|
const WebStorage = {
|
|
30
145
|
name: "Storage",
|
|
31
146
|
equals: other => other === WebStorage,
|
|
32
147
|
dispose: () => {},
|
|
33
148
|
set: (key, value, scope) => {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
sessionStorage?.setItem(key, value);
|
|
39
|
-
notifySecureListeners(key);
|
|
149
|
+
const storage = getBrowserStorage(scope);
|
|
150
|
+
storage?.setItem(key, value);
|
|
151
|
+
if (scope === _Storage.StorageScope.Disk || scope === _Storage.StorageScope.Secure) {
|
|
152
|
+
notifyKeyListeners(getScopedListeners(scope), key);
|
|
40
153
|
}
|
|
41
154
|
},
|
|
42
155
|
get: (key, scope) => {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
} else if (scope === StorageScope.Secure) {
|
|
46
|
-
return sessionStorage?.getItem(key) ?? undefined;
|
|
47
|
-
}
|
|
48
|
-
return undefined;
|
|
156
|
+
const storage = getBrowserStorage(scope);
|
|
157
|
+
return storage?.getItem(key) ?? undefined;
|
|
49
158
|
},
|
|
50
159
|
remove: (key, scope) => {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
sessionStorage?.removeItem(key);
|
|
56
|
-
notifySecureListeners(key);
|
|
160
|
+
const storage = getBrowserStorage(scope);
|
|
161
|
+
storage?.removeItem(key);
|
|
162
|
+
if (scope === _Storage.StorageScope.Disk || scope === _Storage.StorageScope.Secure) {
|
|
163
|
+
notifyKeyListeners(getScopedListeners(scope), key);
|
|
57
164
|
}
|
|
58
165
|
},
|
|
59
166
|
clear: scope => {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
});
|
|
65
|
-
} else if (scope === StorageScope.Secure) {
|
|
66
|
-
sessionStorage?.clear();
|
|
67
|
-
secureListeners.forEach(listeners => {
|
|
68
|
-
listeners.forEach(cb => cb());
|
|
69
|
-
});
|
|
167
|
+
const storage = getBrowserStorage(scope);
|
|
168
|
+
storage?.clear();
|
|
169
|
+
if (scope === _Storage.StorageScope.Disk || scope === _Storage.StorageScope.Secure) {
|
|
170
|
+
notifyAllListeners(getScopedListeners(scope));
|
|
70
171
|
}
|
|
71
172
|
},
|
|
72
173
|
setBatch: (keys, values, scope) => {
|
|
73
|
-
|
|
174
|
+
const storage = getBrowserStorage(scope);
|
|
175
|
+
if (!storage) {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
keys.forEach((key, index) => {
|
|
179
|
+
storage.setItem(key, values[index]);
|
|
180
|
+
});
|
|
181
|
+
if (scope === _Storage.StorageScope.Disk || scope === _Storage.StorageScope.Secure) {
|
|
182
|
+
const listeners = getScopedListeners(scope);
|
|
183
|
+
keys.forEach(key => notifyKeyListeners(listeners, key));
|
|
184
|
+
}
|
|
74
185
|
},
|
|
75
186
|
getBatch: (keys, scope) => {
|
|
76
|
-
|
|
187
|
+
const storage = getBrowserStorage(scope);
|
|
188
|
+
return keys.map(key => storage?.getItem(key) ?? undefined);
|
|
77
189
|
},
|
|
78
190
|
removeBatch: (keys, scope) => {
|
|
79
|
-
|
|
191
|
+
const storage = getBrowserStorage(scope);
|
|
192
|
+
if (!storage) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
keys.forEach(key => {
|
|
196
|
+
storage.removeItem(key);
|
|
197
|
+
});
|
|
198
|
+
if (scope === _Storage.StorageScope.Disk || scope === _Storage.StorageScope.Secure) {
|
|
199
|
+
const listeners = getScopedListeners(scope);
|
|
200
|
+
keys.forEach(key => notifyKeyListeners(listeners, key));
|
|
201
|
+
}
|
|
80
202
|
},
|
|
81
203
|
addOnChange: (_scope, _callback) => {
|
|
82
204
|
return () => {};
|
|
83
205
|
}
|
|
84
206
|
};
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
207
|
+
function getRawValue(key, scope) {
|
|
208
|
+
(0, _internal.assertValidScope)(scope);
|
|
209
|
+
if (scope === _Storage.StorageScope.Memory) {
|
|
210
|
+
const value = memoryStore.get(key);
|
|
211
|
+
return typeof value === "string" ? value : undefined;
|
|
212
|
+
}
|
|
213
|
+
if (scope === _Storage.StorageScope.Secure && hasPendingSecureWrite(key)) {
|
|
214
|
+
return readPendingSecureWrite(key);
|
|
215
|
+
}
|
|
216
|
+
return WebStorage.get(key, scope);
|
|
217
|
+
}
|
|
218
|
+
function setRawValue(key, value, scope) {
|
|
219
|
+
(0, _internal.assertValidScope)(scope);
|
|
220
|
+
if (scope === _Storage.StorageScope.Memory) {
|
|
221
|
+
memoryStore.set(key, value);
|
|
222
|
+
notifyKeyListeners(memoryListeners, key);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
226
|
+
flushSecureWrites();
|
|
227
|
+
clearPendingSecureWrite(key);
|
|
228
|
+
}
|
|
229
|
+
WebStorage.set(key, value, scope);
|
|
230
|
+
cacheRawValue(scope, key, value);
|
|
231
|
+
}
|
|
232
|
+
function removeRawValue(key, scope) {
|
|
233
|
+
(0, _internal.assertValidScope)(scope);
|
|
234
|
+
if (scope === _Storage.StorageScope.Memory) {
|
|
235
|
+
memoryStore.delete(key);
|
|
236
|
+
notifyKeyListeners(memoryListeners, key);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
240
|
+
flushSecureWrites();
|
|
241
|
+
clearPendingSecureWrite(key);
|
|
242
|
+
}
|
|
243
|
+
WebStorage.remove(key, scope);
|
|
244
|
+
cacheRawValue(scope, key, undefined);
|
|
245
|
+
}
|
|
246
|
+
function readMigrationVersion(scope) {
|
|
247
|
+
const raw = getRawValue(_internal.MIGRATION_VERSION_KEY, scope);
|
|
248
|
+
if (raw === undefined) {
|
|
249
|
+
return 0;
|
|
250
|
+
}
|
|
251
|
+
const parsed = Number.parseInt(raw, 10);
|
|
252
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : 0;
|
|
253
|
+
}
|
|
254
|
+
function writeMigrationVersion(scope, version) {
|
|
255
|
+
setRawValue(_internal.MIGRATION_VERSION_KEY, String(version), scope);
|
|
89
256
|
}
|
|
90
257
|
const storage = exports.storage = {
|
|
91
258
|
clear: scope => {
|
|
92
|
-
if (scope === StorageScope.Memory) {
|
|
259
|
+
if (scope === _Storage.StorageScope.Memory) {
|
|
93
260
|
memoryStore.clear();
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
WebStorage.clear(scope);
|
|
261
|
+
notifyAllListeners(memoryListeners);
|
|
262
|
+
return;
|
|
97
263
|
}
|
|
264
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
265
|
+
flushSecureWrites();
|
|
266
|
+
pendingSecureWrites.clear();
|
|
267
|
+
}
|
|
268
|
+
clearScopeRawCache(scope);
|
|
269
|
+
WebStorage.clear(scope);
|
|
98
270
|
},
|
|
99
271
|
clearAll: () => {
|
|
100
|
-
storage.clear(StorageScope.Memory);
|
|
101
|
-
storage.clear(StorageScope.Disk);
|
|
102
|
-
storage.clear(StorageScope.Secure);
|
|
272
|
+
storage.clear(_Storage.StorageScope.Memory);
|
|
273
|
+
storage.clear(_Storage.StorageScope.Disk);
|
|
274
|
+
storage.clear(_Storage.StorageScope.Secure);
|
|
103
275
|
}
|
|
104
276
|
};
|
|
277
|
+
function canUseRawBatchPath(item) {
|
|
278
|
+
return item._hasExpiration === false && item._hasValidation === false;
|
|
279
|
+
}
|
|
105
280
|
function defaultSerialize(value) {
|
|
106
|
-
return
|
|
281
|
+
return (0, _internal.serializeWithPrimitiveFastPath)(value);
|
|
107
282
|
}
|
|
108
283
|
function defaultDeserialize(value) {
|
|
109
|
-
return
|
|
284
|
+
return (0, _internal.deserializeWithPrimitiveFastPath)(value);
|
|
110
285
|
}
|
|
111
286
|
function createStorageItem(config) {
|
|
112
287
|
const serialize = config.serialize ?? defaultSerialize;
|
|
113
288
|
const deserialize = config.deserialize ?? defaultDeserialize;
|
|
114
|
-
const isMemory = config.scope === StorageScope.Memory;
|
|
289
|
+
const isMemory = config.scope === _Storage.StorageScope.Memory;
|
|
290
|
+
const validate = config.validate;
|
|
291
|
+
const onValidationError = config.onValidationError;
|
|
292
|
+
const expiration = config.expiration;
|
|
293
|
+
const expirationTtlMs = expiration?.ttlMs;
|
|
294
|
+
const memoryExpiration = expiration && isMemory ? new Map() : null;
|
|
295
|
+
const readCache = !isMemory && config.readCache === true;
|
|
296
|
+
const coalesceSecureWrites = config.scope === _Storage.StorageScope.Secure && config.coalesceSecureWrites === true;
|
|
297
|
+
const nonMemoryScope = config.scope === _Storage.StorageScope.Disk ? _Storage.StorageScope.Disk : config.scope === _Storage.StorageScope.Secure ? _Storage.StorageScope.Secure : null;
|
|
298
|
+
if (expiration && expiration.ttlMs <= 0) {
|
|
299
|
+
throw new Error("expiration.ttlMs must be greater than 0.");
|
|
300
|
+
}
|
|
115
301
|
const listeners = new Set();
|
|
116
302
|
let unsubscribe = null;
|
|
117
|
-
let lastRaw;
|
|
303
|
+
let lastRaw = undefined;
|
|
118
304
|
let lastValue;
|
|
305
|
+
let hasLastValue = false;
|
|
306
|
+
const invalidateParsedCache = () => {
|
|
307
|
+
lastRaw = undefined;
|
|
308
|
+
lastValue = undefined;
|
|
309
|
+
hasLastValue = false;
|
|
310
|
+
};
|
|
119
311
|
const ensureSubscription = () => {
|
|
120
|
-
if (
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
if (
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
} else if (config.scope === StorageScope.Secure) {
|
|
143
|
-
const listener = () => {
|
|
144
|
-
lastRaw = undefined;
|
|
145
|
-
lastValue = undefined;
|
|
146
|
-
listeners.forEach(l => l());
|
|
147
|
-
};
|
|
148
|
-
if (!secureListeners.has(config.key)) {
|
|
149
|
-
secureListeners.set(config.key, new Set());
|
|
312
|
+
if (unsubscribe) {
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
const listener = () => {
|
|
316
|
+
invalidateParsedCache();
|
|
317
|
+
listeners.forEach(callback => callback());
|
|
318
|
+
};
|
|
319
|
+
if (isMemory) {
|
|
320
|
+
unsubscribe = addKeyListener(memoryListeners, config.key, listener);
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
unsubscribe = addKeyListener(getScopedListeners(nonMemoryScope), config.key, listener);
|
|
324
|
+
};
|
|
325
|
+
const readStoredRaw = () => {
|
|
326
|
+
if (isMemory) {
|
|
327
|
+
if (memoryExpiration) {
|
|
328
|
+
const expiresAt = memoryExpiration.get(config.key);
|
|
329
|
+
if (expiresAt !== undefined && expiresAt <= Date.now()) {
|
|
330
|
+
memoryExpiration.delete(config.key);
|
|
331
|
+
memoryStore.delete(config.key);
|
|
332
|
+
notifyKeyListeners(memoryListeners, config.key);
|
|
333
|
+
return undefined;
|
|
150
334
|
}
|
|
151
|
-
secureListeners.get(config.key).add(listener);
|
|
152
|
-
unsubscribe = () => secureListeners.get(config.key)?.delete(listener);
|
|
153
335
|
}
|
|
336
|
+
return memoryStore.get(config.key);
|
|
154
337
|
}
|
|
338
|
+
if (nonMemoryScope === _Storage.StorageScope.Secure && hasPendingSecureWrite(config.key)) {
|
|
339
|
+
return readPendingSecureWrite(config.key);
|
|
340
|
+
}
|
|
341
|
+
if (readCache) {
|
|
342
|
+
if (hasCachedRawValue(nonMemoryScope, config.key)) {
|
|
343
|
+
return readCachedRawValue(nonMemoryScope, config.key);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
const raw = WebStorage.get(config.key, config.scope);
|
|
347
|
+
cacheRawValue(nonMemoryScope, config.key, raw);
|
|
348
|
+
return raw;
|
|
155
349
|
};
|
|
156
|
-
const
|
|
157
|
-
|
|
350
|
+
const writeStoredRaw = rawValue => {
|
|
351
|
+
cacheRawValue(nonMemoryScope, config.key, rawValue);
|
|
352
|
+
if (coalesceSecureWrites) {
|
|
353
|
+
scheduleSecureWrite(config.key, rawValue);
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
if (nonMemoryScope === _Storage.StorageScope.Secure) {
|
|
357
|
+
clearPendingSecureWrite(config.key);
|
|
358
|
+
}
|
|
359
|
+
WebStorage.set(config.key, rawValue, config.scope);
|
|
360
|
+
};
|
|
361
|
+
const removeStoredRaw = () => {
|
|
362
|
+
cacheRawValue(nonMemoryScope, config.key, undefined);
|
|
363
|
+
if (coalesceSecureWrites) {
|
|
364
|
+
scheduleSecureWrite(config.key, undefined);
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
if (nonMemoryScope === _Storage.StorageScope.Secure) {
|
|
368
|
+
clearPendingSecureWrite(config.key);
|
|
369
|
+
}
|
|
370
|
+
WebStorage.remove(config.key, config.scope);
|
|
371
|
+
};
|
|
372
|
+
const writeValueWithoutValidation = value => {
|
|
158
373
|
if (isMemory) {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
374
|
+
if (memoryExpiration) {
|
|
375
|
+
memoryExpiration.set(config.key, Date.now() + (expirationTtlMs ?? 0));
|
|
376
|
+
}
|
|
377
|
+
memoryStore.set(config.key, value);
|
|
378
|
+
notifyKeyListeners(memoryListeners, config.key);
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
const serialized = serialize(value);
|
|
382
|
+
if (expiration) {
|
|
383
|
+
const envelope = {
|
|
384
|
+
__nitroStorageEnvelope: true,
|
|
385
|
+
expiresAt: Date.now() + expiration.ttlMs,
|
|
386
|
+
payload: serialized
|
|
387
|
+
};
|
|
388
|
+
writeStoredRaw(JSON.stringify(envelope));
|
|
389
|
+
return;
|
|
162
390
|
}
|
|
163
|
-
|
|
391
|
+
writeStoredRaw(serialized);
|
|
392
|
+
};
|
|
393
|
+
const resolveInvalidValue = invalidValue => {
|
|
394
|
+
if (onValidationError) {
|
|
395
|
+
return onValidationError(invalidValue);
|
|
396
|
+
}
|
|
397
|
+
return config.defaultValue;
|
|
398
|
+
};
|
|
399
|
+
const ensureValidatedValue = (candidate, hadStoredValue) => {
|
|
400
|
+
if (!validate || validate(candidate)) {
|
|
401
|
+
return candidate;
|
|
402
|
+
}
|
|
403
|
+
const resolved = resolveInvalidValue(candidate);
|
|
404
|
+
if (validate && !validate(resolved)) {
|
|
405
|
+
return config.defaultValue;
|
|
406
|
+
}
|
|
407
|
+
if (hadStoredValue) {
|
|
408
|
+
writeValueWithoutValidation(resolved);
|
|
409
|
+
}
|
|
410
|
+
return resolved;
|
|
411
|
+
};
|
|
412
|
+
const get = () => {
|
|
413
|
+
const raw = readStoredRaw();
|
|
414
|
+
const canUseCachedValue = !expiration && !memoryExpiration;
|
|
415
|
+
if (canUseCachedValue && raw === lastRaw && hasLastValue) {
|
|
164
416
|
return lastValue;
|
|
165
417
|
}
|
|
166
418
|
lastRaw = raw;
|
|
167
419
|
if (raw === undefined) {
|
|
168
|
-
lastValue = config.defaultValue;
|
|
169
|
-
|
|
170
|
-
lastValue
|
|
420
|
+
lastValue = ensureValidatedValue(config.defaultValue, false);
|
|
421
|
+
hasLastValue = true;
|
|
422
|
+
return lastValue;
|
|
423
|
+
}
|
|
424
|
+
if (isMemory) {
|
|
425
|
+
lastValue = ensureValidatedValue(raw, true);
|
|
426
|
+
hasLastValue = true;
|
|
427
|
+
return lastValue;
|
|
428
|
+
}
|
|
429
|
+
let deserializableRaw = raw;
|
|
430
|
+
if (expiration) {
|
|
431
|
+
try {
|
|
432
|
+
const parsed = JSON.parse(raw);
|
|
433
|
+
if ((0, _internal.isStoredEnvelope)(parsed)) {
|
|
434
|
+
if (parsed.expiresAt <= Date.now()) {
|
|
435
|
+
removeStoredRaw();
|
|
436
|
+
invalidateParsedCache();
|
|
437
|
+
lastValue = ensureValidatedValue(config.defaultValue, false);
|
|
438
|
+
hasLastValue = true;
|
|
439
|
+
return lastValue;
|
|
440
|
+
}
|
|
441
|
+
deserializableRaw = parsed.payload;
|
|
442
|
+
}
|
|
443
|
+
} catch {
|
|
444
|
+
// Keep backward compatibility with legacy raw values.
|
|
445
|
+
}
|
|
171
446
|
}
|
|
447
|
+
lastValue = ensureValidatedValue(deserialize(deserializableRaw), true);
|
|
448
|
+
hasLastValue = true;
|
|
172
449
|
return lastValue;
|
|
173
450
|
};
|
|
174
451
|
const set = valueOrFn => {
|
|
175
452
|
const currentValue = get();
|
|
176
453
|
const newValue = typeof valueOrFn === "function" ? valueOrFn(currentValue) : valueOrFn;
|
|
177
|
-
|
|
178
|
-
if (
|
|
179
|
-
|
|
180
|
-
notifyMemoryListeners(config.key, newValue);
|
|
181
|
-
} else {
|
|
182
|
-
WebStorage.set(config.key, serialize(newValue), config.scope);
|
|
454
|
+
invalidateParsedCache();
|
|
455
|
+
if (validate && !validate(newValue)) {
|
|
456
|
+
throw new Error(`Validation failed for key "${config.key}" in scope "${_Storage.StorageScope[config.scope]}".`);
|
|
183
457
|
}
|
|
458
|
+
writeValueWithoutValidation(newValue);
|
|
184
459
|
};
|
|
185
460
|
const deleteItem = () => {
|
|
186
|
-
|
|
461
|
+
invalidateParsedCache();
|
|
187
462
|
if (isMemory) {
|
|
463
|
+
if (memoryExpiration) {
|
|
464
|
+
memoryExpiration.delete(config.key);
|
|
465
|
+
}
|
|
188
466
|
memoryStore.delete(config.key);
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
WebStorage.remove(config.key, config.scope);
|
|
467
|
+
notifyKeyListeners(memoryListeners, config.key);
|
|
468
|
+
return;
|
|
192
469
|
}
|
|
470
|
+
removeStoredRaw();
|
|
193
471
|
};
|
|
194
472
|
const subscribe = callback => {
|
|
195
473
|
ensureSubscription();
|
|
@@ -202,7 +480,7 @@ function createStorageItem(config) {
|
|
|
202
480
|
}
|
|
203
481
|
};
|
|
204
482
|
};
|
|
205
|
-
|
|
483
|
+
const storageItem = {
|
|
206
484
|
get,
|
|
207
485
|
set,
|
|
208
486
|
delete: deleteItem,
|
|
@@ -210,29 +488,83 @@ function createStorageItem(config) {
|
|
|
210
488
|
serialize,
|
|
211
489
|
deserialize,
|
|
212
490
|
_triggerListeners: () => {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
listeners.forEach(l => l());
|
|
491
|
+
invalidateParsedCache();
|
|
492
|
+
listeners.forEach(listener => listener());
|
|
216
493
|
},
|
|
494
|
+
_hasValidation: validate !== undefined,
|
|
495
|
+
_hasExpiration: expiration !== undefined,
|
|
496
|
+
_readCacheEnabled: readCache,
|
|
217
497
|
scope: config.scope,
|
|
218
498
|
key: config.key
|
|
219
499
|
};
|
|
500
|
+
return storageItem;
|
|
220
501
|
}
|
|
221
502
|
function useStorage(item) {
|
|
222
503
|
const value = (0, _react.useSyncExternalStore)(item.subscribe, item.get, item.get);
|
|
223
504
|
return [value, item.set];
|
|
224
505
|
}
|
|
506
|
+
function useStorageSelector(item, selector, isEqual = Object.is) {
|
|
507
|
+
const selectedRef = (0, _react.useRef)({
|
|
508
|
+
hasValue: false
|
|
509
|
+
});
|
|
510
|
+
const getSelectedSnapshot = () => {
|
|
511
|
+
const nextSelected = selector(item.get());
|
|
512
|
+
const current = selectedRef.current;
|
|
513
|
+
if (current.hasValue && isEqual(current.value, nextSelected)) {
|
|
514
|
+
return current.value;
|
|
515
|
+
}
|
|
516
|
+
selectedRef.current = {
|
|
517
|
+
hasValue: true,
|
|
518
|
+
value: nextSelected
|
|
519
|
+
};
|
|
520
|
+
return nextSelected;
|
|
521
|
+
};
|
|
522
|
+
const selectedValue = (0, _react.useSyncExternalStore)(item.subscribe, getSelectedSnapshot, getSelectedSnapshot);
|
|
523
|
+
return [selectedValue, item.set];
|
|
524
|
+
}
|
|
225
525
|
function useSetStorage(item) {
|
|
226
526
|
return item.set;
|
|
227
527
|
}
|
|
228
528
|
function getBatch(items, scope) {
|
|
229
|
-
|
|
529
|
+
(0, _internal.assertBatchScope)(items, scope);
|
|
530
|
+
if (scope === _Storage.StorageScope.Memory) {
|
|
230
531
|
return items.map(item => item.get());
|
|
231
532
|
}
|
|
232
|
-
const
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
533
|
+
const useRawBatchPath = items.every(item => canUseRawBatchPath(item));
|
|
534
|
+
if (!useRawBatchPath) {
|
|
535
|
+
return items.map(item => item.get());
|
|
536
|
+
}
|
|
537
|
+
const useBatchCache = items.every(item => item._readCacheEnabled === true);
|
|
538
|
+
const rawValues = new Array(items.length);
|
|
539
|
+
const keysToFetch = [];
|
|
540
|
+
const keyIndexes = [];
|
|
541
|
+
items.forEach((item, index) => {
|
|
542
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
543
|
+
if (hasPendingSecureWrite(item.key)) {
|
|
544
|
+
rawValues[index] = readPendingSecureWrite(item.key);
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
if (useBatchCache) {
|
|
549
|
+
if (hasCachedRawValue(scope, item.key)) {
|
|
550
|
+
rawValues[index] = readCachedRawValue(scope, item.key);
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
keysToFetch.push(item.key);
|
|
555
|
+
keyIndexes.push(index);
|
|
556
|
+
});
|
|
557
|
+
if (keysToFetch.length > 0) {
|
|
558
|
+
const fetchedValues = WebStorage.getBatch(keysToFetch, scope);
|
|
559
|
+
fetchedValues.forEach((value, index) => {
|
|
560
|
+
const key = keysToFetch[index];
|
|
561
|
+
const targetIndex = keyIndexes[index];
|
|
562
|
+
rawValues[targetIndex] = value;
|
|
563
|
+
cacheRawValue(scope, key, value);
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
return items.map((item, index) => {
|
|
567
|
+
const raw = rawValues[index];
|
|
236
568
|
if (raw === undefined) {
|
|
237
569
|
return item.get();
|
|
238
570
|
}
|
|
@@ -240,29 +572,125 @@ function getBatch(items, scope) {
|
|
|
240
572
|
});
|
|
241
573
|
}
|
|
242
574
|
function setBatch(items, scope) {
|
|
243
|
-
|
|
575
|
+
(0, _internal.assertBatchScope)(items.map(batchEntry => batchEntry.item), scope);
|
|
576
|
+
if (scope === _Storage.StorageScope.Memory) {
|
|
244
577
|
items.forEach(({
|
|
245
578
|
item,
|
|
246
579
|
value
|
|
247
580
|
}) => item.set(value));
|
|
248
581
|
return;
|
|
249
582
|
}
|
|
250
|
-
const
|
|
251
|
-
const values = items.map(i => i.item.serialize(i.value));
|
|
252
|
-
WebStorage.setBatch(keys, values, scope);
|
|
253
|
-
items.forEach(({
|
|
583
|
+
const useRawBatchPath = items.every(({
|
|
254
584
|
item
|
|
255
|
-
}) =>
|
|
256
|
-
|
|
257
|
-
|
|
585
|
+
}) => canUseRawBatchPath(item));
|
|
586
|
+
if (!useRawBatchPath) {
|
|
587
|
+
items.forEach(({
|
|
588
|
+
item,
|
|
589
|
+
value
|
|
590
|
+
}) => item.set(value));
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
const keys = items.map(entry => entry.item.key);
|
|
594
|
+
const values = items.map(entry => entry.item.serialize(entry.value));
|
|
595
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
596
|
+
flushSecureWrites();
|
|
597
|
+
}
|
|
598
|
+
WebStorage.setBatch(keys, values, scope);
|
|
599
|
+
keys.forEach((key, index) => cacheRawValue(scope, key, values[index]));
|
|
258
600
|
}
|
|
259
601
|
function removeBatch(items, scope) {
|
|
260
|
-
|
|
602
|
+
(0, _internal.assertBatchScope)(items, scope);
|
|
603
|
+
if (scope === _Storage.StorageScope.Memory) {
|
|
261
604
|
items.forEach(item => item.delete());
|
|
262
605
|
return;
|
|
263
606
|
}
|
|
264
607
|
const keys = items.map(item => item.key);
|
|
608
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
609
|
+
flushSecureWrites();
|
|
610
|
+
}
|
|
265
611
|
WebStorage.removeBatch(keys, scope);
|
|
266
|
-
|
|
612
|
+
keys.forEach(key => cacheRawValue(scope, key, undefined));
|
|
613
|
+
}
|
|
614
|
+
function registerMigration(version, migration) {
|
|
615
|
+
if (!Number.isInteger(version) || version <= 0) {
|
|
616
|
+
throw new Error("Migration version must be a positive integer.");
|
|
617
|
+
}
|
|
618
|
+
if (registeredMigrations.has(version)) {
|
|
619
|
+
throw new Error(`Migration version ${version} is already registered.`);
|
|
620
|
+
}
|
|
621
|
+
registeredMigrations.set(version, migration);
|
|
622
|
+
}
|
|
623
|
+
function migrateToLatest(scope = _Storage.StorageScope.Disk) {
|
|
624
|
+
(0, _internal.assertValidScope)(scope);
|
|
625
|
+
const currentVersion = readMigrationVersion(scope);
|
|
626
|
+
const versions = Array.from(registeredMigrations.keys()).filter(version => version > currentVersion).sort((a, b) => a - b);
|
|
627
|
+
let appliedVersion = currentVersion;
|
|
628
|
+
const context = {
|
|
629
|
+
scope,
|
|
630
|
+
getRaw: key => getRawValue(key, scope),
|
|
631
|
+
setRaw: (key, value) => setRawValue(key, value, scope),
|
|
632
|
+
removeRaw: key => removeRawValue(key, scope)
|
|
633
|
+
};
|
|
634
|
+
versions.forEach(version => {
|
|
635
|
+
const migration = registeredMigrations.get(version);
|
|
636
|
+
if (!migration) {
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
migration(context);
|
|
640
|
+
writeMigrationVersion(scope, version);
|
|
641
|
+
appliedVersion = version;
|
|
642
|
+
});
|
|
643
|
+
return appliedVersion;
|
|
644
|
+
}
|
|
645
|
+
function runTransaction(scope, transaction) {
|
|
646
|
+
(0, _internal.assertValidScope)(scope);
|
|
647
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
648
|
+
flushSecureWrites();
|
|
649
|
+
}
|
|
650
|
+
const rollback = new Map();
|
|
651
|
+
const rememberRollback = key => {
|
|
652
|
+
if (rollback.has(key)) {
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
rollback.set(key, getRawValue(key, scope));
|
|
656
|
+
};
|
|
657
|
+
const tx = {
|
|
658
|
+
scope,
|
|
659
|
+
getRaw: key => getRawValue(key, scope),
|
|
660
|
+
setRaw: (key, value) => {
|
|
661
|
+
rememberRollback(key);
|
|
662
|
+
setRawValue(key, value, scope);
|
|
663
|
+
},
|
|
664
|
+
removeRaw: key => {
|
|
665
|
+
rememberRollback(key);
|
|
666
|
+
removeRawValue(key, scope);
|
|
667
|
+
},
|
|
668
|
+
getItem: item => {
|
|
669
|
+
(0, _internal.assertBatchScope)([item], scope);
|
|
670
|
+
return item.get();
|
|
671
|
+
},
|
|
672
|
+
setItem: (item, value) => {
|
|
673
|
+
(0, _internal.assertBatchScope)([item], scope);
|
|
674
|
+
rememberRollback(item.key);
|
|
675
|
+
item.set(value);
|
|
676
|
+
},
|
|
677
|
+
removeItem: item => {
|
|
678
|
+
(0, _internal.assertBatchScope)([item], scope);
|
|
679
|
+
rememberRollback(item.key);
|
|
680
|
+
item.delete();
|
|
681
|
+
}
|
|
682
|
+
};
|
|
683
|
+
try {
|
|
684
|
+
return transaction(tx);
|
|
685
|
+
} catch (error) {
|
|
686
|
+
Array.from(rollback.entries()).reverse().forEach(([key, previousValue]) => {
|
|
687
|
+
if (previousValue === undefined) {
|
|
688
|
+
removeRawValue(key, scope);
|
|
689
|
+
} else {
|
|
690
|
+
setRawValue(key, previousValue, scope);
|
|
691
|
+
}
|
|
692
|
+
});
|
|
693
|
+
throw error;
|
|
694
|
+
}
|
|
267
695
|
}
|
|
268
696
|
//# sourceMappingURL=index.web.js.map
|