@nebutra/queue 0.1.4 → 3.0.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/README.md CHANGED
@@ -1,8 +1,8 @@
1
- > **Status: Foundation** — Type definitions, factory pattern, and provider stubs are complete. The in-memory provider exposes a test-only dead-letter queue, BullMQ exposes retry-exhausted failed jobs, and QStash can map records returned by an injected DLQ fetcher into the shared dead-letter contract.
1
+ > **Status: Foundation** — Type definitions, factory pattern, and provider stubs are complete. The in-memory provider exposes a test-only dead-letter queue, and QStash can map records returned by an injected DLQ fetcher into the shared dead-letter contract.
2
2
 
3
3
  # @nebutra/queue
4
4
 
5
- Provider-agnostic message queue with support for **Upstash QStash** (serverless) and **BullMQ** (self-hosted Redis).
5
+ Single-provider message queue: **Upstash QStash** (serverless) in production, with an in-memory fallback for local dev and tests.
6
6
 
7
7
  ## Quick Start
8
8
 
@@ -96,8 +96,7 @@ The factory auto-detects the provider:
96
96
  |----------|-------------------------------|------------|
97
97
  | 1 | `QUEUE_PROVIDER` env var set | As specified |
98
98
  | 2 | `QSTASH_TOKEN` exists | `qstash` |
99
- | 3 | `REDIS_URL` exists | `bullmq` |
100
- | 4 | Fallback | `memory` |
99
+ | 3 | Fallback | `memory` |
101
100
 
102
101
  ## Environment Variables
103
102
 
@@ -111,13 +110,6 @@ QSTASH_CURRENT_SIGNING_KEY="sig_..."
111
110
  QSTASH_NEXT_SIGNING_KEY="sig_..."
112
111
  ```
113
112
 
114
- ### BullMQ
115
-
116
- ```env
117
- QUEUE_PROVIDER="bullmq"
118
- REDIS_URL="redis://localhost:6379"
119
- ```
120
-
121
113
  ## QStash Webhook Route
122
114
 
123
115
  Mount the verification handler in your API:
@@ -134,6 +126,6 @@ app.post("/api/queue/:queue/:type", async (c) => {
134
126
 
135
127
  ## Failure Observability
136
128
 
137
- Providers may expose `getDeadLetteredJobs(queue?)` for jobs that exhausted retries and need operator attention. The memory provider implements this for deterministic tests and local harnesses. The BullMQ provider maps durable failed jobs whose attempts are exhausted into the same contract, including the original payload, attempt count, configured retry limit, failure reason, and `failedAt` timestamp.
129
+ Providers may expose `getDeadLetteredJobs(queue?)` for jobs that exhausted retries and need operator attention. The memory provider implements this for deterministic tests and local harnesses.
138
130
 
139
131
  For QStash, pass an injected `dlqFetcher` and optional `dlqEndpoint`; this package does not assume unstable provider SDK DLQ APIs. The fetcher returns provider-side records, and the provider maps records whose body is the original `JobPayload` into `DeadLetterJob`. Fetcher errors fail closed to `[]` and are logged.
package/dist/factory.d.ts CHANGED
@@ -12,12 +12,6 @@ import type { QueueConfig, QueueProvider } from "./types";
12
12
  * provider: "qstash",
13
13
  * callbackBaseUrl: "https://api.nebutra.com",
14
14
  * });
15
- *
16
- * // Explicit BullMQ
17
- * const queue = await createQueue({
18
- * provider: "bullmq",
19
- * redisUrl: "redis://localhost:6379",
20
- * });
21
15
  * ```
22
16
  */
23
17
  export declare function createQueue(config?: QueueConfig): Promise<QueueProvider>;
