UNPKG

@imqueue/pg-cache

Version:

PostgreSQL managed cache on Redis for @imqueue-based service methods

112 lines 4.89 kB
/*! * I'm Queue Software Project * Copyright (C) 2025 imqueue.com <support@imqueue.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. * * If you want to use this code in a closed source (commercial) project, you can * purchase a proprietary commercial license. Please contact us at * <support@imqueue.com> to get commercial licensing options. */ import { signature } from './signature.js'; import { TagCache } from '@imqueue/tag-cache'; import {} from './PgCache.js'; import { DEFAULT_CACHE_TTL, declaringPrototype, fetchError, initError, isStandardDecorator, registerChannelsOnce, setError, setInfo, } from './env.js'; /** * Makes channel entry from a given channel name, class method name and options. * * @param name - notification channel, i.e. the watched table name * @param method - decorated method to invalidate when that channel fires * @param options - which channels invalidate the result, its TTL and cache tag decorator options; only the per-channel filter is read here */ export function makeChannel(name, method, options) { return [ method, !Array.isArray(options.channels) ? options.channels[name] : undefined, ]; } /** * Decorator factory `@cacheWith`(CacheWithOptions) * This decorator should be used on a service methods, to set the caching * rules for a method. * * @param options - which channels invalidate the result, its TTL and cache tag */ export function cacheWith(options) { const ttl = options.ttl || DEFAULT_CACHE_TTL; const isFiltered = !Array.isArray(options.channels); const channels = isFiltered ? Object.keys(options.channels || {}) : options.channels; // registers this method's channel entries on the declaring prototype const register = (proto, methodName) => registerChannelsOnce(proto, methodName, pgCacheChannels => { for (const channel of channels) { const pgChannel = (pgCacheChannels[channel] = pgCacheChannels[channel] || []); pgChannel.push(makeChannel(channel, methodName, options)); } }); // builds the caching wrapper; `getClassName` is resolved lazily so it // works in standard mode where the class is unknown at decoration time const wrap = (original, methodName, getClassName, fallback) => async function (...args) { const self = this || fallback; const cache = self.taggedCache; const logger = self.logger || console; const className = getClassName(); if (!cache) { initError(logger, className, methodName, cacheWith); return original.apply(self, args); } const key = signature(className, methodName, args); try { let result = await cache.get(key); if (result === null || result === undefined) { result = original.apply(self, args); if (result && result.then) { result = await result; } const tags = [signature(className, methodName, [])]; cache .set(key, result, tags, ttl) .then(res => setInfo(logger, res, key, cacheWith)) .catch(err => setError(logger, err, key, cacheWith)); } return result; } catch (err) { fetchError(logger, err, key, cacheWith); return original.apply(self, args); } }; return (target, context, descriptor) => { if (isStandardDecorator(context)) { const methodName = String(context.name); let className = ''; context.addInitializer(function () { const proto = declaringPrototype(this, methodName); className = proto.constructor.name; register(proto, methodName); }); return wrap(target, methodName, () => className); } const methodName = String(context); const className = typeof target === 'function' ? target.name : target.constructor.name; register(target, methodName); descriptor.value = wrap(descriptor.value, methodName, () => className, target); return descriptor; }; } //# sourceMappingURL=cacheWith.js.map