@warlock.js/queue 5.32.0 → 5.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/CHANGELOG.md +12 -51
  2. package/README.md +18 -3
  3. package/cjs/index.cjs +644 -168
  4. package/cjs/index.cjs.map +1 -1
  5. package/esm/dashboard-boot.mjs +1 -0
  6. package/esm/dashboard-boot.mjs.map +1 -1
  7. package/esm/dashboard.d.mts.map +1 -1
  8. package/esm/dashboard.mjs +2 -1
  9. package/esm/dashboard.mjs.map +1 -1
  10. package/esm/define-job.d.mts +0 -1
  11. package/esm/define-job.d.mts.map +1 -1
  12. package/esm/define-job.mjs +11 -64
  13. package/esm/define-job.mjs.map +1 -1
  14. package/esm/driver.d.mts +23 -0
  15. package/esm/driver.d.mts.map +1 -0
  16. package/esm/drivers/bullmq.mjs +132 -0
  17. package/esm/drivers/bullmq.mjs.map +1 -0
  18. package/esm/drivers/local-store.mjs +9 -0
  19. package/esm/drivers/local-store.mjs.map +1 -0
  20. package/esm/drivers/local.mjs +307 -0
  21. package/esm/drivers/local.mjs.map +1 -0
  22. package/esm/drivers/memory-store.mjs +48 -0
  23. package/esm/drivers/memory-store.mjs.map +1 -0
  24. package/esm/drivers/sqlite-store.mjs +89 -0
  25. package/esm/drivers/sqlite-store.mjs.map +1 -0
  26. package/esm/failed-jobs.d.mts +2 -8
  27. package/esm/failed-jobs.d.mts.map +1 -1
  28. package/esm/failed-jobs.mjs +5 -30
  29. package/esm/failed-jobs.mjs.map +1 -1
  30. package/esm/index.d.mts +4 -2
  31. package/esm/index.mjs +2 -1
  32. package/esm/job-options.d.mts +9 -0
  33. package/esm/job-options.d.mts.map +1 -0
  34. package/esm/job-options.mjs +20 -0
  35. package/esm/job-options.mjs.map +1 -0
  36. package/esm/queue-driver.d.mts +6 -0
  37. package/esm/queue-driver.d.mts.map +1 -0
  38. package/esm/queue-driver.mjs +37 -0
  39. package/esm/queue-driver.mjs.map +1 -0
  40. package/esm/queue-manager.d.mts +5 -26
  41. package/esm/queue-manager.d.mts.map +1 -1
  42. package/esm/queue-manager.mjs +28 -84
  43. package/esm/queue-manager.mjs.map +1 -1
  44. package/esm/types.d.mts +18 -5
  45. package/esm/types.d.mts.map +1 -1
  46. package/llms-full.txt +129 -3
  47. package/llms.txt +2 -1
  48. package/package.json +4 -4
  49. package/skills/configure-queue/SKILL.md +1 -1
  50. package/skills/index.md +4 -2
  51. package/skills/local-queues/SKILL.md +121 -0
  52. package/skills/overview/SKILL.md +3 -2
