UNPKG

@solomonai/cache

Version:

<div align="center"> <h1 align="center">@solomonai/cache</h1> <h5>Cache all the things</h5> </div>

261 lines (258 loc) 6.11 kB
import { Err, BaseError } from '@unkey/error'; import SuperJSON from 'superjson'; // src/middleware/metrics.ts function withMetrics(metrics) { function wrap(store) { return new StoreWithMetrics({ store, metrics }); } return { wrap }; } var StoreWithMetrics = class { name; store; metrics; constructor(opts) { this.name = opts.store.name; this.store = opts.store; this.metrics = opts.metrics; } async get(namespace, key) { const start = performance.now(); const res = await this.store.get(namespace, key); const now = Date.now(); this.metrics.emit({ metric: "metric.cache.read", hit: typeof res.val !== "undefined", status: res.val ? now <= res.val.freshUntil ? "fresh" : now <= res.val.staleUntil ? "stale" : void 0 : void 0, latency: Math.round(performance.now() - start), tier: this.store.name, key, namespace }); return res; } async set(namespace, key, value) { const start = performance.now(); const res = await this.store.set(namespace, key, value); this.metrics.emit({ metric: "metric.cache.write", latency: Math.round(performance.now() - start), tier: this.store.name, key, namespace }); return res; } async remove(namespace, key) { const start = performance.now(); const res = this.store.remove(namespace, key); this.metrics.emit({ metric: "metric.cache.remove", tier: this.store.name, latency: Math.round(performance.now() - start), key, namespace }); return res; } }; var CacheError = class extends BaseError { name = "CacheError"; retry = false; tier; key; constructor(opts) { super(opts); this.name = "CacheError"; this.tier = opts.tier; this.key = opts.key; } }; // src/middleware/encryption.ts var EncryptedStore = class _EncryptedStore { name; encryptionKey; encryptionKeyHash; store; constructor(opts) { this.name = opts.store.name; this.store = opts.store; this.encryptionKey = opts.encryptionKey; this.encryptionKeyHash = opts.encryptionKeyHash; } /** * */ buildCacheKey(key) { return [key, this.encryptionKeyHash].join("/"); } async get(namespace, key) { const res = await this.store.get(namespace, this.buildCacheKey(key)); if (res.err) { return res; } if (!res.val) { return res; } try { const { iv, ciphertext } = res.val.value; const decrypted = await this.decrypt(iv, ciphertext); res.val.value = SuperJSON.parse(decrypted); } catch (e) { return Err(new CacheError({ tier: this.name, key, message: e.message })); } return res; } async set(namespace, key, value) { const { iv, ciphertext } = await this.encrypt(SuperJSON.stringify(value.value)); value.value = { iv, ciphertext }; const res = await this.store.set(namespace, this.buildCacheKey(key), value); return res; } async remove(namespace, key) { const res = this.store.remove(namespace, this.buildCacheKey(key)); return res; } async encrypt(secret) { const iv = crypto.getRandomValues(new Uint8Array(32)); const ciphertext = await crypto.subtle.encrypt( { name: "AES-GCM", iv }, this.encryptionKey, new TextEncoder().encode(secret) ); return { iv: encode(iv), ciphertext: encode(ciphertext) }; } async decrypt(iv, ciphertext) { const decryptedBuffer = await crypto.subtle.decrypt( { name: "AES-GCM", iv: decode(iv) }, this.encryptionKey, decode(ciphertext) ); return new TextDecoder().decode(decryptedBuffer); } static async fromBase64Key(base64EncodedKey) { const cryptoKey = await crypto.subtle.importKey( "raw", decode(base64EncodedKey), { name: "AES-GCM", length: 256 }, false, ["encrypt", "decrypt"] ); const hash = encode(await crypto.subtle.digest("SHA-256", decode(base64EncodedKey))); return { wrap: (store) => new _EncryptedStore({ store, encryptionKey: cryptoKey, encryptionKeyHash: hash }) }; } }; var base64abc = [ "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "+", "/" ]; function encode(data) { const uint8 = typeof data === "string" ? new TextEncoder().encode(data) : data instanceof Uint8Array ? data : new Uint8Array(data); let result = ""; let i; const l = uint8.length; for (i = 2; i < l; i += 3) { result += base64abc[uint8[i - 2] >> 2]; result += base64abc[(uint8[i - 2] & 3) << 4 | uint8[i - 1] >> 4]; result += base64abc[(uint8[i - 1] & 15) << 2 | uint8[i] >> 6]; result += base64abc[uint8[i] & 63]; } if (i === l + 1) { result += base64abc[uint8[i - 2] >> 2]; result += base64abc[(uint8[i - 2] & 3) << 4]; result += "=="; } if (i === l) { result += base64abc[uint8[i - 2] >> 2]; result += base64abc[(uint8[i - 2] & 3) << 4 | uint8[i - 1] >> 4]; result += base64abc[(uint8[i - 1] & 15) << 2]; result += "="; } return result.replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", ""); } function decode(b64) { const paddedLength = b64.length + (4 - b64.length % 4) % 4; const binString = atob(b64.replaceAll("-", "+").replaceAll("_", "/").padEnd(paddedLength, "=")); const size = binString.length; const bytes = new Uint8Array(size); for (let i = 0; i < size; i++) { bytes[i] = binString.charCodeAt(i); } return bytes; } async function withEncryption(base64Key) { return await EncryptedStore.fromBase64Key(base64Key); } export { EncryptedStore, withEncryption, withMetrics }; //# sourceMappingURL=middleware.mjs.map //# sourceMappingURL=middleware.mjs.map