easy-web-worker 7.0.6 โ†’ 8.0.1

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.
Files changed (60) hide show
  1. package/EasyWebWorker.cjs +1 -0
  2. package/EasyWebWorker.d.ts +2 -2
  3. package/EasyWebWorker.js +1 -1
  4. package/EasyWebWorker.mjs +1 -0
  5. package/EasyWebWorkerMessage.cjs +1 -0
  6. package/EasyWebWorkerMessage.d.ts +2 -2
  7. package/EasyWebWorkerMessage.js +1 -1
  8. package/EasyWebWorkerMessage.mjs +1 -0
  9. package/README.md +670 -734
  10. package/StaticEasyWebWorker.cjs +1 -0
  11. package/StaticEasyWebWorker.d.ts +1 -1
  12. package/StaticEasyWebWorker.js +1 -1
  13. package/StaticEasyWebWorker.mjs +1 -0
  14. package/buildWorker.cjs +1 -0
  15. package/buildWorker.d.ts +7 -0
  16. package/buildWorker.js +1 -0
  17. package/buildWorker.mjs +1 -0
  18. package/bundle.cjs +1 -0
  19. package/bundle.js +1 -1
  20. package/bundle.mjs +1 -0
  21. package/createBlobWorker.cjs +2 -0
  22. package/createBlobWorker.d.ts +1 -0
  23. package/createBlobWorker.js +2 -1
  24. package/createBlobWorker.mjs +2 -0
  25. package/createEasyWebWorker.cjs +1 -0
  26. package/createEasyWebWorker.js +1 -1
  27. package/createEasyWebWorker.mjs +1 -0
  28. package/createStaticEasyWebWorker.cjs +1 -0
  29. package/createStaticEasyWebWorker.js +1 -1
  30. package/createStaticEasyWebWorker.mjs +1 -0
  31. package/createWorker.cjs +4 -0
  32. package/createWorker.d.ts +53 -0
  33. package/createWorker.js +4 -0
  34. package/createWorker.mjs +4 -0
  35. package/defineWorker.cjs +1 -0
  36. package/defineWorker.d.ts +31 -0
  37. package/defineWorker.js +1 -0
  38. package/defineWorker.mjs +1 -0
  39. package/getDefineWorkerTemplate.cjs +1 -0
  40. package/getDefineWorkerTemplate.d.ts +7 -0
  41. package/getDefineWorkerTemplate.js +1 -0
  42. package/getDefineWorkerTemplate.mjs +1 -0
  43. package/getWorkerTemplate.cjs +1 -0
  44. package/getWorkerTemplate.d.ts +3 -7
  45. package/getWorkerTemplate.js +1 -1
  46. package/getWorkerTemplate.mjs +1 -0
  47. package/index.d.ts +3 -0
  48. package/package.json +61 -68
  49. package/types.cjs +1 -0
  50. package/types.d.ts +132 -1
  51. package/types.js +1 -1
  52. package/types.mjs +0 -0
  53. package/uniqueId.cjs +1 -0
  54. package/uniqueId.js +1 -1
  55. package/uniqueId.mjs +1 -0
  56. package/unwrap.cjs +1 -0
  57. package/unwrap.d.ts +16 -0
  58. package/unwrap.js +1 -0
  59. package/unwrap.mjs +1 -0
  60. package/webpack.config.js +0 -86
package/README.md CHANGED
@@ -8,1093 +8,1045 @@
8
8
 
9
9
  <div align="center">
10
10
 
11
- **Real Web Workers. Simple API. Cancelable work.** ๐Ÿš€
11
+ ## **Web Workers are powerful. Now they're also easy.**
12
12
 
13
- _Heavy computation off the main thread โ€” without fighting the native Worker API._ โœจ
13
+ Let `easy-web-worker` handle everything between the threads.
14
+
15
+ You keep the power of native Web Workers without dealing with all the complexity that usually comes with them.
16
+
17
+ `easy-web-worker` handles the plumbing between both sides: message routing, request matching, promises, errors, cancellation, progress, transferable objects, lifecycle management, and worker pools.
14
18
 
