@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.
Files changed (50) 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 +40 -4
  6. package/dist/WorkerTrigger.js +272 -40
  7. package/dist/adapters/BullMQAdapter.d.ts +1 -1
  8. package/dist/adapters/BullMQAdapter.js +5 -42
  9. package/dist/adapters/InMemoryAdapter.d.ts +1 -1
  10. package/dist/adapters/InMemoryAdapter.js +13 -12
  11. package/dist/adapters/KafkaAdapter.d.ts +62 -0
  12. package/dist/adapters/KafkaAdapter.js +236 -0
  13. package/dist/adapters/NATSAdapter.d.ts +110 -0
  14. package/dist/adapters/NATSAdapter.js +394 -0
  15. package/dist/adapters/PgBossAdapter.d.ts +56 -0
  16. package/dist/adapters/PgBossAdapter.js +251 -0
  17. package/dist/adapters/RabbitMQAdapter.d.ts +51 -0
  18. package/dist/adapters/RabbitMQAdapter.js +241 -0
  19. package/dist/adapters/RedisStreamsAdapter.d.ts +64 -0
  20. package/dist/adapters/RedisStreamsAdapter.js +240 -0
  21. package/dist/adapters/SQSAdapter.d.ts +61 -0
  22. package/dist/adapters/SQSAdapter.js +269 -0
  23. package/dist/adapters/factory.d.ts +34 -0
  24. package/dist/adapters/factory.js +103 -0
  25. package/dist/index.d.ts +25 -7
  26. package/dist/index.js +31 -16
  27. package/package.json +27 -5
  28. package/src/WorkerTrigger.test.ts +44 -14
  29. package/src/WorkerTrigger.ts +299 -27
  30. package/src/adapters/InMemoryAdapter.ts +9 -5
  31. package/src/adapters/KafkaAdapter.ts +277 -0
  32. package/src/adapters/NATSAdapter.ts +454 -0
  33. package/src/adapters/PgBossAdapter.ts +293 -0
  34. package/src/adapters/RabbitMQAdapter.ts +285 -0
  35. package/src/adapters/RedisStreamsAdapter.ts +286 -0
  36. package/src/adapters/SQSAdapter.ts +306 -0
  37. package/src/adapters/factory.test.ts +89 -0
  38. package/src/adapters/factory.ts +111 -0
  39. package/src/adapters/new-adapters.test.ts +130 -0
  40. package/src/index.ts +31 -4
  41. package/template/.env.example +13 -0
  42. package/template/package.json +45 -0
  43. package/template/src/Nodes.ts +10 -0
  44. package/template/src/Workflows.ts +8 -0
  45. package/template/src/index.ts +41 -0
  46. package/template/src/runner/WorkerServer.ts +34 -0
  47. package/template/src/runner/types/Workflows.ts +7 -0
  48. package/template/src/workflows/jobs/process-job.ts +47 -0
  49. package/template/tsconfig.json +31 -0
  50. package/template/vitest.config.ts +39 -0
