@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
JavaScript
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