@warlock.js/queue 5.28.0 → 5.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/cjs/index.cjs +15 -15
- package/cjs/index.cjs.map +1 -1
- package/esm/config.d.mts +1 -1
- package/esm/config.d.mts.map +1 -1
- package/esm/config.mjs +1 -1
- package/esm/config.mjs.map +1 -1
- package/esm/dashboard-boot.d.mts +1 -1
- package/esm/dashboard-boot.d.mts.map +1 -1
- package/esm/dashboard-boot.mjs +1 -1
- package/esm/dashboard-boot.mjs.map +1 -1
- package/esm/dashboard-guard-plugin.mjs +1 -1
- package/esm/dashboard-guard-plugin.mjs.map +1 -1
- package/esm/dashboard-middleware-adapter.mjs +1 -1
- package/esm/dashboard-middleware-adapter.mjs.map +1 -1
- package/esm/dashboard.d.mts +1 -1
- package/esm/dashboard.d.mts.map +1 -1
- package/esm/dashboard.mjs +1 -1
- package/esm/dashboard.mjs.map +1 -1
- package/esm/define-job.d.mts +1 -1
- package/esm/define-job.d.mts.map +1 -1
- package/esm/define-job.mjs +1 -1
- package/esm/define-job.mjs.map +1 -1
- package/esm/duration.d.mts +1 -1
- package/esm/duration.d.mts.map +1 -1
- package/esm/duration.mjs +1 -1
- package/esm/duration.mjs.map +1 -1
- package/esm/errors.d.mts +1 -1
- package/esm/errors.d.mts.map +1 -1
- package/esm/errors.mjs +1 -1
- package/esm/errors.mjs.map +1 -1
- package/esm/failed-jobs.d.mts +1 -1
- package/esm/failed-jobs.d.mts.map +1 -1
- package/esm/failed-jobs.mjs +1 -1
- package/esm/failed-jobs.mjs.map +1 -1
- package/esm/job-registry.mjs +1 -1
- package/esm/job-registry.mjs.map +1 -1
- package/esm/process-job.mjs +1 -1
- package/esm/process-job.mjs.map +1 -1
- package/esm/queue-connector.d.mts +1 -1
- package/esm/queue-connector.d.mts.map +1 -1
- package/esm/queue-connector.mjs +1 -1
- package/esm/queue-connector.mjs.map +1 -1
- package/esm/queue-context.d.mts +1 -1
- package/esm/queue-context.d.mts.map +1 -1
- package/esm/queue-context.mjs +1 -1
- package/esm/queue-context.mjs.map +1 -1
- package/esm/queue-dashboard-unguarded.error.d.mts +1 -1
- package/esm/queue-dashboard-unguarded.error.d.mts.map +1 -1
- package/esm/queue-dashboard-unguarded.error.mjs +1 -1
- package/esm/queue-dashboard-unguarded.error.mjs.map +1 -1
- package/esm/queue-manager.d.mts +1 -1
- package/esm/queue-manager.d.mts.map +1 -1
- package/esm/queue-manager.mjs +1 -1
- package/esm/queue-manager.mjs.map +1 -1
- package/esm/types.d.mts +1 -1
- package/esm/types.d.mts.map +1 -1
- package/llms-full.txt +9 -9
- package/llms.txt +4 -4
- package/package.json +5 -4
- package/skills/configure-queue/SKILL.md +2 -2
- package/skills/define-jobs/SKILL.md +1 -1
- package/skills/manage-failed-jobs/SKILL.md +1 -1
- package/skills/overview/SKILL.md +4 -4
- package/skills/queue-notifications/SKILL.md +1 -1
package/esm/define-job.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"define-job.mjs","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"define-job.mjs","names":[],"sources":["../../../../../../../packages/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 { getQueueContext, unwrapPayload, wrapPayload } from \"./queue-context\";\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 // Capture synchronously, in the caller's async context.\n const captured = definition.context === false ? undefined : getQueueContext()?.capture();\n const job = await getQueue(queueName).add(\n definition.name,\n wrapPayload(payload, captured),\n toBullJobOptions(definition, options),\n );\n\n return { id: String(job.id), name: definition.name, queue: queueName };\n },\n async find(id) {\n const queue = getQueue(queueOf(definition));\n const initialJob = await queue.getJob(id);\n\n if (!initialJob || initialJob.name !== definition.name) {\n return undefined;\n }\n\n // Read the state first, then (re)fetch the job. BullMQ writes a job's\n // result/attemptsMade/finishedOn fields *before* it becomes visible\n // under a new state, so re-reading the job after the state is known\n // guarantees those fields are consistent with the reported state\n // (rather than reflecting a moment before the job finished).\n const state = (await initialJob.getState()) as JobState;\n const job = (await queue.getJob(id)) ?? initialJob;\n\n return toSnapshot<TPayload, TResult>(job, state);\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 *\n * @param job The job to read fields from.\n * @param state The job's state; pass a state read *before* `job` was\n * fetched (or re-fetched) so the returned snapshot's fields are consistent\n * with it. If omitted, the state is read from `job` directly.\n */\nexport async function toSnapshot<TPayload, TResult>(\n job: Job,\n state?: JobState,\n): Promise<JobSnapshot<TPayload, TResult>> {\n const resolvedState = state ?? ((await job.getState()) as JobState);\n\n return {\n id: String(job.id),\n name: job.name,\n queue: job.queueName,\n state: resolvedState,\n payload: unwrapPayload(job.data).payload 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":";;;;;;;;;;;;;;;;;;;;;;;;;;AAmCA,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;GAEpC,MAAM,WAAW,WAAW,YAAY,QAAQ,SAAY,gBAAgB,CAAC,EAAE,QAAQ;GACvF,MAAM,MAAM,MAAM,SAAS,SAAS,CAAC,CAAC,IACpC,WAAW,MACX,YAAY,SAAS,QAAQ,GAC7B,iBAAiB,YAAY,OAAO,CACtC;GAEA,OAAO;IAAE,IAAI,OAAO,IAAI,EAAE;IAAG,MAAM,WAAW;IAAM,OAAO;GAAU;EACvE;EACA,MAAM,KAAK,IAAI;GACb,MAAM,QAAQ,SAAS,QAAQ,UAAU,CAAC;GAC1C,MAAM,aAAa,MAAM,MAAM,OAAO,EAAE;GAExC,IAAI,CAAC,cAAc,WAAW,SAAS,WAAW,MAChD;GAQF,MAAM,QAAS,MAAM,WAAW,SAAS;GAGzC,OAAO,WAFM,MAAM,MAAM,OAAO,EAAE,KAAM,YAEE,KAAK;EACjD;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;;;;;;;;;AAUA,eAAsB,WACpB,KACA,OACyC;CACzC,MAAM,gBAAgB,SAAW,MAAM,IAAI,SAAS;CAEpD,OAAO;EACL,IAAI,OAAO,IAAI,EAAE;EACjB,MAAM,IAAI;EACV,OAAO,IAAI;EACX,OAAO;EACP,SAAS,cAAc,IAAI,IAAI,CAAC,CAAC;EACjC,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"}
|
package/esm/duration.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Duration } from "./types.mjs";
|
|
2
2
|
|
|
3
|
-
//#region
|
|
3
|
+
//#region ../../packages/queue/src/duration.d.ts
|
|
4
4
|
/**
|
|
5
5
|
* Convert a {@link Duration} to milliseconds. Numbers are already
|
|
6
6
|
* milliseconds. Anything else is rejected loudly rather than guessed at.
|
package/esm/duration.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"duration.d.mts","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"duration.d.mts","names":[],"sources":["../../../../../../../packages/queue/src/duration.ts"],"mappings":";;;;;AAiBA;;iBAAgB,cAAA,CAAe,KAAwB,EAAjB,QAAQ"}
|
package/esm/duration.mjs
CHANGED
package/esm/duration.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"duration.mjs","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"duration.mjs","names":[],"sources":["../../../../../../../packages/queue/src/duration.ts"],"sourcesContent":["import { InvalidDurationError } from \"./errors\";\nimport type { Duration } from \"./types\";\n\nconst UNIT_MILLISECONDS: Record<string, number> = {\n ms: 1,\n s: 1_000,\n m: 60_000,\n h: 3_600_000,\n d: 86_400_000,\n};\n\nconst DURATION_PATTERN = /^(\\d+(?:\\.\\d+)?)(ms|s|m|h|d)$/;\n\n/**\n * Convert a {@link Duration} to milliseconds. Numbers are already\n * milliseconds. Anything else is rejected loudly rather than guessed at.\n */\nexport function toMilliseconds(value: Duration | string): number {\n if (typeof value === \"number\") {\n if (!Number.isFinite(value) || value < 0) {\n throw new InvalidDurationError(value);\n }\n\n return Math.round(value);\n }\n\n const match = typeof value === \"string\" ? DURATION_PATTERN.exec(value.trim()) : null;\n\n if (!match) {\n throw new InvalidDurationError(value);\n }\n\n return Math.round(Number(match[1]) * UNIT_MILLISECONDS[match[2]!]!);\n}\n"],"mappings":";;;AAGA,MAAM,oBAA4C;CAChD,IAAI;CACJ,GAAG;CACH,GAAG;CACH,GAAG;CACH,GAAG;AACL;AAEA,MAAM,mBAAmB;;;;;AAMzB,SAAgB,eAAe,OAAkC;CAC/D,IAAI,OAAO,UAAU,UAAU;EAC7B,IAAI,CAAC,OAAO,SAAS,KAAK,KAAK,QAAQ,GACrC,MAAM,IAAI,qBAAqB,KAAK;EAGtC,OAAO,KAAK,MAAM,KAAK;CACzB;CAEA,MAAM,QAAQ,OAAO,UAAU,WAAW,iBAAiB,KAAK,MAAM,KAAK,CAAC,IAAI;CAEhF,IAAI,CAAC,OACH,MAAM,IAAI,qBAAqB,KAAK;CAGtC,OAAO,KAAK,MAAM,OAAO,MAAM,EAAE,IAAI,kBAAkB,MAAM,GAAK;AACpE"}
|
package/esm/errors.d.mts
CHANGED
package/esm/errors.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.mts","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"errors.d.mts","names":[],"sources":["../../../../../../../packages/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;;AAXyB;AAU9C;;cAea,qBAAA,SAA8B,KAAK;cAC3B,OAAA,UAAiB,OAAA;IAAY,KAAA;EAAA;AAAA"}
|
package/esm/errors.mjs
CHANGED
package/esm/errors.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.mjs","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"errors.mjs","names":[],"sources":["../../../../../../../packages/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\n/**\n * Throw from a job handler (or a queue context's `restore`) to fail the job\n * permanently — it is not retried, whatever `attempts` allows.\n */\nexport class UnrecoverableJobError extends Error {\n public constructor(message: string, options?: { cause?: unknown }) {\n super(message, options);\n this.name = \"UnrecoverableJobError\";\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;;;;;AAMA,IAAa,wBAAb,cAA2C,MAAM;CAC/C,AAAO,YAAY,SAAiB,SAA+B;EACjE,MAAM,SAAS,OAAO;EACtB,KAAK,OAAO;CACd;AACF"}
|
package/esm/failed-jobs.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { FailedJob } from "./types.mjs";
|
|
2
2
|
|
|
3
|
-
//#region
|
|
3
|
+
//#region ../../packages/queue/src/failed-jobs.d.ts
|
|
4
4
|
type FailedJobsOptions = {
|
|
5
5
|
/** Queue to read. Default: the default queue. */queue?: string; /** First index (newest first). Default `0`. */
|
|
6
6
|
start?: number; /** Last index, inclusive. Default `99`. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"failed-jobs.d.mts","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"failed-jobs.d.mts","names":[],"sources":["../../../../../../../packages/queue/src/failed-jobs.ts"],"mappings":";;;KAOY,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"}
|
package/esm/failed-jobs.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import { defaultQueueName } from "./config.mjs";
|
|
|
3
3
|
import { unwrapPayload } from "./queue-context.mjs";
|
|
4
4
|
import { getQueue } from "./queue-manager.mjs";
|
|
5
5
|
|
|
6
|
-
//#region
|
|
6
|
+
//#region ../../packages/queue/src/failed-jobs.ts
|
|
7
7
|
/**
|
|
8
8
|
* List failed jobs, newest first — jobs that used up every attempt, or
|
|
9
9
|
* failed unrecoverably. Each entry can be retried.
|
package/esm/failed-jobs.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"failed-jobs.mjs","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"failed-jobs.mjs","names":[],"sources":["../../../../../../../packages/queue/src/failed-jobs.ts"],"sourcesContent":["import { unwrapPayload } from \"./queue-context\";\nimport 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: unwrapPayload(job.data).payload,\n attemptsMade: job.attemptsMade,\n failedReason: job.failedReason,\n stacktrace: job.stacktrace ?? [],\n failedAt: job.finishedOn ? new Date(job.finishedOn) : undefined,\n retry: () => job.retry(\"failed\"),\n };\n}\n"],"mappings":";;;;;;;;;;AAoBA,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,cAAc,IAAI,IAAI,CAAC,CAAC;EACjC,cAAc,IAAI;EAClB,cAAc,IAAI;EAClB,YAAY,IAAI,cAAc,CAAC;EAC/B,UAAU,IAAI,aAAa,IAAI,KAAK,IAAI,UAAU,IAAI;EACtD,aAAa,IAAI,MAAM,QAAQ;CACjC;AACF"}
|
package/esm/job-registry.mjs
CHANGED
package/esm/job-registry.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"job-registry.mjs","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"job-registry.mjs","names":[],"sources":["../../../../../../../packages/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"}
|
package/esm/process-job.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import { findRegisteredJob } from "./job-registry.mjs";
|
|
|
3
3
|
import { getQueueContext, unwrapPayload } from "./queue-context.mjs";
|
|
4
4
|
import { UnrecoverableError } from "bullmq";
|
|
5
5
|
|
|
6
|
-
//#region
|
|
6
|
+
//#region ../../packages/queue/src/process-job.ts
|
|
7
7
|
/**
|
|
8
8
|
* The single BullMQ processor every worker runs: route the job to the
|
|
9
9
|
* handler registered under its name.
|
package/esm/process-job.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"process-job.mjs","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"process-job.mjs","names":[],"sources":["../../../../../../../packages/queue/src/process-job.ts"],"sourcesContent":["import { type Job, UnrecoverableError } from \"bullmq\";\nimport { UnrecoverableJobError } from \"./errors\";\nimport { findRegisteredJob } from \"./job-registry\";\nimport { getQueueContext, unwrapPayload } from \"./queue-context\";\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 { payload, context: captured } = unwrapPayload(job.data);\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 context: captured,\n };\n\n const run = async () => definition.handle(payload, context);\n const queueContext = getQueueContext();\n\n try {\n return captured !== undefined && queueContext ? await queueContext.restore(captured, run) : await run();\n } catch (error) {\n if (error instanceof UnrecoverableJobError) {\n throw new UnrecoverableError(error.message);\n }\n\n throw error;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;AAcA,eAAsB,WAAW,KAA4B;CAC3D,MAAM,aAAa,kBAAkB,IAAI,IAAI;CAE7C,IAAI,CAAC,YACH,MAAM,IAAI,mBACR,iBAAiB,IAAI,KAAK,6GAE5B;CAGF,MAAM,EAAE,SAAS,SAAS,aAAa,cAAc,IAAI,IAAI;CAE7D,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;EACA,SAAS;CACX;CAEA,MAAM,MAAM,YAAY,WAAW,OAAO,SAAS,OAAO;CAC1D,MAAM,eAAe,gBAAgB;CAErC,IAAI;EACF,OAAO,aAAa,UAAa,eAAe,MAAM,aAAa,QAAQ,UAAU,GAAG,IAAI,MAAM,IAAI;CACxG,SAAS,OAAO;EACd,IAAI,iBAAiB,uBACnB,MAAM,IAAI,mBAAmB,MAAM,OAAO;EAG5C,MAAM;CACR;AACF"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { QueueConfig } from "./types.mjs";
|
|
2
2
|
import { Connector } from "@warlock.js/core";
|
|
3
3
|
|
|
4
|
-
//#region
|
|
4
|
+
//#region ../../packages/queue/src/queue-connector.d.ts
|
|
5
5
|
/**
|
|
6
6
|
* Boots after every built-in connector (`ConnectorPriority.AI` is `10`), so
|
|
7
7
|
* the logger is up and anything a job handler needs is already connected;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"queue-connector.d.mts","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"queue-connector.d.mts","names":[],"sources":["../../../../../../../packages/queue/src/queue-connector.ts"],"mappings":";;;;;;;AA6BsB;AAkBtB;cA3Ba,wBAAA;AAAA,KAID,qBAAA;EAuBkE;;;;EAlB5E,MAAA,GAAS,WAAW;AAAA;;;;;;;;;;;;;;;;iBAkBN,cAAA,CAAe,OAAA,GAAS,qBAAA,GAA6B,SAAS"}
|
package/esm/queue-connector.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import { closeQueue, startWorkers } from "./queue-manager.mjs";
|
|
|
3
3
|
import { mountQueueDashboard } from "./dashboard-boot.mjs";
|
|
4
4
|
import { log } from "@warlock.js/logger";
|
|
5
5
|
|
|
6
|
-
//#region
|
|
6
|
+
//#region ../../packages/queue/src/queue-connector.ts
|
|
7
7
|
/**
|
|
8
8
|
* Boots after every built-in connector (`ConnectorPriority.AI` is `10`), so
|
|
9
9
|
* the logger is up and anything a job handler needs is already connected;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"queue-connector.mjs","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"queue-connector.mjs","names":[],"sources":["../../../../../../../packages/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 { mountQueueDashboard } from \"./dashboard-boot\";\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 /**\n * Mounts the dashboard, when configured, here rather than in `start()`:\n * `boot()` runs for every late-phase connector, in priority order, before\n * any of them `start()`s — so by the time this runs, the HTTP connector\n * (priority 5, before queue's 11) has already built its Fastify instance\n * and registered its own plugins, but has not yet called `listen()`.\n * Fastify refuses new plugin registrations after `listen()`, so this is\n * the only point in the boot sequence where mounting is possible.\n */\n async boot() {\n const queueConfig = options.config ?? (await readQueueConfig());\n\n if (!queueConfig?.dashboard?.enabled) {\n return;\n }\n\n // The dashboard resolves queues through the active config, so it has to\n // be registered here rather than only in `start()`, which runs after\n // every late connector has booted. `start()` sets it again; the setter\n // is idempotent for the same object.\n setQueueConfig(queueConfig);\n\n const { getHttpServer } = await import(\"@warlock.js/core\");\n\n await mountQueueDashboard(getHttpServer(), queueConfig);\n },\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":";;;;;;;;;;;AAoBA,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;;;;;;;;;;EAUhB,MAAM,OAAO;GACX,MAAM,cAAc,QAAQ,UAAW,MAAM,gBAAgB;GAE7D,IAAI,CAAC,aAAa,WAAW,SAC3B;GAOF,eAAe,WAAW;GAE1B,MAAM,EAAE,kBAAkB,MAAM,OAAO;GAEvC,MAAM,oBAAoB,cAAc,GAAG,WAAW;EACxD;EACA,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"}
|
package/esm/queue-context.d.mts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"queue-context.d.mts","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"queue-context.d.mts","names":[],"sources":["../../../../../../../packages/queue/src/queue-context.ts"],"mappings":";;AAIA;;;KAAY,YAAA;EAIU,mFAFpB,OAAA,QAAe,CAAA,cAEwC;EAAvD,OAAA,GAAU,QAAA,EAAU,CAAA,EAAG,GAAA,QAAW,OAAA,cAAqB,OAAA;AAAA;;;;;;;;;;;AAAO;iBAchD,kBAAA,IAAsB,OAAA,EAAS,YAAA,CAAa,CAAA,IAAK,YAAA,CAAa,CAAA;;iBAO9D,eAAA,CAAgB,OAA0B,EAAjB,YAAY;;iBAKrC,eAAA,IAAmB,YAAY;;iBAK/B,iBAAA"}
|
package/esm/queue-context.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"queue-context.mjs","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"queue-context.mjs","names":[],"sources":["../../../../../../../packages/queue/src/queue-context.ts"],"sourcesContent":["/**\n * Carries ambient state (tenant, request id, …) from the code that\n * dispatches a job into the worker that runs it.\n */\nexport type QueueContext<T> = {\n /** Read the ambient state at dispatch time; `undefined` means nothing to carry. */\n capture: () => T | undefined;\n /** Run `run` with the captured state re-established. */\n restore: (captured: T, run: () => Promise<unknown>) => Promise<unknown>;\n};\n\n/**\n * Define a queue context. `restore`'s `captured` is inferred from `capture`.\n *\n * @example\n * setQueueContext(\n * defineQueueContext({\n * capture: () => tenantStorage.getStore(),\n * restore: (tenant, run) => tenantStorage.run(tenant, run),\n * }),\n * );\n */\nexport function defineQueueContext<T>(context: QueueContext<T>): QueueContext<T> {\n return context;\n}\n\nlet activeContext: QueueContext<any> | undefined;\n\n/** Register the queue context used by every job dispatch and run. */\nexport function setQueueContext(context: QueueContext<any>): void {\n activeContext = context;\n}\n\n/** The registered queue context, if any. */\nexport function getQueueContext(): QueueContext<any> | undefined {\n return activeContext;\n}\n\n/** Forget the registered queue context. */\nexport function resetQueueContext(): void {\n activeContext = undefined;\n}\n\n/** The job-data envelope written when a context was captured. */\nexport type QueueEnvelope = {\n __warlock: { v: 1; context?: unknown };\n payload: unknown;\n};\n\n/** Wrap a payload with the captured context; no context leaves it bare. */\nexport function wrapPayload(payload: unknown, context: unknown): unknown {\n if (context === undefined) return payload;\n\n return { __warlock: { v: 1, context }, payload } satisfies QueueEnvelope;\n}\n\n/** Split job data into payload and captured context; bare data passes through. */\nexport function unwrapPayload(data: unknown): { payload: unknown; context: unknown } {\n const envelope = data as Partial<QueueEnvelope> | null;\n\n if (\n envelope !== null &&\n typeof envelope === \"object\" &&\n typeof envelope.__warlock === \"object\" &&\n envelope.__warlock !== null &&\n envelope.__warlock.v === 1 &&\n \"payload\" in envelope\n ) {\n return { payload: envelope.payload, context: envelope.__warlock.context };\n }\n\n return { payload: data, context: undefined };\n}\n"],"mappings":";;;;;;;;;;;;AAsBA,SAAgB,mBAAsB,SAA2C;CAC/E,OAAO;AACT;AAEA,IAAI;;AAGJ,SAAgB,gBAAgB,SAAkC;CAChE,gBAAgB;AAClB;;AAGA,SAAgB,kBAAiD;CAC/D,OAAO;AACT;;AAGA,SAAgB,oBAA0B;CACxC,gBAAgB;AAClB;;AASA,SAAgB,YAAY,SAAkB,SAA2B;CACvE,IAAI,YAAY,QAAW,OAAO;CAElC,OAAO;EAAE,WAAW;GAAE,GAAG;GAAG;EAAQ;EAAG;CAAQ;AACjD;;AAGA,SAAgB,cAAc,MAAuD;CACnF,MAAM,WAAW;CAEjB,IACE,aAAa,QACb,OAAO,aAAa,YACpB,OAAO,SAAS,cAAc,YAC9B,SAAS,cAAc,QACvB,SAAS,UAAU,MAAM,KACzB,aAAa,UAEb,OAAO;EAAE,SAAS,SAAS;EAAS,SAAS,SAAS,UAAU;CAAQ;CAG1E,OAAO;EAAE,SAAS;EAAM,SAAS;CAAU;AAC7C"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//#region
|
|
1
|
+
//#region ../../packages/queue/src/queue-dashboard-unguarded.error.d.ts
|
|
2
2
|
/**
|
|
3
3
|
* Thrown at boot when `queue.dashboard.enabled` is `true` in production with
|
|
4
4
|
* no guard middleware. The dashboard can retry and delete jobs; mounting it
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"queue-dashboard-unguarded.error.d.mts","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"queue-dashboard-unguarded.error.d.mts","names":[],"sources":["../../../../../../../packages/queue/src/queue-dashboard-unguarded.error.ts"],"mappings":";;AAMA;;;;;cAAa,4BAAA,SAAqC,KAAK;EAAL,WAAA;AAAA"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//#region
|
|
1
|
+
//#region ../../packages/queue/src/queue-dashboard-unguarded.error.ts
|
|
2
2
|
/**
|
|
3
3
|
* Thrown at boot when `queue.dashboard.enabled` is `true` in production with
|
|
4
4
|
* no guard middleware. The dashboard can retry and delete jobs; mounting it
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"queue-dashboard-unguarded.error.mjs","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"queue-dashboard-unguarded.error.mjs","names":[],"sources":["../../../../../../../packages/queue/src/queue-dashboard-unguarded.error.ts"],"sourcesContent":["/**\n * Thrown at boot when `queue.dashboard.enabled` is `true` in production with\n * no guard middleware. The dashboard can retry and delete jobs; mounting it\n * on the open internet without a guard is a production incident waiting to\n * happen, so this fails the boot instead of shipping the hole.\n */\nexport class QueueDashboardUnguardedError extends Error {\n public constructor() {\n super(\n \"queue.dashboard.enabled is true in production with no middleware. The dashboard can \" +\n \"retry and delete jobs, so it must be guarded before it is exposed.\\n\\n\" +\n \"Add a guard middleware:\\n\\n\" +\n \" import { middleware } from \\\"@warlock.js/core\\\";\\n\" +\n \" import { authMiddleware } from \\\"@warlock.js/auth\\\";\\n\\n\" +\n \" const queueConfig: QueueConfig = {\\n\" +\n \" // ...\\n\" +\n \" dashboard: {\\n\" +\n \" enabled: true,\\n\" +\n \" middleware: [authMiddleware(\\\"admin\\\")],\\n\" +\n \" },\\n\" +\n \" };\\n\",\n );\n this.name = \"QueueDashboardUnguardedError\";\n }\n}\n"],"mappings":";;;;;;;AAMA,IAAa,+BAAb,cAAkD,MAAM;CACtD,AAAO,cAAc;EACnB,MACE,6bAYF;EACA,KAAK,OAAO;CACd;AACF"}
|
package/esm/queue-manager.d.mts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"queue-manager.d.mts","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"queue-manager.d.mts","names":[],"sources":["../../../../../../../packages/queue/src/queue-manager.ts"],"mappings":";;;;;AAgBA;;iBAAgB,QAAA,CAAS,IAAA,WAAe,KAAK;;AAAA;AAiC7C;;;;AAA6C;AAgC7C;;;;AAA8B;iBAhCR,YAAA,IAAgB,OAAO;;iBAgC7B,cAAA;AAAA,KAgCJ,iBAAA;EAKH;AAQT;;;EARE,OAAO;AAAA;;;;AAQiE;;iBAApD,UAAA,CAAW,OAAA,GAAS,iBAAA,GAAyB,OAAO"}
|
package/esm/queue-manager.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import { processJob } from "./process-job.mjs";
|
|
|
4
4
|
import { log } from "@warlock.js/logger";
|
|
5
5
|
import { Queue, Worker } from "bullmq";
|
|
6
6
|
|
|
7
|
-
//#region
|
|
7
|
+
//#region ../../packages/queue/src/queue-manager.ts
|
|
8
8
|
const DEFAULT_SHUTDOWN_TIMEOUT = 3e4;
|
|
9
9
|
const queues = /* @__PURE__ */ new Map();
|
|
10
10
|
const workers = /* @__PURE__ */ new Map();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"queue-manager.mjs","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"queue-manager.mjs","names":[],"sources":["../../../../../../../packages/queue/src/queue-manager.ts"],"sourcesContent":["import { log } from \"@warlock.js/logger\";\nimport { Queue, Worker } from \"bullmq\";\nimport { getQueueConfig } from \"./config\";\nimport { onJobRegistered, queueOf, registeredJobs } from \"./job-registry\";\nimport { processJob } from \"./process-job\";\n\nconst DEFAULT_SHUTDOWN_TIMEOUT = 30_000;\n\nconst queues = new Map<string, Queue>();\nconst workers = new Map<string, Worker>();\nlet stopListening: (() => void) | undefined;\n\n/**\n * The BullMQ queue for `name`, created on first use with the configured\n * connection and prefix.\n */\nexport function getQueue(name: string): Queue {\n let queue = queues.get(name);\n\n if (!queue) {\n const config = getQueueConfig();\n\n queue = new Queue(name, {\n connection: config.connection,\n prefix: config.prefix ?? \"warlock\",\n });\n\n queue.on(\"error\", (error) => {\n log.error(\"queue\", \"connection\", error);\n });\n\n queues.set(name, queue);\n }\n\n return queue;\n}\n\n/**\n * Start one worker per queue that has a registered job, and keep starting\n * workers for queues whose first job is defined later.\n *\n * A no-op returning `[]` when `workers.enabled` is `false`, or when this\n * process does not serve the `worker` role (`Application.hasRole`) — a\n * process started with `--role=web`, for example, produces jobs but never\n * runs them in-process. Calling it again while workers run starts only the\n * missing ones.\n *\n * @returns the queue names that now have a worker in this process.\n */\nexport async function startWorkers(): Promise<string[]> {\n const { Application } = await import(\"@warlock.js/core\");\n\n if (!Application.hasRole(\"worker\")) {\n log.info(\n \"queue\",\n \"workers\",\n `queue: workers not started (role: ${[...Application.roles].join(\",\")})`,\n );\n return [];\n }\n\n const config = getQueueConfig();\n\n if (config.workers?.enabled === false) {\n return [];\n }\n\n for (const job of registeredJobs()) {\n ensureWorker(queueOf(job));\n }\n\n stopListening ??= onJobRegistered((job) => {\n ensureWorker(queueOf(job));\n });\n\n await Promise.all([...workers.values()].map((worker) => worker.waitUntilReady()));\n\n return [...workers.keys()];\n}\n\n/** The queue names with a running worker in this process. */\nexport function runningWorkers(): string[] {\n return [...workers.keys()];\n}\n\nfunction ensureWorker(queueName: string): void {\n if (workers.has(queueName)) {\n return;\n }\n\n const config = getQueueConfig();\n\n const worker = new Worker(queueName, processJob, {\n connection: config.connection,\n prefix: config.prefix ?? \"warlock\",\n concurrency: config.workers?.concurrency ?? 1,\n });\n\n worker.on(\"error\", (error) => {\n log.error(\"queue\", \"worker\", error);\n });\n\n worker.on(\"failed\", (job, error) => {\n log.error(\n \"queue\",\n \"job.failed\",\n `${job?.name ?? \"unknown\"} (${job?.id ?? \"?\"}): ${error.message}`,\n );\n });\n\n workers.set(queueName, worker);\n}\n\nexport type CloseQueueOptions = {\n /**\n * How long to wait for active jobs before force-closing workers, in\n * milliseconds. Default: `workers.shutdownTimeout`, else `30000`.\n */\n timeout?: number;\n};\n\n/**\n * Graceful shutdown: stop workers taking new jobs and wait for active ones\n * (bounded by `timeout`, then force-close), then close every queue\n * connection. Safe to call when nothing was started, and more than once.\n */\nexport async function closeQueue(options: CloseQueueOptions = {}): Promise<void> {\n stopListening?.();\n stopListening = undefined;\n\n const timeout = options.timeout ?? configuredShutdownTimeout();\n const closingWorkers = [...workers.values()];\n workers.clear();\n\n await Promise.all(closingWorkers.map((worker) => closeWorker(worker, timeout)));\n\n const closingQueues = [...queues.values()];\n queues.clear();\n\n await Promise.all(closingQueues.map((queue) => queue.close()));\n}\n\nfunction configuredShutdownTimeout(): number {\n try {\n return getQueueConfig().workers?.shutdownTimeout ?? DEFAULT_SHUTDOWN_TIMEOUT;\n } catch {\n return DEFAULT_SHUTDOWN_TIMEOUT;\n }\n}\n\nasync function closeWorker(worker: Worker, timeout: number): Promise<void> {\n let timer: NodeJS.Timeout | undefined;\n\n const timedOut = new Promise<\"timeout\">((resolve) => {\n timer = setTimeout(() => resolve(\"timeout\"), timeout);\n });\n\n // A close already in progress cannot be upgraded to a forced one (BullMQ\n // returns the pending promise), so the timeout path drops the connections\n // instead and lets the graceful close settle in the background.\n const closing = worker.close().then(() => \"closed\" as const);\n closing.catch(() => undefined);\n\n const outcome = await Promise.race([closing, timedOut]);\n\n clearTimeout(timer);\n\n if (outcome === \"timeout\") {\n log.warn(\n \"queue\",\n \"shutdown\",\n `Worker for \"${worker.name}\" still had active jobs after ${timeout}ms; disconnecting. ` +\n \"Those jobs are retried once their lock expires.\",\n );\n\n await worker.disconnect();\n }\n}\n"],"mappings":";;;;;;;AAMA,MAAM,2BAA2B;AAEjC,MAAM,yBAAS,IAAI,IAAmB;AACtC,MAAM,0BAAU,IAAI,IAAoB;AACxC,IAAI;;;;;AAMJ,SAAgB,SAAS,MAAqB;CAC5C,IAAI,QAAQ,OAAO,IAAI,IAAI;CAE3B,IAAI,CAAC,OAAO;EACV,MAAM,SAAS,eAAe;EAE9B,QAAQ,IAAI,MAAM,MAAM;GACtB,YAAY,OAAO;GACnB,QAAQ,OAAO,UAAU;EAC3B,CAAC;EAED,MAAM,GAAG,UAAU,UAAU;GAC3B,IAAI,MAAM,SAAS,cAAc,KAAK;EACxC,CAAC;EAED,OAAO,IAAI,MAAM,KAAK;CACxB;CAEA,OAAO;AACT;;;;;;;;;;;;;AAcA,eAAsB,eAAkC;CACtD,MAAM,EAAE,gBAAgB,MAAM,OAAO;CAErC,IAAI,CAAC,YAAY,QAAQ,QAAQ,GAAG;EAClC,IAAI,KACF,SACA,WACA,qCAAqC,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,KAAK,GAAG,EAAE,EACxE;EACA,OAAO,CAAC;CACV;CAIA,IAFe,eAEN,CAAC,CAAC,SAAS,YAAY,OAC9B,OAAO,CAAC;CAGV,KAAK,MAAM,OAAO,eAAe,GAC/B,aAAa,QAAQ,GAAG,CAAC;CAG3B,kBAAkB,iBAAiB,QAAQ;EACzC,aAAa,QAAQ,GAAG,CAAC;CAC3B,CAAC;CAED,MAAM,QAAQ,IAAI,CAAC,GAAG,QAAQ,OAAO,CAAC,CAAC,CAAC,KAAK,WAAW,OAAO,eAAe,CAAC,CAAC;CAEhF,OAAO,CAAC,GAAG,QAAQ,KAAK,CAAC;AAC3B;;AAGA,SAAgB,iBAA2B;CACzC,OAAO,CAAC,GAAG,QAAQ,KAAK,CAAC;AAC3B;AAEA,SAAS,aAAa,WAAyB;CAC7C,IAAI,QAAQ,IAAI,SAAS,GACvB;CAGF,MAAM,SAAS,eAAe;CAE9B,MAAM,SAAS,IAAI,OAAO,WAAW,YAAY;EAC/C,YAAY,OAAO;EACnB,QAAQ,OAAO,UAAU;EACzB,aAAa,OAAO,SAAS,eAAe;CAC9C,CAAC;CAED,OAAO,GAAG,UAAU,UAAU;EAC5B,IAAI,MAAM,SAAS,UAAU,KAAK;CACpC,CAAC;CAED,OAAO,GAAG,WAAW,KAAK,UAAU;EAClC,IAAI,MACF,SACA,cACA,GAAG,KAAK,QAAQ,UAAU,IAAI,KAAK,MAAM,IAAI,KAAK,MAAM,SAC1D;CACF,CAAC;CAED,QAAQ,IAAI,WAAW,MAAM;AAC/B;;;;;;AAeA,eAAsB,WAAW,UAA6B,CAAC,GAAkB;CAC/E,gBAAgB;CAChB,gBAAgB;CAEhB,MAAM,UAAU,QAAQ,WAAW,0BAA0B;CAC7D,MAAM,iBAAiB,CAAC,GAAG,QAAQ,OAAO,CAAC;CAC3C,QAAQ,MAAM;CAEd,MAAM,QAAQ,IAAI,eAAe,KAAK,WAAW,YAAY,QAAQ,OAAO,CAAC,CAAC;CAE9E,MAAM,gBAAgB,CAAC,GAAG,OAAO,OAAO,CAAC;CACzC,OAAO,MAAM;CAEb,MAAM,QAAQ,IAAI,cAAc,KAAK,UAAU,MAAM,MAAM,CAAC,CAAC;AAC/D;AAEA,SAAS,4BAAoC;CAC3C,IAAI;EACF,OAAO,eAAe,CAAC,CAAC,SAAS,mBAAmB;CACtD,QAAQ;EACN,OAAO;CACT;AACF;AAEA,eAAe,YAAY,QAAgB,SAAgC;CACzE,IAAI;CAEJ,MAAM,WAAW,IAAI,SAAoB,YAAY;EACnD,QAAQ,iBAAiB,QAAQ,SAAS,GAAG,OAAO;CACtD,CAAC;CAKD,MAAM,UAAU,OAAO,MAAM,CAAC,CAAC,WAAW,QAAiB;CAC3D,QAAQ,YAAY,MAAS;CAE7B,MAAM,UAAU,MAAM,QAAQ,KAAK,CAAC,SAAS,QAAQ,CAAC;CAEtD,aAAa,KAAK;CAElB,IAAI,YAAY,WAAW;EACzB,IAAI,KACF,SACA,YACA,eAAe,OAAO,KAAK,gCAAgC,QAAQ,mEAErE;EAEA,MAAM,OAAO,WAAW;CAC1B;AACF"}
|
package/esm/types.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ConnectionOptions } from "bullmq";
|
|
2
2
|
import { Middleware } from "@warlock.js/core";
|
|
3
3
|
|
|
4
|
-
//#region
|
|
4
|
+
//#region ../../packages/queue/src/types.d.ts
|
|
5
5
|
/** Units accepted in a {@link Duration} string. */
|
|
6
6
|
type DurationUnit = "ms" | "s" | "m" | "h" | "d";
|
|
7
7
|
/**
|
package/esm/types.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.mts","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"types.d.mts","names":[],"sources":["../../../../../../../packages/queue/src/types.ts"],"mappings":";;;;;KAIY,YAAA;AAAZ;;;;AAAA,KAMY,QAAA,wBAAgC,YAAY;AAAxD;;;;AAAA,KAMY,UAAA;EAAA,uFAIN,IAAA;EAEA,KAAK;AAAA;AAOX;;;;AAAA,KAAY,YAAA;AAMZ;;;;AAAA,KAAY,UAAA;EAQK,kEANf,QAAA,WAM2B;EAJ3B,OAAA,GAAU,UAAA,EAAV;EAEA,gBAAA,GAAmB,YAAA,EAAnB;EAEA,YAAA,GAAe,YAAA;AAAA;;;AAAY;KAMjB,kBAAA;EAAkB;;;;EAK5B,OAAA,YAOA;EALA,WAAA;EAKe;AAQjB;;;EARE,eAAA;AAAA;;;;;AAkBuB;KAVb,oBAAA;EAgBW,oDAdrB,OAAA,YAmBY;EAjBZ,IAAA;EAyBU;;;;;EAnBV,UAAA,GAAa,UAAU;AAAA;;;;KAMb,WAAA;EAaA;;;;EARV,UAAA,EAAY,iBAAA,EAcF;EAZV,MAAA;EAEA,YAAA,WAUuC;EARvC,iBAAA,GAAoB,UAAA,EAaA;EAXpB,OAAA,GAAU,kBAAA,EAuBM;EArBhB,SAAA,GAAY,oBAAA;AAAA;;KAIF,WAAA,YAAuB,MAAM;;;;KAK7B,UAAA;EAUV,kBARA,EAAA,UAUgB;EARhB,IAAA,UAQ8B;EAN9B,KAAA,UAQI;EANJ,OAAA,UAQA;EANA,WAAA,UAMO;EAJP,QAAA,CAAS,KAAA,EAAO,WAAA,GAAc,OAAA,QAUP;EARvB,GAAA,CAAI,IAAA,WAAe,OAAA,QAQ0B;EAN7C,OAAA;AAAA;;;;KAMU,aAAA,sBAAmC,UAAA;EAQ6B,oEAN1E,OAAA,UAFkC;EAIlC,IAAA,UAFA;EAIA,KAAA;EAEA,MAAA,CAAO,OAAA,EAAS,QAAA,EAAU,OAAA,EAAS,UAAA,GAAa,OAAA,CAAQ,OAAA,IAAW,OAAA;AAAA;;;;KAMzD,eAAA;EAN8C,6CAQxD,KAAA,GAAQ,QAAA;EARkE;AAAA;AAM5E;;EAOE,QAAA;EASoB;;;;EAJpB,KAAA,WAEA;EAAA,QAAA,WAEU;EAAV,OAAA,GAAU,UAAU;AAAA;AAItB;AAAA,KAAY,aAAA;EACV,EAAA;EACA,IAAA;EACA,KAAA;AAAA;;KAIU,QAAA;AAJL;AAAA,KAeK,WAAA;EACV,EAAA;EACA,IAAA;EACA,KAAA;EACA,KAAA,EAAO,QAAA;EACP,OAAA,EAAS,QAAA;EACT,QAAA,EAAU,WAAA;EACV,YAAA;EACA,MAAA,GAAS,OAAA;EACT,YAAA;EACA,SAAA,EAAW,IAAA;EACX,UAAA,GAAa,IAAA;AAAA;;KAIH,SAAA;EACV,EAAA;EACA,IAAA;EACA,KAAA;EACA,OAAA,EAAS,QAAA;EACT,YAAA;EACA,YAAA;EACA,UAAA;EACA,QAAA,GAAW,IAAA,EAlBX;EAoBA,KAAA,IAAS,OAAA;AAAA;;;;KAMC,QAAA;EAAA,SACD,IAAA;EAAA,SACA,KAAA,UAvBE;EAyBX,QAAA,CAAS,OAAA,EAAS,QAAA,EAAU,OAAA,GAAU,eAAA,GAAkB,OAAA,CAAQ,aAAA,GAxBnD;EA0Bb,IAAA,CAAK,EAAA,WAAa,OAAA,CAAQ,WAAA,CAAY,QAAA,EAAU,OAAA;AAAA"}
|
package/llms-full.txt
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
name: configure-queue
|
|
11
|
-
description: 'Configure `@warlock.js/queue`: the declarative `src/config/queue.ts` (`QueueConfig` — `connection`, `prefix`, `defaultQueue`, `defaultJobOptions`, `workers: { enabled, concurrency, shutdownTimeout }`), registering `queueConnector()` in `warlock.config.ts > connectors`, running a dispatch-only process, and graceful shutdown. Programmatic `setQueueConfig` / `startWorkers` / `closeQueue` for scripts and tests. Triggers: `QueueConfig`, `queueConnector`, `setQueueConfig`, `startWorkers`, `closeQueue`, `workers.enabled`, `shutdownTimeout`; "configure the queue", "connect BullMQ to Redis", "disable workers in the web process", "graceful shutdown of jobs". Skip: writing jobs —
|
|
11
|
+
description: 'Configure `@warlock.js/queue`: the declarative `src/config/queue.ts` (`QueueConfig` — `connection`, `prefix`, `defaultQueue`, `defaultJobOptions`, `workers: { enabled, concurrency, shutdownTimeout }`), registering `queueConnector()` in `warlock.config.ts > connectors`, running a dispatch-only process, and graceful shutdown. Programmatic `setQueueConfig` / `startWorkers` / `closeQueue` for scripts and tests. Triggers: `QueueConfig`, `queueConnector`, `setQueueConfig`, `startWorkers`, `closeQueue`, `workers.enabled`, `shutdownTimeout`; "configure the queue", "connect BullMQ to Redis", "disable workers in the web process", "graceful shutdown of jobs". Skip: writing jobs — the `define-jobs` topic.'
|
|
12
12
|
---
|
|
13
13
|
|
|
14
14
|
# Configure the queue
|
|
@@ -64,14 +64,14 @@ Every process must use the same `prefix`, or they will not see each other's jobs
|
|
|
64
64
|
|
|
65
65
|
## Dashboard
|
|
66
66
|
|
|
67
|
-
`QueueConfig` also takes a `dashboard: { enabled, path, middleware }` block — see
|
|
67
|
+
`QueueConfig` also takes a `dashboard: { enabled, path, middleware }` block — see the "Dashboard (optional)" section of the `manage-failed-jobs` topic for the `warlock add bull-board` setup and the production guard (`QueueDashboardUnguardedError`).
|
|
68
68
|
|
|
69
69
|
|
|
70
70
|
## define-jobs `@warlock.js/queue/define-jobs/SKILL.md`
|
|
71
71
|
|
|
72
72
|
---
|
|
73
73
|
name: define-jobs
|
|
74
|
-
description: 'Define and dispatch background jobs with `@warlock.js/queue`: `defineJob({ name, queue?, attempts?, backoff?, removeOnComplete?, removeOnFail?, handle(payload, ctx) })` returns a typed job; `job.dispatch(payload, { delay, priority, jobId, attempts, backoff })`; the handler context (`id`, `attempt`, `maxAttempts`, `progress()`, `log()`); reading a job with `job.find(id)`. Triggers: `defineJob`, `.dispatch(`, `ctx.progress`, `JobContext`, `DispatchOptions`, `backoff`, `attempts`, `priority`, `jobId`; "run this in the background", "retry with backoff", "delay a job", "job progress", "idempotent dispatch". Skip: config and workers —
|
|
74
|
+
description: 'Define and dispatch background jobs with `@warlock.js/queue`: `defineJob({ name, queue?, attempts?, backoff?, removeOnComplete?, removeOnFail?, handle(payload, ctx) })` returns a typed job; `job.dispatch(payload, { delay, priority, jobId, attempts, backoff })`; the handler context (`id`, `attempt`, `maxAttempts`, `progress()`, `log()`); reading a job with `job.find(id)`. Triggers: `defineJob`, `.dispatch(`, `ctx.progress`, `JobContext`, `DispatchOptions`, `backoff`, `attempts`, `priority`, `jobId`; "run this in the background", "retry with backoff", "delay a job", "job progress", "idempotent dispatch". Skip: config and workers — the `configure-queue` topic; failed jobs — the `manage-failed-jobs` topic.'
|
|
75
75
|
---
|
|
76
76
|
|
|
77
77
|
# Define and dispatch jobs
|
|
@@ -142,7 +142,7 @@ setQueueContext(
|
|
|
142
142
|
|
|
143
143
|
---
|
|
144
144
|
name: manage-failed-jobs
|
|
145
|
-
description: 'Inspect and retry failed `@warlock.js/queue` jobs: `failedJobs({ queue, start, end })` returns `FailedJob[]` (`id`, `name`, `payload`, `attemptsMade`, `failedReason`, `stacktrace`, `failedAt`, `retry()`), `retryFailedJob(id, { queue })` (throws `FailedJobNotFoundError`), and the bull-board UI — `warlock add bull-board` (requires: ["queue"], so it installs queue first when missing) writes a `dashboard: { enabled, path, middleware }` block to `src/config/queue.ts`; a guard middleware is required in production or boot throws `QueueDashboardUnguardedError`. Manual `queueDashboard(server, { basePath, queues, middleware })` also available — needs `@bull-board/api` + `@bull-board/fastify`, loaded only on call, missing ones throw `QueueDashboardDependencyError`. Triggers: `failedJobs`, `retryFailedJob`, `queueDashboard`, `bull-board`, `dashboard.enabled`, `QueueDashboardUnguardedError`; "list failed jobs", "retry a failed job", "queue dashboard", "job admin UI". Skip: defining retries —
|
|
145
|
+
description: 'Inspect and retry failed `@warlock.js/queue` jobs: `failedJobs({ queue, start, end })` returns `FailedJob[]` (`id`, `name`, `payload`, `attemptsMade`, `failedReason`, `stacktrace`, `failedAt`, `retry()`), `retryFailedJob(id, { queue })` (throws `FailedJobNotFoundError`), and the bull-board UI — `warlock add bull-board` (requires: ["queue"], so it installs queue first when missing) writes a `dashboard: { enabled, path, middleware }` block to `src/config/queue.ts`; a guard middleware is required in production or boot throws `QueueDashboardUnguardedError`. Manual `queueDashboard(server, { basePath, queues, middleware })` also available — needs `@bull-board/api` + `@bull-board/fastify`, loaded only on call, missing ones throw `QueueDashboardDependencyError`. Triggers: `failedJobs`, `retryFailedJob`, `queueDashboard`, `bull-board`, `dashboard.enabled`, `QueueDashboardUnguardedError`; "list failed jobs", "retry a failed job", "queue dashboard", "job admin UI". Skip: defining retries — the `define-jobs` topic.'
|
|
146
146
|
---
|
|
147
147
|
|
|
148
148
|
# Failed jobs
|
|
@@ -234,17 +234,17 @@ Durable background jobs. Jobs are stored in **Redis** (required), retried on fai
|
|
|
234
234
|
|
|
235
235
|
## Skills index
|
|
236
236
|
|
|
237
|
-
-
|
|
238
|
-
-
|
|
239
|
-
-
|
|
240
|
-
-
|
|
237
|
+
- `configure-queue` — `src/config/queue.ts`, `queueConnector()`, workers on/off, shutdown.
|
|
238
|
+
- `define-jobs` — `defineJob`, `dispatch` options, retries, progress, `find`.
|
|
239
|
+
- `manage-failed-jobs` — `failedJobs`, `retryFailedJob`, the bull-board dashboard.
|
|
240
|
+
- `queue-notifications` — `bullmqQueue()` for notifications `.queue()`.
|
|
241
241
|
|
|
242
242
|
|
|
243
243
|
## queue-notifications `@warlock.js/queue/queue-notifications/SKILL.md`
|
|
244
244
|
|
|
245
245
|
---
|
|
246
246
|
name: queue-notifications
|
|
247
|
-
description: 'Send `@warlock.js/notifications` `.queue()` deliveries through BullMQ with `bullmqQueue({ queue?, attempts?, backoff? })` from `@warlock.js/notifications` — put it in the `queue` slot of `src/config/notifications.ts`; the queue workers deliver. Honours `SendOptions.delay` (number = seconds, or "10m"), retries a failing `channel.send`, and fails at once for a channel missing from the worker config. Triggers: `bullmqQueue`, `NotificationConfig.queue`, `QueuePackageNotInstalledError`; "queue notifications with BullMQ", "retry notification delivery", "delayed notification". Skip: the herald backend —
|
|
247
|
+
description: 'Send `@warlock.js/notifications` `.queue()` deliveries through BullMQ with `bullmqQueue({ queue?, attempts?, backoff? })` from `@warlock.js/notifications` — put it in the `queue` slot of `src/config/notifications.ts`; the queue workers deliver. Honours `SendOptions.delay` (number = seconds, or "10m"), retries a failing `channel.send`, and fails at once for a channel missing from the worker config. Triggers: `bullmqQueue`, `NotificationConfig.queue`, `QueuePackageNotInstalledError`; "queue notifications with BullMQ", "retry notification delivery", "delayed notification". Skip: the herald backend — the `queue-notifications` topic of the `warlock-js-notifications` skill.'
|
|
248
248
|
---
|
|
249
249
|
|
|
250
250
|
# Queue notifications with BullMQ
|
package/llms.txt
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
|
|
7
7
|
## Skills
|
|
8
8
|
|
|
9
|
-
- [configure-queue](@warlock.js/queue/configure-queue/SKILL.md): Configure `@warlock.js/queue`: the declarative `src/config/queue.ts` (`QueueConfig` — `connection`, `prefix`, `defaultQueue`, `defaultJobOptions`, `workers: { enabled, concurrency, shutdownTimeout }`), registering `queueConnector()` in `warlock.config.ts > connectors`, running a dispatch-only process, and graceful shutdown. Programmatic `setQueueConfig` / `startWorkers` / `closeQueue` for scripts and tests. Triggers: `QueueConfig`, `queueConnector`, `setQueueConfig`, `startWorkers`, `closeQueue`, `workers.enabled`, `shutdownTimeout`; "configure the queue", "connect BullMQ to Redis", "disable workers in the web process", "graceful shutdown of jobs". Skip: writing jobs —
|
|
10
|
-
- [define-jobs](@warlock.js/queue/define-jobs/SKILL.md): Define and dispatch background jobs with `@warlock.js/queue`: `defineJob({ name, queue?, attempts?, backoff?, removeOnComplete?, removeOnFail?, handle(payload, ctx) })` returns a typed job; `job.dispatch(payload, { delay, priority, jobId, attempts, backoff })`; the handler context (`id`, `attempt`, `maxAttempts`, `progress()`, `log()`); reading a job with `job.find(id)`. Triggers: `defineJob`, `.dispatch(`, `ctx.progress`, `JobContext`, `DispatchOptions`, `backoff`, `attempts`, `priority`, `jobId`; "run this in the background", "retry with backoff", "delay a job", "job progress", "idempotent dispatch". Skip: config and workers —
|
|
11
|
-
- [manage-failed-jobs](@warlock.js/queue/manage-failed-jobs/SKILL.md): Inspect and retry failed `@warlock.js/queue` jobs: `failedJobs({ queue, start, end })` returns `FailedJob[]` (`id`, `name`, `payload`, `attemptsMade`, `failedReason`, `stacktrace`, `failedAt`, `retry()`), `retryFailedJob(id, { queue })` (throws `FailedJobNotFoundError`), and the bull-board UI — `warlock add bull-board` (requires: ["queue"], so it installs queue first when missing) writes a `dashboard: { enabled, path, middleware }` block to `src/config/queue.ts`; a guard middleware is required in production or boot throws `QueueDashboardUnguardedError`. Manual `queueDashboard(server, { basePath, queues, middleware })` also available — needs `@bull-board/api` + `@bull-board/fastify`, loaded only on call, missing ones throw `QueueDashboardDependencyError`. Triggers: `failedJobs`, `retryFailedJob`, `queueDashboard`, `bull-board`, `dashboard.enabled`, `QueueDashboardUnguardedError`; "list failed jobs", "retry a failed job", "queue dashboard", "job admin UI". Skip: defining retries —
|
|
9
|
+
- [configure-queue](@warlock.js/queue/configure-queue/SKILL.md): Configure `@warlock.js/queue`: the declarative `src/config/queue.ts` (`QueueConfig` — `connection`, `prefix`, `defaultQueue`, `defaultJobOptions`, `workers: { enabled, concurrency, shutdownTimeout }`), registering `queueConnector()` in `warlock.config.ts > connectors`, running a dispatch-only process, and graceful shutdown. Programmatic `setQueueConfig` / `startWorkers` / `closeQueue` for scripts and tests. Triggers: `QueueConfig`, `queueConnector`, `setQueueConfig`, `startWorkers`, `closeQueue`, `workers.enabled`, `shutdownTimeout`; "configure the queue", "connect BullMQ to Redis", "disable workers in the web process", "graceful shutdown of jobs". Skip: writing jobs — the `define-jobs` topic.
|
|
10
|
+
- [define-jobs](@warlock.js/queue/define-jobs/SKILL.md): Define and dispatch background jobs with `@warlock.js/queue`: `defineJob({ name, queue?, attempts?, backoff?, removeOnComplete?, removeOnFail?, handle(payload, ctx) })` returns a typed job; `job.dispatch(payload, { delay, priority, jobId, attempts, backoff })`; the handler context (`id`, `attempt`, `maxAttempts`, `progress()`, `log()`); reading a job with `job.find(id)`. Triggers: `defineJob`, `.dispatch(`, `ctx.progress`, `JobContext`, `DispatchOptions`, `backoff`, `attempts`, `priority`, `jobId`; "run this in the background", "retry with backoff", "delay a job", "job progress", "idempotent dispatch". Skip: config and workers — the `configure-queue` topic; failed jobs — the `manage-failed-jobs` topic.
|
|
11
|
+
- [manage-failed-jobs](@warlock.js/queue/manage-failed-jobs/SKILL.md): Inspect and retry failed `@warlock.js/queue` jobs: `failedJobs({ queue, start, end })` returns `FailedJob[]` (`id`, `name`, `payload`, `attemptsMade`, `failedReason`, `stacktrace`, `failedAt`, `retry()`), `retryFailedJob(id, { queue })` (throws `FailedJobNotFoundError`), and the bull-board UI — `warlock add bull-board` (requires: ["queue"], so it installs queue first when missing) writes a `dashboard: { enabled, path, middleware }` block to `src/config/queue.ts`; a guard middleware is required in production or boot throws `QueueDashboardUnguardedError`. Manual `queueDashboard(server, { basePath, queues, middleware })` also available — needs `@bull-board/api` + `@bull-board/fastify`, loaded only on call, missing ones throw `QueueDashboardDependencyError`. Triggers: `failedJobs`, `retryFailedJob`, `queueDashboard`, `bull-board`, `dashboard.enabled`, `QueueDashboardUnguardedError`; "list failed jobs", "retry a failed job", "queue dashboard", "job admin UI". Skip: defining retries — the `define-jobs` topic.
|
|
12
12
|
- [overview](@warlock.js/queue/overview/SKILL.md): @warlock.js/queue overview for orientation and choosing the focused skill for the task.
|
|
13
|
-
- [queue-notifications](@warlock.js/queue/queue-notifications/SKILL.md): Send `@warlock.js/notifications` `.queue()` deliveries through BullMQ with `bullmqQueue({ queue?, attempts?, backoff? })` from `@warlock.js/notifications` — put it in the `queue` slot of `src/config/notifications.ts`; the queue workers deliver. Honours `SendOptions.delay` (number = seconds, or "10m"), retries a failing `channel.send`, and fails at once for a channel missing from the worker config. Triggers: `bullmqQueue`, `NotificationConfig.queue`, `QueuePackageNotInstalledError`; "queue notifications with BullMQ", "retry notification delivery", "delayed notification". Skip: the herald backend —
|
|
13
|
+
- [queue-notifications](@warlock.js/queue/queue-notifications/SKILL.md): Send `@warlock.js/notifications` `.queue()` deliveries through BullMQ with `bullmqQueue({ queue?, attempts?, backoff? })` from `@warlock.js/notifications` — put it in the `queue` slot of `src/config/notifications.ts`; the queue workers deliver. Honours `SendOptions.delay` (number = seconds, or "10m"), retries a failing `channel.send`, and fails at once for a channel missing from the worker config. Triggers: `bullmqQueue`, `NotificationConfig.queue`, `QueuePackageNotInstalledError`; "queue notifications with BullMQ", "retry notification delivery", "delayed notification". Skip: the herald backend — the `queue-notifications` topic of the `warlock-js-notifications` skill.
|
package/package.json
CHANGED
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
"peerDependencies": {
|
|
12
12
|
"@bull-board/api": "^9.0.0",
|
|
13
13
|
"@bull-board/fastify": "^9.0.0",
|
|
14
|
-
"@warlock.js/core": "5.
|
|
15
|
-
"@warlock.js/logger": "5.
|
|
14
|
+
"@warlock.js/core": "5.30.0",
|
|
15
|
+
"@warlock.js/logger": "5.30.0"
|
|
16
16
|
},
|
|
17
17
|
"peerDependenciesMeta": {
|
|
18
18
|
"@bull-board/api": {
|
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
},
|
|
25
25
|
"repository": {
|
|
26
26
|
"type": "git",
|
|
27
|
-
"url": "https://github.com/warlockjs/
|
|
27
|
+
"url": "git+https://github.com/warlockjs/framework.git",
|
|
28
|
+
"directory": "packages/queue"
|
|
28
29
|
},
|
|
29
30
|
"keywords": [
|
|
30
31
|
"warlock.js",
|
|
@@ -37,7 +38,7 @@
|
|
|
37
38
|
],
|
|
38
39
|
"author": "hassanzohdy",
|
|
39
40
|
"license": "MIT",
|
|
40
|
-
"version": "5.
|
|
41
|
+
"version": "5.30.0",
|
|
41
42
|
"main": "./cjs/index.cjs",
|
|
42
43
|
"module": "./esm/index.mjs",
|
|
43
44
|
"types": "./esm/index.d.mts",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: configure-queue
|
|
3
|
-
description: 'Configure `@warlock.js/queue`: the declarative `src/config/queue.ts` (`QueueConfig` — `connection`, `prefix`, `defaultQueue`, `defaultJobOptions`, `workers: { enabled, concurrency, shutdownTimeout }`), registering `queueConnector()` in `warlock.config.ts > connectors`, running a dispatch-only process, and graceful shutdown. Programmatic `setQueueConfig` / `startWorkers` / `closeQueue` for scripts and tests. Triggers: `QueueConfig`, `queueConnector`, `setQueueConfig`, `startWorkers`, `closeQueue`, `workers.enabled`, `shutdownTimeout`; "configure the queue", "connect BullMQ to Redis", "disable workers in the web process", "graceful shutdown of jobs". Skip: writing jobs —
|
|
3
|
+
description: 'Configure `@warlock.js/queue`: the declarative `src/config/queue.ts` (`QueueConfig` — `connection`, `prefix`, `defaultQueue`, `defaultJobOptions`, `workers: { enabled, concurrency, shutdownTimeout }`), registering `queueConnector()` in `warlock.config.ts > connectors`, running a dispatch-only process, and graceful shutdown. Programmatic `setQueueConfig` / `startWorkers` / `closeQueue` for scripts and tests. Triggers: `QueueConfig`, `queueConnector`, `setQueueConfig`, `startWorkers`, `closeQueue`, `workers.enabled`, `shutdownTimeout`; "configure the queue", "connect BullMQ to Redis", "disable workers in the web process", "graceful shutdown of jobs". Skip: writing jobs — the `define-jobs` topic.'
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Configure the queue
|
|
@@ -56,4 +56,4 @@ Every process must use the same `prefix`, or they will not see each other's jobs
|
|
|
56
56
|
|
|
57
57
|
## Dashboard
|
|
58
58
|
|
|
59
|
-
`QueueConfig` also takes a `dashboard: { enabled, path, middleware }` block — see
|
|
59
|
+
`QueueConfig` also takes a `dashboard: { enabled, path, middleware }` block — see the "Dashboard (optional)" section of the `manage-failed-jobs` topic for the `warlock add bull-board` setup and the production guard (`QueueDashboardUnguardedError`).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: define-jobs
|
|
3
|
-
description: 'Define and dispatch background jobs with `@warlock.js/queue`: `defineJob({ name, queue?, attempts?, backoff?, removeOnComplete?, removeOnFail?, handle(payload, ctx) })` returns a typed job; `job.dispatch(payload, { delay, priority, jobId, attempts, backoff })`; the handler context (`id`, `attempt`, `maxAttempts`, `progress()`, `log()`); reading a job with `job.find(id)`. Triggers: `defineJob`, `.dispatch(`, `ctx.progress`, `JobContext`, `DispatchOptions`, `backoff`, `attempts`, `priority`, `jobId`; "run this in the background", "retry with backoff", "delay a job", "job progress", "idempotent dispatch". Skip: config and workers —
|
|
3
|
+
description: 'Define and dispatch background jobs with `@warlock.js/queue`: `defineJob({ name, queue?, attempts?, backoff?, removeOnComplete?, removeOnFail?, handle(payload, ctx) })` returns a typed job; `job.dispatch(payload, { delay, priority, jobId, attempts, backoff })`; the handler context (`id`, `attempt`, `maxAttempts`, `progress()`, `log()`); reading a job with `job.find(id)`. Triggers: `defineJob`, `.dispatch(`, `ctx.progress`, `JobContext`, `DispatchOptions`, `backoff`, `attempts`, `priority`, `jobId`; "run this in the background", "retry with backoff", "delay a job", "job progress", "idempotent dispatch". Skip: config and workers — the `configure-queue` topic; failed jobs — the `manage-failed-jobs` topic.'
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Define and dispatch jobs
|