@flighthq/storage 0.1.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/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/storage.d.ts +41 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +470 -0
- package/dist/storage.js.map +1 -0
- package/package.json +38 -0
- package/src/storage.test.ts +761 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { StorageBackend, StorageMigration, StorageNamespace, StorageQuota, StorageSignals } from '@flighthq/types';
|
|
2
|
+
export declare function clearStorage(): boolean;
|
|
3
|
+
export declare function clearStorageNamespace(namespace: Readonly<StorageNamespace>): boolean;
|
|
4
|
+
export declare function createStorageNamespace(prefix: string): StorageNamespace;
|
|
5
|
+
export declare function createWebStorageBackend(): StorageBackend;
|
|
6
|
+
export declare function disableStorageSignals(): void;
|
|
7
|
+
export declare function enableStorageSignals(): StorageSignals;
|
|
8
|
+
export declare function getNamespacedStorageByteSize(namespace: Readonly<StorageNamespace>): number;
|
|
9
|
+
export declare function getNamespacedStorageEntries(namespace: Readonly<StorageNamespace>): readonly (readonly [string, string])[];
|
|
10
|
+
export declare function getNamespacedStorageItem(namespace: Readonly<StorageNamespace>, key: string): string | null;
|
|
11
|
+
export declare function getNamespacedStorageKeys(namespace: Readonly<StorageNamespace>): string[];
|
|
12
|
+
export declare function getStorageBackend(): StorageBackend;
|
|
13
|
+
export declare function getStorageBoolean(key: string): boolean | null;
|
|
14
|
+
export declare function getStorageBooleanOr(key: string, fallback: boolean): boolean;
|
|
15
|
+
export declare function getStorageByteSize(): number;
|
|
16
|
+
export declare function getStorageEntries(): readonly (readonly [string, string])[];
|
|
17
|
+
export declare function getStorageItem(key: string): string | null;
|
|
18
|
+
export declare function getStorageItemCount(): number;
|
|
19
|
+
export declare function getStorageItemOr(key: string, fallback: string): string;
|
|
20
|
+
export declare function getStorageItems(keys: readonly string[]): readonly (string | null)[];
|
|
21
|
+
export declare function getStorageJSON<T>(key: string): T | null;
|
|
22
|
+
export declare function getStorageJSONOr<T>(key: string, fallback: T): T;
|
|
23
|
+
export declare function getStorageKeys(): string[];
|
|
24
|
+
export declare function getStorageNumber(key: string): number | null;
|
|
25
|
+
export declare function getStorageNumberOr(key: string, fallback: number): number;
|
|
26
|
+
export declare function getStorageQuotaEstimate(): Promise<StorageQuota | null>;
|
|
27
|
+
export declare function getStorageSignals(): StorageSignals | null;
|
|
28
|
+
export declare function hasNamespacedStorageItem(namespace: Readonly<StorageNamespace>, key: string): boolean;
|
|
29
|
+
export declare function hasStorageItem(key: string): boolean;
|
|
30
|
+
export declare function migrateStorage(namespace: Readonly<StorageNamespace> | null, migrations: readonly Readonly<StorageMigration>[]): number;
|
|
31
|
+
export declare function removeNamespacedStorageItem(namespace: Readonly<StorageNamespace>, key: string): boolean;
|
|
32
|
+
export declare function removeStorageItem(key: string): boolean;
|
|
33
|
+
export declare function removeStorageItems(keys: readonly string[]): boolean;
|
|
34
|
+
export declare function setNamespacedStorageItem(namespace: Readonly<StorageNamespace>, key: string, value: string): boolean;
|
|
35
|
+
export declare function setStorageBackend(backend: StorageBackend | null): void;
|
|
36
|
+
export declare function setStorageBoolean(key: string, value: boolean): boolean;
|
|
37
|
+
export declare function setStorageItem(key: string, value: string): boolean;
|
|
38
|
+
export declare function setStorageItems(record: Readonly<Record<string, string>>): boolean;
|
|
39
|
+
export declare function setStorageJSON<T>(key: string, value: T): boolean;
|
|
40
|
+
export declare function setStorageNumber(key: string, value: number): boolean;
|
|
41
|
+
//# sourceMappingURL=storage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"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"}
|
package/dist/storage.js
ADDED
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
import { clearSignal, createSignal, emitSignal } from '@flighthq/signals';
|
|
2
|
+
// Removes every key. Returns false when the host denies access. Sentinel, not throw.
|
|
3
|
+
export function clearStorage() {
|
|
4
|
+
const result = getStorageBackend().clear();
|
|
5
|
+
if (_signalsActive && result) {
|
|
6
|
+
_emitStorageChange({ key: null, oldValue: null, newValue: null });
|
|
7
|
+
}
|
|
8
|
+
return result;
|
|
9
|
+
}
|
|
10
|
+
// Removes all keys under the namespace without touching other keys. Returns false on denial.
|
|
11
|
+
export function clearStorageNamespace(namespace) {
|
|
12
|
+
const prefix = namespace.prefix + '.';
|
|
13
|
+
const backend = getStorageBackend();
|
|
14
|
+
let success = true;
|
|
15
|
+
for (const key of backend.keys()) {
|
|
16
|
+
if (!key.startsWith(prefix))
|
|
17
|
+
continue;
|
|
18
|
+
if (!backend.removeItem(key))
|
|
19
|
+
success = false;
|
|
20
|
+
}
|
|
21
|
+
return success;
|
|
22
|
+
}
|
|
23
|
+
// Creates a prefix-scoped view into the keyspace. Keys under the namespace are stored as
|
|
24
|
+
// `prefix + '.' + key` and never collide with the global keyspace or other namespaces.
|
|
25
|
+
export function createStorageNamespace(prefix) {
|
|
26
|
+
return { prefix };
|
|
27
|
+
}
|
|
28
|
+
// Builds the default web backend over window.localStorage. Every call is guarded with try/catch:
|
|
29
|
+
// localStorage access can throw in private mode or when storage is disabled. Writes return false and
|
|
30
|
+
// reads return null / [] on failure rather than throwing — storage is an expected-failure surface.
|
|
31
|
+
export function createWebStorageBackend() {
|
|
32
|
+
return {
|
|
33
|
+
getItem(key) {
|
|
34
|
+
const ls = getWebStorage();
|
|
35
|
+
if (ls === null)
|
|
36
|
+
return null;
|
|
37
|
+
try {
|
|
38
|
+
return ls.getItem(key);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
setItem(key, value) {
|
|
45
|
+
const ls = getWebStorage();
|
|
46
|
+
if (ls === null)
|
|
47
|
+
return false;
|
|
48
|
+
try {
|
|
49
|
+
ls.setItem(key, value);
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
removeItem(key) {
|
|
57
|
+
const ls = getWebStorage();
|
|
58
|
+
if (ls === null)
|
|
59
|
+
return false;
|
|
60
|
+
try {
|
|
61
|
+
ls.removeItem(key);
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
clear() {
|
|
69
|
+
const ls = getWebStorage();
|
|
70
|
+
if (ls === null)
|
|
71
|
+
return false;
|
|
72
|
+
try {
|
|
73
|
+
ls.clear();
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
keys() {
|
|
81
|
+
const ls = getWebStorage();
|
|
82
|
+
if (ls === null)
|
|
83
|
+
return [];
|
|
84
|
+
try {
|
|
85
|
+
const out = [];
|
|
86
|
+
for (let i = 0; i < ls.length; i += 1) {
|
|
87
|
+
const key = ls.key(i);
|
|
88
|
+
if (key !== null)
|
|
89
|
+
out.push(key);
|
|
90
|
+
}
|
|
91
|
+
return out;
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return [];
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
subscribeChanges(listener) {
|
|
98
|
+
if (typeof window === 'undefined')
|
|
99
|
+
return () => { };
|
|
100
|
+
const handler = (event) => {
|
|
101
|
+
listener({ key: event.key, oldValue: event.oldValue, newValue: event.newValue });
|
|
102
|
+
};
|
|
103
|
+
window.addEventListener('storage', handler);
|
|
104
|
+
return () => window.removeEventListener('storage', handler);
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
// Stops the storage change-notification group. Removes the DOM storage listener and disconnects
|
|
109
|
+
// all listeners. Pair with enableStorageSignals.
|
|
110
|
+
export function disableStorageSignals() {
|
|
111
|
+
if (!_signalsActive)
|
|
112
|
+
return;
|
|
113
|
+
_signalsActive = false;
|
|
114
|
+
if (_crossTabUnsubscribe !== null) {
|
|
115
|
+
_crossTabUnsubscribe();
|
|
116
|
+
_crossTabUnsubscribe = null;
|
|
117
|
+
}
|
|
118
|
+
if (_signals !== null) {
|
|
119
|
+
clearSignal(_signals.onChange);
|
|
120
|
+
_signals = null;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// Starts the storage change-notification group, wiring cross-tab DOM storage events and enabling
|
|
124
|
+
// same-tab emission for write operations. Idempotent: calling again while already enabled returns
|
|
125
|
+
// the existing signals object. Pair with disableStorageSignals.
|
|
126
|
+
export function enableStorageSignals() {
|
|
127
|
+
if (_signalsActive && _signals !== null)
|
|
128
|
+
return _signals;
|
|
129
|
+
_signals = { onChange: createSignal() };
|
|
130
|
+
_signalsActive = true;
|
|
131
|
+
const backend = getStorageBackend();
|
|
132
|
+
if (backend.subscribeChanges !== undefined) {
|
|
133
|
+
_crossTabUnsubscribe = backend.subscribeChanges((change) => {
|
|
134
|
+
_emitStorageChange(change);
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
return _signals;
|
|
138
|
+
}
|
|
139
|
+
// Returns the estimated UTF-16 byte cost of all keys under the namespace. Returns -1 on denial.
|
|
140
|
+
export function getNamespacedStorageByteSize(namespace) {
|
|
141
|
+
const prefix = namespace.prefix + '.';
|
|
142
|
+
const backend = getStorageBackend();
|
|
143
|
+
const keys = backend.keys();
|
|
144
|
+
if (keys.length === 0)
|
|
145
|
+
return 0;
|
|
146
|
+
let total = 0;
|
|
147
|
+
for (const rawKey of keys) {
|
|
148
|
+
if (!rawKey.startsWith(prefix))
|
|
149
|
+
continue;
|
|
150
|
+
const value = backend.getItem(rawKey);
|
|
151
|
+
if (value === null)
|
|
152
|
+
continue;
|
|
153
|
+
total += (rawKey.length + value.length) * 2;
|
|
154
|
+
}
|
|
155
|
+
return total;
|
|
156
|
+
}
|
|
157
|
+
// Returns all key/value pairs under the given namespace prefix. Keys returned are unprefixed.
|
|
158
|
+
// Returns [] on denial.
|
|
159
|
+
export function getNamespacedStorageEntries(namespace) {
|
|
160
|
+
const prefix = namespace.prefix + '.';
|
|
161
|
+
const backend = getStorageBackend();
|
|
162
|
+
const keys = backend.keys();
|
|
163
|
+
const out = [];
|
|
164
|
+
for (const rawKey of keys) {
|
|
165
|
+
if (!rawKey.startsWith(prefix))
|
|
166
|
+
continue;
|
|
167
|
+
const key = rawKey.slice(prefix.length);
|
|
168
|
+
const value = backend.getItem(rawKey);
|
|
169
|
+
if (value !== null)
|
|
170
|
+
out.push([key, value]);
|
|
171
|
+
}
|
|
172
|
+
return out;
|
|
173
|
+
}
|
|
174
|
+
// Reads a value from the namespace. Returns null on absent key or access denial.
|
|
175
|
+
export function getNamespacedStorageItem(namespace, key) {
|
|
176
|
+
return getStorageBackend().getItem(_namespacedKey(namespace, key));
|
|
177
|
+
}
|
|
178
|
+
// Returns every key stored under the namespace (unprefixed). Returns [] on denial.
|
|
179
|
+
export function getNamespacedStorageKeys(namespace) {
|
|
180
|
+
const prefix = namespace.prefix + '.';
|
|
181
|
+
const out = [];
|
|
182
|
+
for (const rawKey of getStorageBackend().keys()) {
|
|
183
|
+
if (rawKey.startsWith(prefix))
|
|
184
|
+
out.push(rawKey.slice(prefix.length));
|
|
185
|
+
}
|
|
186
|
+
return out;
|
|
187
|
+
}
|
|
188
|
+
// The active storage backend, or a lazily-created web default. There is always a backend.
|
|
189
|
+
export function getStorageBackend() {
|
|
190
|
+
if (_backend === null)
|
|
191
|
+
_backend = createWebStorageBackend();
|
|
192
|
+
return _backend;
|
|
193
|
+
}
|
|
194
|
+
// Reads the stored value as a stored boolean ('true'/'false'). Returns null on absent key,
|
|
195
|
+
// access denial, or unrecognized value.
|
|
196
|
+
export function getStorageBoolean(key) {
|
|
197
|
+
const raw = getStorageBackend().getItem(key);
|
|
198
|
+
if (raw === null)
|
|
199
|
+
return null;
|
|
200
|
+
if (raw === 'true')
|
|
201
|
+
return true;
|
|
202
|
+
if (raw === 'false')
|
|
203
|
+
return false;
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
// Returns the stored boolean, or `fallback` on absent key, access denial, or unrecognized value.
|
|
207
|
+
export function getStorageBooleanOr(key, fallback) {
|
|
208
|
+
return getStorageBoolean(key) ?? fallback;
|
|
209
|
+
}
|
|
210
|
+
// Returns the estimated UTF-16 byte cost of the entire store. Delegates to the backend's
|
|
211
|
+
// byteSize() when available; otherwise enumerates entries. Returns -1 on denial.
|
|
212
|
+
export function getStorageByteSize() {
|
|
213
|
+
const backend = getStorageBackend();
|
|
214
|
+
if (backend.byteSize !== undefined)
|
|
215
|
+
return backend.byteSize();
|
|
216
|
+
const keys = backend.keys();
|
|
217
|
+
if (keys.length === 0)
|
|
218
|
+
return 0;
|
|
219
|
+
let total = 0;
|
|
220
|
+
for (const key of keys) {
|
|
221
|
+
const value = backend.getItem(key);
|
|
222
|
+
if (value === null)
|
|
223
|
+
continue;
|
|
224
|
+
// localStorage stores strings as UTF-16: each code unit is 2 bytes.
|
|
225
|
+
total += (key.length + value.length) * 2;
|
|
226
|
+
}
|
|
227
|
+
return total;
|
|
228
|
+
}
|
|
229
|
+
// Returns all key/value pairs in one pass. Skips keys whose value is null (concurrent removal).
|
|
230
|
+
// Returns [] on denial.
|
|
231
|
+
export function getStorageEntries() {
|
|
232
|
+
const backend = getStorageBackend();
|
|
233
|
+
const keys = backend.keys();
|
|
234
|
+
const out = [];
|
|
235
|
+
for (const key of keys) {
|
|
236
|
+
const value = backend.getItem(key);
|
|
237
|
+
if (value !== null)
|
|
238
|
+
out.push([key, value]);
|
|
239
|
+
}
|
|
240
|
+
return out;
|
|
241
|
+
}
|
|
242
|
+
// Reads a stored value, or null when the key is absent or access is denied.
|
|
243
|
+
export function getStorageItem(key) {
|
|
244
|
+
return getStorageBackend().getItem(key);
|
|
245
|
+
}
|
|
246
|
+
// Returns the number of stored keys. Returns 0 on denial.
|
|
247
|
+
export function getStorageItemCount() {
|
|
248
|
+
return getStorageBackend().keys().length;
|
|
249
|
+
}
|
|
250
|
+
// Returns the stored value, or `fallback` when the key is absent or access is denied.
|
|
251
|
+
export function getStorageItemOr(key, fallback) {
|
|
252
|
+
return getStorageBackend().getItem(key) ?? fallback;
|
|
253
|
+
}
|
|
254
|
+
// Reads multiple keys in one call. Returns a parallel-indexed array with null for absent/denied
|
|
255
|
+
// keys. Returns [] when keys is empty.
|
|
256
|
+
export function getStorageItems(keys) {
|
|
257
|
+
const backend = getStorageBackend();
|
|
258
|
+
const out = new Array(keys.length);
|
|
259
|
+
for (let i = 0; i < keys.length; i += 1) {
|
|
260
|
+
out[i] = backend.getItem(keys[i]);
|
|
261
|
+
}
|
|
262
|
+
return out;
|
|
263
|
+
}
|
|
264
|
+
// Reads and JSON.parses a stored value. Returns null on absent key, parse failure, or access
|
|
265
|
+
// denial — corrupt stored data is an expected-failure surface; do not throw.
|
|
266
|
+
export function getStorageJSON(key) {
|
|
267
|
+
const raw = getStorageBackend().getItem(key);
|
|
268
|
+
if (raw === null)
|
|
269
|
+
return null;
|
|
270
|
+
try {
|
|
271
|
+
return JSON.parse(raw);
|
|
272
|
+
}
|
|
273
|
+
catch {
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
// Returns the parsed stored value, or `fallback` on absent key, parse failure, or access denial.
|
|
278
|
+
export function getStorageJSONOr(key, fallback) {
|
|
279
|
+
const raw = getStorageBackend().getItem(key);
|
|
280
|
+
if (raw === null)
|
|
281
|
+
return fallback;
|
|
282
|
+
try {
|
|
283
|
+
return JSON.parse(raw);
|
|
284
|
+
}
|
|
285
|
+
catch {
|
|
286
|
+
return fallback;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
// Returns every stored key, or [] when access is denied.
|
|
290
|
+
export function getStorageKeys() {
|
|
291
|
+
return getStorageBackend().keys();
|
|
292
|
+
}
|
|
293
|
+
// Reads the stored value as a number. Returns null on absent key, access denial, or parse failure
|
|
294
|
+
// (NaN is treated as parse failure).
|
|
295
|
+
export function getStorageNumber(key) {
|
|
296
|
+
const raw = getStorageBackend().getItem(key);
|
|
297
|
+
if (raw === null)
|
|
298
|
+
return null;
|
|
299
|
+
const n = Number(raw);
|
|
300
|
+
return Number.isNaN(n) ? null : n;
|
|
301
|
+
}
|
|
302
|
+
// Returns the stored number, or `fallback` on absent key, access denial, or parse failure.
|
|
303
|
+
export function getStorageNumberOr(key, fallback) {
|
|
304
|
+
return getStorageNumber(key) ?? fallback;
|
|
305
|
+
}
|
|
306
|
+
// Returns the estimated storage quota from the web backend (navigator.storage.estimate), or null
|
|
307
|
+
// when unavailable. This is async on the browser; the result is best-effort and may be stale.
|
|
308
|
+
// Returns null when the API is absent or returns a denial.
|
|
309
|
+
export async function getStorageQuotaEstimate() {
|
|
310
|
+
if (typeof navigator === 'undefined')
|
|
311
|
+
return null;
|
|
312
|
+
const storage = navigator.storage;
|
|
313
|
+
if (storage?.estimate === undefined)
|
|
314
|
+
return null;
|
|
315
|
+
try {
|
|
316
|
+
const estimate = await storage.estimate();
|
|
317
|
+
const used = typeof estimate.usage === 'number' ? estimate.usage : -1;
|
|
318
|
+
const available = typeof estimate.quota === 'number' && estimate.quota >= 0 ? estimate.quota - (used >= 0 ? used : 0) : -1;
|
|
319
|
+
return { used, available };
|
|
320
|
+
}
|
|
321
|
+
catch {
|
|
322
|
+
return null;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
// Returns the active StorageSignals, or null when signals have not been enabled.
|
|
326
|
+
export function getStorageSignals() {
|
|
327
|
+
return _signals;
|
|
328
|
+
}
|
|
329
|
+
// Returns true when the key exists in the namespace.
|
|
330
|
+
export function hasNamespacedStorageItem(namespace, key) {
|
|
331
|
+
return getStorageBackend().getItem(_namespacedKey(namespace, key)) !== null;
|
|
332
|
+
}
|
|
333
|
+
// Returns true when the key exists in the store.
|
|
334
|
+
export function hasStorageItem(key) {
|
|
335
|
+
return getStorageBackend().getItem(key) !== null;
|
|
336
|
+
}
|
|
337
|
+
// Applies a sequence of versioned migrations to the given namespace (or global keyspace when null).
|
|
338
|
+
// Migrations are run in ascending version order, starting from the stored version + 1. Stores the
|
|
339
|
+
// resulting version under the reserved key `__flight_storage_version` (namespaced or global).
|
|
340
|
+
// Returns the new version number, or -1 on failure.
|
|
341
|
+
export function migrateStorage(namespace, migrations) {
|
|
342
|
+
const versionKey = '__flight_storage_version';
|
|
343
|
+
const raw = namespace !== null ? getNamespacedStorageItem(namespace, versionKey) : getStorageItem(versionKey);
|
|
344
|
+
const currentVersion = raw !== null ? parseInt(raw, 10) : 0;
|
|
345
|
+
const sorted = [...migrations].sort((a, b) => a.version - b.version);
|
|
346
|
+
let newVersion = currentVersion;
|
|
347
|
+
for (const migration of sorted) {
|
|
348
|
+
if (migration.version <= currentVersion)
|
|
349
|
+
continue;
|
|
350
|
+
try {
|
|
351
|
+
migration.migrate(namespace?.prefix ?? null);
|
|
352
|
+
}
|
|
353
|
+
catch {
|
|
354
|
+
return -1;
|
|
355
|
+
}
|
|
356
|
+
newVersion = migration.version;
|
|
357
|
+
}
|
|
358
|
+
if (newVersion !== currentVersion) {
|
|
359
|
+
const stored = namespace !== null
|
|
360
|
+
? setNamespacedStorageItem(namespace, versionKey, String(newVersion))
|
|
361
|
+
: setStorageItem(versionKey, String(newVersion));
|
|
362
|
+
if (!stored)
|
|
363
|
+
return -1;
|
|
364
|
+
}
|
|
365
|
+
return newVersion;
|
|
366
|
+
}
|
|
367
|
+
// Removes a key from the namespace. Returns false on denial.
|
|
368
|
+
export function removeNamespacedStorageItem(namespace, key) {
|
|
369
|
+
return getStorageBackend().removeItem(_namespacedKey(namespace, key));
|
|
370
|
+
}
|
|
371
|
+
// Removes one key. Returns false when the host denies access.
|
|
372
|
+
export function removeStorageItem(key) {
|
|
373
|
+
const oldValue = _signalsActive ? getStorageBackend().getItem(key) : null;
|
|
374
|
+
const result = getStorageBackend().removeItem(key);
|
|
375
|
+
if (_signalsActive && result) {
|
|
376
|
+
_emitStorageChange({ key, oldValue, newValue: null });
|
|
377
|
+
}
|
|
378
|
+
return result;
|
|
379
|
+
}
|
|
380
|
+
// Removes multiple keys. Returns false if any removal fails; partial removals are possible.
|
|
381
|
+
export function removeStorageItems(keys) {
|
|
382
|
+
const backend = getStorageBackend();
|
|
383
|
+
let success = true;
|
|
384
|
+
for (const key of keys) {
|
|
385
|
+
if (!backend.removeItem(key))
|
|
386
|
+
success = false;
|
|
387
|
+
}
|
|
388
|
+
return success;
|
|
389
|
+
}
|
|
390
|
+
// Writes a namespaced value. Returns false on denial/quota.
|
|
391
|
+
export function setNamespacedStorageItem(namespace, key, value) {
|
|
392
|
+
return getStorageBackend().setItem(_namespacedKey(namespace, key), value);
|
|
393
|
+
}
|
|
394
|
+
// Installs a native host storage backend; pass null to fall back to the web default.
|
|
395
|
+
export function setStorageBackend(backend) {
|
|
396
|
+
if (_signalsActive && _crossTabUnsubscribe !== null) {
|
|
397
|
+
_crossTabUnsubscribe();
|
|
398
|
+
_crossTabUnsubscribe = null;
|
|
399
|
+
}
|
|
400
|
+
_backend = backend;
|
|
401
|
+
if (_signalsActive) {
|
|
402
|
+
const b = getStorageBackend();
|
|
403
|
+
if (b.subscribeChanges !== undefined) {
|
|
404
|
+
_crossTabUnsubscribe = b.subscribeChanges((change) => {
|
|
405
|
+
_emitStorageChange(change);
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
// Writes a boolean as 'true' or 'false'. Returns false on denial/quota.
|
|
411
|
+
export function setStorageBoolean(key, value) {
|
|
412
|
+
return setStorageItem(key, value ? 'true' : 'false');
|
|
413
|
+
}
|
|
414
|
+
// Writes a value. Returns false when the host denies access (private mode, quota exceeded).
|
|
415
|
+
export function setStorageItem(key, value) {
|
|
416
|
+
const oldValue = _signalsActive ? getStorageBackend().getItem(key) : null;
|
|
417
|
+
const result = getStorageBackend().setItem(key, value);
|
|
418
|
+
if (_signalsActive && result) {
|
|
419
|
+
_emitStorageChange({ key, oldValue, newValue: value });
|
|
420
|
+
}
|
|
421
|
+
return result;
|
|
422
|
+
}
|
|
423
|
+
// Writes multiple key/value pairs. Returns false if any write fails; partial writes are possible.
|
|
424
|
+
export function setStorageItems(record) {
|
|
425
|
+
const backend = getStorageBackend();
|
|
426
|
+
let success = true;
|
|
427
|
+
for (const key of Object.keys(record)) {
|
|
428
|
+
if (!backend.setItem(key, record[key]))
|
|
429
|
+
success = false;
|
|
430
|
+
}
|
|
431
|
+
return success;
|
|
432
|
+
}
|
|
433
|
+
// JSON.stringifies and stores a value. Returns false on denial/quota or if stringify fails
|
|
434
|
+
// (cyclic value). Corrupt data is an expected-failure surface; do not throw.
|
|
435
|
+
export function setStorageJSON(key, value) {
|
|
436
|
+
let raw;
|
|
437
|
+
try {
|
|
438
|
+
raw = JSON.stringify(value);
|
|
439
|
+
}
|
|
440
|
+
catch {
|
|
441
|
+
return false;
|
|
442
|
+
}
|
|
443
|
+
return setStorageItem(key, raw);
|
|
444
|
+
}
|
|
445
|
+
// Writes a number. Returns false on denial/quota.
|
|
446
|
+
export function setStorageNumber(key, value) {
|
|
447
|
+
return setStorageItem(key, String(value));
|
|
448
|
+
}
|
|
449
|
+
let _backend = null;
|
|
450
|
+
let _signals = null;
|
|
451
|
+
let _signalsActive = false;
|
|
452
|
+
let _crossTabUnsubscribe = null;
|
|
453
|
+
function _emitStorageChange(change) {
|
|
454
|
+
if (_signals !== null)
|
|
455
|
+
emitSignal(_signals.onChange, change);
|
|
456
|
+
}
|
|
457
|
+
function _namespacedKey(namespace, key) {
|
|
458
|
+
return namespace.prefix + '.' + key;
|
|
459
|
+
}
|
|
460
|
+
function getWebStorage() {
|
|
461
|
+
if (typeof window === 'undefined')
|
|
462
|
+
return null;
|
|
463
|
+
try {
|
|
464
|
+
return window.localStorage ?? null;
|
|
465
|
+
}
|
|
466
|
+
catch {
|
|
467
|
+
return null;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
//# sourceMappingURL=storage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.js","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@flighthq/storage",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"default": "./dist/index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"src/**/*.test.ts",
|
|
16
|
+
"!dist/**/*.test.js",
|
|
17
|
+
"!dist/**/*.test.d.ts",
|
|
18
|
+
"!dist/**/*.test.js.map",
|
|
19
|
+
"!dist/**/*.test.d.ts.map"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsc -b",
|
|
23
|
+
"clean": "tsc -b --clean",
|
|
24
|
+
"test": "vitest run --config vitest.config.ts",
|
|
25
|
+
"test:watch": "vitest --watch --config vitest.config.ts",
|
|
26
|
+
"prepack": "npm run clean && npm run clean:dist && npm run build",
|
|
27
|
+
"clean:dist": "tsx ../../scripts/clean-package-dist.ts"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@flighthq/signals": "0.1.0",
|
|
31
|
+
"@flighthq/types": "0.1.0"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"typescript": "^5.3.0"
|
|
35
|
+
},
|
|
36
|
+
"description": "Synchronous key/value persistence over a swappable web/native backend (localStorage by default)",
|
|
37
|
+
"sideEffects": false
|
|
38
|
+
}
|
|
@@ -0,0 +1,761 @@
|
|
|
1
|
+
import type { StorageBackend, StorageNamespace } from '@flighthq/types';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
clearStorage,
|
|
5
|
+
clearStorageNamespace,
|
|
6
|
+
createStorageNamespace,
|
|
7
|
+
createWebStorageBackend,
|
|
8
|
+
disableStorageSignals,
|
|
9
|
+
enableStorageSignals,
|
|
10
|
+
getNamespacedStorageByteSize,
|
|
11
|
+
getNamespacedStorageEntries,
|
|
12
|
+
getNamespacedStorageItem,
|
|
13
|
+
getNamespacedStorageKeys,
|
|
14
|
+
getStorageBackend,
|
|
15
|
+
getStorageBoolean,
|
|
16
|
+
getStorageBooleanOr,
|
|
17
|
+
getStorageByteSize,
|
|
18
|
+
getStorageEntries,
|
|
19
|
+
getStorageItem,
|
|
20
|
+
getStorageItemCount,
|
|
21
|
+
getStorageItemOr,
|
|
22
|
+
getStorageItems,
|
|
23
|
+
getStorageJSON,
|
|
24
|
+
getStorageJSONOr,
|
|
25
|
+
getStorageKeys,
|
|
26
|
+
getStorageNumber,
|
|
27
|
+
getStorageNumberOr,
|
|
28
|
+
getStorageQuotaEstimate,
|
|
29
|
+
getStorageSignals,
|
|
30
|
+
hasNamespacedStorageItem,
|
|
31
|
+
hasStorageItem,
|
|
32
|
+
migrateStorage,
|
|
33
|
+
removeNamespacedStorageItem,
|
|
34
|
+
removeStorageItem,
|
|
35
|
+
removeStorageItems,
|
|
36
|
+
setNamespacedStorageItem,
|
|
37
|
+
setStorageBackend,
|
|
38
|
+
setStorageBoolean,
|
|
39
|
+
setStorageItem,
|
|
40
|
+
setStorageItems,
|
|
41
|
+
setStorageJSON,
|
|
42
|
+
setStorageNumber,
|
|
43
|
+
} from './storage';
|
|
44
|
+
|
|
45
|
+
function fakeBackend(): StorageBackend {
|
|
46
|
+
const map = new Map<string, string>();
|
|
47
|
+
return {
|
|
48
|
+
getItem(key) {
|
|
49
|
+
return map.has(key) ? (map.get(key) as string) : null;
|
|
50
|
+
},
|
|
51
|
+
setItem(key, value) {
|
|
52
|
+
map.set(key, value);
|
|
53
|
+
return true;
|
|
54
|
+
},
|
|
55
|
+
removeItem(key) {
|
|
56
|
+
map.delete(key);
|
|
57
|
+
return true;
|
|
58
|
+
},
|
|
59
|
+
clear() {
|
|
60
|
+
map.clear();
|
|
61
|
+
return true;
|
|
62
|
+
},
|
|
63
|
+
keys() {
|
|
64
|
+
return [...map.keys()];
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function deniedBackend(): StorageBackend {
|
|
70
|
+
return {
|
|
71
|
+
getItem() {
|
|
72
|
+
return null;
|
|
73
|
+
},
|
|
74
|
+
setItem() {
|
|
75
|
+
return false;
|
|
76
|
+
},
|
|
77
|
+
removeItem() {
|
|
78
|
+
return false;
|
|
79
|
+
},
|
|
80
|
+
clear() {
|
|
81
|
+
return false;
|
|
82
|
+
},
|
|
83
|
+
keys() {
|
|
84
|
+
return [];
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
afterEach(() => {
|
|
90
|
+
disableStorageSignals();
|
|
91
|
+
setStorageBackend(null);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
describe('clearStorage', () => {
|
|
95
|
+
it('clears via the active backend', () => {
|
|
96
|
+
setStorageBackend(fakeBackend());
|
|
97
|
+
setStorageItem('a', '1');
|
|
98
|
+
expect(clearStorage()).toBe(true);
|
|
99
|
+
expect(getStorageKeys()).toEqual([]);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('returns false when denied', () => {
|
|
103
|
+
setStorageBackend(deniedBackend());
|
|
104
|
+
expect(clearStorage()).toBe(false);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
describe('clearStorageNamespace', () => {
|
|
109
|
+
it('removes only keys under the prefix', () => {
|
|
110
|
+
setStorageBackend(fakeBackend());
|
|
111
|
+
const ns = createStorageNamespace('ns');
|
|
112
|
+
setNamespacedStorageItem(ns, 'a', '1');
|
|
113
|
+
setNamespacedStorageItem(ns, 'b', '2');
|
|
114
|
+
setStorageItem('global', 'g');
|
|
115
|
+
expect(clearStorageNamespace(ns)).toBe(true);
|
|
116
|
+
expect(getNamespacedStorageKeys(ns)).toEqual([]);
|
|
117
|
+
expect(getStorageItem('global')).toBe('g');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('drives the namespace byte-size and key-set to empty after cleanup', () => {
|
|
121
|
+
setStorageBackend(fakeBackend());
|
|
122
|
+
const ns = createStorageNamespace('ns');
|
|
123
|
+
setNamespacedStorageItem(ns, 'a', '1');
|
|
124
|
+
setNamespacedStorageItem(ns, 'b', '2');
|
|
125
|
+
expect(getNamespacedStorageByteSize(ns)).toBeGreaterThan(0);
|
|
126
|
+
expect(clearStorageNamespace(ns)).toBe(true);
|
|
127
|
+
expect(getNamespacedStorageKeys(ns)).toEqual([]);
|
|
128
|
+
expect(getNamespacedStorageByteSize(ns)).toBe(0);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
describe('createStorageNamespace', () => {
|
|
133
|
+
it('creates a namespace with the given prefix', () => {
|
|
134
|
+
const ns = createStorageNamespace('app');
|
|
135
|
+
expect(ns.prefix).toBe('app');
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
describe('createWebStorageBackend', () => {
|
|
140
|
+
it('returns a backend whose reads yield sentinels without throwing', () => {
|
|
141
|
+
const backend = createWebStorageBackend();
|
|
142
|
+
expect(() => backend.getItem('missing')).not.toThrow();
|
|
143
|
+
expect(Array.isArray(backend.keys())).toBe(true);
|
|
144
|
+
expect(typeof backend.setItem('k', 'v')).toBe('boolean');
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
describe('disableStorageSignals', () => {
|
|
149
|
+
it('is a no-op when signals are not enabled', () => {
|
|
150
|
+
expect(() => disableStorageSignals()).not.toThrow();
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('stops emitting after disable', () => {
|
|
154
|
+
setStorageBackend(fakeBackend());
|
|
155
|
+
enableStorageSignals();
|
|
156
|
+
disableStorageSignals();
|
|
157
|
+
expect(getStorageSignals()).toBeNull();
|
|
158
|
+
// write should not throw after disable
|
|
159
|
+
setStorageItem('x', 'y');
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
describe('enableStorageSignals', () => {
|
|
164
|
+
it('returns a StorageSignals object', () => {
|
|
165
|
+
const sigs = enableStorageSignals();
|
|
166
|
+
expect(sigs).not.toBeNull();
|
|
167
|
+
expect(sigs.onChange).toBeDefined();
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('is idempotent — returns the same object', () => {
|
|
171
|
+
const a = enableStorageSignals();
|
|
172
|
+
const b = enableStorageSignals();
|
|
173
|
+
expect(a).toBe(b);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('emits onChange when setStorageItem is called', () => {
|
|
177
|
+
setStorageBackend(fakeBackend());
|
|
178
|
+
const sigs = enableStorageSignals();
|
|
179
|
+
const changes: Array<{ key: string | null; oldValue: string | null; newValue: string | null }> = [];
|
|
180
|
+
sigs.onChange.emit = (change) => changes.push({ ...change });
|
|
181
|
+
setStorageItem('hello', 'world');
|
|
182
|
+
expect(changes).toHaveLength(1);
|
|
183
|
+
expect(changes[0].key).toBe('hello');
|
|
184
|
+
expect(changes[0].newValue).toBe('world');
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('emits onChange when clearStorage is called', () => {
|
|
188
|
+
setStorageBackend(fakeBackend());
|
|
189
|
+
const sigs = enableStorageSignals();
|
|
190
|
+
const changes: Array<{ key: string | null }> = [];
|
|
191
|
+
sigs.onChange.emit = (change) => changes.push({ key: change.key });
|
|
192
|
+
clearStorage();
|
|
193
|
+
expect(changes).toHaveLength(1);
|
|
194
|
+
expect(changes[0].key).toBeNull();
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('emits onChange when removeStorageItem is called', () => {
|
|
198
|
+
setStorageBackend(fakeBackend());
|
|
199
|
+
setStorageItem('a', '1');
|
|
200
|
+
const sigs = enableStorageSignals();
|
|
201
|
+
const changes: Array<{ key: string | null; newValue: string | null }> = [];
|
|
202
|
+
sigs.onChange.emit = (change) => changes.push({ key: change.key, newValue: change.newValue });
|
|
203
|
+
removeStorageItem('a');
|
|
204
|
+
expect(changes).toHaveLength(1);
|
|
205
|
+
expect(changes[0].key).toBe('a');
|
|
206
|
+
expect(changes[0].newValue).toBeNull();
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
describe('getNamespacedStorageByteSize', () => {
|
|
211
|
+
it('counts only namespaced keys', () => {
|
|
212
|
+
setStorageBackend(fakeBackend());
|
|
213
|
+
const ns = createStorageNamespace('ns');
|
|
214
|
+
setNamespacedStorageItem(ns, 'x', 'y');
|
|
215
|
+
setStorageItem('global', 'g');
|
|
216
|
+
const size = getNamespacedStorageByteSize(ns);
|
|
217
|
+
expect(size).toBeGreaterThan(0);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('returns 0 for empty namespace', () => {
|
|
221
|
+
setStorageBackend(fakeBackend());
|
|
222
|
+
const ns = createStorageNamespace('empty');
|
|
223
|
+
expect(getNamespacedStorageByteSize(ns)).toBe(0);
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
describe('getNamespacedStorageEntries', () => {
|
|
228
|
+
it('returns unprefixed key/value pairs', () => {
|
|
229
|
+
setStorageBackend(fakeBackend());
|
|
230
|
+
const ns = createStorageNamespace('ns');
|
|
231
|
+
setNamespacedStorageItem(ns, 'a', '1');
|
|
232
|
+
setNamespacedStorageItem(ns, 'b', '2');
|
|
233
|
+
setStorageItem('ns.x', 'outside'); // Actually prefix-matching — valid entry
|
|
234
|
+
const entries = getNamespacedStorageEntries(ns);
|
|
235
|
+
const keys = entries.map((e) => e[0]).sort();
|
|
236
|
+
expect(keys).toContain('a');
|
|
237
|
+
expect(keys).toContain('b');
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('does not include global keys', () => {
|
|
241
|
+
setStorageBackend(fakeBackend());
|
|
242
|
+
const ns = createStorageNamespace('app');
|
|
243
|
+
setStorageItem('global', 'g');
|
|
244
|
+
setNamespacedStorageItem(ns, 'local', 'v');
|
|
245
|
+
const entries = getNamespacedStorageEntries(ns);
|
|
246
|
+
expect(entries.every((e) => e[0] !== 'global')).toBe(true);
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
describe('getNamespacedStorageItem', () => {
|
|
251
|
+
it('returns null when absent', () => {
|
|
252
|
+
setStorageBackend(fakeBackend());
|
|
253
|
+
const ns = createStorageNamespace('ns');
|
|
254
|
+
expect(getNamespacedStorageItem(ns, 'missing')).toBeNull();
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it('round-trips through the backend', () => {
|
|
258
|
+
setStorageBackend(fakeBackend());
|
|
259
|
+
const ns = createStorageNamespace('ns');
|
|
260
|
+
setNamespacedStorageItem(ns, 'key', 'val');
|
|
261
|
+
expect(getNamespacedStorageItem(ns, 'key')).toBe('val');
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
describe('getNamespacedStorageKeys', () => {
|
|
266
|
+
it('returns unprefixed keys', () => {
|
|
267
|
+
setStorageBackend(fakeBackend());
|
|
268
|
+
const ns = createStorageNamespace('app');
|
|
269
|
+
setNamespacedStorageItem(ns, 'x', '1');
|
|
270
|
+
setNamespacedStorageItem(ns, 'y', '2');
|
|
271
|
+
setStorageItem('global', 'g');
|
|
272
|
+
const keys = getNamespacedStorageKeys(ns).sort();
|
|
273
|
+
expect(keys).toEqual(['x', 'y']);
|
|
274
|
+
});
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
describe('getStorageBackend', () => {
|
|
278
|
+
it('falls back to a web backend', () => {
|
|
279
|
+
expect(getStorageBackend()).not.toBeNull();
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it('returns the registered backend', () => {
|
|
283
|
+
const backend = fakeBackend();
|
|
284
|
+
setStorageBackend(backend);
|
|
285
|
+
expect(getStorageBackend()).toBe(backend);
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
describe('getStorageBoolean', () => {
|
|
290
|
+
it('returns true for stored "true"', () => {
|
|
291
|
+
setStorageBackend(fakeBackend());
|
|
292
|
+
setStorageItem('flag', 'true');
|
|
293
|
+
expect(getStorageBoolean('flag')).toBe(true);
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
it('returns false for stored "false"', () => {
|
|
297
|
+
setStorageBackend(fakeBackend());
|
|
298
|
+
setStorageItem('flag', 'false');
|
|
299
|
+
expect(getStorageBoolean('flag')).toBe(false);
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it('returns null for absent key', () => {
|
|
303
|
+
setStorageBackend(fakeBackend());
|
|
304
|
+
expect(getStorageBoolean('missing')).toBeNull();
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
it('returns null for unrecognized value', () => {
|
|
308
|
+
setStorageBackend(fakeBackend());
|
|
309
|
+
setStorageItem('flag', '1');
|
|
310
|
+
expect(getStorageBoolean('flag')).toBeNull();
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
describe('getStorageBooleanOr', () => {
|
|
315
|
+
it('returns fallback on absent key', () => {
|
|
316
|
+
setStorageBackend(fakeBackend());
|
|
317
|
+
expect(getStorageBooleanOr('missing', true)).toBe(true);
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it('returns stored value when present', () => {
|
|
321
|
+
setStorageBackend(fakeBackend());
|
|
322
|
+
setStorageItem('flag', 'false');
|
|
323
|
+
expect(getStorageBooleanOr('flag', true)).toBe(false);
|
|
324
|
+
});
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
describe('getStorageByteSize', () => {
|
|
328
|
+
it('returns 0 for an empty store', () => {
|
|
329
|
+
setStorageBackend(fakeBackend());
|
|
330
|
+
expect(getStorageByteSize()).toBe(0);
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
it('returns a positive size after writing', () => {
|
|
334
|
+
setStorageBackend(fakeBackend());
|
|
335
|
+
setStorageItem('key', 'value');
|
|
336
|
+
expect(getStorageByteSize()).toBeGreaterThan(0);
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
it('delegates to backend.byteSize when present', () => {
|
|
340
|
+
const backend = fakeBackend();
|
|
341
|
+
backend.byteSize = () => 42;
|
|
342
|
+
setStorageBackend(backend);
|
|
343
|
+
expect(getStorageByteSize()).toBe(42);
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
describe('getStorageEntries', () => {
|
|
348
|
+
it('returns all key/value pairs', () => {
|
|
349
|
+
setStorageBackend(fakeBackend());
|
|
350
|
+
setStorageItem('a', '1');
|
|
351
|
+
setStorageItem('b', '2');
|
|
352
|
+
const entries = getStorageEntries();
|
|
353
|
+
const sorted = [...entries].sort((a, b) => a[0].localeCompare(b[0]));
|
|
354
|
+
expect(sorted).toEqual([
|
|
355
|
+
['a', '1'],
|
|
356
|
+
['b', '2'],
|
|
357
|
+
]);
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
it('returns [] for a denied backend', () => {
|
|
361
|
+
setStorageBackend(deniedBackend());
|
|
362
|
+
expect(getStorageEntries()).toEqual([]);
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
it('returns [] for an empty store', () => {
|
|
366
|
+
setStorageBackend(fakeBackend());
|
|
367
|
+
expect(getStorageEntries()).toEqual([]);
|
|
368
|
+
});
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
describe('getStorageItem', () => {
|
|
372
|
+
it('returns null for an absent key', () => {
|
|
373
|
+
setStorageBackend(fakeBackend());
|
|
374
|
+
expect(getStorageItem('missing')).toBeNull();
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
it('round-trips through the backend', () => {
|
|
378
|
+
setStorageBackend(fakeBackend());
|
|
379
|
+
setStorageItem('greeting', 'hi');
|
|
380
|
+
expect(getStorageItem('greeting')).toBe('hi');
|
|
381
|
+
});
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
describe('getStorageItemCount', () => {
|
|
385
|
+
it('returns 0 for an empty store', () => {
|
|
386
|
+
setStorageBackend(fakeBackend());
|
|
387
|
+
expect(getStorageItemCount()).toBe(0);
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
it('counts stored keys', () => {
|
|
391
|
+
setStorageBackend(fakeBackend());
|
|
392
|
+
setStorageItem('a', '1');
|
|
393
|
+
setStorageItem('b', '2');
|
|
394
|
+
expect(getStorageItemCount()).toBe(2);
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
it('returns 0 when denied', () => {
|
|
398
|
+
setStorageBackend(deniedBackend());
|
|
399
|
+
expect(getStorageItemCount()).toBe(0);
|
|
400
|
+
});
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
describe('getStorageItemOr', () => {
|
|
404
|
+
it('returns the stored value', () => {
|
|
405
|
+
setStorageBackend(fakeBackend());
|
|
406
|
+
setStorageItem('k', 'v');
|
|
407
|
+
expect(getStorageItemOr('k', 'default')).toBe('v');
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
it('returns the fallback on absent key', () => {
|
|
411
|
+
setStorageBackend(fakeBackend());
|
|
412
|
+
expect(getStorageItemOr('missing', 'fallback')).toBe('fallback');
|
|
413
|
+
});
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
describe('getStorageItems', () => {
|
|
417
|
+
it('returns parallel-indexed values', () => {
|
|
418
|
+
setStorageBackend(fakeBackend());
|
|
419
|
+
setStorageItem('a', '1');
|
|
420
|
+
setStorageItem('b', '2');
|
|
421
|
+
const results = getStorageItems(['a', 'missing', 'b']);
|
|
422
|
+
expect(results).toEqual(['1', null, '2']);
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
it('returns [] for empty keys array', () => {
|
|
426
|
+
setStorageBackend(fakeBackend());
|
|
427
|
+
expect(getStorageItems([])).toEqual([]);
|
|
428
|
+
});
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
describe('getStorageJSON', () => {
|
|
432
|
+
it('parses a valid JSON string', () => {
|
|
433
|
+
setStorageBackend(fakeBackend());
|
|
434
|
+
setStorageItem('obj', '{"x":1}');
|
|
435
|
+
expect(getStorageJSON<{ x: number }>('obj')).toEqual({ x: 1 });
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
it('returns null on absent key', () => {
|
|
439
|
+
setStorageBackend(fakeBackend());
|
|
440
|
+
expect(getStorageJSON('missing')).toBeNull();
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
it('returns null on corrupt stored data (parse failure)', () => {
|
|
444
|
+
setStorageBackend(fakeBackend());
|
|
445
|
+
setStorageItem('bad', 'not-json{');
|
|
446
|
+
expect(getStorageJSON('bad')).toBeNull();
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
it('round-trips through setStorageJSON', () => {
|
|
450
|
+
setStorageBackend(fakeBackend());
|
|
451
|
+
setStorageJSON('data', { hello: 'world' });
|
|
452
|
+
expect(getStorageJSON<{ hello: string }>('data')).toEqual({ hello: 'world' });
|
|
453
|
+
});
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
describe('getStorageJSONOr', () => {
|
|
457
|
+
it('returns fallback on absent key', () => {
|
|
458
|
+
setStorageBackend(fakeBackend());
|
|
459
|
+
expect(getStorageJSONOr('missing', 42)).toBe(42);
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
it('returns fallback on parse failure', () => {
|
|
463
|
+
setStorageBackend(fakeBackend());
|
|
464
|
+
setStorageItem('bad', '{corrupt');
|
|
465
|
+
expect(getStorageJSONOr('bad', 'default')).toBe('default');
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
it('returns the parsed value when present', () => {
|
|
469
|
+
setStorageBackend(fakeBackend());
|
|
470
|
+
setStorageJSON('n', 99);
|
|
471
|
+
expect(getStorageJSONOr('n', 0)).toBe(99);
|
|
472
|
+
});
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
describe('getStorageKeys', () => {
|
|
476
|
+
it('lists stored keys', () => {
|
|
477
|
+
setStorageBackend(fakeBackend());
|
|
478
|
+
setStorageItem('a', '1');
|
|
479
|
+
setStorageItem('b', '2');
|
|
480
|
+
expect(getStorageKeys().sort()).toEqual(['a', 'b']);
|
|
481
|
+
});
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
describe('getStorageNumber', () => {
|
|
485
|
+
it('returns a stored number', () => {
|
|
486
|
+
setStorageBackend(fakeBackend());
|
|
487
|
+
setStorageItem('n', '42');
|
|
488
|
+
expect(getStorageNumber('n')).toBe(42);
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
it('returns null for absent key', () => {
|
|
492
|
+
setStorageBackend(fakeBackend());
|
|
493
|
+
expect(getStorageNumber('missing')).toBeNull();
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
it('returns null for non-numeric value', () => {
|
|
497
|
+
setStorageBackend(fakeBackend());
|
|
498
|
+
setStorageItem('bad', 'nope');
|
|
499
|
+
expect(getStorageNumber('bad')).toBeNull();
|
|
500
|
+
});
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
describe('getStorageNumberOr', () => {
|
|
504
|
+
it('returns fallback on absent key', () => {
|
|
505
|
+
setStorageBackend(fakeBackend());
|
|
506
|
+
expect(getStorageNumberOr('missing', -1)).toBe(-1);
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
it('returns stored value when present', () => {
|
|
510
|
+
setStorageBackend(fakeBackend());
|
|
511
|
+
setStorageItem('n', '7');
|
|
512
|
+
expect(getStorageNumberOr('n', 0)).toBe(7);
|
|
513
|
+
});
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
describe('getStorageQuotaEstimate', () => {
|
|
517
|
+
it('returns null when navigator.storage is absent', async () => {
|
|
518
|
+
const result = await getStorageQuotaEstimate();
|
|
519
|
+
// In jsdom, navigator.storage.estimate may or may not be available — just assert no throw.
|
|
520
|
+
expect(result === null || typeof result === 'object').toBe(true);
|
|
521
|
+
});
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
describe('getStorageSignals', () => {
|
|
525
|
+
it('returns null before enableStorageSignals', () => {
|
|
526
|
+
expect(getStorageSignals()).toBeNull();
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
it('returns the signals after enableStorageSignals', () => {
|
|
530
|
+
const sigs = enableStorageSignals();
|
|
531
|
+
expect(getStorageSignals()).toBe(sigs);
|
|
532
|
+
});
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
describe('hasNamespacedStorageItem', () => {
|
|
536
|
+
it('returns false when absent', () => {
|
|
537
|
+
setStorageBackend(fakeBackend());
|
|
538
|
+
const ns = createStorageNamespace('ns');
|
|
539
|
+
expect(hasNamespacedStorageItem(ns, 'missing')).toBe(false);
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
it('returns true when present', () => {
|
|
543
|
+
setStorageBackend(fakeBackend());
|
|
544
|
+
const ns = createStorageNamespace('ns');
|
|
545
|
+
setNamespacedStorageItem(ns, 'key', 'val');
|
|
546
|
+
expect(hasNamespacedStorageItem(ns, 'key')).toBe(true);
|
|
547
|
+
});
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
describe('hasStorageItem', () => {
|
|
551
|
+
it('returns false for absent key', () => {
|
|
552
|
+
setStorageBackend(fakeBackend());
|
|
553
|
+
expect(hasStorageItem('missing')).toBe(false);
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
it('returns true for a stored key', () => {
|
|
557
|
+
setStorageBackend(fakeBackend());
|
|
558
|
+
setStorageItem('present', 'value');
|
|
559
|
+
expect(hasStorageItem('present')).toBe(true);
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
it('returns false when denied', () => {
|
|
563
|
+
setStorageBackend(deniedBackend());
|
|
564
|
+
expect(hasStorageItem('any')).toBe(false);
|
|
565
|
+
});
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
describe('migrateStorage', () => {
|
|
569
|
+
it('runs migrations in version order', () => {
|
|
570
|
+
setStorageBackend(fakeBackend());
|
|
571
|
+
const calls: number[] = [];
|
|
572
|
+
const migrations = [
|
|
573
|
+
{
|
|
574
|
+
version: 2,
|
|
575
|
+
migrate: () => {
|
|
576
|
+
calls.push(2);
|
|
577
|
+
},
|
|
578
|
+
},
|
|
579
|
+
{
|
|
580
|
+
version: 1,
|
|
581
|
+
migrate: () => {
|
|
582
|
+
calls.push(1);
|
|
583
|
+
},
|
|
584
|
+
},
|
|
585
|
+
];
|
|
586
|
+
const result = migrateStorage(null, migrations);
|
|
587
|
+
expect(result).toBe(2);
|
|
588
|
+
expect(calls).toEqual([1, 2]);
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
it('skips migrations at or below current version', () => {
|
|
592
|
+
setStorageBackend(fakeBackend());
|
|
593
|
+
setStorageItem('__flight_storage_version', '2');
|
|
594
|
+
const calls: number[] = [];
|
|
595
|
+
const migrations = [
|
|
596
|
+
{
|
|
597
|
+
version: 1,
|
|
598
|
+
migrate: () => {
|
|
599
|
+
calls.push(1);
|
|
600
|
+
},
|
|
601
|
+
},
|
|
602
|
+
{
|
|
603
|
+
version: 2,
|
|
604
|
+
migrate: () => {
|
|
605
|
+
calls.push(2);
|
|
606
|
+
},
|
|
607
|
+
},
|
|
608
|
+
{
|
|
609
|
+
version: 3,
|
|
610
|
+
migrate: () => {
|
|
611
|
+
calls.push(3);
|
|
612
|
+
},
|
|
613
|
+
},
|
|
614
|
+
];
|
|
615
|
+
const result = migrateStorage(null, migrations);
|
|
616
|
+
expect(result).toBe(3);
|
|
617
|
+
expect(calls).toEqual([3]);
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
it('works with a namespace', () => {
|
|
621
|
+
setStorageBackend(fakeBackend());
|
|
622
|
+
const ns = createStorageNamespace('app');
|
|
623
|
+
const calls: number[] = [];
|
|
624
|
+
const migrations = [
|
|
625
|
+
{
|
|
626
|
+
version: 1,
|
|
627
|
+
migrate: () => {
|
|
628
|
+
calls.push(1);
|
|
629
|
+
},
|
|
630
|
+
},
|
|
631
|
+
];
|
|
632
|
+
const result = migrateStorage(ns, migrations);
|
|
633
|
+
expect(result).toBe(1);
|
|
634
|
+
expect(calls).toEqual([1]);
|
|
635
|
+
expect(getNamespacedStorageItem(ns, '__flight_storage_version')).toBe('1');
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
it('returns -1 when a migration throws', () => {
|
|
639
|
+
setStorageBackend(fakeBackend());
|
|
640
|
+
const migrations = [
|
|
641
|
+
{
|
|
642
|
+
version: 1,
|
|
643
|
+
migrate: () => {
|
|
644
|
+
throw new Error('fail');
|
|
645
|
+
},
|
|
646
|
+
},
|
|
647
|
+
];
|
|
648
|
+
expect(migrateStorage(null, migrations)).toBe(-1);
|
|
649
|
+
});
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
describe('removeNamespacedStorageItem', () => {
|
|
653
|
+
it('removes a namespaced key', () => {
|
|
654
|
+
setStorageBackend(fakeBackend());
|
|
655
|
+
const ns = createStorageNamespace('ns');
|
|
656
|
+
setNamespacedStorageItem(ns, 'key', 'val');
|
|
657
|
+
expect(removeNamespacedStorageItem(ns, 'key')).toBe(true);
|
|
658
|
+
expect(getNamespacedStorageItem(ns, 'key')).toBeNull();
|
|
659
|
+
});
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
describe('removeStorageItem', () => {
|
|
663
|
+
it('removes via the active backend', () => {
|
|
664
|
+
setStorageBackend(fakeBackend());
|
|
665
|
+
setStorageItem('a', '1');
|
|
666
|
+
expect(removeStorageItem('a')).toBe(true);
|
|
667
|
+
expect(getStorageItem('a')).toBeNull();
|
|
668
|
+
});
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
describe('removeStorageItems', () => {
|
|
672
|
+
it('removes multiple keys', () => {
|
|
673
|
+
setStorageBackend(fakeBackend());
|
|
674
|
+
setStorageItem('a', '1');
|
|
675
|
+
setStorageItem('b', '2');
|
|
676
|
+
expect(removeStorageItems(['a', 'b'])).toBe(true);
|
|
677
|
+
expect(getStorageItem('a')).toBeNull();
|
|
678
|
+
expect(getStorageItem('b')).toBeNull();
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
it('returns false when any removal fails', () => {
|
|
682
|
+
setStorageBackend(deniedBackend());
|
|
683
|
+
expect(removeStorageItems(['x'])).toBe(false);
|
|
684
|
+
});
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
describe('setNamespacedStorageItem', () => {
|
|
688
|
+
it('stores under the prefixed key', () => {
|
|
689
|
+
setStorageBackend(fakeBackend());
|
|
690
|
+
const ns = createStorageNamespace('app');
|
|
691
|
+
setNamespacedStorageItem(ns, 'setting', '42');
|
|
692
|
+
expect(getStorageItem('app.setting')).toBe('42');
|
|
693
|
+
});
|
|
694
|
+
});
|
|
695
|
+
|
|
696
|
+
describe('setStorageBackend', () => {
|
|
697
|
+
it('clears back to the web fallback when passed null', () => {
|
|
698
|
+
setStorageBackend(fakeBackend());
|
|
699
|
+
setStorageBackend(null);
|
|
700
|
+
expect(getStorageBackend()).not.toBeNull();
|
|
701
|
+
});
|
|
702
|
+
});
|
|
703
|
+
|
|
704
|
+
describe('setStorageBoolean', () => {
|
|
705
|
+
it('writes "true" for true', () => {
|
|
706
|
+
setStorageBackend(fakeBackend());
|
|
707
|
+
setStorageBoolean('flag', true);
|
|
708
|
+
expect(getStorageItem('flag')).toBe('true');
|
|
709
|
+
});
|
|
710
|
+
|
|
711
|
+
it('writes "false" for false', () => {
|
|
712
|
+
setStorageBackend(fakeBackend());
|
|
713
|
+
setStorageBoolean('flag', false);
|
|
714
|
+
expect(getStorageItem('flag')).toBe('false');
|
|
715
|
+
});
|
|
716
|
+
});
|
|
717
|
+
|
|
718
|
+
describe('setStorageItem', () => {
|
|
719
|
+
it('writes via the active backend', () => {
|
|
720
|
+
setStorageBackend(fakeBackend());
|
|
721
|
+
expect(setStorageItem('x', 'y')).toBe(true);
|
|
722
|
+
expect(getStorageItem('x')).toBe('y');
|
|
723
|
+
});
|
|
724
|
+
});
|
|
725
|
+
|
|
726
|
+
describe('setStorageItems', () => {
|
|
727
|
+
it('writes all key/value pairs', () => {
|
|
728
|
+
setStorageBackend(fakeBackend());
|
|
729
|
+
expect(setStorageItems({ a: '1', b: '2' })).toBe(true);
|
|
730
|
+
expect(getStorageItem('a')).toBe('1');
|
|
731
|
+
expect(getStorageItem('b')).toBe('2');
|
|
732
|
+
});
|
|
733
|
+
|
|
734
|
+
it('returns false when any write fails', () => {
|
|
735
|
+
setStorageBackend(deniedBackend());
|
|
736
|
+
expect(setStorageItems({ a: '1' })).toBe(false);
|
|
737
|
+
});
|
|
738
|
+
});
|
|
739
|
+
|
|
740
|
+
describe('setStorageJSON', () => {
|
|
741
|
+
it('stores a stringified value', () => {
|
|
742
|
+
setStorageBackend(fakeBackend());
|
|
743
|
+
expect(setStorageJSON('obj', { n: 1 })).toBe(true);
|
|
744
|
+
expect(getStorageItem('obj')).toBe('{"n":1}');
|
|
745
|
+
});
|
|
746
|
+
|
|
747
|
+
it('returns false for cyclic values', () => {
|
|
748
|
+
setStorageBackend(fakeBackend());
|
|
749
|
+
const cyclic: Record<string, unknown> = {};
|
|
750
|
+
cyclic.self = cyclic;
|
|
751
|
+
expect(setStorageJSON('bad', cyclic)).toBe(false);
|
|
752
|
+
});
|
|
753
|
+
});
|
|
754
|
+
|
|
755
|
+
describe('setStorageNumber', () => {
|
|
756
|
+
it('stores the number as a string', () => {
|
|
757
|
+
setStorageBackend(fakeBackend());
|
|
758
|
+
setStorageNumber('n', 3.14);
|
|
759
|
+
expect(getStorageItem('n')).toBe('3.14');
|
|
760
|
+
});
|
|
761
|
+
});
|