📄 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