@resq-sw/decorators 0.1.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 +277 -0
- package/lib/_utils.d.ts +46 -0
- package/lib/_utils.d.ts.map +1 -0
- package/lib/_utils.js +91 -0
- package/lib/_utils.js.map +1 -0
- package/lib/after/after.d.ts +60 -0
- package/lib/after/after.d.ts.map +1 -0
- package/lib/after/after.fn.d.ts +39 -0
- package/lib/after/after.fn.d.ts.map +1 -0
- package/lib/after/after.fn.js +59 -0
- package/lib/after/after.fn.js.map +1 -0
- package/lib/after/after.js +41 -0
- package/lib/after/after.js.map +1 -0
- package/lib/after/after.types.d.ts +86 -0
- package/lib/after/after.types.d.ts.map +1 -0
- package/lib/after/after.types.js +0 -0
- package/lib/after/index.d.ts +3 -0
- package/lib/after/index.js +2 -0
- package/lib/before/before.d.ts +61 -0
- package/lib/before/before.d.ts.map +1 -0
- package/lib/before/before.fn.d.ts +39 -0
- package/lib/before/before.fn.d.ts.map +1 -0
- package/lib/before/before.fn.js +51 -0
- package/lib/before/before.fn.js.map +1 -0
- package/lib/before/before.js +40 -0
- package/lib/before/before.js.map +1 -0
- package/lib/before/before.types.d.ts +48 -0
- package/lib/before/before.types.d.ts.map +1 -0
- package/lib/before/before.types.js +0 -0
- package/lib/before/index.d.ts +3 -0
- package/lib/before/index.js +2 -0
- package/lib/bind/bind.d.ts +75 -0
- package/lib/bind/bind.d.ts.map +1 -0
- package/lib/bind/bind.fn.d.ts +46 -0
- package/lib/bind/bind.fn.d.ts.map +1 -0
- package/lib/bind/bind.fn.js +39 -0
- package/lib/bind/bind.fn.js.map +1 -0
- package/lib/bind/bind.js +64 -0
- package/lib/bind/bind.js.map +1 -0
- package/lib/bind/bind.types.d.ts +36 -0
- package/lib/bind/bind.types.d.ts.map +1 -0
- package/lib/bind/bind.types.js +0 -0
- package/lib/bind/index.d.ts +3 -0
- package/lib/bind/index.js +2 -0
- package/lib/debounce/debounce.d.ts +34 -0
- package/lib/debounce/debounce.d.ts.map +1 -0
- package/lib/debounce/debounce.fn.d.ts +40 -0
- package/lib/debounce/debounce.fn.d.ts.map +1 -0
- package/lib/debounce/debounce.fn.js +47 -0
- package/lib/debounce/debounce.fn.js.map +1 -0
- package/lib/debounce/debounce.js +48 -0
- package/lib/debounce/debounce.js.map +1 -0
- package/lib/debounce/index.d.ts +2 -0
- package/lib/debounce/index.js +2 -0
- package/lib/delay/delay.d.ts +35 -0
- package/lib/delay/delay.d.ts.map +1 -0
- package/lib/delay/delay.fn.d.ts +33 -0
- package/lib/delay/delay.fn.d.ts.map +1 -0
- package/lib/delay/delay.fn.js +38 -0
- package/lib/delay/delay.fn.js.map +1 -0
- package/lib/delay/delay.js +43 -0
- package/lib/delay/delay.js.map +1 -0
- package/lib/delay/index.d.ts +2 -0
- package/lib/delay/index.js +2 -0
- package/lib/delegate/delegate.d.ts +48 -0
- package/lib/delegate/delegate.d.ts.map +1 -0
- package/lib/delegate/delegate.fn.d.ts +57 -0
- package/lib/delegate/delegate.fn.d.ts.map +1 -0
- package/lib/delegate/delegate.fn.js +55 -0
- package/lib/delegate/delegate.fn.js.map +1 -0
- package/lib/delegate/delegate.js +56 -0
- package/lib/delegate/delegate.js.map +1 -0
- package/lib/delegate/delegate.types.d.ts +45 -0
- package/lib/delegate/delegate.types.d.ts.map +1 -0
- package/lib/delegate/delegate.types.js +0 -0
- package/lib/delegate/index.d.ts +3 -0
- package/lib/delegate/index.js +2 -0
- package/lib/exec-time/exec-time.d.ts +42 -0
- package/lib/exec-time/exec-time.d.ts.map +1 -0
- package/lib/exec-time/exec-time.fn.d.ts +50 -0
- package/lib/exec-time/exec-time.fn.d.ts.map +1 -0
- package/lib/exec-time/exec-time.fn.js +91 -0
- package/lib/exec-time/exec-time.fn.js.map +1 -0
- package/lib/exec-time/exec-time.js +55 -0
- package/lib/exec-time/exec-time.js.map +1 -0
- package/lib/exec-time/exec-time.types.d.ts +70 -0
- package/lib/exec-time/exec-time.types.d.ts.map +1 -0
- package/lib/exec-time/exec-time.types.js +0 -0
- package/lib/exec-time/index.d.ts +4 -0
- package/lib/exec-time/index.js +3 -0
- package/lib/execute/execute.d.ts +78 -0
- package/lib/execute/execute.d.ts.map +1 -0
- package/lib/execute/execute.js +82 -0
- package/lib/execute/execute.js.map +1 -0
- package/lib/execute/index.d.ts +2 -0
- package/lib/execute/index.js +2 -0
- package/lib/index.d.ts +30 -0
- package/lib/index.js +19 -0
- package/lib/memoize/index.d.ts +3 -0
- package/lib/memoize/index.js +2 -0
- package/lib/memoize/memoize.d.ts +67 -0
- package/lib/memoize/memoize.d.ts.map +1 -0
- package/lib/memoize/memoize.fn.d.ts +69 -0
- package/lib/memoize/memoize.fn.d.ts.map +1 -0
- package/lib/memoize/memoize.fn.js +43 -0
- package/lib/memoize/memoize.fn.js.map +1 -0
- package/lib/memoize/memoize.js +40 -0
- package/lib/memoize/memoize.js.map +1 -0
- package/lib/memoize/memoize.types.d.ts +107 -0
- package/lib/memoize/memoize.types.d.ts.map +1 -0
- package/lib/memoize/memoize.types.js +0 -0
- package/lib/memoize-async/index.d.ts +4 -0
- package/lib/memoize-async/index.js +3 -0
- package/lib/memoize-async/memoize-async.d.ts +68 -0
- package/lib/memoize-async/memoize-async.d.ts.map +1 -0
- package/lib/memoize-async/memoize-async.fn.d.ts +69 -0
- package/lib/memoize-async/memoize-async.fn.d.ts.map +1 -0
- package/lib/memoize-async/memoize-async.fn.js +52 -0
- package/lib/memoize-async/memoize-async.fn.js.map +1 -0
- package/lib/memoize-async/memoize-async.js +15 -0
- package/lib/memoize-async/memoize-async.js.map +1 -0
- package/lib/memoize-async/memoize-async.types.d.ts +74 -0
- package/lib/memoize-async/memoize-async.types.d.ts.map +1 -0
- package/lib/memoize-async/memoize-async.types.js +0 -0
- package/lib/observer/index.d.ts +3 -0
- package/lib/observer/index.js +2 -0
- package/lib/observer/observer.d.ts +54 -0
- package/lib/observer/observer.d.ts.map +1 -0
- package/lib/observer/observer.js +85 -0
- package/lib/observer/observer.js.map +1 -0
- package/lib/observer/observer.types.d.ts +41 -0
- package/lib/observer/observer.types.d.ts.map +1 -0
- package/lib/observer/observer.types.js +0 -0
- package/lib/rate-limit/index.d.ts +4 -0
- package/lib/rate-limit/index.js +3 -0
- package/lib/rate-limit/rate-limit.d.ts +58 -0
- package/lib/rate-limit/rate-limit.d.ts.map +1 -0
- package/lib/rate-limit/rate-limit.fn.d.ts +43 -0
- package/lib/rate-limit/rate-limit.fn.d.ts.map +1 -0
- package/lib/rate-limit/rate-limit.fn.js +56 -0
- package/lib/rate-limit/rate-limit.fn.js.map +1 -0
- package/lib/rate-limit/rate-limit.js +65 -0
- package/lib/rate-limit/rate-limit.js.map +1 -0
- package/lib/rate-limit/rate-limit.types.d.ts +148 -0
- package/lib/rate-limit/rate-limit.types.d.ts.map +1 -0
- package/lib/rate-limit/rate-limit.types.js +0 -0
- package/lib/rate-limit/simple-rate-limit-counter.d.ts +89 -0
- package/lib/rate-limit/simple-rate-limit-counter.d.ts.map +1 -0
- package/lib/rate-limit/simple-rate-limit-counter.js +98 -0
- package/lib/rate-limit/simple-rate-limit-counter.js.map +1 -0
- package/lib/readonly/index.d.ts +3 -0
- package/lib/readonly/index.js +2 -0
- package/lib/readonly/readonly.d.ts +39 -0
- package/lib/readonly/readonly.d.ts.map +1 -0
- package/lib/readonly/readonly.js +43 -0
- package/lib/readonly/readonly.js.map +1 -0
- package/lib/readonly/readonly.types.d.ts +40 -0
- package/lib/readonly/readonly.types.d.ts.map +1 -0
- package/lib/readonly/readonly.types.js +0 -0
- package/lib/throttle/index.d.ts +2 -0
- package/lib/throttle/index.js +2 -0
- package/lib/throttle/throttle.d.ts +35 -0
- package/lib/throttle/throttle.d.ts.map +1 -0
- package/lib/throttle/throttle.fn.d.ts +42 -0
- package/lib/throttle/throttle.fn.d.ts.map +1 -0
- package/lib/throttle/throttle.fn.js +52 -0
- package/lib/throttle/throttle.fn.js.map +1 -0
- package/lib/throttle/throttle.js +43 -0
- package/lib/throttle/throttle.js.map +1 -0
- package/lib/throttle-async/index.d.ts +2 -0
- package/lib/throttle-async/index.js +2 -0
- package/lib/throttle-async/throttle-async-executor.d.ts +79 -0
- package/lib/throttle-async/throttle-async-executor.d.ts.map +1 -0
- package/lib/throttle-async/throttle-async-executor.js +122 -0
- package/lib/throttle-async/throttle-async-executor.js.map +1 -0
- package/lib/throttle-async/throttle-async.d.ts +68 -0
- package/lib/throttle-async/throttle-async.d.ts.map +1 -0
- package/lib/throttle-async/throttle-async.fn.d.ts +41 -0
- package/lib/throttle-async/throttle-async.fn.d.ts.map +1 -0
- package/lib/throttle-async/throttle-async.fn.js +46 -0
- package/lib/throttle-async/throttle-async.fn.js.map +1 -0
- package/lib/throttle-async/throttle-async.js +45 -0
- package/lib/throttle-async/throttle-async.js.map +1 -0
- package/lib/types.d.ts +81 -0
- package/lib/types.d.ts.map +1 -0
- package/lib/types.js +0 -0
- package/package.json +40 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { Memoizable, MemoizeConfig } from "./memoize.types.js";
|
|
2
|
+
|
|
3
|
+
//#region src/memoize/memoize.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Decorator that caches method results based on their arguments.
|
|
6
|
+
* Subsequent calls with the same arguments return the cached result.
|
|
7
|
+
*
|
|
8
|
+
* @overload
|
|
9
|
+
* @template T - The type of the class containing the decorated method
|
|
10
|
+
* @template D - The return type of the decorated method
|
|
11
|
+
* @returns {Memoizable<T, D>} The decorator function
|
|
12
|
+
*
|
|
13
|
+
* @overload
|
|
14
|
+
* @template T - The type of the class containing the decorated method
|
|
15
|
+
* @template D - The return type of the decorated method
|
|
16
|
+
* @param {MemoizeConfig<T, D>} config - Configuration for memoization
|
|
17
|
+
* @returns {Memoizable<T, D>} The decorator function
|
|
18
|
+
*
|
|
19
|
+
* @overload
|
|
20
|
+
* @template T - The type of the class containing the decorated method
|
|
21
|
+
* @template D - The return type of the decorated method
|
|
22
|
+
* @param {number} expirationTimeMs - Cache expiration time in milliseconds
|
|
23
|
+
* @returns {Memoizable<T, D>} The decorator function
|
|
24
|
+
*
|
|
25
|
+
* @throws {Error} When applied to a non-method property
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```typescript
|
|
29
|
+
* class DataService {
|
|
30
|
+
* // Basic usage - caches indefinitely
|
|
31
|
+
* @memoize()
|
|
32
|
+
* getUser(id: string): User {
|
|
33
|
+
* return this.database.findUser(id);
|
|
34
|
+
* }
|
|
35
|
+
*
|
|
36
|
+
* // With TTL (time to live)
|
|
37
|
+
* @memoize(60000) // Cache for 60 seconds
|
|
38
|
+
* getConfig(): Config {
|
|
39
|
+
* return this.loadConfig();
|
|
40
|
+
* }
|
|
41
|
+
*
|
|
42
|
+
* // With custom cache and key resolver
|
|
43
|
+
* @memoize({
|
|
44
|
+
* cache: new LRUCache<string, User>(100),
|
|
45
|
+
* keyResolver: (userId, includeDetails) => `${userId}-${includeDetails}`,
|
|
46
|
+
* expirationTimeMs: 300000 // 5 minutes
|
|
47
|
+
* })
|
|
48
|
+
* getUserWithDetails(userId: string, includeDetails: boolean): User {
|
|
49
|
+
* return this.fetchUser(userId, includeDetails);
|
|
50
|
+
* }
|
|
51
|
+
* }
|
|
52
|
+
*
|
|
53
|
+
* const service = new DataService();
|
|
54
|
+
*
|
|
55
|
+
* // First call executes the method
|
|
56
|
+
* const user1 = service.getUser('123');
|
|
57
|
+
*
|
|
58
|
+
* // Second call with same argument returns cached result
|
|
59
|
+
* const user2 = service.getUser('123'); // Instant, no database query
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
declare function memoize<T = any, D = any>(): Memoizable<T, D>;
|
|
63
|
+
declare function memoize<T = any, D = any>(config: MemoizeConfig<T, D>): Memoizable<T, D>;
|
|
64
|
+
declare function memoize<T = any, D = any>(expirationTimeMs: number): Memoizable<T, D>;
|
|
65
|
+
//#endregion
|
|
66
|
+
export { memoize };
|
|
67
|
+
//# sourceMappingURL=memoize.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memoize.d.ts","names":[],"sources":["../../src/memoize/memoize.ts"],"mappings":";;;;;AAyHA;;;;;;;;;;;;;;;;;;;;;;AACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAFgB,OAAA,kBAAA,CAAA,GAA6B,UAAA,CAAW,CAAA,EAAG,CAAA;AAAA,iBAC3C,OAAA,kBAAA,CAA0B,MAAA,EAAQ,aAAA,CAAc,CAAA,EAAG,CAAA,IAAK,UAAA,CAAW,CAAA,EAAG,CAAA;AAAA,iBACtE,OAAA,kBAAA,CAA0B,gBAAA,WAA2B,UAAA,CAAW,CAAA,EAAG,CAAA"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { Method } from "../types.js";
|
|
2
|
+
import { MemoizeConfig } from "./memoize.types.js";
|
|
3
|
+
|
|
4
|
+
//#region src/memoize/memoize.fn.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Wraps a method to cache its results based on arguments.
|
|
7
|
+
*
|
|
8
|
+
* @overload
|
|
9
|
+
* @template D - The return type of the original method
|
|
10
|
+
* @template A - The argument types of the original method
|
|
11
|
+
* @param {Method<D, A>} originalMethod - The method to memoize
|
|
12
|
+
* @returns {Method<D, A>} The memoized method
|
|
13
|
+
*
|
|
14
|
+
* @overload
|
|
15
|
+
* @template D - The return type of the original method
|
|
16
|
+
* @template A - The argument types of the original method
|
|
17
|
+
* @param {Method<D, A>} originalMethod - The method to memoize
|
|
18
|
+
* @param {MemoizeConfig<any, D>} config - Configuration for memoization
|
|
19
|
+
* @returns {Method<D, A>} The memoized method
|
|
20
|
+
*
|
|
21
|
+
* @overload
|
|
22
|
+
* @template D - The return type of the original method
|
|
23
|
+
* @template A - The argument types of the original method
|
|
24
|
+
* @param {Method<D, A>} originalMethod - The method to memoize
|
|
25
|
+
* @param {number} expirationTimeMs - Cache expiration time in milliseconds
|
|
26
|
+
* @returns {Method<D, A>} The memoized method
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```typescript
|
|
30
|
+
* class ExpensiveOperations {
|
|
31
|
+
* calculatePrimes(max: number): number[] {
|
|
32
|
+
* const primes = [];
|
|
33
|
+
* for (let i = 2; i <= max; i++) {
|
|
34
|
+
* if (this.isPrime(i)) primes.push(i);
|
|
35
|
+
* }
|
|
36
|
+
* return primes;
|
|
37
|
+
* }
|
|
38
|
+
* }
|
|
39
|
+
*
|
|
40
|
+
* const ops = new ExpensiveOperations();
|
|
41
|
+
*
|
|
42
|
+
* // Basic memoization
|
|
43
|
+
* const memoized = memoizeFn(ops.calculatePrimes.bind(ops));
|
|
44
|
+
* const primes1 = memoized(1000); // Computes
|
|
45
|
+
* const primes2 = memoized(1000); // Returns cached result
|
|
46
|
+
*
|
|
47
|
+
* // With TTL
|
|
48
|
+
* const withTTL = memoizeFn(
|
|
49
|
+
* ops.calculatePrimes.bind(ops),
|
|
50
|
+
* 60000 // Cache for 60 seconds
|
|
51
|
+
* );
|
|
52
|
+
*
|
|
53
|
+
* // With custom config
|
|
54
|
+
* const withConfig = memoizeFn(
|
|
55
|
+
* ops.calculatePrimes.bind(ops),
|
|
56
|
+
* {
|
|
57
|
+
* cache: new Map(),
|
|
58
|
+
* keyResolver: (max) => `primes-${max}`,
|
|
59
|
+
* expirationTimeMs: 300000
|
|
60
|
+
* }
|
|
61
|
+
* );
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
declare function memoizeFn<D = any, A extends any[] = any[]>(originalMethod: Method<D, A>): Method<D, A>;
|
|
65
|
+
declare function memoizeFn<D = any, A extends any[] = any[]>(originalMethod: Method<D, A>, config: MemoizeConfig<any, D>): Method<D, A>;
|
|
66
|
+
declare function memoizeFn<D = any, A extends any[] = any[]>(originalMethod: Method<D, A>, expirationTimeMs: number): Method<D, A>;
|
|
67
|
+
//#endregion
|
|
68
|
+
export { memoizeFn };
|
|
69
|
+
//# sourceMappingURL=memoize.fn.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memoize.fn.d.ts","names":[],"sources":["../../src/memoize/memoize.fn.ts"],"mappings":";;;;;;;;;;;;;AAkFA;;;;;;;;;;;;;;;;;;;;;;;;;;;AAIA;;;;;;;;;;;;;;;;;;;;;;;iBAPgB,SAAA,kCAAA,CACd,cAAA,EAAgB,MAAA,CAAO,CAAA,EAAG,CAAA,IACzB,MAAA,CAAO,CAAA,EAAG,CAAA;AAAA,iBACG,SAAA,kCAAA,CACd,cAAA,EAAgB,MAAA,CAAO,CAAA,EAAG,CAAA,GAC1B,MAAA,EAAQ,aAAA,MAAmB,CAAA,IAC1B,MAAA,CAAO,CAAA,EAAG,CAAA;AAAA,iBACG,SAAA,kCAAA,CACd,cAAA,EAAgB,MAAA,CAAO,CAAA,EAAG,CAAA,GAC1B,gBAAA,WACC,MAAA,CAAO,CAAA,EAAG,CAAA"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { TaskExec } from "../_utils.js";
|
|
2
|
+
//#region src/memoize/memoize.fn.ts
|
|
3
|
+
/**
|
|
4
|
+
* Copyright 2026 ResQ
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this file except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*/
|
|
18
|
+
function memoizeFn(originalMethod, input) {
|
|
19
|
+
const defaultConfig = { cache: /* @__PURE__ */ new Map() };
|
|
20
|
+
const runner = new TaskExec();
|
|
21
|
+
let resolvedConfig = { ...defaultConfig };
|
|
22
|
+
if (typeof input === "number") resolvedConfig.expirationTimeMs = input;
|
|
23
|
+
else resolvedConfig = {
|
|
24
|
+
...resolvedConfig,
|
|
25
|
+
...input
|
|
26
|
+
};
|
|
27
|
+
return function(...args) {
|
|
28
|
+
const keyResolver = typeof resolvedConfig.keyResolver === "string" ? this[resolvedConfig.keyResolver].bind(this) : resolvedConfig.keyResolver;
|
|
29
|
+
const key = keyResolver ? keyResolver(...args) : JSON.stringify(args);
|
|
30
|
+
if (resolvedConfig.cache && !resolvedConfig.cache.has(key)) {
|
|
31
|
+
const response = originalMethod.apply(this, args);
|
|
32
|
+
if (resolvedConfig.expirationTimeMs !== void 0) runner.exec(() => {
|
|
33
|
+
resolvedConfig.cache?.delete(key);
|
|
34
|
+
}, resolvedConfig.expirationTimeMs);
|
|
35
|
+
resolvedConfig.cache.set(key, response);
|
|
36
|
+
}
|
|
37
|
+
return resolvedConfig.cache?.get(key);
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
//#endregion
|
|
41
|
+
export { memoizeFn };
|
|
42
|
+
|
|
43
|
+
//# sourceMappingURL=memoize.fn.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memoize.fn.js","names":[],"sources":["../../src/memoize/memoize.fn.ts"],"sourcesContent":["/**\n * Copyright 2026 ResQ\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { TaskExec } from '../_utils.js';\nimport type { Method } from '../types.js';\nimport type { MemoizeConfig } from './memoize.types.js';\n\n/**\n * Wraps a method to cache its results based on arguments.\n *\n * @overload\n * @template D - The return type of the original method\n * @template A - The argument types of the original method\n * @param {Method<D, A>} originalMethod - The method to memoize\n * @returns {Method<D, A>} The memoized method\n *\n * @overload\n * @template D - The return type of the original method\n * @template A - The argument types of the original method\n * @param {Method<D, A>} originalMethod - The method to memoize\n * @param {MemoizeConfig<any, D>} config - Configuration for memoization\n * @returns {Method<D, A>} The memoized method\n *\n * @overload\n * @template D - The return type of the original method\n * @template A - The argument types of the original method\n * @param {Method<D, A>} originalMethod - The method to memoize\n * @param {number} expirationTimeMs - Cache expiration time in milliseconds\n * @returns {Method<D, A>} The memoized method\n *\n * @example\n * ```typescript\n * class ExpensiveOperations {\n * calculatePrimes(max: number): number[] {\n * const primes = [];\n * for (let i = 2; i <= max; i++) {\n * if (this.isPrime(i)) primes.push(i);\n * }\n * return primes;\n * }\n * }\n *\n * const ops = new ExpensiveOperations();\n *\n * // Basic memoization\n * const memoized = memoizeFn(ops.calculatePrimes.bind(ops));\n * const primes1 = memoized(1000); // Computes\n * const primes2 = memoized(1000); // Returns cached result\n *\n * // With TTL\n * const withTTL = memoizeFn(\n * ops.calculatePrimes.bind(ops),\n * 60000 // Cache for 60 seconds\n * );\n *\n * // With custom config\n * const withConfig = memoizeFn(\n * ops.calculatePrimes.bind(ops),\n * {\n * cache: new Map(),\n * keyResolver: (max) => `primes-${max}`,\n * expirationTimeMs: 300000\n * }\n * );\n * ```\n */\nexport function memoizeFn<D = any, A extends any[] = any[]>(\n originalMethod: Method<D, A>,\n): Method<D, A>;\nexport function memoizeFn<D = any, A extends any[] = any[]>(\n originalMethod: Method<D, A>,\n config: MemoizeConfig<any, D>,\n): Method<D, A>;\nexport function memoizeFn<D = any, A extends any[] = any[]>(\n originalMethod: Method<D, A>,\n expirationTimeMs: number,\n): Method<D, A>;\nexport function memoizeFn<D = any, A extends any[] = any[]>(\n originalMethod: Method<D, A>,\n input?: MemoizeConfig<any, D> | number,\n): Method<D, A> {\n const defaultConfig: MemoizeConfig<any, D> = {\n cache: new Map<string, D>(),\n };\n\n const runner = new TaskExec();\n let resolvedConfig = {\n ...defaultConfig,\n } as MemoizeConfig<any, D>;\n\n if (typeof input === 'number') {\n resolvedConfig.expirationTimeMs = input;\n } else {\n resolvedConfig = {\n ...resolvedConfig,\n ...input,\n };\n }\n\n return function (this: any, ...args: A): D {\n const keyResolver =\n typeof resolvedConfig.keyResolver === 'string'\n ? this[resolvedConfig.keyResolver].bind(this)\n : resolvedConfig.keyResolver;\n\n const key = keyResolver ? keyResolver(...args) : JSON.stringify(args);\n\n if (resolvedConfig.cache && !resolvedConfig.cache.has(key)) {\n const response = originalMethod.apply(this, args);\n\n if (resolvedConfig.expirationTimeMs !== undefined) {\n runner.exec(() => {\n resolvedConfig.cache?.delete(key);\n }, resolvedConfig.expirationTimeMs);\n }\n\n resolvedConfig.cache.set(key, response);\n }\n\n return resolvedConfig.cache?.get(key) as D;\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AA0FA,SAAgB,UACd,gBACA,OACc;CACd,MAAM,gBAAuC,EAC3C,uBAAO,IAAI,KAAgB,EAC5B;CAED,MAAM,SAAS,IAAI,UAAU;CAC7B,IAAI,iBAAiB,EACnB,GAAG,eACJ;AAED,KAAI,OAAO,UAAU,SACnB,gBAAe,mBAAmB;KAElC,kBAAiB;EACf,GAAG;EACH,GAAG;EACJ;AAGH,QAAO,SAAqB,GAAG,MAAY;EACzC,MAAM,cACJ,OAAO,eAAe,gBAAgB,WAClC,KAAK,eAAe,aAAa,KAAK,KAAK,GAC3C,eAAe;EAErB,MAAM,MAAM,cAAc,YAAY,GAAG,KAAK,GAAG,KAAK,UAAU,KAAK;AAErE,MAAI,eAAe,SAAS,CAAC,eAAe,MAAM,IAAI,IAAI,EAAE;GAC1D,MAAM,WAAW,eAAe,MAAM,MAAM,KAAK;AAEjD,OAAI,eAAe,qBAAqB,KAAA,EACtC,QAAO,WAAW;AAChB,mBAAe,OAAO,OAAO,IAAI;MAChC,eAAe,iBAAiB;AAGrC,kBAAe,MAAM,IAAI,KAAK,SAAS;;AAGzC,SAAO,eAAe,OAAO,IAAI,IAAI"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { memoizeFn } from "./memoize.fn.js";
|
|
2
|
+
//#region src/memoize/memoize.ts
|
|
3
|
+
/**
|
|
4
|
+
* @fileoverview Memoize decorator - caches method results based on arguments.
|
|
5
|
+
* Subsequent calls with the same arguments return the cached result instead of
|
|
6
|
+
* re-executing the method.
|
|
7
|
+
*
|
|
8
|
+
* @module @resq/typescript/decorators/memoize
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* class Calculator {
|
|
13
|
+
* @memoize()
|
|
14
|
+
* fibonacci(n: number): number {
|
|
15
|
+
* if (n <= 1) return n;
|
|
16
|
+
* return this.fibonacci(n - 1) + this.fibonacci(n - 2);
|
|
17
|
+
* }
|
|
18
|
+
* }
|
|
19
|
+
*
|
|
20
|
+
* const calc = new Calculator();
|
|
21
|
+
* console.log(calc.fibonacci(40)); // Computes and caches
|
|
22
|
+
* console.log(calc.fibonacci(40)); // Returns cached result instantly
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* @copyright Copyright (c) 2026 ResQ
|
|
26
|
+
* @license MIT
|
|
27
|
+
*/
|
|
28
|
+
function memoize(input) {
|
|
29
|
+
return (target, propertyName, descriptor) => {
|
|
30
|
+
if (descriptor.value) {
|
|
31
|
+
descriptor.value = input === void 0 ? memoizeFn(descriptor.value) : memoizeFn(descriptor.value, input);
|
|
32
|
+
return descriptor;
|
|
33
|
+
}
|
|
34
|
+
throw new Error("@memoize is applicable only on a methods.");
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
//#endregion
|
|
38
|
+
export { memoize };
|
|
39
|
+
|
|
40
|
+
//# sourceMappingURL=memoize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memoize.js","names":[],"sources":["../../src/memoize/memoize.ts"],"sourcesContent":["/**\n * Copyright 2026 ResQ\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { Method } from '../types.js';\n/*\n * Copyright 2026 ResQ\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * @fileoverview Memoize decorator - caches method results based on arguments.\n * Subsequent calls with the same arguments return the cached result instead of\n * re-executing the method.\n *\n * @module @resq/typescript/decorators/memoize\n *\n * @example\n * ```typescript\n * class Calculator {\n * @memoize()\n * fibonacci(n: number): number {\n * if (n <= 1) return n;\n * return this.fibonacci(n - 1) + this.fibonacci(n - 2);\n * }\n * }\n *\n * const calc = new Calculator();\n * console.log(calc.fibonacci(40)); // Computes and caches\n * console.log(calc.fibonacci(40)); // Returns cached result instantly\n * ```\n *\n * @copyright Copyright (c) 2026 ResQ\n * @license MIT\n */\n\nimport { memoizeFn } from './memoize.fn.js';\nimport type { Memoizable, MemoizeConfig } from './memoize.types.js';\n\n/**\n * Decorator that caches method results based on their arguments.\n * Subsequent calls with the same arguments return the cached result.\n *\n * @overload\n * @template T - The type of the class containing the decorated method\n * @template D - The return type of the decorated method\n * @returns {Memoizable<T, D>} The decorator function\n *\n * @overload\n * @template T - The type of the class containing the decorated method\n * @template D - The return type of the decorated method\n * @param {MemoizeConfig<T, D>} config - Configuration for memoization\n * @returns {Memoizable<T, D>} The decorator function\n *\n * @overload\n * @template T - The type of the class containing the decorated method\n * @template D - The return type of the decorated method\n * @param {number} expirationTimeMs - Cache expiration time in milliseconds\n * @returns {Memoizable<T, D>} The decorator function\n *\n * @throws {Error} When applied to a non-method property\n *\n * @example\n * ```typescript\n * class DataService {\n * // Basic usage - caches indefinitely\n * @memoize()\n * getUser(id: string): User {\n * return this.database.findUser(id);\n * }\n *\n * // With TTL (time to live)\n * @memoize(60000) // Cache for 60 seconds\n * getConfig(): Config {\n * return this.loadConfig();\n * }\n *\n * // With custom cache and key resolver\n * @memoize({\n * cache: new LRUCache<string, User>(100),\n * keyResolver: (userId, includeDetails) => `${userId}-${includeDetails}`,\n * expirationTimeMs: 300000 // 5 minutes\n * })\n * getUserWithDetails(userId: string, includeDetails: boolean): User {\n * return this.fetchUser(userId, includeDetails);\n * }\n * }\n *\n * const service = new DataService();\n *\n * // First call executes the method\n * const user1 = service.getUser('123');\n *\n * // Second call with same argument returns cached result\n * const user2 = service.getUser('123'); // Instant, no database query\n * ```\n */\nexport function memoize<T = any, D = any>(): Memoizable<T, D>;\nexport function memoize<T = any, D = any>(config: MemoizeConfig<T, D>): Memoizable<T, D>;\nexport function memoize<T = any, D = any>(expirationTimeMs: number): Memoizable<T, D>;\nexport function memoize<T = any, D = any>(input?: MemoizeConfig<T, D> | number): Memoizable<T, D> {\n return (\n target: T,\n propertyName: keyof T,\n descriptor: TypedPropertyDescriptor<Method<D>>,\n ): TypedPropertyDescriptor<Method<D>> => {\n if (descriptor.value) {\n descriptor.value = input === undefined ? memoizeFn(descriptor.value) : memoizeFn(descriptor.value, input as any);\n\n return descriptor;\n }\n throw new Error('@memoize is applicable only on a methods.');\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AA2HA,SAAgB,QAA0B,OAAwD;AAChG,SACE,QACA,cACA,eACuC;AACvC,MAAI,WAAW,OAAO;AACpB,cAAW,QAAQ,UAAU,KAAA,IAAY,UAAU,WAAW,MAAM,GAAG,UAAU,WAAW,OAAO,MAAa;AAEhH,UAAO;;AAET,QAAM,IAAI,MAAM,4CAA4C"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { Method } from "../types.js";
|
|
2
|
+
|
|
3
|
+
//#region src/memoize/memoize.types.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* @fileoverview Type definitions for the Memoize decorator.
|
|
6
|
+
* Provides types for caching method results.
|
|
7
|
+
*
|
|
8
|
+
* @module @resq/typescript/decorators/memoize/types
|
|
9
|
+
*
|
|
10
|
+
* @copyright Copyright (c) 2026 ResQ
|
|
11
|
+
* @license MIT
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* @fileoverview Type definitions for the Memoize decorator.
|
|
15
|
+
* Provides types for caching method results.
|
|
16
|
+
*
|
|
17
|
+
* @module @resq/typescript/decorators/memoize/types
|
|
18
|
+
*
|
|
19
|
+
* @copyright Copyright (c) 2026 ResQ
|
|
20
|
+
* @license MIT
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* Function type for resolving cache keys from method arguments.
|
|
24
|
+
*
|
|
25
|
+
* @param {...any[]} args - The method arguments
|
|
26
|
+
* @returns {string} The cache key string
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```typescript
|
|
30
|
+
* const keyResolver: KeyResolver = (userId, includeDetails) => {
|
|
31
|
+
* return `${userId}-${includeDetails}`;
|
|
32
|
+
* };
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
type KeyResolver = (...args: unknown[]) => string;
|
|
36
|
+
/**
|
|
37
|
+
* Interface for cache implementations used by the memoize decorator.
|
|
38
|
+
*
|
|
39
|
+
* @interface Cache
|
|
40
|
+
* @template D - The type of values stored in the cache
|
|
41
|
+
* @property {(key: string, value: D) => void} set - Store a value in the cache
|
|
42
|
+
* @property {(key: string) => D | null | undefined} get - Retrieve a value from the cache
|
|
43
|
+
* @property {(key: string) => void} delete - Remove a value from the cache
|
|
44
|
+
* @property {(key: string) => boolean} has - Check if a key exists in the cache
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```typescript
|
|
48
|
+
* const cache: Cache<User> = {
|
|
49
|
+
* set: (key, value) => storage.set(key, value),
|
|
50
|
+
* get: (key) => storage.get(key),
|
|
51
|
+
* delete: (key) => storage.delete(key),
|
|
52
|
+
* has: (key) => storage.has(key)
|
|
53
|
+
* };
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
interface Cache<D> {
|
|
57
|
+
/** Store a value in the cache */
|
|
58
|
+
set: (key: string, value: D) => void;
|
|
59
|
+
/** Retrieve a value from the cache */
|
|
60
|
+
get: (key: string) => D | null | undefined;
|
|
61
|
+
/** Remove a value from the cache */
|
|
62
|
+
delete: (key: string) => void;
|
|
63
|
+
/** Check if a key exists in the cache */
|
|
64
|
+
has: (key: string) => boolean;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Configuration options for the @memoize decorator.
|
|
68
|
+
*
|
|
69
|
+
* @interface MemoizeConfig
|
|
70
|
+
* @template T - The type of the class containing the decorated method
|
|
71
|
+
* @template D - The return type of the decorated method
|
|
72
|
+
* @property {Cache<D>} [cache] - Custom cache implementation (defaults to Map)
|
|
73
|
+
* @property {KeyResolver | keyof T} [keyResolver] - Function or method name for generating cache keys
|
|
74
|
+
* @property {number} [expirationTimeMs] - Time in milliseconds after which cached values expire
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```typescript
|
|
78
|
+
* const config: MemoizeConfig<MyService, User> = {
|
|
79
|
+
* cache: new LRUCache<string, User>(100),
|
|
80
|
+
* keyResolver: (id) => `user-${id}`,
|
|
81
|
+
* expirationTimeMs: 300000 // 5 minutes
|
|
82
|
+
* };
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
interface MemoizeConfig<T, D> {
|
|
86
|
+
/** Custom cache implementation (defaults to Map) */
|
|
87
|
+
cache?: Cache<D>;
|
|
88
|
+
/** Function or method name for generating cache keys */
|
|
89
|
+
keyResolver?: KeyResolver | keyof T;
|
|
90
|
+
/** Time in milliseconds after which cached values expire */
|
|
91
|
+
expirationTimeMs?: number;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Type for the @memoize decorator function.
|
|
95
|
+
*
|
|
96
|
+
* @template T - The type of the class containing the decorated method
|
|
97
|
+
* @template D - The return type of the decorated method
|
|
98
|
+
*
|
|
99
|
+
* @param {T} target - The class prototype
|
|
100
|
+
* @param {keyof T} propertyName - The name of the method being decorated
|
|
101
|
+
* @param {TypedPropertyDescriptor<Method<D>>} descriptor - The property descriptor
|
|
102
|
+
* @returns {TypedPropertyDescriptor<Method<D>>} The modified descriptor
|
|
103
|
+
*/
|
|
104
|
+
type Memoizable<T, D> = (target: T, propertyName: keyof T, descriptor: TypedPropertyDescriptor<Method<D>>) => TypedPropertyDescriptor<Method<D>>;
|
|
105
|
+
//#endregion
|
|
106
|
+
export { Cache, KeyResolver, Memoizable, MemoizeConfig };
|
|
107
|
+
//# sourceMappingURL=memoize.types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memoize.types.d.ts","names":[],"sources":["../../src/memoize/memoize.types.ts"],"mappings":";;;;;;;;;;;;;AA8EA;;;;;;;;;;;;;;;;;;AA8BA;;;KApDY,WAAA,OAAkB,IAAA;;;;;;;;;;;;;;;;;AAwE9B;;;;UAlDiB,KAAA;EAqD4B;EAnD3C,GAAA,GAAM,GAAA,UAAa,KAAA,EAAO,CAAA;EAmDd;EAjDZ,GAAA,GAAM,GAAA,aAAgB,CAAA;EAkDK;EAhD3B,MAAA,GAAS,GAAA;EAgDiB;EA9C1B,GAAA,GAAM,GAAA;AAAA;;;;;;;;;;;;;;;;;;;;UAsBS,aAAA;;EAEf,KAAA,GAAQ,KAAA,CAAM,CAAA;;EAEd,WAAA,GAAc,WAAA,SAAoB,CAAA;;EAElC,gBAAA;AAAA;;;;;;;;;;;;KAcU,UAAA,UACV,MAAA,EAAQ,CAAA,EACR,YAAA,QAAoB,CAAA,EACpB,UAAA,EAAY,uBAAA,CAAwB,MAAA,CAAO,CAAA,OACxC,uBAAA,CAAwB,MAAA,CAAO,CAAA"}
|
|
File without changes
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { AsyncCache, AsyncMemoizable, AsyncMemoizeConfig } from "./memoize-async.types.js";
|
|
2
|
+
import { memoizeAsyncFn } from "./memoize-async.fn.js";
|
|
3
|
+
import { memoizeAsync } from "./memoize-async.js";
|
|
4
|
+
export { AsyncCache, AsyncMemoizable, AsyncMemoizeConfig, memoizeAsync, memoizeAsyncFn };
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { AsyncMemoizable, AsyncMemoizeConfig } from "./memoize-async.types.js";
|
|
2
|
+
|
|
3
|
+
//#region src/memoize-async/memoize-async.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Decorator that caches async method results based on their arguments.
|
|
6
|
+
* Prevents duplicate concurrent requests by returning the same promise
|
|
7
|
+
* for identical calls until the first one resolves.
|
|
8
|
+
*
|
|
9
|
+
* @overload
|
|
10
|
+
* @template T - The type of the class containing the decorated method
|
|
11
|
+
* @template D - The resolved type of the async method
|
|
12
|
+
* @returns {AsyncMemoizable<T, D>} The decorator function
|
|
13
|
+
*
|
|
14
|
+
* @overload
|
|
15
|
+
* @template T - The type of the class containing the decorated method
|
|
16
|
+
* @template D - The resolved type of the async method
|
|
17
|
+
* @param {AsyncMemoizeConfig<T, D>} config - Configuration for memoization
|
|
18
|
+
* @returns {AsyncMemoizable<T, D>} The decorator function
|
|
19
|
+
*
|
|
20
|
+
* @overload
|
|
21
|
+
* @template T - The type of the class containing the decorated method
|
|
22
|
+
* @template D - The resolved type of the async method
|
|
23
|
+
* @param {number} expirationTimeMs - Cache expiration time in milliseconds
|
|
24
|
+
* @returns {AsyncMemoizable<T, D>} The decorator function
|
|
25
|
+
*
|
|
26
|
+
* @throws {Error} When applied to a non-method property
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```typescript
|
|
30
|
+
* class DataService {
|
|
31
|
+
* // Basic usage - caches indefinitely
|
|
32
|
+
* @memoizeAsync()
|
|
33
|
+
* async fetchConfig(): Promise<Config> {
|
|
34
|
+
* return fetch('/api/config').then(r => r.json());
|
|
35
|
+
* }
|
|
36
|
+
*
|
|
37
|
+
* // With TTL
|
|
38
|
+
* @memoizeAsync(60000) // Cache for 60 seconds
|
|
39
|
+
* async getExchangeRates(): Promise<Rates> {
|
|
40
|
+
* return fetch('/api/rates').then(r => r.json());
|
|
41
|
+
* }
|
|
42
|
+
*
|
|
43
|
+
* // With custom cache and key resolver
|
|
44
|
+
* @memoizeAsync({
|
|
45
|
+
* cache: new RedisCache<string, Product>(),
|
|
46
|
+
* keyResolver: (productId, includeDetails) => `product-${productId}-${includeDetails}`,
|
|
47
|
+
* expirationTimeMs: 300000
|
|
48
|
+
* })
|
|
49
|
+
* async getProduct(productId: string, includeDetails: boolean): Promise<Product> {
|
|
50
|
+
* return this.fetchProduct(productId, includeDetails);
|
|
51
|
+
* }
|
|
52
|
+
* }
|
|
53
|
+
*
|
|
54
|
+
* const service = new DataService();
|
|
55
|
+
*
|
|
56
|
+
* // Concurrent calls with same args share the same promise
|
|
57
|
+
* const [product1, product2] = await Promise.all([
|
|
58
|
+
* service.getProduct('123', true),
|
|
59
|
+
* service.getProduct('123', true) // Same promise as above
|
|
60
|
+
* ]);
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
declare function memoizeAsync<T = any, D = any>(): AsyncMemoizable<T, D>;
|
|
64
|
+
declare function memoizeAsync<T = any, D = any>(config: AsyncMemoizeConfig<T, D>): AsyncMemoizable<T, D>;
|
|
65
|
+
declare function memoizeAsync<T = any, D = any>(expirationTimeMs: number): AsyncMemoizable<T, D>;
|
|
66
|
+
//#endregion
|
|
67
|
+
export { memoizeAsync };
|
|
68
|
+
//# sourceMappingURL=memoize-async.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memoize-async.d.ts","names":[],"sources":["../../src/memoize-async/memoize-async.ts"],"mappings":";;;;;AAyGA;;;;;;;;;;;;;;;;;;;;;;AAGA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAJgB,YAAA,kBAAA,CAAA,GAAkC,eAAA,CAAgB,CAAA,EAAG,CAAA;AAAA,iBACrD,YAAA,kBAAA,CACd,MAAA,EAAQ,kBAAA,CAAmB,CAAA,EAAG,CAAA,IAC7B,eAAA,CAAgB,CAAA,EAAG,CAAA;AAAA,iBACN,YAAA,kBAAA,CAA+B,gBAAA,WAA2B,eAAA,CAAgB,CAAA,EAAG,CAAA"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { AsyncMethod } from "../types.js";
|
|
2
|
+
import { AsyncMemoizeConfig } from "./memoize-async.types.js";
|
|
3
|
+
|
|
4
|
+
//#region src/memoize-async/memoize-async.fn.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Wraps an async method to cache its results and deduplicate concurrent calls.
|
|
7
|
+
*
|
|
8
|
+
* @overload
|
|
9
|
+
* @template D - The resolved type of the async method
|
|
10
|
+
* @template A - The argument types of the original method
|
|
11
|
+
* @param {AsyncMethod<D, A>} originalMethod - The async method to memoize
|
|
12
|
+
* @returns {AsyncMethod<D, A>} The memoized method
|
|
13
|
+
*
|
|
14
|
+
* @overload
|
|
15
|
+
* @template D - The resolved type of the async method
|
|
16
|
+
* @template A - The argument types of the original method
|
|
17
|
+
* @param {AsyncMethod<D, A>} originalMethod - The async method to memoize
|
|
18
|
+
* @param {AsyncMemoizeConfig<any, D>} config - Configuration for memoization
|
|
19
|
+
* @returns {AsyncMethod<D, A>} The memoized method
|
|
20
|
+
*
|
|
21
|
+
* @overload
|
|
22
|
+
* @template D - The resolved type of the async method
|
|
23
|
+
* @template A - The argument types of the original method
|
|
24
|
+
* @param {AsyncMethod<D, A>} originalMethod - The async method to memoize
|
|
25
|
+
* @param {number} expirationTimeMs - Cache expiration time in milliseconds
|
|
26
|
+
* @returns {AsyncMethod<D, A>} The memoized method
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```typescript
|
|
30
|
+
* class ApiClient {
|
|
31
|
+
* async fetchData(endpoint: string): Promise<Data> {
|
|
32
|
+
* const response = await fetch(endpoint);
|
|
33
|
+
* return response.json();
|
|
34
|
+
* }
|
|
35
|
+
* }
|
|
36
|
+
*
|
|
37
|
+
* const client = new ApiClient();
|
|
38
|
+
*
|
|
39
|
+
* // Basic memoization
|
|
40
|
+
* const memoized = memoizeAsyncFn(client.fetchData.bind(client));
|
|
41
|
+
*
|
|
42
|
+
* // Concurrent calls share the same promise
|
|
43
|
+
* const promise1 = memoized('/api/data');
|
|
44
|
+
* const promise2 = memoized('/api/data'); // Same promise as above
|
|
45
|
+
* const [data1, data2] = await Promise.all([promise1, promise2]);
|
|
46
|
+
*
|
|
47
|
+
* // With TTL
|
|
48
|
+
* const withTTL = memoizeAsyncFn(
|
|
49
|
+
* client.fetchData.bind(client),
|
|
50
|
+
* 60000 // Cache for 60 seconds
|
|
51
|
+
* );
|
|
52
|
+
*
|
|
53
|
+
* // With custom config
|
|
54
|
+
* const withConfig = memoizeAsyncFn(
|
|
55
|
+
* client.fetchData.bind(client),
|
|
56
|
+
* {
|
|
57
|
+
* cache: new Map(),
|
|
58
|
+
* keyResolver: (endpoint) => endpoint,
|
|
59
|
+
* expirationTimeMs: 300000
|
|
60
|
+
* }
|
|
61
|
+
* );
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
declare function memoizeAsyncFn<D = any, A extends any[] = any[]>(originalMethod: AsyncMethod<D, A>): AsyncMethod<D, A>;
|
|
65
|
+
declare function memoizeAsyncFn<D = any, A extends any[] = any[]>(originalMethod: AsyncMethod<D, A>, config: AsyncMemoizeConfig<any, D>): AsyncMethod<D, A>;
|
|
66
|
+
declare function memoizeAsyncFn<D = any, A extends any[] = any[]>(originalMethod: AsyncMethod<D, A>, expirationTimeMs: number): AsyncMethod<D, A>;
|
|
67
|
+
//#endregion
|
|
68
|
+
export { memoizeAsyncFn };
|
|
69
|
+
//# sourceMappingURL=memoize-async.fn.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memoize-async.fn.d.ts","names":[],"sources":["../../src/memoize-async/memoize-async.fn.ts"],"mappings":";;;;;;;;;;;;;AAkFA;;;;;;;;;;;;;;;;;;;;;;;;;;;AAIA;;;;;;;;;;;;;;;;;;;;;;;iBAPgB,cAAA,kCAAA,CACd,cAAA,EAAgB,WAAA,CAAY,CAAA,EAAG,CAAA,IAC9B,WAAA,CAAY,CAAA,EAAG,CAAA;AAAA,iBACF,cAAA,kCAAA,CACd,cAAA,EAAgB,WAAA,CAAY,CAAA,EAAG,CAAA,GAC/B,MAAA,EAAQ,kBAAA,MAAwB,CAAA,IAC/B,WAAA,CAAY,CAAA,EAAG,CAAA;AAAA,iBACF,cAAA,kCAAA,CACd,cAAA,EAAgB,WAAA,CAAY,CAAA,EAAG,CAAA,GAC/B,gBAAA,WACC,WAAA,CAAY,CAAA,EAAG,CAAA"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { TaskExec, isNumber, isString } from "../_utils.js";
|
|
2
|
+
//#region src/memoize-async/memoize-async.fn.ts
|
|
3
|
+
/**
|
|
4
|
+
* Copyright 2026 ResQ
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this file except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*/
|
|
18
|
+
function memoizeAsyncFn(originalMethod, input) {
|
|
19
|
+
const defaultConfig = { cache: /* @__PURE__ */ new Map() };
|
|
20
|
+
const runner = new TaskExec();
|
|
21
|
+
const promCache = /* @__PURE__ */ new Map();
|
|
22
|
+
let resolvedConfig = { ...defaultConfig };
|
|
23
|
+
if (isNumber(input)) resolvedConfig.expirationTimeMs = input;
|
|
24
|
+
else resolvedConfig = {
|
|
25
|
+
...resolvedConfig,
|
|
26
|
+
...input
|
|
27
|
+
};
|
|
28
|
+
return async function(...args) {
|
|
29
|
+
const keyResolver = isString(resolvedConfig.keyResolver) ? this[resolvedConfig.keyResolver].bind(this) : resolvedConfig.keyResolver;
|
|
30
|
+
let key;
|
|
31
|
+
if (keyResolver) key = keyResolver(...args);
|
|
32
|
+
else key = JSON.stringify(args);
|
|
33
|
+
if (promCache.has(key)) return promCache.get(key);
|
|
34
|
+
const prom = (async () => {
|
|
35
|
+
if (await resolvedConfig.cache?.has(key) ?? false) return await resolvedConfig.cache?.get(key);
|
|
36
|
+
const data = await originalMethod.apply(this, args);
|
|
37
|
+
await resolvedConfig.cache?.set(key, data);
|
|
38
|
+
if (resolvedConfig.expirationTimeMs !== void 0) runner.exec(() => {
|
|
39
|
+
resolvedConfig.cache?.delete(key);
|
|
40
|
+
}, resolvedConfig.expirationTimeMs);
|
|
41
|
+
return data;
|
|
42
|
+
})().finally(() => {
|
|
43
|
+
promCache.delete(key);
|
|
44
|
+
});
|
|
45
|
+
promCache.set(key, prom);
|
|
46
|
+
return prom;
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
//#endregion
|
|
50
|
+
export { memoizeAsyncFn };
|
|
51
|
+
|
|
52
|
+
//# sourceMappingURL=memoize-async.fn.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memoize-async.fn.js","names":[],"sources":["../../src/memoize-async/memoize-async.fn.ts"],"sourcesContent":["/**\n * Copyright 2026 ResQ\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { isNumber, isString, TaskExec } from '../_utils.js';\nimport type { AsyncMethod } from '../types.js';\nimport type { AsyncMemoizeConfig } from './memoize-async.types.js';\n\n/**\n * Wraps an async method to cache its results and deduplicate concurrent calls.\n *\n * @overload\n * @template D - The resolved type of the async method\n * @template A - The argument types of the original method\n * @param {AsyncMethod<D, A>} originalMethod - The async method to memoize\n * @returns {AsyncMethod<D, A>} The memoized method\n *\n * @overload\n * @template D - The resolved type of the async method\n * @template A - The argument types of the original method\n * @param {AsyncMethod<D, A>} originalMethod - The async method to memoize\n * @param {AsyncMemoizeConfig<any, D>} config - Configuration for memoization\n * @returns {AsyncMethod<D, A>} The memoized method\n *\n * @overload\n * @template D - The resolved type of the async method\n * @template A - The argument types of the original method\n * @param {AsyncMethod<D, A>} originalMethod - The async method to memoize\n * @param {number} expirationTimeMs - Cache expiration time in milliseconds\n * @returns {AsyncMethod<D, A>} The memoized method\n *\n * @example\n * ```typescript\n * class ApiClient {\n * async fetchData(endpoint: string): Promise<Data> {\n * const response = await fetch(endpoint);\n * return response.json();\n * }\n * }\n *\n * const client = new ApiClient();\n *\n * // Basic memoization\n * const memoized = memoizeAsyncFn(client.fetchData.bind(client));\n *\n * // Concurrent calls share the same promise\n * const promise1 = memoized('/api/data');\n * const promise2 = memoized('/api/data'); // Same promise as above\n * const [data1, data2] = await Promise.all([promise1, promise2]);\n *\n * // With TTL\n * const withTTL = memoizeAsyncFn(\n * client.fetchData.bind(client),\n * 60000 // Cache for 60 seconds\n * );\n *\n * // With custom config\n * const withConfig = memoizeAsyncFn(\n * client.fetchData.bind(client),\n * {\n * cache: new Map(),\n * keyResolver: (endpoint) => endpoint,\n * expirationTimeMs: 300000\n * }\n * );\n * ```\n */\nexport function memoizeAsyncFn<D = any, A extends any[] = any[]>(\n originalMethod: AsyncMethod<D, A>,\n): AsyncMethod<D, A>;\nexport function memoizeAsyncFn<D = any, A extends any[] = any[]>(\n originalMethod: AsyncMethod<D, A>,\n config: AsyncMemoizeConfig<any, D>,\n): AsyncMethod<D, A>;\nexport function memoizeAsyncFn<D = any, A extends any[] = any[]>(\n originalMethod: AsyncMethod<D, A>,\n expirationTimeMs: number,\n): AsyncMethod<D, A>;\n\nexport function memoizeAsyncFn<D = any, A extends any[] = any[]>(\n originalMethod: AsyncMethod<D, A>,\n input?: AsyncMemoizeConfig<any, D> | number,\n): AsyncMethod<D, A> {\n const defaultConfig: AsyncMemoizeConfig<any, D> = {\n cache: new Map<string, D>(),\n };\n const runner = new TaskExec();\n const promCache = new Map<string, Promise<D>>();\n let resolvedConfig = {\n ...defaultConfig,\n } as AsyncMemoizeConfig<any, D>;\n\n if (isNumber(input)) {\n resolvedConfig.expirationTimeMs = input;\n } else {\n resolvedConfig = {\n ...resolvedConfig,\n ...input,\n };\n }\n\n return async function (this: any, ...args: A): Promise<D> {\n const keyResolver = isString(resolvedConfig.keyResolver)\n ? this[resolvedConfig.keyResolver].bind(this)\n : resolvedConfig.keyResolver;\n\n let key;\n\n if (keyResolver) {\n key = keyResolver(...args);\n } else {\n key = JSON.stringify(args);\n }\n\n if (promCache.has(key)) {\n return promCache.get(key) as Promise<D>;\n }\n\n const prom = (async (): Promise<D> => {\n const inCache = (await resolvedConfig.cache?.has(key)) ?? false;\n\n if (inCache) {\n return (await resolvedConfig.cache?.get(key)) as D;\n }\n\n const data = await originalMethod.apply(this, args);\n await resolvedConfig.cache?.set(key, data);\n\n if (resolvedConfig.expirationTimeMs !== undefined) {\n runner.exec(() => {\n resolvedConfig.cache?.delete(key);\n }, resolvedConfig.expirationTimeMs);\n }\n\n return data;\n })().finally(() => {\n promCache.delete(key);\n });\n\n promCache.set(key, prom);\n\n return prom;\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AA2FA,SAAgB,eACd,gBACA,OACmB;CACnB,MAAM,gBAA4C,EAChD,uBAAO,IAAI,KAAgB,EAC5B;CACD,MAAM,SAAS,IAAI,UAAU;CAC7B,MAAM,4BAAY,IAAI,KAAyB;CAC/C,IAAI,iBAAiB,EACnB,GAAG,eACJ;AAED,KAAI,SAAS,MAAM,CACjB,gBAAe,mBAAmB;KAElC,kBAAiB;EACf,GAAG;EACH,GAAG;EACJ;AAGH,QAAO,eAA2B,GAAG,MAAqB;EACxD,MAAM,cAAc,SAAS,eAAe,YAAY,GACpD,KAAK,eAAe,aAAa,KAAK,KAAK,GAC3C,eAAe;EAEnB,IAAI;AAEJ,MAAI,YACF,OAAM,YAAY,GAAG,KAAK;MAE1B,OAAM,KAAK,UAAU,KAAK;AAG5B,MAAI,UAAU,IAAI,IAAI,CACpB,QAAO,UAAU,IAAI,IAAI;EAG3B,MAAM,QAAQ,YAAwB;AAGpC,OAFiB,MAAM,eAAe,OAAO,IAAI,IAAI,IAAK,MAGxD,QAAQ,MAAM,eAAe,OAAO,IAAI,IAAI;GAG9C,MAAM,OAAO,MAAM,eAAe,MAAM,MAAM,KAAK;AACnD,SAAM,eAAe,OAAO,IAAI,KAAK,KAAK;AAE1C,OAAI,eAAe,qBAAqB,KAAA,EACtC,QAAO,WAAW;AAChB,mBAAe,OAAO,OAAO,IAAI;MAChC,eAAe,iBAAiB;AAGrC,UAAO;MACL,CAAC,cAAc;AACjB,aAAU,OAAO,IAAI;IACrB;AAEF,YAAU,IAAI,KAAK,KAAK;AAExB,SAAO"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { memoizeAsyncFn } from "./memoize-async.fn.js";
|
|
2
|
+
//#region src/memoize-async/memoize-async.ts
|
|
3
|
+
function memoizeAsync(input) {
|
|
4
|
+
return (target, propertyName, descriptor) => {
|
|
5
|
+
if (descriptor.value) {
|
|
6
|
+
descriptor.value = input === void 0 ? memoizeAsyncFn(descriptor.value) : memoizeAsyncFn(descriptor.value, input);
|
|
7
|
+
return descriptor;
|
|
8
|
+
}
|
|
9
|
+
throw new Error("@memoizeAsync is applicable only on a methods.");
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
//#endregion
|
|
13
|
+
export { memoizeAsync };
|
|
14
|
+
|
|
15
|
+
//# sourceMappingURL=memoize-async.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memoize-async.js","names":[],"sources":["../../src/memoize-async/memoize-async.ts"],"sourcesContent":["/**\n * Copyright 2026 ResQ\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * @fileoverview MemoizeAsync decorator - caches async method results based on arguments.\n * Similar to @memoize but designed for async methods with support for promise deduplication.\n *\n * @module @resq/typescript/decorators/memoize-async\n *\n * @example\n * ```typescript\n * class ApiService {\n * @memoizeAsync()\n * async fetchUser(userId: string): Promise<User> {\n * const response = await fetch(`/api/users/${userId}`);\n * return response.json();\n * }\n * }\n *\n * const api = new ApiService();\n * const user1 = await api.fetchUser('123'); // Fetches from API\n * const user2 = await api.fetchUser('123'); // Returns cached promise\n * ```\n *\n * @copyright Copyright (c) 2026 ResQ\n * @license MIT\n */\n\nimport type { AsyncMethod } from '../types.js';\nimport { memoizeAsyncFn } from './memoize-async.fn.js';\nimport type { AsyncMemoizable, AsyncMemoizeConfig } from './memoize-async.types.js';\n\n/**\n * Decorator that caches async method results based on their arguments.\n * Prevents duplicate concurrent requests by returning the same promise\n * for identical calls until the first one resolves.\n *\n * @overload\n * @template T - The type of the class containing the decorated method\n * @template D - The resolved type of the async method\n * @returns {AsyncMemoizable<T, D>} The decorator function\n *\n * @overload\n * @template T - The type of the class containing the decorated method\n * @template D - The resolved type of the async method\n * @param {AsyncMemoizeConfig<T, D>} config - Configuration for memoization\n * @returns {AsyncMemoizable<T, D>} The decorator function\n *\n * @overload\n * @template T - The type of the class containing the decorated method\n * @template D - The resolved type of the async method\n * @param {number} expirationTimeMs - Cache expiration time in milliseconds\n * @returns {AsyncMemoizable<T, D>} The decorator function\n *\n * @throws {Error} When applied to a non-method property\n *\n * @example\n * ```typescript\n * class DataService {\n * // Basic usage - caches indefinitely\n * @memoizeAsync()\n * async fetchConfig(): Promise<Config> {\n * return fetch('/api/config').then(r => r.json());\n * }\n *\n * // With TTL\n * @memoizeAsync(60000) // Cache for 60 seconds\n * async getExchangeRates(): Promise<Rates> {\n * return fetch('/api/rates').then(r => r.json());\n * }\n *\n * // With custom cache and key resolver\n * @memoizeAsync({\n * cache: new RedisCache<string, Product>(),\n * keyResolver: (productId, includeDetails) => `product-${productId}-${includeDetails}`,\n * expirationTimeMs: 300000\n * })\n * async getProduct(productId: string, includeDetails: boolean): Promise<Product> {\n * return this.fetchProduct(productId, includeDetails);\n * }\n * }\n *\n * const service = new DataService();\n *\n * // Concurrent calls with same args share the same promise\n * const [product1, product2] = await Promise.all([\n * service.getProduct('123', true),\n * service.getProduct('123', true) // Same promise as above\n * ]);\n * ```\n */\nexport function memoizeAsync<T = any, D = any>(): AsyncMemoizable<T, D>;\nexport function memoizeAsync<T = any, D = any>(\n config: AsyncMemoizeConfig<T, D>,\n): AsyncMemoizable<T, D>;\nexport function memoizeAsync<T = any, D = any>(expirationTimeMs: number): AsyncMemoizable<T, D>;\nexport function memoizeAsync<T = any, D = any>(\n input?: AsyncMemoizeConfig<T, D> | number,\n): AsyncMemoizable<T, D> {\n return (\n target: T,\n propertyName: keyof T,\n descriptor: TypedPropertyDescriptor<AsyncMethod<D>>,\n ): TypedPropertyDescriptor<AsyncMethod<D>> => {\n if (descriptor.value) {\n descriptor.value = input === undefined ? memoizeAsyncFn(descriptor.value) : memoizeAsyncFn(descriptor.value, input as any);\n\n return descriptor;\n }\n\n throw new Error('@memoizeAsync is applicable only on a methods.');\n };\n}\n"],"mappings":";;AA6GA,SAAgB,aACd,OACuB;AACvB,SACE,QACA,cACA,eAC4C;AAC5C,MAAI,WAAW,OAAO;AACpB,cAAW,QAAQ,UAAU,KAAA,IAAY,eAAe,WAAW,MAAM,GAAG,eAAe,WAAW,OAAO,MAAa;AAE1H,UAAO;;AAGT,QAAM,IAAI,MAAM,iDAAiD"}
|