@fluojs/queue 1.0.1 → 1.0.2
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/README.ko.md +6 -6
- package/README.md +6 -6
- package/dist/helpers.d.ts +3 -1
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js +6 -1
- package/dist/module.d.ts.map +1 -1
- package/dist/module.js +1 -0
- package/dist/service.d.ts +12 -1
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +98 -6
- package/dist/status.d.ts +3 -1
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +33 -0
- package/dist/types.d.ts +1 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/worker-discovery.d.ts +2 -1
- package/dist/worker-discovery.d.ts.map +1 -1
- package/dist/worker-discovery.js +2 -2
- package/package.json +4 -4
package/README.ko.md
CHANGED
|
@@ -92,7 +92,7 @@ QueueModule.forRoot({ clientName: 'jobs' })
|
|
|
92
92
|
|
|
93
93
|
### 부트스트랩 및 종료 수명 주기
|
|
94
94
|
|
|
95
|
-
Queue는 애플리케이션 부트스트랩 중 worker를 탐색하고 Queue가 소유하는 BullMQ 리소스를 만들지만, BullMQ worker processor는 runtime이 전체 애플리케이션 bootstrap/readiness sequence 완료를 표시한 뒤에만 시작합니다. 다른 `onApplicationBootstrap()` hook에서 enqueue한 job은 Queue 서비스가 초기화된 뒤에는 받을 수 있으며, processor는 뒤에 실행되는 async bootstrap hook이나 애플리케이션 readiness보다 앞서 실행되지 않고 bootstrap-ready handoff 이후 실행됩니다.
|
|
95
|
+
Queue는 애플리케이션 부트스트랩 중 worker를 탐색하고 Queue가 소유하는 BullMQ 리소스를 만들지만, BullMQ worker processor는 runtime이 전체 애플리케이션 bootstrap/readiness sequence 완료를 표시한 뒤에만 시작합니다. 다른 `onApplicationBootstrap()` hook에서 enqueue한 job은 Queue 서비스가 초기화된 뒤에는 받을 수 있으며, processor는 뒤에 실행되는 async bootstrap hook이나 애플리케이션 readiness보다 앞서 실행되지 않고 bootstrap-ready handoff 이후 실행됩니다. Queue status는 해당 BullMQ processor가 실제로 시작될 때까지 degraded readiness를 보고합니다. Processor 시작에 실패하면 lifecycle이 `failed`로 이동하고, status snapshot은 worker를 ready로 숨기지 않고 실패를 노출합니다.
|
|
96
96
|
|
|
97
97
|
애플리케이션 종료가 시작되면 Queue는 상태를 `stopping`으로 바꾸고 새 enqueue를 거부한 다음 Queue 소유 worker/queue/connection을 닫고 pending dead-letter write를 drain합니다. Worker 종료는 `workerShutdownTimeoutMs`로 bounded wait를 적용하므로 끝나지 않는 active processor가 애플리케이션 종료를 무기한 막을 수 없습니다. Timeout이 지나면 Queue는 로그를 남기고 BullMQ worker에 force-close를 요청한 뒤 나머지 리소스 정리를 계속합니다.
|
|
98
98
|
|
|
@@ -122,7 +122,7 @@ Job은 JSON으로 직렬화 가능한 plain object여야 합니다. Queue는 enq
|
|
|
122
122
|
### 핵심 구성 요소
|
|
123
123
|
- `QueueModule`: 큐 기능을 위한 기본 모듈입니다.
|
|
124
124
|
- `QueueModule.forRoot(options)`: 애플리케이션 수준 큐 등록을 구성합니다.
|
|
125
|
-
- `QueueLifecycleService`: 작업을 큐에
|
|
125
|
+
- `QueueLifecycleService`: 작업을 큐에 추가하고 lifecycle/status snapshot을 생성(`enqueue(job)`, `createPlatformStatusSnapshot()`)하기 위한 기본 서비스입니다.
|
|
126
126
|
- `@QueueWorker(JobClass, options?)`: 특정 작업을 처리할 핸들러를 지정하는 데코레이터입니다.
|
|
127
127
|
- `QUEUE`: queue facade를 위한 호환성 주입 토큰입니다.
|
|
128
128
|
- `createQueuePlatformStatusSnapshot(...)`: lifecycle/readiness diagnostics를 위한 status snapshot helper입니다.
|
|
@@ -136,9 +136,9 @@ Job은 JSON으로 직렬화 가능한 plain object여야 합니다. Queue는 enq
|
|
|
136
136
|
- `QueueBackoffType`: 지원되는 retry backoff strategy 이름(`fixed`, `exponential`)입니다.
|
|
137
137
|
- `QueueBackoffOptions`: 재시도 백오프 설정(`type`, `delayMs`)을 위한 타입입니다.
|
|
138
138
|
- `QueueRateLimiterOptions`: worker 수준 distributed rate limiter 설정(`max`, `duration`)을 위한 타입입니다.
|
|
139
|
-
- `QueueLifecycleState`: Queue status adapter가 보고하는 lifecycle state(`idle`, `starting`, `started`, `stopping`, `stopped`)입니다.
|
|
140
|
-
- `QueueStatusAdapterInput`: `createQueuePlatformStatusSnapshot(...)`에 전달하는 normalized queue metrics 타입입니다.
|
|
141
|
-
- `QueuePlatformStatusSnapshot`: status helper
|
|
139
|
+
- `QueueLifecycleState`: Queue status adapter가 보고하는 lifecycle state(`idle`, `starting`, `started`, `stopping`, `stopped`, `failed`)입니다.
|
|
140
|
+
- `QueueStatusAdapterInput`: `createQueuePlatformStatusSnapshot(...)`에 전달하는 normalized queue metrics와 worker-start diagnostics 타입입니다.
|
|
141
|
+
- `QueuePlatformStatusSnapshot`: status helper와 `QueueLifecycleService.createPlatformStatusSnapshot()`이 반환하는 Queue 전용 readiness, health, ownership, detail snapshot 타입입니다.
|
|
142
142
|
|
|
143
143
|
`QueueModuleOptions`에는 `workerShutdownTimeoutMs`, `defaultDeadLetterMaxEntries` 같은 lifecycle 및 dead-letter retention 설정도 포함됩니다.
|
|
144
144
|
|
|
@@ -148,7 +148,7 @@ Job은 JSON으로 직렬화 가능한 plain object여야 합니다. Queue는 enq
|
|
|
148
148
|
- `workerShutdownTimeoutMs`: 종료 중 active worker processor를 기다리는 최대 시간입니다. 시간이 지나면 BullMQ worker를 force-close합니다. 기본값은 `30_000`입니다.
|
|
149
149
|
- `defaultDeadLetterMaxEntries`: job별로 유지할 dead-letter record의 최대 개수이며, trimming을 끄려면 `false`를 지정합니다. 기본값은 `1_000`입니다.
|
|
150
150
|
|
|
151
|
-
`createQueuePlatformStatusSnapshot(...)
|
|
151
|
+
`QueueLifecycleService.createPlatformStatusSnapshot()`은 `createQueuePlatformStatusSnapshot(...)`과 같은 공개 snapshot 계약을 사용합니다. Queue가 `started`에 도달하고 탐색된 모든 BullMQ worker processor가 시작된 뒤에만 readiness를 `ready`로 보고합니다. Processor가 아직 pending인 `started` resource와 `starting`은 degraded readiness, `stopping`/`stopped`는 not-ready, worker-start failure는 `workerStartFailures`와 `lastWorkerStartFailure` details를 포함해 not-ready/unhealthy로 보고합니다. Snapshot details에는 Redis dependency id, lifecycle state, ready/discovered worker 수, pending dead-letter write 수, dead-letter drain timeout, `workerShutdownTimeoutMs`가 포함됩니다.
|
|
152
152
|
|
|
153
153
|
singleton `@QueueWorker()` provider/controller만 등록됩니다. request/transient worker는 discovery 중 건너뜁니다.
|
|
154
154
|
|
package/README.md
CHANGED
|
@@ -92,7 +92,7 @@ QueueModule.forRoot({ clientName: 'jobs' })
|
|
|
92
92
|
|
|
93
93
|
### Bootstrap and Shutdown Lifecycle
|
|
94
94
|
|
|
95
|
-
Queue discovers workers and creates queue-owned BullMQ resources during application bootstrap, but BullMQ worker processors are started only after the runtime marks the full application bootstrap/readiness sequence complete. Jobs enqueued by other `onApplicationBootstrap()` hooks can be accepted once the Queue service is initialized, and their processors run after the bootstrap-ready handoff instead of racing ahead of later async bootstrap hooks or application readiness.
|
|
95
|
+
Queue discovers workers and creates queue-owned BullMQ resources during application bootstrap, but BullMQ worker processors are started only after the runtime marks the full application bootstrap/readiness sequence complete. Jobs enqueued by other `onApplicationBootstrap()` hooks can be accepted once the Queue service is initialized, and their processors run after the bootstrap-ready handoff instead of racing ahead of later async bootstrap hooks or application readiness. Queue status reports degraded readiness until those BullMQ processors have actually started; if a processor fails to start, the lifecycle moves to `failed` and status snapshots expose the failure instead of reporting the workers as ready.
|
|
96
96
|
|
|
97
97
|
Application shutdown marks Queue as `stopping`, rejects new enqueue attempts, closes queue-owned workers/queues/connections, and drains pending dead-letter writes. Worker shutdown is bounded by `workerShutdownTimeoutMs` so an active processor that never settles cannot block application shutdown indefinitely. When the timeout elapses, Queue logs the timeout and asks BullMQ to force-close the worker before continuing resource cleanup.
|
|
98
98
|
|
|
@@ -122,7 +122,7 @@ Treat low-level provider assembly as an internal implementation detail: low-leve
|
|
|
122
122
|
### Core
|
|
123
123
|
- `QueueModule`: Main entry point for queue registration.
|
|
124
124
|
- `QueueModule.forRoot(options)`: Registers queue support for an application module.
|
|
125
|
-
- `QueueLifecycleService`: Primary service for enqueuing jobs (`enqueue(job)`).
|
|
125
|
+
- `QueueLifecycleService`: Primary service for enqueuing jobs and creating lifecycle/status snapshots (`enqueue(job)`, `createPlatformStatusSnapshot()`).
|
|
126
126
|
- `@QueueWorker(JobClass, options?)`: Decorator to mark a class as a job handler.
|
|
127
127
|
- `QUEUE`: Compatibility injection token for the queue facade.
|
|
128
128
|
- `createQueuePlatformStatusSnapshot(...)`: Status snapshot helper for lifecycle/readiness diagnostics.
|
|
@@ -136,9 +136,9 @@ Treat low-level provider assembly as an internal implementation detail: low-leve
|
|
|
136
136
|
- `QueueBackoffType`: Supported retry backoff strategy names (`fixed`, `exponential`).
|
|
137
137
|
- `QueueBackoffOptions`: Retry backoff settings (`type`, `delayMs`).
|
|
138
138
|
- `QueueRateLimiterOptions`: Worker-level distributed rate limiter settings (`max`, `duration`).
|
|
139
|
-
- `QueueLifecycleState`: Lifecycle states reported by Queue status adapters (`idle`, `starting`, `started`, `stopping`, `stopped`).
|
|
140
|
-
- `QueueStatusAdapterInput`: Normalized queue metrics passed to `createQueuePlatformStatusSnapshot(...)`.
|
|
141
|
-
- `QueuePlatformStatusSnapshot`: Queue-specific readiness, health, ownership, and detail snapshot returned by the status helper.
|
|
139
|
+
- `QueueLifecycleState`: Lifecycle states reported by Queue status adapters (`idle`, `starting`, `started`, `stopping`, `stopped`, `failed`).
|
|
140
|
+
- `QueueStatusAdapterInput`: Normalized queue metrics and worker-start diagnostics passed to `createQueuePlatformStatusSnapshot(...)`.
|
|
141
|
+
- `QueuePlatformStatusSnapshot`: Queue-specific readiness, health, ownership, and detail snapshot returned by the status helper and `QueueLifecycleService.createPlatformStatusSnapshot()`.
|
|
142
142
|
|
|
143
143
|
`QueueModuleOptions` also includes lifecycle and dead-letter retention controls such as `workerShutdownTimeoutMs` and `defaultDeadLetterMaxEntries`.
|
|
144
144
|
|
|
@@ -148,7 +148,7 @@ Treat low-level provider assembly as an internal implementation detail: low-leve
|
|
|
148
148
|
- `workerShutdownTimeoutMs`: maximum time to wait for active worker processors during shutdown before force-closing the BullMQ worker. Defaults to `30_000`.
|
|
149
149
|
- `defaultDeadLetterMaxEntries`: maximum retained dead-letter records per job, or `false` to disable trimming. Defaults to `1_000`.
|
|
150
150
|
|
|
151
|
-
`createQueuePlatformStatusSnapshot(...)
|
|
151
|
+
`QueueLifecycleService.createPlatformStatusSnapshot()` uses the same public snapshot contract as `createQueuePlatformStatusSnapshot(...)`. It reports readiness as `ready` only after Queue reaches `started` and every discovered BullMQ worker processor has started. `started` resources with pending processors report degraded readiness, `starting` reports degraded readiness, `stopping`/`stopped` report not-ready, and worker-start failures report not-ready/unhealthy with `workerStartFailures` and `lastWorkerStartFailure` details. Snapshot details include the Redis dependency id, lifecycle state, ready/discovered worker counts, pending dead-letter writes, the dead-letter drain timeout, and `workerShutdownTimeoutMs`.
|
|
152
152
|
|
|
153
153
|
Only singleton `@QueueWorker()` providers/controllers are registered. Request/transient workers are skipped during discovery.
|
|
154
154
|
|
package/dist/helpers.d.ts
CHANGED
|
@@ -15,6 +15,8 @@ export interface DiscoveryCandidate {
|
|
|
15
15
|
targetType: Function;
|
|
16
16
|
token: Token;
|
|
17
17
|
}
|
|
18
|
+
/** Selects whether one compiled module participates in worker discovery. */
|
|
19
|
+
export type DiscoveryModuleFilter = (compiledModule: CompiledModule) => boolean;
|
|
18
20
|
/**
|
|
19
21
|
* Scope from provider.
|
|
20
22
|
*
|
|
@@ -38,7 +40,7 @@ export declare function isClassProvider(provider: Provider): provider is Extract
|
|
|
38
40
|
* @param compiledModules The compiled modules.
|
|
39
41
|
* @returns The collect discovery candidates result.
|
|
40
42
|
*/
|
|
41
|
-
export declare function collectDiscoveryCandidates(compiledModules: readonly CompiledModule[]): DiscoveryCandidate[];
|
|
43
|
+
export declare function collectDiscoveryCandidates(compiledModules: readonly CompiledModule[], moduleFilter?: DiscoveryModuleFilter): DiscoveryCandidate[];
|
|
42
44
|
/**
|
|
43
45
|
* Normalize positive integer.
|
|
44
46
|
*
|
package/dist/helpers.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../src/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,KAAK,EAAE,MAAM,cAAc,CAAC;AAE1C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEtD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAE1D;;GAEG;AACH,MAAM,MAAM,KAAK,GAAG,SAAS,GAAG,WAAW,GAAG,WAAW,CAAC;AAE1D;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,KAAK,CAAC;IACb,UAAU,EAAE,QAAQ,CAAC;IACrB,KAAK,EAAE,KAAK,CAAC;CACd;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,GAAG,KAAK,CAU3D;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,QAAQ,GAAG,QAAQ,IAAI,OAAO,CAAC,QAAQ,EAAE;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,QAAQ,EAAE,QAAQ,CAAA;CAAE,CAAC,CAEzH;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,
|
|
1
|
+
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../src/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,KAAK,EAAE,MAAM,cAAc,CAAC;AAE1C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEtD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAE1D;;GAEG;AACH,MAAM,MAAM,KAAK,GAAG,SAAS,GAAG,WAAW,GAAG,WAAW,CAAC;AAE1D;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,KAAK,CAAC;IACb,UAAU,EAAE,QAAQ,CAAC;IACrB,KAAK,EAAE,KAAK,CAAC;CACd;AAED,4EAA4E;AAC5E,MAAM,MAAM,qBAAqB,GAAG,CAAC,cAAc,EAAE,cAAc,KAAK,OAAO,CAAC;AAEhF;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,GAAG,KAAK,CAU3D;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,QAAQ,GAAG,QAAQ,IAAI,OAAO,CAAC,QAAQ,EAAE;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,QAAQ,EAAE,QAAQ,CAAA;CAAE,CAAC,CAEzH;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,CACxC,eAAe,EAAE,SAAS,cAAc,EAAE,EAC1C,YAAY,GAAE,qBAAkC,GAC/C,kBAAkB,EAAE,CAwCtB;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAY5F;AAED;;;;;;GAMG;AACH,wBAAgB,+BAA+B,CAC7C,KAAK,EAAE,MAAM,GAAG,KAAK,GAAG,SAAS,EACjC,QAAQ,EAAE,MAAM,GAAG,KAAK,GACvB,MAAM,GAAG,KAAK,CAgBhB;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,uBAAuB,GAAG,SAAS,GAAG,uBAAuB,GAAG,SAAS,CAS1H;AAED;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAAC,CAAC,EACjC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EACnB,SAAS,EAAE,MAAM,EACjB,mBAAmB,EAAE,MAAM,KAAK,GAC/B,OAAO,CAAC,CAAC,CAAC,CAeZ"}
|
package/dist/helpers.js
CHANGED
|
@@ -8,6 +8,8 @@ import { getClassDiMetadata } from '@fluojs/core/internal';
|
|
|
8
8
|
* Describes the discovery candidate contract.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
/** Selects whether one compiled module participates in worker discovery. */
|
|
12
|
+
|
|
11
13
|
/**
|
|
12
14
|
* Scope from provider.
|
|
13
15
|
*
|
|
@@ -40,9 +42,12 @@ export function isClassProvider(provider) {
|
|
|
40
42
|
* @param compiledModules The compiled modules.
|
|
41
43
|
* @returns The collect discovery candidates result.
|
|
42
44
|
*/
|
|
43
|
-
export function collectDiscoveryCandidates(compiledModules) {
|
|
45
|
+
export function collectDiscoveryCandidates(compiledModules, moduleFilter = () => true) {
|
|
44
46
|
const candidates = [];
|
|
45
47
|
for (const compiledModule of compiledModules) {
|
|
48
|
+
if (!moduleFilter(compiledModule)) {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
46
51
|
for (const provider of compiledModule.definition.providers ?? []) {
|
|
47
52
|
if (typeof provider === 'function') {
|
|
48
53
|
candidates.push({
|
package/dist/module.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AACA,OAAO,EAAgB,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAKhE,OAAO,KAAK,EAAgC,kBAAkB,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AACA,OAAO,EAAgB,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAKhE,OAAO,KAAK,EAAgC,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAuCnF;;GAEG;AACH,qBAAa,WAAW;IACtB;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,MAAM,CAAC,OAAO,CAAC,OAAO,GAAE,kBAAuB,GAAG,UAAU;CAS7D"}
|
package/dist/module.js
CHANGED
|
@@ -14,6 +14,7 @@ function normalizeQueueModuleOptions(options = {}) {
|
|
|
14
14
|
defaultConcurrency: normalizePositiveInteger(options.defaultConcurrency, 1),
|
|
15
15
|
defaultDeadLetterMaxEntries: normalizePositiveIntegerOrFalse(options.defaultDeadLetterMaxEntries, 1_000),
|
|
16
16
|
defaultRateLimiter,
|
|
17
|
+
global: options.global ?? true,
|
|
17
18
|
workerShutdownTimeoutMs: normalizePositiveInteger(options.workerShutdownTimeoutMs, 30_000)
|
|
18
19
|
};
|
|
19
20
|
}
|
package/dist/service.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Container } from '@fluojs/di';
|
|
2
2
|
import type { ApplicationLogger, CompiledModule, OnApplicationBootstrap, OnApplicationShutdown, OnModuleDestroy } from '@fluojs/runtime';
|
|
3
3
|
import { type BootstrapReadySignal } from '@fluojs/runtime/internal';
|
|
4
|
+
import { type QueuePlatformStatusSnapshot } from './status.js';
|
|
4
5
|
import type { NormalizedQueueModuleOptions, Queue } from './types.js';
|
|
5
6
|
/**
|
|
6
7
|
* Lifecycle-managed queue runtime for worker discovery and job dispatch.
|
|
@@ -20,10 +21,14 @@ export declare class QueueLifecycleService implements Queue, OnApplicationBootst
|
|
|
20
21
|
private readonly ownedConnections;
|
|
21
22
|
private readonly deadLetterManager;
|
|
22
23
|
private readonly readyWorkers;
|
|
24
|
+
private readonly runningWorkerJobNames;
|
|
25
|
+
private readonly failedWorkerJobNames;
|
|
26
|
+
private readonly workerStartFailures;
|
|
23
27
|
private lifecycleState;
|
|
24
28
|
private redisClient;
|
|
25
29
|
private startPromise;
|
|
26
30
|
private shutdownPromise;
|
|
31
|
+
private startupFailureRollbackPromise;
|
|
27
32
|
constructor(options: NormalizedQueueModuleOptions, runtimeContainer: Container, compiledModules: readonly CompiledModule[], logger: ApplicationLogger, bootstrapReadySignal?: BootstrapReadySignal);
|
|
28
33
|
onApplicationBootstrap(): Promise<void>;
|
|
29
34
|
onApplicationShutdown(): Promise<void>;
|
|
@@ -42,9 +47,10 @@ export declare class QueueLifecycleService implements Queue, OnApplicationBootst
|
|
|
42
47
|
*
|
|
43
48
|
* @returns A structured snapshot describing lifecycle state, discovered workers, and pending dead-letter writes.
|
|
44
49
|
*/
|
|
45
|
-
createPlatformStatusSnapshot():
|
|
50
|
+
createPlatformStatusSnapshot(): QueuePlatformStatusSnapshot;
|
|
46
51
|
private ensureStarted;
|
|
47
52
|
private startLifecycle;
|
|
53
|
+
private shouldDiscoverWorkersInModule;
|
|
48
54
|
private handleStartupFailure;
|
|
49
55
|
private resolveRedisClient;
|
|
50
56
|
private getRedisClient;
|
|
@@ -58,12 +64,17 @@ export declare class QueueLifecycleService implements Queue, OnApplicationBootst
|
|
|
58
64
|
private registerInitializedWorker;
|
|
59
65
|
private scheduleReadyWorkers;
|
|
60
66
|
private runWorker;
|
|
67
|
+
private markWorkerReadyWhenStarted;
|
|
68
|
+
private recordWorkerStartFailure;
|
|
69
|
+
private rollbackAfterWorkerStartupFailure;
|
|
70
|
+
private toErrorMessage;
|
|
61
71
|
private cleanupWorkerInitializationFailure;
|
|
62
72
|
private createOwnedConnection;
|
|
63
73
|
private executeWorker;
|
|
64
74
|
private resolveWorkerHandler;
|
|
65
75
|
private rehydrateWorkerPayload;
|
|
66
76
|
private shutdown;
|
|
77
|
+
private waitForStartupFailureRollback;
|
|
67
78
|
private waitForInFlightStartup;
|
|
68
79
|
private closeInitializedResources;
|
|
69
80
|
private tryCloseWorker;
|
package/dist/service.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C,OAAO,KAAK,EACV,iBAAiB,EACjB,cAAc,EACd,sBAAsB,EACtB,qBAAqB,EACrB,eAAe,EAChB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAKL,KAAK,oBAAoB,EAC1B,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C,OAAO,KAAK,EACV,iBAAiB,EACjB,cAAc,EACd,sBAAsB,EACtB,qBAAqB,EACrB,eAAe,EAChB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAKL,KAAK,oBAAoB,EAC1B,MAAM,0BAA0B,CAAC;AAKlC,OAAO,EAGL,KAAK,2BAA2B,EACjC,MAAM,aAAa,CAAC;AAGrB,OAAO,KAAK,EACV,4BAA4B,EAC5B,KAAK,EAIN,MAAM,YAAY,CAAC;AA8HpB;;;;;GAKG;AACH,qBACa,qBAAsB,YAAW,KAAK,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,eAAe;IAiB/G,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IACjC,OAAO,CAAC,QAAQ,CAAC,eAAe;IAChC,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,oBAAoB;IApBvC,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAkD;IACvF,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAoC;IACpE,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAqC;IACtE,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAA8B;IAC/D,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAyB;IAC3D,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAqB;IAClD,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAqB;IAC3D,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAqB;IAC1D,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAA4B;IAChE,OAAO,CAAC,cAAc,CAA+B;IACrD,OAAO,CAAC,WAAW,CAA+B;IAClD,OAAO,CAAC,YAAY,CAA4B;IAChD,OAAO,CAAC,eAAe,CAA4B;IACnD,OAAO,CAAC,6BAA6B,CAA4B;gBAG9C,OAAO,EAAE,4BAA4B,EACrC,gBAAgB,EAAE,SAAS,EAC3B,eAAe,EAAE,SAAS,cAAc,EAAE,EAC1C,MAAM,EAAE,iBAAiB,EACzB,oBAAoB,GAAE,oBAAuD;IAK1F,sBAAsB,IAAI,OAAO,CAAC,IAAI,CAAC;IAIvC,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC;IAItC,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IAItC;;;;;;;OAOG;IACG,OAAO,CAAC,IAAI,SAAS,MAAM,EAAE,GAAG,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC;IA2B9D;;;;OAIG;IACH,4BAA4B,IAAI,2BAA2B;YAgB7C,aAAa;YAwBb,cAAc;IAqB5B,OAAO,CAAC,6BAA6B;YAQvB,oBAAoB;YASpB,kBAAkB;IAgBhC,OAAO,CAAC,cAAc;YAQR,iBAAiB;YAOjB,yBAAyB;IAyBvC,OAAO,CAAC,mBAAmB;IAS3B,OAAO,CAAC,oBAAoB;IAa5B,OAAO,CAAC,mBAAmB;IAY3B,OAAO,CAAC,0BAA0B;IAkBlC,OAAO,CAAC,0BAA0B;IASlC,OAAO,CAAC,yBAAyB;IAUjC,OAAO,CAAC,oBAAoB;IAuC5B,OAAO,CAAC,SAAS;YA+BH,0BAA0B;IAkBxC,OAAO,CAAC,wBAAwB;IA6BhC,OAAO,CAAC,iCAAiC;IAgBzC,OAAO,CAAC,cAAc;YAIR,kCAAkC;YAkBlC,qBAAqB;YAiBrB,aAAa;YAOb,oBAAoB;IAyBlC,OAAO,CAAC,sBAAsB;YAQhB,QAAQ;YA0BR,6BAA6B;YAS7B,sBAAsB;YAgBtB,yBAAyB;YAuBzB,cAAc;YAkBd,aAAa;YAQb,uBAAuB;CAOtC"}
|
package/dist/service.js
CHANGED
|
@@ -12,7 +12,7 @@ import { Queue as BullQueue, Worker as BullWorker } from 'bullmq';
|
|
|
12
12
|
import { QueueDeadLetterManager } from './dead-letter-manager.js';
|
|
13
13
|
import { normalizePositiveInteger, withTimeout } from './helpers.js';
|
|
14
14
|
import { createQueuePlatformStatusSnapshot } from './status.js';
|
|
15
|
-
import { QUEUE_OPTIONS } from './tokens.js';
|
|
15
|
+
import { QUEUE, QUEUE_OPTIONS } from './tokens.js';
|
|
16
16
|
import { discoverQueueWorkerDescriptors } from './worker-discovery.js';
|
|
17
17
|
const IMMEDIATE_BOOTSTRAP_READY_SIGNAL = {
|
|
18
18
|
wait: () => Promise.resolve()
|
|
@@ -77,10 +77,14 @@ class QueueLifecycleService {
|
|
|
77
77
|
ownedConnections = [];
|
|
78
78
|
deadLetterManager;
|
|
79
79
|
readyWorkers = [];
|
|
80
|
+
runningWorkerJobNames = new Set();
|
|
81
|
+
failedWorkerJobNames = new Set();
|
|
82
|
+
workerStartFailures = [];
|
|
80
83
|
lifecycleState = 'idle';
|
|
81
84
|
redisClient;
|
|
82
85
|
startPromise;
|
|
83
86
|
shutdownPromise;
|
|
87
|
+
startupFailureRollbackPromise;
|
|
84
88
|
constructor(options, runtimeContainer, compiledModules, logger, bootstrapReadySignal = IMMEDIATE_BOOTSTRAP_READY_SIGNAL) {
|
|
85
89
|
this.options = options;
|
|
86
90
|
this.runtimeContainer = runtimeContainer;
|
|
@@ -133,21 +137,26 @@ class QueueLifecycleService {
|
|
|
133
137
|
* @returns A structured snapshot describing lifecycle state, discovered workers, and pending dead-letter writes.
|
|
134
138
|
*/
|
|
135
139
|
createPlatformStatusSnapshot() {
|
|
140
|
+
const lastWorkerStartFailure = this.workerStartFailures[this.workerStartFailures.length - 1];
|
|
136
141
|
return createQueuePlatformStatusSnapshot({
|
|
137
142
|
dependencyId: getRedisComponentId(this.options.clientName),
|
|
138
143
|
lifecycleState: this.lifecycleState,
|
|
144
|
+
...(lastWorkerStartFailure ? {
|
|
145
|
+
lastWorkerStartFailure: lastWorkerStartFailure.message
|
|
146
|
+
} : {}),
|
|
139
147
|
pendingDeadLetterWrites: this.deadLetterManager.pendingWriteCount,
|
|
140
148
|
queuesReady: this.queuesByJobName.size,
|
|
149
|
+
workerStartFailures: this.workerStartFailures.length,
|
|
141
150
|
workerShutdownTimeoutMs: this.options.workerShutdownTimeoutMs,
|
|
142
151
|
workersDiscovered: this.descriptorsByJobType.size,
|
|
143
|
-
workersReady: this.
|
|
152
|
+
workersReady: this.runningWorkerJobNames.size
|
|
144
153
|
});
|
|
145
154
|
}
|
|
146
155
|
async ensureStarted() {
|
|
147
156
|
if (this.lifecycleState === 'started') {
|
|
148
157
|
return;
|
|
149
158
|
}
|
|
150
|
-
if (this.lifecycleState === 'stopping' || this.lifecycleState === 'stopped') {
|
|
159
|
+
if (this.lifecycleState === 'failed' || this.lifecycleState === 'stopping' || this.lifecycleState === 'stopped') {
|
|
151
160
|
throw new Error(`Queue lifecycle state is ${this.lifecycleState}.`);
|
|
152
161
|
}
|
|
153
162
|
if (!this.startPromise) {
|
|
@@ -166,7 +175,7 @@ class QueueLifecycleService {
|
|
|
166
175
|
const redis = await this.resolveRedisClient();
|
|
167
176
|
this.redisClient = redis;
|
|
168
177
|
this.descriptorsByJobType.clear();
|
|
169
|
-
for (const [jobType, descriptor] of discoverQueueWorkerDescriptors(this.compiledModules, this.options, this.logger)) {
|
|
178
|
+
for (const [jobType, descriptor] of discoverQueueWorkerDescriptors(this.compiledModules, this.options, this.logger, compiledModule => this.shouldDiscoverWorkersInModule(compiledModule))) {
|
|
170
179
|
this.descriptorsByJobType.set(jobType, descriptor);
|
|
171
180
|
}
|
|
172
181
|
await this.initializeWorkers(redis);
|
|
@@ -175,6 +184,12 @@ class QueueLifecycleService {
|
|
|
175
184
|
this.scheduleReadyWorkers();
|
|
176
185
|
}
|
|
177
186
|
}
|
|
187
|
+
shouldDiscoverWorkersInModule(compiledModule) {
|
|
188
|
+
if (this.options.global) {
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
return compiledModule.accessibleTokens.has(_QueueLifecycleServic) || compiledModule.accessibleTokens.has(QUEUE);
|
|
192
|
+
}
|
|
178
193
|
async handleStartupFailure() {
|
|
179
194
|
await this.closeInitializedResources();
|
|
180
195
|
if (this.lifecycleState === 'starting') {
|
|
@@ -278,23 +293,91 @@ class QueueLifecycleService {
|
|
|
278
293
|
descriptor,
|
|
279
294
|
worker
|
|
280
295
|
} of workers) {
|
|
296
|
+
if (this.lifecycleState !== 'started') {
|
|
297
|
+
break;
|
|
298
|
+
}
|
|
281
299
|
this.runWorker(descriptor, worker);
|
|
300
|
+
if (this.lifecycleState !== 'started') {
|
|
301
|
+
break;
|
|
302
|
+
}
|
|
282
303
|
}
|
|
283
304
|
}).catch(error => {
|
|
284
305
|
if (this.lifecycleState !== 'started') {
|
|
285
306
|
return;
|
|
286
307
|
}
|
|
308
|
+
this.lifecycleState = 'failed';
|
|
309
|
+
this.workerStartFailures.push({
|
|
310
|
+
jobName: '*',
|
|
311
|
+
message: this.toErrorMessage(error),
|
|
312
|
+
workerName: 'bootstrap-ready-signal'
|
|
313
|
+
});
|
|
287
314
|
this.logger.error('Failed to start queue workers after application bootstrap readiness.', error, 'QueueLifecycleService');
|
|
315
|
+
this.rollbackAfterWorkerStartupFailure();
|
|
288
316
|
});
|
|
289
317
|
}
|
|
290
318
|
runWorker(descriptor, worker) {
|
|
291
319
|
const runnableWorker = worker;
|
|
292
320
|
if (typeof runnableWorker.run !== 'function') {
|
|
321
|
+
this.recordWorkerStartFailure(descriptor, new Error(`Queue worker ${descriptor.workerName} cannot start because BullMQ Worker.run() is unavailable.`));
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
let runResult;
|
|
325
|
+
try {
|
|
326
|
+
runResult = runnableWorker.run();
|
|
327
|
+
} catch (error) {
|
|
328
|
+
this.recordWorkerStartFailure(descriptor, error);
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
const runPromise = Promise.resolve(runResult);
|
|
332
|
+
void runPromise.catch(error => {
|
|
333
|
+
this.recordWorkerStartFailure(descriptor, error);
|
|
334
|
+
});
|
|
335
|
+
void this.markWorkerReadyWhenStarted(descriptor, runnableWorker, runPromise).catch(error => {
|
|
336
|
+
this.recordWorkerStartFailure(descriptor, error);
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
async markWorkerReadyWhenStarted(descriptor, worker, runPromise) {
|
|
340
|
+
if (typeof worker.waitUntilReady === 'function') {
|
|
341
|
+
await Promise.race([worker.waitUntilReady(), runPromise]);
|
|
342
|
+
} else {
|
|
343
|
+
await Promise.race([Promise.resolve(), runPromise]);
|
|
344
|
+
}
|
|
345
|
+
if (this.lifecycleState !== 'started' || this.failedWorkerJobNames.has(descriptor.jobName)) {
|
|
293
346
|
return;
|
|
294
347
|
}
|
|
295
|
-
|
|
296
|
-
|
|
348
|
+
this.runningWorkerJobNames.add(descriptor.jobName);
|
|
349
|
+
}
|
|
350
|
+
recordWorkerStartFailure(descriptor, error) {
|
|
351
|
+
if (this.lifecycleState === 'stopping' || this.lifecycleState === 'stopped') {
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
if (this.failedWorkerJobNames.has(descriptor.jobName)) {
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
this.failedWorkerJobNames.add(descriptor.jobName);
|
|
358
|
+
this.runningWorkerJobNames.delete(descriptor.jobName);
|
|
359
|
+
this.workerStartFailures.push({
|
|
360
|
+
jobName: descriptor.jobName,
|
|
361
|
+
message: this.toErrorMessage(error),
|
|
362
|
+
workerName: descriptor.workerName
|
|
297
363
|
});
|
|
364
|
+
if (this.lifecycleState === 'started' || this.lifecycleState === 'starting') {
|
|
365
|
+
this.lifecycleState = 'failed';
|
|
366
|
+
}
|
|
367
|
+
this.logger.error(`Failed to start queue worker ${descriptor.workerName} after application bootstrap.`, error, 'QueueLifecycleService');
|
|
368
|
+
this.rollbackAfterWorkerStartupFailure();
|
|
369
|
+
}
|
|
370
|
+
rollbackAfterWorkerStartupFailure() {
|
|
371
|
+
if (!this.startupFailureRollbackPromise) {
|
|
372
|
+
this.startupFailureRollbackPromise = this.closeInitializedResources().then(() => {
|
|
373
|
+
this.redisClient = undefined;
|
|
374
|
+
}).catch(rollbackError => {
|
|
375
|
+
this.logger.error('Failed to roll back queue resources after worker startup failure.', rollbackError, 'QueueLifecycleService');
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
toErrorMessage(error) {
|
|
380
|
+
return error instanceof Error ? error.message : String(error);
|
|
298
381
|
}
|
|
299
382
|
async cleanupWorkerInitializationFailure(resources) {
|
|
300
383
|
if (resources.worker) {
|
|
@@ -362,6 +445,7 @@ class QueueLifecycleService {
|
|
|
362
445
|
this.lifecycleState = 'stopping';
|
|
363
446
|
this.shutdownPromise = (async () => {
|
|
364
447
|
await this.waitForInFlightStartup();
|
|
448
|
+
await this.waitForStartupFailureRollback();
|
|
365
449
|
await this.closeInitializedResources();
|
|
366
450
|
await this.deadLetterManager.drainPendingWrites();
|
|
367
451
|
this.lifecycleState = 'stopped';
|
|
@@ -370,6 +454,13 @@ class QueueLifecycleService {
|
|
|
370
454
|
})();
|
|
371
455
|
await this.shutdownPromise;
|
|
372
456
|
}
|
|
457
|
+
async waitForStartupFailureRollback() {
|
|
458
|
+
if (!this.startupFailureRollbackPromise) {
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
await this.startupFailureRollbackPromise;
|
|
462
|
+
this.startupFailureRollbackPromise = undefined;
|
|
463
|
+
}
|
|
373
464
|
async waitForInFlightStartup() {
|
|
374
465
|
const startup = this.startPromise;
|
|
375
466
|
if (!startup) {
|
|
@@ -390,6 +481,7 @@ class QueueLifecycleService {
|
|
|
390
481
|
this.workersByJobName.clear();
|
|
391
482
|
this.queuesByJobName.clear();
|
|
392
483
|
this.readyWorkers.splice(0);
|
|
484
|
+
this.runningWorkerJobNames.clear();
|
|
393
485
|
for (const worker of workers) {
|
|
394
486
|
await this.tryCloseWorker(worker);
|
|
395
487
|
}
|
package/dist/status.d.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import type { PlatformHealthReport, PlatformReadinessReport, PlatformSnapshot } from '@fluojs/runtime';
|
|
2
2
|
/** Lifecycle phases reported by the queue platform status adapter. */
|
|
3
|
-
export type QueueLifecycleState = 'idle' | 'starting' | 'started' | 'stopping' | 'stopped';
|
|
3
|
+
export type QueueLifecycleState = 'idle' | 'starting' | 'started' | 'stopping' | 'stopped' | 'failed';
|
|
4
4
|
/** Input payload used to derive queue readiness, health, and dependency details. */
|
|
5
5
|
export interface QueueStatusAdapterInput {
|
|
6
6
|
dependencyId?: string;
|
|
7
|
+
lastWorkerStartFailure?: string;
|
|
7
8
|
lifecycleState: QueueLifecycleState;
|
|
8
9
|
pendingDeadLetterWrites: number;
|
|
9
10
|
queuesReady: number;
|
|
11
|
+
workerStartFailures?: number;
|
|
10
12
|
workerShutdownTimeoutMs: number;
|
|
11
13
|
workersDiscovered: number;
|
|
12
14
|
workersReady: number;
|
package/dist/status.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../src/status.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAEvG,sEAAsE;AACtE,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,UAAU,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../src/status.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAEvG,sEAAsE;AACtE,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,UAAU,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC;AAEtG,oFAAoF;AACpF,MAAM,WAAW,uBAAuB;IACtC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,cAAc,EAAE,mBAAmB,CAAC;IACpC,uBAAuB,EAAE,MAAM,CAAC;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,uBAAuB,EAAE,MAAM,CAAC;IAChC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,sFAAsF;AACtF,MAAM,WAAW,2BAA2B;IAC1C,SAAS,EAAE,uBAAuB,CAAC;IACnC,MAAM,EAAE,oBAAoB,CAAC;IAC7B,SAAS,EAAE,gBAAgB,CAAC,WAAW,CAAC,CAAC;IACzC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAoHD;;;;;GAKG;AACH,wBAAgB,iCAAiC,CAAC,KAAK,EAAE,uBAAuB,GAAG,2BAA2B,CAuB7G"}
|
package/dist/status.js
CHANGED
|
@@ -5,7 +5,22 @@
|
|
|
5
5
|
/** Queue-specific platform snapshot returned to health and readiness integrations. */
|
|
6
6
|
|
|
7
7
|
function createReadiness(input) {
|
|
8
|
+
const workerStartFailures = input.workerStartFailures ?? 0;
|
|
9
|
+
if (input.lifecycleState === 'failed' || workerStartFailures > 0) {
|
|
10
|
+
return {
|
|
11
|
+
critical: true,
|
|
12
|
+
reason: 'Queue worker startup failed.',
|
|
13
|
+
status: 'not-ready'
|
|
14
|
+
};
|
|
15
|
+
}
|
|
8
16
|
if (input.lifecycleState === 'started') {
|
|
17
|
+
if (input.workersReady < input.workersDiscovered) {
|
|
18
|
+
return {
|
|
19
|
+
critical: true,
|
|
20
|
+
reason: 'Queue workers are waiting for BullMQ processors to start.',
|
|
21
|
+
status: 'degraded'
|
|
22
|
+
};
|
|
23
|
+
}
|
|
9
24
|
return {
|
|
10
25
|
critical: true,
|
|
11
26
|
status: 'ready'
|
|
@@ -39,6 +54,13 @@ function createReadiness(input) {
|
|
|
39
54
|
};
|
|
40
55
|
}
|
|
41
56
|
function createHealth(input) {
|
|
57
|
+
const workerStartFailures = input.workerStartFailures ?? 0;
|
|
58
|
+
if (input.lifecycleState === 'failed' || workerStartFailures > 0) {
|
|
59
|
+
return {
|
|
60
|
+
reason: 'Queue worker startup failed.',
|
|
61
|
+
status: 'unhealthy'
|
|
62
|
+
};
|
|
63
|
+
}
|
|
42
64
|
if (input.lifecycleState === 'stopped') {
|
|
43
65
|
return {
|
|
44
66
|
reason: 'Queue workers are stopped.',
|
|
@@ -57,6 +79,12 @@ function createHealth(input) {
|
|
|
57
79
|
status: 'degraded'
|
|
58
80
|
};
|
|
59
81
|
}
|
|
82
|
+
if (input.lifecycleState === 'started' && input.workersReady < input.workersDiscovered) {
|
|
83
|
+
return {
|
|
84
|
+
reason: 'Queue workers are waiting for BullMQ processors to start.',
|
|
85
|
+
status: 'degraded'
|
|
86
|
+
};
|
|
87
|
+
}
|
|
60
88
|
if (input.lifecycleState === 'started' && input.pendingDeadLetterWrites > 0) {
|
|
61
89
|
return {
|
|
62
90
|
reason: 'Queue dead-letter writes are still pending.',
|
|
@@ -81,13 +109,18 @@ function createHealth(input) {
|
|
|
81
109
|
* @returns Readiness, health, ownership, and queue detail fields.
|
|
82
110
|
*/
|
|
83
111
|
export function createQueuePlatformStatusSnapshot(input) {
|
|
112
|
+
const workerStartFailures = input.workerStartFailures ?? 0;
|
|
84
113
|
return {
|
|
85
114
|
details: {
|
|
86
115
|
deadLetterDrainTimeoutMs: 5_000,
|
|
87
116
|
dependencies: [input.dependencyId ?? 'redis.default'],
|
|
88
117
|
lifecycleState: input.lifecycleState,
|
|
118
|
+
...(input.lastWorkerStartFailure ? {
|
|
119
|
+
lastWorkerStartFailure: input.lastWorkerStartFailure
|
|
120
|
+
} : {}),
|
|
89
121
|
pendingDeadLetterWrites: input.pendingDeadLetterWrites,
|
|
90
122
|
queuesReady: input.queuesReady,
|
|
123
|
+
workerStartFailures,
|
|
91
124
|
workerShutdownTimeoutMs: input.workerShutdownTimeoutMs,
|
|
92
125
|
workersDiscovered: input.workersDiscovered,
|
|
93
126
|
workersReady: input.workersReady
|
package/dist/types.d.ts
CHANGED
|
@@ -49,6 +49,7 @@ export interface NormalizedQueueModuleOptions {
|
|
|
49
49
|
defaultConcurrency: number;
|
|
50
50
|
defaultDeadLetterMaxEntries: number | false;
|
|
51
51
|
defaultRateLimiter?: QueueRateLimiterOptions;
|
|
52
|
+
global: boolean;
|
|
52
53
|
workerShutdownTimeoutMs: number;
|
|
53
54
|
}
|
|
54
55
|
/** Metadata captured by {@link QueueWorker} during decorator evaluation. */
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAE1C,oFAAoF;AACpF,MAAM,WAAW,YAAY,CAAC,IAAI,SAAS,MAAM,GAAG,MAAM;IACxD,KAAK,GAAG,IAAI,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC;CAC9B;AAED,sEAAsE;AACtE,MAAM,MAAM,gBAAgB,GAAG,OAAO,GAAG,aAAa,CAAC;AAEvD,4DAA4D;AAC5D,MAAM,WAAW,mBAAmB;IAClC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,gBAAgB,CAAC;CACzB;AAED,qEAAqE;AACrE,MAAM,WAAW,uBAAuB;IACtC,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;GAKG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,mBAAmB,CAAC;IAC9B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,uBAAuB,CAAC;CACvC;AAED,iFAAiF;AACjF,MAAM,WAAW,kBAAkB;IACjC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,8EAA8E;IAC9E,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,mBAAmB,CAAC;IACrC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,2BAA2B,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IAC7C,kBAAkB,CAAC,EAAE,uBAAuB,CAAC;IAC7C,kHAAkH;IAClH,uBAAuB,CAAC,EAAE,MAAM,CAAC;CAClC;AAED,yEAAyE;AACzE,MAAM,WAAW,4BAA4B;IAC3C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,mBAAmB,CAAC;IACrC,kBAAkB,EAAE,MAAM,CAAC;IAC3B,2BAA2B,EAAE,MAAM,GAAG,KAAK,CAAC;IAC5C,kBAAkB,CAAC,EAAE,uBAAuB,CAAC;IAC7C,uBAAuB,EAAE,MAAM,CAAC;CACjC;AAED,4EAA4E;AAC5E,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,YAAY,CAAC;IACtB,OAAO,EAAE,kBAAkB,CAAC;CAC7B;AAED,qEAAqE;AACrE,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,mBAAmB,CAAC;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,YAAY,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,uBAAuB,CAAC;IACtC,KAAK,EAAE,KAAK,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,yEAAyE;AACzE,MAAM,WAAW,KAAK;IACpB;;;;;OAKG;IACH,OAAO,CAAC,IAAI,SAAS,MAAM,EAAE,GAAG,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CAC1D"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAE1C,oFAAoF;AACpF,MAAM,WAAW,YAAY,CAAC,IAAI,SAAS,MAAM,GAAG,MAAM;IACxD,KAAK,GAAG,IAAI,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC;CAC9B;AAED,sEAAsE;AACtE,MAAM,MAAM,gBAAgB,GAAG,OAAO,GAAG,aAAa,CAAC;AAEvD,4DAA4D;AAC5D,MAAM,WAAW,mBAAmB;IAClC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,gBAAgB,CAAC;CACzB;AAED,qEAAqE;AACrE,MAAM,WAAW,uBAAuB;IACtC,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;GAKG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,mBAAmB,CAAC;IAC9B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,uBAAuB,CAAC;CACvC;AAED,iFAAiF;AACjF,MAAM,WAAW,kBAAkB;IACjC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,8EAA8E;IAC9E,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,mBAAmB,CAAC;IACrC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,2BAA2B,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IAC7C,kBAAkB,CAAC,EAAE,uBAAuB,CAAC;IAC7C,kHAAkH;IAClH,uBAAuB,CAAC,EAAE,MAAM,CAAC;CAClC;AAED,yEAAyE;AACzE,MAAM,WAAW,4BAA4B;IAC3C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,mBAAmB,CAAC;IACrC,kBAAkB,EAAE,MAAM,CAAC;IAC3B,2BAA2B,EAAE,MAAM,GAAG,KAAK,CAAC;IAC5C,kBAAkB,CAAC,EAAE,uBAAuB,CAAC;IAC7C,MAAM,EAAE,OAAO,CAAC;IAChB,uBAAuB,EAAE,MAAM,CAAC;CACjC;AAED,4EAA4E;AAC5E,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,YAAY,CAAC;IACtB,OAAO,EAAE,kBAAkB,CAAC;CAC7B;AAED,qEAAqE;AACrE,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,mBAAmB,CAAC;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,YAAY,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,uBAAuB,CAAC;IACtC,KAAK,EAAE,KAAK,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,yEAAyE;AACzE,MAAM,WAAW,KAAK;IACpB;;;;;OAKG;IACH,OAAO,CAAC,IAAI,SAAS,MAAM,EAAE,GAAG,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CAC1D"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ApplicationLogger, CompiledModule } from '@fluojs/runtime';
|
|
2
|
+
import { type DiscoveryModuleFilter } from './helpers.js';
|
|
2
3
|
import type { NormalizedQueueModuleOptions, QueueJobType, QueueWorkerDescriptor } from './types.js';
|
|
3
4
|
/**
|
|
4
5
|
* Discover queue worker descriptors.
|
|
@@ -8,5 +9,5 @@ import type { NormalizedQueueModuleOptions, QueueJobType, QueueWorkerDescriptor
|
|
|
8
9
|
* @param logger The logger.
|
|
9
10
|
* @returns The discover queue worker descriptors result.
|
|
10
11
|
*/
|
|
11
|
-
export declare function discoverQueueWorkerDescriptors(compiledModules: readonly CompiledModule[], options: NormalizedQueueModuleOptions, logger: ApplicationLogger): Map<QueueJobType, QueueWorkerDescriptor>;
|
|
12
|
+
export declare function discoverQueueWorkerDescriptors(compiledModules: readonly CompiledModule[], options: NormalizedQueueModuleOptions, logger: ApplicationLogger, moduleFilter?: DiscoveryModuleFilter): Map<QueueJobType, QueueWorkerDescriptor>;
|
|
12
13
|
//# sourceMappingURL=worker-discovery.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worker-discovery.d.ts","sourceRoot":"","sources":["../src/worker-discovery.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"worker-discovery.d.ts","sourceRoot":"","sources":["../src/worker-discovery.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGzE,OAAO,EAA8B,KAAK,qBAAqB,EAAkD,MAAM,cAAc,CAAC;AACtI,OAAO,KAAK,EAAE,4BAA4B,EAAE,YAAY,EAAE,qBAAqB,EAAuB,MAAM,YAAY,CAAC;AAEzH;;;;;;;GAOG;AACH,wBAAgB,8BAA8B,CAC5C,eAAe,EAAE,SAAS,cAAc,EAAE,EAC1C,OAAO,EAAE,4BAA4B,EACrC,MAAM,EAAE,iBAAiB,EACzB,YAAY,CAAC,EAAE,qBAAqB,GACnC,GAAG,CAAC,YAAY,EAAE,qBAAqB,CAAC,CA4C1C"}
|
package/dist/worker-discovery.js
CHANGED
|
@@ -8,10 +8,10 @@ import { collectDiscoveryCandidates, normalizePositiveInteger, normalizeRateLimi
|
|
|
8
8
|
* @param logger The logger.
|
|
9
9
|
* @returns The discover queue worker descriptors result.
|
|
10
10
|
*/
|
|
11
|
-
export function discoverQueueWorkerDescriptors(compiledModules, options, logger) {
|
|
11
|
+
export function discoverQueueWorkerDescriptors(compiledModules, options, logger, moduleFilter) {
|
|
12
12
|
const descriptorsByJobType = new Map();
|
|
13
13
|
const seenJobNames = new Set();
|
|
14
|
-
for (const candidate of collectDiscoveryCandidates(compiledModules)) {
|
|
14
|
+
for (const candidate of collectDiscoveryCandidates(compiledModules, moduleFilter)) {
|
|
15
15
|
const metadata = getQueueWorkerMetadata(candidate.targetType);
|
|
16
16
|
if (!metadata) {
|
|
17
17
|
continue;
|
package/package.json
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"redis",
|
|
11
11
|
"dlq"
|
|
12
12
|
],
|
|
13
|
-
"version": "1.0.
|
|
13
|
+
"version": "1.0.2",
|
|
14
14
|
"private": false,
|
|
15
15
|
"license": "MIT",
|
|
16
16
|
"repository": {
|
|
@@ -39,9 +39,9 @@
|
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"bullmq": "^5.58.0",
|
|
41
41
|
"@fluojs/core": "^1.0.3",
|
|
42
|
-
"@fluojs/di": "^1.0
|
|
43
|
-
"@fluojs/redis": "^1.0.
|
|
44
|
-
"@fluojs/runtime": "^1.1.
|
|
42
|
+
"@fluojs/di": "^1.1.0",
|
|
43
|
+
"@fluojs/redis": "^1.0.2",
|
|
44
|
+
"@fluojs/runtime": "^1.1.8"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"vitest": "^3.2.4"
|