nestworker 2.0.8 → 2.1.0

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.
@@ -1,36 +1,115 @@
1
+ import type { AsyncLocalStorage } from 'node:async_hooks';
1
2
  export type TaskPriority = 'HIGH' | 'NORMAL' | 'LOW';
2
3
  export interface WorkerJob {
4
+ jobId: string;
3
5
  serviceName: string;
4
6
  methodName: string;
5
7
  args: unknown[];
6
- /** Sourced from @WorkerTask({ priority }) — used by WorkerPool to sort the queue */
7
8
  priority: TaskPriority;
8
- /** Sourced from @WorkerTask({ timeout }) — rejects the job after this many ms */
9
9
  timeout?: number;
10
+ /** Retry policy — sourced from @WorkerTask or overridden per call */
11
+ retry?: number;
12
+ retryDelay?: number;
13
+ /** Current attempt index (0 = first attempt) */
14
+ attempt?: number;
15
+ proxyServices?: ProxyServiceDescriptor[];
16
+ /** ALS context snapshot — restored in worker before task runs */
17
+ alsContext?: Record<string, unknown>;
18
+ /** OTEL trace context — W3C traceparent/tracestate headers */
19
+ traceContext?: Record<string, string>;
20
+ /** AbortSignal is non-transferable; we send the signal ID instead */
21
+ abortSignalId?: string;
10
22
  }
11
23
  export interface WorkerResult<T = unknown> {
24
+ type: 'result';
12
25
  ok: boolean;
13
26
  data?: T;
14
- error?: {
15
- message: string;
16
- stack?: string;
17
- };
27
+ error?: SerializedError;
28
+ }
29
+ export interface SerializedError {
30
+ name: string;
31
+ message: string;
32
+ stack?: string;
33
+ code?: string | number;
34
+ /** Any extra own enumerable properties on the original error */
35
+ extra?: Record<string, unknown>;
36
+ }
37
+ export interface WorkerAbortMessage {
38
+ type: 'abort';
39
+ abortSignalId: string;
40
+ }
41
+ export interface ProxyServiceDescriptor {
42
+ propertyKey: string;
43
+ methodNames: string[];
44
+ }
45
+ export interface IpcInvokeRequest {
46
+ type: 'ipc:invoke';
47
+ callId: string;
48
+ propertyKey: string;
49
+ methodName: string;
50
+ args: unknown[];
51
+ }
52
+ export interface IpcInvokeResponse {
53
+ type: 'ipc:result';
54
+ callId: string;
55
+ ok: boolean;
56
+ data?: unknown;
57
+ error?: string;
58
+ }
59
+ export interface WorkerReadySignal {
60
+ type: 'worker:ready';
61
+ }
62
+ export type WorkerInboundMessage = WorkerJob | IpcInvokeResponse | WorkerAbortMessage;
63
+ export type WorkerOutboundMessage = WorkerResult | IpcInvokeRequest | WorkerReadySignal;
64
+ export interface DeadLetterEvent {
65
+ jobId: string;
66
+ serviceName: string;
67
+ methodName: string;
68
+ args: unknown[];
69
+ attempts: number;
70
+ error: SerializedError;
71
+ failedAt: Date;
18
72
  }
19
73
  export interface DiscoveredTask {
20
74
  serviceName: string;
21
75
  methodName: string;
22
76
  priority: TaskPriority;
23
77
  timeout?: number;
24
- /** Bound method on the live main-thread instance */
78
+ retry?: number;
79
+ retryDelay?: number;
25
80
  fn: (...args: unknown[]) => unknown;
26
- /** Class constructor — used to locate the compiled file and read metadata */
27
81
  metatype: new (...args: unknown[]) => unknown;
28
- /** Live main-thread service instance — used to locate dep property keys */
29
82
  instance: unknown;
30
- /** Resolved dep instances from the NestJS container, in declaration order */
31
83
  deps: unknown[];
84
+ proxyInstances: ProxyInstance[];
85
+ }
86
+ export interface ProxyInstance {
87
+ propertyKey: string;
88
+ methodNames: string[];
89
+ instance: Record<string, (...args: unknown[]) => unknown>;
32
90
  }