@@ -0,0 +1,23 @@
1
+ import { DispatchedJob, FailedJob, JobSnapshot } from "./types.mjs";
2
+ import { ResolvedJobOptions } from "./job-options.mjs";
3
+ //#region ../../packages/queue/src/driver.d.ts
4
+ /** Driver-neutral request: data includes any captured queue context. */
5
+ export type DriverDispatch = {
6
+ name: string;
7
+ queue: string;
8
+ data: unknown;
9
+ options: ResolvedJobOptions;
10
+ };
11
+ /** All built-in backends implement this lifecycle and storage contract. */
12
+ export interface QueueDriver {
13
+ dispatch(request: DriverDispatch): Promise<DispatchedJob>;
14
+ find(queue: string, id: string): Promise<JobSnapshot | undefined>;
15
+ failed(queue: string, start: number, end: number): Promise<FailedJob[]>;
16
+ retry(queue: string, id: string): Promise<void>;
17
+ start(queue: string): Promise<void>;
18
+ running(): string[];
19
+ /** Stop taking jobs immediately, then drain or release within timeout. */
20
+ close(timeout: number): Promise<void>;
21
+ }
22
+ //#endregion
23
+ //# sourceMappingURL=driver.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"driver.d.mts","names":[],"sources":["../../../../../../../packages/queue/src/driver.ts"],"mappings":";;;;YAIY;EACV;EACA;EACA;EACA,SAAS;;;iBAIM;EACf,SAAS,SAAS,iBAAiB,QAAQ;EAC3C,KAAK,eAAe,aAAa,QAAQ;EACzC,OAAO,eAAe,eAAe,cAAc,QAAQ;EAC3D,MAAM,eAAe,aAAa;EAClC,MAAM,gBAAgB;EACtB;;EAEA,MAAM,kBAAkB"}
@@ -0,0 +1,132 @@
1
+ import { FailedJobNotFoundError } from "../errors.mjs";
2
+ import { unwrapPayload } from "../queue-context.mjs";
3
+ import { processJob } from "../process-job.mjs";
4
+ import { log } from "@warlock.js/logger";
5
+ import { Queue, Worker } from "bullmq";
6
+
7
+ //#region ../../packages/queue/src/drivers/bullmq.ts
8
+ var BullMQDriver = class {
9
+ constructor(config) {
10
+ this.config = config;
11
+ this.queues = /* @__PURE__ */ new Map();
12
+ this.workers = /* @__PURE__ */ new Map();
13
+ if (!config.connection) throw new Error("The bullmq queue driver requires `connection`.");
14
+ }
15
+ getQueue(name) {
16
+ let queue = this.queues.get(name);
17
+ if (!queue) {
18
+ const connection = this.config.connection;
19
+ if (!connection) throw new Error("The bullmq queue driver requires `connection`.");
20
+ queue = new Queue(name, {
21
+ connection,
22
+ prefix: this.config.prefix ?? "warlock"
23
+ });
24
+ queue.on("error", (error) => log.error("queue", "connection", error));
25
+ this.queues.set(name, queue);
26
+ }
27
+ return queue;
28
+ }
29
+ async dispatch(request) {
30
+ const { backoff, ...rest } = request.options;
31
+ const options = {
32
+ ...rest,
33
+ backoff: typeof backoff === "number" ? {
34
+ type: "fixed",
35
+ delay: backoff
36
+ } : backoff
37
+ };
38
+ const job = await this.getQueue(request.queue).add(request.name, request.data, options);
39
+ return {
40
+ id: String(job.id),
41
+ name: request.name,
42
+ queue: request.queue
43
+ };
44
+ }
45
+ async find(queue, id) {
46
+ const source = this.getQueue(queue);
47
+ const initial = await source.getJob(id);
48
+ if (!initial) return void 0;
49
+ const state = await initial.getState();
50
+ return toSnapshot(await source.getJob(id) ?? initial, state);
51
+ }
52
+ async failed(queue, start, end) {
53
+ return (await this.getQueue(queue).getFailed(start, end)).map((job) => ({
54
+ id: String(job.id),
55
+ name: job.name,
56
+ queue,
57
+ payload: unwrapPayload(job.data).payload,
58
+ attemptsMade: job.attemptsMade,
59
+ failedReason: job.failedReason,
60
+ stacktrace: job.stacktrace ?? [],
61
+ failedAt: job.finishedOn ? new Date(job.finishedOn) : void 0,
62
+ retry: () => job.retry("failed")
63
+ }));
64
+ }
65
+ async retry(queue, id) {
66
+ const job = await this.getQueue(queue).getJob(id);
67
+ if (!job || !await job.isFailed()) throw new FailedJobNotFoundError(id, queue);
68
+ await job.retry("failed");
69
+ }
70
+ async start(queue) {
71
+ let worker = this.workers.get(queue);
72
+ if (!worker) {
73
+ const connection = this.config.connection;
74
+ if (!connection) throw new Error("The bullmq queue driver requires `connection`.");
75
+ worker = new Worker(queue, processJob, {
76
+ connection,
77
+ prefix: this.config.prefix ?? "warlock",
78
+ concurrency: this.config.workers?.concurrency ?? 1
79
+ });
80
+ worker.on("error", (error) => log.error("queue", "worker", error));
81
+ worker.on("failed", (job, error) => log.error("queue", "job.failed", `${job?.name ?? "unknown"} (${job?.id ?? "?"}): ${error.message}`));
82
+ this.workers.set(queue, worker);
83
+ }
84
+ await worker.waitUntilReady();
85
+ }
86
+ running() {
87
+ return [...this.workers.keys()];
88
+ }
89
+ async close(timeout) {
90
+ const workers = [...this.workers.values()];
91
+ this.workers.clear();
92
+ await Promise.all(workers.map((worker) => closeWorker(worker, timeout)));
93
+ const queues = [...this.queues.values()];
94
+ this.queues.clear();
95
+ await Promise.all(queues.map((queue) => queue.close()));
96
+ }
97
+ };
98
+ function toSnapshot(job, state) {
99
+ return {
100
+ id: String(job.id),
101
+ name: job.name,
102
+ queue: job.queueName,
103
+ state,
104
+ payload: unwrapPayload(job.data).payload,
105
+ progress: job.progress,
106
+ attemptsMade: job.attemptsMade,
107
+ result: job.returnvalue,
108
+ failedReason: job.failedReason || void 0,
109
+ createdAt: new Date(job.timestamp),
110
+ finishedAt: job.finishedOn ? new Date(job.finishedOn) : void 0
111
+ };
112
+ }
113
+ async function closeWorker(worker, timeout) {
114
+ let timer;
115
+ const timedOut = new Promise((resolve) => {
116
+ timer = setTimeout(() => resolve("timeout"), timeout);
117
+ });
118
+ const closing = worker.close().then(() => "closed");
119
+ closing.catch(() => void 0);
120
+ try {
121
+ if (await Promise.race([closing, timedOut]) === "timeout") {
122
+ log.warn("queue", "shutdown", `Worker for "${worker.name}" still had active jobs after ${timeout}ms; disconnecting. Those jobs are retried once their lock expires.`);
123
+ await worker.disconnect();
124
+ }
125
+ } finally {
126
+ clearTimeout(timer);
127
+ }
128
+ }
129
+
130
+ //#endregion
131
+ export { BullMQDriver };
132
+ //# sourceMappingURL=bullmq.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bullmq.mjs","names":[],"sources":["../../../../../../../../packages/queue/src/drivers/bullmq.ts"],"sourcesContent":["import { log } from \"@warlock.js/logger\";\nimport { Queue, Worker, type Job, type JobsOptions } from \"bullmq\";\nimport type { DriverDispatch, QueueDriver } from \"../driver\";\nimport { FailedJobNotFoundError } from \"../errors\";\nimport { processJob } from \"../process-job\";\nimport { unwrapPayload } from \"../queue-context\";\nimport type { DispatchedJob, FailedJob, JobSnapshot, JobState, QueueConfig } from \"../types\";\n\nexport class BullMQDriver implements QueueDriver {\n private queues = new Map<string, Queue>();\n private workers = new Map<string, Worker>();\n\n public constructor(private config: QueueConfig) {\n if (!config.connection) throw new Error(\"The bullmq queue driver requires `connection`.\");\n }\n\n public getQueue(name: string): Queue {\n let queue = this.queues.get(name);\n if (!queue) {\n const connection = this.config.connection;\n if (!connection) throw new Error(\"The bullmq queue driver requires `connection`.\");\n queue = new Queue(name, { connection, prefix: this.config.prefix ?? \"warlock\" });\n queue.on(\"error\", (error) => log.error(\"queue\", \"connection\", error));\n this.queues.set(name, queue);\n }\n return queue;\n }\n\n public async dispatch(request: DriverDispatch): Promise<DispatchedJob> {\n const { backoff, ...rest } = request.options;\n const options: JobsOptions = {\n ...rest,\n backoff: typeof backoff === \"number\" ? { type: \"fixed\", delay: backoff } : backoff,\n };\n const job = await this.getQueue(request.queue).add(request.name, request.data, options);\n return { id: String(job.id), name: request.name, queue: request.queue };\n }\n\n public async find(queue: string, id: string): Promise<JobSnapshot | undefined> {\n const source = this.getQueue(queue);\n const initial = await source.getJob(id);\n if (!initial) return undefined;\n // Fetch fields after reading state so completion fields match the state.\n const state = (await initial.getState()) as JobState;\n return toSnapshot((await source.getJob(id)) ?? initial, state);\n }\n\n public async failed(queue: string, start: number, end: number): Promise<FailedJob[]> {\n return (await this.getQueue(queue).getFailed(start, end)).map((job) => ({\n id: String(job.id),\n name: job.name,\n queue,\n payload: unwrapPayload(job.data).payload,\n attemptsMade: job.attemptsMade,\n failedReason: job.failedReason,\n stacktrace: job.stacktrace ?? [],\n failedAt: job.finishedOn ? new Date(job.finishedOn) : undefined,\n retry: () => job.retry(\"failed\"),\n }));\n }\n\n public async retry(queue: string, id: string): Promise<void> {\n const job = await this.getQueue(queue).getJob(id);\n if (!job || !(await job.isFailed())) throw new FailedJobNotFoundError(id, queue);\n await job.retry(\"failed\");\n }\n\n public async start(queue: string): Promise<void> {\n let worker = this.workers.get(queue);\n if (!worker) {\n const connection = this.config.connection;\n if (!connection) throw new Error(\"The bullmq queue driver requires `connection`.\");\n worker = new Worker(queue, processJob, {\n connection,\n prefix: this.config.prefix ?? \"warlock\",\n concurrency: this.config.workers?.concurrency ?? 1,\n });\n worker.on(\"error\", (error) => log.error(\"queue\", \"worker\", error));\n worker.on(\"failed\", (job, error) =>\n log.error(\n \"queue\",\n \"job.failed\",\n `${job?.name ?? \"unknown\"} (${job?.id ?? \"?\"}): ${error.message}`,\n ),\n );\n this.workers.set(queue, worker);\n }\n await worker.waitUntilReady();\n }\n\n public running(): string[] {\n return [...this.workers.keys()];\n }\n\n public async close(timeout: number): Promise<void> {\n const workers = [...this.workers.values()];\n this.workers.clear();\n await Promise.all(workers.map((worker) => closeWorker(worker, timeout)));\n const queues = [...this.queues.values()];\n this.queues.clear();\n await Promise.all(queues.map((queue) => queue.close()));\n }\n}\n\nfunction toSnapshot(job: Job, state: JobState): JobSnapshot {\n return {\n id: String(job.id),\n name: job.name,\n queue: job.queueName,\n state,\n payload: unwrapPayload(job.data).payload,\n progress: job.progress as JobSnapshot[\"progress\"],\n attemptsMade: job.attemptsMade,\n result: job.returnvalue as unknown,\n failedReason: job.failedReason || undefined,\n createdAt: new Date(job.timestamp),\n finishedAt: job.finishedOn ? new Date(job.finishedOn) : undefined,\n };\n}\n\nasync function closeWorker(worker: Worker, timeout: number): Promise<void> {\n let timer: NodeJS.Timeout | undefined;\n const timedOut = new Promise<\"timeout\">((resolve) => {\n timer = setTimeout(() => resolve(\"timeout\"), timeout);\n });\n const closing = worker.close().then(() => \"closed\" as const);\n closing.catch(() => undefined);\n try {\n if ((await Promise.race([closing, timedOut])) === \"timeout\") {\n log.warn(\n \"queue\",\n \"shutdown\",\n `Worker for \"${worker.name}\" still had active jobs after ${timeout}ms; disconnecting. ` +\n \"Those jobs are retried once their lock expires.\",\n );\n await worker.disconnect();\n }\n } finally {\n clearTimeout(timer);\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ //#region ../../packages/queue/src/drivers/local-store.ts
2
+ /** Match JSON transport on every backend, without sharing mutable objects. */
3
+ function jsonCopy(value) {
4
+ return JSON.parse(JSON.stringify({ value })).value;
5
+ }
6
+
7
+ //#endregion
8
+ export { jsonCopy };
9
+ //# sourceMappingURL=local-store.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local-store.mjs","names":[],"sources":["../../../../../../../../packages/queue/src/drivers/local-store.ts"],"sourcesContent":["import type { ResolvedJobOptions } from \"../job-options\";\nimport type { JobProgress, JobState } from \"../types\";\n\nexport type LocalJob = {\n id: string;\n queue: string;\n name: string;\n data: unknown;\n options: ResolvedJobOptions;\n state: JobState;\n createdAt: number;\n readyAt: number;\n attemptsMade: number;\n priority: number;\n progress: JobProgress;\n logs: string[];\n stacktrace: string[];\n failedReason?: string;\n result?: unknown;\n finishedAt?: number;\n token?: string;\n leaseUntil: number;\n};\n\n/** Synchronous transactions contain no user code or await points. */\nexport interface LocalStore {\n transaction<T>(run: () => T): T;\n get(queue: string, id: string): LocalJob | undefined;\n list(queue: string, state?: JobState): LocalJob[];\n /** Earliest eligible job, including active jobs with an expired lease. */\n ready(queue: string, now: number): LocalJob | undefined;\n put(job: LocalJob): void;\n remove(queue: string, id: string): void;\n close(): void;\n}\n\n/** Match JSON transport on every backend, without sharing mutable objects. */\nexport function jsonCopy<T>(value: T): T {\n return (JSON.parse(JSON.stringify({ value })) as { value: T }).value;\n}\n"],"mappings":";;AAqCA,SAAgB,SAAY,OAAa;CACvC,OAAQ,KAAK,MAAM,KAAK,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC,CAAkB;AACjE"}
@@ -0,0 +1,307 @@
1
+ import { FailedJobNotFoundError, UnrecoverableJobError } from "../errors.mjs";
2
+ import { findRegisteredJob } from "../job-registry.mjs";
3
+ import { getQueueContext, unwrapPayload } from "../queue-context.mjs";
4
+ import { jsonCopy } from "./local-store.mjs";
5
+ import { log } from "@warlock.js/logger";
6
+ import { randomUUID } from "node:crypto";
7
+
8
+ //#region ../../packages/queue/src/drivers/local.ts
9
+ /** Shared scheduling and lease semantics for the SQLite and memory stores. */
10
+ var LocalDriver = class {
11
+ constructor(store, config) {
12
+ this.store = store;
13
+ this.workers = /* @__PURE__ */ new Map();
14
+ this.active = /* @__PURE__ */ new Set();
15
+ this.closed = false;
16
+ this.stopping = false;
17
+ try {
18
+ this.leaseDuration = positiveInteger(config.local?.leaseDuration ?? 3e4, "local.leaseDuration", 30);
19
+ this.pollInterval = positiveInteger(config.local?.pollInterval ?? 100, "local.pollInterval");
20
+ this.concurrency = positiveInteger(config.workers?.concurrency ?? 1, "workers.concurrency");
21
+ } catch (error) {
22
+ store.close();
23
+ throw error;
24
+ }
25
+ }
26
+ async dispatch(request) {
27
+ this.assertOpen();
28
+ validateOptions(request);
29
+ const id = request.options.jobId ?? randomUUID();
30
+ const now = Date.now();
31
+ return {
32
+ id: this.store.transaction(() => {
33
+ const previous = this.store.get(request.queue, id);
34
+ if (previous) return previous;
35
+ const entry = {
36
+ id,
37
+ name: request.name,
38
+ queue: request.queue,
39
+ data: jsonCopy(request.data),
40
+ options: jsonCopy(request.options),
41
+ state: request.options.delay ? "delayed" : request.options.priority ? "prioritized" : "waiting",
42
+ createdAt: now,
43
+ readyAt: now + (request.options.delay ?? 0),
44
+ attemptsMade: 0,
45
+ priority: request.options.priority ?? 0,
46
+ progress: 0,
47
+ logs: [],
48
+ stacktrace: [],
49
+ leaseUntil: 0
50
+ };
51
+ this.store.put(entry);
52
+ return entry;
53
+ }).id,
54
+ name: request.name,
55
+ queue: request.queue
56
+ };
57
+ }
58
+ async find(queue, id) {
59
+ this.assertOpen();
60
+ const job = this.store.get(queue, id);
61
+ return job ? snapshot(job) : void 0;
62
+ }
63
+ async failed(queue, start, end) {
64
+ this.assertOpen();
65
+ const jobs = newestFirst(this.store.list(queue, "failed"));
66
+ const last = end < 0 ? jobs.length + end : end;
67
+ return jobs.slice(start, last + 1).map((job) => ({
68
+ id: job.id,
69
+ name: job.name,
70
+ queue,
71
+ payload: unwrapPayload(job.data).payload,
72
+ attemptsMade: job.attemptsMade,
73
+ failedReason: job.failedReason ?? "",
74
+ stacktrace: job.stacktrace,
75
+ failedAt: job.finishedAt === void 0 ? void 0 : new Date(job.finishedAt),
76
+ retry: () => this.retry(queue, job.id)
77
+ }));
78
+ }
79
+ async retry(queue, id) {
80
+ this.assertOpen();
81
+ this.store.transaction(() => {
82
+ const job = this.store.get(queue, id);
83
+ if (!job || job.state !== "failed") throw new FailedJobNotFoundError(id, queue);
84
+ job.state = job.priority ? "prioritized" : "waiting";
85
+ job.attemptsMade = 0;
86
+ job.readyAt = Date.now();
87
+ delete job.finishedAt;
88
+ delete job.failedReason;
89
+ delete job.result;
90
+ job.stacktrace = [];
91
+ this.store.put(job);
92
+ });
93
+ }
94
+ async start(queue) {
95
+ this.assertOpen();
96
+ if (this.stopping) throw new Error("Queue workers are shutting down.");
97
+ if (this.workers.has(queue)) return;
98
+ const worker = { active: /* @__PURE__ */ new Set() };
99
+ this.workers.set(queue, worker);
100
+ this.schedule(queue, worker, 0);
101
+ }
102
+ running() {
103
+ return [...this.workers.keys()];
104
+ }
105
+ async close(timeout) {
106
+ if (this.closed) return;
107
+ this.stopping = true;
108
+ for (const worker of this.workers.values()) clearTimeout(worker.timer);
109
+ this.workers.clear();
110
+ let timer;
111
+ const timedOut = new Promise((resolve) => {
112
+ timer = setTimeout(resolve, timeout);
113
+ });
114
+ try {
115
+ await Promise.race([Promise.allSettled([...this.active].map((entry) => entry.done)), timedOut]);
116
+ } finally {
117
+ clearTimeout(timer);
118
+ try {
119
+ for (const entry of this.active) {
120
+ clearInterval(entry.heartbeat);
121
+ entry.controller.abort(/* @__PURE__ */ new Error("Queue shutdown timeout elapsed."));
122
+ this.mutate(entry.job, entry.token, (job) => {
123
+ job.state = job.priority ? "prioritized" : "waiting";
124
+ job.readyAt = Date.now();
125
+ job.leaseUntil = 0;
126
+ delete job.token;
127
+ });
128
+ }
129
+ } finally {
130
+ this.closed = true;
131
+ for (const entry of this.active) clearInterval(entry.heartbeat);
132
+ this.store.close();
133
+ }
134
+ }
135
+ }
136
+ schedule(queue, worker, delay) {
137
+ if (this.stopping) return;
138
+ worker.timer = setTimeout(() => {
139
+ try {
140
+ while (!this.stopping && worker.active.size < this.concurrency) {
141
+ const token = randomUUID();
142
+ const job = this.store.transaction(() => {
143
+ const now = Date.now();
144
+ const candidate = this.store.ready(queue, now);
145
+ if (!candidate) return void 0;
146
+ candidate.state = "active";
147
+ candidate.token = token;
148
+ candidate.leaseUntil = now + this.leaseDuration;
149
+ this.store.put(candidate);
150
+ return candidate;
151
+ });
152
+ if (!job) break;
153
+ this.run(job, token, worker);
154
+ }
155
+ } catch (error) {
156
+ log.error("queue", "worker", error);
157
+ }
158
+ this.schedule(queue, worker, this.pollInterval);
159
+ }, delay);
160
+ }
161
+ run(job, token, worker) {
162
+ const controller = new AbortController();
163
+ const heartbeat = setInterval(() => {
164
+ try {
165
+ if (!this.mutate(job, token, (current) => {
166
+ current.leaseUntil = Date.now() + this.leaseDuration;
167
+ })) {
168
+ controller.abort(/* @__PURE__ */ new Error("Queue job lease expired or changed owner."));
169
+ clearInterval(heartbeat);
170
+ }
171
+ } catch (error) {
172
+ controller.abort(error);
173
+ clearInterval(heartbeat);
174
+ log.error("queue", "lease", error);
175
+ }
176
+ }, Math.floor(this.leaseDuration / 3));
177
+ const entry = {
178
+ job,
179
+ token,
180
+ controller,
181
+ heartbeat,
182
+ done: Promise.resolve()
183
+ };
184
+ this.active.add(entry);
185
+ worker.active.add(entry);
186
+ entry.done = this.execute(entry).catch((error) => {
187
+ log.error("queue", "job", error);
188
+ }).finally(() => {
189
+ clearInterval(heartbeat);
190
+ this.active.delete(entry);
191
+ worker.active.delete(entry);
192
+ });
193
+ }
194
+ async execute(entry) {
195
+ const { job, token, controller } = entry;
196
+ try {
197
+ const definition = findRegisteredJob(job.name);
198
+ if (!definition) throw new UnrecoverableJobError(`No job named "${job.name}" is defined in this process. Import its definition in the worker.`);
199
+ const { payload, context: captured } = unwrapPayload(job.data);
200
+ const context = {
201
+ id: job.id,
202
+ name: job.name,
203
+ queue: job.queue,
204
+ attempt: job.attemptsMade + 1,
205
+ maxAttempts: job.options.attempts ?? 1,
206
+ context: captured,
207
+ signal: controller.signal,
208
+ progress: async (value) => {
209
+ if (!this.mutate(job, token, (current) => {
210
+ current.progress = jsonCopy(value);
211
+ })) throw new Error("Queue job lease lost.");
212
+ },
213
+ log: async (line) => {
214
+ if (!this.mutate(job, token, (current) => {
215
+ current.logs.push(line);
216
+ })) throw new Error("Queue job lease lost.");
217
+ }
218
+ };
219
+ const run = async () => definition.handle(payload, context);
220
+ const queueContext = getQueueContext();
221
+ const result = jsonCopy(captured !== void 0 && queueContext ? await queueContext.restore(captured, run) : await run());
222
+ this.mutate(job, token, (current) => {
223
+ current.attemptsMade++;
224
+ current.result = result;
225
+ current.state = "completed";
226
+ current.finishedAt = Date.now();
227
+ current.leaseUntil = 0;
228
+ delete current.token;
229
+ }, true);
230
+ } catch (error) {
231
+ this.mutate(job, token, (current) => {
232
+ current.attemptsMade++;
233
+ current.failedReason = error instanceof Error ? error.message : String(error);
234
+ current.stacktrace.push(error instanceof Error ? error.stack ?? error.message : String(error));
235
+ current.leaseUntil = 0;
236
+ delete current.token;
237
+ if (error instanceof UnrecoverableJobError || current.attemptsMade >= (current.options.attempts ?? 1)) {
238
+ current.state = "failed";
239
+ current.finishedAt = Date.now();
240
+ } else {
241
+ const backoff = current.options.backoff;
242
+ const base = typeof backoff === "number" ? backoff : backoff?.delay ?? 0;
243
+ const delay = typeof backoff === "object" && backoff.type === "exponential" ? base * 2 ** (current.attemptsMade - 1) : base;
244
+ current.readyAt = Math.min(Number.MAX_SAFE_INTEGER, Date.now() + delay);
245
+ current.state = delay > 0 ? "delayed" : current.priority ? "prioritized" : "waiting";
246
+ }
247
+ }, true);
248
+ }
249
+ }
250
+ /** Fencing: expired owners cannot renew, finish, or overwrite a new owner's job. */
251
+ mutate(job, token, update, retain = false) {
252
+ if (this.closed) return false;
253
+ return this.store.transaction(() => {
254
+ const current = this.store.get(job.queue, job.id);
255
+ if (!current || !ownsLease(current, token)) return false;
256
+ update(current);
257
+ this.store.put(current);
258
+ if (retain && (current.state === "completed" || current.state === "failed")) {
259
+ const retention = current.state === "completed" ? current.options.removeOnComplete : current.options.removeOnFail;
260
+ if (retention === true) this.store.remove(current.queue, current.id);
261
+ if (typeof retention === "number") for (const old of newestFirst(this.store.list(current.queue, current.state)).slice(retention)) this.store.remove(old.queue, old.id);
262
+ }
263
+ return true;
264
+ });
265
+ }
266
+ assertOpen() {
267
+ if (this.closed) throw new Error("Queue driver is closed.");
268
+ }
269
+ };
270
+ function snapshot(job) {
271
+ return {
272
+ id: job.id,
273
+ name: job.name,
274
+ queue: job.queue,
275
+ state: job.state,
276
+ payload: unwrapPayload(job.data).payload,
277
+ progress: job.progress,
278
+ attemptsMade: job.attemptsMade,
279
+ result: job.result,
280
+ failedReason: job.failedReason,
281
+ createdAt: new Date(job.createdAt),
282
+ finishedAt: job.finishedAt === void 0 ? void 0 : new Date(job.finishedAt)
283
+ };
284
+ }
285
+ function newestFirst(jobs) {
286
+ return jobs.reverse().sort((a, b) => (b.finishedAt ?? 0) - (a.finishedAt ?? 0));
287
+ }
288
+ function positiveInteger(value, name, minimum = 1) {
289
+ if (!Number.isSafeInteger(value) || value < minimum || value > 2147483647) throw new Error(`${name} must be an integer between ${minimum} and 2147483647.`);
290
+ return value;
291
+ }
292
+ function validateOptions({ options }) {
293
+ positiveInteger(options.attempts ?? 1, "attempts");
294
+ if (options.priority !== void 0 && (!Number.isInteger(options.priority) || options.priority < 0 || options.priority > 2097152)) throw new Error("priority must be an integer between 0 and 2097152.");
295
+ const backoff = options.backoff;
296
+ const delay = typeof backoff === "number" ? backoff : backoff?.delay ?? 0;
297
+ if (!Number.isFinite(delay) || delay < 0 || typeof backoff === "object" && !["fixed", "exponential"].includes(backoff.type)) throw new Error("backoff must have a non-negative finite delay and fixed or exponential type.");
298
+ if (options.delay !== void 0 && (!Number.isFinite(options.delay) || options.delay < 0)) throw new Error("delay must be non-negative and finite.");
299
+ for (const retention of [options.removeOnComplete, options.removeOnFail]) if (typeof retention === "number" && (!Number.isInteger(retention) || retention < 0)) throw new Error("Job retention must be a non-negative integer or boolean.");
300
+ }
301
+ function ownsLease(job, token) {
302
+ return job.state === "active" && job.token === token && job.leaseUntil > Date.now();
303
+ }
304
+
305
+ //#endregion
306
+ export { LocalDriver };
307
+ //# sourceMappingURL=local.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local.mjs","names":[],"sources":["../../../../../../../../packages/queue/src/drivers/local.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { log } from \"@warlock.js/logger\";\nimport type { DriverDispatch, QueueDriver } from \"../driver\";\nimport { FailedJobNotFoundError, UnrecoverableJobError } from \"../errors\";\nimport { findRegisteredJob } from \"../job-registry\";\nimport { getQueueContext, unwrapPayload } from \"../queue-context\";\nimport type { DispatchedJob, FailedJob, JobContext, JobSnapshot, QueueConfig } from \"../types\";\nimport { jsonCopy, type LocalJob, type LocalStore } from \"./local-store\";\n\ntype ActiveJob = {\n job: LocalJob;\n token: string;\n controller: AbortController;\n heartbeat: NodeJS.Timeout;\n done: Promise<void>;\n};\ntype LocalWorker = { timer?: NodeJS.Timeout; active: Set<ActiveJob> };\n\n/** Shared scheduling and lease semantics for the SQLite and memory stores. */\nexport class LocalDriver implements QueueDriver {\n private workers = new Map<string, LocalWorker>();\n private active = new Set<ActiveJob>();\n private closed = false;\n private stopping = false;\n private leaseDuration: number;\n private pollInterval: number;\n private concurrency: number;\n\n public constructor(\n private store: LocalStore,\n config: QueueConfig,\n ) {\n try {\n this.leaseDuration = positiveInteger(\n config.local?.leaseDuration ?? 30_000,\n \"local.leaseDuration\",\n 30,\n );\n this.pollInterval = positiveInteger(config.local?.pollInterval ?? 100, \"local.pollInterval\");\n this.concurrency = positiveInteger(config.workers?.concurrency ?? 1, \"workers.concurrency\");\n } catch (error) {\n store.close();\n throw error;\n }\n }\n\n public async dispatch(request: DriverDispatch): Promise<DispatchedJob> {\n this.assertOpen();\n validateOptions(request);\n const id = request.options.jobId ?? randomUUID();\n const now = Date.now();\n const job = this.store.transaction(() => {\n const previous = this.store.get(request.queue, id);\n if (previous) return previous;\n const entry: LocalJob = {\n id,\n name: request.name,\n queue: request.queue,\n data: jsonCopy(request.data),\n options: jsonCopy(request.options),\n state: request.options.delay\n ? \"delayed\"\n : request.options.priority\n ? \"prioritized\"\n : \"waiting\",\n createdAt: now,\n readyAt: now + (request.options.delay ?? 0),\n attemptsMade: 0,\n priority: request.options.priority ?? 0,\n progress: 0,\n logs: [],\n stacktrace: [],\n leaseUntil: 0,\n };\n this.store.put(entry);\n return entry;\n });\n return { id: job.id, name: request.name, queue: request.queue };\n }\n\n public async find(queue: string, id: string): Promise<JobSnapshot | undefined> {\n this.assertOpen();\n const job = this.store.get(queue, id);\n return job ? snapshot(job) : undefined;\n }\n\n public async failed(queue: string, start: number, end: number): Promise<FailedJob[]> {\n this.assertOpen();\n const jobs = newestFirst(this.store.list(queue, \"failed\"));\n const last = end < 0 ? jobs.length + end : end;\n return jobs.slice(start, last + 1).map((job) => ({\n id: job.id,\n name: job.name,\n queue,\n payload: unwrapPayload(job.data).payload,\n attemptsMade: job.attemptsMade,\n failedReason: job.failedReason ?? \"\",\n stacktrace: job.stacktrace,\n failedAt: job.finishedAt === undefined ? undefined : new Date(job.finishedAt),\n retry: () => this.retry(queue, job.id),\n }));\n }\n\n public async retry(queue: string, id: string): Promise<void> {\n this.assertOpen();\n this.store.transaction(() => {\n const job = this.store.get(queue, id);\n if (!job || job.state !== \"failed\") throw new FailedJobNotFoundError(id, queue);\n job.state = job.priority ? \"prioritized\" : \"waiting\";\n job.attemptsMade = 0;\n job.readyAt = Date.now();\n delete job.finishedAt;\n delete job.failedReason;\n delete job.result;\n job.stacktrace = [];\n this.store.put(job);\n });\n }\n\n public async start(queue: string): Promise<void> {\n this.assertOpen();\n if (this.stopping) throw new Error(\"Queue workers are shutting down.\");\n if (this.workers.has(queue)) return;\n const worker: LocalWorker = { active: new Set() };\n this.workers.set(queue, worker);\n this.schedule(queue, worker, 0);\n }\n\n public running(): string[] {\n return [...this.workers.keys()];\n }\n\n public async close(timeout: number): Promise<void> {\n if (this.closed) return;\n this.stopping = true;\n for (const worker of this.workers.values()) clearTimeout(worker.timer);\n this.workers.clear();\n let timer: NodeJS.Timeout | undefined;\n const timedOut = new Promise<void>((resolve) => {\n timer = setTimeout(resolve, timeout);\n });\n try {\n await Promise.race([\n Promise.allSettled([...this.active].map((entry) => entry.done)),\n timedOut,\n ]);\n } finally {\n clearTimeout(timer);\n try {\n for (const entry of this.active) {\n clearInterval(entry.heartbeat);\n entry.controller.abort(new Error(\"Queue shutdown timeout elapsed.\"));\n this.mutate(entry.job, entry.token, (job) => {\n job.state = job.priority ? \"prioritized\" : \"waiting\";\n job.readyAt = Date.now();\n job.leaseUntil = 0;\n delete job.token;\n });\n }\n } finally {\n this.closed = true;\n for (const entry of this.active) clearInterval(entry.heartbeat);\n this.store.close();\n }\n }\n }\n\n private schedule(queue: string, worker: LocalWorker, delay: number): void {\n if (this.stopping) return;\n worker.timer = setTimeout(() => {\n try {\n while (!this.stopping && worker.active.size < this.concurrency) {\n const token = randomUUID();\n const job = this.store.transaction(() => {\n const now = Date.now();\n const candidate = this.store.ready(queue, now);\n if (!candidate) return undefined;\n candidate.state = \"active\";\n candidate.token = token;\n candidate.leaseUntil = now + this.leaseDuration;\n this.store.put(candidate);\n return candidate;\n });\n if (!job) break;\n this.run(job, token, worker);\n }\n } catch (error) {\n log.error(\"queue\", \"worker\", error);\n }\n this.schedule(queue, worker, this.pollInterval);\n }, delay);\n }\n\n private run(job: LocalJob, token: string, worker: LocalWorker): void {\n const controller = new AbortController();\n const heartbeat = setInterval(\n () => {\n try {\n if (\n !this.mutate(job, token, (current) => {\n current.leaseUntil = Date.now() + this.leaseDuration;\n })\n ) {\n controller.abort(new Error(\"Queue job lease expired or changed owner.\"));\n clearInterval(heartbeat);\n }\n } catch (error) {\n controller.abort(error);\n clearInterval(heartbeat);\n log.error(\"queue\", \"lease\", error);\n }\n },\n Math.floor(this.leaseDuration / 3),\n );\n const entry: ActiveJob = { job, token, controller, heartbeat, done: Promise.resolve() };\n this.active.add(entry);\n worker.active.add(entry);\n entry.done = this.execute(entry)\n .catch((error: unknown) => {\n log.error(\"queue\", \"job\", error);\n })\n .finally(() => {\n clearInterval(heartbeat);\n this.active.delete(entry);\n worker.active.delete(entry);\n });\n }\n\n private async execute(entry: ActiveJob): Promise<void> {\n const { job, token, controller } = entry;\n try {\n const definition = findRegisteredJob(job.name);\n if (!definition)\n throw new UnrecoverableJobError(\n `No job named \"${job.name}\" is defined in this process. Import its definition in the worker.`,\n );\n const { payload, context: captured } = unwrapPayload(job.data);\n const context: JobContext = {\n id: job.id,\n name: job.name,\n queue: job.queue,\n attempt: job.attemptsMade + 1,\n maxAttempts: job.options.attempts ?? 1,\n context: captured,\n signal: controller.signal,\n progress: async (value) => {\n if (\n !this.mutate(job, token, (current) => {\n current.progress = jsonCopy(value);\n })\n )\n throw new Error(\"Queue job lease lost.\");\n },\n log: async (line) => {\n if (\n !this.mutate(job, token, (current) => {\n current.logs.push(line);\n })\n )\n throw new Error(\"Queue job lease lost.\");\n },\n };\n const run = async () => definition.handle(payload, context);\n const queueContext = getQueueContext();\n const result = jsonCopy(\n captured !== undefined && queueContext\n ? await queueContext.restore(captured, run)\n : await run(),\n );\n this.mutate(\n job,\n token,\n (current) => {\n current.attemptsMade++;\n current.result = result;\n current.state = \"completed\";\n current.finishedAt = Date.now();\n current.leaseUntil = 0;\n delete current.token;\n },\n true,\n );\n } catch (error) {\n this.mutate(\n job,\n token,\n (current) => {\n current.attemptsMade++;\n current.failedReason = error instanceof Error ? error.message : String(error);\n current.stacktrace.push(\n error instanceof Error ? (error.stack ?? error.message) : String(error),\n );\n current.leaseUntil = 0;\n delete current.token;\n if (\n error instanceof UnrecoverableJobError ||\n current.attemptsMade >= (current.options.attempts ?? 1)\n ) {\n current.state = \"failed\";\n current.finishedAt = Date.now();\n } else {\n const backoff = current.options.backoff;\n const base = typeof backoff === \"number\" ? backoff : (backoff?.delay ?? 0);\n const delay =\n typeof backoff === \"object\" && backoff.type === \"exponential\"\n ? base * 2 ** (current.attemptsMade - 1)\n : base;\n current.readyAt = Math.min(Number.MAX_SAFE_INTEGER, Date.now() + delay);\n current.state = delay > 0 ? \"delayed\" : current.priority ? \"prioritized\" : \"waiting\";\n }\n },\n true,\n );\n }\n }\n\n /** Fencing: expired owners cannot renew, finish, or overwrite a new owner's job. */\n private mutate(\n job: LocalJob,\n token: string,\n update: (current: LocalJob) => void,\n retain = false,\n ): boolean {\n if (this.closed) return false;\n return this.store.transaction(() => {\n const current = this.store.get(job.queue, job.id);\n if (!current || !ownsLease(current, token)) return false;\n update(current);\n this.store.put(current);\n if (retain && (current.state === \"completed\" || current.state === \"failed\")) {\n const retention =\n current.state === \"completed\"\n ? current.options.removeOnComplete\n : current.options.removeOnFail;\n if (retention === true) this.store.remove(current.queue, current.id);\n if (typeof retention === \"number\") {\n for (const old of newestFirst(this.store.list(current.queue, current.state)).slice(\n retention,\n ))\n this.store.remove(old.queue, old.id);\n }\n }\n return true;\n });\n }\n\n private assertOpen(): void {\n if (this.closed) throw new Error(\"Queue driver is closed.\");\n }\n}\n\nfunction snapshot(job: LocalJob): JobSnapshot {\n return {\n id: job.id,\n name: job.name,\n queue: job.queue,\n state: job.state,\n payload: unwrapPayload(job.data).payload,\n progress: job.progress,\n attemptsMade: job.attemptsMade,\n result: job.result,\n failedReason: job.failedReason,\n createdAt: new Date(job.createdAt),\n finishedAt: job.finishedAt === undefined ? undefined : new Date(job.finishedAt),\n };\n}\nfunction newestFirst(jobs: LocalJob[]): LocalJob[] {\n return jobs.reverse().sort((a, b) => (b.finishedAt ?? 0) - (a.finishedAt ?? 0));\n}\nfunction positiveInteger(value: number, name: string, minimum = 1): number {\n if (!Number.isSafeInteger(value) || value < minimum || value > 2_147_483_647)\n throw new Error(`${name} must be an integer between ${minimum} and 2147483647.`);\n return value;\n}\nfunction validateOptions({ options }: DriverDispatch): void {\n positiveInteger(options.attempts ?? 1, \"attempts\");\n if (\n options.priority !== undefined &&\n (!Number.isInteger(options.priority) || options.priority < 0 || options.priority > 2_097_152)\n )\n throw new Error(\"priority must be an integer between 0 and 2097152.\");\n const backoff = options.backoff;\n const delay = typeof backoff === \"number\" ? backoff : (backoff?.delay ?? 0);\n if (\n !Number.isFinite(delay) ||\n delay < 0 ||\n (typeof backoff === \"object\" && ![\"fixed\", \"exponential\"].includes(backoff.type))\n )\n throw new Error(\"backoff must have a non-negative finite delay and fixed or exponential type.\");\n if (options.delay !== undefined && (!Number.isFinite(options.delay) || options.delay < 0))\n throw new Error(\"delay must be non-negative and finite.\");\n for (const retention of [options.removeOnComplete, options.removeOnFail]) {\n if (typeof retention === \"number\" && (!Number.isInteger(retention) || retention < 0))\n throw new Error(\"Job retention must be a non-negative integer or boolean.\");\n }\n}\n\nfunction ownsLease(job: LocalJob, token: string): boolean {\n return job.state === \"active\" && job.token === token && job.leaseUntil > Date.now();\n}\n"],"mappings":"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"}
@@ -0,0 +1,48 @@
1
+ import { jsonCopy } from "./local-store.mjs";
2
+
3
+ //#region ../../packages/queue/src/drivers/memory-store.ts
4
+ var MemoryStore = class {
5
+ constructor() {
6
+ this.jobs = /* @__PURE__ */ new Map();
7
+ }
8
+ transaction(run) {
9
+ const before = new Map(this.jobs);
10
+ try {
11
+ return run();
12
+ } catch (error) {
13
+ this.jobs = before;
14
+ throw error;
15
+ }
16
+ }
17
+ key(queue, id) {
18
+ return JSON.stringify([queue, id]);
19
+ }
20
+ get(queue, id) {
21
+ return jsonCopy(this.jobs.get(this.key(queue, id)));
22
+ }
23
+ list(queue, state) {
24
+ return [...this.jobs.values()].filter((job) => job.queue === queue && (!state || job.state === state)).map(jsonCopy);
25
+ }
26
+ ready(queue, now) {
27
+ let candidate;
28
+ for (const job of this.list(queue)) if ((job.state === "active" && job.leaseUntil <= now || [
29
+ "waiting",
30
+ "delayed",
31
+ "prioritized"
32
+ ].includes(job.state) && job.readyAt <= now) && (!candidate || job.priority < candidate.priority)) candidate = job;
33
+ return candidate;
34
+ }
35
+ put(job) {
36
+ this.jobs.set(this.key(job.queue, job.id), jsonCopy(job));
37
+ }
38
+ remove(queue, id) {
39
+ this.jobs.delete(this.key(queue, id));
40
+ }
41
+ close() {
42
+ this.jobs.clear();
43
+ }
44
+ };
45
+
46
+ //#endregion
47
+ export { MemoryStore };
48
+ //# sourceMappingURL=memory-store.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory-store.mjs","names":[],"sources":["../../../../../../../../packages/queue/src/drivers/memory-store.ts"],"sourcesContent":["import type { JobState } from \"../types\";\nimport { jsonCopy, type LocalJob, type LocalStore } from \"./local-store\";\n\nexport class MemoryStore implements LocalStore {\n private jobs = new Map<string, LocalJob>();\n public transaction<T>(run: () => T): T {\n const before = new Map(this.jobs);\n try {\n return run();\n } catch (error) {\n this.jobs = before;\n throw error;\n }\n }\n private key(queue: string, id: string): string {\n return JSON.stringify([queue, id]);\n }\n public get(queue: string, id: string): LocalJob | undefined {\n return jsonCopy(this.jobs.get(this.key(queue, id)));\n }\n public list(queue: string, state?: JobState): LocalJob[] {\n return [...this.jobs.values()]\n .filter((job) => job.queue === queue && (!state || job.state === state))\n .map(jsonCopy);\n }\n public ready(queue: string, now: number): LocalJob | undefined {\n let candidate: LocalJob | undefined;\n for (const job of this.list(queue)) {\n const eligible =\n (job.state === \"active\" && job.leaseUntil <= now) ||\n ([\"waiting\", \"delayed\", \"prioritized\"].includes(job.state) && job.readyAt <= now);\n if (eligible && (!candidate || job.priority < candidate.priority)) candidate = job;\n }\n return candidate;\n }\n public put(job: LocalJob): void {\n this.jobs.set(this.key(job.queue, job.id), jsonCopy(job));\n }\n public remove(queue: string, id: string): void {\n this.jobs.delete(this.key(queue, id));\n }\n public close(): void {\n this.jobs.clear();\n }\n}\n"],"mappings":";;;AAGA,IAAa,cAAb,MAA+C;;8BAC9B,IAAI,IAAsB;;CACzC,AAAO,YAAe,KAAiB;EACrC,MAAM,SAAS,IAAI,IAAI,KAAK,IAAI;EAChC,IAAI;GACF,OAAO,IAAI;EACb,SAAS,OAAO;GACd,KAAK,OAAO;GACZ,MAAM;EACR;CACF;CACA,AAAQ,IAAI,OAAe,IAAoB;EAC7C,OAAO,KAAK,UAAU,CAAC,OAAO,EAAE,CAAC;CACnC;CACA,AAAO,IAAI,OAAe,IAAkC;EAC1D,OAAO,SAAS,KAAK,KAAK,IAAI,KAAK,IAAI,OAAO,EAAE,CAAC,CAAC;CACpD;CACA,AAAO,KAAK,OAAe,OAA8B;EACvD,OAAO,CAAC,GAAG,KAAK,KAAK,OAAO,CAAC,CAAC,CAC3B,QAAQ,QAAQ,IAAI,UAAU,UAAU,CAAC,SAAS,IAAI,UAAU,MAAM,CAAC,CACvE,IAAI,QAAQ;CACjB;CACA,AAAO,MAAM,OAAe,KAAmC;EAC7D,IAAI;EACJ,KAAK,MAAM,OAAO,KAAK,KAAK,KAAK,GAI/B,KAFG,IAAI,UAAU,YAAY,IAAI,cAAc,OAC5C;GAAC;GAAW;GAAW;EAAa,CAAC,CAAC,SAAS,IAAI,KAAK,KAAK,IAAI,WAAW,SAC9D,CAAC,aAAa,IAAI,WAAW,UAAU,WAAW,YAAY;EAEjF,OAAO;CACT;CACA,AAAO,IAAI,KAAqB;EAC9B,KAAK,KAAK,IAAI,KAAK,IAAI,IAAI,OAAO,IAAI,EAAE,GAAG,SAAS,GAAG,CAAC;CAC1D;CACA,AAAO,OAAO,OAAe,IAAkB;EAC7C,KAAK,KAAK,OAAO,KAAK,IAAI,OAAO,EAAE,CAAC;CACtC;CACA,AAAO,QAAc;EACnB,KAAK,KAAK,MAAM;CAClB;AACF"}