@blokjs/trigger-worker 0.2.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.
- package/CHANGELOG.md +22 -0
- package/dist/WorkerTrigger.d.ts +197 -0
- package/dist/WorkerTrigger.js +311 -0
- package/dist/adapters/BullMQAdapter.d.ts +71 -0
- package/dist/adapters/BullMQAdapter.js +259 -0
- package/dist/adapters/InMemoryAdapter.d.ts +48 -0
- package/dist/adapters/InMemoryAdapter.js +224 -0
- package/dist/index.d.ts +56 -0
- package/dist/index.js +64 -0
- package/package.json +45 -0
- package/src/WorkerTrigger.test.ts +510 -0
- package/src/WorkerTrigger.ts +501 -0
- package/src/adapters/BullMQAdapter.ts +296 -0
- package/src/adapters/InMemoryAdapter.ts +276 -0
- package/src/index.ts +67 -0
- package/tsconfig.json +32 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# @blokjs/trigger-worker
|
|
2
|
+
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Initial public release of Blok packages.
|
|
8
|
+
|
|
9
|
+
This release includes:
|
|
10
|
+
|
|
11
|
+
- Core packages: @blokjs/shared, @blokjs/helper, @blokjs/runner
|
|
12
|
+
- Node packages: @blokjs/api-call, @blokjs/if-else, @blokjs/react
|
|
13
|
+
- Trigger packages: pubsub, queue, webhook, websocket, worker, cron, grpc
|
|
14
|
+
- CLI tool: blokctl
|
|
15
|
+
- Editor support: @blokjs/lsp-server, @blokjs/syntax
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- Updated dependencies
|
|
20
|
+
- @blokjs/shared@0.2.0
|
|
21
|
+
- @blokjs/helper@0.2.0
|
|
22
|
+
- @blokjs/runner@0.2.0
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WorkerTrigger - Background job processing for Blok workflows
|
|
3
|
+
*
|
|
4
|
+
* Extends TriggerBase to support long-running background jobs:
|
|
5
|
+
* - Concurrency controls (max N concurrent jobs)
|
|
6
|
+
* - Retry logic with exponential backoff
|
|
7
|
+
* - Job timeouts
|
|
8
|
+
* - Job priority and delay scheduling
|
|
9
|
+
* - Dead letter queue support
|
|
10
|
+
*
|
|
11
|
+
* Pattern:
|
|
12
|
+
* 1. loadNodes() - Load available nodes into NodeMap
|
|
13
|
+
* 2. loadWorkflows() - Load workflows with worker triggers
|
|
14
|
+
* 3. listen() - Connect to job backend and start processing
|
|
15
|
+
* 4. For each job:
|
|
16
|
+
* - Create context with this.createContext()
|
|
17
|
+
* - Populate ctx.request with job data
|
|
18
|
+
* - Execute workflow via this.run(ctx)
|
|
19
|
+
* - Ack on success, retry or DLQ on failure
|
|
20
|
+
*/
|
|
21
|
+
import type { HelperResponse, WorkerTriggerOpts } from "@blok/helper";
|
|
22
|
+
import { DefaultLogger, type GlobalOptions, type BlokService, TriggerBase, type TriggerResponse } from "@blok/runner";
|
|
23
|
+
import type { Context } from "@blok/shared";
|
|
24
|
+
/**
|
|
25
|
+
* Job received from worker queue
|
|
26
|
+
*/
|
|
27
|
+
export interface WorkerJob {
|
|
28
|
+
/** Unique job ID */
|
|
29
|
+
id: string;
|
|
30
|
+
/** Job data payload */
|
|
31
|
+
data: unknown;
|
|
32
|
+
/** Job metadata headers */
|
|
33
|
+
headers: Record<string, string>;
|
|
34
|
+
/** Queue name this job belongs to */
|
|
35
|
+
queue: string;
|
|
36
|
+
/** Job priority (higher = more important) */
|
|
37
|
+
priority: number;
|
|
38
|
+
/** Number of attempts made so far */
|
|
39
|
+
attempts: number;
|
|
40
|
+
/** Maximum retry attempts */
|
|
41
|
+
maxRetries: number;
|
|
42
|
+
/** Timestamp when job was created */
|
|
43
|
+
createdAt: Date;
|
|
44
|
+
/** Delay before processing (ms) */
|
|
45
|
+
delay?: number;
|
|
46
|
+
/** Job timeout (ms) */
|
|
47
|
+
timeout?: number;
|
|
48
|
+
/** Original raw job from provider */
|
|
49
|
+
raw: unknown;
|
|
50
|
+
/** Mark job as completed */
|
|
51
|
+
complete: () => Promise<void>;
|
|
52
|
+
/** Mark job as failed (optionally requeue) */
|
|
53
|
+
fail: (error: Error, requeue?: boolean) => Promise<void>;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Worker adapter interface - implemented by each job backend
|
|
57
|
+
*/
|
|
58
|
+
export interface WorkerAdapter {
|
|
59
|
+
/** Provider name (e.g., "bullmq", "in-memory") */
|
|
60
|
+
readonly provider: string;
|
|
61
|
+
/** Connect to the job backend */
|
|
62
|
+
connect(): Promise<void>;
|
|
63
|
+
/** Disconnect from the job backend */
|
|
64
|
+
disconnect(): Promise<void>;
|
|
65
|
+
/**
|
|
66
|
+
* Start processing jobs from a queue
|
|
67
|
+
* @param config Worker trigger configuration
|
|
68
|
+
* @param handler Callback for each job
|
|
69
|
+
*/
|
|
70
|
+
process(config: WorkerTriggerOpts, handler: (job: WorkerJob) => Promise<void>): Promise<void>;
|
|
71
|
+
/**
|
|
72
|
+
* Add a job to a queue (for programmatic dispatching)
|
|
73
|
+
* @param queue Queue name
|
|
74
|
+
* @param data Job payload
|
|
75
|
+
* @param opts Job options
|
|
76
|
+
*/
|
|
77
|
+
addJob(queue: string, data: unknown, opts?: {
|
|
78
|
+
priority?: number;
|
|
79
|
+
delay?: number;
|
|
80
|
+
retries?: number;
|
|
81
|
+
timeout?: number;
|
|
82
|
+
jobId?: string;
|
|
83
|
+
}): Promise<string>;
|
|
84
|
+
/** Stop processing a specific queue */
|
|
85
|
+
stopProcessing(queue: string): Promise<void>;
|
|
86
|
+
/** Check if connected */
|
|
87
|
+
isConnected(): boolean;
|
|
88
|
+
/** Health check */
|
|
89
|
+
healthCheck(): Promise<boolean>;
|
|
90
|
+
/** Get queue stats */
|
|
91
|
+
getQueueStats(queue: string): Promise<WorkerQueueStats>;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Queue statistics
|
|
95
|
+
*/
|
|
96
|
+
export interface WorkerQueueStats {
|
|
97
|
+
/** Number of jobs waiting to be processed */
|
|
98
|
+
waiting: number;
|
|
99
|
+
/** Number of jobs currently being processed */
|
|
100
|
+
active: number;
|
|
101
|
+
/** Number of completed jobs */
|
|
102
|
+
completed: number;
|
|
103
|
+
/** Number of failed jobs */
|
|
104
|
+
failed: number;
|
|
105
|
+
/** Number of delayed jobs */
|
|
106
|
+
delayed: number;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Workflow model with worker trigger configuration
|
|
110
|
+
*/
|
|
111
|
+
interface WorkerWorkflowModel {
|
|
112
|
+
path: string;
|
|
113
|
+
config: {
|
|
114
|
+
name: string;
|
|
115
|
+
version: string;
|
|
116
|
+
trigger?: {
|
|
117
|
+
worker?: WorkerTriggerOpts;
|
|
118
|
+
[key: string]: unknown;
|
|
119
|
+
};
|
|
120
|
+
[key: string]: unknown;
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* WorkerTrigger - Abstract base class for worker-based triggers
|
|
125
|
+
*
|
|
126
|
+
* Provides background job processing with:
|
|
127
|
+
* - Configurable concurrency per queue
|
|
128
|
+
* - Automatic retries with exponential backoff
|
|
129
|
+
* - Job timeouts with automatic failure
|
|
130
|
+
* - Priority-based job ordering
|
|
131
|
+
* - Delayed job scheduling
|
|
132
|
+
* - Queue statistics and monitoring
|
|
133
|
+
*/
|
|
134
|
+
export declare abstract class WorkerTrigger extends TriggerBase {
|
|
135
|
+
protected nodeMap: GlobalOptions;
|
|
136
|
+
protected readonly tracer: import("@opentelemetry/api").Tracer;
|
|
137
|
+
protected readonly logger: DefaultLogger;
|
|
138
|
+
protected abstract adapter: WorkerAdapter;
|
|
139
|
+
/** Active queues being processed */
|
|
140
|
+
protected activeQueues: Set<string>;
|
|
141
|
+
protected abstract nodes: Record<string, BlokService<unknown>>;
|
|
142
|
+
protected abstract workflows: Record<string, HelperResponse>;
|
|
143
|
+
constructor();
|
|
144
|
+
/**
|
|
145
|
+
* Load nodes into the node map
|
|
146
|
+
*/
|
|
147
|
+
loadNodes(): void;
|
|
148
|
+
/**
|
|
149
|
+
* Load workflows into the workflow map
|
|
150
|
+
*/
|
|
151
|
+
loadWorkflows(): void;
|
|
152
|
+
/**
|
|
153
|
+
* Start the worker processor - main entry point
|
|
154
|
+
*/
|
|
155
|
+
listen(): Promise<number>;
|
|
156
|
+
/**
|
|
157
|
+
* Stop all workers and disconnect
|
|
158
|
+
*/
|
|
159
|
+
stop(): Promise<void>;
|
|
160
|
+
protected onHmrWorkflowChange(): Promise<void>;
|
|
161
|
+
/**
|
|
162
|
+
* Dispatch a job to a worker queue
|
|
163
|
+
*/
|
|
164
|
+
dispatch(queue: string, data: unknown, opts?: {
|
|
165
|
+
priority?: number;
|
|
166
|
+
delay?: number;
|
|
167
|
+
retries?: number;
|
|
168
|
+
timeout?: number;
|
|
169
|
+
jobId?: string;
|
|
170
|
+
}): Promise<string>;
|
|
171
|
+
/**
|
|
172
|
+
* Get statistics for a queue
|
|
173
|
+
*/
|
|
174
|
+
getQueueStats(queue: string): Promise<WorkerQueueStats>;
|
|
175
|
+
/**
|
|
176
|
+
* Get list of active queues
|
|
177
|
+
*/
|
|
178
|
+
getActiveQueues(): string[];
|
|
179
|
+
/**
|
|
180
|
+
* Get all workflows that have worker triggers
|
|
181
|
+
*/
|
|
182
|
+
protected getWorkerWorkflows(): WorkerWorkflowModel[];
|
|
183
|
+
/**
|
|
184
|
+
* Handle an incoming job
|
|
185
|
+
*/
|
|
186
|
+
protected handleJob(job: WorkerJob, workflow: WorkerWorkflowModel, config: WorkerTriggerOpts): Promise<void>;
|
|
187
|
+
/**
|
|
188
|
+
* Execute workflow with a timeout
|
|
189
|
+
*/
|
|
190
|
+
protected executeWithTimeout(ctx: Context, timeoutMs: number): Promise<TriggerResponse>;
|
|
191
|
+
/**
|
|
192
|
+
* Calculate exponential backoff delay
|
|
193
|
+
* Formula: min(baseDelay * 2^attempt, 30000) + jitter
|
|
194
|
+
*/
|
|
195
|
+
protected calculateBackoff(attempt: number, baseDelay?: number): number;
|
|
196
|
+
}
|
|
197
|
+
export default WorkerTrigger;
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* WorkerTrigger - Background job processing for Blok workflows
|
|
4
|
+
*
|
|
5
|
+
* Extends TriggerBase to support long-running background jobs:
|
|
6
|
+
* - Concurrency controls (max N concurrent jobs)
|
|
7
|
+
* - Retry logic with exponential backoff
|
|
8
|
+
* - Job timeouts
|
|
9
|
+
* - Job priority and delay scheduling
|
|
10
|
+
* - Dead letter queue support
|
|
11
|
+
*
|
|
12
|
+
* Pattern:
|
|
13
|
+
* 1. loadNodes() - Load available nodes into NodeMap
|
|
14
|
+
* 2. loadWorkflows() - Load workflows with worker triggers
|
|
15
|
+
* 3. listen() - Connect to job backend and start processing
|
|
16
|
+
* 4. For each job:
|
|
17
|
+
* - Create context with this.createContext()
|
|
18
|
+
* - Populate ctx.request with job data
|
|
19
|
+
* - Execute workflow via this.run(ctx)
|
|
20
|
+
* - Ack on success, retry or DLQ on failure
|
|
21
|
+
*/
|
|
22
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
+
exports.WorkerTrigger = void 0;
|
|
24
|
+
const runner_1 = require("@blok/runner");
|
|
25
|
+
const api_1 = require("@opentelemetry/api");
|
|
26
|
+
const uuid_1 = require("uuid");
|
|
27
|
+
/**
|
|
28
|
+
* WorkerTrigger - Abstract base class for worker-based triggers
|
|
29
|
+
*
|
|
30
|
+
* Provides background job processing with:
|
|
31
|
+
* - Configurable concurrency per queue
|
|
32
|
+
* - Automatic retries with exponential backoff
|
|
33
|
+
* - Job timeouts with automatic failure
|
|
34
|
+
* - Priority-based job ordering
|
|
35
|
+
* - Delayed job scheduling
|
|
36
|
+
* - Queue statistics and monitoring
|
|
37
|
+
*/
|
|
38
|
+
class WorkerTrigger extends runner_1.TriggerBase {
|
|
39
|
+
nodeMap = {};
|
|
40
|
+
tracer = api_1.trace.getTracer(process.env.PROJECT_NAME || "trigger-worker-workflow", process.env.PROJECT_VERSION || "0.0.1");
|
|
41
|
+
logger = new runner_1.DefaultLogger();
|
|
42
|
+
/** Active queues being processed */
|
|
43
|
+
activeQueues = new Set();
|
|
44
|
+
constructor() {
|
|
45
|
+
super();
|
|
46
|
+
this.loadNodes();
|
|
47
|
+
this.loadWorkflows();
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Load nodes into the node map
|
|
51
|
+
*/
|
|
52
|
+
loadNodes() {
|
|
53
|
+
this.nodeMap.nodes = new runner_1.NodeMap();
|
|
54
|
+
const nodeKeys = Object.keys(this.nodes);
|
|
55
|
+
for (const key of nodeKeys) {
|
|
56
|
+
this.nodeMap.nodes.addNode(key, this.nodes[key]);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Load workflows into the workflow map
|
|
61
|
+
*/
|
|
62
|
+
loadWorkflows() {
|
|
63
|
+
this.nodeMap.workflows = this.workflows;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Start the worker processor - main entry point
|
|
67
|
+
*/
|
|
68
|
+
async listen() {
|
|
69
|
+
const startTime = this.startCounter();
|
|
70
|
+
try {
|
|
71
|
+
// Connect to job backend
|
|
72
|
+
await this.adapter.connect();
|
|
73
|
+
this.logger.log(`Connected to ${this.adapter.provider} worker system`);
|
|
74
|
+
// Register health dependency
|
|
75
|
+
this.registerHealthDependency(`worker-${this.adapter.provider}`, async () => {
|
|
76
|
+
const healthy = await this.adapter.healthCheck();
|
|
77
|
+
return {
|
|
78
|
+
status: healthy ? "healthy" : "unhealthy",
|
|
79
|
+
lastChecked: Date.now(),
|
|
80
|
+
message: healthy ? "Connected" : "Connection lost",
|
|
81
|
+
};
|
|
82
|
+
});
|
|
83
|
+
// Find all workflows with worker triggers
|
|
84
|
+
const workerWorkflows = this.getWorkerWorkflows();
|
|
85
|
+
if (workerWorkflows.length === 0) {
|
|
86
|
+
this.logger.log("No workflows with worker triggers found");
|
|
87
|
+
return this.endCounter(startTime);
|
|
88
|
+
}
|
|
89
|
+
// Start processing each queue
|
|
90
|
+
for (const workflow of workerWorkflows) {
|
|
91
|
+
const config = workflow.config.trigger?.worker;
|
|
92
|
+
this.logger.log(`Starting worker for queue: ${config.queue} (concurrency=${config.concurrency}, retries=${config.retries})`);
|
|
93
|
+
this.activeQueues.add(config.queue);
|
|
94
|
+
await this.adapter.process(config, async (job) => {
|
|
95
|
+
await this.handleJob(job, workflow, config);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
this.logger.log(`Worker trigger started. Processing ${workerWorkflows.length} queue(s)`);
|
|
99
|
+
// Enable HMR in development mode
|
|
100
|
+
if (process.env.BLOK_HMR === "true" || process.env.NODE_ENV === "development") {
|
|
101
|
+
await this.enableHotReload();
|
|
102
|
+
}
|
|
103
|
+
return this.endCounter(startTime);
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
this.logger.error(`Failed to start worker trigger: ${error.message}`);
|
|
107
|
+
throw error;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Stop all workers and disconnect
|
|
112
|
+
*/
|
|
113
|
+
async stop() {
|
|
114
|
+
for (const queue of this.activeQueues) {
|
|
115
|
+
await this.adapter.stopProcessing(queue);
|
|
116
|
+
this.logger.log(`Stopped processing queue: ${queue}`);
|
|
117
|
+
}
|
|
118
|
+
this.activeQueues.clear();
|
|
119
|
+
await this.adapter.disconnect();
|
|
120
|
+
this.destroyMonitoring();
|
|
121
|
+
this.logger.log("Worker trigger stopped");
|
|
122
|
+
}
|
|
123
|
+
async onHmrWorkflowChange() {
|
|
124
|
+
this.logger.log("[HMR] Worker workflow changed, reloading...");
|
|
125
|
+
await this.waitForInFlightRequests();
|
|
126
|
+
await this.stop();
|
|
127
|
+
this.loadWorkflows();
|
|
128
|
+
await this.listen();
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Dispatch a job to a worker queue
|
|
132
|
+
*/
|
|
133
|
+
async dispatch(queue, data, opts) {
|
|
134
|
+
return this.adapter.addJob(queue, data, opts);
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Get statistics for a queue
|
|
138
|
+
*/
|
|
139
|
+
async getQueueStats(queue) {
|
|
140
|
+
return this.adapter.getQueueStats(queue);
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Get list of active queues
|
|
144
|
+
*/
|
|
145
|
+
getActiveQueues() {
|
|
146
|
+
return Array.from(this.activeQueues);
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Get all workflows that have worker triggers
|
|
150
|
+
*/
|
|
151
|
+
getWorkerWorkflows() {
|
|
152
|
+
const workflows = [];
|
|
153
|
+
for (const [path, workflow] of Object.entries(this.nodeMap.workflows || {})) {
|
|
154
|
+
const workflowConfig = workflow._config;
|
|
155
|
+
if (workflowConfig?.trigger) {
|
|
156
|
+
const triggerType = Object.keys(workflowConfig.trigger)[0];
|
|
157
|
+
if (triggerType === "worker" && workflowConfig.trigger.worker) {
|
|
158
|
+
workflows.push({
|
|
159
|
+
path,
|
|
160
|
+
config: workflowConfig,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return workflows;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Handle an incoming job
|
|
169
|
+
*/
|
|
170
|
+
async handleJob(job, workflow, config) {
|
|
171
|
+
const jobId = job.id || (0, uuid_1.v4)();
|
|
172
|
+
const defaultMeter = api_1.metrics.getMeter("default");
|
|
173
|
+
const workerJobs = defaultMeter.createCounter("worker_jobs_processed", {
|
|
174
|
+
description: "Worker jobs processed",
|
|
175
|
+
});
|
|
176
|
+
const workerErrors = defaultMeter.createCounter("worker_jobs_failed", {
|
|
177
|
+
description: "Worker job failures",
|
|
178
|
+
});
|
|
179
|
+
const workerRetries = defaultMeter.createCounter("worker_jobs_retried", {
|
|
180
|
+
description: "Worker job retries",
|
|
181
|
+
});
|
|
182
|
+
await this.tracer.startActiveSpan(`worker:${config.queue}`, async (span) => {
|
|
183
|
+
try {
|
|
184
|
+
const start = performance.now();
|
|
185
|
+
// Initialize configuration for this workflow
|
|
186
|
+
await this.configuration.init(workflow.path, this.nodeMap);
|
|
187
|
+
// Create context
|
|
188
|
+
const ctx = this.createContext(undefined, workflow.path, jobId);
|
|
189
|
+
// Populate request with job data
|
|
190
|
+
ctx.request = {
|
|
191
|
+
body: job.data,
|
|
192
|
+
headers: job.headers,
|
|
193
|
+
query: {},
|
|
194
|
+
params: {
|
|
195
|
+
queue: job.queue,
|
|
196
|
+
jobId: job.id,
|
|
197
|
+
attempt: String(job.attempts),
|
|
198
|
+
priority: String(job.priority),
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
// Store worker metadata in context
|
|
202
|
+
if (!ctx.vars)
|
|
203
|
+
ctx.vars = {};
|
|
204
|
+
ctx.vars["_worker_job"] = {
|
|
205
|
+
id: job.id,
|
|
206
|
+
queue: job.queue,
|
|
207
|
+
attempts: String(job.attempts),
|
|
208
|
+
maxRetries: String(job.maxRetries),
|
|
209
|
+
priority: String(job.priority),
|
|
210
|
+
createdAt: job.createdAt.toISOString(),
|
|
211
|
+
delay: String(job.delay ?? 0),
|
|
212
|
+
timeout: String(job.timeout ?? 0),
|
|
213
|
+
};
|
|
214
|
+
ctx.logger.log(`Processing job ${jobId} from ${config.queue} (attempt ${job.attempts + 1}/${job.maxRetries + 1})`);
|
|
215
|
+
// Execute workflow with timeout if configured
|
|
216
|
+
let response;
|
|
217
|
+
if (config.timeout && config.timeout > 0) {
|
|
218
|
+
response = await this.executeWithTimeout(ctx, config.timeout);
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
response = await this.run(ctx);
|
|
222
|
+
}
|
|
223
|
+
const end = performance.now();
|
|
224
|
+
// Set span attributes
|
|
225
|
+
span.setAttribute("success", true);
|
|
226
|
+
span.setAttribute("job_id", jobId);
|
|
227
|
+
span.setAttribute("queue", config.queue);
|
|
228
|
+
span.setAttribute("attempts", job.attempts);
|
|
229
|
+
span.setAttribute("elapsed_ms", end - start);
|
|
230
|
+
span.setStatus({ code: api_1.SpanStatusCode.OK });
|
|
231
|
+
// Record metrics
|
|
232
|
+
workerJobs.add(1, {
|
|
233
|
+
env: process.env.NODE_ENV,
|
|
234
|
+
queue: config.queue,
|
|
235
|
+
workflow_name: this.configuration.name,
|
|
236
|
+
success: "true",
|
|
237
|
+
});
|
|
238
|
+
ctx.logger.log(`Job completed in ${(end - start).toFixed(2)}ms: ${jobId}`);
|
|
239
|
+
// Mark job as completed
|
|
240
|
+
await job.complete();
|
|
241
|
+
}
|
|
242
|
+
catch (error) {
|
|
243
|
+
const errorMessage = error.message;
|
|
244
|
+
const shouldRetry = job.attempts < job.maxRetries;
|
|
245
|
+
// Set span error
|
|
246
|
+
span.setAttribute("success", false);
|
|
247
|
+
span.setAttribute("will_retry", shouldRetry);
|
|
248
|
+
span.recordException(error);
|
|
249
|
+
span.setStatus({ code: api_1.SpanStatusCode.ERROR, message: errorMessage });
|
|
250
|
+
if (shouldRetry) {
|
|
251
|
+
// Retry with exponential backoff
|
|
252
|
+
const backoffMs = this.calculateBackoff(job.attempts, config.delay);
|
|
253
|
+
workerRetries.add(1, {
|
|
254
|
+
env: process.env.NODE_ENV,
|
|
255
|
+
queue: config.queue,
|
|
256
|
+
workflow_name: this.configuration?.name || "unknown",
|
|
257
|
+
attempt: String(job.attempts + 1),
|
|
258
|
+
});
|
|
259
|
+
this.logger.error(`Job ${jobId} failed (attempt ${job.attempts + 1}/${job.maxRetries + 1}), retrying in ${backoffMs}ms: ${errorMessage}`);
|
|
260
|
+
await job.fail(error, true);
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
// Max retries exhausted - send to DLQ
|
|
264
|
+
workerErrors.add(1, {
|
|
265
|
+
env: process.env.NODE_ENV,
|
|
266
|
+
queue: config.queue,
|
|
267
|
+
workflow_name: this.configuration?.name || "unknown",
|
|
268
|
+
});
|
|
269
|
+
this.logger.error(`Job ${jobId} permanently failed after ${job.attempts + 1} attempts: ${errorMessage}`, error.stack);
|
|
270
|
+
await job.fail(error, false);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
finally {
|
|
274
|
+
span.end();
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Execute workflow with a timeout
|
|
280
|
+
*/
|
|
281
|
+
async executeWithTimeout(ctx, timeoutMs) {
|
|
282
|
+
return new Promise((resolve, reject) => {
|
|
283
|
+
const timer = setTimeout(() => {
|
|
284
|
+
reject(new Error(`Job timed out after ${timeoutMs}ms`));
|
|
285
|
+
}, timeoutMs);
|
|
286
|
+
this.run(ctx)
|
|
287
|
+
.then((result) => {
|
|
288
|
+
clearTimeout(timer);
|
|
289
|
+
resolve(result);
|
|
290
|
+
})
|
|
291
|
+
.catch((error) => {
|
|
292
|
+
clearTimeout(timer);
|
|
293
|
+
reject(error);
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Calculate exponential backoff delay
|
|
299
|
+
* Formula: min(baseDelay * 2^attempt, 30000) + jitter
|
|
300
|
+
*/
|
|
301
|
+
calculateBackoff(attempt, baseDelay) {
|
|
302
|
+
const base = baseDelay ?? 1000;
|
|
303
|
+
const maxDelay = 30000; // 30 seconds max
|
|
304
|
+
const exponential = Math.min(base * Math.pow(2, attempt), maxDelay);
|
|
305
|
+
const jitter = Math.random() * exponential * 0.1; // 10% jitter
|
|
306
|
+
return Math.floor(exponential + jitter);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
exports.WorkerTrigger = WorkerTrigger;
|
|
310
|
+
exports.default = WorkerTrigger;
|
|
311
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiV29ya2VyVHJpZ2dlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9Xb3JrZXJUcmlnZ2VyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQTs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQW1CRzs7O0FBR0gseUNBT3NCO0FBRXRCLDRDQUErRTtBQUMvRSwrQkFBa0M7QUFxSGxDOzs7Ozs7Ozs7O0dBVUc7QUFDSCxNQUFzQixhQUFjLFNBQVEsb0JBQVc7SUFDNUMsT0FBTyxHQUFrQixFQUFtQixDQUFDO0lBQ3BDLE1BQU0sR0FBRyxXQUFLLENBQUMsU0FBUyxDQUMxQyxPQUFPLENBQUMsR0FBRyxDQUFDLFlBQVksSUFBSSx5QkFBeUIsRUFDckQsT0FBTyxDQUFDLEdBQUcsQ0FBQyxlQUFlLElBQUksT0FBTyxDQUN0QyxDQUFDO0lBQ2lCLE1BQU0sR0FBRyxJQUFJLHNCQUFhLEVBQUUsQ0FBQztJQUdoRCxvQ0FBb0M7SUFDMUIsWUFBWSxHQUFnQixJQUFJLEdBQUcsRUFBRSxDQUFDO0lBTWhEO1FBQ0MsS0FBSyxFQUFFLENBQUM7UUFDUixJQUFJLENBQUMsU0FBUyxFQUFFLENBQUM7UUFDakIsSUFBSSxDQUFDLGFBQWEsRUFBRSxDQUFDO0lBQ3RCLENBQUM7SUFFRDs7T0FFRztJQUNILFNBQVM7UUFDUixJQUFJLENBQUMsT0FBTyxDQUFDLEtBQUssR0FBRyxJQUFJLGdCQUFPLEVBQUUsQ0FBQztRQUNuQyxNQUFNLFFBQVEsR0FBRyxNQUFNLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsQ0FBQztRQUN6QyxLQUFLLE1BQU0sR0FBRyxJQUFJLFFBQVEsRUFBRSxDQUFDO1lBQzVCLElBQUksQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxHQUFHLEVBQUUsSUFBSSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDO1FBQ2xELENBQUM7SUFDRixDQUFDO0lBRUQ7O09BRUc7SUFDSCxhQUFhO1FBQ1os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O1FBQ0wsQ0FBQyxDQUFDLENBQUM7SUFDSixDQUFDO0lBRUQ7OztPQUdHO0lBQ08sZ0JBQWdCLENBQUMsT0FBZSxFQUFFLFNBQWtCO1FBQzdELE1BQU0sSUFBSSxHQUFHLFNBQVMsSUFBSSxJQUFJLENBQUM7UUFDL0IsTUFBTSxRQUFRLEdBQUcsS0FBSyxDQUFDLENBQUMsaUJBQWlCO1FBQ3pDLE1BQU0sV0FBVyxHQUFHLElBQUksQ0FBQyxHQUFHLENBQUMsSUFBSSxHQUFHLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQyxFQUFFLE9BQU8sQ0FBQyxFQUFFLFFBQVEsQ0FBQyxDQUFDO1FBQ3BFLE1BQU0sTUFBTSxHQUFHLElBQUksQ0FBQyxNQUFNLEVBQUUsR0FBRyxXQUFXLEdBQUcsR0FBRyxDQUFDLENBQUMsYUFBYTtRQUMvRCxPQUFPLElBQUksQ0FBQyxLQUFLLENBQUMsV0FBVyxHQUFHLE1BQU0sQ0FBQyxDQUFDO0lBQ3pDLENBQUM7Q0FDRDtBQWxWRCxzQ0FrVkM7QUFFRCxrQkFBZSxhQUFhLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIFdvcmtlclRyaWdnZXIgLSBCYWNrZ3JvdW5kIGpvYiBwcm9jZXNzaW5nIGZvciBCbG9rIHdvcmtmbG93c1xuICpcbiAqIEV4dGVuZHMgVHJpZ2dlckJhc2UgdG8gc3VwcG9ydCBsb25nLXJ1bm5pbmcgYmFja2dyb3VuZCBqb2JzOlxuICogLSBDb25jdXJyZW5jeSBjb250cm9scyAobWF4IE4gY29uY3VycmVudCBqb2JzKVxuICogLSBSZXRyeSBsb2dpYyB3aXRoIGV4cG9uZW50aWFsIGJhY2tvZmZcbiAqIC0gSm9iIHRpbWVvdXRzXG4gKiAtIEpvYiBwcmlvcml0eSBhbmQgZGVsYXkgc2NoZWR1bGluZ1xuICogLSBEZWFkIGxldHRlciBxdWV1ZSBzdXBwb3J0XG4gKlxuICogUGF0dGVybjpcbiAqIDEuIGxvYWROb2RlcygpIC0gTG9hZCBhdmFpbGFibGUgbm9kZXMgaW50byBOb2RlTWFwXG4gKiAyLiBsb2FkV29ya2Zsb3dzKCkgLSBMb2FkIHdvcmtmbG93cyB3aXRoIHdvcmtlciB0cmlnZ2Vyc1xuICogMy4gbGlzdGVuKCkgLSBDb25uZWN0IHRvIGpvYiBiYWNrZW5kIGFuZCBzdGFydCBwcm9jZXNzaW5nXG4gKiA0LiBGb3IgZWFjaCBqb2I6XG4gKiAgICAtIENyZWF0ZSBjb250ZXh0IHdpdGggdGhpcy5jcmVhdGVDb250ZXh0KClcbiAqICAgIC0gUG9wdWxhdGUgY3R4LnJlcXVlc3Qgd2l0aCBqb2IgZGF0YVxuICogICAgLSBFeGVjdXRlIHdvcmtmbG93IHZpYSB0aGlzLnJ1bihjdHgpXG4gKiAgICAtIEFjayBvbiBzdWNjZXNzLCByZXRyeSBvciBETFEgb24gZmFpbHVyZVxuICovXG5cbmltcG9ydCB0eXBlIHsgSGVscGVyUmVzcG9uc2UsIFdvcmtlclRyaWdnZXJPcHRzIH0gZnJvbSBcIkBibG9rL2hlbHBlclwiO1xuaW1wb3J0IHtcblx0RGVmYXVsdExvZ2dlcixcblx0dHlwZSBHbG9iYWxPcHRpb25zLFxuXHR0eXBlIEJsb2tTZXJ2aWNlLFxuXHROb2RlTWFwLFxuXHRUcmlnZ2VyQmFzZSxcblx0dHlwZSBUcmlnZ2VyUmVzcG9uc2UsXG59IGZyb20gXCJAYmxvay9ydW5uZXJcIjtcbmltcG9ydCB0eXBlIHsgQ29udGV4dCwgUmVxdWVzdENvbnRleHQgfSBmcm9tIFwiQGJsb2svc2hhcmVkXCI7XG5pbXBvcnQgeyB0eXBlIFNwYW4sIFNwYW5TdGF0dXNDb2RlLCBtZXRyaWNzLCB0cmFjZSB9IGZyb20gXCJAb3BlbnRlbGVtZXRyeS9hcGlcIjtcbmltcG9ydCB7IHY0IGFzIHV1aWQgfSBmcm9tIFwidXVpZFwiO1xuXG4vKipcbiAqIEpvYiByZWNlaXZlZCBmcm9tIHdvcmtlciBxdWV1ZVxuICovXG5leHBvcnQgaW50ZXJmYWNlIFdvcmtlckpvYiB7XG5cdC8qKiBVbmlxdWUgam9iIElEICovXG5cdGlkOiBzdHJpbmc7XG5cdC8qKiBKb2IgZGF0YSBwYXlsb2FkICovXG5cdGRhdGE6IHVua25vd247XG5cdC8qKiBKb2IgbWV0YWRhdGEgaGVhZGVycyAqL1xuXHRoZWFkZXJzOiBSZWNvcmQ8c3RyaW5nLCBzdHJpbmc+O1xuXHQvKiogUXVldWUgbmFtZSB0aGlzIGpvYiBiZWxvbmdzIHRvICovXG5cdHF1ZXVlOiBzdHJpbmc7XG5cdC8qKiBKb2IgcHJpb3JpdHkgKGhpZ2hlciA9IG1vcmUgaW1wb3J0YW50KSAqL1xuXHRwcmlvcml0eTogbnVtYmVyO1xuXHQvKiogTnVtYmVyIG9mIGF0dGVtcHRzIG1hZGUgc28gZmFyICovXG5cdGF0dGVtcHRzOiBudW1iZXI7XG5cdC8qKiBNYXhpbXVtIHJldHJ5IGF0dGVtcHRzICovXG5cdG1heFJldHJpZXM6IG51bWJlcjtcblx0LyoqIFRpbWVzdGFtcCB3aGVuIGpvYiB3YXMgY3JlYXRlZCAqL1xuXHRjcmVhdGVkQXQ6IERhdGU7XG5cdC8qKiBEZWxheSBiZWZvcmUgcHJvY2Vzc2luZyAobXMpICovXG5cdGRlbGF5PzogbnVtYmVyO1xuXHQvKiogSm9iIHRpbWVvdXQgKG1zKSAqL1xuXHR0aW1lb3V0PzogbnVtYmVyO1xuXHQvKiogT3JpZ2luYWwgcmF3IGpvYiBmcm9tIHByb3ZpZGVyICovXG5cdHJhdzogdW5rbm93bjtcblx0LyoqIE1hcmsgam9iIGFzIGNvbXBsZXRlZCAqL1xuXHRjb21wbGV0ZTogKCkgPT4gUHJvbWlzZTx2b2lkPjtcblx0LyoqIE1hcmsgam9iIGFzIGZhaWxlZCAob3B0aW9uYWxseSByZXF1ZXVlKSAqL1xuXHRmYWlsOiAoZXJyb3I6IEVycm9yLCByZXF1ZXVlPzogYm9vbGVhbikgPT4gUHJvbWlzZTx2b2lkPjtcbn1cblxuLyoqXG4gKiBXb3JrZXIgYWRhcHRlciBpbnRlcmZhY2UgLSBpbXBsZW1lbnRlZCBieSBlYWNoIGpvYiBiYWNrZW5kXG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgV29ya2VyQWRhcHRlciB7XG5cdC8qKiBQcm92aWRlciBuYW1lIChlLmcuLCBcImJ1bGxtcVwiLCBcImluLW1lbW9yeVwiKSAqL1xuXHRyZWFkb25seSBwcm92aWRlcjogc3RyaW5nO1xuXG5cdC8qKiBDb25uZWN0IHRvIHRoZSBqb2IgYmFja2VuZCAqL1xuXHRjb25uZWN0KCk6IFByb21pc2U8dm9pZD47XG5cblx0LyoqIERpc2Nvbm5lY3QgZnJvbSB0aGUgam9iIGJhY2tlbmQgKi9cblx0ZGlzY29ubmVjdCgpOiBQcm9taXNlPHZvaWQ+O1xuXG5cdC8qKlxuXHQgKiBTdGFydCBwcm9jZXNzaW5nIGpvYnMgZnJvbSBhIHF1ZXVlXG5cdCAqIEBwYXJhbSBjb25maWcgV29ya2VyIHRyaWdnZXIgY29uZmlndXJhdGlvblxuXHQgKiBAcGFyYW0gaGFuZGxlciBDYWxsYmFjayBmb3IgZWFjaCBqb2Jcblx0ICovXG5cdHByb2Nlc3MoY29uZmlnOiBXb3JrZXJUcmlnZ2VyT3B0cywgaGFuZGxlcjogKGpvYjogV29ya2VySm9iKSA9PiBQcm9taXNlPHZvaWQ+KTogUHJvbWlzZTx2b2lkPjtcblxuXHQvKipcblx0ICogQWRkIGEgam9iIHRvIGEgcXVldWUgKGZvciBwcm9ncmFtbWF0aWMgZGlzcGF0Y2hpbmcpXG5cdCAqIEBwYXJhbSBxdWV1ZSBRdWV1ZSBuYW1lXG5cdCAqIEBwYXJhbSBkYXRhIEpvYiBwYXlsb2FkXG5cdCAqIEBwYXJhbSBvcHRzIEpvYiBvcHRpb25zXG5cdCAqL1xuXHRhZGRKb2IoXG5cdFx0cXVldWU6IHN0cmluZyxcblx0XHRkYXRhOiB1bmtub3duLFxuXHRcdG9wdHM/OiB7XG5cdFx0XHRwcmlvcml0eT86IG51bWJlcjtcblx0XHRcdGRlbGF5PzogbnVtYmVyO1xuXHRcdFx0cmV0cmllcz86IG51bWJlcjtcblx0XHRcdHRpbWVvdXQ/OiBudW1iZXI7XG5cdFx0XHRqb2JJZD86IHN0cmluZztcblx0XHR9LFxuXHQpOiBQcm9taXNlPHN0cmluZz47XG5cblx0LyoqIFN0b3AgcHJvY2Vzc2luZyBhIHNwZWNpZmljIHF1ZXVlICovXG5cdHN0b3BQcm9jZXNzaW5nKHF1ZXVlOiBzdHJpbmcpOiBQcm9taXNlPHZvaWQ+O1xuXG5cdC8qKiBDaGVjayBpZiBjb25uZWN0ZWQgKi9cblx0aXNDb25uZWN0ZWQoKTogYm9vbGVhbjtcblxuXHQvKiogSGVhbHRoIGNoZWNrICovXG5cdGhlYWx0aENoZWNrKCk6IFByb21pc2U8Ym9vbGVhbj47XG5cblx0LyoqIEdldCBxdWV1ZSBzdGF0cyAqL1xuXHRnZXRRdWV1ZVN0YXRzKHF1ZXVlOiBzdHJpbmcpOiBQcm9taXNlPFdvcmtlclF1ZXVlU3RhdHM+O1xufVxuXG4vKipcbiAqIFF1ZXVlIHN0YXRpc3RpY3NcbiAqL1xuZXhwb3J0IGludGVyZmFjZSBXb3JrZXJRdWV1ZVN0YXRzIHtcblx0LyoqIE51bWJlciBvZiBqb2JzIHdhaXRpbmcgdG8gYmUgcHJvY2Vzc2VkICovXG5cdHdhaXRpbmc6IG51bWJlcjtcblx0LyoqIE51bWJlciBvZiBqb2JzIGN1cnJlbnRseSBiZWluZyBwcm9jZXNzZWQgKi9cblx0YWN0aXZlOiBudW1iZXI7XG5cdC8qKiBOdW1iZXIgb2YgY29tcGxldGVkIGpvYnMgKi9cblx0Y29tcGxldGVkOiBudW1iZXI7XG5cdC8qKiBOdW1iZXIgb2YgZmFpbGVkIGpvYnMgKi9cblx0ZmFpbGVkOiBudW1iZXI7XG5cdC8qKiBOdW1iZXIgb2YgZGVsYXllZCBqb2JzICovXG5cdGRlbGF5ZWQ6IG51bWJlcjtcbn1cblxuLyoqXG4gKiBXb3JrZmxvdyBtb2RlbCB3aXRoIHdvcmtlciB0cmlnZ2VyIGNvbmZpZ3VyYXRpb25cbiAqL1xuaW50ZXJmYWNlIFdvcmtlcldvcmtmbG93TW9kZWwge1xuXHRwYXRoOiBzdHJpbmc7XG5cdGNvbmZpZzoge1xuXHRcdG5hbWU6IHN0cmluZztcblx0XHR2ZXJzaW9uOiBzdHJpbmc7XG5cdFx0dHJpZ2dlcj86IHtcblx0XHRcdHdvcmtlcj86IFdvcmtlclRyaWdnZXJPcHRzO1xuXHRcdFx0W2tleTogc3RyaW5nXTogdW5rbm93bjtcblx0XHR9O1xuXHRcdFtrZXk6IHN0cmluZ106IHVua25vd247XG5cdH07XG59XG5cbi8qKlxuICogV29ya2VyVHJpZ2dlciAtIEFic3RyYWN0IGJhc2UgY2xhc3MgZm9yIHdvcmtlci1iYXNlZCB0cmlnZ2Vyc1xuICpcbiAqIFByb3ZpZGVzIGJhY2tncm91bmQgam9iIHByb2Nlc3Npbmcgd2l0aDpcbiAqIC0gQ29uZmlndXJhYmxlIGNvbmN1cnJlbmN5IHBlciBxdWV1ZVxuICogLSBBdXRvbWF0aWMgcmV0cmllcyB3aXRoIGV4cG9uZW50aWFsIGJhY2tvZmZcbiAqIC0gSm9iIHRpbWVvdXRzIHdpdGggYXV0b21hdGljIGZhaWx1cmVcbiAqIC0gUHJpb3JpdHktYmFzZWQgam9iIG9yZGVyaW5nXG4gKiAtIERlbGF5ZWQgam9iIHNjaGVkdWxpbmdcbiAqIC0gUXVldWUgc3RhdGlzdGljcyBhbmQgbW9uaXRvcmluZ1xuICovXG5leHBvcnQgYWJzdHJhY3QgY2xhc3MgV29ya2VyVHJpZ2dlciBleHRlbmRzIFRyaWdnZXJCYXNlIHtcblx0cHJvdGVjdGVkIG5vZGVNYXA6IEdsb2JhbE9wdGlvbnMgPSB7fSBhcyBHbG9iYWxPcHRpb25zO1xuXHRwcm90ZWN0ZWQgcmVhZG9ubHkgdHJhY2VyID0gdHJhY2UuZ2V0VHJhY2VyKFxuXHRcdHByb2Nlc3MuZW52LlBST0pFQ1RfTkFNRSB8fCBcInRyaWdnZXItd29ya2VyLXdvcmtmbG93XCIsXG5cdFx0cHJvY2Vzcy5lbnYuUFJPSkVDVF9WRVJTSU9OIHx8IFwiMC4wLjFcIixcblx0KTtcblx0cHJvdGVjdGVkIHJlYWRvbmx5IGxvZ2dlciA9IG5ldyBEZWZhdWx0TG9nZ2VyKCk7XG5cdHByb3RlY3RlZCBhYnN0cmFjdCBhZGFwdGVyOiBXb3JrZXJBZGFwdGVyO1xuXG5cdC8qKiBBY3RpdmUgcXVldWVzIGJlaW5nIHByb2Nlc3NlZCAqL1xuXHRwcm90ZWN0ZWQgYWN0aXZlUXVldWVzOiBTZXQ8c3RyaW5nPiA9IG5ldyBTZXQoKTtcblxuXHQvLyBTdWJjbGFzc2VzIHByb3ZpZGUgdGhlc2Vcblx0cHJvdGVjdGVkIGFic3RyYWN0IG5vZGVzOiBSZWNvcmQ8c3RyaW5nLCBCbG9rU2VydmljZTx1bmtub3duPj47XG5cdHByb3RlY3RlZCBhYnN0cmFjdCB3b3JrZmxvd3M6IFJlY29yZDxzdHJpbmcsIEhlbHBlclJlc3BvbnNlPjtcblxuXHRjb25zdHJ1Y3RvcigpIHtcblx0XHRzdXBlcigpO1xuXHRcdHRoaXMubG9hZE5vZGVzKCk7XG5cdFx0dGhpcy5sb2FkV29ya2Zsb3dzKCk7XG5cdH1cblxuXHQvKipcblx0ICogTG9hZCBub2RlcyBpbnRvIHRoZSBub2RlIG1hcFxuXHQgKi9cblx0bG9hZE5vZGVzKCk6IHZvaWQge1xuXHRcdHRoaXMubm9kZU1hcC5ub2RlcyA9IG5ldyBOb2RlTWFwKCk7XG5cdFx0Y29uc3Qgbm9kZUtleXMgPSBPYmplY3Qua2V5cyh0aGlzLm5vZGVzKTtcblx0XHRmb3IgKGNvbnN0IGtleSBvZiBub2RlS2V5cykge1xuXHRcdFx0dGhpcy5ub2RlTWFwLm5vZGVzLmFkZE5vZGUoa2V5LCB0aGlzLm5vZGVzW2tleV0pO1xuXHRcdH1cblx0fVxuXG5cdC8qKlxuXHQgKiBMb2FkIHdvcmtmbG93cyBpbnRvIHRoZSB3b3JrZmxvdyBtYXBcblx0ICovXG5cdGxvYWRXb3JrZmxvd3MoKTogdm9pZCB7XG5cdFx0dGhpcy5ub2RlTWFwLndvcmtmbG93cyA9IHRoaXMud29ya2Zsb3dzO1xuXHR9XG5cblx0LyoqXG5cdCAqIFN0YXJ0IHRoZSB3b3JrZXIgcHJvY2Vzc29yIC0gbWFpbiBlbnRyeSBwb2ludFxuXHQgKi9cblx0YXN5bmMgbGlzdGVuKCk6IFByb21pc2U8bnVtYmVyPiB7XG5cdFx0Y29uc3Qgc3RhcnRUaW1lID0gdGhpcy5zdGFydENvdW50ZXIoKTtcblxuXHRcdHRyeSB7XG5cdFx0XHQvLyBDb25uZWN0IHRvIGpvYiBiYWNrZW5kXG5cdFx0XHRhd2FpdCB0aGlzLmFkYXB0ZXIuY29ubmVjdCgpO1xuXHRcdFx0dGhpcy5sb2dnZXIubG9nKGBDb25uZWN0ZWQgdG8gJHt0aGlzLmFkYXB0ZXIucHJvdmlkZXJ9IHdvcmtlciBzeXN0ZW1gKTtcblxuXHRcdFx0Ly8gUmVnaXN0ZXIgaGVhbHRoIGRlcGVuZGVuY3lcblx0XHRcdHRoaXMucmVnaXN0ZXJIZWFsdGhEZXBlbmRlbmN5KGB3b3JrZXItJHt0aGlzLmFkYXB0ZXIucHJvdmlkZXJ9YCwgYXN5bmMgKCkgPT4ge1xuXHRcdFx0XHRjb25zdCBoZWFsdGh5ID0gYXdhaXQgdGhpcy5hZGFwdGVyLmhlYWx0aENoZWNrKCk7XG5cdFx0XHRcdHJldHVybiB7XG5cdFx0XHRcdFx0c3RhdHVzOiBoZWFsdGh5ID8gKFwiaGVhbHRoeVwiIGFzIGNvbnN0KSA6IChcInVuaGVhbHRoeVwiIGFzIGNvbnN0KSxcblx0XHRcdFx0XHRsYXN0Q2hlY2tlZDogRGF0ZS5ub3coKSxcblx0XHRcdFx0XHRtZXNzYWdlOiBoZWFsdGh5ID8gXCJDb25uZWN0ZWRcIiA6IFwiQ29ubmVjdGlvbiBsb3N0XCIsXG5cdFx0XHRcdH07XG5cdFx0XHR9KTtcblxuXHRcdFx0Ly8gRmluZCBhbGwgd29ya2Zsb3dzIHdpdGggd29ya2VyIHRyaWdnZXJzXG5cdFx0XHRjb25zdCB3b3JrZXJXb3JrZmxvd3MgPSB0aGlzLmdldFdvcmtlcldvcmtmbG93cygpO1xuXG5cdFx0XHRpZiAod29ya2VyV29ya2Zsb3dzLmxlbmd0aCA9PT0gMCkge1xuXHRcdFx0XHR0aGlzLmxvZ2dlci5sb2coXCJObyB3b3JrZmxvd3Mgd2l0aCB3b3JrZXIgdHJpZ2dlcnMgZm91bmRcIik7XG5cdFx0XHRcdHJldHVybiB0aGlzLmVuZENvdW50ZXIoc3RhcnRUaW1lKTtcblx0XHRcdH1cblxuXHRcdFx0Ly8gU3RhcnQgcHJvY2Vzc2luZyBlYWNoIHF1ZXVlXG5cdFx0XHRmb3IgKGNvbnN0IHdvcmtmbG93IG9mIHdvcmtlcldvcmtmbG93cykge1xuXHRcdFx0XHRjb25zdCBjb25maWcgPSB3b3JrZmxvdy5jb25maWcudHJpZ2dlcj8ud29ya2VyIGFzIFdvcmtlclRyaWdnZXJPcHRzO1xuXHRcdFx0XHR0aGlzLmxvZ2dlci5sb2coXG5cdFx0XHRcdFx0YFN0YXJ0aW5nIHdvcmtlciBmb3IgcXVldWU6ICR7Y29uZmlnLnF1ZXVlfSAoY29uY3VycmVuY3k9JHtjb25maWcuY29uY3VycmVuY3l9LCByZXRyaWVzPSR7Y29uZmlnLnJldHJpZXN9KWAsXG5cdFx0XHRcdCk7XG5cblx0XHRcdFx0dGhpcy5hY3RpdmVRdWV1ZXMuYWRkKGNvbmZpZy5xdWV1ZSk7XG5cblx0XHRcdFx0YXdhaXQgdGhpcy5hZGFwdGVyLnByb2Nlc3MoY29uZmlnLCBhc3luYyAoam9iKSA9PiB7XG5cdFx0XHRcdFx0YXdhaXQgdGhpcy5oYW5kbGVKb2Ioam9iLCB3b3JrZmxvdywgY29uZmlnKTtcblx0XHRcdFx0fSk7XG5cdFx0XHR9XG5cblx0XHRcdHRoaXMubG9nZ2VyLmxvZyhgV29ya2VyIHRyaWdnZXIgc3RhcnRlZC4gUHJvY2Vzc2luZyAke3dvcmtlcldvcmtmbG93cy5sZW5ndGh9IHF1ZXVlKHMpYCk7XG5cblx0XHRcdC8vIEVuYWJsZSBITVIgaW4gZGV2ZWxvcG1lbnQgbW9kZVxuXHRcdFx0aWYgKHByb2Nlc3MuZW52LkJMT0tfSE1SID09PSBcInRydWVcIiB8fCBwcm9jZXNzLmVudi5OT0RFX0VOViA9PT0gXCJkZXZlbG9wbWVudFwiKSB7XG5cdFx0XHRcdGF3YWl0IHRoaXMuZW5hYmxlSG90UmVsb2FkKCk7XG5cdFx0XHR9XG5cblx0XHRcdHJldHVybiB0aGlzLmVuZENvdW50ZXIoc3RhcnRUaW1lKTtcblx0XHR9IGNhdGNoIChlcnJvcikge1xuXHRcdFx0dGhpcy5sb2dnZXIuZXJyb3IoYEZhaWxlZCB0byBzdGFydCB3b3JrZXIgdHJpZ2dlcjogJHsoZXJyb3IgYXMgRXJyb3IpLm1lc3NhZ2V9YCk7XG5cdFx0XHR0aHJvdyBlcnJvcjtcblx0XHR9XG5cdH1cblxuXHQvKipcblx0ICogU3RvcCBhbGwgd29ya2VycyBhbmQgZGlzY29ubmVjdFxuXHQgKi9cblx0YXN5bmMgc3RvcCgpOiBQcm9taXNlPHZvaWQ+IHtcblx0XHRmb3IgKGNvbnN0IHF1ZXVlIG9mIHRoaXMuYWN0aXZlUXVldWVzKSB7XG5cdFx0XHRhd2FpdCB0aGlzLmFkYXB0ZXIuc3RvcFByb2Nlc3NpbmcocXVldWUpO1xuXHRcdFx0dGhpcy5sb2dnZXIubG9nKGBTdG9wcGVkIHByb2Nlc3NpbmcgcXVldWU6ICR7cXVldWV9YCk7XG5cdFx0fVxuXHRcdHRoaXMuYWN0aXZlUXVldWVzLmNsZWFyKCk7XG5cdFx0YXdhaXQgdGhpcy5hZGFwdGVyLmRpc2Nvbm5lY3QoKTtcblx0XHR0aGlzLmRlc3Ryb3lNb25pdG9yaW5nKCk7XG5cdFx0dGhpcy5sb2dnZXIubG9nKFwiV29ya2VyIHRyaWdnZXIgc3RvcHBlZFwiKTtcblx0fVxuXG5cdHByb3RlY3RlZCBvdmVycmlkZSBhc3luYyBvbkhtcldvcmtmbG93Q2hhbmdlKCk6IFByb21pc2U8dm9pZD4ge1xuXHRcdHRoaXMubG9nZ2VyLmxvZyhcIltITVJdIFdvcmtlciB3b3JrZmxvdyBjaGFuZ2VkLCByZWxvYWRpbmcuLi5cIik7XG5cdFx0YXdhaXQgdGhpcy53YWl0Rm9ySW5GbGlnaHRSZXF1ZXN0cygpO1xuXHRcdGF3YWl0IHRoaXMuc3RvcCgpO1xuXHRcdHRoaXMubG9hZFdvcmtmbG93cygpO1xuXHRcdGF3YWl0IHRoaXMubGlzdGVuKCk7XG5cdH1cblxuXHQvKipcblx0ICogRGlzcGF0Y2ggYSBqb2IgdG8gYSB3b3JrZXIgcXVldWVcblx0ICovXG5cdGFzeW5jIGRpc3BhdGNoKFxuXHRcdHF1ZXVlOiBzdHJpbmcsXG5cdFx0ZGF0YTogdW5rbm93bixcblx0XHRvcHRzPzoge1xuXHRcdFx0cHJpb3JpdHk/OiBudW1iZXI7XG5cdFx0XHRkZWxheT86IG51bWJlcjtcblx0XHRcdHJldHJpZXM/OiBudW1iZXI7XG5cdFx0XHR0aW1lb3V0PzogbnVtYmVyO1xuXHRcdFx0am9iSWQ/OiBzdHJpbmc7XG5cdFx0fSxcblx0KTogUHJvbWlzZTxzdHJpbmc+IHtcblx0XHRyZXR1cm4gdGhpcy5hZGFwdGVyLmFkZEpvYihxdWV1ZSwgZGF0YSwgb3B0cyk7XG5cdH1cblxuXHQvKipcblx0ICogR2V0IHN0YXRpc3RpY3MgZm9yIGEgcXVldWVcblx0ICovXG5cdGFzeW5jIGdldFF1ZXVlU3RhdHMocXVldWU6IHN0cmluZyk6IFByb21pc2U8V29ya2VyUXVldWVTdGF0cz4ge1xuXHRcdHJldHVybiB0aGlzLmFkYXB0ZXIuZ2V0UXVldWVTdGF0cyhxdWV1ZSk7XG5cdH1cblxuXHQvKipcblx0ICogR2V0IGxpc3Qgb2YgYWN0aXZlIHF1ZXVlc1xuXHQgKi9cblx0Z2V0QWN0aXZlUXVldWVzKCk6IHN0cmluZ1tdIHtcblx0XHRyZXR1cm4gQXJyYXkuZnJvbSh0aGlzLmFjdGl2ZVF1ZXVlcyk7XG5cdH1cblxuXHQvKipcblx0ICogR2V0IGFsbCB3b3JrZmxvd3MgdGhhdCBoYXZlIHdvcmtlciB0cmlnZ2Vyc1xuXHQgKi9cblx0cHJvdGVjdGVkIGdldFdvcmtlcldvcmtmbG93cygpOiBXb3JrZXJXb3JrZmxvd01vZGVsW10ge1xuXHRcdGNvbnN0IHdvcmtmbG93czogV29ya2VyV29ya2Zsb3dNb2RlbFtdID0gW107XG5cblx0XHRmb3IgKGNvbnN0IFtwYXRoLCB3b3JrZmxvd10gb2YgT2JqZWN0LmVudHJpZXModGhpcy5ub2RlTWFwLndvcmtmbG93cyB8fCB7fSkpIHtcblx0XHRcdGNvbnN0IHdvcmtmbG93Q29uZmlnID0gKHdvcmtmbG93IGFzIHVua25vd24gYXMgeyBfY29uZmlnOiBXb3JrZXJXb3JrZmxvd01vZGVsW1wiY29uZmlnXCJdIH0pLl9jb25maWc7XG5cblx0XHRcdGlmICh3b3JrZmxvd0NvbmZpZz8udHJpZ2dlcikge1xuXHRcdFx0XHRjb25zdCB0cmlnZ2VyVHlwZSA9IE9iamVjdC5rZXlzKHdvcmtmbG93Q29uZmlnLnRyaWdnZXIpWzBdO1xuXG5cdFx0XHRcdGlmICh0cmlnZ2VyVHlwZSA9PT0gXCJ3b3JrZXJcIiAmJiB3b3JrZmxvd0NvbmZpZy50cmlnZ2VyLndvcmtlcikge1xuXHRcdFx0XHRcdHdvcmtmbG93cy5wdXNoKHtcblx0XHRcdFx0XHRcdHBhdGgsXG5cdFx0XHRcdFx0XHRjb25maWc6IHdvcmtmbG93Q29uZmlnLFxuXHRcdFx0XHRcdH0pO1xuXHRcdFx0XHR9XG5cdFx0XHR9XG5cdFx0fVxuXG5cdFx0cmV0dXJuIHdvcmtmbG93cztcblx0fVxuXG5cdC8qKlxuXHQgKiBIYW5kbGUgYW4gaW5jb21pbmcgam9iXG5cdCAqL1xuXHRwcm90ZWN0ZWQgYXN5bmMgaGFuZGxlSm9iKGpvYjogV29ya2VySm9iLCB3b3JrZmxvdzogV29ya2VyV29ya2Zsb3dNb2RlbCwgY29uZmlnOiBXb3JrZXJUcmlnZ2VyT3B0cyk6IFByb21pc2U8dm9pZD4ge1xuXHRcdGNvbnN0IGpvYklkID0gam9iLmlkIHx8IHV1aWQoKTtcblx0XHRjb25zdCBkZWZhdWx0TWV0ZXIgPSBtZXRyaWNzLmdldE1ldGVyKFwiZGVmYXVsdFwiKTtcblx0XHRjb25zdCB3b3JrZXJKb2JzID0gZGVmYXVsdE1ldGVyLmNyZWF0ZUNvdW50ZXIoXCJ3b3JrZXJfam9ic19wcm9jZXNzZWRcIiwge1xuXHRcdFx0ZGVzY3JpcHRpb246IFwiV29ya2VyIGpvYnMgcHJvY2Vzc2VkXCIsXG5cdFx0fSk7XG5cdFx0Y29uc3Qgd29ya2VyRXJyb3JzID0gZGVmYXVsdE1ldGVyLmNyZWF0ZUNvdW50ZXIoXCJ3b3JrZXJfam9ic19mYWlsZWRcIiwge1xuXHRcdFx0ZGVzY3JpcHRpb246IFwiV29ya2VyIGpvYiBmYWlsdXJlc1wiLFxuXHRcdH0pO1xuXHRcdGNvbnN0IHdvcmtlclJldHJpZXMgPSBkZWZhdWx0TWV0ZXIuY3JlYXRlQ291bnRlcihcIndvcmtlcl9qb2JzX3JldHJpZWRcIiwge1xuXHRcdFx0ZGVzY3JpcHRpb246IFwiV29ya2VyIGpvYiByZXRyaWVzXCIsXG5cdFx0fSk7XG5cblx0XHRhd2FpdCB0aGlzLnRyYWNlci5zdGFydEFjdGl2ZVNwYW4oYHdvcmtlcjoke2NvbmZpZy5xdWV1ZX1gLCBhc3luYyAoc3BhbjogU3BhbikgPT4ge1xuXHRcdFx0dHJ5IHtcblx0XHRcdFx0Y29uc3Qgc3RhcnQgPSBwZXJmb3JtYW5jZS5ub3coKTtcblxuXHRcdFx0XHQvLyBJbml0aWFsaXplIGNvbmZpZ3VyYXRpb24gZm9yIHRoaXMgd29ya2Zsb3dcblx0XHRcdFx0YXdhaXQgdGhpcy5jb25maWd1cmF0aW9uLmluaXQod29ya2Zsb3cucGF0aCwgdGhpcy5ub2RlTWFwKTtcblxuXHRcdFx0XHQvLyBDcmVhdGUgY29udGV4dFxuXHRcdFx0XHRjb25zdCBjdHg6IENvbnRleHQgPSB0aGlzLmNyZWF0ZUNvbnRleHQodW5kZWZpbmVkLCB3b3JrZmxvdy5wYXRoLCBqb2JJZCk7XG5cblx0XHRcdFx0Ly8gUG9wdWxhdGUgcmVxdWVzdCB3aXRoIGpvYiBkYXRhXG5cdFx0XHRcdGN0eC5yZXF1ZXN0ID0ge1xuXHRcdFx0XHRcdGJvZHk6IGpvYi5kYXRhLFxuXHRcdFx0XHRcdGhlYWRlcnM6IGpvYi5oZWFkZXJzLFxuXHRcdFx0XHRcdHF1ZXJ5OiB7fSxcblx0XHRcdFx0XHRwYXJhbXM6IHtcblx0XHRcdFx0XHRcdHF1ZXVlOiBqb2IucXVldWUsXG5cdFx0XHRcdFx0XHRqb2JJZDogam9iLmlkLFxuXHRcdFx0XHRcdFx0YXR0ZW1wdDogU3RyaW5nKGpvYi5hdHRlbXB0cyksXG5cdFx0XHRcdFx0XHRwcmlvcml0eTogU3RyaW5nKGpvYi5wcmlvcml0eSksXG5cdFx0XHRcdFx0fSxcblx0XHRcdFx0fSBhcyB1bmtub3duIGFzIFJlcXVlc3RDb250ZXh0O1xuXG5cdFx0XHRcdC8vIFN0b3JlIHdvcmtlciBtZXRhZGF0YSBpbiBjb250ZXh0XG5cdFx0XHRcdGlmICghY3R4LnZhcnMpIGN0eC52YXJzID0ge307XG5cdFx0XHRcdGN0eC52YXJzW1wiX3dvcmtlcl9qb2JcIl0gPSB7XG5cdFx0XHRcdFx0aWQ6IGpvYi5pZCxcblx0XHRcdFx0XHRxdWV1ZTogam9iLnF1ZXVlLFxuXHRcdFx0XHRcdGF0dGVtcHRzOiBTdHJpbmcoam9iLmF0dGVtcHRzKSxcblx0XHRcdFx0XHRtYXhSZXRyaWVzOiBTdHJpbmcoam9iLm1heFJldHJpZXMpLFxuXHRcdFx0XHRcdHByaW9yaXR5OiBTdHJpbmcoam9iLnByaW9yaXR5KSxcblx0XHRcdFx0XHRjcmVhdGVkQXQ6IGpvYi5jcmVhdGVkQXQudG9JU09TdHJpbmcoKSxcblx0XHRcdFx0XHRkZWxheTogU3RyaW5nKGpvYi5kZWxheSA/PyAwKSxcblx0XHRcdFx0XHR0aW1lb3V0OiBTdHJpbmcoam9iLnRpbWVvdXQgPz8gMCksXG5cdFx0XHRcdH07XG5cblx0XHRcdFx0Y3R4LmxvZ2dlci5sb2coXG5cdFx0XHRcdFx0YFByb2Nlc3Npbmcgam9iICR7am9iSWR9IGZyb20gJHtjb25maWcucXVldWV9IChhdHRlbXB0ICR7am9iLmF0dGVtcHRzICsgMX0vJHtqb2IubWF4UmV0cmllcyArIDF9KWAsXG5cdFx0XHRcdCk7XG5cblx0XHRcdFx0Ly8gRXhlY3V0ZSB3b3JrZmxvdyB3aXRoIHRpbWVvdXQgaWYgY29uZmlndXJlZFxuXHRcdFx0XHRsZXQgcmVzcG9uc2U6IFRyaWdnZXJSZXNwb25zZTtcblx0XHRcdFx0aWYgKGNvbmZpZy50aW1lb3V0ICYmIGNvbmZpZy50aW1lb3V0ID4gMCkge1xuXHRcdFx0XHRcdHJlc3BvbnNlID0gYXdhaXQgdGhpcy5leGVjdXRlV2l0aFRpbWVvdXQoY3R4LCBjb25maWcudGltZW91dCk7XG5cdFx0XHRcdH0gZWxzZSB7XG5cdFx0XHRcdFx0cmVzcG9uc2UgPSBhd2FpdCB0aGlzLnJ1bihjdHgpO1xuXHRcdFx0XHR9XG5cblx0XHRcdFx0Y29uc3QgZW5kID0gcGVyZm9ybWFuY2Uubm93KCk7XG5cblx0XHRcdFx0Ly8gU2V0IHNwYW4gYXR0cmlidXRlc1xuXHRcdFx0XHRzcGFuLnNldEF0dHJpYnV0ZShcInN1Y2Nlc3NcIiwgdHJ1ZSk7XG5cdFx0XHRcdHNwYW4uc2V0QXR0cmlidXRlKFwiam9iX2lkXCIsIGpvYklkKTtcblx0XHRcdFx0c3Bhbi5zZXRBdHRyaWJ1dGUoXCJxdWV1ZVwiLCBjb25maWcucXVldWUpO1xuXHRcdFx0XHRzcGFuLnNldEF0dHJpYnV0ZShcImF0dGVtcHRzXCIsIGpvYi5hdHRlbXB0cyk7XG5cdFx0XHRcdHNwYW4uc2V0QXR0cmlidXRlKFwiZWxhcHNlZF9tc1wiLCBlbmQgLSBzdGFydCk7XG5cdFx0XHRcdHNwYW4uc2V0U3RhdHVzKHsgY29kZTogU3BhblN0YXR1c0NvZGUuT0sgfSk7XG5cblx0XHRcdFx0Ly8gUmVjb3JkIG1ldHJpY3Ncblx0XHRcdFx0d29ya2VySm9icy5hZGQoMSwge1xuXHRcdFx0XHRcdGVudjogcHJvY2Vzcy5lbnYuTk9ERV9FTlYsXG5cdFx0XHRcdFx0cXVldWU6IGNvbmZpZy5xdWV1ZSxcblx0XHRcdFx0XHR3b3JrZmxvd19uYW1lOiB0aGlzLmNvbmZpZ3VyYXRpb24ubmFtZSxcblx0XHRcdFx0XHRzdWNjZXNzOiBcInRydWVcIixcblx0XHRcdFx0fSk7XG5cblx0XHRcdFx0Y3R4LmxvZ2dlci5sb2coYEpvYiBjb21wbGV0ZWQgaW4gJHsoZW5kIC0gc3RhcnQpLnRvRml4ZWQoMil9bXM6ICR7am9iSWR9YCk7XG5cblx0XHRcdFx0Ly8gTWFyayBqb2IgYXMgY29tcGxldGVkXG5cdFx0XHRcdGF3YWl0IGpvYi5jb21wbGV0ZSgpO1xuXHRcdFx0fSBjYXRjaCAoZXJyb3IpIHtcblx0XHRcdFx0Y29uc3QgZXJyb3JNZXNzYWdlID0gKGVycm9yIGFzIEVycm9yKS5tZXNzYWdlO1xuXHRcdFx0XHRjb25zdCBzaG91bGRSZXRyeSA9IGpvYi5hdHRlbXB0cyA8IGpvYi5tYXhSZXRyaWVzO1xuXG5cdFx0XHRcdC8vIFNldCBzcGFuIGVycm9yXG5cdFx0XHRcdHNwYW4uc2V0QXR0cmlidXRlKFwic3VjY2Vzc1wiLCBmYWxzZSk7XG5cdFx0XHRcdHNwYW4uc2V0QXR0cmlidXRlKFwid2lsbF9yZXRyeVwiLCBzaG91bGRSZXRyeSk7XG5cdFx0XHRcdHNwYW4ucmVjb3JkRXhjZXB0aW9uKGVycm9yIGFzIEVycm9yKTtcblx0XHRcdFx0c3Bhbi5zZXRTdGF0dXMoeyBjb2RlOiBTcGFuU3RhdHVzQ29kZS5FUlJPUiwgbWVzc2FnZTogZXJyb3JNZXNzYWdlIH0pO1xuXG5cdFx0XHRcdGlmIChzaG91bGRSZXRyeSkge1xuXHRcdFx0XHRcdC8vIFJldHJ5IHdpdGggZXhwb25lbnRpYWwgYmFja29mZlxuXHRcdFx0XHRcdGNvbnN0IGJhY2tvZmZNcyA9IHRoaXMuY2FsY3VsYXRlQmFja29mZihqb2IuYXR0ZW1wdHMsIGNvbmZpZy5kZWxheSk7XG5cdFx0XHRcdFx0d29ya2VyUmV0cmllcy5hZGQoMSwge1xuXHRcdFx0XHRcdFx0ZW52OiBwcm9jZXNzLmVudi5OT0RFX0VOVixcblx0XHRcdFx0XHRcdHF1ZXVlOiBjb25maWcucXVldWUsXG5cdFx0XHRcdFx0XHR3b3JrZmxvd19uYW1lOiB0aGlzLmNvbmZpZ3VyYXRpb24/Lm5hbWUgfHwgXCJ1bmtub3duXCIsXG5cdFx0XHRcdFx0XHRhdHRlbXB0OiBTdHJpbmcoam9iLmF0dGVtcHRzICsgMSksXG5cdFx0XHRcdFx0fSk7XG5cblx0XHRcdFx0XHR0aGlzLmxvZ2dlci5lcnJvcihcblx0XHRcdFx0XHRcdGBKb2IgJHtqb2JJZH0gZmFpbGVkIChhdHRlbXB0ICR7am9iLmF0dGVtcHRzICsgMX0vJHtqb2IubWF4UmV0cmllcyArIDF9KSwgcmV0cnlpbmcgaW4gJHtiYWNrb2ZmTXN9bXM6ICR7ZXJyb3JNZXNzYWdlfWAsXG5cdFx0XHRcdFx0KTtcblxuXHRcdFx0XHRcdGF3YWl0IGpvYi5mYWlsKGVycm9yIGFzIEVycm9yLCB0cnVlKTtcblx0XHRcdFx0fSBlbHNlIHtcblx0XHRcdFx0XHQvLyBNYXggcmV0cmllcyBleGhhdXN0ZWQgLSBzZW5kIHRvIERMUVxuXHRcdFx0XHRcdHdvcmtlckVycm9ycy5hZGQoMSwge1xuXHRcdFx0XHRcdFx0ZW52OiBwcm9jZXNzLmVudi5OT0RFX0VOVixcblx0XHRcdFx0XHRcdHF1ZXVlOiBjb25maWcucXVldWUsXG5cdFx0XHRcdFx0XHR3b3JrZmxvd19uYW1lOiB0aGlzLmNvbmZpZ3VyYXRpb24/Lm5hbWUgfHwgXCJ1bmtub3duXCIsXG5cdFx0XHRcdFx0fSk7XG5cblx0XHRcdFx0XHR0aGlzLmxvZ2dlci5lcnJvcihcblx0XHRcdFx0XHRcdGBKb2IgJHtqb2JJZH0gcGVybWFuZW50bHkgZmFpbGVkIGFmdGVyICR7am9iLmF0dGVtcHRzICsgMX0gYXR0ZW1wdHM6ICR7ZXJyb3JNZXNzYWdlfWAsXG5cdFx0XHRcdFx0XHQoZXJyb3IgYXMgRXJyb3IpLnN0YWNrLFxuXHRcdFx0XHRcdCk7XG5cblx0XHRcdFx0XHRhd2FpdCBqb2IuZmFpbChlcnJvciBhcyBFcnJvciwgZmFsc2UpO1xuXHRcdFx0XHR9XG5cdFx0XHR9IGZpbmFsbHkge1xuXHRcdFx0XHRzcGFuLmVuZCgpO1xuXHRcdFx0fVxuXHRcdH0pO1xuXHR9XG5cblx0LyoqXG5cdCAqIEV4ZWN1dGUgd29ya2Zsb3cgd2l0aCBhIHRpbWVvdXRcblx0ICovXG5cdHByb3RlY3RlZCBhc3luYyBleGVjdXRlV2l0aFRpbWVvdXQoY3R4OiBDb250ZXh0LCB0aW1lb3V0TXM6IG51bWJlcik6IFByb21pc2U8VHJpZ2dlclJlc3BvbnNlPiB7XG5cdFx0cmV0dXJuIG5ldyBQcm9taXNlPFRyaWdnZXJSZXNwb25zZT4oKHJlc29sdmUsIHJlamVjdCkgPT4ge1xuXHRcdFx0Y29uc3QgdGltZXIgPSBzZXRUaW1lb3V0KCgpID0+IHtcblx0XHRcdFx0cmVqZWN0KG5ldyBFcnJvcihgSm9iIHRpbWVkIG91dCBhZnRlciAke3RpbWVvdXRNc31tc2ApKTtcblx0XHRcdH0sIHRpbWVvdXRNcyk7XG5cblx0XHRcdHRoaXMucnVuKGN0eClcblx0XHRcdFx0LnRoZW4oKHJlc3VsdCkgPT4ge1xuXHRcdFx0XHRcdGNsZWFyVGltZW91dCh0aW1lcik7XG5cdFx0XHRcdFx0cmVzb2x2ZShyZXN1bHQpO1xuXHRcdFx0XHR9KVxuXHRcdFx0XHQuY2F0Y2goKGVycm9yKSA9PiB7XG5cdFx0XHRcdFx0Y2xlYXJUaW1lb3V0KHRpbWVyKTtcblx0XHRcdFx0XHRyZWplY3QoZXJyb3IpO1xuXHRcdFx0XHR9KTtcblx0XHR9KTtcblx0fVxuXG5cdC8qKlxuXHQgKiBDYWxjdWxhdGUgZXhwb25lbnRpYWwgYmFja29mZiBkZWxheVxuXHQgKiBGb3JtdWxhOiBtaW4oYmFzZURlbGF5ICogMl5hdHRlbXB0LCAzMDAwMCkgKyBqaXR0ZXJcblx0ICovXG5cdHByb3RlY3RlZCBjYWxjdWxhdGVCYWNrb2ZmKGF0dGVtcHQ6IG51bWJlciwgYmFzZURlbGF5PzogbnVtYmVyKTogbnVtYmVyIHtcblx0XHRjb25zdCBiYXNlID0gYmFzZURlbGF5ID8/IDEwMDA7XG5cdFx0Y29uc3QgbWF4RGVsYXkgPSAzMDAwMDsgLy8gMzAgc2Vjb25kcyBtYXhcblx0XHRjb25zdCBleHBvbmVudGlhbCA9IE1hdGgubWluKGJhc2UgKiBNYXRoLnBvdygyLCBhdHRlbXB0KSwgbWF4RGVsYXkpO1xuXHRcdGNvbnN0IGppdHRlciA9IE1hdGgucmFuZG9tKCkgKiBleHBvbmVudGlhbCAqIDAuMTsgLy8gMTAlIGppdHRlclxuXHRcdHJldHVybiBNYXRoLmZsb29yKGV4cG9uZW50aWFsICsgaml0dGVyKTtcblx0fVxufVxuXG5leHBvcnQgZGVmYXVsdCBXb3JrZXJUcmlnZ2VyO1xuIl19
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BullMQAdapter - Worker adapter using BullMQ (Redis-backed)
|
|
3
|
+
*
|
|
4
|
+
* Features:
|
|
5
|
+
* - Redis-backed persistent job queues
|
|
6
|
+
* - Configurable concurrency per queue
|
|
7
|
+
* - Job priority support
|
|
8
|
+
* - Delayed job scheduling
|
|
9
|
+
* - Automatic retries with configurable backoff
|
|
10
|
+
* - Queue statistics
|
|
11
|
+
*
|
|
12
|
+
* Requires: bullmq and ioredis as peer dependencies
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* const adapter = new BullMQAdapter({
|
|
17
|
+
* host: "localhost",
|
|
18
|
+
* port: 6379,
|
|
19
|
+
* });
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
import type { WorkerAdapter, WorkerJob, WorkerQueueStats } from "../WorkerTrigger";
|
|
23
|
+
import type { WorkerTriggerOpts } from "@blok/helper";
|
|
24
|
+
/**
|
|
25
|
+
* BullMQ adapter configuration
|
|
26
|
+
*/
|
|
27
|
+
export interface BullMQConfig {
|
|
28
|
+
/** Redis host (default: from REDIS_HOST env or "localhost") */
|
|
29
|
+
host: string;
|
|
30
|
+
/** Redis port (default: from REDIS_PORT env or 6379) */
|
|
31
|
+
port: number;
|
|
32
|
+
/** Redis password (default: from REDIS_PASSWORD env) */
|
|
33
|
+
password?: string;
|
|
34
|
+
/** Redis database (default: from REDIS_DB env or 0) */
|
|
35
|
+
db?: number;
|
|
36
|
+
/** Key prefix for all BullMQ keys */
|
|
37
|
+
prefix?: string;
|
|
38
|
+
/** Max stalled count before job fails */
|
|
39
|
+
maxStalledCount?: number;
|
|
40
|
+
/** Stalled interval in ms */
|
|
41
|
+
stalledInterval?: number;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* BullMQ Worker Adapter
|
|
45
|
+
*
|
|
46
|
+
* Uses BullMQ for robust, Redis-backed job processing with support for
|
|
47
|
+
* priority queues, delayed jobs, retries, and dead letter handling.
|
|
48
|
+
*/
|
|
49
|
+
export declare class BullMQAdapter implements WorkerAdapter {
|
|
50
|
+
readonly provider: "bullmq";
|
|
51
|
+
private connection;
|
|
52
|
+
private workers;
|
|
53
|
+
private queues;
|
|
54
|
+
private connected;
|
|
55
|
+
private readonly config;
|
|
56
|
+
constructor(config?: Partial<BullMQConfig>);
|
|
57
|
+
connect(): Promise<void>;
|
|
58
|
+
disconnect(): Promise<void>;
|
|
59
|
+
process(config: WorkerTriggerOpts, handler: (job: WorkerJob) => Promise<void>): Promise<void>;
|
|
60
|
+
addJob(queue: string, data: unknown, opts?: {
|
|
61
|
+
priority?: number;
|
|
62
|
+
delay?: number;
|
|
63
|
+
retries?: number;
|
|
64
|
+
timeout?: number;
|
|
65
|
+
jobId?: string;
|
|
66
|
+
}): Promise<string>;
|
|
67
|
+
stopProcessing(queue: string): Promise<void>;
|
|
68
|
+
isConnected(): boolean;
|
|
69
|
+
healthCheck(): Promise<boolean>;
|
|
70
|
+
getQueueStats(queue: string): Promise<WorkerQueueStats>;
|
|
71
|
+
}
|