33
91
  export interface WorkerModuleOptions {
34
92
  /** Number of worker threads. Defaults to os.cpus().length */
35
93
  poolSize?: number;
94
+ /**
95
+ * How long (ms) to wait for in-flight jobs to finish before force-killing
96
+ * workers on application shutdown. Defaults to 30_000.
97
+ */
98
+ shutdownTimeout?: number;
99
+ /**
100
+ * AsyncLocalStorage instances whose current store should be propagated
101
+ * into worker tasks. Pass the same ALS instances you use in your app.
102
+ */
103
+ asyncLocalStorages?: AsyncLocalStorage<unknown>[];
104
+ }
105
+ export interface WorkerModuleAsyncOptions {
106
+ inject?: unknown[];
107
+ useFactory: (...args: unknown[]) => WorkerModuleOptions | Promise<WorkerModuleOptions>;
108
+ }
109
+ export interface PoolStats {
110
+ poolSize: number;
111
+ idle: number;
112
+ busy: number;
113
+ queued: number;
114
+ warmingUp: number;
36
115
  }
@@ -1,15 +1,25 @@
1
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
- */
2
+ import type { WorkerModuleOptions, WorkerModuleAsyncOptions } from './worker.interfaces';
13
3
  export declare class WorkerModule {
4
+ /**
5
+ * Register with static options.
6
+ *
7
+ * @example
8
+ * WorkerModule.forRoot({ poolSize: 4 })
9
+ */
14
10
  static forRoot(options?: WorkerModuleOptions): DynamicModule;
11
+ /**
12
+ * Register with async factory — use when options come from ConfigService
13
+ * or other async providers.
14
+ *
15
+ * @example
16
+ * WorkerModule.forRootAsync({
17
+ * inject: [ConfigService],
18
+ * useFactory: (cfg: ConfigService) => ({
19
+ * poolSize: cfg.get<number>('WORKER_POOL_SIZE'),
20
+ * shutdownTimeout: 30_000,
21
+ * }),
22
+ * })
23
+ */
24
+ static forRootAsync(asyncOptions: WorkerModuleAsyncOptions): DynamicModule;
15
25
  }
@@ -12,20 +12,17 @@ const common_1 = require("@nestjs/common");
12
12
  const core_1 = require("@nestjs/core");
13
13
  const worker_service_1 = require("./worker.service");
