UNPKG

@directus/api

Version:

Directus is a real-time API and App dashboard for managing SQL database content

159 lines (157 loc) 5.94 kB
import { getConfigFromEnv } from "./utils/get-config-from-env.js"; import { redisConfigAvailable } from "./redis/utils/redis-config-available.js"; import "./redis/index.js"; import { useBus } from "./bus/lib/use-bus.js"; import "./bus/index.js"; import { useLogger } from "./logger/index.js"; import { getMilliseconds } from "./utils/get-milliseconds.js"; import { clearCache } from "./permissions/cache.js"; import { compress, decompress } from "./utils/compress.js"; import { freezeSchema, unfreezeSchema } from "./utils/freeze-schema.js"; import { validateEnv } from "./utils/validate-env.js"; import { createRequire } from "node:module"; import { useEnv } from "@directus/env"; import Keyv from "keyv"; //#region src/cache.ts const logger = useLogger(); const env = useEnv(); const require = createRequire(import.meta.url); let cache = null; let systemCache = null; let deploymentCache = null; let lockCache = null; let messengerSubscribed = false; let localSchemaCache = null; let memorySchemaCache = null; const messenger = useBus(); if (redisConfigAvailable() && !messengerSubscribed) { messengerSubscribed = true; messenger.subscribe("schemaChanged", async (opts) => { if (env["CACHE_STORE"] === "memory" && env["CACHE_AUTO_PURGE"] && cache && opts?.["autoPurgeCache"] !== false) await cache.clear(); if (opts?.autoPurgeSchema !== false) { await localSchemaCache?.clear(); memorySchemaCache = null; } }); } function getCache() { if (env["CACHE_ENABLED"] === true && cache === null) { validateEnv([ "CACHE_NAMESPACE", "CACHE_TTL", "CACHE_STORE" ]); cache = getKeyvInstance(env["CACHE_STORE"], getMilliseconds(env["CACHE_TTL"])); cache.on("error", (err) => logger.warn(err, `[cache] ${err}`)); } if (systemCache === null) { systemCache = getKeyvInstance(env["CACHE_STORE"], getMilliseconds(env["CACHE_SYSTEM_TTL"]), "_system"); systemCache.on("error", (err) => logger.warn(err, `[system-cache] ${err}`)); } if (deploymentCache === null) { const ttl = getMilliseconds(env["CACHE_DEPLOYMENT_TTL"]) || 5e3; deploymentCache = getKeyvInstance(env["CACHE_STORE"], ttl, "_deployment"); deploymentCache.on("error", (err) => logger.warn(err, `[deployment-cache] ${err}`)); } if (localSchemaCache === null) { localSchemaCache = getKeyvInstance("memory", getMilliseconds(env["CACHE_SYSTEM_TTL"]), "_schema"); localSchemaCache.on("error", (err) => logger.warn(err, `[schema-cache] ${err}`)); } if (lockCache === null) { lockCache = getKeyvInstance(env["CACHE_STORE"], void 0, "_lock"); lockCache.on("error", (err) => logger.warn(err, `[lock-cache] ${err}`)); } return { cache, systemCache, deploymentCache, localSchemaCache, lockCache }; } async function flushCaches(forced) { const { cache: cache$1 } = getCache(); await clearSystemCache({ forced }); await cache$1?.clear(); } async function clearSystemCache(opts) { const { systemCache: systemCache$1, localSchemaCache: localSchemaCache$1, lockCache: lockCache$1 } = getCache(); if (opts?.forced || !await lockCache$1.get("system-cache-lock")) { await lockCache$1.set("system-cache-lock", true, 1e4); await systemCache$1.clear(); await lockCache$1.delete("system-cache-lock"); } if (opts?.autoPurgeSchema !== false) { await localSchemaCache$1.clear(); memorySchemaCache = null; } await clearCache(); messenger.publish("schemaChanged", { autoPurgeCache: opts?.autoPurgeCache, autoPurgeSchema: opts?.autoPurgeSchema }); } async function setSystemCache(key, value, ttl) { const { systemCache: systemCache$1, lockCache: lockCache$1 } = getCache(); if (!await lockCache$1.get("system-cache-lock")) await setCacheValue(systemCache$1, key, value, ttl); } async function getSystemCache(key) { const { systemCache: systemCache$1 } = getCache(); return await getCacheValue(systemCache$1, key); } function setMemorySchemaCache(schema) { if (Object.isFrozen(schema)) memorySchemaCache = schema; else memorySchemaCache = freezeSchema(schema); } function getMemorySchemaCache() { if (env["CACHE_SCHEMA_FREEZE_ENABLED"]) return memorySchemaCache ?? void 0; else if (memorySchemaCache) return unfreezeSchema(memorySchemaCache); } async function setCacheValue(cache$1, key, value, ttl) { const compressed = await compress(value); await cache$1.set(key, compressed, ttl); } async function getCacheValue(cache$1, key) { const value = await cache$1.get(key); if (!value) return void 0; return await decompress(value); } /** * Store a value in cache with its expiration timestamp for TTL tracking */ async function setCacheValueWithExpiry(cache$1, key, value, ttl) { await setCacheValue(cache$1, key, value, ttl); await setCacheValue(cache$1, `${key}__expires_at`, { exp: Date.now() + ttl }, ttl); } /** * Get a cached value along with its remaining TTL */ async function getCacheValueWithTTL(cache$1, key) { const value = await getCacheValue(cache$1, key); if (!value) return void 0; const expiryData = await getCacheValue(cache$1, `${key}__expires_at`); return { data: value, remainingTTL: expiryData?.exp ? Math.max(0, expiryData.exp - Date.now()) : 0 }; } function getKeyvInstance(store, ttl, namespaceSuffix) { switch (store) { case "redis": return new Keyv(getConfig("redis", ttl, namespaceSuffix)); case "memory": default: return new Keyv(getConfig("memory", ttl, namespaceSuffix)); } } function getConfig(store = "memory", ttl, namespaceSuffix = "") { const config = { namespace: `${env["CACHE_NAMESPACE"]}${namespaceSuffix}`, ...ttl && { ttl } }; if (store === "redis") { const { default: KeyvRedis } = require("@keyv/redis"); config.store = new KeyvRedis(env["REDIS"] || getConfigFromEnv("REDIS"), { useRedisSets: false }); } return config; } //#endregion export { clearSystemCache, flushCaches, getCache, getCacheValue, getCacheValueWithTTL, getMemorySchemaCache, getSystemCache, setCacheValue, setCacheValueWithExpiry, setMemorySchemaCache, setSystemCache };