@kirill.konshin/core 0.0.2 → 0.0.4
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/dist/cache.js +179 -101
- package/dist/cache.js.map +1 -1
- package/dist/errors.js +10 -13
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -11
- package/dist/mutex.d.ts +2 -2
- package/dist/mutex.d.ts.map +1 -1
- package/dist/mutex.js +21 -20
- package/dist/mutex.js.map +1 -1
- package/package.json +14 -4
- package/.ctirc +0 -20
- package/.turbo/turbo-build.log +0 -22
- package/.turbo/turbo-test.log +0 -37
- package/CHANGELOG.md +0 -8
- package/demo/worker-demo.ts +0 -73
- package/dist/index.js.map +0 -1
- package/src/cache.test.ts +0 -121
- package/src/cache.ts +0 -245
- package/src/errors.ts +0 -17
- package/src/index.ts +0 -3
- package/src/mutex.ts +0 -22
- package/tsconfig.json +0 -10
- package/turbo.json +0 -10
- package/vite.config.ts +0 -2
package/dist/cache.js
CHANGED
|
@@ -1,106 +1,184 @@
|
|
|
1
1
|
import ManyKeysMap from "many-keys-map";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
//#region src/cache.ts
|
|
3
|
+
var shallowCompare = (prev, next) => Array.from(/* @__PURE__ */ new Set([...Object.keys(prev), ...Object.keys(next)])).every((key) => prev[key] === next[key]);
|
|
4
|
+
var equal = (prev, next) => prev === next;
|
|
5
|
+
var UNUSED = Symbol("UNUSED");
|
|
6
|
+
var ANY = Symbol("*");
|
|
6
7
|
function createTracker(whenDifferent, shallow = false) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
return res;
|
|
16
|
-
};
|
|
8
|
+
let lastDependency = UNUSED;
|
|
9
|
+
const comparator = shallow ? shallowCompare : equal;
|
|
10
|
+
return function tracker(dependency) {
|
|
11
|
+
if (lastDependency !== UNUSED && comparator(lastDependency, dependency)) return false;
|
|
12
|
+
const res = whenDifferent(dependency, lastDependency === UNUSED ? void 0 : lastDependency) || true;
|
|
13
|
+
lastDependency = dependency;
|
|
14
|
+
return res;
|
|
15
|
+
};
|
|
17
16
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
17
|
+
/**
|
|
18
|
+
* https://github.com/futpib/deep-weak-map
|
|
19
|
+
* https://github.com/fregante/many-keys-map
|
|
20
|
+
* https://github.com/fregante/many-keys-weakmap
|
|
21
|
+
* https://github.com/sindresorhus/memoize?tab=readme-ov-file#example-multiple-non-serializable-arguments
|
|
22
|
+
*
|
|
23
|
+
* ```ts
|
|
24
|
+
* const memoized = memo(
|
|
25
|
+
* (file, options, ...args) => { ... },
|
|
26
|
+
* {
|
|
27
|
+
* key: (file, options, ...args) => [file, JSON.stringify(options), ...args],
|
|
28
|
+
* invalidate: (bitmap) => !bitmap.width,
|
|
29
|
+
* dispose: (bitmap) => bitmap.close(),
|
|
30
|
+
* }
|
|
31
|
+
* );
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
function memo(fn, { key = (...k) => k, invalidate, dispose } = {}) {
|
|
35
|
+
const map = new ManyKeysMap();
|
|
36
|
+
const clear = (...condition) => {
|
|
37
|
+
if (!condition?.length) {
|
|
38
|
+
map.forEach((value, serializedKey) => {
|
|
39
|
+
dispose?.(value, ...serializedKey);
|
|
40
|
+
});
|
|
41
|
+
map.clear();
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const keyToClear = key(...condition);
|
|
45
|
+
map.forEach((value, serializedKey) => {
|
|
46
|
+
if (!serializedKey.every((kk, i) => keyToClear[i] === ANY || kk === keyToClear[i])) return;
|
|
47
|
+
dispose?.(value, ...serializedKey);
|
|
48
|
+
map.delete(serializedKey);
|
|
49
|
+
});
|
|
50
|
+
};
|
|
51
|
+
const size = () => map.size;
|
|
52
|
+
async function memoized(...args) {
|
|
53
|
+
const k = key(...args);
|
|
54
|
+
const has = map.has(k);
|
|
55
|
+
const old = map.get(k);
|
|
56
|
+
if (has && !invalidate?.(old, ...k)) return {
|
|
57
|
+
value: map.get(k),
|
|
58
|
+
hit: true
|
|
59
|
+
};
|
|
60
|
+
const value = await fn(...args);
|
|
61
|
+
map.set(k, value);
|
|
62
|
+
return {
|
|
63
|
+
hit: false,
|
|
64
|
+
value
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
memoized.clear = clear;
|
|
68
|
+
memoized.size = size;
|
|
69
|
+
return memoized;
|
|
54
70
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
71
|
+
/**
|
|
72
|
+
* Allows to memoize values by key and invalidate them based on the previous value.
|
|
73
|
+
*
|
|
74
|
+
* This makes possible to implement various one-off and subsequent transformations.
|
|
75
|
+
*
|
|
76
|
+
* 1. `write`: Transform value BEFORE writing to cache, called only once if cache IS NOT present or IS NOT valid
|
|
77
|
+
* 2. `read`: Transform value AFTER it's read from cache, always called if cache IS valid, keep in mind this transform is applied on top of BEFORE
|
|
78
|
+
*
|
|
79
|
+
* Both should return same type or null.
|
|
80
|
+
*
|
|
81
|
+
* If `newValue` is null, old is returned, and no cache is set.
|
|
82
|
+
*
|
|
83
|
+
* ```ts
|
|
84
|
+
* class InputCache extends TypedCache<string, ImageBitmap> {
|
|
85
|
+
* dispose(bitmap: ImageBitmap, key: string) {
|
|
86
|
+
* bitmap.close();
|
|
87
|
+
* }
|
|
88
|
+
* }
|
|
89
|
+
*
|
|
90
|
+
* const cache = new InputCache('input');
|
|
91
|
+
*
|
|
92
|
+
* // Cache provided OffscreenCanvas by name and create context once per canvas
|
|
93
|
+
*
|
|
94
|
+
* class CanvasCache extends TypedCache<string, OffscreenCanvas> {
|
|
95
|
+
* protected write(key: string, canvas: OffscreenCanvas): OffscreenCanvas {
|
|
96
|
+
* const canvas = newValue.getContext('2d');
|
|
97
|
+
* canvas.ctx = canvas;
|
|
98
|
+
* return canvas;
|
|
99
|
+
* }
|
|
100
|
+
* }
|
|
101
|
+
*
|
|
102
|
+
* // Transfer control to offscreen canvas and track removal of original
|
|
103
|
+
* // When called again, always return null, because canvas is handed to worker, and never returned to main
|
|
104
|
+
*
|
|
105
|
+
* class CanvasCache extends TypedCache<HTMLCanvasElement, OffscreenCanvas> {
|
|
106
|
+
*
|
|
107
|
+
* protected write(canvas: HTMLCanvasElement): OffscreenCanvas {
|
|
108
|
+
* canvas.addEventListener(
|
|
109
|
+
* 'remove',
|
|
110
|
+
* function listener() {
|
|
111
|
+
* console.log('Removing canvas from cache', key);
|
|
112
|
+
* this.delete(canvas);
|
|
113
|
+
* key.removeEventListener('remove', listener);
|
|
114
|
+
* },
|
|
115
|
+
* );
|
|
116
|
+
* return canvas.transferControlToOffscreen();
|
|
117
|
+
* }
|
|
118
|
+
*
|
|
119
|
+
* protected read() {
|
|
120
|
+
* return null;
|
|
121
|
+
* }
|
|
122
|
+
* }
|
|
123
|
+
*
|
|
124
|
+
* // Create bitmap from file and check if was used, return null if used
|
|
125
|
+
* // Since bitmaps are also cached in worker, used bitmaps are not sent
|
|
126
|
+
*
|
|
127
|
+
* class BitmapCache extends TypedCache<File, ImageBitmap, File> {
|
|
128
|
+
* protected async write(file: File): Promise<ImageBitmap> {
|
|
129
|
+
* return await createImageBitmap(file);
|
|
130
|
+
* }
|
|
131
|
+
*
|
|
132
|
+
* protected read(bitmap: ImageBitmap): Promise<ImageBitmap> | ImageBitmap {
|
|
133
|
+
* if (!bitmap.width) return null;
|
|
134
|
+
* return bitmap;
|
|
135
|
+
* }
|
|
136
|
+
* }
|
|
137
|
+
*
|
|
138
|
+
* ```
|
|
139
|
+
*/
|
|
140
|
+
var TransformerMap = class extends Map {
|
|
141
|
+
name;
|
|
142
|
+
constructor(name) {
|
|
143
|
+
super();
|
|
144
|
+
this.name = name;
|
|
145
|
+
}
|
|
146
|
+
write(key, newValue, oldValue) {
|
|
147
|
+
return newValue;
|
|
148
|
+
}
|
|
149
|
+
read(oldValue, key) {
|
|
150
|
+
return oldValue;
|
|
151
|
+
}
|
|
152
|
+
delete(key) {
|
|
153
|
+
const val = this.get(key);
|
|
154
|
+
const has = typeof val !== "undefined";
|
|
155
|
+
if (has) this.dispose(val, key);
|
|
156
|
+
super.delete(key);
|
|
157
|
+
return has;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Do something with the value and key before removing it from cache
|
|
161
|
+
* For example, close a file or a bitmap
|
|
162
|
+
*/
|
|
163
|
+
dispose(value, key) {}
|
|
164
|
+
clear() {
|
|
165
|
+
console.log("CLEAR cache", this.name);
|
|
166
|
+
this.forEach(this.dispose);
|
|
167
|
+
super.clear();
|
|
168
|
+
}
|
|
169
|
+
set(key, value) {
|
|
170
|
+
throw new Error("Use memo() method instead");
|
|
171
|
+
}
|
|
172
|
+
async memo(key, newValue) {
|
|
173
|
+
const oldValue = this.get(key);
|
|
174
|
+
if (!!oldValue && (!newValue || newValue === oldValue)) return this.read?.(oldValue, key);
|
|
175
|
+
if (this.has(key)) this.delete(key);
|
|
176
|
+
const value = await this.write(key, newValue, oldValue);
|
|
177
|
+
super.set(key, value);
|
|
178
|
+
return value;
|
|
179
|
+
}
|
|
105
180
|
};
|
|
106
|
-
//#
|
|
181
|
+
//#endregion
|
|
182
|
+
export { ANY, TransformerMap, createTracker, equal, memo, shallowCompare };
|
|
183
|
+
|
|
184
|
+
//# sourceMappingURL=cache.js.map
|
package/dist/cache.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache.js","sources":["../src/cache.ts"],"sourcesContent":["import ManyKeysMap from 'many-keys-map';\n\nexport type MaybePromise<T> = T | Promise<T>;\n\nexport const shallowCompare = (prev: any, next: any): boolean =>\n Array.from(new Set([...Object.keys(prev), ...Object.keys(next)])).every((key) => prev[key] === next[key]);\n\nexport const equal = (prev: any, next: any): boolean => prev === next;\n\nconst UNUSED = Symbol('UNUSED');\nexport const ANY: unique symbol = Symbol('*');\n\nexport function createTracker<Dep = any>(whenDifferent: (next: Dep, prev?: Dep) => any, shallow = false) {\n let lastDependency: Dep = UNUSED as any;\n\n const comparator = shallow ? shallowCompare : equal;\n\n return function tracker(dependency: Dep): Dep | false {\n // console.log('Tracker', lastDependency, dependency);\n\n if (lastDependency !== UNUSED && comparator(lastDependency, dependency)) {\n // console.log('RETAINED cache', { lastDependency, dependency });\n return false;\n }\n\n // console.log('INVALIDATED cache', {lastDependency, dependency});\n\n const res = whenDifferent(dependency, lastDependency === UNUSED ? undefined : lastDependency) || true;\n\n lastDependency = dependency;\n\n return res;\n };\n}\n\n/**\n * https://github.com/futpib/deep-weak-map\n * https://github.com/fregante/many-keys-map\n * https://github.com/fregante/many-keys-weakmap\n * https://github.com/sindresorhus/memoize?tab=readme-ov-file#example-multiple-non-serializable-arguments\n *\n * ```ts\n * const memoized = memo(\n * (file, options, ...args) => { ... },\n * {\n * key: (file, options, ...args) => [file, JSON.stringify(options), ...args],\n * invalidate: (bitmap) => !bitmap.width,\n * dispose: (bitmap) => bitmap.close(),\n * }\n * );\n * ```\n */\nexport function memo<Key extends any[], Val, SerializedKey extends any[] = Key>(\n fn: (...args: Key) => MaybePromise<Val>,\n {\n key = (...k: Key) => k as never as SerializedKey,\n invalidate,\n dispose,\n }: {\n key?: (...key: Key) => SerializedKey;\n invalidate?: (prev: Val, ...key: SerializedKey) => boolean;\n dispose?: (prev: Val, ...key: SerializedKey) => void;\n } = {},\n): {\n (...args: Key): Promise<{\n value?: Val;\n hit: boolean;\n }>;\n clear: (...condition: Key | any[]) => void;\n size: () => number;\n} {\n const map = new ManyKeysMap<SerializedKey, Val>();\n\n //TODO Extend ManyKeysMap\n const clear = (...condition: Key | any[]) => {\n if (!condition?.length) {\n map.forEach((value, serializedKey) => {\n dispose?.(value, ...serializedKey);\n });\n map.clear();\n return;\n }\n\n const keyToClear = key(...(condition as any));\n\n map.forEach((value, serializedKey) => {\n if (!serializedKey.every((kk, i) => keyToClear[i] === ANY || kk === keyToClear[i])) return;\n dispose?.(value, ...serializedKey);\n map.delete(serializedKey);\n });\n };\n\n const size = () => map.size;\n\n async function memoized(...args: Key) {\n const k = key(...args);\n\n const has = map.has(k);\n const old = map.get(k);\n\n if (has && !invalidate?.(old as never as Val, ...k)) {\n return { value: map.get(k), hit: true };\n }\n\n const value = await fn(...args);\n\n map.set(k, value);\n\n return { hit: false, value };\n }\n\n memoized.clear = clear;\n memoized.size = size;\n\n //TODO return mimic-function(memorized, fn);\n\n return memoized;\n}\n\n/**\n * Allows to memoize values by key and invalidate them based on the previous value.\n *\n * This makes possible to implement various one-off and subsequent transformations.\n *\n * 1. `write`: Transform value BEFORE writing to cache, called only once if cache IS NOT present or IS NOT valid\n * 2. `read`: Transform value AFTER it's read from cache, always called if cache IS valid, keep in mind this transform is applied on top of BEFORE\n *\n * Both should return same type or null.\n *\n * If `newValue` is null, old is returned, and no cache is set.\n *\n * ```ts\n * class InputCache extends TypedCache<string, ImageBitmap> {\n * dispose(bitmap: ImageBitmap, key: string) {\n * bitmap.close();\n * }\n * }\n *\n * const cache = new InputCache('input');\n *\n * // Cache provided OffscreenCanvas by name and create context once per canvas\n *\n * class CanvasCache extends TypedCache<string, OffscreenCanvas> {\n * protected write(key: string, canvas: OffscreenCanvas): OffscreenCanvas {\n * const canvas = newValue.getContext('2d');\n * canvas.ctx = canvas;\n * return canvas;\n * }\n * }\n *\n * // Transfer control to offscreen canvas and track removal of original\n * // When called again, always return null, because canvas is handed to worker, and never returned to main\n *\n * class CanvasCache extends TypedCache<HTMLCanvasElement, OffscreenCanvas> {\n *\n * protected write(canvas: HTMLCanvasElement): OffscreenCanvas {\n * canvas.addEventListener(\n * 'remove',\n * function listener() {\n * console.log('Removing canvas from cache', key);\n * this.delete(canvas);\n * key.removeEventListener('remove', listener);\n * },\n * );\n * return canvas.transferControlToOffscreen();\n * }\n *\n * protected read() {\n * return null;\n * }\n * }\n *\n * // Create bitmap from file and check if was used, return null if used\n * // Since bitmaps are also cached in worker, used bitmaps are not sent\n *\n * class BitmapCache extends TypedCache<File, ImageBitmap, File> {\n * protected async write(file: File): Promise<ImageBitmap> {\n * return await createImageBitmap(file);\n * }\n *\n * protected read(bitmap: ImageBitmap): Promise<ImageBitmap> | ImageBitmap {\n * if (!bitmap.width) return null;\n * return bitmap;\n * }\n * }\n *\n * ```\n */\nexport abstract class TransformerMap<Key, Val = Key> extends Map<Key, Val> {\n constructor(protected readonly name: string) {\n // console.warn('CREATE cache', name);\n super();\n }\n\n protected write(key: Key, newValue?: Val, oldValue?: Val): MaybePromise<Val> {\n return newValue as Val;\n }\n\n protected read(oldValue: Val, key: Key): MaybePromise<Val> {\n return oldValue;\n }\n\n delete(key: Key): boolean {\n const val = this.get(key);\n const has = typeof val !== 'undefined';\n if (has) this.dispose(val, key);\n super.delete(key);\n return has;\n }\n\n /**\n * Do something with the value and key before removing it from cache\n * For example, close a file or a bitmap\n */\n protected dispose(value: Val, key: Key): void {}\n\n clear(): void {\n console.log('CLEAR cache', this.name);\n this.forEach(this.dispose);\n super.clear();\n }\n\n set(key: Key, value: Val): this {\n throw new Error('Use memo() method instead');\n }\n\n async memo(key: Key, newValue?: Val): Promise<Val> {\n const oldValue = this.get(key);\n\n if (!!oldValue && (!newValue || newValue === oldValue)) {\n // console.log('FROM cache', this.name, key, 'old value', oldValue);\n return this.read?.(oldValue, key);\n }\n\n if (this.has(key)) this.delete(key);\n\n const value = await this.write(key, newValue, oldValue);\n\n super.set(key, value);\n\n // console.log('INVALIDATED KEY cache', this.name, key, 'has', has, 'invalidate', invalidate, 'new value', value);\n\n return value;\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cache.js","names":[],"sources":["../src/cache.ts"],"sourcesContent":["import ManyKeysMap from 'many-keys-map';\n\nexport type MaybePromise<T> = T | Promise<T>;\n\nexport const shallowCompare = (prev: any, next: any): boolean =>\n Array.from(new Set([...Object.keys(prev), ...Object.keys(next)])).every((key) => prev[key] === next[key]);\n\nexport const equal = (prev: any, next: any): boolean => prev === next;\n\nconst UNUSED = Symbol('UNUSED');\nexport const ANY: unique symbol = Symbol('*');\n\nexport function createTracker<Dep = any>(whenDifferent: (next: Dep, prev?: Dep) => any, shallow = false) {\n let lastDependency: Dep = UNUSED as any;\n\n const comparator = shallow ? shallowCompare : equal;\n\n return function tracker(dependency: Dep): Dep | false {\n // console.log('Tracker', lastDependency, dependency);\n\n if (lastDependency !== UNUSED && comparator(lastDependency, dependency)) {\n // console.log('RETAINED cache', { lastDependency, dependency });\n return false;\n }\n\n // console.log('INVALIDATED cache', {lastDependency, dependency});\n\n const res = whenDifferent(dependency, lastDependency === UNUSED ? undefined : lastDependency) || true;\n\n lastDependency = dependency;\n\n return res;\n };\n}\n\n/**\n * https://github.com/futpib/deep-weak-map\n * https://github.com/fregante/many-keys-map\n * https://github.com/fregante/many-keys-weakmap\n * https://github.com/sindresorhus/memoize?tab=readme-ov-file#example-multiple-non-serializable-arguments\n *\n * ```ts\n * const memoized = memo(\n * (file, options, ...args) => { ... },\n * {\n * key: (file, options, ...args) => [file, JSON.stringify(options), ...args],\n * invalidate: (bitmap) => !bitmap.width,\n * dispose: (bitmap) => bitmap.close(),\n * }\n * );\n * ```\n */\nexport function memo<Key extends any[], Val, SerializedKey extends any[] = Key>(\n fn: (...args: Key) => MaybePromise<Val>,\n {\n key = (...k: Key) => k as never as SerializedKey,\n invalidate,\n dispose,\n }: {\n key?: (...key: Key) => SerializedKey;\n invalidate?: (prev: Val, ...key: SerializedKey) => boolean;\n dispose?: (prev: Val, ...key: SerializedKey) => void;\n } = {},\n): {\n (...args: Key): Promise<{\n value?: Val;\n hit: boolean;\n }>;\n clear: (...condition: Key | any[]) => void;\n size: () => number;\n} {\n const map = new ManyKeysMap<SerializedKey, Val>();\n\n //TODO Extend ManyKeysMap\n const clear = (...condition: Key | any[]) => {\n if (!condition?.length) {\n map.forEach((value, serializedKey) => {\n dispose?.(value, ...serializedKey);\n });\n map.clear();\n return;\n }\n\n const keyToClear = key(...(condition as any));\n\n map.forEach((value, serializedKey) => {\n if (!serializedKey.every((kk, i) => keyToClear[i] === ANY || kk === keyToClear[i])) return;\n dispose?.(value, ...serializedKey);\n map.delete(serializedKey);\n });\n };\n\n const size = () => map.size;\n\n async function memoized(...args: Key) {\n const k = key(...args);\n\n const has = map.has(k);\n const old = map.get(k);\n\n if (has && !invalidate?.(old as never as Val, ...k)) {\n return { value: map.get(k), hit: true };\n }\n\n const value = await fn(...args);\n\n map.set(k, value);\n\n return { hit: false, value };\n }\n\n memoized.clear = clear;\n memoized.size = size;\n\n //TODO return mimic-function(memorized, fn);\n\n return memoized;\n}\n\n/**\n * Allows to memoize values by key and invalidate them based on the previous value.\n *\n * This makes possible to implement various one-off and subsequent transformations.\n *\n * 1. `write`: Transform value BEFORE writing to cache, called only once if cache IS NOT present or IS NOT valid\n * 2. `read`: Transform value AFTER it's read from cache, always called if cache IS valid, keep in mind this transform is applied on top of BEFORE\n *\n * Both should return same type or null.\n *\n * If `newValue` is null, old is returned, and no cache is set.\n *\n * ```ts\n * class InputCache extends TypedCache<string, ImageBitmap> {\n * dispose(bitmap: ImageBitmap, key: string) {\n * bitmap.close();\n * }\n * }\n *\n * const cache = new InputCache('input');\n *\n * // Cache provided OffscreenCanvas by name and create context once per canvas\n *\n * class CanvasCache extends TypedCache<string, OffscreenCanvas> {\n * protected write(key: string, canvas: OffscreenCanvas): OffscreenCanvas {\n * const canvas = newValue.getContext('2d');\n * canvas.ctx = canvas;\n * return canvas;\n * }\n * }\n *\n * // Transfer control to offscreen canvas and track removal of original\n * // When called again, always return null, because canvas is handed to worker, and never returned to main\n *\n * class CanvasCache extends TypedCache<HTMLCanvasElement, OffscreenCanvas> {\n *\n * protected write(canvas: HTMLCanvasElement): OffscreenCanvas {\n * canvas.addEventListener(\n * 'remove',\n * function listener() {\n * console.log('Removing canvas from cache', key);\n * this.delete(canvas);\n * key.removeEventListener('remove', listener);\n * },\n * );\n * return canvas.transferControlToOffscreen();\n * }\n *\n * protected read() {\n * return null;\n * }\n * }\n *\n * // Create bitmap from file and check if was used, return null if used\n * // Since bitmaps are also cached in worker, used bitmaps are not sent\n *\n * class BitmapCache extends TypedCache<File, ImageBitmap, File> {\n * protected async write(file: File): Promise<ImageBitmap> {\n * return await createImageBitmap(file);\n * }\n *\n * protected read(bitmap: ImageBitmap): Promise<ImageBitmap> | ImageBitmap {\n * if (!bitmap.width) return null;\n * return bitmap;\n * }\n * }\n *\n * ```\n */\nexport abstract class TransformerMap<Key, Val = Key> extends Map<Key, Val> {\n constructor(protected readonly name: string) {\n // console.warn('CREATE cache', name);\n super();\n }\n\n protected write(key: Key, newValue?: Val, oldValue?: Val): MaybePromise<Val> {\n return newValue as Val;\n }\n\n protected read(oldValue: Val, key: Key): MaybePromise<Val> {\n return oldValue;\n }\n\n delete(key: Key): boolean {\n const val = this.get(key);\n const has = typeof val !== 'undefined';\n if (has) this.dispose(val, key);\n super.delete(key);\n return has;\n }\n\n /**\n * Do something with the value and key before removing it from cache\n * For example, close a file or a bitmap\n */\n protected dispose(value: Val, key: Key): void {}\n\n clear(): void {\n console.log('CLEAR cache', this.name);\n this.forEach(this.dispose);\n super.clear();\n }\n\n set(key: Key, value: Val): this {\n throw new Error('Use memo() method instead');\n }\n\n async memo(key: Key, newValue?: Val): Promise<Val> {\n const oldValue = this.get(key);\n\n if (!!oldValue && (!newValue || newValue === oldValue)) {\n // console.log('FROM cache', this.name, key, 'old value', oldValue);\n return this.read?.(oldValue, key);\n }\n\n if (this.has(key)) this.delete(key);\n\n const value = await this.write(key, newValue, oldValue);\n\n super.set(key, value);\n\n // console.log('INVALIDATED KEY cache', this.name, key, 'has', has, 'invalidate', invalidate, 'new value', value);\n\n return value;\n }\n}\n"],"mappings":"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"}
|
package/dist/errors.js
CHANGED
|
@@ -1,15 +1,12 @@
|
|
|
1
|
+
//#region src/errors.ts
|
|
1
2
|
function catchErrors(promise, errors) {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
return [error];
|
|
8
|
-
}
|
|
9
|
-
throw error;
|
|
10
|
-
});
|
|
3
|
+
return promise.then((data) => [void 0, data]).catch((error) => {
|
|
4
|
+
if (!errors?.length) return [error];
|
|
5
|
+
if (errors.some((e) => e instanceof error)) return [error];
|
|
6
|
+
throw error;
|
|
7
|
+
});
|
|
11
8
|
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
//# sourceMappingURL=errors.js.map
|
|
9
|
+
//#endregion
|
|
10
|
+
export { catchErrors };
|
|
11
|
+
|
|
12
|
+
//# sourceMappingURL=errors.js.map
|
package/dist/errors.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.js","sources":["../src/errors.ts"],"sourcesContent":["// @see https://www.youtube.com/watch?v=AdmGHwvgaVs\nexport function catchErrors<T, E extends Error = Error>(\n promise: Promise<T>,\n errors?: E[],\n): Promise<any[] | (T | undefined)[]> {\n return promise\n .then((data) => [undefined, data])\n .catch((error) => {\n if (!errors?.length) {\n return [error];\n }\n if (errors.some((e) => e instanceof error)) {\n return [error];\n }\n throw error;\n });\n}\n"],"
|
|
1
|
+
{"version":3,"file":"errors.js","names":[],"sources":["../src/errors.ts"],"sourcesContent":["// @see https://www.youtube.com/watch?v=AdmGHwvgaVs\nexport function catchErrors<T, E extends Error = Error>(\n promise: Promise<T>,\n errors?: E[],\n): Promise<any[] | (T | undefined)[]> {\n return promise\n .then((data) => [undefined, data])\n .catch((error) => {\n if (!errors?.length) {\n return [error];\n }\n if (errors.some((e) => e instanceof error)) {\n return [error];\n }\n throw error;\n });\n}\n"],"mappings":";AACA,SAAgB,YACZ,SACA,QACkC;CAClC,OAAO,QACF,MAAM,SAAS,CAAC,KAAA,GAAW,IAAI,CAAC,CAAC,CACjC,OAAO,UAAU;EACd,IAAI,CAAC,QAAQ,QACT,OAAO,CAAC,KAAK;EAEjB,IAAI,OAAO,MAAM,MAAM,aAAa,KAAK,GACrC,OAAO,CAAC,KAAK;EAEjB,MAAM;CACV,CAAC;AACT"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export * from './cache';
|
|
2
|
-
export * from './errors';
|
|
3
|
-
export * from './mutex';
|
|
1
|
+
export * from './cache.js';
|
|
2
|
+
export * from './errors.js';
|
|
3
|
+
export * from './mutex.js';
|
|
4
4
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -1,14 +1,4 @@
|
|
|
1
1
|
import { ANY, TransformerMap, createTracker, equal, memo, shallowCompare } from "./cache.js";
|
|
2
2
|
import { catchErrors } from "./errors.js";
|
|
3
3
|
import { Mutex } from "./mutex.js";
|
|
4
|
-
export {
|
|
5
|
-
ANY,
|
|
6
|
-
Mutex,
|
|
7
|
-
TransformerMap,
|
|
8
|
-
catchErrors,
|
|
9
|
-
createTracker,
|
|
10
|
-
equal,
|
|
11
|
-
memo,
|
|
12
|
-
shallowCompare
|
|
13
|
-
};
|
|
14
|
-
//# sourceMappingURL=index.js.map
|
|
4
|
+
export { ANY, Mutex, TransformerMap, catchErrors, createTracker, equal, memo, shallowCompare };
|
package/dist/mutex.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export declare class Mutex {
|
|
2
2
|
private promise;
|
|
3
|
-
exec
|
|
4
|
-
wrap
|
|
3
|
+
exec<T extends (...args: any) => any>(fn: T): Promise<ReturnType<T>>;
|
|
4
|
+
wrap<T extends (...args: any) => any>(fn: T): () => Promise<ReturnType<T>>;
|
|
5
5
|
}
|
|
6
6
|
//# sourceMappingURL=mutex.d.ts.map
|
package/dist/mutex.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mutex.d.ts","sourceRoot":"","sources":["../src/mutex.ts"],"names":[],"mappings":"AAEA,qBAAa,KAAK;IACd,OAAO,CAAC,OAAO,CAAqB;IAE7B,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"mutex.d.ts","sourceRoot":"","sources":["../src/mutex.ts"],"names":[],"mappings":"AAEA,qBAAa,KAAK;IACd,OAAO,CAAC,OAAO,CAAqB;IAE7B,IAAI,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,GAAG,KAAK,GAAG,EAAE,EAAE,EAAE,CAAC,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;IAcpE,IAAI,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,GAAG,KAAK,GAAG,EAAE,EAAE,EAAE,CAAC,GAAG,MAAM,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;CAGpF"}
|
package/dist/mutex.js
CHANGED
|
@@ -1,21 +1,22 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
18
|
-
export {
|
|
19
|
-
Mutex
|
|
1
|
+
//#region src/mutex.ts
|
|
2
|
+
var Mutex = class {
|
|
3
|
+
promise = Promise.resolve();
|
|
4
|
+
exec(fn) {
|
|
5
|
+
return new Promise((resolve, reject) => {
|
|
6
|
+
this.promise = this.promise.then(async () => {
|
|
7
|
+
try {
|
|
8
|
+
resolve(await fn());
|
|
9
|
+
} catch (e) {
|
|
10
|
+
reject(e);
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
wrap(fn) {
|
|
16
|
+
return (...args) => this.exec(() => fn(...args));
|
|
17
|
+
}
|
|
20
18
|
};
|
|
21
|
-
//#
|
|
19
|
+
//#endregion
|
|
20
|
+
export { Mutex };
|
|
21
|
+
|
|
22
|
+
//# sourceMappingURL=mutex.js.map
|
package/dist/mutex.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mutex.js","sources":["../src/mutex.ts"],"sourcesContent":["//TODO Implement queue with ranking & sorting\n//TODO https://github.com/sindresorhus/p-queue\nexport class Mutex {\n private promise = Promise.resolve();\n\n public exec
|
|
1
|
+
{"version":3,"file":"mutex.js","names":[],"sources":["../src/mutex.ts"],"sourcesContent":["//TODO Implement queue with ranking & sorting\n//TODO https://github.com/sindresorhus/p-queue\nexport class Mutex {\n private promise = Promise.resolve();\n\n public exec<T extends (...args: any) => any>(fn: T): Promise<ReturnType<T>> {\n return new Promise((resolve, reject) => {\n // wrap in always successful function\n // eslint-disable-next-line promise/prefer-await-to-then\n this.promise = this.promise.then(async () => {\n try {\n resolve(await fn());\n } catch (e) {\n reject(e);\n }\n });\n });\n }\n\n public wrap<T extends (...args: any) => any>(fn: T): () => Promise<ReturnType<T>> {\n return (...args: any) => this.exec(() => fn(...args));\n }\n}\n"],"mappings":";AAEA,IAAa,QAAb,MAAmB;CACf,UAAkB,QAAQ,QAAQ;CAElC,KAA6C,IAA+B;EACxE,OAAO,IAAI,SAAS,SAAS,WAAW;GAGpC,KAAK,UAAU,KAAK,QAAQ,KAAK,YAAY;IACzC,IAAI;KACA,QAAQ,MAAM,GAAG,CAAC;IACtB,SAAS,GAAG;KACR,OAAO,CAAC;IACZ;GACJ,CAAC;EACL,CAAC;CACL;CAEA,KAA6C,IAAqC;EAC9E,QAAQ,GAAG,SAAc,KAAK,WAAW,GAAG,GAAG,IAAI,CAAC;CACxD;AACJ"}
|
package/package.json
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kirill.konshin/core",
|
|
3
3
|
"description": "Utilities",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.4",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"----- BUILD -----": "",
|
|
8
8
|
"clean": "rm -rf dist .tscache tsconfig.tsbuildinfo",
|
|
9
9
|
"build": "vite build",
|
|
10
|
-
"build:index": "ctix build",
|
|
11
|
-
"build:check-types": "attw --pack .",
|
|
12
10
|
"start": "yarn build --watch",
|
|
13
11
|
"wait": "wait-on ./dist/index.js",
|
|
14
12
|
"----- TEST -----": "",
|
|
@@ -40,5 +38,17 @@
|
|
|
40
38
|
"main": "./dist/index.js",
|
|
41
39
|
"module": "./dist/index.js",
|
|
42
40
|
"types": "./dist/index.d.ts",
|
|
43
|
-
"
|
|
41
|
+
"repository": {
|
|
42
|
+
"type": "git",
|
|
43
|
+
"url": "https://github.com/kirill-konshin/utils.git",
|
|
44
|
+
"directory": "packages/core"
|
|
45
|
+
},
|
|
46
|
+
"nx": {
|
|
47
|
+
"tags": [
|
|
48
|
+
"platform:universal"
|
|
49
|
+
]
|
|
50
|
+
},
|
|
51
|
+
"files": [
|
|
52
|
+
"dist"
|
|
53
|
+
]
|
|
44
54
|
}
|
package/.ctirc
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"options": [
|
|
3
|
-
{
|
|
4
|
-
"mode": "create",
|
|
5
|
-
"project": "tsconfig.json",
|
|
6
|
-
"include": "src/**/*.{ts,tsx}",
|
|
7
|
-
"exclude": [
|
|
8
|
-
"**/*.stories.*",
|
|
9
|
-
"**/*.test.*",
|
|
10
|
-
"**/*.fixture.*"
|
|
11
|
-
],
|
|
12
|
-
"startFrom": "src",
|
|
13
|
-
"backup": false,
|
|
14
|
-
"overwrite": true,
|
|
15
|
-
"generationStyle": "default-alias-named-star",
|
|
16
|
-
"output": "src",
|
|
17
|
-
"verbose": true
|
|
18
|
-
}
|
|
19
|
-
]
|
|
20
|
-
}
|
package/.turbo/turbo-build.log
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
[36mvite v7.0.6 [32mbuilding SSR bundle for production...[36m[39m
|
|
2
|
-
- ctix 'create' mode start, ...
|
|
3
|
-
✔ /home/runner/work/utils/utils/packages/core/tsconfig.json loading complete!
|
|
4
|
-
✔ analysis export statements completed!
|
|
5
|
-
- build "index.ts" file start
|
|
6
|
-
- output file exists check, ...
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
✔ ctix 'create' mode complete!
|
|
10
|
-
transforming...
|
|
11
|
-
[32m✓[39m 4 modules transformed.
|
|
12
|
-
rendering chunks...
|
|
13
|
-
|
|
14
|
-
[vite:dts] Start generate declaration files...
|
|
15
|
-
[2mdist/[22m[36mindex.js [39m[1m[2m0.32 kB[22m[1m[22m[2m │ map: 0.09 kB[22m
|
|
16
|
-
[2mdist/[22m[36merrors.js [39m[1m[2m0.33 kB[22m[1m[22m[2m │ map: 0.90 kB[22m
|
|
17
|
-
[2mdist/[22m[36mmutex.js [39m[1m[2m0.40 kB[22m[1m[22m[2m │ map: 1.18 kB[22m
|
|
18
|
-
[2mdist/[22m[36mcache.js [39m[1m[2m2.92 kB[22m[1m[22m[2m │ map: 10.21 kB[22m
|
|
19
|
-
[vite:dts] Declaration files built in 1642ms.
|
|
20
|
-
|
|
21
|
-
[32m✓ built in 4.86s[39m
|
|
22
|
-
Updated package.json with exports
|
package/.turbo/turbo-test.log
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
- ctix 'create' mode start, ...
|
|
2
|
-
✔ /home/runner/work/utils/utils/packages/core/tsconfig.json loading complete!
|
|
3
|
-
✔ analysis export statements completed!
|
|
4
|
-
- build "index.ts" file start
|
|
5
|
-
- output file exists check, ...
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
✔ ctix 'create' mode complete!
|
|
9
|
-
|
|
10
|
-
[1m[46m RUN [49m[22m [36mv3.2.4 [39m[90m/home/runner/work/utils/utils/packages/core[39m
|
|
11
|
-
[2mCoverage enabled with [22m[33mv8[39m
|
|
12
|
-
|
|
13
|
-
[90mstdout[2m | src/cache.test.ts[2m > [22m[2mTransformerMap[2m > [22m[2mkey transform
|
|
14
|
-
[22m[39mCLEAR cache test
|
|
15
|
-
|
|
16
|
-
[32m✓[39m src/cache.test.ts [2m([22m[2m4 tests[22m[2m)[22m[32m 10[2mms[22m[39m
|
|
17
|
-
|
|
18
|
-
[2m Test Files [22m [1m[32m1 passed[39m[22m[90m (1)[39m
|
|
19
|
-
[2m Tests [22m [1m[32m4 passed[39m[22m[90m (4)[39m
|
|
20
|
-
[2m Start at [22m 03:27:08
|
|
21
|
-
[2m Duration [22m 465ms[2m (transform 54ms, setup 0ms, collect 47ms, tests 10ms, environment 0ms, prepare 83ms)[22m
|
|
22
|
-
|
|
23
|
-
[34m % [39m[2mCoverage report from [22m[33mv8[39m
|
|
24
|
-
-----------------|---------|----------|---------|---------|---------------------
|
|
25
|
-
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
|
|
26
|
-
-----------------|---------|----------|---------|---------|---------------------
|
|
27
|
-
All files | 42.85 | 84.84 | 55 | 42.85 |
|
|
28
|
-
demo | 0 | 0 | 0 | 0 |
|
|
29
|
-
worker-demo.ts | 0 | 0 | 0 | 0 | 1-73
|
|
30
|
-
src | 59.09 | 87.5 | 57.89 | 59.09 |
|
|
31
|
-
cache.ts | 81.25 | 89.65 | 56.25 | 81.25 | ...,200-201,224-225
|
|
32
|
-
errors.ts | 0 | 100 | 100 | 0 | 2-17
|
|
33
|
-
index.ts | 0 | 0 | 0 | 0 | 1-3
|
|
34
|
-
mutex.ts | 0 | 100 | 100 | 0 | 3-22
|
|
35
|
-
-----------------|---------|----------|---------|---------|---------------------
|
|
36
|
-
Updated package.json with exports
|
|
37
|
-
Updated package.json with exports
|
package/CHANGELOG.md
DELETED
package/demo/worker-demo.ts
DELETED
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import { RespondersBase, WorkerDialog } from '../src';
|
|
2
|
-
|
|
3
|
-
class Responders extends RespondersBase<Responders> {
|
|
4
|
-
encode = this.create(
|
|
5
|
-
'encode',
|
|
6
|
-
async (
|
|
7
|
-
{ file, abort = false }: { file: File; abort: boolean }, // should have all possible combinations for strong typing
|
|
8
|
-
encodeContext,
|
|
9
|
-
) => {
|
|
10
|
-
// send within same message
|
|
11
|
-
encodeContext.send({ progress: 0 }); // ✅ Valid
|
|
12
|
-
encodeContext.send({ junk: 'xxx' }); // ❌ Bad & should be red
|
|
13
|
-
|
|
14
|
-
// listen same message
|
|
15
|
-
encodeContext.listen(({ abort }) => {
|
|
16
|
-
if (abort) alert('Aborted'); // ✅ Valid
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
// send same message as received
|
|
20
|
-
encodeContext.send({ junk: 'xxx' }); // ❌ Bad & should be red
|
|
21
|
-
encodeContext.send({ progress: 0.5 }); // ✅ Valid
|
|
22
|
-
|
|
23
|
-
// send or listen different message
|
|
24
|
-
encodeContext.withMessage('decode').listen(() => {}); // ✅ Valid
|
|
25
|
-
encodeContext.withMessage('decode').send({ file: null }); // ✅ Valid
|
|
26
|
-
encodeContext.withMessage('decode').send({ junk: 'xxx' }); // ❌ Bad & should be red
|
|
27
|
-
encodeContext.withMessage('junk'); // ❌ Bad & should be red
|
|
28
|
-
|
|
29
|
-
return { bitmap: await createImageBitmap(file), progress: 1.0 }; // should have all possible combinations for strong typing
|
|
30
|
-
},
|
|
31
|
-
);
|
|
32
|
-
|
|
33
|
-
// another responder for demo purposes
|
|
34
|
-
decode = this.create('decode', async ({ bitmap }) => {
|
|
35
|
-
return { file: new File([], 'test') };
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export const workerDialog = new WorkerDialog(self, new Responders(), 'Worker');
|
|
40
|
-
|
|
41
|
-
/////
|
|
42
|
-
|
|
43
|
-
// import type { workerDialog } from './worker';
|
|
44
|
-
|
|
45
|
-
const mainDialog = new WorkerDialog(self, {} as typeof workerDialog.responders, 'Main');
|
|
46
|
-
|
|
47
|
-
mainDialog
|
|
48
|
-
.withMessage('encode')
|
|
49
|
-
.fetch(
|
|
50
|
-
{
|
|
51
|
-
file: new File([], 'test'), // ✅ Valid
|
|
52
|
-
junk: 'xxx', // ❌ Bad & should be red
|
|
53
|
-
},
|
|
54
|
-
(encodeContext) => {
|
|
55
|
-
// Listen same message, partial data update
|
|
56
|
-
encodeContext.listen((data) => {
|
|
57
|
-
console.log(data.progress); // ✅ Valid
|
|
58
|
-
console.log(data.junk); // ❌ Bad & should be red
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
// Listen or send other message
|
|
62
|
-
encodeContext.withMessage('decode').listen((data, decodeContext) => {});
|
|
63
|
-
encodeContext.withMessage('decode').send({ bitmap: null }); // ✅ Valid
|
|
64
|
-
encodeContext.withMessage('decode').send({ junk: 'xxx' }); // ❌❌❌ Bad & should be red, broken
|
|
65
|
-
|
|
66
|
-
// Send abort
|
|
67
|
-
encodeContext.send({ abort: true }); // ✅ Valid
|
|
68
|
-
},
|
|
69
|
-
)
|
|
70
|
-
.then((result) => {
|
|
71
|
-
console.log(result.bitmap); // ✅ Valid
|
|
72
|
-
console.log(result.junk); // ❌ Bad & should be red
|
|
73
|
-
});
|
package/dist/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;"}
|
package/src/cache.test.ts
DELETED
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import { expect, describe, test, vi } from 'vitest';
|
|
2
|
-
import { ANY, MaybePromise, memo, TransformerMap } from './cache';
|
|
3
|
-
|
|
4
|
-
describe('memo', async () => {
|
|
5
|
-
test('simple', async () => {
|
|
6
|
-
const random = vi.fn(() => Math.random());
|
|
7
|
-
const memoized = memo(random);
|
|
8
|
-
|
|
9
|
-
const first = await memoized();
|
|
10
|
-
const second = await memoized();
|
|
11
|
-
|
|
12
|
-
expect(first.hit).toBeFalsy();
|
|
13
|
-
expect(second.hit).toBeTruthy();
|
|
14
|
-
expect(first.value).toBe(second.value);
|
|
15
|
-
expect(memoized.size()).toBe(1);
|
|
16
|
-
|
|
17
|
-
memoized.clear();
|
|
18
|
-
expect(memoized.size()).toBe(0);
|
|
19
|
-
|
|
20
|
-
const third = await memoized();
|
|
21
|
-
expect(third.hit).toBeFalsy();
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
test('with key', async () => {
|
|
25
|
-
const random = vi.fn((arg) => arg + Math.random().toString());
|
|
26
|
-
const memoized = memo(random);
|
|
27
|
-
|
|
28
|
-
const first = await memoized(1);
|
|
29
|
-
const second = await memoized(2);
|
|
30
|
-
|
|
31
|
-
expect(first.hit).toBeFalsy();
|
|
32
|
-
expect(second.hit).toBeFalsy();
|
|
33
|
-
expect(first.value).not.toBe(second.value);
|
|
34
|
-
expect(memoized.size()).toBe(2);
|
|
35
|
-
|
|
36
|
-
const first2 = await memoized(1);
|
|
37
|
-
const second2 = await memoized(2);
|
|
38
|
-
|
|
39
|
-
expect(first2.hit).toBeTruthy();
|
|
40
|
-
expect(first.value).toBe(first2.value);
|
|
41
|
-
expect(second2.hit).toBeTruthy();
|
|
42
|
-
expect(second.value).toBe(second2.value);
|
|
43
|
-
|
|
44
|
-
memoized.clear(1);
|
|
45
|
-
expect(memoized.size()).toBe(1);
|
|
46
|
-
memoized.clear(ANY);
|
|
47
|
-
expect(memoized.size()).toBe(0);
|
|
48
|
-
});
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
describe('TransformerMap', async () => {
|
|
52
|
-
test('simple', async () => {
|
|
53
|
-
const dispose = vi.fn();
|
|
54
|
-
|
|
55
|
-
class Cache extends TransformerMap<number, any> {
|
|
56
|
-
write(key, value, oldValue) {
|
|
57
|
-
value.cached = true;
|
|
58
|
-
value.read = 0;
|
|
59
|
-
return value;
|
|
60
|
-
}
|
|
61
|
-
read(value, key) {
|
|
62
|
-
value.read++;
|
|
63
|
-
return value;
|
|
64
|
-
}
|
|
65
|
-
dispose(value, key) {
|
|
66
|
-
dispose(value, key);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const cache = new Cache('test');
|
|
71
|
-
|
|
72
|
-
const obj = { a: 1 };
|
|
73
|
-
|
|
74
|
-
const x1 = await cache.memo(1, obj);
|
|
75
|
-
expect(x1).toStrictEqual({ a: 1, cached: true, read: 0 });
|
|
76
|
-
|
|
77
|
-
const x2 = await cache.memo(1, obj);
|
|
78
|
-
expect(x2).toStrictEqual({ a: 1, cached: true, read: 1 });
|
|
79
|
-
|
|
80
|
-
const x3 = await cache.memo(1, { a: 2 });
|
|
81
|
-
expect(x3).toStrictEqual({ a: 2, cached: true, read: 0 });
|
|
82
|
-
expect(cache.size).toEqual(1);
|
|
83
|
-
expect(dispose).toBeCalledTimes(1);
|
|
84
|
-
expect(dispose).toBeCalledWith({ a: 1, cached: true, read: 1 }, 1);
|
|
85
|
-
|
|
86
|
-
const x4 = await cache.memo(2, { a: 20 });
|
|
87
|
-
|
|
88
|
-
expect(x4).toStrictEqual({ a: 20, cached: true, read: 0 });
|
|
89
|
-
expect(cache.size).toEqual(2);
|
|
90
|
-
expect(dispose).toBeCalledTimes(1);
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
test('key transform', async () => {
|
|
94
|
-
const dispose = vi.fn();
|
|
95
|
-
|
|
96
|
-
class Cache extends TransformerMap<number, any> {
|
|
97
|
-
write(key, value, oldValue) {
|
|
98
|
-
return { key, read: 0 };
|
|
99
|
-
}
|
|
100
|
-
read(value, key) {
|
|
101
|
-
value.read++;
|
|
102
|
-
return value;
|
|
103
|
-
}
|
|
104
|
-
dispose(value, key) {
|
|
105
|
-
dispose(value, key);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const cache = new Cache('test');
|
|
110
|
-
|
|
111
|
-
const x1 = await cache.memo(1);
|
|
112
|
-
expect(x1).toStrictEqual({ key: 1, read: 0 });
|
|
113
|
-
|
|
114
|
-
const x2 = await cache.memo(1);
|
|
115
|
-
expect(x2).toStrictEqual({ key: 1, read: 1 });
|
|
116
|
-
|
|
117
|
-
cache.clear();
|
|
118
|
-
expect(dispose).toBeCalledTimes(1);
|
|
119
|
-
expect(dispose).toBeCalledWith({ key: 1, read: 1 }, 1);
|
|
120
|
-
});
|
|
121
|
-
});
|
package/src/cache.ts
DELETED
|
@@ -1,245 +0,0 @@
|
|
|
1
|
-
import ManyKeysMap from 'many-keys-map';
|
|
2
|
-
|
|
3
|
-
export type MaybePromise<T> = T | Promise<T>;
|
|
4
|
-
|
|
5
|
-
export const shallowCompare = (prev: any, next: any): boolean =>
|
|
6
|
-
Array.from(new Set([...Object.keys(prev), ...Object.keys(next)])).every((key) => prev[key] === next[key]);
|
|
7
|
-
|
|
8
|
-
export const equal = (prev: any, next: any): boolean => prev === next;
|
|
9
|
-
|
|
10
|
-
const UNUSED = Symbol('UNUSED');
|
|
11
|
-
export const ANY: unique symbol = Symbol('*');
|
|
12
|
-
|
|
13
|
-
export function createTracker<Dep = any>(whenDifferent: (next: Dep, prev?: Dep) => any, shallow = false) {
|
|
14
|
-
let lastDependency: Dep = UNUSED as any;
|
|
15
|
-
|
|
16
|
-
const comparator = shallow ? shallowCompare : equal;
|
|
17
|
-
|
|
18
|
-
return function tracker(dependency: Dep): Dep | false {
|
|
19
|
-
// console.log('Tracker', lastDependency, dependency);
|
|
20
|
-
|
|
21
|
-
if (lastDependency !== UNUSED && comparator(lastDependency, dependency)) {
|
|
22
|
-
// console.log('RETAINED cache', { lastDependency, dependency });
|
|
23
|
-
return false;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// console.log('INVALIDATED cache', {lastDependency, dependency});
|
|
27
|
-
|
|
28
|
-
const res = whenDifferent(dependency, lastDependency === UNUSED ? undefined : lastDependency) || true;
|
|
29
|
-
|
|
30
|
-
lastDependency = dependency;
|
|
31
|
-
|
|
32
|
-
return res;
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* https://github.com/futpib/deep-weak-map
|
|
38
|
-
* https://github.com/fregante/many-keys-map
|
|
39
|
-
* https://github.com/fregante/many-keys-weakmap
|
|
40
|
-
* https://github.com/sindresorhus/memoize?tab=readme-ov-file#example-multiple-non-serializable-arguments
|
|
41
|
-
*
|
|
42
|
-
* ```ts
|
|
43
|
-
* const memoized = memo(
|
|
44
|
-
* (file, options, ...args) => { ... },
|
|
45
|
-
* {
|
|
46
|
-
* key: (file, options, ...args) => [file, JSON.stringify(options), ...args],
|
|
47
|
-
* invalidate: (bitmap) => !bitmap.width,
|
|
48
|
-
* dispose: (bitmap) => bitmap.close(),
|
|
49
|
-
* }
|
|
50
|
-
* );
|
|
51
|
-
* ```
|
|
52
|
-
*/
|
|
53
|
-
export function memo<Key extends any[], Val, SerializedKey extends any[] = Key>(
|
|
54
|
-
fn: (...args: Key) => MaybePromise<Val>,
|
|
55
|
-
{
|
|
56
|
-
key = (...k: Key) => k as never as SerializedKey,
|
|
57
|
-
invalidate,
|
|
58
|
-
dispose,
|
|
59
|
-
}: {
|
|
60
|
-
key?: (...key: Key) => SerializedKey;
|
|
61
|
-
invalidate?: (prev: Val, ...key: SerializedKey) => boolean;
|
|
62
|
-
dispose?: (prev: Val, ...key: SerializedKey) => void;
|
|
63
|
-
} = {},
|
|
64
|
-
): {
|
|
65
|
-
(...args: Key): Promise<{
|
|
66
|
-
value?: Val;
|
|
67
|
-
hit: boolean;
|
|
68
|
-
}>;
|
|
69
|
-
clear: (...condition: Key | any[]) => void;
|
|
70
|
-
size: () => number;
|
|
71
|
-
} {
|
|
72
|
-
const map = new ManyKeysMap<SerializedKey, Val>();
|
|
73
|
-
|
|
74
|
-
//TODO Extend ManyKeysMap
|
|
75
|
-
const clear = (...condition: Key | any[]) => {
|
|
76
|
-
if (!condition?.length) {
|
|
77
|
-
map.forEach((value, serializedKey) => {
|
|
78
|
-
dispose?.(value, ...serializedKey);
|
|
79
|
-
});
|
|
80
|
-
map.clear();
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const keyToClear = key(...(condition as any));
|
|
85
|
-
|
|
86
|
-
map.forEach((value, serializedKey) => {
|
|
87
|
-
if (!serializedKey.every((kk, i) => keyToClear[i] === ANY || kk === keyToClear[i])) return;
|
|
88
|
-
dispose?.(value, ...serializedKey);
|
|
89
|
-
map.delete(serializedKey);
|
|
90
|
-
});
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
const size = () => map.size;
|
|
94
|
-
|
|
95
|
-
async function memoized(...args: Key) {
|
|
96
|
-
const k = key(...args);
|
|
97
|
-
|
|
98
|
-
const has = map.has(k);
|
|
99
|
-
const old = map.get(k);
|
|
100
|
-
|
|
101
|
-
if (has && !invalidate?.(old as never as Val, ...k)) {
|
|
102
|
-
return { value: map.get(k), hit: true };
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const value = await fn(...args);
|
|
106
|
-
|
|
107
|
-
map.set(k, value);
|
|
108
|
-
|
|
109
|
-
return { hit: false, value };
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
memoized.clear = clear;
|
|
113
|
-
memoized.size = size;
|
|
114
|
-
|
|
115
|
-
//TODO return mimic-function(memorized, fn);
|
|
116
|
-
|
|
117
|
-
return memoized;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Allows to memoize values by key and invalidate them based on the previous value.
|
|
122
|
-
*
|
|
123
|
-
* This makes possible to implement various one-off and subsequent transformations.
|
|
124
|
-
*
|
|
125
|
-
* 1. `write`: Transform value BEFORE writing to cache, called only once if cache IS NOT present or IS NOT valid
|
|
126
|
-
* 2. `read`: Transform value AFTER it's read from cache, always called if cache IS valid, keep in mind this transform is applied on top of BEFORE
|
|
127
|
-
*
|
|
128
|
-
* Both should return same type or null.
|
|
129
|
-
*
|
|
130
|
-
* If `newValue` is null, old is returned, and no cache is set.
|
|
131
|
-
*
|
|
132
|
-
* ```ts
|
|
133
|
-
* class InputCache extends TypedCache<string, ImageBitmap> {
|
|
134
|
-
* dispose(bitmap: ImageBitmap, key: string) {
|
|
135
|
-
* bitmap.close();
|
|
136
|
-
* }
|
|
137
|
-
* }
|
|
138
|
-
*
|
|
139
|
-
* const cache = new InputCache('input');
|
|
140
|
-
*
|
|
141
|
-
* // Cache provided OffscreenCanvas by name and create context once per canvas
|
|
142
|
-
*
|
|
143
|
-
* class CanvasCache extends TypedCache<string, OffscreenCanvas> {
|
|
144
|
-
* protected write(key: string, canvas: OffscreenCanvas): OffscreenCanvas {
|
|
145
|
-
* const canvas = newValue.getContext('2d');
|
|
146
|
-
* canvas.ctx = canvas;
|
|
147
|
-
* return canvas;
|
|
148
|
-
* }
|
|
149
|
-
* }
|
|
150
|
-
*
|
|
151
|
-
* // Transfer control to offscreen canvas and track removal of original
|
|
152
|
-
* // When called again, always return null, because canvas is handed to worker, and never returned to main
|
|
153
|
-
*
|
|
154
|
-
* class CanvasCache extends TypedCache<HTMLCanvasElement, OffscreenCanvas> {
|
|
155
|
-
*
|
|
156
|
-
* protected write(canvas: HTMLCanvasElement): OffscreenCanvas {
|
|
157
|
-
* canvas.addEventListener(
|
|
158
|
-
* 'remove',
|
|
159
|
-
* function listener() {
|
|
160
|
-
* console.log('Removing canvas from cache', key);
|
|
161
|
-
* this.delete(canvas);
|
|
162
|
-
* key.removeEventListener('remove', listener);
|
|
163
|
-
* },
|
|
164
|
-
* );
|
|
165
|
-
* return canvas.transferControlToOffscreen();
|
|
166
|
-
* }
|
|
167
|
-
*
|
|
168
|
-
* protected read() {
|
|
169
|
-
* return null;
|
|
170
|
-
* }
|
|
171
|
-
* }
|
|
172
|
-
*
|
|
173
|
-
* // Create bitmap from file and check if was used, return null if used
|
|
174
|
-
* // Since bitmaps are also cached in worker, used bitmaps are not sent
|
|
175
|
-
*
|
|
176
|
-
* class BitmapCache extends TypedCache<File, ImageBitmap, File> {
|
|
177
|
-
* protected async write(file: File): Promise<ImageBitmap> {
|
|
178
|
-
* return await createImageBitmap(file);
|
|
179
|
-
* }
|
|
180
|
-
*
|
|
181
|
-
* protected read(bitmap: ImageBitmap): Promise<ImageBitmap> | ImageBitmap {
|
|
182
|
-
* if (!bitmap.width) return null;
|
|
183
|
-
* return bitmap;
|
|
184
|
-
* }
|
|
185
|
-
* }
|
|
186
|
-
*
|
|
187
|
-
* ```
|
|
188
|
-
*/
|
|
189
|
-
export abstract class TransformerMap<Key, Val = Key> extends Map<Key, Val> {
|
|
190
|
-
constructor(protected readonly name: string) {
|
|
191
|
-
// console.warn('CREATE cache', name);
|
|
192
|
-
super();
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
protected write(key: Key, newValue?: Val, oldValue?: Val): MaybePromise<Val> {
|
|
196
|
-
return newValue as Val;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
protected read(oldValue: Val, key: Key): MaybePromise<Val> {
|
|
200
|
-
return oldValue;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
delete(key: Key): boolean {
|
|
204
|
-
const val = this.get(key);
|
|
205
|
-
const has = typeof val !== 'undefined';
|
|
206
|
-
if (has) this.dispose(val, key);
|
|
207
|
-
super.delete(key);
|
|
208
|
-
return has;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* Do something with the value and key before removing it from cache
|
|
213
|
-
* For example, close a file or a bitmap
|
|
214
|
-
*/
|
|
215
|
-
protected dispose(value: Val, key: Key): void {}
|
|
216
|
-
|
|
217
|
-
clear(): void {
|
|
218
|
-
console.log('CLEAR cache', this.name);
|
|
219
|
-
this.forEach(this.dispose);
|
|
220
|
-
super.clear();
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
set(key: Key, value: Val): this {
|
|
224
|
-
throw new Error('Use memo() method instead');
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
async memo(key: Key, newValue?: Val): Promise<Val> {
|
|
228
|
-
const oldValue = this.get(key);
|
|
229
|
-
|
|
230
|
-
if (!!oldValue && (!newValue || newValue === oldValue)) {
|
|
231
|
-
// console.log('FROM cache', this.name, key, 'old value', oldValue);
|
|
232
|
-
return this.read?.(oldValue, key);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
if (this.has(key)) this.delete(key);
|
|
236
|
-
|
|
237
|
-
const value = await this.write(key, newValue, oldValue);
|
|
238
|
-
|
|
239
|
-
super.set(key, value);
|
|
240
|
-
|
|
241
|
-
// console.log('INVALIDATED KEY cache', this.name, key, 'has', has, 'invalidate', invalidate, 'new value', value);
|
|
242
|
-
|
|
243
|
-
return value;
|
|
244
|
-
}
|
|
245
|
-
}
|
package/src/errors.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
// @see https://www.youtube.com/watch?v=AdmGHwvgaVs
|
|
2
|
-
export function catchErrors<T, E extends Error = Error>(
|
|
3
|
-
promise: Promise<T>,
|
|
4
|
-
errors?: E[],
|
|
5
|
-
): Promise<any[] | (T | undefined)[]> {
|
|
6
|
-
return promise
|
|
7
|
-
.then((data) => [undefined, data])
|
|
8
|
-
.catch((error) => {
|
|
9
|
-
if (!errors?.length) {
|
|
10
|
-
return [error];
|
|
11
|
-
}
|
|
12
|
-
if (errors.some((e) => e instanceof error)) {
|
|
13
|
-
return [error];
|
|
14
|
-
}
|
|
15
|
-
throw error;
|
|
16
|
-
});
|
|
17
|
-
}
|
package/src/index.ts
DELETED
package/src/mutex.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
//TODO Implement queue with ranking & sorting
|
|
2
|
-
//TODO https://github.com/sindresorhus/p-queue
|
|
3
|
-
export class Mutex {
|
|
4
|
-
private promise = Promise.resolve();
|
|
5
|
-
|
|
6
|
-
public exec(fn: (...args: any) => any): ReturnType<typeof fn> {
|
|
7
|
-
return new Promise((resolve, reject) => {
|
|
8
|
-
// wrap in always successful function
|
|
9
|
-
this.promise = this.promise.then(async () => {
|
|
10
|
-
try {
|
|
11
|
-
resolve(await fn());
|
|
12
|
-
} catch (e) {
|
|
13
|
-
reject(e);
|
|
14
|
-
}
|
|
15
|
-
});
|
|
16
|
-
});
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
public wrap(fn: (...args: any) => any): ReturnType<typeof fn> {
|
|
20
|
-
return (...args: any) => this.exec(() => fn(...args));
|
|
21
|
-
}
|
|
22
|
-
}
|
package/tsconfig.json
DELETED
package/turbo.json
DELETED
package/vite.config.ts
DELETED