14
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
15
  let WorkerModule = WorkerModule_1 = class WorkerModule {
16
+ /**
17
+ * Register with static options.
18
+ *
19
+ * @example
20
+ * WorkerModule.forRoot({ poolSize: 4 })
21
+ */
26
22
  static forRoot(options = {}) {
27
23
  return {
28
24
  module: WorkerModule_1,
25
+ global: true,
29
26
  imports: [core_1.DiscoveryModule],
30
27
  providers: [
31
28
  { provide: 'WORKER_OPTIONS', useValue: options },
@@ -35,6 +32,37 @@ let WorkerModule = WorkerModule_1 = class WorkerModule {
35
32
  exports: [worker_service_1.WorkerService],
36
33
  };
37
34
  }
35
+ /**
36
+ * Register with async factory — use when options come from ConfigService
37
+ * or other async providers.
38
+ *
39
+ * @example
40
+ * WorkerModule.forRootAsync({
41
+ * inject: [ConfigService],
42
+ * useFactory: (cfg: ConfigService) => ({
43
+ * poolSize: cfg.get<number>('WORKER_POOL_SIZE'),
44
+ * shutdownTimeout: 30_000,
45
+ * }),
46
+ * })
47
+ */
48
+ static forRootAsync(asyncOptions) {
49
+ const optionsProvider = {
50
+ provide: 'WORKER_OPTIONS',
51
+ inject: (asyncOptions.inject ?? []),
52
+ useFactory: asyncOptions.useFactory,
53
+ };
54
+ return {
55
+ module: WorkerModule_1,
56
+ global: true,
57
+ imports: [core_1.DiscoveryModule],
58
+ providers: [
59
+ optionsProvider,
60
+ discovery_service_1.WorkerDiscoveryService,
61
+ worker_service_1.WorkerService,
62
+ ],
63
+ exports: [worker_service_1.WorkerService],
64
+ };
65
+ }
38
66
  };
39
67
  exports.WorkerModule = WorkerModule;
40
68
  exports.WorkerModule = WorkerModule = WorkerModule_1 = __decorate([
@@ -1 +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"}
1
+ {"version":3,"file":"worker.module.js","sourceRoot":"","sources":["../../src/core/worker.module.ts"],"names":[],"mappings":";;;;;;;;;;AAAA,2CAAiE;AACjE,uCAA+C;AAC/C,qDAAiD;AACjD,sEAAwE;AAIjE,IAAM,YAAY,oBAAlB,MAAM,YAAY;IACvB;;;;;OAKG;IACH,MAAM,CAAC,OAAO,CAAC,UAA+B,EAAE;QAC9C,OAAO;YACL,MAAM,EAAE,cAAY;YACpB,MAAM,EAAE,IAAI;YACZ,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;IAED;;;;;;;;;;;;OAYG;IACH,MAAM,CAAC,YAAY,CAAC,YAAsC;QACxD,MAAM,eAAe,GAAa;YAChC,OAAO,EAAE,gBAAgB;YACzB,MAAM,EAAE,CAAC,YAAY,CAAC,MAAM,IAAI,EAAE,CAAY;YAC9C,UAAU,EAAE,YAAY,CAAC,UAAU;SACpC,CAAC;QAEF,OAAO;YACL,MAAM,EAAE,cAAY;YACpB,MAAM,EAAE,IAAI;YACZ,OAAO,EAAE,CAAC,sBAAe,CAAC;YAC1B,SAAS,EAAE;gBACT,eAAe;gBACf,0CAAsB;gBACtB,8BAAa;aACd;YACD,OAAO,EAAE,CAAC,8BAAa,CAAC;SACzB,CAAC;IACJ,CAAC;CACF,CAAA;AArDY,oCAAY;uBAAZ,YAAY;IADxB,IAAA,eAAM,EAAC,EAAE,CAAC;GACE,YAAY,CAqDxB"}
@@ -1,20 +1,40 @@
1
- import type { WorkerJob } from './worker.interfaces';
1
+ import { EventEmitter } from 'node:events';
2
+ import type { WorkerJob, ProxyInstance, DeadLetterEvent, SerializedError, PoolStats } from './worker.interfaces';
2
3
  import type { SerializedService } from '../di/worker-container';
3
- export declare class WorkerPool {
4
+ export declare interface WorkerPool {
5
+ on(event: 'dead', listener: (event: DeadLetterEvent) => void): this;
6
+ on(event: 'error', listener: (err: Error, job: WorkerJob) => void): this;
7
+ on(event: 'taskStart', listener: (job: WorkerJob) => void): this;
8
+ on(event: 'taskEnd', listener: (job: WorkerJob, durationMs: number) => void): this;
9
+ on(event: 'taskError', listener: (job: WorkerJob, error: SerializedError) => void): this;
10
+ emit(event: 'dead', payload: DeadLetterEvent): boolean;
11
+ emit(event: 'error', err: Error, job: WorkerJob): boolean;
12
+ emit(event: 'taskStart', job: WorkerJob): boolean;
13
+ emit(event: 'taskEnd', job: WorkerJob, durationMs: number): boolean;
14
+ emit(event: 'taskError', job: WorkerJob, error: SerializedError): boolean;
15
+ }
16
+ export declare class WorkerPool extends EventEmitter {
4
17
  private readonly services;
5
18
  private readonly size;
19
+ private readonly shutdownTimeout;
6
20
  private readonly workers;
7
21
  private readonly idle;
22
+ private readonly warmingUp;
8
23
  private readonly queue;
9
24
  private destroyed;
10
25
  private readonly active;
11
- constructor(services: SerializedService[], size?: number);
12
- execute<T = unknown>(job: WorkerJob): Promise<T>;
13
- private preemptIfNeeded;
26
+ /** Maps abortSignalId → worker currently running that job */
27
+ private readonly signalWorkerMap;
28
+ private readonly proxyMap;
29
+ constructor(services: SerializedService[], proxyInstances: ProxyInstance[], size?: number, shutdownTimeout?: number);
30
+ execute<T = unknown>(job: WorkerJob, signal?: AbortSignal): Promise<T>;
31
+ stats(): PoolStats;
32
+ private spawnWorker;
14
33
  private enqueue;
15
34
  private schedule;
16
35
  private dispatch;
17
- private spawnWorker;
36
+ private handleWorkerError;
37
+ private handleWorkerExit;
18
38
  private replaceWorker;
19
39
  destroy(): Promise<void>;
20
40
  }