vivth 0.11.2 → 1.0.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/.vivth/dist/init.mjs +24 -0
- package/README.md +2157 -69
- package/README.src.md +35 -0
- package/bun.lock +57 -3
- package/dev/index.mjs +24 -25
- package/index.mjs +51 -29
- package/package.json +11 -7
- package/src/bundler/CompileMJS.mjs +110 -0
- package/src/bundler/EsBundler.mjs +79 -0
- package/src/class/Console.mjs +62 -0
- package/src/class/Derived.mjs +36 -25
- package/src/class/Effect.mjs +106 -0
- package/src/class/EnvSignal.mjs +88 -0
- package/src/class/EventSignal.mjs +200 -0
- package/src/class/ListDerived.mjs +39 -0
- package/src/class/ListSignal.mjs +256 -0
- package/src/class/Paths.mjs +70 -0
- package/src/class/QChannel.mjs +184 -0
- package/src/class/SafeExit.mjs +131 -0
- package/src/class/Setup.mjs +73 -0
- package/src/class/Signal.mjs +152 -54
- package/src/class/WorkerMainThread.mjs +328 -0
- package/src/class/WorkerResult.mjs +30 -0
- package/src/class/WorkerThread.mjs +151 -0
- package/src/common/Base64URL.mjs +26 -0
- package/src/common/EventNameSpace.mjs +8 -0
- package/src/common/eventObjects.mjs +5 -0
- package/src/common/lazie.mjs +3 -0
- package/src/doc/JSautoDOC.mjs +386 -0
- package/src/doc/parsedFile.mjs +537 -0
- package/src/function/CreateImmutable.mjs +64 -0
- package/src/function/EventCheck.mjs +27 -0
- package/src/function/EventObject.mjs +21 -0
- package/src/function/IsAsync.mjs +23 -0
- package/src/function/LazyFactory.mjs +71 -0
- package/src/function/Timeout.mjs +23 -0
- package/src/function/Try.mjs +64 -0
- package/src/function/TryAsync.mjs +15 -4
- package/src/function/TrySync.mjs +9 -4
- package/src/function/TsToMjs.mjs +67 -0
- package/src/function/WriteFileSafe.mjs +37 -0
- package/src/types/{AnyButUndefined.type.mjs → AnyButUndefined.mjs} +1 -0
- package/src/types/ExtnameType.mjs +6 -0
- package/src/types/IsListSignal.mjs +6 -0
- package/src/types/ListArg.mjs +6 -0
- package/src/types/MutationType.mjs +8 -0
- package/src/types/QCBFIFOReturn.mjs +6 -0
- package/src/types/QCBReturn.mjs +6 -0
- package/tsconfig.json +3 -3
- package/types/dev/index.d.mts +1 -0
- package/types/index.d.mts +34 -8
- package/types/src/bundler/A.d.mts +1 -0
- package/types/src/bundler/CompileMJS.d.mts +8 -0
- package/types/src/bundler/EsBundler.d.mts +7 -0
- package/types/src/class/Console.d.mts +40 -0
- package/types/src/class/Derived.d.mts +21 -9
- package/types/src/class/Effect.d.mts +77 -0
- package/types/src/class/EnvSignal.d.mts +47 -0
- package/types/src/class/EventSignal.d.mts +145 -0
- package/types/src/class/ListDerived.d.mts +35 -0
- package/types/src/class/ListSignal.d.mts +150 -0
- package/types/src/class/Paths.d.mts +50 -0
- package/types/src/class/QChannel.d.mts +115 -0
- package/types/src/class/SafeExit.d.mts +76 -0
- package/types/src/class/Setup.d.mts +76 -0
- package/types/src/class/Signal.d.mts +105 -26
- package/types/src/class/WorkerMainThread.d.mts +149 -0
- package/types/src/class/WorkerResult.d.mts +25 -0
- package/types/src/class/WorkerThread.d.mts +70 -0
- package/types/src/common/Base64URL.d.mts +1 -0
- package/types/src/common/EventNameSpace.d.mts +6 -0
- package/types/src/common/eventObjects.d.mts +3 -0
- package/types/src/common/lazie.d.mts +1 -0
- package/types/src/doc/JSautoDOC.d.mts +76 -0
- package/types/src/doc/parsedFile.d.mts +154 -0
- package/types/src/function/CreateImmutable.d.mts +3 -0
- package/types/src/function/EventCheck.d.mts +2 -0
- package/types/src/function/EventObject.d.mts +4 -0
- package/types/src/function/IsAsync.d.mts +1 -0
- package/types/src/function/LazyFactory.d.mts +4 -0
- package/types/src/function/Timeout.d.mts +1 -0
- package/types/src/function/Try.d.mts +1 -0
- package/types/src/function/TsToMjs.d.mts +4 -0
- package/types/src/function/WriteFileSafe.d.mts +2 -0
- package/types/src/types/{AnyButUndefined.type.d.mts → AnyButUndefined.d.mts} +3 -0
- package/types/src/types/ExtnameType.d.mts +4 -0
- package/types/src/types/IsListSignal.d.mts +4 -0
- package/types/src/types/ListArg.d.mts +4 -0
- package/types/src/types/MutationType.d.mts +5 -0
- package/types/src/types/QCBFIFOReturn.d.mts +4 -0
- package/types/src/types/QCBReturn.d.mts +7 -0
- package/src/class/$.mjs +0 -68
- package/src/class/PingFIFO.mjs +0 -78
- package/src/class/PingUnique.mjs +0 -84
- package/src/class/Q.mjs +0 -98
- package/src/class/QFIFO.mjs +0 -66
- package/src/class/QUnique.mjs +0 -75
- package/src/common.mjs +0 -16
- package/src/function/NewQBlock.mjs +0 -39
- package/types/src/class/$.d.mts +0 -40
- package/types/src/class/PingFIFO.d.mts +0 -57
- package/types/src/class/PingUnique.d.mts +0 -48
- package/types/src/class/Q.d.mts +0 -63
- package/types/src/class/QFIFO.d.mts +0 -47
- package/types/src/class/QUnique.d.mts +0 -46
- package/types/src/common.d.mts +0 -2
- package/types/src/function/NewQBlock.d.mts +0 -1
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import { Base64URL } from '../common/Base64URL.mjs';
|
|
4
|
+
import { closeWorkerThreadEventObject } from '../common/eventObjects.mjs';
|
|
5
|
+
import { Try } from '../function/Try.mjs';
|
|
6
|
+
import { TryAsync } from '../function/TryAsync.mjs';
|
|
7
|
+
import { Console } from './Console.mjs';
|
|
8
|
+
import { Derived } from './Derived.mjs';
|
|
9
|
+
import { Effect } from './Effect.mjs';
|
|
10
|
+
import { Paths } from './Paths.mjs';
|
|
11
|
+
import { SafeExit } from './SafeExit.mjs';
|
|
12
|
+
import { Signal } from './Signal.mjs';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @template A
|
|
16
|
+
* @typedef {import('./WorkerResult.mjs').WorkerResult<A>} WorkerResult
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* @typedef {import('./WorkerThread.mjs').WorkerThread} WorkerThread
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @description
|
|
24
|
+
* - class helper to create `Worker` instance;
|
|
25
|
+
* @template {WorkerThread} WT
|
|
26
|
+
*/
|
|
27
|
+
export class WorkerMainThread {
|
|
28
|
+
static #isRegistered = false;
|
|
29
|
+
/**
|
|
30
|
+
* @description
|
|
31
|
+
* - need to be called first, before any `WorkerMainThread` instantiation:
|
|
32
|
+
* @param {Object} param0
|
|
33
|
+
* @param {typeof WorkerMainThread["workerClass"]} param0.workerClass
|
|
34
|
+
* @param {typeof WorkerMainThread["pathValidator"]} param0.pathValidator
|
|
35
|
+
* ```js
|
|
36
|
+
* async(relativePath) => {
|
|
37
|
+
* // verify whether relativePath exist, then return the full path
|
|
38
|
+
* // use fetch | fs, chained with Paths.instance.root + WorkerMainThread.basePath;
|
|
39
|
+
* }
|
|
40
|
+
* ```
|
|
41
|
+
* @param {typeof WorkerMainThread["basePath"]} [param0.basePath]
|
|
42
|
+
* - additonal realtivePath from rootPath;
|
|
43
|
+
* - default: '';
|
|
44
|
+
* @example
|
|
45
|
+
* import { WorkerMainThread } from 'vivth';
|
|
46
|
+
*
|
|
47
|
+
* WorkerMainThread.setup({
|
|
48
|
+
* workerClass: async () => await (import('worker_threads')).Worker,
|
|
49
|
+
* basePath: 'public/assets/js/workers',
|
|
50
|
+
* pathValidator: async (workerPath, root, base) => {
|
|
51
|
+
* const res = await fetch(`${root}/${base}/${workerPath}`);
|
|
52
|
+
* // might also check wheter it need base or not
|
|
53
|
+
* return await res.ok;
|
|
54
|
+
* },
|
|
55
|
+
* });
|
|
56
|
+
*/
|
|
57
|
+
static setup = ({ workerClass, pathValidator, basePath = '' }) => {
|
|
58
|
+
if (!Paths.root) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (WorkerMainThread.#isRegistered) {
|
|
62
|
+
Console.warn({ message: 'WorkerMainThread.setup, can only be called once' });
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
WorkerMainThread.#isRegistered = true;
|
|
66
|
+
WorkerMainThread.workerClass = workerClass;
|
|
67
|
+
WorkerMainThread.pathValidator = pathValidator;
|
|
68
|
+
WorkerMainThread.basePath = basePath;
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* @description
|
|
72
|
+
* - reference for `Worker` class;
|
|
73
|
+
* - edit via `setup`;
|
|
74
|
+
* @type {()=>Promise<typeof Worker|typeof import('worker_threads').Worker>}
|
|
75
|
+
*/
|
|
76
|
+
static workerClass;
|
|
77
|
+
/**
|
|
78
|
+
* @description
|
|
79
|
+
* - reference for worker file `basePath`;
|
|
80
|
+
* - edit via `setup`;
|
|
81
|
+
* @type {string}
|
|
82
|
+
*/
|
|
83
|
+
static basePath;
|
|
84
|
+
/**
|
|
85
|
+
* @description
|
|
86
|
+
* - reference for validating path;
|
|
87
|
+
* - edit via `setup`;
|
|
88
|
+
* @type {(paths:{worker: string, root:string, base: string})=>Promise<string>}
|
|
89
|
+
*/
|
|
90
|
+
static pathValidator;
|
|
91
|
+
static #options = /** @type {import('worker_threads').WorkerOptions & { type?: 'module' }} */ ({
|
|
92
|
+
type: 'module',
|
|
93
|
+
});
|
|
94
|
+
/**
|
|
95
|
+
* @description
|
|
96
|
+
* - create Worker_instance;
|
|
97
|
+
* @param {string} handler
|
|
98
|
+
* - if `isInline` === `false`, `handler` should be:
|
|
99
|
+
* >- pointing to worker thread file; WHICH
|
|
100
|
+
* >- the path must be relative to `projectRoot`;
|
|
101
|
+
* - if `isInline` === `true`, `handler` should be
|
|
102
|
+
* >- string literal of prebundled worker thread script; OR
|
|
103
|
+
* >- manually made string literal of worker thread script;
|
|
104
|
+
* @param {Omit<WorkerOptions|import('worker_threads').WorkerOptions, 'eval'|'type'>} [options]
|
|
105
|
+
* @param {boolean} [isInline]
|
|
106
|
+
* @example
|
|
107
|
+
* import { WorkerMainThread } from 'vivth';
|
|
108
|
+
*
|
|
109
|
+
* export const myDoubleWorker = new WorkerMainThread('./doubleWorkerThread.mjs');
|
|
110
|
+
*/
|
|
111
|
+
constructor(handler, options = {}, isInline = false) {
|
|
112
|
+
/**
|
|
113
|
+
* @param {WT["Receive"]} ev
|
|
114
|
+
* @returns {void}
|
|
115
|
+
*/
|
|
116
|
+
const listener = (ev) => {
|
|
117
|
+
this.#proxyReceiver.value = ev;
|
|
118
|
+
};
|
|
119
|
+
WorkerMainThread.#workerFilehandler(handler, options, this, listener, isInline);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* @type {import('./Signal.mjs').Signal<import('./WorkerResult.mjs').WorkerResult<WT["Post"]>|MessageEvent<import('./WorkerResult.mjs').WorkerResult<WT["Post"]>>>}
|
|
123
|
+
*/
|
|
124
|
+
#proxyReceiver = new Signal(undefined);
|
|
125
|
+
/**
|
|
126
|
+
* @param {string} handler
|
|
127
|
+
* @param { WorkerOptions
|
|
128
|
+
* | import('worker_threads').WorkerOptions} options
|
|
129
|
+
* @param {WorkerMainThread} worker
|
|
130
|
+
* @param {(any:any)=>void} listener
|
|
131
|
+
* @param {boolean} isInline
|
|
132
|
+
* @returns {Promise<void>}
|
|
133
|
+
*/
|
|
134
|
+
static #workerFilehandler = async (handler, options, worker, listener, isInline) => {
|
|
135
|
+
let resolvedPath;
|
|
136
|
+
if (!isInline) {
|
|
137
|
+
const pathValidator = WorkerMainThread.pathValidator;
|
|
138
|
+
const [resolvedPath_, error] = await TryAsync(async () => {
|
|
139
|
+
return await pathValidator({
|
|
140
|
+
worker: handler,
|
|
141
|
+
root: Paths.root,
|
|
142
|
+
base: WorkerMainThread.basePath,
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
if (error) {
|
|
146
|
+
Console.error({
|
|
147
|
+
error,
|
|
148
|
+
pathValidator,
|
|
149
|
+
message: 'invalid pathValidator inputed to `WorkerMainThread`;',
|
|
150
|
+
});
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
resolvedPath = resolvedPath_;
|
|
154
|
+
} else {
|
|
155
|
+
WorkerMainThread.#options.eval = true;
|
|
156
|
+
resolvedPath = handler;
|
|
157
|
+
}
|
|
158
|
+
const [_1, error1] = await Try({
|
|
159
|
+
universal: async () => {
|
|
160
|
+
const WorkerClass = await WorkerMainThread.workerClass();
|
|
161
|
+
if (!WorkerClass) {
|
|
162
|
+
throw new Error('invalid `Worker` inputed to `WorkerMainThread`;');
|
|
163
|
+
}
|
|
164
|
+
const [_2, error2] = await Try({
|
|
165
|
+
browser: async () => {
|
|
166
|
+
if (!WorkerMainThread.isBrowser) {
|
|
167
|
+
throw new Error('not a browser');
|
|
168
|
+
}
|
|
169
|
+
let worker_;
|
|
170
|
+
if (!isInline) {
|
|
171
|
+
worker_ = worker.#worker.value = new WorkerClass(resolvedPath, {
|
|
172
|
+
...options,
|
|
173
|
+
...WorkerMainThread.#options,
|
|
174
|
+
});
|
|
175
|
+
} else {
|
|
176
|
+
const inlineURL = Base64URL(handler, 'application/javascript', btoa);
|
|
177
|
+
worker_ = worker.#worker.value = new WorkerClass(inlineURL, {
|
|
178
|
+
...options,
|
|
179
|
+
...WorkerMainThread.#options,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
if (!('onmessage' in worker_)) {
|
|
183
|
+
throw new Error();
|
|
184
|
+
}
|
|
185
|
+
worker_.onmessage = listener;
|
|
186
|
+
if (SafeExit.instance) {
|
|
187
|
+
SafeExit.instance.addCallback(async () => {
|
|
188
|
+
worker_.onmessage = null;
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
nonBrowser: async () => {
|
|
193
|
+
const worker_ = (worker.#worker.value = new WorkerClass(resolvedPath, {
|
|
194
|
+
...options,
|
|
195
|
+
...WorkerMainThread.#options,
|
|
196
|
+
}));
|
|
197
|
+
if (!('addEventListener' in worker_)) {
|
|
198
|
+
throw new Error();
|
|
199
|
+
}
|
|
200
|
+
worker_.addEventListener('message', listener);
|
|
201
|
+
if (SafeExit.instance) {
|
|
202
|
+
SafeExit.instance.addCallback(async () => {
|
|
203
|
+
worker_.removeEventListener('message', listener);
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
});
|
|
208
|
+
if (error2) {
|
|
209
|
+
throw new Error();
|
|
210
|
+
}
|
|
211
|
+
if (SafeExit.instance) {
|
|
212
|
+
SafeExit.instance.addCallback(async () => {
|
|
213
|
+
worker.#worker.value.postMessage(closeWorkerThreadEventObject);
|
|
214
|
+
worker.terminate();
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
nodeOrBun: async () => {
|
|
219
|
+
if (!worker.#worker.value) {
|
|
220
|
+
const { Worker } = await import('node:worker_threads');
|
|
221
|
+
worker.#worker.value = new Worker(resolvedPath, {
|
|
222
|
+
...options,
|
|
223
|
+
...WorkerMainThread.#options,
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
const worker_ = worker.#worker.value;
|
|
227
|
+
// @ts-expect-error
|
|
228
|
+
worker_.addListener('message', listener);
|
|
229
|
+
if (SafeExit.instance) {
|
|
230
|
+
SafeExit.instance.addCallback(async () => {
|
|
231
|
+
// @ts-expect-error
|
|
232
|
+
worker_.removeListener('message', listener);
|
|
233
|
+
worker_.postMessage(closeWorkerThreadEventObject);
|
|
234
|
+
worker.terminate();
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
});
|
|
239
|
+
if (error1) {
|
|
240
|
+
Console.error(error1);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
/**
|
|
245
|
+
* @type {boolean}
|
|
246
|
+
*/
|
|
247
|
+
static #isBrowser = undefined;
|
|
248
|
+
/**
|
|
249
|
+
* @description
|
|
250
|
+
* - check whether js run in browser
|
|
251
|
+
* @type {boolean}
|
|
252
|
+
*/
|
|
253
|
+
static get isBrowser() {
|
|
254
|
+
if (WorkerMainThread.#isBrowser === undefined) {
|
|
255
|
+
WorkerMainThread.#isBrowser =
|
|
256
|
+
typeof window !== 'undefined' &&
|
|
257
|
+
typeof location === 'object' &&
|
|
258
|
+
typeof location.origin === 'string';
|
|
259
|
+
}
|
|
260
|
+
return WorkerMainThread.#isBrowser;
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* lazyly generated because node version need to await
|
|
264
|
+
* @type {Signal<Worker | import('worker_threads').Worker>}
|
|
265
|
+
*/
|
|
266
|
+
#worker = new Signal(undefined);
|
|
267
|
+
/**
|
|
268
|
+
* @type {Signal<WT["Receive"]>}
|
|
269
|
+
*/
|
|
270
|
+
#proxyPost = new Signal(undefined);
|
|
271
|
+
#handler = new Effect(async ({ subscribe }) => {
|
|
272
|
+
const postData = subscribe(this.#proxyPost).value;
|
|
273
|
+
const worker = subscribe(this.#worker).value;
|
|
274
|
+
if (!worker || postData === undefined) {
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
worker.postMessage(postData);
|
|
278
|
+
});
|
|
279
|
+
/**
|
|
280
|
+
* @description
|
|
281
|
+
* - terminate all signals that are used on this instance;
|
|
282
|
+
* @type {()=>void}
|
|
283
|
+
*/
|
|
284
|
+
terminate = () => {
|
|
285
|
+
/**
|
|
286
|
+
* this is more for browser, as most of this are automatically cleaned with `SafeExit`;
|
|
287
|
+
*/
|
|
288
|
+
this.#worker.value?.terminate();
|
|
289
|
+
this.#worker.remove.ref();
|
|
290
|
+
this.#handler.options.removeEffect();
|
|
291
|
+
this.#proxyPost.remove.ref();
|
|
292
|
+
this.#proxyReceiver.remove.ref();
|
|
293
|
+
this.receiverSignal.remove.ref();
|
|
294
|
+
};
|
|
295
|
+
/**
|
|
296
|
+
* @description
|
|
297
|
+
* - result signal of the processed message;
|
|
298
|
+
* @type {Derived<WorkerResult<WT["Post"]>>}
|
|
299
|
+
* @example
|
|
300
|
+
* import { Effect } from 'vivth';
|
|
301
|
+
* import { myDoubleWorker } from './myDoubleWorker.mjs';
|
|
302
|
+
*
|
|
303
|
+
* const doubleReceiverSignal = myDoubleWorker.receiverSignal;
|
|
304
|
+
* new Effect(async({ subscribe }) => {
|
|
305
|
+
* const value = subscribe(doubleReceiverSignal).value;
|
|
306
|
+
* // code
|
|
307
|
+
* })
|
|
308
|
+
*/
|
|
309
|
+
receiverSignal = new Derived(async ({ subscribe }) => {
|
|
310
|
+
const val = subscribe(this.#proxyReceiver).value;
|
|
311
|
+
if (val instanceof MessageEvent) {
|
|
312
|
+
return val.data;
|
|
313
|
+
}
|
|
314
|
+
return val;
|
|
315
|
+
});
|
|
316
|
+
/**
|
|
317
|
+
* @description
|
|
318
|
+
* - callback to send message to the worker thread;
|
|
319
|
+
* @type {(event: WT["Receive"])=>void}
|
|
320
|
+
* @example
|
|
321
|
+
* import { myDoubleWorker } from './myDoubleWorker.mjs';
|
|
322
|
+
*
|
|
323
|
+
* myDoubleWorker.postMessage(90);
|
|
324
|
+
*/
|
|
325
|
+
postMessage = (data) => {
|
|
326
|
+
this.#proxyPost.value = data;
|
|
327
|
+
};
|
|
328
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @description
|
|
5
|
+
* - typeHelper for `Worker` message passing;
|
|
6
|
+
* - uses error as value instead;
|
|
7
|
+
* @template POST
|
|
8
|
+
*/
|
|
9
|
+
export class WorkerResult {
|
|
10
|
+
/**
|
|
11
|
+
* @param {POST} data
|
|
12
|
+
* @param {Error|string|undefined} error
|
|
13
|
+
*/
|
|
14
|
+
constructor(data, error) {
|
|
15
|
+
this.data = data;
|
|
16
|
+
this.error = error;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* @description
|
|
20
|
+
* - result value;
|
|
21
|
+
* @type {POST}
|
|
22
|
+
*/
|
|
23
|
+
data;
|
|
24
|
+
/**
|
|
25
|
+
* @description
|
|
26
|
+
* - error value;
|
|
27
|
+
* @type {Error|string|undefined}
|
|
28
|
+
*/
|
|
29
|
+
error;
|
|
30
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import { closeWorkerThreadEventObject } from '../common/eventObjects.mjs';
|
|
4
|
+
import { EventCheck } from '../function/EventCheck.mjs';
|
|
5
|
+
import { LazyFactory } from '../function/LazyFactory.mjs';
|
|
6
|
+
import { Try } from '../function/Try.mjs';
|
|
7
|
+
import { TryAsync } from '../function/TryAsync.mjs';
|
|
8
|
+
import { Console } from './Console.mjs';
|
|
9
|
+
import { QChannel } from './QChannel.mjs';
|
|
10
|
+
import { WorkerResult } from './WorkerResult.mjs';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @description
|
|
14
|
+
* - class helper for `WorkerThread` creation;
|
|
15
|
+
* @template Receive
|
|
16
|
+
* @template Post
|
|
17
|
+
*/
|
|
18
|
+
export class WorkerThread {
|
|
19
|
+
/**
|
|
20
|
+
* @type {{parentPort:()=>Promise<any>}}
|
|
21
|
+
*/
|
|
22
|
+
static #parentPortRef = LazyFactory(() => {
|
|
23
|
+
return { parentPort: async () => (await import('node:worker_threads')).parentPort };
|
|
24
|
+
});
|
|
25
|
+
/**
|
|
26
|
+
* @description
|
|
27
|
+
* - need to be called and exported as new `WorkerThread` class reference;
|
|
28
|
+
* @template Receive_
|
|
29
|
+
* @template Post_
|
|
30
|
+
* @param {{parentPort:()=>Promise<any>}} parentPortRef
|
|
31
|
+
* - correct parentPort reference;
|
|
32
|
+
* @returns {typeof WorkerThread<Receive_, Post_>}
|
|
33
|
+
* @example
|
|
34
|
+
* import { WorkerThread } from 'vivth';
|
|
35
|
+
*
|
|
36
|
+
* WorkerThread.setup({ parentPort: async () => await import('node:worker_threads') });
|
|
37
|
+
* // that is the default value, if your parentPort/equivalent API is not that;
|
|
38
|
+
* // you need to call this method;
|
|
39
|
+
*/
|
|
40
|
+
static setup = (parentPortRef) => {
|
|
41
|
+
this.#parentPortRef = parentPortRef;
|
|
42
|
+
return WorkerThread;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* @returns {QChannel<WorkerThread>}
|
|
46
|
+
*/
|
|
47
|
+
#qChannel = new QChannel();
|
|
48
|
+
/**
|
|
49
|
+
* @param {any} ev
|
|
50
|
+
*/
|
|
51
|
+
static #isCloseWorkerEvent = (ev) => {
|
|
52
|
+
return EventCheck(ev, closeWorkerThreadEventObject);
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* @description
|
|
56
|
+
* - instantiate via created class from `setup` static method;
|
|
57
|
+
* @param {WorkerThread["handler"]} handler
|
|
58
|
+
* @example
|
|
59
|
+
* import { MyWorkerThread } from './MyWorkerThread.mjs';
|
|
60
|
+
*
|
|
61
|
+
* const doubleWorker = new MyWorkerThread((ev, isLastOnQ) => {
|
|
62
|
+
* // if(!isLastOnQ) {
|
|
63
|
+
* // return null; // can be used for imperative debouncing;
|
|
64
|
+
* // }
|
|
65
|
+
* return ev = ev \* 2;
|
|
66
|
+
* });
|
|
67
|
+
*/
|
|
68
|
+
constructor(handler) {
|
|
69
|
+
this.handler = handler;
|
|
70
|
+
const this_ = this;
|
|
71
|
+
Try({
|
|
72
|
+
post: async () => {
|
|
73
|
+
/**
|
|
74
|
+
* @param {MessageEvent<Receive>|Receive} ev
|
|
75
|
+
* @returns {Promise<void>}
|
|
76
|
+
*/
|
|
77
|
+
self.onmessage = async function (ev) {
|
|
78
|
+
const [_, error] = await TryAsync(async () => {
|
|
79
|
+
ev = ev instanceof MessageEvent ? ev.data : ev;
|
|
80
|
+
if (WorkerThread.#isCloseWorkerEvent(ev)) {
|
|
81
|
+
self.onmessage = null;
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const [data, error] = await this_.#qChannel.callback(this, async ({ isLastOnQ }) => {
|
|
85
|
+
return await handler(ev, isLastOnQ);
|
|
86
|
+
});
|
|
87
|
+
if (error) {
|
|
88
|
+
throw error;
|
|
89
|
+
}
|
|
90
|
+
self.postMessage(new WorkerResult(data, undefined));
|
|
91
|
+
});
|
|
92
|
+
if (!error) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
self.postMessage(new WorkerResult(undefined, error?.message ?? 'Unknown error'));
|
|
96
|
+
};
|
|
97
|
+
},
|
|
98
|
+
parentPost: async () => {
|
|
99
|
+
const parentPort = await WorkerThread.#parentPortRef.parentPort();
|
|
100
|
+
/**
|
|
101
|
+
* @param {MessageEvent<Receive>|Receive} ev
|
|
102
|
+
* @returns {Promise<void>}
|
|
103
|
+
*/
|
|
104
|
+
const listener = async function (ev) {
|
|
105
|
+
const [_, error] = await TryAsync(async () => {
|
|
106
|
+
ev = ev instanceof MessageEvent ? ev.data : ev;
|
|
107
|
+
if (WorkerThread.#isCloseWorkerEvent(ev)) {
|
|
108
|
+
parentPort.off('message', listener);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const [data, error] = await this_.#qChannel.callback(this, async ({ isLastOnQ }) => {
|
|
112
|
+
return await handler(ev, isLastOnQ);
|
|
113
|
+
});
|
|
114
|
+
if (error) {
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
parentPort.postMessage(new WorkerResult(data, undefined));
|
|
118
|
+
});
|
|
119
|
+
if (!error) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
parentPort.postMessage(new WorkerResult(undefined, error?.message ?? 'Unknown error'));
|
|
123
|
+
};
|
|
124
|
+
parentPort.on('message', listener);
|
|
125
|
+
},
|
|
126
|
+
}).then(([_, error]) => {
|
|
127
|
+
if (!error) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
Console.error(error);
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* @description
|
|
135
|
+
* - type helper;
|
|
136
|
+
* @type {(ev: Receive, isLastOnQ:boolean) => Post}
|
|
137
|
+
*/
|
|
138
|
+
handler;
|
|
139
|
+
/**
|
|
140
|
+
* @description
|
|
141
|
+
* - helper type, hold no actual value;
|
|
142
|
+
* @type {Receive}
|
|
143
|
+
*/
|
|
144
|
+
Receive;
|
|
145
|
+
/**
|
|
146
|
+
* @description
|
|
147
|
+
* - helper type, hold no actual value;
|
|
148
|
+
* @type {Post}
|
|
149
|
+
*/
|
|
150
|
+
Post;
|
|
151
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @description
|
|
5
|
+
* - create inline base64 url;
|
|
6
|
+
* - usage:
|
|
7
|
+
* >- can be extremely usefull to display file on desktop app webview, without exposing http server;
|
|
8
|
+
* @param {string} fileString
|
|
9
|
+
* @param {string} mimeType
|
|
10
|
+
* @param {(string:string)=>string} btoaFunction
|
|
11
|
+
* - check your js runtime `btoa`;
|
|
12
|
+
* @returns {string}
|
|
13
|
+
* @example
|
|
14
|
+
* import { Base64URL } from 'vivth'
|
|
15
|
+
* import { fileString } from './fileString.mjs';
|
|
16
|
+
*
|
|
17
|
+
* Base64URL(fileString, 'application/javascript', btoa);
|
|
18
|
+
*/
|
|
19
|
+
export const Base64URL = (fileString, mimeType, btoaFunction) => {
|
|
20
|
+
const utf8 = new TextEncoder().encode(fileString);
|
|
21
|
+
let binary = '';
|
|
22
|
+
for (let byte of utf8) {
|
|
23
|
+
binary += String.fromCharCode(byte);
|
|
24
|
+
}
|
|
25
|
+
return `data:${mimeType};base64,${btoaFunction(binary)}`;
|
|
26
|
+
};
|