nestworker 1.1.21 → 2.0.4
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/LICENSE +21 -21
- package/README.md +334 -143
- package/dist/core/worker.interfaces.d.ts +36 -0
- package/dist/{models/task.js → core/worker.interfaces.js} +1 -1
- package/dist/core/worker.interfaces.js.map +1 -0
- package/dist/core/worker.module.d.ts +15 -0
- package/dist/core/worker.module.js +43 -0
- package/dist/core/worker.module.js.map +1 -0
- package/dist/core/worker.pool.d.ts +20 -0
- package/dist/core/worker.pool.js +210 -0
- package/dist/core/worker.pool.js.map +1 -0
- package/dist/core/worker.service.d.ts +87 -0
- package/dist/core/worker.service.js +133 -0
- package/dist/core/worker.service.js.map +1 -0
- package/dist/decorators/worker-task.decorator.d.ts +37 -0
- package/dist/decorators/worker-task.decorator.js +48 -0
- package/dist/decorators/worker-task.decorator.js.map +1 -0
- package/dist/di/di-serializer.d.ts +18 -0
- package/dist/di/di-serializer.js +110 -0
- package/dist/di/di-serializer.js.map +1 -0
- package/dist/di/worker-container.d.ts +58 -0
- package/dist/di/worker-container.js +110 -0
- package/dist/di/worker-container.js.map +1 -0
- package/dist/discovery/discovery.service.d.ts +20 -0
- package/dist/discovery/discovery.service.js +94 -0
- package/dist/discovery/discovery.service.js.map +1 -0
- package/dist/example/config.service.d.ts +13 -0
- package/dist/example/config.service.js +35 -0
- package/dist/example/config.service.js.map +1 -0
- package/dist/example/image.service.d.ts +9 -0
- package/dist/example/image.service.js +108 -0
- package/dist/example/image.service.js.map +1 -0
- package/dist/example/main.d.ts +1 -0
- package/dist/example/main.js +59 -0
- package/dist/example/main.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/worker/worker-runtime.js +55 -0
- package/dist/worker/worker-runtime.js.map +1 -0
- package/package.json +58 -55
- package/CONTRIBUTING.md +0 -33
- package/dist/main.d.ts +0 -3
- package/dist/main.js +0 -29
- package/dist/main.js.map +0 -1
- package/dist/models/index.d.ts +0 -1
- package/dist/models/index.js +0 -18
- package/dist/models/index.js.map +0 -1
- package/dist/models/task.d.ts +0 -5
- package/dist/models/task.js.map +0 -1
- package/dist/tsconfig.build.tsbuildinfo +0 -1
- package/dist/worker/executor.d.ts +0 -5
- package/dist/worker/executor.js +0 -28
- package/dist/worker/executor.js.map +0 -1
- package/dist/worker/skeleton.d.ts +0 -6
- package/dist/worker/skeleton.js +0 -35
- package/dist/worker/skeleton.js.map +0 -1
- package/dist/worker/worker.js +0 -6
- package/dist/worker/worker.js.map +0 -1
- package/nest-cli.json +0 -8
- package/tsconfig.build.json +0 -4
- package/tsconfig.json +0 -25
- /package/dist/worker/{worker.d.ts → worker-runtime.d.ts} +0 -0
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c)
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,143 +1,334 @@
|
|
|
1
|
-

|
|
2
|
-

|
|
3
|
-

|
|
4
|
-

|
|
5
|
-

|
|
6
|
-

|
|
7
|
-

|
|
8
|
-

|
|
9
|
-

|
|
10
|
-