15
19
  [![npm version](https://img.shields.io/npm/v/easy-web-worker.svg)](https://www.npmjs.com/package/easy-web-worker)
16
20
  [![Downloads](https://img.shields.io/npm/dm/easy-web-worker.svg)](https://www.npmjs.com/package/easy-web-worker)
17
21
  [![License](https://img.shields.io/github/license/johnny-quesada-developer/easy-web-worker)](https://github.com/johnny-quesada-developer/easy-web-worker/blob/main/LICENSE)
18
22
 
19
- [**Live Demo**](https://johnny-quesada-developer.github.io/easy-web-workers-example/) โ€ข [**Video Tutorial**](https://www.youtube.com/watch?v=CK-Uri9lDOE) โ€ข [**CodePen**](https://codepen.io/johnnynabetes/full/wvOvygW)
23
+ [**Website**](https://johnny-quesada-developer.github.io/easy-web-worker/) ยท [**Documentation**](https://johnny-quesada-developer.github.io/easy-web-worker/docs/) ยท [**Examples**](https://johnny-quesada-developer.github.io/easy-web-worker/examples/) ยท [**API reference**](https://johnny-quesada-developer.github.io/easy-web-worker/docs/api-reference/) ยท [**Video Tutorial**](https://www.youtube.com/watch?v=CK-Uri9lDOE)
24
+
25
+ Created by [Johnny Quesada](https://github.com/johnny-quesada-developer).
20
26
 
21
27
  </div>
22
28
 
23
29
  ---
24
30
 
25
- ## ๐ŸŽฏ The One-Liner
31
+ ## Start with a single worker
32
+
33
+ **Imagine defining your worker like this:**
26
34
 
27
35
  ```ts
28
- import { createEasyWebWorker } from 'easy-web-worker';
36
+ export const worker = defineWorker(() => ({
37
+ fibonacci,
38
+ }));
29
39
 
30
- const worker = createEasyWebWorker<number, number>(({ onMessage }) => {
31
- onMessage((message) => message.resolve(message.payload * 2));
32
- });
40
+ function fibonacci(n: number): number {
41
+ return n <= 1 ? n : fibonacci(n - 1) + fibonacci(n - 2);
42
+ }
33
43
  ```
34
44
 
35
- **That's it.** No separate worker file. No message-ID plumbing. No manual promise bridge. ๐Ÿงต
45
+ **And calling it like this:**
36
46
 
37
47
  ```ts
38
- const result = await worker.send(21);
48
+ import type { worker as MathWorker } from './math.worker';
49
+ import mathWorkerUrl from './math.worker?worker&url';
50
+
51
+ /**
52
+ * The type of the worker is inferred from it's declaration,
53
+ * so everything is typed on the main thread.
54
+ */
55
+ const mathWorker = createWorker<typeof MathWorker>(mathWorkerUrl);
39
56
 
40
- console.log(result); // 42
57
+ const result = await mathWorker.fibonacci(40); // 102334155
41
58
  ```
42
59
 
43
- Your code runs inside a **real native Web Worker**, while the main thread receives a `CancelablePromise`.
60
+ The computation runs inside a real native Web Worker. The page keeps scrolling, typing and animating while it runs.
61
+
62
+ No manual `postMessage`.
63
+ No message IDs.
64
+ No response matching.
65
+ No hand-written promise bridge.
66
+
67
+ Just functions.
68
+
69
+ > **[See it live โ†’](https://johnny-quesada-developer.github.io/easy-web-worker/#live)** Same task, two ways to run it. On the main thread the page freezes. In a Worker it never stops. It runs in your browser, with real Workers.
44
70
 
45
71
  ---
46
72
 
47
- ## ๐Ÿš€ Why Developers Love This Library
73
+ ## Built for work that should not block the page
48
74
 
49
- ### ๐ŸŽ“ **Small API Surface**
75
+ ### **Functions, not messages**
50
76
 
51
- If you already understand native Web Workers, the mental model stays familiar:
77
+ Every key you return from `defineWorker` becomes a typed method on the main thread.
52
78
 
53
79
  ```ts
54
- const worker = createEasyWebWorker(({ onMessage }) => {
55
- onMessage((message) => {
56
- // Work inside the Worker
57
- message.resolve('done');
58
- });
59
- });
80
+ // The worker does the heavy lifting
81
+ const worker = defineWorker(() => ({
82
+ countWords: (text: string) => text.trim().split(/\s+/).length,
83
+ }));
84
+ ```
60
85
 
61
- await worker.send();
86
+ ```ts
87
+ // The main thread remains 100% responsive
88
+ await text.countWords(article); // CancelablePromise<number>
62
89
  ```
63
90
 
64
- ### โšก **Real Background Execution**
91
+ [**How typed workers work โ†’**](https://johnny-quesada-developer.github.io/easy-web-worker/docs/typed-workers/)
92
+
93
+ ### **Return a value, get a promise**
65
94
 
66
- Move CPU-heavy work away from the browser's main thread so rendering and user interaction can stay responsive.
95
+ A method resolves with what it returns and rejects with what it throws. `async` works the same way.
67
96
 
68
97
  ```ts
69
- const worker = createEasyWebWorker<number, number>(({ onMessage }) => {
70
- const fibonacci = (n: number): number => {
71
- if (n <= 1) return n;
72
- return fibonacci(n - 1) + fibonacci(n - 2);
73
- };
98
+ const worker = defineWorker(() => ({
99
+ parseReport: async (url: string) => {
100
+ const response = await fetch(url);
74
101
 
75
- onMessage((message) => {
76
- message.resolve(fibonacci(message.payload));
77
- });
78
- });
102
+ if (!response.ok) throw new Error('Report not available');
103
+
104
+ return buildReport(await response.text());
105
+ },
106
+ }));
79
107
  ```
80
108
 
81
- ### ๐Ÿ›‘ **Cancelable Promises**
109
+ [**Return values and errors โ†’**](https://johnny-quesada-developer.github.io/easy-web-worker/docs/typed-workers/#return-values-and-errors)
110
+
111
+ ### **Cancellation that reaches the Worker**
82
112
 
83
- Every `send()` returns a `CancelablePromise` from [`easy-cancelable-promise`](https://www.npmjs.com/package/easy-cancelable-promise).
113
+ Every call returns a `CancelablePromise`. Canceling it tells the Worker to stop.
84
114
 
85
115
  ```ts
86
- const task = worker.send(45);
116
+ const task = math.findPrimes(50_000_000);
87
117
 
88
- task.cancel('No longer needed');
118
+ task.cancel('Canceled by user');
89
119
  ```
90
120
 
91
- Cancellation can travel **from the main thread into the Worker**, allowing the Worker to react and release resources.
121
+ [**Run it live โ†’**](https://johnny-quesada-developer.github.io/easy-web-worker/examples/progress-and-cancellation/) ยท [Read the guide](https://johnny-quesada-developer.github.io/easy-web-worker/docs/cancellation-and-progress/#cancellation-reaches-the-worker)
92
122
 
93
- ### ๐Ÿ“Š **Progress Reporting**
123
+ ### **Progress on the same call**
94
124
 
95
- Long-running jobs can report progress without resolving the request.
125
+ Long-running methods report progress through the promise they already returned.
96
126
 
97
127
  ```ts
98
- worker.send(payload).onProgress((percentage) => {
99
- console.log(`${percentage}%`);
128
+ await math.findPrimes(50_000_000).onProgress((percentage) => {
129
+ progressBar.value = percentage;
100
130
  });
101
131
  ```
102
132
 
103
- ### ๐Ÿšฆ **Built-In Worker Pool**
133
+ [**Run it live โ†’**](https://johnny-quesada-developer.github.io/easy-web-worker/examples/progress-and-cancellation/) ยท [Read the guide](https://johnny-quesada-developer.github.io/easy-web-worker/docs/cancellation-and-progress/#progress-on-the-same-call)
104
134
 
105
- Scale a single `EasyWebWorker` across multiple native Worker instances.
135
+ ### **Every core, one option**
136
+
137
+ Set `maxWorkers` and the same object spreads calls across several native Workers.
106
138
 
107
139
  ```ts
108
- const worker = createEasyWebWorker(workerBody, {
109
- maxWorkers: 4,
110
- });
140
+ const math = createWorker<MathWorker>(source, { maxWorkers: 4 });
141
+
142
+ // three calls, three threads, at the same time
143
+ await Promise.all([math.fibonacci(40), math.fibonacci(41), math.fibonacci(42)]);
111
144
  ```
112
145
 
113
- The public API remains the same โ€” the library manages worker creation and message distribution.
146
+ [**Run it live โ†’**](https://johnny-quesada-developer.github.io/easy-web-worker/examples/worker-pool/) ยท [Read the guide](https://johnny-quesada-developer.github.io/easy-web-worker/docs/worker-pools/)
114
147
 
115
- ### ๐Ÿงฉ **Multiple Worker Sources**
148
+ ### **Move large buffers instead of copying them**
116
149
 
117
- Use whichever architecture fits the project:
150
+ Pass transferable objects as the second argument. Ownership moves and nothing is cloned.
118
151
 
119
152
  ```ts
120
- createEasyWebWorker(workerBody); // Runtime Worker template
121
- createEasyWebWorker('./worker.js'); // Static Worker file
122
- createEasyWebWorker(new URL(...)); // URL
123
- createEasyWebWorker(new Worker(...)); // Existing Worker
124
- createEasyWebWorker([worker1, worker2]); // Existing Worker pool
153
+ const blurred = await images.blur(buffer, [buffer]);
125
154
  ```
126
155
 
127
- ---
156
+ [**Run it live โ†’**](https://johnny-quesada-developer.github.io/easy-web-worker/examples/transferable-buffers/) ยท [Read the guide](https://johnny-quesada-developer.github.io/easy-web-worker/docs/transferable-objects/)
128
157
 
129
- ## ๐Ÿ“ฆ Installation
158
+ ### **Any way you create a Worker**
130
159
 
131
- ```bash
132
- npm install easy-web-worker
160
+ A Worker file, a native `Worker`, your own pool, or just a function. The API on top stays the same.
161
+
162
+ ```ts
163
+ createWorker<MathWorker>(new URL('./math.worker.ts', import.meta.url)); // Worker file
164
+ createWorker<MathWorker>(workerUrl); // URL generated by your bundler
165
+ createWorker<MathWorker>(new Worker(...)); // existing Worker
166
+ createWorker<MathWorker>([worker1, worker2]); // existing pool
167
+ createWorker(() => ({ fibonacci })); // a function, no file at all
133
168
  ```
134
169
 
135
- `easy-web-worker` uses [`easy-cancelable-promise`](https://www.npmjs.com/package/easy-cancelable-promise) for the promise lifecycle.
170
+ [**Every source, with the setup for each bundler โ†’**](https://johnny-quesada-developer.github.io/easy-web-worker/docs/creating-workers/)
136
171
 
137
- > **Upgrading from an old release?**
138
- > If your project still imports `cancelable-promise-jq`, replace it with `easy-cancelable-promise`. The old package name is deprecated.
172
+ ### **No Worker file required**
139
173
 
140
- ---
174
+ Pass a function instead of a file. Same methods, same inferred types, nothing to configure.
141
175
 
142
- ## ๐ŸŽฌ Quick Start
176
+ ```ts
177
+ const math = createWorker(() => {
178
+ const fibonacci = (n: number): number =>
179
+ n <= 1 ? n : fibonacci(n - 1) + fibonacci(n - 2);
143
180
 
144
- ### 30 Seconds to a Web Worker
181
+ return { fibonacci };
182
+ });
145
183
 
146
- ```ts
147
- import { createEasyWebWorker } from 'easy-web-worker';
184
+ await math.fibonacci(40); // 102334155, typed as number
185
+ ```
148
186
 
149
- const fibonacciWorker = createEasyWebWorker<number, number>(({ onMessage }) => {
150
- const fibonacci = (n: number): number => {
151
- if (n <= 1) return n;
152
- return fibonacci(n - 1) + fibonacci(n - 2);
153
- };
187
+ [**Run it live โ†’**](https://johnny-quesada-developer.github.io/easy-web-worker/examples/worker-without-a-file/) ยท [Read the guide](https://johnny-quesada-developer.github.io/easy-web-worker/docs/runtime-workers/)
154
188
 
155
- onMessage((message) => {
156
- message.resolve(fibonacci(message.payload));
157
- });
158
- });
189
+ ---
159
190
 
160
- const result = await fibonacciWorker.send(40);
191
+ ## Installation
161
192
 
162
- console.log(result);
193
+ ```bash
194
+ npm install easy-web-worker
163
195
  ```
164
196
 
165
- The Fibonacci calculation executes in a separate native Worker thread instead of blocking the page's main thread. โšก
197
+ - **Any framework**: built on the native Worker API of the browser, with no dependency on React, Vue, Angular, Svelte or any other UI library.
198
+ - **Promises**: calls return a `CancelablePromise` from [`easy-cancelable-promise`](https://www.npmjs.com/package/easy-cancelable-promise), installed with the package.
166
199
 
167
- ### 60 Seconds to Production-Ready
168
-
169
- Run [`jsdiff`](https://www.npmjs.com/package/diff) inside a Web Worker so text comparison never blocks the main thread.
200
+ ---
170
201
 
171
- #### ๐ŸŒ Simple jsdiff implementation.
202
+ ## Quick Start
172
203
 
173
- No Worker file. No extra bundler configuration. Just give `easy-web-worker` the public script URL:
204
+ ### Your first typed worker
174
205
 
175
206
  ```ts
176
- // example.js
177
- import { createEasyWebWorker } from 'easy-web-worker';
207
+ // math.worker.ts
208
+ import { defineWorker } from 'easy-web-worker/defineWorker';
178
209
 
179
- const worker = createEasyWebWorker(
180
- ({ onMessage }) => {
181
- onMessage((message, context) => {
182
- const { input1, input2 } = message.payload;
210
+ const fibonacci = (n: number): number =>
211
+ n <= 1 ? n : fibonacci(n - 1) + fibonacci(n - 2);
183
212
 
184
- message.resolve(context.Diff.diffWords(input1, input2));
185
- });
186
- },
213
+ // 1. Define it (in the Worker file)
214
+ const worker = defineWorker(() => ({ fibonacci }));
215
+
216
+ export type MathWorker = typeof worker;
217
+ ```
218
+
219
+ ```ts
220
+ // main.ts
221
+ import { createWorker } from 'easy-web-worker/createWorker';
222
+ import type { MathWorker } from './math.worker';
223
+
224
+ // 2. Create it (on the main thread)
225
+ const math = createWorker<MathWorker>(
226
+ new URL('./math.worker.ts', import.meta.url),
187
227
  {
188
- scripts: ['https://cdn.jsdelivr.net/npm/diff@9.0.0/dist/diff.min.js'],
228
+ workerOptions: { type: 'module' },
189
229
  }
190
230
  );
191
231
 
192
- const result = await worker.send({
193
- input1: 'Web Workers are powerful.',
194
- input2: 'Web Workers are incredibly powerful.',
195
- });
196
-
197
- console.log(result);
232
+ // 3. Call it
233
+ const result = await math.fibonacci(40); // 102334155
198
234
  ```
199
235
 
200
- **That's it.** The CDN script is loaded inside the Worker, `jsdiff` exposes its `Diff` API there, and your main thread only sends data and awaits the result. ๐Ÿš€
201
-
202
- - โœ… No separate Worker file
203
- - โœ… No npm import for jsdiff in your application bundle
204
- - โœ… No manual `importScripts()`
205
- - โœ… No `postMessage` / `onmessage` plumbing
206
- - โœ… Type-safe request and result
207
- - โœ… Real background execution
236
+ The main thread imports only the **type** of the Worker. TypeScript infers both sides: `math.fibonacci` takes a `number` and returns a `CancelablePromise<number>`. Change the Worker, and the main thread stops compiling until it matches.
208
237
 
209
- #### ๐Ÿ“„ Static Worker file and specific actions
238
+ > Shipping with a bundler? See [Every way to create a worker](#every-way-to-create-a-worker) for the production setup.
239
+ >
240
+ > Prefer a step-by-step page? [Getting started](https://johnny-quesada-developer.github.io/easy-web-worker/docs/getting-started/) covers the same code, and [Keep the page responsive](https://johnny-quesada-developer.github.io/easy-web-worker/examples/keep-the-page-responsive/) runs it in your browser.
210
241
 
211
- If you prefer normal module imports, your own Worker file, or bundler-controlled dependencies, use `StaticEasyWebWorker`.
242
+ ### Progress and cancellation
212
243
 
213
- **`TextDiff.worker.ts`**
244
+ Wrap a method with `onMessage` to receive the message of the call.
214
245
 
215
246
  ```ts
216
- import { diffWords, type Change } from 'diff';
217
- import { StaticEasyWebWorker } from 'easy-web-worker';
218
- import type { ComparePayload, Change } from './TextDiff.types';
219
-
220
- const easyWorker = new StaticEasyWebWorker();
247
+ // math.worker.ts
248
+ const worker = defineWorker(({ onMessage }) => ({
249
+ findPrimes: onMessage(async (limit: number, message) => {
250
+ const primes: number[] = [];
221
251
 
222
- // you can define multiple named methods inside the Worker
223
- easyWorker.onMessage<ComparePayload, Change[]>('compare', (message) => {
224
- const { input1, input2 } = message.payload;
252
+ for (let candidate = 2; candidate <= limit; candidate++) {
253
+ if (isPrime(candidate)) primes.push(candidate);
225
254
 
226
- message.resolve(diffWords(input1, input2));
227
- });
228
-
229
- easyWorker.onMessage<string, string>('uppercase', (message) => {
230
- message.resolve(message.payload.toUpperCase());
231
- });
232
- ```
255
+ if (candidate % 100_000 !== 0) continue;
233
256
 
234
- **That's the whole Worker.** Now connect it to your application:
257
+ // give the Worker a moment to receive a cancellation
258
+ await new Promise((resolve) => setTimeout(resolve));
235
259
 
236
- ```ts
237
- import { EasyWebWorker } from 'easy-web-worker';
238
- import workerUrl from './TextDiff.worker?worker&url';
260
+ if (!message.isPending()) break;
239
261
 
240
- // For vite, use the Worker URL in production and the TypeScript file in development.
241
- const isProduction = import.meta.env.MODE === 'production';
262
+ message.reportProgress((candidate / limit) * 100);
263
+ }
242
264
 
243
- const worker = new EasyWebWorker(
244
- isProduction ? workerUrl : new URL('./TextDiff.worker.ts', import.meta.url),
245
- {
246
- workerOptions: {
247
- type: 'module',
248
- },
249
- }
250
- );
265
+ return primes;
266
+ }),
267
+ }));
268
+ ```
251
269
 
252
- const result = await worker.sendToMethod<ComparePayload, Change[]>('compare', {
253
- input1: 'Web Workers are powerful.',
254
- input2: 'Web Workers are incredibly powerful.',
270
+ ```ts
271
+ // main.ts
272
+ const task = math.findPrimes(50_000_000).onProgress((percentage) => {
273
+ progressBar.value = percentage;
255
274
  });
256
- ```
257
275
 
258
- Both approaches give you the same core result: a normal JavaScript library doing CPU work inside a **Web Worker**, while your application talks to it through a Promise-based API.
276
+ cancelButton.onclick = () => task.cancel('Canceled by user');
259
277
 
260
- | Approach | Best for |
261
- | ----------------------- | ---------------------------------------------------------------------------- |
262
- | ๐ŸŒ CDN + runtime Worker | Fastest setup, demos, small integrations, no Worker file |
263
- | ๐Ÿ“„ Static Worker | Module imports, larger Worker code, bundler control, production architecture |
278
+ const primes = await task;
279
+ ```
264
280
 
265
- And in both cases you still get the `easy-web-worker` features around the Worker:
281
+ Progress and cancellation belong to the call itself, not to a second channel.
266
282
 
267
- - โœ… Promise-based communication
268
- - โœ… Cancellation
269
- - โœ… Progress reporting
270
- - โœ… Named Worker methods
271
- - โœ… Worker pooling when needed
283
+ > This exact pattern runs live in [Progress and cancellation](https://johnny-quesada-developer.github.io/easy-web-worker/examples/progress-and-cancellation/): start a long search, watch it advance, and cancel it.
272
284
 
273
285
  ---
274
286
 
275
- ## ๐ŸŒŸ Core Features Deep Dive
287
+ ## Core Features Deep Dive
276
288
 
277
- ### 1๏ธโƒฃ Runtime Workers with `createEasyWebWorker`
289
+ ### Typed workers with `defineWorker` and `createWorker`
278
290
 
279
- Create a Worker directly from a function template.
291
+ Write the Worker as a set of methods and call them from the main thread like local async functions.
280
292
 
281
- #### ๐ŸŽจ The Basics
293
+ #### The Basics
282
294
 
283
295
  ```ts
284
- import { createEasyWebWorker } from 'easy-web-worker';
285
-
286
- const backgroundWorker = createEasyWebWorker<string, string>(
287
- ({ onMessage }) => {
288
- onMessage((message) => {
289
- message.resolve(`Message from Worker: ${message.payload}`);
290
- });
291
- }
292
- );
296
+ // text.worker.ts
297
+ const worker = defineWorker(() => ({
298
+ countWords: (text: string) => text.trim().split(/\s+/).length,
293
299
 
294
- const result = await backgroundWorker.send('hello!');
300
+ findDuplicates: (lines: string[]) =>
301
+ lines.filter((line, index) => lines.indexOf(line) !== index),
302
+ }));
295
303
 
296
- console.log(result);
304
+ export type TextWorker = typeof worker;
297
305
  ```
298
306
 
299
- The first generic controls the `send()` payload. The second controls the value returned when the Worker calls `message.resolve()`.
300
-
301
307
  ```ts
302
- const worker = createEasyWebWorker<Payload, Result>(workerBody);
303
- ```
304
-
305
- #### ๐ŸŽฏ Named Worker Methods
308
+ // main.ts
309
+ const text = createWorker<TextWorker>(source);
306
310
 
307
- A Worker can expose multiple message handlers instead of routing everything through one callback.
311
+ await text.countWords(article); // number
312
+ await text.findDuplicates(lines); // string[]
313
+ ```
308
314
 
309
- ```ts
310
- const worker = createEasyWebWorker<string, string>(({ onMessage }) => {
311
- onMessage((message) => {
312
- message.resolve(`default: ${message.payload}`);
313
- });
315
+ The contract between the threads lives next to the code that implements it. There is no second file of message types to keep in sync.
314
316
 
315
- onMessage<number, number>('double', (message) => {
316
- message.resolve(message.payload * 2);
317
- });
317
+ How the types cross the threads:
318
318
 
319
- onMessage<{ first: number; second: number }, number>('sum', (message) => {
320
- const { first, second } = message.payload;
321
- message.resolve(first + second);
322
- });
323
- });
319
+ | In the Worker | On the main thread |
320
+ | --------------------------------- | ------------------------------------------------------------- |
321
+ | `() => string` | `() => CancelablePromise<string>` |
322
+ | `(value: number) => number` | `(payload: number, transfer?) => CancelablePromise<number>` |
323
+ | `async (ids: string[]) => User[]` | `(payload: string[], transfer?) => CancelablePromise<User[]>` |
324
+ | `(value?: number) => number` | `(payload?: number, transfer?) => CancelablePromise<number>` |
324
325
 
325
- const defaultResult = await worker.send('hello');
326
- const doubled = await worker.sendToMethod<number, number>('double', 21);
327
- const total = await worker.sendToMethod<
328
- number,
329
- { first: number; second: number }
330
- >('sum', { first: 20, second: 22 });
331
- ```
326
+ A method receives **one payload**. Send several values as an object or a tuple. Required payloads are enforced when TypeScript runs with `strict` or `strictNullChecks`.
332
327
 
333
- This makes it possible to build a small, typed API inside one Worker. ๐Ÿงฉ
328
+ #### What it replaces
334
329
 
335
- #### ๐Ÿง  Worker Scope โ€” Important!
330
+ `fibonacci(40)` on the main thread freezes the page for about a second. A Web Worker fixes that, but the native API gives you messages, not functions. This is the least you write by hand:
336
331
 
337
- A runtime Worker body becomes the Worker source. It **cannot close over arbitrary variables from the main thread**.
332
+ ```ts
333
+ // fibonacci.worker.js
334
+ self.onmessage = ({ data: { id, n } }) => {
335
+ self.postMessage({ id, result: fibonacci(n) });
336
+ };
337
+ ```
338
338
 
339
339
  ```ts
340
- const greeting = 'Hello';
340
+ // main.js
341
+ const worker = new Worker(new URL('./fibonacci.worker.js', import.meta.url));
342
+ const pending = new Map();
343
+ let nextId = 0;
344
+
345
+ worker.onmessage = ({ data: { id, result } }) => {
346
+ pending.get(id)(result);
347
+ pending.delete(id);
348
+ };
341
349
 
342
- createEasyWebWorker(({ onMessage }) => {
343
- onMessage((message) => {
344
- // โŒ `greeting` does not exist inside the Worker scope.
345
- message.resolve(greeting);
350
+ const fibonacci = (n) =>
351
+ new Promise((resolve) => {
352
+ const id = nextId++;
353
+
354
+ pending.set(id, resolve);
355
+ worker.postMessage({ id, n });
346
356
  });
347
- });
348
357
  ```
349
358
 
350
- Everything needed by the Worker must be:
351
-
352
- - Defined inside the Worker body
353
- - Included through reusable Worker templates
354
- - Imported as a script
355
- - Sent in a message
356
- - Passed as a primitive parameter
357
-
358
- #### ๐Ÿ“ฆ Primitive Parameters
359
+ And that is before errors, cancellation, progress, types, or a second method.
359
360
 
360
- For small static values that should exist when the runtime Worker is created, use `primitiveParameters`.
361
+ #### Return values and errors
361
362
 
362
363
  ```ts
363
- const prefix = 'Result:';
364
+ // report.worker.ts
365
+ const worker = defineWorker(() => ({
366
+ parseReport: async (url: string) => {
367
+ const response = await fetch(url);
364
368
 
365
- const worker = createEasyWebWorker<null, string>(
366
- ({ onMessage }, context) => {
367
- const [prefix] = context.primitiveParameters;
369
+ if (!response.ok) {
370
+ throw new Error(`Report not available: ${response.status}`);
371
+ }
368
372
 
369
- onMessage((message) => {
370
- message.resolve(`${prefix} complete`);
371
- });
373
+ return buildReport(await response.text());
372
374
  },
373
- {
374
- primitiveParameters: [prefix],
375
- }
376
- );
377
-
378
- console.log(await worker.send()); // Result: complete
375
+ }));
379
376
  ```
380
377
 
381
- Use regular Worker messages for dynamic application data. `primitiveParameters` are best for small initialization values.
378
+ ```ts
379
+ // main.ts
380
+ try {
381
+ const report = await reports.parseReport('/reports/2026.csv');
382
+ } catch (error) {
383
+ console.log((error as Error).message); // 'Report not available: 404'
384
+ }
385
+ ```
382
386
 
383
- #### ๐Ÿ“Š Progress Without Resolving
387
+ An error rejects that one call. The Worker keeps serving the next one.
384
388
 
385
- The Worker can report progress as many times as necessary before the message finishes.
389
+ #### Three ways to write a method
386
390
 
387
391
  ```ts
388
- const worker = createEasyWebWorker<number[], number>(({ onMessage }) => {
389
- onMessage((message) => {
390
- let total = 0;
392
+ const worker = defineWorker(({ onMessage }) => ({
393
+ // 1. a plain function
394
+ double: (value: number) => value * 2,
395
+
396
+ // 2. onMessage: same thing, with the message and the event typed for you
397
+ count: onMessage(async (to: number, message, event) => {
398
+ message.reportProgress(50);
399
+
400
+ return to;
401
+ }),
402
+
403
+ // 3. onMessage().handle: only the message, you complete it
404
+ wait: onMessage<number, string>().handle((message, event) => {
405
+ const timeoutId = setTimeout(
406
+ () => message.resolve('done'),
407
+ message.payload
408
+ );
409
+
410
+ message.onCancel(() => clearTimeout(timeoutId));
411
+ }),
412
+ }));
413
+ ```
391
414
 
392
- message.payload.forEach((value, index, values) => {
393
- total += value;
394
- message.reportProgress(((index + 1) / values.length) * 100);
395
- });
415
+ | Style | Completes the call | Types |
416
+ | --------------------------------------------- | ----------------------- | --------------------------------- |
417
+ | Plain function | With the returned value | Inferred |
418
+ | `onMessage((payload, message, event) => ...)` | With the returned value | Inferred, `message` typed |
419
+ | `onMessage<P, R>().handle((message) => ...)` | You do | Explicit, `message.resolve` typed |
396
420
 
397
- message.resolve(total);
398
- });
399
- });
421
+ All three are called the same way from the main thread. Use the third for timers, streams, event-based APIs, or to transfer a result.
400
422
 
401
- const result = await worker.send([10, 20, 30, 40]).onProgress((percentage) => {
402
- console.log(percentage);
403
- });
404
- ```
423
+ If a method completes the message itself with `resolve`, `reject` or `cancel`, its return value is ignored.
405
424
 
406
- Output:
425
+ #### The message
407
426
 
408
- ```text
409
- 25
410
- 50
411
- 75
412
- 100
413
- ```
427
+ Available in `onMessage`, in `.handle(...)`, and in the message API.
414
428
 
415
- #### ๐Ÿ›‘ Two-Way Cancellation
429
+ | Member | Purpose |
430
+ | ------------------------------------------------- | --------------------------------------------- |
431
+ | `payload` | Value sent from the main thread |
432
+ | `resolve(result?, transfer?)` | Resolves the call |
433
+ | `reject(reason?, transfer?)` | Rejects the call |
434
+ | `cancel(reason?, transfer?)` | Cancels the call from inside the Worker |
435
+ | `reportProgress(percentage, payload?, transfer?)` | Reports progress and keeps the call pending |
436
+ | `onCancel`, `onResolve`, `onReject`, `onProgress` | Subscribe to each outcome; return unsubscribe |
437
+ | `onFinalize` | Runs once the call ends, whatever the outcome |
438
+ | `isPending()`, `getStatus()` | Current state of the call |
416
439
 
417
- Cancellation is more than rejecting a promise on the main thread. The Worker receives the cancellation event too.
440
+ #### Cancellation
418
441
 
419
- ```ts
420
- const worker = createEasyWebWorker<number, number>(({ onMessage }) => {
421
- onMessage((message) => {
422
- const interval = setInterval(() => {
423
- // long-running work...
424
- }, 100);
442
+ The cancellation reaches the Worker, so the work stops there instead of finishing for nobody. React to it with `message.onCancel(...)` or check `message.isPending()`.
425
443
 
426
- message.onCancel((data) => {
427
- clearInterval(interval);
444
+ Canceling twice, or after the call finished, does nothing.
428
445
 
429
- const reason = data.worker_cancelation?.reason ?? data.canceled?.reason;
446
+ A Worker that never pauses cannot receive a cancellation. See [Stop work that never pauses](#stop-work-that-never-pauses).
430
447
 
431
- console.log('Canceled:', reason);
432
- });
433
- });
434
- });
448
+ #### Transferable objects
435
449
 
436
- const task = worker.send(100);
450
+ ```ts
451
+ // main.ts
452
+ const buffer = await file.arrayBuffer();
437
453
 
438
- task.cancel('User navigated away');
454
+ const blurred = await images.blur(buffer, [buffer]);
439
455
  ```
440
456
 
441
- Inside a Worker, a message can also cancel itself:
457
+ Returned values are cloned. To transfer a result back, complete the call yourself:
442
458
 
443
459
  ```ts
444
- onMessage((message) => {
445
- if (!isValid(message.payload)) {
446
- message.cancel('Invalid payload');
447
- return;
448
- }
449
-
450
- // continue...
451
- });
460
+ // images.worker.ts
461
+ const worker = defineWorker(({ onMessage }) => ({
462
+ blur: onMessage<ArrayBuffer, ArrayBuffer>().handle((message) => {
463
+ const result = applyBlur(message.payload);
464
+
465
+ message.resolve(result, [result]);
466
+ }),
467
+ }));
452
468
  ```
453
469
 
454
- #### ๐Ÿ”„ Message Lifecycle
470
+ #### `unwrap`: the controls behind the methods
455
471
 
456
- Each `IEasyWebWorkerMessage` provides lifecycle hooks:
472
+ The object returned by `createWorker` holds only your methods, so names like `send`, `dispose` or `reboot` stay free for your own API. `unwrap` reaches the `EasyWebWorker` instance behind it.
457
473
 
458
474
  ```ts
459
- onMessage((message) => {
460
- const unsubscribeResolve = message.onResolve(() => {});
461
- const unsubscribeReject = message.onReject(() => {});
462
- const unsubscribeCancel = message.onCancel(() => {});
463
- const unsubscribeProgress = message.onProgress(() => {});
464
- const unsubscribeFinalize = message.onFinalize(() => {});
465
-
466
- const status = message.getStatus();
467
- const pending = message.isPending();
468
-
469
- // Unsubscribe whenever you no longer need a listener.
470
- unsubscribeResolve();
471
- unsubscribeReject();
472
- unsubscribeCancel();
473
- unsubscribeProgress();
474
- unsubscribeFinalize();
475
+ import { createWorker, unwrap } from 'easy-web-worker/createWorker';
475
476
 
476
- message.resolve();
477
- });
477
+ const math = createWorker<MathWorker>(source);
478
+
479
+ await unwrap(math).cancelAll('Leaving the page');
480
+ await unwrap(math).reboot();
481
+ await unwrap(math).dispose();
478
482
  ```
479
483
 
480
- #### ๐Ÿšš Transferable Objects
484
+ | `EasyWebWorker` method | Purpose |
485
+ | -------------------------------------------------------------- | --------------------------------------------------------------- |
486
+ | `send(payload?, transfer?)` | Sends a message to the default handler |
487
+ | `sendToMethod<TResult, TPayload>(method, payload?, transfer?)` | Sends a message to a named method |
488
+ | `cancelAll(reason?, { force? })` | Cancels every pending call; `force` restarts instead of waiting |
489
+ | `override(payload?, reason?, { force? })` | Cancels the pending calls and sends a new message |
490
+ | `overrideAfterCurrent(payload?, reason?)` | Keeps the current call, cancels the rest, sends a new message |
491
+ | `reboot(reason?)` | Terminates the Workers, rejects pending calls, starts again |
492
+ | `dispose()` | Cancels pending calls and releases the Workers |
481
493
 
482
- Both directions can use native `Transferable[]` values to move ownership instead of cloning large buffers.
494
+ The same helper works inside the Worker file, where it returns the Worker-side controls:
483
495
 
484
496
  ```ts
485
- type Payload = {
486
- buffer: ArrayBuffer;
487
- };
497
+ import { defineWorker, unwrap } from 'easy-web-worker/defineWorker';
488
498
 
489
- const worker = createEasyWebWorker<Payload, ArrayBuffer>(({ onMessage }) => {
490
- onMessage((message) => {
491
- const { buffer } = message.payload;
499
+ const worker = defineWorker(() => ({
500
+ shutdown: () => unwrap(worker).close(),
501
+ }));
492
502
 
493
- // Process the buffer...
503
+ unwrap(worker).importScripts('https://example.com/library.js');
494
504
 
495
- message.resolve(buffer, [buffer]);
496
- });
505
+ // handles messages sent without a method: send, override, overrideAfterCurrent
506
+ unwrap(worker).onMessage((message) => {
507
+ message.resolve();
497
508
  });
498
-
499
- const buffer = new ArrayBuffer(1_000_000);
500
-
501
- const processedBuffer = await worker.send({ buffer }, [buffer]);
502
509
  ```
503
510
 
504
- This is especially useful for `ArrayBuffer`, media processing, binary parsing, and other data-heavy workloads.
511
+ One name is not available for a method: `then`. The worker object is not a promise, so `await math` never sends a message.
505
512
 
506
- ---
513
+ #### Entry points
507
514
 
508
- ### 2๏ธโƒฃ Static Workers with `StaticEasyWebWorker`
515
+ Import each side from its own entry, so the Worker bundle carries no main-thread code:
509
516
 
510
- Sometimes a dedicated Worker file is the better architecture โ€” especially when the Worker has its own modules, build pipeline, or large implementation.
517
+ | Entry | Use it in | Exports |
518
+ | ------------------------------ | --------------- | ------------------------------- |
519
+ | `easy-web-worker/defineWorker` | The Worker file | `defineWorker`, `unwrap`, types |
520
+ | `easy-web-worker/createWorker` | The main thread | `createWorker`, `unwrap`, types |
511
521
 
512
- #### ๐ŸŽช The Basics
522
+ Everything is also exported from the root `easy-web-worker` entry.
513
523
 
514
- **`worker.ts`**
524
+ > **On the website:** [Typed workers](https://johnny-quesada-developer.github.io/easy-web-worker/docs/typed-workers/) ยท [Methods and the message](https://johnny-quesada-developer.github.io/easy-web-worker/docs/methods-and-the-message/) ยท [TypeScript](https://johnny-quesada-developer.github.io/easy-web-worker/docs/typescript/) ยท [API reference](https://johnny-quesada-developer.github.io/easy-web-worker/docs/api-reference/)
515
525
 
516
- ```ts
517
- import { createStaticEasyWebWorker } from 'easy-web-worker/createStaticEasyWebWorker';
526
+ ---
518
527
 
519
- const { onMessage } = createStaticEasyWebWorker<number, number>();
528
+ ### Every way to create a worker
520
529
 
521
- onMessage((message) => {
522
- message.resolve(message.payload * 2);
523
- });
530
+ Pick the source that fits how your project builds and serves its Workers. `createWorker`, `createEasyWebWorker` and `new EasyWebWorker(...)` accept all of them. A function means methods for `createWorker` and messages for the other two.
524
531
 
525
- onMessage<string, string>('uppercase', (message) => {
526
- message.resolve(message.payload.toUpperCase());
527
- });
528
- ```
532
+ | Source | Best for | Pool with `maxWorkers` | `reboot` / `force` |
533
+ | ---------------------- | ----------------------------------------------- | ---------------------- | ------------------ |
534
+ | `URL` of a Worker file | Worker files in a bundled project | โœ… | โœ… |
535
+ | `string` path | A Worker file you serve yourself | โœ… | โœ… |
536
+ | Existing `Worker` | Bundlers that detect `new Worker(new URL(...))` | One Worker | โŒ |
537
+ | Existing `Worker[]` | Bringing your own pool | The array is the pool | โŒ |
538
+ | Function | Workers with no file at all | โœ… | โœ… |
539
+ | Function array | Workers composed from reusable pieces | โœ… | โœ… |
529
540
 
530
- **Main thread**
541
+ #### A Worker file in development
531
542
 
532
543
  ```ts
533
- import { createEasyWebWorker } from 'easy-web-worker';
534
-
535
- const worker = createEasyWebWorker<number, number>('./worker.js');
536
-
537
- const result = await worker.send(21);
544
+ const math = createWorker<MathWorker>(
545
+ new URL('./math.worker.ts', import.meta.url),
546
+ {
547
+ workerOptions: { type: 'module' },
548
+ }
549
+ );
538
550
  ```
539
551
 
540
- You keep the `send()`, cancellation, progress, and message lifecycle API while controlling the Worker file yourself.
552
+ `type: 'module'` is required when the Worker file uses `import` or `export`, which every `defineWorker` file does.
541
553
 
542
- #### ๐ŸŽ Vite + TypeScript
554
+ #### Vite: development and production
555
+
556
+ In development, Vite serves the TypeScript file. A production build needs the Worker URL that Vite generates:
543
557
 
544
558
  ```ts
545
- import workerUrl from './worker?worker&url';
546
- import { createEasyWebWorker } from 'easy-web-worker';
559
+ import { createWorker } from 'easy-web-worker/createWorker';
560
+ import workerUrl from './math.worker?worker&url';
561
+ import type { MathWorker } from './math.worker';
547
562
 
548
- const workerSource =
549
- import.meta.env.MODE === 'production'
550
- ? workerUrl
551
- : new URL('./worker.ts', import.meta.url);
563
+ // the Worker URL in production, the TypeScript file in development
564
+ const isProduction = import.meta.env.MODE === 'production';
552
565
 
553
- const worker = createEasyWebWorker(workerSource, {
554
- workerOptions: {
555
- type: 'module',
556
- },
557
- });
566
+ const math = createWorker<MathWorker>(
567
+ isProduction ? workerUrl : new URL('./math.worker.ts', import.meta.url),
568
+ {
569
+ workerOptions: { type: 'module' },
570
+ }
571
+ );
558
572
  ```
559
573
 
560
- #### ๐Ÿ”Œ Existing Native Worker
561
-
562
- Already have a `Worker` instance? Wrap it directly.
574
+ The same source works with the message API:
563
575
 
564
576
  ```ts
565
- const nativeWorker = new Worker(new URL('./worker.js', import.meta.url), {
566
- type: 'module',
567
- });
577
+ import { EasyWebWorker } from 'easy-web-worker';
568
578
 
569
- const worker = createEasyWebWorker(nativeWorker);
579
+ const worker = new EasyWebWorker(
580
+ isProduction ? workerUrl : new URL('./TextDiff.worker.ts', import.meta.url),
581
+ {
582
+ workerOptions: { type: 'module' },
583
+ }
584
+ );
570
585
  ```
571
586
 
572
- Or provide an existing pool:
587
+ #### A native Worker you create
588
+
589
+ webpack 5, Parcel and Vite all detect `new Worker(new URL(...))` and bundle the file for you. Create the Worker that way and hand it over:
573
590
 
574
591
  ```ts
575
- const worker = createEasyWebWorker([
576
- new Worker('./worker-a.js'),
577
- new Worker('./worker-b.js'),
578
- ]);
592
+ const math = createWorker<MathWorker>(
593
+ new Worker(new URL('./math.worker.ts', import.meta.url), { type: 'module' })
594
+ );
579
595
  ```
580
596
 
581
- When an existing `Worker[]` is supplied, those Worker instances become the pool managed by `EasyWebWorker`.
582
-
583
- ---
584
-
585
- ### 3๏ธโƒฃ Concurrency & Worker Pool
597
+ The library cannot create that Worker again, so `reboot` and `force` are not available. Leave `keepAlive` at its default so the Worker is not terminated when idle.
586
598
 
587
- A single `EasyWebWorker` can distribute simultaneous messages across multiple native Workers.
599
+ #### Your own pool
588
600
 
589
- #### ๐Ÿšฆ Scale on Demand
601
+ Pass an array of native Workers and calls are distributed across them:
590
602
 
591
603
  ```ts
592
- const worker = createEasyWebWorker<number, number>(
593
- ({ onMessage }) => {
594
- const fibonacci = (n: number): number => {
595
- if (n <= 1) return n;
596
- return fibonacci(n - 1) + fibonacci(n - 2);
597
- };
598
-
599
- onMessage((message) => {
600
- message.resolve(fibonacci(message.payload));
601
- });
602
- },
603
- {
604
- maxWorkers: 4,
605
- }
606
- );
604
+ const createMathWorker = () =>
605
+ new Worker(new URL('./math.worker.ts', import.meta.url), { type: 'module' });
607
606
 
608
- const results = await Promise.all([
609
- worker.send(40),
610
- worker.send(41),
611
- worker.send(42),
607
+ const math = createWorker<MathWorker>([
608
+ createMathWorker(),
609
+ createMathWorker(),
610
+ createMathWorker(),
612
611
  ]);
613
612
  ```
614
613
 
615
- With `maxWorkers: 4`, the library can create additional Workers as concurrent requests arrive, up to the configured limit.
616
-
617
- #### ๐Ÿ”ฅ Warm Up the Pool
614
+ #### A file you serve yourself
618
615
 
619
- Create the full pool immediately:
616
+ A Worker script that is already built and public only needs its path:
620
617
 
621
618
  ```ts
622
- const worker = createEasyWebWorker(workerBody, {
623
- maxWorkers: 4,
624
- warmUpWorkers: true,
625
- });
619
+ const math = createWorker<MathWorker>('/workers/math.worker.js');
626
620
  ```
627
621
 
628
- Useful when startup latency matters more than keeping the initial resource footprint small.
629
-
630
- #### ๐Ÿ’ค Dispose Idle Workers Automatically
622
+ #### A function, with no file at all
631
623
 
632
624
  ```ts
633
- const worker = createEasyWebWorker(workerBody, {
634
- maxWorkers: 4,
635
- keepAlive: false,
636
- terminationDelay: 5_000,
637
- });
638
- ```
625
+ const math = createWorker(() => ({
626
+ double: (value: number) => value * 2,
627
+ }));
639
628
 
640
- When there are no queued messages, idle Workers are terminated after the configured delay.
641
-
642
- #### โš™๏ธ Concurrency Defaults
629
+ await math.double(21); // 42
630
+ ```
643
631
 
644
- | Option | Current behavior |
645
- | -------------------------- | --------------------------------------------------------------------------- |
646
- | `maxWorkers` | Defaults to `1` |
647
- | `warmUpWorkers` | Defaults to `true` for a single-worker configuration; otherwise `false` |
648
- | `keepAlive` | Defaults to the resolved `warmUpWorkers` value unless explicitly configured |
649
- | `terminationDelay` | Defaults to `1000` ms |
650
- | Existing `Worker[]` source | Pool size comes from the supplied array and the workers are kept alive |
632
+ The function returns the methods, exactly like the builder of `defineWorker`. See [Runtime Workers](#runtime-workers-a-function-as-the-worker) for scope rules, parameters and external scripts.
651
633
 
652
- This lets the default experience behave like a persistent single Worker while making larger pools opt-in and demand-driven.
634
+ > **On the website:** [Creating workers](https://johnny-quesada-developer.github.io/easy-web-worker/docs/creating-workers/) ยท [Troubleshooting](https://johnny-quesada-developer.github.io/easy-web-worker/docs/troubleshooting/)
653
635
 
654
636
  ---
655
637
 
656
- ### 4๏ธโƒฃ Reusable Worker Templates
638
+ ### Worker pools
657
639
 
658
- Runtime Worker bodies are templates, so common Worker functionality can be composed.
640
+ One Worker runs one task at a time. A pool runs several at once, behind the same object.
659
641
 
660
- ```ts
661
- import { createEasyWebWorker, type EasyWebWorkerBody } from 'easy-web-worker';
642
+ #### Scale on demand
662
643
 
663
- const commonTools: EasyWebWorkerBody = ({ onMessage }, context) => {
664
- context.doSomething = () => Promise.resolve('Reusable Worker code');
665
- };
666
-
667
- const worker = createEasyWebWorker([
668
- commonTools,
669
- ({ onMessage }, context) => {
670
- onMessage(async (message) => {
671
- const result = await context.doSomething();
644
+ ```ts
645
+ const math = createWorker<MathWorker>(source, {
646
+ maxWorkers: 4,
647
+ });
672
648
 
673
- message.resolve(result);
674
- });
675
- },
649
+ // three calls, three threads, at the same time
650
+ const results = await Promise.all([
651
+ math.fibonacci(40),
652
+ math.fibonacci(41),
653
+ math.fibonacci(42),
676
654
  ]);
677
-
678
- await worker.send();
679
655
  ```
680
656
 
681
- This is useful when multiple runtime Workers share utilities, message handlers, or initialization logic.
657
+ The calling code does not change. It does not know whether one Worker or four are behind it.
682
658
 
683
- ---
684
-
685
- ### 5๏ธโƒฃ Import Scripts into Runtime Workers
659
+ Workers are created as concurrent calls arrive, so an idle application pays for none of them.
686
660
 
687
- External scripts can be included through the Worker configuration.
661
+ #### Warm up or release the pool
688
662
 
689
663
  ```ts
690
- const worker = createEasyWebWorker(
691
- ({ onMessage }, context) => {
692
- onMessage((message) => {
693
- context.doSomething();
694
- message.resolve();
695
- });
696
- },
697
- {
698
- scripts: ['https://example.com/worker-library.js'],
699
- }
700
- );
701
- ```
702
-
703
- For example, if the imported script adds something to the Worker global scope:
664
+ createWorker<MathWorker>(source, {
665
+ maxWorkers: 4,
666
+ warmUpWorkers: true, // create the whole pool up front, for the fastest first call
667
+ });
704
668
 
705
- ```js
706
- // worker-library.js
707
- self.message = 'Hello from imported script!';
708
- self.doSomething = () => console.log(self.message);
669
+ createWorker<MathWorker>(source, {
670
+ maxWorkers: 4,
671
+ keepAlive: false, // terminate idle Workers...
672
+ terminationDelay: 5_000, // ...after five seconds without work
673
+ });
709
674
  ```
710
675
 
711
- For modern module-heavy Workers, a static Worker file with normal ESM imports is often easier to organize.
712
-
713
- ---
714
-
715
- ## ๐Ÿ”ฅ Advanced Patterns
716
-
717
- ### ๐Ÿ—๏ธ Build a Worker API with Named Methods
718
-
719
- ```ts
720
- type User = {
721
- id: string;
722
- name: string;
723
- };
724
-
725
- const worker = createEasyWebWorker(({ onMessage }) => {
726
- const users = new Map<string, User>();
676
+ #### Configuration
727
677
 
728
- onMessage<User, void>('saveUser', (message) => {
729
- users.set(message.payload.id, message.payload);
730
- message.resolve();
731
- });
678
+ | Option | Default | Purpose |
679
+ | --------------------- | ----------------------- | ---------------------------------------------------- |
680
+ | `maxWorkers` | `1` | Maximum number of native Workers |
681
+ | `warmUpWorkers` | `true` for one Worker | Creates the pool during initialization |
682
+ | `keepAlive` | Same as `warmUpWorkers` | Keeps idle Workers alive |
683
+ | `terminationDelay` | `1000` ms | Wait before terminating idle Workers |
684
+ | `workerOptions` | `{}` | Native `WorkerOptions`, such as `{ type: 'module' }` |
685
+ | `onWorkerError` | Rethrows | Receives the uncaught errors of the Worker |
686
+ | `scripts` | `[]` | Scripts imported into a runtime Worker |
687
+ | `primitiveParameters` | `[]` | Static values for a runtime Worker, in `context` |
732
688
 
733
- onMessage<string, User | null>('getUser', (message) => {
734
- message.resolve(users.get(message.payload) ?? null);
735
- });
689
+ The same options apply to `createWorker`, `createEasyWebWorker` and `new EasyWebWorker(...)`.
736
690
 
737
- onMessage<string, boolean>('deleteUser', (message) => {
738
- message.resolve(users.delete(message.payload));
739
- });
740
- });
691
+ > **On the website:** [Worker pools](https://johnny-quesada-developer.github.io/easy-web-worker/docs/worker-pools/) ยท [Live example: one worker or three](https://johnny-quesada-developer.github.io/easy-web-worker/examples/worker-pool/)
741
692
 
742
- await worker.sendToMethod<void, User>('saveUser', {
743
- id: '42',
744
- name: 'Johnny',
745
- });
746
-
747
- const user = await worker.sendToMethod<User | null, string>('getUser', '42');
748
- ```
693
+ ---
749
694
 
750
- One Worker, multiple typed operations, one shared Worker-local state.
695
+ ### Runtime Workers: a function as the Worker
751
696
 
752
- ### ๐Ÿ”Ž Filter Large Collections with Progress
697
+ Give `createWorker` a function instead of a file. No extra file, no bundler configuration.
753
698
 
754
- The Worker can retrieve, store, and filter a large collection without blocking the main thread.
699
+ #### The Basics
755
700
 
756
701
  ```ts
757
- type Item = Record<string, unknown>;
702
+ import { createWorker } from 'easy-web-worker/createWorker';
758
703
 
759
- const worker = createEasyWebWorker<string, Item[]>(({ onMessage }) => {
760
- const collection = fetch('https://api.example.com/items').then(
761
- (response) => response.json() as Promise<Item[]>
762
- );
704
+ const math = createWorker(({ onMessage }) => {
705
+ const fibonacci = (n: number): number =>
706
+ n <= 1 ? n : fibonacci(n - 1) + fibonacci(n - 2);
763
707
 
764
- const containsValue = (item: unknown, filter: string): boolean => {
765
- if (item === null || item === undefined) return false;
708
+ return {
709
+ fibonacci,
766
710
 
767
- if (typeof item !== 'object') {
768
- return String(item).toLowerCase().includes(filter);
769
- }
711
+ countTo: onMessage(async (limit: number, message) => {
712
+ for (let step = 1; step <= limit; step++) {
713
+ message.reportProgress((step * 100) / limit);
714
+ }
770
715
 
771
- return Object.values(item).some((value) => containsValue(value, filter));
716
+ return limit;
717
+ }),
772
718
  };
719
+ });
773
720
 
774
- onMessage(async (message) => {
775
- const items = await collection;
776
- const filter = message.payload.trim().toLowerCase();
721
+ const result = await math.fibonacci(40); // 102334155
722
+ ```
777
723
 
778
- const result = items.filter((item, index) => {
779
- message.reportProgress(((index + 1) / items.length) * 100);
724
+ The function is the builder of [`defineWorker`](#three-ways-to-write-a-method): it receives `onMessage`, returns the methods, and their types are inferred. There is no type to export or import, because both sides are in the same file.
780
725
 
781
- return containsValue(item, filter);
782
- });
726
+ Everything else works the same: cancellation, progress, transferable objects, `unwrap`, and pools with `maxWorkers`.
783
727
 
784
- message.resolve(result);
785
- });
786
- });
787
- ```
728
+ #### Worker scope
788
729
 
789
- Usage:
730
+ The function becomes the source of a real Worker, so it **cannot use variables from outside**.
790
731
 
791
732
  ```ts
792
- const filtered = await worker
793
- .send('johnny')
794
- .onProgress((percentage) => console.log(percentage));
733
+ const greeting = 'Hello';
795
734
 
796
- console.log(filtered);
735
+ createWorker(() => ({
736
+ // `greeting` does not exist inside the Worker
737
+ greet: (name: string) => `${greeting} ${name}`,
738
+ }));
797
739
  ```
798
740
 
799
- ### ๐Ÿฅ‡ Latest Request Wins with `override()`
741
+ Everything the Worker needs must be defined inside the function, imported as a script, sent as a payload, or passed through `primitiveParameters`.
800
742
 
801
- Useful for search, preview generation, parsing, or any workflow where an older queued result becomes irrelevant.
743
+ #### Parameters and the scope of the Worker
744
+
745
+ The second parameter is the global scope of the Worker. Static values arrive in `context.primitiveParameters`:
802
746
 
803
747
  ```ts
804
- const result = await worker.override(
805
- latestPayload,
806
- 'Superseded by a newer request'
748
+ const worker = createWorker(
749
+ (_helpers, context) => {
750
+ const [prefix] = context.primitiveParameters;
751
+
752
+ return {
753
+ label: (text: string) => `${prefix} ${text}`,
754
+ };
755
+ },
756
+ {
757
+ primitiveParameters: ['Result:'] as [string],
758
+ }
807
759
  );
760
+
761
+ await worker.label('complete'); // 'Result: complete'
808
762
  ```
809
763
 
810
- `override()` cancels the current queued work and sends the new message after cancellation completes.
764
+ #### The message API inside the function
811
765
 
812
- For immediate cancellation/reboot behavior:
766
+ The function also receives `easyWorker`, the worker instance behind the methods. Everything the message API offers stays available, so existing runtime Workers can move over at their own pace.
813
767
 
814
768
  ```ts
815
- await worker.override(latestPayload, 'Superseded', { force: true });
816
- ```
817
-
818
- > `force: true` reboots the Worker. A Worker created directly from an existing native `Worker` instance cannot be rebooted by the library.
769
+ const worker = createWorker<{
770
+ double: (value: number) => number;
771
+ triple: (value: number) => number;
772
+ }>(({ easyWorker }) => {
773
+ // named handler, message style
774
+ easyWorker.onMessage<number, number>('double', (message) => {
775
+ message.resolve(message.payload * 2);
776
+ });
819
777
 
820
- ### ๐Ÿฅˆ Keep the Current Task, Replace the Rest
778
+ // handler for messages sent without a method
779
+ easyWorker.onMessage((message) => {
780
+ message.resolve();
781
+ });
821
782
 
822
- `overrideAfterCurrent()` allows the currently executing message to finish, cancels the other queued messages, then sends the replacement.
783
+ return {
784
+ triple: (value: number) => value * 3,
785
+ shutdown: () => easyWorker.close(),
786
+ };
787
+ });
823
788
 
824
- ```ts
825
- const result = await worker.overrideAfterCurrent(
826
- latestPayload,
827
- 'Queue replaced'
828
- );
789
+ await worker.double(21); // 42
790
+ await worker.triple(21); // 63
829
791
  ```
830
792
 
831
- This is useful when interrupting the current operation would be expensive or unsafe, but stale queued work should still be discarded.
793
+ Only the returned methods are inferred. Handlers registered through `easyWorker` are described in the generic, as above, or called with `unwrap(worker).sendToMethod(...)`.
832
794
 
833
- ### ๐Ÿงน Explicit Cleanup
795
+ #### Import scripts
834
796
 
835
- Dispose the worker when the owning feature or application no longer needs it.
797
+ External libraries load with the `scripts` option and appear in the scope of the Worker:
836
798
 
837
799
  ```ts
838
- await worker.dispose();
839
- ```
840
-
841
- `dispose()` cancels outstanding work, revokes the generated Worker URL when applicable, terminates Worker instances, and clears the pool.
800
+ const text = createWorker(
801
+ (_helpers, context) => {
802
+ // the script adds `Diff` to the scope of the Worker
803
+ const { diffWords } = context.Diff as {
804
+ diffWords: (before: string, after: string) => unknown[];
805
+ };
842
806
 
843
- ---
807
+ return {
808
+ compare: ({ before, after }: { before: string; after: string }) =>
809
+ diffWords(before, after),
810
+ };
811
+ },
812
+ {
813
+ scripts: ['https://cdn.jsdelivr.net/npm/diff@9.0.0/dist/diff.min.js'],
814
+ }
815
+ );
844
816
 
845
- ## ๐Ÿงฐ API Reference
817
+ const changes = await text.compare({ before, after });
818
+ ```
846
819
 
847
- ### `createEasyWebWorker(source, config?)`
820
+ No Worker file is required, and the script does not become part of your application bundle.
848
821
 
849
- Convenience factory that creates an `EasyWebWorker`.
822
+ #### Compose a Worker from several functions
850
823
 
851
- Supported sources:
824
+ Pass an array and every function runs in the same Worker. They share its scope, and the methods they return are merged.
852
825
 
853
826
  ```ts
854
- EasyWebWorkerBody
855
- EasyWebWorkerBody[]
856
- string
857
- URL
858
- Worker
859
- Worker[]
860
- ```
827
+ const worker = createWorker([
828
+ // reusable piece: leaves a helper in the scope of the Worker
829
+ (_helpers, context) => {
830
+ context.round = (value: number) => Math.round(value * 100) / 100;
831
+ },
861
832
 
862
- Example:
833
+ () => ({
834
+ double: (value: number) => value * 2,
835
+ describe: () => 'first',
836
+ }),
863
837
 
864
- ```ts
865
- const worker = createEasyWebWorker<Payload, Result>(workerBody, config);
866
- ```
838
+ (_helpers, context) => {
839
+ const round = context.round as (value: number) => number;
867
840
 
868
- ### `EasyWebWorker<TPayload, TResult>`
841
+ return {
842
+ average: (values: number[]) =>
843
+ round(values.reduce((total, value) => total + value, 0) / values.length),
869
844
 
870
- You can also instantiate the class directly:
871
-
872
- ```ts
873
- import { EasyWebWorker } from 'easy-web-worker';
845
+ // repeated method: the last function wins
846
+ describe: () => 'last',
847
+ };
848
+ },
849
+ ]);
874
850
 
875
- const worker = new EasyWebWorker<Payload, Result>(workerBody, config);
851
+ await worker.double(21); // 42
852
+ await worker.average([1, 2, 2]); // 1.67
853
+ await worker.describe(); // 'last'
876
854
  ```
877
855
 
878
- ### โš™๏ธ Worker Configuration
879
-
880
- | Option | Purpose |
881
- | --------------------- | ------------------------------------------------------------------------------------------------------------------------ |
882
- | `scripts` | Scripts to include in a runtime Worker |
883
- | `workerOptions` | Native [`WorkerOptions`](https://developer.mozilla.org/en-US/docs/Web/API/Worker/Worker) passed when a Worker is created |
884
- | `onWorkerError` | Handles native Worker errors |
885
- | `maxWorkers` | Maximum number of native Workers in the pool |
886
- | `keepAlive` | Keep created Workers alive after work completes |
887
- | `terminationDelay` | Delay before idle Workers are terminated when `keepAlive` is `false` |
888
- | `warmUpWorkers` | Create the configured Worker pool during initialization |
889
- | `primitiveParameters` | Static primitive initialization values exposed through `context.primitiveParameters` |
856
+ Types are merged the same way: `worker.describe` takes the signature of the last function. Write the array inline so each function keeps its own type.
890
857
 
891
- ### ๐Ÿ“ค `send(payload?, transfer?)`
858
+ #### Runtime Workers with the message API
892
859
 
893
- Send a message to the default Worker handler.
860
+ `createEasyWebWorker` builds a Worker from a function too, with messages instead of methods:
894
861
 
895
862
  ```ts
896
- const result = await worker.send(payload);
897
- ```
863
+ import { createEasyWebWorker } from 'easy-web-worker';
898
864
 
899
- With transferables:
865
+ const worker = createEasyWebWorker<number, number>(({ onMessage }) => {
866
+ onMessage((message) => {
867
+ message.resolve(message.payload * 2);
868
+ });
869
+ });
900
870
 
901
- ```ts
902
- const result = await worker.send(payload, [buffer]);
871
+ await worker.send(21); // 42
903
872
  ```
904
873
 
905
- Returns a `CancelablePromise<TResult>`.
874
+ The first generic is the payload of `send()`. The second is the value passed to `message.resolve()`. It also accepts an array of functions that share the scope of the Worker, to compose a Worker from reusable pieces.
906
875
 
907
- ### ๐ŸŽฏ `sendToMethod(method, payload?, transfer?)`
876
+ > **On the website:** [Runtime workers](https://johnny-quesada-developer.github.io/easy-web-worker/docs/runtime-workers/) ยท [Live example: a worker without a file](https://johnny-quesada-developer.github.io/easy-web-worker/examples/worker-without-a-file/)
908
877
 
909
- Send a message to a named `onMessage(method, callback)` handler.
878
+ ---
910
879
 
911
- ```ts
912
- const result = await worker.sendToMethod<Result, Payload>('calculate', payload);
913
- ```
880
+ ### The message API with `StaticEasyWebWorker`
914
881
 
915
- ### ๐Ÿ›‘ `cancelAll(reason?, config?)`
882
+ `defineWorker` is built on a message API that you can use directly, in Worker files and in runtime Workers.
916
883
 
917
- Cancel all currently tracked messages.
884
+ #### The Basics
918
885
 
919
886
  ```ts
920
- await worker.cancelAll('Canceled by user');
921
- ```
887
+ // worker.ts
888
+ import { createStaticEasyWebWorker } from 'easy-web-worker/createStaticEasyWebWorker';
922
889
 
923
- Force an immediate Worker reboot:
890
+ const { onMessage } = createStaticEasyWebWorker<number, number>((message) => {
891
+ message.resolve(message.payload * 2);
892
+ });
924
893
 
925
- ```ts
926
- await worker.cancelAll('Reset', {
927
- force: true,
894
+ onMessage<string, string>('uppercase', (message) => {
895
+ message.resolve(message.payload.toUpperCase());
928
896
  });
929
897
  ```
930
898
 
931
- ### ๐Ÿฅ‡ `override(payload?, reason?, config?)`
899
+ ```ts
900
+ // main.ts
901
+ import { createEasyWebWorker } from 'easy-web-worker';
932
902
 
933
- Cancel current queued work and send a replacement message.
903
+ const worker = createEasyWebWorker<number, number>(source);
934
904
 
935
- ```ts
936
- const result = await worker.override(payload, 'Superseded');
905
+ await worker.send(21); // default handler
906
+ await worker.sendToMethod<string, string>('uppercase', 'hello'); // result type first
937
907
  ```
938
908
 
939
- ### ๐Ÿฅˆ `overrideAfterCurrent(payload?, reason?, config?)`
909
+ #### Typed methods for an existing Worker
940
910
 
941
- Allow the current message to complete, cancel the remaining queue, then send a replacement.
911
+ A Worker written with the message API can still be called with typed methods. Describe them:
942
912
 
943
913
  ```ts
944
- const result = await worker.overrideAfterCurrent(payload, 'Queue replaced');
945
- ```
914
+ const worker = createWorker<{ uppercase: (text: string) => string }>(source);
946
915
 
947
- ### ๐Ÿ”„ `reboot(reason?)`
916
+ await worker.uppercase('hello');
917
+ ```
948
918
 
949
- Terminate the current Worker pool, cancel tracked messages, and initialize the Worker again.
919
+ > **On the website:** [Message API](https://johnny-quesada-developer.github.io/easy-web-worker/docs/message-api/) ยท [API reference](https://johnny-quesada-developer.github.io/easy-web-worker/docs/api-reference/)
950
920
 
951
- ```ts
952
- worker.reboot('Worker configuration reset');
953
- ```
921
+ ---
954
922
 
955
- A Worker created from an existing native `Worker` instance cannot be rebooted by the library.
923
+ ## Advanced Patterns
956
924
 
957
- ### ๐Ÿงน `dispose()`
925
+ ### Latest request wins
958
926
 
959
- Cancel outstanding messages and release the Worker resources.
927
+ For search, previews and autocomplete, an older request is worthless once a newer one exists.
960
928
 
961
929
  ```ts
962
- await worker.dispose();
963
- ```
930
+ const search = createEasyWebWorker<string, SearchResult[]>(searchBody);
964
931
 
965
- ---
932
+ input.oninput = async () => {
933
+ // cancels what is pending, then sends the new message
934
+ const results = await search.override(input.value, 'Superseded');
966
935
 
967
- ## ๐Ÿ’ฌ `IEasyWebWorkerMessage<TPayload, TResult>`
936
+ render(results);
937
+ };
938
+ ```
968
939
 
969
- Every `onMessage()` handler receives an `IEasyWebWorkerMessage`.
940
+ `overrideAfterCurrent` lets the call in progress finish and replaces only what is waiting behind it.
970
941
 
971
- ### ๐Ÿ“ฅ `payload`
942
+ ### Stop work that never pauses
972
943
 
973
- The payload sent from the main thread.
944
+ A Worker stuck in a synchronous loop cannot receive a cancellation. Restart it instead:
974
945
 
975
946
  ```ts
976
- onMessage((message) => {
977
- console.log(message.payload);
978
- });
979
- ```
980
-
981
- ### โœ… `resolve(result?, transfer?)`
982
-
983
- Resolve the main-thread promise.
947
+ import { unwrap } from 'easy-web-worker/createWorker';
984
948
 
985
- ```ts
986
- message.resolve(result);
949
+ // terminates the Workers, rejects the pending calls, starts fresh ones
950
+ await unwrap(math).cancelAll('Taking too long', { force: true });
987
951
  ```
988
952
 
989
- ### โŒ `reject(reason?, transfer?)`
990
-
991
- Reject the main-thread promise.
953
+ ### Clean up when the owner goes away
992
954
 
993
955
  ```ts
994
- message.reject(new Error('Something failed'));
956
+ await unwrap(math).dispose(); // typed workers
957
+ await worker.dispose(); // EasyWebWorker
995
958
  ```
996
959
 
997
- ### ๐Ÿ›‘ `cancel(reason?, transfer?)`
960
+ `dispose` cancels the pending calls, revokes the generated Worker URL when there is one, and terminates the Workers.
998
961
 
999
- Cancel the request from inside the Worker.
962
+ > **On the website:** [Cancellation and progress](https://johnny-quesada-developer.github.io/easy-web-worker/docs/cancellation-and-progress/) ยท [Testing](https://johnny-quesada-developer.github.io/easy-web-worker/docs/testing/) ยท [Troubleshooting](https://johnny-quesada-developer.github.io/easy-web-worker/docs/troubleshooting/)
1000
963
 
1001
- ```ts
1002
- message.cancel('No longer needed');
1003
- ```
1004
-
1005
- ### ๐Ÿ“Š `reportProgress(percentage, payload?, transfer?)`
964
+ ---
1006
965
 
1007
- Report progress while keeping the request pending.
966
+ ## Documentation and examples
1008
967
 
1009
- ```ts
1010
- message.reportProgress(50, {
1011
- processed: 500,
1012
- total: 1000,
1013
- });
1014
- ```
968
+ Everything in this README runs for real on the [easy-web-worker website](https://johnny-quesada-developer.github.io/easy-web-worker/): real Web Workers, in your browser, against the same files that are published to npm.
1015
969
 
1016
- ### ๐ŸŽฌ Lifecycle Subscriptions
970
+ ### See it before you install it
1017
971
 
1018
- ```ts
1019
- message.onResolve(callback);
1020
- message.onReject(callback);
1021
- message.onCancel(callback);
1022
- message.onProgress(callback);
1023
- message.onFinalize(callback);
1024
- ```
972
+ | Live example | What you will see |
973
+ | --- | --- |
974
+ | [Keep the page responsive](https://johnny-quesada-developer.github.io/easy-web-worker/examples/keep-the-page-responsive/) | The same computation on the main thread and in a Worker. One freezes the page, the other does not. |
975
+ | [Progress and cancellation](https://johnny-quesada-developer.github.io/easy-web-worker/examples/progress-and-cancellation/) | A long search that reports how far it is, and stops inside the Worker when you cancel it. |
976
+ | [Worker pool](https://johnny-quesada-developer.github.io/easy-web-worker/examples/worker-pool/) | Three searches with one Worker and then with three. Same answers, sooner. |
977
+ | [Transferable buffers](https://johnny-quesada-developer.github.io/easy-web-worker/examples/transferable-buffers/) | A large buffer moved to the Worker instead of copied. |
978
+ | [Worker without a file](https://johnny-quesada-developer.github.io/easy-web-worker/examples/worker-without-a-file/) | A Worker created from a function, with no extra file and no bundler setup. |
1025
979
 
1026
- Each subscription returns an unsubscribe function.
980
+ Every example shows its full source next to the running result.
1027
981
 
1028
- ### ๐Ÿ” Status
982
+ ### Then go deeper
1029
983
 
1030
- ```ts
1031
- message.getStatus();
1032
- message.isPending();
1033
- ```
984
+ | Guide | What you will find |
985
+ | --- | --- |
986
+ | [Getting started](https://johnny-quesada-developer.github.io/easy-web-worker/docs/getting-started/) | Define your first worker and call it from the main thread. |
987
+ | [Guides](https://johnny-quesada-developer.github.io/easy-web-worker/docs/) | Typed workers, cancellation, progress, transferables, pools and bundler setup. |
988
+ | [API reference](https://johnny-quesada-developer.github.io/easy-web-worker/docs/api-reference/) | `defineWorker`, `createWorker`, `unwrap`, the message and the configuration. |
989
+ | [Testing](https://johnny-quesada-developer.github.io/easy-web-worker/docs/testing/) | Test worker logic as plain functions, and the calling code without a Worker. |
990
+ | [Troubleshooting](https://johnny-quesada-developer.github.io/easy-web-worker/docs/troubleshooting/) | Symptoms, causes and fixes. |
991
+ | [Platform and versions](https://johnny-quesada-developer.github.io/easy-web-worker/docs/platform-and-versions/) | Browser support and what changes when upgrading. |
1034
992
 
1035
993
  ---
1036
994
 
1037
- ## ๐ŸŽจ Worker Source Options
995
+ ## Built to grow with your application
996
+
997
+ | Without `easy-web-worker` | With `easy-web-worker` |
998
+ | ------------------------------------ | ---------------------------------------- |
999
+ | `postMessage()` protocols | Functions |
1000
+ | Manual request IDs | Automatic routing |
1001
+ | Response matching | Promises |
1002
+ | Custom error transport | Throw / catch |
1003
+ | Custom cancellation protocol | `task.cancel()` |
1004
+ | Custom progress messages | `.onProgress()` |
1005
+ | Hand-maintained TypeScript contracts | Inferred types |
1006
+ | Manual Worker orchestration | Built-in pools |
1007
+ | Manual lifecycle handling | Warm-up, keep-alive and idle termination |
1038
1008
 
1039
- | Source | Best for | Worker managed by `easy-web-worker`? |
1040
- | ------------------- | -------------------------------------------- | ------------------------------------ |
1041
- | Function template | Small/medium Worker logic with minimal setup | โœ… |
1042
- | Template array | Composable runtime Worker logic | โœ… |
1043
- | `string` / `URL` | Dedicated static Worker file | โœ… |
1044
- | Existing `Worker` | Integrating an existing native Worker | โœ… Wrapped |
1045
- | Existing `Worker[]` | Supplying your own pre-created pool | โœ… Wrapped |
1009
+ The Worker is still a real Web Worker, and the browser still provides the isolation and the extra thread. `easy-web-worker` removes what you would otherwise build around it.
1046
1010
 
1047
- All approaches use the same message-oriented API on the main thread.
1011
+ > **Write Worker code like an API, call it like normal asynchronous JavaScript, and scale it without rebuilding the communication layer.**
1048
1012
 
1049
1013
  ---
1050
1014
 
1051
- ## ๐ŸŽ“ Learning Resources
1015
+ ## Get Started Now
1052
1016
 
1053
- - ๐ŸŒ [Live example โ€” text diff](https://johnny-quesada-developer.github.io/easy-web-workers-example/)
1054
- - ๐ŸŽฅ [Introduction video](https://www.youtube.com/watch?v=CK-Uri9lDOE)
1055
- - ๐Ÿงฉ [Example repository](https://github.com/johnny-quesada-developer/easy-web-workers-example)
1056
- - ๐Ÿงช [CodePen example](https://codepen.io/johnnynabetes/full/wvOvygW)
1057
- - ๐Ÿ“˜ [MDN โ€” Using Web Workers](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers)
1058
- - ๐Ÿ“ฆ [npm โ€” easy-web-worker](https://www.npmjs.com/package/easy-web-worker)
1017
+ ```bash
1018
+ npm install easy-web-worker
1019
+ ```
1059
1020
 
1060
- ---
1021
+ Then in your Worker file:
1022
+
1023
+ ```ts
1024
+ import { defineWorker } from 'easy-web-worker/defineWorker';
1061
1025
 
1062
- ## ๐ŸŒ Framework Compatibility
1026
+ const fibonacci = (n: number): number =>
1027
+ n <= 1 ? n : fibonacci(n - 1) + fibonacci(n - 2);
1063
1028
 
1064
- `easy-web-worker` is built around the browser's native Worker API, so it is not tied to a UI framework.
1029
+ const worker = defineWorker(() => ({ fibonacci }));
1065
1030
 
1066
- | Environment | Usage |
1067
- | ------------------ | --------------------- |
1068
- | React | โœ… |
1069
- | Angular | โœ… |
1070
- | Vue | โœ… |
1071
- | Svelte | โœ… |
1072
- | Vanilla JavaScript | โœ… |
1073
- | TypeScript | โœ… Strongly typed API |
1031
+ export type MathWorker = typeof worker;
1032
+ ```
1074
1033
 
1075
- The important requirement is a runtime with browser Web Worker support.
1034
+ And in your app:
1076
1035
 
1077
- ---
1036
+ ```ts
1037
+ import { createWorker } from 'easy-web-worker/createWorker';
1038
+ import type { MathWorker } from './math.worker';
1078
1039
 
1079
- ## ๐ŸŽ‰ Why Developers Choose This
1040
+ const math = createWorker<MathWorker>(source);
1080
1041
 
1081
- ### The Bottom Line
1042
+ console.log(await math.fibonacci(40)); // 102334155
1043
+ ```
1082
1044
 
1083
- | What You Get | What You Avoid |
1084
- | ----------------------------- | -------------------------------------- |
1085
- | โœ… Real native Worker threads | โŒ Main-thread CPU bottlenecks |
1086
- | โœ… Promise-based messaging | โŒ Manual message-ID plumbing |
1087
- | โœ… Cancellation | โŒ Abandoned long-running work |
1088
- | โœ… Progress events | โŒ Custom progress protocols |
1089
- | โœ… Named Worker methods | โŒ Giant message-routing switches |
1090
- | โœ… Transferable support | โŒ Unnecessary large-data cloning |
1091
- | โœ… Worker pools | โŒ Hand-written concurrency management |
1092
- | โœ… Runtime + static Workers | โŒ One forced architecture |
1093
- | โœ… TypeScript generics | โŒ Untyped request/response contracts |
1045
+ Continue with the [guides and interactive examples](https://johnny-quesada-developer.github.io/easy-web-worker/) to build your next worker.
1094
1046
 
1095
1047
  ---
1096
1048
 
1097
- ## ๐Ÿค Collaborators
1049
+ ## Collaborators
1098
1050
 
1099
1051
  <div align="center">
1100
1052
 
@@ -1121,35 +1073,19 @@ The important requirement is a runtime with browser Web Worker support.
1121
1073
 
1122
1074
  ---
1123
1075
 
1124
- ## ๐Ÿš€ Get Started Now
1125
-
1126
- ```bash
1127
- npm install easy-web-worker
1128
- ```
1129
-
1130
- Then:
1131
-
1132
- ```ts
1133
- import { createEasyWebWorker } from 'easy-web-worker';
1076
+ ## Built by Johnny Quesada
1134
1077
 
1135
- const worker = createEasyWebWorker<string, string>(({ onMessage }) => {
1136
- onMessage((message) => {
1137
- message.resolve(message.payload.toUpperCase());
1138
- });
1139
- });
1078
+ `easy-web-worker` exists because using another thread should not require building a messaging framework first.
1140
1079
 
1141
- console.log(await worker.send('hello worker'));
1142
- // HELLO WORKER
1143
- ```
1080
+ If it makes your codebase simpler, consider starring the project. It helps other developers discover it.
1144
1081
 
1145
- **That's it. You're running work in a real Web Worker.** ๐ŸŽ‰
1082
+ [**Star on GitHub**](https://github.com/johnny-quesada-developer/easy-web-worker) ยท [**Read the docs**](https://johnny-quesada-developer.github.io/easy-web-worker/docs/) ยท [**Run the examples**](https://johnny-quesada-developer.github.io/easy-web-worker/examples/) ยท [**Report an issue**](https://github.com/johnny-quesada-developer/easy-web-worker/issues)
1146
1083
 
1147
- ---
1148
-
1149
- <div align="center">
1084
+ ## Earlier resources
1150
1085
 
1151
- ### Built with โค๏ธ for developers who want the main thread to stay responsive
1086
+ Demos and walkthroughs from previous versions, kept for reference. For the current API, start with the
1087
+ [documentation website](https://johnny-quesada-developer.github.io/easy-web-worker/).
1152
1088
 
1153
- **[โญ Star on GitHub](https://github.com/johnny-quesada-developer/easy-web-worker)** โ€ข **[๐Ÿ“ Report Issues](https://github.com/johnny-quesada-developer/easy-web-worker/issues)** โ€ข **[๐Ÿงฉ Examples](https://github.com/johnny-quesada-developer/easy-web-workers-example)**
1154
-
1155
- </div>
1089
+ - [Original text diff demo](https://johnny-quesada-developer.github.io/easy-web-workers-example/) and its [source](https://github.com/johnny-quesada-developer/easy-web-workers-example)
1090
+ - [Original video walkthrough](https://www.youtube.com/watch?v=CK-Uri9lDOE)
1091
+ - [Original CodePen example](https://codepen.io/johnnynabetes/full/wvOvygW)