@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,759 @@
|
|
|
1
|
+
// src/adapters/memory.ts
|
|
2
|
+
function memoryStorage() {
|
|
3
|
+
const values = /* @__PURE__ */ new Map();
|
|
4
|
+
return {
|
|
5
|
+
name: "memory",
|
|
6
|
+
scope: "memory",
|
|
7
|
+
capabilities: {
|
|
8
|
+
synchronous: true,
|
|
9
|
+
persistent: false,
|
|
10
|
+
reactive: false,
|
|
11
|
+
crossContext: false,
|
|
12
|
+
expiration: false,
|
|
13
|
+
transactions: false
|
|
14
|
+
},
|
|
15
|
+
get: (key) => values.get(key) ?? null,
|
|
16
|
+
set: (key, value) => void values.set(key, value),
|
|
17
|
+
remove: (key) => void values.delete(key),
|
|
18
|
+
has: (key) => values.has(key),
|
|
19
|
+
keys: () => [...values.keys()],
|
|
20
|
+
clear: () => values.clear()
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// src/core/errors.ts
|
|
25
|
+
var StorageError = class extends Error {
|
|
26
|
+
constructor(code, message, options = {}) {
|
|
27
|
+
super(message);
|
|
28
|
+
this.name = new.target.name;
|
|
29
|
+
this.code = code;
|
|
30
|
+
this.key = options.key;
|
|
31
|
+
if (options.cause !== void 0) this.cause = options.cause;
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
var StorageSerializationError = class extends StorageError {
|
|
35
|
+
constructor(message, options = {}) {
|
|
36
|
+
super("serialization", message, options);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
var StorageDeserializationError = class extends StorageError {
|
|
40
|
+
constructor(message, options = {}) {
|
|
41
|
+
super("deserialization", message, options);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
var StorageEncryptionError = class extends StorageError {
|
|
45
|
+
};
|
|
46
|
+
var StorageEncryptionKeyError = class extends StorageEncryptionError {
|
|
47
|
+
constructor(message, options = {}) {
|
|
48
|
+
super("encryption-key", message, options);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
var StorageEncryptionMetadataError = class extends StorageEncryptionError {
|
|
52
|
+
constructor(message, options = {}) {
|
|
53
|
+
super("encryption-metadata", message, options);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
var StorageEncryptionVersionError = class extends StorageEncryptionError {
|
|
57
|
+
constructor(message, options = {}) {
|
|
58
|
+
super("encryption-version", message, options);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
var StorageEncryptionAlgorithmError = class extends StorageEncryptionError {
|
|
62
|
+
constructor(message, options = {}) {
|
|
63
|
+
super("encryption-algorithm", message, options);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
var StorageDecryptionError = class extends StorageEncryptionError {
|
|
67
|
+
constructor(message, options = {}) {
|
|
68
|
+
super("decryption", message, options);
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
var StorageEncryptedPayloadError = class extends StorageEncryptionError {
|
|
72
|
+
constructor(message, options = {}) {
|
|
73
|
+
super("encryption-payload", message, options);
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
var StorageEncryptionUnavailableError = class extends StorageEncryptionError {
|
|
77
|
+
constructor(message, options = {}) {
|
|
78
|
+
super("encryption-unavailable", message, options);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// src/serialization/json.ts
|
|
83
|
+
function assertJson(value, path, seen) {
|
|
84
|
+
switch (typeof value) {
|
|
85
|
+
case "string":
|
|
86
|
+
case "boolean":
|
|
87
|
+
return;
|
|
88
|
+
case "number":
|
|
89
|
+
if (!Number.isFinite(value)) throw new StorageSerializationError(`${path} is ${value}, which JSON cannot hold.`);
|
|
90
|
+
return;
|
|
91
|
+
case "object":
|
|
92
|
+
break;
|
|
93
|
+
default:
|
|
94
|
+
throw new StorageSerializationError(`${path} is a ${typeof value}, which JSON cannot hold.`);
|
|
95
|
+
}
|
|
96
|
+
if (value === null) return;
|
|
97
|
+
if (seen.has(value)) throw new StorageSerializationError(`${path} refers back to itself.`);
|
|
98
|
+
seen.add(value);
|
|
99
|
+
if (Array.isArray(value)) {
|
|
100
|
+
value.forEach((item, i) => assertJson(item, `${path}[${i}]`, seen));
|
|
101
|
+
} else {
|
|
102
|
+
const proto = Object.getPrototypeOf(value);
|
|
103
|
+
if (proto !== Object.prototype && proto !== null) {
|
|
104
|
+
const name = value.constructor?.name ?? "an object with a prototype";
|
|
105
|
+
throw new StorageSerializationError(`${path} is ${name}, which does not survive JSON. Convert it to plain data first.`);
|
|
106
|
+
}
|
|
107
|
+
for (const [key, item] of Object.entries(value)) {
|
|
108
|
+
if (item === void 0) throw new StorageSerializationError(`${path}.${key} is undefined, which JSON drops. Omit the key or use null.`);
|
|
109
|
+
assertJson(item, `${path}.${key}`, seen);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
seen.delete(value);
|
|
113
|
+
}
|
|
114
|
+
var jsonSerializer = {
|
|
115
|
+
serialize(value) {
|
|
116
|
+
assertJson(value, "value", /* @__PURE__ */ new Set());
|
|
117
|
+
return JSON.stringify(value);
|
|
118
|
+
},
|
|
119
|
+
deserialize(text) {
|
|
120
|
+
return JSON.parse(text);
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
// src/utils/keys.ts
|
|
125
|
+
function namespacePrefix(namespace) {
|
|
126
|
+
return namespace ? `${namespace}:` : "";
|
|
127
|
+
}
|
|
128
|
+
function joinNamespace(parent, child) {
|
|
129
|
+
if (!child) throw new TypeError("A namespace needs a name.");
|
|
130
|
+
return parent ? `${parent}:${child}` : child;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// src/encryption/base64.ts
|
|
134
|
+
function toBase64(bytes) {
|
|
135
|
+
let binary = "";
|
|
136
|
+
for (let i = 0; i < bytes.length; i += 32768) binary += String.fromCharCode(...bytes.subarray(i, i + 32768));
|
|
137
|
+
return btoa(binary);
|
|
138
|
+
}
|
|
139
|
+
function fromBase64(text) {
|
|
140
|
+
if (text.length % 4 !== 0 || !/^[A-Za-z0-9+/]*={0,2}$/.test(text)) throw new TypeError("Not base64.");
|
|
141
|
+
const binary = atob(text);
|
|
142
|
+
const out = new Uint8Array(binary.length);
|
|
143
|
+
for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i);
|
|
144
|
+
return out;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// src/encryption/seal.ts
|
|
148
|
+
var PAYLOAD_VERSION = 1;
|
|
149
|
+
var encoder = new TextEncoder();
|
|
150
|
+
var decoder = new TextDecoder("utf-8", { fatal: true });
|
|
151
|
+
var aad = (storedKey, alg, kid, meta) => encoder.encode(JSON.stringify(["fx-storage", PAYLOAD_VERSION, alg, storedKey, meta.createdAt, meta.expiresAt ?? null, meta.version ?? null, kid ?? null]));
|
|
152
|
+
async function keyFor(sealer, context) {
|
|
153
|
+
if (!sealer.keyProvider) {
|
|
154
|
+
throw new StorageEncryptionKeyError(`${context.key} is encrypted, and this storage has no encryption key provider.`, { key: context.key });
|
|
155
|
+
}
|
|
156
|
+
let result;
|
|
157
|
+
try {
|
|
158
|
+
result = await sealer.keyProvider.getKey(context);
|
|
159
|
+
} catch (error) {
|
|
160
|
+
if (error instanceof StorageEncryptionError) throw error;
|
|
161
|
+
throw new StorageEncryptionKeyError(`The key provider failed for ${context.key}.`, { key: context.key, cause: error });
|
|
162
|
+
}
|
|
163
|
+
const pair = result && "key" in result && "id" in result ? result : { key: result };
|
|
164
|
+
const algorithm = pair.key?.algorithm;
|
|
165
|
+
if (!algorithm || algorithm.name !== "AES-GCM" || algorithm.length !== 256) {
|
|
166
|
+
throw new StorageEncryptionKeyError(`The key provider must return an AES-GCM 256-bit key for ${context.key}.`, { key: context.key });
|
|
167
|
+
}
|
|
168
|
+
if (!pair.key.usages.includes(context.operation)) {
|
|
169
|
+
throw new StorageEncryptionKeyError(`The key for ${context.key} does not allow ${context.operation}.`, { key: context.key });
|
|
170
|
+
}
|
|
171
|
+
return pair;
|
|
172
|
+
}
|
|
173
|
+
async function seal(sealer, context, storedKey, plaintext, meta) {
|
|
174
|
+
const { key, id } = await keyFor(sealer, { ...context, operation: "encrypt" });
|
|
175
|
+
const alg = sealer.provider.algorithm;
|
|
176
|
+
const out = await sealer.provider.encrypt(encoder.encode(plaintext), key, aad(storedKey, alg, id, meta));
|
|
177
|
+
const payload = { v: PAYLOAD_VERSION, alg, iv: toBase64(out.iv), data: toBase64(out.ciphertext), tag: toBase64(out.tag) };
|
|
178
|
+
if (id !== void 0) payload.kid = id;
|
|
179
|
+
return payload;
|
|
180
|
+
}
|
|
181
|
+
async function open(sealer, context, storedKey, payload, meta) {
|
|
182
|
+
const k = context.key;
|
|
183
|
+
const p = payload;
|
|
184
|
+
if (!p || typeof p !== "object" || typeof p.v !== "number") {
|
|
185
|
+
throw new StorageEncryptionMetadataError(`The encryption metadata of ${k} is missing its version.`, { key: k });
|
|
186
|
+
}
|
|
187
|
+
if (p.v !== PAYLOAD_VERSION) throw new StorageEncryptionVersionError(`${k} uses encryption format ${p.v}, which this release cannot read.`, { key: k });
|
|
188
|
+
if (typeof p.alg !== "string" || typeof p.iv !== "string" || typeof p.data !== "string" || typeof p.tag !== "string" || p.kid !== void 0 && typeof p.kid !== "string") {
|
|
189
|
+
throw new StorageEncryptionMetadataError(`The encryption metadata of ${k} is malformed.`, { key: k });
|
|
190
|
+
}
|
|
191
|
+
if (p.alg !== sealer.provider.algorithm) {
|
|
192
|
+
throw new StorageEncryptionAlgorithmError(`${k} is encrypted with ${p.alg}; the provider implements ${sealer.provider.algorithm}.`, { key: k });
|
|
193
|
+
}
|
|
194
|
+
let iv, ciphertext, tag;
|
|
195
|
+
try {
|
|
196
|
+
iv = fromBase64(p.iv);
|
|
197
|
+
ciphertext = fromBase64(p.data);
|
|
198
|
+
tag = fromBase64(p.tag);
|
|
199
|
+
} catch (error) {
|
|
200
|
+
throw new StorageEncryptedPayloadError(`The encrypted payload of ${k} is not valid base64.`, { key: k, cause: error });
|
|
201
|
+
}
|
|
202
|
+
if (iv.length !== 12 || tag.length !== 16) throw new StorageEncryptedPayloadError(`The encrypted payload of ${k} has a wrong IV or tag length.`, { key: k });
|
|
203
|
+
const { key } = await keyFor(sealer, { ...context, operation: "decrypt", keyId: p.kid });
|
|
204
|
+
let bytes;
|
|
205
|
+
try {
|
|
206
|
+
bytes = await sealer.provider.decrypt({ algorithm: p.alg, iv, ciphertext, tag }, key, aad(storedKey, p.alg, p.kid, meta));
|
|
207
|
+
} catch (error) {
|
|
208
|
+
throw new StorageDecryptionError(`${k} failed authentication: wrong key, or the stored entry was changed.`, { key: k, cause: error });
|
|
209
|
+
}
|
|
210
|
+
try {
|
|
211
|
+
return decoder.decode(bytes);
|
|
212
|
+
} catch (error) {
|
|
213
|
+
throw new StorageEncryptedPayloadError(`The decrypted value of ${k} is not UTF-8.`, { key: k, cause: error });
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// src/encryption/web-crypto.ts
|
|
218
|
+
var IV_BYTES = 12;
|
|
219
|
+
var TAG_BYTES = 16;
|
|
220
|
+
function webCryptoProvider(crypto) {
|
|
221
|
+
const resolve = () => {
|
|
222
|
+
const c = crypto ?? globalThis.crypto;
|
|
223
|
+
if (!c?.subtle || !c.getRandomValues) {
|
|
224
|
+
throw new StorageEncryptionUnavailableError("Web Crypto is not available in this runtime; pass a provider in the encryption options.");
|
|
225
|
+
}
|
|
226
|
+
return c;
|
|
227
|
+
};
|
|
228
|
+
return {
|
|
229
|
+
algorithm: "AES-256-GCM",
|
|
230
|
+
async encrypt(data, key, aad2) {
|
|
231
|
+
const c = resolve();
|
|
232
|
+
const iv = c.getRandomValues(new Uint8Array(IV_BYTES));
|
|
233
|
+
const params = { name: "AES-GCM", iv, tagLength: TAG_BYTES * 8 };
|
|
234
|
+
if (aad2) params.additionalData = aad2;
|
|
235
|
+
const sealed = new Uint8Array(await c.subtle.encrypt(params, key, data));
|
|
236
|
+
return {
|
|
237
|
+
algorithm: "AES-256-GCM",
|
|
238
|
+
iv,
|
|
239
|
+
ciphertext: sealed.slice(0, sealed.length - TAG_BYTES),
|
|
240
|
+
tag: sealed.slice(sealed.length - TAG_BYTES)
|
|
241
|
+
};
|
|
242
|
+
},
|
|
243
|
+
async decrypt(data, key, aad2) {
|
|
244
|
+
const c = resolve();
|
|
245
|
+
const sealed = new Uint8Array(data.ciphertext.length + data.tag.length);
|
|
246
|
+
sealed.set(data.ciphertext);
|
|
247
|
+
sealed.set(data.tag, data.ciphertext.length);
|
|
248
|
+
const params = { name: "AES-GCM", iv: data.iv, tagLength: TAG_BYTES * 8 };
|
|
249
|
+
if (aad2) params.additionalData = aad2;
|
|
250
|
+
return new Uint8Array(await c.subtle.decrypt(params, key, sealed));
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// src/core/envelope.ts
|
|
256
|
+
function isEnvelope(x) {
|
|
257
|
+
return !!x && typeof x === "object" && x.v === 1 && ("value" in x || "enc" in x) && typeof x.createdAt === "number";
|
|
258
|
+
}
|
|
259
|
+
function serializeOrThrow(serializer, key, value) {
|
|
260
|
+
try {
|
|
261
|
+
return serializer.serialize(value);
|
|
262
|
+
} catch (error) {
|
|
263
|
+
if (error instanceof StorageError) throw Object.assign(error, { key });
|
|
264
|
+
throw new StorageSerializationError(`The value of ${key} could not be serialized.`, { key, cause: error });
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
function deserializeOrThrow(serializer, key, text) {
|
|
268
|
+
try {
|
|
269
|
+
return serializer.deserialize(text);
|
|
270
|
+
} catch (error) {
|
|
271
|
+
throw new StorageDeserializationError(`The stored value of ${key} could not be read.`, { key, cause: error });
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
function decodeEnvelope(serializer, key, text) {
|
|
275
|
+
const parsed = deserializeOrThrow(serializer, key, text);
|
|
276
|
+
return isEnvelope(parsed) ? parsed : { v: 1, value: parsed, createdAt: 0 };
|
|
277
|
+
}
|
|
278
|
+
var isExpired = (envelope, now) => envelope.expiresAt !== void 0 && envelope.expiresAt <= now;
|
|
279
|
+
function latestVersion(steps) {
|
|
280
|
+
if (!steps) return void 0;
|
|
281
|
+
const versions = Object.keys(steps).map(Number).filter(Number.isFinite);
|
|
282
|
+
return versions.length ? Math.max(...versions) : void 0;
|
|
283
|
+
}
|
|
284
|
+
function migrate(steps, from, value) {
|
|
285
|
+
const latest = latestVersion(steps);
|
|
286
|
+
if (!steps || latest === void 0 || (from ?? 0) >= latest) return void 0;
|
|
287
|
+
for (const version of Object.keys(steps).map(Number).sort((a, b) => a - b)) {
|
|
288
|
+
if (version > (from ?? 0)) value = steps[version](value);
|
|
289
|
+
}
|
|
290
|
+
return { value, version: latest };
|
|
291
|
+
}
|
|
292
|
+
function metaFor(createdAt, ttl, version) {
|
|
293
|
+
const meta = { createdAt };
|
|
294
|
+
if (ttl !== void 0) meta.expiresAt = createdAt + ttl;
|
|
295
|
+
if (version !== void 0) meta.version = version;
|
|
296
|
+
return meta;
|
|
297
|
+
}
|
|
298
|
+
function toEntry(envelope, value) {
|
|
299
|
+
const entry = { value, createdAt: envelope.createdAt };
|
|
300
|
+
if (envelope.expiresAt !== void 0) entry.expiresAt = envelope.expiresAt;
|
|
301
|
+
if (envelope.version !== void 0) entry.version = envelope.version;
|
|
302
|
+
if (envelope.enc) entry.encrypted = true;
|
|
303
|
+
return entry;
|
|
304
|
+
}
|
|
305
|
+
var globToRegExp = (glob) => new RegExp(`^${glob.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".")}$`);
|
|
306
|
+
function selectItems(items, query) {
|
|
307
|
+
const pattern = typeof query.match === "string" ? globToRegExp(query.match) : query.match;
|
|
308
|
+
const out = [];
|
|
309
|
+
for (const item of items) {
|
|
310
|
+
if (query.prefix !== void 0 && !item.key.startsWith(query.prefix)) continue;
|
|
311
|
+
if (pattern && !pattern.test(item.key)) continue;
|
|
312
|
+
if (query.where && !query.where(item)) continue;
|
|
313
|
+
out.push(item);
|
|
314
|
+
if (query.limit !== void 0 && out.length >= query.limit) break;
|
|
315
|
+
}
|
|
316
|
+
return out;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// src/core/bus.ts
|
|
320
|
+
var buses = /* @__PURE__ */ new WeakMap();
|
|
321
|
+
function joinBus(adapter, subscriber) {
|
|
322
|
+
let bus = buses.get(adapter);
|
|
323
|
+
if (!bus) buses.set(adapter, bus = { subscribers: /* @__PURE__ */ new Set() });
|
|
324
|
+
const current = bus;
|
|
325
|
+
current.subscribers.add(subscriber);
|
|
326
|
+
if (current.subscribers.size === 1 && adapter.subscribe) {
|
|
327
|
+
current.stop = adapter.subscribe((change) => {
|
|
328
|
+
for (const s of [...current.subscribers]) s.external(change);
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
return () => {
|
|
332
|
+
current.subscribers.delete(subscriber);
|
|
333
|
+
if (current.subscribers.size === 0) {
|
|
334
|
+
current.stop?.();
|
|
335
|
+
current.stop = void 0;
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
function publish(adapter, type, key, prefix, value) {
|
|
340
|
+
const bus = buses.get(adapter);
|
|
341
|
+
if (!bus) return;
|
|
342
|
+
for (const s of [...bus.subscribers]) {
|
|
343
|
+
if (key === null ? s.prefix.startsWith(prefix) : key.startsWith(s.prefix)) s.local(type, key, value);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// src/core/async-storage.ts
|
|
348
|
+
function createAsyncStorage(options = {}) {
|
|
349
|
+
const adapter = options.adapter ?? memoryStorage();
|
|
350
|
+
const serializer = options.serializer ?? jsonSerializer;
|
|
351
|
+
const now = options.now ?? Date.now;
|
|
352
|
+
const migrations = options.migrations ?? {};
|
|
353
|
+
const prefix = namespacePrefix(options.namespace);
|
|
354
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
355
|
+
let leave;
|
|
356
|
+
const sealer = {
|
|
357
|
+
keyProvider: options.encryption?.keyProvider,
|
|
358
|
+
provider: options.encryption?.provider ?? webCryptoProvider()
|
|
359
|
+
};
|
|
360
|
+
const context = (key) => options.namespace ? { key, namespace: options.namespace } : { key };
|
|
361
|
+
const deliver = (event) => {
|
|
362
|
+
for (const { key, fn } of [...listeners]) if (key === void 0 || event.key === null || key === event.key) fn(event);
|
|
363
|
+
};
|
|
364
|
+
const emit = (event) => publish(adapter, event.type, event.key === null ? null : prefix + event.key, prefix, event.value);
|
|
365
|
+
const store = async (key, value, meta, encrypt) => {
|
|
366
|
+
const envelope = { v: 1, ...meta };
|
|
367
|
+
if (encrypt) {
|
|
368
|
+
if (!sealer.keyProvider) throw new StorageEncryptionKeyError(`Encrypting ${key} needs encryption on the storage.`, { key });
|
|
369
|
+
envelope.enc = await seal(sealer, context(key), prefix + key, serializeOrThrow(serializer, key, value), meta);
|
|
370
|
+
} else {
|
|
371
|
+
envelope.value = value;
|
|
372
|
+
}
|
|
373
|
+
await adapter.set(prefix + key, serializeOrThrow(serializer, key, envelope), { expiresAt: meta.expiresAt });
|
|
374
|
+
};
|
|
375
|
+
const write = async (key, value, meta, encrypt) => {
|
|
376
|
+
await store(key, value, meta, encrypt);
|
|
377
|
+
emit({ type: "set", key, value, external: false });
|
|
378
|
+
};
|
|
379
|
+
const metaOf = ({ createdAt, expiresAt, version }) => {
|
|
380
|
+
const meta = { createdAt };
|
|
381
|
+
if (expiresAt !== void 0) meta.expiresAt = expiresAt;
|
|
382
|
+
if (version !== void 0) meta.version = version;
|
|
383
|
+
return meta;
|
|
384
|
+
};
|
|
385
|
+
const read = async (key) => {
|
|
386
|
+
const text = await adapter.get(prefix + key);
|
|
387
|
+
if (text === null) return null;
|
|
388
|
+
let envelope = decodeEnvelope(serializer, key, text);
|
|
389
|
+
if (isExpired(envelope, now())) {
|
|
390
|
+
await adapter.remove(prefix + key);
|
|
391
|
+
emit({ type: "expire", key, external: false });
|
|
392
|
+
return null;
|
|
393
|
+
}
|
|
394
|
+
let value = envelope.enc ? deserializeOrThrow(serializer, key, await open(sealer, context(key), prefix + key, envelope.enc, metaOf(envelope))) : envelope.value;
|
|
395
|
+
const migrated = migrate(migrations[key], envelope.version, value);
|
|
396
|
+
if (migrated) {
|
|
397
|
+
value = migrated.value;
|
|
398
|
+
const meta = { ...metaOf(envelope), version: migrated.version };
|
|
399
|
+
await store(key, value, meta, !!envelope.enc);
|
|
400
|
+
envelope = { v: 1, ...meta, ...envelope.enc ? { enc: envelope.enc } : { value } };
|
|
401
|
+
}
|
|
402
|
+
return { envelope, value };
|
|
403
|
+
};
|
|
404
|
+
const ownKeys = async () => (await adapter.keys()).filter((k) => k.startsWith(prefix)).map((k) => k.slice(prefix.length));
|
|
405
|
+
const external = (change) => {
|
|
406
|
+
if (change.key === null) return deliver({ type: "clear", key: null, external: true });
|
|
407
|
+
if (!change.key.startsWith(prefix)) return;
|
|
408
|
+
const key = change.key.slice(prefix.length);
|
|
409
|
+
if (change.value === null) return deliver({ type: "remove", key, external: true });
|
|
410
|
+
try {
|
|
411
|
+
const envelope = decodeEnvelope(serializer, key, change.value);
|
|
412
|
+
deliver({ type: "set", key, value: envelope.enc ? void 0 : envelope.value, external: true });
|
|
413
|
+
} catch {
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
const liveItems = async () => {
|
|
417
|
+
const items = [];
|
|
418
|
+
for (const key of (await ownKeys()).sort()) {
|
|
419
|
+
const current = await read(key);
|
|
420
|
+
if (current) items.push({ key, value: current.value, entry: toEntry(current.envelope, current.value) });
|
|
421
|
+
}
|
|
422
|
+
return items;
|
|
423
|
+
};
|
|
424
|
+
const storage = {
|
|
425
|
+
scope: adapter.scope,
|
|
426
|
+
keyPrefix: prefix,
|
|
427
|
+
capabilities: { ...adapter.capabilities, expiration: true },
|
|
428
|
+
encryption: !!sealer.keyProvider,
|
|
429
|
+
async get(key) {
|
|
430
|
+
return (await read(key))?.value ?? null;
|
|
431
|
+
},
|
|
432
|
+
async getEntry(key) {
|
|
433
|
+
const current = await read(key);
|
|
434
|
+
return current ? toEntry(current.envelope, current.value) : null;
|
|
435
|
+
},
|
|
436
|
+
async set(key, value, setOptions = {}) {
|
|
437
|
+
const meta = metaFor(now(), setOptions.ttl, setOptions.version ?? latestVersion(migrations[key]));
|
|
438
|
+
await write(key, value, meta, setOptions.encrypt === true);
|
|
439
|
+
},
|
|
440
|
+
entries: liveItems,
|
|
441
|
+
async search(query) {
|
|
442
|
+
return selectItems(await liveItems(), query);
|
|
443
|
+
},
|
|
444
|
+
async size() {
|
|
445
|
+
let count = 0;
|
|
446
|
+
for (const key of await ownKeys()) {
|
|
447
|
+
const text = await adapter.get(prefix + key);
|
|
448
|
+
if (text !== null && !isExpired(decodeEnvelope(serializer, key, text), now())) count++;
|
|
449
|
+
}
|
|
450
|
+
return count;
|
|
451
|
+
},
|
|
452
|
+
async prune() {
|
|
453
|
+
let removed = 0;
|
|
454
|
+
for (const key of await ownKeys()) {
|
|
455
|
+
const text = await adapter.get(prefix + key);
|
|
456
|
+
if (text === null) continue;
|
|
457
|
+
if (isExpired(decodeEnvelope(serializer, key, text), now())) {
|
|
458
|
+
await adapter.remove(prefix + key);
|
|
459
|
+
emit({ type: "expire", key, external: false });
|
|
460
|
+
removed++;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
return removed;
|
|
464
|
+
},
|
|
465
|
+
async usage() {
|
|
466
|
+
let bytes = 0;
|
|
467
|
+
const keys = await ownKeys();
|
|
468
|
+
for (const key of keys) bytes += ((prefix + key).length + ((await adapter.get(prefix + key))?.length ?? 0)) * 2;
|
|
469
|
+
return { keys: keys.length, bytes };
|
|
470
|
+
},
|
|
471
|
+
async update(key, fn, updateOptions = {}) {
|
|
472
|
+
const current = await read(key);
|
|
473
|
+
const value = await fn(current?.value ?? null);
|
|
474
|
+
const meta = metaFor(now(), void 0, current?.envelope.version ?? latestVersion(migrations[key]));
|
|
475
|
+
if (current?.envelope.expiresAt !== void 0) meta.expiresAt = current.envelope.expiresAt;
|
|
476
|
+
await write(key, value, meta, updateOptions.encrypt ?? !!current?.envelope.enc);
|
|
477
|
+
return value;
|
|
478
|
+
},
|
|
479
|
+
async getOrSet(key, factory, setOptions) {
|
|
480
|
+
const current = await read(key);
|
|
481
|
+
if (current) return current.value;
|
|
482
|
+
const value = await factory();
|
|
483
|
+
await storage.set(key, value, setOptions);
|
|
484
|
+
return value;
|
|
485
|
+
},
|
|
486
|
+
async touch(key, ttl) {
|
|
487
|
+
const current = await read(key);
|
|
488
|
+
if (!current) return false;
|
|
489
|
+
await store(key, current.value, { ...metaOf(current.envelope), expiresAt: now() + ttl }, !!current.envelope.enc);
|
|
490
|
+
return true;
|
|
491
|
+
},
|
|
492
|
+
async snapshot() {
|
|
493
|
+
const out = {};
|
|
494
|
+
for (const item of await liveItems()) out[item.key] = item.entry;
|
|
495
|
+
return out;
|
|
496
|
+
},
|
|
497
|
+
async restore(snapshot, restoreOptions = {}) {
|
|
498
|
+
let written = 0;
|
|
499
|
+
for (const [key, entry] of Object.entries(snapshot)) {
|
|
500
|
+
if (!entry) continue;
|
|
501
|
+
if (!restoreOptions.overwrite && await read(key)) continue;
|
|
502
|
+
if (entry.expiresAt !== void 0 && entry.expiresAt <= now()) continue;
|
|
503
|
+
const meta = { createdAt: entry.createdAt };
|
|
504
|
+
if (entry.expiresAt !== void 0) meta.expiresAt = entry.expiresAt;
|
|
505
|
+
if (entry.version !== void 0) meta.version = entry.version;
|
|
506
|
+
await write(key, entry.value, meta, entry.encrypted === true);
|
|
507
|
+
written++;
|
|
508
|
+
}
|
|
509
|
+
return written;
|
|
510
|
+
},
|
|
511
|
+
async remove(key) {
|
|
512
|
+
await adapter.remove(prefix + key);
|
|
513
|
+
emit({ type: "remove", key, external: false });
|
|
514
|
+
},
|
|
515
|
+
async has(key) {
|
|
516
|
+
const text = await adapter.get(prefix + key);
|
|
517
|
+
if (text === null) return false;
|
|
518
|
+
if (!isExpired(decodeEnvelope(serializer, key, text), now())) return true;
|
|
519
|
+
await adapter.remove(prefix + key);
|
|
520
|
+
emit({ type: "expire", key, external: false });
|
|
521
|
+
return false;
|
|
522
|
+
},
|
|
523
|
+
async keys() {
|
|
524
|
+
return await ownKeys();
|
|
525
|
+
},
|
|
526
|
+
async clear() {
|
|
527
|
+
if (prefix) for (const key of await ownKeys()) await adapter.remove(prefix + key);
|
|
528
|
+
else await adapter.clear();
|
|
529
|
+
emit({ type: "clear", key: null, external: false });
|
|
530
|
+
},
|
|
531
|
+
namespace(name) {
|
|
532
|
+
return createAsyncStorage({ ...options, migrations: void 0, namespace: joinNamespace(options.namespace, name) });
|
|
533
|
+
},
|
|
534
|
+
subscribe(keyOrListener, maybeListener) {
|
|
535
|
+
const entry = typeof keyOrListener === "function" ? { fn: keyOrListener } : { key: keyOrListener, fn: maybeListener };
|
|
536
|
+
listeners.add(entry);
|
|
537
|
+
if (listeners.size === 1) {
|
|
538
|
+
leave = joinBus(adapter, {
|
|
539
|
+
prefix,
|
|
540
|
+
external,
|
|
541
|
+
local(type, key, value) {
|
|
542
|
+
const event = { type, key: key === null ? null : key.slice(prefix.length), external: false };
|
|
543
|
+
if (type === "set") event.value = value;
|
|
544
|
+
deliver(event);
|
|
545
|
+
}
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
return () => {
|
|
549
|
+
if (listeners.delete(entry) && listeners.size === 0) {
|
|
550
|
+
leave?.();
|
|
551
|
+
leave = void 0;
|
|
552
|
+
}
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
};
|
|
556
|
+
return storage;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// src/core/storage.ts
|
|
560
|
+
function createStorage(options = {}) {
|
|
561
|
+
if ("encryption" in options && options.encryption) return createAsyncStorage(options);
|
|
562
|
+
return createSyncStorage(options);
|
|
563
|
+
}
|
|
564
|
+
function createSyncStorage(options = {}) {
|
|
565
|
+
const adapter = options.adapter ?? memoryStorage();
|
|
566
|
+
const serializer = options.serializer ?? jsonSerializer;
|
|
567
|
+
const now = options.now ?? Date.now;
|
|
568
|
+
const migrations = options.migrations ?? {};
|
|
569
|
+
const prefix = namespacePrefix(options.namespace);
|
|
570
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
571
|
+
let leave;
|
|
572
|
+
const deliver = (event) => {
|
|
573
|
+
for (const { key, fn } of [...listeners]) if (key === void 0 || event.key === null || key === event.key) fn(event);
|
|
574
|
+
};
|
|
575
|
+
const emit = (event) => publish(adapter, event.type, event.key === null ? null : prefix + event.key, prefix, event.value);
|
|
576
|
+
const encode = (key, envelope) => serializeOrThrow(serializer, key, envelope);
|
|
577
|
+
const valueOf = (key, envelope) => {
|
|
578
|
+
if (envelope.enc) {
|
|
579
|
+
throw new StorageEncryptionUnavailableError(`${key} is encrypted; read it through createStorage({ encryption }), which is asynchronous.`, { key });
|
|
580
|
+
}
|
|
581
|
+
return envelope.value;
|
|
582
|
+
};
|
|
583
|
+
const read = (key) => {
|
|
584
|
+
const text = adapter.get(prefix + key);
|
|
585
|
+
if (text === null) return null;
|
|
586
|
+
const envelope = decodeEnvelope(serializer, key, text);
|
|
587
|
+
if (isExpired(envelope, now())) {
|
|
588
|
+
adapter.remove(prefix + key);
|
|
589
|
+
emit({ type: "expire", key, external: false });
|
|
590
|
+
return null;
|
|
591
|
+
}
|
|
592
|
+
if (envelope.enc) return envelope;
|
|
593
|
+
const migrated = migrate(migrations[key], envelope.version, envelope.value);
|
|
594
|
+
if (!migrated) return envelope;
|
|
595
|
+
const next = { ...envelope, ...migrated };
|
|
596
|
+
adapter.set(prefix + key, encode(key, next), { expiresAt: next.expiresAt });
|
|
597
|
+
return next;
|
|
598
|
+
};
|
|
599
|
+
const ownKeys = () => adapter.keys().filter((k) => k.startsWith(prefix)).map((k) => k.slice(prefix.length));
|
|
600
|
+
const external = (change) => {
|
|
601
|
+
if (change.key === null) return deliver({ type: "clear", key: null, external: true });
|
|
602
|
+
if (!change.key.startsWith(prefix)) return;
|
|
603
|
+
const key = change.key.slice(prefix.length);
|
|
604
|
+
if (change.value === null) return deliver({ type: "remove", key, external: true });
|
|
605
|
+
try {
|
|
606
|
+
const envelope = decodeEnvelope(serializer, key, change.value);
|
|
607
|
+
deliver({ type: "set", key, value: envelope.enc ? void 0 : envelope.value, external: true });
|
|
608
|
+
} catch {
|
|
609
|
+
}
|
|
610
|
+
};
|
|
611
|
+
const liveItems = () => {
|
|
612
|
+
const items = [];
|
|
613
|
+
for (const key of ownKeys().sort()) {
|
|
614
|
+
const envelope = read(key);
|
|
615
|
+
if (!envelope) continue;
|
|
616
|
+
const value = valueOf(key, envelope);
|
|
617
|
+
items.push({ key, value, entry: toEntry(envelope, value) });
|
|
618
|
+
}
|
|
619
|
+
return items;
|
|
620
|
+
};
|
|
621
|
+
const write = (key, value, meta) => {
|
|
622
|
+
adapter.set(prefix + key, encode(key, { v: 1, value, ...meta }), { expiresAt: meta.expiresAt });
|
|
623
|
+
emit({ type: "set", key, value, external: false });
|
|
624
|
+
};
|
|
625
|
+
const storage = {
|
|
626
|
+
scope: adapter.scope,
|
|
627
|
+
keyPrefix: prefix,
|
|
628
|
+
// The storage expires entries itself, whatever the adapter can do.
|
|
629
|
+
capabilities: { ...adapter.capabilities, expiration: true },
|
|
630
|
+
get(key) {
|
|
631
|
+
const envelope = read(key);
|
|
632
|
+
return envelope ? valueOf(key, envelope) ?? null : null;
|
|
633
|
+
},
|
|
634
|
+
getEntry(key) {
|
|
635
|
+
const envelope = read(key);
|
|
636
|
+
return envelope ? toEntry(envelope, valueOf(key, envelope)) : null;
|
|
637
|
+
},
|
|
638
|
+
set(key, value, setOptions = {}) {
|
|
639
|
+
write(key, value, metaFor(now(), setOptions.ttl, setOptions.version ?? latestVersion(migrations[key])));
|
|
640
|
+
},
|
|
641
|
+
entries() {
|
|
642
|
+
return liveItems();
|
|
643
|
+
},
|
|
644
|
+
search(query) {
|
|
645
|
+
return selectItems(liveItems(), query);
|
|
646
|
+
},
|
|
647
|
+
size() {
|
|
648
|
+
return ownKeys().filter((key) => read(key) !== null).length;
|
|
649
|
+
},
|
|
650
|
+
prune() {
|
|
651
|
+
let removed = 0;
|
|
652
|
+
for (const key of ownKeys()) {
|
|
653
|
+
const text = adapter.get(prefix + key);
|
|
654
|
+
if (text === null) continue;
|
|
655
|
+
if (isExpired(decodeEnvelope(serializer, key, text), now())) {
|
|
656
|
+
adapter.remove(prefix + key);
|
|
657
|
+
emit({ type: "expire", key, external: false });
|
|
658
|
+
removed++;
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
return removed;
|
|
662
|
+
},
|
|
663
|
+
usage() {
|
|
664
|
+
let bytes = 0;
|
|
665
|
+
const keys = ownKeys();
|
|
666
|
+
for (const key of keys) bytes += ((prefix + key).length + (adapter.get(prefix + key)?.length ?? 0)) * 2;
|
|
667
|
+
return { keys: keys.length, bytes };
|
|
668
|
+
},
|
|
669
|
+
update(key, fn) {
|
|
670
|
+
const current = read(key);
|
|
671
|
+
const value = fn(current ? valueOf(key, current) : null);
|
|
672
|
+
const meta = metaFor(now(), void 0, current?.version ?? latestVersion(migrations[key]));
|
|
673
|
+
if (current?.expiresAt !== void 0) meta.expiresAt = current.expiresAt;
|
|
674
|
+
write(key, value, meta);
|
|
675
|
+
return value;
|
|
676
|
+
},
|
|
677
|
+
getOrSet(key, factory, setOptions) {
|
|
678
|
+
const current = read(key);
|
|
679
|
+
if (current) return valueOf(key, current);
|
|
680
|
+
const value = factory();
|
|
681
|
+
storage.set(key, value, setOptions);
|
|
682
|
+
return value;
|
|
683
|
+
},
|
|
684
|
+
touch(key, ttl) {
|
|
685
|
+
const current = read(key);
|
|
686
|
+
if (!current) return false;
|
|
687
|
+
valueOf(key, current);
|
|
688
|
+
const expiresAt = now() + ttl;
|
|
689
|
+
adapter.set(prefix + key, encode(key, { ...current, expiresAt }), { expiresAt });
|
|
690
|
+
return true;
|
|
691
|
+
},
|
|
692
|
+
snapshot() {
|
|
693
|
+
const out = {};
|
|
694
|
+
for (const item of liveItems()) out[item.key] = item.entry;
|
|
695
|
+
return out;
|
|
696
|
+
},
|
|
697
|
+
restore(snapshot, restoreOptions = {}) {
|
|
698
|
+
let written = 0;
|
|
699
|
+
for (const [key, entry] of Object.entries(snapshot)) {
|
|
700
|
+
if (!entry) continue;
|
|
701
|
+
if (entry.encrypted) {
|
|
702
|
+
throw new StorageEncryptionUnavailableError(`${key} was encrypted; restore it through createStorage({ encryption }).`, { key });
|
|
703
|
+
}
|
|
704
|
+
if (!restoreOptions.overwrite && read(key)) continue;
|
|
705
|
+
if (entry.expiresAt !== void 0 && entry.expiresAt <= now()) continue;
|
|
706
|
+
const meta = { createdAt: entry.createdAt };
|
|
707
|
+
if (entry.expiresAt !== void 0) meta.expiresAt = entry.expiresAt;
|
|
708
|
+
if (entry.version !== void 0) meta.version = entry.version;
|
|
709
|
+
write(key, entry.value, meta);
|
|
710
|
+
written++;
|
|
711
|
+
}
|
|
712
|
+
return written;
|
|
713
|
+
},
|
|
714
|
+
remove(key) {
|
|
715
|
+
adapter.remove(prefix + key);
|
|
716
|
+
emit({ type: "remove", key, external: false });
|
|
717
|
+
},
|
|
718
|
+
has(key) {
|
|
719
|
+
return read(key) !== null;
|
|
720
|
+
},
|
|
721
|
+
keys() {
|
|
722
|
+
return ownKeys();
|
|
723
|
+
},
|
|
724
|
+
clear() {
|
|
725
|
+
if (prefix) for (const key of ownKeys()) adapter.remove(prefix + key);
|
|
726
|
+
else adapter.clear();
|
|
727
|
+
emit({ type: "clear", key: null, external: false });
|
|
728
|
+
},
|
|
729
|
+
namespace(name) {
|
|
730
|
+
return createSyncStorage({ adapter, serializer, now, namespace: joinNamespace(options.namespace, name) });
|
|
731
|
+
},
|
|
732
|
+
subscribe(keyOrListener, maybeListener) {
|
|
733
|
+
const entry = typeof keyOrListener === "function" ? { fn: keyOrListener } : { key: keyOrListener, fn: maybeListener };
|
|
734
|
+
listeners.add(entry);
|
|
735
|
+
if (listeners.size === 1) {
|
|
736
|
+
leave = joinBus(adapter, {
|
|
737
|
+
prefix,
|
|
738
|
+
external,
|
|
739
|
+
local(type, key, value) {
|
|
740
|
+
const event = { type, key: key === null ? null : key.slice(prefix.length), external: false };
|
|
741
|
+
if (type === "set") event.value = value;
|
|
742
|
+
deliver(event);
|
|
743
|
+
}
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
return () => {
|
|
747
|
+
if (listeners.delete(entry) && listeners.size === 0) {
|
|
748
|
+
leave?.();
|
|
749
|
+
leave = void 0;
|
|
750
|
+
}
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
};
|
|
754
|
+
return storage;
|
|
755
|
+
}
|
|
756
|
+
export {
|
|
757
|
+
createStorage,
|
|
758
|
+
createSyncStorage
|
|
759
|
+
};
|