@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.
Files changed (63) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/LICENSE +21 -0
  3. package/README.md +121 -0
  4. package/cjs/define-job-DideGKQK.cjs +468 -0
  5. package/cjs/define-job-DideGKQK.cjs.map +1 -0
  6. package/cjs/index.cjs +180 -0
  7. package/cjs/index.cjs.map +1 -0
  8. package/cjs/notifications/index.cjs +68 -0
  9. package/cjs/notifications/index.cjs.map +1 -0
  10. package/esm/config.d.mts +21 -0
  11. package/esm/config.d.mts.map +1 -0
  12. package/esm/config.mjs +32 -0
  13. package/esm/config.mjs.map +1 -0
  14. package/esm/dashboard.d.mts +47 -0
  15. package/esm/dashboard.d.mts.map +1 -0
  16. package/esm/dashboard.mjs +61 -0
  17. package/esm/dashboard.mjs.map +1 -0
  18. package/esm/define-job.d.mts +24 -0
  19. package/esm/define-job.d.mts.map +1 -0
  20. package/esm/define-job.mjs +102 -0
  21. package/esm/define-job.mjs.map +1 -0
  22. package/esm/duration.d.mts +11 -0
  23. package/esm/duration.d.mts.map +1 -0
  24. package/esm/duration.mjs +28 -0
  25. package/esm/duration.mjs.map +1 -0
  26. package/esm/errors.d.mts +36 -0
  27. package/esm/errors.d.mts.map +1 -0
  28. package/esm/errors.mjs +55 -0
  29. package/esm/errors.mjs.map +1 -0
  30. package/esm/failed-jobs.d.mts +23 -0
  31. package/esm/failed-jobs.d.mts.map +1 -0
  32. package/esm/failed-jobs.mjs +39 -0
  33. package/esm/failed-jobs.mjs.map +1 -0
  34. package/esm/index.d.mts +10 -0
  35. package/esm/index.mjs +10 -0
  36. package/esm/job-registry.mjs +39 -0
  37. package/esm/job-registry.mjs.map +1 -0
  38. package/esm/notifications/index.d.mts +2 -0
  39. package/esm/notifications/index.mjs +3 -0
  40. package/esm/notifications/queue-notification-dispatcher.d.mts +34 -0
  41. package/esm/notifications/queue-notification-dispatcher.d.mts.map +1 -0
  42. package/esm/notifications/queue-notification-dispatcher.mjs +67 -0
  43. package/esm/notifications/queue-notification-dispatcher.mjs.map +1 -0
  44. package/esm/process-job.mjs +32 -0
  45. package/esm/process-job.mjs.map +1 -0
  46. package/esm/queue-connector.d.mts +36 -0
  47. package/esm/queue-connector.d.mts.map +1 -0
  48. package/esm/queue-connector.mjs +73 -0
  49. package/esm/queue-connector.mjs.map +1 -0
  50. package/esm/queue-manager.d.mts +36 -0
  51. package/esm/queue-manager.d.mts.map +1 -0
  52. package/esm/queue-manager.mjs +109 -0
  53. package/esm/queue-manager.mjs.map +1 -0
  54. package/esm/types.d.mts +149 -0
  55. package/esm/types.d.mts.map +1 -0
  56. package/llms-full.txt +229 -0
  57. package/llms.txt +13 -0
  58. package/package.json +70 -0
  59. package/skills/configure-queue/SKILL.md +55 -0
  60. package/skills/define-jobs/SKILL.md +51 -0
  61. package/skills/manage-failed-jobs/SKILL.md +38 -0
  62. package/skills/overview/SKILL.md +26 -0
  63. package/skills/queue-notifications/SKILL.md +33 -0
@@ -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"}
@@ -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"}
@@ -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,3 @@
1
+ import { NOTIFICATION_JOB_NAME, queueNotificationDispatcher } from "./queue-notification-dispatcher.mjs";
2
+
3
+ export { NOTIFICATION_JOB_NAME, 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"}