@kirill.konshin/worker 0.0.2 → 0.0.3

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/wrpc.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"wrpc.js","sources":["../src/wrpc.ts"],"sourcesContent":["import { getTransferrable } from './workerUtils';\n\n// Types\n\nexport type Responders = { [key: string]: (arg?: any) => Generator<any> | AsyncGenerator<any> | Promise<any> | any };\n\nexport type Context = {\n message: string;\n id: string;\n signalName?: string;\n done?: boolean;\n abort?: string;\n error?: boolean;\n ack?: boolean;\n promise?: boolean;\n};\n\nexport type Event = MessageEvent<{ ctx: Context; payload: any | Error }>;\n\nexport type WorkerLike = {\n postMessage: (payload: any, transferable: any[]) => void;\n addEventListener: (\n event: string,\n listener: (e: any) => void,\n options?: { signal?: AbortSignal; once?: boolean },\n ) => void;\n removeEventListener: (event: string, listener: (e: any) => void) => void;\n} & any;\n\nexport enum LogLevel {\n disabled = 0,\n error = 1,\n lifecycle = 2,\n detail = 3,\n transport = 4,\n}\n\n// Helpers\n\nexport function deriveController(signal?: AbortSignal): [AbortController, AbortSignal] {\n const controller = new AbortController();\n return [controller, AbortSignal.any([controller.signal, signal].filter(Boolean) as AbortSignal[])];\n}\n\nexport function rejectOnSignal(promise: Promise<any>, signal: AbortSignal): Promise<Event['data']> {\n return Promise.race([\n promise,\n new Promise((resolve, reject) => {\n signal.addEventListener('abort', () => reject('RaceAborted:' + signal.reason), { once: true, signal });\n }),\n ]);\n}\n\n// Lib\n\nexport function wrpc({\n debug = LogLevel.disabled,\n name = (globalThis as any).importScripts ? 'RESPONDER' : 'CALLER',\n}: {\n debug?: LogLevel;\n name?: string;\n} = {}): {\n createResponder: <T extends Responders>(\n worker: WorkerLike,\n responders: T,\n ) => {\n responders: T;\n stop: () => void;\n };\n createCaller: <T extends Responders>(worker: WorkerLike, responders: T) => T;\n} {\n // Logger\n\n function logger(type: string) {\n return (level: number, ctx: Context | null, ...args: any[]) => {\n if (!debug || debug < level) return;\n const { id, message, ...restCtx } = ctx || {};\n console[type](name, message || '*', id || '*', ...args, restCtx);\n };\n }\n\n const log = logger('log');\n const logWarn = logger('warn');\n const logError = logger('error');\n\n // Service\n\n function send(worker: WorkerLike, ctx: Context, payload?: any) {\n log(LogLevel.transport, ctx, 'SEND', payload, typeof payload);\n worker.postMessage({ ctx, payload }, getTransferrable(payload));\n }\n\n //TODO Listener error handling\n function listen(\n worker: WorkerLike,\n context: Context | null,\n listener: (event: Event['data']) => void,\n signal: AbortSignal,\n once: boolean = false,\n ) {\n signal.addEventListener(\n 'abort',\n () => logWarn(LogLevel.detail, context, 'LISTEN SIGNAL ABORT', signal.reason),\n {\n signal,\n once: true,\n },\n );\n worker.addEventListener(\n 'message',\n ({ data }: Event) => {\n log(LogLevel.transport, context, 'LISTEN EVENT', data);\n // no context = listen all, or matching context\n if (!context || (data?.ctx?.message === context.message && data.ctx?.id === context.id)) listener(data);\n },\n { signal, once },\n );\n }\n\n function waitFor(\n worker: WorkerLike,\n ctx: Context,\n condition: (data: Event['data']) => boolean = (data) => true,\n signal: AbortSignal,\n ) {\n const [waitController, waitSignal] = deriveController(signal);\n\n return new Promise<Event['data']>((res) =>\n listen(\n worker,\n ctx,\n (data) => {\n if (!condition(data)) return;\n waitController.abort('WaitFinished');\n res(data);\n },\n waitSignal,\n ),\n );\n }\n\n // Responder\n\n //TODO Name arg\n function createResponder<T extends Responders>(\n worker: WorkerLike,\n responders: T,\n ): {\n responders: T;\n stop: () => void;\n } {\n const mainController = new AbortController();\n\n worker.addEventListener('messageerror', (e) => logError(LogLevel.error, null, 'MESSAGE ERROR', e), {\n signal: mainController.signal,\n });\n\n listen(\n worker,\n null,\n async ({ ctx, payload = {} }) => {\n if (!ctx || !ctx.message || !ctx.id || ctx.abort || ctx.ack) return; // ignore invalid and service messages\n try {\n const responder = responders[ctx.message];\n\n if (!responder) throw new Error('Unknown responder', { cause: ctx });\n\n const [subController, subSignal] = deriveController(mainController.signal);\n\n // always recover signal so that promises also can abort\n if (ctx.signalName) payload[ctx.signalName] = subSignal;\n\n const iterator = responder(payload); // no await!\n\n log(LogLevel.lifecycle, ctx, 'START', iterator);\n\n // PROMISE\n\n // https://stackoverflow.com/a/19660350/5125659 the only right way is to call function and see what it returned\n if (!iterator[Symbol.asyncIterator] && !iterator[Symbol.iterator]) {\n // Errors are caught in generic handler\n send(worker, { ...ctx, promise: true }, await rejectOnSignal(iterator, subSignal));\n log(LogLevel.detail, ctx, 'PROMISE RESOLVED');\n return;\n }\n\n // GENERATOR\n\n try {\n // Listen outside the loop to react immediately\n // Could be a wait for since abort is a one-off, but listeners are disposed by signal anyway, and we don't need to block the async thread\n listen(\n worker,\n ctx,\n (data) => {\n if (!data.ctx.abort) return;\n subController.abort(data.ctx.abort);\n logWarn(LogLevel.lifecycle, ctx, 'GOT ABORT');\n },\n subSignal,\n );\n\n let res: any;\n let nextPayload: any = payload; // does not matter, will be ignored anyway\n do {\n res = await rejectOnSignal(iterator.next(nextPayload), subSignal);\n\n send(worker, { ...ctx, done: res.done }, res.value);\n\n if (res.done) {\n subController.abort('Done');\n break;\n }\n\n const nextData = await rejectOnSignal(\n waitFor(worker, ctx, (data) => !!data.ctx.ack, subSignal),\n subSignal,\n );\n nextPayload = nextData.payload;\n\n log(LogLevel.detail, ctx, 'LOOP ACK');\n } while (!res.done && !subSignal.aborted);\n } catch (e) {\n if (!subSignal.aborted && !e.message.includes('RaceAborted')) {\n logError(LogLevel.error, ctx, 'LOOP ERROR', e);\n send(worker, { ...ctx, error: true, done: true }, e);\n }\n } finally {\n log(LogLevel.detail, ctx, 'LOOP DONE');\n }\n } catch (e) {\n // Generator-specific error handling\n logError(LogLevel.error, ctx, 'ERROR', e);\n send(worker, { ...ctx, error: true, done: true }, e); // Generic error handling\n } finally {\n log(LogLevel.detail, ctx, 'DONE');\n }\n },\n mainController.signal,\n );\n\n function stop() {\n mainController.abort('Stop');\n }\n\n return { responders, stop }; // responders needed for typing\n }\n\n // Caller\n\n //TODO Name arg\n function createCaller<T extends Responders>(worker: WorkerLike, responders: T): T {\n worker.addEventListener('messageerror', (e) => logError(LogLevel.error, null, 'MESSAGE ERROR', e));\n\n //TODO Stop\n\n return new Proxy<T>(responders as any, {\n get(target, prop, receiver) {\n function gen(payload: any) {\n const ctx: Context = { message: prop.toString(), id: crypto.randomUUID() };\n\n let signal: AbortSignal = null as any;\n\n if (payload && Object.is(payload, Object(payload))) {\n for (const [key, value] of Object.entries(payload)) {\n if (value instanceof AbortSignal) {\n log(LogLevel.detail, ctx, 'FOUND SIGNAL', key);\n ctx.signalName = key;\n signal = value;\n payload[key] = null; // will be recovered in responder\n }\n }\n }\n\n const [mainController, mainSignal] = deriveController(signal);\n\n const cancel = (notify: boolean, reason: string = '') => {\n if (mainController.signal.aborted) return;\n logWarn(LogLevel.lifecycle, ctx, 'CANCEL', reason, notify);\n if (reason) mainController.abort(reason); // stop listening\n if (notify) send(worker, { ...ctx, abort: reason }); // worker will break & acknowledge, but it's not important\n };\n\n // Generator\n\n const [promiseController, promiseSignal] = deriveController(mainSignal);\n\n const promise = waitFor(\n worker,\n ctx,\n (data) => !!data.ctx.promise || !!data.ctx.error,\n promiseSignal,\n ).then(({ ctx, payload }) => {\n if (ctx.error) {\n logError(LogLevel.error, ctx, 'PROMISE ERROR', payload);\n throw payload;\n }\n log(LogLevel.lifecycle, ctx, 'PROMISE RESOLVED', payload);\n return payload;\n });\n\n async function* generator() {\n let done = false;\n try {\n // React immediately\n mainSignal.addEventListener('abort', () => cancel(true, mainSignal.reason), {\n once: true,\n signal: mainSignal,\n });\n\n log(LogLevel.detail, ctx, 'GENERATOR START');\n promiseController.abort('GeneratorUsed');\n\n do {\n const data = await rejectOnSignal(\n waitFor(worker, ctx, undefined, mainSignal),\n mainSignal,\n );\n\n const { ctx: ctxIn, payload: value } = data;\n\n log(LogLevel.lifecycle, ctx, 'EVENT', mainSignal.aborted, ctxIn, value);\n\n if (ctxIn.promise) {\n // && !ctxIn.error\n // cancel(false, 'Resolved');\n // return payloadIn;\n logError(LogLevel.error, ctx, 'ERROR', 'Unexpected promise in generator', data);\n throw new Error('Unexpected promise in generator', { cause: data });\n }\n\n if (ctxIn.error) {\n logError(LogLevel.error, ctx, 'ERROR', data);\n cancel(false, 'Error');\n throw value;\n }\n\n if (ctxIn.done) {\n done = true;\n return value;\n } else {\n const nextPayload = yield value;\n if (!mainSignal.aborted) send(worker, { ...ctx, ack: true }, nextPayload);\n }\n } while (!mainSignal.aborted);\n } finally {\n // prevent further cycles in worker once main is closed/cancelled/aborted\n log(LogLevel.detail, ctx, 'DONE');\n cancel(true, mainSignal.aborted ? mainSignal.reason : done ? 'Done' : 'Break');\n }\n }\n\n const iterator = generator();\n\n send(worker, ctx, payload);\n\n // FIXME Nasty hack, return both iterator and promise\n // Consuming code will get both, but IDE should show only proper type\n // Consuming code will likely use only one except someone calls `for await ... of await` which is not useful in any case\n iterator['then'] = promise.then.bind(promise);\n iterator['catch'] = promise.catch.bind(promise);\n iterator['finally'] = promise.finally.bind(promise);\n return iterator;\n }\n\n //TODO mimic-function(gen, fn); but no access to original\n Object.defineProperty(gen, 'name', { value: prop.toString() });\n\n (target as any)[prop] = gen; // so that fn is visible after use, maybe not needed\n\n return gen;\n },\n }) as T;\n }\n\n return { createResponder, createCaller };\n}\n"],"names":["LogLevel","ctx","payload"],"mappings":"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"}
1
+ {"version":3,"file":"wrpc.js","names":[],"sources":["../src/wrpc.ts"],"sourcesContent":["import { getTransferrable } from './workerUtils';\n\n// Types\n\nexport type Responders = { [key: string]: (arg?: any) => Generator<any> | AsyncGenerator<any> | Promise<any> | any };\n\nexport type Context = {\n message: string;\n id: string;\n signalName?: string;\n done?: boolean;\n abort?: string;\n error?: boolean;\n ack?: boolean;\n promise?: boolean;\n};\n\nexport type Event = MessageEvent<{ ctx: Context; payload: any | Error }>;\n\nexport type WorkerLike = {\n postMessage: (payload: any, transferable: any[]) => void;\n addEventListener: (\n event: string,\n listener: (e: any) => void,\n options?: { signal?: AbortSignal; once?: boolean },\n ) => void;\n removeEventListener: (event: string, listener: (e: any) => void) => void;\n} & any;\n\nexport enum LogLevel {\n disabled = 0,\n error = 1,\n lifecycle = 2,\n detail = 3,\n transport = 4,\n}\n\n// Helpers\n\nexport function deriveController(signal?: AbortSignal): [AbortController, AbortSignal] {\n const controller = new AbortController();\n return [controller, AbortSignal.any([controller.signal, signal].filter(Boolean) as AbortSignal[])];\n}\n\nexport function rejectOnSignal(promise: Promise<any>, signal: AbortSignal): Promise<Event['data']> {\n if (signal.aborted) return Promise.reject('RaceAborted:' + signal.reason);\n return Promise.race([\n promise,\n new Promise((resolve, reject) => {\n /*\n * NOTE: no { signal } disposal here — abort algorithms (which remove signal-disposed\n * listeners) run BEFORE the abort event is dispatched, so a listener disposed by the very\n * signal it listens to would be removed without ever firing. { once } self-removes on fire.\n */\n signal.addEventListener('abort', () => reject('RaceAborted:' + signal.reason), { once: true });\n }),\n ]);\n}\n\n/*\n * Names that must never be used as responder method names: `then`/`catch`/`finally` would make the\n * caller thenable (`await caller` would dispatch a bogus RPC), and `toJSON`/`toString`/`constructor`\n * must keep resolving to the plain-object natives so the caller stays introspectable.\n */\nexport const RESERVED_NAMES: string[] = ['then', 'catch', 'finally', 'toJSON', 'toString', 'constructor'];\n\nfunction validateResponders(responders: Responders) {\n for (const key of Object.keys(responders)) {\n if (RESERVED_NAMES.includes(key)) throw new Error(`Responder name \"${key}\" is reserved`);\n }\n}\n\n// Lib\n\nexport function wrpc({\n debug = LogLevel.disabled,\n name = (globalThis as any).importScripts ? 'RESPONDER' : 'CALLER',\n}: {\n debug?: LogLevel;\n name?: string;\n} = {}): {\n createResponder: <T extends Responders>(\n worker: WorkerLike,\n responders: T,\n ) => {\n responders: T;\n stop: () => void;\n };\n createCaller: <T extends Responders>(worker: WorkerLike, responders: T) => T;\n} {\n // Logger\n\n function logger(type: string) {\n return (level: number, ctx: Context | null, ...args: any[]) => {\n if (!debug || debug < level) return;\n const { id, message, ...restCtx } = ctx ?? {};\n console[type](name, message ?? '*', id ?? '*', ...args, restCtx);\n };\n }\n\n const log = logger('log');\n const logWarn = logger('warn');\n const logError = logger('error');\n\n // Service\n\n function send(worker: WorkerLike, ctx: Context, payload?: any) {\n log(LogLevel.transport, ctx, 'SEND', payload, typeof payload);\n worker.postMessage({ ctx, payload }, getTransferrable(payload));\n }\n\n //TODO Listener error handling\n function listen(\n worker: WorkerLike,\n context: Context | null,\n listener: (event: Event['data']) => void,\n signal: AbortSignal,\n once: boolean = false,\n ) {\n // no { signal } disposal — see the note in rejectOnSignal; { once } self-removes on fire\n signal.addEventListener(\n 'abort',\n () => logWarn(LogLevel.detail, context, 'LISTEN SIGNAL ABORT', signal.reason),\n {\n once: true,\n },\n );\n worker.addEventListener(\n 'message',\n ({ data }: Event) => {\n log(LogLevel.transport, context, 'LISTEN EVENT', data);\n // no context = listen all, or matching context\n if (!context || (data?.ctx?.message === context.message && data.ctx?.id === context.id)) listener(data);\n },\n { signal, once },\n );\n }\n\n function waitFor(\n worker: WorkerLike,\n ctx: Context,\n condition: (data: Event['data']) => boolean = (data) => true,\n signal: AbortSignal,\n ) {\n const [waitController, waitSignal] = deriveController(signal);\n\n return new Promise<Event['data']>((resolve) =>\n listen(\n worker,\n ctx,\n (data) => {\n if (!condition(data)) return;\n waitController.abort('WaitFinished');\n resolve(data);\n },\n waitSignal,\n ),\n );\n }\n\n // Responder\n\n //TODO Name arg\n function createResponder<T extends Responders>(\n worker: WorkerLike,\n responders: T,\n ): {\n responders: T;\n stop: () => void;\n } {\n validateResponders(responders);\n\n const mainController = new AbortController();\n\n worker.addEventListener('messageerror', (e) => logError(LogLevel.error, null, 'MESSAGE ERROR', e), {\n signal: mainController.signal,\n });\n\n listen(\n worker,\n null,\n async ({ ctx, payload = {} }) => {\n if (!ctx?.message || !ctx.id || ctx.abort || ctx.ack) return; // ignore invalid and service messages\n try {\n const responder = responders[ctx.message];\n\n if (!responder) throw new Error('Unknown responder', { cause: ctx });\n\n const [subController, subSignal] = deriveController(mainController.signal);\n\n // always recover signal so that promises also can abort\n if (ctx.signalName) payload[ctx.signalName] = subSignal;\n\n const iterator = responder.call(responders, payload); // no await! `.call` so responders can use `this`\n\n log(LogLevel.lifecycle, ctx, 'START', iterator);\n\n // PROMISE\n\n // https://stackoverflow.com/a/19660350/5125659 the only right way is to call function and see what it returned\n if (!iterator[Symbol.asyncIterator] && !iterator[Symbol.iterator]) {\n // Errors are caught in generic handler\n send(worker, { ...ctx, promise: true }, await rejectOnSignal(iterator, subSignal));\n log(LogLevel.detail, ctx, 'PROMISE RESOLVED');\n return;\n }\n\n // GENERATOR\n\n let res: any;\n try {\n /*\n * Listen outside the loop to react immediately\n * Could be a wait for since abort is a one-off, but listeners are disposed by signal anyway, and we don't need to block the async thread\n */\n listen(\n worker,\n ctx,\n (data) => {\n if (!data.ctx.abort) return;\n subController.abort(data.ctx.abort);\n logWarn(LogLevel.lifecycle, ctx, 'GOT ABORT');\n },\n subSignal,\n );\n\n let nextPayload: any = payload; // does not matter, will be ignored anyway\n do {\n res = await rejectOnSignal(iterator.next(nextPayload), subSignal);\n\n send(worker, { ...ctx, done: res.done }, res.value);\n\n if (res.done) {\n subController.abort('Done');\n break;\n }\n\n const nextData = await rejectOnSignal(\n waitFor(worker, ctx, (data) => !!data.ctx.ack, subSignal),\n subSignal,\n );\n nextPayload = nextData.payload;\n\n log(LogLevel.detail, ctx, 'LOOP ACK');\n } while (!res.done && !subSignal.aborted);\n } catch (e) {\n if (!subSignal.aborted && !e.message.includes('RaceAborted')) {\n logError(LogLevel.error, ctx, 'LOOP ERROR', e);\n send(worker, { ...ctx, error: true, done: true }, e);\n }\n } finally {\n /*\n * Run the generator's own finally/cleanup when the loop ended early (abort, break,\n * error). Best-effort, not awaited: if the generator is suspended on an await that\n * never settles, the queued return() never executes — cooperative cancellation via\n * the recovered AbortSignal remains the responder's own job.\n */\n if (!res?.done) {\n try {\n void Promise.resolve(iterator.return?.(undefined)).catch(() => {});\n } catch {\n // sync generator threw during cleanup\n }\n }\n log(LogLevel.detail, ctx, 'LOOP DONE');\n }\n } catch (e) {\n // Generator-specific error handling\n logError(LogLevel.error, ctx, 'ERROR', e);\n send(worker, { ...ctx, error: true, done: true }, e); // Generic error handling\n } finally {\n log(LogLevel.detail, ctx, 'DONE');\n }\n },\n mainController.signal,\n );\n\n function stop() {\n mainController.abort('Stop');\n }\n\n return { responders, stop }; // responders needed for typing\n }\n\n // Caller\n\n //TODO Name arg\n function createCaller<T extends Responders>(worker: WorkerLike, responders: T): T {\n validateResponders(responders);\n\n worker.addEventListener('messageerror', (e) => logError(LogLevel.error, null, 'MESSAGE ERROR', e));\n\n //TODO Stop\n\n return new Proxy<T>(responders as any, {\n get(target, prop, receiver) {\n /*\n * Reserved names and symbols fall through to the plain object: `then` stays undefined so\n * the caller is not thenable (`await caller` would otherwise dispatch a bogus RPC), and\n * `toString`/`constructor` resolve to the inherited natives so the caller can be\n * introspected. validateResponders() forbids creating responders with these names.\n */\n if (typeof prop !== 'string' || RESERVED_NAMES.includes(prop)) {\n return Reflect.get(target, prop, receiver);\n }\n\n function gen(payload: any) {\n const ctx: Context = { message: prop.toString(), id: crypto.randomUUID() };\n\n let signal: AbortSignal = null as any;\n\n if (payload && Object.is(payload, Object(payload))) {\n for (const [key, value] of Object.entries(payload)) {\n if (value instanceof AbortSignal) {\n log(LogLevel.detail, ctx, 'FOUND SIGNAL', key);\n ctx.signalName = key;\n signal = value;\n payload[key] = null; // will be recovered in responder\n }\n }\n }\n\n const [mainController, mainSignal] = deriveController(signal);\n\n const cancel = (notify: boolean, reason: string = '') => {\n if (mainController.signal.aborted) return;\n logWarn(LogLevel.lifecycle, ctx, 'CANCEL', reason, notify);\n if (reason) mainController.abort(reason); // stop listening\n if (notify) send(worker, { ...ctx, abort: reason }); // worker will break & acknowledge, but it's not important\n };\n\n // Generator\n\n const [promiseController, promiseSignal] = deriveController(mainSignal);\n\n const promise = waitFor(worker, ctx, undefined, promiseSignal).then((data) => {\n const { ctx: ctxIn, payload } = data;\n if (ctxIn.error) {\n logError(LogLevel.error, ctx, 'PROMISE ERROR', payload);\n throw payload;\n }\n if (ctxIn.promise) {\n log(LogLevel.lifecycle, ctx, 'PROMISE RESOLVED', payload);\n return payload;\n }\n // a generator frame means the method was awaited instead of iterated\n logError(LogLevel.error, ctx, 'ERROR', 'Cannot await a generator method', data);\n cancel(true, 'AwaitedGenerator');\n throw new Error('Cannot await a generator method, use `for await ... of`', {\n cause: data,\n });\n });\n\n /*\n * Keep at least one rejection handler attached: the promise rejects unobserved when a\n * generator's first frame arrives before iteration starts, or when a fire-and-forget\n * promise call errors. Awaiters get their own branch via the bound then/catch below.\n */\n promise.catch(() => {});\n\n async function* generator() {\n let done = false;\n try {\n // React immediately (no { signal } disposal — see the note in rejectOnSignal)\n mainSignal.addEventListener('abort', () => cancel(true, mainSignal.reason), {\n once: true,\n });\n\n log(LogLevel.detail, ctx, 'GENERATOR START');\n promiseController.abort('GeneratorUsed');\n\n do {\n const data = await rejectOnSignal(\n waitFor(worker, ctx, undefined, mainSignal),\n mainSignal,\n );\n\n const { ctx: ctxIn, payload: value } = data;\n\n log(LogLevel.lifecycle, ctx, 'EVENT', mainSignal.aborted, ctxIn, value);\n\n if (ctxIn.promise) {\n /*\n * && !ctxIn.error\n * cancel(false, 'Resolved');\n * return payloadIn;\n */\n logError(LogLevel.error, ctx, 'ERROR', 'Unexpected promise in generator', data);\n throw new Error('Unexpected promise in generator', { cause: data });\n }\n\n if (ctxIn.error) {\n logError(LogLevel.error, ctx, 'ERROR', data);\n cancel(false, 'Error');\n throw value;\n }\n\n if (ctxIn.done) {\n done = true;\n return value;\n } else {\n const nextPayload = yield value;\n if (!mainSignal.aborted) send(worker, { ...ctx, ack: true }, nextPayload);\n }\n } while (!mainSignal.aborted);\n } finally {\n // prevent further cycles in worker once main is closed/cancelled/aborted\n log(LogLevel.detail, ctx, 'DONE');\n cancel(true, mainSignal.aborted ? mainSignal.reason : done ? 'Done' : 'Break');\n }\n }\n\n const iterator = generator();\n\n send(worker, ctx, payload);\n\n /*\n * FIXME Nasty hack, return both iterator and promise\n * Consuming code will get both, but IDE should show only proper type\n * Consuming code will likely use only one except someone calls `for await ... of await` which is not useful in any case\n */\n iterator['then'] = promise.then.bind(promise);\n iterator['catch'] = promise.catch.bind(promise);\n iterator['finally'] = promise.finally.bind(promise);\n return iterator;\n }\n\n //TODO mimic-function(gen, fn); but no access to original\n Object.defineProperty(gen, 'name', { value: prop.toString() });\n\n (target as any)[prop] = gen; // so that fn is visible after use, maybe not needed\n\n return gen;\n },\n }) as T;\n }\n\n return { createResponder, createCaller };\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,14 +1,12 @@
1
1
  {
2
2
  "name": "@kirill.konshin/worker",
3
3
  "description": "Worker Utilities",
4
- "version": "0.0.2",
4
+ "version": "0.0.3",
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 -----": "",
@@ -37,5 +35,17 @@
37
35
  "main": "./dist/index.js",
38
36
  "module": "./dist/index.js",
39
37
  "types": "./dist/index.d.ts",
40
- "peerDependenciesMeta": {}
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "https://github.com/kirill-konshin/utils.git",
41
+ "directory": "packages/worker"
42
+ },
43
+ "nx": {
44
+ "tags": [
45
+ "platform:worker"
46
+ ]
47
+ },
48
+ "files": [
49
+ "dist"
50
+ ]
41
51
  }
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
- }
@@ -1,22 +0,0 @@
1
- vite v7.0.6 building SSR bundle for production...
2
- - ctix 'create' mode start, ...
3
- ✔ /home/runner/work/utils/utils/packages/worker/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
- ✓ 4 modules transformed.
12
- rendering chunks...
13
-
14
- [vite:dts] Start generate declaration files...
15
- dist/index.js 0.38 kB │ map: 0.09 kB
16
- dist/workerUtils.js 0.97 kB │ map: 1.83 kB
17
- dist/worker.js 7.06 kB │ map: 16.69 kB
18
- dist/wrpc.js 8.73 kB │ map: 20.98 kB
19
- [vite:dts] Declaration files built in 1924ms.
20
-
21
- ✓ built in 5.20s
22
- Updated package.json with exports
@@ -1,97 +0,0 @@
1
- - ctix 'create' mode start, ...
2
- ✔ /home/runner/work/utils/utils/packages/worker/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
-  RUN  v3.2.4 /home/runner/work/utils/utils/packages/worker
11
- Coverage enabled with v8
12
-
13
- stderr | src/wrpc.test.ts
14
- Using an object as a third argument is deprecated. Vitest 4 will throw an error if the third argument is not a timeout number. Please use the second argument for options. See more at https://vitest.dev/guide/migration
15
-
16
- stdout | src/wrpc.test.ts > WRPC > for await ... of return yield
17
- DATA 1
18
-
19
- stdout | src/wrpc.test.ts > WRPC > for await ... of return yield
20
- DATA 2
21
-
22
- stdout | src/wrpc.test.ts > WRPC > for await ... of return yield
23
- DATA 3
24
-
25
- stdout | src/wrpc.test.ts > WRPC > for await ... of return yield
26
- DATA { value: 1, done: false }
27
-
28
- ✓ src/worker.test.ts (8 tests) 14ms
29
- stdout | src/wrpc.test.ts > WRPC > for await ... of return yield
30
- DATA { value: 2, done: false }
31
-
32
- stdout | src/wrpc.test.ts > WRPC > for await ... of return yield
33
- DATA { value: 3, done: false }
34
-
35
- stdout | src/wrpc.test.ts > WRPC > for await ... of return yield
36
- DATA { value: undefined, done: true }
37
-
38
- stdout | src/wrpc.test.ts > WRPC > for await ... of separate return & yield
39
- DATA { progress: 0 }
40
-
41
- stdout | src/wrpc.test.ts > WRPC > for await ... of separate return & yield
42
- DATA { progress: 0.5 }
43
-
44
- stdout | src/wrpc.test.ts > WRPC > for await ... of separate return & yield
45
- DATA { progress: 1, aborted: false }
46
-
47
- stdout | src/wrpc.test.ts > WRPC > do ... while
48
- DATA { value: { progress: 0 }, done: false }
49
-
50
- stdout | src/wrpc.test.ts > WRPC > do ... while
51
- DATA { value: { progress: 0.5 }, done: false }
52
-
53
- stdout | src/wrpc.test.ts > WRPC > do ... while
54
- DATA { value: { progress: 1, aborted: false }, done: false }
55
-
56
- stdout | src/wrpc.test.ts > WRPC > do ... while
57
- DATA { value: 'foo', done: true }
58
-
59
- stdout | src/wrpc.test.ts > WRPC > break
60
- DATA { progress: 0 }
61
-
62
- stdout | src/wrpc.test.ts > WRPC > abort
63
- DATA { progress: 0 }
64
-
65
- ✓ src/wrpc.test.ts (13 tests | 1 skipped) 705ms
66
- ✓ WRPC > stop  503ms
67
- Failed to convert coverage for file:///home/runner/work/utils/utils/packages/worker/src/wrpc.ts.
68
- TypeError: Cannot read properties of undefined (reading 'endCol')
69
- at range.sliceRange (file:///home/runner/work/utils/utils/node_modules/@vitest/coverage-v8/dist/provider.js:556:55)
70
- at CovSource.offsetToOriginalRelative (file:///home/runner/work/utils/utils/node_modules/@vitest/coverage-v8/dist/provider.js:1987:20)
71
- at V8ToIstanbul._maybeRemapStartColEndCol (file:///home/runner/work/utils/utils/node_modules/@vitest/coverage-v8/dist/provider.js:2396:93)
72
- at file:///home/runner/work/utils/utils/node_modules/@vitest/coverage-v8/dist/provider.js:2305:61
73
- at Array.forEach (<anonymous>)
74
- at file:///home/runner/work/utils/utils/node_modules/@vitest/coverage-v8/dist/provider.js:2303:21
75
- at Array.forEach (<anonymous>)
76
- at V8ToIstanbul.applyCoverage (file:///home/runner/work/utils/utils/node_modules/@vitest/coverage-v8/dist/provider.js:2302:13)
77
- at V8CoverageProvider.v8ToIstanbul (file:///home/runner/work/utils/utils/node_modules/@vitest/coverage-v8/dist/provider.js:2732:14)
78
- at async file:///home/runner/work/utils/utils/node_modules/@vitest/coverage-v8/dist/provider.js:2825:23
79
-
80
-  Test Files  2 passed (2)
81
-  Tests  20 passed | 1 skipped (21)
82
-  Start at  03:26:56
83
-  Duration  1.27s (transform 170ms, setup 0ms, collect 229ms, tests 720ms, environment 1ms, prepare 228ms)
84
-
85
-  % Coverage report from v8
86
- -----------------|---------|----------|---------|---------|-------------------
87
- File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
88
- -----------------|---------|----------|---------|---------|-------------------
89
- All files | 97.49 | 91.74 | 92.68 | 97.49 |
90
- index.ts | 0 | 0 | 0 | 0 | 1-3
91
- worker.ts | 100 | 98.71 | 100 | 100 | 57
92
- workerUtils.ts | 100 | 75 | 100 | 100 | 17,22,26,29
93
- wrpc.fixture.ts | 81.81 | 78.57 | 75 | 81.81 | 70,73-76,95-99
94
- wrpc.ts | 100 | 100 | 100 | 100 |
95
- -----------------|---------|----------|---------|---------|-------------------
96
- Updated package.json with exports
97
- Updated package.json with exports
package/CHANGELOG.md DELETED
@@ -1,7 +0,0 @@
1
- # @kirill.konshin/worker
2
-
3
- ## 0.0.2
4
-
5
- ### Patch Changes
6
-
7
- - 63fdba8: Divided core to browser/node/worker-specific packages, CTIX upgrade, etc.
package/dist/index.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;"}
package/src/index.ts DELETED
@@ -1,3 +0,0 @@
1
- export * from './worker';
2
- export * from './workerUtils';
3
- export * from './wrpc';
@@ -1,197 +0,0 @@
1
- import { expect, describe, test, vi } from 'vitest';
2
- import { RespondersBase, WorkerDialog } from './worker';
3
- import EventEmitter from 'node:events';
4
-
5
- // Prepare
6
-
7
- //TODO @vitest/web-worker can ve used, coverage is collected, but this gives more direct access to worker internals
8
- // class WorkerPeer extends EventEmitter implements WorkerLike {
9
- class WorkerPeer extends EventEmitter {
10
- peer: WorkerPeer;
11
-
12
- setPeer(peer: WorkerPeer) {
13
- this.peer = peer;
14
- }
15
-
16
- postMessage(data: any) {
17
- this.peer?.emit('message', { data });
18
- }
19
-
20
- addEventListener(event: string, listener: (e: any) => void, options) {
21
- this[options['once'] ? 'once' : 'on'](event, listener);
22
- options?.signal?.addEventListener('abort', () => this.off(event, listener));
23
- }
24
-
25
- removeEventListener(event: string, listener: (e: any) => void) {
26
- this.off(event, listener);
27
- }
28
- }
29
-
30
- class Responders extends RespondersBase<Responders> {
31
- lastContext: any; // for testing purposes
32
-
33
- test = this.create(
34
- 'test',
35
- async (
36
- { n, abort = false, buf }: { n: number; abort?: boolean; buf?: ArrayBuffer }, // should have all possible combinations for strong typing
37
- ctx,
38
- ) => {
39
- this.lastContext = ctx;
40
-
41
- const res: number[] = [];
42
-
43
- let aborted = false;
44
- let progress = 0;
45
-
46
- ctx.listen(({ abort }) => {
47
- if (abort) aborted = true;
48
- });
49
-
50
- for (const i of Array(n).keys()) {
51
- if (aborted) break;
52
- progress = (i + 1) / n;
53
- ctx.send({ progress });
54
- res.push(i);
55
- }
56
-
57
- return { res, progress, aborted };
58
- },
59
- );
60
-
61
- error = this.create(
62
- 'error',
63
- async (
64
- _, // should have all possible combinations for strong typing
65
- ctx,
66
- ) => {
67
- this.lastContext = ctx;
68
- throw new Error('Test');
69
- },
70
- );
71
- }
72
-
73
- const createWorker = () => {
74
- const mainWorker = new WorkerPeer();
75
- const mainDialog = new WorkerDialog(
76
- mainWorker as never as Worker,
77
- {} as WorkerDialog<Responders>['responders'],
78
- 'Main',
79
- );
80
-
81
- const worker = new WorkerPeer();
82
- const workerDialog = new WorkerDialog(worker as never as Worker, new Responders(), 'Worker');
83
-
84
- mainWorker.setPeer(worker);
85
- worker.setPeer(mainWorker);
86
-
87
- return { mainWorker, mainDialog, worker, workerDialog };
88
- };
89
-
90
- // Tests
91
-
92
- describe('Worker', async () => {
93
- test('simple', async () => {
94
- const { mainDialog, workerDialog } = createWorker();
95
-
96
- const l = vi.fn();
97
- let mctx;
98
-
99
- const result = await mainDialog.withMessage('test').fetch({ n: 3, buf: new ArrayBuffer() }, (ctx) => {
100
- mctx = ctx;
101
- ctx.listen(l);
102
- });
103
-
104
- expect(l).toBeCalledTimes(4); // 3x progress, last
105
- expect(result).toStrictEqual({ res: [0, 1, 2], progress: 1, aborted: false });
106
-
107
- expect(mainDialog.contexts.size).toBe(0);
108
- expect(mctx.debugInfo.closed).toBe(true);
109
-
110
- expect(workerDialog.contexts.size).toBe(2);
111
- expect(workerDialog.contexts.values().next().value.debugInfo).toStrictEqual({
112
- id: '*',
113
- parentId: undefined,
114
- message: 'test',
115
- closed: false,
116
- type: 'response',
117
- listeners: 1,
118
- });
119
- expect(workerDialog.responders.lastContext.debugInfo).toStrictEqual({
120
- id: '1',
121
- parentId: '*',
122
- message: 'test',
123
- closed: true,
124
- type: 'response',
125
- listeners: 0,
126
- });
127
- });
128
-
129
- test('abort', async () => {
130
- const { mainDialog } = createWorker();
131
-
132
- const result = await mainDialog.withMessage('test').fetch({ n: 3 }, (ctx) => {
133
- ctx.listen(() => {
134
- ctx.send({ abort: true });
135
- });
136
- });
137
-
138
- expect(result).toStrictEqual({ res: [0], progress: 0.3333333333333333, aborted: true });
139
- });
140
-
141
- test('faulty listener', async () => {
142
- const { mainDialog } = createWorker();
143
-
144
- await mainDialog.withMessage('test').fetch({ n: 3 }, (ctx) => {
145
- ctx.listen(() => {
146
- throw new Error('Listener');
147
- });
148
- });
149
-
150
- expect.assertions(0); // nothing happened
151
- });
152
-
153
- test('error', async () => {
154
- const { mainDialog } = createWorker();
155
- await expect(async () => mainDialog.withMessage('error').fetch()).rejects.toThrowError('Test');
156
- });
157
-
158
- test('close', async () => {
159
- const { mainDialog, workerDialog } = createWorker();
160
-
161
- mainDialog.close();
162
- workerDialog.close();
163
-
164
- expect(mainDialog.contexts.size).toBe(0);
165
- expect(workerDialog.contexts.size).toBe(0);
166
- });
167
-
168
- test('empty message', async () => {
169
- expect(() => createWorker().mainDialog.withMessage(undefined as any)).toThrowError(
170
- 'New context must have message',
171
- );
172
- });
173
-
174
- test('empty ID', async () => {
175
- const { mainDialog } = createWorker();
176
- const first = mainDialog.withMessage('test');
177
- expect(() => first.withMessage('error', '')).toThrowError('ID is required');
178
- });
179
-
180
- test('nesting', async () => {
181
- const { mainDialog } = createWorker();
182
- const first = mainDialog.withMessage('test');
183
- const second = first.withMessage('error', 'x');
184
- const third = second.withMessage('error');
185
- expect(mainDialog.contexts.size).toBe(3);
186
- expect(first.info).toBe('Main:*/1:test[open,request]');
187
- first.close();
188
- expect(mainDialog.contexts.size).toBe(0);
189
- expect(first.debugInfo.closed).toBe(true);
190
- expect(second.debugInfo.closed).toBe(true);
191
- expect(third.debugInfo.closed).toBe(true);
192
- expect(first.info).toBe('Main:*/1:test[closed,request]'); // id bumped from 0
193
- expect(second.info).toBe('Main:1/x:error[closed,request]'); // new id
194
- expect(third.info).toBe('Main:x/x:error[closed,request]'); // id kept
195
- await expect(() => first.fetch({ n: 1 })).rejects.toThrowError(/Context 1 is closed/);
196
- });
197
- });