mastercache
Version:
Multi-tier cache module for Node.js. Redis, Upstash, CloudfareKV, File, in-memory and others drivers
1 lines • 10.8 kB
Source Map (JSON)
{"version":3,"sources":["../../../../src/drivers/file/file.ts","../../../../src/drivers/base-driver.ts"],"sourcesContent":["import { dirname, join } from 'node:path';\nimport { Worker } from 'node:worker_threads';\nimport { access, mkdir, readFile, writeFile, rm } from 'node:fs/promises';\n\n// import { resolveTtl } from '../../helpers';\nimport { BaseDriver } from '../base-driver';\nimport type { CacheDriver, CreateDriverResult, FileConfig } from '../../types/main';\n\n/**\n * Caching driver for the filesystem\n *\n * - Each key is stored as a file in the filesystem.\n * - Each namespace is a folder created in the parent namespace\n * - Files are stored in the following format: [stringifiedValue, expireTimestamp]\n * - If the expireTimestamp is -1, the value should never expire\n */\nexport class FileDriver extends BaseDriver implements CacheDriver {\n type = 'l2' as const;\n\n /**\n * Root directory for storing the cache files\n */\n #directory: string;\n\n /**\n * Worker thread that will clean up the expired files\n */\n #cleanerWorker?: Worker;\n\n declare config: FileConfig;\n\n constructor(config: FileConfig, isNamespace: boolean = false) {\n super(config);\n\n this.#directory = this.#sanitizePath(join(config.directory, config.prefix || ''));\n\n /**\n * If this is a namespaced class, then we should not start the cleaner\n * worker multiple times. Only the parent class will take care of it.\n */\n if (isNamespace) return;\n if (config.pruneInterval === false) return;\n // this.#cleanerWorker = new Worker(new URL('./cleaner-worker'), {\n // workerData: {\n // directory: this.#directory,\n // pruneInterval: resolveTtl(config.pruneInterval)\n // },\n // });\n }\n\n /**\n * Since keys and namespace uses `:` as a separator, we need to\n * purge them from the given path. We replace them with `/` to\n * create a nested directory structure.\n */\n #sanitizePath(path?: string) {\n if (!path) return '';\n return path.replaceAll(':', '/');\n }\n\n /**\n * Converts the given key to a file path\n */\n #keyToPath(key: string) {\n const keyWithoutPrefix = key.replace(this.prefix, '');\n\n /**\n * Check if the key contains a relative path\n */\n const re = /(\\.\\/|\\.\\.\\/)/g;\n if (re.test(key)) {\n throw new Error(`Invalid key: ${keyWithoutPrefix}. Should not contain relative paths.`);\n }\n\n return join(this.#directory, this.#sanitizePath(keyWithoutPrefix));\n }\n\n /**\n * Check if a file exists at a given path or not\n */\n async pathExists(path: string) {\n try {\n await access(path);\n return true;\n } catch {\n return false;\n }\n }\n\n /**\n * Output a file to the disk and create the directory recursively if\n * it's missing\n */\n async #outputFile(filename: string, content: string) {\n const directory = dirname(filename);\n const pathExists = await this.pathExists(directory);\n if (!pathExists) {\n await mkdir(directory, { recursive: true });\n }\n\n await writeFile(filename, content);\n }\n\n /**\n * Returns a new instance of the driver namespaced\n */\n namespace(namespace: string) {\n return new FileDriver({ ...this.config, prefix: this.createNamespacePrefix(namespace) }, true);\n }\n\n /**\n * Get a value from the cache\n */\n async get(key: string) {\n key = this.getItemKey(key);\n\n const path = this.#keyToPath(key);\n const pathExists = await this.pathExists(path);\n if (!pathExists) {\n return undefined;\n }\n\n const content = await readFile(path, { encoding: 'utf-8' });\n const [value, expire] = JSON.parse(content);\n\n if (expire !== -1 && expire < Date.now()) {\n await this.delete(key);\n return undefined;\n }\n\n return value as string;\n }\n\n /**\n * Get the value of a key and delete it\n *\n * Returns the value if the key exists, undefined otherwise\n */\n async pull(key: string) {\n const value = await this.get(key);\n if (!value) return undefined;\n\n await this.delete(key);\n return value;\n }\n\n /**\n * Put a value in the cache\n * Returns true if the value was set, false otherwise\n */\n async set(key: string, value: string, ttl?: number) {\n key = this.getItemKey(key);\n await this.#outputFile(\n this.#keyToPath(key),\n JSON.stringify([value, ttl ? Date.now() + ttl : -1]),\n );\n\n return true;\n }\n\n /**\n * Check if a key exists in the cache\n */\n async has(key: string) {\n key = this.getItemKey(key);\n\n /**\n * Check if the file exists\n */\n const path = this.#keyToPath(key);\n const pathExists = await this.pathExists(path);\n if (!pathExists) return false;\n\n /**\n * Check if the file is expired\n */\n const content = await readFile(path, { encoding: 'utf-8' });\n const [, expire] = JSON.parse(content);\n\n if (expire !== -1 && expire < Date.now()) {\n await this.delete(key);\n return false;\n }\n\n return true;\n }\n\n /**\n * Remove all items from the cache\n */\n async clear() {\n const cacheExists = await this.pathExists(this.#directory);\n if (!cacheExists) return;\n\n /**\n * By removing the directory and sub-directories, we are also\n * removing the namespaces inside it\n */\n await rm(this.#directory, { recursive: true });\n }\n\n /**\n * Delete a key from the cache\n * Returns true if the key was deleted, false otherwise\n */\n async delete(key: string) {\n key = this.getItemKey(key);\n\n const path = this.#keyToPath(key);\n const pathExists = await this.pathExists(path);\n if (!pathExists) {\n return false;\n }\n\n await rm(path);\n return true;\n }\n\n /**\n * Delete multiple keys from the cache\n */\n async deleteMany(keys: string[]) {\n await Promise.all(keys.map((key) => this.delete(key)));\n return true;\n }\n\n async disconnect() {\n await this.#cleanerWorker?.terminate();\n }\n}\n\n/**\n * Create a new file driver\n */\nexport function fileDriver(options: FileConfig): CreateDriverResult<FileDriver> {\n return { options, factory: (config: FileConfig) => new FileDriver(config) };\n}\n","import type { DriverCommonOptions } from '../types/main';\n\nexport abstract class BaseDriver {\n /**\n * Current cache prefix\n */\n protected prefix: string;\n\n constructor(protected config: DriverCommonOptions) {\n this.prefix = this.#sanitizePrefix(config.prefix);\n }\n\n /**\n * Sanitizes the cache prefix by removing any trailing colons\n */\n #sanitizePrefix(prefix?: string) {\n if (!prefix) return '';\n return prefix.replace(/:+$/, '');\n }\n\n /**\n * Creates a namespace prefix by concatenating the cache prefix with the given namespace\n * If the cache prefix is not defined, the namespace is returned as is\n */\n protected createNamespacePrefix(namespace: string) {\n const sanitizedPrefix = this.#sanitizePrefix(this.prefix);\n return sanitizedPrefix ? `${sanitizedPrefix}:${namespace}` : namespace;\n }\n\n /**\n * Returns the cache key with the prefix added to it, if a prefix is defined\n */\n protected getItemKey(key: string) {\n return this.prefix ? `${this.prefix}:${key}` : key;\n }\n}\n"],"mappings":"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","names":[]}