@drarzter/kafka-client 0.9.4 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +625 -8
- package/dist/chunk-CMO7SMVK.mjs +4814 -0
- package/dist/chunk-CMO7SMVK.mjs.map +1 -0
- package/dist/cli/dlq.d.ts +119 -0
- package/dist/cli/dlq.d.ts.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/{chunk-SM4FZKAZ.mjs → cli/index.js} +964 -264
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/index.mjs +355 -0
- package/dist/cli/index.mjs.map +1 -0
- package/dist/client/config/from-env.d.ts +188 -0
- package/dist/client/config/from-env.d.ts.map +1 -0
- package/dist/client/config/index.d.ts +2 -0
- package/dist/client/config/index.d.ts.map +1 -0
- package/dist/client/errors.d.ts +67 -0
- package/dist/client/errors.d.ts.map +1 -0
- package/dist/client/kafka.client/admin/ops.d.ts +114 -0
- package/dist/client/kafka.client/admin/ops.d.ts.map +1 -0
- package/dist/client/kafka.client/consumer/features/delayed.d.ts +24 -0
- package/dist/client/kafka.client/consumer/features/delayed.d.ts.map +1 -0
- package/dist/client/kafka.client/consumer/features/dlq-replay.d.ts +52 -0
- package/dist/client/kafka.client/consumer/features/dlq-replay.d.ts.map +1 -0
- package/dist/client/kafka.client/consumer/features/routed.d.ts +4 -0
- package/dist/client/kafka.client/consumer/features/routed.d.ts.map +1 -0
- package/dist/client/kafka.client/consumer/features/snapshot.d.ts +10 -0
- package/dist/client/kafka.client/consumer/features/snapshot.d.ts.map +1 -0
- package/dist/client/kafka.client/consumer/features/window.d.ts +5 -0
- package/dist/client/kafka.client/consumer/features/window.d.ts.map +1 -0
- package/dist/client/kafka.client/consumer/handler.d.ts +149 -0
- package/dist/client/kafka.client/consumer/handler.d.ts.map +1 -0
- package/dist/client/kafka.client/consumer/ops.d.ts +51 -0
- package/dist/client/kafka.client/consumer/ops.d.ts.map +1 -0
- package/dist/client/kafka.client/consumer/pipeline.d.ts +167 -0
- package/dist/client/kafka.client/consumer/pipeline.d.ts.map +1 -0
- package/dist/client/kafka.client/consumer/queue.d.ts +37 -0
- package/dist/client/kafka.client/consumer/queue.d.ts.map +1 -0
- package/dist/client/kafka.client/consumer/retry-topic.d.ts +65 -0
- package/dist/client/kafka.client/consumer/retry-topic.d.ts.map +1 -0
- package/dist/client/kafka.client/consumer/setup.d.ts +63 -0
- package/dist/client/kafka.client/consumer/setup.d.ts.map +1 -0
- package/dist/client/kafka.client/consumer/start.d.ts +7 -0
- package/dist/client/kafka.client/consumer/start.d.ts.map +1 -0
- package/dist/client/kafka.client/consumer/stop.d.ts +19 -0
- package/dist/client/kafka.client/consumer/stop.d.ts.map +1 -0
- package/dist/client/kafka.client/consumer/subscribe-retry.d.ts +4 -0
- package/dist/client/kafka.client/consumer/subscribe-retry.d.ts.map +1 -0
- package/dist/client/kafka.client/context.d.ts +72 -0
- package/dist/client/kafka.client/context.d.ts.map +1 -0
- package/dist/client/kafka.client/index.d.ts +155 -0
- package/dist/client/kafka.client/index.d.ts.map +1 -0
- package/dist/client/kafka.client/infra/circuit-breaker.manager.d.ts +61 -0
- package/dist/client/kafka.client/infra/circuit-breaker.manager.d.ts.map +1 -0
- package/dist/client/kafka.client/infra/dedup.store.d.ts +28 -0
- package/dist/client/kafka.client/infra/dedup.store.d.ts.map +1 -0
- package/dist/client/kafka.client/infra/inflight.tracker.d.ts +22 -0
- package/dist/client/kafka.client/infra/inflight.tracker.d.ts.map +1 -0
- package/dist/client/kafka.client/infra/metrics.manager.d.ts +67 -0
- package/dist/client/kafka.client/infra/metrics.manager.d.ts.map +1 -0
- package/dist/client/kafka.client/producer/lifecycle.d.ts +41 -0
- package/dist/client/kafka.client/producer/lifecycle.d.ts.map +1 -0
- package/dist/client/kafka.client/producer/ops.d.ts +70 -0
- package/dist/client/kafka.client/producer/ops.d.ts.map +1 -0
- package/dist/client/kafka.client/producer/send.d.ts +21 -0
- package/dist/client/kafka.client/producer/send.d.ts.map +1 -0
- package/dist/client/kafka.client/validate-options.d.ts +11 -0
- package/dist/client/kafka.client/validate-options.d.ts.map +1 -0
- package/dist/client/message/envelope.d.ts +105 -0
- package/dist/client/message/envelope.d.ts.map +1 -0
- package/dist/client/message/schema-registry.d.ts +105 -0
- package/dist/client/message/schema-registry.d.ts.map +1 -0
- package/dist/client/message/topic.d.ts +138 -0
- package/dist/client/message/topic.d.ts.map +1 -0
- package/dist/client/message/versioned-schema.d.ts +53 -0
- package/dist/client/message/versioned-schema.d.ts.map +1 -0
- package/dist/client/outbox/index.d.ts +4 -0
- package/dist/client/outbox/index.d.ts.map +1 -0
- package/dist/client/outbox/outbox.relay.d.ts +90 -0
- package/dist/client/outbox/outbox.relay.d.ts.map +1 -0
- package/dist/client/outbox/outbox.store.d.ts +42 -0
- package/dist/client/outbox/outbox.store.d.ts.map +1 -0
- package/dist/client/outbox/outbox.types.d.ts +144 -0
- package/dist/client/outbox/outbox.types.d.ts.map +1 -0
- package/dist/client/security/acl.d.ts +108 -0
- package/dist/client/security/acl.d.ts.map +1 -0
- package/dist/client/security/index.d.ts +5 -0
- package/dist/client/security/index.d.ts.map +1 -0
- package/dist/client/security/providers.d.ts +88 -0
- package/dist/client/security/providers.d.ts.map +1 -0
- package/dist/client/security/resolve-security.d.ts +19 -0
- package/dist/client/security/resolve-security.d.ts.map +1 -0
- package/dist/client/security/security.types.d.ts +76 -0
- package/dist/client/security/security.types.d.ts.map +1 -0
- package/dist/client/transport/confluent.transport.d.ts +32 -0
- package/dist/client/transport/confluent.transport.d.ts.map +1 -0
- package/dist/client/transport/transport.interface.d.ts +216 -0
- package/dist/client/transport/transport.interface.d.ts.map +1 -0
- package/dist/client/types/admin.interface.d.ts +174 -0
- package/dist/client/types/admin.interface.d.ts.map +1 -0
- package/dist/client/types/admin.types.d.ts +140 -0
- package/dist/client/types/admin.types.d.ts.map +1 -0
- package/dist/client/types/client.d.ts +21 -0
- package/dist/client/types/client.d.ts.map +1 -0
- package/dist/client/types/common.d.ts +84 -0
- package/dist/client/types/common.d.ts.map +1 -0
- package/dist/client/types/config.types.d.ts +150 -0
- package/dist/client/types/config.types.d.ts.map +1 -0
- package/dist/client/types/consumer.interface.d.ts +115 -0
- package/dist/client/types/consumer.interface.d.ts.map +1 -0
- package/dist/{consumer.types-fFCag3VJ.d.mts → client/types/consumer.types.d.ts} +62 -383
- package/dist/client/types/consumer.types.d.ts.map +1 -0
- package/dist/client/types/dedup.types.d.ts +50 -0
- package/dist/client/types/dedup.types.d.ts.map +1 -0
- package/dist/client/types/lifecycle.interface.d.ts +72 -0
- package/dist/client/types/lifecycle.interface.d.ts.map +1 -0
- package/dist/client/types/producer.interface.d.ts +52 -0
- package/dist/client/types/producer.interface.d.ts.map +1 -0
- package/dist/client/types/producer.types.d.ts +90 -0
- package/dist/client/types/producer.types.d.ts.map +1 -0
- package/dist/client/types.d.ts +8 -0
- package/dist/client/types.d.ts.map +1 -0
- package/dist/core.d.ts +10 -314
- package/dist/core.d.ts.map +1 -0
- package/dist/core.js +1325 -73
- package/dist/core.js.map +1 -1
- package/dist/core.mjs +39 -3
- package/dist/index.d.ts +7 -128
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1342 -73
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +56 -3
- package/dist/index.mjs.map +1 -1
- package/dist/nest/kafka.constants.d.ts +5 -0
- package/dist/nest/kafka.constants.d.ts.map +1 -0
- package/dist/nest/kafka.decorator.d.ts +49 -0
- package/dist/nest/kafka.decorator.d.ts.map +1 -0
- package/dist/nest/kafka.explorer.d.ts +17 -0
- package/dist/nest/kafka.explorer.d.ts.map +1 -0
- package/dist/nest/kafka.health.d.ts +7 -0
- package/dist/nest/kafka.health.d.ts.map +1 -0
- package/dist/nest/kafka.module.d.ts +61 -0
- package/dist/nest/kafka.module.d.ts.map +1 -0
- package/dist/otel.d.ts +83 -5
- package/dist/otel.d.ts.map +1 -0
- package/dist/otel.js +100 -6
- package/dist/otel.js.map +1 -1
- package/dist/otel.mjs +98 -5
- package/dist/otel.mjs.map +1 -1
- package/dist/testing/client.mock.d.ts +47 -0
- package/dist/testing/client.mock.d.ts.map +1 -0
- package/dist/testing/index.d.ts +4 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/test.container.d.ts +63 -0
- package/dist/testing/test.container.d.ts.map +1 -0
- package/dist/{testing.d.mts → testing/transport.fake.d.ts} +7 -111
- package/dist/testing/transport.fake.d.ts.map +1 -0
- package/dist/testing.d.ts +2 -318
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +26 -0
- package/dist/testing.js.map +1 -1
- package/dist/testing.mjs +26 -0
- package/dist/testing.mjs.map +1 -1
- package/package.json +21 -8
- package/dist/chunk-SM4FZKAZ.mjs.map +0 -1
- package/dist/client-1irhGEu0.d.mts +0 -751
- package/dist/client-BpFjkHhr.d.ts +0 -751
- package/dist/consumer.types-fFCag3VJ.d.ts +0 -958
- package/dist/core.d.mts +0 -314
- package/dist/index.d.mts +0 -128
- package/dist/otel.d.mts +0 -27
package/dist/core.js
CHANGED
|
@@ -21,31 +21,50 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var core_exports = {};
|
|
22
22
|
__export(core_exports, {
|
|
23
23
|
HEADER_CORRELATION_ID: () => HEADER_CORRELATION_ID,
|
|
24
|
+
HEADER_DELAYED_TARGET: () => HEADER_DELAYED_TARGET,
|
|
25
|
+
HEADER_DELAYED_UNTIL: () => HEADER_DELAYED_UNTIL,
|
|
24
26
|
HEADER_EVENT_ID: () => HEADER_EVENT_ID,
|
|
25
27
|
HEADER_LAMPORT_CLOCK: () => HEADER_LAMPORT_CLOCK,
|
|
26
28
|
HEADER_SCHEMA_VERSION: () => HEADER_SCHEMA_VERSION,
|
|
27
29
|
HEADER_TIMESTAMP: () => HEADER_TIMESTAMP,
|
|
28
30
|
HEADER_TRACEPARENT: () => HEADER_TRACEPARENT,
|
|
31
|
+
InMemoryDedupStore: () => InMemoryDedupStore,
|
|
32
|
+
InMemoryOutboxStore: () => InMemoryOutboxStore,
|
|
29
33
|
KafkaClient: () => KafkaClient,
|
|
30
34
|
KafkaProcessingError: () => KafkaProcessingError,
|
|
31
35
|
KafkaRetryExhaustedError: () => KafkaRetryExhaustedError,
|
|
32
36
|
KafkaValidationError: () => KafkaValidationError,
|
|
37
|
+
SchemaRegistryClient: () => SchemaRegistryClient,
|
|
38
|
+
awsMskIamProvider: () => awsMskIamProvider,
|
|
33
39
|
buildEnvelopeHeaders: () => buildEnvelopeHeaders,
|
|
40
|
+
consumerOptionsFromEnv: () => consumerOptionsFromEnv,
|
|
34
41
|
decodeHeaders: () => decodeHeaders,
|
|
42
|
+
describeRequiredAcls: () => describeRequiredAcls,
|
|
35
43
|
extractEnvelope: () => extractEnvelope,
|
|
44
|
+
gcpAccessTokenProvider: () => gcpAccessTokenProvider,
|
|
36
45
|
getEnvelopeContext: () => getEnvelopeContext,
|
|
46
|
+
kafkaClientConfigFromEnv: () => kafkaClientConfigFromEnv,
|
|
47
|
+
mergeConsumerOptions: () => mergeConsumerOptions,
|
|
48
|
+
registrySchema: () => registrySchema,
|
|
49
|
+
resolveSecurityOptions: () => resolveSecurityOptions,
|
|
37
50
|
runWithEnvelopeContext: () => runWithEnvelopeContext,
|
|
38
|
-
|
|
51
|
+
startOutboxRelay: () => startOutboxRelay,
|
|
52
|
+
toError: () => toError,
|
|
53
|
+
toKafkaAclCommands: () => toKafkaAclCommands,
|
|
54
|
+
toMskIamPolicy: () => toMskIamPolicy,
|
|
55
|
+
topic: () => topic,
|
|
56
|
+
versionedSchema: () => versionedSchema
|
|
39
57
|
});
|
|
40
58
|
module.exports = __toCommonJS(core_exports);
|
|
41
59
|
|
|
42
|
-
// src/client/
|
|
60
|
+
// src/client/transport/confluent.transport.ts
|
|
43
61
|
var import_kafka_javascript = require("@confluentinc/kafka-javascript");
|
|
44
62
|
var { Kafka: KafkaClass, logLevel: KafkaLogLevel, PartitionAssigners } = import_kafka_javascript.KafkaJS;
|
|
45
63
|
var ConfluentTransaction = class {
|
|
46
64
|
constructor(tx) {
|
|
47
65
|
this.tx = tx;
|
|
48
66
|
}
|
|
67
|
+
tx;
|
|
49
68
|
async send(record) {
|
|
50
69
|
await this.tx.send(record);
|
|
51
70
|
}
|
|
@@ -67,10 +86,17 @@ var ConfluentProducer = class {
|
|
|
67
86
|
constructor(producer) {
|
|
68
87
|
this.producer = producer;
|
|
69
88
|
}
|
|
89
|
+
producer;
|
|
90
|
+
connectPromise;
|
|
70
91
|
async connect() {
|
|
71
|
-
|
|
92
|
+
this.connectPromise ??= this.producer.connect().catch((err) => {
|
|
93
|
+
this.connectPromise = void 0;
|
|
94
|
+
throw err;
|
|
95
|
+
});
|
|
96
|
+
return this.connectPromise;
|
|
72
97
|
}
|
|
73
98
|
async disconnect() {
|
|
99
|
+
this.connectPromise = void 0;
|
|
74
100
|
await this.producer.disconnect();
|
|
75
101
|
}
|
|
76
102
|
async send(record) {
|
|
@@ -85,6 +111,7 @@ var ConfluentConsumer = class {
|
|
|
85
111
|
constructor(consumer) {
|
|
86
112
|
this.consumer = consumer;
|
|
87
113
|
}
|
|
114
|
+
consumer;
|
|
88
115
|
/** Returns the underlying KafkaJS.Consumer — used by ConfluentTransaction.sendOffsets. */
|
|
89
116
|
getNative() {
|
|
90
117
|
return this.consumer;
|
|
@@ -124,6 +151,7 @@ var ConfluentAdmin = class {
|
|
|
124
151
|
constructor(admin) {
|
|
125
152
|
this.admin = admin;
|
|
126
153
|
}
|
|
154
|
+
admin;
|
|
127
155
|
async connect() {
|
|
128
156
|
await this.admin.connect();
|
|
129
157
|
}
|
|
@@ -137,7 +165,7 @@ var ConfluentAdmin = class {
|
|
|
137
165
|
return this.admin.fetchTopicOffsets(topic2);
|
|
138
166
|
}
|
|
139
167
|
async fetchTopicOffsetsByTimestamp(topic2, timestamp) {
|
|
140
|
-
return this.admin.
|
|
168
|
+
return this.admin.fetchTopicOffsetsByTimestamp(topic2, timestamp);
|
|
141
169
|
}
|
|
142
170
|
async fetchOffsets(options) {
|
|
143
171
|
return this.admin.fetchOffsets(options);
|
|
@@ -163,10 +191,29 @@ var ConfluentAdmin = class {
|
|
|
163
191
|
};
|
|
164
192
|
var ConfluentTransport = class {
|
|
165
193
|
kafka;
|
|
166
|
-
constructor(clientId, brokers) {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
194
|
+
constructor(clientId, brokers, security) {
|
|
195
|
+
const kafkaJS = { clientId, brokers, logLevel: KafkaLogLevel.ERROR };
|
|
196
|
+
if (security?.ssl !== void 0) kafkaJS.ssl = security.ssl;
|
|
197
|
+
if (security?.sasl) {
|
|
198
|
+
if (security.sasl.mechanism === "oauthbearer") {
|
|
199
|
+
const provider = security.sasl.oauthBearerProvider;
|
|
200
|
+
kafkaJS.sasl = {
|
|
201
|
+
mechanism: "oauthbearer",
|
|
202
|
+
oauthBearerProvider: async () => {
|
|
203
|
+
const token = await provider();
|
|
204
|
+
return {
|
|
205
|
+
value: token.value,
|
|
206
|
+
principal: token.principal ?? "kafka-client",
|
|
207
|
+
lifetime: token.lifetimeMs ?? Date.now() + 15 * 6e4,
|
|
208
|
+
...token.extensions && { extensions: token.extensions }
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
} else {
|
|
213
|
+
kafkaJS.sasl = security.sasl;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
this.kafka = new KafkaClass({ kafkaJS });
|
|
170
217
|
}
|
|
171
218
|
producer(options) {
|
|
172
219
|
const native = this.kafka.producer({
|
|
@@ -193,6 +240,9 @@ var ConfluentTransport = class {
|
|
|
193
240
|
partitionAssigners: [assigner]
|
|
194
241
|
}
|
|
195
242
|
};
|
|
243
|
+
if (options.groupInstanceId) {
|
|
244
|
+
config["group.instance.id"] = options.groupInstanceId;
|
|
245
|
+
}
|
|
196
246
|
if (options.onRebalance) {
|
|
197
247
|
const cb = options.onRebalance;
|
|
198
248
|
config.rebalance_cb = (err, assignment) => {
|
|
@@ -210,6 +260,25 @@ var ConfluentTransport = class {
|
|
|
210
260
|
}
|
|
211
261
|
};
|
|
212
262
|
|
|
263
|
+
// src/client/kafka.client/infra/dedup.store.ts
|
|
264
|
+
var InMemoryDedupStore = class {
|
|
265
|
+
constructor(states) {
|
|
266
|
+
this.states = states;
|
|
267
|
+
}
|
|
268
|
+
states;
|
|
269
|
+
getLastClock(groupId, topicPartition) {
|
|
270
|
+
return this.states.get(groupId)?.get(topicPartition);
|
|
271
|
+
}
|
|
272
|
+
setLastClock(groupId, topicPartition, clock) {
|
|
273
|
+
let group = this.states.get(groupId);
|
|
274
|
+
if (!group) {
|
|
275
|
+
group = /* @__PURE__ */ new Map();
|
|
276
|
+
this.states.set(groupId, group);
|
|
277
|
+
}
|
|
278
|
+
group.set(topicPartition, clock);
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
|
|
213
282
|
// src/client/message/envelope.ts
|
|
214
283
|
var import_node_async_hooks = require("async_hooks");
|
|
215
284
|
var import_node_crypto = require("crypto");
|
|
@@ -219,6 +288,8 @@ var HEADER_TIMESTAMP = "x-timestamp";
|
|
|
219
288
|
var HEADER_SCHEMA_VERSION = "x-schema-version";
|
|
220
289
|
var HEADER_TRACEPARENT = "traceparent";
|
|
221
290
|
var HEADER_LAMPORT_CLOCK = "x-lamport-clock";
|
|
291
|
+
var HEADER_DELAYED_UNTIL = "x-delayed-until";
|
|
292
|
+
var HEADER_DELAYED_TARGET = "x-delayed-target";
|
|
222
293
|
var envelopeStorage = new import_node_async_hooks.AsyncLocalStorage();
|
|
223
294
|
function getEnvelopeContext() {
|
|
224
295
|
return envelopeStorage.getStore();
|
|
@@ -273,6 +344,9 @@ function extractEnvelope(payload, headers, topic2, partition, offset) {
|
|
|
273
344
|
}
|
|
274
345
|
|
|
275
346
|
// src/client/errors.ts
|
|
347
|
+
function toError(error) {
|
|
348
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
349
|
+
}
|
|
276
350
|
var KafkaProcessingError = class extends Error {
|
|
277
351
|
constructor(message, topic2, originalMessage, options) {
|
|
278
352
|
super(message, options);
|
|
@@ -281,6 +355,8 @@ var KafkaProcessingError = class extends Error {
|
|
|
281
355
|
this.name = "KafkaProcessingError";
|
|
282
356
|
if (options?.cause) this.cause = options.cause;
|
|
283
357
|
}
|
|
358
|
+
topic;
|
|
359
|
+
originalMessage;
|
|
284
360
|
};
|
|
285
361
|
var KafkaValidationError = class extends Error {
|
|
286
362
|
constructor(topic2, originalMessage, options) {
|
|
@@ -290,6 +366,8 @@ var KafkaValidationError = class extends Error {
|
|
|
290
366
|
this.name = "KafkaValidationError";
|
|
291
367
|
if (options?.cause) this.cause = options.cause;
|
|
292
368
|
}
|
|
369
|
+
topic;
|
|
370
|
+
originalMessage;
|
|
293
371
|
};
|
|
294
372
|
var KafkaRetryExhaustedError = class extends KafkaProcessingError {
|
|
295
373
|
constructor(topic2, originalMessage, attempts, options) {
|
|
@@ -302,6 +380,7 @@ var KafkaRetryExhaustedError = class extends KafkaProcessingError {
|
|
|
302
380
|
this.attempts = attempts;
|
|
303
381
|
this.name = "KafkaRetryExhaustedError";
|
|
304
382
|
}
|
|
383
|
+
attempts;
|
|
305
384
|
};
|
|
306
385
|
|
|
307
386
|
// src/client/kafka.client/producer/ops.ts
|
|
@@ -374,7 +453,9 @@ async function buildSendPayload(topicOrDesc, messages, deps, compression) {
|
|
|
374
453
|
value: JSON.stringify(
|
|
375
454
|
await validateMessage(topicOrDesc, m.value, deps, sendCtx)
|
|
376
455
|
),
|
|
377
|
-
key
|
|
456
|
+
// Explicit key wins; otherwise fall back to the descriptor's .key()
|
|
457
|
+
// extractor (runs on the original, pre-validation payload).
|
|
458
|
+
key: m.key ?? topicOrDesc?.__key?.(m.value) ?? null,
|
|
378
459
|
headers: envelopeHeaders
|
|
379
460
|
};
|
|
380
461
|
})
|
|
@@ -383,7 +464,7 @@ async function buildSendPayload(topicOrDesc, messages, deps, compression) {
|
|
|
383
464
|
}
|
|
384
465
|
|
|
385
466
|
// src/client/kafka.client/consumer/ops.ts
|
|
386
|
-
function getOrCreateConsumer(groupId, fromBeginning, autoCommit, deps, partitionAssigner, onFirstAssignment) {
|
|
467
|
+
function getOrCreateConsumer(groupId, fromBeginning, autoCommit, deps, partitionAssigner, onFirstAssignment, groupInstanceId) {
|
|
387
468
|
const { consumers, consumerCreationOptions, transport, onRebalance, logger } = deps;
|
|
388
469
|
if (consumers.has(groupId)) {
|
|
389
470
|
const prev = consumerCreationOptions.get(groupId);
|
|
@@ -416,6 +497,7 @@ function getOrCreateConsumer(groupId, fromBeginning, autoCommit, deps, partition
|
|
|
416
497
|
fromBeginning,
|
|
417
498
|
autoCommit,
|
|
418
499
|
partitionAssigner: partitionAssigner ?? "cooperative-sticky",
|
|
500
|
+
groupInstanceId,
|
|
419
501
|
onRebalance: (type, assignments) => {
|
|
420
502
|
if (type === "assign") fireOnAssignment();
|
|
421
503
|
else if (type === "revoke") scheduleSettle();
|
|
@@ -461,6 +543,7 @@ var AdminOps = class {
|
|
|
461
543
|
constructor(deps) {
|
|
462
544
|
this.deps = deps;
|
|
463
545
|
}
|
|
546
|
+
deps;
|
|
464
547
|
isConnected = false;
|
|
465
548
|
/** Underlying admin client — used by index.ts for topic validation. */
|
|
466
549
|
get admin() {
|
|
@@ -567,7 +650,10 @@ var AdminOps = class {
|
|
|
567
650
|
const found = results.find(
|
|
568
651
|
(r) => r.partition === partition
|
|
569
652
|
);
|
|
570
|
-
return { partition, offset: found
|
|
653
|
+
if (found) return { partition, offset: found.offset };
|
|
654
|
+
const topicOffsets = await this.deps.admin.fetchTopicOffsets(topic2);
|
|
655
|
+
const po = topicOffsets.find((o) => o.partition === partition);
|
|
656
|
+
return { partition, offset: po?.high ?? "0" };
|
|
571
657
|
})
|
|
572
658
|
);
|
|
573
659
|
await this.deps.admin.setOffsets({ groupId: gid, topic: topic2, partitions: offsets });
|
|
@@ -648,7 +734,8 @@ var AdminOps = class {
|
|
|
648
734
|
name: t.name,
|
|
649
735
|
partitions: t.partitions.map((p) => ({
|
|
650
736
|
partition: p.partitionId ?? p.partition ?? 0,
|
|
651
|
-
|
|
737
|
+
// -1 is Kafka's own "no leader" sentinel; 0 is a valid broker id
|
|
738
|
+
leader: p.leader ?? -1,
|
|
652
739
|
replicas: (p.replicas ?? []).map(
|
|
653
740
|
(r) => typeof r === "number" ? r : r.nodeId
|
|
654
741
|
),
|
|
@@ -732,9 +819,6 @@ var AdminOps = class {
|
|
|
732
819
|
};
|
|
733
820
|
|
|
734
821
|
// src/client/kafka.client/consumer/pipeline.ts
|
|
735
|
-
function toError(error) {
|
|
736
|
-
return error instanceof Error ? error : new Error(String(error));
|
|
737
|
-
}
|
|
738
822
|
function sleep(ms) {
|
|
739
823
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
740
824
|
}
|
|
@@ -1018,6 +1102,7 @@ async function executeWithRetry(fn, ctx, deps) {
|
|
|
1018
1102
|
for (const env of envelopes) deps.onMessage?.(env);
|
|
1019
1103
|
return;
|
|
1020
1104
|
}
|
|
1105
|
+
deps.onFailure?.(envelopes[0]);
|
|
1021
1106
|
const isLastAttempt = attempt === maxAttempts;
|
|
1022
1107
|
const reportedError = isLastAttempt && maxAttempts > 1 ? new KafkaRetryExhaustedError(
|
|
1023
1108
|
topic2,
|
|
@@ -1102,8 +1187,13 @@ async function subscribeWithRetry(consumer, topics, logger, retryOpts) {
|
|
|
1102
1187
|
}
|
|
1103
1188
|
}
|
|
1104
1189
|
|
|
1105
|
-
// src/client/kafka.client/consumer/dlq-replay.ts
|
|
1190
|
+
// src/client/kafka.client/consumer/features/dlq-replay.ts
|
|
1106
1191
|
async function replayDlqTopic(topic2, deps, options = {}) {
|
|
1192
|
+
if (topic2.endsWith(".dlq")) {
|
|
1193
|
+
throw new Error(
|
|
1194
|
+
`replayDlq: pass the ORIGINAL topic name \u2014 "${topic2}" already ends in ".dlq" (the ".dlq" suffix is appended internally, so this would read "${topic2}.dlq")`
|
|
1195
|
+
);
|
|
1196
|
+
}
|
|
1107
1197
|
const dlqTopic = `${topic2}.dlq`;
|
|
1108
1198
|
const partitionOffsets = await deps.fetchTopicOffsets(dlqTopic);
|
|
1109
1199
|
const activePartitions = partitionOffsets.filter(
|
|
@@ -1169,6 +1259,7 @@ var MetricsManager = class {
|
|
|
1169
1259
|
constructor(deps) {
|
|
1170
1260
|
this.deps = deps;
|
|
1171
1261
|
}
|
|
1262
|
+
deps;
|
|
1172
1263
|
topicMetrics = /* @__PURE__ */ new Map();
|
|
1173
1264
|
metricsFor(topic2) {
|
|
1174
1265
|
let m = this.topicMetrics.get(topic2);
|
|
@@ -1194,16 +1285,25 @@ var MetricsManager = class {
|
|
|
1194
1285
|
for (const inst of this.deps.instrumentation) inst.onRetry?.(envelope, attempt, maxRetries);
|
|
1195
1286
|
}
|
|
1196
1287
|
/**
|
|
1197
|
-
* Increment the DLQ counter for the envelope's topic
|
|
1198
|
-
*
|
|
1288
|
+
* Increment the DLQ counter for the envelope's topic and fire all `onDlq` instrumentation hooks.
|
|
1289
|
+
* Circuit breaker failures are recorded separately via `notifyFailure` at the
|
|
1290
|
+
* handler-error boundary — dead-lettering itself is not a circuit event.
|
|
1199
1291
|
* @param envelope The message envelope being sent to the DLQ.
|
|
1200
1292
|
* @param reason The reason the message is being dead-lettered.
|
|
1201
|
-
* @param gid Consumer group ID — used to drive circuit breaker state.
|
|
1202
1293
|
*/
|
|
1203
|
-
notifyDlq(envelope, reason
|
|
1294
|
+
notifyDlq(envelope, reason) {
|
|
1204
1295
|
this.metricsFor(envelope.topic).dlqCount++;
|
|
1205
1296
|
for (const inst of this.deps.instrumentation) inst.onDlq?.(envelope, reason);
|
|
1206
|
-
|
|
1297
|
+
}
|
|
1298
|
+
/**
|
|
1299
|
+
* Notify the circuit breaker of a handler failure. Fired on every failed
|
|
1300
|
+
* handler attempt (in-process retries and retry-topic levels included),
|
|
1301
|
+
* independent of whether the message is ultimately dead-lettered.
|
|
1302
|
+
* @param envelope The message envelope whose handler failed.
|
|
1303
|
+
* @param gid Consumer group ID — used to drive circuit breaker state.
|
|
1304
|
+
*/
|
|
1305
|
+
notifyFailure(envelope, gid) {
|
|
1306
|
+
this.deps.onCircuitFailure(envelope, gid);
|
|
1207
1307
|
}
|
|
1208
1308
|
/**
|
|
1209
1309
|
* Increment the deduplication counter for the envelope's topic and fire all `onDuplicate` hooks.
|
|
@@ -1262,6 +1362,7 @@ var InFlightTracker = class {
|
|
|
1262
1362
|
constructor(warn) {
|
|
1263
1363
|
this.warn = warn;
|
|
1264
1364
|
}
|
|
1365
|
+
warn;
|
|
1265
1366
|
inFlightTotal = 0;
|
|
1266
1367
|
drainResolvers = [];
|
|
1267
1368
|
/**
|
|
@@ -1272,10 +1373,16 @@ var InFlightTracker = class {
|
|
|
1272
1373
|
*/
|
|
1273
1374
|
track(fn) {
|
|
1274
1375
|
this.inFlightTotal++;
|
|
1275
|
-
|
|
1376
|
+
const done = () => {
|
|
1276
1377
|
this.inFlightTotal--;
|
|
1277
1378
|
if (this.inFlightTotal === 0) this.drainResolvers.splice(0).forEach((r) => r());
|
|
1278
|
-
}
|
|
1379
|
+
};
|
|
1380
|
+
try {
|
|
1381
|
+
return fn().finally(done);
|
|
1382
|
+
} catch (err) {
|
|
1383
|
+
done();
|
|
1384
|
+
throw err;
|
|
1385
|
+
}
|
|
1279
1386
|
}
|
|
1280
1387
|
/**
|
|
1281
1388
|
* Resolve when all tracked handlers have completed, or after `timeoutMs` elapses.
|
|
@@ -1309,6 +1416,7 @@ var CircuitBreakerManager = class {
|
|
|
1309
1416
|
constructor(deps) {
|
|
1310
1417
|
this.deps = deps;
|
|
1311
1418
|
}
|
|
1419
|
+
deps;
|
|
1312
1420
|
states = /* @__PURE__ */ new Map();
|
|
1313
1421
|
configs = /* @__PURE__ */ new Map();
|
|
1314
1422
|
/**
|
|
@@ -1453,6 +1561,9 @@ var AsyncQueue = class {
|
|
|
1453
1561
|
this.onFull = onFull;
|
|
1454
1562
|
this.onDrained = onDrained;
|
|
1455
1563
|
}
|
|
1564
|
+
highWaterMark;
|
|
1565
|
+
onFull;
|
|
1566
|
+
onDrained;
|
|
1456
1567
|
items = [];
|
|
1457
1568
|
waiting = [];
|
|
1458
1569
|
closed = false;
|
|
@@ -1464,6 +1575,7 @@ var AsyncQueue = class {
|
|
|
1464
1575
|
* @param item The value to enqueue.
|
|
1465
1576
|
*/
|
|
1466
1577
|
push(item) {
|
|
1578
|
+
if (this.closed) return;
|
|
1467
1579
|
if (this.waiting.length > 0) {
|
|
1468
1580
|
this.waiting.shift().resolve({ value: item, done: false });
|
|
1469
1581
|
} else {
|
|
@@ -1514,6 +1626,101 @@ var AsyncQueue = class {
|
|
|
1514
1626
|
}
|
|
1515
1627
|
};
|
|
1516
1628
|
|
|
1629
|
+
// src/client/kafka.client/validate-options.ts
|
|
1630
|
+
function validateClientOptions(clientId, groupId, brokers, options) {
|
|
1631
|
+
const problems = [];
|
|
1632
|
+
if (typeof clientId !== "string" || clientId.trim() === "") {
|
|
1633
|
+
problems.push("clientId must be a non-empty string");
|
|
1634
|
+
}
|
|
1635
|
+
if (typeof groupId !== "string" || groupId.trim() === "") {
|
|
1636
|
+
problems.push("groupId must be a non-empty string");
|
|
1637
|
+
}
|
|
1638
|
+
if (!Array.isArray(brokers) || brokers.length === 0 && !options?.transport) {
|
|
1639
|
+
problems.push("brokers must be a non-empty array of broker addresses");
|
|
1640
|
+
} else if (brokers.some((b) => typeof b !== "string" || b.trim() === "")) {
|
|
1641
|
+
problems.push("brokers must not contain empty entries");
|
|
1642
|
+
}
|
|
1643
|
+
if (options) {
|
|
1644
|
+
const {
|
|
1645
|
+
numPartitions,
|
|
1646
|
+
transactionalId,
|
|
1647
|
+
clockRecovery,
|
|
1648
|
+
lagThrottle
|
|
1649
|
+
} = options;
|
|
1650
|
+
if (numPartitions !== void 0 && (!Number.isInteger(numPartitions) || numPartitions < 1)) {
|
|
1651
|
+
problems.push(
|
|
1652
|
+
`numPartitions must be a positive integer (got ${numPartitions})`
|
|
1653
|
+
);
|
|
1654
|
+
}
|
|
1655
|
+
if (transactionalId !== void 0 && transactionalId.trim() === "") {
|
|
1656
|
+
problems.push("transactionalId must be a non-empty string when set");
|
|
1657
|
+
}
|
|
1658
|
+
if (clockRecovery) {
|
|
1659
|
+
if (!Array.isArray(clockRecovery.topics)) {
|
|
1660
|
+
problems.push("clockRecovery.topics must be an array of topic names");
|
|
1661
|
+
}
|
|
1662
|
+
if (clockRecovery.timeoutMs !== void 0 && !(clockRecovery.timeoutMs > 0)) {
|
|
1663
|
+
problems.push(
|
|
1664
|
+
`clockRecovery.timeoutMs must be > 0 (got ${clockRecovery.timeoutMs})`
|
|
1665
|
+
);
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
if (lagThrottle) {
|
|
1669
|
+
if (!(lagThrottle.maxLag >= 0)) {
|
|
1670
|
+
problems.push(`lagThrottle.maxLag must be >= 0 (got ${lagThrottle.maxLag})`);
|
|
1671
|
+
}
|
|
1672
|
+
if (lagThrottle.pollIntervalMs !== void 0 && !(lagThrottle.pollIntervalMs > 0)) {
|
|
1673
|
+
problems.push(
|
|
1674
|
+
`lagThrottle.pollIntervalMs must be > 0 (got ${lagThrottle.pollIntervalMs})`
|
|
1675
|
+
);
|
|
1676
|
+
}
|
|
1677
|
+
if (lagThrottle.maxWaitMs !== void 0 && !(lagThrottle.maxWaitMs >= 0)) {
|
|
1678
|
+
problems.push(
|
|
1679
|
+
`lagThrottle.maxWaitMs must be >= 0 (got ${lagThrottle.maxWaitMs})`
|
|
1680
|
+
);
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
if (problems.length > 0) {
|
|
1685
|
+
throw new Error(
|
|
1686
|
+
`KafkaClient: invalid configuration:
|
|
1687
|
+
- ${problems.join("\n- ")}`
|
|
1688
|
+
);
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1692
|
+
// src/client/security/resolve-security.ts
|
|
1693
|
+
var LOCAL_HOST_PATTERNS = [
|
|
1694
|
+
/^localhost(:\d+)?$/i,
|
|
1695
|
+
/^127\.\d+\.\d+\.\d+(:\d+)?$/,
|
|
1696
|
+
/^\[?::1\]?(:\d+)?$/,
|
|
1697
|
+
/^0\.0\.0\.0(:\d+)?$/,
|
|
1698
|
+
/^host\.docker\.internal(:\d+)?$/i
|
|
1699
|
+
];
|
|
1700
|
+
function isLocalBroker(broker) {
|
|
1701
|
+
return LOCAL_HOST_PATTERNS.some((re) => re.test(broker.trim()));
|
|
1702
|
+
}
|
|
1703
|
+
function resolveSecurityOptions(security, brokers, logger) {
|
|
1704
|
+
const hasRemoteBroker = brokers.some((b) => !isLocalBroker(b));
|
|
1705
|
+
if (!security?.sasl && security?.ssl !== true) {
|
|
1706
|
+
if (hasRemoteBroker && !security?.allowInsecure) {
|
|
1707
|
+
logger.warn(
|
|
1708
|
+
"Connecting to non-local brokers without TLS or SASL \u2014 traffic and payloads travel in plaintext. Configure `security: { ssl, sasl }` for production clusters, or set `security: { allowInsecure: true }` to acknowledge an intentionally insecure setup."
|
|
1709
|
+
);
|
|
1710
|
+
}
|
|
1711
|
+
return security;
|
|
1712
|
+
}
|
|
1713
|
+
if (security.sasl && security.ssl === void 0) {
|
|
1714
|
+
return { ...security, ssl: true };
|
|
1715
|
+
}
|
|
1716
|
+
if (security.sasl && security.ssl === false) {
|
|
1717
|
+
logger.warn(
|
|
1718
|
+
"SASL credentials are configured with `ssl: false` \u2014 credentials will be sent over plaintext. This is only safe on fully trusted networks."
|
|
1719
|
+
);
|
|
1720
|
+
}
|
|
1721
|
+
return security;
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1517
1724
|
// src/client/kafka.client/producer/lifecycle.ts
|
|
1518
1725
|
var _activeTransactionalIds = /* @__PURE__ */ new Set();
|
|
1519
1726
|
async function ensureTopic(ctx, topic2) {
|
|
@@ -1647,6 +1854,7 @@ async function recoverLamportClockImpl(ctx, topics) {
|
|
|
1647
1854
|
const remaining = new Set(
|
|
1648
1855
|
partitionsToRead.map((p) => `${p.topic}:${p.partition}`)
|
|
1649
1856
|
);
|
|
1857
|
+
let settled = false;
|
|
1650
1858
|
const cleanup = () => {
|
|
1651
1859
|
consumer.disconnect().catch(() => {
|
|
1652
1860
|
}).finally(() => {
|
|
@@ -1654,6 +1862,16 @@ async function recoverLamportClockImpl(ctx, topics) {
|
|
|
1654
1862
|
});
|
|
1655
1863
|
});
|
|
1656
1864
|
};
|
|
1865
|
+
const timeoutTimer = setTimeout(() => {
|
|
1866
|
+
if (settled) return;
|
|
1867
|
+
settled = true;
|
|
1868
|
+
ctx.logger.warn(
|
|
1869
|
+
`Clock recovery: timed out after ${ctx.clockRecoveryTimeoutMs} ms with ${remaining.size} partition(s) unread \u2014 proceeding with partial result`
|
|
1870
|
+
);
|
|
1871
|
+
cleanup();
|
|
1872
|
+
resolve();
|
|
1873
|
+
}, ctx.clockRecoveryTimeoutMs);
|
|
1874
|
+
timeoutTimer.unref?.();
|
|
1657
1875
|
consumer.connect().then(async () => {
|
|
1658
1876
|
const uniqueTopics = [
|
|
1659
1877
|
...new Set(partitionsToRead.map((p) => p.topic))
|
|
@@ -1674,13 +1892,18 @@ async function recoverLamportClockImpl(ctx, topics) {
|
|
|
1674
1892
|
const clock = Number(raw);
|
|
1675
1893
|
if (!Number.isNaN(clock) && clock > maxClock) maxClock = clock;
|
|
1676
1894
|
}
|
|
1677
|
-
if (remaining.size === 0) {
|
|
1895
|
+
if (remaining.size === 0 && !settled) {
|
|
1896
|
+
settled = true;
|
|
1897
|
+
clearTimeout(timeoutTimer);
|
|
1678
1898
|
cleanup();
|
|
1679
1899
|
resolve();
|
|
1680
1900
|
}
|
|
1681
1901
|
}
|
|
1682
1902
|
})
|
|
1683
1903
|
).catch((err) => {
|
|
1904
|
+
if (settled) return;
|
|
1905
|
+
settled = true;
|
|
1906
|
+
clearTimeout(timeoutTimer);
|
|
1684
1907
|
cleanup();
|
|
1685
1908
|
reject(err);
|
|
1686
1909
|
});
|
|
@@ -1721,6 +1944,15 @@ async function preparePayload(ctx, topicOrDesc, messages, compression) {
|
|
|
1721
1944
|
await ensureTopic(ctx, payload.topic);
|
|
1722
1945
|
return payload;
|
|
1723
1946
|
}
|
|
1947
|
+
async function redirectToDelayed(ctx, payload, deliverAfterMs) {
|
|
1948
|
+
const until = String(Date.now() + deliverAfterMs);
|
|
1949
|
+
for (const m of payload.messages) {
|
|
1950
|
+
m.headers[HEADER_DELAYED_UNTIL] = until;
|
|
1951
|
+
m.headers[HEADER_DELAYED_TARGET] = payload.topic;
|
|
1952
|
+
}
|
|
1953
|
+
payload.topic = `${payload.topic}.delayed`;
|
|
1954
|
+
await ensureTopic(ctx, payload.topic);
|
|
1955
|
+
}
|
|
1724
1956
|
async function sendMessageImpl(ctx, topicOrDesc, message, options = {}) {
|
|
1725
1957
|
await waitIfThrottled(ctx);
|
|
1726
1958
|
const payload = await preparePayload(
|
|
@@ -1738,6 +1970,9 @@ async function sendMessageImpl(ctx, topicOrDesc, message, options = {}) {
|
|
|
1738
1970
|
],
|
|
1739
1971
|
options.compression
|
|
1740
1972
|
);
|
|
1973
|
+
if (options.deliverAfterMs && options.deliverAfterMs > 0) {
|
|
1974
|
+
await redirectToDelayed(ctx, payload, options.deliverAfterMs);
|
|
1975
|
+
}
|
|
1741
1976
|
await ctx.producer.send(payload);
|
|
1742
1977
|
ctx.metrics.notifyAfterSend(payload.topic, payload.messages.length);
|
|
1743
1978
|
}
|
|
@@ -1749,6 +1984,9 @@ async function sendBatchImpl(ctx, topicOrDesc, messages, options) {
|
|
|
1749
1984
|
messages,
|
|
1750
1985
|
options?.compression
|
|
1751
1986
|
);
|
|
1987
|
+
if (options?.deliverAfterMs && options.deliverAfterMs > 0) {
|
|
1988
|
+
await redirectToDelayed(ctx, payload, options.deliverAfterMs);
|
|
1989
|
+
}
|
|
1752
1990
|
await ctx.producer.send(payload);
|
|
1753
1991
|
ctx.metrics.notifyAfterSend(payload.topic, payload.messages.length);
|
|
1754
1992
|
}
|
|
@@ -1785,6 +2023,17 @@ async function transactionImpl(ctx, fn) {
|
|
|
1785
2023
|
});
|
|
1786
2024
|
}
|
|
1787
2025
|
ctx.txProducer = await ctx.txProducerInitPromise;
|
|
2026
|
+
const prev = ctx._txChain;
|
|
2027
|
+
let release;
|
|
2028
|
+
ctx._txChain = new Promise((r) => release = r);
|
|
2029
|
+
await prev;
|
|
2030
|
+
try {
|
|
2031
|
+
await runTransaction(ctx, fn);
|
|
2032
|
+
} finally {
|
|
2033
|
+
release();
|
|
2034
|
+
}
|
|
2035
|
+
}
|
|
2036
|
+
async function runTransaction(ctx, fn) {
|
|
1788
2037
|
const tx = await ctx.txProducer.transaction();
|
|
1789
2038
|
try {
|
|
1790
2039
|
const txCtx = {
|
|
@@ -1952,6 +2201,7 @@ async function startLevelConsumer(level, levelTopics, levelGroupId, originalTopi
|
|
|
1952
2201
|
await consumer.commitOffsets([nextOffset]);
|
|
1953
2202
|
return;
|
|
1954
2203
|
}
|
|
2204
|
+
deps.onFailure?.(envelope);
|
|
1955
2205
|
const exhausted = level >= currentMaxRetries;
|
|
1956
2206
|
const reportedError = exhausted && currentMaxRetries > 1 ? new KafkaRetryExhaustedError(
|
|
1957
2207
|
originalTopic,
|
|
@@ -2167,7 +2417,8 @@ async function setupConsumer(ctx, topics, mode, options) {
|
|
|
2167
2417
|
options.autoCommit ?? true,
|
|
2168
2418
|
ctx.consumerOpsDeps,
|
|
2169
2419
|
options.partitionAssigner,
|
|
2170
|
-
resolveReady
|
|
2420
|
+
resolveReady,
|
|
2421
|
+
options.groupInstanceId
|
|
2171
2422
|
);
|
|
2172
2423
|
const schemaMap = buildSchemaMap(
|
|
2173
2424
|
stringTopics,
|
|
@@ -2179,6 +2430,9 @@ async function setupConsumer(ctx, topics, mode, options) {
|
|
|
2179
2430
|
const subscribeTopics = [...topicNames, ...regexTopics];
|
|
2180
2431
|
await ensureConsumerTopics(ctx, topicNames, dlq, options.deduplication);
|
|
2181
2432
|
await consumer.connect();
|
|
2433
|
+
if (dlq || options.retryTopics || options.deduplication) {
|
|
2434
|
+
await ctx.producer.connect();
|
|
2435
|
+
}
|
|
2182
2436
|
await subscribeWithRetry(
|
|
2183
2437
|
consumer,
|
|
2184
2438
|
subscribeTopics,
|
|
@@ -2193,9 +2447,8 @@ async function setupConsumer(ctx, topics, mode, options) {
|
|
|
2193
2447
|
}
|
|
2194
2448
|
function resolveDeduplicationContext(ctx, groupId, options) {
|
|
2195
2449
|
if (!options) return void 0;
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
return { options, state: ctx.dedupStates.get(groupId) };
|
|
2450
|
+
const store = options.store ?? new InMemoryDedupStore(ctx.dedupStates);
|
|
2451
|
+
return { options, store, groupId };
|
|
2199
2452
|
}
|
|
2200
2453
|
function messageDepsFor(ctx, gid, options) {
|
|
2201
2454
|
const notifyRetry = ctx.metrics.notifyRetry.bind(ctx.metrics);
|
|
@@ -2209,9 +2462,10 @@ function messageDepsFor(ctx, gid, options) {
|
|
|
2209
2462
|
notifyRetry(envelope, attempt, max);
|
|
2210
2463
|
return options.onRetry(envelope, attempt, max);
|
|
2211
2464
|
} : notifyRetry,
|
|
2212
|
-
onDlq: (envelope, reason) => ctx.metrics.notifyDlq(envelope, reason
|
|
2465
|
+
onDlq: (envelope, reason) => ctx.metrics.notifyDlq(envelope, reason),
|
|
2213
2466
|
onDuplicate: ctx.metrics.notifyDuplicate.bind(ctx.metrics),
|
|
2214
|
-
onMessage: (envelope) => ctx.metrics.notifyMessage(envelope, gid)
|
|
2467
|
+
onMessage: (envelope) => ctx.metrics.notifyMessage(envelope, gid),
|
|
2468
|
+
onFailure: (envelope) => ctx.metrics.notifyFailure(envelope, gid)
|
|
2215
2469
|
};
|
|
2216
2470
|
}
|
|
2217
2471
|
function buildRetryTopicDeps(ctx) {
|
|
@@ -2250,6 +2504,11 @@ async function launchRetryChain(ctx, gid, topicNames, handleMessage, opts) {
|
|
|
2250
2504
|
schemaMap,
|
|
2251
2505
|
{
|
|
2252
2506
|
...ctx.retryTopicDeps,
|
|
2507
|
+
// Bind circuit breaker events to the MAIN consumer group so failures and
|
|
2508
|
+
// successes inside the retry chain drive the same breaker as the main
|
|
2509
|
+
// consumer (the retry chain has no breaker config of its own).
|
|
2510
|
+
onFailure: (envelope) => ctx.metrics.notifyFailure(envelope, gid),
|
|
2511
|
+
onMessage: (envelope) => ctx.metrics.notifyMessage(envelope, gid),
|
|
2253
2512
|
onLevelStarted: (levelGroupId) => {
|
|
2254
2513
|
ctx.companionGroupIds.get(gid).push(levelGroupId);
|
|
2255
2514
|
}
|
|
@@ -2265,7 +2524,15 @@ async function applyDeduplication(envelope, raw, dedup, dlq, deps) {
|
|
|
2265
2524
|
const incomingClock = Number(clockRaw);
|
|
2266
2525
|
if (Number.isNaN(incomingClock)) return false;
|
|
2267
2526
|
const stateKey = `${envelope.topic}:${envelope.partition}`;
|
|
2268
|
-
|
|
2527
|
+
let lastProcessedClock;
|
|
2528
|
+
try {
|
|
2529
|
+
lastProcessedClock = await dedup.store.getLastClock(dedup.groupId, stateKey) ?? -1;
|
|
2530
|
+
} catch (err) {
|
|
2531
|
+
deps.logger.error(
|
|
2532
|
+
`Dedup store getLastClock failed on ${envelope.topic}[${envelope.partition}] \u2014 treating message as not a duplicate (fail-open): ${err.message}`
|
|
2533
|
+
);
|
|
2534
|
+
return false;
|
|
2535
|
+
}
|
|
2269
2536
|
if (incomingClock <= lastProcessedClock) {
|
|
2270
2537
|
const meta = {
|
|
2271
2538
|
incomingClock,
|
|
@@ -2295,7 +2562,13 @@ async function applyDeduplication(envelope, raw, dedup, dlq, deps) {
|
|
|
2295
2562
|
}
|
|
2296
2563
|
return true;
|
|
2297
2564
|
}
|
|
2298
|
-
|
|
2565
|
+
try {
|
|
2566
|
+
await dedup.store.setLastClock(dedup.groupId, stateKey, incomingClock);
|
|
2567
|
+
} catch (err) {
|
|
2568
|
+
deps.logger.error(
|
|
2569
|
+
`Dedup store setLastClock failed on ${envelope.topic}[${envelope.partition}] \u2014 processing message anyway (fail-open): ${err.message}`
|
|
2570
|
+
);
|
|
2571
|
+
}
|
|
2299
2572
|
return false;
|
|
2300
2573
|
}
|
|
2301
2574
|
async function parseSingleMessage(message, topic2, partition, schemaMap, interceptors, dlq, deps) {
|
|
@@ -2908,7 +3181,7 @@ function stopConsumerByGid(ctx, gid) {
|
|
|
2908
3181
|
return stopConsumerImpl(ctx, gid);
|
|
2909
3182
|
}
|
|
2910
3183
|
|
|
2911
|
-
// src/client/kafka.client/consumer/window.ts
|
|
3184
|
+
// src/client/kafka.client/consumer/features/window.ts
|
|
2912
3185
|
async function startWindowConsumerImpl(ctx, topic2, handler, options) {
|
|
2913
3186
|
const { maxMessages, maxMs, ...consumerOptions } = options;
|
|
2914
3187
|
if (maxMessages <= 0)
|
|
@@ -2922,6 +3195,7 @@ async function startWindowConsumerImpl(ctx, topic2, handler, options) {
|
|
|
2922
3195
|
const buffer = [];
|
|
2923
3196
|
let flushTimer = null;
|
|
2924
3197
|
let windowStart = 0;
|
|
3198
|
+
const onLost = consumerOptions.onMessageLost ?? ctx.onMessageLost;
|
|
2925
3199
|
const flush = async (trigger) => {
|
|
2926
3200
|
if (flushTimer !== null) {
|
|
2927
3201
|
clearTimeout(flushTimer);
|
|
@@ -2929,17 +3203,32 @@ async function startWindowConsumerImpl(ctx, topic2, handler, options) {
|
|
|
2929
3203
|
}
|
|
2930
3204
|
if (buffer.length === 0) return;
|
|
2931
3205
|
const envelopes = buffer.splice(0);
|
|
2932
|
-
|
|
3206
|
+
try {
|
|
3207
|
+
await handler(envelopes, { trigger, windowStart, windowEnd: Date.now() });
|
|
3208
|
+
} catch (err) {
|
|
3209
|
+
const error = toError(err);
|
|
3210
|
+
ctx.logger.error(
|
|
3211
|
+
`startWindowConsumer: ${trigger}-triggered flush failed \u2014 window of ${envelopes.length} message(s) lost:`,
|
|
3212
|
+
error.stack
|
|
3213
|
+
);
|
|
3214
|
+
for (const envelope of envelopes) {
|
|
3215
|
+
await Promise.resolve(
|
|
3216
|
+
onLost?.({
|
|
3217
|
+
topic: envelope.topic,
|
|
3218
|
+
error,
|
|
3219
|
+
attempt: 0,
|
|
3220
|
+
headers: envelope.headers
|
|
3221
|
+
})
|
|
3222
|
+
).catch(() => {
|
|
3223
|
+
});
|
|
3224
|
+
}
|
|
3225
|
+
}
|
|
2933
3226
|
};
|
|
2934
3227
|
const scheduleFlush = () => {
|
|
2935
3228
|
if (flushTimer !== null) return;
|
|
2936
3229
|
flushTimer = setTimeout(() => {
|
|
2937
3230
|
flushTimer = null;
|
|
2938
|
-
flush("time")
|
|
2939
|
-
ctx.logger.warn(
|
|
2940
|
-
`startWindowConsumer: time-triggered flush error \u2014 ${toError(err).message}`
|
|
2941
|
-
);
|
|
2942
|
-
});
|
|
3231
|
+
void flush("time");
|
|
2943
3232
|
}, maxMs);
|
|
2944
3233
|
};
|
|
2945
3234
|
const handle = await startConsumerImpl(
|
|
@@ -2955,40 +3244,13 @@ async function startWindowConsumerImpl(ctx, topic2, handler, options) {
|
|
|
2955
3244
|
);
|
|
2956
3245
|
const originalStop = handle.stop.bind(handle);
|
|
2957
3246
|
handle.stop = async () => {
|
|
2958
|
-
|
|
2959
|
-
clearTimeout(flushTimer);
|
|
2960
|
-
flushTimer = null;
|
|
2961
|
-
}
|
|
2962
|
-
if (buffer.length > 0) {
|
|
2963
|
-
const envelopes = buffer.splice(0);
|
|
2964
|
-
await handler(envelopes, {
|
|
2965
|
-
trigger: "time",
|
|
2966
|
-
windowStart,
|
|
2967
|
-
windowEnd: Date.now()
|
|
2968
|
-
}).catch(async (err) => {
|
|
2969
|
-
const error = toError(err);
|
|
2970
|
-
ctx.logger.warn(
|
|
2971
|
-
`startWindowConsumer: shutdown flush error \u2014 ${error.message}`
|
|
2972
|
-
);
|
|
2973
|
-
for (const envelope of envelopes) {
|
|
2974
|
-
await Promise.resolve(
|
|
2975
|
-
ctx.onMessageLost?.({
|
|
2976
|
-
topic: envelope.topic,
|
|
2977
|
-
error,
|
|
2978
|
-
attempt: 0,
|
|
2979
|
-
headers: envelope.headers
|
|
2980
|
-
})
|
|
2981
|
-
).catch(() => {
|
|
2982
|
-
});
|
|
2983
|
-
}
|
|
2984
|
-
});
|
|
2985
|
-
}
|
|
3247
|
+
await flush("time");
|
|
2986
3248
|
return originalStop();
|
|
2987
3249
|
};
|
|
2988
3250
|
return handle;
|
|
2989
3251
|
}
|
|
2990
3252
|
|
|
2991
|
-
// src/client/kafka.client/consumer/routed.ts
|
|
3253
|
+
// src/client/kafka.client/consumer/features/routed.ts
|
|
2992
3254
|
async function startRoutedConsumerImpl(ctx, topics, routing, options) {
|
|
2993
3255
|
const { header, routes, fallback } = routing;
|
|
2994
3256
|
const handleMessage = async (envelope) => {
|
|
@@ -3003,7 +3265,118 @@ async function startRoutedConsumerImpl(ctx, topics, routing, options) {
|
|
|
3003
3265
|
return startConsumerImpl(ctx, topics, handleMessage, options);
|
|
3004
3266
|
}
|
|
3005
3267
|
|
|
3006
|
-
// src/client/kafka.client/consumer/
|
|
3268
|
+
// src/client/kafka.client/consumer/features/delayed.ts
|
|
3269
|
+
function delayedTopicName(topic2) {
|
|
3270
|
+
return `${topic2}.delayed`;
|
|
3271
|
+
}
|
|
3272
|
+
async function startDelayedRelayImpl(ctx, topics, options) {
|
|
3273
|
+
if (topics.length === 0) {
|
|
3274
|
+
throw new Error("startDelayedRelay: at least one topic is required");
|
|
3275
|
+
}
|
|
3276
|
+
const gid = options?.groupId ?? `${ctx.defaultGroupId}-delayed-relay`;
|
|
3277
|
+
if (ctx.runningConsumers.has(gid)) {
|
|
3278
|
+
throw new Error(
|
|
3279
|
+
`startDelayedRelay("${gid}") called twice \u2014 this group is already consuming. Call stopConsumer("${gid}") first or pass a different groupId.`
|
|
3280
|
+
);
|
|
3281
|
+
}
|
|
3282
|
+
const delayedTopics = topics.map(delayedTopicName);
|
|
3283
|
+
for (const t of delayedTopics) await ensureTopic(ctx, t);
|
|
3284
|
+
const txProducer = await createRetryTxProducer(ctx, `${gid}-tx`);
|
|
3285
|
+
let resolveReady;
|
|
3286
|
+
const readyPromise = new Promise((resolve) => {
|
|
3287
|
+
resolveReady = resolve;
|
|
3288
|
+
});
|
|
3289
|
+
const consumer = getOrCreateConsumer(
|
|
3290
|
+
gid,
|
|
3291
|
+
false,
|
|
3292
|
+
false,
|
|
3293
|
+
ctx.consumerOpsDeps,
|
|
3294
|
+
void 0,
|
|
3295
|
+
resolveReady
|
|
3296
|
+
);
|
|
3297
|
+
await consumer.connect();
|
|
3298
|
+
await subscribeWithRetry(consumer, delayedTopics, ctx.logger);
|
|
3299
|
+
await consumer.run({
|
|
3300
|
+
eachMessage: async ({ topic: stagingTopic, partition, message }) => {
|
|
3301
|
+
const nextOffset = {
|
|
3302
|
+
topic: stagingTopic,
|
|
3303
|
+
partition,
|
|
3304
|
+
offset: (parseInt(message.offset, 10) + 1).toString()
|
|
3305
|
+
};
|
|
3306
|
+
if (!message.value) {
|
|
3307
|
+
await consumer.commitOffsets([nextOffset]);
|
|
3308
|
+
return;
|
|
3309
|
+
}
|
|
3310
|
+
const headers = decodeHeaders(message.headers);
|
|
3311
|
+
const target = headers[HEADER_DELAYED_TARGET] ?? stagingTopic.replace(/\.delayed$/, "");
|
|
3312
|
+
const until = parseInt(
|
|
3313
|
+
headers[HEADER_DELAYED_UNTIL] ?? "0",
|
|
3314
|
+
10
|
|
3315
|
+
);
|
|
3316
|
+
const remaining = until - Date.now();
|
|
3317
|
+
if (remaining > 0) {
|
|
3318
|
+
consumer.pause([{ topic: stagingTopic, partitions: [partition] }]);
|
|
3319
|
+
await sleep(remaining);
|
|
3320
|
+
consumer.resume([{ topic: stagingTopic, partitions: [partition] }]);
|
|
3321
|
+
}
|
|
3322
|
+
const forwardHeaders = Object.fromEntries(
|
|
3323
|
+
Object.entries(headers).filter(
|
|
3324
|
+
([k]) => k !== HEADER_DELAYED_UNTIL && k !== HEADER_DELAYED_TARGET
|
|
3325
|
+
)
|
|
3326
|
+
);
|
|
3327
|
+
const tx = await txProducer.transaction();
|
|
3328
|
+
try {
|
|
3329
|
+
await tx.send({
|
|
3330
|
+
topic: target,
|
|
3331
|
+
messages: [
|
|
3332
|
+
{
|
|
3333
|
+
value: message.value.toString(),
|
|
3334
|
+
key: message.key ? message.key.toString() : null,
|
|
3335
|
+
headers: forwardHeaders
|
|
3336
|
+
}
|
|
3337
|
+
]
|
|
3338
|
+
});
|
|
3339
|
+
await tx.sendOffsets({
|
|
3340
|
+
consumer,
|
|
3341
|
+
topics: [
|
|
3342
|
+
{
|
|
3343
|
+
topic: nextOffset.topic,
|
|
3344
|
+
partitions: [
|
|
3345
|
+
{ partition: nextOffset.partition, offset: nextOffset.offset }
|
|
3346
|
+
]
|
|
3347
|
+
}
|
|
3348
|
+
]
|
|
3349
|
+
});
|
|
3350
|
+
await tx.commit();
|
|
3351
|
+
ctx.logger.debug?.(
|
|
3352
|
+
`Delayed message relayed to "${target}" (deadline ${new Date(until).toISOString()})`
|
|
3353
|
+
);
|
|
3354
|
+
} catch (txErr) {
|
|
3355
|
+
try {
|
|
3356
|
+
await tx.abort();
|
|
3357
|
+
} catch {
|
|
3358
|
+
}
|
|
3359
|
+
ctx.logger.error(
|
|
3360
|
+
`Delayed relay to "${target}" failed \u2014 message will be redelivered:`,
|
|
3361
|
+
toError(txErr).stack
|
|
3362
|
+
);
|
|
3363
|
+
}
|
|
3364
|
+
}
|
|
3365
|
+
});
|
|
3366
|
+
ctx.runningConsumers.set(gid, "eachMessage");
|
|
3367
|
+
ctx.logger.log(
|
|
3368
|
+
`Delayed relay started for: ${delayedTopics.join(", ")} (group: ${gid})`
|
|
3369
|
+
);
|
|
3370
|
+
return {
|
|
3371
|
+
groupId: gid,
|
|
3372
|
+
ready: () => readyPromise,
|
|
3373
|
+
stop: async () => {
|
|
3374
|
+
await stopConsumerImpl(ctx, gid);
|
|
3375
|
+
}
|
|
3376
|
+
};
|
|
3377
|
+
}
|
|
3378
|
+
|
|
3379
|
+
// src/client/kafka.client/consumer/features/snapshot.ts
|
|
3007
3380
|
async function readSnapshotImpl(ctx, topic2, options = {}) {
|
|
3008
3381
|
await ctx.adminOps.ensureConnected();
|
|
3009
3382
|
let offsets;
|
|
@@ -3269,6 +3642,7 @@ var KafkaClient = class {
|
|
|
3269
3642
|
* ```
|
|
3270
3643
|
*/
|
|
3271
3644
|
constructor(clientId, groupId, brokers, options) {
|
|
3645
|
+
validateClientOptions(clientId, groupId, brokers, options);
|
|
3272
3646
|
this.clientId = clientId;
|
|
3273
3647
|
const logger = options?.logger ?? {
|
|
3274
3648
|
log: (msg) => console.log(`[KafkaClient:${clientId}] ${msg}`),
|
|
@@ -3276,7 +3650,8 @@ var KafkaClient = class {
|
|
|
3276
3650
|
error: (msg, ...args) => console.error(`[KafkaClient:${clientId}] ${msg}`, ...args),
|
|
3277
3651
|
debug: (msg, ...args) => console.debug(`[KafkaClient:${clientId}] ${msg}`, ...args)
|
|
3278
3652
|
};
|
|
3279
|
-
const
|
|
3653
|
+
const security = resolveSecurityOptions(options?.security, brokers, logger);
|
|
3654
|
+
const transport = options?.transport ?? new ConfluentTransport(clientId, brokers, security);
|
|
3280
3655
|
const producer = transport.producer();
|
|
3281
3656
|
const runningConsumers = /* @__PURE__ */ new Map();
|
|
3282
3657
|
const consumers = /* @__PURE__ */ new Map();
|
|
@@ -3310,6 +3685,7 @@ var KafkaClient = class {
|
|
|
3310
3685
|
numPartitions: options?.numPartitions ?? 1,
|
|
3311
3686
|
txId: options?.transactionalId ?? `${clientId}-tx`,
|
|
3312
3687
|
clockRecoveryTopics: options?.clockRecovery?.topics ?? [],
|
|
3688
|
+
clockRecoveryTimeoutMs: options?.clockRecovery?.timeoutMs ?? 3e4,
|
|
3313
3689
|
lagThrottleOpts: options?.lagThrottle,
|
|
3314
3690
|
instrumentation: options?.instrumentation ?? [],
|
|
3315
3691
|
onMessageLost: options?.onMessageLost,
|
|
@@ -3319,6 +3695,7 @@ var KafkaClient = class {
|
|
|
3319
3695
|
producer,
|
|
3320
3696
|
txProducer: void 0,
|
|
3321
3697
|
txProducerInitPromise: void 0,
|
|
3698
|
+
_txChain: Promise.resolve(),
|
|
3322
3699
|
retryTxProducers: /* @__PURE__ */ new Map(),
|
|
3323
3700
|
consumers,
|
|
3324
3701
|
runningConsumers,
|
|
@@ -3442,6 +3819,31 @@ var KafkaClient = class {
|
|
|
3442
3819
|
startRoutedConsumer(topics, routing, options) {
|
|
3443
3820
|
return startRoutedConsumerImpl(this.ctx, topics, routing, options);
|
|
3444
3821
|
}
|
|
3822
|
+
// ── Consumer: delayed delivery relay ──────────────────────────────
|
|
3823
|
+
/**
|
|
3824
|
+
* Start a relay that delivers messages produced with
|
|
3825
|
+
* `SendOptions.deliverAfterMs` from `<topic>.delayed` to their target topic
|
|
3826
|
+
* once their deadline passes.
|
|
3827
|
+
*
|
|
3828
|
+
* Forwarding is transactional (produce + source-offset commit are atomic),
|
|
3829
|
+
* so no duplicates are relayed even if the relay crashes mid-forward.
|
|
3830
|
+
* Delivery time is a lower bound — the relay must be running for delayed
|
|
3831
|
+
* messages to be delivered at all.
|
|
3832
|
+
*
|
|
3833
|
+
* @param topics Target topic name(s) whose `<topic>.delayed` staging topics to relay.
|
|
3834
|
+
* @param options Optional `groupId` override (default: `<defaultGroupId>-delayed-relay`).
|
|
3835
|
+
*
|
|
3836
|
+
* @example
|
|
3837
|
+
* ```ts
|
|
3838
|
+
* await kafka.startDelayedRelay(['orders.reminder']);
|
|
3839
|
+
* await kafka.sendMessage('orders.reminder', payload, { deliverAfterMs: 60_000 });
|
|
3840
|
+
* // → delivered to orders.reminder ~60 s later
|
|
3841
|
+
* ```
|
|
3842
|
+
*/
|
|
3843
|
+
async startDelayedRelay(topics, options) {
|
|
3844
|
+
const list = Array.isArray(topics) ? topics : [topics];
|
|
3845
|
+
return startDelayedRelayImpl(this.ctx, list, options);
|
|
3846
|
+
}
|
|
3445
3847
|
// ── Consumer: transactional EOS ───────────────────────────────────
|
|
3446
3848
|
/** @inheritDoc */
|
|
3447
3849
|
async startTransactionalConsumer(topics, handler, options = {}) {
|
|
@@ -3587,34 +3989,884 @@ var KafkaClient = class {
|
|
|
3587
3989
|
function topic(name) {
|
|
3588
3990
|
return {
|
|
3589
3991
|
/** Provide an explicit message type without a runtime schema. */
|
|
3590
|
-
type: () => ({
|
|
3992
|
+
type: () => keyable({
|
|
3591
3993
|
__topic: name,
|
|
3592
3994
|
__type: void 0
|
|
3593
3995
|
}),
|
|
3594
|
-
schema: (schema) => ({
|
|
3996
|
+
schema: (schema) => keyable({
|
|
3595
3997
|
__topic: name,
|
|
3596
3998
|
__type: void 0,
|
|
3597
3999
|
__schema: schema
|
|
3598
4000
|
})
|
|
3599
4001
|
};
|
|
3600
4002
|
}
|
|
4003
|
+
function keyable(desc) {
|
|
4004
|
+
return {
|
|
4005
|
+
...desc,
|
|
4006
|
+
key: (extractor) => ({
|
|
4007
|
+
...desc,
|
|
4008
|
+
__key: extractor
|
|
4009
|
+
})
|
|
4010
|
+
};
|
|
4011
|
+
}
|
|
4012
|
+
|
|
4013
|
+
// src/client/message/versioned-schema.ts
|
|
4014
|
+
function versionedSchema(versions, options) {
|
|
4015
|
+
const registered = Object.keys(versions).map(Number).filter((v) => Number.isInteger(v) && v > 0).sort((a, b) => a - b);
|
|
4016
|
+
if (registered.length === 0) {
|
|
4017
|
+
throw new Error(
|
|
4018
|
+
"versionedSchema: at least one schema version must be registered (keys must be positive integers)"
|
|
4019
|
+
);
|
|
4020
|
+
}
|
|
4021
|
+
const latestVersion = registered[registered.length - 1];
|
|
4022
|
+
return {
|
|
4023
|
+
async parse(data, ctx) {
|
|
4024
|
+
const version = ctx?.version ?? latestVersion;
|
|
4025
|
+
const schema = versions[version];
|
|
4026
|
+
if (!schema) {
|
|
4027
|
+
throw new Error(
|
|
4028
|
+
`versionedSchema: no schema registered for version ${version}${ctx?.topic ? ` (topic "${ctx.topic}")` : ""} \u2014 registered versions: ${registered.join(", ")}`
|
|
4029
|
+
);
|
|
4030
|
+
}
|
|
4031
|
+
const parsed = await schema.parse(data, ctx);
|
|
4032
|
+
if (version < latestVersion && options?.migrate) {
|
|
4033
|
+
return options.migrate(parsed, version, latestVersion);
|
|
4034
|
+
}
|
|
4035
|
+
return parsed;
|
|
4036
|
+
}
|
|
4037
|
+
};
|
|
4038
|
+
}
|
|
4039
|
+
|
|
4040
|
+
// src/client/message/schema-registry.ts
|
|
4041
|
+
var SchemaRegistryClient = class {
|
|
4042
|
+
constructor(options) {
|
|
4043
|
+
this.options = options;
|
|
4044
|
+
if (!options.baseUrl) {
|
|
4045
|
+
throw new Error("SchemaRegistryClient: baseUrl is required");
|
|
4046
|
+
}
|
|
4047
|
+
this.fetchFn = options.fetchFn ?? fetch;
|
|
4048
|
+
this.cacheTtlMs = options.cacheTtlMs ?? 3e5;
|
|
4049
|
+
}
|
|
4050
|
+
options;
|
|
4051
|
+
fetchFn;
|
|
4052
|
+
cacheTtlMs;
|
|
4053
|
+
latestCache = /* @__PURE__ */ new Map();
|
|
4054
|
+
headers() {
|
|
4055
|
+
const h = {
|
|
4056
|
+
"Content-Type": "application/vnd.schemaregistry.v1+json"
|
|
4057
|
+
};
|
|
4058
|
+
if (this.options.auth) {
|
|
4059
|
+
const { username, password } = this.options.auth;
|
|
4060
|
+
h["Authorization"] = "Basic " + Buffer.from(`${username}:${password}`).toString("base64");
|
|
4061
|
+
}
|
|
4062
|
+
return h;
|
|
4063
|
+
}
|
|
4064
|
+
async request(method, path, body) {
|
|
4065
|
+
const url = `${this.options.baseUrl.replace(/\/$/, "")}${path}`;
|
|
4066
|
+
const res = await this.fetchFn(url, {
|
|
4067
|
+
method,
|
|
4068
|
+
headers: this.headers(),
|
|
4069
|
+
...body !== void 0 && { body: JSON.stringify(body) }
|
|
4070
|
+
});
|
|
4071
|
+
if (!res.ok) {
|
|
4072
|
+
const text = await res.text().catch(() => "");
|
|
4073
|
+
throw new Error(
|
|
4074
|
+
`SchemaRegistry ${method} ${path} failed: ${res.status} ${res.statusText}${text ? ` \u2014 ${text}` : ""}`
|
|
4075
|
+
);
|
|
4076
|
+
}
|
|
4077
|
+
return await res.json();
|
|
4078
|
+
}
|
|
4079
|
+
/** Fetch the latest schema registered under `subject`. Cached for `cacheTtlMs`. */
|
|
4080
|
+
async getLatestSchema(subject) {
|
|
4081
|
+
const cached = this.latestCache.get(subject);
|
|
4082
|
+
if (cached && cached.expiresAt > Date.now()) return cached.value;
|
|
4083
|
+
const raw = await this.request("GET", `/subjects/${encodeURIComponent(subject)}/versions/latest`);
|
|
4084
|
+
const value = {
|
|
4085
|
+
id: raw.id,
|
|
4086
|
+
version: raw.version,
|
|
4087
|
+
schema: raw.schema
|
|
4088
|
+
};
|
|
4089
|
+
this.latestCache.set(subject, {
|
|
4090
|
+
value,
|
|
4091
|
+
expiresAt: Date.now() + this.cacheTtlMs
|
|
4092
|
+
});
|
|
4093
|
+
return value;
|
|
4094
|
+
}
|
|
4095
|
+
/** Fetch a specific schema version of a subject. */
|
|
4096
|
+
async getSchemaVersion(subject, version) {
|
|
4097
|
+
const raw = await this.request(
|
|
4098
|
+
"GET",
|
|
4099
|
+
`/subjects/${encodeURIComponent(subject)}/versions/${version}`
|
|
4100
|
+
);
|
|
4101
|
+
return { id: raw.id, version: raw.version, schema: raw.schema };
|
|
4102
|
+
}
|
|
4103
|
+
/**
|
|
4104
|
+
* Register a schema under `subject` (idempotent — re-registering the same
|
|
4105
|
+
* schema returns the existing id). Returns the registry-assigned schema id.
|
|
4106
|
+
*/
|
|
4107
|
+
async registerSchema(subject, schema, schemaType = "JSON") {
|
|
4108
|
+
this.latestCache.delete(subject);
|
|
4109
|
+
return this.request(
|
|
4110
|
+
"POST",
|
|
4111
|
+
`/subjects/${encodeURIComponent(subject)}/versions`,
|
|
4112
|
+
{ schema, schemaType }
|
|
4113
|
+
);
|
|
4114
|
+
}
|
|
4115
|
+
/**
|
|
4116
|
+
* Test `schema` against the subject's compatibility policy without registering.
|
|
4117
|
+
* Returns `true` when the registry reports the schema as compatible.
|
|
4118
|
+
*/
|
|
4119
|
+
async checkCompatibility(subject, schema, schemaType = "JSON") {
|
|
4120
|
+
const res = await this.request(
|
|
4121
|
+
"POST",
|
|
4122
|
+
`/compatibility/subjects/${encodeURIComponent(subject)}/versions/latest`,
|
|
4123
|
+
{ schema, schemaType }
|
|
4124
|
+
);
|
|
4125
|
+
return res.is_compatible;
|
|
4126
|
+
}
|
|
4127
|
+
};
|
|
4128
|
+
function registrySchema(client, subject, options) {
|
|
4129
|
+
const enforceVersion = options?.enforceVersion ?? true;
|
|
4130
|
+
return {
|
|
4131
|
+
async parse(data, ctx) {
|
|
4132
|
+
const latest = await client.getLatestSchema(subject);
|
|
4133
|
+
if (enforceVersion && ctx?.version !== void 0 && ctx.version > latest.version) {
|
|
4134
|
+
throw new Error(
|
|
4135
|
+
`registrySchema: message version ${ctx.version} for subject "${subject}" is newer than the latest registered version ${latest.version} \u2014 register the schema before producing with it`
|
|
4136
|
+
);
|
|
4137
|
+
}
|
|
4138
|
+
if (options?.validator) {
|
|
4139
|
+
return options.validator.parse(data, ctx);
|
|
4140
|
+
}
|
|
4141
|
+
return data;
|
|
4142
|
+
}
|
|
4143
|
+
};
|
|
4144
|
+
}
|
|
4145
|
+
|
|
4146
|
+
// src/client/outbox/outbox.store.ts
|
|
4147
|
+
var InMemoryOutboxStore = class {
|
|
4148
|
+
/** Insertion-ordered rows. `published` flips to true after `markPublished`. */
|
|
4149
|
+
rows = [];
|
|
4150
|
+
/**
|
|
4151
|
+
* Append a message to the outbox. In a real store this INSERT would run inside
|
|
4152
|
+
* the same DB transaction as the corresponding business write.
|
|
4153
|
+
*/
|
|
4154
|
+
add(message) {
|
|
4155
|
+
this.rows.push({ message, published: false });
|
|
4156
|
+
}
|
|
4157
|
+
async fetchUnpublished(limit) {
|
|
4158
|
+
const out = [];
|
|
4159
|
+
for (const row of this.rows) {
|
|
4160
|
+
if (row.published) continue;
|
|
4161
|
+
out.push(row.message);
|
|
4162
|
+
if (out.length >= limit) break;
|
|
4163
|
+
}
|
|
4164
|
+
return out;
|
|
4165
|
+
}
|
|
4166
|
+
async markPublished(ids) {
|
|
4167
|
+
const idSet = new Set(ids);
|
|
4168
|
+
for (const row of this.rows) {
|
|
4169
|
+
if (idSet.has(row.message.id)) row.published = true;
|
|
4170
|
+
}
|
|
4171
|
+
}
|
|
4172
|
+
/** Test helper: count of rows not yet marked published. */
|
|
4173
|
+
get pendingCount() {
|
|
4174
|
+
return this.rows.filter((r) => !r.published).length;
|
|
4175
|
+
}
|
|
4176
|
+
/** Test helper: count of rows marked published. */
|
|
4177
|
+
get publishedCount() {
|
|
4178
|
+
return this.rows.filter((r) => r.published).length;
|
|
4179
|
+
}
|
|
4180
|
+
};
|
|
4181
|
+
|
|
4182
|
+
// src/client/outbox/outbox.relay.ts
|
|
4183
|
+
function toError2(e) {
|
|
4184
|
+
return e instanceof Error ? e : new Error(String(e));
|
|
4185
|
+
}
|
|
4186
|
+
function startOutboxRelay(kafka, store, options = {}) {
|
|
4187
|
+
const pollIntervalMs = options.pollIntervalMs ?? 1e3;
|
|
4188
|
+
const batchSize = options.batchSize ?? 100;
|
|
4189
|
+
const onError = options.onError ?? ((error, batch) => {
|
|
4190
|
+
console.error(
|
|
4191
|
+
`[outbox] batch of ${batch.length} message(s) failed \u2014 will retry:`,
|
|
4192
|
+
error
|
|
4193
|
+
);
|
|
4194
|
+
});
|
|
4195
|
+
const onPublished = options.onPublished;
|
|
4196
|
+
let stopped = false;
|
|
4197
|
+
let running = false;
|
|
4198
|
+
let inFlight = Promise.resolve();
|
|
4199
|
+
const iterate = async () => {
|
|
4200
|
+
let batch = [];
|
|
4201
|
+
try {
|
|
4202
|
+
batch = await store.fetchUnpublished(batchSize);
|
|
4203
|
+
if (batch.length === 0) return;
|
|
4204
|
+
await kafka.transaction(async (tx) => {
|
|
4205
|
+
for (const msg of batch) {
|
|
4206
|
+
await tx.send(msg.topic, msg.payload, {
|
|
4207
|
+
key: msg.key,
|
|
4208
|
+
headers: msg.headers,
|
|
4209
|
+
correlationId: msg.correlationId,
|
|
4210
|
+
eventId: msg.eventId
|
|
4211
|
+
});
|
|
4212
|
+
}
|
|
4213
|
+
});
|
|
4214
|
+
await store.markPublished(batch.map((m) => m.id));
|
|
4215
|
+
onPublished?.(batch.length);
|
|
4216
|
+
} catch (err) {
|
|
4217
|
+
onError(toError2(err), batch);
|
|
4218
|
+
}
|
|
4219
|
+
};
|
|
4220
|
+
const tick = () => {
|
|
4221
|
+
if (stopped || running) return;
|
|
4222
|
+
running = true;
|
|
4223
|
+
inFlight = iterate().finally(() => {
|
|
4224
|
+
running = false;
|
|
4225
|
+
});
|
|
4226
|
+
};
|
|
4227
|
+
const timer = setInterval(tick, pollIntervalMs);
|
|
4228
|
+
timer.unref?.();
|
|
4229
|
+
return {
|
|
4230
|
+
stop: async () => {
|
|
4231
|
+
stopped = true;
|
|
4232
|
+
clearInterval(timer);
|
|
4233
|
+
await inFlight;
|
|
4234
|
+
}
|
|
4235
|
+
};
|
|
4236
|
+
}
|
|
4237
|
+
|
|
4238
|
+
// src/client/security/providers.ts
|
|
4239
|
+
var defaultImport = (specifier) => import(specifier);
|
|
4240
|
+
function awsMskIamProvider(options) {
|
|
4241
|
+
const importFn = options.importFn ?? defaultImport;
|
|
4242
|
+
return async () => {
|
|
4243
|
+
let signer;
|
|
4244
|
+
try {
|
|
4245
|
+
signer = await importFn("aws-msk-iam-sasl-signer-js");
|
|
4246
|
+
} catch {
|
|
4247
|
+
throw new Error(
|
|
4248
|
+
"awsMskIamProvider: package 'aws-msk-iam-sasl-signer-js' is not installed. Run `npm install aws-msk-iam-sasl-signer-js` to enable MSK IAM authentication."
|
|
4249
|
+
);
|
|
4250
|
+
}
|
|
4251
|
+
const { token, expiryTime } = await signer.generateAuthToken({
|
|
4252
|
+
region: options.region
|
|
4253
|
+
});
|
|
4254
|
+
return {
|
|
4255
|
+
value: token,
|
|
4256
|
+
principal: "msk-iam",
|
|
4257
|
+
// expiryTime is epoch ms per the signer's contract
|
|
4258
|
+
lifetimeMs: expiryTime
|
|
4259
|
+
};
|
|
4260
|
+
};
|
|
4261
|
+
}
|
|
4262
|
+
function gcpAccessTokenProvider(options = {}) {
|
|
4263
|
+
const importFn = options.importFn ?? defaultImport;
|
|
4264
|
+
const ttlMs = options.tokenTtlMs ?? 50 * 6e4;
|
|
4265
|
+
return async () => {
|
|
4266
|
+
let lib;
|
|
4267
|
+
try {
|
|
4268
|
+
lib = await importFn("google-auth-library");
|
|
4269
|
+
} catch {
|
|
4270
|
+
throw new Error(
|
|
4271
|
+
"gcpAccessTokenProvider: package 'google-auth-library' is not installed. Run `npm install google-auth-library` to enable GCP authentication."
|
|
4272
|
+
);
|
|
4273
|
+
}
|
|
4274
|
+
const auth = new lib.GoogleAuth({
|
|
4275
|
+
scopes: options.scopes ?? ["https://www.googleapis.com/auth/cloud-platform"]
|
|
4276
|
+
});
|
|
4277
|
+
const token = await auth.getAccessToken();
|
|
4278
|
+
if (!token) {
|
|
4279
|
+
throw new Error(
|
|
4280
|
+
"gcpAccessTokenProvider: google-auth-library returned no access token \u2014 check Application Default Credentials."
|
|
4281
|
+
);
|
|
4282
|
+
}
|
|
4283
|
+
return {
|
|
4284
|
+
value: token,
|
|
4285
|
+
principal: options.principal ?? "gcp",
|
|
4286
|
+
lifetimeMs: Date.now() + ttlMs
|
|
4287
|
+
};
|
|
4288
|
+
};
|
|
4289
|
+
}
|
|
4290
|
+
|
|
4291
|
+
// src/client/security/acl.ts
|
|
4292
|
+
function addResource(out, r) {
|
|
4293
|
+
const key = `${r.resourceType}:${r.patternType}:${r.name}`;
|
|
4294
|
+
const existing = out.get(key);
|
|
4295
|
+
if (existing) {
|
|
4296
|
+
for (const op of r.operations)
|
|
4297
|
+
if (!existing.operations.includes(op)) existing.operations.push(op);
|
|
4298
|
+
if (!existing.reason.includes(r.reason))
|
|
4299
|
+
existing.reason += `; ${r.reason}`;
|
|
4300
|
+
} else {
|
|
4301
|
+
out.set(key, { ...r, operations: [...r.operations] });
|
|
4302
|
+
}
|
|
4303
|
+
}
|
|
4304
|
+
function describeRequiredAcls(input) {
|
|
4305
|
+
const out = /* @__PURE__ */ new Map();
|
|
4306
|
+
const f = input.features ?? {};
|
|
4307
|
+
const produce = input.produceTopics ?? [];
|
|
4308
|
+
const consume = input.consumeTopics ?? [];
|
|
4309
|
+
const groups = input.groupIds ?? [];
|
|
4310
|
+
for (const t of produce) {
|
|
4311
|
+
addResource(out, {
|
|
4312
|
+
resourceType: "topic",
|
|
4313
|
+
patternType: "literal",
|
|
4314
|
+
name: t,
|
|
4315
|
+
operations: ["WRITE", "DESCRIBE"],
|
|
4316
|
+
reason: "sendMessage/sendBatch"
|
|
4317
|
+
});
|
|
4318
|
+
}
|
|
4319
|
+
for (const t of consume) {
|
|
4320
|
+
addResource(out, {
|
|
4321
|
+
resourceType: "topic",
|
|
4322
|
+
patternType: "literal",
|
|
4323
|
+
name: t,
|
|
4324
|
+
operations: ["READ", "DESCRIBE"],
|
|
4325
|
+
reason: "startConsumer"
|
|
4326
|
+
});
|
|
4327
|
+
}
|
|
4328
|
+
for (const g of groups) {
|
|
4329
|
+
addResource(out, {
|
|
4330
|
+
resourceType: "group",
|
|
4331
|
+
patternType: "literal",
|
|
4332
|
+
name: g,
|
|
4333
|
+
operations: ["READ", "DESCRIBE"],
|
|
4334
|
+
reason: "consumer group membership + offset commits"
|
|
4335
|
+
});
|
|
4336
|
+
}
|
|
4337
|
+
if (f.dlq) {
|
|
4338
|
+
for (const t of consume) {
|
|
4339
|
+
addResource(out, {
|
|
4340
|
+
resourceType: "topic",
|
|
4341
|
+
patternType: "literal",
|
|
4342
|
+
name: `${t}.dlq`,
|
|
4343
|
+
operations: ["WRITE", "DESCRIBE"],
|
|
4344
|
+
reason: "dlq: true \u2014 failed messages routed to DLQ"
|
|
4345
|
+
});
|
|
4346
|
+
}
|
|
4347
|
+
}
|
|
4348
|
+
if (f.retryTopics) {
|
|
4349
|
+
for (const t of consume) {
|
|
4350
|
+
for (let level = 1; level <= f.retryTopics.maxRetries; level++) {
|
|
4351
|
+
addResource(out, {
|
|
4352
|
+
resourceType: "topic",
|
|
4353
|
+
patternType: "literal",
|
|
4354
|
+
name: `${t}.retry.${level}`,
|
|
4355
|
+
operations: ["READ", "WRITE", "DESCRIBE"],
|
|
4356
|
+
reason: "retryTopics \u2014 retry chain produce + companion consume"
|
|
4357
|
+
});
|
|
4358
|
+
}
|
|
4359
|
+
}
|
|
4360
|
+
for (const g of groups) {
|
|
4361
|
+
addResource(out, {
|
|
4362
|
+
resourceType: "group",
|
|
4363
|
+
patternType: "prefixed",
|
|
4364
|
+
name: `${g}-retry.`,
|
|
4365
|
+
operations: ["READ", "DESCRIBE"],
|
|
4366
|
+
reason: "retryTopics \u2014 companion retry-level consumer groups"
|
|
4367
|
+
});
|
|
4368
|
+
addResource(out, {
|
|
4369
|
+
resourceType: "transactional-id",
|
|
4370
|
+
patternType: "prefixed",
|
|
4371
|
+
name: `${g}-`,
|
|
4372
|
+
operations: ["WRITE", "DESCRIBE"],
|
|
4373
|
+
reason: "retryTopics \u2014 EOS routing transactions per retry level"
|
|
4374
|
+
});
|
|
4375
|
+
}
|
|
4376
|
+
}
|
|
4377
|
+
if (f.delayedDelivery) {
|
|
4378
|
+
for (const t of [.../* @__PURE__ */ new Set([...produce, ...consume])]) {
|
|
4379
|
+
addResource(out, {
|
|
4380
|
+
resourceType: "topic",
|
|
4381
|
+
patternType: "literal",
|
|
4382
|
+
name: `${t}.delayed`,
|
|
4383
|
+
operations: ["READ", "WRITE", "DESCRIBE"],
|
|
4384
|
+
reason: "deliverAfterMs staging + startDelayedRelay consume"
|
|
4385
|
+
});
|
|
4386
|
+
}
|
|
4387
|
+
for (const g of groups) {
|
|
4388
|
+
addResource(out, {
|
|
4389
|
+
resourceType: "group",
|
|
4390
|
+
patternType: "literal",
|
|
4391
|
+
name: `${g}-delayed-relay`,
|
|
4392
|
+
operations: ["READ", "DESCRIBE"],
|
|
4393
|
+
reason: "startDelayedRelay consumer group"
|
|
4394
|
+
});
|
|
4395
|
+
addResource(out, {
|
|
4396
|
+
resourceType: "transactional-id",
|
|
4397
|
+
patternType: "literal",
|
|
4398
|
+
name: `${g}-delayed-relay-tx`,
|
|
4399
|
+
operations: ["WRITE", "DESCRIBE"],
|
|
4400
|
+
reason: "startDelayedRelay transactional forwarding"
|
|
4401
|
+
});
|
|
4402
|
+
}
|
|
4403
|
+
}
|
|
4404
|
+
if (f.duplicatesTopic) {
|
|
4405
|
+
if (typeof f.duplicatesTopic === "string") {
|
|
4406
|
+
addResource(out, {
|
|
4407
|
+
resourceType: "topic",
|
|
4408
|
+
patternType: "literal",
|
|
4409
|
+
name: f.duplicatesTopic,
|
|
4410
|
+
operations: ["WRITE", "DESCRIBE"],
|
|
4411
|
+
reason: "deduplication.strategy 'topic' \u2014 custom duplicates topic"
|
|
4412
|
+
});
|
|
4413
|
+
} else {
|
|
4414
|
+
for (const t of consume) {
|
|
4415
|
+
addResource(out, {
|
|
4416
|
+
resourceType: "topic",
|
|
4417
|
+
patternType: "literal",
|
|
4418
|
+
name: `${t}.duplicates`,
|
|
4419
|
+
operations: ["WRITE", "DESCRIBE"],
|
|
4420
|
+
reason: "deduplication.strategy 'topic'"
|
|
4421
|
+
});
|
|
4422
|
+
}
|
|
4423
|
+
}
|
|
4424
|
+
}
|
|
4425
|
+
if (f.dlqReplay) {
|
|
4426
|
+
for (const t of consume) {
|
|
4427
|
+
addResource(out, {
|
|
4428
|
+
resourceType: "group",
|
|
4429
|
+
patternType: "prefixed",
|
|
4430
|
+
name: `${t}.dlq-replay`,
|
|
4431
|
+
operations: ["READ", "DESCRIBE", "DELETE"],
|
|
4432
|
+
reason: "replayDlq \u2014 ephemeral/stable replay groups (deleted after use)"
|
|
4433
|
+
});
|
|
4434
|
+
addResource(out, {
|
|
4435
|
+
resourceType: "topic",
|
|
4436
|
+
patternType: "literal",
|
|
4437
|
+
name: `${t}.dlq`,
|
|
4438
|
+
operations: ["READ", "DESCRIBE"],
|
|
4439
|
+
reason: "replayDlq \u2014 reads the DLQ"
|
|
4440
|
+
});
|
|
4441
|
+
}
|
|
4442
|
+
}
|
|
4443
|
+
if (f.snapshots) {
|
|
4444
|
+
addResource(out, {
|
|
4445
|
+
resourceType: "group",
|
|
4446
|
+
patternType: "prefixed",
|
|
4447
|
+
name: `${input.clientId}-snapshot-`,
|
|
4448
|
+
operations: ["READ", "DESCRIBE", "DELETE"],
|
|
4449
|
+
reason: "readSnapshot \u2014 timestamped ephemeral groups (deleted after use)"
|
|
4450
|
+
});
|
|
4451
|
+
}
|
|
4452
|
+
if (f.clockRecovery) {
|
|
4453
|
+
addResource(out, {
|
|
4454
|
+
resourceType: "group",
|
|
4455
|
+
patternType: "prefixed",
|
|
4456
|
+
name: `${input.clientId}-clock-recovery-`,
|
|
4457
|
+
operations: ["READ", "DESCRIBE", "DELETE"],
|
|
4458
|
+
reason: "clockRecovery \u2014 timestamped ephemeral groups (deleted after use)"
|
|
4459
|
+
});
|
|
4460
|
+
}
|
|
4461
|
+
if (f.transactions) {
|
|
4462
|
+
addResource(out, {
|
|
4463
|
+
resourceType: "transactional-id",
|
|
4464
|
+
patternType: "literal",
|
|
4465
|
+
name: `${input.clientId}-tx`,
|
|
4466
|
+
operations: ["WRITE", "DESCRIBE"],
|
|
4467
|
+
reason: "transaction() \u2014 default transactionalId (override-aware: adjust if you set one)"
|
|
4468
|
+
});
|
|
4469
|
+
}
|
|
4470
|
+
if (f.autoCreateTopics) {
|
|
4471
|
+
addResource(out, {
|
|
4472
|
+
resourceType: "cluster",
|
|
4473
|
+
patternType: "literal",
|
|
4474
|
+
name: "kafka-cluster",
|
|
4475
|
+
operations: ["CREATE"],
|
|
4476
|
+
reason: "autoCreateTopics: true \u2014 not recommended in production"
|
|
4477
|
+
});
|
|
4478
|
+
}
|
|
4479
|
+
return [...out.values()];
|
|
4480
|
+
}
|
|
4481
|
+
function toKafkaAclCommands(resources, principal, bootstrapServer = "<bootstrap-server>") {
|
|
4482
|
+
return resources.map((r) => {
|
|
4483
|
+
const ops = r.operations.map((o) => `--operation ${o}`).join(" ");
|
|
4484
|
+
const resourceFlag = r.resourceType === "topic" ? `--topic '${r.name}'` : r.resourceType === "group" ? `--group '${r.name}'` : r.resourceType === "transactional-id" ? `--transactional-id '${r.name}'` : "--cluster";
|
|
4485
|
+
const pattern = r.patternType === "prefixed" ? " --resource-pattern-type prefixed" : "";
|
|
4486
|
+
return `kafka-acls.sh --bootstrap-server ${bootstrapServer} --add --allow-principal '${principal}' ${ops} ${resourceFlag}${pattern} # ${r.reason}`;
|
|
4487
|
+
});
|
|
4488
|
+
}
|
|
4489
|
+
var MSK_TOPIC_ACTIONS = {
|
|
4490
|
+
READ: ["kafka-cluster:ReadData", "kafka-cluster:DescribeTopic"],
|
|
4491
|
+
WRITE: ["kafka-cluster:WriteData", "kafka-cluster:DescribeTopic"],
|
|
4492
|
+
DESCRIBE: ["kafka-cluster:DescribeTopic"],
|
|
4493
|
+
CREATE: ["kafka-cluster:CreateTopic"],
|
|
4494
|
+
DELETE: ["kafka-cluster:DeleteTopic"]
|
|
4495
|
+
};
|
|
4496
|
+
var MSK_GROUP_ACTIONS = {
|
|
4497
|
+
READ: ["kafka-cluster:AlterGroup", "kafka-cluster:DescribeGroup"],
|
|
4498
|
+
DESCRIBE: ["kafka-cluster:DescribeGroup"],
|
|
4499
|
+
DELETE: ["kafka-cluster:DeleteGroup"]
|
|
4500
|
+
};
|
|
4501
|
+
var MSK_TX_ACTIONS = {
|
|
4502
|
+
WRITE: [
|
|
4503
|
+
"kafka-cluster:AlterTransactionalId",
|
|
4504
|
+
"kafka-cluster:DescribeTransactionalId"
|
|
4505
|
+
],
|
|
4506
|
+
DESCRIBE: ["kafka-cluster:DescribeTransactionalId"]
|
|
4507
|
+
};
|
|
4508
|
+
function toMskIamPolicy(resources, cluster) {
|
|
4509
|
+
const { region, accountId, clusterName, clusterUuid } = cluster;
|
|
4510
|
+
const arn = (type, name) => `arn:aws:kafka:${region}:${accountId}:${type}/${clusterName}/${clusterUuid}/${name}`;
|
|
4511
|
+
const statements = [
|
|
4512
|
+
{
|
|
4513
|
+
Sid: "Connect",
|
|
4514
|
+
Effect: "Allow",
|
|
4515
|
+
Action: ["kafka-cluster:Connect"],
|
|
4516
|
+
Resource: [
|
|
4517
|
+
`arn:aws:kafka:${region}:${accountId}:cluster/${clusterName}/${clusterUuid}`
|
|
4518
|
+
]
|
|
4519
|
+
}
|
|
4520
|
+
];
|
|
4521
|
+
let sid = 0;
|
|
4522
|
+
for (const r of resources) {
|
|
4523
|
+
const suffix = r.patternType === "prefixed" ? `${r.name}*` : r.name;
|
|
4524
|
+
let actions = [];
|
|
4525
|
+
let resource;
|
|
4526
|
+
if (r.resourceType === "topic") {
|
|
4527
|
+
actions = [...new Set(r.operations.flatMap((o) => MSK_TOPIC_ACTIONS[o] ?? []))];
|
|
4528
|
+
resource = arn("topic", suffix);
|
|
4529
|
+
} else if (r.resourceType === "group") {
|
|
4530
|
+
actions = [...new Set(r.operations.flatMap((o) => MSK_GROUP_ACTIONS[o] ?? []))];
|
|
4531
|
+
resource = arn("group", suffix);
|
|
4532
|
+
} else if (r.resourceType === "transactional-id") {
|
|
4533
|
+
actions = [...new Set(r.operations.flatMap((o) => MSK_TX_ACTIONS[o] ?? []))];
|
|
4534
|
+
resource = arn("transactional-id", suffix);
|
|
4535
|
+
} else {
|
|
4536
|
+
actions = ["kafka-cluster:CreateTopic"];
|
|
4537
|
+
resource = `arn:aws:kafka:${region}:${accountId}:topic/${clusterName}/${clusterUuid}/*`;
|
|
4538
|
+
}
|
|
4539
|
+
if (actions.length === 0 || !resource) continue;
|
|
4540
|
+
statements.push({
|
|
4541
|
+
Sid: `Acl${sid++}`,
|
|
4542
|
+
Effect: "Allow",
|
|
4543
|
+
Action: actions,
|
|
4544
|
+
Resource: [resource]
|
|
4545
|
+
});
|
|
4546
|
+
}
|
|
4547
|
+
return { Version: "2012-10-17", Statement: statements };
|
|
4548
|
+
}
|
|
4549
|
+
|
|
4550
|
+
// src/client/config/from-env.ts
|
|
4551
|
+
var TRUE_VALUES = /* @__PURE__ */ new Set(["true", "1", "yes"]);
|
|
4552
|
+
var FALSE_VALUES = /* @__PURE__ */ new Set(["false", "0", "no"]);
|
|
4553
|
+
function parseBool(name, raw) {
|
|
4554
|
+
const normalized = raw.trim().toLowerCase();
|
|
4555
|
+
if (TRUE_VALUES.has(normalized)) return true;
|
|
4556
|
+
if (FALSE_VALUES.has(normalized)) return false;
|
|
4557
|
+
throw new Error(
|
|
4558
|
+
`Invalid boolean for ${name}: "${raw}". Use one of true/false, 1/0, yes/no (case-insensitive).`
|
|
4559
|
+
);
|
|
4560
|
+
}
|
|
4561
|
+
function parseNum(name, raw) {
|
|
4562
|
+
const value = Number(raw.trim());
|
|
4563
|
+
if (Number.isNaN(value)) {
|
|
4564
|
+
throw new Error(`Invalid number for ${name}: "${raw}".`);
|
|
4565
|
+
}
|
|
4566
|
+
return value;
|
|
4567
|
+
}
|
|
4568
|
+
function parseList(raw) {
|
|
4569
|
+
return raw.split(",").map((part) => part.trim()).filter((part) => part.length > 0);
|
|
4570
|
+
}
|
|
4571
|
+
function parseEnum(name, raw, allowed) {
|
|
4572
|
+
const value = raw.trim();
|
|
4573
|
+
if (!allowed.includes(value)) {
|
|
4574
|
+
throw new Error(
|
|
4575
|
+
`Invalid value for ${name}: "${raw}". Allowed: ${allowed.join(", ")}.`
|
|
4576
|
+
);
|
|
4577
|
+
}
|
|
4578
|
+
return value;
|
|
4579
|
+
}
|
|
4580
|
+
function readVar(env, key, apply) {
|
|
4581
|
+
const raw = env[key];
|
|
4582
|
+
if (raw === void 0 || raw.trim() === "") return;
|
|
4583
|
+
apply(raw);
|
|
4584
|
+
}
|
|
4585
|
+
function kafkaClientConfigFromEnv(env = process.env, prefix = "KAFKA_") {
|
|
4586
|
+
const options = {};
|
|
4587
|
+
const result = { options };
|
|
4588
|
+
readVar(env, `${prefix}CLIENT_ID`, (raw) => {
|
|
4589
|
+
result.clientId = raw.trim();
|
|
4590
|
+
});
|
|
4591
|
+
readVar(env, `${prefix}GROUP_ID`, (raw) => {
|
|
4592
|
+
result.groupId = raw.trim();
|
|
4593
|
+
});
|
|
4594
|
+
readVar(env, `${prefix}BROKERS`, (raw) => {
|
|
4595
|
+
result.brokers = parseList(raw);
|
|
4596
|
+
});
|
|
4597
|
+
readVar(env, `${prefix}AUTO_CREATE_TOPICS`, (raw) => {
|
|
4598
|
+
options.autoCreateTopics = parseBool(`${prefix}AUTO_CREATE_TOPICS`, raw);
|
|
4599
|
+
});
|
|
4600
|
+
readVar(env, `${prefix}STRICT_SCHEMAS`, (raw) => {
|
|
4601
|
+
options.strictSchemas = parseBool(`${prefix}STRICT_SCHEMAS`, raw);
|
|
4602
|
+
});
|
|
4603
|
+
readVar(env, `${prefix}NUM_PARTITIONS`, (raw) => {
|
|
4604
|
+
options.numPartitions = parseNum(`${prefix}NUM_PARTITIONS`, raw);
|
|
4605
|
+
});
|
|
4606
|
+
readVar(env, `${prefix}TRANSACTIONAL_ID`, (raw) => {
|
|
4607
|
+
options.transactionalId = raw.trim();
|
|
4608
|
+
});
|
|
4609
|
+
readVar(env, `${prefix}CLOCK_RECOVERY_TOPICS`, (raw) => {
|
|
4610
|
+
const topics = parseList(raw);
|
|
4611
|
+
if (topics.length === 0) return;
|
|
4612
|
+
options.clockRecovery = { topics };
|
|
4613
|
+
});
|
|
4614
|
+
readVar(env, `${prefix}CLOCK_RECOVERY_TIMEOUT_MS`, (raw) => {
|
|
4615
|
+
const timeoutMs = parseNum(`${prefix}CLOCK_RECOVERY_TIMEOUT_MS`, raw);
|
|
4616
|
+
if (options.clockRecovery) {
|
|
4617
|
+
options.clockRecovery.timeoutMs = timeoutMs;
|
|
4618
|
+
}
|
|
4619
|
+
});
|
|
4620
|
+
readVar(env, `${prefix}LAG_THROTTLE_MAX_LAG`, (raw) => {
|
|
4621
|
+
options.lagThrottle = {
|
|
4622
|
+
maxLag: parseNum(`${prefix}LAG_THROTTLE_MAX_LAG`, raw)
|
|
4623
|
+
};
|
|
4624
|
+
});
|
|
4625
|
+
readVar(env, `${prefix}LAG_THROTTLE_GROUP_ID`, (raw) => {
|
|
4626
|
+
if (options.lagThrottle) options.lagThrottle.groupId = raw.trim();
|
|
4627
|
+
});
|
|
4628
|
+
readVar(env, `${prefix}LAG_THROTTLE_POLL_INTERVAL_MS`, (raw) => {
|
|
4629
|
+
if (options.lagThrottle) {
|
|
4630
|
+
options.lagThrottle.pollIntervalMs = parseNum(
|
|
4631
|
+
`${prefix}LAG_THROTTLE_POLL_INTERVAL_MS`,
|
|
4632
|
+
raw
|
|
4633
|
+
);
|
|
4634
|
+
}
|
|
4635
|
+
});
|
|
4636
|
+
readVar(env, `${prefix}LAG_THROTTLE_MAX_WAIT_MS`, (raw) => {
|
|
4637
|
+
if (options.lagThrottle) {
|
|
4638
|
+
options.lagThrottle.maxWaitMs = parseNum(
|
|
4639
|
+
`${prefix}LAG_THROTTLE_MAX_WAIT_MS`,
|
|
4640
|
+
raw
|
|
4641
|
+
);
|
|
4642
|
+
}
|
|
4643
|
+
});
|
|
4644
|
+
const security = securityFromEnv(env, prefix);
|
|
4645
|
+
if (security) options.security = security;
|
|
4646
|
+
return result;
|
|
4647
|
+
}
|
|
4648
|
+
function securityFromEnv(env, prefix) {
|
|
4649
|
+
let ssl;
|
|
4650
|
+
let allowInsecure;
|
|
4651
|
+
let mechanism;
|
|
4652
|
+
let username;
|
|
4653
|
+
let password;
|
|
4654
|
+
readVar(env, `${prefix}SSL`, (raw) => {
|
|
4655
|
+
ssl = parseBool(`${prefix}SSL`, raw);
|
|
4656
|
+
});
|
|
4657
|
+
readVar(env, `${prefix}ALLOW_INSECURE`, (raw) => {
|
|
4658
|
+
allowInsecure = parseBool(`${prefix}ALLOW_INSECURE`, raw);
|
|
4659
|
+
});
|
|
4660
|
+
readVar(env, `${prefix}SASL_MECHANISM`, (raw) => {
|
|
4661
|
+
mechanism = parseEnum(`${prefix}SASL_MECHANISM`, raw, [
|
|
4662
|
+
"plain",
|
|
4663
|
+
"scram-sha-256",
|
|
4664
|
+
"scram-sha-512"
|
|
4665
|
+
]);
|
|
4666
|
+
});
|
|
4667
|
+
readVar(env, `${prefix}SASL_USERNAME`, (raw) => {
|
|
4668
|
+
username = raw.trim();
|
|
4669
|
+
});
|
|
4670
|
+
readVar(env, `${prefix}SASL_PASSWORD`, (raw) => {
|
|
4671
|
+
password = raw;
|
|
4672
|
+
});
|
|
4673
|
+
if (ssl === void 0 && allowInsecure === void 0 && mechanism === void 0 && username === void 0 && password === void 0) {
|
|
4674
|
+
return void 0;
|
|
4675
|
+
}
|
|
4676
|
+
const security = {};
|
|
4677
|
+
if (ssl !== void 0) security.ssl = ssl;
|
|
4678
|
+
if (allowInsecure !== void 0) security.allowInsecure = allowInsecure;
|
|
4679
|
+
if (mechanism !== void 0 || username !== void 0 || password !== void 0) {
|
|
4680
|
+
if (mechanism === void 0 || username === void 0 || password === void 0) {
|
|
4681
|
+
throw new Error(
|
|
4682
|
+
`Incomplete SASL configuration: ${prefix}SASL_MECHANISM, ${prefix}SASL_USERNAME, and ${prefix}SASL_PASSWORD must all be set together (oauthbearer must be configured in code).`
|
|
4683
|
+
);
|
|
4684
|
+
}
|
|
4685
|
+
const sasl = { mechanism, username, password };
|
|
4686
|
+
security.sasl = sasl;
|
|
4687
|
+
}
|
|
4688
|
+
return security;
|
|
4689
|
+
}
|
|
4690
|
+
function consumerOptionsFromEnv(env = process.env, prefix = "KAFKA_CONSUMER_") {
|
|
4691
|
+
const options = {};
|
|
4692
|
+
readVar(env, `${prefix}GROUP_ID`, (raw) => {
|
|
4693
|
+
options.groupId = raw.trim();
|
|
4694
|
+
});
|
|
4695
|
+
readVar(env, `${prefix}FROM_BEGINNING`, (raw) => {
|
|
4696
|
+
options.fromBeginning = parseBool(`${prefix}FROM_BEGINNING`, raw);
|
|
4697
|
+
});
|
|
4698
|
+
readVar(env, `${prefix}AUTO_COMMIT`, (raw) => {
|
|
4699
|
+
options.autoCommit = parseBool(`${prefix}AUTO_COMMIT`, raw);
|
|
4700
|
+
});
|
|
4701
|
+
readVar(env, `${prefix}DLQ`, (raw) => {
|
|
4702
|
+
options.dlq = parseBool(`${prefix}DLQ`, raw);
|
|
4703
|
+
});
|
|
4704
|
+
readVar(env, `${prefix}RETRY_MAX_RETRIES`, (raw) => {
|
|
4705
|
+
const retry = {
|
|
4706
|
+
maxRetries: parseNum(`${prefix}RETRY_MAX_RETRIES`, raw)
|
|
4707
|
+
};
|
|
4708
|
+
options.retry = retry;
|
|
4709
|
+
});
|
|
4710
|
+
readVar(env, `${prefix}RETRY_BACKOFF_MS`, (raw) => {
|
|
4711
|
+
if (options.retry) {
|
|
4712
|
+
options.retry.backoffMs = parseNum(`${prefix}RETRY_BACKOFF_MS`, raw);
|
|
4713
|
+
}
|
|
4714
|
+
});
|
|
4715
|
+
readVar(env, `${prefix}RETRY_MAX_BACKOFF_MS`, (raw) => {
|
|
4716
|
+
if (options.retry) {
|
|
4717
|
+
options.retry.maxBackoffMs = parseNum(`${prefix}RETRY_MAX_BACKOFF_MS`, raw);
|
|
4718
|
+
}
|
|
4719
|
+
});
|
|
4720
|
+
readVar(env, `${prefix}RETRY_TOPICS`, (raw) => {
|
|
4721
|
+
options.retryTopics = parseBool(`${prefix}RETRY_TOPICS`, raw);
|
|
4722
|
+
});
|
|
4723
|
+
readVar(env, `${prefix}RETRY_TOPIC_ASSIGNMENT_TIMEOUT_MS`, (raw) => {
|
|
4724
|
+
options.retryTopicAssignmentTimeoutMs = parseNum(
|
|
4725
|
+
`${prefix}RETRY_TOPIC_ASSIGNMENT_TIMEOUT_MS`,
|
|
4726
|
+
raw
|
|
4727
|
+
);
|
|
4728
|
+
});
|
|
4729
|
+
readVar(env, `${prefix}HANDLER_TIMEOUT_MS`, (raw) => {
|
|
4730
|
+
options.handlerTimeoutMs = parseNum(`${prefix}HANDLER_TIMEOUT_MS`, raw);
|
|
4731
|
+
});
|
|
4732
|
+
readVar(env, `${prefix}MESSAGE_TTL_MS`, (raw) => {
|
|
4733
|
+
options.messageTtlMs = parseNum(`${prefix}MESSAGE_TTL_MS`, raw);
|
|
4734
|
+
});
|
|
4735
|
+
readVar(env, `${prefix}DEDUPLICATION_STRATEGY`, (raw) => {
|
|
4736
|
+
const strategy = parseEnum(`${prefix}DEDUPLICATION_STRATEGY`, raw, [
|
|
4737
|
+
"drop",
|
|
4738
|
+
"dlq",
|
|
4739
|
+
"topic"
|
|
4740
|
+
]);
|
|
4741
|
+
const dedup = { strategy };
|
|
4742
|
+
options.deduplication = dedup;
|
|
4743
|
+
});
|
|
4744
|
+
readVar(env, `${prefix}DEDUPLICATION_TOPIC`, (raw) => {
|
|
4745
|
+
if (options.deduplication) {
|
|
4746
|
+
options.deduplication.duplicatesTopic = raw.trim();
|
|
4747
|
+
}
|
|
4748
|
+
});
|
|
4749
|
+
readVar(env, `${prefix}CIRCUIT_BREAKER_THRESHOLD`, (raw) => {
|
|
4750
|
+
const cb = {
|
|
4751
|
+
threshold: parseNum(`${prefix}CIRCUIT_BREAKER_THRESHOLD`, raw)
|
|
4752
|
+
};
|
|
4753
|
+
options.circuitBreaker = cb;
|
|
4754
|
+
});
|
|
4755
|
+
readVar(env, `${prefix}CIRCUIT_BREAKER_RECOVERY_MS`, (raw) => {
|
|
4756
|
+
if (options.circuitBreaker) {
|
|
4757
|
+
options.circuitBreaker.recoveryMs = parseNum(
|
|
4758
|
+
`${prefix}CIRCUIT_BREAKER_RECOVERY_MS`,
|
|
4759
|
+
raw
|
|
4760
|
+
);
|
|
4761
|
+
}
|
|
4762
|
+
});
|
|
4763
|
+
readVar(env, `${prefix}CIRCUIT_BREAKER_WINDOW_SIZE`, (raw) => {
|
|
4764
|
+
if (options.circuitBreaker) {
|
|
4765
|
+
options.circuitBreaker.windowSize = parseNum(
|
|
4766
|
+
`${prefix}CIRCUIT_BREAKER_WINDOW_SIZE`,
|
|
4767
|
+
raw
|
|
4768
|
+
);
|
|
4769
|
+
}
|
|
4770
|
+
});
|
|
4771
|
+
readVar(env, `${prefix}CIRCUIT_BREAKER_HALF_OPEN_SUCCESSES`, (raw) => {
|
|
4772
|
+
if (options.circuitBreaker) {
|
|
4773
|
+
options.circuitBreaker.halfOpenSuccesses = parseNum(
|
|
4774
|
+
`${prefix}CIRCUIT_BREAKER_HALF_OPEN_SUCCESSES`,
|
|
4775
|
+
raw
|
|
4776
|
+
);
|
|
4777
|
+
}
|
|
4778
|
+
});
|
|
4779
|
+
readVar(env, `${prefix}QUEUE_HIGH_WATER_MARK`, (raw) => {
|
|
4780
|
+
options.queueHighWaterMark = parseNum(`${prefix}QUEUE_HIGH_WATER_MARK`, raw);
|
|
4781
|
+
});
|
|
4782
|
+
readVar(env, `${prefix}PARTITION_ASSIGNER`, (raw) => {
|
|
4783
|
+
options.partitionAssigner = parseEnum(`${prefix}PARTITION_ASSIGNER`, raw, [
|
|
4784
|
+
"roundrobin",
|
|
4785
|
+
"range",
|
|
4786
|
+
"cooperative-sticky"
|
|
4787
|
+
]);
|
|
4788
|
+
});
|
|
4789
|
+
readVar(env, `${prefix}GROUP_INSTANCE_ID`, (raw) => {
|
|
4790
|
+
options.groupInstanceId = raw.trim();
|
|
4791
|
+
});
|
|
4792
|
+
readVar(env, `${prefix}SUBSCRIBE_RETRY_RETRIES`, (raw) => {
|
|
4793
|
+
const subscribeRetry = {
|
|
4794
|
+
retries: parseNum(`${prefix}SUBSCRIBE_RETRY_RETRIES`, raw)
|
|
4795
|
+
};
|
|
4796
|
+
options.subscribeRetry = subscribeRetry;
|
|
4797
|
+
});
|
|
4798
|
+
readVar(env, `${prefix}SUBSCRIBE_RETRY_DELAY_MS`, (raw) => {
|
|
4799
|
+
if (options.subscribeRetry) {
|
|
4800
|
+
options.subscribeRetry.backoffMs = parseNum(
|
|
4801
|
+
`${prefix}SUBSCRIBE_RETRY_DELAY_MS`,
|
|
4802
|
+
raw
|
|
4803
|
+
);
|
|
4804
|
+
}
|
|
4805
|
+
});
|
|
4806
|
+
return options;
|
|
4807
|
+
}
|
|
4808
|
+
var NESTED_CONSUMER_KEYS = [
|
|
4809
|
+
"retry",
|
|
4810
|
+
"deduplication",
|
|
4811
|
+
"circuitBreaker",
|
|
4812
|
+
"subscribeRetry"
|
|
4813
|
+
];
|
|
4814
|
+
function isPlainObject(value) {
|
|
4815
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4816
|
+
}
|
|
4817
|
+
function mergeConsumerOptions(...layers) {
|
|
4818
|
+
const result = {};
|
|
4819
|
+
for (const layer of layers) {
|
|
4820
|
+
if (!layer) continue;
|
|
4821
|
+
for (const [key, value] of Object.entries(layer)) {
|
|
4822
|
+
if (value === void 0) continue;
|
|
4823
|
+
if (NESTED_CONSUMER_KEYS.includes(key) && isPlainObject(value) && isPlainObject(result[key])) {
|
|
4824
|
+
result[key] = {
|
|
4825
|
+
...result[key],
|
|
4826
|
+
...value
|
|
4827
|
+
};
|
|
4828
|
+
} else {
|
|
4829
|
+
result[key] = value;
|
|
4830
|
+
}
|
|
4831
|
+
}
|
|
4832
|
+
}
|
|
4833
|
+
return result;
|
|
4834
|
+
}
|
|
3601
4835
|
// Annotate the CommonJS export names for ESM import in node:
|
|
3602
4836
|
0 && (module.exports = {
|
|
3603
4837
|
HEADER_CORRELATION_ID,
|
|
4838
|
+
HEADER_DELAYED_TARGET,
|
|
4839
|
+
HEADER_DELAYED_UNTIL,
|
|
3604
4840
|
HEADER_EVENT_ID,
|
|
3605
4841
|
HEADER_LAMPORT_CLOCK,
|
|
3606
4842
|
HEADER_SCHEMA_VERSION,
|
|
3607
4843
|
HEADER_TIMESTAMP,
|
|
3608
4844
|
HEADER_TRACEPARENT,
|
|
4845
|
+
InMemoryDedupStore,
|
|
4846
|
+
InMemoryOutboxStore,
|
|
3609
4847
|
KafkaClient,
|
|
3610
4848
|
KafkaProcessingError,
|
|
3611
4849
|
KafkaRetryExhaustedError,
|
|
3612
4850
|
KafkaValidationError,
|
|
4851
|
+
SchemaRegistryClient,
|
|
4852
|
+
awsMskIamProvider,
|
|
3613
4853
|
buildEnvelopeHeaders,
|
|
4854
|
+
consumerOptionsFromEnv,
|
|
3614
4855
|
decodeHeaders,
|
|
4856
|
+
describeRequiredAcls,
|
|
3615
4857
|
extractEnvelope,
|
|
4858
|
+
gcpAccessTokenProvider,
|
|
3616
4859
|
getEnvelopeContext,
|
|
4860
|
+
kafkaClientConfigFromEnv,
|
|
4861
|
+
mergeConsumerOptions,
|
|
4862
|
+
registrySchema,
|
|
4863
|
+
resolveSecurityOptions,
|
|
3617
4864
|
runWithEnvelopeContext,
|
|
3618
|
-
|
|
4865
|
+
startOutboxRelay,
|
|
4866
|
+
toError,
|
|
4867
|
+
toKafkaAclCommands,
|
|
4868
|
+
toMskIamPolicy,
|
|
4869
|
+
topic,
|
|
4870
|
+
versionedSchema
|
|
3619
4871
|
});
|
|
3620
4872
|
//# sourceMappingURL=core.js.map
|