@andrewshell/cacheism
Advanced tools
+332
| Object.defineProperty(exports, '__esModule', { value: true }); | ||
| //#region rolldown:runtime | ||
| var __create = Object.create; | ||
| var __defProp = Object.defineProperty; | ||
| var __getOwnPropDesc = Object.getOwnPropertyDescriptor; | ||
| var __getOwnPropNames = Object.getOwnPropertyNames; | ||
| var __getProtoOf = Object.getPrototypeOf; | ||
| var __hasOwnProp = Object.prototype.hasOwnProperty; | ||
| var __copyProps = (to, from, except, desc) => { | ||
| if (from && typeof from === "object" || typeof from === "function") { | ||
| for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) { | ||
| key = keys[i]; | ||
| if (!__hasOwnProp.call(to, key) && key !== except) { | ||
| __defProp(to, key, { | ||
| get: ((k) => from[k]).bind(null, key), | ||
| enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable | ||
| }); | ||
| } | ||
| } | ||
| } | ||
| return to; | ||
| }; | ||
| var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { | ||
| value: mod, | ||
| enumerable: true | ||
| }) : target, mod)); | ||
| //#endregion | ||
| let node_crypto = require("node:crypto"); | ||
| let node_fs = require("node:fs"); | ||
| node_fs = __toESM(node_fs); | ||
| let node_path = require("node:path"); | ||
| node_path = __toESM(node_path); | ||
| let node_fs_promises = require("node:fs/promises"); | ||
| //#region src/etag.ts | ||
| /*! | ||
| * etag | ||
| * Copyright(c) 2014-2016 Douglas Christopher Wilson | ||
| * MIT Licensed | ||
| */ | ||
| /** | ||
| * Generate an entity tag. | ||
| */ | ||
| function entitytag(entity) { | ||
| if (entity.length === 0) return "\"0-2jmj7l5rSw0yVb/vlWAYkK/YBwk\""; | ||
| const hashObj = (0, node_crypto.createHash)("sha1"); | ||
| if (typeof entity === "string") hashObj.update(entity, "utf8"); | ||
| else hashObj.update(entity); | ||
| const hash = hashObj.digest("base64").substring(0, 27); | ||
| return "\"" + (typeof entity === "string" ? Buffer.byteLength(entity, "utf8") : entity.length).toString(16) + "-" + hash + "\""; | ||
| } | ||
| /** | ||
| * Determine if object is a Stats object. | ||
| */ | ||
| function isstats(obj) { | ||
| if (typeof node_fs.Stats === "function" && obj instanceof node_fs.Stats) return true; | ||
| return obj != null && typeof obj === "object" && "ctime" in obj && Object.prototype.toString.call(obj.ctime) === "[object Date]" && "mtime" in obj && Object.prototype.toString.call(obj.mtime) === "[object Date]" && "ino" in obj && typeof obj.ino === "number" && "size" in obj && typeof obj.size === "number"; | ||
| } | ||
| /** | ||
| * Generate a tag for a stat. | ||
| */ | ||
| function stattag(stat) { | ||
| const mtime = stat.mtime.getTime().toString(16); | ||
| return "\"" + stat.size.toString(16) + "-" + mtime + "\""; | ||
| } | ||
| /** | ||
| * Create a simple ETag. | ||
| */ | ||
| function etag(entity, options) { | ||
| if (entity == null) throw new TypeError("argument entity is required"); | ||
| const isStats = isstats(entity); | ||
| const weak = options && typeof options.weak === "boolean" ? options.weak : isStats; | ||
| if (!isStats && typeof entity !== "string" && !Buffer.isBuffer(entity)) throw new TypeError("argument entity must be string, Buffer, or fs.Stats"); | ||
| const tag = isStats ? stattag(entity) : entitytag(entity); | ||
| return weak ? "W/" + tag : tag; | ||
| } | ||
| //#endregion | ||
| //#region src/common.ts | ||
| let Status = /* @__PURE__ */ function(Status$1) { | ||
| Status$1[Status$1["onlyFresh"] = 0] = "onlyFresh"; | ||
| Status$1[Status$1["cacheOnFail"] = 1] = "cacheOnFail"; | ||
| Status$1[Status$1["preferCache"] = 2] = "preferCache"; | ||
| Status$1[Status$1["onlyCache"] = 3] = "onlyCache"; | ||
| return Status$1; | ||
| }({}); | ||
| Object.freeze(Status); | ||
| let Type = /* @__PURE__ */ function(Type$1) { | ||
| Type$1["hit"] = "Hit"; | ||
| Type$1["miss"] = "Miss"; | ||
| return Type$1; | ||
| }({}); | ||
| Object.freeze(Type); | ||
| var Hit = class Hit { | ||
| version = 3; | ||
| cacheName; | ||
| cached = false; | ||
| created; | ||
| data; | ||
| error = null; | ||
| errorTime = null; | ||
| consecutiveErrors = 0; | ||
| etag; | ||
| isHit = true; | ||
| isMiss = false; | ||
| constructor(name, data, existingEtag) { | ||
| this.cacheName = name; | ||
| this.created = /* @__PURE__ */ new Date(); | ||
| this.data = data; | ||
| this.etag = existingEtag ?? etag(JSON.stringify(data)); | ||
| } | ||
| withError(error, consecutiveErrors) { | ||
| const hit = new Hit(this.cacheName, this.data, this.etag); | ||
| hit.cached = this.cached; | ||
| hit.created = this.created; | ||
| hit.error = error; | ||
| hit.errorTime = /* @__PURE__ */ new Date(); | ||
| hit.consecutiveErrors = consecutiveErrors; | ||
| return hit; | ||
| } | ||
| }; | ||
| var Miss = class { | ||
| version = 3; | ||
| cacheName; | ||
| cached = false; | ||
| created; | ||
| data = null; | ||
| error; | ||
| errorTime; | ||
| consecutiveErrors; | ||
| etag = null; | ||
| isHit = false; | ||
| isMiss = true; | ||
| constructor(name, error, consecutiveErrors) { | ||
| this.cacheName = name; | ||
| this.created = /* @__PURE__ */ new Date(); | ||
| this.error = error; | ||
| this.errorTime = new Date(this.created); | ||
| this.consecutiveErrors = consecutiveErrors; | ||
| } | ||
| }; | ||
| var Data = class Data { | ||
| version; | ||
| type; | ||
| cacheName; | ||
| created; | ||
| data; | ||
| error; | ||
| errorTime; | ||
| consecutiveErrors; | ||
| etag; | ||
| constructor(version, type, name, created, data, error, errorTime, consecutiveErrors, etag$1) { | ||
| this.version = version; | ||
| this.type = type; | ||
| this.cacheName = name; | ||
| this.created = created; | ||
| this.data = data; | ||
| this.error = error; | ||
| this.errorTime = errorTime; | ||
| this.consecutiveErrors = consecutiveErrors; | ||
| this.etag = etag$1; | ||
| } | ||
| response() { | ||
| if (this.version !== 3) throw new Error(`Unknown cache version number: ${String(this.version)}`); | ||
| let response; | ||
| if (this.type === Type.hit) { | ||
| response = new Hit(this.cacheName, this.data, this.etag ?? void 0); | ||
| response.cached = true; | ||
| response.created = this.created; | ||
| response.consecutiveErrors = this.consecutiveErrors; | ||
| } else { | ||
| response = new Miss(this.cacheName, this.error ?? "", this.consecutiveErrors); | ||
| response.cached = false; | ||
| response.created = this.created; | ||
| response.errorTime = this.errorTime ?? /* @__PURE__ */ new Date(); | ||
| } | ||
| return response; | ||
| } | ||
| stringify() { | ||
| return JSON.stringify({ | ||
| version: this.version, | ||
| type: this.type, | ||
| cacheName: this.cacheName, | ||
| created: this.created, | ||
| data: this.data, | ||
| error: this.error, | ||
| errorTime: this.errorTime, | ||
| consecutiveErrors: this.consecutiveErrors, | ||
| etag: this.etag | ||
| }, null, 2); | ||
| } | ||
| static fromResponse(response) { | ||
| return new Data(response.version, response.isHit ? Type.hit : Type.miss, response.cacheName, response.created, response.data, response.error, response.errorTime, response.consecutiveErrors, response.etag); | ||
| } | ||
| static parse(value) { | ||
| const parsed = JSON.parse(value); | ||
| if (parsed.version === 2) return new Data(3, Type.hit, parsed.cacheName, new Date(parsed.created), parsed.data, null, null, 0, parsed.etag); | ||
| if (parsed.version === 3) return new Data(3, parsed.type, parsed.cacheName, new Date(parsed.created), parsed.data, parsed.error, parsed.errorTime === null ? null : new Date(parsed.errorTime), parsed.consecutiveErrors, parsed.etag); | ||
| throw new Error(`Unknown cache version number: ${String(parsed.version)}`); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/store-filesystem.ts | ||
| async function mkdir(dirpath) { | ||
| await node_fs.default.promises.mkdir(dirpath, { recursive: true }); | ||
| } | ||
| function createFilesystemStore(config) { | ||
| return { | ||
| async get(cacheName) { | ||
| const data = await (0, node_fs_promises.readFile)(node_path.default.resolve(config.datadir, `${cacheName}.json`), "utf8"); | ||
| return Data.parse(data); | ||
| }, | ||
| async set(data) { | ||
| const filename = node_path.default.resolve(config.datadir, `${data.cacheName}.json`); | ||
| await mkdir(node_path.default.dirname(filename)); | ||
| await (0, node_fs_promises.writeFile)(filename, data.stringify(), "utf8"); | ||
| }, | ||
| async isset(cacheName) { | ||
| const filename = node_path.default.resolve(config.datadir, `${cacheName}.json`); | ||
| try { | ||
| await node_fs.default.promises.access(filename); | ||
| return true; | ||
| } catch { | ||
| return false; | ||
| } | ||
| }, | ||
| async unset(cacheName) { | ||
| await (0, node_fs_promises.rm)(node_path.default.resolve(config.datadir, `${cacheName}.json`), { force: true }); | ||
| } | ||
| }; | ||
| } | ||
| //#endregion | ||
| //#region src/store-memory.ts | ||
| function createMemoryStore(_config) { | ||
| const storeMemory = { | ||
| data: {}, | ||
| get(cacheName) { | ||
| return Promise.resolve(Data.parse(storeMemory.data[cacheName])); | ||
| }, | ||
| set(data) { | ||
| storeMemory.data[data.cacheName] = data.stringify(); | ||
| return Promise.resolve(); | ||
| }, | ||
| isset(cacheName) { | ||
| return Promise.resolve(cacheName in storeMemory.data); | ||
| }, | ||
| unset(cacheName) { | ||
| delete storeMemory.data[cacheName]; | ||
| return Promise.resolve(); | ||
| } | ||
| }; | ||
| return storeMemory; | ||
| } | ||
| //#endregion | ||
| //#region src/index.ts | ||
| function sanitize(string) { | ||
| return string.replaceAll(/[^a-z0-9]+/gi, "-"); | ||
| } | ||
| var Cacheism = class { | ||
| store; | ||
| status = Status; | ||
| type = Type; | ||
| constructor(store) { | ||
| this.store = store; | ||
| } | ||
| async go(cacheDomain, cachePath, status, callback) { | ||
| if (typeof cacheDomain !== "string") throw new TypeError("cacheDomain must be a string"); | ||
| if (typeof cachePath !== "string") throw new TypeError("cachePath must be a string"); | ||
| if (typeof status !== "number" || status < Status.onlyFresh || status > Status.onlyCache) throw new TypeError("status must be a valid Status value (0-3)"); | ||
| if (typeof callback !== "function") throw new TypeError("callback must be a function"); | ||
| const name = this.cacheName(cacheDomain, cachePath); | ||
| let existing = new Miss(name, "Missing cache", 0); | ||
| const hasCache = await this.store.isset(name); | ||
| if (hasCache) existing = (await this.store.get(name)).response(); | ||
| let response; | ||
| try { | ||
| if (status >= Status.preferCache && hasCache && existing.isHit) response = existing; | ||
| else if (status === Status.onlyCache) throw new Error("Missing cache"); | ||
| else { | ||
| const result = await callback(existing); | ||
| if (result instanceof Hit) response = result; | ||
| else response = new Hit(name, result); | ||
| } | ||
| } catch (err) { | ||
| const errorMessage = err instanceof Error ? err.toString() : String(err); | ||
| if (status >= Status.cacheOnFail && hasCache && existing.isHit) response = existing.withError(errorMessage, existing.consecutiveErrors + 1); | ||
| else response = new Miss(name, errorMessage, existing.consecutiveErrors + 1); | ||
| } | ||
| await this.store.set(Data.fromResponse(response)); | ||
| Object.freeze(response); | ||
| return response; | ||
| } | ||
| cacheName(cacheDomain, cachePath) { | ||
| return `${sanitize(cacheDomain)}/${sanitize(cachePath)}`; | ||
| } | ||
| setStore(store) { | ||
| this.store = store; | ||
| } | ||
| hit(name, data, etag$1) { | ||
| return new Hit(name, data, etag$1); | ||
| } | ||
| miss(name, error) { | ||
| return new Miss(name, error, 0); | ||
| } | ||
| static Hit = Hit; | ||
| static Miss = Miss; | ||
| static Data = Data; | ||
| static Status = Status; | ||
| static Type = Type; | ||
| static store = { | ||
| filesystem: createFilesystemStore, | ||
| memory: createMemoryStore | ||
| }; | ||
| }; | ||
| var src_default = Cacheism; | ||
| //#endregion | ||
| exports.Cacheism = Cacheism; | ||
| exports.Data = Data; | ||
| exports.Hit = Hit; | ||
| exports.Miss = Miss; | ||
| exports.Status = Status; | ||
| exports.Type = Type; | ||
| exports.createFilesystemStore = createFilesystemStore; | ||
| exports.createMemoryStore = createMemoryStore; | ||
| exports.default = src_default; | ||
| exports.etag = etag; | ||
| //# sourceMappingURL=index.cjs.map |
| {"version":3,"file":"index.cjs","names":["Stats","generateEtag","etag","response: Hit | Miss","fs","path","storeMemory: MemoryStore","existing: Hit | Miss","response: Hit | Miss","etag"],"sources":["../src/etag.ts","../src/common.ts","../src/store-filesystem.ts","../src/store-memory.ts","../src/index.ts"],"sourcesContent":["/*!\n * etag\n * Copyright(c) 2014-2016 Douglas Christopher Wilson\n * MIT Licensed\n */\n\nimport { createHash } from 'node:crypto';\nimport { Stats } from 'node:fs';\n\nexport interface EtagOptions {\n weak?: boolean;\n}\n\n/**\n * Generate an entity tag.\n */\nfunction entitytag(entity: string | Buffer): string {\n if (entity.length === 0) {\n // fast-path empty\n return '\"0-2jmj7l5rSw0yVb/vlWAYkK/YBwk\"';\n }\n\n // compute hash of entity\n const hashObj = createHash('sha1');\n if (typeof entity === 'string') {\n hashObj.update(entity, 'utf8');\n } else {\n hashObj.update(entity);\n }\n const hash = hashObj.digest('base64').substring(0, 27);\n\n // compute length of entity\n const len = typeof entity === 'string'\n ? Buffer.byteLength(entity, 'utf8')\n : entity.length;\n\n return '\"' + len.toString(16) + '-' + hash + '\"';\n}\n\n/**\n * Determine if object is a Stats object.\n */\nfunction isstats(obj: unknown): obj is Stats {\n // genuine fs.Stats\n if (typeof Stats === 'function' && obj instanceof Stats) {\n return true;\n }\n\n // quack quack\n return obj != null && typeof obj === 'object' &&\n 'ctime' in obj && Object.prototype.toString.call((obj as Stats).ctime) === '[object Date]' &&\n 'mtime' in obj && Object.prototype.toString.call((obj as Stats).mtime) === '[object Date]' &&\n 'ino' in obj && typeof (obj as Stats).ino === 'number' &&\n 'size' in obj && typeof (obj as Stats).size === 'number';\n}\n\n/**\n * Generate a tag for a stat.\n */\nfunction stattag(stat: Stats): string {\n const mtime = stat.mtime.getTime().toString(16);\n const size = stat.size.toString(16);\n\n return '\"' + size + '-' + mtime + '\"';\n}\n\n/**\n * Create a simple ETag.\n */\nexport function etag(entity: string | Buffer | Stats | null | undefined, options?: EtagOptions): string {\n if (entity == null) {\n throw new TypeError('argument entity is required');\n }\n\n // support fs.Stats object\n const isStats = isstats(entity);\n const weak = options && typeof options.weak === 'boolean'\n ? options.weak\n : isStats;\n\n // validate argument\n if (!isStats && typeof entity !== 'string' && !Buffer.isBuffer(entity)) {\n throw new TypeError('argument entity must be string, Buffer, or fs.Stats');\n }\n\n // generate entity tag\n const tag = isStats\n ? stattag(entity)\n : entitytag(entity);\n\n return weak\n ? 'W/' + tag\n : tag;\n}\n\nexport default etag;\n","import { etag as generateEtag } from './etag.js';\n\nexport enum Status {\n onlyFresh = 0,\n cacheOnFail = 1,\n preferCache = 2,\n onlyCache = 3,\n}\nObject.freeze(Status);\n\nexport enum Type {\n hit = 'Hit',\n miss = 'Miss',\n}\nObject.freeze(Type);\n\nexport class Hit {\n readonly version = 3;\n readonly cacheName: string;\n cached = false;\n created: Date;\n data: unknown;\n error: string | null = null;\n errorTime: Date | null = null;\n consecutiveErrors = 0;\n etag: string;\n readonly isHit = true;\n readonly isMiss = false;\n\n constructor(name: string, data: unknown, existingEtag?: string) {\n this.cacheName = name;\n this.created = new Date();\n this.data = data;\n this.etag = existingEtag ?? generateEtag(JSON.stringify(data));\n }\n\n withError(error: string, consecutiveErrors: number): Hit {\n const hit = new Hit(this.cacheName, this.data, this.etag);\n hit.cached = this.cached;\n hit.created = this.created;\n hit.error = error;\n hit.errorTime = new Date();\n hit.consecutiveErrors = consecutiveErrors;\n return hit;\n }\n}\n\nexport class Miss {\n readonly version = 3;\n readonly cacheName: string;\n cached = false;\n created: Date;\n readonly data = null;\n error: string;\n errorTime: Date;\n consecutiveErrors: number;\n readonly etag = null;\n readonly isHit = false;\n readonly isMiss = true;\n\n constructor(name: string, error: string, consecutiveErrors: number) {\n this.cacheName = name;\n this.created = new Date();\n this.error = error;\n this.errorTime = new Date(this.created);\n this.consecutiveErrors = consecutiveErrors;\n }\n}\n\ninterface ParsedData {\n version: number;\n type: string;\n cacheName: string;\n created: string;\n data: unknown;\n error: string | null;\n errorTime: string | null;\n consecutiveErrors: number;\n etag: string | null;\n}\n\nexport class Data {\n version: number;\n type: Type;\n cacheName: string;\n created: Date;\n data: unknown;\n error: string | null;\n errorTime: Date | null;\n consecutiveErrors: number;\n etag: string | null;\n\n constructor(\n version: number,\n type: Type,\n name: string,\n created: Date,\n data: unknown,\n error: string | null,\n errorTime: Date | null,\n consecutiveErrors: number,\n etag: string | null\n ) {\n this.version = version;\n this.type = type;\n this.cacheName = name;\n this.created = created;\n this.data = data;\n this.error = error;\n this.errorTime = errorTime;\n this.consecutiveErrors = consecutiveErrors;\n this.etag = etag;\n }\n\n response(): Hit | Miss {\n if (this.version !== 3) {\n throw new Error(`Unknown cache version number: ${String(this.version)}`);\n }\n\n let response: Hit | Miss;\n\n if (this.type === Type.hit) {\n response = new Hit(this.cacheName, this.data, this.etag ?? undefined);\n response.cached = true;\n response.created = this.created;\n response.consecutiveErrors = this.consecutiveErrors;\n } else {\n response = new Miss(this.cacheName, this.error ?? '', this.consecutiveErrors);\n response.cached = false;\n response.created = this.created;\n response.errorTime = this.errorTime ?? new Date();\n }\n\n return response;\n }\n\n stringify(): string {\n return JSON.stringify({\n version: this.version,\n type: this.type,\n cacheName: this.cacheName,\n created: this.created,\n data: this.data,\n error: this.error,\n errorTime: this.errorTime,\n consecutiveErrors: this.consecutiveErrors,\n etag: this.etag,\n }, null, 2);\n }\n\n static fromResponse(response: Hit | Miss): Data {\n return new Data(\n response.version,\n response.isHit ? Type.hit : Type.miss,\n response.cacheName,\n response.created,\n response.data,\n response.error,\n response.errorTime,\n response.consecutiveErrors,\n response.etag\n );\n }\n\n static parse(value: string): Data {\n const parsed = JSON.parse(value) as ParsedData;\n\n if (parsed.version === 2) {\n return new Data(\n 3,\n Type.hit,\n parsed.cacheName,\n new Date(parsed.created),\n parsed.data,\n null,\n null,\n 0,\n parsed.etag\n );\n }\n\n if (parsed.version === 3) {\n return new Data(\n 3,\n parsed.type as Type,\n parsed.cacheName,\n new Date(parsed.created),\n parsed.data,\n parsed.error,\n parsed.errorTime === null ? null : new Date(parsed.errorTime),\n parsed.consecutiveErrors,\n parsed.etag\n );\n }\n\n throw new Error(`Unknown cache version number: ${String(parsed.version)}`);\n }\n}\n","import path from 'node:path';\nimport fs from 'node:fs';\nimport { readFile, writeFile, rm } from 'node:fs/promises';\nimport { Data } from './common.js';\nimport type { Store } from './store-memory.js';\n\nexport interface FilesystemStoreConfig {\n datadir: string;\n}\n\nasync function mkdir(dirpath: string): Promise<void> {\n await fs.promises.mkdir(dirpath, { recursive: true });\n}\n\nexport function createFilesystemStore(config: FilesystemStoreConfig): Store {\n return {\n async get(cacheName: string): Promise<Data> {\n const filename = path.resolve(config.datadir, `${cacheName}.json`);\n const data = await readFile(filename, 'utf8');\n return Data.parse(data);\n },\n\n async set(data: Data): Promise<void> {\n const filename = path.resolve(config.datadir, `${data.cacheName}.json`);\n await mkdir(path.dirname(filename));\n await writeFile(filename, data.stringify(), 'utf8');\n },\n\n async isset(cacheName: string): Promise<boolean> {\n const filename = path.resolve(config.datadir, `${cacheName}.json`);\n try {\n await fs.promises.access(filename);\n return true;\n } catch {\n return false;\n }\n },\n\n async unset(cacheName: string): Promise<void> {\n const filename = path.resolve(config.datadir, `${cacheName}.json`);\n await rm(filename, { force: true });\n }\n };\n}\n\nexport default createFilesystemStore;\n","import { Data } from './common.js';\n\nexport interface Store {\n get(cacheName: string): Promise<Data>;\n set(data: Data): Promise<void>;\n isset(cacheName: string): Promise<boolean>;\n unset(cacheName: string): Promise<void>;\n}\n\nexport interface MemoryStore extends Store {\n data: Record<string, string>;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type -- Future expansion\nexport interface MemoryStoreConfig {}\n\nexport function createMemoryStore(_config?: MemoryStoreConfig): MemoryStore {\n const storeMemory: MemoryStore = {\n data: {},\n\n get(cacheName: string): Promise<Data> {\n return Promise.resolve(Data.parse(storeMemory.data[cacheName]));\n },\n\n set(data: Data): Promise<void> {\n storeMemory.data[data.cacheName] = data.stringify();\n return Promise.resolve();\n },\n\n isset(cacheName: string): Promise<boolean> {\n return Promise.resolve(cacheName in storeMemory.data);\n },\n\n unset(cacheName: string): Promise<void> {\n // eslint-disable-next-line @typescript-eslint/no-dynamic-delete\n delete storeMemory.data[cacheName];\n return Promise.resolve();\n }\n };\n\n return storeMemory;\n}\n\nexport default createMemoryStore;\n","import { Hit, Miss, Data, Status, Type } from './common.js';\nimport { etag } from './etag.js';\nimport { createFilesystemStore } from './store-filesystem.js';\nimport { createMemoryStore, type Store } from './store-memory.js';\n\nfunction sanitize(string: string): string {\n return string.replaceAll(/[^a-z0-9]+/gi, '-');\n}\n\nexport class Cacheism {\n store: Store;\n status = Status;\n type = Type;\n\n constructor(store: Store) {\n this.store = store;\n }\n\n async go(\n cacheDomain: string,\n cachePath: string,\n status: Status,\n callback: (existing: Hit | Miss) => Promise<unknown>\n ): Promise<Hit | Miss> {\n // Input validation\n if (typeof cacheDomain !== 'string') {\n throw new TypeError('cacheDomain must be a string');\n }\n if (typeof cachePath !== 'string') {\n throw new TypeError('cachePath must be a string');\n }\n if (typeof status !== 'number' || status < Status.onlyFresh || status > Status.onlyCache) {\n throw new TypeError('status must be a valid Status value (0-3)');\n }\n if (typeof callback !== 'function') {\n throw new TypeError('callback must be a function');\n }\n\n const name = this.cacheName(cacheDomain, cachePath);\n let existing: Hit | Miss = new Miss(name, 'Missing cache', 0);\n const hasCache = await this.store.isset(name);\n\n if (hasCache) {\n existing = (await this.store.get(name)).response();\n }\n\n let response: Hit | Miss;\n\n try {\n if (status >= Status.preferCache && hasCache && existing.isHit) {\n response = existing;\n } else if (status === Status.onlyCache) {\n throw new Error('Missing cache');\n } else {\n const result = await callback(existing);\n if (result instanceof Hit) {\n response = result;\n } else {\n response = new Hit(name, result);\n }\n }\n } catch (err) {\n const errorMessage = err instanceof Error ? err.toString() : String(err);\n\n if (status >= Status.cacheOnFail && hasCache && existing.isHit) {\n response = existing.withError(errorMessage, existing.consecutiveErrors + 1);\n } else {\n response = new Miss(name, errorMessage, existing.consecutiveErrors + 1);\n }\n }\n\n await this.store.set(Data.fromResponse(response));\n Object.freeze(response);\n return response;\n }\n\n cacheName(cacheDomain: string, cachePath: string): string {\n return `${sanitize(cacheDomain)}/${sanitize(cachePath)}`;\n }\n\n setStore(store: Store): void {\n this.store = store;\n }\n\n hit(name: string, data: unknown, etag?: string): Hit {\n return new Hit(name, data, etag);\n }\n\n miss(name: string, error: string): Miss {\n return new Miss(name, error, 0);\n }\n\n // Static members for backward compatibility\n static Hit = Hit;\n static Miss = Miss;\n static Data = Data;\n static Status = Status;\n static Type = Type;\n\n static store = {\n filesystem: createFilesystemStore,\n memory: createMemoryStore,\n };\n}\n\n// Named exports for ESM users\nexport { Hit, Miss, Data, Status, Type };\nexport { etag };\nexport { createFilesystemStore, createMemoryStore };\nexport type { Store } from './store-memory.js';\nexport type { FilesystemStoreConfig } from './store-filesystem.js';\nexport type { MemoryStoreConfig, MemoryStore } from './store-memory.js';\n\n// Default export for backward compatibility\nexport default Cacheism;\n"],"mappings":"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"} |
+111
| import { Stats } from "node:fs"; | ||
| //#region src/common.d.ts | ||
| declare enum Status { | ||
| onlyFresh = 0, | ||
| cacheOnFail = 1, | ||
| preferCache = 2, | ||
| onlyCache = 3, | ||
| } | ||
| declare enum Type { | ||
| hit = "Hit", | ||
| miss = "Miss", | ||
| } | ||
| declare class Hit { | ||
| readonly version = 3; | ||
| readonly cacheName: string; | ||
| cached: boolean; | ||
| created: Date; | ||
| data: unknown; | ||
| error: string | null; | ||
| errorTime: Date | null; | ||
| consecutiveErrors: number; | ||
| etag: string; | ||
| readonly isHit = true; | ||
| readonly isMiss = false; | ||
| constructor(name: string, data: unknown, existingEtag?: string); | ||
| withError(error: string, consecutiveErrors: number): Hit; | ||
| } | ||
| declare class Miss { | ||
| readonly version = 3; | ||
| readonly cacheName: string; | ||
| cached: boolean; | ||
| created: Date; | ||
| readonly data: null; | ||
| error: string; | ||
| errorTime: Date; | ||
| consecutiveErrors: number; | ||
| readonly etag: null; | ||
| readonly isHit = false; | ||
| readonly isMiss = true; | ||
| constructor(name: string, error: string, consecutiveErrors: number); | ||
| } | ||
| declare class Data { | ||
| version: number; | ||
| type: Type; | ||
| cacheName: string; | ||
| created: Date; | ||
| data: unknown; | ||
| error: string | null; | ||
| errorTime: Date | null; | ||
| consecutiveErrors: number; | ||
| etag: string | null; | ||
| constructor(version: number, type: Type, name: string, created: Date, data: unknown, error: string | null, errorTime: Date | null, consecutiveErrors: number, etag: string | null); | ||
| response(): Hit | Miss; | ||
| stringify(): string; | ||
| static fromResponse(response: Hit | Miss): Data; | ||
| static parse(value: string): Data; | ||
| } | ||
| //#endregion | ||
| //#region src/etag.d.ts | ||
| interface EtagOptions { | ||
| weak?: boolean; | ||
| } | ||
| /** | ||
| * Create a simple ETag. | ||
| */ | ||
| declare function etag(entity: string | Buffer | Stats | null | undefined, options?: EtagOptions): string; | ||
| //#endregion | ||
| //#region src/store-memory.d.ts | ||
| interface Store { | ||
| get(cacheName: string): Promise<Data>; | ||
| set(data: Data): Promise<void>; | ||
| isset(cacheName: string): Promise<boolean>; | ||
| unset(cacheName: string): Promise<void>; | ||
| } | ||
| interface MemoryStore extends Store { | ||
| data: Record<string, string>; | ||
| } | ||
| interface MemoryStoreConfig {} | ||
| declare function createMemoryStore(_config?: MemoryStoreConfig): MemoryStore; | ||
| //#endregion | ||
| //#region src/store-filesystem.d.ts | ||
| interface FilesystemStoreConfig { | ||
| datadir: string; | ||
| } | ||
| declare function createFilesystemStore(config: FilesystemStoreConfig): Store; | ||
| //#endregion | ||
| //#region src/index.d.ts | ||
| declare class Cacheism { | ||
| store: Store; | ||
| status: typeof Status; | ||
| type: typeof Type; | ||
| constructor(store: Store); | ||
| go(cacheDomain: string, cachePath: string, status: Status, callback: (existing: Hit | Miss) => Promise<unknown>): Promise<Hit | Miss>; | ||
| cacheName(cacheDomain: string, cachePath: string): string; | ||
| setStore(store: Store): void; | ||
| hit(name: string, data: unknown, etag?: string): Hit; | ||
| miss(name: string, error: string): Miss; | ||
| static Hit: typeof Hit; | ||
| static Miss: typeof Miss; | ||
| static Data: typeof Data; | ||
| static Status: typeof Status; | ||
| static Type: typeof Type; | ||
| static store: { | ||
| filesystem: typeof createFilesystemStore; | ||
| memory: typeof createMemoryStore; | ||
| }; | ||
| } | ||
| //#endregion | ||
| export { Cacheism, Cacheism as default, Data, type FilesystemStoreConfig, Hit, type MemoryStore, type MemoryStoreConfig, Miss, Status, type Store, Type, createFilesystemStore, createMemoryStore, etag }; | ||
| //# sourceMappingURL=index.d.cts.map |
+111
| import { Stats } from "node:fs"; | ||
| //#region src/common.d.ts | ||
| declare enum Status { | ||
| onlyFresh = 0, | ||
| cacheOnFail = 1, | ||
| preferCache = 2, | ||
| onlyCache = 3, | ||
| } | ||
| declare enum Type { | ||
| hit = "Hit", | ||
| miss = "Miss", | ||
| } | ||
| declare class Hit { | ||
| readonly version = 3; | ||
| readonly cacheName: string; | ||
| cached: boolean; | ||
| created: Date; | ||
| data: unknown; | ||
| error: string | null; | ||
| errorTime: Date | null; | ||
| consecutiveErrors: number; | ||
| etag: string; | ||
| readonly isHit = true; | ||
| readonly isMiss = false; | ||
| constructor(name: string, data: unknown, existingEtag?: string); | ||
| withError(error: string, consecutiveErrors: number): Hit; | ||
| } | ||
| declare class Miss { | ||
| readonly version = 3; | ||
| readonly cacheName: string; | ||
| cached: boolean; | ||
| created: Date; | ||
| readonly data: null; | ||
| error: string; | ||
| errorTime: Date; | ||
| consecutiveErrors: number; | ||
| readonly etag: null; | ||
| readonly isHit = false; | ||
| readonly isMiss = true; | ||
| constructor(name: string, error: string, consecutiveErrors: number); | ||
| } | ||
| declare class Data { | ||
| version: number; | ||
| type: Type; | ||
| cacheName: string; | ||
| created: Date; | ||
| data: unknown; | ||
| error: string | null; | ||
| errorTime: Date | null; | ||
| consecutiveErrors: number; | ||
| etag: string | null; | ||
| constructor(version: number, type: Type, name: string, created: Date, data: unknown, error: string | null, errorTime: Date | null, consecutiveErrors: number, etag: string | null); | ||
| response(): Hit | Miss; | ||
| stringify(): string; | ||
| static fromResponse(response: Hit | Miss): Data; | ||
| static parse(value: string): Data; | ||
| } | ||
| //#endregion | ||
| //#region src/etag.d.ts | ||
| interface EtagOptions { | ||
| weak?: boolean; | ||
| } | ||
| /** | ||
| * Create a simple ETag. | ||
| */ | ||
| declare function etag(entity: string | Buffer | Stats | null | undefined, options?: EtagOptions): string; | ||
| //#endregion | ||
| //#region src/store-memory.d.ts | ||
| interface Store { | ||
| get(cacheName: string): Promise<Data>; | ||
| set(data: Data): Promise<void>; | ||
| isset(cacheName: string): Promise<boolean>; | ||
| unset(cacheName: string): Promise<void>; | ||
| } | ||
| interface MemoryStore extends Store { | ||
| data: Record<string, string>; | ||
| } | ||
| interface MemoryStoreConfig {} | ||
| declare function createMemoryStore(_config?: MemoryStoreConfig): MemoryStore; | ||
| //#endregion | ||
| //#region src/store-filesystem.d.ts | ||
| interface FilesystemStoreConfig { | ||
| datadir: string; | ||
| } | ||
| declare function createFilesystemStore(config: FilesystemStoreConfig): Store; | ||
| //#endregion | ||
| //#region src/index.d.ts | ||
| declare class Cacheism { | ||
| store: Store; | ||
| status: typeof Status; | ||
| type: typeof Type; | ||
| constructor(store: Store); | ||
| go(cacheDomain: string, cachePath: string, status: Status, callback: (existing: Hit | Miss) => Promise<unknown>): Promise<Hit | Miss>; | ||
| cacheName(cacheDomain: string, cachePath: string): string; | ||
| setStore(store: Store): void; | ||
| hit(name: string, data: unknown, etag?: string): Hit; | ||
| miss(name: string, error: string): Miss; | ||
| static Hit: typeof Hit; | ||
| static Miss: typeof Miss; | ||
| static Data: typeof Data; | ||
| static Status: typeof Status; | ||
| static Type: typeof Type; | ||
| static store: { | ||
| filesystem: typeof createFilesystemStore; | ||
| memory: typeof createMemoryStore; | ||
| }; | ||
| } | ||
| //#endregion | ||
| export { Cacheism, Cacheism as default, Data, type FilesystemStoreConfig, Hit, type MemoryStore, type MemoryStoreConfig, Miss, Status, type Store, Type, createFilesystemStore, createMemoryStore, etag }; | ||
| //# sourceMappingURL=index.d.mts.map |
+293
| import { createHash } from "node:crypto"; | ||
| import fs, { Stats } from "node:fs"; | ||
| import path from "node:path"; | ||
| import { readFile, rm, writeFile } from "node:fs/promises"; | ||
| //#region src/etag.ts | ||
| /*! | ||
| * etag | ||
| * Copyright(c) 2014-2016 Douglas Christopher Wilson | ||
| * MIT Licensed | ||
| */ | ||
| /** | ||
| * Generate an entity tag. | ||
| */ | ||
| function entitytag(entity) { | ||
| if (entity.length === 0) return "\"0-2jmj7l5rSw0yVb/vlWAYkK/YBwk\""; | ||
| const hashObj = createHash("sha1"); | ||
| if (typeof entity === "string") hashObj.update(entity, "utf8"); | ||
| else hashObj.update(entity); | ||
| const hash = hashObj.digest("base64").substring(0, 27); | ||
| return "\"" + (typeof entity === "string" ? Buffer.byteLength(entity, "utf8") : entity.length).toString(16) + "-" + hash + "\""; | ||
| } | ||
| /** | ||
| * Determine if object is a Stats object. | ||
| */ | ||
| function isstats(obj) { | ||
| if (typeof Stats === "function" && obj instanceof Stats) return true; | ||
| return obj != null && typeof obj === "object" && "ctime" in obj && Object.prototype.toString.call(obj.ctime) === "[object Date]" && "mtime" in obj && Object.prototype.toString.call(obj.mtime) === "[object Date]" && "ino" in obj && typeof obj.ino === "number" && "size" in obj && typeof obj.size === "number"; | ||
| } | ||
| /** | ||
| * Generate a tag for a stat. | ||
| */ | ||
| function stattag(stat) { | ||
| const mtime = stat.mtime.getTime().toString(16); | ||
| return "\"" + stat.size.toString(16) + "-" + mtime + "\""; | ||
| } | ||
| /** | ||
| * Create a simple ETag. | ||
| */ | ||
| function etag(entity, options) { | ||
| if (entity == null) throw new TypeError("argument entity is required"); | ||
| const isStats = isstats(entity); | ||
| const weak = options && typeof options.weak === "boolean" ? options.weak : isStats; | ||
| if (!isStats && typeof entity !== "string" && !Buffer.isBuffer(entity)) throw new TypeError("argument entity must be string, Buffer, or fs.Stats"); | ||
| const tag = isStats ? stattag(entity) : entitytag(entity); | ||
| return weak ? "W/" + tag : tag; | ||
| } | ||
| //#endregion | ||
| //#region src/common.ts | ||
| let Status = /* @__PURE__ */ function(Status$1) { | ||
| Status$1[Status$1["onlyFresh"] = 0] = "onlyFresh"; | ||
| Status$1[Status$1["cacheOnFail"] = 1] = "cacheOnFail"; | ||
| Status$1[Status$1["preferCache"] = 2] = "preferCache"; | ||
| Status$1[Status$1["onlyCache"] = 3] = "onlyCache"; | ||
| return Status$1; | ||
| }({}); | ||
| Object.freeze(Status); | ||
| let Type = /* @__PURE__ */ function(Type$1) { | ||
| Type$1["hit"] = "Hit"; | ||
| Type$1["miss"] = "Miss"; | ||
| return Type$1; | ||
| }({}); | ||
| Object.freeze(Type); | ||
| var Hit = class Hit { | ||
| version = 3; | ||
| cacheName; | ||
| cached = false; | ||
| created; | ||
| data; | ||
| error = null; | ||
| errorTime = null; | ||
| consecutiveErrors = 0; | ||
| etag; | ||
| isHit = true; | ||
| isMiss = false; | ||
| constructor(name, data, existingEtag) { | ||
| this.cacheName = name; | ||
| this.created = /* @__PURE__ */ new Date(); | ||
| this.data = data; | ||
| this.etag = existingEtag ?? etag(JSON.stringify(data)); | ||
| } | ||
| withError(error, consecutiveErrors) { | ||
| const hit = new Hit(this.cacheName, this.data, this.etag); | ||
| hit.cached = this.cached; | ||
| hit.created = this.created; | ||
| hit.error = error; | ||
| hit.errorTime = /* @__PURE__ */ new Date(); | ||
| hit.consecutiveErrors = consecutiveErrors; | ||
| return hit; | ||
| } | ||
| }; | ||
| var Miss = class { | ||
| version = 3; | ||
| cacheName; | ||
| cached = false; | ||
| created; | ||
| data = null; | ||
| error; | ||
| errorTime; | ||
| consecutiveErrors; | ||
| etag = null; | ||
| isHit = false; | ||
| isMiss = true; | ||
| constructor(name, error, consecutiveErrors) { | ||
| this.cacheName = name; | ||
| this.created = /* @__PURE__ */ new Date(); | ||
| this.error = error; | ||
| this.errorTime = new Date(this.created); | ||
| this.consecutiveErrors = consecutiveErrors; | ||
| } | ||
| }; | ||
| var Data = class Data { | ||
| version; | ||
| type; | ||
| cacheName; | ||
| created; | ||
| data; | ||
| error; | ||
| errorTime; | ||
| consecutiveErrors; | ||
| etag; | ||
| constructor(version, type, name, created, data, error, errorTime, consecutiveErrors, etag$1) { | ||
| this.version = version; | ||
| this.type = type; | ||
| this.cacheName = name; | ||
| this.created = created; | ||
| this.data = data; | ||
| this.error = error; | ||
| this.errorTime = errorTime; | ||
| this.consecutiveErrors = consecutiveErrors; | ||
| this.etag = etag$1; | ||
| } | ||
| response() { | ||
| if (this.version !== 3) throw new Error(`Unknown cache version number: ${String(this.version)}`); | ||
| let response; | ||
| if (this.type === Type.hit) { | ||
| response = new Hit(this.cacheName, this.data, this.etag ?? void 0); | ||
| response.cached = true; | ||
| response.created = this.created; | ||
| response.consecutiveErrors = this.consecutiveErrors; | ||
| } else { | ||
| response = new Miss(this.cacheName, this.error ?? "", this.consecutiveErrors); | ||
| response.cached = false; | ||
| response.created = this.created; | ||
| response.errorTime = this.errorTime ?? /* @__PURE__ */ new Date(); | ||
| } | ||
| return response; | ||
| } | ||
| stringify() { | ||
| return JSON.stringify({ | ||
| version: this.version, | ||
| type: this.type, | ||
| cacheName: this.cacheName, | ||
| created: this.created, | ||
| data: this.data, | ||
| error: this.error, | ||
| errorTime: this.errorTime, | ||
| consecutiveErrors: this.consecutiveErrors, | ||
| etag: this.etag | ||
| }, null, 2); | ||
| } | ||
| static fromResponse(response) { | ||
| return new Data(response.version, response.isHit ? Type.hit : Type.miss, response.cacheName, response.created, response.data, response.error, response.errorTime, response.consecutiveErrors, response.etag); | ||
| } | ||
| static parse(value) { | ||
| const parsed = JSON.parse(value); | ||
| if (parsed.version === 2) return new Data(3, Type.hit, parsed.cacheName, new Date(parsed.created), parsed.data, null, null, 0, parsed.etag); | ||
| if (parsed.version === 3) return new Data(3, parsed.type, parsed.cacheName, new Date(parsed.created), parsed.data, parsed.error, parsed.errorTime === null ? null : new Date(parsed.errorTime), parsed.consecutiveErrors, parsed.etag); | ||
| throw new Error(`Unknown cache version number: ${String(parsed.version)}`); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/store-filesystem.ts | ||
| async function mkdir(dirpath) { | ||
| await fs.promises.mkdir(dirpath, { recursive: true }); | ||
| } | ||
| function createFilesystemStore(config) { | ||
| return { | ||
| async get(cacheName) { | ||
| const data = await readFile(path.resolve(config.datadir, `${cacheName}.json`), "utf8"); | ||
| return Data.parse(data); | ||
| }, | ||
| async set(data) { | ||
| const filename = path.resolve(config.datadir, `${data.cacheName}.json`); | ||
| await mkdir(path.dirname(filename)); | ||
| await writeFile(filename, data.stringify(), "utf8"); | ||
| }, | ||
| async isset(cacheName) { | ||
| const filename = path.resolve(config.datadir, `${cacheName}.json`); | ||
| try { | ||
| await fs.promises.access(filename); | ||
| return true; | ||
| } catch { | ||
| return false; | ||
| } | ||
| }, | ||
| async unset(cacheName) { | ||
| await rm(path.resolve(config.datadir, `${cacheName}.json`), { force: true }); | ||
| } | ||
| }; | ||
| } | ||
| //#endregion | ||
| //#region src/store-memory.ts | ||
| function createMemoryStore(_config) { | ||
| const storeMemory = { | ||
| data: {}, | ||
| get(cacheName) { | ||
| return Promise.resolve(Data.parse(storeMemory.data[cacheName])); | ||
| }, | ||
| set(data) { | ||
| storeMemory.data[data.cacheName] = data.stringify(); | ||
| return Promise.resolve(); | ||
| }, | ||
| isset(cacheName) { | ||
| return Promise.resolve(cacheName in storeMemory.data); | ||
| }, | ||
| unset(cacheName) { | ||
| delete storeMemory.data[cacheName]; | ||
| return Promise.resolve(); | ||
| } | ||
| }; | ||
| return storeMemory; | ||
| } | ||
| //#endregion | ||
| //#region src/index.ts | ||
| function sanitize(string) { | ||
| return string.replaceAll(/[^a-z0-9]+/gi, "-"); | ||
| } | ||
| var Cacheism = class { | ||
| store; | ||
| status = Status; | ||
| type = Type; | ||
| constructor(store) { | ||
| this.store = store; | ||
| } | ||
| async go(cacheDomain, cachePath, status, callback) { | ||
| if (typeof cacheDomain !== "string") throw new TypeError("cacheDomain must be a string"); | ||
| if (typeof cachePath !== "string") throw new TypeError("cachePath must be a string"); | ||
| if (typeof status !== "number" || status < Status.onlyFresh || status > Status.onlyCache) throw new TypeError("status must be a valid Status value (0-3)"); | ||
| if (typeof callback !== "function") throw new TypeError("callback must be a function"); | ||
| const name = this.cacheName(cacheDomain, cachePath); | ||
| let existing = new Miss(name, "Missing cache", 0); | ||
| const hasCache = await this.store.isset(name); | ||
| if (hasCache) existing = (await this.store.get(name)).response(); | ||
| let response; | ||
| try { | ||
| if (status >= Status.preferCache && hasCache && existing.isHit) response = existing; | ||
| else if (status === Status.onlyCache) throw new Error("Missing cache"); | ||
| else { | ||
| const result = await callback(existing); | ||
| if (result instanceof Hit) response = result; | ||
| else response = new Hit(name, result); | ||
| } | ||
| } catch (err) { | ||
| const errorMessage = err instanceof Error ? err.toString() : String(err); | ||
| if (status >= Status.cacheOnFail && hasCache && existing.isHit) response = existing.withError(errorMessage, existing.consecutiveErrors + 1); | ||
| else response = new Miss(name, errorMessage, existing.consecutiveErrors + 1); | ||
| } | ||
| await this.store.set(Data.fromResponse(response)); | ||
| Object.freeze(response); | ||
| return response; | ||
| } | ||
| cacheName(cacheDomain, cachePath) { | ||
| return `${sanitize(cacheDomain)}/${sanitize(cachePath)}`; | ||
| } | ||
| setStore(store) { | ||
| this.store = store; | ||
| } | ||
| hit(name, data, etag$1) { | ||
| return new Hit(name, data, etag$1); | ||
| } | ||
| miss(name, error) { | ||
| return new Miss(name, error, 0); | ||
| } | ||
| static Hit = Hit; | ||
| static Miss = Miss; | ||
| static Data = Data; | ||
| static Status = Status; | ||
| static Type = Type; | ||
| static store = { | ||
| filesystem: createFilesystemStore, | ||
| memory: createMemoryStore | ||
| }; | ||
| }; | ||
| var src_default = Cacheism; | ||
| //#endregion | ||
| export { Cacheism, Data, Hit, Miss, Status, Type, createFilesystemStore, createMemoryStore, src_default as default, etag }; | ||
| //# sourceMappingURL=index.mjs.map |
| {"version":3,"file":"index.mjs","names":["generateEtag","etag","response: Hit | Miss","storeMemory: MemoryStore","existing: Hit | Miss","response: Hit | Miss","etag"],"sources":["../src/etag.ts","../src/common.ts","../src/store-filesystem.ts","../src/store-memory.ts","../src/index.ts"],"sourcesContent":["/*!\n * etag\n * Copyright(c) 2014-2016 Douglas Christopher Wilson\n * MIT Licensed\n */\n\nimport { createHash } from 'node:crypto';\nimport { Stats } from 'node:fs';\n\nexport interface EtagOptions {\n weak?: boolean;\n}\n\n/**\n * Generate an entity tag.\n */\nfunction entitytag(entity: string | Buffer): string {\n if (entity.length === 0) {\n // fast-path empty\n return '\"0-2jmj7l5rSw0yVb/vlWAYkK/YBwk\"';\n }\n\n // compute hash of entity\n const hashObj = createHash('sha1');\n if (typeof entity === 'string') {\n hashObj.update(entity, 'utf8');\n } else {\n hashObj.update(entity);\n }\n const hash = hashObj.digest('base64').substring(0, 27);\n\n // compute length of entity\n const len = typeof entity === 'string'\n ? Buffer.byteLength(entity, 'utf8')\n : entity.length;\n\n return '\"' + len.toString(16) + '-' + hash + '\"';\n}\n\n/**\n * Determine if object is a Stats object.\n */\nfunction isstats(obj: unknown): obj is Stats {\n // genuine fs.Stats\n if (typeof Stats === 'function' && obj instanceof Stats) {\n return true;\n }\n\n // quack quack\n return obj != null && typeof obj === 'object' &&\n 'ctime' in obj && Object.prototype.toString.call((obj as Stats).ctime) === '[object Date]' &&\n 'mtime' in obj && Object.prototype.toString.call((obj as Stats).mtime) === '[object Date]' &&\n 'ino' in obj && typeof (obj as Stats).ino === 'number' &&\n 'size' in obj && typeof (obj as Stats).size === 'number';\n}\n\n/**\n * Generate a tag for a stat.\n */\nfunction stattag(stat: Stats): string {\n const mtime = stat.mtime.getTime().toString(16);\n const size = stat.size.toString(16);\n\n return '\"' + size + '-' + mtime + '\"';\n}\n\n/**\n * Create a simple ETag.\n */\nexport function etag(entity: string | Buffer | Stats | null | undefined, options?: EtagOptions): string {\n if (entity == null) {\n throw new TypeError('argument entity is required');\n }\n\n // support fs.Stats object\n const isStats = isstats(entity);\n const weak = options && typeof options.weak === 'boolean'\n ? options.weak\n : isStats;\n\n // validate argument\n if (!isStats && typeof entity !== 'string' && !Buffer.isBuffer(entity)) {\n throw new TypeError('argument entity must be string, Buffer, or fs.Stats');\n }\n\n // generate entity tag\n const tag = isStats\n ? stattag(entity)\n : entitytag(entity);\n\n return weak\n ? 'W/' + tag\n : tag;\n}\n\nexport default etag;\n","import { etag as generateEtag } from './etag.js';\n\nexport enum Status {\n onlyFresh = 0,\n cacheOnFail = 1,\n preferCache = 2,\n onlyCache = 3,\n}\nObject.freeze(Status);\n\nexport enum Type {\n hit = 'Hit',\n miss = 'Miss',\n}\nObject.freeze(Type);\n\nexport class Hit {\n readonly version = 3;\n readonly cacheName: string;\n cached = false;\n created: Date;\n data: unknown;\n error: string | null = null;\n errorTime: Date | null = null;\n consecutiveErrors = 0;\n etag: string;\n readonly isHit = true;\n readonly isMiss = false;\n\n constructor(name: string, data: unknown, existingEtag?: string) {\n this.cacheName = name;\n this.created = new Date();\n this.data = data;\n this.etag = existingEtag ?? generateEtag(JSON.stringify(data));\n }\n\n withError(error: string, consecutiveErrors: number): Hit {\n const hit = new Hit(this.cacheName, this.data, this.etag);\n hit.cached = this.cached;\n hit.created = this.created;\n hit.error = error;\n hit.errorTime = new Date();\n hit.consecutiveErrors = consecutiveErrors;\n return hit;\n }\n}\n\nexport class Miss {\n readonly version = 3;\n readonly cacheName: string;\n cached = false;\n created: Date;\n readonly data = null;\n error: string;\n errorTime: Date;\n consecutiveErrors: number;\n readonly etag = null;\n readonly isHit = false;\n readonly isMiss = true;\n\n constructor(name: string, error: string, consecutiveErrors: number) {\n this.cacheName = name;\n this.created = new Date();\n this.error = error;\n this.errorTime = new Date(this.created);\n this.consecutiveErrors = consecutiveErrors;\n }\n}\n\ninterface ParsedData {\n version: number;\n type: string;\n cacheName: string;\n created: string;\n data: unknown;\n error: string | null;\n errorTime: string | null;\n consecutiveErrors: number;\n etag: string | null;\n}\n\nexport class Data {\n version: number;\n type: Type;\n cacheName: string;\n created: Date;\n data: unknown;\n error: string | null;\n errorTime: Date | null;\n consecutiveErrors: number;\n etag: string | null;\n\n constructor(\n version: number,\n type: Type,\n name: string,\n created: Date,\n data: unknown,\n error: string | null,\n errorTime: Date | null,\n consecutiveErrors: number,\n etag: string | null\n ) {\n this.version = version;\n this.type = type;\n this.cacheName = name;\n this.created = created;\n this.data = data;\n this.error = error;\n this.errorTime = errorTime;\n this.consecutiveErrors = consecutiveErrors;\n this.etag = etag;\n }\n\n response(): Hit | Miss {\n if (this.version !== 3) {\n throw new Error(`Unknown cache version number: ${String(this.version)}`);\n }\n\n let response: Hit | Miss;\n\n if (this.type === Type.hit) {\n response = new Hit(this.cacheName, this.data, this.etag ?? undefined);\n response.cached = true;\n response.created = this.created;\n response.consecutiveErrors = this.consecutiveErrors;\n } else {\n response = new Miss(this.cacheName, this.error ?? '', this.consecutiveErrors);\n response.cached = false;\n response.created = this.created;\n response.errorTime = this.errorTime ?? new Date();\n }\n\n return response;\n }\n\n stringify(): string {\n return JSON.stringify({\n version: this.version,\n type: this.type,\n cacheName: this.cacheName,\n created: this.created,\n data: this.data,\n error: this.error,\n errorTime: this.errorTime,\n consecutiveErrors: this.consecutiveErrors,\n etag: this.etag,\n }, null, 2);\n }\n\n static fromResponse(response: Hit | Miss): Data {\n return new Data(\n response.version,\n response.isHit ? Type.hit : Type.miss,\n response.cacheName,\n response.created,\n response.data,\n response.error,\n response.errorTime,\n response.consecutiveErrors,\n response.etag\n );\n }\n\n static parse(value: string): Data {\n const parsed = JSON.parse(value) as ParsedData;\n\n if (parsed.version === 2) {\n return new Data(\n 3,\n Type.hit,\n parsed.cacheName,\n new Date(parsed.created),\n parsed.data,\n null,\n null,\n 0,\n parsed.etag\n );\n }\n\n if (parsed.version === 3) {\n return new Data(\n 3,\n parsed.type as Type,\n parsed.cacheName,\n new Date(parsed.created),\n parsed.data,\n parsed.error,\n parsed.errorTime === null ? null : new Date(parsed.errorTime),\n parsed.consecutiveErrors,\n parsed.etag\n );\n }\n\n throw new Error(`Unknown cache version number: ${String(parsed.version)}`);\n }\n}\n","import path from 'node:path';\nimport fs from 'node:fs';\nimport { readFile, writeFile, rm } from 'node:fs/promises';\nimport { Data } from './common.js';\nimport type { Store } from './store-memory.js';\n\nexport interface FilesystemStoreConfig {\n datadir: string;\n}\n\nasync function mkdir(dirpath: string): Promise<void> {\n await fs.promises.mkdir(dirpath, { recursive: true });\n}\n\nexport function createFilesystemStore(config: FilesystemStoreConfig): Store {\n return {\n async get(cacheName: string): Promise<Data> {\n const filename = path.resolve(config.datadir, `${cacheName}.json`);\n const data = await readFile(filename, 'utf8');\n return Data.parse(data);\n },\n\n async set(data: Data): Promise<void> {\n const filename = path.resolve(config.datadir, `${data.cacheName}.json`);\n await mkdir(path.dirname(filename));\n await writeFile(filename, data.stringify(), 'utf8');\n },\n\n async isset(cacheName: string): Promise<boolean> {\n const filename = path.resolve(config.datadir, `${cacheName}.json`);\n try {\n await fs.promises.access(filename);\n return true;\n } catch {\n return false;\n }\n },\n\n async unset(cacheName: string): Promise<void> {\n const filename = path.resolve(config.datadir, `${cacheName}.json`);\n await rm(filename, { force: true });\n }\n };\n}\n\nexport default createFilesystemStore;\n","import { Data } from './common.js';\n\nexport interface Store {\n get(cacheName: string): Promise<Data>;\n set(data: Data): Promise<void>;\n isset(cacheName: string): Promise<boolean>;\n unset(cacheName: string): Promise<void>;\n}\n\nexport interface MemoryStore extends Store {\n data: Record<string, string>;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type -- Future expansion\nexport interface MemoryStoreConfig {}\n\nexport function createMemoryStore(_config?: MemoryStoreConfig): MemoryStore {\n const storeMemory: MemoryStore = {\n data: {},\n\n get(cacheName: string): Promise<Data> {\n return Promise.resolve(Data.parse(storeMemory.data[cacheName]));\n },\n\n set(data: Data): Promise<void> {\n storeMemory.data[data.cacheName] = data.stringify();\n return Promise.resolve();\n },\n\n isset(cacheName: string): Promise<boolean> {\n return Promise.resolve(cacheName in storeMemory.data);\n },\n\n unset(cacheName: string): Promise<void> {\n // eslint-disable-next-line @typescript-eslint/no-dynamic-delete\n delete storeMemory.data[cacheName];\n return Promise.resolve();\n }\n };\n\n return storeMemory;\n}\n\nexport default createMemoryStore;\n","import { Hit, Miss, Data, Status, Type } from './common.js';\nimport { etag } from './etag.js';\nimport { createFilesystemStore } from './store-filesystem.js';\nimport { createMemoryStore, type Store } from './store-memory.js';\n\nfunction sanitize(string: string): string {\n return string.replaceAll(/[^a-z0-9]+/gi, '-');\n}\n\nexport class Cacheism {\n store: Store;\n status = Status;\n type = Type;\n\n constructor(store: Store) {\n this.store = store;\n }\n\n async go(\n cacheDomain: string,\n cachePath: string,\n status: Status,\n callback: (existing: Hit | Miss) => Promise<unknown>\n ): Promise<Hit | Miss> {\n // Input validation\n if (typeof cacheDomain !== 'string') {\n throw new TypeError('cacheDomain must be a string');\n }\n if (typeof cachePath !== 'string') {\n throw new TypeError('cachePath must be a string');\n }\n if (typeof status !== 'number' || status < Status.onlyFresh || status > Status.onlyCache) {\n throw new TypeError('status must be a valid Status value (0-3)');\n }\n if (typeof callback !== 'function') {\n throw new TypeError('callback must be a function');\n }\n\n const name = this.cacheName(cacheDomain, cachePath);\n let existing: Hit | Miss = new Miss(name, 'Missing cache', 0);\n const hasCache = await this.store.isset(name);\n\n if (hasCache) {\n existing = (await this.store.get(name)).response();\n }\n\n let response: Hit | Miss;\n\n try {\n if (status >= Status.preferCache && hasCache && existing.isHit) {\n response = existing;\n } else if (status === Status.onlyCache) {\n throw new Error('Missing cache');\n } else {\n const result = await callback(existing);\n if (result instanceof Hit) {\n response = result;\n } else {\n response = new Hit(name, result);\n }\n }\n } catch (err) {\n const errorMessage = err instanceof Error ? err.toString() : String(err);\n\n if (status >= Status.cacheOnFail && hasCache && existing.isHit) {\n response = existing.withError(errorMessage, existing.consecutiveErrors + 1);\n } else {\n response = new Miss(name, errorMessage, existing.consecutiveErrors + 1);\n }\n }\n\n await this.store.set(Data.fromResponse(response));\n Object.freeze(response);\n return response;\n }\n\n cacheName(cacheDomain: string, cachePath: string): string {\n return `${sanitize(cacheDomain)}/${sanitize(cachePath)}`;\n }\n\n setStore(store: Store): void {\n this.store = store;\n }\n\n hit(name: string, data: unknown, etag?: string): Hit {\n return new Hit(name, data, etag);\n }\n\n miss(name: string, error: string): Miss {\n return new Miss(name, error, 0);\n }\n\n // Static members for backward compatibility\n static Hit = Hit;\n static Miss = Miss;\n static Data = Data;\n static Status = Status;\n static Type = Type;\n\n static store = {\n filesystem: createFilesystemStore,\n memory: createMemoryStore,\n };\n}\n\n// Named exports for ESM users\nexport { Hit, Miss, Data, Status, Type };\nexport { etag };\nexport { createFilesystemStore, createMemoryStore };\nexport type { Store } from './store-memory.js';\nexport type { FilesystemStoreConfig } from './store-filesystem.js';\nexport type { MemoryStoreConfig, MemoryStore } from './store-memory.js';\n\n// Default export for backward compatibility\nexport default Cacheism;\n"],"mappings":"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"} |
+45
-9
| { | ||
| "name": "@andrewshell/cacheism", | ||
| "version": "2.0.0", | ||
| "version": "3.0.0", | ||
| "description": "Simple caching library", | ||
| "main": "lib/cacheism.js", | ||
| "type": "module", | ||
| "main": "./dist/index.cjs", | ||
| "module": "./dist/index.mjs", | ||
| "types": "./dist/index.d.mts", | ||
| "exports": { | ||
| ".": { | ||
| "import": { | ||
| "types": "./dist/index.d.mts", | ||
| "default": "./dist/index.mjs" | ||
| }, | ||
| "require": { | ||
| "types": "./dist/index.d.cts", | ||
| "default": "./dist/index.cjs" | ||
| } | ||
| } | ||
| }, | ||
| "files": [ | ||
| "dist" | ||
| ], | ||
| "packageManager": "pnpm@9.15.1", | ||
| "engines": { | ||
| "node": ">=20.0.0" | ||
| }, | ||
| "scripts": { | ||
| "test": "mocha" | ||
| "build": "tsdown", | ||
| "test": "pnpm build && c8 node --test test/*.test.cjs", | ||
| "coverage": "c8 report --reporter=text-lcov", | ||
| "typecheck": "tsc --noEmit", | ||
| "lint": "eslint src/ test/", | ||
| "lint:fix": "eslint src/ test/ --fix", | ||
| "prepublishOnly": "pnpm build", | ||
| "prepare": "husky" | ||
| }, | ||
@@ -24,9 +53,16 @@ "repository": { | ||
| "devDependencies": { | ||
| "expect.js": "^0.3.1", | ||
| "mocha": "^10.2.0", | ||
| "mockdate": "^3.0.5" | ||
| }, | ||
| "dependencies": { | ||
| "etag": "^1.8.1" | ||
| "@commitlint/cli": "^20.2.0", | ||
| "@commitlint/config-conventional": "^20.2.0", | ||
| "@eslint/js": "^9.39.1", | ||
| "@types/node": "^25.0.0", | ||
| "c8": "^10.1.3", | ||
| "eslint": "^9.39.1", | ||
| "husky": "^9.1.7", | ||
| "mockdate": "^3.0.5", | ||
| "safe-buffer": "^5.2.1", | ||
| "seedrandom": "^3.0.5", | ||
| "tsdown": "^0.17.2", | ||
| "typescript": "^5.9.3", | ||
| "typescript-eslint": "^8.49.0" | ||
| } | ||
| } |
| # This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node | ||
| # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs | ||
| name: Node.js CI | ||
| on: | ||
| push: | ||
| branches: [ master ] | ||
| pull_request: | ||
| branches: [ master ] | ||
| jobs: | ||
| build: | ||
| runs-on: ubuntu-latest | ||
| strategy: | ||
| matrix: | ||
| node-version: [14.x, 16.x, 18.x] | ||
| # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ | ||
| steps: | ||
| - uses: actions/checkout@v3 | ||
| - name: Use Node.js ${{ matrix.node-version }} | ||
| uses: actions/setup-node@v3 | ||
| with: | ||
| node-version: ${{ matrix.node-version }} | ||
| cache: 'npm' | ||
| - run: npm ci | ||
| - name: Archive npm failure logs | ||
| uses: actions/upload-artifact@v3 | ||
| if: failure() | ||
| with: | ||
| name: npm-logs | ||
| path: ~/.npm/_logs | ||
| - run: npm test |
| const common = require('./common'); | ||
| function _sanitize(string) { | ||
| return string.replaceAll(new RegExp('[^a-z0-9]+', 'ig'), '-'); | ||
| } | ||
| function Cacheism(store) { | ||
| this.store = store; | ||
| this.status = common.Status; | ||
| this.type = common.Type; | ||
| } | ||
| Cacheism.prototype.go = async function (cacheDomain, cachePath, status, callback) { | ||
| let response, name = this.cacheName(cacheDomain, cachePath); | ||
| let existing = new common.Miss(name, new Error('Missing cache'), 0); | ||
| let hasCache = await this.store.isset(name); | ||
| if (hasCache) { | ||
| existing = (await this.store.get(name)).response(); | ||
| } | ||
| try { | ||
| if (status >= this.status.preferCache && hasCache && existing.isHit) { | ||
| response = existing; | ||
| } else if (status === this.status.onlyCache) { | ||
| throw new Error('Missing cache'); | ||
| } else { | ||
| response = await callback(existing); | ||
| if (!(response instanceof common.Hit)) { | ||
| response = new common.Hit(name, response); | ||
| } | ||
| } | ||
| } catch (err) { | ||
| if (status >= this.status.cacheOnFail && hasCache) { | ||
| response = existing; | ||
| response.error = err.toString(); | ||
| response.errorTime = new Date(); | ||
| response.consecutiveErrors++; | ||
| } else { | ||
| response = new common.Miss( | ||
| name, | ||
| err.toString(), | ||
| existing.consecutiveErrors + 1 | ||
| ); | ||
| } | ||
| } | ||
| await this.store.set(common.Data.fromResponse(response)); | ||
| Object.freeze(response); | ||
| return response; | ||
| } | ||
| Cacheism.prototype.cacheName = function (cacheDomain, cachePath) { | ||
| return `${_sanitize(cacheDomain)}/${_sanitize(cachePath)}`; | ||
| } | ||
| Cacheism.prototype.setStore = function (store) { | ||
| this.store = store; | ||
| } | ||
| Cacheism.prototype.hit = function (name, data, etag) { | ||
| return new common.Hit(name, data, etag); | ||
| } | ||
| Cacheism.prototype.miss = function (name, error) { | ||
| return new common.Miss(name, error); | ||
| } | ||
| Cacheism.Hit = common.Hit; | ||
| Cacheism.Miss = common.Miss; | ||
| Cacheism.Data = common.Data; | ||
| Cacheism.Status = common.Status; | ||
| Cacheism.Type = common.Type; | ||
| Cacheism.store = { | ||
| filesystem: require('./store-filesystem'), | ||
| memory: require('./store-memory'), | ||
| }; | ||
| module.exports = Cacheism; |
-167
| const generateEtag = require('etag'); | ||
| class Status {} | ||
| Status.onlyFresh = 0; | ||
| Status.cacheOnFail = 1; | ||
| Status.preferCache = 2; | ||
| Status.onlyCache = 3; | ||
| class Type {} | ||
| Type.hit = 'Hit'; | ||
| Type.miss = 'Miss'; | ||
| Object.freeze(Status); | ||
| class Hit { | ||
| constructor(name, data, etag) { | ||
| this.version = 3; | ||
| this.cacheName = name; | ||
| this.cached = false; | ||
| this.created = new Date(); | ||
| this.data = data; | ||
| this.error = null; | ||
| this.errorTime = null; | ||
| this.consecutiveErrors = 0; | ||
| this.etag = null == etag ? generateEtag(JSON.stringify(data)) : etag; | ||
| this.isHit = true; | ||
| this.isMiss = false; | ||
| } | ||
| } | ||
| class Miss { | ||
| constructor(name, error, consecutiveErrors) { | ||
| this.version = 3; | ||
| this.cacheName = name; | ||
| this.cached = false; | ||
| this.created = new Date(); | ||
| this.data = null; | ||
| this.error = error; | ||
| this.errorTime = new Date(this.created); | ||
| this.consecutiveErrors = consecutiveErrors; | ||
| this.etag = null; | ||
| this.isHit = false; | ||
| this.isMiss = true; | ||
| } | ||
| } | ||
| class Data { | ||
| constructor(version, type, name, created, data, error, errorTime, consecutiveErrors, etag) { | ||
| this.version = version; | ||
| this.type = type; | ||
| this.cacheName = name; | ||
| this.created = created; | ||
| this.data = data; | ||
| this.error = error; | ||
| this.errorTime = errorTime; | ||
| this.consecutiveErrors = consecutiveErrors; | ||
| this.etag = etag; | ||
| } | ||
| response() { | ||
| if (3 !== this.version) { | ||
| throw new Error(`Unknown cache version number: ${this.version}`); | ||
| } | ||
| let response; | ||
| if (this.type === Type.hit) { | ||
| response = new Hit(this.cacheName, this.data, this.etag); | ||
| response.cached = true; | ||
| response.created = this.created; | ||
| response.consecutiveErrors = this.consecutiveErrors; | ||
| } else { | ||
| response = new Miss(this.cacheName, this.error, this.consecutiveErrors); | ||
| response.cached = false; | ||
| response.created = this.created; | ||
| response.errorTime = this.errorTime; | ||
| } | ||
| return response; | ||
| } | ||
| stringify() { | ||
| return JSON.stringify({ | ||
| version: this.version, | ||
| type: this.type, | ||
| cacheName: this.cacheName, | ||
| created: this.created, | ||
| data: this.data, | ||
| error: this.error, | ||
| errorTime: this.errorTime, | ||
| consecutiveErrors: this.consecutiveErrors, | ||
| etag: this.etag, | ||
| }, null, 2); | ||
| } | ||
| static fromResponse(response) { | ||
| return new Data( | ||
| response.version, | ||
| response.isHit ? Type.hit : Type.miss, | ||
| response.cacheName, | ||
| response.created, | ||
| response.data, | ||
| response.error, | ||
| response.errorTime, | ||
| response.consecutiveErrors, | ||
| response.etag | ||
| ); | ||
| } | ||
| static parse(value) { | ||
| const parsed = JSON.parse(value); | ||
| if (2 === parsed.version) { | ||
| return new Data( | ||
| 3, | ||
| Type.hit, | ||
| parsed.cacheName, | ||
| new Date(parsed.created), | ||
| parsed.data, | ||
| null, | ||
| null, | ||
| 0, | ||
| parsed.etag | ||
| ); | ||
| } | ||
| if (3 === parsed.version) { | ||
| return new Data( | ||
| 3, | ||
| parsed.type, | ||
| parsed.cacheName, | ||
| new Date(parsed.created), | ||
| parsed.data, | ||
| parsed.error, | ||
| null === parsed.errorTime ? null : new Date(parsed.errorTime), | ||
| parsed.consecutiveErrors, | ||
| parsed.etag | ||
| ); | ||
| } | ||
| throw new Error(`Unknown cache version number: ${parsed.version}`); | ||
| } | ||
| } | ||
| module.exports = { Hit, Miss, Data, Status, Type }; | ||
| /** | ||
| * String.prototype.replaceAll() polyfill | ||
| * https://gomakethings.com/how-to-replace-a-section-of-a-string-with-another-one-with-vanilla-js/ | ||
| * @author Chris Ferdinandi | ||
| * @license MIT | ||
| */ | ||
| if (!String.prototype.replaceAll) { | ||
| String.prototype.replaceAll = function(str, newStr){ | ||
| // If a regex pattern | ||
| if (Object.prototype.toString.call(str).toLowerCase() === '[object regexp]') { | ||
| return this.replace(str, newStr); | ||
| } | ||
| // If a string | ||
| return this.replace(new RegExp(str, 'g'), newStr); | ||
| }; | ||
| } |
| const common = require('./common'); | ||
| const storeFilesystem = { }; | ||
| const path = require('path'); | ||
| const fs = require('fs'); | ||
| const fsPromises = require('fs/promises'); | ||
| module.exports = function (config) { | ||
| function _mkdir(dirpath) { | ||
| if (false === fs.existsSync(dirpath)) { | ||
| fs.mkdirSync(dirpath, { recursive: true }); | ||
| } | ||
| } | ||
| _mkdir(config.datadir); | ||
| storeFilesystem.get = async (cacheName) => { | ||
| const filename = path.resolve(config.datadir, `${cacheName}.json`); | ||
| const data = await fsPromises.readFile(filename, 'utf8'); | ||
| return common.Data.parse(data); | ||
| } | ||
| storeFilesystem.set = async (data) => { | ||
| const filename = path.resolve(config.datadir, `${data.cacheName}.json`); | ||
| _mkdir(path.dirname(filename)); | ||
| await fsPromises.writeFile(filename, data.stringify(), 'utf8'); | ||
| } | ||
| storeFilesystem.isset = async (cacheName) => { | ||
| const filename = path.resolve(config.datadir, `${cacheName}.json`); | ||
| return fs.existsSync(filename); | ||
| } | ||
| storeFilesystem.unset = async (cacheName) => { | ||
| const filename = path.resolve(config.datadir, `${cacheName}.json`); | ||
| await fsPromises.rm(filename, { force: true }); | ||
| } | ||
| return storeFilesystem; | ||
| } |
| const common = require('./common'); | ||
| module.exports = function (config) { | ||
| const storeMemory = { data: {} }; | ||
| storeMemory.get = async (cacheName) => { | ||
| return common.Data.parse(storeMemory.data[cacheName]); | ||
| } | ||
| storeMemory.set = async (data) => { | ||
| storeMemory.data[data.cacheName] = data.stringify(); | ||
| } | ||
| storeMemory.isset = async (cacheName) => { | ||
| return null != storeMemory.data[cacheName]; | ||
| } | ||
| storeMemory.unset = async (cacheName) => { | ||
| delete storeMemory.data[cacheName]; | ||
| } | ||
| return storeMemory; | ||
| } |
| const expect = require('expect.js'); | ||
| const mockdate = require('mockdate'); | ||
| const fs = require('fs'); | ||
| const path = require('path'); | ||
| const datadir = path.resolve(__dirname, './cache-filesystem'); | ||
| const Cacheism = require('../lib/cacheism'); | ||
| const cache = new Cacheism(Cacheism.store.filesystem({ datadir })); | ||
| const helpers = require('./helpers'); | ||
| describe('filesystem', function() { | ||
| beforeEach(function() { | ||
| // runs before each test in this block | ||
| if (fs.existsSync(datadir)) { | ||
| fs.rmSync(datadir, { recursive: true, force: true }); | ||
| } | ||
| }); | ||
| after(function() { | ||
| // runs once after the last test in this block | ||
| if (fs.existsSync(datadir)) { | ||
| fs.rmSync(datadir, { recursive: true, force: true }); | ||
| } | ||
| }); | ||
| it('should export as a function', function() { | ||
| expect(cache.go).to.be.a('function'); | ||
| }); | ||
| describe('when status=onlyFresh', async function () { | ||
| describe('and no existing cache', async function () { | ||
| it('should return a Hit (live value) on success', async function () { | ||
| const c = await cache.go('-internal', 'cache', Cacheism.Status.onlyFresh, async () => { | ||
| return 'live'; | ||
| }); | ||
| helpers.expectCacheHit(c, false, 'live'); | ||
| helpers.expectCacheNoErrors(c); | ||
| expect(await cache.store.isset('-internal/cache')).to.be(true); | ||
| const d = await cache.store.get('-internal/cache'); | ||
| helpers.expectDataHit(d, 'live', c.etag); | ||
| helpers.expectDataNoErrors(d); | ||
| }); | ||
| it('should return a Miss on error', async function () { | ||
| const c = await cache.go('-internal', 'cache', Cacheism.Status.onlyFresh, async () => { | ||
| throw Error('cache error'); | ||
| }); | ||
| helpers.expectCacheMiss(c, false, null); | ||
| helpers.expectCacheErrors(c, 'cache error', 1); | ||
| expect(c.created).to.eql(c.errorTime); | ||
| expect(await cache.store.isset('-internal/cache')).to.be(true); | ||
| const d = await cache.store.get('-internal/cache'); | ||
| helpers.expectDataMiss(d, null); | ||
| helpers.expectDataErrors(d, 'Error: cache error', 1); | ||
| expect(d.created).to.eql(d.errorTime); | ||
| }); | ||
| }); | ||
| describe('and having existing cache', async function () { | ||
| it('should return a Hit (live value) on success', async function () { | ||
| mockdate.set('2000-11-22'); | ||
| await cache.store.set(Cacheism.Data.fromResponse( | ||
| new Cacheism.Hit('-internal/cache', 'cached') | ||
| )); | ||
| mockdate.reset(); | ||
| const c = await cache.go('-internal', 'cache', Cacheism.Status.onlyFresh, async () => { | ||
| return 'live'; | ||
| }); | ||
| helpers.expectCacheHit(c, false, 'live'); | ||
| helpers.expectCacheNoErrors(c); | ||
| expect(await cache.store.isset('-internal/cache')).to.be(true); | ||
| const d = await cache.store.get('-internal/cache'); | ||
| helpers.expectDataHit(d, 'live', c.etag); | ||
| helpers.expectDataNoErrors(d); | ||
| }); | ||
| it('should return a Miss on error', async function () { | ||
| mockdate.set('2000-11-22'); | ||
| await cache.store.set(Cacheism.Data.fromResponse( | ||
| new Cacheism.Hit('-internal/cache', 'cached') | ||
| )); | ||
| mockdate.reset(); | ||
| const c = await cache.go('-internal', 'cache', Cacheism.Status.onlyFresh, async () => { | ||
| throw Error('cache error'); | ||
| }); | ||
| helpers.expectCacheMiss(c, false, null); | ||
| helpers.expectCacheErrors(c, 'cache error', 1); | ||
| expect(c.created).to.eql(c.errorTime); | ||
| expect(await cache.store.isset('-internal/cache')).to.be(true); | ||
| const d = await cache.store.get('-internal/cache'); | ||
| helpers.expectDataMiss(d, null); | ||
| helpers.expectDataErrors(d, 'Error: cache error', 1); | ||
| expect(d.created).to.eql(d.errorTime); | ||
| }); | ||
| }); | ||
| }); | ||
| describe('when status=cacheOnFail', function () { | ||
| describe('and no existing cache', async function () { | ||
| it('should return a Hit (live value) on success', async function () { | ||
| const c = await cache.go('-internal', 'cache', Cacheism.Status.cacheOnFail, async () => { | ||
| return 'live'; | ||
| }); | ||
| helpers.expectCacheHit(c, false, 'live'); | ||
| helpers.expectCacheNoErrors(c); | ||
| expect(await cache.store.isset('-internal/cache')).to.be(true); | ||
| const d = await cache.store.get('-internal/cache'); | ||
| helpers.expectDataHit(d, 'live', c.etag); | ||
| helpers.expectDataNoErrors(d); | ||
| }); | ||
| it('should return a Miss on error', async function () { | ||
| const c = await cache.go('-internal', 'cache', Cacheism.Status.cacheOnFail, async () => { | ||
| throw Error('cache error'); | ||
| }); | ||
| helpers.expectCacheMiss(c, false, null); | ||
| helpers.expectCacheErrors(c, 'cache error', 1); | ||
| expect(c.created).to.eql(c.errorTime); | ||
| expect(await cache.store.isset('-internal/cache')).to.be(true); | ||
| const d = await cache.store.get('-internal/cache'); | ||
| helpers.expectDataMiss(d, null, c.etag); | ||
| helpers.expectDataErrors(d, 'Error: cache error', 1); | ||
| expect(d.created).to.eql(d.errorTime); | ||
| }); | ||
| }); | ||
| describe('and having existing cache', async function () { | ||
| it('should return a Hit (live value) on success', async function () { | ||
| mockdate.set('2000-11-22'); | ||
| await cache.store.set(Cacheism.Data.fromResponse( | ||
| new Cacheism.Hit('-internal/cache', 'cached') | ||
| )); | ||
| mockdate.reset(); | ||
| const c = await cache.go('-internal', 'cache', Cacheism.Status.cacheOnFail, async () => { | ||
| return 'live'; | ||
| }); | ||
| helpers.expectCacheHit(c, false, 'live'); | ||
| helpers.expectCacheNoErrors(c); | ||
| expect(await cache.store.isset('-internal/cache')).to.be(true); | ||
| const d = await cache.store.get('-internal/cache'); | ||
| helpers.expectDataHit(d, 'live', c.etag); | ||
| helpers.expectDataNoErrors(d); | ||
| }); | ||
| it('should return a Hit (cached value) on error', async function () { | ||
| mockdate.set('2000-11-22'); | ||
| await cache.store.set(Cacheism.Data.fromResponse( | ||
| new Cacheism.Hit('-internal/cache', 'cached') | ||
| )); | ||
| mockdate.reset(); | ||
| const c = await cache.go('-internal', 'cache', Cacheism.Status.cacheOnFail, async () => { | ||
| throw Error('cache error'); | ||
| }); | ||
| helpers.expectCacheHit(c, true, 'cached'); | ||
| helpers.expectCacheErrors(c, 'cache error', 1); | ||
| expect(c.created).not.to.eql(c.errorTime); | ||
| expect(await cache.store.isset('-internal/cache')).to.be(true); | ||
| const d = await cache.store.get('-internal/cache'); | ||
| helpers.expectDataHit(d, 'cached', c.etag); | ||
| helpers.expectDataErrors(d, 'Error: cache error', 1); | ||
| expect(d.created).not.to.eql(d.errorTime); | ||
| }); | ||
| }); | ||
| }); | ||
| describe('when status=preferCache', function () { | ||
| describe('and no existing cache', async function () { | ||
| it('should return a Hit (live value) on success', async function () { | ||
| const c = await cache.go('-internal', 'cache', Cacheism.Status.preferCache, async () => { | ||
| return 'live'; | ||
| }); | ||
| helpers.expectCacheHit(c, false, 'live'); | ||
| helpers.expectCacheNoErrors(c); | ||
| expect(await cache.store.isset('-internal/cache')).to.be(true); | ||
| const d = await cache.store.get('-internal/cache'); | ||
| helpers.expectDataHit(d, 'live', c.etag); | ||
| helpers.expectDataNoErrors(d); | ||
| }); | ||
| it('should return a Miss on error', async function () { | ||
| const c = await cache.go('-internal', 'cache', Cacheism.Status.preferCache, async () => { | ||
| throw Error('cache error'); | ||
| }); | ||
| helpers.expectCacheMiss(c, false, null); | ||
| helpers.expectCacheErrors(c, 'cache error', 1); | ||
| expect(c.created).to.eql(c.errorTime); | ||
| expect(await cache.store.isset('-internal/cache')).to.be(true); | ||
| const d = await cache.store.get('-internal/cache'); | ||
| helpers.expectDataMiss(d, null, c.etag); | ||
| helpers.expectDataErrors(d, 'Error: cache error', 1); | ||
| expect(d.created).to.eql(d.errorTime); | ||
| }); | ||
| }); | ||
| describe('and having existing cache', async function () { | ||
| it('should return a Hit (cached value) on success', async function () { | ||
| mockdate.set('2000-11-22'); | ||
| await cache.store.set(Cacheism.Data.fromResponse( | ||
| new Cacheism.Hit('-internal/cache', 'cached') | ||
| )); | ||
| mockdate.reset(); | ||
| const c = await cache.go('-internal', 'cache', Cacheism.Status.preferCache, async () => { | ||
| return 'live'; | ||
| }); | ||
| helpers.expectCacheHit(c, true, 'cached'); | ||
| helpers.expectCacheNoErrors(c); | ||
| expect(await cache.store.isset('-internal/cache')).to.be(true); | ||
| const d = await cache.store.get('-internal/cache'); | ||
| helpers.expectDataHit(d, 'cached', c.etag); | ||
| helpers.expectDataNoErrors(d); | ||
| }); | ||
| it('should return a Hit (cached value) on error', async function () { | ||
| mockdate.set('2000-11-22'); | ||
| await cache.store.set(Cacheism.Data.fromResponse( | ||
| new Cacheism.Hit('-internal/cache', 'cached') | ||
| )); | ||
| mockdate.reset(); | ||
| const c = await cache.go('-internal', 'cache', Cacheism.Status.preferCache, async () => { | ||
| throw Error('cache error'); | ||
| }); | ||
| helpers.expectCacheHit(c, true, 'cached'); | ||
| helpers.expectCacheNoErrors(c); | ||
| expect(await cache.store.isset('-internal/cache')).to.be(true); | ||
| const d = await cache.store.get('-internal/cache'); | ||
| helpers.expectDataHit(d, 'cached', c.etag); | ||
| helpers.expectDataNoErrors(d); | ||
| }); | ||
| }); | ||
| }); | ||
| describe('when status=onlyCache', function () { | ||
| describe('and no existing cache', async function () { | ||
| it('should return a Miss on success', async function () { | ||
| const c = await cache.go('-internal', 'cache', Cacheism.Status.onlyCache, async () => { | ||
| return 'live'; | ||
| }); | ||
| helpers.expectCacheMiss(c, false, null); | ||
| helpers.expectCacheErrors(c, 'Missing cache', 1); | ||
| expect(c.created).to.eql(c.errorTime); | ||
| expect(await cache.store.isset('-internal/cache')).to.be(true); | ||
| const d = await cache.store.get('-internal/cache'); | ||
| helpers.expectDataMiss(d, null, null); | ||
| helpers.expectDataErrors(d, 'Error: Missing cache', 1); | ||
| expect(d.created).to.eql(d.errorTime); | ||
| }); | ||
| it('should return a Miss on error', async function () { | ||
| const c = await cache.go('-internal', 'cache', Cacheism.Status.onlyCache, async () => { | ||
| throw Error('cache error'); | ||
| }); | ||
| helpers.expectCacheMiss(c, false, null); | ||
| helpers.expectCacheErrors(c, 'Missing cache', 1); | ||
| expect(c.created).to.eql(c.errorTime); | ||
| expect(await cache.store.isset('-internal/cache')).to.be(true); | ||
| const d = await cache.store.get('-internal/cache'); | ||
| helpers.expectDataMiss(d, null, null); | ||
| helpers.expectDataErrors(d, 'Error: Missing cache', 1); | ||
| expect(d.created).to.eql(d.errorTime); | ||
| }); | ||
| }); | ||
| describe('and having existing cache', async function () { | ||
| it('should return a Hit (cached value) on success', async function () { | ||
| mockdate.set('2000-11-22'); | ||
| await cache.store.set(Cacheism.Data.fromResponse( | ||
| new Cacheism.Hit('-internal/cache', 'cached') | ||
| )); | ||
| mockdate.reset(); | ||
| const c = await cache.go('-internal', 'cache', Cacheism.Status.onlyCache, async () => { | ||
| return 'live'; | ||
| }); | ||
| helpers.expectCacheHit(c, true, 'cached'); | ||
| helpers.expectCacheNoErrors(c); | ||
| expect(await cache.store.isset('-internal/cache')).to.be(true); | ||
| const d = await cache.store.get('-internal/cache'); | ||
| helpers.expectDataHit(d, 'cached', c.etag); | ||
| helpers.expectDataNoErrors(d); | ||
| }); | ||
| it('should return a Hit (cached value) on error', async function () { | ||
| mockdate.set('2000-11-22'); | ||
| await cache.store.set(Cacheism.Data.fromResponse( | ||
| new Cacheism.Hit('-internal/cache', 'cached') | ||
| )); | ||
| mockdate.reset(); | ||
| const c = await cache.go('-internal', 'cache', Cacheism.Status.onlyCache, async () => { | ||
| return 'live'; | ||
| }); | ||
| helpers.expectCacheHit(c, true, 'cached'); | ||
| helpers.expectCacheNoErrors(c); | ||
| expect(await cache.store.isset('-internal/cache')).to.be(true); | ||
| const d = await cache.store.get('-internal/cache'); | ||
| helpers.expectDataHit(d, 'cached', c.etag); | ||
| helpers.expectDataNoErrors(d); | ||
| }); | ||
| }); | ||
| }); | ||
| }); |
| const expect = require('expect.js'); | ||
| const mockdate = require('mockdate'); | ||
| const Cacheism = require('../lib/cacheism'); | ||
| function expectCacheHit(c, cached, data) { | ||
| expect(c).to.be.a(Cacheism.Hit); | ||
| expect(c).to.have.property('version', 3); | ||
| expect(c).to.have.property('cacheName', '-internal/cache'); | ||
| expect(c).to.have.property('cached', cached); | ||
| expect(c).to.have.property('created'); | ||
| expect(c.created).to.be.a(Date); | ||
| expect(c).to.have.property('data', data); | ||
| expect(c).to.have.property('etag'); | ||
| } | ||
| function expectCacheMiss(c, cached, data) { | ||
| expect(c).to.be.a(Cacheism.Miss); | ||
| expect(c).to.have.property('version', 3); | ||
| expect(c).to.have.property('cacheName', '-internal/cache'); | ||
| expect(c).to.have.property('cached', cached); | ||
| expect(c).to.have.property('created'); | ||
| expect(c.created).to.be.a(Date); | ||
| expect(c).to.have.property('data', data); | ||
| expect(c).to.have.property('etag', null); | ||
| } | ||
| function expectCacheNoErrors(c) { | ||
| expect(c).to.have.property('error', null); | ||
| expect(c).to.have.property('errorTime', null); | ||
| expect(c).to.have.property('consecutiveErrors', 0); | ||
| } | ||
| function expectCacheErrors(c, error, errors) { | ||
| expect(c).to.have.property('error', error); | ||
| expect(c).to.have.property('errorTime'); | ||
| expect(c.errorTime).to.be.a(Date); | ||
| expect(c).to.have.property('consecutiveErrors', errors); | ||
| } | ||
| function expectDataHit(d, data, etag) { | ||
| expect(d).to.be.a(Cacheism.Data); | ||
| expect(d).to.have.property('version', 3); | ||
| expect(d).to.have.property('type', Cacheism.Type.hit); | ||
| expect(d).to.have.property('created'); | ||
| expect(d.created).to.be.a(Date); | ||
| expect(d).to.have.property('data', data); | ||
| expect(d).to.have.property('etag', etag); | ||
| } | ||
| function expectDataMiss(d, data, etag) { | ||
| expect(d).to.be.a(Cacheism.Data); | ||
| expect(d).to.have.property('version', 3); | ||
| expect(d).to.have.property('type', Cacheism.Type.miss); | ||
| expect(d).to.have.property('created'); | ||
| expect(d.created).to.be.a(Date); | ||
| expect(d).to.have.property('data', data); | ||
| expect(d).to.have.property('etag', etag); | ||
| } | ||
| function expectDataNoErrors(d) { | ||
| expect(d).to.have.property('error', null); | ||
| expect(d).to.have.property('errorTime', null); | ||
| expect(d).to.have.property('consecutiveErrors', 0); | ||
| } | ||
| function expectDataErrors(d, error, errors) { | ||
| expect(d).to.have.property('error', error); | ||
| expect(d).to.have.property('errorTime'); | ||
| expect(d.errorTime).to.be.a(Date); | ||
| expect(d).to.have.property('consecutiveErrors', errors); | ||
| } | ||
| module.exports = { | ||
| expectCacheHit, | ||
| expectCacheMiss, | ||
| expectCacheNoErrors, | ||
| expectCacheErrors, | ||
| expectDataHit, | ||
| expectDataMiss, | ||
| expectDataNoErrors, | ||
| expectDataErrors, | ||
| }; |
-392
| const expect = require('expect.js'); | ||
| const mockdate = require('mockdate'); | ||
| const Cacheism = require('../lib/cacheism'); | ||
| const cache = new Cacheism(Cacheism.store.memory()); | ||
| const helpers = require('./helpers'); | ||
| describe.only('memory', function() { | ||
| beforeEach(function() { | ||
| // runs before each test in this block | ||
| cache.store.data = {}; | ||
| }); | ||
| it('should export as a function', function() { | ||
| expect(cache.go).to.be.a('function'); | ||
| }); | ||
| describe('when status=onlyFresh', async function () { | ||
| describe('and no existing cache', async function () { | ||
| it('should return a Hit (live value) on success', async function () { | ||
| const c = await cache.go('-internal', 'cache', Cacheism.Status.onlyFresh, async () => { | ||
| return 'live'; | ||
| }); | ||
| helpers.expectCacheHit(c, false, 'live'); | ||
| helpers.expectCacheNoErrors(c); | ||
| expect(await cache.store.isset('-internal/cache')).to.be(true); | ||
| const d = await cache.store.get('-internal/cache'); | ||
| helpers.expectDataHit(d, 'live', c.etag); | ||
| helpers.expectDataNoErrors(d); | ||
| }); | ||
| it('should return a Miss on error', async function () { | ||
| let c, d, e; | ||
| for (e = 1; e < 3; e++) { | ||
| c = await cache.go('-internal', 'cache', Cacheism.Status.onlyFresh, async () => { | ||
| throw Error('cache error'); | ||
| }); | ||
| helpers.expectCacheMiss(c, false, null); | ||
| helpers.expectCacheErrors(c, 'Error: cache error', e); | ||
| expect(await cache.store.isset('-internal/cache')).to.be(true); | ||
| d = await cache.store.get('-internal/cache'); | ||
| helpers.expectDataMiss(d, null); | ||
| helpers.expectDataErrors(d, 'Error: cache error', e); | ||
| } | ||
| }); | ||
| }); | ||
| describe('and having existing cache', async function () { | ||
| it('should return a Hit (live value) on success', async function () { | ||
| mockdate.set('2000-11-22'); | ||
| await cache.store.set(Cacheism.Data.fromResponse( | ||
| new Cacheism.Hit('-internal/cache', 'cached') | ||
| )); | ||
| mockdate.reset(); | ||
| const c = await cache.go('-internal', 'cache', Cacheism.Status.onlyFresh, async () => { | ||
| return 'live'; | ||
| }); | ||
| helpers.expectCacheHit(c, false, 'live'); | ||
| helpers.expectCacheNoErrors(c); | ||
| expect(await cache.store.isset('-internal/cache')).to.be(true); | ||
| const d = await cache.store.get('-internal/cache'); | ||
| helpers.expectDataHit(d, 'live', c.etag); | ||
| helpers.expectDataNoErrors(d); | ||
| }); | ||
| it('should return a Miss on error', async function () { | ||
| mockdate.set('2000-11-22'); | ||
| await cache.store.set(Cacheism.Data.fromResponse( | ||
| new Cacheism.Hit('-internal/cache', 'cached') | ||
| )); | ||
| mockdate.reset(); | ||
| let c, d, e; | ||
| for (e = 1; e < 3; e++) { | ||
| c = await cache.go('-internal', 'cache', Cacheism.Status.onlyFresh, async () => { | ||
| throw Error('cache error'); | ||
| }); | ||
| helpers.expectCacheMiss(c, false, null); | ||
| helpers.expectCacheErrors(c, 'Error: cache error', e); | ||
| expect(await cache.store.isset('-internal/cache')).to.be(true); | ||
| d = await cache.store.get('-internal/cache'); | ||
| helpers.expectDataMiss(d, null); | ||
| helpers.expectDataErrors(d, 'Error: cache error', e); | ||
| } | ||
| }); | ||
| }); | ||
| }); | ||
| describe('when status=cacheOnFail', function () { | ||
| describe('and no existing cache', async function () { | ||
| it('should return a Hit (live value) on success', async function () { | ||
| const c = await cache.go('-internal', 'cache', Cacheism.Status.cacheOnFail, async () => { | ||
| return 'live'; | ||
| }); | ||
| helpers.expectCacheHit(c, false, 'live'); | ||
| helpers.expectCacheNoErrors(c); | ||
| expect(await cache.store.isset('-internal/cache')).to.be(true); | ||
| const d = await cache.store.get('-internal/cache'); | ||
| helpers.expectDataHit(d, 'live', c.etag); | ||
| helpers.expectDataNoErrors(d); | ||
| }); | ||
| it('should return a Miss on error', async function () { | ||
| let c, d, e; | ||
| for (e = 1; e < 3; e++) { | ||
| c = await cache.go('-internal', 'cache', Cacheism.Status.cacheOnFail, async () => { | ||
| throw Error('cache error'); | ||
| }); | ||
| helpers.expectCacheMiss(c, false, null); | ||
| helpers.expectCacheErrors(c, 'Error: cache error', e); | ||
| expect(await cache.store.isset('-internal/cache')).to.be(true); | ||
| d = await cache.store.get('-internal/cache'); | ||
| helpers.expectDataMiss(d, null, c.etag); | ||
| helpers.expectDataErrors(d, 'Error: cache error', e); | ||
| } | ||
| }); | ||
| }); | ||
| describe('and having existing cache', async function () { | ||
| it('should return a Hit (live value) on success', async function () { | ||
| mockdate.set('2000-11-22'); | ||
| await cache.store.set(Cacheism.Data.fromResponse( | ||
| new Cacheism.Hit('-internal/cache', 'cached') | ||
| )); | ||
| mockdate.reset(); | ||
| const c = await cache.go('-internal', 'cache', Cacheism.Status.cacheOnFail, async () => { | ||
| return 'live'; | ||
| }); | ||
| helpers.expectCacheHit(c, false, 'live'); | ||
| helpers.expectCacheNoErrors(c); | ||
| expect(await cache.store.isset('-internal/cache')).to.be(true); | ||
| const d = await cache.store.get('-internal/cache'); | ||
| helpers.expectDataHit(d, 'live', c.etag); | ||
| helpers.expectDataNoErrors(d); | ||
| }); | ||
| it('should return a Hit (cached value) on error', async function () { | ||
| mockdate.set('2000-11-22'); | ||
| await cache.store.set(Cacheism.Data.fromResponse( | ||
| new Cacheism.Hit('-internal/cache', 'cached') | ||
| )); | ||
| mockdate.reset(); | ||
| let c, d, e; | ||
| for (e = 1; e < 3; e++) { | ||
| c = await cache.go('-internal', 'cache', Cacheism.Status.cacheOnFail, async () => { | ||
| throw Error('cache error'); | ||
| }); | ||
| helpers.expectCacheHit(c, true, 'cached'); | ||
| helpers.expectCacheErrors(c, 'Error: cache error', e); | ||
| expect(await cache.store.isset('-internal/cache')).to.be(true); | ||
| d = await cache.store.get('-internal/cache'); | ||
| helpers.expectDataHit(d, 'cached', c.etag); | ||
| helpers.expectDataErrors(d, 'Error: cache error', e); | ||
| } | ||
| }); | ||
| }); | ||
| }); | ||
| describe('when status=preferCache', function () { | ||
| describe('and no existing cache', async function () { | ||
| it('should return a Hit (live value) on success', async function () { | ||
| const c = await cache.go('-internal', 'cache', Cacheism.Status.preferCache, async () => { | ||
| return 'live'; | ||
| }); | ||
| helpers.expectCacheHit(c, false, 'live'); | ||
| helpers.expectCacheNoErrors(c); | ||
| expect(await cache.store.isset('-internal/cache')).to.be(true); | ||
| const d = await cache.store.get('-internal/cache'); | ||
| helpers.expectDataHit(d, 'live', c.etag); | ||
| helpers.expectDataNoErrors(d); | ||
| }); | ||
| it('should return a Miss on error', async function () { | ||
| let c, d, e; | ||
| for (e = 1; e < 3; e++) { | ||
| c = await cache.go('-internal', 'cache', Cacheism.Status.preferCache, async () => { | ||
| throw Error('cache error'); | ||
| }); | ||
| helpers.expectCacheMiss(c, false, null); | ||
| helpers.expectCacheErrors(c, 'Error: cache error', e); | ||
| expect(await cache.store.isset('-internal/cache')).to.be(true); | ||
| d = await cache.store.get('-internal/cache'); | ||
| helpers.expectDataMiss(d, null, c.etag); | ||
| helpers.expectDataErrors(d, 'Error: cache error', e); | ||
| } | ||
| }); | ||
| }); | ||
| describe('and having existing cache', async function () { | ||
| it('should return a Hit (cached value) on success', async function () { | ||
| mockdate.set('2000-11-22'); | ||
| await cache.store.set(Cacheism.Data.fromResponse( | ||
| new Cacheism.Hit('-internal/cache', 'cached') | ||
| )); | ||
| mockdate.reset(); | ||
| const c = await cache.go('-internal', 'cache', Cacheism.Status.preferCache, async () => { | ||
| return 'live'; | ||
| }); | ||
| helpers.expectCacheHit(c, true, 'cached'); | ||
| helpers.expectCacheNoErrors(c); | ||
| expect(await cache.store.isset('-internal/cache')).to.be(true); | ||
| const d = await cache.store.get('-internal/cache'); | ||
| helpers.expectDataHit(d, 'cached', c.etag); | ||
| helpers.expectDataNoErrors(d); | ||
| }); | ||
| it('should return a Hit (cached value) on error', async function () { | ||
| mockdate.set('2000-11-22'); | ||
| await cache.store.set(Cacheism.Data.fromResponse( | ||
| new Cacheism.Hit('-internal/cache', 'cached') | ||
| )); | ||
| mockdate.reset(); | ||
| let c, d, e; | ||
| for (e = 1; e < 3; e++) { | ||
| c = await cache.go('-internal', 'cache', Cacheism.Status.preferCache, async () => { | ||
| throw Error('cache error'); | ||
| }); | ||
| helpers.expectCacheHit(c, true, 'cached'); | ||
| helpers.expectCacheNoErrors(c); | ||
| expect(await cache.store.isset('-internal/cache')).to.be(true); | ||
| d = await cache.store.get('-internal/cache'); | ||
| helpers.expectDataHit(d, 'cached', c.etag); | ||
| helpers.expectDataNoErrors(d); | ||
| } | ||
| }); | ||
| }); | ||
| }); | ||
| describe('when status=onlyCache', function () { | ||
| describe('and no existing cache', async function () { | ||
| it('should return a Miss on success', async function () { | ||
| const c = await cache.go('-internal', 'cache', Cacheism.Status.onlyCache, async () => { | ||
| return 'live'; | ||
| }); | ||
| helpers.expectCacheMiss(c, false, null); | ||
| helpers.expectCacheErrors(c, 'Error: Missing cache', 1); | ||
| expect(await cache.store.isset('-internal/cache')).to.be(true); | ||
| const d = await cache.store.get('-internal/cache'); | ||
| helpers.expectDataMiss(d, null, null); | ||
| helpers.expectDataErrors(d, 'Error: Missing cache', 1); | ||
| }); | ||
| it('should return a Miss on error', async function () { | ||
| let c, d, e; | ||
| for (e = 1; e < 3; e++) { | ||
| c = await cache.go('-internal', 'cache', Cacheism.Status.onlyCache, async () => { | ||
| throw Error('cache error'); | ||
| }); | ||
| helpers.expectCacheMiss(c, false, null); | ||
| helpers.expectCacheErrors(c, 'Error: Missing cache', e); | ||
| expect(await cache.store.isset('-internal/cache')).to.be(true); | ||
| d = await cache.store.get('-internal/cache'); | ||
| helpers.expectDataMiss(d, null, null); | ||
| helpers.expectDataErrors(d, 'Error: Missing cache', e); | ||
| } | ||
| }); | ||
| }); | ||
| describe('and having existing cache', async function () { | ||
| it('should return a Hit (cached value) on success', async function () { | ||
| mockdate.set('2000-11-22'); | ||
| await cache.store.set(Cacheism.Data.fromResponse( | ||
| new Cacheism.Hit('-internal/cache', 'cached') | ||
| )); | ||
| mockdate.reset(); | ||
| const c = await cache.go('-internal', 'cache', Cacheism.Status.onlyCache, async () => { | ||
| return 'live'; | ||
| }); | ||
| helpers.expectCacheHit(c, true, 'cached'); | ||
| helpers.expectCacheNoErrors(c); | ||
| expect(await cache.store.isset('-internal/cache')).to.be(true); | ||
| const d = await cache.store.get('-internal/cache'); | ||
| helpers.expectDataHit(d, 'cached', c.etag); | ||
| helpers.expectDataNoErrors(d); | ||
| }); | ||
| it('should return a Hit (cached value) on error', async function () { | ||
| mockdate.set('2000-11-22'); | ||
| await cache.store.set(Cacheism.Data.fromResponse( | ||
| new Cacheism.Hit('-internal/cache', 'cached') | ||
| )); | ||
| mockdate.reset(); | ||
| const c = await cache.go('-internal', 'cache', Cacheism.Status.onlyCache, async () => { | ||
| return 'live'; | ||
| }); | ||
| helpers.expectCacheHit(c, true, 'cached'); | ||
| helpers.expectCacheNoErrors(c); | ||
| expect(await cache.store.isset('-internal/cache')).to.be(true); | ||
| const d = await cache.store.get('-internal/cache'); | ||
| helpers.expectDataHit(d, 'cached', c.etag); | ||
| helpers.expectDataNoErrors(d); | ||
| }); | ||
| }); | ||
| }); | ||
| }); |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 2 instances in 1 package
71526
69.69%0
-100%0
-100%Yes
NaN13
333.33%9
-18.18%612
-31%- Removed
- Removed