@blumintinc/typescript-memoize 1.2.0 → 1.3.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/package.json CHANGED
@@ -1,65 +1,65 @@
1
- {
2
- "name": "@blumintinc/typescript-memoize",
3
- "version": "1.2.0",
4
- "description": "Memoize decorator for Typescript with deep equality",
5
- "main": "./dist/memoize-decorator.js",
6
- "module": "./dist/es2015/memoize-decorator.js",
7
- "typings": "./dist/memoize-decorator.d.ts",
8
- "scripts": {
9
- "test": "karma start test/karma.config.js --single-run",
10
- "prepublish": "npm run build && npm run build:es2015",
11
- "clean": "rm -rf ./dist",
12
- "build": "tsc",
13
- "build:es2015": "tsc --module es2015 --target es2015 --outDir dist/es2015",
14
- "tslint": "tslint --project .",
15
- "eslint": "npm run tslint"
16
- },
17
- "private": false,
18
- "publishConfig": {
19
- "access": "public"
20
- },
21
- "files": [
22
- "dist",
23
- "src"
24
- ],
25
- "repository": {
26
- "type": "git",
27
- "url": "git+https://github.com/BluMintInc/typescript-memoize.git"
28
- },
29
- "keywords": [
30
- "typescript",
31
- "memoize",
32
- "functional",
33
- "decorator"
34
- ],
35
- "author": "Joseph O'Connor",
36
- "license": "MIT",
37
- "bugs": {
38
- "url": "https://github.com/BluMintInc/typescript-memoize/issues"
39
- },
40
- "homepage": "https://github.com/BluMintInc/typescript-memoize#readme",
41
- "devDependencies": {
42
- "@types/jasmine": "3.6.2",
43
- "@types/node": "14.14.9",
44
- "awesome-typescript-loader": "5.2.1",
45
- "es6-shim": "0.35.6",
46
- "jasmine-core": "3.6.0",
47
- "karma": "6.3.16",
48
- "karma-chrome-launcher": "3.1.0",
49
- "karma-es6-shim": "1.0.0",
50
- "karma-jasmine": "4.0.1",
51
- "karma-jasmine-html-reporter": "1.5.4",
52
- "karma-junit-reporter": "2.0.1",
53
- "karma-sourcemap-loader": "0.3.8",
54
- "karma-spec-reporter": "0.0.32",
55
- "karma-threshold-reporter": "0.1.15",
56
- "karma-typescript": "5.2.0",
57
- "source-map-loader": "1.1.2",
58
- "tslib": "2.0.3",
59
- "tslint": "6.1.3",
60
- "typescript": "4.1.2"
61
- },
62
- "dependencies": {
63
- "fast-deep-equal": "^3.1.3"
64
- }
65
- }
1
+ {
2
+ "name": "@blumintinc/typescript-memoize",
3
+ "version": "1.3.0",
4
+ "description": "Memoize decorator for Typescript with deep equality",
5
+ "main": "./dist/memoize-decorator.js",
6
+ "module": "./dist/es2015/memoize-decorator.js",
7
+ "typings": "./dist/memoize-decorator.d.ts",
8
+ "scripts": {
9
+ "test": "karma start test/karma.config.js --single-run",
10
+ "prepublish": "npm run build && npm run build:es2015",
11
+ "clean": "rm -rf ./dist",
12
+ "build": "tsc",
13
+ "build:es2015": "tsc --module es2015 --target es2015 --outDir dist/es2015",
14
+ "tslint": "tslint --project .",
15
+ "eslint": "npm run tslint"
16
+ },
17
+ "private": false,
18
+ "publishConfig": {
19
+ "access": "public"
20
+ },
21
+ "files": [
22
+ "dist",
23
+ "src"
24
+ ],
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "git+https://github.com/BluMintInc/typescript-memoize.git"
28
+ },
29
+ "keywords": [
30
+ "typescript",
31
+ "memoize",
32
+ "functional",
33
+ "decorator"
34
+ ],
35
+ "author": "Joseph O'Connor",
36
+ "license": "MIT",
37
+ "bugs": {
38
+ "url": "https://github.com/BluMintInc/typescript-memoize/issues"
39
+ },
40
+ "homepage": "https://github.com/BluMintInc/typescript-memoize#readme",
41
+ "devDependencies": {
42
+ "@types/jasmine": "3.6.2",
43
+ "@types/node": "14.14.9",
44
+ "es6-shim": "0.35.6",
45
+ "jasmine-core": "3.6.0",
46
+ "karma": "6.3.16",
47
+ "karma-chrome-launcher": "3.1.0",
48
+ "karma-es6-shim": "1.0.0",
49
+ "karma-jasmine": "4.0.1",
50
+ "karma-jasmine-html-reporter": "1.5.4",
51
+ "karma-junit-reporter": "2.0.1",
52
+ "karma-sourcemap-loader": "0.3.8",
53
+ "karma-spec-reporter": "0.0.32",
54
+ "karma-threshold-reporter": "0.1.15",
55
+ "karma-typescript": "5.5.4",
56
+ "karma-typescript-es6-transform": "^5.5.4",
57
+ "source-map-loader": "1.1.2",
58
+ "tslib": "2.0.3",
59
+ "tslint": "6.1.3",
60
+ "typescript": "4.1.2"
61
+ },
62
+ "dependencies": {
63
+ "@blumintinc/fast-deep-equal": "^4.0.0"
64
+ }
65
+ }
@@ -1,250 +1,251 @@
1
- const equal = require('fast-deep-equal');
2
-
3
- interface MemoizeArgs {
4
- expiring?: number;
5
- hashFunction?: boolean | ((...args: any[]) => any);
6
- tags?: string[];
7
- useDeepEqual?: boolean;
8
- }
9
-
10
- export function Memoize(args?: MemoizeArgs | MemoizeArgs['hashFunction']) {
11
- let hashFunction: MemoizeArgs['hashFunction'];
12
- let duration: MemoizeArgs['expiring'];
13
- let tags: MemoizeArgs['tags'];
14
- let useDeepEqual: MemoizeArgs['useDeepEqual'] = true;
15
-
16
- if (typeof args === 'object') {
17
- hashFunction = args.hashFunction;
18
- duration = args.expiring;
19
- tags = args.tags;
20
- useDeepEqual = args.useDeepEqual ?? true;
21
- } else {
22
- hashFunction = args;
23
- }
24
-
25
- return (target: Object, propertyKey: string, descriptor: TypedPropertyDescriptor<any>) => {
26
- if (descriptor.value != null) {
27
- descriptor.value = getNewFunction(descriptor.value, hashFunction, duration, tags, useDeepEqual);
28
- } else if (descriptor.get != null) {
29
- descriptor.get = getNewFunction(descriptor.get, hashFunction, duration, tags, useDeepEqual);
30
- } else {
31
- throw 'Only put a Memoize() decorator on a method or get accessor.';
32
- }
33
- };
34
- }
35
-
36
- export function MemoizeExpiring(expiring: number, hashFunction?: MemoizeArgs['hashFunction']) {
37
- return Memoize({
38
- expiring,
39
- hashFunction
40
- });
41
- }
42
-
43
- const clearCacheTagsMap: Map<string, Map<any, any>[]> = new Map();
44
-
45
- export function clear (tags: string[]): number {
46
- const cleared: Set<Map<any, any>> = new Set();
47
- for (const tag of tags) {
48
- const maps = clearCacheTagsMap.get(tag);
49
- if (maps) {
50
- for (const mp of maps) {
51
- if (!cleared.has(mp)) {
52
- mp.clear();
53
- cleared.add(mp);
54
- }
55
- }
56
- }
57
- }
58
- return cleared.size;
59
- }
60
-
61
- // A wrapper around Map that uses deep equality for key comparison
62
- class DeepEqualMap<K, V> {
63
- private map = new Map<string, { key: K, value: V }>();
64
-
65
- has(key: K): boolean {
66
- const entries = Array.from(this.map.values());
67
- for (const entry of entries) {
68
- if (equal(entry.key, key)) {
69
- return true;
70
- }
71
- }
72
- return false;
73
- }
74
-
75
- get(key: K): V | undefined {
76
- const entries = Array.from(this.map.values());
77
- for (const entry of entries) {
78
- if (equal(entry.key, key)) {
79
- return entry.value;
80
- }
81
- }
82
- return undefined;
83
- }
84
-
85
- set(key: K, value: V): this {
86
- const entries = Array.from(this.map.entries());
87
- for (const [serializedKey, entry] of entries) {
88
- if (equal(entry.key, key)) {
89
- this.map.delete(serializedKey);
90
- break;
91
- }
92
- }
93
-
94
- const serializedKey = `${Date.now()}_${Math.random()}`;
95
- this.map.set(serializedKey, { key, value });
96
- return this;
97
- }
98
-
99
- clear(): void {
100
- this.map.clear();
101
- }
102
- }
103
-
104
- function getNewFunction(
105
- originalMethod: () => void,
106
- hashFunction?: MemoizeArgs['hashFunction'],
107
- duration: number = 0,
108
- tags?: MemoizeArgs['tags'],
109
- useDeepEqual: boolean = true
110
- ) {
111
- const propMapName = Symbol(`__memoized_map__`);
112
- const propDeepMapName = Symbol(`__memoized_deep_map__`);
113
-
114
- // The function returned here gets called instead of originalMethod.
115
- return function (...args: any[]) {
116
- let returnedValue: any;
117
-
118
- // Get or create appropriate map based on deep equality requirement
119
- if (useDeepEqual) {
120
- if (!this.hasOwnProperty(propDeepMapName)) {
121
- Object.defineProperty(this, propDeepMapName, {
122
- configurable: false,
123
- enumerable: false,
124
- writable: false,
125
- value: new DeepEqualMap<any, any>()
126
- });
127
- }
128
- let myMap: DeepEqualMap<any, any> = this[propDeepMapName];
129
-
130
- if (Array.isArray(tags)) {
131
- for (const tag of tags) {
132
- // Since DeepEqualMap doesn't match the Map interface exactly,
133
- // we wrap it in a Map for tag clearing purposes
134
- const mapWrapper = {
135
- clear: () => myMap.clear()
136
- } as any;
137
-
138
- if (clearCacheTagsMap.has(tag)) {
139
- clearCacheTagsMap.get(tag).push(mapWrapper);
140
- } else {
141
- clearCacheTagsMap.set(tag, [mapWrapper]);
142
- }
143
- }
144
- }
145
-
146
- let hashKey: any;
147
-
148
- // If true is passed as first parameter, will automatically use every argument
149
- if (hashFunction === true) {
150
- hashKey = args;
151
- } else if (hashFunction) {
152
- hashKey = hashFunction.apply(this, args);
153
- } else if (args.length > 0) {
154
- hashKey = args.length === 1 ? args[0] : args;
155
- } else {
156
- hashKey = this;
157
- }
158
-
159
- // Handle expiration
160
- const timestampKey = { __timestamp: true, key: hashKey };
161
- let isExpired: boolean = false;
162
-
163
- if (duration > 0) {
164
- if (!myMap.has(timestampKey)) {
165
- isExpired = true;
166
- } else {
167
- let timestamp = myMap.get(timestampKey);
168
- isExpired = (Date.now() - timestamp) > duration;
169
- }
170
- }
171
-
172
- if (myMap.has(hashKey) && !isExpired) {
173
- returnedValue = myMap.get(hashKey);
174
- } else {
175
- returnedValue = originalMethod.apply(this, args);
176
- myMap.set(hashKey, returnedValue);
177
- if (duration > 0) {
178
- myMap.set(timestampKey, Date.now());
179
- }
180
- }
181
- } else {
182
- // Original implementation with standard Map (shallow equality)
183
- if (!this.hasOwnProperty(propMapName)) {
184
- Object.defineProperty(this, propMapName, {
185
- configurable: false,
186
- enumerable: false,
187
- writable: false,
188
- value: new Map<any, any>()
189
- });
190
- }
191
- let myMap: Map<any, any> = this[propMapName];
192
-
193
- if (Array.isArray(tags)) {
194
- for (const tag of tags) {
195
- if (clearCacheTagsMap.has(tag)) {
196
- clearCacheTagsMap.get(tag).push(myMap);
197
- } else {
198
- clearCacheTagsMap.set(tag, [myMap]);
199
- }
200
- }
201
- }
202
-
203
- if (hashFunction || args.length > 0 || duration > 0) {
204
- let hashKey: any;
205
-
206
- // If true is passed as first parameter, will automatically use every argument, passed to string
207
- if (hashFunction === true) {
208
- hashKey = args.map(a => a.toString()).join('!');
209
- } else if (hashFunction) {
210
- hashKey = hashFunction.apply(this, args);
211
- } else {
212
- hashKey = args[0];
213
- }
214
-
215
- const timestampKey = `${hashKey}__timestamp`;
216
- let isExpired: boolean = false;
217
- if (duration > 0) {
218
- if (!myMap.has(timestampKey)) {
219
- // "Expired" since it was never called before
220
- isExpired = true;
221
- } else {
222
- let timestamp = myMap.get(timestampKey);
223
- isExpired = (Date.now() - timestamp) > duration;
224
- }
225
- }
226
-
227
- if (myMap.has(hashKey) && !isExpired) {
228
- returnedValue = myMap.get(hashKey);
229
- } else {
230
- returnedValue = originalMethod.apply(this, args);
231
- myMap.set(hashKey, returnedValue);
232
- if (duration > 0) {
233
- myMap.set(timestampKey, Date.now());
234
- }
235
- }
236
-
237
- } else {
238
- const hashKey = this;
239
- if (myMap.has(hashKey)) {
240
- returnedValue = myMap.get(hashKey);
241
- } else {
242
- returnedValue = originalMethod.apply(this, args);
243
- myMap.set(hashKey, returnedValue);
244
- }
245
- }
246
- }
247
-
248
- return returnedValue;
249
- };
250
- }
1
+ import equal from '@blumintinc/fast-deep-equal';
2
+
3
+ interface MemoizeArgs {
4
+ expiring?: number;
5
+ hashFunction?: boolean | ((...args: any[]) => any);
6
+ tags?: string[];
7
+ useDeepEqual?: boolean;
8
+ }
9
+
10
+
11
+ export function Memoize(args?: MemoizeArgs | MemoizeArgs['hashFunction']) {
12
+ let hashFunction: MemoizeArgs['hashFunction'];
13
+ let duration: MemoizeArgs['expiring'];
14
+ let tags: MemoizeArgs['tags'];
15
+ let useDeepEqual: MemoizeArgs['useDeepEqual'] = true;
16
+
17
+ if (typeof args === 'object') {
18
+ hashFunction = args.hashFunction;
19
+ duration = args.expiring;
20
+ tags = args.tags;
21
+ useDeepEqual = args.useDeepEqual ?? true;
22
+ } else {
23
+ hashFunction = args;
24
+ }
25
+
26
+ return (target: Object, propertyKey: string, descriptor: TypedPropertyDescriptor<any>) => {
27
+ if (descriptor.value != null) {
28
+ descriptor.value = getNewFunction(descriptor.value, hashFunction, duration, tags, useDeepEqual);
29
+ } else if (descriptor.get != null) {
30
+ descriptor.get = getNewFunction(descriptor.get, hashFunction, duration, tags, useDeepEqual);
31
+ } else {
32
+ throw 'Only put a Memoize() decorator on a method or get accessor.';
33
+ }
34
+ };
35
+ }
36
+
37
+ export function MemoizeExpiring(expiring: number, hashFunction?: MemoizeArgs['hashFunction']) {
38
+ return Memoize({
39
+ expiring,
40
+ hashFunction
41
+ });
42
+ }
43
+
44
+ const clearCacheTagsMap: Map<string, Map<any, any>[]> = new Map();
45
+
46
+ export function clear (tags: string[]): number {
47
+ const cleared: Set<Map<any, any>> = new Set();
48
+ for (const tag of tags) {
49
+ const maps = clearCacheTagsMap.get(tag);
50
+ if (maps) {
51
+ for (const mp of maps) {
52
+ if (!cleared.has(mp)) {
53
+ mp.clear();
54
+ cleared.add(mp);
55
+ }
56
+ }
57
+ }
58
+ }
59
+ return cleared.size;
60
+ }
61
+
62
+ // A wrapper around Map that uses deep equality for key comparison
63
+ class DeepEqualMap<K, V> {
64
+ private map = new Map<string, { key: K, value: V }>();
65
+
66
+ has(key: K): boolean {
67
+ const entries = Array.from(this.map.values());
68
+ for (const entry of entries) {
69
+ if (equal(entry.key, key)) {
70
+ return true;
71
+ }
72
+ }
73
+ return false;
74
+ }
75
+
76
+ get(key: K): V | undefined {
77
+ const entries = Array.from(this.map.values());
78
+ for (const entry of entries) {
79
+ if (equal(entry.key, key)) {
80
+ return entry.value;
81
+ }
82
+ }
83
+ return undefined;
84
+ }
85
+
86
+ set(key: K, value: V): this {
87
+ const entries = Array.from(this.map.entries());
88
+ for (const [serializedKey, entry] of entries) {
89
+ if (equal(entry.key, key)) {
90
+ this.map.delete(serializedKey);
91
+ break;
92
+ }
93
+ }
94
+
95
+ const serializedKey = `${Date.now()}_${Math.random()}`;
96
+ this.map.set(serializedKey, { key, value });
97
+ return this;
98
+ }
99
+
100
+ clear(): void {
101
+ this.map.clear();
102
+ }
103
+ }
104
+
105
+ function getNewFunction(
106
+ originalMethod: () => void,
107
+ hashFunction?: MemoizeArgs['hashFunction'],
108
+ duration: number = 0,
109
+ tags?: MemoizeArgs['tags'],
110
+ useDeepEqual: boolean = true
111
+ ) {
112
+ const propMapName = Symbol(`__memoized_map__`);
113
+ const propDeepMapName = Symbol(`__memoized_deep_map__`);
114
+
115
+ // The function returned here gets called instead of originalMethod.
116
+ return function (...args: any[]) {
117
+ let returnedValue: any;
118
+
119
+ // Get or create appropriate map based on deep equality requirement
120
+ if (useDeepEqual) {
121
+ if (!this.hasOwnProperty(propDeepMapName)) {
122
+ Object.defineProperty(this, propDeepMapName, {
123
+ configurable: false,
124
+ enumerable: false,
125
+ writable: false,
126
+ value: new DeepEqualMap<any, any>()
127
+ });
128
+ }
129
+ let myMap: DeepEqualMap<any, any> = this[propDeepMapName];
130
+
131
+ if (Array.isArray(tags)) {
132
+ for (const tag of tags) {
133
+ // Since DeepEqualMap doesn't match the Map interface exactly,
134
+ // we wrap it in a Map for tag clearing purposes
135
+ const mapWrapper = {
136
+ clear: () => myMap.clear()
137
+ } as any;
138
+
139
+ if (clearCacheTagsMap.has(tag)) {
140
+ clearCacheTagsMap.get(tag).push(mapWrapper);
141
+ } else {
142
+ clearCacheTagsMap.set(tag, [mapWrapper]);
143
+ }
144
+ }
145
+ }
146
+
147
+ let hashKey: any;
148
+
149
+ // If true is passed as first parameter, will automatically use every argument
150
+ if (hashFunction === true) {
151
+ hashKey = args;
152
+ } else if (hashFunction) {
153
+ hashKey = hashFunction.apply(this, args);
154
+ } else if (args.length > 0) {
155
+ hashKey = args.length === 1 ? args[0] : args;
156
+ } else {
157
+ hashKey = this;
158
+ }
159
+
160
+ // Handle expiration
161
+ const timestampKey = { __timestamp: true, key: hashKey };
162
+ let isExpired: boolean = false;
163
+
164
+ if (duration > 0) {
165
+ if (!myMap.has(timestampKey)) {
166
+ isExpired = true;
167
+ } else {
168
+ let timestamp = myMap.get(timestampKey);
169
+ isExpired = (Date.now() - timestamp) > duration;
170
+ }
171
+ }
172
+
173
+ if (myMap.has(hashKey) && !isExpired) {
174
+ returnedValue = myMap.get(hashKey);
175
+ } else {
176
+ returnedValue = originalMethod.apply(this, args);
177
+ myMap.set(hashKey, returnedValue);
178
+ if (duration > 0) {
179
+ myMap.set(timestampKey, Date.now());
180
+ }
181
+ }
182
+ } else {
183
+ // Original implementation with standard Map (shallow equality)
184
+ if (!this.hasOwnProperty(propMapName)) {
185
+ Object.defineProperty(this, propMapName, {
186
+ configurable: false,
187
+ enumerable: false,
188
+ writable: false,
189
+ value: new Map<any, any>()
190
+ });
191
+ }
192
+ let myMap: Map<any, any> = this[propMapName];
193
+
194
+ if (Array.isArray(tags)) {
195
+ for (const tag of tags) {
196
+ if (clearCacheTagsMap.has(tag)) {
197
+ clearCacheTagsMap.get(tag).push(myMap);
198
+ } else {
199
+ clearCacheTagsMap.set(tag, [myMap]);
200
+ }
201
+ }
202
+ }
203
+
204
+ if (hashFunction || args.length > 0 || duration > 0) {
205
+ let hashKey: any;
206
+
207
+ // If true is passed as first parameter, will automatically use every argument, passed to string
208
+ if (hashFunction === true) {
209
+ hashKey = args.map(a => a.toString()).join('!');
210
+ } else if (hashFunction) {
211
+ hashKey = hashFunction.apply(this, args);
212
+ } else {
213
+ hashKey = args[0];
214
+ }
215
+
216
+ const timestampKey = `${hashKey}__timestamp`;
217
+ let isExpired: boolean = false;
218
+ if (duration > 0) {
219
+ if (!myMap.has(timestampKey)) {
220
+ // "Expired" since it was never called before
221
+ isExpired = true;
222
+ } else {
223
+ let timestamp = myMap.get(timestampKey);
224
+ isExpired = (Date.now() - timestamp) > duration;
225
+ }
226
+ }
227
+
228
+ if (myMap.has(hashKey) && !isExpired) {
229
+ returnedValue = myMap.get(hashKey);
230
+ } else {
231
+ returnedValue = originalMethod.apply(this, args);
232
+ myMap.set(hashKey, returnedValue);
233
+ if (duration > 0) {
234
+ myMap.set(timestampKey, Date.now());
235
+ }
236
+ }
237
+
238
+ } else {
239
+ const hashKey = this;
240
+ if (myMap.has(hashKey)) {
241
+ returnedValue = myMap.get(hashKey);
242
+ } else {
243
+ returnedValue = originalMethod.apply(this, args);
244
+ myMap.set(hashKey, returnedValue);
245
+ }
246
+ }
247
+ }
248
+
249
+ return returnedValue;
250
+ };
251
+ }