@babelqueue/redis 1.1.0 → 1.3.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 +67 -15
- package/dist/index.cjs +703 -61
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +282 -65
- package/dist/index.d.ts +282 -65
- package/dist/index.js +697 -60
- package/dist/index.js.map +1 -1
- package/package.json +7 -4
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/headers.ts"],"sourcesContent":["/**\n * Redis adapter for BabelQueue.\n *\n * A canonical-envelope **publisher** and a URN-routed **consumer** over the §1 reliable-queue\n * pattern (`RPUSH` to produce, `BRPOPLPUSH` the head to a `<queue>:processing` list to reserve so\n * an in-flight message survives a crash, `LREM` to acknowledge), so a Redis-based Node service\n * speaks the same wire contract as the PHP, Python and Go SDKs.\n *\n * Redis lists carry **no native metadata** — the list element **is** the canonical envelope JSON,\n * byte-for-byte, with **no wrapping** (unlike `@babelqueue/bullmq`, which uses BullMQ's own job\n * layout). Routing and tracing read the body's `job` / `trace_id`. This is a Node-owned reliable\n * queue; full parity with Laravel's reserved-sorted-set reservation on a *shared* Redis queue is a\n * separate task — for a mixed PHP+Node fleet on one queue, prefer a queue this consumer owns\n * end-to-end.\n *\n * This implements §1 of the broker-bindings contract. `ioredis` is an optional peer — you provide\n * the client (an `ioredis` instance satisfies the adapter structurally).\n *\n * **Out-of-band headers (ADR-0028).** A Redis list element *is* the canonical envelope JSON (the\n * `LREM` ack handle is that exact value), so — unlike AMQP headers or SQS `MessageAttributes` —\n * there is no native per-message metadata channel. To carry a {@link HeaderCarrier} (e.g. a W3C\n * `traceparent` for cross-hop span linkage) the adapter owns a tiny JSON *frame* distinct from the\n * wire envelope:\n *\n * {\"__bq_frame\":1,\"headers\":{\"traceparent\":\"00-…\"},\"body\":\"<raw wire envelope>\"}\n *\n * `RPUSH` stores the frame, so the `LREM` ack handle stays byte-for-byte what was pushed and the\n * reliable-queue semantics (`RPUSH`/`BRPOPLPUSH`/`LREM`) are untouched. Framing is **opt-in and\n * backward compatible**: only `publish` with a non-empty `headers` carrier writes a frame; a plain\n * publish stores the **bare** envelope byte-for-byte, exactly as before. The consumer detects\n * frame-vs-bare by the reserved `__bq_frame` sentinel (a frozen envelope can never carry it), so a\n * bare value consumes with empty headers and cross-version queues interoperate. This mirrors the\n * Go `/redis` and PHP `RedisTransport` framing byte-for-byte. GR-1: the wire envelope is never\n * touched.\n */\n\nimport { annotate, BabelQueueError, EnvelopeCodec, UnknownUrnError, UnknownUrnStrategy } from \"@babelqueue/core\";\nimport type { Envelope, HeaderCarrier, IncomingEnvelope } from \"@babelqueue/core\";\n\nimport { sanitizeHeaders } from \"./headers.js\";\n\n// --- Minimal Redis shape (a structural subset of ioredis) ----------------------\n\n/** The subset of an ioredis client this adapter calls. */\nexport interface RedisClient {\n /** Append a value to the tail of a list; returns the new length. */\n rpush(key: string, value: string): Promise<number>;\n /** Atomically pop the head of `source` and push it to the tail of `destination`, blocking up\n * to `timeout` seconds; resolves to the value, or `null` on timeout. */\n brpoplpush(source: string, destination: string, timeout: number): Promise<string | null>;\n /** Remove `count` occurrences of `value` from a list; returns how many were removed. */\n lrem(key: string, count: number, value: string): Promise<number>;\n}\n\n// --- Payload (contract §1) -----------------------------------------------------\n\n/** The list element for an envelope: the canonical JSON, verbatim — no wrapping (§1). */\nexport function redisValue(envelope: Envelope): string {\n return EnvelopeCodec.encode(envelope);\n}\n\n// --- Header frame (ADR-0028) ---------------------------------------------------\n\n/** The reserved discriminator key + current version of the transport-owned header frame. */\nexport const FRAME_KEY = \"__bq_frame\";\nconst FRAME_VERSION = 1;\n\n/** The transport-owned frame the list value carries when out-of-band headers accompany a message. */\ninterface HeaderFrame {\n __bq_frame: number;\n headers?: HeaderCarrier;\n body: string;\n}\n\n/**\n * The pure produce-side decision: the exact string to `RPUSH` for `body` + `headers`. With no\n * usable headers it returns `body` verbatim (the **bare** form, so a plain publish and a\n * header-less publish store byte-identical values); otherwise it returns the transport-owned frame\n * JSON. Kept pure so the framing decision is unit-testable without a broker.\n */\nexport function frameValue(body: string, headers: HeaderCarrier | null | undefined): string {\n const clean = sanitizeHeaders(headers);\n if (Object.keys(clean).length === 0) return body;\n const frame: HeaderFrame = { [FRAME_KEY]: FRAME_VERSION, headers: clean, body };\n return JSON.stringify(frame);\n}\n\n/**\n * Interpret a stored Redis list value: `[wire-envelope-body, headers]`. A value is a header frame\n * iff it is a JSON object carrying the reserved `__bq_frame` sentinel (a frozen wire envelope never\n * has it); then it yields the unframed body plus the carried headers. Any other value — a bare\n * envelope, non-JSON, or JSON without the sentinel — is returned verbatim as the body with empty\n * headers, so older / cross-version queue values consume exactly as before.\n */\nexport function unframe(value: string): [string, HeaderCarrier] {\n // Cheap reject: a frame is always a JSON object, and the sentinel substring must appear. This\n // avoids a full parse for the overwhelmingly common bare-envelope case (only short-circuits\n // negatives).\n if (value === \"\" || value[0] !== \"{\" || !value.includes(`\"${FRAME_KEY}\"`)) {\n return [value, {}];\n }\n let decoded: unknown;\n try {\n decoded = JSON.parse(value);\n } catch {\n return [value, {}];\n }\n if (\n decoded == null ||\n typeof decoded !== \"object\" ||\n !(FRAME_KEY in decoded) ||\n !(decoded as HeaderFrame)[FRAME_KEY] ||\n typeof (decoded as HeaderFrame).body !== \"string\"\n ) {\n return [value, {}];\n }\n const frame = decoded as HeaderFrame;\n return [frame.body, sanitizeHeaders(frame.headers)];\n}\n\n/** The out-of-band headers carried by a stored list value (empty for a bare value). */\nexport function headersOf(value: string): HeaderCarrier {\n return unframe(value)[1];\n}\n\n// --- Publisher -----------------------------------------------------------------\n\n/** Options for {@link RedisPublisher.publish}. */\nexport interface PublishOptions {\n traceId?: string;\n /**\n * Out-of-band transport headers to carry beside the frozen envelope (ADR-0028) — e.g. a W3C\n * `traceparent` written by `@babelqueue/core/otel`'s `publish`. A non-empty carrier RPUSHes a\n * transport-owned frame; an empty/omitted carrier stays a byte-identical bare publish.\n */\n headers?: HeaderCarrier;\n}\n\n/** Sends canonical-envelope messages to one Redis list (§1 reliable-queue). */\nexport class RedisPublisher {\n private constructor(\n private readonly client: RedisClient,\n private readonly queue: string,\n ) {}\n\n /** A publisher over `client`, producing to the list named `queue`. */\n static create(client: RedisClient, queue: string): RedisPublisher {\n return new RedisPublisher(client, queue);\n }\n\n /**\n * Build + `RPUSH` the canonical envelope; returns the message id (`meta.id`). When\n * `options.headers` carries any out-of-band header (e.g. a `traceparent`, ADR-0028) the value\n * pushed is a transport-owned frame that wraps the envelope; otherwise the bare envelope is\n * pushed byte-for-byte (GR-1, no regression).\n */\n async publish(urn: string, data: Record<string, unknown>, options: PublishOptions = {}): Promise<string> {\n const envelope = EnvelopeCodec.make(urn, data, { queue: this.queue, traceId: options.traceId });\n await this.client.rpush(this.queue, frameValue(redisValue(envelope), options.headers));\n return envelope.meta.id;\n }\n}\n\n// --- Consumer ------------------------------------------------------------------\n\n/**\n * A URN handler. Receives the validated envelope, the raw stored list element, and the out-of-band\n * {@link HeaderCarrier} carried beside it (empty for a bare value). Pass `headers` to\n * `@babelqueue/core/otel`'s `wrapHandler` to link the consumer span as a child of the producer\n * span (ADR-0028).\n */\nexport type BabelHandler = (envelope: Envelope, raw: string, headers: HeaderCarrier) => unknown | Promise<unknown>;\n\n/** A map of URN → handler. */\nexport type BabelHandlers = Record<string, BabelHandler>;\n\n/** Options for {@link RedisConsumer}. */\nexport interface RedisConsumerOptions {\n /** Suffix for the per-queue reservation list (default `\":processing\"`). */\n processingSuffix?: string;\n /** Block timeout (seconds) for a single reserve (default 5). */\n blockTimeout?: number;\n /** Attempts before terminal dead-lettering (default 3). */\n maxTries?: number;\n /** The dead-letter list (default `<queue>.dlq`); set to `null` to drop on terminal failure. */\n deadLetterQueue?: string | null;\n /** Strategy for a URN with no handler (default `fail`). */\n unknownUrn?: string;\n /** Called for a non-conformant message, an unmapped URN, or a throwing handler. */\n onError?: (error: unknown, envelope: IncomingEnvelope | null, raw: string) => void;\n}\n\n/**\n * Consumes a Redis list: reserve the head into `<queue>:processing` (`BRPOPLPUSH`), decode +\n * validate, route to the handler for its URN, then `LREM` it from the reservation list on success.\n * A throwing handler requeues the envelope with `attempts + 1` (at-least-once) up to `maxTries`,\n * then dead-letters to `<queue>.dlq`. Redis has no native delivery count, so `attempts` lives in\n * the body and the runtime owns retry.\n */\nexport class RedisConsumer {\n private readonly processing: string;\n private readonly blockTimeout: number;\n private readonly maxTries: number;\n private readonly dlq: string | null;\n private readonly unknownUrn: string;\n\n constructor(\n private readonly client: RedisClient,\n private readonly queue: string,\n private readonly handlers: BabelHandlers,\n private readonly options: RedisConsumerOptions = {},\n ) {\n this.processing = queue + (options.processingSuffix ?? \":processing\");\n this.blockTimeout = options.blockTimeout ?? 5;\n this.maxTries = options.maxTries ?? 3;\n this.dlq = options.deadLetterQueue === undefined ? `${queue}.dlq` : options.deadLetterQueue;\n this.unknownUrn = options.unknownUrn ?? UnknownUrnStrategy.FAIL;\n }\n\n /** Reserve + route + settle one message. Returns true if one was handled, false on timeout. */\n async poll(): Promise<boolean> {\n const raw = await this.client.brpoplpush(this.queue, this.processing, this.blockTimeout);\n if (raw == null) return false;\n await this.handle(raw);\n return true;\n }\n\n /** Poll while `shouldContinue` returns true. */\n async run(shouldContinue: () => boolean): Promise<void> {\n while (shouldContinue()) {\n await this.poll();\n }\n }\n\n /**\n * Route + settle one reserved element. Exposed for testing.\n *\n * `raw` is the **stored** list value (the `LREM` ack handle); it may be a transport-owned header\n * frame or a bare envelope. It is unframed first so the envelope is decoded from the verbatim\n * wire body while the carried out-of-band headers (e.g. a `traceparent`, ADR-0028) are surfaced\n * to the handler. Settling/dead-lettering still operate on the original stored value.\n */\n async handle(raw: string): Promise<void> {\n const [body, headers] = unframe(raw);\n const decoded = EnvelopeCodec.decode(body);\n\n if (!EnvelopeCodec.accepts(decoded)) {\n this.report(new BabelQueueError(\"Rejected a non-conformant BabelQueue envelope from Redis.\"), decoded, raw);\n await this.deadLetterRaw(raw);\n await this.settle(raw);\n return;\n }\n\n const envelope = decoded as Envelope;\n const urn = EnvelopeCodec.urn(envelope);\n const handler = this.handlers[urn];\n if (!handler) {\n await this.onUnknownUrn(envelope, urn, raw);\n return;\n }\n\n try {\n await handler(envelope, raw, headers);\n await this.settle(raw);\n } catch (error) {\n this.report(error, envelope, raw);\n await this.retryOrDeadLetter(envelope, raw, error);\n }\n }\n\n private async onUnknownUrn(envelope: Envelope, urn: string, raw: string): Promise<void> {\n switch (this.unknownUrn) {\n case UnknownUrnStrategy.DELETE:\n await this.settle(raw);\n return;\n case UnknownUrnStrategy.DEAD_LETTER:\n await this.deadLetter(envelope, \"unknown_urn\");\n await this.settle(raw);\n return;\n case UnknownUrnStrategy.RELEASE:\n await this.requeue(envelope);\n await this.settle(raw);\n return;\n default: {\n // FAIL: surface and leave the element in the reservation list (recoverable).\n const error = new UnknownUrnError(urn);\n this.report(error, envelope, raw);\n throw error;\n }\n }\n }\n\n private async retryOrDeadLetter(envelope: Envelope, raw: string, error: unknown): Promise<void> {\n if ((envelope.attempts ?? 0) + 1 < this.maxTries) {\n await this.requeue(envelope);\n } else {\n await this.deadLetter(envelope, \"failed\", error);\n }\n await this.settle(raw);\n }\n\n /** Republish with `attempts + 1` onto the work list (at-least-once retry). */\n private async requeue(envelope: Envelope): Promise<void> {\n const bumped: Envelope = { ...envelope, attempts: (envelope.attempts ?? 0) + 1 };\n await this.client.rpush(this.queue, redisValue(bumped));\n }\n\n private async deadLetter(envelope: Envelope, reason: string, error?: unknown): Promise<void> {\n if (!this.dlq) return; // dead-lettering disabled → drop\n const annotated = annotate(envelope, reason, envelope.meta?.queue ?? this.queue, {\n attempts: envelope.attempts ?? 0,\n error: error instanceof Error ? error.message : null,\n exception: error instanceof Error ? error.name : null,\n });\n await this.client.rpush(this.dlq, redisValue(annotated));\n }\n\n private async deadLetterRaw(raw: string): Promise<void> {\n if (!this.dlq) return;\n await this.client.rpush(this.dlq, raw);\n }\n\n /** Remove the element from the reservation list (acknowledge). */\n private async settle(raw: string): Promise<void> {\n await this.client.lrem(this.processing, 1, raw);\n }\n\n private report(error: unknown, envelope: IncomingEnvelope | null, raw: string): void {\n this.options.onError?.(error, envelope, raw);\n }\n}\n","/**\n * Pure, dependency-free helpers for the out-of-band transport-header seam (ADR-0028) — the Node\n * adapter mirror of Go's `sanitizeHeaders` and PHP's `Support\\Headers`. They fold an injected\n * {@link HeaderCarrier} (e.g. a W3C `traceparent`) onto a transport's metadata channel **without\n * clobbering** the contract headers it already carries.\n *\n * Headers ride beside the frozen wire envelope (GR-1), never inside it.\n */\n\nimport type { HeaderCarrier } from \"@babelqueue/core\";\n\n/**\n * Copy `headers`, dropping blank keys and blank values. Returns an empty object when nothing\n * survives, so a caller can treat the result as \"no headers\" via `Object.keys(...).length === 0`.\n * A nullish input yields an empty object.\n */\nexport function sanitizeHeaders(headers: HeaderCarrier | null | undefined): HeaderCarrier {\n const out: HeaderCarrier = {};\n if (!headers) return out;\n for (const key of Object.keys(headers)) {\n const value = headers[key];\n if (key === \"\" || value == null || value === \"\") continue;\n out[key] = value;\n }\n return out;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/headers.ts","../src/poison-record.ts"],"sourcesContent":["/**\n * Redis adapter for BabelQueue.\n *\n * A canonical-envelope **publisher** and a URN-routed **consumer** over two Redis layouts\n * (broker-bindings §1, ADR-0033):\n *\n * - **`legacy`** (default) — the reliable-queue pattern: `RPUSH` to produce, `LMOVE` the head of\n * `<queue>` to `<queue>:processing` to reserve (a Lua script that also writes a recovery deadline\n * to `<queue>:processing:deadlines`), and a deadline-gated `LREM` to acknowledge, so an in-flight\n * message survives a crash. A **recovery sweep** runs before every reserve and moves a reservation\n * whose deadline (`retryAfter`) passed back onto `<queue>`, body untouched. The pop end changed\n * from the tail (`BRPOPLPUSH`) to the head (`LMOVE LEFT RIGHT`) so the queue is FIFO, like the Go,\n * Python, Java and .NET SDKs.\n * - **`zset`** — the Laravel-compatible layout (`queues:<q>`, `:delayed`, `:reserved`, `:notify`):\n * delayed publish, `retryAfter` recovery through the reserved sorted set, a Lua reserve that\n * increments `attempts`, and blocking pop on the `:notify` list. Opt-in via `layout: \"zset\"`.\n *\n * Redis lists carry **no native metadata** — the list element **is** the canonical envelope JSON,\n * byte-for-byte, with **no wrapping** (unlike `@babelqueue/bullmq`, which uses BullMQ's own job\n * layout). Routing and tracing read the body's `job` / `trace_id`.\n *\n * `ioredis` is an optional peer — you provide the client (an `ioredis` instance satisfies the\n * adapter structurally).\n *\n * **Out-of-band headers (ADR-0028) — legacy layout only.** A Redis list element *is* the canonical\n * envelope JSON, so there is no native per-message metadata channel. To carry a\n * {@link HeaderCarrier} (e.g. a W3C `traceparent`) the legacy layout owns a tiny JSON *frame*\n * distinct from the wire envelope:\n *\n * {\"__bq_frame\":1,\"headers\":{\"traceparent\":\"00-…\"},\"body\":\"<raw wire envelope>\"}\n *\n * Framing is **opt-in and backward compatible**: only `publish` with a non-empty `headers` carrier\n * writes a frame; a plain publish stores the **bare** envelope byte-for-byte. The consumer detects\n * frame-vs-bare by the reserved `__bq_frame` sentinel (a frozen envelope can never carry it).\n *\n * **The zset layout never writes a frame (ADR-0033 A3).** A Laravel worker on the same queue would\n * read the frame as a job, so a header-carrying publish drops the headers and stores the bare\n * envelope; a `traceparent` is therefore not propagated in this layout (`trace_id` stays in the\n * envelope, GR-4). GR-1: the wire envelope is never touched.\n */\n\nimport { randomBytes } from \"node:crypto\";\n\nimport { annotate, BabelQueueError, EnvelopeCodec, UnknownUrnError, UnknownUrnStrategy } from \"@babelqueue/core\";\nimport type { Envelope, HeaderCarrier, IncomingEnvelope } from \"@babelqueue/core\";\n\nimport { sanitizeHeaders } from \"./headers.js\";\nimport { buildPoisonRecord } from \"./poison-record.js\";\n\n// --- Minimal Redis shape (a structural subset of ioredis) ----------------------\n\n/** The subset of an ioredis client this adapter calls. */\nexport interface RedisClient {\n /** Append a value to the tail of a list; returns the new length. */\n rpush(key: string, value: string): Promise<number>;\n /** Add `member` to a sorted set with `score`. */\n zadd(key: string, score: number | string, member: string): Promise<number | string>;\n /** Blocking pop from the head of a list for up to `timeout` seconds (fractions need Redis 6+);\n * resolves to `[key, value]`, or `null` on timeout. */\n blpop(key: string, timeout: number): Promise<[string, string] | null>;\n /** Run a Lua script with `numKeys` keys followed by the arguments. */\n eval(script: string, numKeys: number, ...args: Array<string | number>): Promise<unknown>;\n /**\n * The binary-safe twin of {@link RedisClient.eval} (ioredis `evalBuffer`): string replies come back as\n * `Buffer`s and `Buffer` arguments are sent byte for byte. Optional for publishing, but required to consume\n * ({@link RedisQueue.reserve} / {@link RedisConsumer} throw without it): the reserve scripts and the settle\n * scripts of a body that is not valid UTF-8 use it so the exact stored bytes survive. Invalid UTF-8 is poison\n * and a poison record never carries a U+FFFD substitution (broker-bindings.md section 1.1, ADR-0035 section 1),\n * which a string-only `eval` cannot honour.\n */\n evalBuffer?(script: string, numKeys: number, ...args: Array<string | number | Buffer>): Promise<unknown>;\n}\n\n// --- Payload (contract §1) -----------------------------------------------------\n\n/** The list element for an envelope: the canonical JSON, verbatim — no wrapping (§1). */\nexport function redisValue(envelope: Envelope): string {\n return EnvelopeCodec.encode(envelope);\n}\n\n\n// --- Header frame (ADR-0028) ---------------------------------------------------\n\n/** The reserved discriminator key + current version of the transport-owned header frame. */\nexport const FRAME_KEY = \"__bq_frame\";\nconst FRAME_VERSION = 1;\n\n/** The transport-owned frame the list value carries when out-of-band headers accompany a message. */\ninterface HeaderFrame {\n __bq_frame: number;\n headers?: HeaderCarrier;\n body: string;\n}\n\n/**\n * The pure produce-side decision: the exact string to `RPUSH` for `body` + `headers`. With no\n * usable headers it returns `body` verbatim (the **bare** form, so a plain publish and a\n * header-less publish store byte-identical values); otherwise it returns the transport-owned frame\n * JSON. Kept pure so the framing decision is unit-testable without a broker.\n */\nexport function frameValue(body: string, headers: HeaderCarrier | null | undefined): string {\n const clean = sanitizeHeaders(headers);\n if (Object.keys(clean).length === 0) return body;\n const frame: HeaderFrame = { [FRAME_KEY]: FRAME_VERSION, headers: clean, body };\n return JSON.stringify(frame);\n}\n\n/**\n * Interpret a stored Redis list value: `[wire-envelope-body, headers]`. A value is a header frame\n * iff it is a JSON object carrying the reserved `__bq_frame` sentinel (a frozen wire envelope never\n * has it); then it yields the unframed body plus the carried headers. Any other value — a bare\n * envelope, non-JSON, or JSON without the sentinel — is returned verbatim as the body with empty\n * headers, so older / cross-version queue values consume exactly as before.\n */\nexport function unframe(value: string): [string, HeaderCarrier] {\n // Cheap reject: a frame is always a JSON object, and the sentinel substring must appear. This\n // avoids a full parse for the overwhelmingly common bare-envelope case (only short-circuits\n // negatives).\n if (value === \"\" || value[0] !== \"{\" || !value.includes(`\"${FRAME_KEY}\"`)) {\n return [value, {}];\n }\n let decoded: unknown;\n try {\n decoded = JSON.parse(value);\n } catch {\n return [value, {}];\n }\n if (\n decoded == null ||\n typeof decoded !== \"object\" ||\n !(FRAME_KEY in decoded) ||\n !(decoded as HeaderFrame)[FRAME_KEY] ||\n typeof (decoded as HeaderFrame).body !== \"string\"\n ) {\n return [value, {}];\n }\n const frame = decoded as HeaderFrame;\n return [frame.body, sanitizeHeaders(frame.headers)];\n}\n\n/** The out-of-band headers carried by a stored list value (empty for a bare value). */\nexport function headersOf(value: string): HeaderCarrier {\n return unframe(value)[1];\n}\n\n\n// --- Layouts, keys and Lua (ADR-0033) -------------------------------------------\n\n/** The Redis layout a queue uses. `legacy` is the default `:processing` reliable queue. */\nexport type RedisLayout = \"legacy\" | \"zset\";\n\n/** The four keys of the Laravel-compatible zset layout. */\nexport interface ZsetKeys {\n ready: string;\n delayed: string;\n reserved: string;\n notify: string;\n}\n\n/** The default key prefix of the zset layout (Laravel's `queues:`). */\nexport const ZSET_KEY_PREFIX = \"queues:\";\n\n/**\n * The zset-layout keys for `queue`: `queues:<q>`, `:delayed`, `:reserved`, `:notify`. In a Redis\n * Cluster, hash tags (`{orders}`) are the caller's responsibility (put them in `queue`).\n */\nexport function zsetKeys(queue: string, prefix: string = ZSET_KEY_PREFIX): ZsetKeys {\n const ready = `${prefix}${queue}`;\n return { ready, delayed: `${ready}:delayed`, reserved: `${ready}:reserved`, notify: `${ready}:notify` };\n}\n\n/**\n * The Lua scripts, exported so a test double can dispatch `eval` on script identity. Every script\n * is atomic; none of them rewrites a body except the zset reserve (Laravel's `cjson` increment).\n */\nexport const LUA = {\n /** KEYS=[q, q:processing, q:processing:deadlines] ARGV=[deadline, token]. Returns the raw body or nil. */\n legacyReserve: `\nlocal raw = redis.call('LMOVE', KEYS[1], KEYS[2], 'LEFT', 'RIGHT')\nif not raw then return false end\nredis.call('ZADD', KEYS[3], ARGV[1], ARGV[2] .. ':' .. raw)\nreturn raw\n`,\n /** KEYS=[q, q:processing, q:processing:deadlines] ARGV=[floor(now), batch]. Returns the count moved. */\n legacySweep: `\nlocal batch = tonumber(ARGV[2])\nlocal members\nif batch > 0 then\n members = redis.call('ZRANGEBYSCORE', KEYS[3], '-inf', ARGV[1], 'LIMIT', 0, batch)\nelse\n members = redis.call('ZRANGEBYSCORE', KEYS[3], '-inf', ARGV[1])\nend\nlocal moved = 0\nfor _, m in ipairs(members) do\n if #m >= 34 and string.sub(m, 33, 33) == ':' then\n local raw = string.sub(m, 34)\n if redis.call('LREM', KEYS[2], 1, raw) == 1 then\n redis.call('RPUSH', KEYS[1], raw)\n moved = moved + 1\n end\n end\n redis.call('ZREM', KEYS[3], m)\nend\nreturn moved\n`,\n /** KEYS=[q:processing, q:processing:deadlines] ARGV=[token, raw]. 1 if the reservation was live. */\n legacyAck: `\nif redis.call('ZREM', KEYS[2], ARGV[1] .. ':' .. ARGV[2]) == 1 then\n redis.call('LREM', KEYS[1], 1, ARGV[2])\n return 1\nend\nreturn 0\n`,\n /** KEYS=[q, q:processing, q:processing:deadlines] ARGV=[token, raw, body]. */\n legacyRelease: `\nif redis.call('ZREM', KEYS[3], ARGV[1] .. ':' .. ARGV[2]) == 1 then\n redis.call('LREM', KEYS[2], 1, ARGV[2])\n redis.call('RPUSH', KEYS[1], ARGV[3])\n return 1\nend\nreturn 0\n`,\n /** KEYS=[q:processing, q:processing:deadlines, dlq] ARGV=[token, raw, body]. */\n legacyDeadLetter: `\nif redis.call('ZREM', KEYS[2], ARGV[1] .. ':' .. ARGV[2]) == 1 then\n redis.call('LREM', KEYS[1], 1, ARGV[2])\n redis.call('RPUSH', KEYS[3], ARGV[3])\n return 1\nend\nreturn 0\n`,\n /**\n * KEYS=[q, q:reserved, q:notify] ARGV=[expiry]. Returns `{job, reserved, flag}` or nil; flag `'1'`\n * is poison (`reserved == job`), `'0'` a delivered body. Nothing after the LPOP may raise, so the\n * re-encode is guarded too: lua-cjson decodes `1e999` / `NaN` / `Infinity` but refuses to encode\n * them, and a bare `cjson.encode` here would lose the already-popped message (broker-bindings\n * section 1.1, ADR-0033 A1). An unencodable body takes the poison path.\n */\n zsetReserve: `\nlocal job = redis.call('LPOP', KEYS[1])\nif not job then return false end\nlocal ok, decoded = pcall(cjson.decode, job)\nif ok and type(decoded) == 'table' and type(decoded['attempts']) == 'number' then\n decoded['attempts'] = decoded['attempts'] + 1\n local eok, reserved = pcall(cjson.encode, decoded)\n if eok and type(reserved) == 'string' then\n redis.call('ZADD', KEYS[2], ARGV[1], reserved)\n redis.call('LPOP', KEYS[3])\n return {job, reserved, '0'}\n end\nend\nredis.call('ZADD', KEYS[2], ARGV[1], job)\nreturn {job, job, '1'}\n`,\n /** KEYS=[source zset, q, q:notify] ARGV=[floor(now), batch]. Returns the count moved. */\n zsetMigrate: `\nlocal batch = tonumber(ARGV[2])\nlocal val\nif batch > 0 then\n val = redis.call('ZRANGEBYSCORE', KEYS[1], '-inf', ARGV[1], 'LIMIT', 0, batch)\nelse\n val = redis.call('ZRANGEBYSCORE', KEYS[1], '-inf', ARGV[1])\nend\nif #val > 0 then\n redis.call('ZREMRANGEBYRANK', KEYS[1], 0, #val - 1)\n for i = 1, #val, 100 do\n redis.call('RPUSH', KEYS[2], unpack(val, i, math.min(i + 99, #val)))\n end\n for _ = 1, #val do\n redis.call('RPUSH', KEYS[3], '1')\n end\nend\nreturn #val\n`,\n /** KEYS=[q, q:notify] ARGV=[body]. */\n zsetPush: `\nredis.call('RPUSH', KEYS[1], ARGV[1])\nredis.call('RPUSH', KEYS[2], '1')\nreturn 1\n`,\n /** KEYS=[q:reserved] ARGV=[member]. */\n zsetAck: `\nreturn redis.call('ZREM', KEYS[1], ARGV[1])\n`,\n /** KEYS=[q:delayed, q:reserved] ARGV=[member, score, body]. The ZADD is unconditional. */\n zsetRelease: `\nredis.call('ZREM', KEYS[2], ARGV[1])\nredis.call('ZADD', KEYS[1], ARGV[2], ARGV[3])\nreturn 1\n`,\n /**\n * KEYS=[q:reserved, dlq] ARGV=[member, body]. Terminal-failure dead-letter, gated on the ZREM like\n * the legacy scripts: a late dead-letter whose reservation was already recovered (back on `<q>`)\n * writes nothing, so the body is never both ready and dead-lettered. A poison reservation uses\n * `zsetDeadLetterPoison` instead (unconditional, as the contract requires).\n */\n zsetDeadLetter: `\nif redis.call('ZREM', KEYS[1], ARGV[1]) == 1 then\n redis.call('RPUSH', KEYS[2], ARGV[2])\n return 1\nend\nreturn 0\n`,\n /**\n * KEYS=[q:reserved, dlq] ARGV=[member, body]. The poison dead-letter (broker-bindings section 1.1):\n * the SDK MUST write the message to the DLQ and `ZREM` it, unconditionally. Two byte-equal poison\n * bodies reserved together share one `:reserved` member, so a `ZREM` gate would drop the second\n * copy without ever writing it.\n */\n zsetDeadLetterPoison: `\nredis.call('RPUSH', KEYS[2], ARGV[2])\nredis.call('ZREM', KEYS[1], ARGV[1])\nreturn 1\n`,\n} as const;\n\n// --- Transport -----------------------------------------------------------------\n\n/** Options for {@link RedisQueue} (and, through it, {@link RedisPublisher} / {@link RedisConsumer}). */\nexport interface RedisQueueOptions {\n /** `\"legacy\"` (default, the `:processing` reliable queue) or `\"zset\"` (Laravel-compatible). */\n layout?: RedisLayout;\n /**\n * Seconds before an unacknowledged reservation is recovered (default 90). `null` disables\n * recovery (the legacy deadline member is written with score `+inf`; the zset `:reserved`\n * migrate is skipped). A negative or non-finite value is a configuration error. In the zset\n * layout a recovery counts as an attempt. The handler timeout SHOULD be shorter than this.\n */\n retryAfter?: number | null;\n /** Max items one migrate/sweep moves; `-1` (default) or `0` means no limit. */\n migrationBatchSize?: number;\n /** Epoch-seconds clock (fractions allowed); injectable for tests. Default `Date.now() / 1000`. */\n clock?: () => number;\n /** Zset key prefix (default `\"queues:\"`). Ignored by the legacy layout. */\n keyPrefix?: string;\n /** Suffix for the legacy reservation list (default `\":processing\"`). */\n processingSuffix?: string;\n /** The dead-letter list (default `<queue>.dlq`); `null` drops on terminal failure. */\n deadLetterQueue?: string | null;\n /** Receives the \"reservation expired\" warning (default `console.warn`). */\n onWarn?: (message: string) => void;\n}\n\n/** A legacy reservation: the raw body plus the 32-hex token of its deadline member. */\nexport interface LegacyReservation {\n layout: \"legacy\";\n raw: string;\n token: string;\n /**\n * The exact stored bytes, set only when they are not valid UTF-8 (so `raw` carries U+FFFD substitutions).\n * The settle scripts and the poison record use these, never the lossy `raw`.\n */\n rawBytes?: Buffer;\n}\n\n/** A zset reservation: the raw body (what the handler sees) and the exact `:reserved` member. */\nexport interface ZsetReservation {\n layout: \"zset\";\n raw: string;\n reserved: string;\n /** The exact stored bytes of the job / `:reserved` member, set only when they are not valid UTF-8. */\n rawBytes?: Buffer;\n reservedBytes?: Buffer;\n /**\n * Set when the reserve script flagged the body as poison (undecodable, or `attempts` not a\n * number): its dead-letter is then unconditional, not gated on the `ZREM`.\n */\n poison?: boolean;\n}\n\n/** Whatever a reserve handed out; pass it back to ack / release / deadLetter. */\nexport type Reservation = LegacyReservation | ZsetReservation;\n\n/** The outcome of one non-blocking {@link RedisQueue.reserve}. */\nexport interface ReserveResult {\n outcome: \"delivered\" | \"empty\" | \"poison\";\n /** The raw body the handler sees (absent on `empty`). */\n raw?: string;\n /** The reservation handle (absent on `empty`). */\n reservation?: Reservation;\n /** Zset only: the delivery count the handler is told (`job.attempts + 1`); absent on poison. */\n attempts?: number;\n}\n\n/**\n * A reply value as `[text, bytes]`. `bytes` is set only when the value is a `Buffer` that is not valid UTF-8,\n * i.e. when `text` would carry U+FFFD substitutions; valid UTF-8 needs no side copy.\n */\nfunction readValue(value: unknown): [string, Buffer | undefined] {\n if (!Buffer.isBuffer(value)) return [String(value), undefined];\n const text = value.toString(\"utf8\");\n return [text, Buffer.from(text, \"utf8\").equals(value) ? undefined : value];\n}\n\n/** The exact stored bytes of a reservation's value, as the settle scripts must compare them. */\nfunction storedValue(reservation: Reservation): string | Buffer {\n return reservation.rawBytes ?? reservation.raw;\n}\n\n/** The exact `:reserved` member of a zset reservation. */\nfunction zsetMember(reservation: ZsetReservation): string | Buffer {\n return reservation.reservedBytes ?? reservation.reserved;\n}\n\n/** A fresh 128-bit reservation token: 32 lowercase hex characters from the OS CSPRNG. */\nexport function newReservationToken(): string {\n return randomBytes(16).toString(\"hex\");\n}\n\nfunction validateRetryAfter(value: number | null | undefined): number | null {\n if (value === undefined) return 90;\n if (value === null) return null;\n if (typeof value !== \"number\" || !Number.isFinite(value) || value < 0) {\n throw new BabelQueueError(`Invalid retryAfter ${String(value)}: must be a non-negative number of seconds, or null.`);\n }\n return value;\n}\n\n/**\n * The Redis operations behind the publisher and the consumer, one set per layout. The methods are\n * non-blocking and take their time from the injectable clock, so they can be driven step by step\n * (the conformance suite does exactly that).\n */\nexport class RedisQueue {\n readonly layout: RedisLayout;\n readonly retryAfter: number | null;\n readonly migrationBatchSize: number;\n readonly deadLetterKey: string | null;\n /** The zset keys (zset layout) — also usable to inspect the legacy ready list via `ready`. */\n readonly keys: ZsetKeys;\n readonly processingKey: string;\n readonly deadlinesKey: string;\n\n private readonly clock: () => number;\n private readonly warn: (message: string) => void;\n\n constructor(\n private readonly client: RedisClient,\n readonly queue: string,\n options: RedisQueueOptions = {},\n ) {\n this.layout = options.layout ?? \"legacy\";\n if (this.layout !== \"legacy\" && this.layout !== \"zset\") {\n throw new BabelQueueError(`Unknown Redis layout ${String(this.layout)}: expected \"legacy\" or \"zset\".`);\n }\n this.retryAfter = validateRetryAfter(options.retryAfter);\n const batch = options.migrationBatchSize ?? -1;\n this.migrationBatchSize = Number.isFinite(batch) && batch > 0 ? Math.floor(batch) : -1;\n this.clock = options.clock ?? (() => Date.now() / 1000);\n this.warn = options.onWarn ?? ((message) => console.warn(message));\n this.deadLetterKey = options.deadLetterQueue === undefined ? `${queue}.dlq` : options.deadLetterQueue;\n this.keys = this.layout === \"zset\" ? zsetKeys(queue, options.keyPrefix) : { ready: queue, delayed: \"\", reserved: \"\", notify: \"\" };\n this.processingKey = queue + (options.processingSuffix ?? \":processing\");\n this.deadlinesKey = `${this.processingKey}:deadlines`;\n }\n\n /**\n * Run a script. `binary` (or any `Buffer` argument) selects the client's `evalBuffer`, when it has one,\n * so replies and arguments are exact bytes.\n */\n private run(script: string, keys: string[], args: Array<string | number | Buffer>, binary = false): Promise<unknown> {\n const useBuffer = (binary || args.some((a) => Buffer.isBuffer(a))) && this.client.evalBuffer !== undefined;\n if (useBuffer) return this.client.evalBuffer!(script, keys.length, ...keys, ...args);\n return this.client.eval(script, keys.length, ...keys, ...(args as Array<string | number>));\n }\n\n private nowSeconds(): number {\n return Math.floor(this.clock());\n }\n\n private deadlineScore(): string {\n return this.retryAfter === null ? \"+inf\" : String(this.nowSeconds() + this.retryAfter);\n }\n\n /** Append a body to the ready list (zset: also one `:notify` token). */\n async push(body: string): Promise<void> {\n if (this.layout === \"zset\") {\n await this.run(LUA.zsetPush, [this.keys.ready, this.keys.notify], [body]);\n } else {\n await this.client.rpush(this.queue, body);\n }\n }\n\n /**\n * Schedule a body for `delay` seconds from now: `ZADD <q>:delayed floor(now)+ceil(delay)` with\n * the body byte-for-byte and no `:notify` token. Zset layout only.\n */\n async pushDelayed(body: string, delay: number): Promise<void> {\n if (this.layout !== \"zset\") {\n throw new BabelQueueError(\"A delayed publish requires layout \\\"zset\\\"; the legacy layout has no delayed set.\");\n }\n if (typeof delay !== \"number\" || !Number.isFinite(delay) || delay < 0) {\n throw new BabelQueueError(`Invalid delay ${String(delay)}: must be a non-negative number of seconds.`);\n }\n await this.client.zadd(this.keys.delayed, this.nowSeconds() + Math.ceil(delay), body);\n }\n\n /** Zset: move due `:delayed` items, then due `:reserved` ones (skipped if `retryAfter` is null). */\n async migrate(): Promise<number> {\n if (this.layout !== \"zset\") return 0;\n let moved = await this.migrateFrom(this.keys.delayed);\n if (this.retryAfter !== null) moved += await this.migrateFrom(this.keys.reserved);\n return moved;\n }\n\n private async migrateFrom(source: string): Promise<number> {\n const moved = await this.run(\n LUA.zsetMigrate,\n [source, this.keys.ready, this.keys.notify],\n [this.nowSeconds(), this.migrationBatchSize],\n );\n return Number(moved);\n }\n\n /** Legacy: move expired reservations back onto the ready list (body untouched). */\n async sweep(): Promise<number> {\n if (this.layout !== \"legacy\") return 0;\n const moved = await this.run(\n LUA.legacySweep,\n [this.queue, this.processingKey, this.deadlinesKey],\n [this.nowSeconds(), this.migrationBatchSize],\n );\n return Number(moved);\n }\n\n /** Recovery and migration for the layout; runs before every reserve. */\n async maintain(): Promise<number> {\n return this.layout === \"zset\" ? this.migrate() : this.sweep();\n }\n\n /** One non-blocking reserve, preceded by the layout's migrate / sweep. */\n async reserve(): Promise<ReserveResult> {\n // Checked before anything is popped, so nothing is lost: a string-only client would turn invalid UTF-8\n // into U+FFFD and hand a lossy body to a handler, which broker-bindings.md section 1.1 forbids.\n if (this.client.evalBuffer === undefined) {\n throw new BabelQueueError(\n \"The Redis client must provide evalBuffer (ioredis does) to consume: invalid UTF-8 is poison and its exact bytes must be read (broker-bindings.md section 1.1).\",\n );\n }\n await this.maintain();\n return this.layout === \"zset\" ? this.reserveZset() : this.reserveLegacy();\n }\n\n private async reserveLegacy(): Promise<ReserveResult> {\n const token = newReservationToken();\n const reply = await this.run(\n LUA.legacyReserve,\n [this.queue, this.processingKey, this.deadlinesKey],\n [this.deadlineScore(), token],\n true,\n );\n if (typeof reply !== \"string\" && !Buffer.isBuffer(reply)) return { outcome: \"empty\" };\n const [raw, rawBytes] = readValue(reply);\n const reservation: LegacyReservation = { layout: \"legacy\", raw, token };\n if (rawBytes) reservation.rawBytes = rawBytes;\n return { outcome: \"delivered\", raw, reservation };\n }\n\n private async reserveZset(): Promise<ReserveResult> {\n const reply = await this.run(\n LUA.zsetReserve,\n [this.keys.ready, this.keys.reserved, this.keys.notify],\n [this.deadlineScore()],\n true,\n );\n if (!Array.isArray(reply) || reply.length < 2) return { outcome: \"empty\" };\n const [raw, rawBytes] = readValue(reply[0]);\n const [reserved, reservedBytes] = readValue(reply[1]);\n const reservation: ZsetReservation = { layout: \"zset\", raw, reserved };\n if (rawBytes) reservation.rawBytes = rawBytes;\n if (reservedBytes) reservation.reservedBytes = reservedBytes;\n // The script's third element is the authoritative poison flag; a two-element reply (a test\n // double) falls back to the `reserved == job` signal.\n const poison = reply.length >= 3 ? readValue(reply[2])[0] === \"1\" : raw === reserved;\n if (poison) return { outcome: \"poison\", raw, reservation: { ...reservation, poison: true } };\n return { outcome: \"delivered\", raw, reservation, attempts: attemptsOf(raw) + 1 };\n }\n\n /** Acknowledge. Legacy: false (plus a warning) if the reservation had already expired. */\n async ack(reservation: Reservation): Promise<boolean> {\n if (reservation.layout === \"zset\") {\n return Number(await this.run(LUA.zsetAck, [this.keys.reserved], [zsetMember(reservation)])) === 1;\n }\n const live = await this.run(LUA.legacyAck, [this.processingKey, this.deadlinesKey], [reservation.token, storedValue(reservation)]);\n return this.liveOrWarn(live, reservation);\n }\n\n /**\n * Give a reservation back. Legacy: the body (default: `raw`) goes to the ready list, gated on the\n * deadline member; `delay` is ignored. Zset: one script `ZREM`s the `:reserved` member and `ZADD`s\n * `<q>:delayed` at `floor(now)+ceil(delay)` with the body (default: `raw` re-encoded with\n * `attempts + 1`, every other key preserved).\n */\n async release(reservation: Reservation, delay = 0, body?: string): Promise<boolean> {\n if (reservation.layout === \"zset\") {\n if (typeof delay !== \"number\" || !Number.isFinite(delay) || delay < 0) {\n throw new BabelQueueError(`Invalid delay ${String(delay)}: must be a non-negative number of seconds.`);\n }\n const bumped = body ?? bumpAttempts(reservation.raw);\n await this.run(\n LUA.zsetRelease,\n [this.keys.delayed, this.keys.reserved],\n [zsetMember(reservation), this.nowSeconds() + Math.ceil(delay), bumped],\n );\n return true;\n }\n const live = await this.run(\n LUA.legacyRelease,\n [this.queue, this.processingKey, this.deadlinesKey],\n [reservation.token, storedValue(reservation), body ?? storedValue(reservation)],\n );\n return this.liveOrWarn(live, reservation);\n }\n\n /** Write `body` (default: `raw`) to the dead-letter list and settle the reservation. */\n async deadLetter(reservation: Reservation, body?: string): Promise<boolean> {\n const payload = body ?? reservation.raw;\n if (this.deadLetterKey === null) return this.ack(reservation);\n if (reservation.layout === \"zset\" && reservation.poison === true) {\n // Poison (section 1.1): always write to the DLQ and ZREM, even when a byte-equal twin already\n // removed the shared `:reserved` member.\n await this.run(\n LUA.zsetDeadLetterPoison,\n [this.keys.reserved, this.deadLetterKey],\n [zsetMember(reservation), payload],\n );\n return true;\n }\n if (reservation.layout === \"zset\") {\n const settled = await this.run(\n LUA.zsetDeadLetter,\n [this.keys.reserved, this.deadLetterKey],\n [zsetMember(reservation), payload],\n );\n if (Number(settled) === 1) return true;\n this.warn(\n `reservation expired: queue \"${this.queue}\" reservation was already recovered or settled; nothing dead-lettered`,\n );\n return false;\n }\n const live = await this.run(\n LUA.legacyDeadLetter,\n [this.processingKey, this.deadlinesKey, this.deadLetterKey],\n [reservation.token, storedValue(reservation), payload],\n );\n return this.liveOrWarn(live, reservation);\n }\n\n private liveOrWarn(reply: unknown, reservation: LegacyReservation): boolean {\n if (Number(reply) === 1) return true;\n this.warn(\n `reservation expired: queue \"${this.queue}\" token ${reservation.token} was already swept or settled; left the queue untouched`,\n );\n return false;\n }\n}\n\n/** `job.attempts` of a raw body, 0 when absent or unreadable. */\nfunction attemptsOf(raw: string): number {\n try {\n const decoded: unknown = JSON.parse(raw);\n const attempts = (decoded as { attempts?: unknown } | null)?.attempts;\n return typeof attempts === \"number\" ? attempts : 0;\n } catch {\n return 0;\n }\n}\n\n/**\n * The raw body with `attempts` raised by one and every other byte untouched (ADR-0031): the zset\n * release / recovery body. Only the `attempts` value is spliced, so unknown keys keep their exact\n * text, including integers above 2^53 that a `JSON.parse` / `JSON.stringify` round trip would round.\n * Falls back to the raw value when it is not a JSON object.\n */\nfunction bumpAttempts(raw: string): string {\n let decoded: unknown;\n try {\n decoded = JSON.parse(raw);\n } catch {\n return raw;\n }\n if (decoded === null || typeof decoded !== \"object\" || Array.isArray(decoded)) return raw;\n\n const scan = scanTopLevel(raw);\n const spans = scan.members.filter((member) => member.key === \"attempts\");\n const last = spans[spans.length - 1];\n const current = last === undefined ? null : raw.slice(last.start, last.end);\n const next = current !== null && NUMBER_LITERAL.test(current) ? incrementNumberLiteral(current) : \"1\";\n if (last === undefined) {\n const member = `\"attempts\":${next}`;\n return `${raw.slice(0, scan.close)}${scan.members.length > 0 ? \",\" : \"\"}${member}${raw.slice(scan.close)}`;\n }\n let result = raw;\n for (const span of [...spans].reverse()) result = `${result.slice(0, span.start)}${next}${result.slice(span.end)}`;\n return result;\n}\n\nconst NUMBER_LITERAL = /^-?\\d+(?:\\.\\d+)?(?:[eE][+-]?\\d+)?$/;\n\n/** `literal + 1`, exact for an integer literal of any size and a plain `Number` sum otherwise. */\nfunction incrementNumberLiteral(literal: string): string {\n if (/^-?\\d+$/.test(literal)) return (BigInt(literal) + 1n).toString();\n return String(Number(literal) + 1);\n}\n\ninterface TopLevelScan {\n /** The key and the `[start, end)` span of the value text of each top-level member, in order. */\n members: Array<{ key: string; start: number; end: number }>;\n /** Index of the closing `}` of the top-level object. */\n close: number;\n}\n\n/** Locate the top-level members of an already-validated JSON object text (no re-serialisation). */\nfunction scanTopLevel(text: string): TopLevelScan {\n const members: TopLevelScan[\"members\"] = [];\n const skipSpace = (from: number): number => {\n let i = from;\n while (i < text.length && \" \\t\\n\\r\".includes(text.charAt(i))) i++;\n return i;\n };\n const stringEnd = (from: number): number => {\n let i = from + 1;\n while (text.charAt(i) !== '\"') i += text.charAt(i) === \"\\\\\" ? 2 : 1;\n return i + 1;\n };\n let i = skipSpace(text.indexOf(\"{\") + 1);\n while (text.charAt(i) !== \"}\") {\n if (text.charAt(i) === \",\") i = skipSpace(i + 1);\n const keyEnd = stringEnd(i);\n const key = JSON.parse(text.slice(i, keyEnd)) as string;\n i = skipSpace(skipSpace(keyEnd) + 1);\n const start = i;\n let depth = 0;\n while (i < text.length) {\n const c = text.charAt(i);\n if (c === '\"') {\n i = stringEnd(i);\n continue;\n }\n if (c === \"{\" || c === \"[\") depth++;\n else if (c === \"}\" || c === \"]\") {\n if (depth === 0) break;\n depth--;\n } else if (c === \",\" && depth === 0) break;\n i++;\n }\n let end = i;\n while (end > start && \" \\t\\n\\r\".includes(text.charAt(end - 1))) end--;\n members.push({ key, start, end });\n i = skipSpace(i);\n }\n return { members, close: i };\n}\n\n// --- Publisher -----------------------------------------------------------------\n\n/** Options for {@link RedisPublisher.publish}. */\nexport interface PublishOptions {\n traceId?: string;\n /**\n * Out-of-band transport headers to carry beside the frozen envelope (ADR-0028) — e.g. a W3C\n * `traceparent`. Legacy layout: a non-empty carrier RPUSHes a transport-owned frame; an\n * empty/omitted carrier stays a byte-identical bare publish. **Zset layout: dropped** (ADR-0033\n * A3) — the bare envelope is written and the `traceparent` is not propagated.\n */\n headers?: HeaderCarrier;\n /**\n * Deliver after `delay` seconds (`ZADD <q>:delayed floor(now)+ceil(delay)`, no `:notify` token).\n * Requires `layout: \"zset\"`; the legacy layout throws {@link BabelQueueError}.\n */\n delay?: number;\n}\n\n/** Sends canonical-envelope messages to one Redis queue. */\nexport class RedisPublisher {\n private readonly transport: RedisQueue;\n\n private constructor(\n client: RedisClient,\n private readonly queue: string,\n options: RedisQueueOptions,\n ) {\n this.transport = new RedisQueue(client, queue, options);\n }\n\n /** A publisher over `client`, producing to the queue named `queue`. */\n static create(client: RedisClient, queue: string, options: RedisQueueOptions = {}): RedisPublisher {\n return new RedisPublisher(client, queue, options);\n }\n\n /**\n * Build + push the canonical envelope; returns the message id (`meta.id`). Legacy layout: a\n * non-empty `options.headers` pushes a transport-owned frame (ADR-0028), otherwise the bare\n * envelope byte-for-byte. Zset layout: always the bare envelope (A3); with `options.delay > 0`\n * it goes to `<q>:delayed` instead of the ready list.\n */\n async publish(urn: string, data: Record<string, unknown>, options: PublishOptions = {}): Promise<string> {\n const delay = options.delay ?? 0;\n if (typeof delay !== \"number\" || !Number.isFinite(delay) || delay < 0) {\n throw new BabelQueueError(`Invalid delay ${String(delay)}: must be a non-negative number of seconds.`);\n }\n if (delay > 0 && this.transport.layout !== \"zset\") {\n throw new BabelQueueError(\"A delayed publish requires layout \\\"zset\\\"; the legacy layout has no delayed set.\");\n }\n const envelope = EnvelopeCodec.make(urn, data, { queue: this.queue, traceId: options.traceId });\n const body = redisValue(envelope);\n if (this.transport.layout === \"zset\") {\n if (delay > 0) await this.transport.pushDelayed(body, delay);\n else await this.transport.push(body);\n } else {\n await this.transport.push(frameValue(body, options.headers));\n }\n return envelope.meta.id;\n }\n}\n\n// --- Consumer ------------------------------------------------------------------\n\n/**\n * A URN handler. Receives the validated envelope, the raw stored list element, and the out-of-band\n * {@link HeaderCarrier} carried beside it (empty for a bare value and always empty in the zset\n * layout). Pass `headers` to `@babelqueue/core/otel`'s `wrapHandler` to link the consumer span as a\n * child of the producer span (ADR-0028).\n */\nexport type BabelHandler = (envelope: Envelope, raw: string, headers: HeaderCarrier) => unknown | Promise<unknown>;\n\n/** A map of URN → handler. */\nexport type BabelHandlers = Record<string, BabelHandler>;\n\n/** Options for {@link RedisConsumer}. */\nexport interface RedisConsumerOptions extends RedisQueueOptions {\n /**\n * How long one {@link RedisConsumer.poll} waits for a message, in seconds (default 5). Legacy:\n * a sleep-poll (100 ms, doubling, capped at 1 s). Zset: `BLPOP` on `:notify` in slices of at most\n * 1 s, migrating between slices. `0` or less means one non-blocking attempt.\n */\n blockTimeout?: number;\n /** Attempts before terminal dead-lettering (default 3). */\n maxTries?: number;\n /** Strategy for a URN with no handler (default `fail`). */\n unknownUrn?: string;\n /** Called for a non-conformant message, an unmapped URN, or a throwing handler. */\n onError?: (error: unknown, envelope: IncomingEnvelope | null, raw: string) => void;\n}\n\nconst SLEEP_START_MS = 100;\nconst SLEEP_CAP_MS = 1000;\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Consumes a Redis queue: reserve one message (legacy: `LMOVE` into `<queue>:processing` with a\n * deadline; zset: the Laravel pop), decode + validate, route to the handler for its URN, then\n * acknowledge. A throwing handler gives the message back with `attempts + 1` (at-least-once) up to\n * `maxTries`, then dead-letters to `<queue>.dlq`. Redis has no native delivery count, so `attempts`\n * lives in the body and the runtime owns retry.\n */\nexport class RedisConsumer {\n private readonly transport: RedisQueue;\n private readonly blockTimeout: number;\n private readonly maxTries: number;\n private readonly unknownUrn: string;\n\n constructor(\n private readonly client: RedisClient,\n private readonly queue: string,\n private readonly handlers: BabelHandlers,\n private readonly options: RedisConsumerOptions = {},\n ) {\n this.transport = new RedisQueue(client, queue, options);\n this.blockTimeout = options.blockTimeout ?? 5;\n this.maxTries = options.maxTries ?? 3;\n this.unknownUrn = options.unknownUrn ?? UnknownUrnStrategy.FAIL;\n }\n\n /** The underlying transport (for diagnostics and tests). */\n get redisQueue(): RedisQueue {\n return this.transport;\n }\n\n /** Reserve + route + settle one message. Returns true if one was handled, false on timeout. */\n async poll(): Promise<boolean> {\n const startedAt = Date.now();\n let wait = SLEEP_START_MS;\n for (;;) {\n const result = await this.transport.reserve();\n if (result.outcome !== \"empty\") {\n await this.dispatch(result);\n return true;\n }\n const remainingMs = this.blockTimeout * 1000 - (Date.now() - startedAt);\n if (remainingMs <= 0) return false;\n if (this.transport.layout === \"zset\") {\n // Wake on a :notify token; migrate again after at most one second (ADR-0033).\n await this.client.blpop(this.transport.keys.notify, Math.min(1, remainingMs / 1000));\n } else {\n await sleep(Math.min(wait, remainingMs));\n wait = Math.min(wait * 2, SLEEP_CAP_MS);\n }\n }\n }\n\n /** Poll while `shouldContinue` returns true. */\n async run(shouldContinue: () => boolean): Promise<void> {\n while (shouldContinue()) {\n await this.poll();\n }\n }\n\n private async dispatch(result: ReserveResult): Promise<void> {\n const reservation = result.reservation as Reservation;\n // `rawBytes` is only set for a stored value that is not valid UTF-8: poison even when cjson could decode it\n // (broker-bindings section 1.1, \"Invalid UTF-8\"). It is never delivered, retried or re-published.\n if (result.outcome === \"poison\" || reservation.rawBytes !== undefined) {\n // Undecodable body, non-numeric attempts (A1) or invalid UTF-8: dead-letter it as a poison record\n // (ADR-0035) and leave no residue.\n const error = new BabelQueueError(\"Rejected an undecodable BabelQueue message from Redis.\");\n this.report(error, null, reservation.raw);\n await this.transport.deadLetter(reservation, this.poisonRecord(reservation, error));\n return;\n }\n await this.handle(reservation);\n }\n\n /**\n * Route + settle one reserved message. Exposed for testing.\n *\n * Legacy layout: `reservation.raw` may be a transport-owned header frame or a bare envelope; it is\n * unframed first so the envelope is decoded from the verbatim wire body while the carried headers\n * (e.g. a `traceparent`, ADR-0028) reach the handler. The zset layout is never framed (A3).\n */\n async handle(reservation: Reservation): Promise<void> {\n const raw = reservation.raw;\n if (reservation.rawBytes !== undefined) {\n // Invalid UTF-8 is poison (broker-bindings section 1.1): never decoded, handled or re-published.\n const error = new BabelQueueError(\"Rejected a non-UTF-8 BabelQueue message from Redis.\");\n this.report(error, null, raw);\n await this.transport.deadLetter(reservation, this.poisonRecord(reservation, error));\n return;\n }\n const [body, headers] = reservation.layout === \"legacy\" ? unframe(raw) : [raw, {} as HeaderCarrier];\n const decoded = EnvelopeCodec.decode(body);\n\n if (!EnvelopeCodec.accepts(decoded)) {\n const error = new BabelQueueError(\"Rejected a non-conformant BabelQueue envelope from Redis.\");\n this.report(error, decoded, raw);\n await this.transport.deadLetter(reservation, this.poisonRecord(reservation, error));\n return;\n }\n\n const envelope = decoded as Envelope;\n const urn = EnvelopeCodec.urn(envelope);\n const handler = this.handlers[urn];\n if (!handler) {\n await this.onUnknownUrn(envelope, urn, reservation);\n return;\n }\n\n try {\n await handler(envelope, raw, headers);\n } catch (error) {\n this.report(error, envelope, raw);\n await this.retryOrDeadLetter(envelope, reservation, error);\n return;\n }\n await this.transport.ack(reservation);\n }\n\n private async onUnknownUrn(envelope: Envelope, urn: string, reservation: Reservation): Promise<void> {\n switch (this.unknownUrn) {\n case UnknownUrnStrategy.DELETE:\n await this.transport.ack(reservation);\n return;\n case UnknownUrnStrategy.DEAD_LETTER:\n await this.deadLetter(envelope, reservation, \"unknown_urn\");\n return;\n case UnknownUrnStrategy.RELEASE:\n await this.requeue(envelope, reservation);\n return;\n default: {\n // FAIL: report, then take the bounded retry/dead-letter path (Go routeUnknown / Python\n // _route_unknown parity). Leaving the reservation in place would let retryAfter recovery\n // bring it back with an unchanged attempts count: an endless reserve/recover loop.\n const error = new UnknownUrnError(urn);\n this.report(error, envelope, reservation.raw);\n await this.retryOrDeadLetter(envelope, reservation, error);\n return;\n }\n }\n }\n\n private async retryOrDeadLetter(envelope: Envelope, reservation: Reservation, error: unknown): Promise<void> {\n // The counted attempt: the delivered attempts plus one for this delivery (ADR-0036 decision 8).\n const counted = (envelope.attempts ?? 0) + 1;\n if (counted < this.maxTries) {\n await this.requeue(envelope, reservation);\n } else {\n // An unknown URN dead-letters with its own reason, like Go and Python.\n await this.deadLetter(\n envelope,\n reservation,\n error instanceof UnknownUrnError ? \"unknown_urn\" : \"failed\",\n error,\n counted,\n );\n }\n }\n\n /** Give the message back with `attempts + 1` (at-least-once retry). */\n private async requeue(envelope: Envelope, reservation: Reservation): Promise<void> {\n if (reservation.layout === \"zset\") {\n // Default body: the raw message with only `attempts` raised, unknown keys preserved.\n await this.transport.release(reservation, 0);\n return;\n }\n const bumped: Envelope = { ...envelope, attempts: (envelope.attempts ?? 0) + 1 };\n await this.transport.release(reservation, 0, redisValue(bumped));\n }\n\n /**\n * Write the annotated envelope to the DLQ. `counted` is given on the exhaustion path (retries used up): the\n * delivered attempts plus one for the current delivery, written to both `dead_letter.attempts` and the\n * record's top-level `attempts`, like Go and Python. A direct dead-letter that makes no retry decision\n * (`unknownUrn: dead_letter`) omits it and records the delivered value, which is informational.\n */\n private async deadLetter(\n envelope: Envelope,\n reservation: Reservation,\n reason: string,\n error?: unknown,\n counted?: number,\n ): Promise<void> {\n const attempts = counted ?? envelope.attempts ?? 0;\n const annotated = annotate({ ...envelope, attempts }, reason, envelope.meta?.queue ?? this.queue, {\n attempts,\n error: error instanceof Error ? error.message : null,\n exception: error instanceof Error ? error.name : null,\n });\n await this.transport.deadLetter(reservation, redisValue(annotated));\n }\n\n /**\n * The poison DLQ record of a reservation (ADR-0035, broker-bindings section 1.1): the delivered body (a legacy\n * header frame is unwrapped first) with a `dead_letter` block of reason `poison`. A strict JSON object is\n * spliced, anything else is wrapped as `{\"raw\", \"dead_letter\"}`.\n *\n * A body that is not valid UTF-8 is read as exact bytes (`evalBuffer`, see {@link RedisClient.evalBuffer}) and\n * kept as stored (no frame is peeled off it), so it lands in the base64 wrapper, never as U+FFFD text.\n */\n private poisonRecord(reservation: Reservation, cause: BabelQueueError): string {\n const exact = reservation.rawBytes;\n const [body] = exact !== undefined || reservation.layout !== \"legacy\" ? [reservation.raw] : unframe(reservation.raw);\n const counted = attemptsOf(body);\n const attempts = Number.isSafeInteger(counted) && counted >= 0 ? counted : 0;\n const deadLetter = annotate({ attempts } as unknown as Envelope, \"poison\", this.queue, {\n attempts,\n error: cause.message,\n exception: cause.name,\n }).dead_letter;\n return buildPoisonRecord(exact ?? Buffer.from(body, \"utf8\"), JSON.stringify(deadLetter));\n }\n\n private report(error: unknown, envelope: IncomingEnvelope | null, raw: string): void {\n this.options.onError?.(error, envelope, raw);\n }\n}\n","/**\n * Pure, dependency-free helpers for the out-of-band transport-header seam (ADR-0028) — the Node\n * adapter mirror of Go's `sanitizeHeaders` and PHP's `Support\\Headers`. They fold an injected\n * {@link HeaderCarrier} (e.g. a W3C `traceparent`) onto a transport's metadata channel **without\n * clobbering** the contract headers it already carries.\n *\n * Headers ride beside the frozen wire envelope (GR-1), never inside it.\n */\n\nimport type { HeaderCarrier } from \"@babelqueue/core\";\n\n/**\n * Copy `headers`, dropping blank keys and blank values. Returns an empty object when nothing\n * survives, so a caller can treat the result as \"no headers\" via `Object.keys(...).length === 0`.\n * A nullish input yields an empty object.\n */\nexport function sanitizeHeaders(headers: HeaderCarrier | null | undefined): HeaderCarrier {\n const out: HeaderCarrier = {};\n if (!headers) return out;\n for (const key of Object.keys(headers)) {\n const value = headers[key];\n if (key === \"\" || value == null || value === \"\") continue;\n out[key] = value;\n }\n return out;\n}\n","// KEEP IN SYNC with babelqueue-node/src/poisonRecord.ts (@babelqueue/core, buildPoisonRecord): this is a verbatim\n// copy, because the installed core dependency of this package predates the export. Delete this file and import\n// buildPoisonRecord from @babelqueue/core once the dependency range is bumped to the release that ships it.\n// The DLQ record of a poison body (ADR-0035, error-handling.md section 6).\n//\n// A body that is a strict RFC 8259 JSON object, valid UTF-8 and without a top-level \"dead_letter\" member is\n// spliced at byte level: every original byte is kept and only the dead_letter member is inserted, so no value is\n// ever re-encoded (a 1e400 or a 64-bit integer stays as written, a repeated member name is not collapsed).\n// Anything else is wrapped as {\"raw\": <string>, \"raw_encoding\"?: \"base64\", \"dead_letter\": ...}: \"raw\" is the body\n// as plain text when it is valid UTF-8, otherwise the standard base64 of the exact bytes with\n// \"raw_encoding\": \"base64\". U+FFFD substitution never happens.\n\nconst JSON_SPACE = /^[ \\t\\r\\n]+|[ \\t\\r\\n]+$/g;\n\n/** Decodes `bytes` as strict UTF-8 (no replacement, BOM kept), or returns `null` when they are not valid UTF-8. */\nfunction strictUtf8(bytes: Uint8Array): string | null {\n try {\n return new TextDecoder(\"utf-8\", { fatal: true, ignoreBOM: true }).decode(bytes);\n } catch {\n return null;\n }\n}\n\n/**\n * Rejects a `deadLetter` argument that is not the serialized form of exactly one JSON object: it is spliced into\n * the record verbatim, so anything else (a fragment, an array, trailing members, a bare value) would produce a\n * record that is not valid JSON or carries a second top-level member.\n */\nfunction assertJsonObject(deadLetter: string): void {\n let parsed: unknown;\n try {\n parsed = JSON.parse(deadLetter);\n } catch {\n throw new TypeError(\"deadLetter must be the JSON text of a single object\");\n }\n if (parsed === null || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n throw new TypeError(\"deadLetter must be the JSON text of a single object\");\n }\n}\n\n/**\n * Builds the DLQ record for `body` (the exact record value bytes), with `deadLetter` (the JSON of the\n * dead_letter block, which must be a single JSON object, else a `TypeError` is thrown) as a top-level member.\n */\nexport function buildPoisonRecord(body: Uint8Array, deadLetter: string): string {\n assertJsonObject(deadLetter);\n const text = strictUtf8(body);\n if (text !== null && isSpliceableObject(text)) {\n const trimmed = text.replace(JSON_SPACE, \"\");\n const inner = trimmed.slice(1, -1).replace(JSON_SPACE, \"\");\n return inner === \"\"\n ? `{\"dead_letter\":${deadLetter}}`\n : `${trimmed.slice(0, -1)},\"dead_letter\":${deadLetter}}`;\n }\n const raw =\n text !== null\n ? `\"raw\":${JSON.stringify(text)}`\n : `\"raw\":${JSON.stringify(Buffer.from(body).toString(\"base64\"))},\"raw_encoding\":\"base64\"`;\n return `{${raw},\"dead_letter\":${deadLetter}}`;\n}\n\n/**\n * True when `text` is a single strict RFC 8259 JSON object that carries no unpaired surrogate escape and no\n * top-level \"dead_letter\" member (ADR-0035 section 1).\n */\nfunction isSpliceableObject(text: string): boolean {\n try {\n JSON.parse(text); // rejects NaN / Infinity / a BOM / trailing data / control characters / an empty body\n } catch {\n return false;\n }\n let i = 0;\n while (i < text.length && isJsonSpace(text.charCodeAt(i))) i++;\n if (text[i] !== \"{\") return false;\n\n let depth = 0;\n let expectKey = false;\n while (i < text.length) {\n const c = text[i]!;\n if (c === \"{\" || c === \"[\") {\n depth++;\n expectKey = c === \"{\" && depth === 1;\n i++;\n } else if (c === \"}\" || c === \"]\") {\n depth--;\n i++;\n } else if (c === \",\") {\n expectKey = depth === 1;\n i++;\n } else if (c === '\"') {\n const end = scanJsonString(text, i);\n if (end < 0) return false;\n if (depth === 1 && expectKey) {\n if ((JSON.parse(text.slice(i, end)) as string) === \"dead_letter\") return false;\n expectKey = false;\n }\n i = end;\n } else {\n i++;\n }\n }\n return true;\n}\n\nfunction isJsonSpace(c: number): boolean {\n return c === 0x20 || c === 0x09 || c === 0x0d || c === 0x0a;\n}\n\n/**\n * Scans the (already syntax-validated) string literal starting at `text[start]` and returns the index just past\n * its closing quote, or -1 when it holds an unpaired surrogate escape.\n */\nfunction scanJsonString(text: string, start: number): number {\n let i = start + 1;\n while (i < text.length) {\n const c = text[i]!;\n if (c === '\"') return i + 1;\n if (c !== \"\\\\\") {\n i++;\n continue;\n }\n if (text[i + 1] !== \"u\") {\n i += 2;\n continue;\n }\n const cp = hex4(text, i + 2);\n if (cp < 0) return -1;\n i += 6;\n if (cp >= 0xd800 && cp <= 0xdbff) {\n if (text[i] !== \"\\\\\" || text[i + 1] !== \"u\") return -1;\n const lo = hex4(text, i + 2);\n if (lo < 0xdc00 || lo > 0xdfff) return -1;\n i += 6;\n } else if (cp >= 0xdc00 && cp <= 0xdfff) {\n return -1;\n }\n }\n return -1;\n}\n\nfunction hex4(text: string, at: number): number {\n const digits = text.slice(at, at + 4);\n return /^[0-9a-fA-F]{4}$/.test(digits) ? parseInt(digits, 16) : -1;\n}\n"],"mappings":"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","names":["i"]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@babelqueue/redis",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Redis adapter for BabelQueue — a canonical-envelope publisher and a URN-routed consumer over the §1 reliable-queue pattern (
|
|
3
|
+
"version": "1.3.0",
|
|
4
|
+
"description": "Redis adapter for BabelQueue — a canonical-envelope publisher and a URN-routed consumer over the §1 reliable-queue pattern — the legacy `:processing` list (default, deadline-recovered) or the Laravel-compatible zset layout (delays, `retry_after` recovery) — on ioredis.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"babelqueue",
|
|
7
7
|
"redis",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"directory": "packages/redis"
|
|
21
21
|
},
|
|
22
22
|
"license": "MIT",
|
|
23
|
-
"author": "Muhammet Şafak <info@muhammetsafak.com
|
|
23
|
+
"author": "Muhammet Şafak <info@muhammetsafak.com>",
|
|
24
24
|
"type": "module",
|
|
25
25
|
"exports": {
|
|
26
26
|
".": {
|
|
@@ -50,7 +50,10 @@
|
|
|
50
50
|
"test": "node --import tsx --test test/*.test.ts"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"@babelqueue/core": "^1.
|
|
53
|
+
"@babelqueue/core": "^1.13.0"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"ioredis": "^5.0.0"
|
|
54
57
|
},
|
|
55
58
|
"peerDependencies": {
|
|
56
59
|
"ioredis": "^5.0.0"
|