@venizia/ignis-docs 0.2.1-0 → 0.2.1-1
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/content/best-practices/architectural-patterns.md +3 -3
- package/content/best-practices/code-style-standards/naming-conventions.md +1 -1
- package/content/best-practices/contribution-workflow.md +2 -2
- package/content/best-practices/error-handling.md +94 -89
- package/content/best-practices/security-guidelines.md +5 -5
- package/content/extensions/components/api-reference.md +22 -21
- package/content/extensions/components/authentication/api.md +64 -28
- package/content/extensions/components/authentication/errors.md +19 -5
- package/content/extensions/components/authentication/index.md +19 -18
- package/content/extensions/components/authentication/usage.md +55 -30
- package/content/extensions/components/authorization/api.md +351 -84
- package/content/extensions/components/authorization/errors.md +51 -17
- package/content/extensions/components/authorization/getting-started.md +227 -0
- package/content/extensions/components/authorization/index.md +24 -16
- package/content/extensions/components/authorization/usage.md +45 -21
- package/content/extensions/components/health-check.md +15 -9
- package/content/extensions/components/index.md +24 -90
- package/content/extensions/components/mail/api.md +105 -54
- package/content/extensions/components/mail/errors.md +12 -10
- package/content/extensions/components/mail/index.md +32 -13
- package/content/extensions/components/mail/usage.md +26 -18
- package/content/extensions/components/request-tracker.md +18 -14
- package/content/extensions/components/socket-io/api.md +377 -882
- package/content/extensions/components/socket-io/errors.md +49 -51
- package/content/extensions/components/socket-io/index.md +72 -88
- package/content/extensions/components/socket-io/usage.md +107 -117
- package/content/extensions/components/static-asset/api.md +83 -31
- package/content/extensions/components/static-asset/errors.md +18 -7
- package/content/extensions/components/static-asset/index.md +18 -11
- package/content/extensions/components/static-asset/usage.md +11 -6
- package/content/extensions/components/template/index.md +3 -3
- package/content/extensions/components/websocket/api.md +58 -27
- package/content/extensions/components/websocket/errors.md +3 -3
- package/content/extensions/components/websocket/index.md +8 -7
- package/content/extensions/components/websocket/usage.md +21 -8
- package/content/extensions/helpers/cron/index.md +8 -7
- package/content/extensions/helpers/crypto/index.md +16 -8
- package/content/extensions/helpers/crypto/reference.md +96 -24
- package/content/extensions/helpers/env/index.md +14 -10
- package/content/extensions/helpers/error/index.md +99 -23
- package/content/extensions/helpers/index.md +61 -47
- package/content/extensions/helpers/inversion/index.md +23 -6
- package/content/extensions/helpers/inversion/reference.md +30 -22
- package/content/extensions/helpers/kafka/admin.md +3 -2
- package/content/extensions/helpers/kafka/compile-binary.md +69 -44
- package/content/extensions/helpers/kafka/consumer.md +24 -21
- package/content/extensions/helpers/kafka/examples.md +22 -234
- package/content/extensions/helpers/kafka/index.md +30 -62
- package/content/extensions/helpers/kafka/producer.md +31 -26
- package/content/extensions/helpers/kafka/schema-registry.md +19 -14
- package/content/extensions/helpers/logger/hf-logger.md +49 -22
- package/content/extensions/helpers/logger/index.md +37 -12
- package/content/extensions/helpers/logger/pino.md +31 -11
- package/content/extensions/helpers/logger/reference.md +243 -52
- package/content/extensions/helpers/network/api.md +65 -30
- package/content/extensions/helpers/network/index.md +32 -11
- package/content/extensions/helpers/queue/index.md +17 -6
- package/content/extensions/helpers/queue/reference.md +52 -25
- package/content/extensions/helpers/redis/index.md +29 -11
- package/content/extensions/helpers/redis/reference.md +85 -28
- package/content/extensions/helpers/secrets/index.md +82 -12
- package/content/extensions/helpers/socket-io/api.md +40 -21
- package/content/extensions/helpers/socket-io/index.md +26 -10
- package/content/extensions/helpers/storage/api.md +42 -24
- package/content/extensions/helpers/storage/index.md +10 -7
- package/content/extensions/helpers/types/index.md +20 -7
- package/content/extensions/helpers/types/reference.md +53 -14
- package/content/extensions/helpers/uid/index.md +166 -10
- package/content/extensions/helpers/websocket/api.md +52 -13
- package/content/extensions/helpers/websocket/index.md +7 -4
- package/content/extensions/helpers/worker-thread/index.md +9 -5
- package/content/extensions/helpers/worker-thread/reference.md +20 -20
- package/content/extensions/index.md +38 -39
- package/content/extensions/src-details/mcp-server.md +96 -548
- package/content/guides/core-concepts/persistent/datasources.md +2 -2
- package/content/guides/core-concepts/persistent/index.md +5 -1
- package/content/guides/core-concepts/persistent/pglite.md +218 -0
- package/content/guides/core-concepts/persistent/postgres-drivers.md +21 -15
- package/content/guides/core-concepts/persistent/search-meilisearch.md +22 -12
- package/content/guides/core-concepts/persistent/search-typesense.md +26 -14
- package/content/guides/core-concepts/persistent/sqlite.md +296 -0
- package/content/guides/core-concepts/persistent/transactions.md +12 -11
- package/content/guides/get-started/5-minute-quickstart.md +59 -206
- package/content/guides/get-started/philosophy.md +135 -670
- package/content/guides/get-started/setup.md +53 -74
- package/content/guides/migrations/redis-helpers-migration.md +4 -3
- package/content/guides/migrations/scoped-rbac-migration.md +5 -5
- package/content/guides/migrations/unified-connectors-migration.md +7 -6
- package/content/guides/tutorials/realtime-chat.md +1 -1
- package/content/references/base/application.md +63 -21
- package/content/references/base/components.md +3 -3
- package/content/references/base/connectors.md +14 -14
- package/content/references/base/controllers.md +12 -12
- package/content/references/base/datasources-reference.md +32 -31
- package/content/references/base/datasources.md +6 -6
- package/content/references/base/dependency-injection.md +12 -11
- package/content/references/base/filter-system/application-usage.md +54 -30
- package/content/references/base/filter-system/array-operators.md +24 -46
- package/content/references/base/filter-system/comparison-operators.md +47 -67
- package/content/references/base/filter-system/default-filter.md +59 -53
- package/content/references/base/filter-system/fields-order-pagination.md +92 -146
- package/content/references/base/filter-system/index.md +25 -13
- package/content/references/base/filter-system/json-filtering.md +45 -184
- package/content/references/base/filter-system/list-operators.md +23 -53
- package/content/references/base/filter-system/logical-operators.md +63 -121
- package/content/references/base/filter-system/null-operators.md +34 -104
- package/content/references/base/filter-system/pattern-matching.md +40 -55
- package/content/references/base/filter-system/quick-reference.md +86 -198
- package/content/references/base/filter-system/range-operators.md +18 -46
- package/content/references/base/filter-system/tips.md +6 -6
- package/content/references/base/filter-system/use-cases.md +33 -15
- package/content/references/base/grpc-controllers.md +53 -17
- package/content/references/base/index.md +5 -3
- package/content/references/base/middlewares.md +11 -10
- package/content/references/base/models-reference.md +17 -17
- package/content/references/base/models.md +4 -3
- package/content/references/base/providers.md +8 -8
- package/content/references/base/repositories/advanced.md +224 -326
- package/content/references/base/repositories/index.md +19 -6
- package/content/references/base/repositories/mixins.md +5 -5
- package/content/references/base/repositories/relations.md +160 -293
- package/content/references/base/repositories/soft-deletable.md +16 -6
- package/content/references/base/secrets.md +17 -13
- package/content/references/base/services.md +6 -4
- package/content/references/configuration/environment-variables.md +31 -23
- package/content/references/configuration/index.md +6 -4
- package/content/references/index.md +1 -1
- package/content/references/utilities/duration.md +85 -0
- package/content/references/utilities/index.md +5 -1
- package/content/references/utilities/jsx-reference.md +11 -11
- package/content/references/utilities/jsx.md +2 -2
- package/content/references/utilities/module.md +78 -25
- package/content/references/utilities/request.md +2 -1
- package/content/references/utilities/retry.md +139 -0
- package/content/references/utilities/schema.md +2 -2
- package/content/references/utilities/statuses-reference.md +4 -4
- package/content/references/utilities/statuses.md +5 -5
- package/dist/mcp-server/index.js +0 -0
- package/dist/mcp-server/tools/github/list-project-files.tool.d.ts +1 -1
- package/dist/mcp-server/tools/github/list-project-files.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/github/list-project-files.tool.js +3 -3
- package/dist/mcp-server/tools/github/search-code.tool.js +1 -1
- package/dist/mcp-server/tools/github/verify-dependencies.tool.js +1 -1
- package/dist/mcp-server/tools/github/view-source-file.tool.js +1 -1
- package/package.json +17 -16
|
@@ -1,45 +1,16 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Kafka - Compiling to a Single Binary
|
|
3
|
-
description: The
|
|
3
|
+
description: The platformaticKafkaPlugins() Bun bundler plugins required when compiling an app that imports a Kafka helper
|
|
4
4
|
difficulty: advanced
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Compiling to a Single Binary
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
## The problem
|
|
12
|
-
|
|
13
|
-
- **A compiled binary crashes on startup** if the application imports any Kafka helper and the build did not register the plugin:
|
|
14
|
-
|
|
15
|
-
```
|
|
16
|
-
ENOENT: no such file or directory, open '/$bunfs/dist/native.wasm'
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
- **The crash happens before your code runs.** It fires while the module graph is still loading - before the IGNIS application boots, so no log line, no lifecycle hook, and no error handler of yours ever executes.
|
|
20
|
-
|
|
21
|
-
## Why it happens
|
|
22
|
-
|
|
23
|
-
- **The codecs are WebAssembly, not native bindings.** `@platformatic/kafka` computes Kafka's CRC32C checksums and lz4/snappy compression in WebAssembly, through `@platformatic/wasm-utils`.
|
|
24
|
-
- **The default entrypoint reads the wasm payload from disk** at module load time:
|
|
25
|
-
|
|
26
|
-
```javascript
|
|
27
|
-
// @platformatic/wasm-utils/dist/index.js
|
|
28
|
-
const wasm = readFileSync(new URL('../dist/native.wasm', import.meta.url));
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
- **`bun build --compile` embeds JavaScript modules only.** Assets such as `native.wasm` are never carried into the executable. Inside the binary, `import.meta.url` resolves against the virtual `/$bunfs` filesystem, the file isn't there, and the read throws.
|
|
32
|
-
- **Running from source is unaffected.** `bun run` / `bun .` keep `node_modules` on disk, so the read succeeds - the bug only exists in compiled binaries.
|
|
33
|
-
|
|
34
|
-
## The fix
|
|
35
|
-
|
|
36
|
-
- **A second entrypoint ships the payload inline.** `@platformatic/wasm-utils/bundled` exposes the same API with the wasm payload inlined as base64 - no filesystem read.
|
|
37
|
-
- **`platformaticWasmPlugin()` swaps one entrypoint for the other** at bundle time, so the rest of your code and `@platformatic/kafka` itself need no changes.
|
|
38
|
-
- **Compile through a Bun build script, not the CLI.** `bun build --compile` on the command line cannot register plugins:
|
|
9
|
+
`platformaticKafkaPlugins()` returns every Bun bundler plugin `@platformatic/kafka` needs to survive `bun build --compile`. Register them whenever you compile an application that imports a Kafka helper. Skip them, and the binary crashes on its first run.
|
|
39
10
|
|
|
40
11
|
```typescript
|
|
41
12
|
// scripts/compile.ts
|
|
42
|
-
import {
|
|
13
|
+
import { platformaticKafkaPlugins } from '@venizia/ignis-helpers/kafka';
|
|
43
14
|
|
|
44
15
|
const built = await Bun.build({
|
|
45
16
|
entrypoints: ['./dist/index.js'],
|
|
@@ -50,7 +21,7 @@ const built = await Bun.build({
|
|
|
50
21
|
target: process.env.BUN_TARGET ?? 'bun-linux-x64',
|
|
51
22
|
outfile: './dist/bin',
|
|
52
23
|
},
|
|
53
|
-
plugins:
|
|
24
|
+
plugins: platformaticKafkaPlugins(),
|
|
54
25
|
});
|
|
55
26
|
|
|
56
27
|
if (!built.success) {
|
|
@@ -59,6 +30,8 @@ if (!built.success) {
|
|
|
59
30
|
}
|
|
60
31
|
```
|
|
61
32
|
|
|
33
|
+
`bun build --compile` on the command line cannot register plugins. Compile through a build script instead:
|
|
34
|
+
|
|
62
35
|
```json
|
|
63
36
|
{
|
|
64
37
|
"scripts": {
|
|
@@ -67,32 +40,84 @@ if (!built.success) {
|
|
|
67
40
|
}
|
|
68
41
|
```
|
|
69
42
|
|
|
70
|
-
|
|
43
|
+
## The two crashes
|
|
71
44
|
|
|
72
|
-
|
|
45
|
+
| Crash | Plugin that prevents it |
|
|
46
|
+
|---|---|
|
|
47
|
+
| `ENOENT: no such file or directory, open '/$bunfs/dist/native.wasm'` | `platformaticWasmPlugin()` |
|
|
48
|
+
| `Cannot find package 'ajv-draft-04' from '/$bunfs/root/index.js'` | `platformaticRequirePlugin()` |
|
|
49
|
+
|
|
50
|
+
Both fire while the module graph is still loading, before the IGNIS application boots. No log line, no lifecycle hook, and no error handler of yours ever runs.
|
|
51
|
+
|
|
52
|
+
Running from source is unaffected. `bun run` and `bun .` keep `node_modules` on disk, so both reads succeed. The crashes exist only in compiled binaries.
|
|
53
|
+
|
|
54
|
+
## Why the wasm read fails
|
|
73
55
|
|
|
74
|
-
|
|
56
|
+
`@platformatic/kafka` computes Kafka's CRC32C checksums and lz4/snappy compression in WebAssembly, through `@platformatic/wasm-utils`. That package reads its payload from disk at module load:
|
|
57
|
+
|
|
58
|
+
```javascript
|
|
59
|
+
// @platformatic/wasm-utils/dist/index.js
|
|
60
|
+
const wasm = readFileSync(new URL('../dist/native.wasm', import.meta.url));
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
`bun build --compile` embeds JavaScript modules only. Assets such as `native.wasm` never reach the executable, so `import.meta.url` resolves against the virtual `/$bunfs` filesystem and the read throws.
|
|
64
|
+
|
|
65
|
+
`platformaticWasmPlugin()` redirects the import to `@platformatic/wasm-utils/bundled`, the same API with the payload inlined as base64.
|
|
66
|
+
|
|
67
|
+
## Why the package lookup fails
|
|
68
|
+
|
|
69
|
+
Since 2.8.0, `@platformatic/kafka` resolves two dependencies at module scope through `createRequire`:
|
|
70
|
+
|
|
71
|
+
```javascript
|
|
72
|
+
// @platformatic/kafka/dist/registries/confluent-schema-registry.js
|
|
73
|
+
const require = createRequire(import.meta.url);
|
|
74
|
+
const AjvDraft04 = require('ajv-draft-04');
|
|
75
|
+
const draft06MetaSchema = require('ajv/dist/refs/json-schema-draft-06.json');
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Three facts make that fatal in a binary:
|
|
79
|
+
|
|
80
|
+
- **The bundler cannot see through `createRequire`.** Neither module is embedded, and the bare specifier survives verbatim into the binary.
|
|
81
|
+
- **The module always loads.** `dist/index.js` re-exports the schema registry, so importing anything from the package runs those two lines. A schema registry you never use still crashes the binary.
|
|
82
|
+
- **A compiled binary has no `node_modules`.** `import.meta.url` resolves against `/$bunfs` and the lookup fails.
|
|
83
|
+
|
|
84
|
+
`platformaticRequirePlugin()` rewrites each module-scope `require()` into a static import at bundle time, so the bundler pulls the module into the binary.
|
|
85
|
+
|
|
86
|
+
> [!WARNING]
|
|
87
|
+
> Installing `ajv-draft-04` as a direct dependency does not fix this. The defect is the resolution mechanism, not a missing package, so the second line fails identically.
|
|
88
|
+
|
|
89
|
+
## Verifying
|
|
75
90
|
|
|
76
91
|
```bash
|
|
77
|
-
|
|
78
|
-
./dist/bin
|
|
92
|
+
./dist/bin # boots instead of crashing
|
|
93
|
+
grep -ac 'native.wasm' ./dist/bin # 0 - the wasm payload is inlined
|
|
94
|
+
grep -ac 'ajv-draft-04' ./dist/bin # 0 - the package is bundled in
|
|
79
95
|
```
|
|
80
96
|
|
|
81
|
-
The
|
|
97
|
+
The last count is meaningful only for a minified build. An unminified bundle keeps package paths in module comments.
|
|
98
|
+
|
|
99
|
+
The binary grows by roughly 76 KB for the base64 wasm module, plus the size of the two hoisted packages.
|
|
82
100
|
|
|
83
101
|
## Notes
|
|
84
102
|
|
|
85
|
-
- **
|
|
86
|
-
- **
|
|
87
|
-
- **
|
|
103
|
+
- **Register the pair, not the individual plugins.** `platformaticKafkaPlugins()` picks up any future addition without another edit to your compile script. `platformaticWasmPlugin()` and `platformaticRequirePlugin()` stay exported for a build that needs one alone.
|
|
104
|
+
- **Upgrading `@platformatic/kafka` does not remove the need for the plugins.** Every release to date imports the default `@platformatic/wasm-utils` entrypoint, and 2.8.0 introduced the `createRequire` calls in a minor release.
|
|
105
|
+
- **An unresolvable specifier now fails the build**, with Bun's own resolution error and `built.success === false`. That is the point of hoisting to a static import: you learn at build time instead of on the first run in production.
|
|
106
|
+
- **Both plugins resolve relative to the importing module's own directory.** That works with hoisted and isolated `node_modules` layouts alike, and pins no package version.
|
|
107
|
+
- **Apps that never import a Kafka helper need no plugins.** Nothing pulls in `@platformatic/wasm-utils` or the schema registry, and neither plugin ever fires.
|
|
108
|
+
- **`platformaticRequirePlugin()` skips `protobufjs` and `@node-rs/crc32`.** Both are optional peers of `@platformatic/kafka`, and a static import would break builds that do not install them.
|
|
109
|
+
- **Patching `node_modules` during the build achieves the same result, but at a cost.** It mutates a dependency in place, pins the store path to one version, and leaves the tree dirty when a build fails. The plugins need none of that.
|
|
88
110
|
|
|
89
111
|
## See also
|
|
90
112
|
|
|
91
|
-
- [Kafka Overview](./) - the four helpers
|
|
113
|
+
- [Kafka Overview](./) - the four helpers these plugins protect
|
|
92
114
|
- [Producer](./producer) / [Consumer](./consumer) / [Admin](./admin) - any of these importing into a compiled binary triggers this caveat
|
|
115
|
+
- [Schema Registry](./schema-registry) - the helper that wraps the module the second crash comes from
|
|
93
116
|
- [Examples & Troubleshooting](./examples) - other Kafka connection and configuration errors
|
|
94
117
|
|
|
95
118
|
**Files:**
|
|
96
119
|
|
|
120
|
+
- [`packages/helpers/src/modules/queue/kafka/bundler/plugins.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/queue/kafka/bundler/plugins.ts) - `platformaticKafkaPlugins`
|
|
97
121
|
- [`packages/helpers/src/modules/queue/kafka/bundler/platformatic-wasm.plugin.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/queue/kafka/bundler/platformatic-wasm.plugin.ts) - `platformaticWasmPlugin`
|
|
98
|
-
- [`packages/helpers/src/modules/queue/kafka/bundler/
|
|
122
|
+
- [`packages/helpers/src/modules/queue/kafka/bundler/platformatic-require.plugin.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/queue/kafka/bundler/platformatic-require.plugin.ts) - `platformaticRequirePlugin`
|
|
123
|
+
- [`packages/helpers/src/modules/queue/kafka/bundler/common/constants.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/queue/kafka/bundler/common/constants.ts) - `KafkaBundlerPluginNames`, `PlatformaticWasmSpecifiers`, `PlatformaticRequireSpecifiers`
|
|
@@ -6,7 +6,7 @@ difficulty: intermediate
|
|
|
6
6
|
|
|
7
7
|
# Consumer
|
|
8
8
|
|
|
9
|
-
The `KafkaConsumerHelper` wraps `@platformatic/kafka`'s `Consumer` with health tracking, graceful shutdown, message callbacks,
|
|
9
|
+
The `KafkaConsumerHelper` wraps `@platformatic/kafka`'s `Consumer` with health tracking, graceful shutdown, message callbacks, group callbacks, automatic reconnect, and lag monitoring.
|
|
10
10
|
|
|
11
11
|
```typescript
|
|
12
12
|
class KafkaConsumerHelper<
|
|
@@ -46,12 +46,12 @@ Plus the shared [Connection & Authentication](./producer#connection--authenticat
|
|
|
46
46
|
|
|
47
47
|
| Option | Type | Default | Description |
|
|
48
48
|
|--------|------|---------|-------------|
|
|
49
|
-
| `groupId` | `string` |
|
|
49
|
+
| `groupId` | `string` | - | Consumer group ID. **Required** |
|
|
50
50
|
| `identifier` | `string` | `'kafka-consumer'` | Scoped logging identifier |
|
|
51
|
-
| `deserializers` | `Partial<Deserializers<K,V,HK,HV>>` |
|
|
51
|
+
| `deserializers` | `Partial<Deserializers<K,V,HK,HV>>` | - | Key/value/header deserializers |
|
|
52
52
|
| `autocommit` | `boolean \| number` | `false` | Auto-commit offsets. `true` = default interval, `number` = custom ms |
|
|
53
|
-
| `sessionTimeout` | `number` | `60000` | Session timeout
|
|
54
|
-
| `heartbeatInterval` | `number` | `10000` | Heartbeat interval
|
|
53
|
+
| `sessionTimeout` | `number` | `60000` | Session timeout - consumer removed from group if no heartbeat |
|
|
54
|
+
| `heartbeatInterval` | `number` | `10000` | Heartbeat interval - must be less than `sessionTimeout` |
|
|
55
55
|
| `rebalanceTimeout` | `number` | `sessionTimeout` | Max time for rebalance. Defaults to the value of `sessionTimeout` |
|
|
56
56
|
| `highWaterMark` | `number` | `1024` | Stream buffer size (messages) |
|
|
57
57
|
| `minBytes` | `number` | `1` | Min bytes per fetch response |
|
|
@@ -59,9 +59,9 @@ Plus the shared [Connection & Authentication](./producer#connection--authenticat
|
|
|
59
59
|
| `maxWaitTime` | `number` | `5000` | Max time (ms) broker waits for `minBytes` |
|
|
60
60
|
| `metadataMaxAge` | `number` | `300000` | Metadata cache TTL (ms) |
|
|
61
61
|
| `groupProtocol` | `'classic' \| 'consumer'` | `'classic'` | Consumer group protocol. `'consumer'` = KIP-848 (Kafka 3.7+) |
|
|
62
|
-
| `groupInstanceId` | `string` |
|
|
62
|
+
| `groupInstanceId` | `string` | - | Static group membership ID - prevents rebalance on restart |
|
|
63
63
|
| `shutdownTimeout` | `number` | `30000` | Graceful shutdown timeout in ms |
|
|
64
|
-
| `registry` | `SchemaRegistry` |
|
|
64
|
+
| `registry` | `SchemaRegistry` | - | Schema registry for auto deser |
|
|
65
65
|
|
|
66
66
|
### Lifecycle Callbacks
|
|
67
67
|
|
|
@@ -169,14 +169,15 @@ Stream 'error' event
|
|
|
169
169
|
-> onMessageError({ error }) (no message available)
|
|
170
170
|
```
|
|
171
171
|
|
|
172
|
-
- **`onMessage` is the main processing callback
|
|
173
|
-
- **`onMessageDone` fires only after `onMessage` resolves successfully
|
|
174
|
-
- **`onMessageError` fires if `onMessage` throws
|
|
175
|
-
- **The stream `'error'` listener is always attached**, whether or not you pass `onMessageError`. An `EventEmitter` `'error'` event with zero listeners
|
|
172
|
+
- **`onMessage` is the main processing callback.** Put your business logic here.
|
|
173
|
+
- **`onMessageDone` fires only after `onMessage` resolves successfully.** Use it for logging, metrics, and similar side effects. An error thrown from `onMessageDone` also triggers `onMessageError`.
|
|
174
|
+
- **`onMessageError` fires if `onMessage` throws.** It also fires for the stream's own `'error'` event. That case carries no `message` - it's a stream-level error, not a per-message one.
|
|
175
|
+
- **The stream `'error'` listener is always attached**, whether or not you pass `onMessageError`. An `EventEmitter` `'error'` event with zero listeners becomes an uncaught exception.
|
|
176
|
+
- **Pull-style consumers benefit too.** Without this listener, a consumer using `start()` + `getStream()` with no `onMessage` would take down the whole process on the first broker drop.
|
|
176
177
|
|
|
177
178
|
## start()
|
|
178
179
|
|
|
179
|
-
`start()` creates the consume stream and wires all message callbacks.
|
|
180
|
+
`start()` creates the consume stream and wires all message callbacks. Call it explicitly after construction - the constructor never calls it for you. It also guards against duplicate starts: calling it twice logs a warning and returns immediately.
|
|
180
181
|
|
|
181
182
|
```typescript
|
|
182
183
|
interface IKafkaConsumeStartOptions {
|
|
@@ -197,8 +198,8 @@ interface IKafkaConsumeStartOptions {
|
|
|
197
198
|
|
|
198
199
|
| Fallback | Description |
|
|
199
200
|
|----------|-------------|
|
|
200
|
-
| `'latest'` (default) | Start from latest
|
|
201
|
-
| `'earliest'` | Start from beginning
|
|
201
|
+
| `'latest'` (default) | Start from latest - ignore historical messages |
|
|
202
|
+
| `'earliest'` | Start from beginning - process all historical messages |
|
|
202
203
|
| `'fail'` | Throw an error |
|
|
203
204
|
|
|
204
205
|
| Reconnect option | Default | Description |
|
|
@@ -227,11 +228,13 @@ await helper.start({
|
|
|
227
228
|
|
|
228
229
|
When `onMessage` is provided, `start()` drives a background consume loop on top of the stream. That loop reconnects on its own:
|
|
229
230
|
|
|
230
|
-
- **Only the callback-driven loop reconnects.** A pull-style consumer using `getStream()` or `consumer.consume()` directly owns its own retry logic
|
|
231
|
-
- **A full broker outage marks the session stale.**
|
|
232
|
-
- **
|
|
231
|
+
- **Only the callback-driven loop reconnects.** A pull-style consumer using `getStream()` or `consumer.consume()` directly owns its own retry logic. The helper's automatic reconnect runs only inside `startConsumeLoop`, which is wired exclusively when `onMessage` is set.
|
|
232
|
+
- **A full broker outage marks the session stale.** This happens when every broker disconnects - `getConnectedBrokerCount()` drops to `0` via `client:broker:disconnect` or `client:broker:failed` events.
|
|
233
|
+
- **The helper reacts immediately.** It destroys the current stream right away, instead of waiting for the stream to error out on its own.
|
|
234
|
+
- **Reconnect rebuilds the client after a stale session.** The next attempt, after `reconnectDelayMs`, constructs a brand-new `@platformatic/kafka` `Consumer` with the original options and swaps it in.
|
|
235
|
+
- **A fresh client forces a clean group rejoin**, instead of reusing session state Kafka has likely already expired. Lag monitoring re-arms automatically on the new client.
|
|
233
236
|
- **Attempts are capped.** After `maxReconnectAttempts` consecutive failures, the consume loop exits and logs an error. Message processing stops until you call `start()` again with a new set of options.
|
|
234
|
-
- **Every retry is logged
|
|
237
|
+
- **Every retry is logged**: the attempt number, the delay, and the current connected-broker count. A stuck reconnect loop stays visible in application logs without extra instrumentation.
|
|
235
238
|
|
|
236
239
|
## Lag Monitoring
|
|
237
240
|
|
|
@@ -243,7 +246,7 @@ helper.startLagMonitoring({ topics: ['orders'], interval: 10_000 });
|
|
|
243
246
|
helper.stopLagMonitoring();
|
|
244
247
|
```
|
|
245
248
|
|
|
246
|
-
Lag data
|
|
249
|
+
Lag data arrives through the `onLag` callback; errors through `onLagError`. `interval` defaults to `30000` ms. Calling `startLagMonitoring()` twice logs a warning instead of starting a second poll loop.
|
|
247
250
|
|
|
248
251
|
For one-time lag checks, use the underlying consumer directly:
|
|
249
252
|
|
|
@@ -378,7 +381,7 @@ consumer.generationId; // number
|
|
|
378
381
|
consumer.assignments; // GroupAssignment[] | null
|
|
379
382
|
consumer.isActive(); // boolean
|
|
380
383
|
|
|
381
|
-
// Static membership
|
|
384
|
+
// Static membership - prevents rebalance on restart
|
|
382
385
|
const helper = KafkaConsumerHelper.newInstance({
|
|
383
386
|
...
|
|
384
387
|
groupInstanceId: 'worker-1',
|
|
@@ -410,7 +413,7 @@ Topic "orders" (3 partitions)
|
|
|
410
413
|
- [Kafka Overview](./) - the four helpers, shared health/close API, and the compile-binary caveat
|
|
411
414
|
- [Producer](./producer) - the sending side, plus the shared Connection & Authentication options
|
|
412
415
|
- [Admin](./admin) - create topics and inspect consumer groups from outside the running consumer
|
|
413
|
-
- [Examples & Troubleshooting](./examples) -
|
|
416
|
+
- [Examples & Troubleshooting](./examples) - IoC wiring and the common connection-error lookup table
|
|
414
417
|
|
|
415
418
|
**Files:**
|
|
416
419
|
|
|
@@ -1,140 +1,17 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Kafka Examples & Troubleshooting
|
|
3
|
-
description:
|
|
3
|
+
description: A multi-topic admin setup script, wiring Kafka helpers into IGNIS IoC, and a common-error lookup table
|
|
4
4
|
difficulty: intermediate
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Examples & Troubleshooting
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
## Producer: Send Messages with Health Monitoring
|
|
12
|
-
|
|
13
|
-
```typescript
|
|
14
|
-
import { KafkaProducerHelper, KafkaAcks } from '@venizia/ignis-helpers/kafka';
|
|
15
|
-
import { stringSerializers } from '@platformatic/kafka';
|
|
16
|
-
|
|
17
|
-
const helper = KafkaProducerHelper.newInstance({
|
|
18
|
-
bootstrapBrokers: ['broker1:9092', 'broker2:9092'],
|
|
19
|
-
clientId: 'interval-producer',
|
|
20
|
-
serializers: stringSerializers,
|
|
21
|
-
acks: KafkaAcks.ALL,
|
|
22
|
-
onBrokerConnect: ({ broker }) => console.log(`Connected to ${broker.host}:${broker.port}`),
|
|
23
|
-
onBrokerDisconnect: ({ broker }) => console.warn(`Disconnected from ${broker.host}`),
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
const producer = helper.getProducer();
|
|
27
|
-
let count = 0;
|
|
28
|
-
|
|
29
|
-
const interval = setInterval(async () => {
|
|
30
|
-
if (!helper.isHealthy()) {
|
|
31
|
-
console.warn('Producer not healthy, skipping...');
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
await producer.send({
|
|
36
|
-
messages: [{
|
|
37
|
-
topic: 'events',
|
|
38
|
-
key: `key-${count % 3}`,
|
|
39
|
-
value: JSON.stringify({ index: count, timestamp: new Date().toISOString() }),
|
|
40
|
-
}],
|
|
41
|
-
});
|
|
42
|
-
count++;
|
|
43
|
-
}, 100);
|
|
44
|
-
|
|
45
|
-
process.on('SIGINT', async () => {
|
|
46
|
-
clearInterval(interval);
|
|
47
|
-
console.log(`Shutting down... (sent ${count} messages)`);
|
|
48
|
-
await helper.close();
|
|
49
|
-
process.exit(0);
|
|
50
|
-
});
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
## Consumer: Callback-Based with Lag Monitoring
|
|
54
|
-
|
|
55
|
-
```typescript
|
|
56
|
-
import { KafkaConsumerHelper } from '@venizia/ignis-helpers/kafka';
|
|
57
|
-
import { stringDeserializers } from '@platformatic/kafka';
|
|
58
|
-
|
|
59
|
-
const helper = KafkaConsumerHelper.newInstance({
|
|
60
|
-
bootstrapBrokers: ['broker1:9092', 'broker2:9092'],
|
|
61
|
-
clientId: 'event-consumer',
|
|
62
|
-
groupId: 'processing-group',
|
|
63
|
-
deserializers: stringDeserializers,
|
|
64
|
-
|
|
65
|
-
onMessage: async ({ message }) => {
|
|
66
|
-
const data = JSON.parse(message.value!);
|
|
67
|
-
console.log(`Processing: ${message.key} -> ${JSON.stringify(data)}`);
|
|
68
|
-
await message.commit();
|
|
69
|
-
},
|
|
70
|
-
onMessageDone: ({ message }) => {
|
|
71
|
-
console.log(`Done: ${message.key}`);
|
|
72
|
-
},
|
|
73
|
-
onMessageError: ({ error, message }) => {
|
|
74
|
-
console.error(`Error processing ${message?.key}:`, error.message);
|
|
75
|
-
},
|
|
76
|
-
|
|
77
|
-
onGroupJoin: ({ groupId, memberId }) => {
|
|
78
|
-
console.log(`Joined group ${groupId} as ${memberId}`);
|
|
79
|
-
},
|
|
80
|
-
onGroupRebalance: ({ groupId }) => {
|
|
81
|
-
console.log(`Rebalance in ${groupId}`);
|
|
82
|
-
},
|
|
83
|
-
|
|
84
|
-
onLag: ({ lag }) => {
|
|
85
|
-
for (const [topic, partitionLags] of lag) {
|
|
86
|
-
partitionLags.forEach((lagValue, partition) => {
|
|
87
|
-
if (lagValue > 1000n) {
|
|
88
|
-
console.warn(`High lag on ${topic}[${partition}]: ${lagValue}`);
|
|
89
|
-
}
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
},
|
|
93
|
-
onLagError: ({ error }) => console.error('Lag error:', error),
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
await helper.start({ topics: ['events'] });
|
|
97
|
-
helper.startLagMonitoring({ topics: ['events'], interval: 10_000 });
|
|
98
|
-
|
|
99
|
-
process.on('SIGINT', async () => {
|
|
100
|
-
await helper.close();
|
|
101
|
-
process.exit(0);
|
|
102
|
-
});
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
## Consumer: Direct Stream Access (for-await)
|
|
106
|
-
|
|
107
|
-
```typescript
|
|
108
|
-
import { KafkaConsumerHelper } from '@venizia/ignis-helpers/kafka';
|
|
109
|
-
import { stringDeserializers } from '@platformatic/kafka';
|
|
110
|
-
|
|
111
|
-
const helper = KafkaConsumerHelper.newInstance({
|
|
112
|
-
bootstrapBrokers: ['localhost:9092'],
|
|
113
|
-
clientId: 'stream-consumer',
|
|
114
|
-
groupId: 'stream-group',
|
|
115
|
-
deserializers: stringDeserializers,
|
|
116
|
-
onBrokerConnect: ({ broker }) => console.log(`Connected to ${broker.host}`),
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
// Use the consumer directly for async iterator pattern
|
|
120
|
-
const consumer = helper.getConsumer();
|
|
121
|
-
const stream = await consumer.consume({
|
|
122
|
-
topics: ['orders'],
|
|
123
|
-
mode: 'committed',
|
|
124
|
-
fallbackMode: 'latest',
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
for await (const message of stream) {
|
|
128
|
-
console.log(`${message.topic}[${message.partition}] @${message.offset}: ${message.key} -> ${message.value}`);
|
|
129
|
-
await message.commit();
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
await stream.close();
|
|
133
|
-
await helper.close();
|
|
134
|
-
```
|
|
9
|
+
Two examples not covered on the reference pages, plus a lookup table for the errors you'll actually hit.
|
|
135
10
|
|
|
136
11
|
## Admin: Topic Setup Script
|
|
137
12
|
|
|
13
|
+
A deploy-time script that creates several topics with production configuration in one call. Run it once, from a `migrate` step or a CI job.
|
|
14
|
+
|
|
138
15
|
```typescript
|
|
139
16
|
import { KafkaAdminHelper } from '@venizia/ignis-helpers/kafka';
|
|
140
17
|
|
|
@@ -147,7 +24,6 @@ async function setupTopics() {
|
|
|
147
24
|
|
|
148
25
|
const admin = helper.getAdmin();
|
|
149
26
|
|
|
150
|
-
// Create topics
|
|
151
27
|
await admin.createTopics({
|
|
152
28
|
topics: ['orders', 'inventory', 'notifications'],
|
|
153
29
|
partitions: 6,
|
|
@@ -158,11 +34,8 @@ async function setupTopics() {
|
|
|
158
34
|
],
|
|
159
35
|
});
|
|
160
36
|
|
|
161
|
-
// Verify
|
|
162
37
|
const topics = await admin.listTopics({ includeInternals: false });
|
|
163
38
|
console.log('Topics:', topics);
|
|
164
|
-
|
|
165
|
-
// Health check
|
|
166
39
|
console.log('Healthy:', helper.isHealthy());
|
|
167
40
|
|
|
168
41
|
await helper.close();
|
|
@@ -171,99 +44,14 @@ async function setupTopics() {
|
|
|
171
44
|
setupTopics();
|
|
172
45
|
```
|
|
173
46
|
|
|
174
|
-
## Exactly-Once: Consume-Transform-Produce
|
|
175
|
-
|
|
176
|
-
```typescript
|
|
177
|
-
import { KafkaProducerHelper, KafkaConsumerHelper } from '@venizia/ignis-helpers/kafka';
|
|
178
|
-
import { stringSerializers, stringDeserializers } from '@platformatic/kafka';
|
|
179
|
-
|
|
180
|
-
const producer = KafkaProducerHelper.newInstance({
|
|
181
|
-
bootstrapBrokers: ['localhost:9092'],
|
|
182
|
-
clientId: 'eos-producer',
|
|
183
|
-
serializers: stringSerializers,
|
|
184
|
-
transactionalId: 'eos-tx',
|
|
185
|
-
idempotent: true,
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
const consumer = KafkaConsumerHelper.newInstance({
|
|
189
|
-
bootstrapBrokers: ['localhost:9092'],
|
|
190
|
-
clientId: 'eos-consumer',
|
|
191
|
-
groupId: 'eos-group',
|
|
192
|
-
deserializers: stringDeserializers,
|
|
193
|
-
autocommit: false,
|
|
194
|
-
|
|
195
|
-
onMessage: async ({ message }) => {
|
|
196
|
-
// Consume-transform-produce within a single transaction
|
|
197
|
-
const transformed = JSON.stringify({
|
|
198
|
-
...JSON.parse(message.value!),
|
|
199
|
-
processedAt: new Date().toISOString(),
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
await producer.runInTransaction(async ({ send, addConsumer, addOffset }) => {
|
|
203
|
-
await addConsumer(consumer.getConsumer());
|
|
204
|
-
await addOffset(message);
|
|
205
|
-
await send({
|
|
206
|
-
messages: [{ topic: 'processed-events', key: message.key, value: transformed }],
|
|
207
|
-
});
|
|
208
|
-
});
|
|
209
|
-
},
|
|
210
|
-
onMessageError: ({ error }) => console.error('Processing error:', error),
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
await consumer.start({ topics: ['raw-events'] });
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
## Schema Registry: Validated Messages
|
|
217
|
-
|
|
218
|
-
```typescript
|
|
219
|
-
import {
|
|
220
|
-
KafkaSchemaRegistryHelper,
|
|
221
|
-
KafkaProducerHelper,
|
|
222
|
-
KafkaConsumerHelper,
|
|
223
|
-
} from '@venizia/ignis-helpers/kafka';
|
|
224
|
-
|
|
225
|
-
const registry = KafkaSchemaRegistryHelper.newInstance({
|
|
226
|
-
url: 'http://localhost:8081',
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
const producer = KafkaProducerHelper.newInstance({
|
|
230
|
-
bootstrapBrokers: ['localhost:9092'],
|
|
231
|
-
clientId: 'schema-producer',
|
|
232
|
-
registry: registry.getRegistry(),
|
|
233
|
-
onBrokerConnect: ({ broker }) => console.log(`Producer connected to ${broker.host}`),
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
// Sends schema-validated objects
|
|
237
|
-
await producer.getProducer().send({
|
|
238
|
-
messages: [{
|
|
239
|
-
topic: 'orders',
|
|
240
|
-
key: 'order-1',
|
|
241
|
-
value: { id: 1, status: 'created', total: 99.99 },
|
|
242
|
-
}],
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
const consumer = KafkaConsumerHelper.newInstance({
|
|
246
|
-
bootstrapBrokers: ['localhost:9092'],
|
|
247
|
-
clientId: 'schema-consumer',
|
|
248
|
-
groupId: 'schema-group',
|
|
249
|
-
registry: registry.getRegistry(),
|
|
250
|
-
onMessage: async ({ message }) => {
|
|
251
|
-
// message.value is auto-deserialized to the schema type
|
|
252
|
-
console.log(message.value.id, message.value.status);
|
|
253
|
-
await message.commit();
|
|
254
|
-
},
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
await consumer.start({ topics: ['orders'] });
|
|
258
|
-
```
|
|
259
|
-
|
|
260
47
|
## Using Helpers with IGNIS IoC
|
|
261
48
|
|
|
49
|
+
Bind each helper once at boot. Inject it wherever you need to publish or consume - the same pattern IGNIS uses for every other connected resource.
|
|
50
|
+
|
|
262
51
|
```typescript
|
|
263
52
|
import {
|
|
264
53
|
KafkaProducerHelper,
|
|
265
54
|
KafkaConsumerHelper,
|
|
266
|
-
KafkaAdminHelper,
|
|
267
55
|
} from '@venizia/ignis-helpers/kafka';
|
|
268
56
|
import { stringSerializers, stringDeserializers } from '@platformatic/kafka';
|
|
269
57
|
import { inject } from '@venizia/ignis-inversion';
|
|
@@ -312,24 +100,22 @@ export class OrderEventService {
|
|
|
312
100
|
|
|
313
101
|
## Troubleshooting
|
|
314
102
|
|
|
315
|
-
### Common Issues
|
|
316
|
-
|
|
317
103
|
| Error | Cause | Fix |
|
|
318
104
|
|-------|-------|-----|
|
|
319
|
-
| `ECONNREFUSED localhost:9092` | Broker `advertised.listeners` set to `localhost` but connecting remotely | Set `KAFKA_ADVERTISED_LISTENERS`
|
|
320
|
-
| `Request timed out` | SASL handshake or broker unreachable | Add `connectTimeout: 30_000, requestTimeout: 30_000` |
|
|
321
|
-
| `Connection closed` | Connecting without SASL to a SASL-required listener | Check `KAFKA_LISTENER_SECURITY_PROTOCOL_MAP`
|
|
322
|
-
| `Cannot find a suitable SASL mechanism` | Wrong mechanism
|
|
323
|
-
| `Failed to deserialize a message` |
|
|
324
|
-
| `JSON.stringify cannot serialize BigInt` | `message.offset` and `message.timestamp` are `bigint` | Use custom replacer: `(_k, v) => typeof v === 'bigint' ? v.toString() : v` |
|
|
325
|
-
| Consumer idle
|
|
326
|
-
| `isHealthy()` returns `false` |
|
|
327
|
-
| `isReady()` returns `false` (consumer) | Consumer
|
|
328
|
-
| Graceful shutdown
|
|
105
|
+
| `ECONNREFUSED localhost:9092` | Broker `advertised.listeners` set to `localhost` but you're connecting remotely | Set `KAFKA_ADVERTISED_LISTENERS` to the correct external host IP |
|
|
106
|
+
| `Request timed out` | SASL handshake stalled, or the broker is unreachable | Add `connectTimeout: 30_000, requestTimeout: 30_000` |
|
|
107
|
+
| `Connection closed` | Connecting without SASL to a SASL-required listener | Check `KAFKA_LISTENER_SECURITY_PROTOCOL_MAP` - use `SASL_PLAINTEXT` |
|
|
108
|
+
| `Cannot find a suitable SASL mechanism` | Wrong mechanism, e.g. `PLAIN` when the broker only supports `SCRAM-SHA-512` | Read the error for the supported mechanisms, match `mechanism` to one |
|
|
109
|
+
| `Failed to deserialize a message` | Serializer and deserializer don't match | Match the serde on both sides. For old data, use a new consumer group or recreate the topic |
|
|
110
|
+
| `JSON.stringify cannot serialize BigInt` | `message.offset` and `message.timestamp` are `bigint` | Use a custom replacer: `(_k, v) => typeof v === 'bigint' ? v.toString() : v` |
|
|
111
|
+
| Consumer sits idle, no messages | More consumers than partitions | Make sure `numPartitions >= numConsumers` |
|
|
112
|
+
| `isHealthy()` returns `false` | Every broker disconnected - one idle disconnect alone won't trigger this | Check broker addresses, SASL config, network. `getConnectedBrokerCount()` gives the exact count |
|
|
113
|
+
| `isReady()` returns `false` (consumer) | Consumer isn't active - `start()` was never called, or the stream closed | Call `await helper.start({ topics })` before checking readiness |
|
|
114
|
+
| Graceful shutdown times out | In-flight requests are taking too long | Raise `shutdownTimeout`, or call `close({ isForce: true })` |
|
|
329
115
|
|
|
330
116
|
### Docker Kafka Configuration
|
|
331
117
|
|
|
332
|
-
|
|
118
|
+
Connecting from outside a Dockerized Kafka needs two listeners: one for containers talking to each other, one for the host.
|
|
333
119
|
|
|
334
120
|
```yaml
|
|
335
121
|
environment:
|
|
@@ -343,9 +129,11 @@ environment:
|
|
|
343
129
|
CONTROLLER:PLAINTEXT
|
|
344
130
|
```
|
|
345
131
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
132
|
+
| Listener | Used for |
|
|
133
|
+
|---|---|
|
|
134
|
+
| `INTERNAL` | Inter-broker communication |
|
|
135
|
+
| `EXTERNAL` | Client connections from outside Docker |
|
|
136
|
+
| `CONTROLLER` | KRaft controller communication |
|
|
349
137
|
|
|
350
138
|
## See also
|
|
351
139
|
|