@jointly/cache-candidate 1.1.4 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -6
- package/dist/index.js +1 -1
- package/dist/internal.d.ts +4 -4
- package/dist/models.d.ts +2 -2
- package/dist/plugins/index.d.ts +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
This is a library providing both a higher-order function and a decorator to cache the result of a function/method if given conditions are met.
|
|
4
4
|
|
|
5
|
+
# How do I install it?
|
|
6
|
+
|
|
7
|
+
You can install it by using the following command:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @jointly/cache-candidate
|
|
11
|
+
```
|
|
12
|
+
|
|
5
13
|
# Examples
|
|
6
14
|
|
|
7
15
|
## Use-case #1: DB query
|
|
@@ -25,7 +33,7 @@ await cachedGetUsers({ name: 'John' }); // <-- This won't be cached, because the
|
|
|
25
33
|
await cachedGetUsers({ name: 'John' }); // <-- This won't be cached, because the requestsThreshold is 3 in the last 30 seconds
|
|
26
34
|
await cachedGetUsers({ name: 'John' }); // <-- This WILL be cached, because the requestsThreshold is 3 in the last 30 seconds!
|
|
27
35
|
await cachedGetUsers({ name: 'Jack' }); // <-- This won't be cached, because parameters are different
|
|
28
|
-
await sleep(
|
|
36
|
+
await sleep(61000); // <-- This will wait enough time (ttl) for the cache record to expire
|
|
29
37
|
await cachedGetUsers({ name: 'John' }); // <-- This won't be cached, because the requestsThreshold is 3 in the last 30 seconds
|
|
30
38
|
```
|
|
31
39
|
## Use-case #2: DB query - Different timeFrame
|
|
@@ -48,7 +56,7 @@ await cachedGetUsers({ name: 'John' }); // <-- This won't be cached, because the
|
|
|
48
56
|
await cachedGetUsers({ name: 'John' }); // <-- This won't be cached, because the requestsThreshold is 3 in the last 45 seconds
|
|
49
57
|
await cachedGetUsers({ name: 'John' }); // <-- This WILL be cached, because the requestsThreshold is 3 in the last 45 seconds!
|
|
50
58
|
await cachedGetUsers({ name: 'Jack' }); // <-- This won't be cached, because parameters are different
|
|
51
|
-
await sleep(
|
|
59
|
+
await sleep(31000); // <-- This will wait enough time (ttl) for the cache record to expire
|
|
52
60
|
await cachedGetUsers({ name: 'John' }); // <-- This WILL be cached, because the requestsThreshold is 3 in the last 45 seconds!
|
|
53
61
|
```
|
|
54
62
|
|
|
@@ -71,7 +79,7 @@ const cachedGetUsers = cacheCandidate(getUsers, {
|
|
|
71
79
|
await cachedGetUsers({ name: 'John' }); // <-- This will be cached, because the candidateFunction returns true
|
|
72
80
|
await cachedGetUsers({ name: 'John' }); // <-- This will return the cached value
|
|
73
81
|
await cachedGetUsers({ name: 'Jack' }); // <-- This won't be cached, because the candidateFunction returns false
|
|
74
|
-
await sleep(
|
|
82
|
+
await sleep(31000); // <-- This will invalidate the cache because of the ttl
|
|
75
83
|
```
|
|
76
84
|
|
|
77
85
|
# How does it work?
|
|
@@ -141,8 +149,8 @@ The conditions are, within the given `timeFrame`:
|
|
|
141
149
|
|
|
142
150
|
- If a `candidateFunction` is provided, it returns `true` at least once.
|
|
143
151
|
The candidateFunction ignores all the other conditions.
|
|
144
|
-
- If a `
|
|
145
|
-
- If only a `
|
|
152
|
+
- If a `millisecondThreshold` is provided, the function execution time passed such threshold at least `requestsThreshold` times.
|
|
153
|
+
- If only a `requestsThreshold` is provided (default), the function is called at least `requestsThreshold` times.
|
|
146
154
|
|
|
147
155
|
# Other Info
|
|
148
156
|
|
|
@@ -199,4 +207,4 @@ The cache key is composed based on the following criteria, allowing multiple fil
|
|
|
199
207
|
|
|
200
208
|
# Contributing
|
|
201
209
|
|
|
202
|
-
Please, refer to the [CONTRIBUTING.md](https://github.com/JointlyTech/cache-candidate/blob/main/CONTRIBUTING.md) file for more information.
|
|
210
|
+
Please, refer to the [CONTRIBUTING.md](https://github.com/JointlyTech/cache-candidate/blob/main/CONTRIBUTING.md) file for more information.
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var $=Object.create;var
|
|
1
|
+
"use strict";var $=Object.create;var m=Object.defineProperty;var L=Object.getOwnPropertyDescriptor;var U=Object.getOwnPropertyNames;var W=Object.getPrototypeOf,X=Object.prototype.hasOwnProperty;var b=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports),K=(e,t)=>{for(var n in t)m(e,n,{get:t[n],enumerable:!0})},H=(e,t,n,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let a of U(t))!X.call(e,a)&&a!==n&&m(e,a,{get:()=>t[a],enumerable:!(r=L(t,a))||r.enumerable});return e};var y=(e,t,n)=>(n=e!=null?$(W(e)):{},H(t||!e||!e.__esModule?m(n,"default",{value:e,enumerable:!0}):n,e)),B=e=>H(m({},"__esModule",{value:!0}),e);var A=b((Fe,k)=>{"use strict";var T=Object.defineProperty,z=Object.getOwnPropertyDescriptor,G=Object.getOwnPropertyNames,V=Object.prototype.hasOwnProperty,Y=(e,t)=>{for(var n in t)T(e,n,{get:t[n],enumerable:!0})},Z=(e,t,n,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let a of G(t))!V.call(e,a)&&a!==n&&T(e,a,{get:()=>t[a],enumerable:!(r=z(t,a))||r.enumerable});return e},ee=e=>Z(T({},"__esModule",{value:!0}),e),w={};Y(w,{Hooks:()=>F});k.exports=ee(w);var F=(e=>(e.INIT="INIT",e.EXECUTION_PRE="EXECUTION_PRE",e.EXECUTION_POST="EXECUTION_POST",e.DATACACHE_RECORD_ADD_PRE="DATACACHE_RECORD_ADD_PRE",e.DATACACHE_RECORD_ADD_POST="DATACACHE_RECORD_ADD_POST",e.DATACACHE_RECORD_DELETE_PRE="DATACACHE_RECORD_DELETE_PRE",e.DATACACHE_RECORD_DELETE_POST="DATACACHE_RECORD_DELETE_POST",e.CACHE_HIT="CACHE_HIT",e))(F||{})});var I=b((ke,N)=>{"use strict";var D=Object.defineProperty,te=Object.getOwnPropertyDescriptor,ne=Object.getOwnPropertyNames,ae=Object.prototype.hasOwnProperty,re=(e,t)=>{for(var n in t)D(e,n,{get:t[n],enumerable:!0})},oe=(e,t,n,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let a of ne(t))!ae.call(e,a)&&a!==n&&D(e,a,{get:()=>t[a],enumerable:!(r=te(t,a))||r.enumerable});return e},ie=e=>oe(D({},"__esModule",{value:!0}),e),S={};re(S,{Hook:()=>se,hook:()=>de});N.exports=ie(S);var ce={before:()=>{},after:()=>{}},ue={before:()=>{},after:()=>{}};function se(e){return function(t,n,r){let a={},o=r.value,{before:i,after:c}={...ce,...e};return r.value=function(...u){i({context:a,args:u,target:t,propertyKey:n,descriptor:r});let s=o.apply(this,u);return c({context:a,args:u,target:t,propertyKey:n,descriptor:r,result:s}),s},r}}function de(e){return function(t){let n={},{before:r,after:a}={...ue,...e};return function(...o){r({context:n,args:o});let i=t(...o);return a({context:n,args:o,result:i}),i}}}});var xe={};K(xe,{CacheCandidate:()=>Pe,CacheCandidateOptionsDefault:()=>E,DataCacheRecordNotFound:()=>f,RunningQueryRecordNotFound:()=>g,cacheCandidate:()=>Re});module.exports=B(xe);var v=(e=new Map)=>({get:async t=>e.get(JSON.stringify(t)),has:async t=>e.has(JSON.stringify(t)),set:async(t,n)=>e.set(JSON.stringify(t),n),delete:async t=>e.delete(JSON.stringify(t))});var E={ttl:6e5,timeFrame:3e4,requestsThreshold:3,cache:v(),keepAlive:!1,plugins:[],events:{onCacheHit:e=>e,onCacheSet:e=>e,onCacheDelete:e=>e,onBeforeFunctionExecution:e=>e,onAfterFunctionExecution:e=>e}};var f=Symbol("DataCacheRecordNotFound"),g=Symbol("RunningQueryRecordNotFound");var J=require("crypto");var M=y(A()),j=y(I());async function l(e,t,n){for(let r of t){let a=r.hooks.filter(i=>i.hook===e);if(a.length>1)throw new Error(`Only one hook instance per plugin is allowed. ${r.name} has ${a.length} instances of ${e}}`);if(a.length===0)continue;let o=a[0];if(le(o))await o.action(n,r.additionalParameters);else throw new Error(`Hook ${e} for plugin ${r.name} is not actionable.`)}}function le(e){return[e.action!==void 0,typeof e.action=="function"].every(t=>t===!0)}function Q(e,t,n,r){return(0,j.hook)({before:()=>{l(e,n.plugins,r)},after:({result:a})=>{l(t,n.plugins,{...r,result:a})}})}function O({options:e}){e.plugins&&e.plugins.forEach(t=>{if(!t.hooks||!Array.isArray(t.hooks)||t.hooks.length===0)throw new Error(`Plugin ${t.name} has no hooks.`);t.hooks.forEach(n=>{if(!M.Hooks[n.hook])throw new Error(`Invalid hook ${n}`)})})}var h=y(A());function he(e,t){return Date.now()<e+t.timeFrame}function Ce({key:e,timeframeCache:t,executionTime:n,executionEnd:r}){t.has(e)?t.get(e).push({executionTime:n,executionEnd:r}):t.set(e,[{executionEnd:r,executionTime:n}])}function fe({options:e,key:t,timeframeCache:n}){if(n.has(t)){let r=n.get(t),a=r.filter(({executionEnd:o})=>he(o,e));r.length!==a.length&&n.set(t,a)}}function me({key:e,runningQueryCache:t}){return t.has(e)?t.get(e):g}function P(...e){return(0,J.createHash)("sha256").update(e.join("|")).digest("hex")}function ge({birthTime:e,options:t}){return Date.now()-e>=t.ttl}async function pe({options:e,key:t,HookPayload:n}){if(await e.cache.has(t)){let{result:r,birthTime:a}=await e.cache.get(t);return ge({birthTime:a,options:e})?(await R({options:e,key:t,HookPayload:n}),f):r}return f}async function ye({options:e,key:t,result:n}){return e.cache.set(t,{result:n,birthTime:Date.now()},e.ttl)}async function R({options:e,key:t,HookPayload:n}){Q(h.Hooks.DATACACHE_RECORD_DELETE_PRE,h.Hooks.DATACACHE_RECORD_DELETE_POST,e,n)(e.cache.delete)(t).then(()=>{e.events.onCacheDelete({key:t})})}async function q({result:e,runningQueryCache:t,key:n,executionStart:r,options:a,timeframeCache:o,timeoutCache:i,args:c,HookPayload:u}){let s=Date.now(),C=s-r;a.events.onAfterFunctionExecution({key:n,executionTime:C}),Ce({key:n,timeframeCache:o,executionTime:C,executionEnd:s}),Ee({options:a,key:n,timeframeCache:o,executionTime:C,args:c})>=a.requestsThreshold?(await l(h.Hooks.DATACACHE_RECORD_ADD_PRE,a.plugins,u),ye({options:a,key:n,result:e}).then(async()=>{await l(h.Hooks.DATACACHE_RECORD_ADD_POST,a.plugins,{...u,result:e}),a.events.onCacheSet({key:n})}).finally(()=>{t.delete(n),i.set(n,setTimeout(()=>{R({options:a,key:n,HookPayload:u}),i.delete(n)},a.ttl).unref())})):t.delete(n)}function Ee({options:e,key:t,timeframeCache:n,executionTime:r,args:a}){let o=0,i=n.get(t);return e.candidateFunction?o=Ae(e,r,a,i):e.millisecondThreshold?o=Te(r,e,i):o=i.length,o}function Te(e,t,n){let r=0;return e>t.millisecondThreshold&&(r=n.filter(a=>a.executionTime>t.millisecondThreshold).length),r}function Ae(e,t,n,r){let a=0;return e.candidateFunction({timeFrameCacheRecords:r,options:e,args:n})&&(a=e.requestsThreshold),a}function De({timeoutCache:e,key:t,options:n,HookPayload:r}){clearTimeout(e.get(t)),e.set(t,setTimeout(()=>{R({options:n,key:t,HookPayload:r}),e.delete(t)},n.ttl).unref())}function Oe(e=10){return Math.random().toString(36).substring(2,e+2)}async function x({options:e,key:t,timeoutCache:n,runningQueryCache:r,timeframeCache:a,args:o,originalMethod:i}){let c={options:{...e,plugins:void 0},key:t,timeoutCache:n,runningQueryCache:r,timeframeCache:a,fnArgs:o};await l(h.Hooks.INIT,e.plugins,c);let u=await pe({options:e,key:t,HookPayload:c});if(u!==f)return e.keepAlive&&De({timeoutCache:n,key:t,options:e,HookPayload:c}),await l(h.Hooks.CACHE_HIT,e.plugins,{...c,result:u}),e.events.onCacheHit({key:t}),Promise.resolve(u);let s=me({key:t,runningQueryCache:r});if(s!==g)return await l(h.Hooks.CACHE_HIT,e.plugins,{...c,result:s}),s;fe({options:e,key:t,timeframeCache:a}),await l(h.Hooks.EXECUTION_PRE,e.plugins,c),e.events.onBeforeFunctionExecution({key:t});let C=Date.now(),d=i(...o);return await l(h.Hooks.EXECUTION_POST,e.plugins,{...c,result:d}),d instanceof Promise?(r.set(t,d),d.then(p=>q({result:p,runningQueryCache:r,key:t,executionStart:C,options:e,timeframeCache:a,timeoutCache:n,args:o,HookPayload:c})),d):(q({result:d,runningQueryCache:r,key:t,executionStart:C,options:e,timeframeCache:a,timeoutCache:n,args:o,HookPayload:c}),d)}function _(e){let t=new Map,n=new Map,r=Oe(),a=new Map,o={...E,...e};return{timeframeCache:t,runningQueryCache:n,uniqueIdentifier:r,timeoutCache:a,options:o}}function Pe(e={}){let{timeframeCache:t,runningQueryCache:n,uniqueIdentifier:r,timeoutCache:a,options:o}=_(e);return O({options:o}),function(i,c,u){let s=u.value;u.value=async function(...C){let d=i.constructor.name+JSON.stringify(this),p=P([c,r,d,JSON.stringify(C)]);return x({options:o,key:p,timeoutCache:a,runningQueryCache:n,timeframeCache:t,args:C,originalMethod:s})}}}function Re(e,t={}){let{timeframeCache:n,runningQueryCache:r,uniqueIdentifier:a,timeoutCache:o,options:i}=_(t);return O({options:i}),async(...c)=>x({options:i,key:P([a,JSON.stringify(c)]),timeoutCache:o,runningQueryCache:r,timeframeCache:n,args:c,originalMethod:e})}0&&(module.exports={CacheCandidate,CacheCandidateOptionsDefault,DataCacheRecordNotFound,RunningQueryRecordNotFound,cacheCandidate});
|
package/dist/internal.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { CacheCandidateOptions,
|
|
1
|
+
import { CacheCandidateOptions, TimeoutCache, RunningQueryCache, TimeFrameCache } from './models';
|
|
2
2
|
export declare function getDataCacheKey(...args: any[]): string;
|
|
3
3
|
export declare function uniqid(length?: number): string;
|
|
4
|
-
export declare function letsCandidate({ options, key,
|
|
4
|
+
export declare function letsCandidate({ options, key, timeoutCache, runningQueryCache, timeframeCache, args, originalMethod }: {
|
|
5
5
|
options: CacheCandidateOptions;
|
|
6
6
|
key: string;
|
|
7
|
-
|
|
7
|
+
timeoutCache: TimeoutCache;
|
|
8
8
|
runningQueryCache: RunningQueryCache;
|
|
9
9
|
timeframeCache: TimeFrameCache;
|
|
10
10
|
args: any[];
|
|
@@ -14,6 +14,6 @@ export declare function getInitialState(_options: Partial<CacheCandidateOptions>
|
|
|
14
14
|
timeframeCache: TimeFrameCache;
|
|
15
15
|
runningQueryCache: RunningQueryCache;
|
|
16
16
|
uniqueIdentifier: string;
|
|
17
|
-
|
|
17
|
+
timeoutCache: TimeoutCache;
|
|
18
18
|
options: CacheCandidateOptions;
|
|
19
19
|
};
|
package/dist/models.d.ts
CHANGED
|
@@ -37,7 +37,7 @@ export interface CacheCandidateOptions {
|
|
|
37
37
|
executionTime: number;
|
|
38
38
|
}) => void;
|
|
39
39
|
};
|
|
40
|
-
plugins
|
|
40
|
+
plugins: Array<CacheCandidatePluginWithAdditionalParameters>;
|
|
41
41
|
}
|
|
42
42
|
export interface DataCacheRecord {
|
|
43
43
|
result: any;
|
|
@@ -49,6 +49,6 @@ export interface TimeFrameCacheRecord {
|
|
|
49
49
|
}
|
|
50
50
|
export type TimeFrameCache = Map<string, Array<TimeFrameCacheRecord>>;
|
|
51
51
|
export type RunningQueryCache = Map<string, Promise<any>>;
|
|
52
|
-
export type
|
|
52
|
+
export type TimeoutCache = Map<string, NodeJS.Timeout>;
|
|
53
53
|
export declare const DataCacheRecordNotFound: unique symbol;
|
|
54
54
|
export declare const RunningQueryRecordNotFound: unique symbol;
|
package/dist/plugins/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CacheCandidatePluginWithAdditionalParameters, Hooks, PluginPayload } from '@jointly/cache-candidate-plugin-base';
|
|
2
2
|
import { CacheCandidateOptions } from '../models';
|
|
3
|
-
export declare function ExecuteHook(hook: Hooks, plugins: CacheCandidatePluginWithAdditionalParameters
|
|
3
|
+
export declare function ExecuteHook(hook: Hooks, plugins: Array<CacheCandidatePluginWithAdditionalParameters>, payload: PluginPayload): Promise<void>;
|
|
4
4
|
export declare function pluginHookWrap(HookBefore: Hooks, HookAfter: Hooks, options: CacheCandidateOptions, payload: PluginPayload): (fn: (...args: any[]) => unknown) => (...args: any[]) => unknown;
|
|
5
5
|
export declare function checkHooks({ options }: {
|
|
6
6
|
options: CacheCandidateOptions;
|