@blokjs/trigger-worker 0.4.0 → 0.6.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.
Files changed (37) hide show
  1. package/__tests__/integration/nats-adapter.real-nats.test.ts +116 -0
  2. package/__tests__/integration/pgboss-adapter.real-pg.test.ts +164 -0
  3. package/__tests__/integration/rabbitmq-adapter.real-rabbitmq.test.ts +179 -0
  4. package/__tests__/integration/sqs-adapter.real-sqs.test.ts +228 -0
  5. package/dist/WorkerTrigger.d.ts +37 -1
  6. package/dist/WorkerTrigger.js +142 -21
  7. package/dist/adapters/InMemoryAdapter.js +10 -5
  8. package/dist/adapters/KafkaAdapter.d.ts +62 -0
  9. package/dist/adapters/KafkaAdapter.js +236 -0
  10. package/dist/adapters/NATSAdapter.js +3 -3
  11. package/dist/adapters/PgBossAdapter.d.ts +56 -0
  12. package/dist/adapters/PgBossAdapter.js +251 -0
  13. package/dist/adapters/RabbitMQAdapter.d.ts +51 -0
  14. package/dist/adapters/RabbitMQAdapter.js +241 -0
  15. package/dist/adapters/RedisStreamsAdapter.d.ts +64 -0
  16. package/dist/adapters/RedisStreamsAdapter.js +240 -0
  17. package/dist/adapters/SQSAdapter.d.ts +61 -0
  18. package/dist/adapters/SQSAdapter.js +269 -0
  19. package/dist/adapters/factory.d.ts +34 -0
  20. package/dist/adapters/factory.js +103 -0
  21. package/dist/index.d.ts +21 -4
  22. package/dist/index.js +24 -4
  23. package/package.json +23 -5
  24. package/src/WorkerTrigger.ts +153 -22
  25. package/src/adapters/InMemoryAdapter.ts +9 -5
  26. package/src/adapters/KafkaAdapter.ts +277 -0
  27. package/src/adapters/NATSAdapter.ts +4 -2
  28. package/src/adapters/PgBossAdapter.ts +293 -0
  29. package/src/adapters/RabbitMQAdapter.ts +285 -0
  30. package/src/adapters/RedisStreamsAdapter.ts +286 -0
  31. package/src/adapters/SQSAdapter.ts +306 -0
  32. package/src/adapters/factory.test.ts +89 -0
  33. package/src/adapters/factory.ts +111 -0
  34. package/src/adapters/new-adapters.test.ts +130 -0
  35. package/src/index.ts +30 -4
  36. package/template/package.json +6 -6
  37. package/template/src/workflows/jobs/process-job.ts +37 -35
