@walkeros/server-destination-kafka 3.4.0-next-1776749829492

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.
@@ -0,0 +1,212 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __export = (target, all) => {
3
+ for (var name in all)
4
+ __defProp(target, name, { get: all[name], enumerable: true });
5
+ };
6
+
7
+ // src/examples/env.ts
8
+ var env_exports = {};
9
+ __export(env_exports, {
10
+ push: () => push,
11
+ simulation: () => simulation
12
+ });
13
+ var asyncConnect = () => Promise.resolve();
14
+ var asyncDisconnect = () => Promise.resolve();
15
+ var asyncSend = () => Promise.resolve([]);
16
+ function createMockProducer() {
17
+ return {
18
+ connect: asyncConnect,
19
+ disconnect: asyncDisconnect,
20
+ send: asyncSend
21
+ };
22
+ }
23
+ var mockProducerFactory = () => createMockProducer();
24
+ var MockKafkaClient = class {
25
+ constructor(_config) {
26
+ }
27
+ producer(config) {
28
+ return mockProducerFactory(config);
29
+ }
30
+ };
31
+ var MockKafkaConstructor = MockKafkaClient;
32
+ var MockCompressionTypes = {
33
+ None: 0,
34
+ GZIP: 1,
35
+ Snappy: 2,
36
+ LZ4: 3,
37
+ ZSTD: 4
38
+ };
39
+ var push = {
40
+ Kafka: {
41
+ Kafka: MockKafkaConstructor,
42
+ CompressionTypes: MockCompressionTypes
43
+ }
44
+ };
45
+ var simulation = ["call:producer.send"];
46
+
47
+ // src/examples/step.ts
48
+ var step_exports = {};
49
+ __export(step_exports, {
50
+ defaultEvent: () => defaultEvent,
51
+ ignoredEvent: () => ignoredEvent,
52
+ keyFromUser: () => keyFromUser,
53
+ mappedData: () => mappedData,
54
+ mappedEventName: () => mappedEventName,
55
+ topicOverride: () => topicOverride
56
+ });
57
+ import { getEvent } from "@walkeros/core";
58
+ var defaultEvent = {
59
+ in: getEvent("page view", {
60
+ timestamp: 1700000100
61
+ }),
62
+ out: [
63
+ [
64
+ "producer.send",
65
+ {
66
+ topic: "walkeros-events",
67
+ messages: [
68
+ {
69
+ key: "page_view",
70
+ value: "json:event",
71
+ headers: { "content-type": "application/json" },
72
+ timestamp: "1700000100"
73
+ }
74
+ ],
75
+ acks: -1,
76
+ compression: 1
77
+ }
78
+ ]
79
+ ]
80
+ };
81
+ var mappedEventName = {
82
+ in: getEvent("order complete", {
83
+ timestamp: 1700000101
84
+ }),
85
+ mapping: {
86
+ name: "purchase"
87
+ },
88
+ out: [
89
+ [
90
+ "producer.send",
91
+ {
92
+ topic: "walkeros-events",
93
+ messages: [
94
+ {
95
+ key: "purchase",
96
+ value: "json:event",
97
+ headers: { "content-type": "application/json" },
98
+ timestamp: "1700000101"
99
+ }
100
+ ],
101
+ acks: -1,
102
+ compression: 1
103
+ }
104
+ ]
105
+ ]
106
+ };
107
+ var mappedData = {
108
+ in: getEvent("order complete", {
109
+ timestamp: 1700000102,
110
+ data: { id: "ORD-400", total: 99.99, currency: "EUR" }
111
+ }),
112
+ mapping: {
113
+ name: "purchase",
114
+ data: {
115
+ map: {
116
+ order_id: "data.id",
117
+ revenue: "data.total",
118
+ currency: "data.currency"
119
+ }
120
+ }
121
+ },
122
+ out: [
123
+ [
124
+ "producer.send",
125
+ {
126
+ topic: "walkeros-events",
127
+ messages: [
128
+ {
129
+ key: "purchase",
130
+ value: "json:data",
131
+ headers: { "content-type": "application/json" },
132
+ timestamp: "1700000102"
133
+ }
134
+ ],
135
+ acks: -1,
136
+ compression: 1
137
+ }
138
+ ]
139
+ ]
140
+ };
141
+ var keyFromUser = {
142
+ in: getEvent("user signup", {
143
+ timestamp: 1700000103,
144
+ user: { id: "usr-789" },
145
+ data: { plan: "pro" }
146
+ }),
147
+ settings: {
148
+ kafka: {
149
+ brokers: ["localhost:9092"],
150
+ topic: "walkeros-events",
151
+ key: "user.id"
152
+ }
153
+ },
154
+ out: [
155
+ [
156
+ "producer.send",
157
+ {
158
+ topic: "walkeros-events",
159
+ messages: [
160
+ {
161
+ key: "usr-789",
162
+ value: "json:event",
163
+ headers: { "content-type": "application/json" },
164
+ timestamp: "1700000103"
165
+ }
166
+ ],
167
+ acks: -1,
168
+ compression: 1
169
+ }
170
+ ]
171
+ ]
172
+ };
173
+ var topicOverride = {
174
+ in: getEvent("order complete", {
175
+ timestamp: 1700000104,
176
+ data: { id: "ORD-500", total: 42 }
177
+ }),
178
+ mapping: {
179
+ settings: {
180
+ topic: "orders-stream"
181
+ }
182
+ },
183
+ out: [
184
+ [
185
+ "producer.send",
186
+ {
187
+ topic: "orders-stream",
188
+ messages: [
189
+ {
190
+ key: "order_complete",
191
+ value: "json:event",
192
+ headers: { "content-type": "application/json" },
193
+ timestamp: "1700000104"
194
+ }
195
+ ],
196
+ acks: -1,
197
+ compression: 1
198
+ }
199
+ ]
200
+ ]
201
+ };
202
+ var ignoredEvent = {
203
+ in: getEvent("debug noise", {
204
+ timestamp: 1700000105
205
+ }),
206
+ mapping: { ignore: true },
207
+ out: []
208
+ };
209
+ export {
210
+ env_exports as env,
211
+ step_exports as step
212
+ };
@@ -0,0 +1,154 @@
1
+ import { Destination as Destination$1 } from '@walkeros/core';
2
+ import { DestinationServer } from '@walkeros/server-core';
3
+
4
+ /**
5
+ * Mock-friendly Producer interface used by the destination.
6
+ * Tests provide this via env.Kafka; production creates a real
7
+ * kafkajs Producer and adapts it through settings._producer.
8
+ */
9
+ interface KafkaProducerMock {
10
+ connect: () => Promise<void>;
11
+ disconnect: () => Promise<void>;
12
+ send: (record: ProducerRecord) => Promise<unknown>;
13
+ }
14
+ /**
15
+ * Mock-friendly Kafka client interface (subset of kafkajs.Kafka).
16
+ */
17
+ interface KafkaClientMock {
18
+ producer: (config?: ProducerConfig) => KafkaProducerMock;
19
+ }
20
+ /**
21
+ * Constructor signature for the Kafka client. Accepts a config
22
+ * object and returns a client with producer() factory.
23
+ */
24
+ type KafkaClientConstructor = new (config: KafkaClientConfig) => KafkaClientMock;
25
+ interface KafkaClientConfig {
26
+ clientId?: string;
27
+ brokers: string[];
28
+ ssl?: boolean | Record<string, unknown>;
29
+ sasl?: SASLConfig;
30
+ connectionTimeout?: number;
31
+ requestTimeout?: number;
32
+ retry?: RetryConfig;
33
+ }
34
+ interface ProducerConfig {
35
+ allowAutoTopicCreation?: boolean;
36
+ idempotent?: boolean;
37
+ }
38
+ interface ProducerRecord {
39
+ topic: string;
40
+ messages: ProducerMessage[];
41
+ acks?: number;
42
+ compression?: number;
43
+ timeout?: number;
44
+ }
45
+ interface ProducerMessage {
46
+ key?: string;
47
+ value: string;
48
+ headers?: Record<string, string>;
49
+ timestamp?: string;
50
+ partition?: number;
51
+ }
52
+ interface CompressionTypesMap {
53
+ None: number;
54
+ GZIP: number;
55
+ Snappy: number;
56
+ LZ4: number;
57
+ ZSTD: number;
58
+ }
59
+ type CompressionType = 'none' | 'gzip' | 'snappy' | 'lz4' | 'zstd';
60
+ interface SASLConfig {
61
+ mechanism: 'plain' | 'scram-sha-256' | 'scram-sha-512' | 'aws' | 'oauthbearer';
62
+ username?: string;
63
+ password?: string;
64
+ accessKeyId?: string;
65
+ secretAccessKey?: string;
66
+ sessionToken?: string;
67
+ authorizationIdentity?: string;
68
+ }
69
+ interface RetryConfig {
70
+ maxRetryTime?: number;
71
+ initialRetryTime?: number;
72
+ retries?: number;
73
+ }
74
+ interface KafkaSettings {
75
+ brokers: string[];
76
+ clientId?: string;
77
+ ssl?: boolean | Record<string, unknown>;
78
+ sasl?: SASLConfig;
79
+ connectionTimeout?: number;
80
+ requestTimeout?: number;
81
+ topic: string;
82
+ acks?: number;
83
+ timeout?: number;
84
+ compression?: CompressionType;
85
+ idempotent?: boolean;
86
+ allowAutoTopicCreation?: boolean;
87
+ key?: string;
88
+ headers?: Record<string, string>;
89
+ retry?: RetryConfig;
90
+ _producer?: KafkaProducerMock;
91
+ }
92
+ interface Settings {
93
+ kafka: KafkaSettings;
94
+ }
95
+ type InitSettings = Partial<Settings>;
96
+ interface Mapping {
97
+ /** Override message key mapping path for this rule. */
98
+ key?: string;
99
+ /** Override topic for this rule. */
100
+ topic?: string;
101
+ }
102
+ /**
103
+ * Env -- optional Kafka SDK override. Production leaves this undefined
104
+ * and the destination creates real Kafka client instances. Tests provide
105
+ * mocks via env.Kafka.
106
+ */
107
+ interface Env extends DestinationServer.Env {
108
+ Kafka?: {
109
+ Kafka: KafkaClientConstructor;
110
+ CompressionTypes: CompressionTypesMap;
111
+ };
112
+ }
113
+ type Types = Destination$1.Types<Settings, Mapping, Env, InitSettings>;
114
+ interface Destination extends DestinationServer.Destination<Types> {
115
+ init: DestinationServer.InitFn<Types>;
116
+ }
117
+ type Config = {
118
+ settings: Settings;
119
+ } & DestinationServer.Config<Types>;
120
+ type InitFn = DestinationServer.InitFn<Types>;
121
+ type PushFn = DestinationServer.PushFn<Types>;
122
+ type PartialConfig = DestinationServer.PartialConfig<Types>;
123
+ type PushEvents = DestinationServer.PushEvents<Mapping>;
124
+
125
+ type index_CompressionType = CompressionType;
126
+ type index_CompressionTypesMap = CompressionTypesMap;
127
+ type index_Config = Config;
128
+ type index_Destination = Destination;
129
+ type index_Env = Env;
130
+ type index_InitFn = InitFn;
131
+ type index_InitSettings = InitSettings;
132
+ type index_KafkaClientConfig = KafkaClientConfig;
133
+ type index_KafkaClientConstructor = KafkaClientConstructor;
134
+ type index_KafkaClientMock = KafkaClientMock;
135
+ type index_KafkaProducerMock = KafkaProducerMock;
136
+ type index_KafkaSettings = KafkaSettings;
137
+ type index_Mapping = Mapping;
138
+ type index_PartialConfig = PartialConfig;
139
+ type index_ProducerConfig = ProducerConfig;
140
+ type index_ProducerMessage = ProducerMessage;
141
+ type index_ProducerRecord = ProducerRecord;
142
+ type index_PushEvents = PushEvents;
143
+ type index_PushFn = PushFn;
144
+ type index_RetryConfig = RetryConfig;
145
+ type index_SASLConfig = SASLConfig;
146
+ type index_Settings = Settings;
147
+ type index_Types = Types;
148
+ declare namespace index {
149
+ export type { index_CompressionType as CompressionType, index_CompressionTypesMap as CompressionTypesMap, index_Config as Config, index_Destination as Destination, index_Env as Env, index_InitFn as InitFn, index_InitSettings as InitSettings, index_KafkaClientConfig as KafkaClientConfig, index_KafkaClientConstructor as KafkaClientConstructor, index_KafkaClientMock as KafkaClientMock, index_KafkaProducerMock as KafkaProducerMock, index_KafkaSettings as KafkaSettings, index_Mapping as Mapping, index_PartialConfig as PartialConfig, index_ProducerConfig as ProducerConfig, index_ProducerMessage as ProducerMessage, index_ProducerRecord as ProducerRecord, index_PushEvents as PushEvents, index_PushFn as PushFn, index_RetryConfig as RetryConfig, index_SASLConfig as SASLConfig, index_Settings as Settings, index_Types as Types };
150
+ }
151
+
152
+ declare const destinationKafka: Destination;
153
+
154
+ export { index as DestinationKafka, destinationKafka as default, destinationKafka };
@@ -0,0 +1,154 @@
1
+ import { Destination as Destination$1 } from '@walkeros/core';
2
+ import { DestinationServer } from '@walkeros/server-core';
3
+
4
+ /**
5
+ * Mock-friendly Producer interface used by the destination.
6
+ * Tests provide this via env.Kafka; production creates a real
7
+ * kafkajs Producer and adapts it through settings._producer.
8
+ */
9
+ interface KafkaProducerMock {
10
+ connect: () => Promise<void>;
11
+ disconnect: () => Promise<void>;
12
+ send: (record: ProducerRecord) => Promise<unknown>;
13
+ }
14
+ /**
15
+ * Mock-friendly Kafka client interface (subset of kafkajs.Kafka).
16
+ */
17
+ interface KafkaClientMock {
18
+ producer: (config?: ProducerConfig) => KafkaProducerMock;
19
+ }
20
+ /**
21
+ * Constructor signature for the Kafka client. Accepts a config
22
+ * object and returns a client with producer() factory.
23
+ */
24
+ type KafkaClientConstructor = new (config: KafkaClientConfig) => KafkaClientMock;
25
+ interface KafkaClientConfig {
26
+ clientId?: string;
27
+ brokers: string[];
28
+ ssl?: boolean | Record<string, unknown>;
29
+ sasl?: SASLConfig;
30
+ connectionTimeout?: number;
31
+ requestTimeout?: number;
32
+ retry?: RetryConfig;
33
+ }
34
+ interface ProducerConfig {
35
+ allowAutoTopicCreation?: boolean;
36
+ idempotent?: boolean;
37
+ }
38
+ interface ProducerRecord {
39
+ topic: string;
40
+ messages: ProducerMessage[];
41
+ acks?: number;
42
+ compression?: number;
43
+ timeout?: number;
44
+ }
45
+ interface ProducerMessage {
46
+ key?: string;
47
+ value: string;
48
+ headers?: Record<string, string>;
49
+ timestamp?: string;
50
+ partition?: number;
51
+ }
52
+ interface CompressionTypesMap {
53
+ None: number;
54
+ GZIP: number;
55
+ Snappy: number;
56
+ LZ4: number;
57
+ ZSTD: number;
58
+ }
59
+ type CompressionType = 'none' | 'gzip' | 'snappy' | 'lz4' | 'zstd';
60
+ interface SASLConfig {
61
+ mechanism: 'plain' | 'scram-sha-256' | 'scram-sha-512' | 'aws' | 'oauthbearer';
62
+ username?: string;
63
+ password?: string;
64
+ accessKeyId?: string;
65
+ secretAccessKey?: string;
66
+ sessionToken?: string;
67
+ authorizationIdentity?: string;
68
+ }
69
+ interface RetryConfig {
70
+ maxRetryTime?: number;
71
+ initialRetryTime?: number;
72
+ retries?: number;
73
+ }
74
+ interface KafkaSettings {
75
+ brokers: string[];
76
+ clientId?: string;
77
+ ssl?: boolean | Record<string, unknown>;
78
+ sasl?: SASLConfig;
79
+ connectionTimeout?: number;
80
+ requestTimeout?: number;
81
+ topic: string;
82
+ acks?: number;
83
+ timeout?: number;
84
+ compression?: CompressionType;
85
+ idempotent?: boolean;
86
+ allowAutoTopicCreation?: boolean;
87
+ key?: string;
88
+ headers?: Record<string, string>;
89
+ retry?: RetryConfig;
90
+ _producer?: KafkaProducerMock;
91
+ }
92
+ interface Settings {
93
+ kafka: KafkaSettings;
94
+ }
95
+ type InitSettings = Partial<Settings>;
96
+ interface Mapping {
97
+ /** Override message key mapping path for this rule. */
98
+ key?: string;
99
+ /** Override topic for this rule. */
100
+ topic?: string;
101
+ }
102
+ /**
103
+ * Env -- optional Kafka SDK override. Production leaves this undefined
104
+ * and the destination creates real Kafka client instances. Tests provide
105
+ * mocks via env.Kafka.
106
+ */
107
+ interface Env extends DestinationServer.Env {
108
+ Kafka?: {
109
+ Kafka: KafkaClientConstructor;
110
+ CompressionTypes: CompressionTypesMap;
111
+ };
112
+ }
113
+ type Types = Destination$1.Types<Settings, Mapping, Env, InitSettings>;
114
+ interface Destination extends DestinationServer.Destination<Types> {
115
+ init: DestinationServer.InitFn<Types>;
116
+ }
117
+ type Config = {
118
+ settings: Settings;
119
+ } & DestinationServer.Config<Types>;
120
+ type InitFn = DestinationServer.InitFn<Types>;
121
+ type PushFn = DestinationServer.PushFn<Types>;
122
+ type PartialConfig = DestinationServer.PartialConfig<Types>;
123
+ type PushEvents = DestinationServer.PushEvents<Mapping>;
124
+
125
+ type index_CompressionType = CompressionType;
126
+ type index_CompressionTypesMap = CompressionTypesMap;
127
+ type index_Config = Config;
128
+ type index_Destination = Destination;
129
+ type index_Env = Env;
130
+ type index_InitFn = InitFn;
131
+ type index_InitSettings = InitSettings;
132
+ type index_KafkaClientConfig = KafkaClientConfig;
133
+ type index_KafkaClientConstructor = KafkaClientConstructor;
134
+ type index_KafkaClientMock = KafkaClientMock;
135
+ type index_KafkaProducerMock = KafkaProducerMock;
136
+ type index_KafkaSettings = KafkaSettings;
137
+ type index_Mapping = Mapping;
138
+ type index_PartialConfig = PartialConfig;
139
+ type index_ProducerConfig = ProducerConfig;
140
+ type index_ProducerMessage = ProducerMessage;
141
+ type index_ProducerRecord = ProducerRecord;
142
+ type index_PushEvents = PushEvents;
143
+ type index_PushFn = PushFn;
144
+ type index_RetryConfig = RetryConfig;
145
+ type index_SASLConfig = SASLConfig;
146
+ type index_Settings = Settings;
147
+ type index_Types = Types;
148
+ declare namespace index {
149
+ export type { index_CompressionType as CompressionType, index_CompressionTypesMap as CompressionTypesMap, index_Config as Config, index_Destination as Destination, index_Env as Env, index_InitFn as InitFn, index_InitSettings as InitSettings, index_KafkaClientConfig as KafkaClientConfig, index_KafkaClientConstructor as KafkaClientConstructor, index_KafkaClientMock as KafkaClientMock, index_KafkaProducerMock as KafkaProducerMock, index_KafkaSettings as KafkaSettings, index_Mapping as Mapping, index_PartialConfig as PartialConfig, index_ProducerConfig as ProducerConfig, index_ProducerMessage as ProducerMessage, index_ProducerRecord as ProducerRecord, index_PushEvents as PushEvents, index_PushFn as PushFn, index_RetryConfig as RetryConfig, index_SASLConfig as SASLConfig, index_Settings as Settings, index_Types as Types };
150
+ }
151
+
152
+ declare const destinationKafka: Destination;
153
+
154
+ export { index as DestinationKafka, destinationKafka as default, destinationKafka };
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ "use strict";var e,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,o=Object.getOwnPropertyNames,r=Object.prototype.hasOwnProperty,i={};((e,n)=>{for(var o in n)t(e,o,{get:n[o],enumerable:!0})})(i,{DestinationKafka:()=>u,default:()=>f,destinationKafka:()=>p}),module.exports=(e=i,((e,i,a,s)=>{if(i&&"object"==typeof i||"function"==typeof i)for(let l of o(i))r.call(e,l)||l===a||t(e,l,{get:()=>i[l],enumerable:!(s=n(i,l))||s.enumerable});return e})(t({},"__esModule",{value:!0}),e));var a=require("@walkeros/core"),s={none:0,gzip:1,snappy:2,lz4:3,zstd:4};var l=require("@walkeros/core"),c=async function(e,{config:t,rule:n,data:o,collector:r,env:i,logger:a}){var c,u,p,f;const k=t.settings,d=null==k?void 0:k.kafka;if(!d)return void a.warn("Kafka settings missing");const g=d._producer;if(!g)return void a.warn("Kafka producer not initialized");const y=(0,l.isString)(null==n?void 0:n.name)?n.name:e.name,m=null!=(c=null==n?void 0:n.settings)?c:{},v=(0,l.isString)(m.topic)?m.topic:d.topic,w=(0,l.isString)(m.key)?m.key:d.key,b=await async function(e,t,n,o){if(n){const t=await(0,l.getMappingValue)(e,n,{collector:o});if((0,l.isString)(t)&&t.length>0)return t}return t.replace(/\s+/g,"_")}(e,y,w,r),h=(0,l.isObject)(o)&&Object.keys(o).length>0?JSON.stringify(o):JSON.stringify(e),K={"content-type":"application/json",...null!=(u=d.headers)?u:{}},S=i,O=function(e,t){var n,o,r;const i=null!=e?e:"gzip";if("none"===i)return 0;const a=null==(n=null==t?void 0:t.Kafka)?void 0:n.CompressionTypes;if(a)return null!=(o={none:a.None,gzip:a.GZIP,snappy:a.Snappy,lz4:a.LZ4,zstd:a.ZSTD}[i])?o:a.GZIP;return null!=(r=s[i])?r:1}(d.compression,S),j={topic:v,messages:[{key:b,value:h,headers:K,timestamp:String(null!=(p=e.timestamp)?p:Date.now())}],acks:null!=(f=d.acks)?f:-1,compression:O};void 0!==d.timeout&&(j.timeout=d.timeout),a.debug("Kafka push",{topic:v,key:b,event:y});try{await g.send(j)}catch(e){a.error("Kafka push failed",{topic:v,error:e instanceof Error?e.message:String(e),event:y})}};var u={},p={type:"kafka",config:{},async init({config:e,logger:t,env:n}){var o;const r=function(e={},t){var n,o,r,i,a,s,l;const c=null!=(o=(null!=(n=e.settings)?n:{}).kafka)?o:{};c.brokers&&0!==c.brokers.length||t.throw("Config settings kafka.brokers missing"),c.topic||t.throw("Config settings kafka.topic missing");const u={kafka:{...c,brokers:c.brokers,topic:c.topic,clientId:null!=(r=c.clientId)?r:"walkeros",acks:null!=(i=c.acks)?i:-1,compression:null!=(a=c.compression)?a:"gzip",idempotent:null!=(s=c.idempotent)&&s,allowAutoTopicCreation:null!=(l=c.allowAutoTopicCreation)&&l}};return{...e,settings:u}}(e,t),i=r.settings.kafka;if(i._producer)return r;let s;if(function(e){var t;return!!(0,a.isObject)(e)&&"function"==typeof(null==(t=e.Kafka)?void 0:t.Kafka)}(n)){s=null==(o=n.Kafka)?void 0:o.Kafka}if(!s)try{s=require("kafkajs").Kafka}catch(e){return t.throw(`Failed to load kafkajs: ${String(e)}`),r}const l=new s({clientId:i.clientId,brokers:i.brokers,ssl:i.ssl,sasl:i.sasl,connectionTimeout:i.connectionTimeout,requestTimeout:i.requestTimeout,retry:i.retry}),c={allowAutoTopicCreation:i.allowAutoTopicCreation,idempotent:i.idempotent},u=l.producer(c);try{await u.connect()}catch(e){return t.error("Kafka producer connect failed",{error:String(e)}),t.throw(`Kafka producer connect failed: ${String(e)}`),r}return i._producer=u,r},push:async(e,t)=>await c(e,t),async destroy({config:e}){var t;const n=null==e?void 0:e.settings,o=null==(t=null==n?void 0:n.kafka)?void 0:t._producer;if(o)try{await o.disconnect()}finally{n.kafka._producer=void 0}}},f=p;//# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/config.ts","../src/push.ts","../src/types/index.ts"],"sourcesContent":["import type {\n Destination,\n Settings,\n Env,\n KafkaClientMock,\n KafkaClientConfig,\n KafkaClientConstructor,\n ProducerConfig,\n} from './types';\nimport { getConfig, isKafkaEnv } from './config';\nimport { push } from './push';\n\n// Types re-export\nexport * as DestinationKafka from './types';\n\nexport const destinationKafka: Destination = {\n type: 'kafka',\n\n config: {},\n\n async init({ config: partialConfig, logger, env }) {\n const config = getConfig(partialConfig, logger);\n const settings = config.settings as Settings;\n const kafka = settings.kafka;\n\n // Skip creation if a producer has already been wired in (testing).\n if (kafka._producer) return config;\n\n let Constructor: KafkaClientConstructor | undefined;\n\n // Prefer env-injected constructor (testing, dependency injection).\n if (isKafkaEnv(env)) {\n const envTyped = env as Env;\n Constructor = envTyped.Kafka?.Kafka;\n }\n\n // Production path: load real kafkajs SDK.\n if (!Constructor) {\n try {\n // Use dynamic require to allow tests to mock via jest.mock('kafkajs').\n const kafkajs = require('kafkajs') as {\n Kafka: KafkaClientConstructor;\n };\n Constructor = kafkajs.Kafka;\n } catch (err) {\n logger.throw(`Failed to load kafkajs: ${String(err)}`);\n return config;\n }\n }\n\n const clientConfig: KafkaClientConfig = {\n clientId: kafka.clientId,\n brokers: kafka.brokers,\n ssl: kafka.ssl,\n sasl: kafka.sasl,\n connectionTimeout: kafka.connectionTimeout,\n requestTimeout: kafka.requestTimeout,\n retry: kafka.retry,\n };\n\n const client: KafkaClientMock = new Constructor(clientConfig);\n\n const producerConfig: ProducerConfig = {\n allowAutoTopicCreation: kafka.allowAutoTopicCreation,\n idempotent: kafka.idempotent,\n };\n\n const producer = client.producer(producerConfig);\n\n try {\n await producer.connect();\n } catch (err) {\n logger.error('Kafka producer connect failed', { error: String(err) });\n logger.throw(`Kafka producer connect failed: ${String(err)}`);\n return config;\n }\n\n kafka._producer = producer;\n\n return config;\n },\n\n async push(event, context) {\n return await push(event, context);\n },\n\n async destroy({ config }) {\n const settings = config?.settings as Settings | undefined;\n const producer = settings?.kafka?._producer;\n if (producer) {\n try {\n await producer.disconnect();\n } finally {\n settings.kafka._producer = undefined;\n }\n }\n },\n};\n\nexport default destinationKafka;\n","import type {\n Config,\n Settings,\n KafkaSettings,\n PartialConfig,\n Env,\n CompressionType,\n CompressionTypesMap,\n} from './types';\nimport type { Logger } from '@walkeros/core';\nimport { isObject } from '@walkeros/core';\n\n/** Fallback compression codec map when env is not provided. */\nconst COMPRESSION_FALLBACK: Record<CompressionType, number> = {\n none: 0,\n gzip: 1,\n snappy: 2,\n lz4: 3,\n zstd: 4,\n};\n\nexport function getConfig(\n partialConfig: PartialConfig = {},\n logger: Logger.Instance,\n): Config {\n const raw = (partialConfig.settings ?? {}) as Partial<Settings>;\n const kafka: Partial<KafkaSettings> =\n raw.kafka ?? ({} as Partial<KafkaSettings>);\n\n if (!kafka.brokers || kafka.brokers.length === 0) {\n logger.throw('Config settings kafka.brokers missing');\n }\n if (!kafka.topic) {\n logger.throw('Config settings kafka.topic missing');\n }\n\n const kafkaSettings: KafkaSettings = {\n ...kafka,\n brokers: kafka.brokers as string[],\n topic: kafka.topic as string,\n clientId: kafka.clientId ?? 'walkeros',\n acks: kafka.acks ?? -1,\n compression: kafka.compression ?? 'gzip',\n idempotent: kafka.idempotent ?? false,\n allowAutoTopicCreation: kafka.allowAutoTopicCreation ?? false,\n };\n\n const settings: Settings = { kafka: kafkaSettings };\n\n return { ...partialConfig, settings };\n}\n\nexport function getCompressionType(\n compression: CompressionType | undefined,\n env: Env | undefined,\n): number {\n const codec = compression ?? 'gzip';\n if (codec === 'none') return 0;\n\n const types: CompressionTypesMap | undefined = env?.Kafka?.CompressionTypes;\n if (types) {\n const lookup: Record<CompressionType, number> = {\n none: types.None,\n gzip: types.GZIP,\n snappy: types.Snappy,\n lz4: types.LZ4,\n zstd: types.ZSTD,\n };\n return lookup[codec] ?? types.GZIP;\n }\n\n return COMPRESSION_FALLBACK[codec] ?? 1;\n}\n\nexport function isKafkaEnv(env: unknown): env is Env {\n if (!isObject(env)) return false;\n const maybe = env as { Kafka?: { Kafka?: unknown } };\n return typeof maybe.Kafka?.Kafka === 'function';\n}\n","import type {\n PushFn,\n KafkaSettings,\n Env,\n ProducerMessage,\n ProducerRecord,\n KafkaProducerMock,\n} from './types';\nimport type { Collector } from '@walkeros/core';\nimport { getMappingValue, isObject, isString } from '@walkeros/core';\nimport { getCompressionType } from './config';\n\nexport const push: PushFn = async function (\n event,\n { config, rule, data, collector, env, logger },\n) {\n const settings = config.settings as { kafka?: KafkaSettings } | undefined;\n const kafka: KafkaSettings | undefined = settings?.kafka;\n\n if (!kafka) {\n logger.warn('Kafka settings missing');\n return;\n }\n\n const producer = kafka._producer;\n if (!producer) {\n logger.warn('Kafka producer not initialized');\n return;\n }\n\n // Derive event name (rule.name overrides)\n const eventName = isString(rule?.name) ? rule.name : event.name;\n\n // Derive topic: rule override -> destination default\n const ruleSettings = rule?.settings ?? {};\n const topic = isString(ruleSettings.topic) ? ruleSettings.topic : kafka.topic;\n\n // Derive message key\n const keyPath = isString(ruleSettings.key) ? ruleSettings.key : kafka.key;\n const key = await deriveKey(event, eventName, keyPath, collector);\n\n // Serialize message value: mapped data (when present) -> event\n const value =\n isObject(data) && Object.keys(data).length > 0\n ? JSON.stringify(data)\n : JSON.stringify(event);\n\n // Build headers\n const headers: Record<string, string> = {\n 'content-type': 'application/json',\n ...(kafka.headers ?? {}),\n };\n\n // Resolve compression codec via env lookup\n const envTyped = env as Env | undefined;\n const compression = getCompressionType(kafka.compression, envTyped);\n\n const message: ProducerMessage = {\n key,\n value,\n headers,\n timestamp: String(event.timestamp ?? Date.now()),\n };\n\n const record: ProducerRecord = {\n topic,\n messages: [message],\n acks: kafka.acks ?? -1,\n compression,\n };\n\n if (kafka.timeout !== undefined) record.timeout = kafka.timeout;\n\n logger.debug('Kafka push', { topic, key, event: eventName });\n\n try {\n await (producer as KafkaProducerMock).send(record);\n } catch (error) {\n logger.error('Kafka push failed', {\n topic,\n error: error instanceof Error ? error.message : String(error),\n event: eventName,\n });\n }\n};\n\nasync function deriveKey(\n event: Parameters<PushFn>[0],\n eventName: string,\n keyPath: string | undefined,\n collector: Collector.Instance,\n): Promise<string> {\n if (keyPath) {\n const resolved = await getMappingValue(event, keyPath, { collector });\n if (isString(resolved) && resolved.length > 0) return resolved;\n }\n // Default: event name with space replaced for partition-friendly keys.\n return eventName.replace(/\\s+/g, '_');\n}\n","import type { Destination as CoreDestination } from '@walkeros/core';\nimport type { DestinationServer } from '@walkeros/server-core';\n\n/**\n * Mock-friendly Producer interface used by the destination.\n * Tests provide this via env.Kafka; production creates a real\n * kafkajs Producer and adapts it through settings._producer.\n */\nexport interface KafkaProducerMock {\n connect: () => Promise<void>;\n disconnect: () => Promise<void>;\n send: (record: ProducerRecord) => Promise<unknown>;\n}\n\n/**\n * Mock-friendly Kafka client interface (subset of kafkajs.Kafka).\n */\nexport interface KafkaClientMock {\n producer: (config?: ProducerConfig) => KafkaProducerMock;\n}\n\n/**\n * Constructor signature for the Kafka client. Accepts a config\n * object and returns a client with producer() factory.\n */\nexport type KafkaClientConstructor = new (\n config: KafkaClientConfig,\n) => KafkaClientMock;\n\nexport interface KafkaClientConfig {\n clientId?: string;\n brokers: string[];\n ssl?: boolean | Record<string, unknown>;\n sasl?: SASLConfig;\n connectionTimeout?: number;\n requestTimeout?: number;\n retry?: RetryConfig;\n}\n\nexport interface ProducerConfig {\n allowAutoTopicCreation?: boolean;\n idempotent?: boolean;\n}\n\nexport interface ProducerRecord {\n topic: string;\n messages: ProducerMessage[];\n acks?: number;\n compression?: number;\n timeout?: number;\n}\n\nexport interface ProducerMessage {\n key?: string;\n value: string;\n headers?: Record<string, string>;\n timestamp?: string;\n partition?: number;\n}\n\nexport interface CompressionTypesMap {\n None: number;\n GZIP: number;\n Snappy: number;\n LZ4: number;\n ZSTD: number;\n}\n\nexport type CompressionType = 'none' | 'gzip' | 'snappy' | 'lz4' | 'zstd';\n\nexport interface SASLConfig {\n mechanism:\n | 'plain'\n | 'scram-sha-256'\n | 'scram-sha-512'\n | 'aws'\n | 'oauthbearer';\n username?: string;\n password?: string;\n accessKeyId?: string;\n secretAccessKey?: string;\n sessionToken?: string;\n authorizationIdentity?: string;\n}\n\nexport interface RetryConfig {\n maxRetryTime?: number;\n initialRetryTime?: number;\n retries?: number;\n}\n\nexport interface KafkaSettings {\n // Connection\n brokers: string[];\n clientId?: string;\n ssl?: boolean | Record<string, unknown>;\n sasl?: SASLConfig;\n connectionTimeout?: number;\n requestTimeout?: number;\n\n // Producer\n topic: string;\n acks?: number;\n timeout?: number;\n compression?: CompressionType;\n idempotent?: boolean;\n allowAutoTopicCreation?: boolean;\n\n // Message\n key?: string;\n headers?: Record<string, string>;\n\n // Advanced\n retry?: RetryConfig;\n\n // Runtime -- set during init, not user-facing\n _producer?: KafkaProducerMock;\n}\n\nexport interface Settings {\n kafka: KafkaSettings;\n}\n\nexport type InitSettings = Partial<Settings>;\n\nexport interface Mapping {\n /** Override message key mapping path for this rule. */\n key?: string;\n /** Override topic for this rule. */\n topic?: string;\n}\n\n/**\n * Env -- optional Kafka SDK override. Production leaves this undefined\n * and the destination creates real Kafka client instances. Tests provide\n * mocks via env.Kafka.\n */\nexport interface Env extends DestinationServer.Env {\n Kafka?: {\n Kafka: KafkaClientConstructor;\n CompressionTypes: CompressionTypesMap;\n };\n}\n\nexport type Types = CoreDestination.Types<Settings, Mapping, Env, InitSettings>;\n\nexport interface Destination extends DestinationServer.Destination<Types> {\n init: DestinationServer.InitFn<Types>;\n}\n\nexport type Config = {\n settings: Settings;\n} & DestinationServer.Config<Types>;\n\nexport type InitFn = DestinationServer.InitFn<Types>;\nexport type PushFn = DestinationServer.PushFn<Types>;\nexport type PartialConfig = DestinationServer.PartialConfig<Types>;\nexport type PushEvents = DestinationServer.PushEvents<Mapping>;\n"],"mappings":"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","names":["import_core"]}
package/dist/index.mjs ADDED
@@ -0,0 +1 @@
1
+ var e=(e=>"undefined"!=typeof require?require:"undefined"!=typeof Proxy?new Proxy(e,{get:(e,n)=>("undefined"!=typeof require?require:e)[n]}):e)(function(e){if("undefined"!=typeof require)return require.apply(this,arguments);throw Error('Dynamic require of "'+e+'" is not supported')});import{isObject as n}from"@walkeros/core";var t={none:0,gzip:1,snappy:2,lz4:3,zstd:4};import{getMappingValue as o,isObject as r,isString as i}from"@walkeros/core";var a=async function(e,{config:n,rule:a,data:s,collector:c,env:l,logger:u}){var f,p,d,k;const g=n.settings,y=null==g?void 0:g.kafka;if(!y)return void u.warn("Kafka settings missing");const m=y._producer;if(!m)return void u.warn("Kafka producer not initialized");const v=i(null==a?void 0:a.name)?a.name:e.name,w=null!=(f=null==a?void 0:a.settings)?f:{},h=i(w.topic)?w.topic:y.topic,K=i(w.key)?w.key:y.key,b=await async function(e,n,t,r){if(t){const n=await o(e,t,{collector:r});if(i(n)&&n.length>0)return n}return n.replace(/\s+/g,"_")}(e,v,K,c),T=r(s)&&Object.keys(s).length>0?JSON.stringify(s):JSON.stringify(e),q={"content-type":"application/json",...null!=(p=y.headers)?p:{}},z=l,S=function(e,n){var o,r,i;const a=null!=e?e:"gzip";if("none"===a)return 0;const s=null==(o=null==n?void 0:n.Kafka)?void 0:o.CompressionTypes;if(s)return null!=(r={none:s.None,gzip:s.GZIP,snappy:s.Snappy,lz4:s.LZ4,zstd:s.ZSTD}[a])?r:s.GZIP;return null!=(i=t[a])?i:1}(y.compression,z),C={topic:h,messages:[{key:b,value:T,headers:q,timestamp:String(null!=(d=e.timestamp)?d:Date.now())}],acks:null!=(k=y.acks)?k:-1,compression:S};void 0!==y.timeout&&(C.timeout=y.timeout),u.debug("Kafka push",{topic:h,key:b,event:v});try{await m.send(C)}catch(e){u.error("Kafka push failed",{topic:h,error:e instanceof Error?e.message:String(e),event:v})}};var s={},c={type:"kafka",config:{},async init({config:t,logger:o,env:r}){var i;const a=function(e={},n){var t,o,r,i,a,s,c;const l=null!=(o=(null!=(t=e.settings)?t:{}).kafka)?o:{};l.brokers&&0!==l.brokers.length||n.throw("Config settings kafka.brokers missing"),l.topic||n.throw("Config settings kafka.topic missing");const u={kafka:{...l,brokers:l.brokers,topic:l.topic,clientId:null!=(r=l.clientId)?r:"walkeros",acks:null!=(i=l.acks)?i:-1,compression:null!=(a=l.compression)?a:"gzip",idempotent:null!=(s=l.idempotent)&&s,allowAutoTopicCreation:null!=(c=l.allowAutoTopicCreation)&&c}};return{...e,settings:u}}(t,o),s=a.settings.kafka;if(s._producer)return a;let c;if(function(e){var t;return!!n(e)&&"function"==typeof(null==(t=e.Kafka)?void 0:t.Kafka)}(r)){c=null==(i=r.Kafka)?void 0:i.Kafka}if(!c)try{c=e("kafkajs").Kafka}catch(e){return o.throw(`Failed to load kafkajs: ${String(e)}`),a}const l=new c({clientId:s.clientId,brokers:s.brokers,ssl:s.ssl,sasl:s.sasl,connectionTimeout:s.connectionTimeout,requestTimeout:s.requestTimeout,retry:s.retry}),u={allowAutoTopicCreation:s.allowAutoTopicCreation,idempotent:s.idempotent},f=l.producer(u);try{await f.connect()}catch(e){return o.error("Kafka producer connect failed",{error:String(e)}),o.throw(`Kafka producer connect failed: ${String(e)}`),a}return s._producer=f,a},push:async(e,n)=>await a(e,n),async destroy({config:e}){var n;const t=null==e?void 0:e.settings,o=null==(n=null==t?void 0:t.kafka)?void 0:n._producer;if(o)try{await o.disconnect()}finally{t.kafka._producer=void 0}}},l=c;export{s as DestinationKafka,l as default,c as destinationKafka};//# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/config.ts","../src/push.ts","../src/types/index.ts","../src/index.ts"],"sourcesContent":["import type {\n Config,\n Settings,\n KafkaSettings,\n PartialConfig,\n Env,\n CompressionType,\n CompressionTypesMap,\n} from './types';\nimport type { Logger } from '@walkeros/core';\nimport { isObject } from '@walkeros/core';\n\n/** Fallback compression codec map when env is not provided. */\nconst COMPRESSION_FALLBACK: Record<CompressionType, number> = {\n none: 0,\n gzip: 1,\n snappy: 2,\n lz4: 3,\n zstd: 4,\n};\n\nexport function getConfig(\n partialConfig: PartialConfig = {},\n logger: Logger.Instance,\n): Config {\n const raw = (partialConfig.settings ?? {}) as Partial<Settings>;\n const kafka: Partial<KafkaSettings> =\n raw.kafka ?? ({} as Partial<KafkaSettings>);\n\n if (!kafka.brokers || kafka.brokers.length === 0) {\n logger.throw('Config settings kafka.brokers missing');\n }\n if (!kafka.topic) {\n logger.throw('Config settings kafka.topic missing');\n }\n\n const kafkaSettings: KafkaSettings = {\n ...kafka,\n brokers: kafka.brokers as string[],\n topic: kafka.topic as string,\n clientId: kafka.clientId ?? 'walkeros',\n acks: kafka.acks ?? -1,\n compression: kafka.compression ?? 'gzip',\n idempotent: kafka.idempotent ?? false,\n allowAutoTopicCreation: kafka.allowAutoTopicCreation ?? false,\n };\n\n const settings: Settings = { kafka: kafkaSettings };\n\n return { ...partialConfig, settings };\n}\n\nexport function getCompressionType(\n compression: CompressionType | undefined,\n env: Env | undefined,\n): number {\n const codec = compression ?? 'gzip';\n if (codec === 'none') return 0;\n\n const types: CompressionTypesMap | undefined = env?.Kafka?.CompressionTypes;\n if (types) {\n const lookup: Record<CompressionType, number> = {\n none: types.None,\n gzip: types.GZIP,\n snappy: types.Snappy,\n lz4: types.LZ4,\n zstd: types.ZSTD,\n };\n return lookup[codec] ?? types.GZIP;\n }\n\n return COMPRESSION_FALLBACK[codec] ?? 1;\n}\n\nexport function isKafkaEnv(env: unknown): env is Env {\n if (!isObject(env)) return false;\n const maybe = env as { Kafka?: { Kafka?: unknown } };\n return typeof maybe.Kafka?.Kafka === 'function';\n}\n","import type {\n PushFn,\n KafkaSettings,\n Env,\n ProducerMessage,\n ProducerRecord,\n KafkaProducerMock,\n} from './types';\nimport type { Collector } from '@walkeros/core';\nimport { getMappingValue, isObject, isString } from '@walkeros/core';\nimport { getCompressionType } from './config';\n\nexport const push: PushFn = async function (\n event,\n { config, rule, data, collector, env, logger },\n) {\n const settings = config.settings as { kafka?: KafkaSettings } | undefined;\n const kafka: KafkaSettings | undefined = settings?.kafka;\n\n if (!kafka) {\n logger.warn('Kafka settings missing');\n return;\n }\n\n const producer = kafka._producer;\n if (!producer) {\n logger.warn('Kafka producer not initialized');\n return;\n }\n\n // Derive event name (rule.name overrides)\n const eventName = isString(rule?.name) ? rule.name : event.name;\n\n // Derive topic: rule override -> destination default\n const ruleSettings = rule?.settings ?? {};\n const topic = isString(ruleSettings.topic) ? ruleSettings.topic : kafka.topic;\n\n // Derive message key\n const keyPath = isString(ruleSettings.key) ? ruleSettings.key : kafka.key;\n const key = await deriveKey(event, eventName, keyPath, collector);\n\n // Serialize message value: mapped data (when present) -> event\n const value =\n isObject(data) && Object.keys(data).length > 0\n ? JSON.stringify(data)\n : JSON.stringify(event);\n\n // Build headers\n const headers: Record<string, string> = {\n 'content-type': 'application/json',\n ...(kafka.headers ?? {}),\n };\n\n // Resolve compression codec via env lookup\n const envTyped = env as Env | undefined;\n const compression = getCompressionType(kafka.compression, envTyped);\n\n const message: ProducerMessage = {\n key,\n value,\n headers,\n timestamp: String(event.timestamp ?? Date.now()),\n };\n\n const record: ProducerRecord = {\n topic,\n messages: [message],\n acks: kafka.acks ?? -1,\n compression,\n };\n\n if (kafka.timeout !== undefined) record.timeout = kafka.timeout;\n\n logger.debug('Kafka push', { topic, key, event: eventName });\n\n try {\n await (producer as KafkaProducerMock).send(record);\n } catch (error) {\n logger.error('Kafka push failed', {\n topic,\n error: error instanceof Error ? error.message : String(error),\n event: eventName,\n });\n }\n};\n\nasync function deriveKey(\n event: Parameters<PushFn>[0],\n eventName: string,\n keyPath: string | undefined,\n collector: Collector.Instance,\n): Promise<string> {\n if (keyPath) {\n const resolved = await getMappingValue(event, keyPath, { collector });\n if (isString(resolved) && resolved.length > 0) return resolved;\n }\n // Default: event name with space replaced for partition-friendly keys.\n return eventName.replace(/\\s+/g, '_');\n}\n","import type { Destination as CoreDestination } from '@walkeros/core';\nimport type { DestinationServer } from '@walkeros/server-core';\n\n/**\n * Mock-friendly Producer interface used by the destination.\n * Tests provide this via env.Kafka; production creates a real\n * kafkajs Producer and adapts it through settings._producer.\n */\nexport interface KafkaProducerMock {\n connect: () => Promise<void>;\n disconnect: () => Promise<void>;\n send: (record: ProducerRecord) => Promise<unknown>;\n}\n\n/**\n * Mock-friendly Kafka client interface (subset of kafkajs.Kafka).\n */\nexport interface KafkaClientMock {\n producer: (config?: ProducerConfig) => KafkaProducerMock;\n}\n\n/**\n * Constructor signature for the Kafka client. Accepts a config\n * object and returns a client with producer() factory.\n */\nexport type KafkaClientConstructor = new (\n config: KafkaClientConfig,\n) => KafkaClientMock;\n\nexport interface KafkaClientConfig {\n clientId?: string;\n brokers: string[];\n ssl?: boolean | Record<string, unknown>;\n sasl?: SASLConfig;\n connectionTimeout?: number;\n requestTimeout?: number;\n retry?: RetryConfig;\n}\n\nexport interface ProducerConfig {\n allowAutoTopicCreation?: boolean;\n idempotent?: boolean;\n}\n\nexport interface ProducerRecord {\n topic: string;\n messages: ProducerMessage[];\n acks?: number;\n compression?: number;\n timeout?: number;\n}\n\nexport interface ProducerMessage {\n key?: string;\n value: string;\n headers?: Record<string, string>;\n timestamp?: string;\n partition?: number;\n}\n\nexport interface CompressionTypesMap {\n None: number;\n GZIP: number;\n Snappy: number;\n LZ4: number;\n ZSTD: number;\n}\n\nexport type CompressionType = 'none' | 'gzip' | 'snappy' | 'lz4' | 'zstd';\n\nexport interface SASLConfig {\n mechanism:\n | 'plain'\n | 'scram-sha-256'\n | 'scram-sha-512'\n | 'aws'\n | 'oauthbearer';\n username?: string;\n password?: string;\n accessKeyId?: string;\n secretAccessKey?: string;\n sessionToken?: string;\n authorizationIdentity?: string;\n}\n\nexport interface RetryConfig {\n maxRetryTime?: number;\n initialRetryTime?: number;\n retries?: number;\n}\n\nexport interface KafkaSettings {\n // Connection\n brokers: string[];\n clientId?: string;\n ssl?: boolean | Record<string, unknown>;\n sasl?: SASLConfig;\n connectionTimeout?: number;\n requestTimeout?: number;\n\n // Producer\n topic: string;\n acks?: number;\n timeout?: number;\n compression?: CompressionType;\n idempotent?: boolean;\n allowAutoTopicCreation?: boolean;\n\n // Message\n key?: string;\n headers?: Record<string, string>;\n\n // Advanced\n retry?: RetryConfig;\n\n // Runtime -- set during init, not user-facing\n _producer?: KafkaProducerMock;\n}\n\nexport interface Settings {\n kafka: KafkaSettings;\n}\n\nexport type InitSettings = Partial<Settings>;\n\nexport interface Mapping {\n /** Override message key mapping path for this rule. */\n key?: string;\n /** Override topic for this rule. */\n topic?: string;\n}\n\n/**\n * Env -- optional Kafka SDK override. Production leaves this undefined\n * and the destination creates real Kafka client instances. Tests provide\n * mocks via env.Kafka.\n */\nexport interface Env extends DestinationServer.Env {\n Kafka?: {\n Kafka: KafkaClientConstructor;\n CompressionTypes: CompressionTypesMap;\n };\n}\n\nexport type Types = CoreDestination.Types<Settings, Mapping, Env, InitSettings>;\n\nexport interface Destination extends DestinationServer.Destination<Types> {\n init: DestinationServer.InitFn<Types>;\n}\n\nexport type Config = {\n settings: Settings;\n} & DestinationServer.Config<Types>;\n\nexport type InitFn = DestinationServer.InitFn<Types>;\nexport type PushFn = DestinationServer.PushFn<Types>;\nexport type PartialConfig = DestinationServer.PartialConfig<Types>;\nexport type PushEvents = DestinationServer.PushEvents<Mapping>;\n","import type {\n Destination,\n Settings,\n Env,\n KafkaClientMock,\n KafkaClientConfig,\n KafkaClientConstructor,\n ProducerConfig,\n} from './types';\nimport { getConfig, isKafkaEnv } from './config';\nimport { push } from './push';\n\n// Types re-export\nexport * as DestinationKafka from './types';\n\nexport const destinationKafka: Destination = {\n type: 'kafka',\n\n config: {},\n\n async init({ config: partialConfig, logger, env }) {\n const config = getConfig(partialConfig, logger);\n const settings = config.settings as Settings;\n const kafka = settings.kafka;\n\n // Skip creation if a producer has already been wired in (testing).\n if (kafka._producer) return config;\n\n let Constructor: KafkaClientConstructor | undefined;\n\n // Prefer env-injected constructor (testing, dependency injection).\n if (isKafkaEnv(env)) {\n const envTyped = env as Env;\n Constructor = envTyped.Kafka?.Kafka;\n }\n\n // Production path: load real kafkajs SDK.\n if (!Constructor) {\n try {\n // Use dynamic require to allow tests to mock via jest.mock('kafkajs').\n const kafkajs = require('kafkajs') as {\n Kafka: KafkaClientConstructor;\n };\n Constructor = kafkajs.Kafka;\n } catch (err) {\n logger.throw(`Failed to load kafkajs: ${String(err)}`);\n return config;\n }\n }\n\n const clientConfig: KafkaClientConfig = {\n clientId: kafka.clientId,\n brokers: kafka.brokers,\n ssl: kafka.ssl,\n sasl: kafka.sasl,\n connectionTimeout: kafka.connectionTimeout,\n requestTimeout: kafka.requestTimeout,\n retry: kafka.retry,\n };\n\n const client: KafkaClientMock = new Constructor(clientConfig);\n\n const producerConfig: ProducerConfig = {\n allowAutoTopicCreation: kafka.allowAutoTopicCreation,\n idempotent: kafka.idempotent,\n };\n\n const producer = client.producer(producerConfig);\n\n try {\n await producer.connect();\n } catch (err) {\n logger.error('Kafka producer connect failed', { error: String(err) });\n logger.throw(`Kafka producer connect failed: ${String(err)}`);\n return config;\n }\n\n kafka._producer = producer;\n\n return config;\n },\n\n async push(event, context) {\n return await push(event, context);\n },\n\n async destroy({ config }) {\n const settings = config?.settings as Settings | undefined;\n const producer = settings?.kafka?._producer;\n if (producer) {\n try {\n await producer.disconnect();\n } finally {\n settings.kafka._producer = undefined;\n }\n }\n },\n};\n\nexport default destinationKafka;\n"],"mappings":"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","names":["isObject","isObject"]}