@solomonai/cache
Version:
<div align="center"> <h1 align="center">@solomonai/cache</h1> <h5>Cache all the things</h5> </div>
263 lines (260 loc) • 6.83 kB
JavaScript
import { Err, Ok, BaseError } from '@unkey/error';
import superjson from 'superjson';
// src/stores/cloudflare.ts
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/stores/cloudflare.ts
var CloudflareStore = class {
config;
name = "cloudflare";
constructor(config) {
this.config = config;
}
createCacheKey(namespace, key) {
return new URL(
`https://${this.config.domain}/cache/${this.config.cacheBuster ?? "v1"}/${String(
namespace
)}/${key}`
);
}
async get(namespace, key) {
let res;
try {
res = await caches.default.match(new Request(this.createCacheKey(namespace, key)));
} catch (err) {
return Err(
new CacheError({
tier: this.name,
key,
message: err.message
})
);
}
if (!res) {
return Ok(void 0);
}
const raw = await res.text();
try {
const entry = superjson.parse(raw);
return Ok(entry);
} catch (err) {
return Err(
new CacheError({
tier: this.name,
key,
message: err.message
})
);
}
}
async set(namespace, key, entry) {
const req = new Request(this.createCacheKey(namespace, key));
const res = new Response(superjson.stringify(entry), {
headers: {
"Content-Type": "application/json",
"Cache-Control": `public, max-age=${Math.floor(entry.staleUntil / 1e3)}`
}
});
try {
await caches.default.put(req, res);
return Ok();
} catch (err) {
return Err(
new CacheError({
tier: this.name,
key,
message: err.message
})
);
}
}
async remove(namespace, keys) {
const cacheKeys = (Array.isArray(keys) ? keys : [keys]).map(
(key) => this.createCacheKey(namespace, key)
);
await Promise.all(cacheKeys.map((key) => caches.default.delete(key)));
return await fetch(
`https://api.cloudflare.com/client/v4zones/${this.config.zoneId}/purge_cache`,
{
method: "POST",
headers: {
Authorization: `Bearer ${this.config.cloudflareApiKey}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
files: cacheKeys.map((k) => k.toString())
})
}
).then((res) => res.body?.cancel()).then(() => Ok()).catch(
(err) => Err(
new CacheError({
tier: this.name,
key: keys.toString(),
message: err.message
})
)
);
}
};
var MemoryStore = class {
state;
unstableEvictOnSet;
name = "memory";
constructor(config) {
this.state = config.persistentMap;
this.unstableEvictOnSet = config.unstableEvictOnSet;
}
buildCacheKey(namespace, key) {
return [namespace, key].join("::");
}
async get(namespace, key) {
const value = this.state.get(this.buildCacheKey(namespace, key));
if (!value) {
return Promise.resolve(Ok(void 0));
}
if (value.expires <= Date.now()) {
await this.remove(namespace, key);
}
return Promise.resolve(Ok(value.entry));
}
async set(namespace, key, entry) {
this.state.set(this.buildCacheKey(namespace, key), {
expires: entry.staleUntil,
entry
});
if (this.unstableEvictOnSet && Math.random() < this.unstableEvictOnSet.frequency) {
const now = Date.now();
this.state.forEach((value, key2, map) => {
if (value.expires < now) {
map.delete(key2);
}
});
for (const [k] of this.state) {
if (this.state.size <= this.unstableEvictOnSet.maxItems) {
break;
}
this.state.delete(k);
}
}
return Promise.resolve(Ok());
}
async remove(namespace, keys) {
const cacheKeys = (Array.isArray(keys) ? keys : [keys]).map(
(key) => this.buildCacheKey(namespace, key).toString()
);
for (const key of cacheKeys) {
this.state.delete(key);
}
return Promise.resolve(Ok());
}
};
var UpstashRedisStore = class {
redis;
name = "upstash-redis";
constructor(config) {
this.redis = config.redis;
}
buildCacheKey(namespace, key) {
return [namespace, key].join("::");
}
async get(namespace, key) {
const value = await this.redis.get(this.buildCacheKey(namespace, key));
if (!value) {
return Ok(void 0);
}
return Ok(value);
}
async set(namespace, key, entry) {
await this.redis.set(this.buildCacheKey(namespace, key), entry, {
pxat: entry.staleUntil
});
return Ok();
}
async remove(namespace, keys) {
const cacheKeys = (Array.isArray(keys) ? keys : [keys]).map(
(key) => this.buildCacheKey(namespace, key).toString()
);
this.redis.del(...cacheKeys);
return Ok();
}
};
var LibSQLStore = class {
tableName;
client;
name = "libsql";
constructor(config) {
this.client = config.client;
this.tableName = config.tableName || "cache";
}
buildCacheKey(namespace, key) {
return `${namespace}::${key}`;
}
async get(namespace, key) {
try {
const result = await this.client.execute({
sql: `SELECT value, freshUntil, staleUntil FROM ${this.tableName} WHERE key = ?`,
args: [this.buildCacheKey(namespace, key)]
});
if (result.rows.length === 0) {
return Ok(void 0);
}
const row = result.rows[0];
const entry = {
value: JSON.parse(row.value),
freshUntil: Number(row.freshUntil),
staleUntil: Number(row.staleUntil)
};
return Ok(entry);
} catch {
return Ok(void 0);
}
}
async set(namespace, key, entry) {
try {
await this.client.execute({
sql: `INSERT OR REPLACE INTO ${this.tableName} (key, value, freshUntil, staleUntil)
VALUES (?, ?, ?, ?)`,
args: [
this.buildCacheKey(namespace, key),
JSON.stringify(entry.value),
entry.freshUntil,
entry.staleUntil
]
});
return Ok();
} catch {
return Ok();
}
}
async remove(namespace, keys) {
const cacheKeys = (Array.isArray(keys) ? keys : [keys]).map(
(key) => this.buildCacheKey(namespace, key).toString()
);
try {
await Promise.all(
cacheKeys.map(
(cacheKey) => this.client.execute({
sql: `DELETE FROM ${this.tableName} WHERE key = ?`,
args: [cacheKey]
})
)
);
return Ok();
} catch {
return Ok();
}
}
};
export { CloudflareStore, LibSQLStore, MemoryStore, UpstashRedisStore };
//# sourceMappingURL=stores.mjs.map
//# sourceMappingURL=stores.mjs.map