@relaybus/kafka 0.3.1 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +91 -37
- package/dist/index.cjs +163 -47
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +113 -17
- package/dist/index.d.ts +113 -17
- package/dist/index.js +164 -49
- package/dist/index.js.map +1 -1
- package/package.json +6 -5
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { RelayBusError, NonRetryableError, assertEventType, toErrorInfo, systemClock
|
|
1
|
+
import { RelayBusError, NonRetryableError, assertEventType, ConfigurationError, toErrorInfo, systemClock } from '@relaybus/core';
|
|
2
2
|
import { createCloudEventReaders, DecodedEventReader, mediaTypeOf, isJsonObject, observeRejection, createJsonEnvelopeFormat, SubscribingTransport, writeStructuredCloudEvent, schedule, BoundedInteger, PositiveInteger, ExponentialBackoff, Milliseconds } from '@relaybus/core/internal';
|
|
3
3
|
export { fromHeader, fromPayloadField } from '@relaybus/core/internal';
|
|
4
4
|
|
|
@@ -436,15 +436,30 @@ function jsonEnvelope(mapping) {
|
|
|
436
436
|
transportIds: [RECORD_POSITION],
|
|
437
437
|
failures: { invalid, unencodable: (reason) => new UnencodableEventError(reason) }
|
|
438
438
|
});
|
|
439
|
-
const contentType = mapping.contentType ?? DEFAULT_CONTENT_TYPE;
|
|
439
|
+
const contentType = mapping.contentTypeHeader === false ? {} : { [CONTENT_TYPE]: mapping.contentType ?? DEFAULT_CONTENT_TYPE };
|
|
440
440
|
const stampsTime = mapping.occurredAt === void 0;
|
|
441
|
-
const ownHeaders = (envelope) => mapping.id === RECORD_POSITION ? {
|
|
441
|
+
const ownHeaders = (envelope) => mapping.id === RECORD_POSITION ? { ...contentType, [EVENT_ID_HEADER]: envelope.id } : contentType;
|
|
442
|
+
const recordKey = resolveRecordKey(mapping.recordKey);
|
|
442
443
|
return {
|
|
443
|
-
encode: (envelope) => encodeJson(envelope, { format, stampsTime, ownHeaders }),
|
|
444
|
+
encode: (envelope) => encodeJson(envelope, { format, stampsTime, ownHeaders, recordKey }),
|
|
444
445
|
decode: (record) => decodeJson(record, format)
|
|
445
446
|
};
|
|
446
447
|
}
|
|
447
|
-
function
|
|
448
|
+
function resolveRecordKey(recordKey) {
|
|
449
|
+
if (recordKey === void 0 || recordKey === "event-key") {
|
|
450
|
+
return (envelope) => envelope.key;
|
|
451
|
+
}
|
|
452
|
+
if (recordKey === "none") {
|
|
453
|
+
return () => null;
|
|
454
|
+
}
|
|
455
|
+
if (typeof recordKey === "function") {
|
|
456
|
+
return recordKey;
|
|
457
|
+
}
|
|
458
|
+
throw new ConfigurationError(
|
|
459
|
+
`jsonEnvelope recordKey must be 'event-key', 'none' or a function, received '${String(recordKey)}'`
|
|
460
|
+
);
|
|
461
|
+
}
|
|
462
|
+
function encodeJson(envelope, { format, stampsTime, ownHeaders, recordKey }) {
|
|
448
463
|
assertRepresentable(envelope);
|
|
449
464
|
const { body, headers } = format.write(envelope);
|
|
450
465
|
const taken = [CONTENT_TYPE, EVENT_ID_HEADER].find((name) => Object.hasOwn(headers, name));
|
|
@@ -454,7 +469,7 @@ function encodeJson(envelope, { format, stampsTime, ownHeaders }) {
|
|
|
454
469
|
);
|
|
455
470
|
}
|
|
456
471
|
return {
|
|
457
|
-
key: envelope
|
|
472
|
+
key: recordKey(envelope),
|
|
458
473
|
value: serializeJson(envelope, body),
|
|
459
474
|
headers: { ...headers, ...ownHeaders(envelope) },
|
|
460
475
|
...stampsTime ? { timestamp: recordTimestamp(envelope) } : {}
|
|
@@ -485,9 +500,6 @@ function recordTimestamp(envelope) {
|
|
|
485
500
|
formatTime(envelope);
|
|
486
501
|
return String(envelope.occurredAt.getTime());
|
|
487
502
|
}
|
|
488
|
-
function tracksMembership(consumer) {
|
|
489
|
-
return consumer.on !== void 0 && consumer.events?.GROUP_JOIN !== void 0;
|
|
490
|
-
}
|
|
491
503
|
var GroupJoinWatch = class {
|
|
492
504
|
#settings;
|
|
493
505
|
#waiter;
|
|
@@ -513,7 +525,7 @@ var GroupJoinWatch = class {
|
|
|
513
525
|
eachMessage
|
|
514
526
|
}) {
|
|
515
527
|
await consumer.connect();
|
|
516
|
-
await consumer.subscribe(
|
|
528
|
+
await consumer.subscribe(this.#settings.dialect.subscription(topics, fromBeginning));
|
|
517
529
|
const joined = this.#wait(consumer);
|
|
518
530
|
try {
|
|
519
531
|
await consumer.run({ eachMessage });
|
|
@@ -523,7 +535,7 @@ var GroupJoinWatch = class {
|
|
|
523
535
|
}
|
|
524
536
|
}
|
|
525
537
|
#wait(consumer) {
|
|
526
|
-
if (!tracksMembership(consumer)) {
|
|
538
|
+
if (!this.#settings.dialect.tracksMembership(consumer)) {
|
|
527
539
|
return { promise: Promise.resolve(), dispose: () => void 0 };
|
|
528
540
|
}
|
|
529
541
|
const { name, groupId, timings } = this.#settings;
|
|
@@ -597,7 +609,7 @@ var KafkaProducerSession = class {
|
|
|
597
609
|
delayMs: timings.publishTimeoutMs
|
|
598
610
|
});
|
|
599
611
|
});
|
|
600
|
-
const sending = producer.send(record);
|
|
612
|
+
const sending = producer.send(this.#settings.dialect.record(record));
|
|
601
613
|
sending.catch(() => void 0);
|
|
602
614
|
try {
|
|
603
615
|
await Promise.race([sending, timeout]);
|
|
@@ -606,8 +618,12 @@ var KafkaProducerSession = class {
|
|
|
606
618
|
}
|
|
607
619
|
}
|
|
608
620
|
async #open() {
|
|
609
|
-
const { kafka,
|
|
610
|
-
|
|
621
|
+
const { kafka, dialect, injectedProducer } = this.#settings;
|
|
622
|
+
if (injectedProducer !== void 0) {
|
|
623
|
+
this.#connected = true;
|
|
624
|
+
return withoutLifecycle(injectedProducer);
|
|
625
|
+
}
|
|
626
|
+
const producer = kafka.producer(dialect.producerConfig(this.#settings));
|
|
611
627
|
try {
|
|
612
628
|
await producer.connect();
|
|
613
629
|
} catch (error) {
|
|
@@ -618,6 +634,88 @@ var KafkaProducerSession = class {
|
|
|
618
634
|
return producer;
|
|
619
635
|
}
|
|
620
636
|
};
|
|
637
|
+
function withoutLifecycle(producer) {
|
|
638
|
+
return {
|
|
639
|
+
send: (record) => producer.send(record),
|
|
640
|
+
disconnect: () => Promise.resolve()
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
var KAFKAJS_DIALECT = {
|
|
644
|
+
producerConfig: ({ producerConfig, maxInFlightRequests }) => ({
|
|
645
|
+
...producerConfig,
|
|
646
|
+
idempotent: true,
|
|
647
|
+
maxInFlightRequests
|
|
648
|
+
}),
|
|
649
|
+
consumerConfig: ({ consumerConfig, groupId }) => ({ ...consumerConfig, groupId }),
|
|
650
|
+
subscription: (topics, fromBeginning) => ({ topics, fromBeginning }),
|
|
651
|
+
record: (record) => record,
|
|
652
|
+
tracksMembership: (consumer) => consumer.on !== void 0 && consumer.events?.GROUP_JOIN !== void 0,
|
|
653
|
+
observe: (consumer, membership) => {
|
|
654
|
+
if (!KAFKAJS_DIALECT.tracksMembership(consumer)) {
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
const names = consumer.events;
|
|
658
|
+
const listen = (name, listener) => {
|
|
659
|
+
if (name !== void 0) {
|
|
660
|
+
consumer.on(name, listener);
|
|
661
|
+
}
|
|
662
|
+
};
|
|
663
|
+
listen(names.GROUP_JOIN, () => membership.joined());
|
|
664
|
+
listen(names.REBALANCING, () => membership.rebalancing());
|
|
665
|
+
listen(names.CRASH, (event) => membership.crashed(event));
|
|
666
|
+
listen(names.STOP, () => membership.left());
|
|
667
|
+
listen(names.DISCONNECT, () => membership.left());
|
|
668
|
+
}
|
|
669
|
+
};
|
|
670
|
+
var ASSIGN_PARTITIONS = -175;
|
|
671
|
+
var REVOKE_PARTITIONS = -174;
|
|
672
|
+
var CONFLUENT_DIALECT = {
|
|
673
|
+
producerConfig: ({ producerConfig, maxInFlightRequests }) => {
|
|
674
|
+
const user = producerConfig;
|
|
675
|
+
return {
|
|
676
|
+
...user,
|
|
677
|
+
kafkaJS: { ...user.kafkaJS, idempotent: true, maxInFlightRequests, acks: -1 },
|
|
678
|
+
"enable.idempotence": true,
|
|
679
|
+
"max.in.flight": maxInFlightRequests,
|
|
680
|
+
acks: -1
|
|
681
|
+
};
|
|
682
|
+
},
|
|
683
|
+
consumerConfig: ({ consumerConfig, groupId, fromBeginning }, membership) => {
|
|
684
|
+
const user = consumerConfig;
|
|
685
|
+
const userCallback = typeof user.rebalance_cb === "function" ? user.rebalance_cb : void 0;
|
|
686
|
+
return {
|
|
687
|
+
...user,
|
|
688
|
+
kafkaJS: { ...user.kafkaJS, groupId, fromBeginning },
|
|
689
|
+
"group.id": groupId,
|
|
690
|
+
"auto.offset.reset": fromBeginning ? "earliest" : "latest",
|
|
691
|
+
rebalance_cb: (...args) => {
|
|
692
|
+
if (args[0].code === ASSIGN_PARTITIONS) {
|
|
693
|
+
membership.joined();
|
|
694
|
+
} else if (args[0].code === REVOKE_PARTITIONS) {
|
|
695
|
+
membership.rebalancing();
|
|
696
|
+
}
|
|
697
|
+
return userCallback?.(...args);
|
|
698
|
+
}
|
|
699
|
+
};
|
|
700
|
+
},
|
|
701
|
+
subscription: (topics) => ({ topics }),
|
|
702
|
+
record: ({ acks: _acks, ...record }) => record,
|
|
703
|
+
tracksMembership: () => true,
|
|
704
|
+
observe: () => void 0
|
|
705
|
+
};
|
|
706
|
+
function clientDialect(client) {
|
|
707
|
+
if (client === void 0 || client === "kafkajs") {
|
|
708
|
+
return KAFKAJS_DIALECT;
|
|
709
|
+
}
|
|
710
|
+
if (client === "confluent") {
|
|
711
|
+
return CONFLUENT_DIALECT;
|
|
712
|
+
}
|
|
713
|
+
throw new ConfigurationError(
|
|
714
|
+
`Unknown Kafka client '${String(client)}'; use 'kafkajs' or 'confluent'`
|
|
715
|
+
);
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
// src/kafka-settings.ts
|
|
621
719
|
var DEFAULTS = {
|
|
622
720
|
name: "kafka",
|
|
623
721
|
maxInFlightRequests: 1,
|
|
@@ -635,6 +733,8 @@ function resolveKafkaSettings(options) {
|
|
|
635
733
|
return {
|
|
636
734
|
name: options.name ?? DEFAULTS.name,
|
|
637
735
|
kafka: options.kafka,
|
|
736
|
+
injectedProducer: resolveInjectedProducer(options),
|
|
737
|
+
dialect: clientDialect(options.client),
|
|
638
738
|
groupId: options.groupId,
|
|
639
739
|
fromBeginning: options.fromBeginning ?? true,
|
|
640
740
|
maxInFlightRequests: BoundedInteger.of(
|
|
@@ -649,6 +749,23 @@ function resolveKafkaSettings(options) {
|
|
|
649
749
|
timings: resolveTimings(options)
|
|
650
750
|
};
|
|
651
751
|
}
|
|
752
|
+
function resolveInjectedProducer(options) {
|
|
753
|
+
const { producer, kafka, producerConfig, maxInFlightRequests } = options;
|
|
754
|
+
if (producer === void 0) {
|
|
755
|
+
if (kafka === void 0) {
|
|
756
|
+
throw new ConfigurationError(
|
|
757
|
+
"Give 'kafka', or an already-connected 'producer' to publish with"
|
|
758
|
+
);
|
|
759
|
+
}
|
|
760
|
+
return void 0;
|
|
761
|
+
}
|
|
762
|
+
if (producerConfig !== void 0 || maxInFlightRequests !== void 0) {
|
|
763
|
+
throw new ConfigurationError(
|
|
764
|
+
"Give 'producerConfig' and 'maxInFlightRequests' to a producer you inject yourself"
|
|
765
|
+
);
|
|
766
|
+
}
|
|
767
|
+
return producer;
|
|
768
|
+
}
|
|
652
769
|
function resolveCodec({ codec, decode }) {
|
|
653
770
|
if (decode === void 0) {
|
|
654
771
|
return codec ?? cloudEventsBinary;
|
|
@@ -718,7 +835,7 @@ function assertCanSubscribe(state, {
|
|
|
718
835
|
settings,
|
|
719
836
|
destinations
|
|
720
837
|
}) {
|
|
721
|
-
const { name, groupId, invalidPolicy } = settings;
|
|
838
|
+
const { name, groupId, invalidPolicy, kafka } = settings;
|
|
722
839
|
if (state === "closed") {
|
|
723
840
|
throw new TransportClosedError(`Transport '${name}' is closed`);
|
|
724
841
|
}
|
|
@@ -727,6 +844,9 @@ function assertCanSubscribe(state, {
|
|
|
727
844
|
`Transport '${name}' already subscribed; list every topic in one subscription or use one KafkaTransport per consumer group`
|
|
728
845
|
);
|
|
729
846
|
}
|
|
847
|
+
if (kafka === void 0) {
|
|
848
|
+
throw new ConfigurationError(`Transport '${name}' needs 'kafka' to create its consumer`);
|
|
849
|
+
}
|
|
730
850
|
if (groupId === void 0 || groupId.length === 0) {
|
|
731
851
|
throw new ConfigurationError(`Transport '${name}' needs a groupId to subscribe`);
|
|
732
852
|
}
|
|
@@ -745,10 +865,7 @@ function assertCanSubscribe(state, {
|
|
|
745
865
|
|
|
746
866
|
// src/kafka-records.ts
|
|
747
867
|
function decodeRecord(codec, { topic, partition, message }) {
|
|
748
|
-
|
|
749
|
-
return decodeEnvelope(message);
|
|
750
|
-
}
|
|
751
|
-
const record = {
|
|
868
|
+
return decodeKafkaRecord(codec, {
|
|
752
869
|
topic,
|
|
753
870
|
partition,
|
|
754
871
|
offset: message.offset,
|
|
@@ -756,7 +873,12 @@ function decodeRecord(codec, { topic, partition, message }) {
|
|
|
756
873
|
value: message.value,
|
|
757
874
|
headers: message.headers,
|
|
758
875
|
timestamp: message.timestamp
|
|
759
|
-
};
|
|
876
|
+
});
|
|
877
|
+
}
|
|
878
|
+
function decodeKafkaRecord(codec, record) {
|
|
879
|
+
if (codec === cloudEventsBinary) {
|
|
880
|
+
return decodeEnvelope(record);
|
|
881
|
+
}
|
|
760
882
|
return completeDecodedEvent(codec.decode(record), record);
|
|
761
883
|
}
|
|
762
884
|
function deadLetterRecord({ topic, partition, message }, {
|
|
@@ -894,10 +1016,12 @@ var KafkaTransport = class extends SubscribingTransport {
|
|
|
894
1016
|
async #subscribe({ destinations, onMessage, hooks }) {
|
|
895
1017
|
assertCanSubscribe(this.#state, { settings: this.#settings, destinations });
|
|
896
1018
|
this.#state = "subscribed";
|
|
897
|
-
const { kafka,
|
|
898
|
-
const
|
|
1019
|
+
const { kafka, dialect, groupId } = this.#settings;
|
|
1020
|
+
const membership = this.#observeConsumer();
|
|
1021
|
+
const consumer = kafka.consumer(dialect.consumerConfig(this.#settings, membership));
|
|
899
1022
|
this.#consumer = consumer;
|
|
900
|
-
this.#
|
|
1023
|
+
this.#groupJoined = !dialect.tracksMembership(consumer);
|
|
1024
|
+
dialect.observe(consumer, membership);
|
|
901
1025
|
const start = () => this.#groupJoin.start(consumer, {
|
|
902
1026
|
topics: [...destinations],
|
|
903
1027
|
fromBeginning: this.#settings.fromBeginning,
|
|
@@ -1038,34 +1162,25 @@ var KafkaTransport = class extends SubscribingTransport {
|
|
|
1038
1162
|
}
|
|
1039
1163
|
}
|
|
1040
1164
|
}
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
this.#groupJoined = true;
|
|
1044
|
-
return;
|
|
1045
|
-
}
|
|
1165
|
+
/** What the transport does when the consumer joins, rebalances, crashes or leaves its group. */
|
|
1166
|
+
#observeConsumer() {
|
|
1046
1167
|
const { logger, groupId } = this.#settings;
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1168
|
+
return {
|
|
1169
|
+
joined: () => {
|
|
1170
|
+
this.#groupJoined = true;
|
|
1171
|
+
this.#groupJoin.joined();
|
|
1172
|
+
logger.log(`[KafkaTransport](observeConsumer) Joined group | group=${groupId}`);
|
|
1173
|
+
},
|
|
1174
|
+
rebalancing: () => {
|
|
1175
|
+
this.#groupJoined = false;
|
|
1176
|
+
this.#stalledPartitions.clear();
|
|
1177
|
+
logger.warn(`[KafkaTransport](observeConsumer) Rebalancing | group=${groupId}`);
|
|
1178
|
+
},
|
|
1179
|
+
crashed: (event) => this.#handleCrash(event),
|
|
1180
|
+
left: () => {
|
|
1181
|
+
this.#groupJoined = false;
|
|
1051
1182
|
}
|
|
1052
1183
|
};
|
|
1053
|
-
listen(names.GROUP_JOIN, () => {
|
|
1054
|
-
this.#groupJoined = true;
|
|
1055
|
-
this.#groupJoin.joined();
|
|
1056
|
-
logger.log(`[KafkaTransport](observeConsumer) Joined group | group=${groupId}`);
|
|
1057
|
-
});
|
|
1058
|
-
listen(names.REBALANCING, () => {
|
|
1059
|
-
this.#groupJoined = false;
|
|
1060
|
-
this.#stalledPartitions.clear();
|
|
1061
|
-
logger.warn(`[KafkaTransport](observeConsumer) Rebalancing | group=${groupId}`);
|
|
1062
|
-
});
|
|
1063
|
-
listen(names.CRASH, (event) => this.#handleCrash(event));
|
|
1064
|
-
const leftGroup = () => {
|
|
1065
|
-
this.#groupJoined = false;
|
|
1066
|
-
};
|
|
1067
|
-
listen(names.STOP, leftGroup);
|
|
1068
|
-
listen(names.DISCONNECT, leftGroup);
|
|
1069
1184
|
}
|
|
1070
1185
|
#handleCrash(event) {
|
|
1071
1186
|
const { error, restart } = event.payload ?? {};
|
|
@@ -1108,6 +1223,6 @@ var KafkaTransport = class extends SubscribingTransport {
|
|
|
1108
1223
|
}
|
|
1109
1224
|
};
|
|
1110
1225
|
|
|
1111
|
-
export { GroupJoinTimeoutError, HandlerFailedError, InvalidKafkaMessageError, KafkaTransport, PublishTimeoutError, TransportClosedError, UnencodableEventError, cloudEventsBinary, cloudEventsStructured, decodeCloudEvent, decodeEnvelope, encodeEnvelope, jsonEnvelope, plainJsonDecoder };
|
|
1226
|
+
export { GroupJoinTimeoutError, HandlerFailedError, InvalidKafkaMessageError, KafkaTransport, PublishTimeoutError, TransportClosedError, UnencodableEventError, cloudEventsBinary, cloudEventsStructured, decodeCloudEvent, decodeEnvelope, decodeKafkaRecord, encodeEnvelope, jsonEnvelope, plainJsonDecoder };
|
|
1112
1227
|
//# sourceMappingURL=index.js.map
|
|
1113
1228
|
//# sourceMappingURL=index.js.map
|