UNPKG

@clickup/ent-framework

Version:

A PostgreSQL graph-database-alike library with microsharding and row-level security

124 lines 4.09 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.QueryCache = void 0; const quick_lru_1 = __importDefault(require("quick-lru")); const Shard_1 = require("../abstract/Shard"); const VCFlavor_1 = require("./VCFlavor"); const OPS = [ "loadNullable", "loadByNullable", "selectBy", "select", "count", "exists", ]; /** * Caches Ents loaded by a particular VC. I.e. the same query running for the * same VC twice will quickly return the same Ents. This is typically enabled on * web servers only, to deliver the fastest UI response. */ class QueryCache { /** * Creates the QueryCache object. It enable caching only if VCWithQueryCache * was manually added to the VC by the user, otherwise caching is a no-op. */ constructor(vc) { if (vc.freshness === Shard_1.MASTER) { this.whyOff = "MASTER freshness"; return; } const flavor = vc.flavor(VCFlavor_1.VCWithQueryCache); if (!flavor) { this.whyOff = "No VCWithQueryCache flavor"; } else if (flavor.options.maxQueries <= 0) { this.whyOff = "VCWithQueryCache#maxQueries is not positive"; } else { this.maxQueries = flavor.options.maxQueries; } } /** * Saves a Promise to the cache slot for `op`. If this Promise rejects, the * slot will automatically be cleared (we don't cache rejected Promises to not * have a risk of caching a transient DB error). */ set(EntClass, op, key, value) { if (!this.maxQueries) { // Caching is turned off. return this; } let byOp = (this.byEntClass ??= new WeakMap()).get(EntClass); if (!byOp) { byOp = {}; this.byEntClass.set(EntClass, byOp); } const slot = (byOp[op] ??= new quick_lru_1.default({ maxSize: this.maxQueries })); if (value !== undefined) { slot.set(key, value); // As a side effect of value rejection, clear the cache slot. Note: // although we don't re-throw in the callback, this does not swallow the // rejection, because we don't save the result of value.catch() anywhere // and don't return it. It's a pure side effect. value.catch(() => slot.delete(key)); } else { slot.delete(key); } return this; } /** * Deletes cache slots or keys for an Ent. If key is null, skips the deletion. * If key is undefined (i.e. not passed), then deletes all slots. */ delete(EntClass, ops, key) { if (key === null) { return this; } const byOp = this.byEntClass?.get(EntClass); if (!byOp) { return this; } for (const op of ops) { if (key === undefined) { delete byOp[op]; } else { byOp[op]?.delete(key); } } return this; } /** * This method is non-async on intent. We store Promises in the cache, not end * values, because we want the code to join awaiting an ongoing operation in * case it's inflight already. */ get(EntClass, op, key) { const byOp = this.byEntClass?.get(EntClass); if (!byOp) { return undefined; } return byOp[op]?.get(key); } /** * Read-through caching pattern. */ async through(EntClass, op, key, creator) { if (!this.maxQueries) { // Caching is turned off. return creator(); } let value = this.get(EntClass, op, key); if (value === undefined) { value = creator(); this.set(EntClass, op, key, value); } return value; } } exports.QueryCache = QueryCache; //# sourceMappingURL=QueryCache.js.map