@warlock.js/queue 5.13.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 +22 -0
- package/LICENSE +21 -0
- package/README.md +121 -0
- package/cjs/define-job-DideGKQK.cjs +468 -0
- package/cjs/define-job-DideGKQK.cjs.map +1 -0
- package/cjs/index.cjs +180 -0
- package/cjs/index.cjs.map +1 -0
- package/cjs/notifications/index.cjs +68 -0
- package/cjs/notifications/index.cjs.map +1 -0
- package/esm/config.d.mts +21 -0
- package/esm/config.d.mts.map +1 -0
- package/esm/config.mjs +32 -0
- package/esm/config.mjs.map +1 -0
- package/esm/dashboard.d.mts +47 -0
- package/esm/dashboard.d.mts.map +1 -0
- package/esm/dashboard.mjs +61 -0
- package/esm/dashboard.mjs.map +1 -0
- package/esm/define-job.d.mts +24 -0
- package/esm/define-job.d.mts.map +1 -0
- package/esm/define-job.mjs +102 -0
- package/esm/define-job.mjs.map +1 -0
- package/esm/duration.d.mts +11 -0
- package/esm/duration.d.mts.map +1 -0
- package/esm/duration.mjs +28 -0
- package/esm/duration.mjs.map +1 -0
- package/esm/errors.d.mts +36 -0
- package/esm/errors.d.mts.map +1 -0
- package/esm/errors.mjs +55 -0
- package/esm/errors.mjs.map +1 -0
- package/esm/failed-jobs.d.mts +23 -0
- package/esm/failed-jobs.d.mts.map +1 -0
- package/esm/failed-jobs.mjs +39 -0
- package/esm/failed-jobs.mjs.map +1 -0
- package/esm/index.d.mts +10 -0
- package/esm/index.mjs +10 -0
- package/esm/job-registry.mjs +39 -0
- package/esm/job-registry.mjs.map +1 -0
- package/esm/notifications/index.d.mts +2 -0
- package/esm/notifications/index.mjs +3 -0
- package/esm/notifications/queue-notification-dispatcher.d.mts +34 -0
- package/esm/notifications/queue-notification-dispatcher.d.mts.map +1 -0
- package/esm/notifications/queue-notification-dispatcher.mjs +67 -0
- package/esm/notifications/queue-notification-dispatcher.mjs.map +1 -0
- package/esm/process-job.mjs +32 -0
- package/esm/process-job.mjs.map +1 -0
- package/esm/queue-connector.d.mts +36 -0
- package/esm/queue-connector.d.mts.map +1 -0
- package/esm/queue-connector.mjs +73 -0
- package/esm/queue-connector.mjs.map +1 -0
- package/esm/queue-manager.d.mts +36 -0
- package/esm/queue-manager.d.mts.map +1 -0
- package/esm/queue-manager.mjs +109 -0
- package/esm/queue-manager.mjs.map +1 -0
- package/esm/types.d.mts +149 -0
- package/esm/types.d.mts.map +1 -0
- package/llms-full.txt +229 -0
- package/llms.txt +13 -0
- package/package.json +70 -0
- package/skills/configure-queue/SKILL.md +55 -0
- package/skills/define-jobs/SKILL.md +51 -0
- package/skills/manage-failed-jobs/SKILL.md +38 -0
- package/skills/overview/SKILL.md +26 -0
- package/skills/queue-notifications/SKILL.md +33 -0
package/esm/duration.mjs
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { InvalidDurationError } from "./errors.mjs";
|
|
2
|
+
|
|
3
|
+
//#region ../queue/src/duration.ts
|
|
4
|
+
const UNIT_MILLISECONDS = {
|
|
5
|
+
ms: 1,
|
|
6
|
+
s: 1e3,
|
|
7
|
+
m: 6e4,
|
|
8
|
+
h: 36e5,
|
|
9
|
+
d: 864e5
|
|
10
|
+
};
|
|
11
|
+
const DURATION_PATTERN = /^(\d+(?:\.\d+)?)(ms|s|m|h|d)$/;
|
|
12
|
+
/**
|
|
13
|
+
* Convert a {@link Duration} to milliseconds. Numbers are already
|
|
14
|
+
* milliseconds. Anything else is rejected loudly rather than guessed at.
|
|
15
|
+
*/
|
|
16
|
+
function toMilliseconds(value) {
|
|
17
|
+
if (typeof value === "number") {
|
|
18
|
+
if (!Number.isFinite(value) || value < 0) throw new InvalidDurationError(value);
|
|
19
|
+
return Math.round(value);
|
|
20
|
+
}
|
|
21
|
+
const match = typeof value === "string" ? DURATION_PATTERN.exec(value.trim()) : null;
|
|
22
|
+
if (!match) throw new InvalidDurationError(value);
|
|
23
|
+
return Math.round(Number(match[1]) * UNIT_MILLISECONDS[match[2]]);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
//#endregion
|
|
27
|
+
export { toMilliseconds };
|
|
28
|
+
//# sourceMappingURL=duration.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"duration.mjs","names":[],"sources":["../../../../../../queue/src/duration.ts"],"sourcesContent":["import { InvalidDurationError } from \"./errors\";\nimport type { Duration } from \"./types\";\n\nconst UNIT_MILLISECONDS: Record<string, number> = {\n ms: 1,\n s: 1_000,\n m: 60_000,\n h: 3_600_000,\n d: 86_400_000,\n};\n\nconst DURATION_PATTERN = /^(\\d+(?:\\.\\d+)?)(ms|s|m|h|d)$/;\n\n/**\n * Convert a {@link Duration} to milliseconds. Numbers are already\n * milliseconds. Anything else is rejected loudly rather than guessed at.\n */\nexport function toMilliseconds(value: Duration | string): number {\n if (typeof value === \"number\") {\n if (!Number.isFinite(value) || value < 0) {\n throw new InvalidDurationError(value);\n }\n\n return Math.round(value);\n }\n\n const match = typeof value === \"string\" ? DURATION_PATTERN.exec(value.trim()) : null;\n\n if (!match) {\n throw new InvalidDurationError(value);\n }\n\n return Math.round(Number(match[1]) * UNIT_MILLISECONDS[match[2]!]!);\n}\n"],"mappings":";;;AAGA,MAAM,oBAA4C;CAChD,IAAI;CACJ,GAAG;CACH,GAAG;CACH,GAAG;CACH,GAAG;AACL;AAEA,MAAM,mBAAmB;;;;;AAMzB,SAAgB,eAAe,OAAkC;CAC/D,IAAI,OAAO,UAAU,UAAU;EAC7B,IAAI,CAAC,OAAO,SAAS,KAAK,KAAK,QAAQ,GACrC,MAAM,IAAI,qBAAqB,KAAK;EAGtC,OAAO,KAAK,MAAM,KAAK;CACzB;CAEA,MAAM,QAAQ,OAAO,UAAU,WAAW,iBAAiB,KAAK,MAAM,KAAK,CAAC,IAAI;CAEhF,IAAI,CAAC,OACH,MAAM,IAAI,qBAAqB,KAAK;CAGtC,OAAO,KAAK,MAAM,OAAO,MAAM,EAAE,IAAI,kBAAkB,MAAM,GAAK;AACpE"}
|
package/esm/errors.d.mts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
//#region ../queue/src/errors.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Thrown when the queue is used before `setQueueConfig` (or the queue
|
|
4
|
+
* connector) supplied a configuration.
|
|
5
|
+
*/
|
|
6
|
+
declare class QueueNotConfiguredError extends Error {
|
|
7
|
+
constructor();
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Thrown for a malformed duration such as `"10 minutes"`.
|
|
11
|
+
*/
|
|
12
|
+
declare class InvalidDurationError extends Error {
|
|
13
|
+
constructor(value: unknown);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Thrown by `defineJob` for an invalid definition.
|
|
17
|
+
*/
|
|
18
|
+
declare class InvalidJobDefinitionError extends Error {
|
|
19
|
+
constructor(message: string);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Thrown by `retryFailedJob` when no failed job has the given id.
|
|
23
|
+
*/
|
|
24
|
+
declare class FailedJobNotFoundError extends Error {
|
|
25
|
+
constructor(id: string, queue: string);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Thrown by `queueDashboard` when an optional bull-board package is not
|
|
29
|
+
* installed.
|
|
30
|
+
*/
|
|
31
|
+
declare class QueueDashboardDependencyError extends Error {
|
|
32
|
+
constructor(missing: string);
|
|
33
|
+
}
|
|
34
|
+
//#endregion
|
|
35
|
+
export { FailedJobNotFoundError, InvalidDurationError, InvalidJobDefinitionError, QueueDashboardDependencyError, QueueNotConfiguredError };
|
|
36
|
+
//# sourceMappingURL=errors.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.mts","names":[],"sources":["../../../../../../queue/src/errors.ts"],"mappings":";;AAIA;;;cAAa,uBAAA,SAAgC,KAAK;EAAL,WAAA;AAAA;;;;cAchC,oBAAA,SAA6B,KAAK;cAC1B,KAAA;AAAA;;AAAc;AAYnC;cAAa,yBAAA,SAAkC,KAAK;cAC/B,OAAA;AAAA;;;;cASR,sBAAA,SAA+B,KAAK;cAC5B,EAAA,UAAY,KAAA;AAAA;;;;;cAUpB,6BAAA,SAAsC,KAAK;cACnC,OAAA;AAAA"}
|
package/esm/errors.mjs
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
//#region ../queue/src/errors.ts
|
|
2
|
+
/**
|
|
3
|
+
* Thrown when the queue is used before `setQueueConfig` (or the queue
|
|
4
|
+
* connector) supplied a configuration.
|
|
5
|
+
*/
|
|
6
|
+
var QueueNotConfiguredError = class extends Error {
|
|
7
|
+
constructor() {
|
|
8
|
+
super("@warlock.js/queue is not configured. Add src/config/queue.ts exporting a QueueConfig and register queueConnector() in warlock.config.ts > connectors, or call setQueueConfig() yourself.");
|
|
9
|
+
this.name = "QueueNotConfiguredError";
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Thrown for a malformed duration such as `"10 minutes"`.
|
|
14
|
+
*/
|
|
15
|
+
var InvalidDurationError = class extends Error {
|
|
16
|
+
constructor(value) {
|
|
17
|
+
super(`Invalid duration ${JSON.stringify(value)}: expected milliseconds as a non-negative number or a string like "500ms", "30s", "10m", "2h", "1d".`);
|
|
18
|
+
this.name = "InvalidDurationError";
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Thrown by `defineJob` for an invalid definition.
|
|
23
|
+
*/
|
|
24
|
+
var InvalidJobDefinitionError = class extends Error {
|
|
25
|
+
constructor(message) {
|
|
26
|
+
super(message);
|
|
27
|
+
this.name = "InvalidJobDefinitionError";
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Thrown by `retryFailedJob` when no failed job has the given id.
|
|
32
|
+
*/
|
|
33
|
+
var FailedJobNotFoundError = class extends Error {
|
|
34
|
+
constructor(id, queue) {
|
|
35
|
+
super(`No failed job with id "${id}" on queue "${queue}".`);
|
|
36
|
+
this.name = "FailedJobNotFoundError";
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Thrown by `queueDashboard` when an optional bull-board package is not
|
|
41
|
+
* installed.
|
|
42
|
+
*/
|
|
43
|
+
var QueueDashboardDependencyError = class extends Error {
|
|
44
|
+
constructor(missing) {
|
|
45
|
+
super(`The queue dashboard needs the optional package "${missing}", which is not installed.\nInstall both bull-board packages:
|
|
46
|
+
|
|
47
|
+
npm install @bull-board/api @bull-board/fastify
|
|
48
|
+
`);
|
|
49
|
+
this.name = "QueueDashboardDependencyError";
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
//#endregion
|
|
54
|
+
export { FailedJobNotFoundError, InvalidDurationError, InvalidJobDefinitionError, QueueDashboardDependencyError, QueueNotConfiguredError };
|
|
55
|
+
//# sourceMappingURL=errors.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.mjs","names":[],"sources":["../../../../../../queue/src/errors.ts"],"sourcesContent":["/**\n * Thrown when the queue is used before `setQueueConfig` (or the queue\n * connector) supplied a configuration.\n */\nexport class QueueNotConfiguredError extends Error {\n public constructor() {\n super(\n \"@warlock.js/queue is not configured. Add src/config/queue.ts exporting a QueueConfig \" +\n \"and register queueConnector() in warlock.config.ts > connectors, \" +\n \"or call setQueueConfig() yourself.\",\n );\n this.name = \"QueueNotConfiguredError\";\n }\n}\n\n/**\n * Thrown for a malformed duration such as `\"10 minutes\"`.\n */\nexport class InvalidDurationError extends Error {\n public constructor(value: unknown) {\n super(\n `Invalid duration ${JSON.stringify(value)}: expected milliseconds as a non-negative number ` +\n `or a string like \"500ms\", \"30s\", \"10m\", \"2h\", \"1d\".`,\n );\n this.name = \"InvalidDurationError\";\n }\n}\n\n/**\n * Thrown by `defineJob` for an invalid definition.\n */\nexport class InvalidJobDefinitionError extends Error {\n public constructor(message: string) {\n super(message);\n this.name = \"InvalidJobDefinitionError\";\n }\n}\n\n/**\n * Thrown by `retryFailedJob` when no failed job has the given id.\n */\nexport class FailedJobNotFoundError extends Error {\n public constructor(id: string, queue: string) {\n super(`No failed job with id \"${id}\" on queue \"${queue}\".`);\n this.name = \"FailedJobNotFoundError\";\n }\n}\n\n/**\n * Thrown by `queueDashboard` when an optional bull-board package is not\n * installed.\n */\nexport class QueueDashboardDependencyError extends Error {\n public constructor(missing: string) {\n super(\n `The queue dashboard needs the optional package \"${missing}\", which is not installed.\\n` +\n \"Install both bull-board packages:\\n\\n\" +\n \" npm install @bull-board/api @bull-board/fastify\\n\",\n );\n this.name = \"QueueDashboardDependencyError\";\n }\n}\n"],"mappings":";;;;;AAIA,IAAa,0BAAb,cAA6C,MAAM;CACjD,AAAO,cAAc;EACnB,MACE,0LAGF;EACA,KAAK,OAAO;CACd;AACF;;;;AAKA,IAAa,uBAAb,cAA0C,MAAM;CAC9C,AAAO,YAAY,OAAgB;EACjC,MACE,oBAAoB,KAAK,UAAU,KAAK,EAAE,qGAE5C;EACA,KAAK,OAAO;CACd;AACF;;;;AAKA,IAAa,4BAAb,cAA+C,MAAM;CACnD,AAAO,YAAY,SAAiB;EAClC,MAAM,OAAO;EACb,KAAK,OAAO;CACd;AACF;;;;AAKA,IAAa,yBAAb,cAA4C,MAAM;CAChD,AAAO,YAAY,IAAY,OAAe;EAC5C,MAAM,0BAA0B,GAAG,cAAc,MAAM,GAAG;EAC1D,KAAK,OAAO;CACd;AACF;;;;;AAMA,IAAa,gCAAb,cAAmD,MAAM;CACvD,AAAO,YAAY,SAAiB;EAClC,MACE,mDAAmD,QAAQ;;;CAG7D;EACA,KAAK,OAAO;CACd;AACF"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { FailedJob } from "./types.mjs";
|
|
2
|
+
|
|
3
|
+
//#region ../queue/src/failed-jobs.d.ts
|
|
4
|
+
type FailedJobsOptions = {
|
|
5
|
+
/** Queue to read. Default: the default queue. */queue?: string; /** First index (newest first). Default `0`. */
|
|
6
|
+
start?: number; /** Last index, inclusive. Default `99`. */
|
|
7
|
+
end?: number;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* List failed jobs, newest first — jobs that used up every attempt, or
|
|
11
|
+
* failed unrecoverably. Each entry can be retried.
|
|
12
|
+
*/
|
|
13
|
+
declare function failedJobs(options?: FailedJobsOptions): Promise<FailedJob[]>;
|
|
14
|
+
/**
|
|
15
|
+
* Retry one failed job by id: it goes back to waiting with its attempts reset.
|
|
16
|
+
* Throws {@link FailedJobNotFoundError} when no FAILED job has that id.
|
|
17
|
+
*/
|
|
18
|
+
declare function retryFailedJob(id: string, options?: {
|
|
19
|
+
queue?: string;
|
|
20
|
+
}): Promise<void>;
|
|
21
|
+
//#endregion
|
|
22
|
+
export { FailedJobsOptions, failedJobs, retryFailedJob };
|
|
23
|
+
//# sourceMappingURL=failed-jobs.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"failed-jobs.d.mts","names":[],"sources":["../../../../../../queue/src/failed-jobs.ts"],"mappings":";;;KAMY,iBAAA;mDAEV,KAAA,WAF2B;EAI3B,KAAA,WAJ2B;EAM3B,GAAA;AAAA;;;AAAG;AAOL;iBAAsB,UAAA,CAAW,OAAA,GAAS,iBAAA,GAAyB,OAAA,CAAQ,SAAA;;;;;iBAWrD,cAAA,CAAe,EAAA,UAAY,OAAA;EAAW,KAAA;AAAA,IAAwB,OAAO"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { FailedJobNotFoundError } from "./errors.mjs";
|
|
2
|
+
import { defaultQueueName } from "./config.mjs";
|
|
3
|
+
import { getQueue } from "./queue-manager.mjs";
|
|
4
|
+
|
|
5
|
+
//#region ../queue/src/failed-jobs.ts
|
|
6
|
+
/**
|
|
7
|
+
* List failed jobs, newest first — jobs that used up every attempt, or
|
|
8
|
+
* failed unrecoverably. Each entry can be retried.
|
|
9
|
+
*/
|
|
10
|
+
async function failedJobs(options = {}) {
|
|
11
|
+
return (await getQueue(options.queue ?? defaultQueueName()).getFailed(options.start ?? 0, options.end ?? 99)).map((job) => toFailedJob(job));
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Retry one failed job by id: it goes back to waiting with its attempts reset.
|
|
15
|
+
* Throws {@link FailedJobNotFoundError} when no FAILED job has that id.
|
|
16
|
+
*/
|
|
17
|
+
async function retryFailedJob(id, options = {}) {
|
|
18
|
+
const queueName = options.queue ?? defaultQueueName();
|
|
19
|
+
const job = await getQueue(queueName).getJob(id);
|
|
20
|
+
if (!job || !await job.isFailed()) throw new FailedJobNotFoundError(id, queueName);
|
|
21
|
+
await job.retry("failed");
|
|
22
|
+
}
|
|
23
|
+
function toFailedJob(job) {
|
|
24
|
+
return {
|
|
25
|
+
id: String(job.id),
|
|
26
|
+
name: job.name,
|
|
27
|
+
queue: job.queueName,
|
|
28
|
+
payload: job.data,
|
|
29
|
+
attemptsMade: job.attemptsMade,
|
|
30
|
+
failedReason: job.failedReason,
|
|
31
|
+
stacktrace: job.stacktrace ?? [],
|
|
32
|
+
failedAt: job.finishedOn ? new Date(job.finishedOn) : void 0,
|
|
33
|
+
retry: () => job.retry("failed")
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
//#endregion
|
|
38
|
+
export { failedJobs, retryFailedJob };
|
|
39
|
+
//# sourceMappingURL=failed-jobs.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"failed-jobs.mjs","names":[],"sources":["../../../../../../queue/src/failed-jobs.ts"],"sourcesContent":["import type { Job } from \"bullmq\";\nimport { defaultQueueName } from \"./config\";\nimport { FailedJobNotFoundError } from \"./errors\";\nimport { getQueue } from \"./queue-manager\";\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/**\n * List failed jobs, newest first — jobs that used up every attempt, or\n * failed unrecoverably. Each entry can be retried.\n */\nexport async function failedJobs(options: FailedJobsOptions = {}): Promise<FailedJob[]> {\n const queueName = options.queue ?? defaultQueueName();\n const jobs = await getQueue(queueName).getFailed(options.start ?? 0, options.end ?? 99);\n\n return jobs.map((job) => toFailedJob(job));\n}\n\n/**\n * Retry one failed job by id: it goes back to waiting with its attempts reset.\n * Throws {@link FailedJobNotFoundError} when no FAILED job has that id.\n */\nexport async function retryFailedJob(id: string, options: { queue?: string } = {}): Promise<void> {\n const queueName = options.queue ?? defaultQueueName();\n const job = await getQueue(queueName).getJob(id);\n\n if (!job || !(await job.isFailed())) {\n throw new FailedJobNotFoundError(id, queueName);\n }\n\n await job.retry(\"failed\");\n}\n\nfunction toFailedJob(job: Job): FailedJob {\n return {\n id: String(job.id),\n name: job.name,\n queue: job.queueName,\n payload: job.data,\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"],"mappings":";;;;;;;;;AAmBA,eAAsB,WAAW,UAA6B,CAAC,GAAyB;CAItF,QAAO,MAFY,SADD,QAAQ,SAAS,iBAAiB,CACf,CAAC,CAAC,UAAU,QAAQ,SAAS,GAAG,QAAQ,OAAO,EAAE,EAE3E,CAAC,KAAK,QAAQ,YAAY,GAAG,CAAC;AAC3C;;;;;AAMA,eAAsB,eAAe,IAAY,UAA8B,CAAC,GAAkB;CAChG,MAAM,YAAY,QAAQ,SAAS,iBAAiB;CACpD,MAAM,MAAM,MAAM,SAAS,SAAS,CAAC,CAAC,OAAO,EAAE;CAE/C,IAAI,CAAC,OAAO,CAAE,MAAM,IAAI,SAAS,GAC/B,MAAM,IAAI,uBAAuB,IAAI,SAAS;CAGhD,MAAM,IAAI,MAAM,QAAQ;AAC1B;AAEA,SAAS,YAAY,KAAqB;CACxC,OAAO;EACL,IAAI,OAAO,IAAI,EAAE;EACjB,MAAM,IAAI;EACV,OAAO,IAAI;EACX,SAAS,IAAI;EACb,cAAc,IAAI;EAClB,cAAc,IAAI;EAClB,YAAY,IAAI,cAAc,CAAC;EAC/B,UAAU,IAAI,aAAa,IAAI,KAAK,IAAI,UAAU,IAAI;EACtD,aAAa,IAAI,MAAM,QAAQ;CACjC;AACF"}
|
package/esm/index.d.mts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { DispatchOptions, DispatchedJob, Duration, DurationUnit, FailedJob, JobBackoff, JobContext, JobDefinition, JobOptions, JobProgress, JobRetention, JobSnapshot, JobState, QueueConfig, QueueJob, QueueWorkersConfig } from "./types.mjs";
|
|
2
|
+
import { defaultQueueName, getQueueConfig, resetQueueConfig, setQueueConfig } from "./config.mjs";
|
|
3
|
+
import { DashboardServer, QueueDashboardOptions, loadBullBoard, queueDashboard } from "./dashboard.mjs";
|
|
4
|
+
import { defineJob } from "./define-job.mjs";
|
|
5
|
+
import { toMilliseconds } from "./duration.mjs";
|
|
6
|
+
import { FailedJobNotFoundError, InvalidDurationError, InvalidJobDefinitionError, QueueDashboardDependencyError, QueueNotConfiguredError } from "./errors.mjs";
|
|
7
|
+
import { FailedJobsOptions, failedJobs, retryFailedJob } from "./failed-jobs.mjs";
|
|
8
|
+
import { QUEUE_CONNECTOR_PRIORITY, QueueConnectorOptions, queueConnector } from "./queue-connector.mjs";
|
|
9
|
+
import { CloseQueueOptions, closeQueue, getQueue, runningWorkers, startWorkers } from "./queue-manager.mjs";
|
|
10
|
+
export { type CloseQueueOptions, DashboardServer, DispatchOptions, DispatchedJob, Duration, DurationUnit, FailedJob, FailedJobNotFoundError, FailedJobsOptions, InvalidDurationError, InvalidJobDefinitionError, JobBackoff, JobContext, JobDefinition, JobOptions, JobProgress, JobRetention, JobSnapshot, JobState, QUEUE_CONNECTOR_PRIORITY, QueueConfig, QueueConnectorOptions, QueueDashboardDependencyError, QueueDashboardOptions, QueueJob, QueueNotConfiguredError, QueueWorkersConfig, closeQueue, defaultQueueName, defineJob, failedJobs, getQueue, getQueueConfig, loadBullBoard, queueConnector, queueDashboard, resetQueueConfig, retryFailedJob, runningWorkers, setQueueConfig, startWorkers, toMilliseconds };
|
package/esm/index.mjs
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { FailedJobNotFoundError, InvalidDurationError, InvalidJobDefinitionError, QueueDashboardDependencyError, QueueNotConfiguredError } from "./errors.mjs";
|
|
2
|
+
import { defaultQueueName, getQueueConfig, resetQueueConfig, setQueueConfig } from "./config.mjs";
|
|
3
|
+
import { closeQueue, getQueue, runningWorkers, startWorkers } from "./queue-manager.mjs";
|
|
4
|
+
import { loadBullBoard, queueDashboard } from "./dashboard.mjs";
|
|
5
|
+
import { toMilliseconds } from "./duration.mjs";
|
|
6
|
+
import { defineJob } from "./define-job.mjs";
|
|
7
|
+
import { failedJobs, retryFailedJob } from "./failed-jobs.mjs";
|
|
8
|
+
import { QUEUE_CONNECTOR_PRIORITY, queueConnector } from "./queue-connector.mjs";
|
|
9
|
+
|
|
10
|
+
export { FailedJobNotFoundError, InvalidDurationError, InvalidJobDefinitionError, QUEUE_CONNECTOR_PRIORITY, QueueDashboardDependencyError, QueueNotConfiguredError, closeQueue, defaultQueueName, defineJob, failedJobs, getQueue, getQueueConfig, loadBullBoard, queueConnector, queueDashboard, resetQueueConfig, retryFailedJob, runningWorkers, setQueueConfig, startWorkers, toMilliseconds };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { defaultQueueName } from "./config.mjs";
|
|
2
|
+
|
|
3
|
+
//#region ../queue/src/job-registry.ts
|
|
4
|
+
const jobs = /* @__PURE__ */ new Map();
|
|
5
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
6
|
+
/**
|
|
7
|
+
* Register a definition under its name.
|
|
8
|
+
*
|
|
9
|
+
* Re-registering a name REPLACES the previous definition: in development a
|
|
10
|
+
* job module is re-evaluated on every reload, and refusing the second
|
|
11
|
+
* evaluation would break the reload. Job names must therefore be unique
|
|
12
|
+
* across the app — two different modules using one name leave only the
|
|
13
|
+
* later handler active.
|
|
14
|
+
*/
|
|
15
|
+
function registerJob(job) {
|
|
16
|
+
jobs.set(job.name, job);
|
|
17
|
+
for (const listener of listeners) listener(job);
|
|
18
|
+
}
|
|
19
|
+
/** The definition registered under `name`, if any. */
|
|
20
|
+
function findRegisteredJob(name) {
|
|
21
|
+
return jobs.get(name);
|
|
22
|
+
}
|
|
23
|
+
/** Every registered definition. */
|
|
24
|
+
function registeredJobs() {
|
|
25
|
+
return [...jobs.values()];
|
|
26
|
+
}
|
|
27
|
+
/** The queue a definition runs on, resolved against the active config. */
|
|
28
|
+
function queueOf(job) {
|
|
29
|
+
return job.queue ?? defaultQueueName();
|
|
30
|
+
}
|
|
31
|
+
/** Be told whenever a job is registered. Returns an unsubscribe function. */
|
|
32
|
+
function onJobRegistered(listener) {
|
|
33
|
+
listeners.add(listener);
|
|
34
|
+
return () => listeners.delete(listener);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
//#endregion
|
|
38
|
+
export { findRegisteredJob, onJobRegistered, queueOf, registerJob, registeredJobs };
|
|
39
|
+
//# sourceMappingURL=job-registry.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"job-registry.mjs","names":[],"sources":["../../../../../../queue/src/job-registry.ts"],"sourcesContent":["import { defaultQueueName } from \"./config\";\nimport type { JobDefinition } from \"./types\";\n\n/** A registered definition, payload/result erased for storage. */\nexport type RegisteredJob = JobDefinition<unknown, unknown>;\n\ntype RegistryListener = (job: RegisteredJob) => void;\n\nconst jobs = new Map<string, RegisteredJob>();\nconst listeners = new Set<RegistryListener>();\n\n/**\n * Register a definition under its name.\n *\n * Re-registering a name REPLACES the previous definition: in development a\n * job module is re-evaluated on every reload, and refusing the second\n * evaluation would break the reload. Job names must therefore be unique\n * across the app — two different modules using one name leave only the\n * later handler active.\n */\nexport function registerJob(job: RegisteredJob): void {\n jobs.set(job.name, job);\n\n for (const listener of listeners) {\n listener(job);\n }\n}\n\n/** The definition registered under `name`, if any. */\nexport function findRegisteredJob(name: string): RegisteredJob | undefined {\n return jobs.get(name);\n}\n\n/** Every registered definition. */\nexport function registeredJobs(): RegisteredJob[] {\n return [...jobs.values()];\n}\n\n/** The queue a definition runs on, resolved against the active config. */\nexport function queueOf(job: Pick<RegisteredJob, \"queue\">): string {\n return job.queue ?? defaultQueueName();\n}\n\n/** Be told whenever a job is registered. Returns an unsubscribe function. */\nexport function onJobRegistered(listener: RegistryListener): () => void {\n listeners.add(listener);\n\n return () => listeners.delete(listener);\n}\n"],"mappings":";;;AAQA,MAAM,uBAAO,IAAI,IAA2B;AAC5C,MAAM,4BAAY,IAAI,IAAsB;;;;;;;;;;AAW5C,SAAgB,YAAY,KAA0B;CACpD,KAAK,IAAI,IAAI,MAAM,GAAG;CAEtB,KAAK,MAAM,YAAY,WACrB,SAAS,GAAG;AAEhB;;AAGA,SAAgB,kBAAkB,MAAyC;CACzE,OAAO,KAAK,IAAI,IAAI;AACtB;;AAGA,SAAgB,iBAAkC;CAChD,OAAO,CAAC,GAAG,KAAK,OAAO,CAAC;AAC1B;;AAGA,SAAgB,QAAQ,KAA2C;CACjE,OAAO,IAAI,SAAS,iBAAiB;AACvC;;AAGA,SAAgB,gBAAgB,UAAwC;CACtE,UAAU,IAAI,QAAQ;CAEtB,aAAa,UAAU,OAAO,QAAQ;AACxC"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { NOTIFICATION_JOB_NAME, NotificationJobPayload, QueueNotificationDispatcherOptions, queueNotificationDispatcher } from "./queue-notification-dispatcher.mjs";
|
|
2
|
+
export { NOTIFICATION_JOB_NAME, NotificationJobPayload, QueueNotificationDispatcherOptions, queueNotificationDispatcher };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { JobBackoff } from "../types.mjs";
|
|
2
|
+
import { QueueDispatcher } from "@warlock.js/notifications";
|
|
3
|
+
|
|
4
|
+
//#region ../queue/src/notifications/queue-notification-dispatcher.d.ts
|
|
5
|
+
/** The job name notification deliveries run under. */
|
|
6
|
+
declare const NOTIFICATION_JOB_NAME = "warlock.notifications.deliver";
|
|
7
|
+
type NotificationJobPayload = Parameters<QueueDispatcher["dispatch"]>[0];
|
|
8
|
+
type QueueNotificationDispatcherOptions = {
|
|
9
|
+
/** Queue to deliver on. Default: the default queue. */queue?: string; /** Attempts per delivery. Default: `queue.defaultJobOptions.attempts`, else `1`. */
|
|
10
|
+
attempts?: number; /** Backoff between attempts. */
|
|
11
|
+
backoff?: JobBackoff;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Create the dispatcher for `NotificationConfig.queue`.
|
|
15
|
+
*
|
|
16
|
+
* - `SendOptions.delay` is honoured: a number is SECONDS (notifications'
|
|
17
|
+
* convention), a string is a duration such as `"10m"`.
|
|
18
|
+
* - A failing `channel.send` throws, so the delivery is retried per
|
|
19
|
+
* `attempts` / `backoff` and ends in `failedJobs()` when exhausted.
|
|
20
|
+
* - A channel missing from the worker's notifications config fails at once,
|
|
21
|
+
* without retries.
|
|
22
|
+
*
|
|
23
|
+
* @example src/config/notifications.ts
|
|
24
|
+
* import { queueNotificationDispatcher } from "@warlock.js/queue/notifications";
|
|
25
|
+
*
|
|
26
|
+
* const config: NotificationConfig = {
|
|
27
|
+
* channels: { mail: mailChannel() },
|
|
28
|
+
* queue: queueNotificationDispatcher({ attempts: 3, backoff: { type: "exponential", delay: 5000 } }),
|
|
29
|
+
* };
|
|
30
|
+
*/
|
|
31
|
+
declare function queueNotificationDispatcher(options?: QueueNotificationDispatcherOptions): QueueDispatcher;
|
|
32
|
+
//#endregion
|
|
33
|
+
export { NOTIFICATION_JOB_NAME, NotificationJobPayload, QueueNotificationDispatcherOptions, queueNotificationDispatcher };
|
|
34
|
+
//# sourceMappingURL=queue-notification-dispatcher.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queue-notification-dispatcher.d.mts","names":[],"sources":["../../../../../../../queue/src/notifications/queue-notification-dispatcher.ts"],"mappings":";;;;AAoB+D;AAAA,cAFlD,qBAAA;AAAA,KAED,sBAAA,GAAyB,UAAU,CAAC,eAAA;AAAA,KAEpC,kCAAA;EAMU,uDAJpB,KAAA,WAEA;EAAA,QAAA,WAEU;EAAV,OAAA,GAAU,UAAU;AAAA;AAqBtB;;;;;;;;AAEkB;;;;;;;;;;AAFlB,iBAAgB,2BAAA,CACd,OAAA,GAAS,kCAAA,GACR,eAAe"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { toMilliseconds } from "../duration.mjs";
|
|
2
|
+
import { defineJob } from "../define-job.mjs";
|
|
3
|
+
import { UnrecoverableError } from "bullmq";
|
|
4
|
+
import { getNotificationConfig } from "@warlock.js/notifications";
|
|
5
|
+
|
|
6
|
+
//#region ../queue/src/notifications/queue-notification-dispatcher.ts
|
|
7
|
+
/**
|
|
8
|
+
* BullMQ-backed `QueueDispatcher` for `@warlock.js/notifications`.
|
|
9
|
+
*
|
|
10
|
+
* Notifications renders the payload and resolves the route BEFORE handing a
|
|
11
|
+
* job to its dispatcher, so the job (`{ channel, route, payload, options }`)
|
|
12
|
+
* is plain JSON. This adapter enqueues it as a queue job; the job's handler,
|
|
13
|
+
* running in any worker process, looks the channel up by name in that
|
|
14
|
+
* process's notifications config and calls `channel.send`.
|
|
15
|
+
*
|
|
16
|
+
* Notifications itself has no dependency on this package.
|
|
17
|
+
*/
|
|
18
|
+
/** The job name notification deliveries run under. */
|
|
19
|
+
const NOTIFICATION_JOB_NAME = "warlock.notifications.deliver";
|
|
20
|
+
/**
|
|
21
|
+
* Create the dispatcher for `NotificationConfig.queue`.
|
|
22
|
+
*
|
|
23
|
+
* - `SendOptions.delay` is honoured: a number is SECONDS (notifications'
|
|
24
|
+
* convention), a string is a duration such as `"10m"`.
|
|
25
|
+
* - A failing `channel.send` throws, so the delivery is retried per
|
|
26
|
+
* `attempts` / `backoff` and ends in `failedJobs()` when exhausted.
|
|
27
|
+
* - A channel missing from the worker's notifications config fails at once,
|
|
28
|
+
* without retries.
|
|
29
|
+
*
|
|
30
|
+
* @example src/config/notifications.ts
|
|
31
|
+
* import { queueNotificationDispatcher } from "@warlock.js/queue/notifications";
|
|
32
|
+
*
|
|
33
|
+
* const config: NotificationConfig = {
|
|
34
|
+
* channels: { mail: mailChannel() },
|
|
35
|
+
* queue: queueNotificationDispatcher({ attempts: 3, backoff: { type: "exponential", delay: 5000 } }),
|
|
36
|
+
* };
|
|
37
|
+
*/
|
|
38
|
+
function queueNotificationDispatcher(options = {}) {
|
|
39
|
+
const deliver = defineNotificationJob(options);
|
|
40
|
+
return { async dispatch(job) {
|
|
41
|
+
await deliver.dispatch(job, { delay: job.options.delay === void 0 ? void 0 : notificationDelay(job.options.delay) });
|
|
42
|
+
} };
|
|
43
|
+
}
|
|
44
|
+
function defineNotificationJob(options) {
|
|
45
|
+
return defineJob({
|
|
46
|
+
name: NOTIFICATION_JOB_NAME,
|
|
47
|
+
queue: options.queue,
|
|
48
|
+
attempts: options.attempts,
|
|
49
|
+
backoff: options.backoff,
|
|
50
|
+
async handle(job) {
|
|
51
|
+
const channel = getNotificationConfig().channels[job.channel];
|
|
52
|
+
if (!channel) throw new UnrecoverableError(`Notification channel "${job.channel}" is not configured in this worker's notifications config.`);
|
|
53
|
+
await channel.send({
|
|
54
|
+
payload: job.payload,
|
|
55
|
+
route: job.route,
|
|
56
|
+
options: job.options
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
function notificationDelay(delay) {
|
|
62
|
+
return typeof delay === "number" ? delay * 1e3 : toMilliseconds(delay);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
//#endregion
|
|
66
|
+
export { NOTIFICATION_JOB_NAME, queueNotificationDispatcher };
|
|
67
|
+
//# sourceMappingURL=queue-notification-dispatcher.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queue-notification-dispatcher.mjs","names":[],"sources":["../../../../../../../queue/src/notifications/queue-notification-dispatcher.ts"],"sourcesContent":["/**\n * BullMQ-backed `QueueDispatcher` for `@warlock.js/notifications`.\n *\n * Notifications renders the payload and resolves the route BEFORE handing a\n * job to its dispatcher, so the job (`{ channel, route, payload, options }`)\n * is plain JSON. This adapter enqueues it as a queue job; the job's handler,\n * running in any worker process, looks the channel up by name in that\n * process's notifications config and calls `channel.send`.\n *\n * Notifications itself has no dependency on this package.\n */\nimport { getNotificationConfig, type QueueDispatcher } from \"@warlock.js/notifications\";\nimport { UnrecoverableError } from \"bullmq\";\nimport { defineJob } from \"../define-job\";\nimport { toMilliseconds } from \"../duration\";\nimport type { Duration, JobBackoff, QueueJob } from \"../types\";\n\n/** The job name notification deliveries run under. */\nexport const NOTIFICATION_JOB_NAME = \"warlock.notifications.deliver\";\n\nexport type NotificationJobPayload = Parameters<QueueDispatcher[\"dispatch\"]>[0];\n\nexport type QueueNotificationDispatcherOptions = {\n /** Queue to deliver on. Default: the default queue. */\n queue?: string;\n /** Attempts per delivery. Default: `queue.defaultJobOptions.attempts`, else `1`. */\n attempts?: number;\n /** Backoff between attempts. */\n backoff?: JobBackoff;\n};\n\n/**\n * Create the dispatcher for `NotificationConfig.queue`.\n *\n * - `SendOptions.delay` is honoured: a number is SECONDS (notifications'\n * convention), a string is a duration such as `\"10m\"`.\n * - A failing `channel.send` throws, so the delivery is retried per\n * `attempts` / `backoff` and ends in `failedJobs()` when exhausted.\n * - A channel missing from the worker's notifications config fails at once,\n * without retries.\n *\n * @example src/config/notifications.ts\n * import { queueNotificationDispatcher } from \"@warlock.js/queue/notifications\";\n *\n * const config: NotificationConfig = {\n * channels: { mail: mailChannel() },\n * queue: queueNotificationDispatcher({ attempts: 3, backoff: { type: \"exponential\", delay: 5000 } }),\n * };\n */\nexport function queueNotificationDispatcher(\n options: QueueNotificationDispatcherOptions = {},\n): QueueDispatcher {\n const deliver = defineNotificationJob(options);\n\n return {\n async dispatch(job) {\n await deliver.dispatch(job, {\n delay: job.options.delay === undefined ? undefined : notificationDelay(job.options.delay),\n });\n },\n };\n}\n\nfunction defineNotificationJob(\n options: QueueNotificationDispatcherOptions,\n): QueueJob<NotificationJobPayload, void> {\n return defineJob<NotificationJobPayload, void>({\n name: NOTIFICATION_JOB_NAME,\n queue: options.queue,\n attempts: options.attempts,\n backoff: options.backoff,\n async handle(job) {\n const channels = getNotificationConfig().channels as Record<\n string,\n { send(context: never): Promise<void> } | undefined\n >;\n const channel = channels[job.channel];\n\n if (!channel) {\n throw new UnrecoverableError(\n `Notification channel \"${job.channel}\" is not configured in this worker's notifications config.`,\n );\n }\n\n await channel.send({ payload: job.payload, route: job.route, options: job.options } as never);\n },\n });\n}\n\nfunction notificationDelay(delay: number | string): Duration {\n return typeof delay === \"number\" ? delay * 1_000 : toMilliseconds(delay);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAkBA,MAAa,wBAAwB;;;;;;;;;;;;;;;;;;;AA+BrC,SAAgB,4BACd,UAA8C,CAAC,GAC9B;CACjB,MAAM,UAAU,sBAAsB,OAAO;CAE7C,OAAO,EACL,MAAM,SAAS,KAAK;EAClB,MAAM,QAAQ,SAAS,KAAK,EAC1B,OAAO,IAAI,QAAQ,UAAU,SAAY,SAAY,kBAAkB,IAAI,QAAQ,KAAK,EAC1F,CAAC;CACH,EACF;AACF;AAEA,SAAS,sBACP,SACwC;CACxC,OAAO,UAAwC;EAC7C,MAAM;EACN,OAAO,QAAQ;EACf,UAAU,QAAQ;EAClB,SAAS,QAAQ;EACjB,MAAM,OAAO,KAAK;GAKhB,MAAM,UAJW,sBAAsB,CAAC,CAAC,SAIhB,IAAI;GAE7B,IAAI,CAAC,SACH,MAAM,IAAI,mBACR,yBAAyB,IAAI,QAAQ,2DACvC;GAGF,MAAM,QAAQ,KAAK;IAAE,SAAS,IAAI;IAAS,OAAO,IAAI;IAAO,SAAS,IAAI;GAAQ,CAAU;EAC9F;CACF,CAAC;AACH;AAEA,SAAS,kBAAkB,OAAkC;CAC3D,OAAO,OAAO,UAAU,WAAW,QAAQ,MAAQ,eAAe,KAAK;AACzE"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { findRegisteredJob } from "./job-registry.mjs";
|
|
2
|
+
import { UnrecoverableError } from "bullmq";
|
|
3
|
+
|
|
4
|
+
//#region ../queue/src/process-job.ts
|
|
5
|
+
/**
|
|
6
|
+
* The single BullMQ processor every worker runs: route the job to the
|
|
7
|
+
* handler registered under its name.
|
|
8
|
+
*
|
|
9
|
+
* A name with no handler in this process fails with `UnrecoverableError` —
|
|
10
|
+
* retrying cannot make a missing definition appear, so it must not burn
|
|
11
|
+
* through its attempts.
|
|
12
|
+
*/
|
|
13
|
+
async function processJob(job) {
|
|
14
|
+
const definition = findRegisteredJob(job.name);
|
|
15
|
+
if (!definition) throw new UnrecoverableError(`No job named "${job.name}" is defined in this process. Make sure the module that calls defineJob() is imported by the worker process.`);
|
|
16
|
+
const context = {
|
|
17
|
+
id: String(job.id),
|
|
18
|
+
name: job.name,
|
|
19
|
+
queue: job.queueName,
|
|
20
|
+
attempt: job.attemptsMade + 1,
|
|
21
|
+
maxAttempts: job.opts.attempts ?? 1,
|
|
22
|
+
progress: (value) => job.updateProgress(value),
|
|
23
|
+
log: async (line) => {
|
|
24
|
+
await job.log(line);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
return definition.handle(job.data, context);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
//#endregion
|
|
31
|
+
export { processJob };
|
|
32
|
+
//# sourceMappingURL=process-job.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"process-job.mjs","names":[],"sources":["../../../../../../queue/src/process-job.ts"],"sourcesContent":["import { type Job, UnrecoverableError } from \"bullmq\";\nimport { findRegisteredJob } from \"./job-registry\";\nimport type { JobContext } from \"./types\";\n\n/**\n * The single BullMQ processor every worker runs: route the job to the\n * handler registered under its name.\n *\n * A name with no handler in this process fails with `UnrecoverableError` —\n * retrying cannot make a missing definition appear, so it must not burn\n * through its attempts.\n */\nexport async function processJob(job: Job): Promise<unknown> {\n const definition = findRegisteredJob(job.name);\n\n if (!definition) {\n throw new UnrecoverableError(\n `No job named \"${job.name}\" is defined in this process. ` +\n \"Make sure the module that calls defineJob() is imported by the worker process.\",\n );\n }\n\n const context: JobContext = {\n id: String(job.id),\n name: job.name,\n queue: job.queueName,\n attempt: job.attemptsMade + 1,\n maxAttempts: job.opts.attempts ?? 1,\n progress: (value) => job.updateProgress(value),\n log: async (line) => {\n await job.log(line);\n },\n };\n\n return definition.handle(job.data, context);\n}\n"],"mappings":";;;;;;;;;;;;AAYA,eAAsB,WAAW,KAA4B;CAC3D,MAAM,aAAa,kBAAkB,IAAI,IAAI;CAE7C,IAAI,CAAC,YACH,MAAM,IAAI,mBACR,iBAAiB,IAAI,KAAK,6GAE5B;CAGF,MAAM,UAAsB;EAC1B,IAAI,OAAO,IAAI,EAAE;EACjB,MAAM,IAAI;EACV,OAAO,IAAI;EACX,SAAS,IAAI,eAAe;EAC5B,aAAa,IAAI,KAAK,YAAY;EAClC,WAAW,UAAU,IAAI,eAAe,KAAK;EAC7C,KAAK,OAAO,SAAS;GACnB,MAAM,IAAI,IAAI,IAAI;EACpB;CACF;CAEA,OAAO,WAAW,OAAO,IAAI,MAAM,OAAO;AAC5C"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { QueueConfig } from "./types.mjs";
|
|
2
|
+
import { Connector } from "@warlock.js/core";
|
|
3
|
+
|
|
4
|
+
//#region ../queue/src/queue-connector.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Boots after every built-in connector (`ConnectorPriority.AI` is `10`), so
|
|
7
|
+
* the logger is up and anything a job handler needs is already connected;
|
|
8
|
+
* shuts down before them for the same reason.
|
|
9
|
+
*/
|
|
10
|
+
declare const QUEUE_CONNECTOR_PRIORITY = 11;
|
|
11
|
+
type QueueConnectorOptions = {
|
|
12
|
+
/**
|
|
13
|
+
* Supply the configuration directly instead of reading the `queue` config
|
|
14
|
+
* key (`src/config/queue.ts`).
|
|
15
|
+
*/
|
|
16
|
+
config?: QueueConfig;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Construct the queue connector.
|
|
20
|
+
*
|
|
21
|
+
* Runs in the `late` lifecycle phase — after app code is imported — so every
|
|
22
|
+
* `defineJob` in the app has registered before workers start. At start it
|
|
23
|
+
* reads the `queue` config and starts in-process workers unless
|
|
24
|
+
* `workers.enabled` is `false`; at shutdown it closes workers (waiting for
|
|
25
|
+
* active jobs, bounded by `workers.shutdownTimeout`) and then the queues.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* // warlock.config.ts
|
|
29
|
+
* import { queueConnector } from "@warlock.js/queue";
|
|
30
|
+
*
|
|
31
|
+
* export default defineConfig({ connectors: [queueConnector()] });
|
|
32
|
+
*/
|
|
33
|
+
declare function queueConnector(options?: QueueConnectorOptions): Connector;
|
|
34
|
+
//#endregion
|
|
35
|
+
export { QUEUE_CONNECTOR_PRIORITY, QueueConnectorOptions, queueConnector };
|
|
36
|
+
//# sourceMappingURL=queue-connector.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queue-connector.d.mts","names":[],"sources":["../../../../../../queue/src/queue-connector.ts"],"mappings":";;;;;;;AA4BsB;AAkBtB;cA3Ba,wBAAA;AAAA,KAID,qBAAA;EAuBkE;;;;EAlB5E,MAAA,GAAS,WAAW;AAAA;;;;;;;;;;;;;;;;iBAkBN,cAAA,CAAe,OAAA,GAAS,qBAAA,GAA6B,SAAS"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { resetQueueConfig, setQueueConfig } from "./config.mjs";
|
|
2
|
+
import { closeQueue, startWorkers } from "./queue-manager.mjs";
|
|
3
|
+
import { log } from "@warlock.js/logger";
|
|
4
|
+
|
|
5
|
+
//#region ../queue/src/queue-connector.ts
|
|
6
|
+
/**
|
|
7
|
+
* Boots after every built-in connector (`ConnectorPriority.AI` is `10`), so
|
|
8
|
+
* the logger is up and anything a job handler needs is already connected;
|
|
9
|
+
* shuts down before them for the same reason.
|
|
10
|
+
*/
|
|
11
|
+
const QUEUE_CONNECTOR_PRIORITY = 11;
|
|
12
|
+
const WATCHED_FILES = ["src/config/queue.ts"];
|
|
13
|
+
/**
|
|
14
|
+
* Construct the queue connector.
|
|
15
|
+
*
|
|
16
|
+
* Runs in the `late` lifecycle phase — after app code is imported — so every
|
|
17
|
+
* `defineJob` in the app has registered before workers start. At start it
|
|
18
|
+
* reads the `queue` config and starts in-process workers unless
|
|
19
|
+
* `workers.enabled` is `false`; at shutdown it closes workers (waiting for
|
|
20
|
+
* active jobs, bounded by `workers.shutdownTimeout`) and then the queues.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* // warlock.config.ts
|
|
24
|
+
* import { queueConnector } from "@warlock.js/queue";
|
|
25
|
+
*
|
|
26
|
+
* export default defineConfig({ connectors: [queueConnector()] });
|
|
27
|
+
*/
|
|
28
|
+
function queueConnector(options = {}) {
|
|
29
|
+
let active = false;
|
|
30
|
+
const connector = {
|
|
31
|
+
name: "queue",
|
|
32
|
+
priority: 11,
|
|
33
|
+
lifecyclePhase: "late",
|
|
34
|
+
isActive: () => active,
|
|
35
|
+
boot: () => void 0,
|
|
36
|
+
async start() {
|
|
37
|
+
const queueConfig = options.config ?? await readQueueConfig();
|
|
38
|
+
if (!queueConfig) {
|
|
39
|
+
log.warn("queue", "configured", "queueConnector() is registered but no `queue` config was found (src/config/queue.ts); queue not started");
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
setQueueConfig(queueConfig);
|
|
43
|
+
const started = await startWorkers();
|
|
44
|
+
active = true;
|
|
45
|
+
log.info("queue", "configured", started.length > 0 ? `Queue workers running for: ${started.join(", ")}` : "Queue configured (no in-process workers)");
|
|
46
|
+
},
|
|
47
|
+
async restart() {
|
|
48
|
+
await connector.shutdown();
|
|
49
|
+
await connector.start();
|
|
50
|
+
},
|
|
51
|
+
async shutdown() {
|
|
52
|
+
if (!active) return;
|
|
53
|
+
await closeQueue();
|
|
54
|
+
resetQueueConfig();
|
|
55
|
+
active = false;
|
|
56
|
+
},
|
|
57
|
+
shouldRestart(changedFiles) {
|
|
58
|
+
return changedFiles.some((file) => {
|
|
59
|
+
const normalized = file.replace(/\\/g, "/");
|
|
60
|
+
return WATCHED_FILES.some((watched) => normalized === watched || normalized.endsWith(`/${watched}`));
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
return connector;
|
|
65
|
+
}
|
|
66
|
+
async function readQueueConfig() {
|
|
67
|
+
const { config } = await import("@warlock.js/core");
|
|
68
|
+
return config.get("queue");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
//#endregion
|
|
72
|
+
export { QUEUE_CONNECTOR_PRIORITY, queueConnector };
|
|
73
|
+
//# sourceMappingURL=queue-connector.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queue-connector.mjs","names":[],"sources":["../../../../../../queue/src/queue-connector.ts"],"sourcesContent":["/**\n * The queue's connector for `warlock.config.ts > connectors`.\n *\n * Deliberately a plain object with TYPE-ONLY imports from core: the config\n * file that constructs it must not drag core's runtime graph (or BullMQ) in\n * at config-load time. Core is imported lazily inside `start()`, where the\n * app has already loaded it.\n */\nimport type { Connector, ConnectorLifecyclePhase } from \"@warlock.js/core\";\nimport { log } from \"@warlock.js/logger\";\nimport { resetQueueConfig, setQueueConfig } from \"./config\";\nimport { closeQueue, startWorkers } from \"./queue-manager\";\nimport type { QueueConfig } from \"./types\";\n\n/**\n * Boots after every built-in connector (`ConnectorPriority.AI` is `10`), so\n * the logger is up and anything a job handler needs is already connected;\n * shuts down before them for the same reason.\n */\nexport const QUEUE_CONNECTOR_PRIORITY = 11;\n\nconst WATCHED_FILES = [\"src/config/queue.ts\"];\n\nexport type QueueConnectorOptions = {\n /**\n * Supply the configuration directly instead of reading the `queue` config\n * key (`src/config/queue.ts`).\n */\n config?: QueueConfig;\n};\n\n/**\n * Construct the queue connector.\n *\n * Runs in the `late` lifecycle phase — after app code is imported — so every\n * `defineJob` in the app has registered before workers start. At start it\n * reads the `queue` config and starts in-process workers unless\n * `workers.enabled` is `false`; at shutdown it closes workers (waiting for\n * active jobs, bounded by `workers.shutdownTimeout`) and then the queues.\n *\n * @example\n * // warlock.config.ts\n * import { queueConnector } from \"@warlock.js/queue\";\n *\n * export default defineConfig({ connectors: [queueConnector()] });\n */\nexport function queueConnector(options: QueueConnectorOptions = {}): Connector {\n let active = false;\n\n const connector: Connector = {\n name: \"queue\",\n priority: QUEUE_CONNECTOR_PRIORITY,\n // Core's `ConnectorLifecyclePhase.Late`; the value is spelled out so this\n // module stays free of a runtime import of core.\n lifecyclePhase: \"late\" as ConnectorLifecyclePhase,\n isActive: () => active,\n boot: () => undefined,\n async start() {\n const queueConfig = options.config ?? (await readQueueConfig());\n\n if (!queueConfig) {\n log.warn(\n \"queue\",\n \"configured\",\n \"queueConnector() is registered but no `queue` config was found (src/config/queue.ts); queue not started\",\n );\n return;\n }\n\n setQueueConfig(queueConfig);\n const started = await startWorkers();\n active = true;\n\n log.info(\n \"queue\",\n \"configured\",\n started.length > 0\n ? `Queue workers running for: ${started.join(\", \")}`\n : \"Queue configured (no in-process workers)\",\n );\n },\n async restart() {\n await connector.shutdown();\n await connector.start();\n },\n async shutdown() {\n if (!active) {\n return;\n }\n\n await closeQueue();\n resetQueueConfig();\n active = false;\n },\n shouldRestart(changedFiles) {\n return changedFiles.some((file) => {\n const normalized = file.replace(/\\\\/g, \"/\");\n\n return WATCHED_FILES.some((watched) => normalized === watched || normalized.endsWith(`/${watched}`));\n });\n },\n };\n\n return connector;\n}\n\nasync function readQueueConfig(): Promise<QueueConfig | undefined> {\n const { config } = await import(\"@warlock.js/core\");\n\n return config.get<QueueConfig | undefined>(\"queue\");\n}\n"],"mappings":";;;;;;;;;;AAmBA,MAAa,2BAA2B;AAExC,MAAM,gBAAgB,CAAC,qBAAqB;;;;;;;;;;;;;;;;AAyB5C,SAAgB,eAAe,UAAiC,CAAC,GAAc;CAC7E,IAAI,SAAS;CAEb,MAAM,YAAuB;EAC3B,MAAM;EACN;EAGA,gBAAgB;EAChB,gBAAgB;EAChB,YAAY;EACZ,MAAM,QAAQ;GACZ,MAAM,cAAc,QAAQ,UAAW,MAAM,gBAAgB;GAE7D,IAAI,CAAC,aAAa;IAChB,IAAI,KACF,SACA,cACA,yGACF;IACA;GACF;GAEA,eAAe,WAAW;GAC1B,MAAM,UAAU,MAAM,aAAa;GACnC,SAAS;GAET,IAAI,KACF,SACA,cACA,QAAQ,SAAS,IACb,8BAA8B,QAAQ,KAAK,IAAI,MAC/C,0CACN;EACF;EACA,MAAM,UAAU;GACd,MAAM,UAAU,SAAS;GACzB,MAAM,UAAU,MAAM;EACxB;EACA,MAAM,WAAW;GACf,IAAI,CAAC,QACH;GAGF,MAAM,WAAW;GACjB,iBAAiB;GACjB,SAAS;EACX;EACA,cAAc,cAAc;GAC1B,OAAO,aAAa,MAAM,SAAS;IACjC,MAAM,aAAa,KAAK,QAAQ,OAAO,GAAG;IAE1C,OAAO,cAAc,MAAM,YAAY,eAAe,WAAW,WAAW,SAAS,IAAI,SAAS,CAAC;GACrG,CAAC;EACH;CACF;CAEA,OAAO;AACT;AAEA,eAAe,kBAAoD;CACjE,MAAM,EAAE,WAAW,MAAM,OAAO;CAEhC,OAAO,OAAO,IAA6B,OAAO;AACpD"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Queue } from "bullmq";
|
|
2
|
+
|
|
3
|
+
//#region ../queue/src/queue-manager.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* The BullMQ queue for `name`, created on first use with the configured
|
|
6
|
+
* connection and prefix.
|
|
7
|
+
*/
|
|
8
|
+
declare function getQueue(name: string): Queue;
|
|
9
|
+
/**
|
|
10
|
+
* Start one worker per queue that has a registered job, and keep starting
|
|
11
|
+
* workers for queues whose first job is defined later.
|
|
12
|
+
*
|
|
13
|
+
* A no-op returning `[]` when `workers.enabled` is `false`. Calling it again
|
|
14
|
+
* while workers run starts only the missing ones.
|
|
15
|
+
*
|
|
16
|
+
* @returns the queue names that now have a worker in this process.
|
|
17
|
+
*/
|
|
18
|
+
declare function startWorkers(): Promise<string[]>;
|
|
19
|
+
/** The queue names with a running worker in this process. */
|
|
20
|
+
declare function runningWorkers(): string[];
|
|
21
|
+
type CloseQueueOptions = {
|
|
22
|
+
/**
|
|
23
|
+
* How long to wait for active jobs before force-closing workers, in
|
|
24
|
+
* milliseconds. Default: `workers.shutdownTimeout`, else `30000`.
|
|
25
|
+
*/
|
|
26
|
+
timeout?: number;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Graceful shutdown: stop workers taking new jobs and wait for active ones
|
|
30
|
+
* (bounded by `timeout`, then force-close), then close every queue
|
|
31
|
+
* connection. Safe to call when nothing was started, and more than once.
|
|
32
|
+
*/
|
|
33
|
+
declare function closeQueue(options?: CloseQueueOptions): Promise<void>;
|
|
34
|
+
//#endregion
|
|
35
|
+
export { CloseQueueOptions, closeQueue, getQueue, runningWorkers, startWorkers };
|
|
36
|
+
//# sourceMappingURL=queue-manager.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queue-manager.d.mts","names":[],"sources":["../../../../../../queue/src/queue-manager.ts"],"mappings":";;;;;AAgBA;;iBAAgB,QAAA,CAAS,IAAA,WAAe,KAAK;;AAAA;AA8B7C;;;;AAA6C;AAqB7C;;iBArBsB,YAAA,IAAgB,OAAO;;iBAqB7B,cAAA;AAAA,KA4BJ,iBAAA;EAAiB;;;AAKpB;EAAP,OAAO;AAAA;;;;;;iBAQa,UAAA,CAAW,OAAA,GAAS,iBAAA,GAAyB,OAAO"}
|