@jointly/cache-candidate 1.0.0 → 1.0.2

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 CHANGED
@@ -2,89 +2,134 @@
2
2
 
3
3
  This is a library providing both a decorator and a higher-order function to cache the result of a method if given conditions are met.
4
4
 
5
- # How does it work?
6
-
7
- ## Decorator
8
-
9
- The decorator expects to receive an object with a partial of the properties contained in the `CacheCandidateOptions` interface.
10
- Every non-passed property will be set to its default value using the `CacheCandidateOptionsDefault` object.
11
- The decorator will return a method that will return a Promise fulfilled with the cached value if the method has already been called with the same arguments and the conditions are met.
12
- The conditions are, within the given `timeFrame`:
13
-
14
- - If a `candidateFunction` is provided, it returns `true` for at least `requestThreshold` times.
15
- - If a `millisecondsThreshold` is provided, it passed such threshold (Execution time) at least `requestThreshold` times.
16
- - If only a `requestThreshold` is provided (default), it is called at least `requestThreshold` times.
17
-
18
- You can pass an additional `dependencyKeys` property to the decorator options which provides an invalidation mechanism to be called manually in your codebase.
19
- This property can be either an array of string, a function that returns an array of string or a function that returns a Promise fulfilled with an array of string.
20
- Both the function and the Promise will receive the result of the method on which the CacheCandidate operates.
21
- In case of an async method, the promise will be fulfilled before passing the result to the `dependencyKeys` function.
22
- The `dependencyKeys` function will be called only if the cache adapter correctly sets the value in the cache (i.e. the `.set` method is fulfilled).
23
-
24
- ## Higher-order function
25
-
26
- Everything is the same as the decorator, but the function takes the original function as the first argument and the options as the second argument.
27
- The function returns a new function that will return a Promise fulfilled with the cached value if the method has already been called with the same arguments and the conditions are met.
28
-
29
- ## Key composition
5
+ # Examples
6
+
7
+ ## Use-case #1: Generic example
8
+
9
+ ```ts
10
+ import { cacheCandidate } from '@jointly/cache-candidate';
11
+
12
+ let counter = 0;
13
+ // The function to be cached. In the example, it's a simple function that returns a Promise fulfilled with the sum of the current counter and the passed step.
14
+ const mockFn = (step: number) =>
15
+ new Promise((resolve) => {
16
+ counter += step;
17
+ resolve(counter);
18
+ });
19
+
20
+ // The wrapped function. The options are the default ones.
21
+ const wrappedMockFn = cacheCandidate(mockFn, {
22
+ requestsThreshold: 1,
23
+ ttl: 800,
24
+ });
25
+
26
+ let result: unknown;
27
+ result = await wrappedMockFn(1);
28
+ await sleep(EXECUTION_MARGIN); // Simulating a delay to allow the event loop to run
29
+ expect(result).toBe(1);
30
+ result = await wrappedMockFn(1);
31
+ await sleep(EXECUTION_MARGIN);
32
+ expect(result).toBe(1);
33
+ ```
30
34
 
31
- The cache key is composed based on the following criteria:
35
+ ## Use-case #2: DB query
32
36
 
33
- ### Decorator
37
+ ```js
38
+ import { cacheCandidate } from '@jointly/cache-candidate';
34
39
 
35
- - The class constructor name.
36
- - The method name.
37
- - The arguments passed to the method. (JSON.stringify)
38
- - `instanceIdentifier`: A uniqid generated for each instance of the class. It uses the instance properties to generate the id. (JSON.stringify)
39
- - `uniqueIdentifier`: A uniqid generated to allow multiple files to contain the same class with the same method.
40
+ function getUsers(filters = {}) {
41
+ return db.query('SELECT * FROM users WHERE ?', filters);
42
+ }
40
43
 
44
+ const cachedGetUsers = cacheCandidate(getUsers, {
45
+ requestsThreshold: 3,
46
+ ttl: 30000,
47
+ });
48
+
49
+ await cachedGetUsers({ name: 'John' }); // <-- This won't be cached, because the requestsThreshold is 3
50
+ await cachedGetUsers({ name: 'John' }); // <-- This won't be cached, because the requestsThreshold is 3
51
+ await cachedGetUsers({ name: 'John' }); // <-- This will be cached, because the requestsThreshold is 3!
52
+ await cachedGetUsers({ name: 'Jack' }); // <-- This won't be cached, because parameters are different
53
+ await sleep(30000); // <-- This will invalidate the cache because of the ttl
54
+ await cachedGetUsers({ name: 'John' }); // <-- This won't be cached, because the requestsThreshold is 3
55
+ ```
41
56
 