@@ -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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiUmVkaXNTdHJlYW1zQWRhcHRlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9hZGFwdGVycy9SZWRpc1N0cmVhbXNBZGFwdGVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0EyQkc7QUFHSCxPQUFPLEVBQUUsRUFBRSxJQUFJLElBQUksRUFBRSxNQUFNLE1BQU0sQ0FBQztBQWtDbEMsTUFBTSxPQUFPLG1CQUFtQjtJQUN0QixRQUFRLEdBQUcsT0FBZ0IsQ0FBQztJQUNwQixNQUFNLENBQXFCO0lBQ3BDLE1BQU0sR0FBdUIsSUFBSSxDQUFDO0lBQ2xDLE9BQU8sR0FBNkIsSUFBSSxHQUFHLEVBQUUsQ0FBQztJQUM5QyxTQUFTLEdBQUcsS0FBSyxDQUFDO0lBQ2xCLEtBQUssR0FBb0MsSUFBSSxHQUFHLEVBQUUsQ0FBQztJQUNuRCxZQUFZLEdBQUcsUUFBUSxJQUFJLEVBQUUsQ0FBQyxLQUFLLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxFQUFFLENBQUM7SUFFcEQsWUFBWSxNQUFvQztRQUMvQyxJQUFJLENBQUMsTUFBTSxHQUFHO1lBQ2IsSUFBSSxFQUFFLE1BQU0sRUFBRSxJQUFJLElBQUksT0FBTyxDQUFDLEdBQUcsQ0FBQyxVQUFVLElBQUksV0FBVztZQUMzRCxJQUFJLEVBQUUsTUFBTSxFQUFFLElBQUksSUFBSSxNQUFNLENBQUMsUUFBUSxDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsVUFBVSxJQUFJLE1BQU0sRUFBRSxFQUFFLENBQUM7WUFDM0UsUUFBUSxFQUFFLE1BQU0sRUFBRSxRQUFRLElBQUksT0FBTyxDQUFDLEdBQUcsQ0FBQyxjQUFjO1lBQ3hELEVBQUUsRUFBRSxNQUFNLEVBQUUsRUFBRSxJQUFJLE1BQU0sQ0FBQyxRQUFRLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxRQUFRLElBQUksR0FBRyxFQUFFLEVBQUUsQ0FBQztZQUNsRSxPQUFPLEVBQUUs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QUFDO1FBQ2QsQ0FBQztJQUNGLENBQUM7SUFFRCxLQUFLLENBQUMsYUFBYSxDQUFDLEtBQWE7UUFDaEMsTUFBTSxRQUFRLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsS0FBSyxDQUFDLElBQUksRUFBRSxTQUFTLEVBQUUsQ0FBQyxFQUFFLE1BQU0sRUFBRSxDQUFDLEVBQUUsTUFBTSxFQUFFLENBQUMsRUFBRSxDQUFDO1FBQ2pGLElBQUksT0FBTyxHQUFHLENBQUMsQ0FBQztRQUNoQixJQUFJLENBQUM7WUFDSixPQUFPLEdBQUcsQ0FBQyxNQUFNLElBQUksQ0FBQyxNQUFNLEVBQUUsSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDO1FBQ2pELENBQUM7UUFBQyxNQUFNLENBQUM7WUFDUixZQUFZO1FBQ2IsQ0FBQztRQUNELE9BQU87WUFDTixPQUFPO1lBQ1AsTUFBTSxFQUFFLFFBQVEsQ0FBQyxNQUFNO1lBQ3ZCLFNBQVMsRUFBRSxRQUFRLENBQUMsU0FBUztZQUM3QixNQUFNLEVBQUUsUUFBUSxDQUFDLE1BQU07WUFDdkIsT0FBTyxFQUFFLENBQUM7U0FDVixDQUFDO0lBQ0gsQ0FBQztDQUNEIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBSZWRpc1N0cmVhbXNBZGFwdGVyIOKAlCB2MC43IFBSIDUg4oCUIFdvcmtlciBhZGFwdGVyIGJhY2tlZCBieSBSZWRpc1xuICogU3RyZWFtcyB2aWEgYGlvcmVkaXNgLiBDb25zdW1lcyBmcm9tIGEgc3RyZWFtICh0aGUgYHF1ZXVlYCBmaWVsZClcbiAqIHdpdGggYSBjb25zdW1lciBncm91cCAoYGNvbnN1bWVyR3JvdXBgKTsgcHJvZHVjZXMgdmlhIGBYQUREYC5cbiAqXG4gKiBTZW1hbnRpY3M6XG4gKiAgIC0gKipDb25zdW1lciBncm91cHMqKjogcmVxdWlyZWQuIGBjb25zdW1lckdyb3VwYCBkZWZhdWx0cyB0b1xuICogICAgIGBcIiR7cXVldWV9LWdyb3VwXCJgLiBUaGUgZ3JvdXAgaXMgYXV0by1jcmVhdGVkIChgTUtTVFJFQU1gIG9uIHRoZVxuICogICAgIHN0cmVhbSB0b28pIG9uIGZpcnN0IGBwcm9jZXNzKClgIGNhbGwuXG4gKiAgIC0gKipDb25zdW1lciBuYW1lKio6IHBlci1wcm9jZXNzIHV1aWQsIHNvIG11bHRpcGxlIGluc3RhbmNlcyBvZlxuICogICAgIHRoZSBzYW1lIHdvcmtlciBwcm9jZXNzIGRvbid0IHNoYXJlIHBlbmRpbmcgZW50cmllcy5cbiAqICAgLSAqKlJldHJpZXMqKjogcGVuZGluZyBlbnRyaWVzIGFyZSBYQUNLJ2Qgb24gc3VjY2VzcyAvIGxlZnRcbiAqICAgICB1bmFja2VkIG9uIGZhaWx1cmUgKHZpc2libGUgaW4gYFhQRU5ESU5HYCkuIEEgcmVkcml2ZSBsb29wXG4gKiAgICAgcmVhZHMgcGVuZGluZyBlbnRyaWVzIG9sZGVyIHRoYW4gYHRpbWVvdXRgIGFuZCByZS1kZWxpdmVyc1xuICogICAgIHRvIHRoZSBjdXJyZW50IGNvbnN1bWVyLlxuICogICAtICoqQXV0by1jbGFpbSoqOiBza2lwcGVkIGluIHYxIOKAlCBvcGVyYXRvcnMgc2hvdWxkIHJ1biBYQVVUT0NMQUlNXG4gKiAgICAgcGVyaW9kaWNhbGx5IG91dCBvZiBiYW5kLlxuICpcbiAqIFJlcXVpcmVzIGBpb3JlZGlzYCBhcyBhIHBlZXIgZGVwZW5kZW5jeTpcbiAqXG4gKiAgICAgYnVuIGFkZCBpb3JlZGlzXG4gKlxuICogRW52aXJvbm1lbnQgdmFyaWFibGVzOlxuICogICAtIGBSRURJU19IT1NUYCAoZGVmYXVsdCBgbG9jYWxob3N0YClcbiAqICAgLSBgUkVESVNfUE9SVGAgKGRlZmF1bHQgYDYzNzlgKVxuICogICAtIGBSRURJU19QQVNTV09SRGBcbiAqICAgLSBgUkVESVNfREJgXG4gKi9cblxuaW1wb3J0IHR5cGUgeyBXb3JrZXJUcmlnZ2VyT3B0cyB9IGZyb20gXCJAYmxva2pzL2hlbHBlclwiO1xuaW1wb3J0IHsgdjQgYXMgdXVpZCB9IGZyb20gXCJ1dWlkXCI7XG5pbXBvcnQgdHlwZSB7IFdvcmtlckFkYXB0ZXIsIFdvcmtlckpvYiwgV29ya2VyUXVldWVTdGF0cyB9IGZyb20gXCIuLi9Xb3JrZXJUcmlnZ2VyXCI7XG5cbmV4cG9ydCBpbnRlcmZhY2UgUmVkaXNTdHJlYW1zQ29uZmlnIHtcblx0aG9zdDogc3RyaW5nO1xuXHRwb3J0OiBudW1iZXI7XG5cdHBhc3N3b3JkPzogc3RyaW5nO1xuXHRkYj86IG51bWJlcjtcblx0YmxvY2tNczogbnVtYmVyO1xuXHRjb3VudDogbnVtYmVyO1xufVxuXG5pbnRlcmZhY2UgUmVkaXNDbGllbnQge1xuXHR4YWRkKHN0cmVhbTogc3RyaW5nLCAuLi5hcmdzOiBzdHJpbmdbXSk6IFByb21pc2U8c3RyaW5nPjtcblx0eHJlYWRncm91cCguLi5hcmdzOiBzdHJpbmdbXSk6IFByb21pc2U8QXJyYXk8W3N0cmluZywgQXJyYXk8W3N0cmluZywgc3RyaW5nW11dPl0+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=
@@ -0,0 +1,61 @@
1
+ /**
2
+ * SQSAdapter — v0.7 PR 5 — Worker adapter backed by AWS SQS via
3
+ * `@aws-sdk/client-sqs`. Polls a queue URL (the `queue` field) with
4
+ * long-polling; processes messages with manual delete (ACK).
5
+ *
6
+ * Semantics:
7
+ * - **Long polling**: `WaitTimeSeconds=20` by default — minimises
8
+ * poll cost. `concurrency` controls how many parallel
9
+ * `ReceiveMessage` loops run.
10
+ * - **Visibility timeout**: configured via `timeout` (ms → s).
11
+ * Messages reappear after this if the worker doesn't delete them.
12
+ * - **Retries**: SQS handles retries automatically via redrive
13
+ * policy on the queue itself. The adapter doesn't simulate
14
+ * retries client-side — set `MaxReceiveCount` on the queue's
15
+ * redrive policy and a DLQ via `deadLetterQueue`.
16
+ * - **FIFO support**: when the queue URL ends with `.fifo`, the
17
+ * adapter passes `MessageGroupId` from `dedupId` or a default.
18
+ *
19
+ * Requires `@aws-sdk/client-sqs` as a peer dependency:
20
+ *
21
+ * bun add @aws-sdk/client-sqs
22
+ *
23
+ * Environment variables (standard AWS SDK):
24
+ * - `AWS_REGION` — default `us-east-1`.
25
+ * - `AWS_ACCESS_KEY_ID` — credentials (or use a profile).
26
+ * - `AWS_SECRET_ACCESS_KEY`
27
+ * - `SQS_ENDPOINT_URL` — for local SQS (LocalStack / ElasticMQ).
28
+ */
29
+ import type { WorkerTriggerOpts } from "@blokjs/helper";
30
+ import type { WorkerAdapter, WorkerJob, WorkerQueueStats } from "../WorkerTrigger";
31
+ export interface SQSConfig {
32
+ region: string;
33
+ endpoint?: string;
34
+ waitTimeSeconds: number;
35
+ maxNumberOfMessages: number;
36
+ }
37
+ export declare class SQSAdapter implements WorkerAdapter {
38
+ readonly provider: "sqs";
39
+ private readonly config;
40
+ private client;
41
+ private commands;
42
+ private runners;
43
+ private connected;
44
+ private stats;
45
+ constructor(config?: Partial<SQSConfig>);
46
+ connect(): Promise<void>;
47
+ disconnect(): Promise<void>;
48
+ process(config: WorkerTriggerOpts, handler: (job: WorkerJob) => Promise<void>): Promise<void>;
49
+ private runConsumerLoop;
50
+ addJob(queue: string, data: unknown, opts?: {
51
+ priority?: number;
52
+ delay?: number;
53
+ retries?: number;
54
+ timeout?: number;
55
+ jobId?: string;
56
+ }): Promise<string>;
57
+ stopProcessing(queue: string): Promise<void>;
58
+ isConnected(): boolean;
59
+ healthCheck(): Promise<boolean>;
60
+ getQueueStats(queue: string): Promise<WorkerQueueStats>;
61
+ }