@@ -0,0 +1,241 @@
1
+ /**
2
+ * RabbitMQAdapter — v0.7 PR 5 — Worker adapter backed by RabbitMQ via
3
+ * the `amqplib` driver. Direct-exchange / queue model — the `queue`
4
+ * field maps to an AMQP queue name; messages are consumed with
5
+ * manual ACK. The default exchange (`""`) is used for publishing.
6
+ *
7
+ * Features:
8
+ * - Concurrency via `prefetch(N)` per channel.
9
+ * - Retries via `nack` with requeue=true until `retries` is hit, then
10
+ * drop or DLQ-route based on `deadLetterQueue` config.
11
+ * - Priorities via the `x-max-priority` queue arg (AMQP standard).
12
+ * - Delayed delivery via the `x-delayed-message` plugin when
13
+ * available; falls back to immediate dispatch otherwise.
14
+ *
15
+ * Requires `amqplib` as a peer dependency:
16
+ *
17
+ * bun add amqplib
18
+ *
19
+ * Environment variables:
20
+ * - `AMQP_URL` — full AMQP connection string (default `amqp://localhost`).
21
+ * - `AMQP_VHOST` — virtual host (default `/`).
22
+ */
23
+ import { v4 as uuid } from "uuid";
24
+ export class RabbitMQAdapter {
25
+ provider = "rabbitmq";
26
+ config;
27
+ conn = null;
28
+ channels = new Map();
29
+ connected = false;
30
+ stats = new Map();
31
+ constructor(config) {
32
+ this.config = {
33
+ url: config?.url ?? process.env.AMQP_URL ?? "amqp://localhost",
34
+ vhost: config?.vhost ?? process.env.AMQP_VHOST,
35
+ };
36
+ }
37
+ async connect() {
38
+ if (this.connected)
39
+ return;
40
+ try {
41
+ // biome-ignore lint/suspicious/noExplicitAny: amqplib's connect returns a loose ConnectionLike.
42
+ const amqp = await import("amqplib");
43
+ this.conn = (await amqp.connect(this.config.url, { vhost: this.config.vhost }));
44
+ this.connected = true;
45
+ }
46
+ catch (err) {
47
+ throw new Error(`[blok][rabbitmq] connect failed: ${err.message}. Install amqplib as a peer dependency: bun add amqplib`);
48
+ }
49
+ }
50
+ async disconnect() {
51
+ if (!this.connected)
52
+ return;
53
+ for (const [, entry] of this.channels) {
54
+ try {
55
+ if (entry.consumerTag)
56
+ await entry.channel.cancel(entry.consumerTag);
57
+ await entry.channel.close();
58
+ }
59
+ catch {
60
+ /* ignore */
61
+ }
62
+ }
63
+ this.channels.clear();
64
+ try {
65
+ await this.conn?.close();
66
+ }
67
+ catch {
68
+ /* ignore */
69
+ }
70
+ this.conn = null;
71
+ this.connected = false;
72
+ }
73
+ async process(config, handler) {
74
+ if (!this.connected || !this.conn)
75
+ throw new Error("[blok][rabbitmq] not connected. Call connect() first.");
76
+ const channel = await this.conn.createChannel();
77
+ await channel.prefetch(config.concurrency ?? 1);
78
+ const queueArgs = {};
79
+ if (config.deadLetterQueue) {
80
+ queueArgs["x-dead-letter-exchange"] = "";
81
+ queueArgs["x-dead-letter-routing-key"] = config.deadLetterQueue;
82
+ await channel.assertQueue(config.deadLetterQueue, { durable: true });
83
+ }
84
+ await channel.assertQueue(config.queue, { durable: true, arguments: queueArgs });
85
+ this.stats.set(config.queue, { completed: 0, failed: 0, active: 0 });
86
+ const stats = this.stats.get(config.queue);
87
+ const maxAttempts = (config.retries ?? 3) + 1;
88
+ const { consumerTag } = await channel.consume(config.queue, (msg) => {
89
+ if (!msg)
90
+ return;
91
+ void (async () => {
92
+ const payloadString = msg.content.toString("utf8");
93
+ let data;
94
+ try {
95
+ data = payloadString.length > 0 ? JSON.parse(payloadString) : null;
96
+ }
97
+ catch {
98
+ data = payloadString;
99
+ }
100
+ const headers = {};
101
+ for (const [k, v] of Object.entries(msg.properties.headers ?? {}))
102
+ headers[k] = String(v);
103
+ const attempts = Number.parseInt(String(msg.properties.headers?.["x-blok-attempt"] ?? 0), 10);
104
+ // Tracks whether the message has already been ack/nack'd via
105
+ // the WorkerJob API (`complete` / `fail`). The wrapping
106
+ // try/catch below also wants to ack on handler success and
107
+ // nack on handler throw — without this flag we'd
108
+ // double-ack the same delivery tag and Rabbit closes the
109
+ // channel with PRECONDITION_FAILED (caught by the
110
+ // real-broker integration test in
111
+ // `__tests__/integration/rabbitmq-adapter.real-rabbitmq.test.ts`).
112
+ let settled = false;
113
+ const job = {
114
+ id: msg.properties.messageId ?? `${config.queue}:${msg.fields.deliveryTag}`,
115
+ data,
116
+ headers,
117
+ queue: config.queue,
118
+ priority: msg.properties.priority ?? config.priority ?? 0,
119
+ attempts,
120
+ maxRetries: config.retries ?? 3,
121
+ createdAt: msg.properties.timestamp ? new Date(msg.properties.timestamp) : new Date(),
122
+ timeout: config.timeout,
123
+ raw: msg,
124
+ complete: async () => {
125
+ if (settled)
126
+ return;
127
+ channel.ack(msg);
128
+ stats.completed += 1;
129
+ settled = true;
130
+ },
131
+ fail: async (err, requeue) => {
132
+ if (settled)
133
+ return;
134
+ stats.failed += 1;
135
+ const exceeded = attempts + 1 >= maxAttempts;
136
+ channel.nack(msg, false, !exceeded && requeue !== false);
137
+ settled = true;
138
+ },
139
+ };
140
+ stats.active += 1;
141
+ try {
142
+ await handler(job);
143
+ if (!settled && config.ack !== false) {
144
+ channel.ack(msg);
145
+ stats.completed += 1;
146
+ settled = true;
147
+ }
148
+ }
149
+ catch {
150
+ if (!settled) {
151
+ stats.failed += 1;
152
+ const exceeded = attempts + 1 >= maxAttempts;
153
+ channel.nack(msg, false, !exceeded);
154
+ settled = true;
155
+ }
156
+ }
157
+ finally {
158
+ stats.active = Math.max(0, stats.active - 1);
159
+ }
160
+ })();
161
+ }, { noAck: config.ack === false });
162
+ this.channels.set(config.queue, { channel, consumerTag });
163
+ }
164
+ async addJob(queue, data, opts) {
165
+ if (!this.connected || !this.conn)
166
+ throw new Error("[blok][rabbitmq] not connected. Call connect() first.");
167
+ let channel = this.channels.get(queue)?.channel;
168
+ if (!channel) {
169
+ channel = await this.conn.createChannel();
170
+ await channel.assertQueue(queue, { durable: true });
171
+ }
172
+ const messageId = opts?.jobId ?? uuid();
173
+ const headers = {};
174
+ if (typeof opts?.delay === "number")
175
+ headers["x-delay"] = opts.delay;
176
+ const ok = channel.sendToQueue(queue, Buffer.from(typeof data === "string" ? data : JSON.stringify(data)), {
177
+ persistent: true,
178
+ messageId,
179
+ priority: opts?.priority,
180
+ timestamp: Date.now(),
181
+ headers,
182
+ });
183
+ if (!ok) {
184
+ // Channel is in flow-controlled state. The send is still
185
+ // accepted; the channel will emit a 'drain' event when ready.
186
+ // We don't currently surface backpressure to callers.
187
+ }
188
+ return messageId;
189
+ }
190
+ async stopProcessing(queue) {
191
+ const entry = this.channels.get(queue);
192
+ if (!entry)
193
+ return;
194
+ try {
195
+ if (entry.consumerTag)
196
+ await entry.channel.cancel(entry.consumerTag);
197
+ await entry.channel.close();
198
+ }
199
+ catch {
200
+ /* ignore */
201
+ }
202
+ this.channels.delete(queue);
203
+ }
204
+ isConnected() {
205
+ return this.connected;
206
+ }
207
+ async healthCheck() {
208
+ if (!this.connected || !this.conn)
209
+ return false;
210
+ try {
211
+ const channel = await this.conn.createChannel();
212
+ await channel.close();
213
+ return true;
214
+ }
215
+ catch {
216
+ return false;
217
+ }
218
+ }
219
+ async getQueueStats(queue) {
220
+ const counters = this.stats.get(queue) ?? { completed: 0, failed: 0, active: 0 };
221
+ let waiting = 0;
222
+ const entry = this.channels.get(queue);
223
+ if (entry) {
224
+ try {
225
+ const info = await entry.channel.checkQueue(queue);
226
+ waiting = info.messageCount;
227
+ }
228
+ catch {
229
+ /* ignore */
230
+ }
231
+ }
232
+ return {
233
+ waiting,
234
+ active: counters.active,
235
+ completed: counters.completed,
236
+ failed: counters.failed,
237
+ delayed: 0,
238
+ };
239
+ }
240
+ }
241
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiUmFiYml0TVFBZGFwdGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2FkYXB0ZXJzL1JhYmJpdE1RQWRhcHRlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBcUJHO0FBR0gsT0FBTyxFQUFFLEVBQUUsSUFBSSxJQUFJLEVBQUUsTUFBTSxNQUFNLENBQUM7QUE0Q2xDLE1BQU0sT0FBTyxlQUFlO0lBQ2xCLFFBQVEsR0FBRyxVQUFtQixDQUFDO0lBQ3ZCLE1BQU0sQ0FBaUI7SUFDaEMsSUFBSSxHQUE0QixJQUFJLENBQUM7SUFDckMsUUFBUSxHQUFrRSxJQUFJLEdBQUcsRUFBRSxDQUFDO0lBQ3BGLFNBQVMsR0FBRyxLQUFLLENBQUM7SUFDbEIsS0FBSyxHQUFvQyxJQUFJLEdBQUcsRUFBRSxDQUFDO0lBRTNELFlBQVksTUFBZ0M7UUFDM0MsSUFBSSxDQUFDLE1BQU0sR0FBRztZQUNiLEdBQUcsRUFBRSxNQUFNLEVBQUUsR0FBRyxJQUFJLE9BQU8sQ0FBQyxHQUFHLENBQUMsUUFBUSxJQUFJLGtCQUFrQjtZQUM5RCxLQUFLLEVBQUUsTUFBTSxFQUFFLEtBQUssSUFBSSxPQUFPLENBQUMsR0FBRyxDQUFDLFVBQVU7U0FDOUMsQ0FBQztJQUNILENBQUM7SUFFRCxLQUFLLENBQUMsT0FBTztRQUNaLElBQUksSUFBSSxDQUFDLFNBQVM7WUFBRSxPQUFPO1FBQzNCLElBQUksQ0FBQztZQUNKLGdHQUFnRztZQUNoRyxNQUFNLElBQUksR0FBUSxNQUFNLE1BQU0sQ0FBQyxTQUFTLENBQUMsQ0FBQztZQUMxQyxJQUFJLENBQUMsSUFBSSxHQUFHLENBQUMsTUFBTSxJQUFJLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsR0FBRyxFQUFFLEVBQUUsS0FBSyxFQUFFLElBQUksQ0FBQyxNQUFNLENBQUMsS0FBSyxFQUFFLENBQUMsQ0FBcUIsQ0FBQztZQUNwRyxJQUFJLENBQUMs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LENBQUMsS0FBSyxDQUFDLENBQUM7UUFDdkMsSUFBSSxLQUFLLEVBQUUsQ0FBQztZQUNYLElBQUksQ0FBQztnQkFDSixNQUFNLElBQUksR0FBRyxNQUFNLEtBQUssQ0FBQyxPQUFPLENBQUMsVUFBVSxDQUFDLEtBQUssQ0FBQyxDQUFDO2dCQUNuRCxPQUFPLEdBQUcsSUFBSSxDQUFDLFlBQVksQ0FBQztZQUM3QixDQUFDO1lBQUMsTUFBTSxDQUFDO2dCQUNSLFlBQVk7WUFDYixDQUFDO1FBQ0YsQ0FBQztRQUNELE9BQU87WUFDTixPQUFPO1lBQ1AsTUFBTSxFQUFFLFFBQVEsQ0FBQyxNQUFNO1lBQ3ZCLFNBQVMsRUFBRSxRQUFRLENBQUMsU0FBUztZQUM3QixNQUFNLEVBQUUsUUFBUSxDQUFDLE1BQU07WUFDdkIsT0FBTyxFQUFFLENBQUM7U0FDVixDQUFDO0lBQ0gsQ0FBQztDQUNEIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBSYWJiaXRNUUFkYXB0ZXIg4oCUIHYwLjcgUFIgNSDigJQgV29ya2VyIGFkYXB0ZXIgYmFja2VkIGJ5IFJhYmJpdE1RIHZpYVxuICogdGhlIGBhbXFwbGliYCBkcml2ZXIuIERpcmVjdC1leGNoYW5nZSAvIHF1ZXVlIG1vZGVsIOKAlCB0aGUgYHF1ZXVlYFxuICogZmllbGQgbWFwcyB0byBhbiBBTVFQIHF1ZXVlIG5hbWU7IG1lc3NhZ2VzIGFyZSBjb25zdW1lZCB3aXRoXG4gKiBtYW51YWwgQUNLLiBUaGUgZGVmYXVsdCBleGNoYW5nZSAoYFwiXCJgKSBpcyB1c2VkIGZvciBwdWJsaXNoaW5nLlxuICpcbiAqIEZlYXR1cmVzOlxuICogICAtIENvbmN1cnJlbmN5IHZpYSBgcHJlZmV0Y2goTilgIHBlciBjaGFubmVsLlxuICogICAtIFJldHJpZXMgdmlhIGBuYWNrYCB3aXRoIHJlcXVldWU9dHJ1ZSB1bnRpbCBgcmV0cmllc2AgaXMgaGl0LCB0aGVuXG4gKiAgICAgZHJvcCBvciBETFEtcm91dGUgYmFzZWQgb24gYGRlYWRMZXR0ZXJRdWV1ZWAgY29uZmlnLlxuICogICAtIFByaW9yaXRpZXMgdmlhIHRoZSBgeC1tYXgtcHJpb3JpdHlgIHF1ZXVlIGFyZyAoQU1RUCBzdGFuZGFyZCkuXG4gKiAgIC0gRGVsYXllZCBkZWxpdmVyeSB2aWEgdGhlIGB4LWRlbGF5ZWQtbWVzc2FnZWAgcGx1Z2luIHdoZW5cbiAqICAgICBhdmFpbGFibGU7IGZhbGxzIGJhY2sgdG8gaW1tZWRpYXRlIGRpc3BhdGNoIG90aGVyd2lzZS5cbiAqXG4gKiBSZXF1aXJlcyBgYW1xcGxpYmAgYXMgYSBwZWVyIGRlcGVuZGVuY3k6XG4gKlxuICogICAgIGJ1biBhZGQgYW1xcGxpYlxuICpcbiAqIEVudmlyb25tZW50IHZhcmlhYmxlczpcbiAqICAgLSBgQU1RUF9VUkxgIOKAlCBmdWxsIEFNUVAgY29ubmVjdGlvbiBzdHJpbmcgKGRlZmF1bHQgYGFtcXA6Ly9sb2NhbGhvc3RgKS5cbiAqICAgLSBgQU1RUF9WSE9TVGAg4oCUIHZpcnR1YWwgaG9zdCAoZGVmYXVsdCBgL2ApLlxuICovXG5cbmltcG9ydCB0eXBlIHsgV29ya2VyVHJpZ2dlck9wdHMgfSBmcm9tIFwiQGJsb2tqcy9oZWxwZXJcIjtcbmltcG9ydCB7IHY0IGFzIHV1aWQgfSBmcm9tIFwidXVpZFwiO1xuaW1wb3J0IHR5cGUgeyBXb3JrZXJBZGFwdGVyLCBXb3JrZXJKb2IsIFdvcmtlclF1ZXVlU3RhdHMgfSBmcm9tIFwiLi4vV29ya2VyVHJpZ2dlclwiO1xuXG5leHBvcnQgaW50ZXJmYWNlIFJhYmJpdE1RQ29uZmlnIHtcblx0dXJsOiBzdHJpbmc7XG5cdHZob3N0Pzogc3RyaW5nO1xufVxuXG5pbnRlcmZhY2UgUmFiYml0Q2hhbm5lbCB7XG5cdHByZWZldGNoKG46IG51bWJlcik6IFByb21pc2U8dm9pZD47XG5cdGFzc2VydFF1ZXVlKFxuXHRcdG5hbWU6IHN0cmluZyxcblx0XHRvcHRzPzogUmVjb3JkPHN0cmluZywgdW5rbm93bj4sXG5cdCk6IFByb21pc2U8eyBxdWV1ZTogc3RyaW5nOyBtZXNzYWdlQ291bnQ6IG51bWJlcjsgY29uc3VtZXJDb3VudDogbnVtYmVyIH0+O1xuXHRjaGVja1F1ZXVlKG5hbWU6IHN0cmluZyk6IFByb21pc2U8eyBxdWV1ZTogc3RyaW5nOyBtZXNzYWdlQ291bnQ6IG51bWJlcjsgY29uc3VtZXJDb3VudDogbnVtYmVyIH0+O1xuXHRjb25zdW1lKFxuXHRcdHF1ZXVlOiBzdHJpbmcsXG5cdFx0Y2I6IChcblx0XHRcdG1zZzoge1xuXHRcdFx0XHRjb250ZW50OiBCdWZmZXI7XG5cdFx0XHRcdGZpZWxkczogeyBkZWxpdmVyeVRhZzogbnVtYmVyOyByZWRlbGl2ZXJlZDogYm9vbGVhbiB9O1xuXHRcdFx0XHRwcm9wZXJ0aWVzOiB7IG1lc3NhZ2VJZD86IHN0cmluZzsgcHJpb3JpdHk/OiBudW1iZXI7IHRpbWVzdGFtcD86IG51bWJlcjsgaGVhZGVycz86IFJlY29yZDxzdHJpbmcsIHVua25vd24+IH07XG5cdFx0XHR9IHwgbnVsbCxcblx0XHQpID0+IHZvaWQsXG5cdFx0b3B0cz86IHsgbm9BY2s/OiBib29sZWFuOyBjb25zdW1lclRhZz86IHN0cmluZyB9LFxuXHQpOiBQcm9taXNlPHsgY29uc3VtZXJUYWc6IHN0cmluZyB9Pjtcblx0Y2FuY2VsKGNvbnN1bWVyVGFnOiBzdHJpbmcpOiBQcm9taXNlPHZvaWQ+O1xuXHRhY2sobXNnOiB7IGZpZWxkczogeyBkZWxpdmVyeVRhZzogbnVtYmVyIH0gfSk6IHZvaWQ7XG5cdG5hY2sobXNnOiB7IGZpZWxkczogeyBkZWxpdmVyeVRhZzogbnVtYmVyIH0gfSwgYWxsVXBUbz86IGJvb2xlYW4sIHJlcXVldWU/OiBib29sZWFuKTogdm9pZDtcblx0c2VuZFRvUXVldWUocXVldWU6IHN0cmluZywgY29udGVudDogQnVmZmVyLCBvcHRzPzogUmVjb3JkPHN0cmluZywgdW5rbm93bj4pOiBib29sZWFuO1xuXHRjbG9zZSgpOiBQcm9taXNlPHZvaWQ+O1xufVxuXG5pbnRlcmZhY2UgUmFiYml0Q29ubmVjdGlvbiB7XG5cdGNyZWF0ZUNoYW5uZWwoKTogUHJvbWlzZTxSYWJiaXRDaGFubmVsPjtcblx0Y2xvc2UoKTogUHJvbWlzZTx2b2lkPjtcbn1cblxuaW50ZXJmYWNlIFF1ZXVlU3RhdHNDb3VudGVycyB7XG5cdGNvbXBsZXRlZDogbnVtYmVyO1xuXHRmYWlsZWQ6IG51bWJlcjtcblx0YWN0aXZlOiBudW1iZXI7XG59XG5cbmV4cG9ydCBjbGFzcyBSYWJiaXRNUUFkYXB0ZXIgaW1wbGVtZW50cyBXb3JrZXJBZGFwdGVyIHtcblx0cmVhZG9ubHkgcHJvdmlkZXIgPSBcInJhYmJpdG1xXCIgYXMgY29uc3Q7XG5cdHByaXZhdGUgcmVhZG9ubHkgY29uZmlnOiBSYWJiaXRNUUNvbmZpZztcblx0cHJpdmF0ZSBjb25uOiBSYWJiaXRDb25uZWN0aW9uIHwgbnVsbCA9IG51bGw7XG5cdHByaXZhdGUgY2hhbm5lbHM6IE1hcDxzdHJpbmcsIHsgY2hhbm5lbDogUmFiYml0Q2hhbm5lbDsgY29uc3VtZXJUYWc/OiBzdHJpbmcgfT4gPSBuZXcgTWFwKCk7XG5cdHByaXZhdGUgY29ubmVjdGVkID0gZmFsc2U7XG5cdHByaXZhdGUgc3RhdHM6IE1hcDxzdHJpbmcsIFF1ZXVlU3RhdHNDb3VudGVycz4gPSBuZXcgTWFwKCk7XG5cblx0Y29uc3RydWN0b3IoY29uZmlnPzogUGFydGlhbDxSYWJiaXRNUUNvbmZpZz4pIHtcblx0XHR0aGlzLmNvbmZpZyA9IHtcblx0XHRcdHVybDogY29uZmlnPy51cmwgPz8gcHJvY2Vzcy5lbnYuQU1RUF9VUkwgPz8gXCJhbXFwOi8vbG9jYWxob3N0XCIsXG5cdFx0XHR2aG9zdDogY29uZmlnPy52aG9zdCA/PyBwcm9jZXNzLmVudi5BTVFQX1ZIT1NULFxuXHRcdH07XG5cdH1cblxuXHRhc3luYyBjb25uZWN0KCk6IFByb21pc2U8dm9pZD4ge1xuXHRcdGlmICh0aGlzLmNvbm5lY3RlZCkgcmV0dXJuO1xuXHRcdHRyeSB7XG5cdFx0XHQvLyBiaW9tZS1pZ25vcmUgbGludC9zdXNwaWNpb3VzL25vRXhwbGljaXRBbnk6IGFtcXBsaWIncyBjb25uZWN0IHJldHVybnMgYSBsb29zZSBDb25uZWN0aW9uTGlrZS5cblx0XHRcdGNvbnN0IGFtcXA6IGFueSA9IGF3YWl0IGltcG9ydChcImFtcXBsaWJcIik7XG5cdFx0XHR0aGlzLmNvbm4gPSAoYXdhaXQgYW1xcC5jb25uZWN0KHRoaXMuY29uZmlnLnVybCwgeyB2aG9zdDogdGhpcy5jb25maWcudmhvc3QgfSkpIGFzIFJhYmJpdENvbm5lY3Rpb247XG5cdFx0XHR0aGlzLmNvbm5lY3RlZCA9IHRydWU7XG5cdFx0fSBjYXRjaCAoZXJyKSB7XG5cdFx0XHR0aHJvdyBuZXcgRXJyb3IoXG5cdFx0XHRcdGBbYmxva11bcmFiYml0bXFdIGNvbm5lY3QgZmFpbGVkOiAkeyhlcnIgYXMgRXJyb3IpLm1lc3NhZ2V9LiBJbnN0YWxsIGFtcXBsaWIgYXMgYSBwZWVyIGRlcGVuZGVuY3k6IGJ1biBhZGQgYW1xcGxpYmAsXG5cdFx0XHQpO1xuXHRcdH1cblx0fVxuXG5cdGFzeW5jIGRpc2Nvbm5lY3QoKTogUHJvbWlzZTx2b2lkPiB7XG5cdFx0aWYgKCF0aGlzLmNvbm5lY3RlZCkgcmV0dXJuO1xuXHRcdGZvciAoY29uc3QgWywgZW50cnldIG9mIHRoaXMuY2hhbm5lbHMpIHtcblx0XHRcdHRyeSB7XG5cdFx0XHRcdGlmIChlbnRyeS5jb25zdW1lclRhZykgYXdhaXQgZW50cnkuY2hhbm5lbC5jYW5jZWwoZW50cnkuY29uc3VtZXJUYWcpO1xuXHRcdFx0XHRhd2FpdCBlbnRyeS5jaGFubmVsLmNsb3NlKCk7XG5cdFx0XHR9IGNhdGNoIHtcblx0XHRcdFx0LyogaWdub3JlICovXG5cdFx0XHR9XG5cdFx0fVxuXHRcdHRoaXMuY2hhbm5lbHMuY2xlYXIoKTtcblx0XHR0cnkge1xuXHRcdFx0YXdhaXQgdGhpcy5jb25uPy5jbG9zZSgpO1xuXHRcdH0gY2F0Y2gge1xuXHRcdFx0LyogaWdub3JlICovXG5cdFx0fVxuXHRcdHRoaXMuY29ubiA9IG51bGw7XG5cdFx0dGhpcy5jb25uZWN0ZWQgPSBmYWxzZTtcblx0fVxuXG5cdGFzeW5jIHByb2Nlc3MoY29uZmlnOiBXb3JrZXJUcmlnZ2VyT3B0cywgaGFuZGxlcjogKGpvYjogV29ya2VySm9iKSA9PiBQcm9taXNlPHZvaWQ+KTogUHJvbWlzZTx2b2lkPiB7XG5cdFx0aWYgKCF0aGlzLmNvbm5lY3RlZCB8fCAhdGhpcy5jb25uKSB0aHJvdyBuZXcgRXJyb3IoXCJbYmxva11bcmFiYml0bXFdIG5vdCBjb25uZWN0ZWQuIENhbGwgY29ubmVjdCgpIGZpcnN0LlwiKTtcblx0XHRjb25zdCBjaGFubmVsID0gYXdhaXQgdGhpcy5jb25uLmNyZWF0ZUNoYW5uZWwoKTtcblx0XHRhd2FpdCBjaGFubmVsLnByZWZldGNoKGNvbmZpZy5jb25jdXJyZW5jeSA/PyAxKTtcblxuXHRcdGNvbnN0IHF1ZXVlQXJnczogUmVjb3JkPHN0cmluZywgdW5rbm93bj4gPSB7fTtcblx0XHRpZiAoY29uZmlnLmRlYWRMZXR0ZXJRdWV1ZSkge1xuXHRcdFx0cXVldWVBcmdzW1wieC1kZWFkLWxldHRlci1leGNoYW5nZVwiXSA9IFwiXCI7XG5cdFx0XHRxdWV1ZUFyZ3NbXCJ4LWRlYWQtbGV0dGVyLXJvdXRpbmcta2V5XCJdID0gY29uZmlnLmRlYWRMZXR0ZXJRdWV1ZTtcblx0XHRcdGF3YWl0IGNoYW5uZWwuYXNzZXJ0UXVldWUoY29uZmlnLmRlYWRMZXR0ZXJRdWV1ZSwgeyBkdXJhYmxlOiB0cnVlIH0pO1xuXHRcdH1cblx0XHRhd2FpdCBjaGFubmVsLmFzc2VydFF1ZXVlKGNvbmZpZy5xdWV1ZSwgeyBkdXJhYmxlOiB0cnVlLCBhcmd1bWVudHM6IHF1ZXVlQXJncyB9KTtcblxuXHRcdHRoaXMuc3RhdHMuc2V0KGNvbmZpZy5xdWV1ZSwgeyBjb21wbGV0ZWQ6IDAsIGZhaWxlZDogMCwgYWN0aXZlOiAwIH0pO1xuXHRcdGNvbnN0IHN0YXRzID0gdGhpcy5zdGF0cy5nZXQoY29uZmlnLnF1ZXVlKSBhcyBRdWV1ZVN0YXRzQ291bnRlcnM7XG5cdFx0Y29uc3QgbWF4QXR0ZW1wdHMgPSAoY29uZmlnLnJldHJpZXMgPz8gMykgKyAxO1xuXG5cdFx0Y29uc3QgeyBjb25zdW1lclRhZyB9ID0gYXdhaXQgY2hhbm5lbC5jb25zdW1lKFxuXHRcdFx0Y29uZmlnLnF1ZXVlLFxuXHRcdFx0KG1zZykgPT4ge1xuXHRcdFx0XHRpZiAoIW1zZykgcmV0dXJuO1xuXHRcdFx0XHR2b2lkIChhc3luYyAoKSA9PiB7XG5cdFx0XHRcdFx0Y29uc3QgcGF5bG9hZFN0cmluZyA9IG1zZy5jb250ZW50LnRvU3RyaW5nKFwidXRmOFwiKTtcblx0XHRcdFx0XHRsZXQgZGF0YTogdW5rbm93bjtcblx0XHRcdFx0XHR0cnkge1xuXHRcdFx0XHRcdFx0ZGF0YSA9IHBheWxvYWRTdHJpbmcubGVuZ3RoID4gMCA/IEpTT04ucGFyc2UocGF5bG9hZFN0cmluZykgOiBudWxsO1xuXHRcdFx0XHRcdH0gY2F0Y2gge1xuXHRcdFx0XHRcdFx0ZGF0YSA9IHBheWxvYWRTdHJpbmc7XG5cdFx0XHRcdFx0fVxuXHRcdFx0XHRcdGNvbnN0IGhlYWRlcnM6IFJlY29yZDxzdHJpbmcsIHN0cmluZz4gPSB7fTtcblx0XHRcdFx0XHRmb3IgKGNvbnN0IFtrLCB2XSBvZiBPYmplY3QuZW50cmllcyhtc2cucHJvcGVydGllcy5oZWFkZXJzID8/IHt9KSkgaGVhZGVyc1trXSA9IFN0cmluZyh2KTtcblx0XHRcdFx0XHRjb25zdCBhdHRlbXB0cyA9IE51bWJlci5wYXJzZUludChTdHJpbmcobXNnLnByb3BlcnRpZXMuaGVhZGVycz8uW1wieC1ibG9rLWF0dGVtcHRcIl0gPz8gMCksIDEwKTtcblx0XHRcdFx0XHQvLyBUcmFja3Mgd2hldGhlciB0aGUgbWVzc2FnZSBoYXMgYWxyZWFkeSBiZWVuIGFjay9uYWNrJ2QgdmlhXG5cdFx0XHRcdFx0Ly8gdGhlIFdvcmtlckpvYiBBUEkgKGBjb21wbGV0ZWAgLyBgZmFpbGApLiBUaGUgd3JhcHBpbmdcblx0XHRcdFx0XHQvLyB0cnkvY2F0Y2ggYmVsb3cgYWxzbyB3YW50cyB0byBhY2sgb24gaGFuZGxlciBzdWNjZXNzIGFuZFxuXHRcdFx0XHRcdC8vIG5hY2sgb24gaGFuZGxlciB0aHJvdyDigJQgd2l0aG91dCB0aGlzIGZsYWcgd2UnZFxuXHRcdFx0XHRcdC8vIGRvdWJsZS1hY2sgdGhlIHNhbWUgZGVsaXZlcnkgdGFnIGFuZCBSYWJiaXQgY2xvc2VzIHRoZVxuXHRcdFx0XHRcdC8vIGNoYW5uZWwgd2l0aCBQUkVDT05ESVRJT05fRkFJTEVEIChjYXVnaHQgYnkgdGhlXG5cdFx0XHRcdFx0Ly8gcmVhbC1icm9rZXIgaW50ZWdyYXRpb24gdGVzdCBpblxuXHRcdFx0XHRcdC8vIGBfX3Rlc3RzX18vaW50ZWdyYXRpb24vcmFiYml0bXEtYWRhcHRlci5yZWFsLXJhYmJpdG1xLnRlc3QudHNgKS5cblx0XHRcdFx0XHRsZXQgc2V0dGxlZCA9IGZhbHNlO1xuXHRcdFx0XHRcdGNvbnN0IGpvYjogV29ya2VySm9iID0ge1xuXHRcdFx0XHRcdFx0aWQ6IG1zZy5wcm9wZXJ0aWVzLm1lc3NhZ2VJZCA/PyBgJHtjb25maWcucXVldWV9OiR7bXNnLmZpZWxkcy5kZWxpdmVyeVRhZ31gLFxuXHRcdFx0XHRcdFx0ZGF0YSxcblx0XHRcdFx0XHRcdGhlYWRlcnMsXG5cdFx0XHRcdFx0XHRxdWV1ZTogY29uZmlnLnF1ZXVlLFxuXHRcdFx0XHRcdFx0cHJpb3JpdHk6IG1zZy5wcm9wZXJ0aWVzLnByaW9yaXR5ID8/IGNvbmZpZy5wcmlvcml0eSA/PyAwLFxuXHRcdFx0XHRcdFx0YXR0ZW1wdHMsXG5cdFx0XHRcdFx0XHRtYXhSZXRyaWVzOiBjb25maWcucmV0cmllcyA/PyAzLFxuXHRcdFx0XHRcdFx0Y3JlYXRlZEF0OiBtc2cucHJvcGVydGllcy50aW1lc3RhbXAgPyBuZXcgRGF0ZShtc2cucHJvcGVydGllcy50aW1lc3RhbXApIDogbmV3IERhdGUoKSxcblx0XHRcdFx0XHRcdHRpbWVvdXQ6IGNvbmZpZy50aW1lb3V0LFxuXHRcdFx0XHRcdFx0cmF3OiBtc2csXG5cdFx0XHRcdFx0XHRjb21wbGV0ZTogYXN5bmMgKCkgPT4ge1xuXHRcdFx0XHRcdFx0XHRpZiAoc2V0dGxlZCkgcmV0dXJuO1xuXHRcdFx0XHRcdFx0XHRjaGFubmVsLmFjayhtc2cpO1xuXHRcdFx0XHRcdFx0XHRzdGF0cy5jb21wbGV0ZWQgKz0gMTtcblx0XHRcdFx0XHRcdFx0c2V0dGxlZCA9IHRydWU7XG5cdFx0XHRcdFx0XHR9LFxuXHRcdFx0XHRcdFx0ZmFpbDogYXN5bmMgKGVycjogRXJyb3IsIHJlcXVldWU/OiBib29sZWFuKSA9PiB7XG5cdFx0XHRcdFx0XHRcdGlmIChzZXR0bGVkKSByZXR1cm47XG5cdFx0XHRcdFx0XHRcdHN0YXRzLmZhaWxlZCArPSAxO1xuXHRcdFx0XHRcdFx0XHRjb25zdCBleGNlZWRlZCA9IGF0dGVtcHRzICsgMSA+PSBtYXhBdHRlbXB0cztcblx0XHRcdFx0XHRcdFx0Y2hhbm5lbC5uYWNrKG1zZywgZmFsc2UsICFleGNlZWRlZCAmJiByZXF1ZXVlICE9PSBmYWxzZSk7XG5cdFx0XHRcdFx0XHRcdHNldHRsZWQgPSB0cnVlO1xuXHRcdFx0XHRcdFx0fSxcblx0XHRcdFx0XHR9O1xuXHRcdFx0XHRcdHN0YXRzLmFjdGl2ZSArPSAxO1xuXHRcdFx0XHRcdHRyeSB7XG5cdFx0XHRcdFx0XHRhd2FpdCBoYW5kbGVyKGpvYik7XG5cdFx0XHRcdFx0XHRpZiAoIXNldHRsZWQgJiYgY29uZmlnLmFjayAhPT0gZmFsc2UpIHtcblx0XHRcdFx0XHRcdFx0Y2hhbm5lbC5hY2sobXNnKTtcblx0XHRcdFx0XHRcdFx0c3RhdHMuY29tcGxldGVkICs9IDE7XG5cdFx0XHRcdFx0XHRcdHNldHRsZWQgPSB0cnVlO1xuXHRcdFx0XHRcdFx0fVxuXHRcdFx0XHRcdH0gY2F0Y2gge1xuXHRcdFx0XHRcdFx0aWYgKCFzZXR0bGVkKSB7XG5cdFx0XHRcdFx0XHRcdHN0YXRzLmZhaWxlZCArPSAxO1xuXHRcdFx0XHRcdFx0XHRjb25zdCBleGNlZWRlZCA9IGF0dGVtcHRzICsgMSA+PSBtYXhBdHRlbXB0cztcblx0XHRcdFx0XHRcdFx0Y2hhbm5lbC5uYWNrKG1zZywgZmFsc2UsICFleGNlZWRlZCk7XG5cdFx0XHRcdFx0XHRcdHNldHRsZWQgPSB0cnVlO1xuXHRcdFx0XHRcdFx0fVxuXHRcdFx0XHRcdH0gZmluYWxseSB7XG5cdFx0XHRcdFx0XHRzdGF0cy5hY3RpdmUgPSBNYXRoLm1heCgwLCBzdGF0cy5hY3RpdmUgLSAxKTtcblx0XHRcdFx0XHR9XG5cdFx0XHRcdH0pKCk7XG5cdFx0XHR9LFxuXHRcdFx0eyBub0FjazogY29uZmlnLmFjayA9PT0gZmFsc2UgfSxcblx0XHQpO1xuXHRcdHRoaXMuY2hhbm5lbHMuc2V0KGNvbmZpZy5xdWV1ZSwgeyBjaGFubmVsLCBjb25zdW1lclRhZyB9KTtcblx0fVxuXG5cdGFzeW5jIGFkZEpvYihcblx0XHRxdWV1ZTogc3RyaW5nLFxuXHRcdGRhdGE6IHVua25vd24sXG5cdFx0b3B0cz86IHsgcHJpb3JpdHk/OiBudW1iZXI7IGRlbGF5PzogbnVtYmVyOyByZXRyaWVzPzogbnVtYmVyOyB0aW1lb3V0PzogbnVtYmVyOyBqb2JJZD86IHN0cmluZyB9LFxuXHQpOiBQcm9taXNlPHN0cmluZz4ge1xuXHRcdGlmICghdGhpcy5jb25uZWN0ZWQgfHwgIXRoaXMuY29ubikgdGhyb3cgbmV3IEVycm9yKFwiW2Jsb2tdW3JhYmJpdG1xXSBub3QgY29ubmVjdGVkLiBDYWxsIGNvbm5lY3QoKSBmaXJzdC5cIik7XG5cdFx0bGV0IGNoYW5uZWwgPSB0aGlzLmNoYW5uZWxzLmdldChxdWV1ZSk/LmNoYW5uZWw7XG5cdFx0aWYgKCFjaGFubmVsKSB7XG5cdFx0XHRjaGFubmVsID0gYXdhaXQgdGhpcy5jb25uLmNyZWF0ZUNoYW5uZWwoKTtcblx0XHRcdGF3YWl0IGNoYW5uZWwuYXNzZXJ0UXVldWUocXVldWUsIHsgZHVyYWJsZTogdHJ1ZSB9KTtcblx0XHR9XG5cdFx0Y29uc3QgbWVzc2FnZUlkID0gb3B0cz8uam9iSWQgPz8gdXVpZCgpO1xuXHRcdGNvbnN0IGhlYWRlcnM6IFJlY29yZDxzdHJpbmcsIHVua25vd24+ID0ge307XG5cdFx0aWYgKHR5cGVvZiBvcHRzPy5kZWxheSA9PT0gXCJudW1iZXJcIikgaGVhZGVyc1tcIngtZGVsYXlcIl0gPSBvcHRzLmRlbGF5O1xuXHRcdGNvbnN0IG9rID0gY2hhbm5lbC5zZW5kVG9RdWV1ZShxdWV1ZSwgQnVmZmVyLmZyb20odHlwZW9mIGRhdGEgPT09IFwic3RyaW5nXCIgPyBkYXRhIDogSlNPTi5zdHJpbmdpZnkoZGF0YSkpLCB7XG5cdFx0XHRwZXJzaXN0ZW50OiB0cnVlLFxuXHRcdFx0bWVzc2FnZUlkLFxuXHRcdFx0cHJpb3JpdHk6IG9wdHM/LnByaW9yaXR5LFxuXHRcdFx0dGltZXN0YW1wOiBEYXRlLm5vdygpLFxuXHRcdFx0aGVhZGVycyxcblx0XHR9KTtcblx0XHRpZiAoIW9rKSB7XG5cdFx0XHQvLyBDaGFubmVsIGlzIGluIGZsb3ctY29udHJvbGxlZCBzdGF0ZS4gVGhlIHNlbmQgaXMgc3RpbGxcblx0XHRcdC8vIGFjY2VwdGVkOyB0aGUgY2hhbm5lbCB3aWxsIGVtaXQgYSAnZHJhaW4nIGV2ZW50IHdoZW4gcmVhZHkuXG5cdFx0XHQvLyBXZSBkb24ndCBjdXJyZW50bHkgc3VyZmFjZSBiYWNrcHJlc3N1cmUgdG8gY2FsbGVycy5cblx0XHR9XG5cdFx0cmV0dXJuIG1lc3NhZ2VJZDtcblx0fVxuXG5cdGFzeW5jIHN0b3BQcm9jZXNzaW5nKHF1ZXVlOiBzdHJpbmcpOiBQcm9taXNlPHZvaWQ+IHtcblx0XHRjb25zdCBlbnRyeSA9IHRoaXMuY2hhbm5lbHMuZ2V0KHF1ZXVlKTtcblx0XHRpZiAoIWVudHJ5KSByZXR1cm47XG5cdFx0dHJ5IHtcblx0XHRcdGlmIChlbnRyeS5jb25zdW1lclRhZykgYXdhaXQgZW50cnkuY2hhbm5lbC5jYW5jZWwoZW50cnkuY29uc3VtZXJUYWcpO1xuXHRcdFx0YXdhaXQgZW50cnkuY2hhbm5lbC5jbG9zZSgpO1xuXHRcdH0gY2F0Y2gge1xuXHRcdFx0LyogaWdub3JlICovXG5cdFx0fVxuXHRcdHRoaXMuY2hhbm5lbHMuZGVsZXRlKHF1ZXVlKTtcblx0fVxuXG5cdGlzQ29ubmVjdGVkKCk6IGJvb2xlYW4ge1xuXHRcdHJldHVybiB0aGlzLmNvbm5lY3RlZDtcblx0fVxuXG5cdGFzeW5jIGhlYWx0aENoZWNrKCk6IFByb21pc2U8Ym9vbGVhbj4ge1xuXHRcdGlmICghdGhpcy5jb25uZWN0ZWQgfHwgIXRoaXMuY29ubikgcmV0dXJuIGZhbHNlO1xuXHRcdHRyeSB7XG5cdFx0XHRjb25zdCBjaGFubmVsID0gYXdhaXQgdGhpcy5jb25uLmNyZWF0ZUNoYW5uZWwoKTtcblx0XHRcdGF3YWl0IGNoYW5uZWwuY2xvc2UoKTtcblx0XHRcdHJldHVybiB0cnVlO1xuXHRcdH0gY2F0Y2gge1xuXHRcdFx0cmV0dXJuIGZhbHNlO1xuXHRcdH1cblx0fVxuXG5cdGFzeW5jIGdldFF1ZXVlU3RhdHMocXVldWU6IHN0cmluZyk6IFByb21pc2U8V29ya2VyUXVldWVTdGF0cz4ge1xuXHRcdGNvbnN0IGNvdW50ZXJzID0gdGhpcy5zdGF0cy5nZXQocXVldWUpID8/IHsgY29tcGxldGVkOiAwLCBmYWlsZWQ6IDAsIGFjdGl2ZTogMCB9O1xuXHRcdGxldCB3YWl0aW5nID0gMDtcblx0XHRjb25zdCBlbnRyeSA9IHRoaXMuY2hhbm5lbHMuZ2V0KHF1ZXVlKTtcblx0XHRpZiAoZW50cnkpIHtcblx0XHRcdHRyeSB7XG5cdFx0XHRcdGNvbnN0IGluZm8gPSBhd2FpdCBlbnRyeS5jaGFubmVsLmNoZWNrUXVldWUocXVldWUpO1xuXHRcdFx0XHR3YWl0aW5nID0gaW5mby5tZXNzYWdlQ291bnQ7XG5cdFx0XHR9IGNhdGNoIHtcblx0XHRcdFx0LyogaWdub3JlICovXG5cdFx0XHR9XG5cdFx0fVxuXHRcdHJldHVybiB7XG5cdFx0XHR3YWl0aW5nLFxuXHRcdFx0YWN0aXZlOiBjb3VudGVycy5hY3RpdmUsXG5cdFx0XHRjb21wbGV0ZWQ6IGNvdW50ZXJzLmNvbXBsZXRlZCxcblx0XHRcdGZhaWxlZDogY291bnRlcnMuZmFpbGVkLFxuXHRcdFx0ZGVsYXllZDogMCxcblx0XHR9O1xuXHR9XG59XG4iXX0=
@@ -0,0 +1,64 @@
1
+ /**
2
+ * RedisStreamsAdapter — v0.7 PR 5 — Worker adapter backed by Redis
3
+ * Streams via `ioredis`. Consumes from a stream (the `queue` field)
4
+ * with a consumer group (`consumerGroup`); produces via `XADD`.
5
+ *
6
+ * Semantics:
7
+ * - **Consumer groups**: required. `consumerGroup` defaults to
8
+ * `"${queue}-group"`. The group is auto-created (`MKSTREAM` on the
9
+ * stream too) on first `process()` call.
10
+ * - **Consumer name**: per-process uuid, so multiple instances of
11
+ * the same worker process don't share pending entries.
12
+ * - **Retries**: pending entries are XACK'd on success / left
13
+ * unacked on failure (visible in `XPENDING`). A redrive loop
14
+ * reads pending entries older than `timeout` and re-delivers
15
+ * to the current consumer.
16
+ * - **Auto-claim**: skipped in v1 — operators should run XAUTOCLAIM
17
+ * periodically out of band.
18
+ *
19
+ * Requires `ioredis` as a peer dependency:
20
+ *
21
+ * bun add ioredis
22
+ *
23
+ * Environment variables:
24
+ * - `REDIS_HOST` (default `localhost`)
25
+ * - `REDIS_PORT` (default `6379`)
26
+ * - `REDIS_PASSWORD`
27
+ * - `REDIS_DB`
28
+ */
29
+ import type { WorkerTriggerOpts } from "@blokjs/helper";
30
+ import type { WorkerAdapter, WorkerJob, WorkerQueueStats } from "../WorkerTrigger";
31
+ export interface RedisStreamsConfig {
32
+ host: string;
33
+ port: number;
34
+ password?: string;
35
+ db?: number;
36
+ blockMs: number;
37
+ count: number;
38
+ }
39
+ export declare class RedisStreamsAdapter implements WorkerAdapter {
40
+ readonly provider: "redis";
41
+ private readonly config;
42
+ private client;
43
+ private runners;
44
+ private connected;
45
+ private stats;
46
+ private consumerName;
47
+ constructor(config?: Partial<RedisStreamsConfig>);
48
+ connect(): Promise<void>;
49
+ disconnect(): Promise<void>;
50
+ process(config: WorkerTriggerOpts, handler: (job: WorkerJob) => Promise<void>): Promise<void>;
51
+ private runConsumerLoop;
52
+ private fieldsToObject;
53
+ addJob(queue: string, data: unknown, opts?: {
54
+ priority?: number;
55
+ delay?: number;
56
+ retries?: number;
57
+ timeout?: number;
58
+ jobId?: string;
59
+ }): Promise<string>;
60
+ stopProcessing(queue: string): Promise<void>;
61
+ isConnected(): boolean;
62
+ healthCheck(): Promise<boolean>;
63
+ getQueueStats(queue: string): Promise<WorkerQueueStats>;
64
+ }
@@ -0,0 +1,240 @@
1
+ /**
2
+ * RedisStreamsAdapter — v0.7 PR 5 — Worker adapter backed by Redis
3
+ * Streams via `ioredis`. Consumes from a stream (the `queue` field)
4
+ * with a consumer group (`consumerGroup`); produces via `XADD`.
5
+ *
6
+ * Semantics:
7
+ * - **Consumer groups**: required. `consumerGroup` defaults to
8
+ * `"${queue}-group"`. The group is auto-created (`MKSTREAM` on the
9
+ * stream too) on first `process()` call.
10
+ * - **Consumer name**: per-process uuid, so multiple instances of
11
+ * the same worker process don't share pending entries.
12
+ * - **Retries**: pending entries are XACK'd on success / left
13
+ * unacked on failure (visible in `XPENDING`). A redrive loop
14
+ * reads pending entries older than `timeout` and re-delivers
15
+ * to the current consumer.
16
+ * - **Auto-claim**: skipped in v1 — operators should run XAUTOCLAIM
17
+ * periodically out of band.
18
+ *
19
+ * Requires `ioredis` as a peer dependency:
20
+ *
21
+ * bun add ioredis
22
+ *
23
+ * Environment variables:
24
+ * - `REDIS_HOST` (default `localhost`)
25
+ * - `REDIS_PORT` (default `6379`)
26
+ * - `REDIS_PASSWORD`
27
+ * - `REDIS_DB`
28
+ */
29
+ import { v4 as uuid } from "uuid";
30
+ export class RedisStreamsAdapter {
31
+ provider = "redis";
32
+ config;
33
+ client = null;
34
+ runners = new Map();
35
+ connected = false;
36
+ stats = new Map();
37
+ consumerName = `blok-${uuid().slice(0, 8)}`;
38
+ constructor(config) {
39
+ this.config = {
40
+ host: config?.host ?? process.env.REDIS_HOST ?? "localhost",
41
+ port: config?.port ?? Number.parseInt(process.env.REDIS_PORT ?? "6379", 10),
42
+ password: config?.password ?? process.env.REDIS_PASSWORD,
43
+ db: config?.db ?? Number.parseInt(process.env.REDIS_DB ?? "0", 10),
44
+ blockMs: config?.blockMs ?? 5000,
45
+ count: config?.count ?? 10,
46
+ };
47
+ }
48
+ async connect() {
49
+ if (this.connected)
50
+ return;
51
+ try {
52
+ // biome-ignore lint/suspicious/noExplicitAny: ioredis is a runtime peer dep.
53
+ const ioredis = await import("ioredis");
54
+ const IORedis = ioredis.default ?? ioredis.Redis ?? ioredis;
55
+ this.client = new IORedis({
56
+ host: this.config.host,
57
+ port: this.config.port,
58
+ password: this.config.password,
59
+ db: this.config.db,
60
+ });
61
+ await this.client.ping();
62
+ this.connected = true;
63
+ }
64
+ catch (err) {
65
+ throw new Error(`[blok][redis] connect failed: ${err.message}. Install ioredis as a peer dependency: bun add ioredis`);
66
+ }
67
+ }
68
+ async disconnect() {
69
+ if (!this.connected)
70
+ return;
71
+ for (const runner of this.runners.values())
72
+ runner.stop = true;
73
+ // Wait for in-flight loops to drain.
74
+ const drainDeadline = Date.now() + 2000;
75
+ while (Date.now() < drainDeadline) {
76
+ let active = 0;
77
+ for (const r of this.runners.values())
78
+ active += r.loops;
79
+ if (active === 0)
80
+ break;
81
+ await new Promise((r) => setTimeout(r, 50));
82
+ }
83
+ this.runners.clear();
84
+ try {
85
+ await this.client?.quit();
86
+ }
87
+ catch {
88
+ /* ignore */
89
+ }
90
+ this.client = null;
91
+ this.connected = false;
92
+ }
93
+ async process(config, handler) {
94
+ if (!this.connected || !this.client)
95
+ throw new Error("[blok][redis] not connected. Call connect() first.");
96
+ const stream = config.queue;
97
+ const group = config.consumerGroup ?? `${stream}-group`;
98
+ // Create group + stream if missing. XGROUP CREATE with MKSTREAM
99
+ // errors with "BUSYGROUP" if the group already exists — swallow it.
100
+ try {
101
+ await this.client.xgroup("CREATE", stream, group, "$", "MKSTREAM");
102
+ }
103
+ catch (err) {
104
+ if (!/BUSYGROUP/i.test(err.message))
105
+ throw err;
106
+ }
107
+ const runner = { stop: false, loops: 0 };
108
+ this.runners.set(stream, runner);
109
+ this.stats.set(stream, { completed: 0, failed: 0, active: 0 });
110
+ const stats = this.stats.get(stream);
111
+ const concurrency = Math.max(1, config.concurrency ?? 1);
112
+ for (let i = 0; i < concurrency; i += 1) {
113
+ void this.runConsumerLoop(stream, group, config, handler, runner, stats);
114
+ }
115
+ }
116
+ async runConsumerLoop(stream, group, config, handler, runner, stats) {
117
+ if (!this.client)
118
+ return;
119
+ runner.loops += 1;
120
+ try {
121
+ while (!runner.stop) {
122
+ let entries = null;
123
+ try {
124
+ entries = await this.client.xreadgroup("GROUP", group, this.consumerName, "COUNT", String(this.config.count), "BLOCK", String(this.config.blockMs), "STREAMS", stream, ">");
125
+ }
126
+ catch (err) {
127
+ await new Promise((r) => setTimeout(r, 1000));
128
+ continue;
129
+ }
130
+ if (!entries)
131
+ continue;
132
+ for (const [, msgs] of entries) {
133
+ for (const [id, fields] of msgs) {
134
+ if (runner.stop)
135
+ break;
136
+ stats.active += 1;
137
+ try {
138
+ const payload = this.fieldsToObject(fields);
139
+ const dataString = typeof payload.data === "string" ? payload.data : "";
140
+ let data;
141
+ try {
142
+ data = dataString.length > 0 ? JSON.parse(dataString) : null;
143
+ }
144
+ catch {
145
+ data = dataString;
146
+ }
147
+ const job = {
148
+ id,
149
+ data,
150
+ headers: {},
151
+ queue: stream,
152
+ priority: config.priority ?? 0,
153
+ attempts: 0,
154
+ maxRetries: config.retries ?? 0,
155
+ createdAt: new Date(Number.parseInt(id.split("-")[0] ?? String(Date.now()), 10)),
156
+ timeout: config.timeout,
157
+ raw: { id, fields },
158
+ complete: async () => {
159
+ await this.client?.xack(stream, group, id);
160
+ stats.completed += 1;
161
+ },
162
+ fail: async (_err) => {
163
+ stats.failed += 1;
164
+ },
165
+ };
166
+ await handler(job);
167
+ if (config.ack !== false)
168
+ await this.client?.xack(stream, group, id);
169
+ stats.completed += 1;
170
+ }
171
+ catch {
172
+ stats.failed += 1;
173
+ // Pending entry left unacked — picked up by XAUTOCLAIM.
174
+ }
175
+ finally {
176
+ stats.active = Math.max(0, stats.active - 1);
177
+ }
178
+ }
179
+ }
180
+ }
181
+ }
182
+ finally {
183
+ runner.loops -= 1;
184
+ }
185
+ }
186
+ fieldsToObject(fields) {
187
+ const out = {};
188
+ for (let i = 0; i < fields.length; i += 2) {
189
+ out[fields[i]] = fields[i + 1];
190
+ }
191
+ return out;
192
+ }
193
+ async addJob(queue, data, opts) {
194
+ if (!this.connected || !this.client)
195
+ throw new Error("[blok][redis] not connected. Call connect() first.");
196
+ const payload = typeof data === "string" ? data : JSON.stringify(data);
197
+ const args = [];
198
+ if (opts?.jobId)
199
+ args.push("NOMKSTREAM");
200
+ const id = await this.client.xadd(queue, "*", "data", payload, "jobId", opts?.jobId ?? "");
201
+ return id;
202
+ }
203
+ async stopProcessing(queue) {
204
+ const runner = this.runners.get(queue);
205
+ if (runner)
206
+ runner.stop = true;
207
+ }
208
+ isConnected() {
209
+ return this.connected;
210
+ }
211
+ async healthCheck() {
212
+ if (!this.connected || !this.client)
213
+ return false;
214
+ try {
215
+ const pong = await this.client.ping();
216
+ return pong === "PONG";
217
+ }
218
+ catch {
219
+ return false;
220
+ }
221
+ }
222
+ async getQueueStats(queue) {
223
+ const counters = this.stats.get(queue) ?? { completed: 0, failed: 0, active: 0 };
224
+ let waiting = 0;
225
+ try {
226
+ waiting = (await this.client?.xlen(queue)) ?? 0;
227
+ }
228
+ catch {
229
+ /* ignore */
230
+ }
231
+ return {
232
+ waiting,
233
+ active: counters.active,
234
+ completed: counters.completed,
235
+ failed: counters.failed,
236
+ delayed: 0,
237
+ };
238
+ }
239
+ }
240
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiUmVkaXNTdHJlYW1zQWRhcHRlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9hZGFwdGVycy9SZWRpc1N0cmVhbXNBZGFwdGVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0EyQkc7QUFHSCxPQUFPLEVBQUUsRUFBRSxJQUFJLElBQUksRUFBRSxNQUFNLE1BQU0sQ0FBQztBQWtDbEMsTUFBTSxPQUFPLG1CQUFtQjtJQUN0QixRQUFRLEdBQUcsT0FBZ0IsQ0FBQztJQUNwQixNQUFNLENBQXFCO0lBQ3BDLE1BQU0sR0FBdUIsSUFBSSxDQUFDO0lBQ2xDLE9BQU8sR0FBNkIsSUFBSSxHQUFHLEVBQUUsQ0FBQztJQUM5QyxTQUFTLEdBQUcsS0FBSyxDQUFDO0lBQ2xCLEtBQUssR0FBb0MsSUFBSSxHQUFHLEVBQUUsQ0FBQztJQUNuRCxZQUFZLEdBQUcsUUFBUSxJQUFJLEVBQUUsQ0FBQyxLQUFLLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxFQUFFLENBQUM7SUFFcEQsWUFBWSxNQUFvQztRQUMvQyxJQUFJLENBQUMsTUFBTSxHQUFHO1lBQ2IsSUFBSSxFQUFFLE1BQU0sRUFBRSxJQUFJLElBQUksT0FBTyxDQUFDLEdBQUcsQ0FBQyxVQUFVLElBQUksV0FBVztZQUMzRCxJQUFJLEVBQUUsTUFBTSxFQUFFLElBQUksSUFBSSxNQUFNLENBQUMsUUFBUSxDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsVUFBVSxJQUFJLE1BQU0sRUFBRSxFQUFFLENBQUM7WUFDM0UsUUFBUSxFQUFFLE1BQU0sRUFBRSxRQUFRLElBQUksT0FBTyxDQUFDLEdBQUcsQ0FBQyxjQUFjO1lBQ3hELEVBQUUsRUFBRSxNQUFNLEVBQUUsRUFBRSxJQUFJLE1BQU0sQ0FBQyxRQUFRLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxRQUFRLElBQUksR0FBRyxFQUFFLEVBQUUsQ0FBQztZQUNsRSxPQUFPLEVBQUUsTUFBTSxFQUFFLE9BQU8sSUFBSSxJQUFJO1lBQ2hDLEtBQUssRUFBRSxNQUFNLEVBQUUsS0FBSyxJQUFJLEVBQUU7U0FDMUIsQ0FBQztJQUNILENBQUM7SUFFRCxLQUFLLENBQUMsT0FBTztRQUNaLElBQUksSUFBSSxDQUFDLFNBQVM7WUFBRSxPQUFPO1FBQzNCLElBQUksQ0FBQztZQUNKLDZFQUE2RTtZQUM3RSxNQUFNLE9BQU8sR0FBUSxNQUFNLE1BQU0sQ0FBQyxTQUFTLENBQUMsQ0FBQztZQUM3QyxNQUFNLE9BQU8sR0FBRyxPQUFPLENBQUMsT0FBTyxJQUFJLE9BQU8sQ0FBQyxLQUFLLElBQUksT0FBTyxDQUFDO1lBQzVELElBQUksQ0FBQyxNQUFNLEdBQUcsSUFBSSxPQUFPLENBQUM7Z0JBQ3pCLElBQUksRUFBRSxJQUFJLENBQUMsTUFBTSxDQUFDLElBQUk7Z0JBQ3RCLElBQUksRUFBRSxJQUFJLENBQUMsTUFBTSxDQUFDLElBQUk7Z0JBQ3RCLFFBQVEsRUFBRSxJQUFJLENBQUMsTUFBTSxDQUFDLFFBQVE7Z0JBQzlCLEVBQUUsRUFBRSxJQUFJLENBQUMsTUFBTSxDQUFDLEVBQUU7YUFDbEIsQ0FBZ0IsQ0FBQztZQUNsQixNQUFNLElBQUksQ0FBQyxNQUFNLENBQUMsSUFBSSxFQUFFLENBQUM7WUFDekIsSUFBSSxDQUFDLFNBQVMsR0FBRyxJQUFJLENBQUM7UUFDdkIsQ0FBQztRQUFDLE9BQU8sR0FBRyxFQUFFLENBQUM7WUFDZCxNQUFNLElBQUksS0FBSyxDQUNkLGlDQUFrQyxHQUFhLENBQUMsT0FBTyx5REFBeUQsQ0FDaEgsQ0FBQztRQUNILENBQUM7SUFDRixDQUFDO0lBRUQsS0FBSyxDQUFDLFVBQVU7UUFDZixJQUFJLENBQUMsSUFBSSxDQUFDLFNBQVM7WUFBRSxPQUFPO1FBQzVCLEtBQUssTUFBTSxNQUFNLElBQUksSUFBSSxDQUFDLE9BQU8sQ0FBQyxNQUFNLEVBQUU7WUFBRSxNQUFNLENBQUMsSUFBSSxHQUFHLElBQUksQ0FBQztRQUMvRCxxQ0FBcUM7UUFDckMsTUFBTSxhQUFhLEdBQUcsSUFBSSxDQUFDLEdBQUcsRUFBRSxHQUFHLElBQUksQ0FBQztRQUN4QyxPQUFPLElBQUksQ0FBQyxHQUFHLEVBQUUsR0FBRyxhQUFhLEVBQUUsQ0FBQztZQUNuQyxJQUFJLE1BQU0sR0FBRyxDQUFDLENBQUM7WUFDZixLQUFLLE1BQU0sQ0FBQyxJQUFJLElBQUksQ0FBQyxPQUFPLENBQUMsTUFBTSxFQUFFO2dCQUFFLE1BQU0sSUFBSSxDQUFDLENBQUMsS0FBSyxDQUFDO1lBQ3pELElBQUksTUFBTSxLQUFLLENBQUM7Z0JBQUUsTUFBTTtZQUN4QixNQUFNLElBQUksT0FBTyxDQUFDLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxVQUFVLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDLENBQUM7UUFDN0MsQ0FBQztRQUNELElBQUksQ0FBQyxPQUFPLENBQUMsS0FBSyxFQUFFLENBQUM7UUFDckIsSUFBSSxDQUFDO1lBQ0osTUFBTSxJQUFJLENBQUMsTUFBTSxFQUFFLElBQUksRUFBRSxDQUFDO1FBQzNCLENBQUM7UUFBQyxNQUFNLENBQUM7WUFDUixZQUFZO1FBQ2IsQ0FBQztRQUNELElBQUksQ0FBQyxNQUFNLEdBQUcsSUFBSSxDQUFDO1FBQ25CLElBQUksQ0FBQyxTQUFTLEdBQUcsS0FBSyxDQUFDO0lBQ3hCLENBQUM7SUFFRCxLQUFLLENBQUMsT0FBTyxDQUFDLE1BQXlCLEVBQUUsT0FBMEM7UUFDbEYsSUFBSSxDQUFDLElBQUksQ0FBQyxTQUFTLElBQUksQ0FBQyxJQUFJLENBQUMsTUFBTTtZQUFFLE1BQU0sSUFBSSxLQUFLLENBQUMsb0RBQW9ELENBQUMsQ0FBQztRQUMzRyxNQUFNLE1BQU0sR0FBRyxNQUFNLENBQUMsS0FBSyxDQUFDO1FBQzVCLE1BQU0sS0FBSyxHQUFHLE1BQU0sQ0FBQyxhQUFhLElBQUksR0FBRyxNQUFNLFFBQVEsQ0FBQztRQUN4RCxnRUFBZ0U7UUFDaEUsb0VBQW9FO1FBQ3BFLElBQUksQ0FBQztZQUNKLE1BQU0sSUFBSSxDQUFDLE1BQU0sQ0FBQyxNQUFNLENBQUMsUUFBUSxFQUFFLE1BQU0sRUFBRSxLQUFLLEVBQUUsR0FBRyxFQUFFLFVBQVUsQ0FBQyxDQUFDO1FBQ3BFLENBQUM7UUFBQyxPQUFPLEdBQUcsRUFBRSxDQUFDO1lBQ2QsSUFBSSxDQUFDLFlBQVksQ0FBQyxJQUFJLENBQUUsR0FBYSxDQUFDLE9BQU8sQ0FBQztnQkFBRSxNQUFNLEdBQUcsQ0FBQztRQUMzRCxDQUFDO1FBRUQsTUFBTSxNQUFNLEdBQWdCLEVBQUUsSUFBSSxFQUFFLEtBQUssRUFBRSxLQUFLLEVBQUUsQ0FBQyxFQUFFLENBQUM7UUFDdEQsSUFBSSxDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsTUFBTSxFQUFFLE1BQU0sQ0FBQyxDQUFDO1FBQ2pDLElBQUksQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLE1BQU0sRUFBRSxFQUFFLFNBQVMsRUFBRSxDQUFDLEVBQUUsTUFBTSxFQUFFLENBQUMsRUFBRSxNQUFNLEVBQUUsQ0FBQyxFQUFFLENBQUMsQ0FBQztRQUMvRCxNQUFNLEtBQUssR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxNQUFNLENBQXVCLENBQUM7UUFFM0QsTUFBTSxXQUFXLEdBQUcsSUFBSSxDQUFDLEdBQUcsQ0FBQyxDQUFDLEVBQUUsTUFBTSxDQUFDLFdBQVcsSUFBSSxDQUFDLENBQUMsQ0FBQztRQUN6RCxLQUFLLElBQUksQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLEdBQUcsV0FBVyxFQUFFLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQztZQUN6QyxLQUFLLElBQUksQ0FBQyxlQUFlLENBQUMsTUFBTSxFQUFFLEtBQUssRUFBRSxNQUFNLEVBQUUsT0FBTyxFQUFFLE1BQU0sRUFBRSxLQUFLLENBQUMsQ0FBQztRQUMxRSxDQUFDO0lBQ0YsQ0FBQztJQUVPLEtBQUssQ0FBQyxlQUFlLENBQzVCLE1BQWMsRUFDZCxLQUFhLEVBQ2IsTUFBeUIsRUFDekIsT0FBMEMsRUFDMUMsTUFBbUIsRUFDbkIsS0FBeUI7UUFFekIsSUFBSSxDQUFDLElBQUksQ0FBQyxNQUFNO1lBQUUsT0FBTztRQUN6QixNQUFNLENBQUMsS0FBSyxJQUFJLENBQUMsQ0FBQztRQUNsQixJQUFJLENBQUM7WUFDSixPQUFPLENBQUMsTUFBTSxDQUFDLElBQUksRUFBRSxDQUFDO2dCQUNyQixJQUFJLE9BQU8sR0FBc0QsSUFBSSxDQUFDO2dCQUN0RSxJQUFJLENBQUM7b0JBQ0osT0FBTyxHQUFHLE1BQU0sSUFBSSxDQUFDLE1BQU0sQ0FBQyxVQUFVLENBQ3JDLE9BQU8sRUFDUCxLQUFLLEVBQ0wsSUFBSSxDQUFDLFlBQVksRUFDakIsT0FBTyxFQUNQLE1BQU0sQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxFQUN6QixPQUFPLEVBQ1AsTUFBTSxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsT0FBTyxDQUFDLEVBQzNCLFNBQVMsRUFDVCxNQUFNLEVBQ04sR0FBRyxDQUNILENBQUM7Z0JBQ0gsQ0FBQztnQkFBQyxPQUFPLEdBQUcsRUFBRSxDQUFDO29CQUNkLE1BQU0sSUFBSSxPQUFPLENBQUMsQ0FBQyxDQUFDLEVBQUUsRUFBRSxDQUFDLFVBQVUsQ0FBQyxDQUFDLEVBQUUsSUFBSSxDQUFDLENBQUMsQ0FBQztvQkFDOUMsU0FBUztnQkFDVixDQUFDO2dCQUNELElBQUksQ0FBQyxPQUFPO29CQUFFLFNBQVM7Z0JBQ3ZCLEtBQUssTUFBTSxDQUFDLEVBQUUsSUFBSSxDQUFDLElBQUksT0FBTyxFQUFFLENBQUM7b0JBQ2hDLEtBQUssTUFBTSxDQUFDLEVBQUUsRUFBRSxNQUFNLENBQUMsSUFBSSxJQUFJLEVBQUUsQ0FBQzt3QkFDakMsSUFBSSxNQUFNLENBQUMsSUFBSTs0QkFBRSxNQUFNO3dCQUN2QixLQUFLLENBQUMsTUFBTSxJQUFJLENBQUMsQ0FBQzt3QkFDbEIsSUFBSSxDQUFDOzRCQUNKLE1BQU0sT0FBTyxHQUFHLElBQUksQ0FBQyxjQUFjLENBQUMsTUFBTSxDQUFDLENBQUM7NEJBQzVDLE1BQU0sVUFBVSxHQUFHLE9BQU8sT0FBTyxDQUFDLElBQUksS0FBSyxRQUFRLENBQUMsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQzs0QkFDeEUsSUFBSSxJQUFhLENBQUM7NEJBQ2xCLElBQUksQ0FBQztnQ0FDSixJQUFJLEdBQUcsVUFBVSxDQUFDLE1BQU0sR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsVUFBVSxDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQzs0QkFDOUQsQ0FBQzs0QkFBQyxNQUFNLENBQUM7Z0NBQ1IsSUFBSSxHQUFHLFVBQVUsQ0FBQzs0QkFDbkIsQ0FBQzs0QkFDRCxNQUFNLEdBQUcsR0FBYztnQ0FDdEIsRUFBRTtnQ0FDRixJQUFJO2dDQUNKLE9BQU8sRUFBRSxFQUFFO2dDQUNYLEtBQUssRUFBRSxNQUFNO2dDQUNiLFFBQVEsRUFBRSxNQUFNLENBQUMsUUFBUSxJQUFJLENBQUM7Z0NBQzlCLFFBQVEsRUFBRSxDQUFDO2dDQUNYLFVBQVUsRUFBRSxNQUFNLENBQUMsT0FBTyxJQUFJLENBQUM7Z0NBQy9CLFNBQVMsRUFBRSxJQUFJLElBQUksQ0FBQyxNQUFNLENBQUMsUUFBUSxDQUFDLEVBQUUsQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksTUFBTSxDQUFDLElBQUksQ0FBQyxHQUFHLEVBQUUsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDO2dDQUNoRixPQUFPLEVBQUUsTUFBTSxDQUFDLE9BQU87Z0NBQ3ZCLEdBQUcsRUFBRSxFQUFFLEVBQUUsRUFBRSxNQUFNLEVBQUU7Z0NBQ25CLFFBQVEsRUFBRSxLQUFLLElBQUksRUFBRTtvQ0FDcEIsTUFBTSxJQUFJLENBQUMsTUFBTSxFQUFFLElBQUksQ0FBQyxNQUFNLEVBQUUsS0FBSyxFQUFFLEVBQUUsQ0FBQyxDQUFDO29DQUMzQyxLQUFLLENBQUMsU0FBUyxJQUFJLENBQUMsQ0FBQztnQ0FDdEIsQ0FBQztnQ0FDRCxJQUFJLEVBQUUsS0FBSyxFQUFFLElBQVcsRUFBRSxFQUFFO29DQUMzQixLQUFLLENBQUMsTUFBTSxJQUFJLENBQUMsQ0FBQztnQ0FDbkIsQ0FBQzs2QkFDRCxDQUFDOzRCQUNGLE1BQU0sT0FBTyxDQUFDLEdBQUcsQ0FBQyxDQUFDOzRCQUNuQixJQUFJLE1BQU0sQ0FBQyxHQUFHLEtBQUssS0FBSztnQ0FBRSxNQUFNLElBQUksQ0FBQyxNQUFNLEVBQUUsSUFBSSxDQUFDLE1BQU0sRUFBRSxLQUFLLEVBQUUsRUFBRSxDQUFDLENBQUM7NEJBQ3JFLEtBQUssQ0FBQyxTQUFTLElBQUksQ0FBQyxDQUFDO3dCQUN0QixDQUFDO3dCQUFDLE1BQU0sQ0FBQzs0QkFDUixLQUFLLENBQUMsTUFBTSxJQUFJLENBQUMsQ0FBQzs0QkFDbEIsd0RBQXdEO3dCQUN6RCxDQUFDO2dDQUFTLENBQUM7NEJBQ1YsS0FBSyxDQUFDLE1BQU0sR0FBRyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUMsRUFBRSxLQUFLLENBQUMsTUFBTSxHQUFHLENBQUMsQ0FBQyxDQUFDO3dCQUM5QyxDQUFDO29CQUNGLENBQUM7Z0JBQ0YsQ0FBQztZQUNGLENBQUM7UUFDRixDQUFDO2dCQUFTLENBQUM7WUFDVixNQUFNLENBQUMsS0FBSyxJQUFJLENBQUMsQ0FBQztRQUNuQixDQUFDO0lBQ0YsQ0FBQztJQUVPLGNBQWMsQ0FBQyxNQUFnQjtRQUN0QyxNQUFNLEdBQUcsR0FBMkIsRUFBRSxDQUFDO1FBQ3ZDLEtBQUssSUFBSSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsR0FBRyxNQUFNLENBQUMsTUFBTSxFQUFFLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQztZQUMzQyxHQUFHLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxDQUFDLEdBQUcsTUFBTSxDQUFDLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQztRQUNoQyxDQUFDO1FBQ0QsT0FBTyxHQUFHLENBQUM7SUFDWixDQUFDO0lBRUQsS0FBSyxDQUFDLE1BQU0sQ0FDWCxLQUFhLEVBQ2IsSUFBYSxFQUNiLElBQWdHO1FBRWhHLElBQUksQ0FBQyxJQUFJLENBQUMsU0FBUyxJQUFJLENBQUMsSUFBSSxDQUFDLE1BQU07WUFBRSxNQUFNLElBQUksS0FBSyxDQUFDLG9EQUFvRCxDQUFDLENBQUM7UUFDM0csTUFBTSxPQUFPLEdBQUcsT0FBTyxJQUFJLEtBQUssUUFBUSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDdkUsTUFBTSxJQUFJLEdBQWEsRUFBRSxDQUFDO1FBQzFCLElBQUksSUFBSSxFQUFFLEtBQUs7WUFBRSxJQUFJLENBQUMsSUFBSSxDQUFDLFlBQVksQ0FBQyxDQUFDO1FBQ3pDLE1BQU0sRUFBRSxHQUFHLE1BQU0sSUFBSSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsS0FBSyxFQUFFLEdBQUcsRUFBRSxNQUFNLEVBQUUsT0FBTyxFQUFFLE9BQU8sRUFBRSxJQUFJLEVBQUUsS0FBSyxJQUFJLEVBQUUsQ0FBQyxDQUFDO1FBQzNGLE9BQU8sRUFBRSxDQUFDO0lBQ1gsQ0FBQztJQUVELEtBQUssQ0FBQyxjQUFjLENBQUMsS0FBYTtRQUNqQyxNQUFNLE1BQU0sR0FBRyxJQUFJLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxLQUFLLENBQUMsQ0FBQztRQUN2QyxJQUFJLE1BQU07WUFBRSxNQUFNLENBQUMsSUFBSSxHQUFHLElBQUksQ0FBQztJQUNoQyxDQUFDO0lBRUQsV0FBVztRQUNWLE9BQU8sSUFBSSxDQUFDLFNBQVMsQ0FBQztJQUN2QixDQUFDO0lBRUQsS0FBSyxDQUFDLFdBQVc7UUFDaEIsSUFBSSxDQUFDLElBQUksQ0FBQyxTQUFTLElBQUksQ0FBQyxJQUFJLENBQUMsTUFBTTtZQUFFLE9BQU8sS0FBSyxDQUFDO1FBQ2xELElBQUksQ0FBQztZQUNKLE1BQU0sSUFBSSxHQUFHLE1BQU0sSUFBSSxDQUFDLE1BQU0sQ0FBQyxJQUFJLEVBQUUsQ0FBQztZQUN0QyxPQUFPLElBQUksS0FBSyxNQUFNLENBQUM7UUFDeEIsQ0FBQztRQUFDLE1BQU0sQ0FBQztZQUNSLE9BQU8sS0FBSyxDQUFDO1FBQ2QsQ0FBQztJQUNGLENBQUM7SUFFRCxLQUFLLENBQUMsYUFBYSxDQUFDLEtBQWE7UUFDaEMsTUFBTSxRQUFRLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsS0FBSyxDQUFDLElBQUksRUFBRSxTQUFTLEVBQUUsQ0FBQyxFQUFFLE1BQU0sRUFBRSxDQUFDLEVBQUUsTUFBTSxFQUFFLENBQUMsRUFBRSxDQUFDO1FBQ2pGLElBQUksT0FBTyxHQUFHLENBQUMsQ0FBQztRQUNoQixJQUFJLENBQUM7WUFDSixPQUFPLEdBQUcsQ0FBQyxNQUFNLElBQUksQ0FBQyxNQUFNLEVBQUUsSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDO1FBQ2pELENBQUM7UUFBQyxNQUFNLENBQUM7WUFDUixZQUFZO1FBQ2IsQ0FBQztRQUNELE9BQU87WUFDTixPQUFPO1lBQ1AsTUFBTSxFQUFFLFFBQVEsQ0FBQyxNQUFNO1lBQ3ZCLFNBQVMsRUFBRSxRQUFRLENBQUMsU0FBUztZQUM3QixNQUFNLEVBQUUsUUFBUSxDQUFDLE1BQU07WUFDdkIsT0FBTyxFQUFFLENBQUM7U0FDVixDQUFDO0lBQ0gsQ0FBQztDQUNEIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBSZWRpc1N0cmVhbXNBZGFwdGVyIOKAlCB2MC43IFBSIDUg4oCUIFdvcmtlciBhZGFwdGVyIGJhY2tlZCBieSBSZWRpc1xuICogU3RyZWFtcyB2aWEgYGlvcmVkaXNgLiBDb25zdW1lcyBmcm9tIGEgc3RyZWFtICh0aGUgYHF1ZXVlYCBmaWVsZClcbiAqIHdpdGggYSBjb25zdW1lciBncm91cCAoYGNvbnN1bWVyR3JvdXBgKTsgcHJvZHVjZXMgdmlhIGBYQUREYC5cbiAqXG4gKiBTZW1hbnRpY3M6XG4gKiAgIC0gKipDb25zdW1lciBncm91cHMqKjogcmVxdWlyZWQuIGBjb25zdW1lckdyb3VwYCBkZWZhdWx0cyB0b1xuICogICAgIGBcIiR7cXVldWV9LWdyb3VwXCJgLiBUaGUgZ3JvdXAgaXMgYXV0by1jcmVhdGVkIChgTUtTVFJFQU1gIG9uIHRoZVxuICogICAgIHN0cmVhbSB0b28pIG9uIGZpcnN0IGBwcm9jZXNzKClgIGNhbGwuXG4gKiAgIC0gKipDb25zdW1lciBuYW1lKio6IHBlci1wcm9jZXNzIHV1aWQsIHNvIG11bHRpcGxlIGluc3RhbmNlcyBvZlxuICogICAgIHRoZSBzYW1lIHdvcmtlciBwcm9jZXNzIGRvbid0IHNoYXJlIHBlbmRpbmcgZW50cmllcy5cbiAqICAgLSAqKlJldHJpZXMqKjogcGVuZGluZyBlbnRyaWVzIGFyZSBYQUNLJ2Qgb24gc3VjY2VzcyAvIGxlZnRcbiAqICAgICB1bmFja2VkIG9uIGZhaWx1cmUgKHZpc2libGUgaW4gYFhQRU5ESU5HYCkuIEEgcmVkcml2ZSBsb29wXG4gKiAgICAgcmVhZHMgcGVuZGluZyBlbnRyaWVzIG9sZGVyIHRoYW4gYHRpbWVvdXRgIGFuZCByZS1kZWxpdmVyc1xuICogICAgIHRvIHRoZSBjdXJyZW50IGNvbnN1bWVyLlxuICogICAtICoqQXV0by1jbGFpbSoqOiBza2lwcGVkIGluIHYxIOKAlCBvcGVyYXRvcnMgc2hvdWxkIHJ1biBYQVVUT0NMQUlNXG4gKiAgICAgcGVyaW9kaWNhbGx5IG91dCBvZiBiYW5kLlxuICpcbiAqIFJlcXVpcmVzIGBpb3JlZGlzYCBhcyBhIHBlZXIgZGVwZW5kZW5jeTpcbiAqXG4gKiAgICAgYnVuIGFkZCBpb3JlZGlzXG4gKlxuICogRW52aXJvbm1lbnQgdmFyaWFibGVzOlxuICogICAtIGBSRURJU19IT1NUYCAoZGVmYXVsdCBgbG9jYWxob3N0YClcbiAqICAgLSBgUkVESVNfUE9SVGAgKGRlZmF1bHQgYDYzNzlgKVxuICogICAtIGBSRURJU19QQVNTV09SRGBcbiAqICAgLSBgUkVESVNfREJgXG4gKi9cblxuaW1wb3J0IHR5cGUgeyBXb3JrZXJUcmlnZ2VyT3B0cyB9IGZyb20gXCJAYmxva2pzL2hlbHBlclwiO1xuaW1wb3J0IHsgdjQgYXMgdXVpZCB9IGZyb20gXCJ1dWlkXCI7XG5pbXBvcnQgdHlwZSB7IFdvcmtlckFkYXB0ZXIsIFdvcmtlckpvYiwgV29ya2VyUXVldWVTdGF0cyB9IGZyb20gXCIuLi9Xb3JrZXJUcmlnZ2VyXCI7XG5cbmV4cG9ydCBpbnRlcmZhY2UgUmVkaXNTdHJlYW1zQ29uZmlnIHtcblx0aG9zdDogc3RyaW5nO1xuXHRwb3J0OiBudW1iZXI7XG5cdHBhc3N3b3JkPzogc3RyaW5nO1xuXHRkYj86IG51bWJlcjtcblx0YmxvY2tNczogbnVtYmVyO1xuXHRjb3VudDogbnVtYmVyO1xufVxuXG5pbnRlcmZhY2UgUmVkaXNDbGllbnQge1xuXHR4YWRkKHN0cmVhbTogc3RyaW5nLCAuLi5hcmdzOiBzdHJpbmdbXSk6IFByb21pc2U8c3RyaW5nPjtcblx0eHJlYWRncm91cCguLi5hcmdzOiBzdHJpbmdbXSk6IFByb21pc2U8QXJyYXk8W3N0cmluZywgQXJyYXk8W3N0cmluZywgc3RyaW5nW11dPl0+IHwgbnVsbD47XG5cdHhncm91cCguLi5hcmdzOiBzdHJpbmdbXSk6IFByb21pc2U8c3RyaW5nPjtcblx0eGFjayhzdHJlYW06IHN0cmluZywgZ3JvdXA6IHN0cmluZywgLi4uaWRzOiBzdHJpbmdbXSk6IFByb21pc2U8bnVtYmVyPjtcblx0eGxlbihzdHJlYW06IHN0cmluZyk6IFByb21pc2U8bnVtYmVyPjtcblx0eHBlbmRpbmcoc3RyZWFtOiBzdHJpbmcsIGdyb3VwOiBzdHJpbmcpOiBQcm9taXNlPHVua25vd24+O1xuXHRwaW5nKCk6IFByb21pc2U8c3RyaW5nPjtcblx0cXVpdCgpOiBQcm9taXNlPHN0cmluZz47XG59XG5cbmludGVyZmFjZSBRdWV1ZVJ1bm5lciB7XG5cdHN0b3A6IGJvb2xlYW47XG5cdGxvb3BzOiBudW1iZXI7XG59XG5cbmludGVyZmFjZSBRdWV1ZVN0YXRzQ291bnRlcnMge1xuXHRjb21wbGV0ZWQ6IG51bWJlcjtcblx0ZmFpbGVkOiBudW1iZXI7XG5cdGFjdGl2ZTogbnVtYmVyO1xufVxuXG5leHBvcnQgY2xhc3MgUmVkaXNTdHJlYW1zQWRhcHRlciBpbXBsZW1lbnRzIFdvcmtlckFkYXB0ZXIge1xuXHRyZWFkb25seSBwcm92aWRlciA9IFwicmVkaXNcIiBhcyBjb25zdDtcblx0cHJpdmF0ZSByZWFkb25seSBjb25maWc6IFJlZGlzU3RyZWFtc0NvbmZpZztcblx0cHJpdmF0ZSBjbGllbnQ6IFJlZGlzQ2xpZW50IHwgbnVsbCA9IG51bGw7XG5cdHByaXZhdGUgcnVubmVyczogTWFwPHN0cmluZywgUXVldWVSdW5uZXI+ID0gbmV3IE1hcCgpO1xuXHRwcml2YXRlIGNvbm5lY3RlZCA9IGZhbHNlO1xuXHRwcml2YXRlIHN0YXRzOiBNYXA8c3RyaW5nLCBRdWV1ZVN0YXRzQ291bnRlcnM+ID0gbmV3IE1hcCgpO1xuXHRwcml2YXRlIGNvbnN1bWVyTmFtZSA9IGBibG9rLSR7dXVpZCgpLnNsaWNlKDAsIDgpfWA7XG5cblx0Y29uc3RydWN0b3IoY29uZmlnPzogUGFydGlhbDxSZWRpc1N0cmVhbXNDb25maWc+KSB7XG5cdFx0dGhpcy5jb25maWcgPSB7XG5cdFx0XHRob3N0OiBjb25maWc/Lmhvc3QgPz8gcHJvY2Vzcy5lbnYuUkVESVNfSE9TVCA/PyBcImxvY2FsaG9zdFwiLFxuXHRcdFx0cG9ydDogY29uZmlnPy5wb3J0ID8/IE51bWJlci5wYXJzZUludChwcm9jZXNzLmVudi5SRURJU19QT1JUID8/IFwiNjM3OVwiLCAxMCksXG5cdFx0XHRwYXNzd29yZDogY29uZmlnPy5wYXNzd29yZCA/PyBwcm9jZXNzLmVudi5SRURJU19QQVNTV09SRCxcblx0XHRcdGRiOiBjb25maWc/LmRiID8/IE51bWJlci5wYXJzZUludChwcm9jZXNzLmVudi5SRURJU19EQiA/PyBcIjBcIiwgMTApLFxuXHRcdFx0YmxvY2tNczogY29uZmlnPy5ibG9ja01zID8/IDUwMDAsXG5cdFx0XHRjb3VudDogY29uZmlnPy5jb3VudCA/PyAxMCxcblx0XHR9O1xuXHR9XG5cblx0YXN5bmMgY29ubmVjdCgpOiBQcm9taXNlPHZvaWQ+IHtcblx0XHRpZiAodGhpcy5jb25uZWN0ZWQpIHJldHVybjtcblx0XHR0cnkge1xuXHRcdFx0Ly8gYmlvbWUtaWdub3JlIGxpbnQvc3VzcGljaW91cy9ub0V4cGxpY2l0QW55OiBpb3JlZGlzIGlzIGEgcnVudGltZSBwZWVyIGRlcC5cblx0XHRcdGNvbnN0IGlvcmVkaXM6IGFueSA9IGF3YWl0IGltcG9ydChcImlvcmVkaXNcIik7XG5cdFx0XHRjb25zdCBJT1JlZGlzID0gaW9yZWRpcy5kZWZhdWx0ID8/IGlvcmVkaXMuUmVkaXMgPz8gaW9yZWRpcztcblx0XHRcdHRoaXMuY2xpZW50ID0gbmV3IElPUmVkaXMoe1xuXHRcdFx0XHRob3N0OiB0aGlzLmNvbmZpZy5ob3N0LFxuXHRcdFx0XHRwb3J0OiB0aGlzLmNvbmZpZy5wb3J0LFxuXHRcdFx0XHRwYXNzd29yZDogdGhpcy5jb25maWcucGFzc3dvcmQsXG5cdFx0XHRcdGRiOiB0aGlzLmNvbmZpZy5kYixcblx0XHRcdH0pIGFzIFJlZGlzQ2xpZW50O1xuXHRcdFx0YXdhaXQgdGhpcy5jbGllbnQucGluZygpO1xuXHRcdFx0dGhpcy5jb25uZWN0ZWQgPSB0cnVlO1xuXHRcdH0gY2F0Y2ggKGVycikge1xuXHRcdFx0dGhyb3cgbmV3IEVycm9yKFxuXHRcdFx0XHRgW2Jsb2tdW3JlZGlzXSBjb25uZWN0IGZhaWxlZDogJHsoZXJyIGFzIEVycm9yKS5tZXNzYWdlfS4gSW5zdGFsbCBpb3JlZGlzIGFzIGEgcGVlciBkZXBlbmRlbmN5OiBidW4gYWRkIGlvcmVkaXNgLFxuXHRcdFx0KTtcblx0XHR9XG5cdH1cblxuXHRhc3luYyBkaXNjb25uZWN0KCk6IFByb21pc2U8dm9pZD4ge1xuXHRcdGlmICghdGhpcy5jb25uZWN0ZWQpIHJldHVybjtcblx0XHRmb3IgKGNvbnN0IHJ1bm5lciBvZiB0aGlzLnJ1bm5lcnMudmFsdWVzKCkpIHJ1bm5lci5zdG9wID0gdHJ1ZTtcblx0XHQvLyBXYWl0IGZvciBpbi1mbGlnaHQgbG9vcHMgdG8gZHJhaW4uXG5cdFx0Y29uc3QgZHJhaW5EZWFkbGluZSA9IERhdGUubm93KCkgKyAyMDAwO1xuXHRcdHdoaWxlIChEYXRlLm5vdygpIDwgZHJhaW5EZWFkbGluZSkge1xuXHRcdFx0bGV0IGFjdGl2ZSA9IDA7XG5cdFx0XHRmb3IgKGNvbnN0IHIgb2YgdGhpcy5ydW5uZXJzLnZhbHVlcygpKSBhY3RpdmUgKz0gci5sb29wcztcblx0XHRcdGlmIChhY3RpdmUgPT09IDApIGJyZWFrO1xuXHRcdFx0YXdhaXQgbmV3IFByb21pc2UoKHIpID0+IHNldFRpbWVvdXQociwgNTApKTtcblx0XHR9XG5cdFx0dGhpcy5ydW5uZXJzLmNsZWFyKCk7XG5cdFx0dHJ5IHtcblx0XHRcdGF3YWl0IHRoaXMuY2xpZW50Py5xdWl0KCk7XG5cdFx0fSBjYXRjaCB7XG5cdFx0XHQvKiBpZ25vcmUgKi9cblx0XHR9XG5cdFx0dGhpcy5jbGllbnQgPSBudWxsO1xuXHRcdHRoaXMuY29ubmVjdGVkID0gZmFsc2U7XG5cdH1cblxuXHRhc3luYyBwcm9jZXNzKGNvbmZpZzogV29ya2VyVHJpZ2dlck9wdHMsIGhhbmRsZXI6IChqb2I6IFdvcmtlckpvYikgPT4gUHJvbWlzZTx2b2lkPik6IFByb21pc2U8dm9pZD4ge1xuXHRcdGlmICghdGhpcy5jb25uZWN0ZWQgfHwgIXRoaXMuY2xpZW50KSB0aHJvdyBuZXcgRXJyb3IoXCJbYmxva11bcmVkaXNdIG5vdCBjb25uZWN0ZWQuIENhbGwgY29ubmVjdCgpIGZpcnN0LlwiKTtcblx0XHRjb25zdCBzdHJlYW0gPSBjb25maWcucXVldWU7XG5cdFx0Y29uc3QgZ3JvdXAgPSBjb25maWcuY29uc3VtZXJHcm91cCA/PyBgJHtzdHJlYW19LWdyb3VwYDtcblx0XHQvLyBDcmVhdGUgZ3JvdXAgKyBzdHJlYW0gaWYgbWlzc2luZy4gWEdST1VQIENSRUFURSB3aXRoIE1LU1RSRUFNXG5cdFx0Ly8gZXJyb3JzIHdpdGggXCJCVVNZR1JPVVBcIiBpZiB0aGUgZ3JvdXAgYWxyZWFkeSBleGlzdHMg4oCUIHN3YWxsb3cgaXQuXG5cdFx0dHJ5IHtcblx0XHRcdGF3YWl0IHRoaXMuY2xpZW50Lnhncm91cChcIkNSRUFURVwiLCBzdHJlYW0sIGdyb3VwLCBcIiRcIiwgXCJNS1NUUkVBTVwiKTtcblx0XHR9IGNhdGNoIChlcnIpIHtcblx0XHRcdGlmICghL0JVU1lHUk9VUC9pLnRlc3QoKGVyciBhcyBFcnJvcikubWVzc2FnZSkpIHRocm93IGVycjtcblx0XHR9XG5cblx0XHRjb25zdCBydW5uZXI6IFF1ZXVlUnVubmVyID0geyBzdG9wOiBmYWxzZSwgbG9vcHM6IDAgfTtcblx0XHR0aGlzLnJ1bm5lcnMuc2V0KHN0cmVhbSwgcnVubmVyKTtcblx0XHR0aGlzLnN0YXRzLnNldChzdHJlYW0sIHsgY29tcGxldGVkOiAwLCBmYWlsZWQ6IDAsIGFjdGl2ZTogMCB9KTtcblx0XHRjb25zdCBzdGF0cyA9IHRoaXMuc3RhdHMuZ2V0KHN0cmVhbSkgYXMgUXVldWVTdGF0c0NvdW50ZXJzO1xuXG5cdFx0Y29uc3QgY29uY3VycmVuY3kgPSBNYXRoLm1heCgxLCBjb25maWcuY29uY3VycmVuY3kgPz8gMSk7XG5cdFx0Zm9yIChsZXQgaSA9IDA7IGkgPCBjb25jdXJyZW5jeTsgaSArPSAxKSB7XG5cdFx0XHR2b2lkIHRoaXMucnVuQ29uc3VtZXJMb29wKHN0cmVhbSwgZ3JvdXAsIGNvbmZpZywgaGFuZGxlciwgcnVubmVyLCBzdGF0cyk7XG5cdFx0fVxuXHR9XG5cblx0cHJpdmF0ZSBhc3luYyBydW5Db25zdW1lckxvb3AoXG5cdFx0c3RyZWFtOiBzdHJpbmcsXG5cdFx0Z3JvdXA6IHN0cmluZyxcblx0XHRjb25maWc6IFdvcmtlclRyaWdnZXJPcHRzLFxuXHRcdGhhbmRsZXI6IChqb2I6IFdvcmtlckpvYikgPT4gUHJvbWlzZTx2b2lkPixcblx0XHRydW5uZXI6IFF1ZXVlUnVubmVyLFxuXHRcdHN0YXRzOiBRdWV1ZVN0YXRzQ291bnRlcnMsXG5cdCk6IFByb21pc2U8dm9pZD4ge1xuXHRcdGlmICghdGhpcy5jbGllbnQpIHJldHVybjtcblx0XHRydW5uZXIubG9vcHMgKz0gMTtcblx0XHR0cnkge1xuXHRcdFx0d2hpbGUgKCFydW5uZXIuc3RvcCkge1xuXHRcdFx0XHRsZXQgZW50cmllczogQXJyYXk8W3N0cmluZywgQXJyYXk8W3N0cmluZywgc3RyaW5nW11dPl0+IHwgbnVsbCA9IG51bGw7XG5cdFx0XHRcdHRyeSB7XG5cdFx0XHRcdFx0ZW50cmllcyA9IGF3YWl0IHRoaXMuY2xpZW50LnhyZWFkZ3JvdXAoXG5cdFx0XHRcdFx0XHRcIkdST1VQXCIsXG5cdFx0XHRcdFx0XHRncm91cCxcblx0XHRcdFx0XHRcdHRoaXMuY29uc3VtZXJOYW1lLFxuXHRcdFx0XHRcdFx0XCJDT1VOVFwiLFxuXHRcdFx0XHRcdFx0U3RyaW5nKHRoaXMuY29uZmlnLmNvdW50KSxcblx0XHRcdFx0XHRcdFwiQkxPQ0tcIixcblx0XHRcdFx0XHRcdFN0cmluZyh0aGlzLmNvbmZpZy5ibG9ja01zKSxcblx0XHRcdFx0XHRcdFwiU1RSRUFNU1wiLFxuXHRcdFx0XHRcdFx0c3RyZWFtLFxuXHRcdFx0XHRcdFx0XCI+XCIsXG5cdFx0XHRcdFx0KTtcblx0XHRcdFx0fSBjYXRjaCAoZXJyKSB7XG5cdFx0XHRcdFx0YXdhaXQgbmV3IFByb21pc2UoKHIpID0+IHNldFRpbWVvdXQociwgMTAwMCkpO1xuXHRcdFx0XHRcdGNvbnRpbnVlO1xuXHRcdFx0XHR9XG5cdFx0XHRcdGlmICghZW50cmllcykgY29udGludWU7XG5cdFx0XHRcdGZvciAoY29uc3QgWywgbXNnc10gb2YgZW50cmllcykge1xuXHRcdFx0XHRcdGZvciAoY29uc3QgW2lkLCBmaWVsZHNdIG9mIG1zZ3MpIHtcblx0XHRcdFx0XHRcdGlmIChydW5uZXIuc3RvcCkgYnJlYWs7XG5cdFx0XHRcdFx0XHRzdGF0cy5hY3RpdmUgKz0gMTtcblx0XHRcdFx0XHRcdHRyeSB7XG5cdFx0XHRcdFx0XHRcdGNvbnN0IHBheWxvYWQgPSB0aGlzLmZpZWxkc1RvT2JqZWN0KGZpZWxkcyk7XG5cdFx0XHRcdFx0XHRcdGNvbnN0IGRhdGFTdHJpbmcgPSB0eXBlb2YgcGF5bG9hZC5kYXRhID09PSBcInN0cmluZ1wiID8gcGF5bG9hZC5kYXRhIDogXCJcIjtcblx0XHRcdFx0XHRcdFx0bGV0IGRhdGE6IHVua25vd247XG5cdFx0XHRcdFx0XHRcdHRyeSB7XG5cdFx0XHRcdFx0XHRcdFx0ZGF0YSA9IGRhdGFTdHJpbmcubGVuZ3RoID4gMCA/IEpTT04ucGFyc2UoZGF0YVN0cmluZykgOiBudWxsO1xuXHRcdFx0XHRcdFx0XHR9IGNhdGNoIHtcblx0XHRcdFx0XHRcdFx0XHRkYXRhID0gZGF0YVN0cmluZztcblx0XHRcdFx0XHRcdFx0fVxuXHRcdFx0XHRcdFx0XHRjb25zdCBqb2I6IFdvcmtlckpvYiA9IHtcblx0XHRcdFx0XHRcdFx0XHRpZCxcblx0XHRcdFx0XHRcdFx0XHRkYXRhLFxuXHRcdFx0XHRcdFx0XHRcdGhlYWRlcnM6IHt9LFxuXHRcdFx0XHRcdFx0XHRcdHF1ZXVlOiBzdHJlYW0sXG5cdFx0XHRcdFx0XHRcdFx0cHJpb3JpdHk6IGNvbmZpZy5wcmlvcml0eSA/PyAwLFxuXHRcdFx0XHRcdFx0XHRcdGF0dGVtcHRzOiAwLFxuXHRcdFx0XHRcdFx0XHRcdG1heFJldHJpZXM6IGNvbmZpZy5yZXRyaWVzID8/IDAsXG5cdFx0XHRcdFx0XHRcdFx0Y3JlYXRlZEF0OiBuZXcgRGF0ZShOdW1iZXIucGFyc2VJbnQoaWQuc3BsaXQoXCItXCIpWzBdID8/IFN0cmluZyhEYXRlLm5vdygpKSwgMTApKSxcblx0XHRcdFx0XHRcdFx0XHR0aW1lb3V0OiBjb25maWcudGltZW91dCxcblx0XHRcdFx0XHRcdFx0XHRyYXc6IHsgaWQsIGZpZWxkcyB9LFxuXHRcdFx0XHRcdFx0XHRcdGNvbXBsZXRlOiBhc3luYyAoKSA9PiB7XG5cdFx0XHRcdFx0XHRcdFx0XHRhd2FpdCB0aGlzLmNsaWVudD8ueGFjayhzdHJlYW0sIGdyb3VwLCBpZCk7XG5cdFx0XHRcdFx0XHRcdFx0XHRzdGF0cy5jb21wbGV0ZWQgKz0gMTtcblx0XHRcdFx0XHRcdFx0XHR9LFxuXHRcdFx0XHRcdFx0XHRcdGZhaWw6IGFzeW5jIChfZXJyOiBFcnJvcikgPT4ge1xuXHRcdFx0XHRcdFx0XHRcdFx0c3RhdHMuZmFpbGVkICs9IDE7XG5cdFx0XHRcdFx0XHRcdFx0fSxcblx0XHRcdFx0XHRcdFx0fTtcblx0XHRcdFx0XHRcdFx0YXdhaXQgaGFuZGxlcihqb2IpO1xuXHRcdFx0XHRcdFx0XHRpZiAoY29uZmlnLmFjayAhPT0gZmFsc2UpIGF3YWl0IHRoaXMuY2xpZW50Py54YWNrKHN0cmVhbSwgZ3JvdXAsIGlkKTtcblx0XHRcdFx0XHRcdFx0c3RhdHMuY29tcGxldGVkICs9IDE7XG5cdFx0XHRcdFx0XHR9IGNhdGNoIHtcblx0XHRcdFx0XHRcdFx0c3RhdHMuZmFpbGVkICs9IDE7XG5cdFx0XHRcdFx0XHRcdC8vIFBlbmRpbmcgZW50cnkgbGVmdCB1bmFja2VkIOKAlCBwaWNrZWQgdXAgYnkgWEFVVE9DTEFJTS5cblx0XHRcdFx0XHRcdH0gZmluYWxseSB7XG5cdFx0XHRcdFx0XHRcdHN0YXRzLmFjdGl2ZSA9IE1hdGgubWF4KDAsIHN0YXRzLmFjdGl2ZSAtIDEpO1xuXHRcdFx0XHRcdFx0fVxuXHRcdFx0XHRcdH1cblx0XHRcdFx0fVxuXHRcdFx0fVxuXHRcdH0gZmluYWxseSB7XG5cdFx0XHRydW5uZXIubG9vcHMgLT0gMTtcblx0XHR9XG5cdH1cblxuXHRwcml2YXRlIGZpZWxkc1RvT2JqZWN0KGZpZWxkczogc3RyaW5nW10pOiBSZWNvcmQ8c3RyaW5nLCBzdHJpbmc+IHtcblx0XHRjb25zdCBvdXQ6IFJlY29yZDxzdHJpbmcsIHN0cmluZz4gPSB7fTtcblx0XHRmb3IgKGxldCBpID0gMDsgaSA8IGZpZWxkcy5sZW5ndGg7IGkgKz0gMikge1xuXHRcdFx0b3V0W2ZpZWxkc1tpXV0gPSBmaWVsZHNbaSArIDFdO1xuXHRcdH1cblx0XHRyZXR1cm4gb3V0O1xuXHR9XG5cblx0YXN5bmMgYWRkSm9iKFxuXHRcdHF1ZXVlOiBzdHJpbmcsXG5cdFx0ZGF0YTogdW5rbm93bixcblx0XHRvcHRzPzogeyBwcmlvcml0eT86IG51bWJlcjsgZGVsYXk/OiBudW1iZXI7IHJldHJpZXM/OiBudW1iZXI7IHRpbWVvdXQ/OiBudW1iZXI7IGpvYklkPzogc3RyaW5nIH0sXG5cdCk6IFByb21pc2U8c3RyaW5nPiB7XG5cdFx0aWYgKCF0aGlzLmNvbm5lY3RlZCB8fCAhdGhpcy5jbGllbnQpIHRocm93IG5ldyBFcnJvcihcIltibG9rXVtyZWRpc10gbm90IGNvbm5lY3RlZC4gQ2FsbCBjb25uZWN0KCkgZmlyc3QuXCIpO1xuXHRcdGNvbnN0IHBheWxvYWQgPSB0eXBlb2YgZGF0YSA9PT0gXCJzdHJpbmdcIiA/IGRhdGEgOiBKU09OLnN0cmluZ2lmeShkYXRhKTtcblx0XHRjb25zdCBhcmdzOiBzdHJpbmdbXSA9IFtdO1xuXHRcdGlmIChvcHRzPy5qb2JJZCkgYXJncy5wdXNoKFwiTk9NS1NUUkVBTVwiKTtcblx0XHRjb25zdCBpZCA9IGF3YWl0IHRoaXMuY2xpZW50LnhhZGQocXVldWUsIFwiKlwiLCBcImRhdGFcIiwgcGF5bG9hZCwgXCJqb2JJZFwiLCBvcHRzPy5qb2JJZCA/PyBcIlwiKTtcblx0XHRyZXR1cm4gaWQ7XG5cdH1cblxuXHRhc3luYyBzdG9wUHJvY2Vzc2luZyhxdWV1ZTogc3RyaW5nKTogUHJvbWlzZTx2b2lkPiB7XG5cdFx0Y29uc3QgcnVubmVyID0gdGhpcy5ydW5uZXJzLmdldChxdWV1ZSk7XG5cdFx0aWYgKHJ1bm5lcikgcnVubmVyLnN0b3AgPSB0cnVlO1xuXHR9XG5cblx0aXNDb25uZWN0ZWQoKTogYm9vbGVhbiB7XG5cdFx0cmV0dXJuIHRoaXMuY29ubmVjdGVkO1xuXHR9XG5cblx0YXN5bmMgaGVhbHRoQ2hlY2soKTogUHJvbWlzZTxib29sZWFuPiB7XG5cdFx0aWYgKCF0aGlzLmNvbm5lY3RlZCB8fCAhdGhpcy5jbGllbnQpIHJldHVybiBmYWxzZTtcblx0XHR0cnkge1xuXHRcdFx0Y29uc3QgcG9uZyA9IGF3YWl0IHRoaXMuY2xpZW50LnBpbmcoKTtcblx0XHRcdHJldHVybiBwb25nID09PSBcIlBPTkdcIjtcblx0XHR9IGNhdGNoIHtcblx0XHRcdHJldHVybiBmYWxzZTtcblx0XHR9XG5cdH1cblxuXHRhc3luYyBnZXRRdWV1ZVN0YXRzKHF1ZXVlOiBzdHJpbmcpOiBQcm9taXNlPFdvcmtlclF1ZXVlU3RhdHM+IHtcblx0XHRjb25zdCBjb3VudGVycyA9IHRoaXMuc3RhdHMuZ2V0KHF1ZXVlKSA/PyB7IGNvbXBsZXRlZDogMCwgZmFpbGVkOiAwLCBhY3RpdmU6IDAgfTtcblx0XHRsZXQgd2FpdGluZyA9IDA7XG5cdFx0dHJ5IHtcblx0XHRcdHdhaXRpbmcgPSAoYXdhaXQgdGhpcy5jbGllbnQ/LnhsZW4ocXVldWUpKSA/PyAwO1xuXHRcdH0gY2F0Y2gge1xuXHRcdFx0LyogaWdub3JlICovXG5cdFx0fVxuXHRcdHJldHVybiB7XG5cdFx0XHR3YWl0aW5nLFxuXHRcdFx0YWN0aXZlOiBjb3VudGVycy5hY3RpdmUsXG5cdFx0XHRjb21wbGV0ZWQ6IGNvdW50ZXJzLmNvbXBsZXRlZCxcblx0XHRcdGZhaWxlZDogY291bnRlcnMuZmFpbGVkLFxuXHRcdFx0ZGVsYXllZDogMCxcblx0XHR9O1xuXHR9XG59XG4iXX0=