42
- ### Higher-order function
57
+ # How does it work?
43
58
 
44
- - The arguments passed to the method. (JSON.stringify)
45
- - `uniqueIdentifier`: A uniqid generated to allow multiple files to contain the same class with the same method.
59
+ ## Higher-order function
46
60
 
61
+ The library exposes the `cacheCandidate` function which accepts the function to be cached as the first argument and the options as the second argument.
62
+ The returned function is an async function which returns a Promise fulfilled with the cached value if the method has already been called with the same arguments and the conditions are met.
63
+ The options available are:
64
+
65
+ - `ttl` (_optional_): The time to live of the cache record in milliseconds. Default: `600000` (10 minutes).
66
+ - `timeFrame` (_optional_): The timeframe considered for the condition checks. Default: `30000` (30 seconds).
67
+ - `candidateFunction` (_optional_): The function to be called to check if the conditions are met. If not passed, this criteria will be ignored.
68
+ - `millisecondThreshold` (_optional_): The threshold in milliseconds to be considered for the condition checks. If not passed, this criteria will be ignored.
69
+ - `requestsThreshold` (_optional_): The number of requests to be considered for the condition checks. Default: `3`.
70
+ - `keepAlive` (_optional_): If `true`, the cache record will be kept alive at every request. Default: `false`.
71
+ - `cache` (_optional_): The cache adapter to be used. Default: `A memory cache based on Maps, but with Promises`.
72
+ Available adapters are:
73
+ - `makeRedisCache`: A cache adapter based on Redis. Pass a Redis client as the first argument.
74
+ - `events` (_optional_): Listener functions to be called at specific bits of the process.
75
+ Available events are:
76
+ - `onCacheHit`: Called when the cache entry is hit.
77
+ - `onCacheSet`: Called when the cache entry is set.
78
+ - `onCacheDelete`: Called when the cache entry is deleted.
79
+ - `onBeforeFunctionExecution`: Called before the function execution.
80
+ - `onAfterFunctionExecution`: Called after the function execution.
81
+ - `plugins` (_optional_): An array of plugins to be used. Default: `[]`.
82
+ Please, refer to the [@jointly/cache-candidate-plugin-base](https://github.com/JointlyTech/cache-candidate-plugin-base) package for more information.
47
83
 
48
- ## Cache invalidation
84
+ ## Decorator
49
85
 
50
- The cache invalidation is done using the exported `cacheCandidateDependencyManager` object.
51
- The object exposes the `invalidate` method which accepts a string.
52
- The string is one of the dependency keys returned by the `dependencyKeys` function/array defined in the decorator options.
86
+ The decorator expects to receive the options as the first argument and works exactly as the higher-order function.
53
87
 
54
88
  ### Example
55
89
 
56
- ```typescript
57
- import { cacheCandidate, cacheCandidateDependencyManager } from 'cache-candidate';
90
+ ```ts
91
+ import { CacheCandidate } from '@jointly/cache-candidate';
58
92
 
59
93
  class MyClass {
60
94
  @CacheCandidate({
61
- dependencyKeys: (users) => {
62
- return users.map((user) => `users-${user.id}`);
63
- },
95
+ requestsThreshold: 3,
96
+ ttl: 30000,
64
97
  })
65
- public async getUsers() {
66
- // Do something
67
- return users;
68
- }
69
-
70
- public async updateUser(user) {
71
- // Do something
72
- cacheCandidateDependencyManager.invalidate(`users-${user.id}`);
98
+ async getUsers(filters = {}) {
99
+ return db.query('SELECT * FROM users WHERE ?', filters);
73
100
  }
74
101
  }
75
102
 
76
- const myClass = new MyClass();
77
- const users = await myClass.getUsers();
78
- users[0].name = 'New name';
79
- await myClass.updateUser(users[0]); // This will invalidate the cache
103
+ const myInstance = new MyClass();
104
+ await myInstance.getUsers({ name: 'John' }); // <-- This won't be cached, because the requestsThreshold is 3
105
+ await myInstance.getUsers({ name: 'John' }); // <-- This won't be cached, because the requestsThreshold is 3
106
+ await myInstance.getUsers({ name: 'John' }); // <-- This will be cached, because the requestsThreshold is 3!
80
107
  ```
81
108
 
109
+ ## Conditions / Criterias
110
+ The conditions are, within the given `timeFrame`:
111
+
112
+ - If a `candidateFunction` is provided, it returns `true` at least once.
113
+ The candidateFunction ignores all the other conditions.
114
+ - If a `millisecondsThreshold` is provided, it passed such threshold (Execution time) at least `requestThreshold` times.
115
+ - If only a `requestThreshold` is provided (default), it is called at least `requestThreshold` times.
82
116
 
83
117
  # Other Info
84
118
 
119
+ ## Plugins
120
+
121
+ The library supports plugins to extend its functionality.
122
+ Please, refer to the [@jointly/cache-candidate-plugin-base](https://github.com/JointlyTech/cache-candidate-plugin-base) package for the documentation on how to create a plugin.
123
+
124
+ ### Available plugins
125
+
126
+ - [@jointly/cache-candidate-plugin-dependency-keys](https://github.com/JointlyTech/cache-candidate-plugin-dependency-keys): Allows to define dependency keys when setting the cache record.
127
+ This allows to invalidate the cache record when a dependency changes.
128
+
85
129
  ## Constraints
86
130
 
87
- - The decorator can only be applied to methods that return a `Promise`.
131
+ - The decorator should only be applied to methods that return a `Promise`.
132
+ Please, refer to the [Considerations on synchronous methods](#considerations-on-synchronous-methods) section for more information.
88
133
  - The `candidateFunction` must be synchronous to maintain good performances. If passed an async function bypassing type checking, the candidateFunction will return a Promise thus not working properly.
89
134
 
90
135
  ## Cache Stampede
@@ -97,7 +142,7 @@ The `runningQueries` Map will be cleaned after the method execution is finished
97
142
 
98
143
  The decorator doesn't consider the correct execution of the given cache methods.
99
144
  It isn't the decorator's responsibility to check if the cache methods are working properly.
100
- The only consideration done is based on the fact that the `set` method will eventually fulfill or reject the Promise as it uses the `.finally` method to delete the key from the `runningQueries` Map and set the timeout to clean the cache record.
145
+ The only consideration done is based on the fact that the `set` method will eventually fulfill or reject the Promise as it uses the `.finally` Promise method to delete the key from the `runningQueries` Map and set the timeout to clean the cache record.
101
146
 
102
147
  ## Problems with synchronous methods
103
148
 
@@ -111,6 +156,23 @@ The decorator uses the `instanceIdentifier` and `uniqueIdentifier` to identify t
111
156
  The `instanceIdentifier` is generated using the instance properties, so if the instance properties change, the `instanceIdentifier` will change as well, thus changing the data cache key.
112
157
  The `uniqueIdentifier` is generated using the class name and the method name.
113
158
 
159
+ ## Key composition
160
+
161
+ The cache key is composed based on the following criteria, allowing multiple files to export the same class name with the same method name / the same function names without conflicts:
162
+
163
+ ### Higher-order function
164
+
165
+ - The arguments passed to the method. (JSON.stringify)
166
+ - `uniqueIdentifier`: A uniqid generated to allow multiple files to contain the same class with the same method.
167
+
168
+ ### Decorator
169
+
170
+ - The method name.
171
+ - `uniqueIdentifier`: A uniqid generated to allow multiple files to contain the same class with the same method.
172
+ - `instanceIdentifier`: A uniqid generated for each instance of the class. It uses the instance properties to generate the id. (JSON.stringify)
173
+ - The arguments passed to the method. (JSON.stringify)
174
+
175
+
114
176
  ## Dictionary
115
177
 
116
178
  ### DataCache
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- "use strict";var U=Object.create;var f=Object.defineProperty;var Q=Object.getOwnPropertyDescriptor;var K=Object.getOwnPropertyNames;var G=Object.getPrototypeOf,j=Object.prototype.hasOwnProperty;var _=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports),q=(e,t)=>{for(var n in t)f(e,n,{get:t[n],enumerable:!0})},x=(e,t,n,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let a of K(t))!j.call(e,a)&&a!==n&&f(e,a,{get:()=>t[a],enumerable:!(r=Q(t,a))||r.enumerable});return e};var H=(e,t,n)=>(n=e!=null?U(G(e)):{},x(t||!e||!e.__esModule?f(n,"default",{value:e,enumerable:!0}):n,e)),J=e=>x(f({},"__esModule",{value:!0}),e);var F=_((ve,v)=>{"use strict";var y=Object.defineProperty,X=Object.getOwnPropertyDescriptor,W=Object.getOwnPropertyNames,$=Object.prototype.hasOwnProperty,B=(e,t)=>{for(var n in t)y(e,n,{get:t[n],enumerable:!0})},Y=(e,t,n,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let a of W(t))!$.call(e,a)&&a!==n&&y(e,a,{get:()=>t[a],enumerable:!(r=X(t,a))||r.enumerable});return e},z=e=>Y(y({},"__esModule",{value:!0}),e),N={};B(N,{Hook:()=>ee,hook:()=>te});v.exports=z(N);var V={before:()=>{},after:()=>{}},Z={before:()=>{},after:()=>{}};function ee(e){return function(t,n,r){let a={},i=r.value,{before:o,after:c}={...V,...e};return r.value=function(...u){o({context:a,args:u,target:t,propertyKey:n,descriptor:r});let s=i.apply(this,u);return c({context:a,args:u,target:t,propertyKey:n,descriptor:r,result:s}),s},r}}function te(e){return function(t){let n={},{before:r,after:a}={...Z,...e};return function(...i){r({context:n,args:i});let o=t(...i);return a({context:n,args:i,result:o}),o}}}});var w=_((be,S)=>{"use strict";var D=Object.defineProperty,re=Object.getOwnPropertyDescriptor,ie=Object.getOwnPropertyNames,oe=Object.prototype.hasOwnProperty,ce=(e,t)=>{for(var n in t)D(e,n,{get:t[n],enumerable:!0})},ue=(e,t,n,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let a of ie(t))!oe.call(e,a)&&a!==n&&D(e,a,{get:()=>t[a],enumerable:!(r=re(t,a))||r.enumerable});return e},se=e=>ue(D({},"__esModule",{value:!0}),e),b={};ce(b,{Hooks:()=>I});S.exports=se(b);var I=(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))(I||{})});var Oe={};q(Oe,{CacheCandidate:()=>Ae,CacheCandidateOptionsDefault:()=>T,DataCacheRecordNotFound:()=>h,Events:()=>p,RunningQueryRecordNotFound:()=>g,cacheCandidate:()=>Re});module.exports=J(Oe);var P=(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 T={ttl:6e5,timeFrame:3e4,requestsThreshold:3,cache:P(),keepAlive:!1,plugins:[],events:{onCacheHit:e=>e,onCacheSet:e=>e,onCacheDelete:e=>e,onBeforeFunctionExecution:e=>e,onAfterFunctionExecution:e=>e,onLog:e=>e}};var p=(a=>(a.RUNNING_QUERY="RUNNING_QUERY",a.CHECKING_CANDIDATE_FUNCTION="CHECKING_CANDIDATE_FUNCTION",a.CHECKING_MILLISECOND_THRESHOLD="CHECKING_MILLISECOND_THRESHOLD",a.CHECKING_REQUESTS_THRESHOLD="CHECKING_REQUESTS_THRESHOLD",a))(p||{}),h=Symbol("DataCacheRecordNotFound"),g=Symbol("RunningQueryRecordNotFound");var M=require("crypto");var ne=H(F());async function C(e,t=[],n){for(let r of t){let a=r.hooks.filter(o=>o.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 i=a[0];if(ae(i))await i.action(n,r.additionalParameters);else throw new Error(`Hook ${e} for plugin ${r.name} is not actionable.`)}}function ae(e){return[e.action!==void 0,typeof e.action=="function"].every(t=>t===!0)}var l=H(w());function de(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 le({options:e,key:t,timeframeCache:n}){if(n.has(t)){let r=n.get(t),a=r.filter(({executionEnd:i})=>de(i,e));r.length!==a.length&&n.set(t,a)}}function me({options:e,key:t,runningQueryCache:n}){return n.has(t)?(e.events.onLog({key:t,event:"RUNNING_QUERY"}),n.get(t)):g}function A(...e){return(0,M.createHash)("sha256").update(e.join("|")).digest("hex")}function he({birthTime:e,options:t}){return Date.now()-e>=t.timeFrame}async function fe({options:e,key:t,HookPayload:n}){if(await e.cache.has(t)){let{result:r,birthTime:a}=await e.cache.get(t);return he({birthTime:a,options:e})?(await k({options:e,key:t,HookPayload:n}),h):r}return h}async function ge({options:e,key:t,result:n}){return e.cache.set(t,{result:n,birthTime:Date.now()},e.ttl)}async function k({options:e,key:t,HookPayload:n}){await C(l.Hooks.DATACACHE_RECORD_DELETE_PRE,e.plugins,n),await e.cache.delete(t),await C(l.Hooks.DATACACHE_RECORD_DELETE_POST,e.plugins,n),e.events.onCacheDelete({key:t})}async function L({result:e,runningQueryCache:t,key:n,executionStart:r,options:a,timeframeCache:i,timeFrameTimeoutCache:o,args:c,HookPayload:u}){let s=Date.now(),m=s-r;a.events.onAfterFunctionExecution({key:n,executionTime:m}),Ce({key:n,timeframeCache:i,executionTime:m,executionEnd:s}),Ee({options:a,key:n,timeframeCache:i,executionTime:m,args:c})>=a.requestsThreshold&&(await C(l.Hooks.DATACACHE_RECORD_ADD_PRE,a.plugins,u),ge({options:a,key:n,result:e}).then(async()=>{await C(l.Hooks.DATACACHE_RECORD_ADD_POST,a.plugins,{...u,result:e}),a.events.onCacheSet({key:n})}).finally(()=>{t.delete(n),o.set(n,setTimeout(()=>{k({options:a,key:n,HookPayload:u})},a.ttl).unref())}))}function Ee({options:e,key:t,timeframeCache:n,executionTime:r,args:a}){let i=0,o=n.get(t);return e.candidateFunction?(e.events.onLog({key:t,event:"CHECKING_CANDIDATE_FUNCTION"}),i=pe(e,r,a,o)):e.millisecondThreshold?(e.events.onLog({key:t,event:"CHECKING_MILLISECOND_THRESHOLD"}),i=Te(r,e,o)):(e.events.onLog({key:t,event:"CHECKING_REQUESTS_THRESHOLD"}),i=o.length),i}function Te(e,t,n){let r=0;return e>t.millisecondThreshold&&(r=n.filter(a=>a.executionTime>t.millisecondThreshold).length),r}function pe(e,t,n,r){let a=0;return e.candidateFunction({timeFrameCacheRecords:r,options:e,args:n})&&(a=e.requestsThreshold),a}function ye({timeFrameTimeoutCache:e,key:t,options:n}){clearTimeout(e.get(t)),e.set(t,setTimeout(()=>{n.cache.delete(t)},n.ttl).unref())}function De(e=10){return Math.random().toString(36).substring(2,e+2)}async function R({options:e,key:t,timeFrameTimeoutCache:n,runningQueryCache:r,timeframeCache:a,args:i,originalMethod:o}){let c={options:{...e,plugins:void 0},key:t,timeFrameTimeoutCache:n,runningQueryCache:r,timeframeCache:a,fnArgs:i};await C(l.Hooks.INIT,e.plugins,c);let u=await fe({options:e,key:t,HookPayload:c});if(u!==h)return e.keepAlive&&ye({timeFrameTimeoutCache:n,key:t,options:e}),await C(l.Hooks.CACHE_HIT,e.plugins,{...c,result:u}),e.events.onCacheHit({key:t}),Promise.resolve(u);let s=me({options:e,key:t,runningQueryCache:r});if(s!==g)return await C(l.Hooks.CACHE_HIT,e.plugins,{...c,result:s}),e.events.onCacheHit({key:t}),s;le({options:e,key:t,timeframeCache:a}),await C(l.Hooks.EXECUTION_PRE,e.plugins,c),e.events.onBeforeFunctionExecution({key:t});let m=Date.now(),d=o(...i);return await C(l.Hooks.EXECUTION_POST,e.plugins,{...c,result:d}),d instanceof Promise?(r.set(t,d),d.then(E=>L({result:E,runningQueryCache:r,key:t,executionStart:m,options:e,timeframeCache:a,timeFrameTimeoutCache:n,args:i,HookPayload:c})),d):(L({result:d,runningQueryCache:r,key:t,executionStart:m,options:e,timeframeCache:a,timeFrameTimeoutCache:n,args:i,HookPayload:c}),d)}function O(e){let t=new Map,n=new Map,r=De(),a=new Map,i={...T,...e};return{timeframeCache:t,runningQueryCache:n,uniqueIdentifier:r,timeFrameTimeoutCache:a,options:i}}function Ae(e={}){let{timeframeCache:t,runningQueryCache:n,uniqueIdentifier:r,timeFrameTimeoutCache:a,options:i}=O(e);return function(o,c,u){let s=u.value;u.value=async function(...m){let d=o.constructor.name+JSON.stringify(this),E=A([o.constructor.name,c,r,d,JSON.stringify(m)]);return R({options:i,key:E,timeFrameTimeoutCache:a,runningQueryCache:n,timeframeCache:t,args:m,originalMethod:s})}}}function Re(e,t={}){let{timeframeCache:n,runningQueryCache:r,uniqueIdentifier:a,timeFrameTimeoutCache:i,options:o}=O(t);return async(...c)=>R({options:o,key:A([a,JSON.stringify(c)]),timeFrameTimeoutCache:i,runningQueryCache:r,timeframeCache:n,args:c,originalMethod:e})}0&&(module.exports={CacheCandidate,CacheCandidateOptionsDefault,DataCacheRecordNotFound,Events,RunningQueryRecordNotFound,cacheCandidate});
1
+ "use strict";var j=Object.create;var f=Object.defineProperty;var Q=Object.getOwnPropertyDescriptor;var q=Object.getOwnPropertyNames;var J=Object.getPrototypeOf,L=Object.prototype.hasOwnProperty;var O=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports),U=(e,t)=>{for(var n in t)f(e,n,{get:t[n],enumerable:!0})},x=(e,t,n,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let a of q(t))!L.call(e,a)&&a!==n&&f(e,a,{get:()=>t[a],enumerable:!(r=Q(t,a))||r.enumerable});return e};var P=(e,t,n)=>(n=e!=null?j(J(e)):{},x(t||!e||!e.__esModule?f(n,"default",{value:e,enumerable:!0}):n,e)),X=e=>x(f({},"__esModule",{value:!0}),e);var v=O((Fe,F)=>{"use strict";var T=Object.defineProperty,K=Object.getOwnPropertyDescriptor,W=Object.getOwnPropertyNames,$=Object.prototype.hasOwnProperty,B=(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 W(t))!$.call(e,a)&&a!==n&&T(e,a,{get:()=>t[a],enumerable:!(r=K(t,a))||r.enumerable});return e},G=e=>z(T({},"__esModule",{value:!0}),e),b={};B(b,{Hook:()=>Z,hook:()=>ee});F.exports=G(b);var V={before:()=>{},after:()=>{}},Y={before:()=>{},after:()=>{}};function Z(e){return function(t,n,r){let a={},i=r.value,{before:o,after:c}={...V,...e};return r.value=function(...u){o({context:a,args:u,target:t,propertyKey:n,descriptor:r});let s=i.apply(this,u);return c({context:a,args:u,target:t,propertyKey:n,descriptor:r,result:s}),s},r}}function ee(e){return function(t){let n={},{before:r,after:a}={...Y,...e};return function(...i){r({context:n,args:i});let o=t(...i);return a({context:n,args:i,result:o}),o}}}});var N=O((He,S)=>{"use strict";var E=Object.defineProperty,ae=Object.getOwnPropertyDescriptor,re=Object.getOwnPropertyNames,ie=Object.prototype.hasOwnProperty,oe=(e,t)=>{for(var n in t)E(e,n,{get:t[n],enumerable:!0})},ce=(e,t,n,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let a of re(t))!ie.call(e,a)&&a!==n&&E(e,a,{get:()=>t[a],enumerable:!(r=ae(t,a))||r.enumerable});return e},ue=e=>ce(E({},"__esModule",{value:!0}),e),H={};oe(H,{Hooks:()=>w});S.exports=ue(H);var w=(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))(w||{})});var Re={};U(Re,{CacheCandidate:()=>Ae,CacheCandidateOptionsDefault:()=>y,DataCacheRecordNotFound:()=>h,RunningQueryRecordNotFound:()=>g,cacheCandidate:()=>De});module.exports=X(Re);var _=(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 y={ttl:6e5,timeFrame:3e4,requestsThreshold:3,cache:_(),keepAlive:!1,plugins:[],events:{onCacheHit:e=>e,onCacheSet:e=>e,onCacheDelete:e=>e,onBeforeFunctionExecution:e=>e,onAfterFunctionExecution:e=>e}};var h=Symbol("DataCacheRecordNotFound"),g=Symbol("RunningQueryRecordNotFound");var k=require("crypto");var te=P(v());async function l(e,t=[],n){for(let r of t){let a=r.hooks.filter(o=>o.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 i=a[0];if(ne(i))await i.action(n,r.additionalParameters);else throw new Error(`Hook ${e} for plugin ${r.name} is not actionable.`)}}function ne(e){return[e.action!==void 0,typeof e.action=="function"].every(t=>t===!0)}var C=P(N());function se(e,t){return Date.now()<e+t.timeFrame}function de({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 le({options:e,key:t,timeframeCache:n}){if(n.has(t)){let r=n.get(t),a=r.filter(({executionEnd:i})=>se(i,e));r.length!==a.length&&n.set(t,a)}}function Ce({key:e,runningQueryCache:t}){return t.has(e)?t.get(e):g}function A(...e){return(0,k.createHash)("sha256").update(e.join("|")).digest("hex")}function me({birthTime:e,options:t}){return Date.now()-e>=t.timeFrame}async function he({options:e,key:t,HookPayload:n}){if(await e.cache.has(t)){let{result:r,birthTime:a}=await e.cache.get(t);return me({birthTime:a,options:e})?(await M({options:e,key:t,HookPayload:n}),h):r}return h}async function fe({options:e,key:t,result:n}){return e.cache.set(t,{result:n,birthTime:Date.now()},e.ttl)}async function M({options:e,key:t,HookPayload:n}){await l(C.Hooks.DATACACHE_RECORD_DELETE_PRE,e.plugins,n),await e.cache.delete(t),await l(C.Hooks.DATACACHE_RECORD_DELETE_POST,e.plugins,n),e.events.onCacheDelete({key:t})}async function I({result:e,runningQueryCache:t,key:n,executionStart:r,options:a,timeframeCache:i,timeFrameTimeoutCache:o,args:c,HookPayload:u}){let s=Date.now(),m=s-r;a.events.onAfterFunctionExecution({key:n,executionTime:m}),de({key:n,timeframeCache:i,executionTime:m,executionEnd:s}),ge({options:a,key:n,timeframeCache:i,executionTime:m,args:c})>=a.requestsThreshold&&(await l(C.Hooks.DATACACHE_RECORD_ADD_PRE,a.plugins,u),fe({options:a,key:n,result:e}).then(async()=>{await l(C.Hooks.DATACACHE_RECORD_ADD_POST,a.plugins,{...u,result:e}),a.events.onCacheSet({key:n})}).finally(()=>{t.delete(n),o.set(n,setTimeout(()=>{M({options:a,key:n,HookPayload:u})},a.ttl).unref())}))}function ge({options:e,key:t,timeframeCache:n,executionTime:r,args:a}){let i=0,o=n.get(t);return e.candidateFunction?i=ye(e,r,a,o):e.millisecondThreshold?i=pe(r,e,o):i=o.length,i}function pe(e,t,n){let r=0;return e>t.millisecondThreshold&&(r=n.filter(a=>a.executionTime>t.millisecondThreshold).length),r}function ye(e,t,n,r){let a=0;return e.candidateFunction({timeFrameCacheRecords:r,options:e,args:n})&&(a=e.requestsThreshold),a}function Te({timeFrameTimeoutCache:e,key:t,options:n}){clearTimeout(e.get(t)),e.set(t,setTimeout(()=>{n.cache.delete(t)},n.ttl).unref())}function Ee(e=10){return Math.random().toString(36).substring(2,e+2)}async function D({options:e,key:t,timeFrameTimeoutCache:n,runningQueryCache:r,timeframeCache:a,args:i,originalMethod:o}){let c={options:{...e,plugins:void 0},key:t,timeFrameTimeoutCache:n,runningQueryCache:r,timeframeCache:a,fnArgs:i};await l(C.Hooks.INIT,e.plugins,c);let u=await he({options:e,key:t,HookPayload:c});if(u!==h)return e.keepAlive&&Te({timeFrameTimeoutCache:n,key:t,options:e}),await l(C.Hooks.CACHE_HIT,e.plugins,{...c,result:u}),e.events.onCacheHit({key:t}),Promise.resolve(u);let s=Ce({key:t,runningQueryCache:r});if(s!==g)return await l(C.Hooks.CACHE_HIT,e.plugins,{...c,result:s}),e.events.onCacheHit({key:t}),s;le({options:e,key:t,timeframeCache:a}),await l(C.Hooks.EXECUTION_PRE,e.plugins,c),e.events.onBeforeFunctionExecution({key:t});let m=Date.now(),d=o(...i);return await l(C.Hooks.EXECUTION_POST,e.plugins,{...c,result:d}),d instanceof Promise?(r.set(t,d),d.then(p=>I({result:p,runningQueryCache:r,key:t,executionStart:m,options:e,timeframeCache:a,timeFrameTimeoutCache:n,args:i,HookPayload:c})),d):(I({result:d,runningQueryCache:r,key:t,executionStart:m,options:e,timeframeCache:a,timeFrameTimeoutCache:n,args:i,HookPayload:c}),d)}function R(e){let t=new Map,n=new Map,r=Ee(),a=new Map,i={...y,...e};return{timeframeCache:t,runningQueryCache:n,uniqueIdentifier:r,timeFrameTimeoutCache:a,options:i}}function Ae(e={}){let{timeframeCache:t,runningQueryCache:n,uniqueIdentifier:r,timeFrameTimeoutCache:a,options:i}=R(e);return function(o,c,u){let s=u.value;u.value=async function(...m){let d=o.constructor.name+JSON.stringify(this),p=A([c,r,d,JSON.stringify(m)]);return D({options:i,key:p,timeFrameTimeoutCache:a,runningQueryCache:n,timeframeCache:t,args:m,originalMethod:s})}}}function De(e,t={}){let{timeframeCache:n,runningQueryCache:r,uniqueIdentifier:a,timeFrameTimeoutCache:i,options:o}=R(t);return async(...c)=>D({options:o,key:A([a,JSON.stringify(c)]),timeFrameTimeoutCache:i,runningQueryCache:r,timeframeCache:n,args:c,originalMethod:e})}0&&(module.exports={CacheCandidate,CacheCandidateOptionsDefault,DataCacheRecordNotFound,RunningQueryRecordNotFound,cacheCandidate});
package/dist/models.d.ts CHANGED
@@ -36,10 +36,6 @@ export interface CacheCandidateOptions {
36
36
  key: string;
37
37
  executionTime: number;
38
38
  }) => void;
39
- onLog: ({ key, event }: {
40
- key: string;
41
- event: Events;
42
- }) => void;
43
39
  };
44
40
  plugins?: Array<CacheCandidatePluginWithAdditionalParameters>;
45
41
  }
@@ -54,11 +50,5 @@ export interface TimeFrameCacheRecord {
54
50
  export type TimeFrameCache = Map<string, Array<TimeFrameCacheRecord>>;
55
51
  export type RunningQueryCache = Map<string, Promise<any>>;
56
52
  export type TimeFrameTimeoutCache = Map<string, NodeJS.Timeout>;
57
- export declare enum Events {
58
- RUNNING_QUERY = "RUNNING_QUERY",
59
- CHECKING_CANDIDATE_FUNCTION = "CHECKING_CANDIDATE_FUNCTION",
60
- CHECKING_MILLISECOND_THRESHOLD = "CHECKING_MILLISECOND_THRESHOLD",
61
- CHECKING_REQUESTS_THRESHOLD = "CHECKING_REQUESTS_THRESHOLD"
62
- }
63
53
  export declare const DataCacheRecordNotFound: unique symbol;
64
54
  export declare const RunningQueryRecordNotFound: unique symbol;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@jointly/cache-candidate",
3
3
  "private": false,
4
- "version": "1.0.0",
4
+ "version": "1.0.2",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",
7
7
  "types": "dist/index.d.ts",