nestworker 2.1.5 → 2.1.7

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 (35) hide show
  1. package/README.md +684 -611
  2. package/dist/core/worker.interfaces.d.ts +38 -3
  3. package/dist/core/worker.module.js +1 -5
  4. package/dist/core/worker.module.js.map +1 -1
  5. package/dist/core/worker.pool.d.ts +52 -15
  6. package/dist/core/worker.pool.js +399 -223
  7. package/dist/core/worker.pool.js.map +1 -1
  8. package/dist/core/worker.service.d.ts +37 -3
  9. package/dist/core/worker.service.js +93 -18
  10. package/dist/core/worker.service.js.map +1 -1
  11. package/dist/decorators/worker-task.decorator.js.map +1 -1
  12. package/dist/di/di-serializer.js +27 -16
  13. package/dist/di/di-serializer.js.map +1 -1
  14. package/dist/di/worker-container.d.ts +24 -5
  15. package/dist/di/worker-container.js +70 -30
  16. package/dist/di/worker-container.js.map +1 -1
  17. package/dist/discovery/discovery.service.js +6 -15
  18. package/dist/discovery/discovery.service.js.map +1 -1
  19. package/dist/example/bench.js +10 -2
  20. package/dist/example/bench.js.map +1 -1
  21. package/dist/example/image.service.d.ts +8 -0
  22. package/dist/example/image.service.js +31 -0
  23. package/dist/example/image.service.js.map +1 -1
  24. package/dist/example/main.js +26 -9
  25. package/dist/example/main.js.map +1 -1
  26. package/dist/health/worker.health.js +4 -3
  27. package/dist/health/worker.health.js.map +1 -1
  28. package/dist/index.d.ts +3 -2
  29. package/dist/index.js +3 -1
  30. package/dist/index.js.map +1 -1
  31. package/dist/metrics/worker.metrics.js +6 -2
  32. package/dist/metrics/worker.metrics.js.map +1 -1
  33. package/dist/worker/worker-runtime.js +81 -72
  34. package/dist/worker/worker-runtime.js.map +1 -1
  35. package/package.json +85 -67
