@beespoke/micro-server-kafka 0.0.0-stage → 2.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,3 +1,97 @@
1
- # Temporary Holding Version
1
+ # @beespoke/micro-server-kafka
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ KafkaJS producers, consumers, topic helpers, and application adapter discovery.
4
+
5
+ Supports Node 24+ and Bun 1.3.14+.
6
+
7
+ ## Install
8
+
9
+ ```sh
10
+ npm install @beespoke/micro-server @beespoke/micro-server-kafka
11
+ ```
12
+
13
+ Use `bun add` in place of `npm install` for Bun projects.
14
+
15
+ ## Configure the plugin
16
+
17
+ ```ts
18
+ import { defineConfig } from '@beespoke/micro-server';
19
+
20
+ export default defineConfig({
21
+ plugins: ['@beespoke/micro-server-kafka'],
22
+ pluginOptions: {
23
+ '@beespoke/micro-server-kafka': {
24
+ config: {
25
+ clientId: 'my-api',
26
+ brokers: (process.env.KAFKA_BROKERS || '127.0.0.1:9092').split(','),
27
+ },
28
+ consumerPath: './kafka/consumers',
29
+ producerPath: './kafka/producers',
30
+ },
31
+ },
32
+ });
33
+ ```
34
+
35
+ Create both discovery directories before starting:
36
+
37
+ ```sh
38
+ mkdir -p kafka/consumers kafka/producers
39
+ ```
40
+
41
+ Requires reachable Kafka brokers. `config` accepts KafkaJS options such as TLS, SASL, retries, and timeouts. Optional `rootDir` changes the application discovery root. Discovered classes must follow `ServeKafka`'s `_`, `connect`, and `init` adapter conventions; bare `BaseConsumer` and `BaseProducer` classes are not automatically configured adapters.
42
+
43
+ ## Standalone producer
44
+
45
+ For explicit application-owned clients, HTTP core is optional:
46
+
47
+ ```ts
48
+ import { BaseKafka, BaseProducer } from '@beespoke/micro-server-kafka';
49
+
50
+ const kafka = new BaseKafka({
51
+ clientId: 'worker',
52
+ brokers: ['127.0.0.1:9092'],
53
+ });
54
+ const producer = new BaseProducer(kafka);
55
+ await producer.connect();
56
+ try {
57
+ await producer.publish({
58
+ topic: 'orders', event: 'created', message: { orderId: '123' },
59
+ });
60
+ } finally {
61
+ await producer.disconnect();
62
+ }
63
+ ```
64
+
65
+ Root exports include `BaseKafka`, `BaseProducer`, `BaseConsumer`, `ServeKafka`, `ServeKafkaOptions`, and message/publish types. The plugin owns cleanup of clients acquired through discovery; standalone applications own their clients' connect/disconnect lifecycle.
66
+
67
+ ## Start the application
68
+
69
+ Save the configuration as `micro-server.config.ts` next to your application's `package.json`, then create `index.ts`:
70
+
71
+ ```ts
72
+ import { ApplicationServer } from '@beespoke/micro-server';
73
+
74
+ await ApplicationServer.bootstrap();
75
+ for (const signal of ['SIGINT', 'SIGTERM'] as const) {
76
+ process.once(signal, () => {
77
+ void ApplicationServer.close().catch(error => {
78
+ console.error(error);
79
+ process.exitCode = 1;
80
+ });
81
+ });
82
+ }
83
+ ```
84
+
85
+ Run `bun index.ts` or `node --env-file-if-exists=.env index.ts`. Node requires ESM (`"type": "module"` in package.json). When using Bun, load configuration secrets from the application environment or its `.env` file.
86
+
87
+ ## TypeScript and lifecycle
88
+
89
+ Add this type-only import to an application-owned `plugin-types.d.ts` for configuration autocomplete:
90
+
91
+ ```ts
92
+ import type {} from '@beespoke/micro-server-kafka/plugin';
93
+ ```
94
+
95
+ Core validates plugin options before setup and owns cleanup after successful startup or startup failure. Await `ApplicationServer.bootstrap()` before using the configured client, and call `ApplicationServer.close()` during application shutdown. Importing the package does not open a connection. Relative plugin paths resolve from the selected configuration file's directory.
96
+
97
+ The root export can also be used without HTTP core. In that case, explicitly initialize and close the client. The `/plugin` entry is the integration adapter for `@beespoke/micro-server`.
@@ -0,0 +1,14 @@
1
+ import type { ConsumerRunConfig, Consumer, ConsumerConfig } from 'kafkajs';
2
+ import type { BaseKafka } from './BaseKafka.js';
3
+ import type { MessageHandler } from './BrokerType.js';
4
+ export declare class BaseConsumer {
5
+ consumer: Consumer | null;
6
+ private readonly kafka;
7
+ private connecting?;
8
+ private closing?;
9
+ private connected;
10
+ constructor(baseKafka: BaseKafka);
11
+ connect(groupId: string, config?: ConsumerConfig): Promise<void>;
12
+ disconnect(): Promise<void>;
13
+ subscribe(topic: string, messageHandler: MessageHandler, consumerRunConfig?: ConsumerRunConfig): Promise<void>;
14
+ }
@@ -0,0 +1,11 @@
1
+ import { Kafka, type KafkaConfig } from 'kafkajs';
2
+ export declare class BaseKafka {
3
+ private readonly kafka;
4
+ constructor(config: KafkaConfig);
5
+ getKafka(): Kafka;
6
+ createTopics(topics: {
7
+ topic: string;
8
+ partitions: number;
9
+ replicationFactor: number;
10
+ }[]): Promise<void>;
11
+ }
@@ -0,0 +1,14 @@
1
+ import { type ProducerConfig } from 'kafkajs';
2
+ import type { BaseKafka } from './BaseKafka.js';
3
+ import type { PublishType } from './BrokerType.js';
4
+ export declare class BaseProducer {
5
+ private producer;
6
+ private readonly kafka;
7
+ private connecting?;
8
+ private closing?;
9
+ private connected;
10
+ constructor(baseKafka: BaseKafka);
11
+ connect(config?: ProducerConfig): Promise<void>;
12
+ disconnect(): Promise<void>;
13
+ publish(data: PublishType): Promise<boolean>;
14
+ }
@@ -0,0 +1,12 @@
1
+ export type PublishType = {
2
+ topic: string;
3
+ headers?: Record<string, string>;
4
+ event: string;
5
+ message: Record<string, any>;
6
+ };
7
+ export type MessageType = {
8
+ headers?: Record<string, string>;
9
+ event: string;
10
+ data: any;
11
+ };
12
+ export type MessageHandler = (message: MessageType) => Promise<void>;
@@ -0,0 +1,13 @@
1
+ /** Structural subset of the core context; standalone clients need no HTTP dependency. */
2
+ export interface Logger {
3
+ info(...args: unknown[]): void;
4
+ warn(...args: unknown[]): void;
5
+ error(...args: unknown[]): void;
6
+ debug(...args: unknown[]): void;
7
+ }
8
+ export interface PluginContext {
9
+ readonly logger: Logger;
10
+ readonly configDir: string;
11
+ onStart(hook: () => void | Promise<void>): void;
12
+ onClose(hook: () => void | Promise<void>): void;
13
+ }
@@ -0,0 +1,25 @@
1
+ import type { KafkaConfig } from 'kafkajs';
2
+ import type { PluginContext, Logger } from './PluginContext.js';
3
+ export interface ServeKafkaOptions {
4
+ /** Explicit root takes precedence over the application's config directory. */
5
+ rootDir?: string;
6
+ logger?: Logger;
7
+ }
8
+ /** Discovers existing consumer/producer classes, including their static `_` facade. */
9
+ export declare class ServeKafka {
10
+ private readonly config;
11
+ private readonly consumerPath;
12
+ private readonly producerPath;
13
+ private readonly options;
14
+ readonly name = "kafka";
15
+ private rootDir;
16
+ private logger;
17
+ private readonly resources;
18
+ private cleanupFailed;
19
+ constructor(config: KafkaConfig, consumerPath?: string, producerPath?: string, options?: ServeKafkaOptions);
20
+ setup(context: PluginContext): Promise<void>;
21
+ startConsumers(): Promise<void>;
22
+ startProducers(): Promise<void>;
23
+ private start;
24
+ close(): Promise<void>;
25
+ }
@@ -0,0 +1,6 @@
1
+ export { BaseKafka } from './BaseKafka.js';
2
+ export { BaseConsumer } from './BaseConsumer.js';
3
+ export { BaseProducer } from './BaseProducer.js';
4
+ export { ServeKafka } from './ServeKafka.js';
5
+ export type { ServeKafkaOptions } from './ServeKafka.js';
6
+ export type { PublishType, MessageType, MessageHandler } from './BrokerType.js';
package/dist/index.js ADDED
@@ -0,0 +1,286 @@
1
+ // src/BaseKafka.ts
2
+ import { Kafka, logLevel } from "kafkajs";
3
+
4
+ class BaseKafka {
5
+ kafka;
6
+ constructor(config) {
7
+ this.kafka = new Kafka({ ...config, logLevel: config.logLevel ?? logLevel.INFO });
8
+ }
9
+ getKafka() {
10
+ return this.kafka;
11
+ }
12
+ async createTopics(topics) {
13
+ const admin = this.kafka.admin();
14
+ let failure;
15
+ let failed = false;
16
+ try {
17
+ await admin.connect();
18
+ const existingTopics = await admin.listTopics();
19
+ for (const { topic, partitions, replicationFactor } of topics) {
20
+ if (!existingTopics.includes(topic)) {
21
+ await admin.createTopics({ topics: [{ topic, numPartitions: partitions, replicationFactor }] });
22
+ }
23
+ }
24
+ } catch (error) {
25
+ failure = error;
26
+ failed = true;
27
+ }
28
+ try {
29
+ await admin.disconnect();
30
+ } catch (error) {
31
+ if (failed)
32
+ throw new AggregateError([failure, error], "Kafka topic creation and admin cleanup failed", { cause: failure });
33
+ throw error;
34
+ }
35
+ if (failed)
36
+ throw failure;
37
+ }
38
+ }
39
+ // src/BaseConsumer.ts
40
+ class BaseConsumer {
41
+ consumer = null;
42
+ kafka;
43
+ connecting;
44
+ closing;
45
+ connected = false;
46
+ constructor(baseKafka) {
47
+ this.kafka = baseKafka.getKafka();
48
+ }
49
+ async connect(groupId, config) {
50
+ if (this.closing)
51
+ await this.closing;
52
+ if (this.connecting)
53
+ return this.connecting;
54
+ if (this.connected)
55
+ return;
56
+ if (this.consumer)
57
+ await this.disconnect();
58
+ const consumer = this.kafka.consumer({ groupId, ...config });
59
+ this.consumer = consumer;
60
+ this.connecting = (async () => {
61
+ try {
62
+ await consumer.connect();
63
+ this.connected = true;
64
+ } catch (error) {
65
+ try {
66
+ await consumer.disconnect();
67
+ this.consumer = null;
68
+ } catch (cleanupError) {
69
+ throw new AggregateError([error, cleanupError], "Kafka consumer connection and cleanup failed", { cause: error });
70
+ }
71
+ throw error;
72
+ }
73
+ })();
74
+ try {
75
+ await this.connecting;
76
+ } finally {
77
+ this.connecting = undefined;
78
+ }
79
+ }
80
+ async disconnect() {
81
+ if (this.closing)
82
+ return this.closing;
83
+ this.closing = (async () => {
84
+ try {
85
+ await this.connecting;
86
+ } catch {}
87
+ const consumer = this.consumer;
88
+ this.connected = false;
89
+ if (consumer) {
90
+ await consumer.disconnect();
91
+ this.consumer = null;
92
+ }
93
+ })();
94
+ try {
95
+ await this.closing;
96
+ } finally {
97
+ this.closing = undefined;
98
+ }
99
+ }
100
+ async subscribe(topic, messageHandler, consumerRunConfig) {
101
+ const consumer = this.consumer;
102
+ if (!this.connected || !consumer)
103
+ throw new Error("Consumer not connected");
104
+ await consumer.subscribe({ topic, fromBeginning: true });
105
+ await consumer.run({
106
+ ...consumerRunConfig,
107
+ eachMessage: async ({ topic: topic2, partition, message }) => {
108
+ const parsedMessage = {
109
+ headers: message.headers,
110
+ event: message.key?.toString() || "",
111
+ data: message.value ? JSON.parse(message.value.toString()) : null
112
+ };
113
+ await messageHandler(parsedMessage);
114
+ await consumer.commitOffsets([{ topic: topic2, partition, offset: (BigInt(message.offset) + 1n).toString() }]);
115
+ }
116
+ });
117
+ }
118
+ }
119
+ // src/BaseProducer.ts
120
+ import { Partitioners } from "kafkajs";
121
+
122
+ class BaseProducer {
123
+ producer = null;
124
+ kafka;
125
+ connecting;
126
+ closing;
127
+ connected = false;
128
+ constructor(baseKafka) {
129
+ this.kafka = baseKafka.getKafka();
130
+ }
131
+ async connect(config) {
132
+ if (this.closing)
133
+ await this.closing;
134
+ if (this.connecting)
135
+ return this.connecting;
136
+ if (this.connected)
137
+ return;
138
+ if (this.producer)
139
+ await this.disconnect();
140
+ const producer = this.kafka.producer({ createPartitioner: Partitioners.DefaultPartitioner, ...config });
141
+ this.producer = producer;
142
+ this.connecting = (async () => {
143
+ try {
144
+ await producer.connect();
145
+ this.connected = true;
146
+ } catch (error) {
147
+ try {
148
+ await producer.disconnect();
149
+ this.producer = null;
150
+ } catch (cleanupError) {
151
+ throw new AggregateError([error, cleanupError], "Kafka producer connection and cleanup failed", { cause: error });
152
+ }
153
+ throw error;
154
+ }
155
+ })();
156
+ try {
157
+ await this.connecting;
158
+ } finally {
159
+ this.connecting = undefined;
160
+ }
161
+ }
162
+ async disconnect() {
163
+ if (this.closing)
164
+ return this.closing;
165
+ this.closing = (async () => {
166
+ try {
167
+ await this.connecting;
168
+ } catch {}
169
+ const producer = this.producer;
170
+ this.connected = false;
171
+ if (producer) {
172
+ await producer.disconnect();
173
+ this.producer = null;
174
+ }
175
+ })();
176
+ try {
177
+ await this.closing;
178
+ } finally {
179
+ this.closing = undefined;
180
+ }
181
+ }
182
+ async publish(data) {
183
+ if (!this.connected || !this.producer)
184
+ throw new Error("Producer not connected");
185
+ const result = await this.producer.send({
186
+ topic: data.topic,
187
+ messages: [{ headers: data.headers, key: data.event, value: JSON.stringify(data.message) }]
188
+ });
189
+ return result.length > 0;
190
+ }
191
+ }
192
+ // src/ServeKafka.ts
193
+ import { readdir } from "node:fs/promises";
194
+ import { resolve } from "node:path";
195
+ import { pathToFileURL } from "node:url";
196
+
197
+ class ServeKafka {
198
+ config;
199
+ consumerPath;
200
+ producerPath;
201
+ options;
202
+ name = "kafka";
203
+ rootDir;
204
+ logger;
205
+ resources = new Map;
206
+ cleanupFailed = false;
207
+ constructor(config, consumerPath = "kafka/consumers", producerPath = "kafka/producers", options = {}) {
208
+ this.config = config;
209
+ this.consumerPath = consumerPath;
210
+ this.producerPath = producerPath;
211
+ this.options = options;
212
+ this.rootDir = resolve(options.rootDir ?? process.cwd());
213
+ this.logger = options.logger ?? console;
214
+ }
215
+ async setup(context) {
216
+ this.rootDir = resolve(this.options.rootDir ?? context.configDir);
217
+ this.logger = this.options.logger ?? context.logger;
218
+ context.onClose(() => this.close());
219
+ await this.startConsumers();
220
+ await this.startProducers();
221
+ }
222
+ async startConsumers() {
223
+ await this.start(this.consumerPath, "consumer");
224
+ }
225
+ async startProducers() {
226
+ await this.start(this.producerPath, "producer");
227
+ }
228
+ async start(directory, kind) {
229
+ if (this.cleanupFailed)
230
+ throw new Error("Kafka adapter cleanup failed; await close() successfully before restarting");
231
+ try {
232
+ const fullDirectory = resolve(this.rootDir, directory);
233
+ const files = (await readdir(fullDirectory)).filter((file) => new RegExp(`\\.${kind}\\.(?:ts|mts|js|mjs)$`).test(file)).sort();
234
+ for (const file of files) {
235
+ const filePath = resolve(fullDirectory, file);
236
+ if (this.resources.has(filePath))
237
+ continue;
238
+ const AdapterClass = (await import(pathToFileURL(filePath).href)).default;
239
+ if (typeof AdapterClass !== "function")
240
+ throw new Error(`Kafka adapter ${file} must default-export a class`);
241
+ const instance = new AdapterClass(this.config);
242
+ const client = AdapterClass._ ?? instance._ ?? instance;
243
+ if (typeof client.connect !== "function" || typeof client.disconnect !== "function") {
244
+ throw new Error(`Kafka adapter ${file} must expose connect() and disconnect(), directly or through static _`);
245
+ }
246
+ this.resources.set(filePath, client);
247
+ const baseName = file.replace(new RegExp(`\\.${kind}\\.(?:ts|mts|js|mjs)$`), "");
248
+ await client.connect(kind === "consumer" ? `${baseName}-group` : this.config.clientId);
249
+ if (kind === "consumer") {
250
+ if (typeof AdapterClass.init === "function")
251
+ await AdapterClass.init();
252
+ else if (typeof instance.init === "function")
253
+ await instance.init();
254
+ }
255
+ this.logger.info(`Started ${kind}: ${baseName}`);
256
+ }
257
+ } catch (error) {
258
+ try {
259
+ await this.close();
260
+ } catch (cleanupError) {
261
+ throw new AggregateError([error, cleanupError], "Kafka startup and cleanup failed", { cause: error });
262
+ }
263
+ throw error;
264
+ }
265
+ }
266
+ async close() {
267
+ const errors = [];
268
+ for (const [key, client] of [...this.resources].reverse()) {
269
+ try {
270
+ await client.disconnect();
271
+ this.resources.delete(key);
272
+ } catch (error) {
273
+ errors.push(error);
274
+ }
275
+ }
276
+ this.cleanupFailed = errors.length > 0;
277
+ if (errors.length)
278
+ throw new AggregateError(errors, "Failed to close Kafka adapters");
279
+ }
280
+ }
281
+ export {
282
+ ServeKafka,
283
+ BaseProducer,
284
+ BaseKafka,
285
+ BaseConsumer
286
+ };
@@ -0,0 +1,15 @@
1
+ import type { PluginDefinition } from '@beespoke/micro-server';
2
+ import type { KafkaConfig } from 'kafkajs';
3
+ export interface KafkaPluginOptions {
4
+ config: KafkaConfig;
5
+ consumerPath?: string;
6
+ producerPath?: string;
7
+ rootDir?: string;
8
+ }
9
+ declare module '@beespoke/micro-server' {
10
+ interface PluginOptionsRegistry {
11
+ '@beespoke/micro-server-kafka': KafkaPluginOptions;
12
+ }
13
+ }
14
+ declare const definition: PluginDefinition<KafkaPluginOptions>;
15
+ export default definition;
@@ -0,0 +1,88 @@
1
+ // src/plugin/index.ts
2
+ import { ServeKafka } from "../index.js";
3
+ function object(value) {
4
+ return !!value && typeof value === "object" && !Array.isArray(value);
5
+ }
6
+ var definition = {
7
+ name: "@beespoke/micro-server-kafka",
8
+ validate(options) {
9
+ if (!object(options))
10
+ throw new Error("Kafka options must be an object");
11
+ if (Object.keys(options).some((key) => !["config", "consumerPath", "producerPath", "rootDir"].includes(key)))
12
+ throw new Error("Kafka options contain an unknown field");
13
+ for (const key of ["consumerPath", "producerPath", "rootDir"]) {
14
+ if (options[key] !== undefined && (typeof options[key] !== "string" || !options[key].trim()))
15
+ throw new Error(`Kafka ${key} must be a nonempty string`);
16
+ }
17
+ const config = options.config;
18
+ if (!object(config))
19
+ throw new Error("Kafka config must be an object");
20
+ const configKeys = ["brokers", "ssl", "sasl", "clientId", "connectionTimeout", "authenticationTimeout", "reauthenticationThreshold", "requestTimeout", "enforceRequestTimeout", "retry", "socketFactory", "logLevel", "logCreator"];
21
+ if (Object.keys(config).some((key) => !configKeys.includes(key)))
22
+ throw new Error("Kafka config contains an unknown field");
23
+ if (typeof config.brokers !== "function" && (!Array.isArray(config.brokers) || !config.brokers.length || config.brokers.some((broker) => typeof broker !== "string" || !broker.trim())))
24
+ throw new Error("Kafka config.brokers must be a nonempty string array or function");
25
+ if (config.clientId !== undefined && (typeof config.clientId !== "string" || !config.clientId.trim()))
26
+ throw new Error("Kafka config.clientId must be a nonempty string");
27
+ for (const key of ["connectionTimeout", "authenticationTimeout", "reauthenticationThreshold", "requestTimeout"]) {
28
+ if (config[key] !== undefined && (typeof config[key] !== "number" || !Number.isFinite(config[key]) || config[key] < 0))
29
+ throw new Error(`Kafka config.${key} must be a nonnegative number`);
30
+ }
31
+ for (const key of ["socketFactory", "logCreator"]) {
32
+ if (config[key] !== undefined && typeof config[key] !== "function")
33
+ throw new Error(`Kafka config.${key} must be a function`);
34
+ }
35
+ if (config.enforceRequestTimeout !== undefined && typeof config.enforceRequestTimeout !== "boolean")
36
+ throw new Error("Kafka config.enforceRequestTimeout must be a boolean");
37
+ if (config.logLevel !== undefined && ![0, 1, 2, 4, 5].includes(config.logLevel))
38
+ throw new Error("Kafka config.logLevel must be a Kafka log level");
39
+ if (config.ssl !== undefined && typeof config.ssl !== "boolean" && !object(config.ssl))
40
+ throw new Error("Kafka config.ssl must be a boolean or TLS options object");
41
+ if (config.retry !== undefined) {
42
+ if (!object(config.retry))
43
+ throw new Error("Kafka config.retry must be an object");
44
+ const retryKeys = ["maxRetryTime", "initialRetryTime", "factor", "multiplier", "retries", "restartOnFailure"];
45
+ if (Object.keys(config.retry).some((key) => !retryKeys.includes(key)))
46
+ throw new Error("Kafka config.retry contains an unknown field");
47
+ for (const key of retryKeys.filter((key2) => key2 !== "restartOnFailure")) {
48
+ const value = config.retry[key];
49
+ if (value !== undefined && (typeof value !== "number" || !Number.isFinite(value) || value < 0))
50
+ throw new Error(`Kafka config.retry.${key} must be a nonnegative number`);
51
+ }
52
+ if (config.retry.restartOnFailure !== undefined && typeof config.retry.restartOnFailure !== "function")
53
+ throw new Error("Kafka config.retry.restartOnFailure must be a function");
54
+ }
55
+ if (config.sasl !== undefined) {
56
+ const sasl = config.sasl;
57
+ if (!object(sasl) || typeof sasl.mechanism !== "string" || !sasl.mechanism.trim())
58
+ throw new Error("Kafka config.sasl.mechanism must be a nonempty string");
59
+ if (sasl.authenticationProvider !== undefined) {
60
+ if (typeof sasl.authenticationProvider !== "function")
61
+ throw new Error("Kafka config.sasl.authenticationProvider must be a function");
62
+ } else if (["plain", "scram-sha-256", "scram-sha-512"].includes(sasl.mechanism)) {
63
+ for (const key of ["username", "password"]) {
64
+ if (typeof sasl[key] !== "string" || !sasl[key].trim())
65
+ throw new Error(`Kafka config.sasl.${key} must be a nonempty string`);
66
+ }
67
+ } else if (sasl.mechanism === "oauthbearer") {
68
+ if (typeof sasl.oauthBearerProvider !== "function")
69
+ throw new Error("Kafka config.sasl.oauthBearerProvider must be a function");
70
+ } else if (sasl.mechanism === "aws") {
71
+ for (const key of ["authorizationIdentity", "accessKeyId", "secretAccessKey"]) {
72
+ if (typeof sasl[key] !== "string" || !sasl[key].trim())
73
+ throw new Error(`Kafka config.sasl.${key} must be a nonempty string`);
74
+ }
75
+ if (sasl.sessionToken !== undefined && typeof sasl.sessionToken !== "string")
76
+ throw new Error("Kafka config.sasl.sessionToken must be a string");
77
+ } else
78
+ throw new Error("Kafka config.sasl requires a supported mechanism or authenticationProvider");
79
+ }
80
+ },
81
+ create(options) {
82
+ return new ServeKafka(options.config, options.consumerPath, options.producerPath, { rootDir: options.rootDir });
83
+ }
84
+ };
85
+ var plugin_default = definition;
86
+ export {
87
+ plugin_default as default
88
+ };
package/package.json CHANGED
@@ -1,6 +1,37 @@
1
1
  {
2
2
  "name": "@beespoke/micro-server-kafka",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "2.0.3",
4
+ "type": "module",
5
+ "license": "MIT",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ },
13
+ "./plugin": {
14
+ "types": "./dist/plugin/index.d.ts",
15
+ "import": "./dist/plugin/index.js",
16
+ "default": "./dist/plugin/index.js"
17
+ }
18
+ },
19
+ "files": [
20
+ "dist"
21
+ ],
22
+ "scripts": {
23
+ "build": "bun ../../scripts/build-package.ts"
24
+ },
25
+ "dependencies": {
26
+ "kafkajs": "^2.2.4",
27
+ "@types/node": "^24.0.0"
28
+ },
29
+ "peerDependencies": {
30
+ "@beespoke/micro-server": "^2.0.3"
31
+ },
32
+ "peerDependenciesMeta": {
33
+ "@beespoke/micro-server": {
34
+ "optional": true
35
+ }
36
+ }
37
+ }