@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.
- package/CHANGELOG.md +12 -51
- package/README.md +18 -3
- package/cjs/index.cjs +644 -168
- package/cjs/index.cjs.map +1 -1
- package/esm/dashboard-boot.mjs +1 -0
- package/esm/dashboard-boot.mjs.map +1 -1
- package/esm/dashboard.d.mts.map +1 -1
- package/esm/dashboard.mjs +2 -1
- package/esm/dashboard.mjs.map +1 -1
- package/esm/define-job.d.mts +0 -1
- package/esm/define-job.d.mts.map +1 -1
- package/esm/define-job.mjs +11 -64
- package/esm/define-job.mjs.map +1 -1
- package/esm/driver.d.mts +23 -0
- package/esm/driver.d.mts.map +1 -0
- package/esm/drivers/bullmq.mjs +132 -0
- package/esm/drivers/bullmq.mjs.map +1 -0
- package/esm/drivers/local-store.mjs +9 -0
- package/esm/drivers/local-store.mjs.map +1 -0
- package/esm/drivers/local.mjs +307 -0
- package/esm/drivers/local.mjs.map +1 -0
- package/esm/drivers/memory-store.mjs +48 -0
- package/esm/drivers/memory-store.mjs.map +1 -0
- package/esm/drivers/sqlite-store.mjs +89 -0
- package/esm/drivers/sqlite-store.mjs.map +1 -0
- package/esm/failed-jobs.d.mts +2 -8
- package/esm/failed-jobs.d.mts.map +1 -1
- package/esm/failed-jobs.mjs +5 -30
- package/esm/failed-jobs.mjs.map +1 -1
- package/esm/index.d.mts +4 -2
- package/esm/index.mjs +2 -1
- package/esm/job-options.d.mts +9 -0
- package/esm/job-options.d.mts.map +1 -0
- package/esm/job-options.mjs +20 -0
- package/esm/job-options.mjs.map +1 -0
- package/esm/queue-driver.d.mts +6 -0
- package/esm/queue-driver.d.mts.map +1 -0
- package/esm/queue-driver.mjs +37 -0
- package/esm/queue-driver.mjs.map +1 -0
- package/esm/queue-manager.d.mts +5 -26
- package/esm/queue-manager.d.mts.map +1 -1
- package/esm/queue-manager.mjs +28 -84
- package/esm/queue-manager.mjs.map +1 -1
- package/esm/types.d.mts +18 -5
- package/esm/types.d.mts.map +1 -1
- package/llms-full.txt +129 -3
- package/llms.txt +2 -1
- package/package.json +4 -4
- package/skills/configure-queue/SKILL.md +1 -1
- package/skills/index.md +4 -2
- package/skills/local-queues/SKILL.md +121 -0
- package/skills/overview/SKILL.md +3 -2
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { mkdirSync } from "node:fs";
|
|
3
|
+
import { dirname, resolve } from "node:path";
|
|
4
|
+
|
|
5
|
+
//#region ../../packages/queue/src/drivers/sqlite-store.ts
|
|
6
|
+
/** node:sqlite is loaded only when the local backend is selected. */
|
|
7
|
+
var SqliteStore = class {
|
|
8
|
+
constructor(config) {
|
|
9
|
+
const path = resolve(config.local?.path ?? ".warlock/queue.sqlite");
|
|
10
|
+
if (config.local?.path === ":memory:") throw new Error("Use driver: 'memory' for non-durable queues.");
|
|
11
|
+
let sqlite;
|
|
12
|
+
try {
|
|
13
|
+
sqlite = createRequire(import.meta.url)("node:sqlite");
|
|
14
|
+
} catch (cause) {
|
|
15
|
+
throw new Error("The local queue driver requires Node >= 22.15 with node:sqlite available without flags.", { cause });
|
|
16
|
+
}
|
|
17
|
+
if (typeof sqlite.DatabaseSync !== "function") throw new Error("node:sqlite.DatabaseSync is unavailable.");
|
|
18
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
19
|
+
this.db = new sqlite.DatabaseSync(path);
|
|
20
|
+
this.namespace = config.prefix ?? "warlock";
|
|
21
|
+
try {
|
|
22
|
+
this.db.exec(`
|
|
23
|
+
PRAGMA busy_timeout = 5000;
|
|
24
|
+
PRAGMA journal_mode = WAL;
|
|
25
|
+
PRAGMA synchronous = FULL;
|
|
26
|
+
CREATE TABLE IF NOT EXISTS warlock_queue_jobs (
|
|
27
|
+
seq INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
28
|
+
namespace TEXT NOT NULL, queue TEXT NOT NULL, id TEXT NOT NULL,
|
|
29
|
+
state TEXT NOT NULL, ready_at REAL NOT NULL, lease_until REAL NOT NULL,
|
|
30
|
+
priority INTEGER NOT NULL, body TEXT NOT NULL,
|
|
31
|
+
UNIQUE(namespace, queue, id)
|
|
32
|
+
);
|
|
33
|
+
CREATE INDEX IF NOT EXISTS warlock_queue_ready
|
|
34
|
+
ON warlock_queue_jobs(namespace, queue, state, ready_at, lease_until);
|
|
35
|
+
`);
|
|
36
|
+
} catch (error) {
|
|
37
|
+
this.db.close();
|
|
38
|
+
throw error;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
transaction(run) {
|
|
42
|
+
this.db.exec("BEGIN IMMEDIATE");
|
|
43
|
+
try {
|
|
44
|
+
const result = run();
|
|
45
|
+
this.db.exec("COMMIT");
|
|
46
|
+
return result;
|
|
47
|
+
} catch (error) {
|
|
48
|
+
this.db.exec("ROLLBACK");
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
get(queue, id) {
|
|
53
|
+
return decode(this.db.prepare("SELECT body FROM warlock_queue_jobs WHERE namespace = ? AND queue = ? AND id = ?").get(this.namespace, queue, id));
|
|
54
|
+
}
|
|
55
|
+
list(queue, state) {
|
|
56
|
+
return (state ? this.db.prepare("SELECT body FROM warlock_queue_jobs WHERE namespace = ? AND queue = ? AND state = ? ORDER BY seq").all(this.namespace, queue, state) : this.db.prepare("SELECT body FROM warlock_queue_jobs WHERE namespace = ? AND queue = ? ORDER BY seq").all(this.namespace, queue)).flatMap((row) => {
|
|
57
|
+
const job = decode(row);
|
|
58
|
+
return job ? [job] : [];
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
ready(queue, now) {
|
|
62
|
+
return decode(this.db.prepare(`SELECT body FROM warlock_queue_jobs
|
|
63
|
+
WHERE namespace = ? AND queue = ? AND (
|
|
64
|
+
(state IN ('waiting', 'delayed', 'prioritized') AND ready_at <= ?) OR
|
|
65
|
+
(state = 'active' AND lease_until <= ?)
|
|
66
|
+
) ORDER BY priority, seq LIMIT 1`).get(this.namespace, queue, now, now));
|
|
67
|
+
}
|
|
68
|
+
put(job) {
|
|
69
|
+
this.db.prepare(`INSERT INTO warlock_queue_jobs
|
|
70
|
+
(namespace, queue, id, state, ready_at, lease_until, priority, body)
|
|
71
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
72
|
+
ON CONFLICT(namespace, queue, id) DO UPDATE SET
|
|
73
|
+
state = excluded.state, ready_at = excluded.ready_at,
|
|
74
|
+
lease_until = excluded.lease_until, priority = excluded.priority, body = excluded.body`).run(this.namespace, job.queue, job.id, job.state, job.readyAt, job.leaseUntil, job.priority, JSON.stringify(job));
|
|
75
|
+
}
|
|
76
|
+
remove(queue, id) {
|
|
77
|
+
this.db.prepare("DELETE FROM warlock_queue_jobs WHERE namespace = ? AND queue = ? AND id = ?").run(this.namespace, queue, id);
|
|
78
|
+
}
|
|
79
|
+
close() {
|
|
80
|
+
this.db.close();
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
function decode(row) {
|
|
84
|
+
return typeof row?.body === "string" ? JSON.parse(row.body) : void 0;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
//#endregion
|
|
88
|
+
export { SqliteStore };
|
|
89
|
+
//# sourceMappingURL=sqlite-store.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqlite-store.mjs","names":[],"sources":["../../../../../../../../packages/queue/src/drivers/sqlite-store.ts"],"sourcesContent":["import { mkdirSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { dirname, resolve } from \"node:path\";\nimport type { JobState, QueueConfig } from \"../types\";\nimport type { LocalJob, LocalStore } from \"./local-store\";\n\n/**\n * The part of `node:sqlite` this store uses. Declared here so apps type-check the\n * shipped source without depending on a particular `@types/node` release.\n * https://nodejs.org/docs/latest-v22.x/api/sqlite.html\n */\ntype SqliteValue = null | number | bigint | string | Uint8Array;\ntype SqliteRow = Record<string, unknown>;\ntype SqliteStatement = {\n all(...parameters: SqliteValue[]): SqliteRow[];\n get(...parameters: SqliteValue[]): SqliteRow | undefined;\n run(...parameters: SqliteValue[]): unknown;\n};\ntype DatabaseSync = {\n exec(sql: string): void;\n prepare(sql: string): SqliteStatement;\n close(): void;\n};\ntype SqliteModule = { DatabaseSync: new (path: string) => DatabaseSync };\n\n/** node:sqlite is loaded only when the local backend is selected. */\nexport class SqliteStore implements LocalStore {\n private db: DatabaseSync;\n private namespace: string;\n\n public constructor(config: QueueConfig) {\n const path = resolve(config.local?.path ?? \".warlock/queue.sqlite\");\n if (config.local?.path === \":memory:\")\n throw new Error(\"Use driver: 'memory' for non-durable queues.\");\n let sqlite: SqliteModule;\n try {\n sqlite = createRequire(import.meta.url)(\"node:sqlite\") as SqliteModule;\n } catch (cause) {\n throw new Error(\n \"The local queue driver requires Node >= 22.15 with node:sqlite available without flags.\",\n { cause },\n );\n }\n if (typeof sqlite.DatabaseSync !== \"function\")\n throw new Error(\"node:sqlite.DatabaseSync is unavailable.\");\n mkdirSync(dirname(path), { recursive: true });\n this.db = new sqlite.DatabaseSync(path);\n this.namespace = config.prefix ?? \"warlock\";\n try {\n // Use the v22.15 API; constructor timeout options were introduced later.\n this.db.exec(`\n PRAGMA busy_timeout = 5000;\n PRAGMA journal_mode = WAL;\n PRAGMA synchronous = FULL;\n CREATE TABLE IF NOT EXISTS warlock_queue_jobs (\n seq INTEGER PRIMARY KEY AUTOINCREMENT,\n namespace TEXT NOT NULL, queue TEXT NOT NULL, id TEXT NOT NULL,\n state TEXT NOT NULL, ready_at REAL NOT NULL, lease_until REAL NOT NULL,\n priority INTEGER NOT NULL, body TEXT NOT NULL,\n UNIQUE(namespace, queue, id)\n );\n CREATE INDEX IF NOT EXISTS warlock_queue_ready\n ON warlock_queue_jobs(namespace, queue, state, ready_at, lease_until);\n `);\n } catch (error) {\n this.db.close();\n throw error;\n }\n }\n\n public transaction<T>(run: () => T): T {\n this.db.exec(\"BEGIN IMMEDIATE\");\n try {\n const result = run();\n this.db.exec(\"COMMIT\");\n return result;\n } catch (error) {\n this.db.exec(\"ROLLBACK\");\n throw error;\n }\n }\n\n public get(queue: string, id: string): LocalJob | undefined {\n return decode(\n this.db\n .prepare(\"SELECT body FROM warlock_queue_jobs WHERE namespace = ? AND queue = ? AND id = ?\")\n .get(this.namespace, queue, id),\n );\n }\n public list(queue: string, state?: JobState): LocalJob[] {\n const rows = state\n ? this.db\n .prepare(\n \"SELECT body FROM warlock_queue_jobs WHERE namespace = ? AND queue = ? AND state = ? ORDER BY seq\",\n )\n .all(this.namespace, queue, state)\n : this.db\n .prepare(\n \"SELECT body FROM warlock_queue_jobs WHERE namespace = ? AND queue = ? ORDER BY seq\",\n )\n .all(this.namespace, queue);\n return rows.flatMap((row) => {\n const job = decode(row);\n return job ? [job] : [];\n });\n }\n public ready(queue: string, now: number): LocalJob | undefined {\n return decode(\n this.db\n .prepare(`SELECT body FROM warlock_queue_jobs\n WHERE namespace = ? AND queue = ? AND (\n (state IN ('waiting', 'delayed', 'prioritized') AND ready_at <= ?) OR\n (state = 'active' AND lease_until <= ?)\n ) ORDER BY priority, seq LIMIT 1`)\n .get(this.namespace, queue, now, now),\n );\n }\n public put(job: LocalJob): void {\n this.db\n .prepare(`INSERT INTO warlock_queue_jobs\n (namespace, queue, id, state, ready_at, lease_until, priority, body)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(namespace, queue, id) DO UPDATE SET\n state = excluded.state, ready_at = excluded.ready_at,\n lease_until = excluded.lease_until, priority = excluded.priority, body = excluded.body`)\n .run(\n this.namespace,\n job.queue,\n job.id,\n job.state,\n job.readyAt,\n job.leaseUntil,\n job.priority,\n JSON.stringify(job),\n );\n }\n public remove(queue: string, id: string): void {\n this.db\n .prepare(\"DELETE FROM warlock_queue_jobs WHERE namespace = ? AND queue = ? AND id = ?\")\n .run(this.namespace, queue, id);\n }\n public close(): void {\n this.db.close();\n }\n}\n\nfunction decode(row: Record<string, unknown> | undefined): LocalJob | undefined {\n return typeof row?.body === \"string\" ? (JSON.parse(row.body) as LocalJob) : undefined;\n}\n"],"mappings":";;;;;;AA0BA,IAAa,cAAb,MAA+C;CAI7C,AAAO,YAAY,QAAqB;EACtC,MAAM,OAAO,QAAQ,OAAO,OAAO,QAAQ,uBAAuB;EAClE,IAAI,OAAO,OAAO,SAAS,YACzB,MAAM,IAAI,MAAM,8CAA8C;EAChE,IAAI;EACJ,IAAI;GACF,SAAS,cAAc,YAAY,GAAG,CAAC,CAAC,aAAa;EACvD,SAAS,OAAO;GACd,MAAM,IAAI,MACR,2FACA,EAAE,MAAM,CACV;EACF;EACA,IAAI,OAAO,OAAO,iBAAiB,YACjC,MAAM,IAAI,MAAM,0CAA0C;EAC5D,UAAU,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;EAC5C,KAAK,KAAK,IAAI,OAAO,aAAa,IAAI;EACtC,KAAK,YAAY,OAAO,UAAU;EAClC,IAAI;GAEF,KAAK,GAAG,KAAK;;;;;;;;;;;;;OAaZ;EACH,SAAS,OAAO;GACd,KAAK,GAAG,MAAM;GACd,MAAM;EACR;CACF;CAEA,AAAO,YAAe,KAAiB;EACrC,KAAK,GAAG,KAAK,iBAAiB;EAC9B,IAAI;GACF,MAAM,SAAS,IAAI;GACnB,KAAK,GAAG,KAAK,QAAQ;GACrB,OAAO;EACT,SAAS,OAAO;GACd,KAAK,GAAG,KAAK,UAAU;GACvB,MAAM;EACR;CACF;CAEA,AAAO,IAAI,OAAe,IAAkC;EAC1D,OAAO,OACL,KAAK,GACF,QAAQ,kFAAkF,CAAC,CAC3F,IAAI,KAAK,WAAW,OAAO,EAAE,CAClC;CACF;CACA,AAAO,KAAK,OAAe,OAA8B;EAYvD,QAXa,QACT,KAAK,GACF,QACC,kGACF,CAAC,CACA,IAAI,KAAK,WAAW,OAAO,KAAK,IACnC,KAAK,GACF,QACC,oFACF,CAAC,CACA,IAAI,KAAK,WAAW,KAAK,EACrB,CAAC,SAAS,QAAQ;GAC3B,MAAM,MAAM,OAAO,GAAG;GACtB,OAAO,MAAM,CAAC,GAAG,IAAI,CAAC;EACxB,CAAC;CACH;CACA,AAAO,MAAM,OAAe,KAAmC;EAC7D,OAAO,OACL,KAAK,GACF,QAAQ;;;;uCAIsB,CAAC,CAC/B,IAAI,KAAK,WAAW,OAAO,KAAK,GAAG,CACxC;CACF;CACA,AAAO,IAAI,KAAqB;EAC9B,KAAK,GACF,QAAQ;;;;;+FAKgF,CAAC,CACzF,IACC,KAAK,WACL,IAAI,OACJ,IAAI,IACJ,IAAI,OACJ,IAAI,SACJ,IAAI,YACJ,IAAI,UACJ,KAAK,UAAU,GAAG,CACpB;CACJ;CACA,AAAO,OAAO,OAAe,IAAkB;EAC7C,KAAK,GACF,QAAQ,6EAA6E,CAAC,CACtF,IAAI,KAAK,WAAW,OAAO,EAAE;CAClC;CACA,AAAO,QAAc;EACnB,KAAK,GAAG,MAAM;CAChB;AACF;AAEA,SAAS,OAAO,KAAgE;CAC9E,OAAO,OAAO,KAAK,SAAS,WAAY,KAAK,MAAM,IAAI,IAAI,IAAiB;AAC9E"}
|
package/esm/failed-jobs.d.mts
CHANGED
|
@@ -8,15 +8,9 @@ export type FailedJobsOptions = {
|
|
|
8
8
|
/** Last index, inclusive. Default `99`. */
|
|
9
9
|
end?: number;
|
|
10
10
|
};
|
|
11
|
-
/**
|
|
12
|
-
* List failed jobs, newest first — jobs that used up every attempt, or
|
|
13
|
-
* failed unrecoverably. Each entry can be retried.
|
|
14
|
-
*/
|
|
11
|
+
/** List failed jobs, newest first, using the selected driver. */
|
|
15
12
|
export declare function failedJobs(options?: FailedJobsOptions): Promise<FailedJob[]>;
|
|
16
|
-
/**
|
|
17
|
-
* Retry one failed job by id: it goes back to waiting with its attempts reset.
|
|
18
|
-
* Throws {@link FailedJobNotFoundError} when no FAILED job has that id.
|
|
19
|
-
*/
|
|
13
|
+
/** Move a failed job back to waiting, resetting its attempts. */
|
|
20
14
|
export declare function retryFailedJob(id: string, options?: {
|
|
21
15
|
queue?: string;
|
|
22
16
|
}): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"failed-jobs.d.mts","names":[],"sources":["../../../../../../../packages/queue/src/failed-jobs.ts"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"failed-jobs.d.mts","names":[],"sources":["../../../../../../../packages/queue/src/failed-jobs.ts"],"mappings":";;YAIY;;EAEV;;EAEA;;EAEA;;;wBAIoB,WAAW,UAAS,oBAAyB,QAAQ;;wBASrD,eAAe,YAAY;EAAW;IAAwB"}
|
package/esm/failed-jobs.mjs
CHANGED
|
@@ -1,39 +1,14 @@
|
|
|
1
|
-
import { FailedJobNotFoundError } from "./errors.mjs";
|
|
2
1
|
import { defaultQueueName } from "./config.mjs";
|
|
3
|
-
import {
|
|
4
|
-
import { getQueue } from "./queue-manager.mjs";
|
|
2
|
+
import { getQueueDriver } from "./queue-driver.mjs";
|
|
5
3
|
|
|
6
4
|
//#region ../../packages/queue/src/failed-jobs.ts
|
|
7
|
-
/**
|
|
8
|
-
* List failed jobs, newest first — jobs that used up every attempt, or
|
|
9
|
-
* failed unrecoverably. Each entry can be retried.
|
|
10
|
-
*/
|
|
5
|
+
/** List failed jobs, newest first, using the selected driver. */
|
|
11
6
|
async function failedJobs(options = {}) {
|
|
12
|
-
|
|
13
|
-
return (await getQueue(queueName).getFailed(options.start ?? 0, options.end ?? 99)).map((job) => toFailedJob(job));
|
|
7
|
+
return getQueueDriver().failed(options.queue ?? defaultQueueName(), options.start ?? 0, options.end ?? 99);
|
|
14
8
|
}
|
|
15
|
-
/**
|
|
16
|
-
* Retry one failed job by id: it goes back to waiting with its attempts reset.
|
|
17
|
-
* Throws {@link FailedJobNotFoundError} when no FAILED job has that id.
|
|
18
|
-
*/
|
|
9
|
+
/** Move a failed job back to waiting, resetting its attempts. */
|
|
19
10
|
async function retryFailedJob(id, options = {}) {
|
|
20
|
-
|
|
21
|
-
const job = await getQueue(queueName).getJob(id);
|
|
22
|
-
if (!job || !await job.isFailed()) throw new FailedJobNotFoundError(id, queueName);
|
|
23
|
-
await job.retry("failed");
|
|
24
|
-
}
|
|
25
|
-
function toFailedJob(job) {
|
|
26
|
-
return {
|
|
27
|
-
id: String(job.id),
|
|
28
|
-
name: job.name,
|
|
29
|
-
queue: job.queueName,
|
|
30
|
-
payload: unwrapPayload(job.data).payload,
|
|
31
|
-
attemptsMade: job.attemptsMade,
|
|
32
|
-
failedReason: job.failedReason,
|
|
33
|
-
stacktrace: job.stacktrace ?? [],
|
|
34
|
-
failedAt: job.finishedOn ? new Date(job.finishedOn) : void 0,
|
|
35
|
-
retry: () => job.retry("failed")
|
|
36
|
-
};
|
|
11
|
+
await getQueueDriver().retry(options.queue ?? defaultQueueName(), id);
|
|
37
12
|
}
|
|
38
13
|
|
|
39
14
|
//#endregion
|
package/esm/failed-jobs.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"failed-jobs.mjs","names":[],"sources":["../../../../../../../packages/queue/src/failed-jobs.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"failed-jobs.mjs","names":[],"sources":["../../../../../../../packages/queue/src/failed-jobs.ts"],"sourcesContent":["import { defaultQueueName } from \"./config\";\nimport { getQueueDriver } from \"./queue-driver\";\nimport type { FailedJob } from \"./types\";\n\nexport type FailedJobsOptions = {\n /** Queue to read. Default: the default queue. */\n queue?: string;\n /** First index (newest first). Default `0`. */\n start?: number;\n /** Last index, inclusive. Default `99`. */\n end?: number;\n};\n\n/** List failed jobs, newest first, using the selected driver. */\nexport async function failedJobs(options: FailedJobsOptions = {}): Promise<FailedJob[]> {\n return getQueueDriver().failed(\n options.queue ?? defaultQueueName(),\n options.start ?? 0,\n options.end ?? 99,\n );\n}\n\n/** Move a failed job back to waiting, resetting its attempts. */\nexport async function retryFailedJob(id: string, options: { queue?: string } = {}): Promise<void> {\n await getQueueDriver().retry(options.queue ?? defaultQueueName(), id);\n}\n"],"mappings":";;;;;AAcA,eAAsB,WAAW,UAA6B,CAAC,GAAyB;CACtF,OAAO,eAAe,CAAC,CAAC,OACtB,QAAQ,SAAS,iBAAiB,GAClC,QAAQ,SAAS,GACjB,QAAQ,OAAO,EACjB;AACF;;AAGA,eAAsB,eAAe,IAAY,UAA8B,CAAC,GAAkB;CAChG,MAAM,eAAe,CAAC,CAAC,MAAM,QAAQ,SAAS,iBAAiB,GAAG,EAAE;AACtE"}
|
package/esm/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DispatchOptions, DispatchedJob, Duration, DurationUnit, FailedJob, JobBackoff, JobContext, JobDefinition, JobOptions, JobProgress, JobRetention, JobSnapshot, JobState, QueueConfig, QueueDashboardConfig, QueueJob, QueueWorkersConfig } from "./types.mjs";
|
|
1
|
+
import { DispatchOptions, DispatchedJob, Duration, DurationUnit, FailedJob, JobBackoff, JobContext, JobDefinition, JobOptions, JobProgress, JobRetention, JobSnapshot, JobState, LocalQueueConfig, QueueConfig, QueueDashboardConfig, QueueJob, QueueWorkersConfig } from "./types.mjs";
|
|
2
2
|
import { defaultQueueName, getQueueConfig, resetQueueConfig, setQueueConfig } from "./config.mjs";
|
|
3
3
|
import { DashboardServer, QueueDashboardOptions, loadBullBoard, queueDashboard } from "./dashboard.mjs";
|
|
4
4
|
import { DEFAULT_DASHBOARD_PATH, mountQueueDashboard } from "./dashboard-boot.mjs";
|
|
@@ -12,4 +12,6 @@ import { QUEUE_CONNECTOR_PRIORITY, QueueConnectorOptions, queueConnector } from
|
|
|
12
12
|
import { QueueContext, defineQueueContext, getQueueContext, resetQueueContext, setQueueContext } from "./queue-context.mjs";
|
|
13
13
|
import { QueueDashboardUnguardedError } from "./queue-dashboard-unguarded.error.mjs";
|
|
14
14
|
import { CloseQueueOptions, closeQueue, getQueue, runningWorkers, startWorkers } from "./queue-manager.mjs";
|
|
15
|
-
|
|
15
|
+
import { DriverDispatch, QueueDriver } from "./driver.mjs";
|
|
16
|
+
import { getQueueDriver } from "./queue-driver.mjs";
|
|
17
|
+
export { type CloseQueueOptions, DEFAULT_DASHBOARD_PATH, DashboardServer, type DispatchInterceptor, DispatchOptions, DispatchedJob, type DriverDispatch, Duration, DurationUnit, FailedJob, FailedJobNotFoundError, FailedJobsOptions, InvalidDurationError, InvalidJobDefinitionError, JobBackoff, JobContext, JobDefinition, JobOptions, JobProgress, JobRetention, JobSnapshot, JobState, LocalQueueConfig, QUEUE_CONNECTOR_PRIORITY, QueueConfig, QueueConnectorOptions, type QueueContext, QueueDashboardConfig, QueueDashboardDependencyError, QueueDashboardOptions, QueueDashboardUnguardedError, type QueueDriver, QueueJob, QueueNotConfiguredError, QueueWorkersConfig, type RegisteredJob, UnrecoverableJobError, closeQueue, defaultQueueName, defineJob, defineQueueContext, failedJobs, getDispatchInterceptor, getQueue, getQueueConfig, getQueueContext, getQueueDriver, loadBullBoard, mountQueueDashboard, queueConnector, queueDashboard, queueOf, registeredJobs, resetQueueConfig, resetQueueContext, retryFailedJob, runningWorkers, setDispatchInterceptor, setQueueConfig, setQueueContext, startWorkers, toMilliseconds };
|
package/esm/index.mjs
CHANGED
|
@@ -2,6 +2,7 @@ import { FailedJobNotFoundError, InvalidDurationError, InvalidJobDefinitionError
|
|
|
2
2
|
import { defaultQueueName, getQueueConfig, resetQueueConfig, setQueueConfig } from "./config.mjs";
|
|
3
3
|
import { queueOf, registeredJobs } from "./job-registry.mjs";
|
|
4
4
|
import { defineQueueContext, getQueueContext, resetQueueContext, setQueueContext } from "./queue-context.mjs";
|
|
5
|
+
import { getQueueDriver } from "./queue-driver.mjs";
|
|
5
6
|
import { closeQueue, getQueue, runningWorkers, startWorkers } from "./queue-manager.mjs";
|
|
6
7
|
import { loadBullBoard, queueDashboard } from "./dashboard.mjs";
|
|
7
8
|
import { QueueDashboardUnguardedError } from "./queue-dashboard-unguarded.error.mjs";
|
|
@@ -12,4 +13,4 @@ import { defineJob } from "./define-job.mjs";
|
|
|
12
13
|
import { failedJobs, retryFailedJob } from "./failed-jobs.mjs";
|
|
13
14
|
import { QUEUE_CONNECTOR_PRIORITY, queueConnector } from "./queue-connector.mjs";
|
|
14
15
|
|
|
15
|
-
export { DEFAULT_DASHBOARD_PATH, FailedJobNotFoundError, InvalidDurationError, InvalidJobDefinitionError, QUEUE_CONNECTOR_PRIORITY, QueueDashboardDependencyError, QueueDashboardUnguardedError, QueueNotConfiguredError, UnrecoverableJobError, closeQueue, defaultQueueName, defineJob, defineQueueContext, failedJobs, getDispatchInterceptor, getQueue, getQueueConfig, getQueueContext, loadBullBoard, mountQueueDashboard, queueConnector, queueDashboard, queueOf, registeredJobs, resetQueueConfig, resetQueueContext, retryFailedJob, runningWorkers, setDispatchInterceptor, setQueueConfig, setQueueContext, startWorkers, toMilliseconds };
|
|
16
|
+
export { DEFAULT_DASHBOARD_PATH, FailedJobNotFoundError, InvalidDurationError, InvalidJobDefinitionError, QUEUE_CONNECTOR_PRIORITY, QueueDashboardDependencyError, QueueDashboardUnguardedError, QueueNotConfiguredError, UnrecoverableJobError, closeQueue, defaultQueueName, defineJob, defineQueueContext, failedJobs, getDispatchInterceptor, getQueue, getQueueConfig, getQueueContext, getQueueDriver, loadBullBoard, mountQueueDashboard, queueConnector, queueDashboard, queueOf, registeredJobs, resetQueueConfig, resetQueueContext, retryFailedJob, runningWorkers, setDispatchInterceptor, setQueueConfig, setQueueContext, startWorkers, toMilliseconds };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { JobOptions } from "./types.mjs";
|
|
2
|
+
//#region ../../packages/queue/src/job-options.d.ts
|
|
3
|
+
export type ResolvedJobOptions = JobOptions & {
|
|
4
|
+
delay?: number;
|
|
5
|
+
priority?: number;
|
|
6
|
+
jobId?: string;
|
|
7
|
+
};
|
|
8
|
+
//#endregion
|
|
9
|
+
//# sourceMappingURL=job-options.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"job-options.d.mts","names":[],"sources":["../../../../../../../packages/queue/src/job-options.ts"],"mappings":";;YAIY,qBAAqB;EAC/B;EACA;EACA"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { getQueueConfig } from "./config.mjs";
|
|
2
|
+
import { toMilliseconds } from "./duration.mjs";
|
|
3
|
+
|
|
4
|
+
//#region ../../packages/queue/src/job-options.ts
|
|
5
|
+
function resolveJobOptions(definition, options) {
|
|
6
|
+
const defaults = getQueueConfig().defaultJobOptions ?? {};
|
|
7
|
+
return {
|
|
8
|
+
attempts: options.attempts ?? definition.attempts ?? defaults.attempts,
|
|
9
|
+
backoff: options.backoff ?? definition.backoff ?? defaults.backoff,
|
|
10
|
+
removeOnComplete: definition.removeOnComplete ?? defaults.removeOnComplete,
|
|
11
|
+
removeOnFail: definition.removeOnFail ?? defaults.removeOnFail,
|
|
12
|
+
delay: options.delay === void 0 ? void 0 : toMilliseconds(options.delay),
|
|
13
|
+
priority: options.priority,
|
|
14
|
+
jobId: options.jobId
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
//#endregion
|
|
19
|
+
export { resolveJobOptions };
|
|
20
|
+
//# sourceMappingURL=job-options.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"job-options.mjs","names":[],"sources":["../../../../../../../packages/queue/src/job-options.ts"],"sourcesContent":["import { getQueueConfig } from \"./config\";\nimport { toMilliseconds } from \"./duration\";\nimport type { DispatchOptions, JobOptions } from \"./types\";\n\nexport type ResolvedJobOptions = JobOptions & {\n delay?: number;\n priority?: number;\n jobId?: string;\n};\n\nexport function resolveJobOptions(\n definition: JobOptions,\n options: DispatchOptions,\n): ResolvedJobOptions {\n const defaults = getQueueConfig().defaultJobOptions ?? {};\n return {\n attempts: options.attempts ?? definition.attempts ?? defaults.attempts,\n backoff: options.backoff ?? definition.backoff ?? defaults.backoff,\n removeOnComplete: definition.removeOnComplete ?? defaults.removeOnComplete,\n removeOnFail: definition.removeOnFail ?? defaults.removeOnFail,\n delay: options.delay === undefined ? undefined : toMilliseconds(options.delay),\n priority: options.priority,\n jobId: options.jobId,\n };\n}\n"],"mappings":";;;;AAUA,SAAgB,kBACd,YACA,SACoB;CACpB,MAAM,WAAW,eAAe,CAAC,CAAC,qBAAqB,CAAC;CACxD,OAAO;EACL,UAAU,QAAQ,YAAY,WAAW,YAAY,SAAS;EAC9D,SAAS,QAAQ,WAAW,WAAW,WAAW,SAAS;EAC3D,kBAAkB,WAAW,oBAAoB,SAAS;EAC1D,cAAc,WAAW,gBAAgB,SAAS;EAClD,OAAO,QAAQ,UAAU,SAAY,SAAY,eAAe,QAAQ,KAAK;EAC7E,UAAU,QAAQ;EAClB,OAAO,QAAQ;CACjB;AACF"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { QueueDriver } from "./driver.mjs";
|
|
2
|
+
//#region ../../packages/queue/src/queue-driver.d.ts
|
|
3
|
+
/** The configured backend, created on first use. Close it before changing config. */
|
|
4
|
+
export declare function getQueueDriver(): QueueDriver;
|
|
5
|
+
//#endregion
|
|
6
|
+
//# sourceMappingURL=queue-driver.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queue-driver.d.mts","names":[],"sources":["../../../../../../../packages/queue/src/queue-driver.ts"],"mappings":";;;wBAUgB,kBAAkB"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { getQueueConfig } from "./config.mjs";
|
|
2
|
+
import { BullMQDriver } from "./drivers/bullmq.mjs";
|
|
3
|
+
import { LocalDriver } from "./drivers/local.mjs";
|
|
4
|
+
import { MemoryStore } from "./drivers/memory-store.mjs";
|
|
5
|
+
import { SqliteStore } from "./drivers/sqlite-store.mjs";
|
|
6
|
+
|
|
7
|
+
//#region ../../packages/queue/src/queue-driver.ts
|
|
8
|
+
let activeDriver;
|
|
9
|
+
/** The configured backend, created on first use. Close it before changing config. */
|
|
10
|
+
function getQueueDriver() {
|
|
11
|
+
if (!activeDriver) {
|
|
12
|
+
const config = getQueueConfig();
|
|
13
|
+
switch (config.driver ?? "bullmq") {
|
|
14
|
+
case "bullmq":
|
|
15
|
+
activeDriver = new BullMQDriver(config);
|
|
16
|
+
break;
|
|
17
|
+
case "local":
|
|
18
|
+
activeDriver = new LocalDriver(new SqliteStore(config), config);
|
|
19
|
+
break;
|
|
20
|
+
case "memory":
|
|
21
|
+
activeDriver = new LocalDriver(new MemoryStore(), config);
|
|
22
|
+
break;
|
|
23
|
+
default: throw new Error(`Unknown queue driver: ${String(config.driver)}`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return activeDriver;
|
|
27
|
+
}
|
|
28
|
+
function currentQueueDriver() {
|
|
29
|
+
return activeDriver;
|
|
30
|
+
}
|
|
31
|
+
function forgetQueueDriver() {
|
|
32
|
+
activeDriver = void 0;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
//#endregion
|
|
36
|
+
export { currentQueueDriver, forgetQueueDriver, getQueueDriver };
|
|
37
|
+
//# sourceMappingURL=queue-driver.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queue-driver.mjs","names":[],"sources":["../../../../../../../packages/queue/src/queue-driver.ts"],"sourcesContent":["import { getQueueConfig } from \"./config\";\nimport type { QueueDriver } from \"./driver\";\nimport { BullMQDriver } from \"./drivers/bullmq\";\nimport { LocalDriver } from \"./drivers/local\";\nimport { MemoryStore } from \"./drivers/memory-store\";\nimport { SqliteStore } from \"./drivers/sqlite-store\";\n\nlet activeDriver: QueueDriver | undefined;\n\n/** The configured backend, created on first use. Close it before changing config. */\nexport function getQueueDriver(): QueueDriver {\n if (!activeDriver) {\n const config = getQueueConfig();\n switch (config.driver ?? \"bullmq\") {\n case \"bullmq\":\n activeDriver = new BullMQDriver(config);\n break;\n case \"local\":\n activeDriver = new LocalDriver(new SqliteStore(config), config);\n break;\n case \"memory\":\n activeDriver = new LocalDriver(new MemoryStore(), config);\n break;\n default:\n throw new Error(`Unknown queue driver: ${String(config.driver)}`);\n }\n }\n return activeDriver;\n}\n\nexport function currentQueueDriver(): QueueDriver | undefined {\n return activeDriver;\n}\n\nexport function forgetQueueDriver(): void {\n activeDriver = undefined;\n}\n"],"mappings":";;;;;;;AAOA,IAAI;;AAGJ,SAAgB,iBAA8B;CAC5C,IAAI,CAAC,cAAc;EACjB,MAAM,SAAS,eAAe;EAC9B,QAAQ,OAAO,UAAU,UAAzB;GACE,KAAK;IACH,eAAe,IAAI,aAAa,MAAM;IACtC;GACF,KAAK;IACH,eAAe,IAAI,YAAY,IAAI,YAAY,MAAM,GAAG,MAAM;IAC9D;GACF,KAAK;IACH,eAAe,IAAI,YAAY,IAAI,YAAY,GAAG,MAAM;IACxD;GACF,SACE,MAAM,IAAI,MAAM,yBAAyB,OAAO,OAAO,MAAM,GAAG;EACpE;CACF;CACA,OAAO;AACT;AAEA,SAAgB,qBAA8C;CAC5D,OAAO;AACT;AAEA,SAAgB,oBAA0B;CACxC,eAAe;AACjB"}
|
package/esm/queue-manager.d.mts
CHANGED
|
@@ -1,37 +1,16 @@
|
|
|
1
1
|
import { Queue } from "bullmq";
|
|
2
2
|
//#region ../../packages/queue/src/queue-manager.d.ts
|
|
3
|
-
/**
|
|
4
|
-
* The BullMQ queue for `name`, created on first use with the configured
|
|
5
|
-
* connection and prefix.
|
|
6
|
-
*/
|
|
3
|
+
/** Low-level BullMQ access. For portable operations use defineJob / failedJobs. */
|
|
7
4
|
export declare function getQueue(name: string): Queue;
|
|
8
|
-
/**
|
|
9
|
-
* Start one worker per queue that has a registered job, and keep starting
|
|
10
|
-
* workers for queues whose first job is defined later.
|
|
11
|
-
*
|
|
12
|
-
* A no-op returning `[]` when `workers.enabled` is `false`, or when this
|
|
13
|
-
* process does not serve the `worker` role (`Application.hasRole`) — a
|
|
14
|
-
* process started with `--role=web`, for example, produces jobs but never
|
|
15
|
-
* runs them in-process. Calling it again while workers run starts only the
|
|
16
|
-
* missing ones.
|
|
17
|
-
*
|
|
18
|
-
* @returns the queue names that now have a worker in this process.
|
|
19
|
-
*/
|
|
5
|
+
/** Start workers for registered queues, honouring process roles and workers.enabled. */
|
|
20
6
|
export declare function startWorkers(): Promise<string[]>;
|
|
21
|
-
/**
|
|
7
|
+
/** Names of queues with workers in this process. */
|
|
22
8
|
export declare function runningWorkers(): string[];
|
|
23
9
|
export type CloseQueueOptions = {
|
|
24
|
-
/**
|
|
25
|
-
* How long to wait for active jobs before force-closing workers, in
|
|
26
|
-
* milliseconds. Default: `workers.shutdownTimeout`, else `30000`.
|
|
27
|
-
*/
|
|
10
|
+
/** Drain timeout in ms, default workers.shutdownTimeout, else 30000. */
|
|
28
11
|
timeout?: number;
|
|
29
12
|
};
|
|
30
|
-
/**
|
|
31
|
-
* Graceful shutdown: stop workers taking new jobs and wait for active ones
|
|
32
|
-
* (bounded by `timeout`, then force-close), then close every queue
|
|
33
|
-
* connection. Safe to call when nothing was started, and more than once.
|
|
34
|
-
*/
|
|
13
|
+
/** Stop taking jobs, drain active handlers, then release leases and close storage. */
|
|
35
14
|
export declare function closeQueue(options?: CloseQueueOptions): Promise<void>;
|
|
36
15
|
//#endregion
|
|
37
16
|
//# sourceMappingURL=queue-manager.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"queue-manager.d.mts","names":[],"sources":["../../../../../../../packages/queue/src/queue-manager.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"queue-manager.d.mts","names":[],"sources":["../../../../../../../packages/queue/src/queue-manager.ts"],"mappings":";;;wBAWgB,SAAS,eAAe;;wBAQlB,gBAAgB;;wBAuBtB;YAIJ;;EAEV;;;wBAIc,WAAW,UAAS,oBAAyB"}
|
package/esm/queue-manager.mjs
CHANGED
|
@@ -1,114 +1,58 @@
|
|
|
1
1
|
import { getQueueConfig } from "./config.mjs";
|
|
2
2
|
import { onJobRegistered, queueOf, registeredJobs } from "./job-registry.mjs";
|
|
3
|
-
import {
|
|
3
|
+
import { BullMQDriver } from "./drivers/bullmq.mjs";
|
|
4
|
+
import { currentQueueDriver, forgetQueueDriver, getQueueDriver } from "./queue-driver.mjs";
|
|
4
5
|
import { log } from "@warlock.js/logger";
|
|
5
|
-
import { Queue, Worker } from "bullmq";
|
|
6
6
|
|
|
7
7
|
//#region ../../packages/queue/src/queue-manager.ts
|
|
8
|
-
const DEFAULT_SHUTDOWN_TIMEOUT = 3e4;
|
|
9
|
-
const queues = /* @__PURE__ */ new Map();
|
|
10
|
-
const workers = /* @__PURE__ */ new Map();
|
|
11
8
|
let stopListening;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
* connection and prefix.
|
|
15
|
-
*/
|
|
9
|
+
let closing;
|
|
10
|
+
/** Low-level BullMQ access. For portable operations use defineJob / failedJobs. */
|
|
16
11
|
function getQueue(name) {
|
|
17
|
-
|
|
18
|
-
if (!queue)
|
|
19
|
-
|
|
20
|
-
queue = new Queue(name, {
|
|
21
|
-
connection: config.connection,
|
|
22
|
-
prefix: config.prefix ?? "warlock"
|
|
23
|
-
});
|
|
24
|
-
queue.on("error", (error) => {
|
|
25
|
-
log.error("queue", "connection", error);
|
|
26
|
-
});
|
|
27
|
-
queues.set(name, queue);
|
|
28
|
-
}
|
|
29
|
-
return queue;
|
|
12
|
+
const driver = getQueueDriver();
|
|
13
|
+
if (!(driver instanceof BullMQDriver)) throw new Error("getQueue() requires the bullmq queue driver.");
|
|
14
|
+
return driver.getQueue(name);
|
|
30
15
|
}
|
|
31
|
-
/**
|
|
32
|
-
* Start one worker per queue that has a registered job, and keep starting
|
|
33
|
-
* workers for queues whose first job is defined later.
|
|
34
|
-
*
|
|
35
|
-
* A no-op returning `[]` when `workers.enabled` is `false`, or when this
|
|
36
|
-
* process does not serve the `worker` role (`Application.hasRole`) — a
|
|
37
|
-
* process started with `--role=web`, for example, produces jobs but never
|
|
38
|
-
* runs them in-process. Calling it again while workers run starts only the
|
|
39
|
-
* missing ones.
|
|
40
|
-
*
|
|
41
|
-
* @returns the queue names that now have a worker in this process.
|
|
42
|
-
*/
|
|
16
|
+
/** Start workers for registered queues, honouring process roles and workers.enabled. */
|
|
43
17
|
async function startWorkers() {
|
|
18
|
+
if (closing) await closing;
|
|
44
19
|
const { Application } = await import("@warlock.js/core");
|
|
45
20
|
if (!Application.hasRole("worker")) {
|
|
46
21
|
log.info("queue", "workers", `queue: workers not started (role: ${[...Application.roles].join(",")})`);
|
|
47
22
|
return [];
|
|
48
23
|
}
|
|
49
24
|
if (getQueueConfig().workers?.enabled === false) return [];
|
|
50
|
-
|
|
25
|
+
const driver = getQueueDriver();
|
|
51
26
|
stopListening ??= onJobRegistered((job) => {
|
|
52
|
-
|
|
27
|
+
driver.start(queueOf(job)).catch((error) => log.error("queue", "worker", error));
|
|
53
28
|
});
|
|
54
|
-
await Promise.all([...
|
|
55
|
-
return
|
|
29
|
+
await Promise.all([...new Set(registeredJobs().map(queueOf))].map((queue) => driver.start(queue)));
|
|
30
|
+
return driver.running();
|
|
56
31
|
}
|
|
57
|
-
/**
|
|
32
|
+
/** Names of queues with workers in this process. */
|
|
58
33
|
function runningWorkers() {
|
|
59
|
-
return
|
|
60
|
-
}
|
|
61
|
-
function ensureWorker(queueName) {
|
|
62
|
-
if (workers.has(queueName)) return;
|
|
63
|
-
const config = getQueueConfig();
|
|
64
|
-
const worker = new Worker(queueName, processJob, {
|
|
65
|
-
connection: config.connection,
|
|
66
|
-
prefix: config.prefix ?? "warlock",
|
|
67
|
-
concurrency: config.workers?.concurrency ?? 1
|
|
68
|
-
});
|
|
69
|
-
worker.on("error", (error) => {
|
|
70
|
-
log.error("queue", "worker", error);
|
|
71
|
-
});
|
|
72
|
-
worker.on("failed", (job, error) => {
|
|
73
|
-
log.error("queue", "job.failed", `${job?.name ?? "unknown"} (${job?.id ?? "?"}): ${error.message}`);
|
|
74
|
-
});
|
|
75
|
-
workers.set(queueName, worker);
|
|
34
|
+
return currentQueueDriver()?.running() ?? [];
|
|
76
35
|
}
|
|
77
|
-
/**
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
* connection. Safe to call when nothing was started, and more than once.
|
|
81
|
-
*/
|
|
82
|
-
async function closeQueue(options = {}) {
|
|
36
|
+
/** Stop taking jobs, drain active handlers, then release leases and close storage. */
|
|
37
|
+
function closeQueue(options = {}) {
|
|
38
|
+
if (closing) return closing;
|
|
83
39
|
stopListening?.();
|
|
84
40
|
stopListening = void 0;
|
|
41
|
+
const driver = currentQueueDriver();
|
|
42
|
+
if (!driver) return Promise.resolve();
|
|
85
43
|
const timeout = options.timeout ?? configuredShutdownTimeout();
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
44
|
+
if (!Number.isFinite(timeout) || timeout < 0) return Promise.reject(/* @__PURE__ */ new Error("Queue shutdown timeout must be non-negative and finite."));
|
|
45
|
+
closing = driver.close(timeout).finally(() => {
|
|
46
|
+
forgetQueueDriver();
|
|
47
|
+
closing = void 0;
|
|
48
|
+
});
|
|
49
|
+
return closing;
|
|
92
50
|
}
|
|
93
51
|
function configuredShutdownTimeout() {
|
|
94
52
|
try {
|
|
95
|
-
return getQueueConfig().workers?.shutdownTimeout ??
|
|
53
|
+
return getQueueConfig().workers?.shutdownTimeout ?? 3e4;
|
|
96
54
|
} catch {
|
|
97
|
-
return
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
async function closeWorker(worker, timeout) {
|
|
101
|
-
let timer;
|
|
102
|
-
const timedOut = new Promise((resolve) => {
|
|
103
|
-
timer = setTimeout(() => resolve("timeout"), timeout);
|
|
104
|
-
});
|
|
105
|
-
const closing = worker.close().then(() => "closed");
|
|
106
|
-
closing.catch(() => void 0);
|
|
107
|
-
const outcome = await Promise.race([closing, timedOut]);
|
|
108
|
-
clearTimeout(timer);
|
|
109
|
-
if (outcome === "timeout") {
|
|
110
|
-
log.warn("queue", "shutdown", `Worker for "${worker.name}" still had active jobs after ${timeout}ms; disconnecting. Those jobs are retried once their lock expires.`);
|
|
111
|
-
await worker.disconnect();
|
|
55
|
+
return 3e4;
|
|
112
56
|
}
|
|
113
57
|
}
|
|
114
58
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"queue-manager.mjs","names":[],"sources":["../../../../../../../packages/queue/src/queue-manager.ts"],"sourcesContent":["import { log } from \"@warlock.js/logger\";\nimport { Queue
|
|
1
|
+
{"version":3,"file":"queue-manager.mjs","names":[],"sources":["../../../../../../../packages/queue/src/queue-manager.ts"],"sourcesContent":["import { log } from \"@warlock.js/logger\";\nimport type { Queue } from \"bullmq\";\nimport { getQueueConfig } from \"./config\";\nimport { BullMQDriver } from \"./drivers/bullmq\";\nimport { onJobRegistered, queueOf, registeredJobs } from \"./job-registry\";\nimport { currentQueueDriver, forgetQueueDriver, getQueueDriver } from \"./queue-driver\";\n\nlet stopListening: (() => void) | undefined;\nlet closing: Promise<void> | undefined;\n\n/** Low-level BullMQ access. For portable operations use defineJob / failedJobs. */\nexport function getQueue(name: string): Queue {\n const driver = getQueueDriver();\n if (!(driver instanceof BullMQDriver))\n throw new Error(\"getQueue() requires the bullmq queue driver.\");\n return driver.getQueue(name);\n}\n\n/** Start workers for registered queues, honouring process roles and workers.enabled. */\nexport async function startWorkers(): Promise<string[]> {\n if (closing) await closing;\n const { Application } = await import(\"@warlock.js/core\");\n if (!Application.hasRole(\"worker\")) {\n log.info(\n \"queue\",\n \"workers\",\n `queue: workers not started (role: ${[...Application.roles].join(\",\")})`,\n );\n return [];\n }\n if (getQueueConfig().workers?.enabled === false) return [];\n const driver = getQueueDriver();\n stopListening ??= onJobRegistered((job) => {\n void driver.start(queueOf(job)).catch((error: unknown) => log.error(\"queue\", \"worker\", error));\n });\n await Promise.all(\n [...new Set(registeredJobs().map(queueOf))].map((queue) => driver.start(queue)),\n );\n return driver.running();\n}\n\n/** Names of queues with workers in this process. */\nexport function runningWorkers(): string[] {\n return currentQueueDriver()?.running() ?? [];\n}\n\nexport type CloseQueueOptions = {\n /** Drain timeout in ms, default workers.shutdownTimeout, else 30000. */\n timeout?: number;\n};\n\n/** Stop taking jobs, drain active handlers, then release leases and close storage. */\nexport function closeQueue(options: CloseQueueOptions = {}): Promise<void> {\n if (closing) return closing;\n stopListening?.();\n stopListening = undefined;\n const driver = currentQueueDriver();\n if (!driver) return Promise.resolve();\n const timeout = options.timeout ?? configuredShutdownTimeout();\n if (!Number.isFinite(timeout) || timeout < 0)\n return Promise.reject(new Error(\"Queue shutdown timeout must be non-negative and finite.\"));\n closing = driver.close(timeout).finally(() => {\n forgetQueueDriver();\n closing = undefined;\n });\n return closing;\n}\n\nfunction configuredShutdownTimeout(): number {\n try {\n return getQueueConfig().workers?.shutdownTimeout ?? 30_000;\n } catch {\n return 30_000;\n }\n}\n"],"mappings":";;;;;;;AAOA,IAAI;AACJ,IAAI;;AAGJ,SAAgB,SAAS,MAAqB;CAC5C,MAAM,SAAS,eAAe;CAC9B,IAAI,EAAE,kBAAkB,eACtB,MAAM,IAAI,MAAM,8CAA8C;CAChE,OAAO,OAAO,SAAS,IAAI;AAC7B;;AAGA,eAAsB,eAAkC;CACtD,IAAI,SAAS,MAAM;CACnB,MAAM,EAAE,gBAAgB,MAAM,OAAO;CACrC,IAAI,CAAC,YAAY,QAAQ,QAAQ,GAAG;EAClC,IAAI,KACF,SACA,WACA,qCAAqC,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,KAAK,GAAG,EAAE,EACxE;EACA,OAAO,CAAC;CACV;CACA,IAAI,eAAe,CAAC,CAAC,SAAS,YAAY,OAAO,OAAO,CAAC;CACzD,MAAM,SAAS,eAAe;CAC9B,kBAAkB,iBAAiB,QAAQ;EACzC,AAAK,OAAO,MAAM,QAAQ,GAAG,CAAC,CAAC,CAAC,OAAO,UAAmB,IAAI,MAAM,SAAS,UAAU,KAAK,CAAC;CAC/F,CAAC;CACD,MAAM,QAAQ,IACZ,CAAC,GAAG,IAAI,IAAI,eAAe,CAAC,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,UAAU,OAAO,MAAM,KAAK,CAAC,CAChF;CACA,OAAO,OAAO,QAAQ;AACxB;;AAGA,SAAgB,iBAA2B;CACzC,OAAO,mBAAmB,CAAC,EAAE,QAAQ,KAAK,CAAC;AAC7C;;AAQA,SAAgB,WAAW,UAA6B,CAAC,GAAkB;CACzE,IAAI,SAAS,OAAO;CACpB,gBAAgB;CAChB,gBAAgB;CAChB,MAAM,SAAS,mBAAmB;CAClC,IAAI,CAAC,QAAQ,OAAO,QAAQ,QAAQ;CACpC,MAAM,UAAU,QAAQ,WAAW,0BAA0B;CAC7D,IAAI,CAAC,OAAO,SAAS,OAAO,KAAK,UAAU,GACzC,OAAO,QAAQ,uBAAO,IAAI,MAAM,yDAAyD,CAAC;CAC5F,UAAU,OAAO,MAAM,OAAO,CAAC,CAAC,cAAc;EAC5C,kBAAkB;EAClB,UAAU;CACZ,CAAC;CACD,OAAO;AACT;AAEA,SAAS,4BAAoC;CAC3C,IAAI;EACF,OAAO,eAAe,CAAC,CAAC,SAAS,mBAAmB;CACtD,QAAQ;EACN,OAAO;CACT;AACF"}
|
package/esm/types.d.mts
CHANGED
|
@@ -71,16 +71,27 @@ export type QueueDashboardConfig = {
|
|
|
71
71
|
*/
|
|
72
72
|
middleware?: Middleware[];
|
|
73
73
|
};
|
|
74
|
-
/**
|
|
75
|
-
|
|
76
|
-
*/
|
|
74
|
+
/** Single-host storage and local worker timing. */
|
|
75
|
+
export type LocalQueueConfig = {
|
|
76
|
+
/** SQLite file on a local disk. Default `.warlock/queue.sqlite`. */
|
|
77
|
+
path?: string;
|
|
78
|
+
/** Renewable worker lease in milliseconds. Default `30000`, minimum `30`. */
|
|
79
|
+
leaseDuration?: number;
|
|
80
|
+
/** Idle worker polling interval in milliseconds. Default `100`. */
|
|
81
|
+
pollInterval?: number;
|
|
82
|
+
};
|
|
83
|
+
/** The `queue` configuration key — `src/config/queue.ts`. */
|
|
77
84
|
export type QueueConfig = {
|
|
85
|
+
/** Backend. Default `"bullmq"`; `"local"` uses SQLite; `"memory"` is process-local. */
|
|
86
|
+
driver?: "bullmq" | "local" | "memory";
|
|
87
|
+
/** Local driver storage and worker timing (timing also applies to memory). */
|
|
88
|
+
local?: LocalQueueConfig;
|
|
78
89
|
/**
|
|
79
90
|
* Redis connection. Any BullMQ connection option: `{ host, port, password, db }`,
|
|
80
91
|
* `{ url }`, or an ioredis instance.
|
|
81
92
|
*/
|
|
82
|
-
connection
|
|
83
|
-
/**
|
|
93
|
+
connection?: ConnectionOptions;
|
|
94
|
+
/** Storage namespace for every queue. Default `"warlock"`. */
|
|
84
95
|
prefix?: string;
|
|
85
96
|
/** Queue name used when a job does not name one. Default `"default"`. */
|
|
86
97
|
defaultQueue?: string;
|
|
@@ -113,6 +124,8 @@ export type JobContext = {
|
|
|
113
124
|
log(line: string): Promise<void>;
|
|
114
125
|
/** State captured at dispatch by the registered queue context, if any. */
|
|
115
126
|
context: unknown;
|
|
127
|
+
/** Aborted when a local/memory worker loses its lease or shutdown times out. */
|
|
128
|
+
signal?: AbortSignal;
|
|
116
129
|
};
|
|
117
130
|
/**
|
|
118
131
|
* The definition passed to `defineJob`.
|
package/esm/types.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.mts","names":[],"sources":["../../../../../../../packages/queue/src/types.ts"],"mappings":";;;;YAIY;;;;;YAMA,gCAAgC;;;;;YAMhC;;EAIN;;EAEA;;;;;;YAOM;;;;;YAMA;;EAEV;;EAEA,UAAU;;EAEV,mBAAmB;;EAEnB,eAAe;;;;;YAML;;;;;EAKV;;EAEA;;;;;EAKA;;;;;;;YAQU;;EAEV;;EAEA;;;;;;EAMA,aAAa;;;;;
|
|
1
|
+
{"version":3,"file":"types.d.mts","names":[],"sources":["../../../../../../../packages/queue/src/types.ts"],"mappings":";;;;YAIY;;;;;YAMA,gCAAgC;;;;;YAMhC;;EAIN;;EAEA;;;;;;YAOM;;;;;YAMA;;EAEV;;EAEA,UAAU;;EAEV,mBAAmB;;EAEnB,eAAe;;;;;YAML;;;;;EAKV;;EAEA;;;;;EAKA;;;;;;;YAQU;;EAEV;;EAEA;;;;;;EAMA,aAAa;;;YAIH;;EAEV;;EAEA;;EAEA;;;YAIU;;EAEV;;EAEA,QAAQ;;;;;EAKR,aAAa;;EAEb;;EAEA;;EAEA,oBAAoB;;EAEpB,UAAU;;EAEV,YAAY;;;YAIF,uBAAuB;;;;YAKvB;;EAEV;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA,SAAS,OAAO,cAAc;;EAE9B,IAAI,eAAe;;EAEnB;;EAEA,SAAS;;;;;YAMC,cAAc,UAAU,WAAW;;EAE7C;;EAEA;;EAEA;;EAEA,OAAO,SAAS,UAAU,SAAS,aAAa,QAAQ,WAAW;;;;;YAMzD;;EAEV,QAAQ;;;;;EAKR;;;;;EAKA;;EAEA;;EAEA,UAAU;;;YAIA;EACV;EACA;EACA;;;YAIU;;YAWA,YAAY,oBAAoB;EAC1C;EACA;EACA;EACA,OAAO;EACP,SAAS;EACT,UAAU;EACV;EACA,SAAS;EACT;EACA,WAAW;EACX,aAAa;;;YAIH,UAAU;EACpB;EACA;EACA;EACA,SAAS;EACT;EACA;EACA;EACA,WAAW;;EAEX,SAAS;;;;;YAMC,SAAS,UAAU;WACpB;WACA;;EAET,SAAS,SAAS,UAAU,UAAU,kBAAkB,QAAQ;;EAEhE,KAAK,aAAa,QAAQ,YAAY,UAAU"}
|