📄 src/models/worker-client.ts
D-OPEN SOVEREIGN
1/*
2 * Copyright (c) 2026 DataPond D-Library Pty Ltd. ("The Sanctuary")
3 * Non-Profit Public Library — The World Library
4 *
5 * D-SAFE Certification issued by POND Enterprise.
6 * Published under the D-Open Code Sovereign Licence v1.0 framework
7 * DataPond D-Library All exclusive Right reserved on modifiying the code - CC Attribution to data pond, Non modifiable, Non Commercial.
8 * https://registry.world.bibliotech.com/licence
9 * Source code donated to DataPond D-Library by it's author: data pond.
10 *
11 * Technical Guardian ("The Shield"): Pond Enterprise Pty Ltd. (ACN 694 747 987)
12 * All liability claims about D-SAFE certification issues coming from the published D-Safe direction must be addressed with Pond Enterprise Pty Ltd.
13 * Code Modification automatically voids the certified liability protection provided by Pond Enterprise.
14 */
15import {proResolver} from "./promises.ts";
16import {WorkerEvent} from "./enums.ts";
17import {IThread, WorkerPayload} from "./interfaces.ts";
18
19
20const forwardEventToWindow = (event: WorkerEvent, detail: any) => {
21 if (typeof window !== 'undefined' && typeof CustomEvent !== 'undefined') {
22 window.dispatchEvent(new CustomEvent(event as string, {detail, bubbles:true}));
23 }
24}
25
26
27export class Thread implements IThread {
28 _worker: Worker
29 _rrRequest: {[id: string]: { res: Function, rej: Function }} = {};
30 _eventListeners: {[event: string]: Array<Function>} = {}
31 _proReady = proResolver<true>()
32 _counter= 0;
33
34 constructor(path: string | URL) {
35 this._worker = new Worker(path,{
36 type: 'module' // Use module type if you are using ES modules in your worker
37 });
38 this._rrRequest = {};
39 this._worker.addEventListener('message', (e) => {
40 this._onMessage(e.data);
41 })
42 this._worker.addEventListener('messageerror', (e) => {
43
44 this._onError(e)
45 })
46 this._worker.onerror = e => this._onError(e);
47 this.addEventListener(WorkerEvent.WorkerReady, () => {
48 this._proReady.resolve(true)
49 })
50 this.addEventListener(WorkerEvent.WorkerLoadingError, (e:any) => {
51 console.log('Worker loading Error', e)
52 this._proReady.reject(e)
53 })
54 }
55
56 get ready(): Promise<true> {
57 return this._proReady.pro;
58 }
59
60 addEventListener(event: WorkerEvent, listener: Function) {
61 if (!(event in this._eventListeners)) {
62 this._eventListeners[event] = [];
63 }
64 this._eventListeners[event].push(listener);
65
66 }
67
68 _onMessage(e: any) {
69 const { id, result, eventName, payload } = e;
70 if (eventName && eventName !== 'undefined' && eventName.length > 0) {
71 forwardEventToWindow(eventName, payload);
72 if (eventName in this._eventListeners) {
73 this._eventListeners[eventName].forEach(listener => listener( payload));
74 } else {
75 // console.warn(`[worker] warn: onMessage(): no listener for event: ${eventName}`);
76 }
77 return;
78 }
79
80 const { data, error } = result;
81 if (id in this._rrRequest) {
82
83 const { res, rej } = this._rrRequest[id];
84 delete this._rrRequest[id];
85 error ? rej(error) : res(data);
86 // console.timeEnd(`procssing request ${id} took`)
87 } else {
88 console.error('nop; invalid request id:', id);
89 }
90 }
91 _onError(e:any) {
92 console.error('error in Thread',this, e);
93 this._cancelPendingRequests();
94 }
95 _sendRequest(data: WorkerPayload, opts= { transferables: []}) {
96 return new Promise((res, rej) => {
97 let id;
98 do {
99 id = `req-id-${this._counter}-${Math.random()}`;
100 this._counter += 1;
101 } while (id in this._rrRequest);
102
103 this._rrRequest[id] = { res, rej };
104
105 if (this._worker) {
106 // console.time(`processing request ${id} took`)
107 // @ts-ignore
108 this._worker.postMessage({ id, data }, opts.transferables.length > 0 ? opts.transferables : undefined);
109 } else {
110 console.log('_sendRequest(): nop (worker already terminated?)');
111 }
112 });
113 }
114
115 sendRequest(payload: WorkerPayload, transferables=[]) {
116 return this._sendRequest(payload, { transferables });
117 }
118 getWorker() {
119 return this._worker;
120 }
121 _cancelPendingRequests() {
122 let count = 0;
123 Object.entries(this._rrRequest).forEach(([id, rr]) => {
124 rr.rej(`canceled: ${id}`);
125 delete this._rrRequest[id];
126 count += 1;
127 });
128 // console.log(`_cancelPendingRequests(): canceled ${count} req(s)`);
129 if (Object.keys(this._rrRequest).length !== 0) {
130 throw 'panic: the rr map should have been cleared!';
131 }
132 }
133 terminate() {
134 this._cancelPendingRequests();
135 let promise = null;
136 // https://developer.mozilla.org/en-US/docs/Web/API/Worker/terminate
137 this._worker.terminate();
138 // @ts-ignore
139 this._worker = null;
140 return promise || undefined;
141 }
142}
143
144let _worker: Thread;
145let _workerPro = proResolver<Thread>()
146
147export const initWorker = async (workerPath:string | URL): Promise<Thread> => {
148 if (_workerPro.isStarted()) {
149 return _workerPro.pro;
150 }
151 _workerPro.start()
152 _worker = new Thread(workerPath)
153 _worker.addEventListener(WorkerEvent.WorkerReady, () => {
154 _workerPro.resolve(_worker)
155 });
156 _worker.addEventListener(WorkerEvent.WorkerLoadingError, (e: any) => {
157 _workerPro.reject(e)
158 });
159 return _workerPro.pro
160}
161