@@ -6,8 +6,13 @@ export interface WorkerJob {
6
6
  methodName: string;
7
7
  args: unknown[];
8
8
  proxyServices?: ProxyServiceDescriptor[];
9
- /** ALS context snapshot — restored in worker before task runs */
10
- alsContext?: Record<string, unknown>;
9
+ /**
10
+ * ALS context snapshot. Index `i` corresponds to the i-th storage in
11
+ * `WorkerModuleOptions.asyncLocalStorages`. Encoded as an array — smaller
12
+ * structuredClone payload than an object, and survives >10 storages
13
+ * (the old string-keyed encoding broke past 9).
14
+ */
15
+ alsContext?: unknown[];
11
16
  /** OTEL trace context — W3C traceparent/tracestate headers */
12
17
  traceContext?: Record<string, string>;
13
18
  /** AbortSignal is non-transferable; we send the signal ID instead */
@@ -43,6 +48,13 @@ export interface IpcInvokeRequest {
43
48
  propertyKey: string;
44
49
  methodName: string;
45
50
  args: unknown[];
51
+ /**
52
+ * If the originating worker task was invoked with an AbortSignal, the
53
+ * worker forwards its abortSignalId here so the main thread can wire the
54
+ * proxy call to the same signal — preventing proxy methods from running
55
+ * past their parent task's cancellation.
56
+ */
57
+ abortSignalId?: number;
46
58
  }
47
59
  export interface IpcInvokeResponse {
48
60
  type: 'ipc:result';
@@ -79,7 +91,8 @@ export interface DiscoveredTask {
79
91
  priority: TaskPriority;
80
92
  timeout?: number;
81
93
  retry?: number;
82
- retryDelay?: number;
94
+ /** Number or function — functions are evaluated main-thread per attempt. */
95
+ retryDelay?: number | ((attempt: number) => number);
83
96
  fn: (...args: unknown[]) => unknown;
84
97
  metatype: new (...args: unknown[]) => unknown;
85
98
  instance: unknown;
@@ -111,11 +124,29 @@ export interface WorkerModuleOptions {
111
124
  * workers on application shutdown. Defaults to 30_000.
112
125
  */
113
126
  shutdownTimeout?: number;
127
+ /**
128
+ * Maximum number of pending tasks allowed in the queue. When the queue is
129
+ * full, `WorkerService.run()` rejects with a `QueueFullError` instead of
130
+ * enqueuing — gives callers a chance to apply backpressure. Defaults to
131
+ * `Infinity` (unbounded, legacy behaviour).
132
+ */
133
+ maxQueueDepth?: number;
114
134
  /**
115
135
  * AsyncLocalStorage instances whose current store should be propagated
116
136
  * into worker tasks. Pass the same ALS instances you use in your app.
117
137
  */
118
138
  asyncLocalStorages?: AsyncLocalStorage<unknown>[];
139
+ /**
140
+ * Optional logger sink. Defaults to `@nestjs/common`'s `Logger`. Pass any
141
+ * object with `error`/`warn`/`debug` methods to integrate pino, winston,
142
+ * bunyan, etc.
143
+ */
144
+ logger?: WorkerLogger;
145
+ }
146
+ export interface WorkerLogger {
147
+ error(message: string, trace?: string): void;
148
+ warn(message: string): void;
149
+ debug?(message: string): void;
119
150
  }
120
151
  export interface WorkerModuleAsyncOptions {
121
152
  inject?: unknown[];
@@ -127,4 +158,8 @@ export interface PoolStats {
127
158
  busy: number;
128
159
  queued: number;
129
160
  warmingUp: number;
161
+ /** Queue depth as a fraction of `maxQueueDepth` (0..1). 0 when unbounded. */
162
+ saturation: number;
163
+ /** Configured `maxQueueDepth` (Infinity when unbounded). */
164
+ maxQueueDepth: number;
130
165
  }
@@ -55,11 +55,7 @@ let WorkerModule = WorkerModule_1 = class WorkerModule {
55
55
  module: WorkerModule_1,
56
56
  global: true,
57
57
  imports: [core_1.DiscoveryModule],
58
- providers: [
59
- optionsProvider,
60
- discovery_service_1.WorkerDiscoveryService,
61
- worker_service_1.WorkerService,
62
- ],
58
+ providers: [optionsProvider, discovery_service_1.WorkerDiscoveryService, worker_service_1.WorkerService],
63
59
  exports: [worker_service_1.WorkerService],
64
60
  };
65
61
  }
@@ -1 +1 @@
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
+ {"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;AAOjE,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,CAAC,eAAe,EAAE,0CAAsB,EAAE,8BAAa,CAAC;YACnE,OAAO,EAAE,CAAC,8BAAa,CAAC;SACzB,CAAC;IACJ,CAAC;CACF,CAAA;AAjDY,oCAAY;uBAAZ,YAAY;IADxB,IAAA,eAAM,EAAC,EAAE,CAAC;GACE,YAAY,CAiDxB"}
@@ -1,6 +1,9 @@
1
1
  import { EventEmitter } from 'node:events';
2
2
  import type { WorkerJob, TaskPriority, ProxyInstance, DeadLetterEvent, SerializedError, PoolStats } from './worker.interfaces';
3
3
  import type { SerializedService } from '../di/worker-container';
4
+ export declare class QueueFullError extends Error {
5
+ constructor(depth: number);
6
+ }
4
7
  export declare interface WorkerPool {
5
8
  on(event: 'dead', listener: (event: DeadLetterEvent) => void): this;
6
9
  on(event: 'error', listener: (err: Error, job: WorkerJob) => void): this;
@@ -26,8 +29,13 @@ export declare class WorkerPool extends EventEmitter {
26
29
  */
27
30
  private readonly idle;
28
31
  private readonly warmingUp;
29
- private readonly queue;
30
- private queueHead;
32
+ private readonly queueHigh;
33
+ private readonly queueNormal;
34
+ private readonly queueLow;
35
+ private queueHighHead;
36
+ private queueNormalHead;
37
+ private queueLowHead;
38
+ private queuedCount;
31
39
  private destroyed;
32
40
  private activeCount;
33
41
  /**
@@ -39,36 +47,65 @@ export declare class WorkerPool extends EventEmitter {
39
47
  * coalesced into a single postMessage per worker.
40
48
  */
41
49
  private scheduleQueued;
42
- /** Maps abortSignalId → worker currently running that job */
43
- private readonly signalWorkerMap;
44
50
  private readonly concurrency;
51
+ private readonly maxQueueDepth;
45
52
  private readonly proxyMap;
46
- constructor(services: SerializedService[], proxyInstances: ProxyInstance[], size?: number, shutdownTimeout?: number, concurrency?: number);
53
+ constructor(services: SerializedService[], proxyInstances: ProxyInstance[], size?: number, shutdownTimeout?: number, concurrency?: number, maxQueueDepth?: number);
47
54
  execute<T = unknown>(job: WorkerJob, meta: {
48
55
  priority: TaskPriority;
49
56
  timeout?: number;
50
57
  retry?: number;
51
- retryDelay?: number;
58
+ retryDelay?: number | ((attempt: number) => number);
52
59
  }, signal?: AbortSignal): Promise<T>;
60
+ /** Remove the abort listener installed in `execute()` (idempotent). */
61
+ private detachAbort;
53
62
  stats(): PoolStats;
54
63
  private spawnWorker;
55
64
  private handleIpcInvoke;
65
+ /**
66
+ * Look up the original caller's AbortSignal by abortSignalId. Used by
67
+ * `handleIpcInvoke` to forward cancellation into proxy methods.
68
+ * Scan is bounded by `concurrency * poolSize` and only happens on proxy
69
+ * calls from signal-bearing tasks, so we don't bother with a side index.
70
+ */
71
+ private findSignalById;
56
72
  private enqueue;
57
- private schedule;
58
73
  /**
59
- * Synchronous drain used on the COMPLETION path when a worker becomes
60
- * idle as a result of a result message arriving, we want to hand it the
61
- * next queued job in the SAME tick. The microtask-deferred `schedule()`
62
- * adds a full microtask hop per round-trip, which dominates throughput
63
- * for short tasks with concurrency=1.
74
+ * Pop the next ready task in priority order (HIGH > NORMAL > LOW). Skips
75
+ * tombstoned entries (aborted-while-queued) and lazily compacts each
76
+ * bucket once its head pointer wastes >1024 slots.
64
77
  *
65
- * Initial-burst dispatch still goes through the deferred `schedule()` so
66
- * synchronous floods of `execute()` calls get coalesced into per-worker
67
- * batches.
78
+ * Returns `undefined` when all buckets are empty or contain only
79
+ * tombstones caller must check.
80
+ */
81
+ private dequeue;
82
+ private maybeCompact;
83
+ /** True iff at least one bucket has a non-tombstone entry remaining. */
84
+ private hasQueued;
85
+ private schedule;
86
+ /**
87
+ * Synchronous drain used on the COMPLETION path. Initial-burst dispatch
88
+ * still goes through the deferred `schedule()` so synchronous floods of
89
+ * `execute()` calls get coalesced into per-worker batches.
68
90
  */
69
91
  private dispatchNow;
70
92
  /** Pre-bound for queueMicrotask — avoids closure allocation per schedule. */
71
93
  private readonly drain;
94
+ /**
95
+ * Common dispatch loop shared by `dispatchNow` (synchronous, post-result)
96
+ * and `drain` (microtask, post-burst). Pops idle slots and ships jobs as
97
+ * either bare `WorkerJob` (single) or `WorkerJobBatch` envelopes.
98
+ *
99
+ * @param fastSingle When true, take a fast path for "exactly one job to
100
+ * exactly one idle worker" that skips the per-worker Map allocation.
101
+ */
102
+ private flushDispatch;
103
+ /**
104
+ * Post a single job; on serialization failure synthesise a failure result
105
+ * so the caller's promise rejects instead of hanging on the active map.
106
+ */
107
+ private safePostJob;
108
+ private safePostBatch;
72
109
  private prepareDispatch;
73
110
  /** Called from the persistent message listener when a job result arrives. */
74
111
  private completeJob;