@basaltkit/queue-kafka 1.1.0 → 1.1.2
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 -3
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -70,7 +70,23 @@ await Job.dispatch(payload, { delay: '5m' }) // → throws UnsupportedJobOptionE
|
|
|
70
70
|
// with onUnsupported: 'warn' (default) → warns once and runs immediately
|
|
71
71
|
```
|
|
72
72
|
|
|
73
|
-
|
|
73
|
+
**If what you need is *streaming*/pub-sub** (rather than jobs with retry/delay), the natural fit in Basalt is usually `@basaltkit/events`, not `@basaltkit/queue`.
|
|
74
|
+
|
|
75
|
+
### Inspection: no `list()` / `queue:jobs`
|
|
76
|
+
|
|
77
|
+
This driver deliberately does **not** implement the queue's optional
|
|
78
|
+
`list(queue, options)` capability, so `basalt queue:jobs` reports it as
|
|
79
|
+
unsupported.
|
|
80
|
+
|
|
81
|
+
Here the reason is not destructiveness — reading a Kafka topic does not remove
|
|
82
|
+
records — but **meaning**. Kafka has no per-message state: there is no
|
|
83
|
+
`waiting`/`active`/`completed`/`failed` set, no broker-assigned job id, and no
|
|
84
|
+
way to tell a record still to be processed from one processed an hour ago. A
|
|
85
|
+
`list()` would return an arbitrary window of the log within retention, dressed
|
|
86
|
+
up in job states it invented. That would make the same API mean something
|
|
87
|
+
different depending on the driver, which is the coupling this API exists to
|
|
88
|
+
remove. Use your Kafka tooling (`kafka-console-consumer`, a UI) plus the
|
|
89
|
+
`<topic>.retry` / `<topic>.dead` topics instead.
|
|
74
90
|
|
|
75
91
|
## How it works
|
|
76
92
|
|
|
@@ -94,9 +110,57 @@ The worker's concurrency is passed as `partitionsConsumedConcurrently` — actua
|
|
|
94
110
|
| `retrySuffix` | `string` | `'.retry'` | Suffix for the retry topic. |
|
|
95
111
|
| `deadSuffix` | `string` | `'.dead'` | Suffix for the dead-letter topic. |
|
|
96
112
|
| `client` | `KafkaClient` | kafkajs | Injectable client — used in tests without a broker. |
|
|
97
|
-
| `onError` | `(error, { source
|
|
113
|
+
| `onError` | `(error: unknown, info: { source: 'consumer' \| 'producer'; queue?: string }) => void` | contextual `console.error` | The driver's single fault channel — see below. |
|
|
114
|
+
|
|
115
|
+
Implements the `QueueDriver` contract from `@basaltkit/queue`. It does **not** implement the optional `stats` / `retryFailed`, so `basalt queue:stats` and `basalt queue:retry` report the operation as unsupported — use your Kafka tooling for consumer-group lag instead.
|
|
116
|
+
|
|
117
|
+
### Failure hooks
|
|
118
|
+
|
|
119
|
+
`onError` is the only callback, and its default (`console.error` with the source and queue) is
|
|
120
|
+
never silent. There is no `onJobFailed`: a job that exhausts `attempts` is produced to
|
|
121
|
+
`<topic>.dead`, which *is* the report — monitor that topic.
|
|
122
|
+
|
|
123
|
+
| `source` | Raised when | What the driver does next |
|
|
124
|
+
|---|---|---|
|
|
125
|
+
| `'consumer'` | A worker's `connect`/`subscribe`/`run` rejected at boot (broker unreachable, missing topic, bad ACLs). | Reports and stops. Without this the rejection would float and be process-fatal, and the app would report healthy with **zero** workers. |
|
|
126
|
+
| `'producer'` | The retry / dead-letter **re-publish itself failed** while handling a failed job. | Reports, then **rethrows** — see below. |
|
|
127
|
+
|
|
128
|
+
### Redelivery when the DLQ produce fails
|
|
129
|
+
|
|
130
|
+
The subtle case. A job's handler throws, so the driver tries to re-route the message — to
|
|
131
|
+
`<topic>.retry` if attempts remain, otherwise to `<topic>.dead`. If *that* produce also fails
|
|
132
|
+
(the producer lost its broker connection, the dead topic doesn't exist, the request timed out),
|
|
133
|
+
the failed job exists nowhere but in the message currently being consumed.
|
|
134
|
+
|
|
135
|
+
kafkajs auto-commits offsets after `eachMessage` **resolves**. So the driver:
|
|
136
|
+
|
|
137
|
+
1. reports the publish failure through `onError({ source: 'producer', queue })`, then
|
|
138
|
+
2. **rethrows** it, so `eachMessage` rejects and the offset is **not** committed.
|
|
139
|
+
|
|
140
|
+
Kafka then redelivers the same message and the driver tries the whole thing again — at-least-once
|
|
141
|
+
rather than a job that quietly evaporated during a producer outage. It is the Kafka equivalent of
|
|
142
|
+
RabbitMQ leaving a message unacked.
|
|
143
|
+
|
|
144
|
+
Two consequences worth planning for:
|
|
145
|
+
|
|
146
|
+
- **Handlers must be idempotent.** A redelivered message re-runs the handler that already failed,
|
|
147
|
+
and a message whose re-publish succeeded is never redelivered — but a partition stalls on the
|
|
148
|
+
failing message while the producer is down, so ordered downstream work backs up behind it.
|
|
149
|
+
- **A normal failure path does commit.** When the re-publish *succeeds*, the failure is
|
|
150
|
+
considered handled: the offset commits and the retry copy carries the incremented
|
|
151
|
+
`x-basalt-attempt` header. Redelivery only happens on the produce failure itself.
|
|
152
|
+
|
|
153
|
+
### Exported errors
|
|
154
|
+
|
|
155
|
+
This driver throws no error classes of its own. `UnsupportedJobOptionError`
|
|
156
|
+
(`QUEUE_UNSUPPORTED_OPTION`) comes from `@basaltkit/queue` when a `delay`/`priority` dispatch
|
|
157
|
+
meets `onUnsupported: 'throw'`; everything else surfaces through `onError`.
|
|
158
|
+
|
|
159
|
+
### Hard limits
|
|
98
160
|
|
|
99
|
-
|
|
161
|
+
The attempt counters travel in message headers, which any producer on the topic could write, so
|
|
162
|
+
the consumer clamps the `x-basalt-attempts` it reads to at most **50**. A crafted message cannot
|
|
163
|
+
drive an unbounded retry loop.
|
|
100
164
|
|
|
101
165
|
## How it connects to other modules
|
|
102
166
|
|
package/package.json
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@basaltkit/queue-kafka",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.2",
|
|
4
|
+
"engines": {
|
|
5
|
+
"node": ">=22.5.0"
|
|
6
|
+
},
|
|
4
7
|
"description": "Kafka driver for @basaltkit/queue: produce/consume jobs with retry and dead-letter topics (no delayed delivery or priority — Kafka has neither).",
|
|
5
8
|
"license": "MIT",
|
|
6
9
|
"type": "module",
|
|
10
|
+
"sideEffects": false,
|
|
7
11
|
"exports": {
|
|
8
12
|
".": {
|
|
9
13
|
"types": "./dist/index.d.ts",
|
|
@@ -14,7 +18,7 @@
|
|
|
14
18
|
"dist"
|
|
15
19
|
],
|
|
16
20
|
"dependencies": {
|
|
17
|
-
"@basaltkit/queue": "^1.
|
|
21
|
+
"@basaltkit/queue": "^1.5.0"
|
|
18
22
|
},
|
|
19
23
|
"peerDependencies": {
|
|
20
24
|
"kafkajs": "^2.0.0"
|