@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,583 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/core/async-storage.ts
|
|
21
|
+
var async_storage_exports = {};
|
|
22
|
+
__export(async_storage_exports, {
|
|
23
|
+
createAsyncStorage: () => createAsyncStorage
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(async_storage_exports);
|
|
26
|
+
|
|
27
|
+
// src/adapters/memory.ts
|
|
28
|
+
function memoryStorage() {
|
|
29
|
+
const values = /* @__PURE__ */ new Map();
|
|
30
|
+
return {
|
|
31
|
+
name: "memory",
|
|
32
|
+
scope: "memory",
|
|
33
|
+
capabilities: {
|
|
34
|
+
synchronous: true,
|
|
35
|
+
persistent: false,
|
|
36
|
+
reactive: false,
|
|
37
|
+
crossContext: false,
|
|
38
|
+
expiration: false,
|
|
39
|
+
transactions: false
|
|
40
|
+
},
|
|
41
|
+
get: (key) => values.get(key) ?? null,
|
|
42
|
+
set: (key, value) => void values.set(key, value),
|
|
43
|
+
remove: (key) => void values.delete(key),
|
|
44
|
+
has: (key) => values.has(key),
|
|
45
|
+
keys: () => [...values.keys()],
|
|
46
|
+
clear: () => values.clear()
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// src/core/errors.ts
|
|
51
|
+
var StorageError = class extends Error {
|
|
52
|
+
constructor(code, message, options = {}) {
|
|
53
|
+
super(message);
|
|
54
|
+
this.name = new.target.name;
|
|
55
|
+
this.code = code;
|
|
56
|
+
this.key = options.key;
|
|
57
|
+
if (options.cause !== void 0) this.cause = options.cause;
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
var StorageSerializationError = class extends StorageError {
|
|
61
|
+
constructor(message, options = {}) {
|
|
62
|
+
super("serialization", message, options);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
var StorageDeserializationError = class extends StorageError {
|
|
66
|
+
constructor(message, options = {}) {
|
|
67
|
+
super("deserialization", message, options);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
var StorageEncryptionError = class extends StorageError {
|
|
71
|
+
};
|
|
72
|
+
var StorageEncryptionKeyError = class extends StorageEncryptionError {
|
|
73
|
+
constructor(message, options = {}) {
|
|
74
|
+
super("encryption-key", message, options);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
var StorageEncryptionMetadataError = class extends StorageEncryptionError {
|
|
78
|
+
constructor(message, options = {}) {
|
|
79
|
+
super("encryption-metadata", message, options);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
var StorageEncryptionVersionError = class extends StorageEncryptionError {
|
|
83
|
+
constructor(message, options = {}) {
|
|
84
|
+
super("encryption-version", message, options);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
var StorageEncryptionAlgorithmError = class extends StorageEncryptionError {
|
|
88
|
+
constructor(message, options = {}) {
|
|
89
|
+
super("encryption-algorithm", message, options);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
var StorageDecryptionError = class extends StorageEncryptionError {
|
|
93
|
+
constructor(message, options = {}) {
|
|
94
|
+
super("decryption", message, options);
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
var StorageEncryptedPayloadError = class extends StorageEncryptionError {
|
|
98
|
+
constructor(message, options = {}) {
|
|
99
|
+
super("encryption-payload", message, options);
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
var StorageEncryptionUnavailableError = class extends StorageEncryptionError {
|
|
103
|
+
constructor(message, options = {}) {
|
|
104
|
+
super("encryption-unavailable", message, options);
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// src/encryption/base64.ts
|
|
109
|
+
function toBase64(bytes) {
|
|
110
|
+
let binary = "";
|
|
111
|
+
for (let i = 0; i < bytes.length; i += 32768) binary += String.fromCharCode(...bytes.subarray(i, i + 32768));
|
|
112
|
+
return btoa(binary);
|
|
113
|
+
}
|
|
114
|
+
function fromBase64(text) {
|
|
115
|
+
if (text.length % 4 !== 0 || !/^[A-Za-z0-9+/]*={0,2}$/.test(text)) throw new TypeError("Not base64.");
|
|
116
|
+
const binary = atob(text);
|
|
117
|
+
const out = new Uint8Array(binary.length);
|
|
118
|
+
for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i);
|
|
119
|
+
return out;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// src/encryption/seal.ts
|
|
123
|
+
var PAYLOAD_VERSION = 1;
|
|
124
|
+
var encoder = new TextEncoder();
|
|
125
|
+
var decoder = new TextDecoder("utf-8", { fatal: true });
|
|
126
|
+
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]));
|
|
127
|
+
async function keyFor(sealer, context) {
|
|
128
|
+
if (!sealer.keyProvider) {
|
|
129
|
+
throw new StorageEncryptionKeyError(`${context.key} is encrypted, and this storage has no encryption key provider.`, { key: context.key });
|
|
130
|
+
}
|
|
131
|
+
let result;
|
|
132
|
+
try {
|
|
133
|
+
result = await sealer.keyProvider.getKey(context);
|
|
134
|
+
} catch (error) {
|
|
135
|
+
if (error instanceof StorageEncryptionError) throw error;
|
|
136
|
+
throw new StorageEncryptionKeyError(`The key provider failed for ${context.key}.`, { key: context.key, cause: error });
|
|
137
|
+
}
|
|
138
|
+
const pair = result && "key" in result && "id" in result ? result : { key: result };
|
|
139
|
+
const algorithm = pair.key?.algorithm;
|
|
140
|
+
if (!algorithm || algorithm.name !== "AES-GCM" || algorithm.length !== 256) {
|
|
141
|
+
throw new StorageEncryptionKeyError(`The key provider must return an AES-GCM 256-bit key for ${context.key}.`, { key: context.key });
|
|
142
|
+
}
|
|
143
|
+
if (!pair.key.usages.includes(context.operation)) {
|
|
144
|
+
throw new StorageEncryptionKeyError(`The key for ${context.key} does not allow ${context.operation}.`, { key: context.key });
|
|
145
|
+
}
|
|
146
|
+
return pair;
|
|
147
|
+
}
|
|
148
|
+
async function seal(sealer, context, storedKey, plaintext, meta) {
|
|
149
|
+
const { key, id } = await keyFor(sealer, { ...context, operation: "encrypt" });
|
|
150
|
+
const alg = sealer.provider.algorithm;
|
|
151
|
+
const out = await sealer.provider.encrypt(encoder.encode(plaintext), key, aad(storedKey, alg, id, meta));
|
|
152
|
+
const payload = { v: PAYLOAD_VERSION, alg, iv: toBase64(out.iv), data: toBase64(out.ciphertext), tag: toBase64(out.tag) };
|
|
153
|
+
if (id !== void 0) payload.kid = id;
|
|
154
|
+
return payload;
|
|
155
|
+
}
|
|
156
|
+
async function open(sealer, context, storedKey, payload, meta) {
|
|
157
|
+
const k = context.key;
|
|
158
|
+
const p = payload;
|
|
159
|
+
if (!p || typeof p !== "object" || typeof p.v !== "number") {
|
|
160
|
+
throw new StorageEncryptionMetadataError(`The encryption metadata of ${k} is missing its version.`, { key: k });
|
|
161
|
+
}
|
|
162
|
+
if (p.v !== PAYLOAD_VERSION) throw new StorageEncryptionVersionError(`${k} uses encryption format ${p.v}, which this release cannot read.`, { key: k });
|
|
163
|
+
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") {
|
|
164
|
+
throw new StorageEncryptionMetadataError(`The encryption metadata of ${k} is malformed.`, { key: k });
|
|
165
|
+
}
|
|
166
|
+
if (p.alg !== sealer.provider.algorithm) {
|
|
167
|
+
throw new StorageEncryptionAlgorithmError(`${k} is encrypted with ${p.alg}; the provider implements ${sealer.provider.algorithm}.`, { key: k });
|
|
168
|
+
}
|
|
169
|
+
let iv, ciphertext, tag;
|
|
170
|
+
try {
|
|
171
|
+
iv = fromBase64(p.iv);
|
|
172
|
+
ciphertext = fromBase64(p.data);
|
|
173
|
+
tag = fromBase64(p.tag);
|
|
174
|
+
} catch (error) {
|
|
175
|
+
throw new StorageEncryptedPayloadError(`The encrypted payload of ${k} is not valid base64.`, { key: k, cause: error });
|
|
176
|
+
}
|
|
177
|
+
if (iv.length !== 12 || tag.length !== 16) throw new StorageEncryptedPayloadError(`The encrypted payload of ${k} has a wrong IV or tag length.`, { key: k });
|
|
178
|
+
const { key } = await keyFor(sealer, { ...context, operation: "decrypt", keyId: p.kid });
|
|
179
|
+
let bytes;
|
|
180
|
+
try {
|
|
181
|
+
bytes = await sealer.provider.decrypt({ algorithm: p.alg, iv, ciphertext, tag }, key, aad(storedKey, p.alg, p.kid, meta));
|
|
182
|
+
} catch (error) {
|
|
183
|
+
throw new StorageDecryptionError(`${k} failed authentication: wrong key, or the stored entry was changed.`, { key: k, cause: error });
|
|
184
|
+
}
|
|
185
|
+
try {
|
|
186
|
+
return decoder.decode(bytes);
|
|
187
|
+
} catch (error) {
|
|
188
|
+
throw new StorageEncryptedPayloadError(`The decrypted value of ${k} is not UTF-8.`, { key: k, cause: error });
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// src/encryption/web-crypto.ts
|
|
193
|
+
var IV_BYTES = 12;
|
|
194
|
+
var TAG_BYTES = 16;
|
|
195
|
+
function webCryptoProvider(crypto) {
|
|
196
|
+
const resolve = () => {
|
|
197
|
+
const c = crypto ?? globalThis.crypto;
|
|
198
|
+
if (!c?.subtle || !c.getRandomValues) {
|
|
199
|
+
throw new StorageEncryptionUnavailableError("Web Crypto is not available in this runtime; pass a provider in the encryption options.");
|
|
200
|
+
}
|
|
201
|
+
return c;
|
|
202
|
+
};
|
|
203
|
+
return {
|
|
204
|
+
algorithm: "AES-256-GCM",
|
|
205
|
+
async encrypt(data, key, aad2) {
|
|
206
|
+
const c = resolve();
|
|
207
|
+
const iv = c.getRandomValues(new Uint8Array(IV_BYTES));
|
|
208
|
+
const params = { name: "AES-GCM", iv, tagLength: TAG_BYTES * 8 };
|
|
209
|
+
if (aad2) params.additionalData = aad2;
|
|
210
|
+
const sealed = new Uint8Array(await c.subtle.encrypt(params, key, data));
|
|
211
|
+
return {
|
|
212
|
+
algorithm: "AES-256-GCM",
|
|
213
|
+
iv,
|
|
214
|
+
ciphertext: sealed.slice(0, sealed.length - TAG_BYTES),
|
|
215
|
+
tag: sealed.slice(sealed.length - TAG_BYTES)
|
|
216
|
+
};
|
|
217
|
+
},
|
|
218
|
+
async decrypt(data, key, aad2) {
|
|
219
|
+
const c = resolve();
|
|
220
|
+
const sealed = new Uint8Array(data.ciphertext.length + data.tag.length);
|
|
221
|
+
sealed.set(data.ciphertext);
|
|
222
|
+
sealed.set(data.tag, data.ciphertext.length);
|
|
223
|
+
const params = { name: "AES-GCM", iv: data.iv, tagLength: TAG_BYTES * 8 };
|
|
224
|
+
if (aad2) params.additionalData = aad2;
|
|
225
|
+
return new Uint8Array(await c.subtle.decrypt(params, key, sealed));
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// src/serialization/json.ts
|
|
231
|
+
function assertJson(value, path, seen) {
|
|
232
|
+
switch (typeof value) {
|
|
233
|
+
case "string":
|
|
234
|
+
case "boolean":
|
|
235
|
+
return;
|
|
236
|
+
case "number":
|
|
237
|
+
if (!Number.isFinite(value)) throw new StorageSerializationError(`${path} is ${value}, which JSON cannot hold.`);
|
|
238
|
+
return;
|
|
239
|
+
case "object":
|
|
240
|
+
break;
|
|
241
|
+
default:
|
|
242
|
+
throw new StorageSerializationError(`${path} is a ${typeof value}, which JSON cannot hold.`);
|
|
243
|
+
}
|
|
244
|
+
if (value === null) return;
|
|
245
|
+
if (seen.has(value)) throw new StorageSerializationError(`${path} refers back to itself.`);
|
|
246
|
+
seen.add(value);
|
|
247
|
+
if (Array.isArray(value)) {
|
|
248
|
+
value.forEach((item, i) => assertJson(item, `${path}[${i}]`, seen));
|
|
249
|
+
} else {
|
|
250
|
+
const proto = Object.getPrototypeOf(value);
|
|
251
|
+
if (proto !== Object.prototype && proto !== null) {
|
|
252
|
+
const name = value.constructor?.name ?? "an object with a prototype";
|
|
253
|
+
throw new StorageSerializationError(`${path} is ${name}, which does not survive JSON. Convert it to plain data first.`);
|
|
254
|
+
}
|
|
255
|
+
for (const [key, item] of Object.entries(value)) {
|
|
256
|
+
if (item === void 0) throw new StorageSerializationError(`${path}.${key} is undefined, which JSON drops. Omit the key or use null.`);
|
|
257
|
+
assertJson(item, `${path}.${key}`, seen);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
seen.delete(value);
|
|
261
|
+
}
|
|
262
|
+
var jsonSerializer = {
|
|
263
|
+
serialize(value) {
|
|
264
|
+
assertJson(value, "value", /* @__PURE__ */ new Set());
|
|
265
|
+
return JSON.stringify(value);
|
|
266
|
+
},
|
|
267
|
+
deserialize(text) {
|
|
268
|
+
return JSON.parse(text);
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
// src/utils/keys.ts
|
|
273
|
+
function namespacePrefix(namespace) {
|
|
274
|
+
return namespace ? `${namespace}:` : "";
|
|
275
|
+
}
|
|
276
|
+
function joinNamespace(parent, child) {
|
|
277
|
+
if (!child) throw new TypeError("A namespace needs a name.");
|
|
278
|
+
return parent ? `${parent}:${child}` : child;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// src/core/envelope.ts
|
|
282
|
+
function isEnvelope(x) {
|
|
283
|
+
return !!x && typeof x === "object" && x.v === 1 && ("value" in x || "enc" in x) && typeof x.createdAt === "number";
|
|
284
|
+
}
|
|
285
|
+
function serializeOrThrow(serializer, key, value) {
|
|
286
|
+
try {
|
|
287
|
+
return serializer.serialize(value);
|
|
288
|
+
} catch (error) {
|
|
289
|
+
if (error instanceof StorageError) throw Object.assign(error, { key });
|
|
290
|
+
throw new StorageSerializationError(`The value of ${key} could not be serialized.`, { key, cause: error });
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
function deserializeOrThrow(serializer, key, text) {
|
|
294
|
+
try {
|
|
295
|
+
return serializer.deserialize(text);
|
|
296
|
+
} catch (error) {
|
|
297
|
+
throw new StorageDeserializationError(`The stored value of ${key} could not be read.`, { key, cause: error });
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
function decodeEnvelope(serializer, key, text) {
|
|
301
|
+
const parsed = deserializeOrThrow(serializer, key, text);
|
|
302
|
+
return isEnvelope(parsed) ? parsed : { v: 1, value: parsed, createdAt: 0 };
|
|
303
|
+
}
|
|
304
|
+
var isExpired = (envelope, now) => envelope.expiresAt !== void 0 && envelope.expiresAt <= now;
|
|
305
|
+
function latestVersion(steps) {
|
|
306
|
+
if (!steps) return void 0;
|
|
307
|
+
const versions = Object.keys(steps).map(Number).filter(Number.isFinite);
|
|
308
|
+
return versions.length ? Math.max(...versions) : void 0;
|
|
309
|
+
}
|
|
310
|
+
function migrate(steps, from, value) {
|
|
311
|
+
const latest = latestVersion(steps);
|
|
312
|
+
if (!steps || latest === void 0 || (from ?? 0) >= latest) return void 0;
|
|
313
|
+
for (const version of Object.keys(steps).map(Number).sort((a, b) => a - b)) {
|
|
314
|
+
if (version > (from ?? 0)) value = steps[version](value);
|
|
315
|
+
}
|
|
316
|
+
return { value, version: latest };
|
|
317
|
+
}
|
|
318
|
+
function metaFor(createdAt, ttl, version) {
|
|
319
|
+
const meta = { createdAt };
|
|
320
|
+
if (ttl !== void 0) meta.expiresAt = createdAt + ttl;
|
|
321
|
+
if (version !== void 0) meta.version = version;
|
|
322
|
+
return meta;
|
|
323
|
+
}
|
|
324
|
+
function toEntry(envelope, value) {
|
|
325
|
+
const entry = { value, createdAt: envelope.createdAt };
|
|
326
|
+
if (envelope.expiresAt !== void 0) entry.expiresAt = envelope.expiresAt;
|
|
327
|
+
if (envelope.version !== void 0) entry.version = envelope.version;
|
|
328
|
+
if (envelope.enc) entry.encrypted = true;
|
|
329
|
+
return entry;
|
|
330
|
+
}
|
|
331
|
+
var globToRegExp = (glob) => new RegExp(`^${glob.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".")}$`);
|
|
332
|
+
function selectItems(items, query) {
|
|
333
|
+
const pattern = typeof query.match === "string" ? globToRegExp(query.match) : query.match;
|
|
334
|
+
const out = [];
|
|
335
|
+
for (const item of items) {
|
|
336
|
+
if (query.prefix !== void 0 && !item.key.startsWith(query.prefix)) continue;
|
|
337
|
+
if (pattern && !pattern.test(item.key)) continue;
|
|
338
|
+
if (query.where && !query.where(item)) continue;
|
|
339
|
+
out.push(item);
|
|
340
|
+
if (query.limit !== void 0 && out.length >= query.limit) break;
|
|
341
|
+
}
|
|
342
|
+
return out;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// src/core/bus.ts
|
|
346
|
+
var buses = /* @__PURE__ */ new WeakMap();
|
|
347
|
+
function joinBus(adapter, subscriber) {
|
|
348
|
+
let bus = buses.get(adapter);
|
|
349
|
+
if (!bus) buses.set(adapter, bus = { subscribers: /* @__PURE__ */ new Set() });
|
|
350
|
+
const current = bus;
|
|
351
|
+
current.subscribers.add(subscriber);
|
|
352
|
+
if (current.subscribers.size === 1 && adapter.subscribe) {
|
|
353
|
+
current.stop = adapter.subscribe((change) => {
|
|
354
|
+
for (const s of [...current.subscribers]) s.external(change);
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
return () => {
|
|
358
|
+
current.subscribers.delete(subscriber);
|
|
359
|
+
if (current.subscribers.size === 0) {
|
|
360
|
+
current.stop?.();
|
|
361
|
+
current.stop = void 0;
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
function publish(adapter, type, key, prefix, value) {
|
|
366
|
+
const bus = buses.get(adapter);
|
|
367
|
+
if (!bus) return;
|
|
368
|
+
for (const s of [...bus.subscribers]) {
|
|
369
|
+
if (key === null ? s.prefix.startsWith(prefix) : key.startsWith(s.prefix)) s.local(type, key, value);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// src/core/async-storage.ts
|
|
374
|
+
function createAsyncStorage(options = {}) {
|
|
375
|
+
const adapter = options.adapter ?? memoryStorage();
|
|
376
|
+
const serializer = options.serializer ?? jsonSerializer;
|
|
377
|
+
const now = options.now ?? Date.now;
|
|
378
|
+
const migrations = options.migrations ?? {};
|
|
379
|
+
const prefix = namespacePrefix(options.namespace);
|
|
380
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
381
|
+
let leave;
|
|
382
|
+
const sealer = {
|
|
383
|
+
keyProvider: options.encryption?.keyProvider,
|
|
384
|
+
provider: options.encryption?.provider ?? webCryptoProvider()
|
|
385
|
+
};
|
|
386
|
+
const context = (key) => options.namespace ? { key, namespace: options.namespace } : { key };
|
|
387
|
+
const deliver = (event) => {
|
|
388
|
+
for (const { key, fn } of [...listeners]) if (key === void 0 || event.key === null || key === event.key) fn(event);
|
|
389
|
+
};
|
|
390
|
+
const emit = (event) => publish(adapter, event.type, event.key === null ? null : prefix + event.key, prefix, event.value);
|
|
391
|
+
const store = async (key, value, meta, encrypt) => {
|
|
392
|
+
const envelope = { v: 1, ...meta };
|
|
393
|
+
if (encrypt) {
|
|
394
|
+
if (!sealer.keyProvider) throw new StorageEncryptionKeyError(`Encrypting ${key} needs encryption on the storage.`, { key });
|
|
395
|
+
envelope.enc = await seal(sealer, context(key), prefix + key, serializeOrThrow(serializer, key, value), meta);
|
|
396
|
+
} else {
|
|
397
|
+
envelope.value = value;
|
|
398
|
+
}
|
|
399
|
+
await adapter.set(prefix + key, serializeOrThrow(serializer, key, envelope), { expiresAt: meta.expiresAt });
|
|
400
|
+
};
|
|
401
|
+
const write = async (key, value, meta, encrypt) => {
|
|
402
|
+
await store(key, value, meta, encrypt);
|
|
403
|
+
emit({ type: "set", key, value, external: false });
|
|
404
|
+
};
|
|
405
|
+
const metaOf = ({ createdAt, expiresAt, version }) => {
|
|
406
|
+
const meta = { createdAt };
|
|
407
|
+
if (expiresAt !== void 0) meta.expiresAt = expiresAt;
|
|
408
|
+
if (version !== void 0) meta.version = version;
|
|
409
|
+
return meta;
|
|
410
|
+
};
|
|
411
|
+
const read = async (key) => {
|
|
412
|
+
const text = await adapter.get(prefix + key);
|
|
413
|
+
if (text === null) return null;
|
|
414
|
+
let envelope = decodeEnvelope(serializer, key, text);
|
|
415
|
+
if (isExpired(envelope, now())) {
|
|
416
|
+
await adapter.remove(prefix + key);
|
|
417
|
+
emit({ type: "expire", key, external: false });
|
|
418
|
+
return null;
|
|
419
|
+
}
|
|
420
|
+
let value = envelope.enc ? deserializeOrThrow(serializer, key, await open(sealer, context(key), prefix + key, envelope.enc, metaOf(envelope))) : envelope.value;
|
|
421
|
+
const migrated = migrate(migrations[key], envelope.version, value);
|
|
422
|
+
if (migrated) {
|
|
423
|
+
value = migrated.value;
|
|
424
|
+
const meta = { ...metaOf(envelope), version: migrated.version };
|
|
425
|
+
await store(key, value, meta, !!envelope.enc);
|
|
426
|
+
envelope = { v: 1, ...meta, ...envelope.enc ? { enc: envelope.enc } : { value } };
|
|
427
|
+
}
|
|
428
|
+
return { envelope, value };
|
|
429
|
+
};
|
|
430
|
+
const ownKeys = async () => (await adapter.keys()).filter((k) => k.startsWith(prefix)).map((k) => k.slice(prefix.length));
|
|
431
|
+
const external = (change) => {
|
|
432
|
+
if (change.key === null) return deliver({ type: "clear", key: null, external: true });
|
|
433
|
+
if (!change.key.startsWith(prefix)) return;
|
|
434
|
+
const key = change.key.slice(prefix.length);
|
|
435
|
+
if (change.value === null) return deliver({ type: "remove", key, external: true });
|
|
436
|
+
try {
|
|
437
|
+
const envelope = decodeEnvelope(serializer, key, change.value);
|
|
438
|
+
deliver({ type: "set", key, value: envelope.enc ? void 0 : envelope.value, external: true });
|
|
439
|
+
} catch {
|
|
440
|
+
}
|
|
441
|
+
};
|
|
442
|
+
const liveItems = async () => {
|
|
443
|
+
const items = [];
|
|
444
|
+
for (const key of (await ownKeys()).sort()) {
|
|
445
|
+
const current = await read(key);
|
|
446
|
+
if (current) items.push({ key, value: current.value, entry: toEntry(current.envelope, current.value) });
|
|
447
|
+
}
|
|
448
|
+
return items;
|
|
449
|
+
};
|
|
450
|
+
const storage = {
|
|
451
|
+
scope: adapter.scope,
|
|
452
|
+
keyPrefix: prefix,
|
|
453
|
+
capabilities: { ...adapter.capabilities, expiration: true },
|
|
454
|
+
encryption: !!sealer.keyProvider,
|
|
455
|
+
async get(key) {
|
|
456
|
+
return (await read(key))?.value ?? null;
|
|
457
|
+
},
|
|
458
|
+
async getEntry(key) {
|
|
459
|
+
const current = await read(key);
|
|
460
|
+
return current ? toEntry(current.envelope, current.value) : null;
|
|
461
|
+
},
|
|
462
|
+
async set(key, value, setOptions = {}) {
|
|
463
|
+
const meta = metaFor(now(), setOptions.ttl, setOptions.version ?? latestVersion(migrations[key]));
|
|
464
|
+
await write(key, value, meta, setOptions.encrypt === true);
|
|
465
|
+
},
|
|
466
|
+
entries: liveItems,
|
|
467
|
+
async search(query) {
|
|
468
|
+
return selectItems(await liveItems(), query);
|
|
469
|
+
},
|
|
470
|
+
async size() {
|
|
471
|
+
let count = 0;
|
|
472
|
+
for (const key of await ownKeys()) {
|
|
473
|
+
const text = await adapter.get(prefix + key);
|
|
474
|
+
if (text !== null && !isExpired(decodeEnvelope(serializer, key, text), now())) count++;
|
|
475
|
+
}
|
|
476
|
+
return count;
|
|
477
|
+
},
|
|
478
|
+
async prune() {
|
|
479
|
+
let removed = 0;
|
|
480
|
+
for (const key of await ownKeys()) {
|
|
481
|
+
const text = await adapter.get(prefix + key);
|
|
482
|
+
if (text === null) continue;
|
|
483
|
+
if (isExpired(decodeEnvelope(serializer, key, text), now())) {
|
|
484
|
+
await adapter.remove(prefix + key);
|
|
485
|
+
emit({ type: "expire", key, external: false });
|
|
486
|
+
removed++;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
return removed;
|
|
490
|
+
},
|
|
491
|
+
async usage() {
|
|
492
|
+
let bytes = 0;
|
|
493
|
+
const keys = await ownKeys();
|
|
494
|
+
for (const key of keys) bytes += ((prefix + key).length + ((await adapter.get(prefix + key))?.length ?? 0)) * 2;
|
|
495
|
+
return { keys: keys.length, bytes };
|
|
496
|
+
},
|
|
497
|
+
async update(key, fn, updateOptions = {}) {
|
|
498
|
+
const current = await read(key);
|
|
499
|
+
const value = await fn(current?.value ?? null);
|
|
500
|
+
const meta = metaFor(now(), void 0, current?.envelope.version ?? latestVersion(migrations[key]));
|
|
501
|
+
if (current?.envelope.expiresAt !== void 0) meta.expiresAt = current.envelope.expiresAt;
|
|
502
|
+
await write(key, value, meta, updateOptions.encrypt ?? !!current?.envelope.enc);
|
|
503
|
+
return value;
|
|
504
|
+
},
|
|
505
|
+
async getOrSet(key, factory, setOptions) {
|
|
506
|
+
const current = await read(key);
|
|
507
|
+
if (current) return current.value;
|
|
508
|
+
const value = await factory();
|
|
509
|
+
await storage.set(key, value, setOptions);
|
|
510
|
+
return value;
|
|
511
|
+
},
|
|
512
|
+
async touch(key, ttl) {
|
|
513
|
+
const current = await read(key);
|
|
514
|
+
if (!current) return false;
|
|
515
|
+
await store(key, current.value, { ...metaOf(current.envelope), expiresAt: now() + ttl }, !!current.envelope.enc);
|
|
516
|
+
return true;
|
|
517
|
+
},
|
|
518
|
+
async snapshot() {
|
|
519
|
+
const out = {};
|
|
520
|
+
for (const item of await liveItems()) out[item.key] = item.entry;
|
|
521
|
+
return out;
|
|
522
|
+
},
|
|
523
|
+
async restore(snapshot, restoreOptions = {}) {
|
|
524
|
+
let written = 0;
|
|
525
|
+
for (const [key, entry] of Object.entries(snapshot)) {
|
|
526
|
+
if (!entry) continue;
|
|
527
|
+
if (!restoreOptions.overwrite && await read(key)) continue;
|
|
528
|
+
if (entry.expiresAt !== void 0 && entry.expiresAt <= now()) continue;
|
|
529
|
+
const meta = { createdAt: entry.createdAt };
|
|
530
|
+
if (entry.expiresAt !== void 0) meta.expiresAt = entry.expiresAt;
|
|
531
|
+
if (entry.version !== void 0) meta.version = entry.version;
|
|
532
|
+
await write(key, entry.value, meta, entry.encrypted === true);
|
|
533
|
+
written++;
|
|
534
|
+
}
|
|
535
|
+
return written;
|
|
536
|
+
},
|
|
537
|
+
async remove(key) {
|
|
538
|
+
await adapter.remove(prefix + key);
|
|
539
|
+
emit({ type: "remove", key, external: false });
|
|
540
|
+
},
|
|
541
|
+
async has(key) {
|
|
542
|
+
const text = await adapter.get(prefix + key);
|
|
543
|
+
if (text === null) return false;
|
|
544
|
+
if (!isExpired(decodeEnvelope(serializer, key, text), now())) return true;
|
|
545
|
+
await adapter.remove(prefix + key);
|
|
546
|
+
emit({ type: "expire", key, external: false });
|
|
547
|
+
return false;
|
|
548
|
+
},
|
|
549
|
+
async keys() {
|
|
550
|
+
return await ownKeys();
|
|
551
|
+
},
|
|
552
|
+
async clear() {
|
|
553
|
+
if (prefix) for (const key of await ownKeys()) await adapter.remove(prefix + key);
|
|
554
|
+
else await adapter.clear();
|
|
555
|
+
emit({ type: "clear", key: null, external: false });
|
|
556
|
+
},
|
|
557
|
+
namespace(name) {
|
|
558
|
+
return createAsyncStorage({ ...options, migrations: void 0, namespace: joinNamespace(options.namespace, name) });
|
|
559
|
+
},
|
|
560
|
+
subscribe(keyOrListener, maybeListener) {
|
|
561
|
+
const entry = typeof keyOrListener === "function" ? { fn: keyOrListener } : { key: keyOrListener, fn: maybeListener };
|
|
562
|
+
listeners.add(entry);
|
|
563
|
+
if (listeners.size === 1) {
|
|
564
|
+
leave = joinBus(adapter, {
|
|
565
|
+
prefix,
|
|
566
|
+
external,
|
|
567
|
+
local(type, key, value) {
|
|
568
|
+
const event = { type, key: key === null ? null : key.slice(prefix.length), external: false };
|
|
569
|
+
if (type === "set") event.value = value;
|
|
570
|
+
deliver(event);
|
|
571
|
+
}
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
return () => {
|
|
575
|
+
if (listeners.delete(entry) && listeners.size === 0) {
|
|
576
|
+
leave?.();
|
|
577
|
+
leave = void 0;
|
|
578
|
+
}
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
};
|
|
582
|
+
return storage;
|
|
583
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { AsyncStorageOptions, AsyncTypedStorage, StorageSchema } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* A typed storage whose operations all return promises. It works over a synchronous adapter or
|
|
4
|
+
* an asynchronous one (IndexedDB, a remote store), and it is the storage that can encrypt.
|
|
5
|
+
*
|
|
6
|
+
* It reads and writes the same envelope as the synchronous storage, so the two can share an
|
|
7
|
+
* adapter. Without `encryption`, writing with `{ encrypt: true }` or reading an encrypted item
|
|
8
|
+
* throws `StorageEncryptionKeyError`.
|
|
9
|
+
*
|
|
10
|
+
* @param options As for `createStorage`, plus `encryption` and an asynchronous `adapter`.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* const storage = createAsyncStorage<{ token: string }>({ encryption: { keyProvider } });
|
|
14
|
+
* await storage.set('token', token, { encrypt: true, ttl: 3_600_000 });
|
|
15
|
+
*/
|
|
16
|
+
export declare function createAsyncStorage<S extends StorageSchema = StorageSchema>(options?: AsyncStorageOptions<S>): AsyncTypedStorage<S>;
|
|
17
|
+
//# sourceMappingURL=async-storage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"async-storage.d.ts","sourceRoot":"","sources":["../../src/core/async-storage.ts"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EAAsC,mBAAmB,EAAE,iBAAiB,EAA4B,aAAa,EAAc,MAAM,YAAY,CAAC;AAIlK;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,aAAa,GAAG,aAAa,EAAE,OAAO,GAAE,mBAAmB,CAAC,CAAC,CAAM,GAAG,iBAAiB,CAAC,CAAC,CAAC,CAyPtI"}
|