|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
import {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
1
|
+

|
|
2
|
+

|
|
3
|
+

|
|
4
|
+

|
|
5
|
+

|
|
6
|
+

|
|
7
|
+

|
|
8
|
+

|
|
9
|
+

|
|
10
|
+

|
|
11
|
+
|
|
12
|
+
<p align="center">
|
|
13
|
+
<img src="icon.svg" width="120" alt="nestworker" />
|
|
14
|
+
</p>
|
|
15
|
+
|
|
16
|
+
# nestworker
|
|
17
|
+
|
|
18
|
+
Enterprise-grade worker thread module for NestJS. Offload CPU-bound work to a managed pool of Node.js worker threads without blocking the event loop — with decorator-driven auto-discovery, priority queuing, and transparent NestJS dependency injection inside workers.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Features
|
|
23
|
+
|
|
24
|
+
- **Worker pool** — pre-spawned threads with backpressure queue; no jobs are ever dropped
|
|
25
|
+
- **Priority queue** — `HIGH / NORMAL / LOW`, binary-search sorted
|
|
26
|
+
- **Decorator discovery** — `@WorkerClass` + `@WorkerTask` replace all manual registration
|
|
27
|
+
- **DI in workers** — declared deps are snapshotted and reconstructed in each thread
|
|
28
|
+
- **Dynamic imports** — use `await import('node:os')` inside task methods
|
|
29
|
+
- **Per-task timeout** — configurable via decorator or overridden per call
|
|
30
|
+
- **Safe shutdown** — drains queue, terminates workers with a 2-second deadline
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Requirements
|
|
35
|
+
|
|
36
|
+
| Package | Version |
|
|
37
|
+
|---|---|
|
|
38
|
+
| Node.js | ≥ 16 (worker_threads) |
|
|
39
|
+
| `@nestjs/common` | `^10` or `^11` |
|
|
40
|
+
| `@nestjs/core` | `^10` or `^11` |
|
|
41
|
+
| `reflect-metadata` | `^0.1` or `^0.2` |
|
|
42
|
+
| TypeScript `target` | `ES2022` or higher |
|
|
43
|
+
|
|
44
|
+
`tsconfig.json` must have:
|
|
45
|
+
|
|
46
|
+
```json
|
|
47
|
+
{
|
|
48
|
+
"compilerOptions": {
|
|
49
|
+
"target": "ES2022",
|
|
50
|
+
"experimentalDecorators": true,
|
|
51
|
+
"emitDecoratorMetadata": true
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Installation
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
npm install nestworker
|
|
62
|
+
```
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Quick Start
|
|
66
|
+
|
|
67
|
+
### 1. Import `WorkerModule` in your root module
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
// app.module.ts
|
|
71
|
+
import { Module } from '@nestjs/common';
|
|
72
|
+
import { WorkerModule } from 'nestworker';
|
|
73
|
+
import { ConfigService } from './config.service';
|
|
74
|
+
import { ImageService } from './image.service';
|
|
75
|
+
|
|
76
|
+
@Module({
|
|
77
|
+
imports: [WorkerModule.forRoot()],
|
|
78
|
+
providers: [ConfigService, ImageService], // register your @WorkerClass providers here
|
|
79
|
+
})
|
|
80
|
+
export class AppModule {}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### 2. Decorate your service
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
// image.service.ts
|
|
87
|
+
import { Injectable } from '@nestjs/common';
|
|
88
|
+
import { WorkerClass, WorkerTask } from 'nestworker';
|
|
89
|
+
import { ConfigService } from './config.service';
|
|
90
|
+
|
|
91
|
+
@Injectable()
|
|
92
|
+
@WorkerClass({ deps: [ConfigService] }) // deps are injected into the worker
|
|
93
|
+
export class ImageService {
|
|
94
|
+
constructor(private readonly configService: ConfigService) {}
|
|
95
|
+
|
|
96
|
+
@WorkerTask({ priority: 'HIGH' })
|
|
97
|
+
resizeImage(value: number): number {
|
|
98
|
+
// runs in a worker thread — configService works normally here
|
|
99
|
+
const multiplier = this.configService.getNumber('MULTIPLIER');
|
|
100
|
+
let total = 0;
|
|
101
|
+
for (let i = 0; i < 10_000_000; i++) total += i * value * multiplier;
|
|
102
|
+
return total;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
@WorkerTask({ priority: 'NORMAL', timeout: 5000 })
|
|
106
|
+
generateThumbnail(width: number, height: number): string {
|
|
107
|
+
let hash = 0;
|
|
108
|
+
for (let i = 0; i < 5_000_000; i++) hash ^= (i * width * height) | 0;
|
|
109
|
+
return `thumb_${hash.toString(16)}_${width}x${height}.webp`;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### 3. Inject `WorkerService` and call `run()`
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
// image.controller.ts
|
|
118
|
+
import { Controller, Get } from '@nestjs/common';
|
|
119
|
+
import { WorkerService } from 'nestworker';
|
|
120
|
+
|
|
121
|
+
@Controller('images')
|
|
122
|
+
export class ImageController {
|
|
123
|
+
constructor(private readonly workerService: WorkerService) {}
|
|
124
|
+
|
|
125
|
+
@Get('resize')
|
|
126
|
+
async resize() {
|
|
127
|
+
return this.workerService.run<number>('ImageService', 'resizeImage', [5]);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
@Get('thumbnail')
|
|
131
|
+
async thumbnail() {
|
|
132
|
+
return this.workerService.run<string>(
|
|
133
|
+
'ImageService', 'generateThumbnail', [1920, 1080]
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Using Modules Inside Task Methods
|
|
141
|
+
|
|
142
|
+
Worker tasks are reconstructed from class source via `eval()`. Top-level `import` statements from your file are **not available** inside the worker. Use one of these two patterns instead.
|
|
143
|
+
|
|
144
|
+
### Dynamic import — preferred
|
|
145
|
+
|
|
146
|
+
`import()` is a language keyword, not a variable. It works natively inside `eval()`'d code with no setup, and is compatible with both ESM and CommonJS projects.
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
@WorkerTask()
|
|
150
|
+
async moduleImport(): Promise<string> {
|
|
151
|
+
const os = await import('node:os');
|
|
152
|
+
return `Import os size ${os.cpus().length}`
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Inline `require()` — CJS projects only
|
|
157
|
+
|
|
158
|
+
`require` is injected into the eval scope by `WorkerContainer`, so it works in CommonJS projects.
|
|
159
|
+
|
|
160
|
+
```ts
|
|
161
|
+
@WorkerTask()
|
|
162
|
+
async moduleRequire(): Promise<string> {
|
|
163
|
+
const os = require('node:os');
|
|
164
|
+
return `Require os size ${os.cpus().length}`
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### What is safe to import inside a worker
|
|
169
|
+
|
|
170
|
+
| ✅ Safe | ❌ Not safe |
|
|
171
|
+
|---|---|
|
|
172
|
+
| Node built-ins: `os`, `path`, `crypto`, `zlib`, `fs` | HTTP clients (`axios`, `fetch`) |
|
|
173
|
+
| Pure computation libraries | Database drivers |
|
|
174
|
+
| `Buffer`, `Math`, `Date` | `Socket`, `Stream` |
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## API
|
|
179
|
+
|
|
180
|
+
### `WorkerModule.forRoot(options?)`
|
|
181
|
+
|
|
182
|
+
Registers the module globally. Call once at the application root.
|
|
183
|
+
|
|
184
|
+
```ts
|
|
185
|
+
WorkerModule.forRoot({
|
|
186
|
+
poolSize: 4, // default: os.cpus().length
|
|
187
|
+
})
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
| Option | Type | Default | Description |
|
|
191
|
+
|---|---|---|---|
|
|
192
|
+
| `poolSize` | `number` | `os.cpus().length` | Number of worker threads to spawn |
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
### `@WorkerClass(options?)`
|
|
197
|
+
|
|
198
|
+
Class decorator. Marks a NestJS provider as a container of worker tasks.
|
|
199
|
+
|
|
200
|
+
```ts
|
|
201
|
+
@WorkerClass({ deps: [ConfigService, LoggerService] })
|
|
202
|
+
export class MyService { ... }
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
| Option | Type | Description |
|
|
206
|
+
|---|---|---|
|
|
207
|
+
| `deps` | `Type[]` | Injectable dependencies to reconstruct inside workers |
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
### `@WorkerTask(options?)`
|
|
212
|
+
|
|
213
|
+
Method decorator. Marks a method to be offloaded to a worker thread.
|
|
214
|
+
|
|
215
|
+
```ts
|
|
216
|
+
@WorkerTask({ priority: 'HIGH', timeout: 3000 })
|
|
217
|
+
heavyComputation(input: number): number { ... }
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
| Option | Type | Default | Description |
|
|
221
|
+
|---|---|---|---|
|
|
222
|
+
| `priority` | `'HIGH' \| 'NORMAL' \| 'LOW'` | `'NORMAL'` | Queue priority — `HIGH` jobs run first |
|
|
223
|
+
| `timeout` | `number` | `undefined` | Reject the job after this many ms |
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
### `WorkerService.run<T>(serviceName, methodName, args, overrides?)`
|
|
228
|
+
|
|
229
|
+
Executes a `@WorkerTask` method in a worker thread.
|
|
230
|
+
|
|
231
|
+
```ts
|
|
232
|
+
// Uses priority/timeout from the @WorkerTask decorator
|
|
233
|
+
const result = await workerService.run<number>('ImageService', 'resizeImage', [5]);
|
|
234
|
+
|
|
235
|
+
// Override priority or timeout for a specific call
|
|
236
|
+
const result = await workerService.run<number>(
|
|
237
|
+
'ImageService', 'resizeImage', [5],
|
|
238
|
+
{ priority: 'LOW', timeout: 10_000 }
|
|
239
|
+
);
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
| Parameter | Type | Description |
|
|
243
|
+
|---|---|---|
|
|
244
|
+
| `serviceName` | `string` | Class name of the `@WorkerClass` provider |
|
|
245
|
+
| `methodName` | `string` | Method name decorated with `@WorkerTask` |
|
|
246
|
+
| `args` | `unknown[]` | Arguments to pass — must be structuredClone-compatible |
|
|
247
|
+
| `overrides` | `object` | Optional `priority` / `timeout` override for this call |
|
|
248
|
+
|
|
249
|
+
Returns a `Promise<T>` that resolves with the method's return value.
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## How DI in Workers Works
|
|
254
|
+
|
|
255
|
+
Worker threads run in an isolated V8 context — they share no heap with the main thread. Passing live NestJS services across the boundary is impossible.
|
|
256
|
+
|
|
257
|
+
This module solves it in three steps:
|
|
258
|
+
|
|
259
|
+
**1. Main thread — `serializeForWorker()`**
|
|
260
|
+
|
|
261
|
+
`Class.toString()` extracts each class as a plain JS source string (no imports, no decorators). Each dep's data properties are snapshotted via `structuredClone`. Both are sent to workers via `workerData`.
|
|
262
|
+
|
|
263
|
+
**2. Worker thread — `WorkerContainer`**
|
|
264
|
+
|
|
265
|
+
The class source strings are `eval()`'d back into constructors. Each dep is reconstructed as `Object.create(DepClass.prototype) + Object.assign(snapshot)` — restoring prototype methods AND runtime state. The service class is then `new ServiceClass(...depInstances)`.
|
|
266
|
+
|
|
267
|
+
**3. Result**
|
|
268
|
+
|
|
269
|
+
`this.configService.get('KEY')` inside a worker task works exactly as on the main thread — as long as the dep reads from plain data (no DB connections, no HTTP clients).
|
|
270
|
+
|
|
271
|
+
```
|
|
272
|
+
MAIN THREAD WORKER THREAD
|
|
273
|
+
──────────────────────────────── ────────────────────────────────────
|
|
274
|
+
WorkerService.run()
|
|
275
|
+
→ discovery.scan()
|
|
276
|
+
→ ConfigService live instance
|
|
277
|
+
→ snapshot: { config: {...} } → Object.create(ConfigService.prototype)
|
|
278
|
+
→ classSource: "class Cfg..." → eval("class ConfigService { get()... }")
|
|
279
|
+
Object.assign(inst, snapshot)
|
|
280
|
+
→ ImageService classSource → eval("class ImageService {...}")
|
|
281
|
+
new ImageService(configInst)
|
|
282
|
+
this.configService.get() ✓
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### What deps can be passed to workers
|
|
286
|
+
|
|
287
|
+
✅ Services holding plain config data (`Record`, `Map`, arrays, primitives)
|
|
288
|
+
✅ Services whose methods only read from their own properties
|
|
289
|
+
❌ Services that hold DB connections, HTTP clients, or open streams
|
|
290
|
+
❌ Services with `Socket`, `Stream`, or non-cloneable properties
|
|
291
|
+
|
|
292
|
+
---
|
|
293
|
+
|
|
294
|
+
## Priority Queue
|
|
295
|
+
|
|
296
|
+
Jobs queue when all threads are busy. The queue is sorted by priority — `HIGH` always runs before `NORMAL` which runs before `LOW`. Within the same priority, jobs are FIFO.
|
|
297
|
+
|
|
298
|
+
```ts
|
|
299
|
+
// These four tasks are dispatched to the pool concurrently.
|
|
300
|
+
// HIGH tasks run first regardless of arrival order.
|
|
301
|
+
await Promise.all([
|
|
302
|
+
workerService.run('Svc', 'lowPriorityTask', [], { priority: 'LOW' }),
|
|
303
|
+
workerService.run('Svc', 'highPriorityTask', [], { priority: 'HIGH' }),
|
|
304
|
+
workerService.run('Svc', 'normalPriorityTask', [], { priority: 'NORMAL' }),
|
|
305
|
+
workerService.run('Svc', 'highPriorityTask2', [], { priority: 'HIGH' }),
|
|
306
|
+
]);
|
|
307
|
+
```
|
|
308
|
+
---
|
|
309
|
+
|
|
310
|
+
## Constraints
|
|
311
|
+
|
|
312
|
+
### Arguments and Return Values
|
|
313
|
+
|
|
314
|
+
Task arguments and return values cross a thread boundary via `postMessage()` and must be [structuredClone](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm) compatible.
|
|
315
|
+
|
|
316
|
+
| ✅ Supported | ❌ Not supported |
|
|
317
|
+
|---|---|
|
|
318
|
+
| Primitives, plain objects, arrays | Class instances |
|
|
319
|
+
| `Map`, `Set`, `ArrayBuffer` | Functions |
|
|
320
|
+
| `TypedArray`, `DataView` | `Promise`, `WeakMap` |
|
|
321
|
+
|
|
322
|
+
### Circular deps
|
|
323
|
+
|
|
324
|
+
Circular dependencies between `@WorkerClass({ deps })` entries are not supported.
|
|
325
|
+
|
|
326
|
+
---
|
|
327
|
+
|
|
328
|
+
## Contributing
|
|
329
|
+
|
|
330
|
+
See the [contributing guide](https://github.com/VaheHak/nestworker/blob/master/CONTRIBUTING.md) for detailed instructions on how to get started with our project.
|
|
331
|
+
|
|
332
|
+
## License
|
|
333
|
+
|
|
334
|
+
Licensed under [MIT](https://github.com/VaheHak/nestworker/blob/master/LICENSE).
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export type TaskPriority = 'HIGH' | 'NORMAL' | 'LOW';
|
|
2
|
+
export interface WorkerJob {
|
|
3
|
+
serviceName: string;
|
|
4
|
+
methodName: string;
|
|
5
|
+
args: unknown[];
|
|
6
|
+
/** Sourced from @WorkerTask({ priority }) — used by WorkerPool to sort the queue */
|
|
7
|
+
priority: TaskPriority;
|
|
8
|
+
/** Sourced from @WorkerTask({ timeout }) — rejects the job after this many ms */
|
|
9
|
+
timeout?: number;
|
|
10
|
+
}
|
|
11
|
+
export interface WorkerResult<T = unknown> {
|
|
12
|
+
ok: boolean;
|
|
13
|
+
data?: T;
|
|
14
|
+
error?: {
|
|
15
|
+
message: string;
|
|
16
|
+
stack?: string;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export interface DiscoveredTask {
|
|
20
|
+
serviceName: string;
|
|
21
|
+
methodName: string;
|
|
22
|
+
priority: TaskPriority;
|
|
23
|
+
timeout?: number;
|
|
24
|
+
/** Bound method on the live main-thread instance */
|
|
25
|
+
fn: (...args: unknown[]) => unknown;
|
|
26
|
+
/** Class constructor — used to locate the compiled file and read metadata */
|
|
27
|
+
metatype: new (...args: unknown[]) => unknown;
|
|
28
|
+
/** Live main-thread service instance — used to locate dep property keys */
|
|
29
|
+
instance: unknown;
|
|
30
|
+
/** Resolved dep instances from the NestJS container, in declaration order */
|
|
31
|
+
deps: unknown[];
|
|
32
|
+
}
|
|
33
|
+
export interface WorkerModuleOptions {
|
|
34
|
+
/** Number of worker threads. Defaults to os.cpus().length */
|
|
35
|
+
poolSize?: number;
|
|
36
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker.interfaces.js","sourceRoot":"","sources":["../../src/core/worker.interfaces.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { DynamicModule } from '@nestjs/common';
|
|
2
|
+
import type { WorkerModuleOptions } from './worker.interfaces';
|
|
3
|
+
/**
|
|
4
|
+
* WorkerModule – import once at the root of your application.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* @Module({
|
|
8
|
+
* imports: [WorkerModule.forRoot()],
|
|
9
|
+
* providers: [ConfigService, ImageService],
|
|
10
|
+
* })
|
|
11
|
+
* export class AppModule {}
|
|
12
|
+
*/
|
|
13
|
+
export declare class WorkerModule {
|
|
14
|
+
static forRoot(options?: WorkerModuleOptions): DynamicModule;
|
|
15
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var WorkerModule_1;
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.WorkerModule = void 0;
|
|
11
|
+
const common_1 = require("@nestjs/common");
|
|
12
|
+
const core_1 = require("@nestjs/core");
|
|
13
|
+
const worker_service_1 = require("./worker.service");
|
|
14
|
+
const discovery_service_1 = require("../discovery/discovery.service");
|
|
15
|
+
/**
|
|
16
|
+
* WorkerModule – import once at the root of your application.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* @Module({
|
|
20
|
+
* imports: [WorkerModule.forRoot()],
|
|
21
|
+
* providers: [ConfigService, ImageService],
|
|
22
|
+
* })
|
|
23
|
+
* export class AppModule {}
|
|
24
|
+
*/
|
|
25
|
+
let WorkerModule = WorkerModule_1 = class WorkerModule {
|
|
26
|
+
static forRoot(options = {}) {
|
|
27
|
+
return {
|
|
28
|
+
module: WorkerModule_1,
|
|
29
|
+
imports: [core_1.DiscoveryModule],
|
|
30
|
+
providers: [
|
|
31
|
+
{ provide: 'WORKER_OPTIONS', useValue: options },
|
|
32
|
+
discovery_service_1.WorkerDiscoveryService,
|
|
33
|
+
worker_service_1.WorkerService,
|
|
34
|
+
],
|
|
35
|
+
exports: [worker_service_1.WorkerService],
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
exports.WorkerModule = WorkerModule;
|
|
40
|
+
exports.WorkerModule = WorkerModule = WorkerModule_1 = __decorate([
|
|
41
|
+
(0, common_1.Module)({})
|
|
42
|
+
], WorkerModule);
|
|
43
|
+
//# sourceMappingURL=worker.module.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker.module.js","sourceRoot":"","sources":["../../src/core/worker.module.ts"],"names":[],"mappings":";;;;;;;;;;AAAA,2CAAuD;AACvD,uCAA+C;AAC/C,qDAAiD;AACjD,sEAAwE;AAGxE;;;;;;;;;GASG;AAEI,IAAM,YAAY,oBAAlB,MAAM,YAAY;IACvB,MAAM,CAAC,OAAO,CAAC,UAA+B,EAAE;QAC9C,OAAO;YACL,MAAM,EAAE,cAAY;YACpB,OAAO,EAAE,CAAC,sBAAe,CAAC;YAC1B,SAAS,EAAE;gBACT,EAAE,OAAO,EAAE,gBAAgB,EAAE,QAAQ,EAAE,OAAO,EAAE;gBAChD,0CAAsB;gBACtB,8BAAa;aACd;YACD,OAAO,EAAE,CAAC,8BAAa,CAAC;SACzB,CAAC;IACJ,CAAC;CACF,CAAA;AAbY,oCAAY;uBAAZ,YAAY;IADxB,IAAA,eAAM,EAAC,EAAE,CAAC;GACE,YAAY,CAaxB"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { WorkerJob } from './worker.interfaces';
|
|
2
|
+
import type { SerializedService } from '../di/worker-container';
|
|
3
|
+
export declare class WorkerPool {
|
|
4
|
+
private readonly services;
|
|
5
|
+
private readonly size;
|
|
6
|
+
private readonly workers;
|
|
7
|
+
private readonly idle;
|
|
8
|
+
private readonly queue;
|
|
9
|
+
private destroyed;
|
|
10
|
+
private readonly active;
|
|
11
|
+
constructor(services: SerializedService[], size?: number);
|
|
12
|
+
execute<T = unknown>(job: WorkerJob): Promise<T>;
|
|
13
|
+
private preemptIfNeeded;
|
|
14
|
+
private enqueue;
|
|
15
|
+
private schedule;
|
|
16
|
+
private dispatch;
|
|
17
|
+
private spawnWorker;
|
|
18
|
+
private replaceWorker;
|
|
19
|
+
destroy(): Promise<void>;
|
|
20
|
+
}
|