@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
package/cjs/index.cjs ADDED
@@ -0,0 +1,180 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+ const require_define_job = require('./define-job-DideGKQK.cjs');
3
+ let _warlock_js_logger = require("@warlock.js/logger");
4
+
5
+ //#region ../queue/src/dashboard.ts
6
+ /**
7
+ * Mount the bull-board failed/active/delayed job UI on Warlock's Fastify
8
+ * server. Requires the optional packages `@bull-board/api` and
9
+ * `@bull-board/fastify`; they are loaded only when this is called, and a
10
+ * missing one throws {@link QueueDashboardDependencyError}.
11
+ *
12
+ * Call it before the HTTP server starts listening, and put it behind your
13
+ * own authentication — the dashboard can retry and delete jobs.
14
+ *
15
+ * @example
16
+ * import { getHttpServer } from "@warlock.js/core";
17
+ * await queueDashboard(getHttpServer(), { basePath: "/admin/queues" });
18
+ */
19
+ async function queueDashboard(server, options = {}) {
20
+ const basePath = options.basePath ?? "/admin/queues";
21
+ const { createBullBoard, BullMQAdapter, FastifyAdapter } = await loadBullBoard();
22
+ const queueNames = options.queues ?? [...new Set([require_define_job.defaultQueueName(), ...require_define_job.registeredJobs().map((job) => require_define_job.queueOf(job))])];
23
+ const serverAdapter = new FastifyAdapter();
24
+ serverAdapter.setBasePath(basePath);
25
+ createBullBoard({
26
+ queues: queueNames.map((name) => new BullMQAdapter(require_define_job.getQueue(name))),
27
+ serverAdapter
28
+ });
29
+ await server.register(serverAdapter.registerPlugin(), { prefix: basePath });
30
+ }
31
+ /**
32
+ * Load the optional bull-board packages. Exported for tests of the missing
33
+ * dependency path; `importer` defaults to a real dynamic import.
34
+ */
35
+ async function loadBullBoard(importer = (specifier) => import(specifier)) {
36
+ const api = await importOptional(importer, "@bull-board/api");
37
+ const adapter = await importOptional(importer, "@bull-board/api/bullMQAdapter");
38
+ const fastify = await importOptional(importer, "@bull-board/fastify");
39
+ return {
40
+ createBullBoard: api.createBullBoard,
41
+ BullMQAdapter: adapter.BullMQAdapter,
42
+ FastifyAdapter: fastify.FastifyAdapter
43
+ };
44
+ }
45
+ async function importOptional(importer, specifier) {
46
+ try {
47
+ return await importer(specifier);
48
+ } catch (error) {
49
+ if (isModuleNotFound(error)) throw new require_define_job.QueueDashboardDependencyError(specifier.split("/").slice(0, 2).join("/"));
50
+ throw error;
51
+ }
52
+ }
53
+ function isModuleNotFound(error) {
54
+ const code = error?.code;
55
+ return code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND";
56
+ }
57
+
58
+ //#endregion
59
+ //#region ../queue/src/failed-jobs.ts
60
+ /**
61
+ * List failed jobs, newest first — jobs that used up every attempt, or
62
+ * failed unrecoverably. Each entry can be retried.
63
+ */
64
+ async function failedJobs(options = {}) {
65
+ return (await require_define_job.getQueue(options.queue ?? require_define_job.defaultQueueName()).getFailed(options.start ?? 0, options.end ?? 99)).map((job) => toFailedJob(job));
66
+ }
67
+ /**
68
+ * Retry one failed job by id: it goes back to waiting with its attempts reset.
69
+ * Throws {@link FailedJobNotFoundError} when no FAILED job has that id.
70
+ */
71
+ async function retryFailedJob(id, options = {}) {
72
+ const queueName = options.queue ?? require_define_job.defaultQueueName();
73
+ const job = await require_define_job.getQueue(queueName).getJob(id);
74
+ if (!job || !await job.isFailed()) throw new require_define_job.FailedJobNotFoundError(id, queueName);
75
+ await job.retry("failed");
76
+ }
77
+ function toFailedJob(job) {
78
+ return {
79
+ id: String(job.id),
80
+ name: job.name,
81
+ queue: job.queueName,
82
+ payload: job.data,
83
+ attemptsMade: job.attemptsMade,
84
+ failedReason: job.failedReason,
85
+ stacktrace: job.stacktrace ?? [],
86
+ failedAt: job.finishedOn ? new Date(job.finishedOn) : void 0,
87
+ retry: () => job.retry("failed")
88
+ };
89
+ }
90
+
91
+ //#endregion
92
+ //#region ../queue/src/queue-connector.ts
93
+ /**
94
+ * Boots after every built-in connector (`ConnectorPriority.AI` is `10`), so
95
+ * the logger is up and anything a job handler needs is already connected;
96
+ * shuts down before them for the same reason.
97
+ */
98
+ const QUEUE_CONNECTOR_PRIORITY = 11;
99
+ const WATCHED_FILES = ["src/config/queue.ts"];
100
+ /**
101
+ * Construct the queue connector.
102
+ *
103
+ * Runs in the `late` lifecycle phase — after app code is imported — so every
104
+ * `defineJob` in the app has registered before workers start. At start it
105
+ * reads the `queue` config and starts in-process workers unless
106
+ * `workers.enabled` is `false`; at shutdown it closes workers (waiting for
107
+ * active jobs, bounded by `workers.shutdownTimeout`) and then the queues.
108
+ *
109
+ * @example
110
+ * // warlock.config.ts
111
+ * import { queueConnector } from "@warlock.js/queue";
112
+ *
113
+ * export default defineConfig({ connectors: [queueConnector()] });
114
+ */
115
+ function queueConnector(options = {}) {
116
+ let active = false;
117
+ const connector = {
118
+ name: "queue",
119
+ priority: 11,
120
+ lifecyclePhase: "late",
121
+ isActive: () => active,
122
+ boot: () => void 0,
123
+ async start() {
124
+ const queueConfig = options.config ?? await readQueueConfig();
125
+ if (!queueConfig) {
126
+ _warlock_js_logger.log.warn("queue", "configured", "queueConnector() is registered but no `queue` config was found (src/config/queue.ts); queue not started");
127
+ return;
128
+ }
129
+ require_define_job.setQueueConfig(queueConfig);
130
+ const started = await require_define_job.startWorkers();
131
+ active = true;
132
+ _warlock_js_logger.log.info("queue", "configured", started.length > 0 ? `Queue workers running for: ${started.join(", ")}` : "Queue configured (no in-process workers)");
133
+ },
134
+ async restart() {
135
+ await connector.shutdown();
136
+ await connector.start();
137
+ },
138
+ async shutdown() {
139
+ if (!active) return;
140
+ await require_define_job.closeQueue();
141
+ require_define_job.resetQueueConfig();
142
+ active = false;
143
+ },
144
+ shouldRestart(changedFiles) {
145
+ return changedFiles.some((file) => {
146
+ const normalized = file.replace(/\\/g, "/");
147
+ return WATCHED_FILES.some((watched) => normalized === watched || normalized.endsWith(`/${watched}`));
148
+ });
149
+ }
150
+ };
151
+ return connector;
152
+ }
153
+ async function readQueueConfig() {
154
+ const { config } = await import("@warlock.js/core");
155
+ return config.get("queue");
156
+ }
157
+
158
+ //#endregion
159
+ exports.FailedJobNotFoundError = require_define_job.FailedJobNotFoundError;
160
+ exports.InvalidDurationError = require_define_job.InvalidDurationError;
161
+ exports.InvalidJobDefinitionError = require_define_job.InvalidJobDefinitionError;
162
+ exports.QUEUE_CONNECTOR_PRIORITY = QUEUE_CONNECTOR_PRIORITY;
163
+ exports.QueueDashboardDependencyError = require_define_job.QueueDashboardDependencyError;
164
+ exports.QueueNotConfiguredError = require_define_job.QueueNotConfiguredError;
165
+ exports.closeQueue = require_define_job.closeQueue;
166
+ exports.defaultQueueName = require_define_job.defaultQueueName;
167
+ exports.defineJob = require_define_job.defineJob;
168
+ exports.failedJobs = failedJobs;
169
+ exports.getQueue = require_define_job.getQueue;
170
+ exports.getQueueConfig = require_define_job.getQueueConfig;
171
+ exports.loadBullBoard = loadBullBoard;
172
+ exports.queueConnector = queueConnector;
173
+ exports.queueDashboard = queueDashboard;
174
+ exports.resetQueueConfig = require_define_job.resetQueueConfig;
175
+ exports.retryFailedJob = retryFailedJob;
176
+ exports.runningWorkers = require_define_job.runningWorkers;
177
+ exports.setQueueConfig = require_define_job.setQueueConfig;
178
+ exports.startWorkers = require_define_job.startWorkers;
179
+ exports.toMilliseconds = require_define_job.toMilliseconds;
180
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","names":["defaultQueueName","registeredJobs","queueOf","getQueue","QueueDashboardDependencyError","getQueue","defaultQueueName","FailedJobNotFoundError","startWorkers","closeQueue"],"sources":["../../../../../../queue/src/dashboard.ts","../../../../../../queue/src/failed-jobs.ts","../../../../../../queue/src/queue-connector.ts"],"sourcesContent":["import { defaultQueueName } from \"./config\";\nimport { QueueDashboardDependencyError } from \"./errors\";\nimport { queueOf, registeredJobs } from \"./job-registry\";\nimport { getQueue } from \"./queue-manager\";\n\n/**\n * The part of a Fastify instance the dashboard needs. Warlock's HTTP server\n * (`getHttpServer()` from `@warlock.js/core`) satisfies it.\n */\nexport type DashboardServer = {\n register(plugin: never, options: { prefix: string }): unknown;\n};\n\nexport type QueueDashboardOptions = {\n /** URL prefix the dashboard is mounted on. Default `\"/admin/queues\"`. */\n basePath?: string;\n /** Queues to show. Default: every queue with a defined job, plus the default queue. */\n queues?: string[];\n};\n\ntype BullBoardModules = {\n createBullBoard: (options: { queues: unknown[]; serverAdapter: unknown }) => unknown;\n BullMQAdapter: new (queue: unknown) => unknown;\n FastifyAdapter: new () => {\n setBasePath(path: string): unknown;\n registerPlugin(): unknown;\n };\n};\n\n/**\n * Mount the bull-board failed/active/delayed job UI on Warlock's Fastify\n * server. Requires the optional packages `@bull-board/api` and\n * `@bull-board/fastify`; they are loaded only when this is called, and a\n * missing one throws {@link QueueDashboardDependencyError}.\n *\n * Call it before the HTTP server starts listening, and put it behind your\n * own authentication — the dashboard can retry and delete jobs.\n *\n * @example\n * import { getHttpServer } from \"@warlock.js/core\";\n * await queueDashboard(getHttpServer(), { basePath: \"/admin/queues\" });\n */\nexport async function queueDashboard(\n server: DashboardServer,\n options: QueueDashboardOptions = {},\n): Promise<void> {\n const basePath = options.basePath ?? \"/admin/queues\";\n const { createBullBoard, BullMQAdapter, FastifyAdapter } = await loadBullBoard();\n const queueNames =\n options.queues ?? [...new Set([defaultQueueName(), ...registeredJobs().map((job) => queueOf(job))])];\n\n const serverAdapter = new FastifyAdapter();\n serverAdapter.setBasePath(basePath);\n\n createBullBoard({\n queues: queueNames.map((name) => new BullMQAdapter(getQueue(name))),\n serverAdapter,\n });\n\n await server.register(serverAdapter.registerPlugin() as never, { prefix: basePath });\n}\n\n/**\n * Load the optional bull-board packages. Exported for tests of the missing\n * dependency path; `importer` defaults to a real dynamic import.\n */\nexport async function loadBullBoard(\n importer: (specifier: string) => Promise<Record<string, unknown>> = (specifier) => import(specifier),\n): Promise<BullBoardModules> {\n const api = await importOptional(importer, \"@bull-board/api\");\n const adapter = await importOptional(importer, \"@bull-board/api/bullMQAdapter\");\n const fastify = await importOptional(importer, \"@bull-board/fastify\");\n\n return {\n createBullBoard: api.createBullBoard as BullBoardModules[\"createBullBoard\"],\n BullMQAdapter: adapter.BullMQAdapter as BullBoardModules[\"BullMQAdapter\"],\n FastifyAdapter: fastify.FastifyAdapter as BullBoardModules[\"FastifyAdapter\"],\n };\n}\n\nasync function importOptional(\n importer: (specifier: string) => Promise<Record<string, unknown>>,\n specifier: string,\n): Promise<Record<string, unknown>> {\n try {\n return await importer(specifier);\n } catch (error) {\n if (isModuleNotFound(error)) {\n throw new QueueDashboardDependencyError(specifier.split(\"/\").slice(0, 2).join(\"/\"));\n }\n\n throw error;\n }\n}\n\nfunction isModuleNotFound(error: unknown): boolean {\n const code = (error as { code?: unknown } | undefined)?.code;\n\n return code === \"ERR_MODULE_NOT_FOUND\" || code === \"MODULE_NOT_FOUND\";\n}\n","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","/**\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":"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"}
@@ -0,0 +1,68 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+ const require_define_job = require('../define-job-DideGKQK.cjs');
3
+ let bullmq = require("bullmq");
4
+ let _warlock_js_notifications = require("@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 require_define_job.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 = (0, _warlock_js_notifications.getNotificationConfig)().channels[job.channel];
52
+ if (!channel) throw new bullmq.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 : require_define_job.toMilliseconds(delay);
63
+ }
64
+
65
+ //#endregion
66
+ exports.NOTIFICATION_JOB_NAME = NOTIFICATION_JOB_NAME;
67
+ exports.queueNotificationDispatcher = queueNotificationDispatcher;
68
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","names":["defineJob","UnrecoverableError","toMilliseconds"],"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,OAAOA,6BAAwC;EAC7C,MAAM;EACN,OAAO,QAAQ;EACf,UAAU,QAAQ;EAClB,SAAS,QAAQ;EACjB,MAAM,OAAO,KAAK;GAKhB,MAAM,+DAJiC,CAAC,CAAC,SAIhB,IAAI;GAE7B,IAAI,CAAC,SACH,MAAM,IAAIC,0BACR,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,MAAQC,kCAAe,KAAK;AACzE"}
@@ -0,0 +1,21 @@
1
+ import { QueueConfig } from "./types.mjs";
2
+
3
+ //#region ../queue/src/config.d.ts
4
+ /**
5
+ * Set the active queue configuration. In a Warlock app the queue connector
6
+ * calls this at boot with `src/config/queue.ts`; scripts and tests may call
7
+ * it directly. Replaces (does not merge) any previous configuration.
8
+ */
9
+ declare function setQueueConfig(config: QueueConfig): void;
10
+ /**
11
+ * The active queue configuration. Throws {@link QueueNotConfiguredError}
12
+ * when none was set.
13
+ */
14
+ declare function getQueueConfig(): QueueConfig;
15
+ /** Forget the active configuration. */
16
+ declare function resetQueueConfig(): void;
17
+ /** The queue a job runs on when it names none. */
18
+ declare function defaultQueueName(): string;
19
+ //#endregion
20
+ export { defaultQueueName, getQueueConfig, resetQueueConfig, setQueueConfig };
21
+ //# sourceMappingURL=config.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.mts","names":[],"sources":["../../../../../../queue/src/config.ts"],"mappings":";;;;;AAUA;;;iBAAgB,cAAA,CAAe,MAAmB,EAAX,WAAW;AAAA;AAQlD;;;AARkD,iBAQlC,cAAA,IAAkB,WAAW;AAAA;AAAA,iBAS7B,gBAAA;;iBAKA,gBAAA"}
package/esm/config.mjs ADDED
@@ -0,0 +1,32 @@
1
+ import { QueueNotConfiguredError } from "./errors.mjs";
2
+
3
+ //#region ../queue/src/config.ts
4
+ let activeConfig;
5
+ /**
6
+ * Set the active queue configuration. In a Warlock app the queue connector
7
+ * calls this at boot with `src/config/queue.ts`; scripts and tests may call
8
+ * it directly. Replaces (does not merge) any previous configuration.
9
+ */
10
+ function setQueueConfig(config) {
11
+ activeConfig = config;
12
+ }
13
+ /**
14
+ * The active queue configuration. Throws {@link QueueNotConfiguredError}
15
+ * when none was set.
16
+ */
17
+ function getQueueConfig() {
18
+ if (!activeConfig) throw new QueueNotConfiguredError();
19
+ return activeConfig;
20
+ }
21
+ /** Forget the active configuration. */
22
+ function resetQueueConfig() {
23
+ activeConfig = void 0;
24
+ }
25
+ /** The queue a job runs on when it names none. */
26
+ function defaultQueueName() {
27
+ return activeConfig?.defaultQueue ?? "default";
28
+ }
29
+
30
+ //#endregion
31
+ export { defaultQueueName, getQueueConfig, resetQueueConfig, setQueueConfig };
32
+ //# sourceMappingURL=config.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.mjs","names":[],"sources":["../../../../../../queue/src/config.ts"],"sourcesContent":["import { QueueNotConfiguredError } from \"./errors\";\nimport type { QueueConfig } from \"./types\";\n\nlet activeConfig: QueueConfig | undefined;\n\n/**\n * Set the active queue configuration. In a Warlock app the queue connector\n * calls this at boot with `src/config/queue.ts`; scripts and tests may call\n * it directly. Replaces (does not merge) any previous configuration.\n */\nexport function setQueueConfig(config: QueueConfig): void {\n activeConfig = config;\n}\n\n/**\n * The active queue configuration. Throws {@link QueueNotConfiguredError}\n * when none was set.\n */\nexport function getQueueConfig(): QueueConfig {\n if (!activeConfig) {\n throw new QueueNotConfiguredError();\n }\n\n return activeConfig;\n}\n\n/** Forget the active configuration. */\nexport function resetQueueConfig(): void {\n activeConfig = undefined;\n}\n\n/** The queue a job runs on when it names none. */\nexport function defaultQueueName(): string {\n return activeConfig?.defaultQueue ?? \"default\";\n}\n"],"mappings":";;;AAGA,IAAI;;;;;;AAOJ,SAAgB,eAAe,QAA2B;CACxD,eAAe;AACjB;;;;;AAMA,SAAgB,iBAA8B;CAC5C,IAAI,CAAC,cACH,MAAM,IAAI,wBAAwB;CAGpC,OAAO;AACT;;AAGA,SAAgB,mBAAyB;CACvC,eAAe;AACjB;;AAGA,SAAgB,mBAA2B;CACzC,OAAO,cAAc,gBAAgB;AACvC"}
@@ -0,0 +1,47 @@
1
+ //#region ../queue/src/dashboard.d.ts
2
+ /**
3
+ * The part of a Fastify instance the dashboard needs. Warlock's HTTP server
4
+ * (`getHttpServer()` from `@warlock.js/core`) satisfies it.
5
+ */
6
+ type DashboardServer = {
7
+ register(plugin: never, options: {
8
+ prefix: string;
9
+ }): unknown;
10
+ };
11
+ type QueueDashboardOptions = {
12
+ /** URL prefix the dashboard is mounted on. Default `"/admin/queues"`. */basePath?: string; /** Queues to show. Default: every queue with a defined job, plus the default queue. */
13
+ queues?: string[];
14
+ };
15
+ type BullBoardModules = {
16
+ createBullBoard: (options: {
17
+ queues: unknown[];
18
+ serverAdapter: unknown;
19
+ }) => unknown;
20
+ BullMQAdapter: new (queue: unknown) => unknown;
21
+ FastifyAdapter: new () => {
22
+ setBasePath(path: string): unknown;
23
+ registerPlugin(): unknown;
24
+ };
25
+ };
26
+ /**
27
+ * Mount the bull-board failed/active/delayed job UI on Warlock's Fastify
28
+ * server. Requires the optional packages `@bull-board/api` and
29
+ * `@bull-board/fastify`; they are loaded only when this is called, and a
30
+ * missing one throws {@link QueueDashboardDependencyError}.
31
+ *
32
+ * Call it before the HTTP server starts listening, and put it behind your
33
+ * own authentication — the dashboard can retry and delete jobs.
34
+ *
35
+ * @example
36
+ * import { getHttpServer } from "@warlock.js/core";
37
+ * await queueDashboard(getHttpServer(), { basePath: "/admin/queues" });
38
+ */
39
+ declare function queueDashboard(server: DashboardServer, options?: QueueDashboardOptions): Promise<void>;
40
+ /**
41
+ * Load the optional bull-board packages. Exported for tests of the missing
42
+ * dependency path; `importer` defaults to a real dynamic import.
43
+ */
44
+ declare function loadBullBoard(importer?: (specifier: string) => Promise<Record<string, unknown>>): Promise<BullBoardModules>;
45
+ //#endregion
46
+ export { DashboardServer, QueueDashboardOptions, loadBullBoard, queueDashboard };
47
+ //# sourceMappingURL=dashboard.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dashboard.d.mts","names":[],"sources":["../../../../../../queue/src/dashboard.ts"],"mappings":";;AASA;;;KAAY,eAAA;EACV,QAAA,CAAS,MAAA,SAAe,OAAA;IAAW,MAAA;EAAA;AAAA;AAAA,KAGzB,qBAAA;EAHyC,yEAKnD,QAAA,WAF+B;EAI/B,MAAM;AAAA;AAAA,KAGH,gBAAA;EACH,eAAA,GAAkB,OAAA;IAAW,MAAA;IAAmB,aAAA;EAAA;EAChD,aAAA,OAAoB,KAAA;EACpB,cAAA;IACE,WAAA,CAAY,IAAA;IACZ,cAAA;EAAA;AAAA;;;;;;AAAc;AAiBlB;;;;;;;iBAAsB,cAAA,CACpB,MAAA,EAAQ,eAAA,EACR,OAAA,GAAS,qBAAA,GACR,OAAA;;;;;iBAqBmB,aAAA,CACpB,QAAA,IAAW,SAAA,aAAsB,OAAA,CAAQ,MAAA,qBACxC,OAAA,CAAQ,gBAAA"}
@@ -0,0 +1,61 @@
1
+ import { QueueDashboardDependencyError } from "./errors.mjs";
2
+ import { defaultQueueName } from "./config.mjs";
3
+ import { queueOf, registeredJobs } from "./job-registry.mjs";
4
+ import { getQueue } from "./queue-manager.mjs";
5
+
6
+ //#region ../queue/src/dashboard.ts
7
+ /**
8
+ * Mount the bull-board failed/active/delayed job UI on Warlock's Fastify
9
+ * server. Requires the optional packages `@bull-board/api` and
10
+ * `@bull-board/fastify`; they are loaded only when this is called, and a
11
+ * missing one throws {@link QueueDashboardDependencyError}.
12
+ *
13
+ * Call it before the HTTP server starts listening, and put it behind your
14
+ * own authentication — the dashboard can retry and delete jobs.
15
+ *
16
+ * @example
17
+ * import { getHttpServer } from "@warlock.js/core";
18
+ * await queueDashboard(getHttpServer(), { basePath: "/admin/queues" });
19
+ */
20
+ async function queueDashboard(server, options = {}) {
21
+ const basePath = options.basePath ?? "/admin/queues";
22
+ const { createBullBoard, BullMQAdapter, FastifyAdapter } = await loadBullBoard();
23
+ const queueNames = options.queues ?? [...new Set([defaultQueueName(), ...registeredJobs().map((job) => queueOf(job))])];
24
+ const serverAdapter = new FastifyAdapter();
25
+ serverAdapter.setBasePath(basePath);
26
+ createBullBoard({
27
+ queues: queueNames.map((name) => new BullMQAdapter(getQueue(name))),
28
+ serverAdapter
29
+ });
30
+ await server.register(serverAdapter.registerPlugin(), { prefix: basePath });
31
+ }
32
+ /**
33
+ * Load the optional bull-board packages. Exported for tests of the missing
34
+ * dependency path; `importer` defaults to a real dynamic import.
35
+ */
36
+ async function loadBullBoard(importer = (specifier) => import(specifier)) {
37
+ const api = await importOptional(importer, "@bull-board/api");
38
+ const adapter = await importOptional(importer, "@bull-board/api/bullMQAdapter");
39
+ const fastify = await importOptional(importer, "@bull-board/fastify");
40
+ return {
41
+ createBullBoard: api.createBullBoard,
42
+ BullMQAdapter: adapter.BullMQAdapter,
43
+ FastifyAdapter: fastify.FastifyAdapter
44
+ };
45
+ }
46
+ async function importOptional(importer, specifier) {
47
+ try {
48
+ return await importer(specifier);
49
+ } catch (error) {
50
+ if (isModuleNotFound(error)) throw new QueueDashboardDependencyError(specifier.split("/").slice(0, 2).join("/"));
51
+ throw error;
52
+ }
53
+ }
54
+ function isModuleNotFound(error) {
55
+ const code = error?.code;
56
+ return code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND";
57
+ }
58
+
59
+ //#endregion
60
+ export { loadBullBoard, queueDashboard };
61
+ //# sourceMappingURL=dashboard.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dashboard.mjs","names":[],"sources":["../../../../../../queue/src/dashboard.ts"],"sourcesContent":["import { defaultQueueName } from \"./config\";\nimport { QueueDashboardDependencyError } from \"./errors\";\nimport { queueOf, registeredJobs } from \"./job-registry\";\nimport { getQueue } from \"./queue-manager\";\n\n/**\n * The part of a Fastify instance the dashboard needs. Warlock's HTTP server\n * (`getHttpServer()` from `@warlock.js/core`) satisfies it.\n */\nexport type DashboardServer = {\n register(plugin: never, options: { prefix: string }): unknown;\n};\n\nexport type QueueDashboardOptions = {\n /** URL prefix the dashboard is mounted on. Default `\"/admin/queues\"`. */\n basePath?: string;\n /** Queues to show. Default: every queue with a defined job, plus the default queue. */\n queues?: string[];\n};\n\ntype BullBoardModules = {\n createBullBoard: (options: { queues: unknown[]; serverAdapter: unknown }) => unknown;\n BullMQAdapter: new (queue: unknown) => unknown;\n FastifyAdapter: new () => {\n setBasePath(path: string): unknown;\n registerPlugin(): unknown;\n };\n};\n\n/**\n * Mount the bull-board failed/active/delayed job UI on Warlock's Fastify\n * server. Requires the optional packages `@bull-board/api` and\n * `@bull-board/fastify`; they are loaded only when this is called, and a\n * missing one throws {@link QueueDashboardDependencyError}.\n *\n * Call it before the HTTP server starts listening, and put it behind your\n * own authentication — the dashboard can retry and delete jobs.\n *\n * @example\n * import { getHttpServer } from \"@warlock.js/core\";\n * await queueDashboard(getHttpServer(), { basePath: \"/admin/queues\" });\n */\nexport async function queueDashboard(\n server: DashboardServer,\n options: QueueDashboardOptions = {},\n): Promise<void> {\n const basePath = options.basePath ?? \"/admin/queues\";\n const { createBullBoard, BullMQAdapter, FastifyAdapter } = await loadBullBoard();\n const queueNames =\n options.queues ?? [...new Set([defaultQueueName(), ...registeredJobs().map((job) => queueOf(job))])];\n\n const serverAdapter = new FastifyAdapter();\n serverAdapter.setBasePath(basePath);\n\n createBullBoard({\n queues: queueNames.map((name) => new BullMQAdapter(getQueue(name))),\n serverAdapter,\n });\n\n await server.register(serverAdapter.registerPlugin() as never, { prefix: basePath });\n}\n\n/**\n * Load the optional bull-board packages. Exported for tests of the missing\n * dependency path; `importer` defaults to a real dynamic import.\n */\nexport async function loadBullBoard(\n importer: (specifier: string) => Promise<Record<string, unknown>> = (specifier) => import(specifier),\n): Promise<BullBoardModules> {\n const api = await importOptional(importer, \"@bull-board/api\");\n const adapter = await importOptional(importer, \"@bull-board/api/bullMQAdapter\");\n const fastify = await importOptional(importer, \"@bull-board/fastify\");\n\n return {\n createBullBoard: api.createBullBoard as BullBoardModules[\"createBullBoard\"],\n BullMQAdapter: adapter.BullMQAdapter as BullBoardModules[\"BullMQAdapter\"],\n FastifyAdapter: fastify.FastifyAdapter as BullBoardModules[\"FastifyAdapter\"],\n };\n}\n\nasync function importOptional(\n importer: (specifier: string) => Promise<Record<string, unknown>>,\n specifier: string,\n): Promise<Record<string, unknown>> {\n try {\n return await importer(specifier);\n } catch (error) {\n if (isModuleNotFound(error)) {\n throw new QueueDashboardDependencyError(specifier.split(\"/\").slice(0, 2).join(\"/\"));\n }\n\n throw error;\n }\n}\n\nfunction isModuleNotFound(error: unknown): boolean {\n const code = (error as { code?: unknown } | undefined)?.code;\n\n return code === \"ERR_MODULE_NOT_FOUND\" || code === \"MODULE_NOT_FOUND\";\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AA0CA,eAAsB,eACpB,QACA,UAAiC,CAAC,GACnB;CACf,MAAM,WAAW,QAAQ,YAAY;CACrC,MAAM,EAAE,iBAAiB,eAAe,mBAAmB,MAAM,cAAc;CAC/E,MAAM,aACJ,QAAQ,UAAU,CAAC,GAAG,IAAI,IAAI,CAAC,iBAAiB,GAAG,GAAG,eAAe,CAAC,CAAC,KAAK,QAAQ,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC;CAErG,MAAM,gBAAgB,IAAI,eAAe;CACzC,cAAc,YAAY,QAAQ;CAElC,gBAAgB;EACd,QAAQ,WAAW,KAAK,SAAS,IAAI,cAAc,SAAS,IAAI,CAAC,CAAC;EAClE;CACF,CAAC;CAED,MAAM,OAAO,SAAS,cAAc,eAAe,GAAY,EAAE,QAAQ,SAAS,CAAC;AACrF;;;;;AAMA,eAAsB,cACpB,YAAqE,cAAc,OAAO,YAC/D;CAC3B,MAAM,MAAM,MAAM,eAAe,UAAU,iBAAiB;CAC5D,MAAM,UAAU,MAAM,eAAe,UAAU,+BAA+B;CAC9E,MAAM,UAAU,MAAM,eAAe,UAAU,qBAAqB;CAEpE,OAAO;EACL,iBAAiB,IAAI;EACrB,eAAe,QAAQ;EACvB,gBAAgB,QAAQ;CAC1B;AACF;AAEA,eAAe,eACb,UACA,WACkC;CAClC,IAAI;EACF,OAAO,MAAM,SAAS,SAAS;CACjC,SAAS,OAAO;EACd,IAAI,iBAAiB,KAAK,GACxB,MAAM,IAAI,8BAA8B,UAAU,MAAM,GAAG,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC;EAGpF,MAAM;CACR;AACF;AAEA,SAAS,iBAAiB,OAAyB;CACjD,MAAM,OAAQ,OAA0C;CAExD,OAAO,SAAS,0BAA0B,SAAS;AACrD"}
@@ -0,0 +1,24 @@
1
+ import { JobDefinition, QueueJob } from "./types.mjs";
2
+ //#region ../queue/src/define-job.d.ts
3
+ /**
4
+ * Define a background job.
5
+ *
6
+ * The definition is registered by name so any worker in the process can run
7
+ * it; the returned object dispatches it with a typed payload.
8
+ *
9
+ * @example
10
+ * export const sendInvoice = defineJob({
11
+ * name: "invoices.send",
12
+ * attempts: 5,
13
+ * backoff: { type: "exponential", delay: 2000 },
14
+ * async handle(payload: { invoiceId: string }, ctx) {
15
+ * await ctx.progress(50);
16
+ * },
17
+ * });
18
+ *
19
+ * await sendInvoice.dispatch({ invoiceId: "42" }, { delay: "10m", priority: 1 });
20
+ */
21
+ declare function defineJob<TPayload, TResult = unknown>(definition: JobDefinition<TPayload, TResult>): QueueJob<TPayload, TResult>;
22
+ //#endregion
23
+ export { defineJob };
24
+ //# sourceMappingURL=define-job.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"define-job.d.mts","names":[],"sources":["../../../../../../queue/src/define-job.ts"],"mappings":";;;;AAkCA;;;;;;;;;;;;;;;;iBAAgB,SAAA,8BACd,UAAA,EAAY,aAAA,CAAc,QAAA,EAAU,OAAA,IACnC,QAAA,CAAS,QAAA,EAAU,OAAA"}
@@ -0,0 +1,102 @@
1
+ import { InvalidJobDefinitionError } from "./errors.mjs";
2
+ import { getQueueConfig } from "./config.mjs";
3
+ import { queueOf, registerJob } from "./job-registry.mjs";
4
+ import { getQueue } from "./queue-manager.mjs";
5
+ import { toMilliseconds } from "./duration.mjs";
6
+
7
+ //#region ../queue/src/define-job.ts
8
+ /**
9
+ * Define a background job.
10
+ *
11
+ * The definition is registered by name so any worker in the process can run
12
+ * it; the returned object dispatches it with a typed payload.
13
+ *
14
+ * @example
15
+ * export const sendInvoice = defineJob({
16
+ * name: "invoices.send",
17
+ * attempts: 5,
18
+ * backoff: { type: "exponential", delay: 2000 },
19
+ * async handle(payload: { invoiceId: string }, ctx) {
20
+ * await ctx.progress(50);
21
+ * },
22
+ * });
23
+ *
24
+ * await sendInvoice.dispatch({ invoiceId: "42" }, { delay: "10m", priority: 1 });
25
+ */
26
+ function defineJob(definition) {
27
+ assertValidDefinition(definition);
28
+ registerJob(definition);
29
+ return {
30
+ name: definition.name,
31
+ get queue() {
32
+ return queueOf(definition);
33
+ },
34
+ async dispatch(payload, options = {}) {
35
+ const queueName = queueOf(definition);
36
+ const job = await getQueue(queueName).add(definition.name, payload, toBullJobOptions(definition, options));
37
+ return {
38
+ id: String(job.id),
39
+ name: definition.name,
40
+ queue: queueName
41
+ };
42
+ },
43
+ async find(id) {
44
+ const job = await getQueue(queueOf(definition)).getJob(id);
45
+ if (!job || job.name !== definition.name) return;
46
+ return toSnapshot(job);
47
+ }
48
+ };
49
+ }
50
+ function assertValidDefinition(definition) {
51
+ if (typeof definition.name !== "string" || definition.name.trim() === "") throw new InvalidJobDefinitionError("defineJob() requires a non-empty `name`.");
52
+ if (typeof definition.handle !== "function") throw new InvalidJobDefinitionError(`defineJob("${definition.name}") requires a \`handle(payload, ctx)\` function.`);
53
+ if (definition.attempts !== void 0 && !(Number.isInteger(definition.attempts) && definition.attempts >= 1)) throw new InvalidJobDefinitionError(`defineJob("${definition.name}"): \`attempts\` must be an integer >= 1, got ${definition.attempts}.`);
54
+ }
55
+ /**
56
+ * Merge app defaults < job definition < dispatch options into BullMQ's shape.
57
+ */
58
+ function toBullJobOptions(definition, options) {
59
+ const defaults = getQueueConfig().defaultJobOptions ?? {};
60
+ const attempts = options.attempts ?? definition.attempts ?? defaults.attempts;
61
+ const backoff = options.backoff ?? definition.backoff ?? defaults.backoff;
62
+ const removeOnComplete = definition.removeOnComplete ?? defaults.removeOnComplete;
63
+ const removeOnFail = definition.removeOnFail ?? defaults.removeOnFail;
64
+ const bullOptions = {};
65
+ if (attempts !== void 0) bullOptions.attempts = attempts;
66
+ if (backoff !== void 0) bullOptions.backoff = toBullBackoff(backoff);
67
+ if (removeOnComplete !== void 0) bullOptions.removeOnComplete = removeOnComplete;
68
+ if (removeOnFail !== void 0) bullOptions.removeOnFail = removeOnFail;
69
+ if (options.delay !== void 0) bullOptions.delay = toMilliseconds(options.delay);
70
+ if (options.priority !== void 0) bullOptions.priority = options.priority;
71
+ if (options.jobId !== void 0) bullOptions.jobId = options.jobId;
72
+ return bullOptions;
73
+ }
74
+ function toBullBackoff(backoff) {
75
+ return typeof backoff === "number" ? {
76
+ type: "fixed",
77
+ delay: backoff
78
+ } : backoff;
79
+ }
80
+ /**
81
+ * A plain view of a BullMQ job.
82
+ */
83
+ async function toSnapshot(job) {
84
+ const state = await job.getState();
85
+ return {
86
+ id: String(job.id),
87
+ name: job.name,
88
+ queue: job.queueName,
89
+ state,
90
+ payload: job.data,
91
+ progress: job.progress,
92
+ attemptsMade: job.attemptsMade,
93
+ result: job.returnvalue,
94
+ failedReason: job.failedReason || void 0,
95
+ createdAt: new Date(job.timestamp),
96
+ finishedAt: job.finishedOn ? new Date(job.finishedOn) : void 0
97
+ };
98
+ }
99
+
100
+ //#endregion
101
+ export { defineJob };
102
+ //# sourceMappingURL=define-job.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"define-job.mjs","names":[],"sources":["../../../../../../queue/src/define-job.ts"],"sourcesContent":["import type { Job, JobsOptions } from \"bullmq\";\nimport { getQueueConfig } from \"./config\";\nimport { toMilliseconds } from \"./duration\";\nimport { InvalidJobDefinitionError } from \"./errors\";\nimport { queueOf, registerJob, type RegisteredJob } from \"./job-registry\";\nimport { getQueue } from \"./queue-manager\";\nimport type {\n DispatchOptions,\n JobBackoff,\n JobDefinition,\n JobOptions,\n JobSnapshot,\n JobState,\n QueueJob,\n} from \"./types\";\n\n/**\n * Define a background job.\n *\n * The definition is registered by name so any worker in the process can run\n * it; the returned object dispatches it with a typed payload.\n *\n * @example\n * export const sendInvoice = defineJob({\n * name: \"invoices.send\",\n * attempts: 5,\n * backoff: { type: \"exponential\", delay: 2000 },\n * async handle(payload: { invoiceId: string }, ctx) {\n * await ctx.progress(50);\n * },\n * });\n *\n * await sendInvoice.dispatch({ invoiceId: \"42\" }, { delay: \"10m\", priority: 1 });\n */\nexport function defineJob<TPayload, TResult = unknown>(\n definition: JobDefinition<TPayload, TResult>,\n): QueueJob<TPayload, TResult> {\n assertValidDefinition(definition);\n registerJob(definition as RegisteredJob);\n\n return {\n name: definition.name,\n get queue() {\n return queueOf(definition);\n },\n async dispatch(payload, options = {}) {\n const queueName = queueOf(definition);\n const job = await getQueue(queueName).add(\n definition.name,\n payload,\n toBullJobOptions(definition, options),\n );\n\n return { id: String(job.id), name: definition.name, queue: queueName };\n },\n async find(id) {\n const job = await getQueue(queueOf(definition)).getJob(id);\n\n if (!job || job.name !== definition.name) {\n return undefined;\n }\n\n return toSnapshot<TPayload, TResult>(job);\n },\n };\n}\n\nfunction assertValidDefinition(definition: JobDefinition<unknown, unknown>): void {\n if (typeof definition.name !== \"string\" || definition.name.trim() === \"\") {\n throw new InvalidJobDefinitionError(\"defineJob() requires a non-empty `name`.\");\n }\n\n if (typeof definition.handle !== \"function\") {\n throw new InvalidJobDefinitionError(\n `defineJob(\"${definition.name}\") requires a \\`handle(payload, ctx)\\` function.`,\n );\n }\n\n if (definition.attempts !== undefined && !(Number.isInteger(definition.attempts) && definition.attempts >= 1)) {\n throw new InvalidJobDefinitionError(\n `defineJob(\"${definition.name}\"): \\`attempts\\` must be an integer >= 1, got ${definition.attempts}.`,\n );\n }\n}\n\n/**\n * Merge app defaults < job definition < dispatch options into BullMQ's shape.\n */\nfunction toBullJobOptions(definition: JobOptions, options: DispatchOptions): JobsOptions {\n const defaults = getQueueConfig().defaultJobOptions ?? {};\n const attempts = options.attempts ?? definition.attempts ?? defaults.attempts;\n const backoff = options.backoff ?? definition.backoff ?? defaults.backoff;\n const removeOnComplete = definition.removeOnComplete ?? defaults.removeOnComplete;\n const removeOnFail = definition.removeOnFail ?? defaults.removeOnFail;\n\n const bullOptions: JobsOptions = {};\n\n if (attempts !== undefined) bullOptions.attempts = attempts;\n if (backoff !== undefined) bullOptions.backoff = toBullBackoff(backoff);\n if (removeOnComplete !== undefined) bullOptions.removeOnComplete = removeOnComplete;\n if (removeOnFail !== undefined) bullOptions.removeOnFail = removeOnFail;\n if (options.delay !== undefined) bullOptions.delay = toMilliseconds(options.delay);\n if (options.priority !== undefined) bullOptions.priority = options.priority;\n if (options.jobId !== undefined) bullOptions.jobId = options.jobId;\n\n return bullOptions;\n}\n\nfunction toBullBackoff(backoff: JobBackoff): JobsOptions[\"backoff\"] {\n return typeof backoff === \"number\" ? { type: \"fixed\", delay: backoff } : backoff;\n}\n\n/**\n * A plain view of a BullMQ job.\n */\nexport async function toSnapshot<TPayload, TResult>(\n job: Job,\n): Promise<JobSnapshot<TPayload, TResult>> {\n const state = (await job.getState()) as JobState;\n\n return {\n id: String(job.id),\n name: job.name,\n queue: job.queueName,\n state,\n payload: job.data as TPayload,\n progress: job.progress as JobSnapshot[\"progress\"],\n attemptsMade: job.attemptsMade,\n result: job.returnvalue as TResult | undefined,\n failedReason: job.failedReason || undefined,\n createdAt: new Date(job.timestamp),\n finishedAt: job.finishedOn ? new Date(job.finishedOn) : undefined,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAkCA,SAAgB,UACd,YAC6B;CAC7B,sBAAsB,UAAU;CAChC,YAAY,UAA2B;CAEvC,OAAO;EACL,MAAM,WAAW;EACjB,IAAI,QAAQ;GACV,OAAO,QAAQ,UAAU;EAC3B;EACA,MAAM,SAAS,SAAS,UAAU,CAAC,GAAG;GACpC,MAAM,YAAY,QAAQ,UAAU;GACpC,MAAM,MAAM,MAAM,SAAS,SAAS,CAAC,CAAC,IACpC,WAAW,MACX,SACA,iBAAiB,YAAY,OAAO,CACtC;GAEA,OAAO;IAAE,IAAI,OAAO,IAAI,EAAE;IAAG,MAAM,WAAW;IAAM,OAAO;GAAU;EACvE;EACA,MAAM,KAAK,IAAI;GACb,MAAM,MAAM,MAAM,SAAS,QAAQ,UAAU,CAAC,CAAC,CAAC,OAAO,EAAE;GAEzD,IAAI,CAAC,OAAO,IAAI,SAAS,WAAW,MAClC;GAGF,OAAO,WAA8B,GAAG;EAC1C;CACF;AACF;AAEA,SAAS,sBAAsB,YAAmD;CAChF,IAAI,OAAO,WAAW,SAAS,YAAY,WAAW,KAAK,KAAK,MAAM,IACpE,MAAM,IAAI,0BAA0B,0CAA0C;CAGhF,IAAI,OAAO,WAAW,WAAW,YAC/B,MAAM,IAAI,0BACR,cAAc,WAAW,KAAK,iDAChC;CAGF,IAAI,WAAW,aAAa,UAAa,EAAE,OAAO,UAAU,WAAW,QAAQ,KAAK,WAAW,YAAY,IACzG,MAAM,IAAI,0BACR,cAAc,WAAW,KAAK,gDAAgD,WAAW,SAAS,EACpG;AAEJ;;;;AAKA,SAAS,iBAAiB,YAAwB,SAAuC;CACvF,MAAM,WAAW,eAAe,CAAC,CAAC,qBAAqB,CAAC;CACxD,MAAM,WAAW,QAAQ,YAAY,WAAW,YAAY,SAAS;CACrE,MAAM,UAAU,QAAQ,WAAW,WAAW,WAAW,SAAS;CAClE,MAAM,mBAAmB,WAAW,oBAAoB,SAAS;CACjE,MAAM,eAAe,WAAW,gBAAgB,SAAS;CAEzD,MAAM,cAA2B,CAAC;CAElC,IAAI,aAAa,QAAW,YAAY,WAAW;CACnD,IAAI,YAAY,QAAW,YAAY,UAAU,cAAc,OAAO;CACtE,IAAI,qBAAqB,QAAW,YAAY,mBAAmB;CACnE,IAAI,iBAAiB,QAAW,YAAY,eAAe;CAC3D,IAAI,QAAQ,UAAU,QAAW,YAAY,QAAQ,eAAe,QAAQ,KAAK;CACjF,IAAI,QAAQ,aAAa,QAAW,YAAY,WAAW,QAAQ;CACnE,IAAI,QAAQ,UAAU,QAAW,YAAY,QAAQ,QAAQ;CAE7D,OAAO;AACT;AAEA,SAAS,cAAc,SAA6C;CAClE,OAAO,OAAO,YAAY,WAAW;EAAE,MAAM;EAAS,OAAO;CAAQ,IAAI;AAC3E;;;;AAKA,eAAsB,WACpB,KACyC;CACzC,MAAM,QAAS,MAAM,IAAI,SAAS;CAElC,OAAO;EACL,IAAI,OAAO,IAAI,EAAE;EACjB,MAAM,IAAI;EACV,OAAO,IAAI;EACX;EACA,SAAS,IAAI;EACb,UAAU,IAAI;EACd,cAAc,IAAI;EAClB,QAAQ,IAAI;EACZ,cAAc,IAAI,gBAAgB;EAClC,WAAW,IAAI,KAAK,IAAI,SAAS;EACjC,YAAY,IAAI,aAAa,IAAI,KAAK,IAAI,UAAU,IAAI;CAC1D;AACF"}
@@ -0,0 +1,11 @@
1
+ import { Duration } from "./types.mjs";
2
+
3
+ //#region ../queue/src/duration.d.ts
4
+ /**
5
+ * Convert a {@link Duration} to milliseconds. Numbers are already
6
+ * milliseconds. Anything else is rejected loudly rather than guessed at.
7
+ */
8
+ declare function toMilliseconds(value: Duration | string): number;
9
+ //#endregion
10
+ export { toMilliseconds };
11
+ //# sourceMappingURL=duration.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"duration.d.mts","names":[],"sources":["../../../../../../queue/src/duration.ts"],"mappings":";;;;;AAiBA;;iBAAgB,cAAA,CAAe,KAAwB,EAAjB,QAAQ"}