@fluixi/storage 0.1.0-alpha.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/LICENSE +21 -0
- package/README.md +30 -0
- package/dist/adapters/memory.cjs +46 -0
- package/dist/adapters/memory.d.ts +8 -0
- package/dist/adapters/memory.d.ts.map +1 -0
- package/dist/adapters/memory.js +26 -0
- package/dist/adapters/memory.mjs +25 -0
- package/dist/browser/document-cookie.cjs +101 -0
- package/dist/browser/document-cookie.d.ts +14 -0
- package/dist/browser/document-cookie.d.ts.map +1 -0
- package/dist/browser/document-cookie.js +53 -0
- package/dist/browser/document-cookie.mjs +80 -0
- package/dist/browser/index.cjs +209 -0
- package/dist/browser/index.d.ts +4 -0
- package/dist/browser/index.d.ts.map +1 -0
- package/dist/browser/index.js +3 -0
- package/dist/browser/index.mjs +187 -0
- package/dist/browser/web-storage.cjs +150 -0
- package/dist/browser/web-storage.d.ts +6 -0
- package/dist/browser/web-storage.d.ts.map +1 -0
- package/dist/browser/web-storage.js +105 -0
- package/dist/browser/web-storage.mjs +127 -0
- package/dist/core/async-storage.cjs +583 -0
- package/dist/core/async-storage.d.ts +17 -0
- package/dist/core/async-storage.d.ts.map +1 -0
- package/dist/core/async-storage.js +275 -0
- package/dist/core/async-storage.mjs +560 -0
- package/dist/core/bus.cjs +52 -0
- package/dist/core/bus.d.ts +19 -0
- package/dist/core/bus.d.ts.map +1 -0
- package/dist/core/bus.js +31 -0
- package/dist/core/bus.mjs +31 -0
- package/dist/core/envelope.cjs +119 -0
- package/dist/core/envelope.d.ts +38 -0
- package/dist/core/envelope.d.ts.map +1 -0
- package/dist/core/envelope.js +83 -0
- package/dist/core/envelope.mjs +96 -0
- package/dist/core/errors.cjs +115 -0
- package/dist/core/errors.d.ts +109 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +99 -0
- package/dist/core/errors.mjs +94 -0
- package/dist/core/storage.cjs +782 -0
- package/dist/core/storage.d.ts +63 -0
- package/dist/core/storage.d.ts.map +1 -0
- package/dist/core/storage.js +260 -0
- package/dist/core/storage.mjs +759 -0
- package/dist/core/types.cjs +18 -0
- package/dist/core/types.d.ts +239 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +1 -0
- package/dist/core/types.mjs +0 -0
- package/dist/encryption/base64.cjs +38 -0
- package/dist/encryption/base64.d.ts +4 -0
- package/dist/encryption/base64.d.ts.map +1 -0
- package/dist/encryption/base64.js +16 -0
- package/dist/encryption/base64.mjs +17 -0
- package/dist/encryption/keys.cjs +77 -0
- package/dist/encryption/keys.d.ts +14 -0
- package/dist/encryption/keys.d.ts.map +1 -0
- package/dist/encryption/keys.js +28 -0
- package/dist/encryption/keys.mjs +54 -0
- package/dist/encryption/seal.cjs +154 -0
- package/dist/encryption/seal.d.ts +24 -0
- package/dist/encryption/seal.d.ts.map +1 -0
- package/dist/encryption/seal.js +83 -0
- package/dist/encryption/seal.mjs +131 -0
- package/dist/encryption/types.cjs +18 -0
- package/dist/encryption/types.d.ts +50 -0
- package/dist/encryption/types.d.ts.map +1 -0
- package/dist/encryption/types.js +1 -0
- package/dist/encryption/types.mjs +0 -0
- package/dist/encryption/web-crypto.cjs +81 -0
- package/dist/encryption/web-crypto.d.ts +9 -0
- package/dist/encryption/web-crypto.d.ts.map +1 -0
- package/dist/encryption/web-crypto.js +45 -0
- package/dist/encryption/web-crypto.mjs +58 -0
- package/dist/index.cjs +870 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.mjs +848 -0
- package/dist/serialization/json.cjs +83 -0
- package/dist/serialization/json.d.ts +3 -0
- package/dist/serialization/json.d.ts.map +1 -0
- package/dist/serialization/json.js +51 -0
- package/dist/serialization/json.mjs +60 -0
- package/dist/serialization/serializer.cjs +28 -0
- package/dist/serialization/serializer.d.ts +4 -0
- package/dist/serialization/serializer.d.ts.map +1 -0
- package/dist/serialization/serializer.js +4 -0
- package/dist/serialization/serializer.mjs +7 -0
- package/dist/server/contracts.cjs +36 -0
- package/dist/server/contracts.d.ts +27 -0
- package/dist/server/contracts.d.ts.map +1 -0
- package/dist/server/contracts.js +16 -0
- package/dist/server/contracts.mjs +15 -0
- package/dist/server/cookie.cjs +126 -0
- package/dist/server/cookie.d.ts +21 -0
- package/dist/server/cookie.d.ts.map +1 -0
- package/dist/server/cookie.js +71 -0
- package/dist/server/cookie.mjs +105 -0
- package/dist/server/index.cjs +155 -0
- package/dist/server/index.d.ts +5 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +4 -0
- package/dist/server/index.mjs +133 -0
- package/dist/server/request.cjs +46 -0
- package/dist/server/request.d.ts +7 -0
- package/dist/server/request.d.ts.map +1 -0
- package/dist/server/request.js +25 -0
- package/dist/server/request.mjs +25 -0
- package/dist/tsconfig.lib.tsbuildinfo +1 -0
- package/dist/utils/cookie-names.cjs +38 -0
- package/dist/utils/cookie-names.d.ts +10 -0
- package/dist/utils/cookie-names.d.ts.map +1 -0
- package/dist/utils/cookie-names.js +15 -0
- package/dist/utils/cookie-names.mjs +17 -0
- package/dist/utils/copy.cjs +38 -0
- package/dist/utils/copy.d.ts +17 -0
- package/dist/utils/copy.d.ts.map +1 -0
- package/dist/utils/copy.js +15 -0
- package/dist/utils/copy.mjs +17 -0
- package/dist/utils/environment.cjs +28 -0
- package/dist/utils/environment.d.ts +3 -0
- package/dist/utils/environment.d.ts.map +1 -0
- package/dist/utils/environment.js +4 -0
- package/dist/utils/environment.mjs +7 -0
- package/dist/utils/keys.cjs +33 -0
- package/dist/utils/keys.d.ts +5 -0
- package/dist/utils/keys.d.ts.map +1 -0
- package/dist/utils/keys.js +10 -0
- package/dist/utils/keys.mjs +12 -0
- package/package.json +61 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { EncryptionOptions } from '../encryption/types.js';
|
|
2
|
+
import type { AsyncStorageOptions, AsyncTypedStorage, StorageOptions, StorageSchema, TypedStorage } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* A typed storage with per-item encryption. Every operation returns a promise, since Web Crypto
|
|
5
|
+
* is asynchronous and encrypting must not block the main thread.
|
|
6
|
+
*
|
|
7
|
+
* Items are stored in clear unless written with `{ encrypt: true }`; reads detect encrypted
|
|
8
|
+
* items and decrypt them, so one storage holds both kinds.
|
|
9
|
+
*
|
|
10
|
+
* @param options `encryption.keyProvider` supplies the AES-256-GCM key; the rest is as for the
|
|
11
|
+
* synchronous overload, and `adapter` may also be asynchronous.
|
|
12
|
+
* @returns The same storage as `createAsyncStorage(options)`.
|
|
13
|
+
* @throws {StorageEncryptionError} From reads and writes, never from this call: a missing or
|
|
14
|
+
* unusable key, a payload that fails authentication, an unknown format.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* const storage = createStorage<{ theme: string; session: Session }>({
|
|
18
|
+
* adapter: localStorageAdapter(),
|
|
19
|
+
* encryption: { keyProvider: staticKeyProvider(generateEncryptionKey()) },
|
|
20
|
+
* });
|
|
21
|
+
* await storage.set('theme', 'dark');
|
|
22
|
+
* await storage.set('session', session, { encrypt: true });
|
|
23
|
+
* const current = await storage.get('session');
|
|
24
|
+
*/
|
|
25
|
+
export declare function createStorage<S extends StorageSchema = StorageSchema>(options: AsyncStorageOptions<S> & {
|
|
26
|
+
encryption: EncryptionOptions;
|
|
27
|
+
}): AsyncTypedStorage<S>;
|
|
28
|
+
/**
|
|
29
|
+
* A typed, synchronous storage over any adapter, in memory by default.
|
|
30
|
+
*
|
|
31
|
+
* Each value is kept in an envelope with its write time, expiry and schema version, so `ttl`,
|
|
32
|
+
* migrations and search behave the same on every backend. Type it with a schema and the value
|
|
33
|
+
* type follows the key.
|
|
34
|
+
*
|
|
35
|
+
* Pass `encryption` to get the asynchronous storage instead (the other overload).
|
|
36
|
+
*
|
|
37
|
+
* @param options `adapter` (default `memoryStorage()`), `namespace` (a key prefix such as
|
|
38
|
+
* `app:`), `serializer` (default the strict JSON one), `migrations` per key, and `now` for tests.
|
|
39
|
+
* @returns The same storage as `createSyncStorage(options)`.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* const prefs = createStorage<{ theme: 'light' | 'dark' }>({ adapter: localStorageAdapter(), namespace: 'app' });
|
|
43
|
+
* prefs.set('theme', 'dark', { ttl: 86_400_000 });
|
|
44
|
+
* prefs.get('theme'); // 'dark' | null
|
|
45
|
+
*/
|
|
46
|
+
export declare function createStorage<S extends StorageSchema = StorageSchema>(options?: StorageOptions<S>): TypedStorage<S>;
|
|
47
|
+
/**
|
|
48
|
+
* The synchronous storage, by name: what `createStorage` returns without `encryption`. Use it
|
|
49
|
+
* when the code relies on reads returning at once and should not compile if someone adds
|
|
50
|
+
* encryption later.
|
|
51
|
+
*
|
|
52
|
+
* It needs a synchronous adapter. It can list, check, expire and remove encrypted items
|
|
53
|
+
* written by the asynchronous storage, but reading their value throws
|
|
54
|
+
* `StorageEncryptionUnavailableError`.
|
|
55
|
+
*
|
|
56
|
+
* @param options See `createStorage`.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* const cache = createSyncStorage<{ draft: string }>({ namespace: 'editor' });
|
|
60
|
+
* cache.set('draft', text);
|
|
61
|
+
*/
|
|
62
|
+
export declare function createSyncStorage<S extends StorageSchema = StorageSchema>(options?: StorageOptions<S>): TypedStorage<S>;
|
|
63
|
+
//# sourceMappingURL=storage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../src/core/storage.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAOhE,OAAO,KAAK,EAEV,mBAAmB,EACnB,iBAAiB,EAKjB,cAAc,EACd,aAAa,EAEb,YAAY,EACb,MAAM,YAAY,CAAC;AAEpB;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,aAAa,GAAG,aAAa,EAAE,OAAO,EAAE,mBAAmB,CAAC,CAAC,CAAC,GAAG;IAAE,UAAU,EAAE,iBAAiB,CAAA;CAAE,GAAG,iBAAiB,CAAC,CAAC,CAAC,CAAC;AAClK;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,aAAa,GAAG,aAAa,EAAE,OAAO,CAAC,EAAE,cAAc,CAAC,CAAC,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC;AAQrH;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,aAAa,GAAG,aAAa,EAAE,OAAO,GAAE,cAAc,CAAC,CAAC,CAAM,GAAG,YAAY,CAAC,CAAC,CAAC,CA0O3H"}
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The storage: one typed API over any adapter. Values are wrapped in an envelope with their
|
|
3
|
+
* metadata, so expiry, versions and migrations work the same on every backend.
|
|
4
|
+
*/
|
|
5
|
+
import { memoryStorage } from '../adapters/memory.js';
|
|
6
|
+
import { jsonSerializer } from '../serialization/json.js';
|
|
7
|
+
import { joinNamespace, namespacePrefix } from '../utils/keys.js';
|
|
8
|
+
import { createAsyncStorage } from './async-storage.js';
|
|
9
|
+
import { joinBus, publish } from './bus.js';
|
|
10
|
+
import { decodeEnvelope, isExpired, latestVersion, metaFor, migrate, selectItems, serializeOrThrow, toEntry } from './envelope.js';
|
|
11
|
+
import { StorageEncryptionUnavailableError } from './errors.js';
|
|
12
|
+
export function createStorage(options = {}) {
|
|
13
|
+
if ('encryption' in options && options.encryption)
|
|
14
|
+
return createAsyncStorage(options);
|
|
15
|
+
return createSyncStorage(options);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The synchronous storage, by name: what `createStorage` returns without `encryption`. Use it
|
|
19
|
+
* when the code relies on reads returning at once and should not compile if someone adds
|
|
20
|
+
* encryption later.
|
|
21
|
+
*
|
|
22
|
+
* It needs a synchronous adapter. It can list, check, expire and remove encrypted items
|
|
23
|
+
* written by the asynchronous storage, but reading their value throws
|
|
24
|
+
* `StorageEncryptionUnavailableError`.
|
|
25
|
+
*
|
|
26
|
+
* @param options See `createStorage`.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* const cache = createSyncStorage<{ draft: string }>({ namespace: 'editor' });
|
|
30
|
+
* cache.set('draft', text);
|
|
31
|
+
*/
|
|
32
|
+
export function createSyncStorage(options = {}) {
|
|
33
|
+
const adapter = options.adapter ?? memoryStorage();
|
|
34
|
+
const serializer = options.serializer ?? jsonSerializer;
|
|
35
|
+
const now = options.now ?? Date.now;
|
|
36
|
+
const migrations = (options.migrations ?? {});
|
|
37
|
+
const prefix = namespacePrefix(options.namespace);
|
|
38
|
+
const listeners = new Set();
|
|
39
|
+
let leave;
|
|
40
|
+
const deliver = (event) => {
|
|
41
|
+
for (const { key, fn } of [...listeners])
|
|
42
|
+
if (key === undefined || event.key === null || key === event.key)
|
|
43
|
+
fn(event);
|
|
44
|
+
};
|
|
45
|
+
// Through the adapter's bus, so every storage over it hears the write, this one included.
|
|
46
|
+
const emit = (event) => publish(adapter, event.type, event.key === null ? null : prefix + event.key, prefix, event.value);
|
|
47
|
+
const encode = (key, envelope) => serializeOrThrow(serializer, key, envelope);
|
|
48
|
+
/** The plaintext value. An encrypted one needs the async storage, which can decrypt. */
|
|
49
|
+
const valueOf = (key, envelope) => {
|
|
50
|
+
if (envelope.enc) {
|
|
51
|
+
throw new StorageEncryptionUnavailableError(`${key} is encrypted; read it through createStorage({ encryption }), which is asynchronous.`, { key });
|
|
52
|
+
}
|
|
53
|
+
return envelope.value;
|
|
54
|
+
};
|
|
55
|
+
/** The live entry, expiry applied and migrations run, or null. */
|
|
56
|
+
const read = (key) => {
|
|
57
|
+
const text = adapter.get(prefix + key);
|
|
58
|
+
if (text === null)
|
|
59
|
+
return null;
|
|
60
|
+
const envelope = decodeEnvelope(serializer, key, text);
|
|
61
|
+
if (isExpired(envelope, now())) {
|
|
62
|
+
adapter.remove(prefix + key);
|
|
63
|
+
emit({ type: 'expire', key: key, external: false });
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
// An encrypted value is migrated when the async storage reads it.
|
|
67
|
+
if (envelope.enc)
|
|
68
|
+
return envelope;
|
|
69
|
+
const migrated = migrate(migrations[key], envelope.version, envelope.value);
|
|
70
|
+
if (!migrated)
|
|
71
|
+
return envelope;
|
|
72
|
+
const next = { ...envelope, ...migrated };
|
|
73
|
+
adapter.set(prefix + key, encode(key, next), { expiresAt: next.expiresAt });
|
|
74
|
+
return next;
|
|
75
|
+
};
|
|
76
|
+
const ownKeys = () => adapter
|
|
77
|
+
.keys()
|
|
78
|
+
.filter((k) => k.startsWith(prefix))
|
|
79
|
+
.map((k) => k.slice(prefix.length));
|
|
80
|
+
// Changes the adapter saw from elsewhere (another tab), in this namespace only.
|
|
81
|
+
const external = (change) => {
|
|
82
|
+
if (change.key === null)
|
|
83
|
+
return deliver({ type: 'clear', key: null, external: true });
|
|
84
|
+
if (!change.key.startsWith(prefix))
|
|
85
|
+
return;
|
|
86
|
+
const key = change.key.slice(prefix.length);
|
|
87
|
+
if (change.value === null)
|
|
88
|
+
return deliver({ type: 'remove', key, external: true });
|
|
89
|
+
try {
|
|
90
|
+
const envelope = decodeEnvelope(serializer, key, change.value);
|
|
91
|
+
deliver({ type: 'set', key, value: envelope.enc ? undefined : envelope.value, external: true });
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
// Unreadable text from another writer: reported when it is read, not here.
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
const liveItems = () => {
|
|
98
|
+
const items = [];
|
|
99
|
+
for (const key of ownKeys().sort()) {
|
|
100
|
+
const envelope = read(key);
|
|
101
|
+
if (!envelope)
|
|
102
|
+
continue;
|
|
103
|
+
const value = valueOf(key, envelope);
|
|
104
|
+
items.push({ key: key, value: value, entry: toEntry(envelope, value) });
|
|
105
|
+
}
|
|
106
|
+
return items;
|
|
107
|
+
};
|
|
108
|
+
const write = (key, value, meta) => {
|
|
109
|
+
adapter.set(prefix + key, encode(key, { v: 1, value, ...meta }), { expiresAt: meta.expiresAt });
|
|
110
|
+
emit({ type: 'set', key: key, value, external: false });
|
|
111
|
+
};
|
|
112
|
+
const storage = {
|
|
113
|
+
scope: adapter.scope,
|
|
114
|
+
keyPrefix: prefix,
|
|
115
|
+
// The storage expires entries itself, whatever the adapter can do.
|
|
116
|
+
capabilities: { ...adapter.capabilities, expiration: true },
|
|
117
|
+
get(key) {
|
|
118
|
+
const envelope = read(key);
|
|
119
|
+
return (envelope ? valueOf(key, envelope) ?? null : null);
|
|
120
|
+
},
|
|
121
|
+
getEntry(key) {
|
|
122
|
+
const envelope = read(key);
|
|
123
|
+
return envelope ? toEntry(envelope, valueOf(key, envelope)) : null;
|
|
124
|
+
},
|
|
125
|
+
set(key, value, setOptions = {}) {
|
|
126
|
+
write(key, value, metaFor(now(), setOptions.ttl, setOptions.version ?? latestVersion(migrations[key])));
|
|
127
|
+
},
|
|
128
|
+
entries() {
|
|
129
|
+
return liveItems();
|
|
130
|
+
},
|
|
131
|
+
search(query) {
|
|
132
|
+
return selectItems(liveItems(), query);
|
|
133
|
+
},
|
|
134
|
+
size() {
|
|
135
|
+
return ownKeys().filter((key) => read(key) !== null).length;
|
|
136
|
+
},
|
|
137
|
+
prune() {
|
|
138
|
+
let removed = 0;
|
|
139
|
+
for (const key of ownKeys()) {
|
|
140
|
+
const text = adapter.get(prefix + key);
|
|
141
|
+
if (text === null)
|
|
142
|
+
continue;
|
|
143
|
+
if (isExpired(decodeEnvelope(serializer, key, text), now())) {
|
|
144
|
+
adapter.remove(prefix + key);
|
|
145
|
+
emit({ type: 'expire', key: key, external: false });
|
|
146
|
+
removed++;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return removed;
|
|
150
|
+
},
|
|
151
|
+
usage() {
|
|
152
|
+
let bytes = 0;
|
|
153
|
+
const keys = ownKeys();
|
|
154
|
+
for (const key of keys)
|
|
155
|
+
bytes += ((prefix + key).length + (adapter.get(prefix + key)?.length ?? 0)) * 2;
|
|
156
|
+
return { keys: keys.length, bytes };
|
|
157
|
+
},
|
|
158
|
+
update(key, fn) {
|
|
159
|
+
const current = read(key);
|
|
160
|
+
const value = fn((current ? valueOf(key, current) : null));
|
|
161
|
+
const meta = metaFor(now(), undefined, current?.version ?? latestVersion(migrations[key]));
|
|
162
|
+
if (current?.expiresAt !== undefined)
|
|
163
|
+
meta.expiresAt = current.expiresAt;
|
|
164
|
+
write(key, value, meta);
|
|
165
|
+
return value;
|
|
166
|
+
},
|
|
167
|
+
getOrSet(key, factory, setOptions) {
|
|
168
|
+
const current = read(key);
|
|
169
|
+
if (current)
|
|
170
|
+
return valueOf(key, current);
|
|
171
|
+
const value = factory();
|
|
172
|
+
storage.set(key, value, setOptions);
|
|
173
|
+
return value;
|
|
174
|
+
},
|
|
175
|
+
touch(key, ttl) {
|
|
176
|
+
const current = read(key);
|
|
177
|
+
if (!current)
|
|
178
|
+
return false;
|
|
179
|
+
// The expiry of an encrypted entry is authenticated, so only the async storage can move it.
|
|
180
|
+
valueOf(key, current);
|
|
181
|
+
const expiresAt = now() + ttl;
|
|
182
|
+
adapter.set(prefix + key, encode(key, { ...current, expiresAt }), { expiresAt });
|
|
183
|
+
return true;
|
|
184
|
+
},
|
|
185
|
+
snapshot() {
|
|
186
|
+
const out = {};
|
|
187
|
+
for (const item of liveItems())
|
|
188
|
+
out[item.key] = item.entry;
|
|
189
|
+
return out;
|
|
190
|
+
},
|
|
191
|
+
restore(snapshot, restoreOptions = {}) {
|
|
192
|
+
let written = 0;
|
|
193
|
+
for (const [key, entry] of Object.entries(snapshot)) {
|
|
194
|
+
if (!entry)
|
|
195
|
+
continue;
|
|
196
|
+
if (entry.encrypted) {
|
|
197
|
+
throw new StorageEncryptionUnavailableError(`${key} was encrypted; restore it through createStorage({ encryption }).`, { key });
|
|
198
|
+
}
|
|
199
|
+
if (!restoreOptions.overwrite && read(key))
|
|
200
|
+
continue;
|
|
201
|
+
if (entry.expiresAt !== undefined && entry.expiresAt <= now())
|
|
202
|
+
continue;
|
|
203
|
+
const meta = { createdAt: entry.createdAt };
|
|
204
|
+
if (entry.expiresAt !== undefined)
|
|
205
|
+
meta.expiresAt = entry.expiresAt;
|
|
206
|
+
if (entry.version !== undefined)
|
|
207
|
+
meta.version = entry.version;
|
|
208
|
+
write(key, entry.value, meta);
|
|
209
|
+
written++;
|
|
210
|
+
}
|
|
211
|
+
return written;
|
|
212
|
+
},
|
|
213
|
+
remove(key) {
|
|
214
|
+
adapter.remove(prefix + key);
|
|
215
|
+
emit({ type: 'remove', key, external: false });
|
|
216
|
+
},
|
|
217
|
+
has(key) {
|
|
218
|
+
return read(key) !== null;
|
|
219
|
+
},
|
|
220
|
+
keys() {
|
|
221
|
+
return ownKeys();
|
|
222
|
+
},
|
|
223
|
+
clear() {
|
|
224
|
+
if (prefix)
|
|
225
|
+
for (const key of ownKeys())
|
|
226
|
+
adapter.remove(prefix + key);
|
|
227
|
+
else
|
|
228
|
+
adapter.clear();
|
|
229
|
+
emit({ type: 'clear', key: null, external: false });
|
|
230
|
+
},
|
|
231
|
+
namespace(name) {
|
|
232
|
+
return createSyncStorage({ adapter, serializer, now, namespace: joinNamespace(options.namespace, name) });
|
|
233
|
+
},
|
|
234
|
+
subscribe(keyOrListener, maybeListener) {
|
|
235
|
+
const entry = typeof keyOrListener === 'function'
|
|
236
|
+
? { fn: keyOrListener }
|
|
237
|
+
: { key: keyOrListener, fn: maybeListener };
|
|
238
|
+
listeners.add(entry);
|
|
239
|
+
if (listeners.size === 1) {
|
|
240
|
+
leave = joinBus(adapter, {
|
|
241
|
+
prefix,
|
|
242
|
+
external,
|
|
243
|
+
local(type, key, value) {
|
|
244
|
+
const event = { type, key: key === null ? null : key.slice(prefix.length), external: false };
|
|
245
|
+
if (type === 'set')
|
|
246
|
+
event.value = value;
|
|
247
|
+
deliver(event);
|
|
248
|
+
},
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
return () => {
|
|
252
|
+
if (listeners.delete(entry) && listeners.size === 0) {
|
|
253
|
+
leave?.();
|
|
254
|
+
leave = undefined;
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
},
|
|
258
|
+
};
|
|
259
|
+
return storage;
|
|
260
|
+
}
|