@blokjs/trigger-worker 0.2.1 → 0.6.1
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/__tests__/integration/nats-adapter.real-nats.test.ts +116 -0
- package/__tests__/integration/pgboss-adapter.real-pg.test.ts +164 -0
- package/__tests__/integration/rabbitmq-adapter.real-rabbitmq.test.ts +179 -0
- package/__tests__/integration/sqs-adapter.real-sqs.test.ts +228 -0
- package/dist/WorkerTrigger.d.ts +40 -4
- package/dist/WorkerTrigger.js +272 -40
- package/dist/adapters/BullMQAdapter.d.ts +1 -1
- package/dist/adapters/BullMQAdapter.js +5 -42
- package/dist/adapters/InMemoryAdapter.d.ts +1 -1
- package/dist/adapters/InMemoryAdapter.js +13 -12
- package/dist/adapters/KafkaAdapter.d.ts +62 -0
- package/dist/adapters/KafkaAdapter.js +236 -0
- package/dist/adapters/NATSAdapter.d.ts +110 -0
- package/dist/adapters/NATSAdapter.js +394 -0
- package/dist/adapters/PgBossAdapter.d.ts +56 -0
- package/dist/adapters/PgBossAdapter.js +251 -0
- package/dist/adapters/RabbitMQAdapter.d.ts +51 -0
- package/dist/adapters/RabbitMQAdapter.js +241 -0
- package/dist/adapters/RedisStreamsAdapter.d.ts +64 -0
- package/dist/adapters/RedisStreamsAdapter.js +240 -0
- package/dist/adapters/SQSAdapter.d.ts +61 -0
- package/dist/adapters/SQSAdapter.js +269 -0
- package/dist/adapters/factory.d.ts +34 -0
- package/dist/adapters/factory.js +103 -0
- package/dist/index.d.ts +25 -7
- package/dist/index.js +31 -16
- package/package.json +27 -5
- package/src/WorkerTrigger.test.ts +44 -14
- package/src/WorkerTrigger.ts +299 -27
- package/src/adapters/InMemoryAdapter.ts +9 -5
- package/src/adapters/KafkaAdapter.ts +277 -0
- package/src/adapters/NATSAdapter.ts +454 -0
- package/src/adapters/PgBossAdapter.ts +293 -0
- package/src/adapters/RabbitMQAdapter.ts +285 -0
- package/src/adapters/RedisStreamsAdapter.ts +286 -0
- package/src/adapters/SQSAdapter.ts +306 -0
- package/src/adapters/factory.test.ts +89 -0
- package/src/adapters/factory.ts +111 -0
- package/src/adapters/new-adapters.test.ts +130 -0
- package/src/index.ts +31 -4
- package/template/.env.example +13 -0
- package/template/package.json +45 -0
- package/template/src/Nodes.ts +10 -0
- package/template/src/Workflows.ts +8 -0
- package/template/src/index.ts +41 -0
- package/template/src/runner/WorkerServer.ts +34 -0
- package/template/src/runner/types/Workflows.ts +7 -0
- package/template/src/workflows/jobs/process-job.ts +47 -0
- package/template/tsconfig.json +31 -0
- package/template/vitest.config.ts +39 -0
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NATSAdapter - NATS JetStream worker adapter for WorkerTrigger
|
|
3
|
+
*
|
|
4
|
+
* Uses NATS JetStream for persistent background job processing with:
|
|
5
|
+
* - Pull-based consumers with configurable concurrency
|
|
6
|
+
* - Server-side retry config (max_deliver)
|
|
7
|
+
* - Ack wait for job timeouts
|
|
8
|
+
* - Priority via message headers
|
|
9
|
+
* - Delayed job scheduling
|
|
10
|
+
* - Queue statistics via consumer info
|
|
11
|
+
*
|
|
12
|
+
* Requires: npm install nats
|
|
13
|
+
*
|
|
14
|
+
* Environment variables:
|
|
15
|
+
* - NATS_SERVERS: Comma-separated NATS server URLs (default: localhost:4222)
|
|
16
|
+
* - NATS_TOKEN: Authentication token (optional)
|
|
17
|
+
* - NATS_USER: Username for auth (optional)
|
|
18
|
+
* - NATS_PASS: Password for auth (optional)
|
|
19
|
+
* - NATS_STREAM_NAME: JetStream stream name (default: blok-worker)
|
|
20
|
+
*/
|
|
21
|
+
import { v4 as uuid } from "uuid";
|
|
22
|
+
/**
|
|
23
|
+
* Tier 2 polish — compute the consumer-side hold time for a NATS message
|
|
24
|
+
* with an `x-delay` header. NATS JetStream stores `x-delay` as opaque
|
|
25
|
+
* metadata; the broker does NOT defer delivery on it. The consumer is
|
|
26
|
+
* responsible for honouring the delay between the message's first-publish
|
|
27
|
+
* timestamp and `createdMs + delay`.
|
|
28
|
+
*
|
|
29
|
+
* Returns the milliseconds to wait. Clamps to >= 0; returns 0 when the
|
|
30
|
+
* delay has already elapsed (the message was queued for longer than the
|
|
31
|
+
* delay) or when no delay was set.
|
|
32
|
+
*
|
|
33
|
+
* Exported for unit testability — the consumer message handler in
|
|
34
|
+
* `NATSWorkerAdapter.process()` mocks the NATS client extensively, so
|
|
35
|
+
* isolating the math here keeps the surface easy to verify.
|
|
36
|
+
*/
|
|
37
|
+
export function computeXDelayHoldMs(delay, createdMs, nowMs) {
|
|
38
|
+
if (!delay || delay <= 0)
|
|
39
|
+
return 0;
|
|
40
|
+
const dispatchAt = createdMs + delay;
|
|
41
|
+
return Math.max(0, dispatchAt - nowMs);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* NATSWorkerAdapter - NATS JetStream implementation of WorkerAdapter
|
|
45
|
+
*/
|
|
46
|
+
export class NATSWorkerAdapter {
|
|
47
|
+
provider = "nats";
|
|
48
|
+
// biome-ignore lint/suspicious/noExplicitAny: NATS types are dynamically imported (optional peer dependency)
|
|
49
|
+
nc = null;
|
|
50
|
+
// biome-ignore lint/suspicious/noExplicitAny: NATS types are dynamically imported
|
|
51
|
+
js = null;
|
|
52
|
+
// biome-ignore lint/suspicious/noExplicitAny: NATS types are dynamically imported
|
|
53
|
+
jsm = null;
|
|
54
|
+
connected = false;
|
|
55
|
+
config;
|
|
56
|
+
// biome-ignore lint/suspicious/noExplicitAny: NATS consumer instances
|
|
57
|
+
consumers = new Map();
|
|
58
|
+
// biome-ignore lint/suspicious/noExplicitAny: NATS consume iterators
|
|
59
|
+
consumeIterators = new Map();
|
|
60
|
+
constructor(config) {
|
|
61
|
+
this.config = {
|
|
62
|
+
servers: config?.servers || (process.env.NATS_SERVERS || "localhost:4222").split(","),
|
|
63
|
+
token: config?.token || process.env.NATS_TOKEN,
|
|
64
|
+
user: config?.user || process.env.NATS_USER,
|
|
65
|
+
pass: config?.pass || process.env.NATS_PASS,
|
|
66
|
+
streamName: config?.streamName || process.env.NATS_STREAM_NAME || "blok-worker",
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Connect to NATS and initialize JetStream
|
|
71
|
+
*/
|
|
72
|
+
async connect() {
|
|
73
|
+
if (this.connected)
|
|
74
|
+
return;
|
|
75
|
+
try {
|
|
76
|
+
const nats = await import("nats");
|
|
77
|
+
const connectOpts = {
|
|
78
|
+
servers: this.config.servers,
|
|
79
|
+
};
|
|
80
|
+
if (this.config.token)
|
|
81
|
+
connectOpts.token = this.config.token;
|
|
82
|
+
if (this.config.user)
|
|
83
|
+
connectOpts.user = this.config.user;
|
|
84
|
+
if (this.config.pass)
|
|
85
|
+
connectOpts.pass = this.config.pass;
|
|
86
|
+
this.nc = await nats.connect(connectOpts);
|
|
87
|
+
this.js = this.nc.jetstream();
|
|
88
|
+
this.jsm = await this.nc.jetstreamManager();
|
|
89
|
+
this.connected = true;
|
|
90
|
+
console.log(`[NATSWorkerAdapter] Connected to NATS: ${this.config.servers.join(", ")}`);
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
throw new Error(`Failed to connect to NATS: ${error.message}. Make sure nats is installed: npm install nats`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Disconnect from NATS
|
|
98
|
+
*/
|
|
99
|
+
async disconnect() {
|
|
100
|
+
if (!this.connected)
|
|
101
|
+
return;
|
|
102
|
+
try {
|
|
103
|
+
// Stop all consume iterators
|
|
104
|
+
for (const [, iter] of this.consumeIterators) {
|
|
105
|
+
try {
|
|
106
|
+
iter.stop();
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
// Iterator may already be stopped
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
this.consumeIterators.clear();
|
|
113
|
+
this.consumers.clear();
|
|
114
|
+
await this.nc.drain();
|
|
115
|
+
this.connected = false;
|
|
116
|
+
console.log("[NATSWorkerAdapter] Disconnected from NATS");
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
console.error(`[NATSWorkerAdapter] Disconnect error: ${error.message}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Start processing jobs from a queue
|
|
124
|
+
*/
|
|
125
|
+
async process(config, handler) {
|
|
126
|
+
if (!this.connected) {
|
|
127
|
+
throw new Error("Not connected. Call connect() first.");
|
|
128
|
+
}
|
|
129
|
+
const nats = await import("nats");
|
|
130
|
+
const queue = config.queue;
|
|
131
|
+
const streamName = this.config.streamName || "blok-worker";
|
|
132
|
+
const subject = `worker.${queue}`;
|
|
133
|
+
const durableName = `blok-worker-${queue}`;
|
|
134
|
+
// Ensure stream exists with worker subjects
|
|
135
|
+
await this.ensureStream(streamName, [subject]);
|
|
136
|
+
// Create or update durable pull consumer with worker semantics
|
|
137
|
+
const ackWaitNs = ((config.timeout ?? 30000) + 5000) * 1_000_000; // timeout + 5s buffer, in nanoseconds
|
|
138
|
+
await this.jsm.consumers.add(streamName, {
|
|
139
|
+
durable_name: durableName,
|
|
140
|
+
ack_policy: nats.AckPolicy.Explicit,
|
|
141
|
+
max_deliver: (config.retries ?? 3) + 1, // +1 because first attempt counts
|
|
142
|
+
ack_wait: ackWaitNs,
|
|
143
|
+
filter_subjects: [subject],
|
|
144
|
+
});
|
|
145
|
+
// Get consumer handle
|
|
146
|
+
const consumer = await this.js.consumers.get(streamName, durableName);
|
|
147
|
+
this.consumers.set(queue, consumer);
|
|
148
|
+
// Start consuming
|
|
149
|
+
const iter = await consumer.consume();
|
|
150
|
+
this.consumeIterators.set(queue, iter);
|
|
151
|
+
// Process jobs in background
|
|
152
|
+
(async () => {
|
|
153
|
+
const semaphore = new Semaphore(config.concurrency ?? 1);
|
|
154
|
+
for await (const msg of iter) {
|
|
155
|
+
await semaphore.acquire();
|
|
156
|
+
// Process each job concurrently up to concurrency limit
|
|
157
|
+
(async () => {
|
|
158
|
+
try {
|
|
159
|
+
// Parse job data
|
|
160
|
+
let data;
|
|
161
|
+
try {
|
|
162
|
+
const codec = nats.JSONCodec();
|
|
163
|
+
data = codec.decode(msg.data);
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
try {
|
|
167
|
+
const sc = nats.StringCodec();
|
|
168
|
+
data = JSON.parse(sc.decode(msg.data));
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
data = msg.data;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
// Extract headers
|
|
175
|
+
const headers = {};
|
|
176
|
+
if (msg.headers) {
|
|
177
|
+
for (const [key, values] of msg.headers) {
|
|
178
|
+
headers[key] = Array.isArray(values) ? values[0] : values;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
// Extract job metadata from headers
|
|
182
|
+
const jobId = headers["x-job-id"] || msg.headers?.get("Nats-Msg-Id") || uuid();
|
|
183
|
+
const priority = Number.parseInt(headers["x-priority"] || "0", 10);
|
|
184
|
+
const delay = Number.parseInt(headers["x-delay"] || "0", 10);
|
|
185
|
+
const timeout = Number.parseInt(headers["x-timeout"] || "0", 10);
|
|
186
|
+
// Get redelivery count (attempts)
|
|
187
|
+
const info = msg.info;
|
|
188
|
+
const attempts = info.redeliveryCount ?? 0;
|
|
189
|
+
const maxRetries = config.retries ?? 3;
|
|
190
|
+
// Create WorkerJob
|
|
191
|
+
const workerJob = {
|
|
192
|
+
id: jobId,
|
|
193
|
+
data,
|
|
194
|
+
headers,
|
|
195
|
+
queue,
|
|
196
|
+
priority,
|
|
197
|
+
attempts,
|
|
198
|
+
maxRetries,
|
|
199
|
+
createdAt: new Date(info.timestampNanos ? Math.floor(Number(info.timestampNanos) / 1_000_000) : Date.now()),
|
|
200
|
+
delay: delay || undefined,
|
|
201
|
+
timeout: timeout || config.timeout || undefined,
|
|
202
|
+
raw: msg,
|
|
203
|
+
complete: async () => {
|
|
204
|
+
msg.ack();
|
|
205
|
+
},
|
|
206
|
+
fail: async (error, requeue) => {
|
|
207
|
+
if (requeue) {
|
|
208
|
+
// nak() tells the server to redeliver
|
|
209
|
+
msg.nak();
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
// term() terminates delivery — no more retries
|
|
213
|
+
msg.term();
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
};
|
|
217
|
+
// Tier 2 polish — enforce `x-delay` header on the consumer side.
|
|
218
|
+
// NATS JetStream stores `x-delay` as opaque metadata; the broker
|
|
219
|
+
// does NOT defer delivery on it. We implement consumer-side
|
|
220
|
+
// holding here. createdMs is the message's first-publish timestamp;
|
|
221
|
+
// hold until createdMs + delay. Single-process semantics — for
|
|
222
|
+
// long deferrals, prefer trigger-level `delay` (DeferredRunScheduler).
|
|
223
|
+
const createdMs = info.timestampNanos ? Math.floor(Number(info.timestampNanos) / 1_000_000) : Date.now();
|
|
224
|
+
const waitMs = computeXDelayHoldMs(delay, createdMs, Date.now());
|
|
225
|
+
if (waitMs > 0) {
|
|
226
|
+
await new Promise((resolve) => setTimeout(resolve, waitMs));
|
|
227
|
+
}
|
|
228
|
+
await handler(workerJob);
|
|
229
|
+
}
|
|
230
|
+
catch (error) {
|
|
231
|
+
console.error(`[NATSWorkerAdapter] Error processing job from ${queue}: ${error.message}`);
|
|
232
|
+
try {
|
|
233
|
+
msg.nak();
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
// Already acked/nacked
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
finally {
|
|
240
|
+
semaphore.release();
|
|
241
|
+
}
|
|
242
|
+
})();
|
|
243
|
+
}
|
|
244
|
+
})();
|
|
245
|
+
console.log(`[NATSWorkerAdapter] Processing queue: ${queue} (concurrency=${config.concurrency ?? 1}, retries=${config.retries ?? 3})`);
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Add a job to a worker queue
|
|
249
|
+
*/
|
|
250
|
+
async addJob(queue, data, opts) {
|
|
251
|
+
if (!this.connected) {
|
|
252
|
+
throw new Error("Not connected. Call connect() first.");
|
|
253
|
+
}
|
|
254
|
+
const nats = await import("nats");
|
|
255
|
+
const subject = `worker.${queue}`;
|
|
256
|
+
const streamName = this.config.streamName || "blok-worker";
|
|
257
|
+
// Ensure stream has this subject
|
|
258
|
+
await this.ensureStream(streamName, [subject]);
|
|
259
|
+
// Build headers with job metadata
|
|
260
|
+
const hdrs = nats.headers();
|
|
261
|
+
const jobId = opts?.jobId || uuid();
|
|
262
|
+
hdrs.set("x-job-id", jobId);
|
|
263
|
+
hdrs.set("Nats-Msg-Id", jobId); // Deduplication
|
|
264
|
+
if (opts?.priority)
|
|
265
|
+
hdrs.set("x-priority", String(opts.priority));
|
|
266
|
+
if (opts?.delay)
|
|
267
|
+
hdrs.set("x-delay", String(opts.delay));
|
|
268
|
+
if (opts?.timeout)
|
|
269
|
+
hdrs.set("x-timeout", String(opts.timeout));
|
|
270
|
+
// Encode and publish
|
|
271
|
+
const codec = nats.JSONCodec();
|
|
272
|
+
await this.js.publish(subject, codec.encode(data), { headers: hdrs });
|
|
273
|
+
return jobId;
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Stop processing a specific queue
|
|
277
|
+
*/
|
|
278
|
+
async stopProcessing(queue) {
|
|
279
|
+
const iter = this.consumeIterators.get(queue);
|
|
280
|
+
if (iter) {
|
|
281
|
+
try {
|
|
282
|
+
iter.stop();
|
|
283
|
+
}
|
|
284
|
+
catch {
|
|
285
|
+
// Already stopped
|
|
286
|
+
}
|
|
287
|
+
this.consumeIterators.delete(queue);
|
|
288
|
+
}
|
|
289
|
+
this.consumers.delete(queue);
|
|
290
|
+
console.log(`[NATSWorkerAdapter] Stopped processing queue: ${queue}`);
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Check if connected
|
|
294
|
+
*/
|
|
295
|
+
isConnected() {
|
|
296
|
+
return this.connected;
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Health check
|
|
300
|
+
*/
|
|
301
|
+
async healthCheck() {
|
|
302
|
+
if (!this.connected || !this.nc)
|
|
303
|
+
return false;
|
|
304
|
+
try {
|
|
305
|
+
const info = this.nc.info;
|
|
306
|
+
return info !== undefined;
|
|
307
|
+
}
|
|
308
|
+
catch {
|
|
309
|
+
return false;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Get queue statistics from JetStream consumer info
|
|
314
|
+
*/
|
|
315
|
+
async getQueueStats(queue) {
|
|
316
|
+
if (!this.connected) {
|
|
317
|
+
return { waiting: 0, active: 0, completed: 0, failed: 0, delayed: 0 };
|
|
318
|
+
}
|
|
319
|
+
try {
|
|
320
|
+
const streamName = this.config.streamName || "blok-worker";
|
|
321
|
+
const durableName = `blok-worker-${queue}`;
|
|
322
|
+
const info = await this.jsm.consumers.info(streamName, durableName);
|
|
323
|
+
return {
|
|
324
|
+
waiting: info.num_pending ?? 0,
|
|
325
|
+
active: info.num_ack_pending ?? 0,
|
|
326
|
+
completed: info.delivered?.consumer_seq ?? 0,
|
|
327
|
+
failed: info.num_redelivered ?? 0,
|
|
328
|
+
delayed: 0, // NATS doesn't have a native delayed count
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
catch {
|
|
332
|
+
return { waiting: 0, active: 0, completed: 0, failed: 0, delayed: 0 };
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Ensure a JetStream stream exists with the given subjects
|
|
337
|
+
*/
|
|
338
|
+
async ensureStream(name, subjects) {
|
|
339
|
+
try {
|
|
340
|
+
const info = await this.jsm.streams.info(name);
|
|
341
|
+
// Merge new subjects with existing
|
|
342
|
+
const existingSubjects = info.config.subjects || [];
|
|
343
|
+
const allSubjects = [...new Set([...existingSubjects, ...subjects])];
|
|
344
|
+
if (allSubjects.length !== existingSubjects.length) {
|
|
345
|
+
await this.jsm.streams.update(name, {
|
|
346
|
+
...info.config,
|
|
347
|
+
subjects: allSubjects,
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
catch {
|
|
352
|
+
// Stream doesn't exist, create it
|
|
353
|
+
await this.jsm.streams.add({
|
|
354
|
+
name,
|
|
355
|
+
subjects,
|
|
356
|
+
// biome-ignore lint/suspicious/noExplicitAny: nats JetStream retention policy enum
|
|
357
|
+
retention: "workqueue",
|
|
358
|
+
max_deliver: 4, // default: 3 retries + 1 initial attempt
|
|
359
|
+
// biome-ignore lint/suspicious/noExplicitAny: nats JetStream storage type enum
|
|
360
|
+
storage: "file",
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Simple semaphore for concurrency control
|
|
367
|
+
*/
|
|
368
|
+
class Semaphore {
|
|
369
|
+
permits;
|
|
370
|
+
waiting = [];
|
|
371
|
+
constructor(permits) {
|
|
372
|
+
this.permits = permits;
|
|
373
|
+
}
|
|
374
|
+
async acquire() {
|
|
375
|
+
if (this.permits > 0) {
|
|
376
|
+
this.permits--;
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
return new Promise((resolve) => {
|
|
380
|
+
this.waiting.push(resolve);
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
release() {
|
|
384
|
+
const next = this.waiting.shift();
|
|
385
|
+
if (next) {
|
|
386
|
+
next();
|
|
387
|
+
}
|
|
388
|
+
else {
|
|
389
|
+
this.permits++;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
export default NATSWorkerAdapter;
|
|
394
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiTkFUU0FkYXB0ZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvYWRhcHRlcnMvTkFUU0FkYXB0ZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0FtQkc7QUFHSCxPQUFPLEVBQUUsRUFBRSxJQUFJLElBQUksRUFBRSxNQUFNLE1BQU0sQ0FBQztBQUdsQzs7Ozs7Ozs7Ozs7Ozs7R0FjRztBQUNILE1BQU0sVUFBVSxtQkFBbUIsQ0FBQyxLQUFhLEVBQUUsU0FBaUIsRUFBRSxLQUFhO0lBQ2xGLElBQUksQ0FBQyxLQUFLLElBQUksS0FBSyxJQUFJLENBQUM7UUFBRSxPQUFPLENBQUMsQ0FBQztJQUNuQyxNQUFNLFVBQVUsR0FBRyxTQUFTLEdBQUcsS0FBSyxDQUFDO0lBQ3JDLE9BQU8sSUFBSSxDQUFDLEdBQUcsQ0FBQyxDQUFDLEVBQUUsVUFBVSxHQUFHLEtBQUssQ0FBQyxDQUFDO0FBQ3hDLENBQUM7QUFrQkQ7O0dBRUc7QUFDSCxNQUFNLE9BQU8saUJBQWlCO0lBQ3BCLFFBQVEsR0FBRyxNQUFlLENBQUM7SUFFcEMsNkdBQTZHO0lBQ3JHLEVBQUUsR0FBUSxJQUFJLENBQUM7SUFDdkIsa0ZBQWtGO0lBQzFFLEVBQUUsR0FBUSxJQUFJLENBQUM7SUFDdkIsa0ZBQWtGO0lBQzFFLEdBQUcsR0FBUSxJQUFJLENBQUM7SUFDaEIsU0FBUyxHQUFHLEtBQUssQ0FBQztJQUNsQixNQUFNLENBQW1CO0lBQ2pDLHNFQUFzRTtJQUM5RCxTQUFTLEdBQXFCLElBQUksR0FBRyxFQUFFLENBQUM7SUFDaEQscUVBQXFFO0lBQzdELGdCQUFnQixHQUFxQixJQUFJLEdBQUcsRUFBRSxDQUFDO0lBRXZELFlBQVksTUFBa0M7UUFDN0MsSUFBSSxDQUFDLE1BQU0sR0FBRztZQUNiLE9BQU8sRUFBRSxNQUFNLEVBQUUsT0FBTyxJQUFJLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxZQUFZLElBQUksZ0JBQWdC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LENBQU8sQ0FBQyxPQUFPLEVBQUUsRUFBRTtZQUNwQyxJQUFJLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsQ0FBQztRQUM1QixDQUFDLENBQUMsQ0FBQztJQUNKLENBQUM7SUFFRCxPQUFPO1FBQ04sTUFBTSxJQUFJLEdBQUcsSUFBSSxDQUFDLE9BQU8sQ0FBQyxLQUFLLEVBQUUsQ0FBQztRQUNsQyxJQUFJLElBQUksRUFBRSxDQUFDO1lBQ1YsSUFBSSxFQUFFLENBQUM7UUFDUixDQUFDO2FBQU0sQ0FBQztZQUNQLElBQUksQ0FBQyxPQUFPLEVBQUUsQ0FBQztRQUNoQixDQUFDO0lBQ0YsQ0FBQztDQUNEO0FBRUQsZUFBZSxpQkFBaUIsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogTkFUU0FkYXB0ZXIgLSBOQVRTIEpldFN0cmVhbSB3b3JrZXIgYWRhcHRlciBmb3IgV29ya2VyVHJpZ2dlclxuICpcbiAqIFVzZXMgTkFUUyBKZXRTdHJlYW0gZm9yIHBlcnNpc3RlbnQgYmFja2dyb3VuZCBqb2IgcHJvY2Vzc2luZyB3aXRoOlxuICogLSBQdWxsLWJhc2VkIGNvbnN1bWVycyB3aXRoIGNvbmZpZ3VyYWJsZSBjb25jdXJyZW5jeVxuICogLSBTZXJ2ZXItc2lkZSByZXRyeSBjb25maWcgKG1heF9kZWxpdmVyKVxuICogLSBBY2sgd2FpdCBmb3Igam9iIHRpbWVvdXRzXG4gKiAtIFByaW9yaXR5IHZpYSBtZXNzYWdlIGhlYWRlcnNcbiAqIC0gRGVsYXllZCBqb2Igc2NoZWR1bGluZ1xuICogLSBRdWV1ZSBzdGF0aXN0aWNzIHZpYSBjb25zdW1lciBpbmZvXG4gKlxuICogUmVxdWlyZXM6IG5wbSBpbnN0YWxsIG5hdHNcbiAqXG4gKiBFbnZpcm9ubWVudCB2YXJpYWJsZXM6XG4gKiAtIE5BVFNfU0VSVkVSUzogQ29tbWEtc2VwYXJhdGVkIE5BVFMgc2VydmVyIFVSTHMgKGRlZmF1bHQ6IGxvY2FsaG9zdDo0MjIyKVxuICogLSBOQVRTX1RPS0VOOiBBdXRoZW50aWNhdGlvbiB0b2tlbiAob3B0aW9uYWwpXG4gKiAtIE5BVFNfVVNFUjogVXNlcm5hbWUgZm9yIGF1dGggKG9wdGlvbmFsKVxuICogLSBOQVRTX1BBU1M6IFBhc3N3b3JkIGZvciBhdXRoIChvcHRpb25hbClcbiAqIC0gTkFUU19TVFJFQU1fTkFNRTogSmV0U3RyZWFtIHN0cmVhbSBuYW1lIChkZWZhdWx0OiBibG9rLXdvcmtlcilcbiAqL1xuXG5pbXBvcnQgdHlwZSB7IFdvcmtlclRyaWdnZXJPcHRzIH0gZnJvbSBcIkBibG9ranMvaGVscGVyXCI7XG5pbXBvcnQgeyB2NCBhcyB1dWlkIH0gZnJvbSBcInV1aWRcIjtcbmltcG9ydCB0eXBlIHsgV29ya2VyQWRhcHRlciwgV29ya2VySm9iLCBXb3JrZXJRdWV1ZVN0YXRzIH0gZnJvbSBcIi4uL1dvcmtlclRyaWdnZXJcIjtcblxuLyoqXG4gKiBUaWVyIDIgcG9saXNoIOKAlCBjb21wdXRlIHRoZSBjb25zdW1lci1zaWRlIGhvbGQgdGltZSBmb3IgYSBOQVRTIG1lc3NhZ2VcbiAqIHdpdGggYW4gYHgtZGVsYXlgIGhlYWRlci4gTkFUUyBKZXRTdHJlYW0gc3RvcmVzIGB4LWRlbGF5YCBhcyBvcGFxdWVcbiAqIG1ldGFkYXRhOyB0aGUgYnJva2VyIGRvZXMgTk9UIGRlZmVyIGRlbGl2ZXJ5IG9uIGl0LiBUaGUgY29uc3VtZXIgaXNcbiAqIHJlc3BvbnNpYmxlIGZvciBob25vdXJpbmcgdGhlIGRlbGF5IGJldHdlZW4gdGhlIG1lc3NhZ2UncyBmaXJzdC1wdWJsaXNoXG4gKiB0aW1lc3RhbXAgYW5kIGBjcmVhdGVkTXMgKyBkZWxheWAuXG4gKlxuICogUmV0dXJucyB0aGUgbWlsbGlzZWNvbmRzIHRvIHdhaXQuIENsYW1wcyB0byA+PSAwOyByZXR1cm5zIDAgd2hlbiB0aGVcbiAqIGRlbGF5IGhhcyBhbHJlYWR5IGVsYXBzZWQgKHRoZSBtZXNzYWdlIHdhcyBxdWV1ZWQgZm9yIGxvbmdlciB0aGFuIHRoZVxuICogZGVsYXkpIG9yIHdoZW4gbm8gZGVsYXkgd2FzIHNldC5cbiAqXG4gKiBFeHBvcnRlZCBmb3IgdW5pdCB0ZXN0YWJpbGl0eSDigJQgdGhlIGNvbnN1bWVyIG1lc3NhZ2UgaGFuZGxlciBpblxuICogYE5BVFNXb3JrZXJBZGFwdGVyLnByb2Nlc3MoKWAgbW9ja3MgdGhlIE5BVFMgY2xpZW50IGV4dGVuc2l2ZWx5LCBzb1xuICogaXNvbGF0aW5nIHRoZSBtYXRoIGhlcmUga2VlcHMgdGhlIHN1cmZhY2UgZWFzeSB0byB2ZXJpZnkuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBjb21wdXRlWERlbGF5SG9sZE1zKGRlbGF5OiBudW1iZXIsIGNyZWF0ZWRNczogbnVtYmVyLCBub3dNczogbnVtYmVyKTogbnVtYmVyIHtcblx0aWYgKCFkZWxheSB8fCBkZWxheSA8PSAwKSByZXR1cm4gMDtcblx0Y29uc3QgZGlzcGF0Y2hBdCA9IGNyZWF0ZWRNcyArIGRlbGF5O1xuXHRyZXR1cm4gTWF0aC5tYXgoMCwgZGlzcGF0Y2hBdCAtIG5vd01zKTtcbn1cblxuLyoqXG4gKiBOQVRTIHdvcmtlciBhZGFwdGVyIGNvbmZpZ3VyYXRpb25cbiAqL1xuZXhwb3J0IGludGVyZmFjZSBOQVRTV29ya2VyQ29uZmlnIHtcblx0LyoqIE5BVFMgc2VydmVyIFVSTHMgKi9cblx0c2VydmVyczogc3RyaW5nW107XG5cdC8qKiBBdXRoZW50aWNhdGlvbiB0b2tlbiAqL1xuXHR0b2tlbj86IHN0cmluZztcblx0LyoqIFVzZXJuYW1lICovXG5cdHVzZXI/OiBzdHJpbmc7XG5cdC8qKiBQYXNzd29yZCAqL1xuXHRwYXNzPzogc3RyaW5nO1xuXHQvKiogSmV0U3RyZWFtIHN0cmVhbSBuYW1lIChkZWZhdWx0OiBcImJsb2std29ya2VyXCIpICovXG5cdHN0cmVhbU5hbWU/OiBzdHJpbmc7XG59XG5cbi8qKlxuICogTkFUU1dvcmtlckFkYXB0ZXIgLSBOQVRTIEpldFN0cmVhbSBpbXBsZW1lbnRhdGlvbiBvZiBXb3JrZXJBZGFwdGVyXG4gKi9cbmV4cG9ydCBjbGFzcyBOQVRTV29ya2VyQWRhcHRlciBpbXBsZW1lbnRzIFdvcmtlckFkYXB0ZXIge1xuXHRyZWFkb25seSBwcm92aWRlciA9IFwibmF0c1wiIGFzIGNvbnN0O1xuXG5cdC8vIGJpb21lLWlnbm9yZSBsaW50L3N1c3BpY2lvdXMvbm9FeHBsaWNpdEFueTogTkFUUyB0eXBlcyBhcmUgZHluYW1pY2FsbHkgaW1wb3J0ZWQgKG9wdGlvbmFsIHBlZXIgZGVwZW5kZW5jeSlcblx0cHJpdmF0ZSBuYzogYW55ID0gbnVsbDtcblx0Ly8gYmlvbWUtaWdub3JlIGxpbnQvc3VzcGljaW91cy9ub0V4cGxpY2l0QW55OiBOQVRTIHR5cGVzIGFyZSBkeW5hbWljYWxseSBpbXBvcnRlZFxuXHRwcml2YXRlIGpzOiBhbnkgPSBudWxsO1xuXHQvLyBiaW9tZS1pZ25vcmUgbGludC9zdXNwaWNpb3VzL25vRXhwbGljaXRBbnk6IE5BVFMgdHlwZXMgYXJlIGR5bmFtaWNhbGx5IGltcG9ydGVkXG5cdHByaXZhdGUganNtOiBhbnkgPSBudWxsO1xuXHRwcml2YXRlIGNvbm5lY3RlZCA9IGZhbHNlO1xuXHRwcml2YXRlIGNvbmZpZzogTkFUU1dvcmtlckNvbmZpZztcblx0Ly8gYmlvbWUtaWdub3JlIGxpbnQvc3VzcGljaW91cy9ub0V4cGxpY2l0QW55OiBOQVRTIGNvbnN1bWVyIGluc3RhbmNlc1xuXHRwcml2YXRlIGNvbnN1bWVyczogTWFwPHN0cmluZywgYW55PiA9IG5ldyBNYXAoKTtcblx0Ly8gYmlvbWUtaWdub3JlIGxpbnQvc3VzcGljaW91cy9ub0V4cGxpY2l0QW55OiBOQVRTIGNvbnN1bWUgaXRlcmF0b3JzXG5cdHByaXZhdGUgY29uc3VtZUl0ZXJhdG9yczogTWFwPHN0cmluZywgYW55PiA9IG5ldyBNYXAoKTtcblxuXHRjb25zdHJ1Y3Rvcihjb25maWc/OiBQYXJ0aWFsPE5BVFNXb3JrZXJDb25maWc+KSB7XG5cdFx0dGhpcy5jb25maWcgPSB7XG5cdFx0XHRzZXJ2ZXJzOiBjb25maWc/LnNlcnZlcnMgfHwgKHByb2Nlc3MuZW52Lk5BVFNfU0VSVkVSUyB8fCBcImxvY2FsaG9zdDo0MjIyXCIpLnNwbGl0KFwiLFwiKSxcblx0XHRcdHRva2VuOiBjb25maWc/LnRva2VuIHx8IHByb2Nlc3MuZW52Lk5BVFNfVE9LRU4sXG5cdFx0XHR1c2VyOiBjb25maWc/LnVzZXIgfHwgcHJvY2Vzcy5lbnYuTkFUU19VU0VSLFxuXHRcdFx0cGFzczogY29uZmlnPy5wYXNzIHx8IHByb2Nlc3MuZW52Lk5BVFNfUEFTUyxcblx0XHRcdHN0cmVhbU5hbWU6IGNvbmZpZz8uc3RyZWFtTmFtZSB8fCBwcm9jZXNzLmVudi5OQVRTX1NUUkVBTV9OQU1FIHx8IFwiYmxvay13b3JrZXJcIixcblx0XHR9O1xuXHR9XG5cblx0LyoqXG5cdCAqIENvbm5lY3QgdG8gTkFUUyBhbmQgaW5pdGlhbGl6ZSBKZXRTdHJlYW1cblx0ICovXG5cdGFzeW5jIGNvbm5lY3QoKTogUHJvbWlzZTx2b2lkPiB7XG5cdFx0aWYgKHRoaXMuY29ubmVjdGVkKSByZXR1cm47XG5cblx0XHR0cnkge1xuXHRcdFx0Y29uc3QgbmF0cyA9IGF3YWl0IGltcG9ydChcIm5hdHNcIik7XG5cblx0XHRcdGNvbnN0IGNvbm5lY3RPcHRzOiBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPiA9IHtcblx0XHRcdFx0c2VydmVyczogdGhpcy5jb25maWcuc2VydmVycyxcblx0XHRcdH07XG5cblx0XHRcdGlmICh0aGlzLmNvbmZpZy50b2tlbikgY29ubmVjdE9wdHMudG9rZW4gPSB0aGlzLmNvbmZpZy50b2tlbjtcblx0XHRcdGlmICh0aGlzLmNvbmZpZy51c2VyKSBjb25uZWN0T3B0cy51c2VyID0gdGhpcy5jb25maWcudXNlcjtcblx0XHRcdGlmICh0aGlzLmNvbmZpZy5wYXNzKSBjb25uZWN0T3B0cy5wYXNzID0gdGhpcy5jb25maWcucGFzcztcblxuXHRcdFx0dGhpcy5uYyA9IGF3YWl0IG5hdHMuY29ubmVjdChjb25uZWN0T3B0cyk7XG5cdFx0XHR0aGlzLmpzID0gdGhpcy5uYy5qZXRzdHJlYW0oKTtcblx0XHRcdHRoaXMuanNtID0gYXdhaXQgdGhpcy5uYy5qZXRzdHJlYW1NYW5hZ2VyKCk7XG5cblx0XHRcdHRoaXMuY29ubmVjdGVkID0gdHJ1ZTtcblx0XHRcdGNvbnNvbGUubG9nKGBbTkFUU1dvcmtlckFkYXB0ZXJdIENvbm5lY3RlZCB0byBOQVRTOiAke3RoaXMuY29uZmlnLnNlcnZlcnMuam9pbihcIiwgXCIpfWApO1xuXHRcdH0gY2F0Y2ggKGVycm9yKSB7XG5cdFx0XHR0aHJvdyBuZXcgRXJyb3IoXG5cdFx0XHRcdGBGYWlsZWQgdG8gY29ubmVjdCB0byBOQVRTOiAkeyhlcnJvciBhcyBFcnJvcikubWVzc2FnZX0uIE1ha2Ugc3VyZSBuYXRzIGlzIGluc3RhbGxlZDogbnBtIGluc3RhbGwgbmF0c2AsXG5cdFx0XHQpO1xuXHRcdH1cblx0fVxuXG5cdC8qKlxuXHQgKiBEaXNjb25uZWN0IGZyb20gTkFUU1xuXHQgKi9cblx0YXN5bmMgZGlzY29ubmVjdCgpOiBQcm9taXNlPHZvaWQ+IHtcblx0XHRpZiAoIXRoaXMuY29ubmVjdGVkKSByZXR1cm47XG5cblx0XHR0cnkge1xuXHRcdFx0Ly8gU3RvcCBhbGwgY29uc3VtZSBpdGVyYXRvcnNcblx0XHRcdGZvciAoY29uc3QgWywgaXRlcl0gb2YgdGhpcy5jb25zdW1lSXRlcmF0b3JzKSB7XG5cdFx0XHRcdHRyeSB7XG5cdFx0XHRcdFx0aXRlci5zdG9wKCk7XG5cdFx0XHRcdH0gY2F0Y2gge1xuXHRcdFx0XHRcdC8vIEl0ZXJhdG9yIG1heSBhbHJlYWR5IGJlIHN0b3BwZWRcblx0XHRcdFx0fVxuXHRcdFx0fVxuXHRcdFx0dGhpcy5jb25zdW1lSXRlcmF0b3JzLmNsZWFyKCk7XG5cdFx0XHR0aGlzLmNvbnN1bWVycy5jbGVhcigpO1xuXG5cdFx0XHRhd2FpdCB0aGlzLm5jLmRyYWluKCk7XG5cdFx0XHR0aGlzLmNvbm5lY3RlZCA9IGZhbHNlO1xuXHRcdFx0Y29uc29sZS5sb2coXCJbTkFUU1dvcmtlckFkYXB0ZXJdIERpc2Nvbm5lY3RlZCBmcm9tIE5BVFNcIik7XG5cdFx0fSBjYXRjaCAoZXJyb3IpIHtcblx0XHRcdGNvbnNvbGUuZXJyb3IoYFtOQVRTV29ya2VyQWRhcHRlcl0gRGlzY29ubmVjdCBlcnJvcjogJHsoZXJyb3IgYXMgRXJyb3IpLm1lc3NhZ2V9YCk7XG5cdFx0fVxuXHR9XG5cblx0LyoqXG5cdCAqIFN0YXJ0IHByb2Nlc3Npbmcgam9icyBmcm9tIGEgcXVldWVcblx0ICovXG5cdGFzeW5jIHByb2Nlc3MoY29uZmlnOiBXb3JrZXJUcmlnZ2VyT3B0cywgaGFuZGxlcjogKGpvYjogV29ya2VySm9iKSA9PiBQcm9taXNlPHZvaWQ+KTogUHJvbWlzZTx2b2lkPiB7XG5cdFx0aWYgKCF0aGlzLmNvbm5lY3RlZCkge1xuXHRcdFx0dGhyb3cgbmV3IEVycm9yKFwiTm90IGNvbm5lY3RlZC4gQ2FsbCBjb25uZWN0KCkgZmlyc3QuXCIpO1xuXHRcdH1cblxuXHRcdGNvbnN0IG5hdHMgPSBhd2FpdCBpbXBvcnQoXCJuYXRzXCIpO1xuXHRcdGNvbnN0IHF1ZXVlID0gY29uZmlnLnF1ZXVlO1xuXHRcdGNvbnN0IHN0cmVhbU5hbWUgPSB0aGlzLmNvbmZpZy5zdHJlYW1OYW1lIHx8IFwiYmxvay13b3JrZXJcIjtcblx0XHRjb25zdCBzdWJqZWN0ID0gYHdvcmtlci4ke3F1ZXVlfWA7XG5cdFx0Y29uc3QgZHVyYWJsZU5hbWUgPSBgYmxvay13b3JrZXItJHtxdWV1ZX1gO1xuXG5cdFx0Ly8gRW5zdXJlIHN0cmVhbSBleGlzdHMgd2l0aCB3b3JrZXIgc3ViamVjdHNcblx0XHRhd2FpdCB0aGlzLmVuc3VyZVN0cmVhbShzdHJlYW1OYW1lLCBbc3ViamVjdF0pO1xuXG5cdFx0Ly8gQ3JlYXRlIG9yIHVwZGF0ZSBkdXJhYmxlIHB1bGwgY29uc3VtZXIgd2l0aCB3b3JrZXIgc2VtYW50aWNzXG5cdFx0Y29uc3QgYWNrV2FpdE5zID0gKChjb25maWcudGltZW91dCA/PyAzMDAwMCkgKyA1MDAwKSAqIDFfMDAwXzAwMDsgLy8gdGltZW91dCArIDVzIGJ1ZmZlciwgaW4gbmFub3NlY29uZHNcblx0XHRhd2FpdCB0aGlzLmpzbS5jb25zdW1lcnMuYWRkKHN0cmVhbU5hbWUsIHtcblx0XHRcdGR1cmFibGVfbmFtZTogZHVyYWJsZU5hbWUsXG5cdFx0XHRhY2tfcG9saWN5OiBuYXRzLkFja1BvbGljeS5FeHBsaWNpdCxcblx0XHRcdG1heF9kZWxpdmVyOiAoY29uZmlnLnJldHJpZXMgPz8gMykgKyAxLCAvLyArMSBiZWNhdXNlIGZpcnN0IGF0dGVtcHQgY291bnRzXG5cdFx0XHRhY2tfd2FpdDogYWNrV2FpdE5zLFxuXHRcdFx0ZmlsdGVyX3N1YmplY3RzOiBbc3ViamVjdF0sXG5cdFx0fSk7XG5cblx0XHQvLyBHZXQgY29uc3VtZXIgaGFuZGxlXG5cdFx0Y29uc3QgY29uc3VtZXIgPSBhd2FpdCB0aGlzLmpzLmNvbnN1bWVycy5nZXQoc3RyZWFtTmFtZSwgZHVyYWJsZU5hbWUpO1xuXHRcdHRoaXMuY29uc3VtZXJzLnNldChxdWV1ZSwgY29uc3VtZXIpO1xuXG5cdFx0Ly8gU3RhcnQgY29uc3VtaW5nXG5cdFx0Y29uc3QgaXRlciA9IGF3YWl0IGNvbnN1bWVyLmNvbnN1bWUoKTtcblx0XHR0aGlzLmNvbnN1bWVJdGVyYXRvcnMuc2V0KHF1ZXVlLCBpdGVyKTtcblxuXHRcdC8vIFByb2Nlc3Mgam9icyBpbiBiYWNrZ3JvdW5kXG5cdFx0KGFzeW5jICgpID0+IHtcblx0XHRcdGNvbnN0IHNlbWFwaG9yZSA9IG5ldyBTZW1hcGhvcmUoY29uZmlnLmNvbmN1cnJlbmN5ID8/IDEpO1xuXG5cdFx0XHRmb3IgYXdhaXQgKGNvbnN0IG1zZyBvZiBpdGVyKSB7XG5cdFx0XHRcdGF3YWl0IHNlbWFwaG9yZS5hY3F1aXJlKCk7XG5cblx0XHRcdFx0Ly8gUHJvY2VzcyBlYWNoIGpvYiBjb25jdXJyZW50bHkgdXAgdG8gY29uY3VycmVuY3kgbGltaXRcblx0XHRcdFx0KGFzeW5jICgpID0+IHtcblx0XHRcdFx0XHR0cnkge1xuXHRcdFx0XHRcdFx0Ly8gUGFyc2Ugam9iIGRhdGFcblx0XHRcdFx0XHRcdGxldCBkYXRhOiB1bmtub3duO1xuXHRcdFx0XHRcdFx0dHJ5IHtcblx0XHRcdFx0XHRcdFx0Y29uc3QgY29kZWMgPSBuYXRzLkpTT05Db2RlYygpO1xuXHRcdFx0XHRcdFx0XHRkYXRhID0gY29kZWMuZGVjb2RlKG1zZy5kYXRhKTtcblx0XHRcdFx0XHRcdH0gY2F0Y2gge1xuXHRcdFx0XHRcdFx0XHR0cnkge1xuXHRcdFx0XHRcdFx0XHRcdGNvbnN0IHNjID0gbmF0cy5TdHJpbmdDb2RlYygpO1xuXHRcdFx0XHRcdFx0XHRcdGRhdGEgPSBKU09OLnBhcnNlKHNjLmRlY29kZShtc2cuZGF0YSkpO1xuXHRcdFx0XHRcdFx0XHR9IGNhdGNoIHtcblx0XHRcdFx0XHRcdFx0XHRkYXRhID0gbXNnLmRhdGE7XG5cdFx0XHRcdFx0XHRcdH1cblx0XHRcdFx0XHRcdH1cblxuXHRcdFx0XHRcdFx0Ly8gRXh0cmFjdCBoZWFkZXJzXG5cdFx0XHRcdFx0XHRjb25zdCBoZWFkZXJzOiBSZWNvcmQ8c3RyaW5nLCBzdHJpbmc+ID0ge307XG5cdFx0XHRcdFx0XHRpZiAobXNnLmhlYWRlcnMpIHtcblx0XHRcdFx0XHRcdFx0Zm9yIChjb25zdCBba2V5LCB2YWx1ZXNdIG9mIG1zZy5oZWFkZXJzKSB7XG5cdFx0XHRcdFx0XHRcdFx0aGVhZGVyc1trZXldID0gQXJyYXkuaXNBcnJheSh2YWx1ZXMpID8gdmFsdWVzWzBdIDogdmFsdWVzO1xuXHRcdFx0XHRcdFx0XHR9XG5cdFx0XHRcdFx0XHR9XG5cblx0XHRcdFx0XHRcdC8vIEV4dHJhY3Qgam9iIG1ldGFkYXRhIGZyb20gaGVhZGVyc1xuXHRcdFx0XHRcdFx0Y29uc3Qgam9iSWQgPSBoZWFkZXJzW1wieC1qb2ItaWRcIl0gfHwgbXNnLmhlYWRlcnM/LmdldChcIk5hdHMtTXNnLUlkXCIpIHx8IHV1aWQoKTtcblx0XHRcdFx0XHRcdGNvbnN0IHByaW9yaXR5ID0gTnVtYmVyLnBhcnNlSW50KGhlYWRlcnNbXCJ4LXByaW9yaXR5XCJdIHx8IFwiMFwiLCAxMCk7XG5cdFx0XHRcdFx0XHRjb25zdCBkZWxheSA9IE51bWJlci5wYXJzZUludChoZWFkZXJzW1wieC1kZWxheVwiXSB8fCBcIjBcIiwgMTApO1xuXHRcdFx0XHRcdFx0Y29uc3QgdGltZW91dCA9IE51bWJlci5wYXJzZUludChoZWFkZXJzW1wieC10aW1lb3V0XCJdIHx8IFwiMFwiLCAxMCk7XG5cblx0XHRcdFx0XHRcdC8vIEdldCByZWRlbGl2ZXJ5IGNvdW50IChhdHRlbXB0cylcblx0XHRcdFx0XHRcdGNvbnN0IGluZm8gPSBtc2cuaW5mbztcblx0XHRcdFx0XHRcdGNvbnN0IGF0dGVtcHRzID0gaW5mby5yZWRlbGl2ZXJ5Q291bnQgPz8gMDtcblx0XHRcdFx0XHRcdGNvbnN0IG1heFJldHJpZXMgPSBjb25maWcucmV0cmllcyA/PyAzO1xuXG5cdFx0XHRcdFx0XHQvLyBDcmVhdGUgV29ya2VySm9iXG5cdFx0XHRcdFx0XHRjb25zdCB3b3JrZXJKb2I6IFdvcmtlckpvYiA9IHtcblx0XHRcdFx0XHRcdFx0aWQ6IGpvYklkLFxuXHRcdFx0XHRcdFx0XHRkYXRhLFxuXHRcdFx0XHRcdFx0XHRoZWFkZXJzLFxuXHRcdFx0XHRcdFx0XHRxdWV1ZSxcblx0XHRcdFx0XHRcdFx0cHJpb3JpdHksXG5cdFx0XHRcdFx0XHRcdGF0dGVtcHRzLFxuXHRcdFx0XHRcdFx0XHRtYXhSZXRyaWVzLFxuXHRcdFx0XHRcdFx0XHRjcmVhdGVkQXQ6IG5ldyBEYXRlKFxuXHRcdFx0XHRcdFx0XHRcdGluZm8udGltZXN0YW1wTmFub3MgPyBNYXRoLmZsb29yKE51bWJlcihpbmZvLnRpbWVzdGFtcE5hbm9zKSAvIDFfMDAwXzAwMCkgOiBEYXRlLm5vdygpLFxuXHRcdFx0XHRcdFx0XHQpLFxuXHRcdFx0XHRcdFx0XHRkZWxheTogZGVsYXkgfHwgdW5kZWZpbmVkLFxuXHRcdFx0XHRcdFx0XHR0aW1lb3V0OiB0aW1lb3V0IHx8IGNvbmZpZy50aW1lb3V0IHx8IHVuZGVmaW5lZCxcblx0XHRcdFx0XHRcdFx0cmF3OiBtc2csXG5cdFx0XHRcdFx0XHRcdGNvbXBsZXRlOiBhc3luYyAoKSA9PiB7XG5cdFx0XHRcdFx0XHRcdFx0bXNnLmFjaygpO1xuXHRcdFx0XHRcdFx0XHR9LFxuXHRcdFx0XHRcdFx0XHRmYWlsOiBhc3luYyAoZXJyb3I6IEVycm9yLCByZXF1ZXVlPzogYm9vbGVhbikgPT4ge1xuXHRcdFx0XHRcdFx0XHRcdGlmIChyZXF1ZXVlKSB7XG5cdFx0XHRcdFx0XHRcdFx0XHQvLyBuYWsoKSB0ZWxscyB0aGUgc2VydmVyIHRvIHJlZGVsaXZlclxuXHRcdFx0XHRcdFx0XHRcdFx0bXNnLm5haygpO1xuXHRcdFx0XHRcdFx0XHRcdH0gZWxzZSB7XG5cdFx0XHRcdFx0XHRcdFx0XHQvLyB0ZXJtKCkgdGVybWluYXRlcyBkZWxpdmVyeSDigJQgbm8gbW9yZSByZXRyaWVzXG5cdFx0XHRcdFx0XHRcdFx0XHRtc2cudGVybSgpO1xuXHRcdFx0XHRcdFx0XHRcdH1cblx0XHRcdFx0XHRcdFx0fSxcblx0XHRcdFx0XHRcdH07XG5cblx0XHRcdFx0XHRcdC8vIFRpZXIgMiBwb2xpc2gg4oCUIGVuZm9yY2UgYHgtZGVsYXlgIGhlYWRlciBvbiB0aGUgY29uc3VtZXIgc2lkZS5cblx0XHRcdFx0XHRcdC8vIE5BVFMgSmV0U3RyZWFtIHN0b3JlcyBgeC1kZWxheWAgYXMgb3BhcXVlIG1ldGFkYXRhOyB0aGUgYnJva2VyXG5cdFx0XHRcdFx0XHQvLyBkb2VzIE5PVCBkZWZlciBkZWxpdmVyeSBvbiBpdC4gV2UgaW1wbGVtZW50IGNvbnN1bWVyLXNpZGVcblx0XHRcdFx0XHRcdC8vIGhvbGRpbmcgaGVyZS4gY3JlYXRlZE1zIGlzIHRoZSBtZXNzYWdlJ3MgZmlyc3QtcHVibGlzaCB0aW1lc3RhbXA7XG5cdFx0XHRcdFx0XHQvLyBob2xkIHVudGlsIGNyZWF0ZWRNcyArIGRlbGF5LiBTaW5nbGUtcHJvY2VzcyBzZW1hbnRpY3Mg4oCUIGZvclxuXHRcdFx0XHRcdFx0Ly8gbG9uZyBkZWZlcnJhbHMsIHByZWZlciB0cmlnZ2VyLWxldmVsIGBkZWxheWAgKERlZmVycmVkUnVuU2NoZWR1bGVyKS5cblx0XHRcdFx0XHRcdGNvbnN0IGNyZWF0ZWRNcyA9IGluZm8udGltZXN0YW1wTmFub3MgPyBNYXRoLmZsb29yKE51bWJlcihpbmZvLnRpbWVzdGFtcE5hbm9zKSAvIDFfMDAwXzAwMCkgOiBEYXRlLm5vdygpO1xuXHRcdFx0XHRcdFx0Y29uc3Qgd2FpdE1zID0gY29tcHV0ZVhEZWxheUhvbGRNcyhkZWxheSwgY3JlYXRlZE1zLCBEYXRlLm5vdygpKTtcblx0XHRcdFx0XHRcdGlmICh3YWl0TXMgPiAwKSB7XG5cdFx0XHRcdFx0XHRcdGF3YWl0IG5ldyBQcm9taXNlPHZvaWQ+KChyZXNvbHZlKSA9PiBzZXRUaW1lb3V0KHJlc29sdmUsIHdhaXRNcykpO1xuXHRcdFx0XHRcdFx0fVxuXG5cdFx0XHRcdFx0XHRhd2FpdCBoYW5kbGVyKHdvcmtlckpvYik7XG5cdFx0XHRcdFx0fSBjYXRjaCAoZXJyb3IpIHtcblx0XHRcdFx0XHRcdGNvbnNvbGUuZXJyb3IoYFtOQVRTV29ya2VyQWRhcHRlcl0gRXJyb3IgcHJvY2Vzc2luZyBqb2IgZnJvbSAke3F1ZXVlfTogJHsoZXJyb3IgYXMgRXJyb3IpLm1lc3NhZ2V9YCk7XG5cdFx0XHRcdFx0XHR0cnkge1xuXHRcdFx0XHRcdFx0XHRtc2cubmFrKCk7XG5cdFx0XHRcdFx0XHR9IGNhdGNoIHtcblx0XHRcdFx0XHRcdFx0Ly8gQWxyZWFkeSBhY2tlZC9uYWNrZWRcblx0XHRcdFx0XHRcdH1cblx0XHRcdFx0XHR9IGZpbmFsbHkge1xuXHRcdFx0XHRcdFx0c2VtYXBob3JlLnJlbGVhc2UoKTtcblx0XHRcdFx0XHR9XG5cdFx0XHRcdH0pKCk7XG5cdFx0XHR9XG5cdFx0fSkoKTtcblxuXHRcdGNvbnNvbGUubG9nKFxuXHRcdFx0YFtOQVRTV29ya2VyQWRhcHRlcl0gUHJvY2Vzc2luZyBxdWV1ZTogJHtxdWV1ZX0gKGNvbmN1cnJlbmN5PSR7Y29uZmlnLmNvbmN1cnJlbmN5ID8/IDF9LCByZXRyaWVzPSR7Y29uZmlnLnJldHJpZXMgPz8gM30pYCxcblx0XHQpO1xuXHR9XG5cblx0LyoqXG5cdCAqIEFkZCBhIGpvYiB0byBhIHdvcmtlciBxdWV1ZVxuXHQgKi9cblx0YXN5bmMgYWRkSm9iKFxuXHRcdHF1ZXVlOiBzdHJpbmcsXG5cdFx0ZGF0YTogdW5rbm93bixcblx0XHRvcHRzPzoge1xuXHRcdFx0cHJpb3JpdHk/OiBudW1iZXI7XG5cdFx0XHRkZWxheT86IG51bWJlcjtcblx0XHRcdHJldHJpZXM/OiBudW1iZXI7XG5cdFx0XHR0aW1lb3V0PzogbnVtYmVyO1xuXHRcdFx0am9iSWQ/OiBzdHJpbmc7XG5cdFx0fSxcblx0KTogUHJvbWlzZTxzdHJpbmc+IHtcblx0XHRpZiAoIXRoaXMuY29ubmVjdGVkKSB7XG5cdFx0XHR0aHJvdyBuZXcgRXJyb3IoXCJOb3QgY29ubmVjdGVkLiBDYWxsIGNvbm5lY3QoKSBmaXJzdC5cIik7XG5cdFx0fVxuXG5cdFx0Y29uc3QgbmF0cyA9IGF3YWl0IGltcG9ydChcIm5hdHNcIik7XG5cdFx0Y29uc3Qgc3ViamVjdCA9IGB3b3JrZXIuJHtxdWV1ZX1gO1xuXHRcdGNvbnN0IHN0cmVhbU5hbWUgPSB0aGlzLmNvbmZpZy5zdHJlYW1OYW1lIHx8IFwiYmxvay13b3JrZXJcIjtcblxuXHRcdC8vIEVuc3VyZSBzdHJlYW0gaGFzIHRoaXMgc3ViamVjdFxuXHRcdGF3YWl0IHRoaXMuZW5zdXJlU3RyZWFtKHN0cmVhbU5hbWUsIFtzdWJqZWN0XSk7XG5cblx0XHQvLyBCdWlsZCBoZWFkZXJzIHdpdGggam9iIG1ldGFkYXRhXG5cdFx0Y29uc3QgaGRycyA9IG5hdHMuaGVhZGVycygpO1xuXHRcdGNvbnN0IGpvYklkID0gb3B0cz8uam9iSWQgfHwgdXVpZCgpO1xuXHRcdGhkcnMuc2V0KFwieC1qb2ItaWRcIiwgam9iSWQpO1xuXHRcdGhkcnMuc2V0KFwiTmF0cy1Nc2ctSWRcIiwgam9iSWQpOyAvLyBEZWR1cGxpY2F0aW9uXG5cdFx0aWYgKG9wdHM/LnByaW9yaXR5KSBoZHJzLnNldChcIngtcHJpb3JpdHlcIiwgU3RyaW5nKG9wdHMucHJpb3JpdHkpKTtcblx0XHRpZiAob3B0cz8uZGVsYXkpIGhkcnMuc2V0KFwieC1kZWxheVwiLCBTdHJpbmcob3B0cy5kZWxheSkpO1xuXHRcdGlmIChvcHRzPy50aW1lb3V0KSBoZHJzLnNldChcIngtdGltZW91dFwiLCBTdHJpbmcob3B0cy50aW1lb3V0KSk7XG5cblx0XHQvLyBFbmNvZGUgYW5kIHB1Ymxpc2hcblx0XHRjb25zdCBjb2RlYyA9IG5hdHMuSlNPTkNvZGVjKCk7XG5cdFx0YXdhaXQgdGhpcy5qcy5wdWJsaXNoKHN1YmplY3QsIGNvZGVjLmVuY29kZShkYXRhKSwgeyBoZWFkZXJzOiBoZHJzIH0pO1xuXG5cdFx0cmV0dXJuIGpvYklkO1xuXHR9XG5cblx0LyoqXG5cdCAqIFN0b3AgcHJvY2Vzc2luZyBhIHNwZWNpZmljIHF1ZXVlXG5cdCAqL1xuXHRhc3luYyBzdG9wUHJvY2Vzc2luZyhxdWV1ZTogc3RyaW5nKTogUHJvbWlzZTx2b2lkPiB7XG5cdFx0Y29uc3QgaXRlciA9IHRoaXMuY29uc3VtZUl0ZXJhdG9ycy5nZXQocXVldWUpO1xuXHRcdGlmIChpdGVyKSB7XG5cdFx0XHR0cnkge1xuXHRcdFx0XHRpdGVyLnN0b3AoKTtcblx0XHRcdH0gY2F0Y2gge1xuXHRcdFx0XHQvLyBBbHJlYWR5IHN0b3BwZWRcblx0XHRcdH1cblx0XHRcdHRoaXMuY29uc3VtZUl0ZXJhdG9ycy5kZWxldGUocXVldWUpO1xuXHRcdH1cblx0XHR0aGlzLmNvbnN1bWVycy5kZWxldGUocXVldWUpO1xuXHRcdGNvbnNvbGUubG9nKGBbTkFUU1dvcmtlckFkYXB0ZXJdIFN0b3BwZWQgcHJvY2Vzc2luZyBxdWV1ZTogJHtxdWV1ZX1gKTtcblx0fVxuXG5cdC8qKlxuXHQgKiBDaGVjayBpZiBjb25uZWN0ZWRcblx0ICovXG5cdGlzQ29ubmVjdGVkKCk6IGJvb2xlYW4ge1xuXHRcdHJldHVybiB0aGlzLmNvbm5lY3RlZDtcblx0fVxuXG5cdC8qKlxuXHQgKiBIZWFsdGggY2hlY2tcblx0ICovXG5cdGFzeW5jIGhlYWx0aENoZWNrKCk6IFByb21pc2U8Ym9vbGVhbj4ge1xuXHRcdGlmICghdGhpcy5jb25uZWN0ZWQgfHwgIXRoaXMubmMpIHJldHVybiBmYWxzZTtcblx0XHR0cnkge1xuXHRcdFx0Y29uc3QgaW5mbyA9IHRoaXMubmMuaW5mbztcblx0XHRcdHJldHVybiBpbmZvICE9PSB1bmRlZmluZWQ7XG5cdFx0fSBjYXRjaCB7XG5cdFx0XHRyZXR1cm4gZmFsc2U7XG5cdFx0fVxuXHR9XG5cblx0LyoqXG5cdCAqIEdldCBxdWV1ZSBzdGF0aXN0aWNzIGZyb20gSmV0U3RyZWFtIGNvbnN1bWVyIGluZm9cblx0ICovXG5cdGFzeW5jIGdldFF1ZXVlU3RhdHMocXVldWU6IHN0cmluZyk6IFByb21pc2U8V29ya2VyUXVldWVTdGF0cz4ge1xuXHRcdGlmICghdGhpcy5jb25uZWN0ZWQpIHtcblx0XHRcdHJldHVybiB7IHdhaXRpbmc6IDAsIGFjdGl2ZTogMCwgY29tcGxldGVkOiAwLCBmYWlsZWQ6IDAsIGRlbGF5ZWQ6IDAgfTtcblx0XHR9XG5cblx0XHR0cnkge1xuXHRcdFx0Y29uc3Qgc3RyZWFtTmFtZSA9IHRoaXMuY29uZmlnLnN0cmVhbU5hbWUgfHwgXCJibG9rLXdvcmtlclwiO1xuXHRcdFx0Y29uc3QgZHVyYWJsZU5hbWUgPSBgYmxvay13b3JrZXItJHtxdWV1ZX1gO1xuXG5cdFx0XHRjb25zdCBpbmZvID0gYXdhaXQgdGhpcy5qc20uY29uc3VtZXJzLmluZm8oc3RyZWFtTmFtZSwgZHVyYWJsZU5hbWUpO1xuXG5cdFx0XHRyZXR1cm4ge1xuXHRcdFx0XHR3YWl0aW5nOiBpbmZvLm51bV9wZW5kaW5nID8/IDAsXG5cdFx0XHRcdGFjdGl2ZTogaW5mby5udW1fYWNrX3BlbmRpbmcgPz8gMCxcblx0XHRcdFx0Y29tcGxldGVkOiBpbmZvLmRlbGl2ZXJlZD8uY29uc3VtZXJfc2VxID8/IDAsXG5cdFx0XHRcdGZhaWxlZDogaW5mby5udW1fcmVkZWxpdmVyZWQgPz8gMCxcblx0XHRcdFx0ZGVsYXllZDogMCwgLy8gTkFUUyBkb2Vzbid0IGhhdmUgYSBuYXRpdmUgZGVsYXllZCBjb3VudFxuXHRcdFx0fTtcblx0XHR9IGNhdGNoIHtcblx0XHRcdHJldHVybiB7IHdhaXRpbmc6IDAsIGFjdGl2ZTogMCwgY29tcGxldGVkOiAwLCBmYWlsZWQ6IDAsIGRlbGF5ZWQ6IDAgfTtcblx0XHR9XG5cdH1cblxuXHQvKipcblx0ICogRW5zdXJlIGEgSmV0U3RyZWFtIHN0cmVhbSBleGlzdHMgd2l0aCB0aGUgZ2l2ZW4gc3ViamVjdHNcblx0ICovXG5cdHByaXZhdGUgYXN5bmMgZW5zdXJlU3RyZWFtKG5hbWU6IHN0cmluZywgc3ViamVjdHM6IHN0cmluZ1tdKTogUHJvbWlzZTx2b2lkPiB7XG5cdFx0dHJ5IHtcblx0XHRcdGNvbnN0IGluZm8gPSBhd2FpdCB0aGlzLmpzbS5zdHJlYW1zLmluZm8obmFtZSk7XG5cblx0XHRcdC8vIE1lcmdlIG5ldyBzdWJqZWN0cyB3aXRoIGV4aXN0aW5nXG5cdFx0XHRjb25zdCBleGlzdGluZ1N1YmplY3RzID0gaW5mby5jb25maWcuc3ViamVjdHMgfHwgW107XG5cdFx0XHRjb25zdCBhbGxTdWJqZWN0cyA9IFsuLi5uZXcgU2V0KFsuLi5leGlzdGluZ1N1YmplY3RzLCAuLi5zdWJqZWN0c10pXTtcblxuXHRcdFx0aWYgKGFsbFN1YmplY3RzLmxlbmd0aCAhPT0gZXhpc3RpbmdTdWJqZWN0cy5sZW5ndGgpIHtcblx0XHRcdFx0YXdhaXQgdGhpcy5qc20uc3RyZWFtcy51cGRhdGUobmFtZSwge1xuXHRcdFx0XHRcdC4uLmluZm8uY29uZmlnLFxuXHRcdFx0XHRcdHN1YmplY3RzOiBhbGxTdWJqZWN0cyxcblx0XHRcdFx0fSk7XG5cdFx0XHR9XG5cdFx0fSBjYXRjaCB7XG5cdFx0XHQvLyBTdHJlYW0gZG9lc24ndCBleGlzdCwgY3JlYXRlIGl0XG5cdFx0XHRhd2FpdCB0aGlzLmpzbS5zdHJlYW1zLmFkZCh7XG5cdFx0XHRcdG5hbWUsXG5cdFx0XHRcdHN1YmplY3RzLFxuXHRcdFx0XHQvLyBiaW9tZS1pZ25vcmUgbGludC9zdXNwaWNpb3VzL25vRXhwbGljaXRBbnk6IG5hdHMgSmV0U3RyZWFtIHJldGVudGlvbiBwb2xpY3kgZW51bVxuXHRcdFx0XHRyZXRlbnRpb246IFwid29ya3F1ZXVlXCIgYXMgYW55LFxuXHRcdFx0XHRtYXhfZGVsaXZlcjogNCwgLy8gZGVmYXVsdDogMyByZXRyaWVzICsgMSBpbml0aWFsIGF0dGVtcHRcblx0XHRcdFx0Ly8gYmlvbWUtaWdub3JlIGxpbnQvc3VzcGljaW91cy9ub0V4cGxpY2l0QW55OiBuYXRzIEpldFN0cmVhbSBzdG9yYWdlIHR5cGUgZW51bVxuXHRcdFx0XHRzdG9yYWdlOiBcImZpbGVcIiBhcyBhbnksXG5cdFx0XHR9KTtcblx0XHR9XG5cdH1cbn1cblxuLyoqXG4gKiBTaW1wbGUgc2VtYXBob3JlIGZvciBjb25jdXJyZW5jeSBjb250cm9sXG4gKi9cbmNsYXNzIFNlbWFwaG9yZSB7XG5cdHByaXZhdGUgcGVybWl0czogbnVtYmVyO1xuXHRwcml2YXRlIHdhaXRpbmc6IEFycmF5PCgpID0+IHZvaWQ+ID0gW107XG5cblx0Y29uc3RydWN0b3IocGVybWl0czogbnVtYmVyKSB7XG5cdFx0dGhpcy5wZXJtaXRzID0gcGVybWl0cztcblx0fVxuXG5cdGFzeW5jIGFjcXVpcmUoKTogUHJvbWlzZTx2b2lkPiB7XG5cdFx0aWYgKHRoaXMucGVybWl0cyA+IDApIHtcblx0XHRcdHRoaXMucGVybWl0cy0tO1xuXHRcdFx0cmV0dXJuO1xuXHRcdH1cblx0XHRyZXR1cm4gbmV3IFByb21pc2U8dm9pZD4oKHJlc29sdmUpID0+IHtcblx0XHRcdHRoaXMud2FpdGluZy5wdXNoKHJlc29sdmUpO1xuXHRcdH0pO1xuXHR9XG5cblx0cmVsZWFzZSgpOiB2b2lkIHtcblx0XHRjb25zdCBuZXh0ID0gdGhpcy53YWl0aW5nLnNoaWZ0KCk7XG5cdFx0aWYgKG5leHQpIHtcblx0XHRcdG5leHQoKTtcblx0XHR9IGVsc2Uge1xuXHRcdFx0dGhpcy5wZXJtaXRzKys7XG5cdFx0fVxuXHR9XG59XG5cbmV4cG9ydCBkZWZhdWx0IE5BVFNXb3JrZXJBZGFwdGVyO1xuIl19
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PgBossAdapter — v0.7 PR 5 — Worker adapter backed by `pg-boss`,
|
|
3
|
+
* which stores jobs in PostgreSQL. Operationally attractive when
|
|
4
|
+
* Postgres is already in the stack — no extra broker infra needed.
|
|
5
|
+
*
|
|
6
|
+
* Semantics:
|
|
7
|
+
* - `pg-boss` handles concurrency, retries, dead-letter, priority,
|
|
8
|
+
* delays, and exactly-once scheduling natively — the adapter
|
|
9
|
+
* forwards `config.concurrency`, `config.retries`, etc. to the
|
|
10
|
+
* `boss.work(queue, opts, handler)` and `boss.send(queue, data, opts)`
|
|
11
|
+
* calls directly.
|
|
12
|
+
* - **Single connection per process** — `pg-boss` manages its own
|
|
13
|
+
* pool; we instantiate one `PgBoss` instance per `PgBossAdapter`.
|
|
14
|
+
* - **Schema**: `pg-boss` creates its own schema (`pgboss` by
|
|
15
|
+
* default) on first start. Tables are migrated automatically.
|
|
16
|
+
*
|
|
17
|
+
* Requires `pg-boss` as a peer dependency:
|
|
18
|
+
*
|
|
19
|
+
* bun add pg-boss
|
|
20
|
+
*
|
|
21
|
+
* Environment variables:
|
|
22
|
+
* - `DATABASE_URL` — Postgres connection string (default
|
|
23
|
+
* `postgres://localhost:5432/blok`).
|
|
24
|
+
* - `PG_BOSS_SCHEMA` — schema name (default `"pgboss"`).
|
|
25
|
+
*/
|
|
26
|
+
import type { WorkerTriggerOpts } from "@blokjs/helper";
|
|
27
|
+
import type { WorkerAdapter, WorkerJob, WorkerQueueStats } from "../WorkerTrigger";
|
|
28
|
+
export interface PgBossConfig {
|
|
29
|
+
connectionString: string;
|
|
30
|
+
schema?: string;
|
|
31
|
+
}
|
|
32
|
+
export declare class PgBossAdapter implements WorkerAdapter {
|
|
33
|
+
readonly provider: "pg-boss";
|
|
34
|
+
private readonly config;
|
|
35
|
+
private boss;
|
|
36
|
+
private workIds;
|
|
37
|
+
private connected;
|
|
38
|
+
private stats;
|
|
39
|
+
constructor(config?: Partial<PgBossConfig>);
|
|
40
|
+
connect(): Promise<void>;
|
|
41
|
+
disconnect(): Promise<void>;
|
|
42
|
+
process(config: WorkerTriggerOpts, handler: (job: WorkerJob) => Promise<void>): Promise<void>;
|
|
43
|
+
private runOneJob;
|
|
44
|
+
private ensureQueue;
|
|
45
|
+
addJob(queue: string, data: unknown, opts?: {
|
|
46
|
+
priority?: number;
|
|
47
|
+
delay?: number;
|
|
48
|
+
retries?: number;
|
|
49
|
+
timeout?: number;
|
|
50
|
+
jobId?: string;
|
|
51
|
+
}): Promise<string>;
|
|
52
|
+
stopProcessing(queue: string): Promise<void>;
|
|
53
|
+
isConnected(): boolean;
|
|
54
|
+
healthCheck(): Promise<boolean>;
|
|
55
|
+
getQueueStats(queue: string): Promise<WorkerQueueStats>;
|
|
56
|
+
}
|