@@ -1 +1 @@
1
- {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../src/factory.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAqB,MAAM,SAAS,CAAC;AAwC7E;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,WAAW,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,aAAa,CAAC,CA0E9E;AAED;;;GAGG;AACH,wBAAsB,QAAQ,IAAI,OAAO,CAAC,aAAa,CAAC,CAKvD;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,aAAa,GAAG,IAAI,CAEtD;AAED;;GAEG;AACH,wBAAsB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAKhD;AAMD,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAEtD;;GAEG;AACH,wBAAgB,SAAS,CACvB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,CAAC,EAAE,UAAU,GACnB,UAAU,CASZ"}
1
+ {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../src/factory.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAqB,MAAM,SAAS,CAAC;AAoC7E;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,WAAW,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,aAAa,CAAC,CAsC9E;AAED;;;GAGG;AACH,wBAAsB,QAAQ,IAAI,OAAO,CAAC,aAAa,CAAC,CAKvD;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,aAAa,GAAG,IAAI,CAEtD;AAED;;GAEG;AACH,wBAAsB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAKhD;AAMD,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAEtD;;GAEG;AACH,wBAAgB,SAAS,CACvB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,CAAC,EAAE,UAAU,GACnB,UAAU,CASZ"}
package/dist/factory.js CHANGED
@@ -18,11 +18,7 @@ function resolveQueueProvider(explicit) {
18
18
  return resolveProviderType({
19
19
  explicit,
20
20
  envVarName: "QUEUE_PROVIDER",
21
- detectors: [
22
- { provider: "qstash", when: envPresent("QSTASH_TOKEN") },
23
- { provider: "bullmq", when: envPresent("REDIS_URL") },
24
- { provider: "sqs", when: envPresent("AWS_SQS_QUEUE_URL") },
25
- ],
21
+ detectors: [{ provider: "qstash", when: envPresent("QSTASH_TOKEN") }],
26
22
  fallback: "memory",
27
23
  });
28
24
  }
@@ -30,7 +26,7 @@ function assertMemoryProviderAllowed() {
30
26
  assertProviderAllowed("memory", {
31
27
  disallowedInProd: ["memory"],
32
28
  overrideEnv: "ALLOW_MEMORY_QUEUE_IN_PRODUCTION",
33
- message: "Refusing to use the in-memory queue provider in production. Configure QSTASH_TOKEN or REDIS_URL, or set ALLOW_MEMORY_QUEUE_IN_PRODUCTION=true for an explicit temporary override.",
29
+ message: "Refusing to use the in-memory queue provider in production. Configure QSTASH_TOKEN, or set ALLOW_MEMORY_QUEUE_IN_PRODUCTION=true for an explicit temporary override.",
34
30
  });
35
31
  }
36
32
  /**
@@ -46,12 +42,6 @@ function assertMemoryProviderAllowed() {
46
42
  * provider: "qstash",
47
43
  * callbackBaseUrl: "https://api.nebutra.com",
48
44
  * });
49
- *
50
- * // Explicit BullMQ
51
- * const queue = await createQueue({
52
- * provider: "bullmq",
53
- * redisUrl: "redis://localhost:6379",
54
- * });
55
45
  * ```
56
46
  */
57
47
  export async function createQueue(config) {
@@ -79,34 +69,6 @@ export async function createQueue(config) {
79
69
  ...(qstashConfig?.dlqFetcher !== undefined ? { dlqFetcher: qstashConfig.dlqFetcher } : {}),
80
70
  });
81
71
  }
82
- case "bullmq": {
83
- const { BullMQProvider } = await import("./providers/bullmq");
84
- const bullConfig = config;
85
- return new BullMQProvider({
86
- ...(bullConfig?.redisUrl !== undefined ? { redisUrl: bullConfig.redisUrl } : {}),
87
- ...(bullConfig?.concurrency !== undefined ? { concurrency: bullConfig.concurrency } : {}),
88
- ...(bullConfig?.prefix !== undefined ? { prefix: bullConfig.prefix } : {}),
89
- });
90
- }
91
- case "sqs": {
92
- const { SQSProvider } = await import("./providers/sqs");
93
- const sqsConfig = config;
94
- return new SQSProvider({
95
- ...(sqsConfig?.region !== undefined ? { region: sqsConfig.region } : {}),
96
- ...(sqsConfig?.queueUrl !== undefined ? { queueUrl: sqsConfig.queueUrl } : {}),
97
- ...(sqsConfig?.accessKeyId !== undefined ? { accessKeyId: sqsConfig.accessKeyId } : {}),
98
- ...(sqsConfig?.secretAccessKey !== undefined
99
- ? { secretAccessKey: sqsConfig.secretAccessKey }
100
- : {}),
101
- ...(sqsConfig?.waitTimeSeconds !== undefined
102
- ? { waitTimeSeconds: sqsConfig.waitTimeSeconds }
103
- : {}),
104
- ...(sqsConfig?.maxMessages !== undefined ? { maxMessages: sqsConfig.maxMessages } : {}),
105
- ...(sqsConfig?.visibilityTimeoutSeconds !== undefined
106
- ? { visibilityTimeoutSeconds: sqsConfig.visibilityTimeoutSeconds }
107
- : {}),
108
- });
109
- }
110
72
  case "memory": {
111
73
  assertMemoryProviderAllowed();
112
74
  const { MemoryProvider } = await import("./providers/memory");
package/dist/index.d.ts CHANGED
@@ -1,12 +1,11 @@
1
1
  export { closeQueue, createJob, createQueue, getQueue, setQueue, } from "./factory";
2
2
  export { createQStashWebhookHandler } from "./middleware/qstash-verify";
3
- export { BullMQProvider } from "./providers/bullmq";
4
3
  export { MemoryProvider } from "./providers/memory";
5
4
  export { getQStashHandler, getQStashHandlerKeys, QStashProvider } from "./providers/qstash";
6
5
  export type { QueuebaseBackoff, QueuebaseClient, QueuebaseClientOptions, QueuebaseEnqueueResult, QueuebaseJobContext, QueuebaseJobDefinition, QueuebaseJobRouter, QueuebaseJobs, QueuebaseSchedule, QueuebaseScheduleConfig, QueuebaseScheduleMetadata, } from "./queuebase";
7
6
  export { createDefaultQueuebaseJobs, createJobClient, createJobRouter, createQueuebaseWebhookHandler, defineQueueJob, getQueuebaseCallbackUrl, listQueuebaseSchedules, queuebaseJobClient, queuebaseJobs, queuebaseWebhookHandler, } from "./queuebase";
8
7
  export type { InvitationCleanupClient, RegisterDefaultScheduledJobsOptions, ScheduledJob, ScheduledJobResult, SessionCleanupClient, } from "./scheduled/index";
9
8
  export { clearScheduledJobs, createInvitationCleanup, createSessionCleanup, getScheduledJob, invitationCleanup, listScheduledJobs, registerDefaultScheduledJobs, registerScheduledJob, runInvitationCleanup, runSessionCleanup, sessionCleanup, } from "./scheduled/index";
10
- export type { BullMQProviderConfig, DeadLetterJob, JobHandler, JobLifecycleAction, JobLifecycleActionResult, JobLifecycleEvent, JobOptions, JobPayload, JobResult, JobStatus, JobStatusInfo, MemoryProviderConfig, QStashProviderConfig, QueueConfig, QueueProvider, QueueProviderType, } from "./types";
9
+ export type { DeadLetterJob, JobHandler, JobLifecycleAction, JobLifecycleActionResult, JobLifecycleEvent, JobOptions, JobPayload, JobResult, JobStatus, JobStatusInfo, MemoryProviderConfig, QStashProviderConfig, QueueConfig, QueueProvider, QueueProviderType, } from "./types";
11
10
  export { JobOptionsSchema, JobPayloadSchema } from "./types";
12
11
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAgBA,OAAO,EACL,UAAU,EACV,SAAS,EACT,WAAW,EACX,QAAQ,EACR,QAAQ,GACT,MAAM,WAAW,CAAC;AAEnB,OAAO,EAAE,0BAA0B,EAAE,MAAM,4BAA4B,CAAC;AACxE,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEpD,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAC5F,YAAY,EACV,gBAAgB,EAChB,eAAe,EACf,sBAAsB,EACtB,sBAAsB,EACtB,mBAAmB,EACnB,sBAAsB,EACtB,kBAAkB,EAClB,aAAa,EACb,iBAAiB,EACjB,uBAAuB,EACvB,yBAAyB,GAC1B,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,0BAA0B,EAC1B,eAAe,EACf,eAAe,EACf,6BAA6B,EAC7B,cAAc,EACd,uBAAuB,EACvB,sBAAsB,EACtB,kBAAkB,EAClB,aAAa,EACb,uBAAuB,GACxB,MAAM,aAAa,CAAC;AAErB,YAAY,EACV,uBAAuB,EACvB,mCAAmC,EACnC,YAAY,EACZ,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,kBAAkB,EAClB,uBAAuB,EACvB,oBAAoB,EACpB,eAAe,EACf,iBAAiB,EACjB,iBAAiB,EACjB,4BAA4B,EAC5B,oBAAoB,EACpB,oBAAoB,EACpB,iBAAiB,EACjB,cAAc,GACf,MAAM,mBAAmB,CAAC;AAE3B,YAAY,EACV,oBAAoB,EACpB,aAAa,EACb,UAAU,EACV,kBAAkB,EAClB,wBAAwB,EACxB,iBAAiB,EACjB,UAAU,EACV,UAAU,EACV,SAAS,EACT,SAAS,EACT,aAAa,EACb,oBAAoB,EACpB,oBAAoB,EACpB,WAAW,EACX,aAAa,EACb,iBAAiB,GAClB,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAeA,OAAO,EACL,UAAU,EACV,SAAS,EACT,WAAW,EACX,QAAQ,EACR,QAAQ,GACT,MAAM,WAAW,CAAC;AAEnB,OAAO,EAAE,0BAA0B,EAAE,MAAM,4BAA4B,CAAC;AACxE,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEpD,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAC5F,YAAY,EACV,gBAAgB,EAChB,eAAe,EACf,sBAAsB,EACtB,sBAAsB,EACtB,mBAAmB,EACnB,sBAAsB,EACtB,kBAAkB,EAClB,aAAa,EACb,iBAAiB,EACjB,uBAAuB,EACvB,yBAAyB,GAC1B,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,0BAA0B,EAC1B,eAAe,EACf,eAAe,EACf,6BAA6B,EAC7B,cAAc,EACd,uBAAuB,EACvB,sBAAsB,EACtB,kBAAkB,EAClB,aAAa,EACb,uBAAuB,GACxB,MAAM,aAAa,CAAC;AAErB,YAAY,EACV,uBAAuB,EACvB,mCAAmC,EACnC,YAAY,EACZ,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,kBAAkB,EAClB,uBAAuB,EACvB,oBAAoB,EACpB,eAAe,EACf,iBAAiB,EACjB,iBAAiB,EACjB,4BAA4B,EAC5B,oBAAoB,EACpB,oBAAoB,EACpB,iBAAiB,EACjB,cAAc,GACf,MAAM,mBAAmB,CAAC;AAE3B,YAAY,EACV,aAAa,EACb,UAAU,EACV,kBAAkB,EAClB,wBAAwB,EACxB,iBAAiB,EACjB,UAAU,EACV,UAAU,EACV,SAAS,EACT,SAAS,EACT,aAAa,EACb,oBAAoB,EACpB,oBAAoB,EACpB,WAAW,EACX,aAAa,EACb,iBAAiB,GAClB,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC"}
package/dist/index.js CHANGED
@@ -3,7 +3,6 @@
3
3
  // =============================================================================
4
4
  // Supports:
5
5
  // - Upstash QStash (serverless, HTTP-based)
6
- // - BullMQ (self-hosted Redis)
7
6
  // - In-memory (dev/test only)
8
7
  //
9
8
  // Usage:
@@ -16,7 +15,6 @@
16
15
  export { closeQueue, createJob, createQueue, getQueue, setQueue, } from "./factory";
17
16
  // ── Middleware ───────────────────────────────────────────────────────────────
18
17
  export { createQStashWebhookHandler } from "./middleware/qstash-verify";
19
- export { BullMQProvider } from "./providers/bullmq";
20
18
  export { MemoryProvider } from "./providers/memory";
21
19
  // ── Providers (tree-shakable direct imports) ────────────────────────────────
22
20
  export { getQStashHandler, getQStashHandlerKeys, QStashProvider } from "./providers/qstash";
package/dist/queuebase.js CHANGED
@@ -42,7 +42,9 @@ export const queuebaseJobClient = createJobClient(queuebaseJobs, {
42
42
  function getBaseUrlFromEnv() {
43
43
  if (process.env.NEXT_PUBLIC_SITE_URL)
44
44
  return process.env.NEXT_PUBLIC_SITE_URL;
45
- if (process.env.VERCEL_URL)
46
- return `https://${process.env.VERCEL_URL}`;
45
+ // Fly sets FLY_APP_NAME on every Machine; the Vercel fallback went with the
46
+ // Vercel deploy surface on 2026-09-22.
47
+ if (process.env.FLY_APP_NAME)
48
+ return `https://${process.env.FLY_APP_NAME}.fly.dev`;
47
49
  return `http://localhost:${process.env.PORT ?? "3001"}`;
48
50
  }
@@ -5,7 +5,7 @@
5
5
  //
6
6
  // Schedule activation is decoupled from job definition: jobs are registered
7
7
  // here with their cron expression, and an external scheduler (Vercel Cron,
8
- // QStash schedules, BullMQ repeatable jobs) triggers them via HTTP route or
8
+ // QStash schedules) triggers them via HTTP route or
9
9
  // worker.
10
10
  //
11
11
  // Default activation in this codebase is Vercel Cron — see
package/dist/types.d.ts CHANGED
@@ -3,11 +3,9 @@ import { z } from "zod";
3
3
  * Supported queue backend providers.
4
4
  *
5
5
  * - `qstash` — Upstash QStash (serverless, HTTP-based, zero infra)
6
- * - `bullmq` — BullMQ over self-hosted Redis (full-featured, self-managed)
7
- * - `sqs` — AWS SQS (managed, pull-based, ideal for AWS-native deployments)
8
6
  * - `memory` — In-memory queue for local dev & testing (NOT for production)
9
7
  */
10
- export type QueueProviderType = "qstash" | "bullmq" | "sqs" | "memory";
8
+ export type QueueProviderType = "qstash" | "memory";
11
9
  export declare const JobOptionsSchema: z.ZodObject<{
12
10
  idempotencyKey: z.ZodOptional<z.ZodString>;
13
11
  delaySec: z.ZodOptional<z.ZodNumber>;
@@ -119,7 +117,6 @@ export interface QueueProvider {
119
117
  enqueueBatch(jobs: JobPayload[]): Promise<JobResult[]>;
120
118
  /**
121
119
  * Register a handler for a given job type.
122
- * - BullMQ: starts a Worker that polls Redis.
123
120
  * - QStash: returns an HTTP handler to mount in your API route.
124
121
  * - Memory: processes inline.
125
122
  */
@@ -169,34 +166,8 @@ export interface QStashProviderConfig {
169
166
  /** Injectable provider-side DLQ fetcher. No Upstash SDK DLQ APIs are assumed. */
170
167
  dlqFetcher?: QueueDeadLetterFetcher;
171
168
  }
172
- export interface BullMQProviderConfig {
173
- provider: "bullmq";
174
- /** Redis connection URL (defaults to `process.env.REDIS_URL`) */
175
- redisUrl?: string;
176
- /** Default concurrency per worker (default: 5) */
177
- concurrency?: number;
178
- /** Key prefix for Redis keys (default: "nebutra:queue") */
179
- prefix?: string;
180
- }
181
169
  export interface MemoryProviderConfig {
182
170
  provider: "memory";
183
171
  }
184
- export interface SQSProviderConfig {
185
- provider: "sqs";
186
- /** AWS region (defaults to `process.env.AWS_REGION`) */
187
- region?: string;
188
- /** SQS queue URL (defaults to `process.env.AWS_SQS_QUEUE_URL`) */
189
- queueUrl?: string;
190
- /** AWS access key (defaults to `process.env.AWS_ACCESS_KEY_ID`) */
191
- accessKeyId?: string;
192
- /** AWS secret key (defaults to `process.env.AWS_SECRET_ACCESS_KEY`) */
193
- secretAccessKey?: string;
194
- /** Long-poll wait time in seconds when receiving (1-20, default 20) */
195
- waitTimeSeconds?: number;
196
- /** Max messages per receive call (1-10, default 10) */
197
- maxMessages?: number;
198
- /** Visibility timeout in seconds — how long a received message is hidden (default 30) */
199
- visibilityTimeoutSeconds?: number;
200
- }
201
- export type QueueConfig = QStashProviderConfig | BullMQProviderConfig | SQSProviderConfig | MemoryProviderConfig;
172
+ export type QueueConfig = QStashProviderConfig | MemoryProviderConfig;
202
173
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js CHANGED
@@ -7,7 +7,7 @@ export const JobOptionsSchema = z.object({
7
7
  delaySec: z.number().int().min(0).optional(),
8
8
  /** Maximum retry attempts on failure (default: 3) */
9
9
  maxRetries: z.number().int().min(0).optional(),
10
- /** Priority: lower number = higher priority (BullMQ only, ignored by QStash) */
10
+ /** Priority: lower number = higher priority (ignored by QStash) */
11
11
  priority: z.number().int().min(0).optional(),
12
12
  /** Cron expression for recurring jobs (e.g. "0 3 * * *") */
13
13
  cron: z.string().optional(),
package/package.json CHANGED
@@ -1,17 +1,16 @@
1
1
  {
2
2
  "name": "@nebutra/queue",
3
- "version": "0.1.4",
3
+ "version": "3.0.0",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "nebutra": {
8
+ "graph": "core",
8
9
  "status": "foundation",
9
10
  "productionReady": false,
10
11
  "requires": [
11
12
  "QSTASH_TOKEN (+ signing keys) for serverless",
12
13
  "Configured QStash dlqFetcher/dlqEndpoint for provider-side DLQ observability",
13
- "REDIS_URL for BullMQ self-hosted",
14
- "AWS_SQS_QUEUE_URL (+ AWS credentials) for SQS",
15
14
  "Host-injected getSystemDb for registerDefaultScheduledJobs (optional scheduled jobs)"
16
15
  ],
17
16
  "gaps": [
@@ -29,14 +28,6 @@
29
28
  "types": "./dist/providers/qstash.d.ts",
30
29
  "import": "./dist/providers/qstash.js"
31
30
  },
32
- "./bullmq": {
33
- "types": "./dist/providers/bullmq.d.ts",
34
- "import": "./dist/providers/bullmq.js"
35
- },
36
- "./sqs": {
37
- "types": "./dist/providers/sqs.d.ts",
38
- "import": "./dist/providers/sqs.js"
39
- },
40
31
  "./queuebase": {
41
32
  "types": "./dist/queuebase.d.ts",
42
33
  "import": "./dist/queuebase.js"
@@ -55,13 +46,10 @@
55
46
  "README.md"
56
47
  ],
57
48
  "dependencies": {
58
- "@aws-sdk/client-sqs": "^3.700.0",
59
49
  "@upstash/qstash": "^2.7.17",
60
- "bullmq": "^5.34.8",
61
- "ioredis": "^5.4.2",
62
50
  "zod": "^4.3.6",
63
- "@nebutra/logger": "0.1.2",
64
- "@nebutra/provider-factory": "0.2.3"
51
+ "@nebutra/logger": "3.0.0",
52
+ "@nebutra/provider-factory": "3.0.0"
65
53
  },
66
54
  "devDependencies": {
67
55
  "typescript": "^5.9.3",
@@ -1,32 +0,0 @@
1
- import type { BullMQProviderConfig, DeadLetterJob, JobHandler, JobPayload, JobResult, JobStatusInfo, QueueProvider } from "../types";
2
- interface BullMQFailedJobLike {
3
- id?: string;
4
- data: JobPayload;
5
- attemptsMade: number;
6
- opts: {
7
- attempts?: number;
8
- };
9
- failedReason?: string;
10
- finishedOn?: number;
11
- }
12
- export declare function toBullMQDeadLetterJob(queue: string, job: BullMQFailedJobLike): DeadLetterJob | undefined;
13
- export declare class BullMQProvider implements QueueProvider {
14
- readonly name: "bullmq";
15
- private connection;
16
- private queues;
17
- private workers;
18
- private handlers;
19
- private prefix;
20
- private concurrency;
21
- constructor(config: Omit<BullMQProviderConfig, "provider">);
22
- private getOrCreateQueue;
23
- enqueue(job: JobPayload): Promise<JobResult>;
24
- enqueueBatch(jobs: JobPayload[]): Promise<JobResult[]>;
25
- registerHandler<T extends Record<string, unknown>>(queue: string, type: string, handler: JobHandler<T>): void;
26
- private ensureWorker;
27
- getJobStatus(jobId: string, queue: string): Promise<JobStatusInfo | undefined>;
28
- getDeadLetteredJobs(queue?: string): Promise<DeadLetterJob[]>;
29
- close(): Promise<void>;
30
- }
31
- export {};
32
- //# sourceMappingURL=bullmq.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"bullmq.d.ts","sourceRoot":"","sources":["../../src/providers/bullmq.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,oBAAoB,EACpB,aAAa,EACb,UAAU,EACV,UAAU,EACV,SAAS,EAET,aAAa,EACb,aAAa,EACd,MAAM,UAAU,CAAC;AAIlB,UAAU,mBAAmB;IAC3B,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,UAAU,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,mBAAmB,GACvB,aAAa,GAAG,SAAS,CAe3B;AAaD,qBAAa,cAAe,YAAW,aAAa;IAClD,QAAQ,CAAC,IAAI,EAAG,QAAQ,CAAU;IAElC,OAAO,CAAC,UAAU,CAAU;IAC5B,OAAO,CAAC,MAAM,CAAiC;IAC/C,OAAO,CAAC,OAAO,CAAkC;IACjD,OAAO,CAAC,QAAQ,CAAsC;IACtD,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,WAAW,CAAS;gBAEhB,MAAM,EAAE,IAAI,CAAC,oBAAoB,EAAE,UAAU,CAAC;IA+B1D,OAAO,CAAC,gBAAgB;IAoBlB,OAAO,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC;IAgC5C,YAAY,CAAC,IAAI,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;IA0C5D,eAAe,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/C,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC,GACrB,IAAI;IAUP,OAAO,CAAC,YAAY;IAyDd,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC;IAiC9E,mBAAmB,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;IAkB7D,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CA4B7B"}
@@ -1,258 +0,0 @@
1
- import { logger } from "@nebutra/logger";
2
- import { Queue, Worker } from "bullmq";
3
- import IORedis from "ioredis";
4
- export function toBullMQDeadLetterJob(queue, job) {
5
- const maxRetries = job.opts.attempts ?? job.data.options?.maxRetries ?? 3;
6
- if (job.attemptsMade < maxRetries)
7
- return undefined;
8
- return {
9
- id: job.id ?? job.data.id,
10
- queue,
11
- type: job.data.type,
12
- originalJob: job.data,
13
- attempts: job.attemptsMade,
14
- maxRetries,
15
- failedReason: job.failedReason ?? "Job failed without a provider reason",
16
- provider: "bullmq",
17
- failedAt: new Date(job.finishedOn ?? Date.now()).toISOString(),
18
- };
19
- }
20
- // BullMQ Provider — self-hosted Redis queue backend
21
- // =============================================================================
22
- // Uses BullMQ (the successor to Bull) for job queuing over a standard Redis
23
- // instance. Ideal for customers who self-host Redis or need features like
24
- // priority queues, rate limiting, and the Bull Board dashboard.
25
- //
26
- // Architecture:
27
- // enqueue() → Redis LPUSH via BullMQ Queue
28
- // registerHandler → BullMQ Worker (long-polling BRPOPLPUSH)
29
- // getJobStatus → Redis HGET on the job hash
30
- // =============================================================================
31
- export class BullMQProvider {
32
- name = "bullmq";
33
- connection;
34
- queues = new Map();
35
- workers = new Map();
36
- handlers = new Map();
37
- prefix;
38
- concurrency;
39
- constructor(config) {
40
- const redisUrl = config.redisUrl ?? process.env.REDIS_URL;
41
- if (!redisUrl) {
42
- throw new Error("Redis URL not configured. Set REDIS_URL env var or pass `redisUrl` in config.");
43
- }
44
- this.prefix = config.prefix ?? "nebutra:queue";
45
- this.concurrency = config.concurrency ?? 5;
46
- this.connection = new IORedis(redisUrl, {
47
- maxRetriesPerRequest: null, // Required by BullMQ
48
- enableReadyCheck: false,
49
- lazyConnect: true,
50
- });
51
- this.connection.on("error", (err) => {
52
- logger.error("[queue:bullmq] Redis connection error", {
53
- error: err.message,
54
- });
55
- });
56
- logger.info("[queue:bullmq] Provider initialised", {
57
- prefix: this.prefix,
58
- concurrency: this.concurrency,
59
- });
60
- }
61
- // ── Internal helpers ────────────────────────────────────────────────────
62
- getOrCreateQueue(queueName) {
63
- let queue = this.queues.get(queueName);
64
- if (!queue) {
65
- queue = new Queue(queueName, {
66
- connection: this.connection.duplicate(),
67
- prefix: this.prefix,
68
- defaultJobOptions: {
69
- attempts: 3,
70
- backoff: { type: "exponential", delay: 1000 },
71
- removeOnComplete: { count: 1000 },
72
- removeOnFail: { count: 5000 },
73
- },
74
- });
75
- this.queues.set(queueName, queue);
76
- }
77
- return queue;
78
- }
79
- // ── Enqueue ─────────────────────────────────────────────────────────────
80
- async enqueue(job) {
81
- const queue = this.getOrCreateQueue(job.queue);
82
- try {
83
- const bullJob = await queue.add(job.type, job, {
84
- jobId: job.id,
85
- ...(job.options?.delaySec ? { delay: job.options.delaySec * 1000 } : {}),
86
- attempts: job.options?.maxRetries ?? 3,
87
- ...(job.options?.priority !== undefined ? { priority: job.options.priority } : {}),
88
- ...(job.options?.cron ? { repeat: { pattern: job.options.cron } } : {}),
89
- });
90
- logger.info("[queue:bullmq] Job enqueued", {
91
- jobId: bullJob.id,
92
- queue: job.queue,
93
- type: job.type,
94
- });
95
- return {
96
- jobId: bullJob.id ?? job.id,
97
- accepted: true,
98
- provider: "bullmq",
99
- };
100
- }
101
- catch (error) {
102
- logger.error("[queue:bullmq] Failed to enqueue job", {
103
- jobId: job.id,
104
- error: error instanceof Error ? error.message : String(error),
105
- });
106
- throw error;
107
- }
108
- }
109
- async enqueueBatch(jobs) {
110
- // Group jobs by queue name for efficient bulk add
111
- const grouped = new Map();
112
- for (const job of jobs) {
113
- const list = grouped.get(job.queue) ?? [];
114
- list.push(job);
115
- grouped.set(job.queue, list);
116
- }
117
- const results = [];
118
- for (const [queueName, queueJobs] of grouped) {
119
- const queue = this.getOrCreateQueue(queueName);
120
- const bulkData = queueJobs.map((job) => ({
121
- name: job.type,
122
- data: job,
123
- opts: {
124
- jobId: job.id,
125
- ...(job.options?.delaySec ? { delay: job.options.delaySec * 1000 } : {}),
126
- attempts: job.options?.maxRetries ?? 3,
127
- ...(job.options?.priority !== undefined ? { priority: job.options.priority } : {}),
128
- },
129
- }));
130
- const bullJobs = await queue.addBulk(bulkData);
131
- for (let i = 0; i < bullJobs.length; i++) {
132
- results.push({
133
- jobId: bullJobs[i]?.id ?? queueJobs[i]?.id ?? "",
134
- accepted: true,
135
- provider: "bullmq",
136
- });
137
- }
138
- }
139
- logger.info("[queue:bullmq] Batch enqueued", { count: jobs.length });
140
- return results;
141
- }
142
- // ── Handler Registration ────────────────────────────────────────────────
143
- registerHandler(queue, type, handler) {
144
- const key = `${queue}:${type}`;
145
- this.handlers.set(key, handler);
146
- // Create or update the worker for this queue
147
- this.ensureWorker(queue);
148
- logger.info("[queue:bullmq] Handler registered", { key });
149
- }
150
- ensureWorker(queueName) {
151
- if (this.workers.has(queueName))
152
- return;
153
- const worker = new Worker(queueName, async (bullJob) => {
154
- const payload = bullJob.data;
155
- const key = `${payload.queue}:${payload.type}`;
156
- const handler = this.handlers.get(key);
157
- if (!handler) {
158
- logger.warn("[queue:bullmq] No handler registered for job type", {
159
- key,
160
- jobId: payload.id,
161
- });
162
- throw new Error(`No handler registered for ${key}`);
163
- }
164
- await handler(payload);
165
- }, {
166
- connection: this.connection.duplicate(),
167
- prefix: this.prefix,
168
- concurrency: this.concurrency,
169
- removeOnComplete: { count: 1000 },
170
- removeOnFail: { count: 5000 },
171
- });
172
- worker.on("completed", (job) => {
173
- logger.info("[queue:bullmq] Job completed", {
174
- jobId: job.id,
175
- queue: queueName,
176
- });
177
- });
178
- worker.on("failed", (job, err) => {
179
- logger.error("[queue:bullmq] Job failed", {
180
- jobId: job?.id,
181
- queue: queueName,
182
- error: err.message,
183
- attempts: job?.attemptsMade,
184
- });
185
- });
186
- worker.on("error", (err) => {
187
- logger.error("[queue:bullmq] Worker error", {
188
- queue: queueName,
189
- error: err.message,
190
- });
191
- });
192
- this.workers.set(queueName, worker);
193
- }
194
- // ── Job Status ──────────────────────────────────────────────────────────
195
- async getJobStatus(jobId, queue) {
196
- const q = this.queues.get(queue);
197
- if (!q)
198
- return undefined;
199
- const bullJob = await q.getJob(jobId);
200
- if (!bullJob)
201
- return undefined;
202
- const state = await bullJob.getState();
203
- const statusMap = {
204
- completed: "completed",
205
- failed: "failed",
206
- delayed: "delayed",
207
- active: "active",
208
- waiting: "waiting",
209
- "waiting-children": "waiting",
210
- prioritized: "pending",
211
- unknown: "pending",
212
- };
213
- return {
214
- id: bullJob.id ?? jobId,
215
- queue,
216
- status: statusMap[state] ?? "pending",
217
- attempts: bullJob.attemptsMade,
218
- maxRetries: bullJob.opts.attempts ?? 3,
219
- ...(bullJob.failedReason ? { failedReason: bullJob.failedReason } : {}),
220
- createdAt: new Date(bullJob.timestamp).toISOString(),
221
- ...(bullJob.processedOn ? { processedAt: new Date(bullJob.processedOn).toISOString() } : {}),
222
- ...(bullJob.finishedOn ? { completedAt: new Date(bullJob.finishedOn).toISOString() } : {}),
223
- };
224
- }
225
- async getDeadLetteredJobs(queue) {
226
- const entries = queue !== undefined ? [[queue, this.getOrCreateQueue(queue)]] : this.queues;
227
- const deadLetters = [];
228
- for (const [queueName, q] of entries) {
229
- const failedJobs = await q.getFailed(0, -1);
230
- for (const failedJob of failedJobs) {
231
- const deadLetter = toBullMQDeadLetterJob(queueName, failedJob);
232
- if (deadLetter)
233
- deadLetters.push(deadLetter);
234
- }
235
- }
236
- return deadLetters;
237
- }
238
- // ── Lifecycle ───────────────────────────────────────────────────────────
239
- async close() {
240
- const closeTasks = [];
241
- for (const [name, worker] of this.workers) {
242
- closeTasks.push(worker.close().then(() => {
243
- logger.info("[queue:bullmq] Worker closed", { queue: name });
244
- }));
245
- }
246
- for (const [name, queue] of this.queues) {
247
- closeTasks.push(queue.close().then(() => {
248
- logger.info("[queue:bullmq] Queue closed", { queue: name });
249
- }));
250
- }
251
- await Promise.allSettled(closeTasks);
252
- await this.connection.quit();
253
- this.workers.clear();
254
- this.queues.clear();
255
- this.handlers.clear();
256
- logger.info("[queue:bullmq] Provider closed");
257
- }
258
- }
@@ -1,18 +0,0 @@
1
- import type { JobHandler, JobPayload, JobResult, QueueProvider, SQSProviderConfig } from "../types";
2
- export declare class SQSProvider implements QueueProvider {
3
- readonly name: "sqs";
4
- private client;
5
- private queueUrl;
6
- private waitTimeSeconds;
7
- private maxMessages;
8
- private visibilityTimeoutSeconds;
9
- constructor(config: Omit<SQSProviderConfig, "provider">);
10
- enqueue(job: JobPayload): Promise<JobResult>;
11
- enqueueBatch(jobs: JobPayload[]): Promise<JobResult[]>;
12
- registerHandler<T extends Record<string, unknown>>(queue: string, type: string, handler: JobHandler<T>): void;
13
- private pollLoop;
14
- private dispatch;
15
- private deleteMessage;
16
- close(): Promise<void>;
17
- }
18
- //# sourceMappingURL=sqs.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"sqs.d.ts","sourceRoot":"","sources":["../../src/providers/sqs.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,SAAS,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AA6BpG,qBAAa,WAAY,YAAW,aAAa;IAC/C,QAAQ,CAAC,IAAI,EAAG,KAAK,CAAU;IAE/B,OAAO,CAAC,MAAM,CAAY;IAC1B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,wBAAwB,CAAS;gBAE7B,MAAM,EAAE,IAAI,CAAC,iBAAiB,EAAE,UAAU,CAAC;IA+BjD,OAAO,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC;IAmC5C,YAAY,CAAC,IAAI,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;IA6C5D,eAAe,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/C,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC,GACrB,IAAI;YAgBO,QAAQ;YAgCR,QAAQ;YAmCR,aAAa;IAcrB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAM7B"}
@@ -1,197 +0,0 @@
1
- import { DeleteMessageCommand, ReceiveMessageCommand, SendMessageBatchCommand, SendMessageCommand, SQSClient, } from "@aws-sdk/client-sqs";
2
- import { logger } from "@nebutra/logger";
3
- const handlerRegistry = new Map();
4
- function handlerKey(queue, type) {
5
- return `${queue}:${type}`;
6
- }
7
- export class SQSProvider {
8
- name = "sqs";
9
- client;
10
- queueUrl;
11
- waitTimeSeconds;
12
- maxMessages;
13
- visibilityTimeoutSeconds;
14
- constructor(config) {
15
- const region = config.region ?? process.env.AWS_REGION;
16
- const queueUrl = config.queueUrl ?? process.env.AWS_SQS_QUEUE_URL;
17
- if (!region) {
18
- throw new Error("SQS region not configured. Set AWS_REGION env var or pass `region`.");
19
- }
20
- if (!queueUrl) {
21
- throw new Error("SQS queue URL not configured. Set AWS_SQS_QUEUE_URL env var or pass `queueUrl`.");
22
- }
23
- const accessKeyId = config.accessKeyId ?? process.env.AWS_ACCESS_KEY_ID;
24
- const secretAccessKey = config.secretAccessKey ?? process.env.AWS_SECRET_ACCESS_KEY;
25
- this.client = new SQSClient({
26
- region,
27
- // If explicit creds provided, use them; otherwise AWS SDK uses default
28
- // chain (IAM role on EC2/ECS/Lambda, ~/.aws/credentials, env, etc.).
29
- ...(accessKeyId && secretAccessKey ? { credentials: { accessKeyId, secretAccessKey } } : {}),
30
- });
31
- this.queueUrl = queueUrl;
32
- this.waitTimeSeconds = config.waitTimeSeconds ?? 20;
33
- this.maxMessages = config.maxMessages ?? 10;
34
- this.visibilityTimeoutSeconds = config.visibilityTimeoutSeconds ?? 30;
35
- logger.info("[queue:sqs] Provider initialised", { region, queueUrl: queueUrl.slice(0, 60) });
36
- }
37
- // ── Enqueue ─────────────────────────────────────────────────────────────
38
- async enqueue(job) {
39
- try {
40
- const response = await this.client.send(new SendMessageCommand({
41
- QueueUrl: this.queueUrl,
42
- MessageBody: JSON.stringify(job),
43
- ...(job.options?.delaySec ? { DelaySeconds: Math.min(job.options.delaySec, 900) } : {}),
44
- ...(job.options?.idempotencyKey
45
- ? {
46
- // FIFO queues use deduplication ID; standard queues ignore it.
47
- MessageDeduplicationId: job.options.idempotencyKey,
48
- MessageGroupId: job.queue,
49
- }
50
- : {}),
51
- }));
52
- const messageId = response.MessageId ?? job.id;
53
- logger.info("[queue:sqs] Job enqueued", { jobId: job.id, messageId });
54
- return {
55
- jobId: messageId,
56
- accepted: true,
57
- provider: "sqs",
58
- };
59
- }
60
- catch (error) {
61
- logger.error("[queue:sqs] Failed to enqueue job", {
62
- jobId: job.id,
63
- error: error instanceof Error ? error.message : String(error),
64
- });
65
- throw error;
66
- }
67
- }
68
- async enqueueBatch(jobs) {
69
- // SQS SendMessageBatch caps at 10 entries per request — chunk if needed.
70
- const results = [];
71
- for (let i = 0; i < jobs.length; i += 10) {
72
- const chunk = jobs.slice(i, i + 10);
73
- try {
74
- const response = await this.client.send(new SendMessageBatchCommand({
75
- QueueUrl: this.queueUrl,
76
- Entries: chunk.map((job, idx) => ({
77
- Id: `msg-${idx}-${Date.now()}`,
78
- MessageBody: JSON.stringify(job),
79
- ...(job.options?.delaySec
80
- ? { DelaySeconds: Math.min(job.options.delaySec, 900) }
81
- : {}),
82
- })),
83
- }));
84
- const successful = response.Successful ?? [];
85
- for (let j = 0; j < chunk.length; j++) {
86
- const ok = successful[j];
87
- results.push({
88
- jobId: ok?.MessageId ?? chunk[j]?.id ?? "",
89
- accepted: Boolean(ok),
90
- provider: "sqs",
91
- });
92
- }
93
- }
94
- catch (error) {
95
- logger.error("[queue:sqs] Batch enqueue failed, falling back to sequential", {
96
- error: error instanceof Error ? error.message : String(error),
97
- });
98
- for (const job of chunk) {
99
- try {
100
- results.push(await this.enqueue(job));
101
- }
102
- catch {
103
- results.push({ jobId: job.id, accepted: false, provider: "sqs" });
104
- }
105
- }
106
- }
107
- }
108
- return results;
109
- }
110
- // ── Handler Registration & Poll Loop ─────────────────────────────────────
111
- registerHandler(queue, type, handler) {
112
- const key = handlerKey(queue, type);
113
- if (handlerRegistry.has(key)) {
114
- logger.warn("[queue:sqs] Handler already registered, replacing", { key });
115
- const existing = handlerRegistry.get(key);
116
- existing?.abort.abort();
117
- }
118
- const abort = new AbortController();
119
- handlerRegistry.set(key, { handler: handler, abort });
120
- void this.pollLoop(queue, type, handler, abort.signal);
121
- logger.info("[queue:sqs] Handler registered + poll loop started", { key });
122
- }
123
- async pollLoop(queue, type, handler, signal) {
124
- while (!signal.aborted) {
125
- try {
126
- const response = await this.client.send(new ReceiveMessageCommand({
127
- QueueUrl: this.queueUrl,
128
- MaxNumberOfMessages: this.maxMessages,
129
- WaitTimeSeconds: this.waitTimeSeconds,
130
- VisibilityTimeout: this.visibilityTimeoutSeconds,
131
- }));
132
- const messages = response.Messages ?? [];
133
- for (const msg of messages) {
134
- if (signal.aborted)
135
- break;
136
- await this.dispatch(msg, queue, type, handler);
137
- }
138
- }
139
- catch (error) {
140
- if (signal.aborted)
141
- break;
142
- logger.error("[queue:sqs] Poll error, sleeping 5s", {
143
- error: error instanceof Error ? error.message : String(error),
144
- });
145
- await new Promise((r) => setTimeout(r, 5000));
146
- }
147
- }
148
- }
149
- async dispatch(msg, queue, type, handler) {
150
- if (!msg.Body || !msg.ReceiptHandle)
151
- return;
152
- let job;
153
- try {
154
- job = JSON.parse(msg.Body);
155
- }
156
- catch {
157
- logger.error("[queue:sqs] Malformed message body, deleting", { messageId: msg.MessageId });
158
- await this.deleteMessage(msg.ReceiptHandle);
159
- return;
160
- }
161
- if (job.queue !== queue || job.type !== type) {
162
- // Not for this handler — leave it for whoever owns it. Visibility timeout
163
- // will expire and another consumer can pick it up.
164
- return;
165
- }
166
- try {
167
- await handler(job);
168
- await this.deleteMessage(msg.ReceiptHandle);
169
- }
170
- catch (error) {
171
- logger.error("[queue:sqs] Handler failed; message will be retried by SQS", {
172
- jobId: job.id,
173
- error: error instanceof Error ? error.message : String(error),
174
- });
175
- // Don't delete — SQS will redeliver after visibility timeout.
176
- // Configure a DLQ on the SQS queue side for terminal failure handling.
177
- }
178
- }
179
- async deleteMessage(receiptHandle) {
180
- try {
181
- await this.client.send(new DeleteMessageCommand({ QueueUrl: this.queueUrl, ReceiptHandle: receiptHandle }));
182
- }
183
- catch (error) {
184
- logger.error("[queue:sqs] DeleteMessage failed", {
185
- error: error instanceof Error ? error.message : String(error),
186
- });
187
- }
188
- }
189
- // ── Lifecycle ───────────────────────────────────────────────────────────
190
- async close() {
191
- for (const entry of handlerRegistry.values())
192
- entry.abort.abort();
193
- handlerRegistry.clear();
194
- this.client.destroy();
195
- logger.info("[queue:sqs] Provider closed");
196
- }
197
- }