@ohos-ports/confluentinc-kafka-javascript 1.10.0-beta.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/CHANGELOG.md +457 -0
- package/LICENSE.kafkajs +31 -0
- package/LICENSE.node-rdkafka +25 -0
- package/LICENSE.txt +20 -0
- package/README.md +133 -0
- package/binding.gyp +184 -0
- package/build/Release/confluent-kafka-javascript.node +0 -0
- package/build/deps/librdkafka++.so.1 +0 -0
- package/build/deps/librdkafka.Makefile +6 -0
- package/build/deps/librdkafka.so.1 +0 -0
- package/build/deps/librdkafka.target.mk +55 -0
- package/deps/librdkafka/.clang-format +136 -0
- package/deps/librdkafka/.clang-format-cpp +103 -0
- package/deps/librdkafka/.dir-locals.el +10 -0
- package/deps/librdkafka/.formatignore +35 -0
- package/deps/librdkafka/.gdbmacros +19 -0
- package/deps/librdkafka/CHANGELOG.md +2477 -0
- package/deps/librdkafka/CMakeLists.txt +291 -0
- package/deps/librdkafka/CODE_OF_CONDUCT.md +46 -0
- package/deps/librdkafka/CONFIGURATION.md +212 -0
- package/deps/librdkafka/CONTRIBUTING.md +431 -0
- package/deps/librdkafka/Doxyfile +2375 -0
- package/deps/librdkafka/INTRODUCTION.md +2752 -0
- package/deps/librdkafka/LICENSE +26 -0
- package/deps/librdkafka/LICENSE.cjson +22 -0
- package/deps/librdkafka/LICENSE.crc32c +28 -0
- package/deps/librdkafka/LICENSE.fnv1a +18 -0
- package/deps/librdkafka/LICENSE.hdrhistogram +27 -0
- package/deps/librdkafka/LICENSE.lz4 +26 -0
- package/deps/librdkafka/LICENSE.murmur2 +25 -0
- package/deps/librdkafka/LICENSE.nanopb +22 -0
- package/deps/librdkafka/LICENSE.opentelemetry +203 -0
- package/deps/librdkafka/LICENSE.pycrc +23 -0
- package/deps/librdkafka/LICENSE.queue +31 -0
- package/deps/librdkafka/LICENSE.regexp +5 -0
- package/deps/librdkafka/LICENSE.snappy +36 -0
- package/deps/librdkafka/LICENSE.tinycthread +26 -0
- package/deps/librdkafka/LICENSE.wingetopt +49 -0
- package/deps/librdkafka/LICENSES.txt +625 -0
- package/deps/librdkafka/Makefile +125 -0
- package/deps/librdkafka/README.md +201 -0
- package/deps/librdkafka/README.win32 +26 -0
- package/deps/librdkafka/STATISTICS.md +624 -0
- package/deps/librdkafka/configure +214 -0
- package/deps/librdkafka/configure.self +341 -0
- package/deps/librdkafka/dev-conf.sh +123 -0
- package/deps/librdkafka/lds-gen.py +73 -0
- package/deps/librdkafka/mainpage.doxy +40 -0
- package/deps/librdkafka/mklove/Makefile.base +329 -0
- package/deps/librdkafka/mklove/modules/configure.atomics +144 -0
- package/deps/librdkafka/mklove/modules/configure.base +2484 -0
- package/deps/librdkafka/mklove/modules/configure.builtin +70 -0
- package/deps/librdkafka/mklove/modules/configure.cc +186 -0
- package/deps/librdkafka/mklove/modules/configure.cxx +8 -0
- package/deps/librdkafka/mklove/modules/configure.fileversion +65 -0
- package/deps/librdkafka/mklove/modules/configure.gitversion +29 -0
- package/deps/librdkafka/mklove/modules/configure.good_cflags +18 -0
- package/deps/librdkafka/mklove/modules/configure.host +132 -0
- package/deps/librdkafka/mklove/modules/configure.lib +49 -0
- package/deps/librdkafka/mklove/modules/configure.libcurl +99 -0
- package/deps/librdkafka/mklove/modules/configure.libsasl2 +36 -0
- package/deps/librdkafka/mklove/modules/configure.libssl +147 -0
- package/deps/librdkafka/mklove/modules/configure.libzstd +58 -0
- package/deps/librdkafka/mklove/modules/configure.parseversion +95 -0
- package/deps/librdkafka/mklove/modules/configure.pic +16 -0
- package/deps/librdkafka/mklove/modules/configure.socket +20 -0
- package/deps/librdkafka/mklove/modules/configure.zlib +61 -0
- package/deps/librdkafka/mklove/modules/patches/README.md +8 -0
- package/deps/librdkafka/mklove/modules/patches/libcurl.0000-no-runtime-linking-check.patch +11 -0
- package/deps/librdkafka/mklove/modules/patches/libssl.0000-osx-rand-include-fix-OpenSSL-PR16409.patch +56 -0
- package/deps/librdkafka/service.yml +182 -0
- package/deps/librdkafka/src/CMakeLists.txt +379 -0
- package/deps/librdkafka/src/Makefile +105 -0
- package/deps/librdkafka/src/README.lz4.md +30 -0
- package/deps/librdkafka/src/cJSON.c +3191 -0
- package/deps/librdkafka/src/cJSON.h +306 -0
- package/deps/librdkafka/src/crc32c.c +430 -0
- package/deps/librdkafka/src/crc32c.h +38 -0
- package/deps/librdkafka/src/generate_proto.sh +66 -0
- package/deps/librdkafka/src/librdkafka_cgrp_synch.png +0 -0
- package/deps/librdkafka/src/lz4.c +2727 -0
- package/deps/librdkafka/src/lz4.h +842 -0
- package/deps/librdkafka/src/lz4frame.c +2078 -0
- package/deps/librdkafka/src/lz4frame.h +692 -0
- package/deps/librdkafka/src/lz4frame_static.h +47 -0
- package/deps/librdkafka/src/lz4hc.c +1631 -0
- package/deps/librdkafka/src/lz4hc.h +413 -0
- package/deps/librdkafka/src/nanopb/pb.h +917 -0
- package/deps/librdkafka/src/nanopb/pb_common.c +388 -0
- package/deps/librdkafka/src/nanopb/pb_common.h +49 -0
- package/deps/librdkafka/src/nanopb/pb_decode.c +1727 -0
- package/deps/librdkafka/src/nanopb/pb_decode.h +193 -0
- package/deps/librdkafka/src/nanopb/pb_encode.c +1000 -0
- package/deps/librdkafka/src/nanopb/pb_encode.h +185 -0
- package/deps/librdkafka/src/opentelemetry/common.pb.c +32 -0
- package/deps/librdkafka/src/opentelemetry/common.pb.h +170 -0
- package/deps/librdkafka/src/opentelemetry/metrics.options +2 -0
- package/deps/librdkafka/src/opentelemetry/metrics.pb.c +67 -0
- package/deps/librdkafka/src/opentelemetry/metrics.pb.h +966 -0
- package/deps/librdkafka/src/opentelemetry/resource.pb.c +12 -0
- package/deps/librdkafka/src/opentelemetry/resource.pb.h +58 -0
- package/deps/librdkafka/src/queue.h +850 -0
- package/deps/librdkafka/src/rd.h +592 -0
- package/deps/librdkafka/src/rdaddr.c +255 -0
- package/deps/librdkafka/src/rdaddr.h +202 -0
- package/deps/librdkafka/src/rdatomic.h +262 -0
- package/deps/librdkafka/src/rdavg.h +260 -0
- package/deps/librdkafka/src/rdavl.c +210 -0
- package/deps/librdkafka/src/rdavl.h +250 -0
- package/deps/librdkafka/src/rdbase64.c +200 -0
- package/deps/librdkafka/src/rdbase64.h +43 -0
- package/deps/librdkafka/src/rdbuf.c +1884 -0
- package/deps/librdkafka/src/rdbuf.h +375 -0
- package/deps/librdkafka/src/rdcrc32.c +114 -0
- package/deps/librdkafka/src/rdcrc32.h +170 -0
- package/deps/librdkafka/src/rddl.c +179 -0
- package/deps/librdkafka/src/rddl.h +43 -0
- package/deps/librdkafka/src/rdendian.h +175 -0
- package/deps/librdkafka/src/rdfloat.h +67 -0
- package/deps/librdkafka/src/rdfnv1a.c +113 -0
- package/deps/librdkafka/src/rdfnv1a.h +35 -0
- package/deps/librdkafka/src/rdgz.c +120 -0
- package/deps/librdkafka/src/rdgz.h +46 -0
- package/deps/librdkafka/src/rdhdrhistogram.c +721 -0
- package/deps/librdkafka/src/rdhdrhistogram.h +87 -0
- package/deps/librdkafka/src/rdhttp.c +831 -0
- package/deps/librdkafka/src/rdhttp.h +101 -0
- package/deps/librdkafka/src/rdinterval.h +177 -0
- package/deps/librdkafka/src/rdkafka.c +7287 -0
- package/deps/librdkafka/src/rdkafka.h +11795 -0
- package/deps/librdkafka/src/rdkafka_admin.c +10165 -0
- package/deps/librdkafka/src/rdkafka_admin.h +658 -0
- package/deps/librdkafka/src/rdkafka_assignment.c +1282 -0
- package/deps/librdkafka/src/rdkafka_assignment.h +73 -0
- package/deps/librdkafka/src/rdkafka_assignor.c +1800 -0
- package/deps/librdkafka/src/rdkafka_assignor.h +402 -0
- package/deps/librdkafka/src/rdkafka_aux.c +409 -0
- package/deps/librdkafka/src/rdkafka_aux.h +174 -0
- package/deps/librdkafka/src/rdkafka_background.c +222 -0
- package/deps/librdkafka/src/rdkafka_broker.c +6736 -0
- package/deps/librdkafka/src/rdkafka_broker.h +851 -0
- package/deps/librdkafka/src/rdkafka_buf.c +543 -0
- package/deps/librdkafka/src/rdkafka_buf.h +1525 -0
- package/deps/librdkafka/src/rdkafka_cert.c +576 -0
- package/deps/librdkafka/src/rdkafka_cert.h +62 -0
- package/deps/librdkafka/src/rdkafka_cgrp.c +8310 -0
- package/deps/librdkafka/src/rdkafka_cgrp.h +589 -0
- package/deps/librdkafka/src/rdkafka_conf.c +5177 -0
- package/deps/librdkafka/src/rdkafka_conf.h +746 -0
- package/deps/librdkafka/src/rdkafka_confval.h +97 -0
- package/deps/librdkafka/src/rdkafka_coord.c +623 -0
- package/deps/librdkafka/src/rdkafka_coord.h +132 -0
- package/deps/librdkafka/src/rdkafka_error.c +228 -0
- package/deps/librdkafka/src/rdkafka_error.h +80 -0
- package/deps/librdkafka/src/rdkafka_event.c +502 -0
- package/deps/librdkafka/src/rdkafka_event.h +126 -0
- package/deps/librdkafka/src/rdkafka_feature.c +898 -0
- package/deps/librdkafka/src/rdkafka_feature.h +104 -0
- package/deps/librdkafka/src/rdkafka_fetcher.c +3820 -0
- package/deps/librdkafka/src/rdkafka_fetcher.h +62 -0
- package/deps/librdkafka/src/rdkafka_header.c +220 -0
- package/deps/librdkafka/src/rdkafka_header.h +76 -0
- package/deps/librdkafka/src/rdkafka_idempotence.c +807 -0
- package/deps/librdkafka/src/rdkafka_idempotence.h +144 -0
- package/deps/librdkafka/src/rdkafka_int.h +1505 -0
- package/deps/librdkafka/src/rdkafka_interceptor.c +842 -0
- package/deps/librdkafka/src/rdkafka_interceptor.h +104 -0
- package/deps/librdkafka/src/rdkafka_lz4.c +450 -0
- package/deps/librdkafka/src/rdkafka_lz4.h +49 -0
- package/deps/librdkafka/src/rdkafka_metadata.c +2347 -0
- package/deps/librdkafka/src/rdkafka_metadata.h +350 -0
- package/deps/librdkafka/src/rdkafka_metadata_cache.c +1183 -0
- package/deps/librdkafka/src/rdkafka_mock.c +4514 -0
- package/deps/librdkafka/src/rdkafka_mock.h +867 -0
- package/deps/librdkafka/src/rdkafka_mock_cgrp.c +1877 -0
- package/deps/librdkafka/src/rdkafka_mock_handlers.c +5255 -0
- package/deps/librdkafka/src/rdkafka_mock_int.h +1005 -0
- package/deps/librdkafka/src/rdkafka_mock_sharegrp.c +1207 -0
- package/deps/librdkafka/src/rdkafka_msg.c +2619 -0
- package/deps/librdkafka/src/rdkafka_msg.h +629 -0
- package/deps/librdkafka/src/rdkafka_msgbatch.h +62 -0
- package/deps/librdkafka/src/rdkafka_msgset.h +105 -0
- package/deps/librdkafka/src/rdkafka_msgset_reader.c +2099 -0
- package/deps/librdkafka/src/rdkafka_msgset_writer.c +1474 -0
- package/deps/librdkafka/src/rdkafka_offset.c +1565 -0
- package/deps/librdkafka/src/rdkafka_offset.h +150 -0
- package/deps/librdkafka/src/rdkafka_op.c +1219 -0
- package/deps/librdkafka/src/rdkafka_op.h +1065 -0
- package/deps/librdkafka/src/rdkafka_partition.c +5109 -0
- package/deps/librdkafka/src/rdkafka_partition.h +1215 -0
- package/deps/librdkafka/src/rdkafka_pattern.c +228 -0
- package/deps/librdkafka/src/rdkafka_pattern.h +70 -0
- package/deps/librdkafka/src/rdkafka_plugin.c +213 -0
- package/deps/librdkafka/src/rdkafka_plugin.h +41 -0
- package/deps/librdkafka/src/rdkafka_proto.h +739 -0
- package/deps/librdkafka/src/rdkafka_protocol.h +131 -0
- package/deps/librdkafka/src/rdkafka_queue.c +1351 -0
- package/deps/librdkafka/src/rdkafka_queue.h +1235 -0
- package/deps/librdkafka/src/rdkafka_range_assignor.c +1748 -0
- package/deps/librdkafka/src/rdkafka_request.c +7298 -0
- package/deps/librdkafka/src/rdkafka_request.h +761 -0
- package/deps/librdkafka/src/rdkafka_roundrobin_assignor.c +123 -0
- package/deps/librdkafka/src/rdkafka_sasl.c +589 -0
- package/deps/librdkafka/src/rdkafka_sasl.h +63 -0
- package/deps/librdkafka/src/rdkafka_sasl_cyrus.c +722 -0
- package/deps/librdkafka/src/rdkafka_sasl_int.h +89 -0
- package/deps/librdkafka/src/rdkafka_sasl_oauthbearer.c +1833 -0
- package/deps/librdkafka/src/rdkafka_sasl_oauthbearer.h +52 -0
- package/deps/librdkafka/src/rdkafka_sasl_oauthbearer_oidc.c +1827 -0
- package/deps/librdkafka/src/rdkafka_sasl_oauthbearer_oidc.h +52 -0
- package/deps/librdkafka/src/rdkafka_sasl_plain.c +142 -0
- package/deps/librdkafka/src/rdkafka_sasl_scram.c +858 -0
- package/deps/librdkafka/src/rdkafka_sasl_win32.c +550 -0
- package/deps/librdkafka/src/rdkafka_share_acknowledgement.c +1445 -0
- package/deps/librdkafka/src/rdkafka_share_acknowledgement.h +373 -0
- package/deps/librdkafka/src/rdkafka_ssl.c +2235 -0
- package/deps/librdkafka/src/rdkafka_ssl.h +86 -0
- package/deps/librdkafka/src/rdkafka_sticky_assignor.c +4785 -0
- package/deps/librdkafka/src/rdkafka_subscription.c +411 -0
- package/deps/librdkafka/src/rdkafka_telemetry.c +761 -0
- package/deps/librdkafka/src/rdkafka_telemetry.h +52 -0
- package/deps/librdkafka/src/rdkafka_telemetry_decode.c +1307 -0
- package/deps/librdkafka/src/rdkafka_telemetry_decode.h +59 -0
- package/deps/librdkafka/src/rdkafka_telemetry_encode.c +1309 -0
- package/deps/librdkafka/src/rdkafka_telemetry_encode.h +459 -0
- package/deps/librdkafka/src/rdkafka_timer.c +407 -0
- package/deps/librdkafka/src/rdkafka_timer.h +117 -0
- package/deps/librdkafka/src/rdkafka_topic.c +2382 -0
- package/deps/librdkafka/src/rdkafka_topic.h +369 -0
- package/deps/librdkafka/src/rdkafka_transport.c +1309 -0
- package/deps/librdkafka/src/rdkafka_transport.h +99 -0
- package/deps/librdkafka/src/rdkafka_transport_int.h +100 -0
- package/deps/librdkafka/src/rdkafka_txnmgr.c +3256 -0
- package/deps/librdkafka/src/rdkafka_txnmgr.h +171 -0
- package/deps/librdkafka/src/rdkafka_zstd.c +226 -0
- package/deps/librdkafka/src/rdkafka_zstd.h +57 -0
- package/deps/librdkafka/src/rdlist.c +593 -0
- package/deps/librdkafka/src/rdlist.h +444 -0
- package/deps/librdkafka/src/rdlog.c +89 -0
- package/deps/librdkafka/src/rdlog.h +41 -0
- package/deps/librdkafka/src/rdmap.c +508 -0
- package/deps/librdkafka/src/rdmap.h +492 -0
- package/deps/librdkafka/src/rdmurmur2.c +167 -0
- package/deps/librdkafka/src/rdmurmur2.h +35 -0
- package/deps/librdkafka/src/rdports.c +61 -0
- package/deps/librdkafka/src/rdports.h +38 -0
- package/deps/librdkafka/src/rdposix.h +250 -0
- package/deps/librdkafka/src/rdrand.c +151 -0
- package/deps/librdkafka/src/rdrand.h +72 -0
- package/deps/librdkafka/src/rdregex.c +156 -0
- package/deps/librdkafka/src/rdregex.h +43 -0
- package/deps/librdkafka/src/rdsignal.h +57 -0
- package/deps/librdkafka/src/rdstring.c +645 -0
- package/deps/librdkafka/src/rdstring.h +98 -0
- package/deps/librdkafka/src/rdsysqueue.h +404 -0
- package/deps/librdkafka/src/rdtime.h +356 -0
- package/deps/librdkafka/src/rdtypes.h +86 -0
- package/deps/librdkafka/src/rdunittest.c +564 -0
- package/deps/librdkafka/src/rdunittest.h +232 -0
- package/deps/librdkafka/src/rdunittest_acknowledge.c +1177 -0
- package/deps/librdkafka/src/rdunittest_fetcher.c +1211 -0
- package/deps/librdkafka/src/rdunittest_msgset_errors.c +1521 -0
- package/deps/librdkafka/src/rdvarint.c +134 -0
- package/deps/librdkafka/src/rdvarint.h +165 -0
- package/deps/librdkafka/src/rdwin32.h +382 -0
- package/deps/librdkafka/src/rdxxhash.c +1030 -0
- package/deps/librdkafka/src/rdxxhash.h +328 -0
- package/deps/librdkafka/src/regexp.c +1352 -0
- package/deps/librdkafka/src/regexp.h +41 -0
- package/deps/librdkafka/src/snappy.c +1866 -0
- package/deps/librdkafka/src/snappy.h +62 -0
- package/deps/librdkafka/src/snappy_compat.h +138 -0
- package/deps/librdkafka/src/statistics_schema.json +444 -0
- package/deps/librdkafka/src/tinycthread.c +932 -0
- package/deps/librdkafka/src/tinycthread.h +503 -0
- package/deps/librdkafka/src/tinycthread_extra.c +210 -0
- package/deps/librdkafka/src/tinycthread_extra.h +218 -0
- package/deps/librdkafka/src/win32_config.h +58 -0
- package/deps/librdkafka/src-cpp/CMakeLists.txt +90 -0
- package/deps/librdkafka/src-cpp/ConfImpl.cpp +84 -0
- package/deps/librdkafka/src-cpp/ConsumerImpl.cpp +244 -0
- package/deps/librdkafka/src-cpp/HandleImpl.cpp +436 -0
- package/deps/librdkafka/src-cpp/HeadersImpl.cpp +48 -0
- package/deps/librdkafka/src-cpp/KafkaConsumerImpl.cpp +296 -0
- package/deps/librdkafka/src-cpp/Makefile +55 -0
- package/deps/librdkafka/src-cpp/MessageImpl.cpp +38 -0
- package/deps/librdkafka/src-cpp/MetadataImpl.cpp +170 -0
- package/deps/librdkafka/src-cpp/ProducerImpl.cpp +197 -0
- package/deps/librdkafka/src-cpp/QueueImpl.cpp +70 -0
- package/deps/librdkafka/src-cpp/README.md +16 -0
- package/deps/librdkafka/src-cpp/RdKafka.cpp +59 -0
- package/deps/librdkafka/src-cpp/TopicImpl.cpp +124 -0
- package/deps/librdkafka/src-cpp/TopicPartitionImpl.cpp +57 -0
- package/deps/librdkafka/src-cpp/rdkafkacpp.h +3797 -0
- package/deps/librdkafka/src-cpp/rdkafkacpp_int.h +1641 -0
- package/deps/librdkafka/tests/0000-unittests.c +72 -0
- package/deps/librdkafka/tests/0001-multiobj.c +102 -0
- package/deps/librdkafka/tests/0002-unkpart.c +244 -0
- package/deps/librdkafka/tests/0003-msgmaxsize.c +173 -0
- package/deps/librdkafka/tests/0004-conf.c +934 -0
- package/deps/librdkafka/tests/0005-order.c +133 -0
- package/deps/librdkafka/tests/0006-symbols.c +163 -0
- package/deps/librdkafka/tests/0007-autotopic.c +136 -0
- package/deps/librdkafka/tests/0008-reqacks.c +179 -0
- package/deps/librdkafka/tests/0009-mock_cluster.c +272 -0
- package/deps/librdkafka/tests/0011-produce_batch.c +753 -0
- package/deps/librdkafka/tests/0012-produce_consume.c +537 -0
- package/deps/librdkafka/tests/0013-null-msgs.c +473 -0
- package/deps/librdkafka/tests/0014-reconsume-191.c +512 -0
- package/deps/librdkafka/tests/0015-offset_seeks.c +172 -0
- package/deps/librdkafka/tests/0016-client_swname.c +181 -0
- package/deps/librdkafka/tests/0017-compression.c +140 -0
- package/deps/librdkafka/tests/0018-cgrp_term.c +444 -0
- package/deps/librdkafka/tests/0019-list_groups.c +289 -0
- package/deps/librdkafka/tests/0020-destroy_hang.c +162 -0
- package/deps/librdkafka/tests/0021-rkt_destroy.c +72 -0
- package/deps/librdkafka/tests/0022-consume_batch.c +331 -0
- package/deps/librdkafka/tests/0025-timers.c +147 -0
- package/deps/librdkafka/tests/0026-consume_pause.c +547 -0
- package/deps/librdkafka/tests/0028-long_topicnames.c +79 -0
- package/deps/librdkafka/tests/0029-assign_offset.c +202 -0
- package/deps/librdkafka/tests/0030-offset_commit.c +589 -0
- package/deps/librdkafka/tests/0031-get_offsets.c +235 -0
- package/deps/librdkafka/tests/0033-regex_subscribe.c +536 -0
- package/deps/librdkafka/tests/0034-offset_reset.c +398 -0
- package/deps/librdkafka/tests/0035-api_version.c +73 -0
- package/deps/librdkafka/tests/0036-partial_fetch.c +87 -0
- package/deps/librdkafka/tests/0037-destroy_hang_local.c +85 -0
- package/deps/librdkafka/tests/0038-performance.c +121 -0
- package/deps/librdkafka/tests/0039-event.c +284 -0
- package/deps/librdkafka/tests/0040-io_event.c +257 -0
- package/deps/librdkafka/tests/0041-fetch_max_bytes.c +97 -0
- package/deps/librdkafka/tests/0042-many_topics.c +252 -0
- package/deps/librdkafka/tests/0043-no_connection.c +77 -0
- package/deps/librdkafka/tests/0044-partition_cnt.c +94 -0
- package/deps/librdkafka/tests/0045-subscribe_update.c +1010 -0
- package/deps/librdkafka/tests/0046-rkt_cache.c +65 -0
- package/deps/librdkafka/tests/0047-partial_buf_tmout.c +98 -0
- package/deps/librdkafka/tests/0048-partitioner.c +283 -0
- package/deps/librdkafka/tests/0049-consume_conn_close.c +162 -0
- package/deps/librdkafka/tests/0050-subscribe_adds.c +145 -0
- package/deps/librdkafka/tests/0051-assign_adds.c +126 -0
- package/deps/librdkafka/tests/0052-msg_timestamps.c +238 -0
- package/deps/librdkafka/tests/0053-stats_cb.cpp +527 -0
- package/deps/librdkafka/tests/0054-offset_time.cpp +236 -0
- package/deps/librdkafka/tests/0055-producer_latency.c +539 -0
- package/deps/librdkafka/tests/0056-balanced_group_mt.c +315 -0
- package/deps/librdkafka/tests/0057-invalid_topic.cpp +112 -0
- package/deps/librdkafka/tests/0058-log.cpp +123 -0
- package/deps/librdkafka/tests/0059-bsearch.cpp +241 -0
- package/deps/librdkafka/tests/0060-op_prio.cpp +163 -0
- package/deps/librdkafka/tests/0061-consumer_lag.cpp +295 -0
- package/deps/librdkafka/tests/0062-stats_event.c +126 -0
- package/deps/librdkafka/tests/0063-clusterid.cpp +180 -0
- package/deps/librdkafka/tests/0064-interceptors.c +529 -0
- package/deps/librdkafka/tests/0065-yield.cpp +140 -0
- package/deps/librdkafka/tests/0066-plugins.cpp +143 -0
- package/deps/librdkafka/tests/0067-empty_topic.cpp +151 -0
- package/deps/librdkafka/tests/0068-produce_timeout.c +136 -0
- package/deps/librdkafka/tests/0069-consumer_add_parts.c +119 -0
- package/deps/librdkafka/tests/0070-null_empty.cpp +197 -0
- package/deps/librdkafka/tests/0072-headers_ut.c +448 -0
- package/deps/librdkafka/tests/0073-headers.c +381 -0
- package/deps/librdkafka/tests/0074-producev.c +87 -0
- package/deps/librdkafka/tests/0075-retry.c +290 -0
- package/deps/librdkafka/tests/0076-produce_retry.c +452 -0
- package/deps/librdkafka/tests/0077-compaction.c +366 -0
- package/deps/librdkafka/tests/0078-c_from_cpp.cpp +96 -0
- package/deps/librdkafka/tests/0079-fork.c +93 -0
- package/deps/librdkafka/tests/0080-admin_ut.c +3095 -0
- package/deps/librdkafka/tests/0081-admin.c +5633 -0
- package/deps/librdkafka/tests/0082-fetch_max_bytes.cpp +137 -0
- package/deps/librdkafka/tests/0083-cb_event.c +233 -0
- package/deps/librdkafka/tests/0084-destroy_flags.c +208 -0
- package/deps/librdkafka/tests/0085-headers.cpp +392 -0
- package/deps/librdkafka/tests/0086-purge.c +368 -0
- package/deps/librdkafka/tests/0088-produce_metadata_timeout.c +162 -0
- package/deps/librdkafka/tests/0089-max_poll_interval.c +511 -0
- package/deps/librdkafka/tests/0090-idempotence.c +171 -0
- package/deps/librdkafka/tests/0091-max_poll_interval_timeout.c +295 -0
- package/deps/librdkafka/tests/0092-mixed_msgver.c +103 -0
- package/deps/librdkafka/tests/0093-holb.c +200 -0
- package/deps/librdkafka/tests/0094-idempotence_msg_timeout.c +231 -0
- package/deps/librdkafka/tests/0095-all_brokers_down.cpp +122 -0
- package/deps/librdkafka/tests/0097-ssl_verify.cpp +674 -0
- package/deps/librdkafka/tests/0098-consumer-txn.cpp +1218 -0
- package/deps/librdkafka/tests/0099-commit_metadata.c +194 -0
- package/deps/librdkafka/tests/0100-thread_interceptors.cpp +195 -0
- package/deps/librdkafka/tests/0101-fetch-from-follower.cpp +446 -0
- package/deps/librdkafka/tests/0102-static_group_rebalance.c +836 -0
- package/deps/librdkafka/tests/0103-transactions.c +1383 -0
- package/deps/librdkafka/tests/0104-fetch_from_follower_mock.c +625 -0
- package/deps/librdkafka/tests/0105-transactions_mock.c +3930 -0
- package/deps/librdkafka/tests/0106-cgrp_sess_timeout.c +318 -0
- package/deps/librdkafka/tests/0107-topic_recreate.c +259 -0
- package/deps/librdkafka/tests/0109-auto_create_topics.cpp +278 -0
- package/deps/librdkafka/tests/0110-batch_size.cpp +182 -0
- package/deps/librdkafka/tests/0111-delay_create_topics.cpp +127 -0
- package/deps/librdkafka/tests/0112-assign_unknown_part.c +87 -0
- package/deps/librdkafka/tests/0113-cooperative_rebalance.cpp +3473 -0
- package/deps/librdkafka/tests/0114-sticky_partitioning.cpp +176 -0
- package/deps/librdkafka/tests/0115-producer_auth.cpp +182 -0
- package/deps/librdkafka/tests/0116-kafkaconsumer_close.cpp +216 -0
- package/deps/librdkafka/tests/0117-mock_errors.c +331 -0
- package/deps/librdkafka/tests/0118-commit_rebalance.c +154 -0
- package/deps/librdkafka/tests/0119-consumer_auth.cpp +167 -0
- package/deps/librdkafka/tests/0120-asymmetric_subscription.c +185 -0
- package/deps/librdkafka/tests/0121-clusterid.c +115 -0
- package/deps/librdkafka/tests/0122-buffer_cleaning_after_rebalance.c +227 -0
- package/deps/librdkafka/tests/0123-connections_max_idle.c +98 -0
- package/deps/librdkafka/tests/0124-openssl_invalid_engine.c +69 -0
- package/deps/librdkafka/tests/0125-immediate_flush.c +144 -0
- package/deps/librdkafka/tests/0126-oauthbearer_oidc.c +1164 -0
- package/deps/librdkafka/tests/0127-fetch_queue_backoff.cpp +165 -0
- package/deps/librdkafka/tests/0128-sasl_callback_queue.cpp +217 -0
- package/deps/librdkafka/tests/0129-fetch_aborted_msgs.c +79 -0
- package/deps/librdkafka/tests/0130-store_offsets.c +178 -0
- package/deps/librdkafka/tests/0131-connect_timeout.c +81 -0
- package/deps/librdkafka/tests/0132-strategy_ordering.c +179 -0
- package/deps/librdkafka/tests/0133-ssl_keys.c +150 -0
- package/deps/librdkafka/tests/0134-ssl_provider.c +92 -0
- package/deps/librdkafka/tests/0135-sasl_credentials.cpp +279 -0
- package/deps/librdkafka/tests/0136-resolve_cb.c +181 -0
- package/deps/librdkafka/tests/0137-barrier_batch_consume.c +752 -0
- package/deps/librdkafka/tests/0138-admin_mock.c +281 -0
- package/deps/librdkafka/tests/0139-offset_validation_mock.c +950 -0
- package/deps/librdkafka/tests/0140-commit_metadata.cpp +108 -0
- package/deps/librdkafka/tests/0142-reauthentication.c +931 -0
- package/deps/librdkafka/tests/0143-exponential_backoff_mock.c +552 -0
- package/deps/librdkafka/tests/0144-idempotence_mock.c +373 -0
- package/deps/librdkafka/tests/0145-pause_resume_mock.c +119 -0
- package/deps/librdkafka/tests/0146-metadata_mock.c +505 -0
- package/deps/librdkafka/tests/0147-consumer_group_consumer_mock.c +1210 -0
- package/deps/librdkafka/tests/0148-offset_fetch_commit_error_mock.c +569 -0
- package/deps/librdkafka/tests/0149-broker-same-host-port.c +140 -0
- package/deps/librdkafka/tests/0150-telemetry_mock.c +651 -0
- package/deps/librdkafka/tests/0151-purge-brokers.c +566 -0
- package/deps/librdkafka/tests/0152-rebootstrap.c +59 -0
- package/deps/librdkafka/tests/0153-memberid.c +128 -0
- package/deps/librdkafka/tests/0155-share_group_heartbeat_mock.c +2695 -0
- package/deps/librdkafka/tests/0156-share_consumer_fetch_mock.c +1551 -0
- package/deps/librdkafka/tests/0157-share_consumer_ack_mock.c +1788 -0
- package/deps/librdkafka/tests/0158-share_consumer_transactions_mock.c +467 -0
- package/deps/librdkafka/tests/0170-share_consumer_subscription.c +2192 -0
- package/deps/librdkafka/tests/0171-share_consumer_consume.c +2150 -0
- package/deps/librdkafka/tests/0172-share_consumer_acknowledge.c +1919 -0
- package/deps/librdkafka/tests/0173-share_consumer_commit_async.c +2735 -0
- package/deps/librdkafka/tests/0174-share_consumer_concurrency.c +1434 -0
- package/deps/librdkafka/tests/0175-share_consumer_groups.c +574 -0
- package/deps/librdkafka/tests/0176-share_consumer_commit_sync.c +2986 -0
- package/deps/librdkafka/tests/0177-share_consumer_transactions.c +1815 -0
- package/deps/librdkafka/tests/0178-share_consumer_close.c +2102 -0
- package/deps/librdkafka/tests/0179-share_consumer_destroy.c +2482 -0
- package/deps/librdkafka/tests/0180-share_consumer_config.c +1677 -0
- package/deps/librdkafka/tests/0181-share_consumer_topic_delete.c +354 -0
- package/deps/librdkafka/tests/0182-share_consumer_error_handling_mock.c +3358 -0
- package/deps/librdkafka/tests/0183-share_consumer_leader_change_mock.c +1234 -0
- package/deps/librdkafka/tests/0184-share_consumer_topic_recreate.c +1356 -0
- package/deps/librdkafka/tests/0185-share_consumer_max_poll_interval.c +557 -0
- package/deps/librdkafka/tests/0186-share_consumer_fatal_error.c +360 -0
- package/deps/librdkafka/tests/0190-share_consumer_telemetry.c +387 -0
- package/deps/librdkafka/tests/1000-unktopic.c +164 -0
- package/deps/librdkafka/tests/8000-idle.cpp +60 -0
- package/deps/librdkafka/tests/8001-fetch_from_follower_mock_manual.c +113 -0
- package/deps/librdkafka/tests/CMakeLists.txt +193 -0
- package/deps/librdkafka/tests/LibrdkafkaTestApp.py +291 -0
- package/deps/librdkafka/tests/Makefile +182 -0
- package/deps/librdkafka/tests/README.md +514 -0
- package/deps/librdkafka/tests/autotest.sh +33 -0
- package/deps/librdkafka/tests/backtrace.gdb +30 -0
- package/deps/librdkafka/tests/broker_version_tests.py +315 -0
- package/deps/librdkafka/tests/buildbox.sh +17 -0
- package/deps/librdkafka/tests/cleanup-checker-tests.sh +20 -0
- package/deps/librdkafka/tests/cluster_testing.py +203 -0
- package/deps/librdkafka/tests/delete-test-topics.sh +56 -0
- package/deps/librdkafka/tests/fixtures/oauthbearer/jwt_assertion_template.json +10 -0
- package/deps/librdkafka/tests/fixtures/ssl/Makefile +8 -0
- package/deps/librdkafka/tests/fixtures/ssl/README.md +13 -0
- package/deps/librdkafka/tests/fixtures/ssl/client.keystore.intermediate.p12 +0 -0
- package/deps/librdkafka/tests/fixtures/ssl/client.keystore.p12 +0 -0
- package/deps/librdkafka/tests/fixtures/ssl/client2.certificate.intermediate.pem +72 -0
- package/deps/librdkafka/tests/fixtures/ssl/client2.certificate.pem +50 -0
- package/deps/librdkafka/tests/fixtures/ssl/client2.intermediate.key +46 -0
- package/deps/librdkafka/tests/fixtures/ssl/client2.key +46 -0
- package/deps/librdkafka/tests/fixtures/ssl/create_keys.sh +168 -0
- package/deps/librdkafka/tests/fuzzers/Makefile +12 -0
- package/deps/librdkafka/tests/fuzzers/README.md +31 -0
- package/deps/librdkafka/tests/fuzzers/fuzz_regex.c +74 -0
- package/deps/librdkafka/tests/fuzzers/helpers.h +90 -0
- package/deps/librdkafka/tests/gen-ssl-certs.sh +165 -0
- package/deps/librdkafka/tests/interactive_broker_version.py +170 -0
- package/deps/librdkafka/tests/interceptor_test/CMakeLists.txt +16 -0
- package/deps/librdkafka/tests/interceptor_test/Makefile +22 -0
- package/deps/librdkafka/tests/interceptor_test/interceptor_test.c +314 -0
- package/deps/librdkafka/tests/interceptor_test/interceptor_test.h +54 -0
- package/deps/librdkafka/tests/java/IncrementalRebalanceCli.java +97 -0
- package/deps/librdkafka/tests/java/Makefile +13 -0
- package/deps/librdkafka/tests/java/Murmur2Cli.java +46 -0
- package/deps/librdkafka/tests/java/README.md +14 -0
- package/deps/librdkafka/tests/java/TransactionProducerCli.java +162 -0
- package/deps/librdkafka/tests/java/run-class.sh +11 -0
- package/deps/librdkafka/tests/librdkafka.suppressions +483 -0
- package/deps/librdkafka/tests/lz4_manual_test.sh +59 -0
- package/deps/librdkafka/tests/multi-broker-version-test.sh +50 -0
- package/deps/librdkafka/tests/parse-refcnt.sh +43 -0
- package/deps/librdkafka/tests/performance_plot.py +115 -0
- package/deps/librdkafka/tests/plugin_test/Makefile +19 -0
- package/deps/librdkafka/tests/plugin_test/plugin_test.c +58 -0
- package/deps/librdkafka/tests/requirements.txt +2 -0
- package/deps/librdkafka/tests/run-all-tests.sh +81 -0
- package/deps/librdkafka/tests/run-consumer-tests.sh +16 -0
- package/deps/librdkafka/tests/run-producer-tests.sh +16 -0
- package/deps/librdkafka/tests/run-test-batches.py +157 -0
- package/deps/librdkafka/tests/run-test.sh +141 -0
- package/deps/librdkafka/tests/rusage.c +249 -0
- package/deps/librdkafka/tests/sasl_test.py +289 -0
- package/deps/librdkafka/tests/scenarios/README.md +6 -0
- package/deps/librdkafka/tests/scenarios/ak23.json +6 -0
- package/deps/librdkafka/tests/scenarios/default.json +5 -0
- package/deps/librdkafka/tests/scenarios/noautocreate.json +5 -0
- package/deps/librdkafka/tests/share_telemetry_verify/share-otel-collector-config.yaml +28 -0
- package/deps/librdkafka/tests/sockem.c +801 -0
- package/deps/librdkafka/tests/sockem.h +85 -0
- package/deps/librdkafka/tests/sockem_ctrl.c +145 -0
- package/deps/librdkafka/tests/sockem_ctrl.h +61 -0
- package/deps/librdkafka/tests/test.c +8376 -0
- package/deps/librdkafka/tests/test.conf.example +27 -0
- package/deps/librdkafka/tests/test.h +1150 -0
- package/deps/librdkafka/tests/testcpp.cpp +131 -0
- package/deps/librdkafka/tests/testcpp.h +388 -0
- package/deps/librdkafka/tests/testshared.h +416 -0
- package/deps/librdkafka/tests/tools/README.md +4 -0
- package/deps/librdkafka/tests/tools/stats/README.md +21 -0
- package/deps/librdkafka/tests/tools/stats/filter.jq +42 -0
- package/deps/librdkafka/tests/tools/stats/graph.py +150 -0
- package/deps/librdkafka/tests/tools/stats/requirements.txt +3 -0
- package/deps/librdkafka/tests/tools/stats/to_csv.py +124 -0
- package/deps/librdkafka/tests/trivup/trivup-0.15.0.tar.gz +0 -0
- package/deps/librdkafka/tests/until-fail.sh +87 -0
- package/deps/librdkafka/tests/xxxx-assign_partition.c +122 -0
- package/deps/librdkafka/tests/xxxx-metadata.cpp +159 -0
- package/deps/librdkafka/vcpkg.json +23 -0
- package/deps/librdkafka/win32/README.md +5 -0
- package/deps/librdkafka/win32/build-package.bat +3 -0
- package/deps/librdkafka/win32/build.bat +19 -0
- package/deps/librdkafka/win32/common.vcxproj +84 -0
- package/deps/librdkafka/win32/interceptor_test/interceptor_test.vcxproj +87 -0
- package/deps/librdkafka/win32/librdkafka.autopkg.template +54 -0
- package/deps/librdkafka/win32/librdkafka.master.testing.targets +13 -0
- package/deps/librdkafka/win32/librdkafka.sln +226 -0
- package/deps/librdkafka/win32/librdkafka.vcxproj +282 -0
- package/deps/librdkafka/win32/librdkafkacpp/librdkafkacpp.vcxproj +104 -0
- package/deps/librdkafka/win32/msbuild.ps1 +58 -0
- package/deps/librdkafka/win32/openssl_engine_example/openssl_engine_example.vcxproj +132 -0
- package/deps/librdkafka/win32/package-zip.ps1 +46 -0
- package/deps/librdkafka/win32/packages/repositories.config +4 -0
- package/deps/librdkafka/win32/push-package.bat +4 -0
- package/deps/librdkafka/win32/rdkafka_complex_consumer_example_cpp/rdkafka_complex_consumer_example_cpp.vcxproj +67 -0
- package/deps/librdkafka/win32/rdkafka_example/rdkafka_example.vcxproj +97 -0
- package/deps/librdkafka/win32/rdkafka_performance/rdkafka_performance.vcxproj +97 -0
- package/deps/librdkafka/win32/setup-msys2.ps1 +47 -0
- package/deps/librdkafka/win32/setup-vcpkg.ps1 +34 -0
- package/deps/librdkafka/win32/tests/test.conf.example +25 -0
- package/deps/librdkafka/win32/tests/tests.vcxproj +278 -0
- package/deps/librdkafka/win32/win_ssl_cert_store/win_ssl_cert_store.vcxproj +132 -0
- package/deps/librdkafka/win32/wingetopt.c +564 -0
- package/deps/librdkafka/win32/wingetopt.h +101 -0
- package/deps/librdkafka/win32/wintime.h +33 -0
- package/deps/librdkafka.gyp +83 -0
- package/deps/windows-install.py +80 -0
- package/index.d.ts +3 -0
- package/lib/admin.js +732 -0
- package/lib/client.js +679 -0
- package/lib/error.js +501 -0
- package/lib/index.js +8 -0
- package/lib/kafka-consumer-stream.js +399 -0
- package/lib/kafka-consumer.js +798 -0
- package/lib/kafkajs/_admin.js +1052 -0
- package/lib/kafkajs/_common.js +887 -0
- package/lib/kafkajs/_consumer.js +2156 -0
- package/lib/kafkajs/_consumer_cache.js +310 -0
- package/lib/kafkajs/_error.js +298 -0
- package/lib/kafkajs/_kafka.js +125 -0
- package/lib/kafkajs/_linked-list.js +220 -0
- package/lib/kafkajs/_producer.js +863 -0
- package/lib/kafkajs/index.js +5 -0
- package/lib/producer/high-level-producer.js +400 -0
- package/lib/producer-stream.js +337 -0
- package/lib/producer.js +405 -0
- package/lib/rdkafka.js +44 -0
- package/lib/tools/ref-counter.js +53 -0
- package/lib/topic-partition.js +91 -0
- package/lib/topic.js +43 -0
- package/lib/util.js +55 -0
- package/librdkafka.js +12 -0
- package/package.json +85 -0
- package/shim/source_location +54 -0
- package/src/admin.cc +1597 -0
- package/src/admin.h +112 -0
- package/src/binding.cc +76 -0
- package/src/binding.h +25 -0
- package/src/callbacks.cc +676 -0
- package/src/callbacks.h +302 -0
- package/src/common.cc +1628 -0
- package/src/common.h +192 -0
- package/src/config.cc +219 -0
- package/src/config.h +61 -0
- package/src/connection.cc +703 -0
- package/src/connection.h +117 -0
- package/src/errors.cc +119 -0
- package/src/errors.h +60 -0
- package/src/kafka-consumer.cc +1539 -0
- package/src/kafka-consumer.h +147 -0
- package/src/producer.cc +917 -0
- package/src/producer.h +124 -0
- package/src/topic.cc +173 -0
- package/src/topic.h +54 -0
- package/src/workers.cc +1714 -0
- package/src/workers.h +690 -0
- package/types/config.d.ts +1269 -0
- package/types/errors.d.ts +373 -0
- package/types/kafkajs.d.ts +568 -0
- package/types/rdkafka.d.ts +571 -0
- package/util/configure.js +32 -0
- package/util/generate-docs.sh +18 -0
- package/util/get-env.js +6 -0
- package/util/test-compile.js +11 -0
- package/util/test-producer-delivery.js +100 -0
|
@@ -0,0 +1,2156 @@
|
|
|
1
|
+
const LibrdKafkaError = require('../error');
|
|
2
|
+
const { Admin } = require('./_admin');
|
|
3
|
+
const error = require('./_error');
|
|
4
|
+
const RdKafka = require('../rdkafka');
|
|
5
|
+
const {
|
|
6
|
+
kafkaJSToRdKafkaConfig,
|
|
7
|
+
topicPartitionOffsetToRdKafka,
|
|
8
|
+
topicPartitionOffsetMetadataToRdKafka,
|
|
9
|
+
topicPartitionOffsetMetadataToKafkaJS,
|
|
10
|
+
createBindingMessageMetadata,
|
|
11
|
+
createKafkaJsErrorFromLibRdKafkaError,
|
|
12
|
+
notImplemented,
|
|
13
|
+
loggerTrampoline,
|
|
14
|
+
DefaultLogger,
|
|
15
|
+
CompatibilityErrorMessages,
|
|
16
|
+
severityToLogLevel,
|
|
17
|
+
checkAllowedKeys,
|
|
18
|
+
logLevel,
|
|
19
|
+
Lock,
|
|
20
|
+
partitionKey,
|
|
21
|
+
DeferredPromise,
|
|
22
|
+
Timer
|
|
23
|
+
} = require('./_common');
|
|
24
|
+
const { Buffer } = require('buffer');
|
|
25
|
+
const MessageCache = require('./_consumer_cache');
|
|
26
|
+
const { hrtime } = require('process');
|
|
27
|
+
|
|
28
|
+
const ConsumerState = Object.freeze({
|
|
29
|
+
INIT: 0,
|
|
30
|
+
CONNECTING: 1,
|
|
31
|
+
CONNECTED: 2,
|
|
32
|
+
DISCONNECTING: 3,
|
|
33
|
+
DISCONNECTED: 4,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* A list of supported partition assignor types.
|
|
38
|
+
* @enum {string}
|
|
39
|
+
* @readonly
|
|
40
|
+
* @memberof KafkaJS
|
|
41
|
+
*/
|
|
42
|
+
const PartitionAssigners = {
|
|
43
|
+
roundRobin: 'roundrobin',
|
|
44
|
+
range: 'range',
|
|
45
|
+
cooperativeSticky: 'cooperative-sticky',
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Consumer for reading messages from Kafka (promise-based, async API).
|
|
50
|
+
*
|
|
51
|
+
* The consumer allows reading messages from the Kafka cluster, and provides
|
|
52
|
+
* methods to configure and control various aspects of that. This class should
|
|
53
|
+
* not be instantiated directly, and rather, an instance of
|
|
54
|
+
* [Kafka]{@link KafkaJS.Kafka} should be used.
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* const { Kafka } = require('@confluentinc/kafka-javascript');
|
|
58
|
+
* const kafka = new Kafka({ 'bootstrap.servers': 'localhost:9092' });
|
|
59
|
+
* const consumer = kafka.consumer({ 'group.id': 'test-group' });
|
|
60
|
+
* await consumer.connect();
|
|
61
|
+
* await consumer.subscribe({ topics: ["test-topic"] });
|
|
62
|
+
* consumer.run({
|
|
63
|
+
* eachMessage: async ({ topic, partition, message }) => { console.log({topic, partition, message}); }
|
|
64
|
+
* });
|
|
65
|
+
* @memberof KafkaJS
|
|
66
|
+
* @see [Consumer example]{@link https://github.com/confluentinc/confluent-kafka-javascript/blob/master/examples/consumer.js}
|
|
67
|
+
*/
|
|
68
|
+
class Consumer {
|
|
69
|
+
/**
|
|
70
|
+
* The config supplied by the user.
|
|
71
|
+
* @type {import("../../types/kafkajs").ConsumerConstructorConfig|null}
|
|
72
|
+
*/
|
|
73
|
+
#userConfig = null;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* The config realized after processing any compatibility options.
|
|
77
|
+
* @type {import("../../types/config").ConsumerGlobalConfig|null}
|
|
78
|
+
*/
|
|
79
|
+
#internalConfig = null;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* internalClient is the node-rdkafka client used by the API.
|
|
83
|
+
* @type {import("../rdkafka").Consumer|null}
|
|
84
|
+
*/
|
|
85
|
+
#internalClient = null;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* connectPromiseFunc is the set of promise functions used to resolve/reject the connect() promise.
|
|
89
|
+
* @type {{resolve: Function, reject: Function}|{}}
|
|
90
|
+
*/
|
|
91
|
+
#connectPromiseFunc = {};
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Stores the first error encountered while connecting (if any). This is what we
|
|
95
|
+
* want to reject with.
|
|
96
|
+
* @type {Error|null}
|
|
97
|
+
*/
|
|
98
|
+
#connectionError = null;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* state is the current state of the consumer.
|
|
102
|
+
* @type {ConsumerState}
|
|
103
|
+
*/
|
|
104
|
+
#state = ConsumerState.INIT;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Contains a mapping of topic+partition to an offset that the user wants to seek to.
|
|
108
|
+
* The keys are of the type "<topic>|<partition>".
|
|
109
|
+
* @type {Map<string, number>}
|
|
110
|
+
*/
|
|
111
|
+
#pendingSeeks = new Map();
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Stores the map of paused partitions keys to TopicPartition objects.
|
|
115
|
+
* @type {Map<string, TopicPartition>}
|
|
116
|
+
*/
|
|
117
|
+
#pausedPartitions = new Map();
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Contains a list of stored topics/regexes that the user has subscribed to.
|
|
121
|
+
* @type {Array<string|RegExp>}
|
|
122
|
+
*/
|
|
123
|
+
#storedSubscriptions = [];
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* A logger for the consumer.
|
|
127
|
+
* @type {import("../../types/kafkajs").Logger}
|
|
128
|
+
*/
|
|
129
|
+
#logger = new DefaultLogger();
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* A map of topic+partition to the offset that was last consumed.
|
|
133
|
+
* The keys are of the type "<topic>|<partition>".
|
|
134
|
+
* @type {Map<string, number>}
|
|
135
|
+
*/
|
|
136
|
+
#lastConsumedOffsets = new Map();
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* A lock for consuming and disconnecting.
|
|
140
|
+
* This lock should be held whenever we want to change the state from CONNECTED to any state other than CONNECTED.
|
|
141
|
+
* In practical terms, this lock is held whenever we're consuming a message, or disconnecting.
|
|
142
|
+
* @type {Lock}
|
|
143
|
+
*/
|
|
144
|
+
#lock = new Lock();
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Whether the consumer is running.
|
|
148
|
+
* @type {boolean}
|
|
149
|
+
*/
|
|
150
|
+
#running = false;
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* The message cache for KafkaJS compatibility mode.
|
|
154
|
+
* @type {MessageCache|null}
|
|
155
|
+
*/
|
|
156
|
+
#messageCache = null;
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* The maximum size of the message cache.
|
|
160
|
+
* Will be adjusted dynamically.
|
|
161
|
+
*/
|
|
162
|
+
#messageCacheMaxSize = 1;
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Whether the user has enabled manual offset management (commits).
|
|
166
|
+
*/
|
|
167
|
+
#autoCommit = false;
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Signals an intent to disconnect the consumer.
|
|
171
|
+
*/
|
|
172
|
+
#disconnectStarted = false;
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Number of partitions owned by the consumer.
|
|
176
|
+
* @note This value may or may not be completely accurate, it's more so a hint for spawning concurrent workers.
|
|
177
|
+
*/
|
|
178
|
+
#partitionCount = 0;
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Maximum batch size passed in eachBatch calls.
|
|
182
|
+
*/
|
|
183
|
+
#maxBatchSize = 32;
|
|
184
|
+
/**
|
|
185
|
+
* Maximum batch size multiplied by #concurrency.
|
|
186
|
+
*/
|
|
187
|
+
#maxBatchesSize = 32;
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Maximum cache size in milliseconds per worker.
|
|
191
|
+
* Based on the consumer rate estimated through the eachMessage/eachBatch calls.
|
|
192
|
+
*
|
|
193
|
+
* @default 1500
|
|
194
|
+
*/
|
|
195
|
+
#maxCacheSizePerWorkerMs = 1500;
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Whether worker termination has been scheduled.
|
|
199
|
+
*/
|
|
200
|
+
#workerTerminationScheduled = new DeferredPromise();
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* The worker functions currently running in the consumer.
|
|
204
|
+
*/
|
|
205
|
+
#workers = [];
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* The number of partitions to consume concurrently as set by the user, or 1.
|
|
209
|
+
*/
|
|
210
|
+
#concurrency = 1;
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Promise that resolves together with last in progress fetch.
|
|
214
|
+
* It's set to null when no fetch is in progress.
|
|
215
|
+
*/
|
|
216
|
+
#fetchInProgress;
|
|
217
|
+
/**
|
|
218
|
+
* Are we waiting for the queue to be non-empty?
|
|
219
|
+
*/
|
|
220
|
+
#nonEmpty = null;
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Whether any rebalance callback is in progress.
|
|
224
|
+
* That can last more than the fetch itself given it's not awaited.
|
|
225
|
+
* So we await it after fetch is done.
|
|
226
|
+
*/
|
|
227
|
+
#rebalanceCbInProgress;
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Promise that is resolved on fetch to restart max poll interval timer.
|
|
231
|
+
*/
|
|
232
|
+
#maxPollIntervalRestart = new DeferredPromise();
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Initial default value for max poll interval.
|
|
236
|
+
*/
|
|
237
|
+
#maxPollIntervalMs = 300000;
|
|
238
|
+
/**
|
|
239
|
+
* Maximum interval between poll calls from workers,
|
|
240
|
+
* if exceeded, the cache is cleared so a new poll can be made
|
|
241
|
+
* before reaching the max poll interval.
|
|
242
|
+
* It's set to max poll interval value.
|
|
243
|
+
*/
|
|
244
|
+
#cacheExpirationTimeoutMs = 300000;
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Last fetch real time clock in nanoseconds.
|
|
248
|
+
*/
|
|
249
|
+
#lastFetchClockNs = 0n;
|
|
250
|
+
/**
|
|
251
|
+
* Last number of messages fetched.
|
|
252
|
+
*/
|
|
253
|
+
#lastFetchedMessageCnt = 0n;
|
|
254
|
+
/**
|
|
255
|
+
* Last fetch concurrency used.
|
|
256
|
+
*/
|
|
257
|
+
#lastFetchedConcurrency = 0n;
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* List of pending operations to be executed after
|
|
261
|
+
* all workers reach the end of their current processing.
|
|
262
|
+
*/
|
|
263
|
+
#pendingOperations = [];
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Maps topic-partition key to the batch payload for marking staleness.
|
|
267
|
+
*
|
|
268
|
+
* Only used with eachBatch.
|
|
269
|
+
* NOTE: given that size of this map will never exceed #concurrency, a
|
|
270
|
+
* linear search might actually be faster over what will generally be <10 elems.
|
|
271
|
+
* But a map makes conceptual sense. Revise at a later point if needed.
|
|
272
|
+
*/
|
|
273
|
+
#topicPartitionToBatchPayload = new Map();
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* The client name used by the consumer for logging - determined by librdkafka
|
|
277
|
+
* using a combination of clientId and an integer.
|
|
278
|
+
* @type {string|undefined}
|
|
279
|
+
*/
|
|
280
|
+
#clientName = undefined;
|
|
281
|
+
|
|
282
|
+
// Convenience function to create the metadata object needed for logging.
|
|
283
|
+
#createConsumerBindingMessageMetadata() {
|
|
284
|
+
return createBindingMessageMetadata(this.#clientName);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* This method should not be used directly. See {@link KafkaJS.Consumer}.
|
|
289
|
+
* @constructor
|
|
290
|
+
* @param {import("../../types/kafkajs").ConsumerConfig} kJSConfig
|
|
291
|
+
*/
|
|
292
|
+
constructor(kJSConfig) {
|
|
293
|
+
this.#userConfig = kJSConfig;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* @returns {import("../rdkafka").Consumer | null} the internal node-rdkafka client.
|
|
298
|
+
* @note only for internal use and subject to API changes.
|
|
299
|
+
* @private
|
|
300
|
+
*/
|
|
301
|
+
_getInternalClient() {
|
|
302
|
+
return this.#internalClient;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Create a new admin client using the underlying connections of the consumer.
|
|
307
|
+
*
|
|
308
|
+
* The consumer must be connected before connecting the resulting admin client.
|
|
309
|
+
* The usage of the admin client is limited to the lifetime of the consumer.
|
|
310
|
+
* The consumer's logger is shared with the admin client.
|
|
311
|
+
* @returns {KafkaJS.Admin}
|
|
312
|
+
*/
|
|
313
|
+
dependentAdmin() {
|
|
314
|
+
return new Admin(null, this);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
#config() {
|
|
318
|
+
if (!this.#internalConfig)
|
|
319
|
+
this.#internalConfig = this.#finalizedConfig();
|
|
320
|
+
return this.#internalConfig;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Clear the message cache, and reset to stored positions.
|
|
325
|
+
*
|
|
326
|
+
* @param {Array<{topic: string, partition: number}>|null} topicPartitions to clear the cache for, if null, then clear all assigned.
|
|
327
|
+
* @private
|
|
328
|
+
*/
|
|
329
|
+
async #clearCacheAndResetPositions() {
|
|
330
|
+
/* Seek to stored offset for each topic partition. It's possible that we've
|
|
331
|
+
* consumed messages upto N from the internalClient, but the user has stale'd the cache
|
|
332
|
+
* after consuming just k (< N) messages. We seek back to last consumed offset + 1. */
|
|
333
|
+
this.#messageCache.clear();
|
|
334
|
+
const clearPartitions = this.assignment();
|
|
335
|
+
const seeks = [];
|
|
336
|
+
for (const topicPartition of clearPartitions) {
|
|
337
|
+
const key = partitionKey(topicPartition);
|
|
338
|
+
if (!this.#lastConsumedOffsets.has(key))
|
|
339
|
+
continue;
|
|
340
|
+
|
|
341
|
+
const lastConsumedOffsets = this.#lastConsumedOffsets.get(key);
|
|
342
|
+
const topicPartitionOffsets = [
|
|
343
|
+
{
|
|
344
|
+
topic: topicPartition.topic,
|
|
345
|
+
partition: topicPartition.partition,
|
|
346
|
+
offset: lastConsumedOffsets.offset,
|
|
347
|
+
leaderEpoch: lastConsumedOffsets.leaderEpoch,
|
|
348
|
+
}
|
|
349
|
+
];
|
|
350
|
+
seeks.push(this.#seekInternal(topicPartitionOffsets));
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
await Promise.allSettled(seeks);
|
|
354
|
+
try {
|
|
355
|
+
await Promise.all(seeks);
|
|
356
|
+
} catch (err) {
|
|
357
|
+
/* TODO: we should cry more about this and render the consumer unusable. */
|
|
358
|
+
this.#logger.error(`Seek error. This is effectively a fatal error: ${err.stack}`);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
#unassign(assignment) {
|
|
363
|
+
if (this.#internalClient.rebalanceProtocol() === "EAGER") {
|
|
364
|
+
this.#internalClient.unassign();
|
|
365
|
+
this.#messageCache.clear();
|
|
366
|
+
this.#partitionCount = 0;
|
|
367
|
+
} else {
|
|
368
|
+
this.#internalClient.incrementalUnassign(assignment);
|
|
369
|
+
this.#messageCache.markStale(assignment);
|
|
370
|
+
this.#partitionCount -= assignment.length;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Used as a trampoline to the user's rebalance listener, if any.
|
|
376
|
+
* @param {Error} err - error in rebalance
|
|
377
|
+
* @param {import("../../types").TopicPartition[]} assignment
|
|
378
|
+
* @private
|
|
379
|
+
*/
|
|
380
|
+
async #rebalanceCallback(err, assignment) {
|
|
381
|
+
const isLost = this.#internalClient.assignmentLost();
|
|
382
|
+
let assignmentFnCalled = false;
|
|
383
|
+
this.#logger.info(
|
|
384
|
+
`Received rebalance event with message: '${err.message}' and ${assignment.length} partition(s), isLost: ${isLost}`,
|
|
385
|
+
this.#createConsumerBindingMessageMetadata());
|
|
386
|
+
/* We allow the user to modify the assignment by returning it. If a truthy
|
|
387
|
+
* value is returned, we use that and do not apply any pending seeks to it either.
|
|
388
|
+
* The user can alternatively use the assignmentFns argument.
|
|
389
|
+
* Precedence is given to the calling of functions within assignmentFns. */
|
|
390
|
+
let assignmentModified = false;
|
|
391
|
+
|
|
392
|
+
const assignmentFn = (userAssignment) => {
|
|
393
|
+
if (assignmentFnCalled)
|
|
394
|
+
return;
|
|
395
|
+
assignmentFnCalled = true;
|
|
396
|
+
|
|
397
|
+
if (this.#internalClient.rebalanceProtocol() === "EAGER") {
|
|
398
|
+
this.#internalClient.assign(userAssignment);
|
|
399
|
+
this.#partitionCount = userAssignment.length;
|
|
400
|
+
} else {
|
|
401
|
+
this.#internalClient.incrementalAssign(userAssignment);
|
|
402
|
+
this.#partitionCount += userAssignment.length;
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
const unassignmentFn = (userAssignment) => {
|
|
407
|
+
if (assignmentFnCalled)
|
|
408
|
+
return;
|
|
409
|
+
|
|
410
|
+
assignmentFnCalled = true;
|
|
411
|
+
if (this.#disconnectStarted)
|
|
412
|
+
this.#unassign(userAssignment);
|
|
413
|
+
else
|
|
414
|
+
this.#addPendingOperation(() => this.#unassign(userAssignment));
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
try {
|
|
418
|
+
err = LibrdKafkaError.create(err);
|
|
419
|
+
const userSpecifiedRebalanceCb = this.#userConfig['rebalance_cb'];
|
|
420
|
+
|
|
421
|
+
if (typeof userSpecifiedRebalanceCb === 'function') {
|
|
422
|
+
const assignmentFns = {
|
|
423
|
+
assign: assignmentFn,
|
|
424
|
+
unassign: unassignmentFn,
|
|
425
|
+
assignmentLost: () => isLost,
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
let alternateAssignment = null;
|
|
429
|
+
try {
|
|
430
|
+
alternateAssignment = await userSpecifiedRebalanceCb(err, assignment, assignmentFns);
|
|
431
|
+
} catch (e) {
|
|
432
|
+
this.#logger.error(`Error from user's rebalance callback: ${e.stack}, `+
|
|
433
|
+
'continuing with the default rebalance behavior.');
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
if (alternateAssignment) {
|
|
437
|
+
assignment = alternateAssignment;
|
|
438
|
+
assignmentModified = true;
|
|
439
|
+
}
|
|
440
|
+
} else if (err.code !== LibrdKafkaError.codes.ERR__ASSIGN_PARTITIONS && err.code !== LibrdKafkaError.codes.ERR__REVOKE_PARTITIONS) {
|
|
441
|
+
throw new Error(`Unexpected rebalance_cb error code ${err.code}`);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
} finally {
|
|
445
|
+
/* Emit the event */
|
|
446
|
+
this.#internalClient.emit('rebalance', err, assignment);
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* We never need to clear the cache in case of a rebalance.
|
|
450
|
+
* This is because rebalances are triggered ONLY when we call the consume()
|
|
451
|
+
* method of the internalClient.
|
|
452
|
+
* In case consume() is being called, we've already either consumed all the messages
|
|
453
|
+
* in the cache, or timed out (this.#messageCache.cachedTime is going to exceed max.poll.interval)
|
|
454
|
+
* and marked the cache stale. This means that the cache is always expired when a rebalance
|
|
455
|
+
* is triggered.
|
|
456
|
+
* This is applicable both for incremental and non-incremental rebalances.
|
|
457
|
+
* Multiple consume()s cannot be called together, too, because we make sure that only
|
|
458
|
+
* one worker is calling into the internal consumer at a time.
|
|
459
|
+
*/
|
|
460
|
+
try {
|
|
461
|
+
|
|
462
|
+
if (err.code === LibrdKafkaError.codes.ERR__ASSIGN_PARTITIONS) {
|
|
463
|
+
|
|
464
|
+
const checkPendingSeeks = this.#pendingSeeks.size !== 0;
|
|
465
|
+
if (checkPendingSeeks && !assignmentModified && !assignmentFnCalled)
|
|
466
|
+
assignment = this.#assignAsPerSeekedOffsets(assignment);
|
|
467
|
+
|
|
468
|
+
assignmentFn(assignment);
|
|
469
|
+
|
|
470
|
+
} else {
|
|
471
|
+
unassignmentFn(assignment);
|
|
472
|
+
}
|
|
473
|
+
} catch (e) {
|
|
474
|
+
// Ignore exceptions if we are not connected
|
|
475
|
+
if (this.#internalClient.isConnected()) {
|
|
476
|
+
this.#internalClient.emit('rebalance.error', e);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Schedule worker termination here, in case the number of workers is not equal to the target concurrency.
|
|
482
|
+
* We need to do this so we will respawn workers with the correct concurrency count.
|
|
483
|
+
*/
|
|
484
|
+
const workersToSpawn = Math.max(1, Math.min(this.#concurrency, this.#partitionCount));
|
|
485
|
+
if (workersToSpawn !== this.#workers.length) {
|
|
486
|
+
this.#resolveWorkerTerminationScheduled();
|
|
487
|
+
/* We don't need to await the workers here. We are OK if the termination and respawning
|
|
488
|
+
* occurs later, since even if we have a few more or few less workers for a while, it's
|
|
489
|
+
* not a big deal. */
|
|
490
|
+
}
|
|
491
|
+
this.#rebalanceCbInProgress.resolve();
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
#kafkaJSToConsumerConfig(kjsConfig, isClassicProtocol = true) {
|
|
496
|
+
if (!kjsConfig || Object.keys(kjsConfig).length === 0) {
|
|
497
|
+
return {};
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
const disallowedKey = checkAllowedKeys('consumer', kjsConfig);
|
|
501
|
+
if (disallowedKey !== null) {
|
|
502
|
+
throw new error.KafkaJSError(CompatibilityErrorMessages.unsupportedKey(disallowedKey),
|
|
503
|
+
{ code: error.ErrorCodes.ERR__INVALID_ARG });
|
|
504
|
+
}
|
|
505
|
+
const rdKafkaConfig = kafkaJSToRdKafkaConfig(kjsConfig);
|
|
506
|
+
|
|
507
|
+
this.#logger = new DefaultLogger();
|
|
508
|
+
|
|
509
|
+
/* Consumer specific configuration */
|
|
510
|
+
if (Object.hasOwn(kjsConfig, 'groupId')) {
|
|
511
|
+
rdKafkaConfig['group.id'] = kjsConfig.groupId;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
if (Object.hasOwn(kjsConfig, 'partitionAssigners')) {
|
|
515
|
+
kjsConfig.partitionAssignors = kjsConfig.partitionAssigners;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
if (Object.hasOwn(kjsConfig, 'partitionAssignors')) {
|
|
519
|
+
if (!isClassicProtocol) {
|
|
520
|
+
throw new error.KafkaJSError(
|
|
521
|
+
"partitionAssignors is not supported when group.protocol is not 'classic'.",
|
|
522
|
+
{ code: error.ErrorCodes.ERR__INVALID_ARG }
|
|
523
|
+
);
|
|
524
|
+
}
|
|
525
|
+
if (!Array.isArray(kjsConfig.partitionAssignors)) {
|
|
526
|
+
throw new error.KafkaJSError(CompatibilityErrorMessages.partitionAssignors(), { code: error.ErrorCodes.ERR__INVALID_ARG });
|
|
527
|
+
}
|
|
528
|
+
kjsConfig.partitionAssignors.forEach(assignor => {
|
|
529
|
+
if (typeof assignor !== 'string')
|
|
530
|
+
throw new error.KafkaJSError(CompatibilityErrorMessages.partitionAssignors(), { code: error.ErrorCodes.ERR__INVALID_ARG });
|
|
531
|
+
});
|
|
532
|
+
rdKafkaConfig['partition.assignment.strategy'] = kjsConfig.partitionAssignors.join(',');
|
|
533
|
+
} else if (isClassicProtocol) {
|
|
534
|
+
rdKafkaConfig['partition.assignment.strategy'] = PartitionAssigners.roundRobin;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
if (Object.hasOwn(kjsConfig, 'sessionTimeout')) {
|
|
538
|
+
if (!isClassicProtocol) {
|
|
539
|
+
throw new error.KafkaJSError(
|
|
540
|
+
"sessionTimeout is not supported when group.protocol is not 'classic'.",
|
|
541
|
+
{ code: error.ErrorCodes.ERR__INVALID_ARG }
|
|
542
|
+
);
|
|
543
|
+
}
|
|
544
|
+
rdKafkaConfig['session.timeout.ms'] = kjsConfig.sessionTimeout;
|
|
545
|
+
} else if (isClassicProtocol) {
|
|
546
|
+
rdKafkaConfig['session.timeout.ms'] = 30000;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
if (Object.hasOwn(kjsConfig, 'heartbeatInterval')) {
|
|
550
|
+
if (!isClassicProtocol) {
|
|
551
|
+
throw new error.KafkaJSError(
|
|
552
|
+
"heartbeatInterval is not supported when group.protocol is not 'classic'.",
|
|
553
|
+
{ code: error.ErrorCodes.ERR__INVALID_ARG }
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
rdKafkaConfig['heartbeat.interval.ms'] = kjsConfig.heartbeatInterval;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
if (Object.hasOwn(kjsConfig, 'rebalanceTimeout')) {
|
|
560
|
+
/* In librdkafka, we use the max poll interval as the rebalance timeout as well. */
|
|
561
|
+
rdKafkaConfig['max.poll.interval.ms'] = +kjsConfig.rebalanceTimeout;
|
|
562
|
+
} else if (!rdKafkaConfig['max.poll.interval.ms']) {
|
|
563
|
+
rdKafkaConfig['max.poll.interval.ms'] = 300000; /* librdkafka default */
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
if (Object.hasOwn(kjsConfig, 'metadataMaxAge')) {
|
|
567
|
+
rdKafkaConfig['topic.metadata.refresh.interval.ms'] = kjsConfig.metadataMaxAge;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
if (Object.hasOwn(kjsConfig, 'allowAutoTopicCreation')) {
|
|
571
|
+
rdKafkaConfig['allow.auto.create.topics'] = kjsConfig.allowAutoTopicCreation;
|
|
572
|
+
} else {
|
|
573
|
+
rdKafkaConfig['allow.auto.create.topics'] = true;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
if (Object.hasOwn(kjsConfig, 'maxBytesPerPartition')) {
|
|
577
|
+
rdKafkaConfig['max.partition.fetch.bytes'] = kjsConfig.maxBytesPerPartition;
|
|
578
|
+
} else {
|
|
579
|
+
rdKafkaConfig['max.partition.fetch.bytes'] = 1048576;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
if (Object.hasOwn(kjsConfig, 'maxWaitTimeInMs')) {
|
|
583
|
+
rdKafkaConfig['fetch.wait.max.ms'] = kjsConfig.maxWaitTimeInMs;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
if (Object.hasOwn(kjsConfig, 'minBytes')) {
|
|
587
|
+
rdKafkaConfig['fetch.min.bytes'] = kjsConfig.minBytes;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
if (Object.hasOwn(kjsConfig, 'maxBytes')) {
|
|
591
|
+
rdKafkaConfig['fetch.message.max.bytes'] = kjsConfig.maxBytes;
|
|
592
|
+
} else {
|
|
593
|
+
rdKafkaConfig['fetch.message.max.bytes'] = 10485760;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
if (Object.hasOwn(kjsConfig, 'readUncommitted')) {
|
|
597
|
+
rdKafkaConfig['isolation.level'] = kjsConfig.readUncommitted ? 'read_uncommitted' : 'read_committed';
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
if (Object.hasOwn(kjsConfig, 'maxInFlightRequests')) {
|
|
601
|
+
rdKafkaConfig['max.in.flight'] = kjsConfig.maxInFlightRequests;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
if (Object.hasOwn(kjsConfig, 'rackId')) {
|
|
605
|
+
rdKafkaConfig['client.rack'] = kjsConfig.rackId;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
if (Object.hasOwn(kjsConfig, 'fromBeginning')) {
|
|
609
|
+
rdKafkaConfig['auto.offset.reset'] = kjsConfig.fromBeginning ? 'earliest' : 'latest';
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
if (Object.hasOwn(kjsConfig, 'autoCommit')) {
|
|
613
|
+
rdKafkaConfig['enable.auto.commit'] = kjsConfig.autoCommit;
|
|
614
|
+
} else {
|
|
615
|
+
rdKafkaConfig['enable.auto.commit'] = true;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
if (Object.hasOwn(kjsConfig, 'autoCommitInterval')) {
|
|
619
|
+
rdKafkaConfig['auto.commit.interval.ms'] = kjsConfig.autoCommitInterval;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
if (Object.hasOwn(kjsConfig, 'autoCommitThreshold')) {
|
|
623
|
+
throw new error.KafkaJSError(CompatibilityErrorMessages.runOptionsAutoCommitThreshold(), { code: error.ErrorCodes.ERR__NOT_IMPLEMENTED });
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
/* Set the logger */
|
|
627
|
+
if (Object.hasOwn(kjsConfig, 'logger')) {
|
|
628
|
+
this.#logger = kjsConfig.logger;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
/* Set the log level - INFO for compatibility with kafkaJS, or DEBUG if that is turned
|
|
632
|
+
* on using the logLevel property. rdKafkaConfig.log_level is guaranteed to be set if we're
|
|
633
|
+
* here, and containing the correct value. */
|
|
634
|
+
this.#logger.setLogLevel(severityToLogLevel[rdKafkaConfig.log_level]);
|
|
635
|
+
|
|
636
|
+
return rdKafkaConfig;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
#finalizedConfig() {
|
|
640
|
+
const protocol = this.#userConfig['group.protocol'];
|
|
641
|
+
const isClassicProtocol = protocol === undefined ||
|
|
642
|
+
(typeof protocol === 'string' && protocol.toLowerCase() === 'classic');
|
|
643
|
+
/* Creates an rdkafka config based off the kafkaJS block. Switches to compatibility mode if the block exists. */
|
|
644
|
+
let compatibleConfig = this.#kafkaJSToConsumerConfig(this.#userConfig.kafkaJS, isClassicProtocol);
|
|
645
|
+
|
|
646
|
+
/* There can be multiple different and conflicting config directives for setting the log level:
|
|
647
|
+
* 1. If there's a kafkaJS block:
|
|
648
|
+
* a. If there's a logLevel directive in the kafkaJS block, set the logger level accordingly.
|
|
649
|
+
* b. If there's no logLevel directive, set the logger level to INFO.
|
|
650
|
+
* (both these are already handled in the conversion method above).
|
|
651
|
+
* 2. If there is a log_level or debug directive in the main config, set the logger level accordingly.
|
|
652
|
+
* !This overrides any different value provided in the kafkaJS block!
|
|
653
|
+
* a. If there's a log_level directive, set the logger level accordingly.
|
|
654
|
+
* b. If there's a debug directive, set the logger level to DEBUG regardless of anything else. This is because
|
|
655
|
+
* librdkafka ignores log_level if debug is set, and our behaviour should be identical.
|
|
656
|
+
* 3. There's nothing at all. Take no action in this case, let the logger use its default log level.
|
|
657
|
+
*/
|
|
658
|
+
if (Object.hasOwn(this.#userConfig, 'log_level')) {
|
|
659
|
+
this.#logger.setLogLevel(severityToLogLevel[this.#userConfig.log_level]);
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
if (Object.hasOwn(this.#userConfig, 'debug')) {
|
|
663
|
+
this.#logger.setLogLevel(logLevel.DEBUG);
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
let rdKafkaConfig = Object.assign(compatibleConfig, this.#userConfig);
|
|
667
|
+
|
|
668
|
+
/* Delete properties which are already processed, or cannot be passed to node-rdkafka */
|
|
669
|
+
delete rdKafkaConfig.kafkaJS;
|
|
670
|
+
|
|
671
|
+
/* Certain properties that the user has set are overridden. We use trampolines to accommodate the user's callbacks.
|
|
672
|
+
* TODO: add trampoline method for offset commit callback. */
|
|
673
|
+
rdKafkaConfig['offset_commit_cb'] = true;
|
|
674
|
+
rdKafkaConfig['rebalance_cb'] = (err, assignment) => {
|
|
675
|
+
this.#rebalanceCbInProgress = new DeferredPromise();
|
|
676
|
+
this.#rebalanceCallback(err, assignment).catch(e =>
|
|
677
|
+
{
|
|
678
|
+
if (this.#logger)
|
|
679
|
+
this.#logger.error(`Error from rebalance callback: ${e.stack}`);
|
|
680
|
+
});
|
|
681
|
+
};
|
|
682
|
+
|
|
683
|
+
/* We handle offset storage within the promisified API by ourselves. Thus we don't allow the user to change this
|
|
684
|
+
* setting and set it to false. */
|
|
685
|
+
if (Object.hasOwn(this.#userConfig, 'enable.auto.offset.store')) {
|
|
686
|
+
throw new error.KafkaJSError(
|
|
687
|
+
"Changing 'enable.auto.offset.store' is unsupported while using the promisified API.",
|
|
688
|
+
{ code: error.ErrorCodes.ERR__INVALID_ARG });
|
|
689
|
+
}
|
|
690
|
+
rdKafkaConfig['enable.auto.offset.store'] = false;
|
|
691
|
+
|
|
692
|
+
if (!Object.hasOwn(rdKafkaConfig, 'enable.auto.commit')) {
|
|
693
|
+
this.#autoCommit = true; /* librdkafka default. */
|
|
694
|
+
} else {
|
|
695
|
+
this.#autoCommit = rdKafkaConfig['enable.auto.commit'];
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
/**
|
|
699
|
+
* Actual max poll interval is twice the configured max poll interval,
|
|
700
|
+
* because we want to ensure that when we ask for worker termination,
|
|
701
|
+
* and there is one last message to be processed, we can process it in
|
|
702
|
+
* the configured max poll interval time.
|
|
703
|
+
* This will cause the rebalance callback timeout to be double
|
|
704
|
+
* the value of the configured max poll interval.
|
|
705
|
+
* But it's expected otherwise we cannot have a cache and need to consider
|
|
706
|
+
* max poll interval reached on processing the very first message.
|
|
707
|
+
*/
|
|
708
|
+
this.#maxPollIntervalMs = rdKafkaConfig['max.poll.interval.ms'] ?? 300000;
|
|
709
|
+
this.#cacheExpirationTimeoutMs = this.#maxPollIntervalMs;
|
|
710
|
+
rdKafkaConfig['max.poll.interval.ms'] = this.#maxPollIntervalMs * 2;
|
|
711
|
+
|
|
712
|
+
if (Object.hasOwn(rdKafkaConfig, 'js.consumer.max.batch.size')) {
|
|
713
|
+
const maxBatchSize = +rdKafkaConfig['js.consumer.max.batch.size'];
|
|
714
|
+
if (!Number.isInteger(maxBatchSize) || (maxBatchSize <= 0 && maxBatchSize !== -1)) {
|
|
715
|
+
throw new error.KafkaJSError(
|
|
716
|
+
"'js.consumer.max.batch.size' must be a positive integer or -1 for unlimited batch size.",
|
|
717
|
+
{ code: error.ErrorCodes.ERR__INVALID_ARG });
|
|
718
|
+
}
|
|
719
|
+
this.#maxBatchSize = maxBatchSize;
|
|
720
|
+
this.#maxBatchesSize = maxBatchSize;
|
|
721
|
+
if (maxBatchSize === -1) {
|
|
722
|
+
this.#messageCacheMaxSize = Number.MAX_SAFE_INTEGER;
|
|
723
|
+
}
|
|
724
|
+
delete rdKafkaConfig['js.consumer.max.batch.size'];
|
|
725
|
+
}
|
|
726
|
+
if (Object.hasOwn(rdKafkaConfig, 'js.consumer.max.cache.size.per.worker.ms')) {
|
|
727
|
+
const maxCacheSizePerWorkerMs = +rdKafkaConfig['js.consumer.max.cache.size.per.worker.ms'];
|
|
728
|
+
if (!Number.isInteger(maxCacheSizePerWorkerMs) || (maxCacheSizePerWorkerMs <= 0)) {
|
|
729
|
+
throw new error.KafkaJSError(
|
|
730
|
+
"'js.consumer.max.cache.size.per.worker.ms' must be a positive integer.",
|
|
731
|
+
{ code: error.ErrorCodes.ERR__INVALID_ARG });
|
|
732
|
+
}
|
|
733
|
+
this.#maxCacheSizePerWorkerMs = maxCacheSizePerWorkerMs;
|
|
734
|
+
delete rdKafkaConfig['js.consumer.max.cache.size.per.worker.ms'];
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
if (Object.hasOwn(rdKafkaConfig, 'stats_cb')) {
|
|
738
|
+
if (typeof rdKafkaConfig['stats_cb'] === 'function')
|
|
739
|
+
this.#statsCb = rdKafkaConfig['stats_cb'];
|
|
740
|
+
delete rdKafkaConfig['stats_cb'];
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
return rdKafkaConfig;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
#readyCb() {
|
|
747
|
+
if (this.#state !== ConsumerState.CONNECTING) {
|
|
748
|
+
/* The connectPromiseFunc might not be set, so we throw such an error. It's a state error that we can't recover from. Probably a bug. */
|
|
749
|
+
throw new error.KafkaJSError(`Ready callback called in invalid state ${this.#state}`, { code: error.ErrorCodes.ERR__STATE });
|
|
750
|
+
}
|
|
751
|
+
this.#state = ConsumerState.CONNECTED;
|
|
752
|
+
|
|
753
|
+
/* Slight optimization for cases where the size of messages in our subscription is less than the cache size. */
|
|
754
|
+
this.#internalClient.setDefaultIsTimeoutOnlyForFirstMessage(true);
|
|
755
|
+
|
|
756
|
+
// We will fetch only those messages which are already on the queue. Since we will be
|
|
757
|
+
// woken up by #queueNonEmptyCb, we don't need to set a wait timeout.
|
|
758
|
+
this.#internalClient.setDefaultConsumeTimeout(0);
|
|
759
|
+
|
|
760
|
+
this.#clientName = this.#internalClient.name;
|
|
761
|
+
this.#logger.info('Consumer connected', this.#createConsumerBindingMessageMetadata());
|
|
762
|
+
|
|
763
|
+
// Resolve the promise.
|
|
764
|
+
this.#connectPromiseFunc['resolve']();
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
/**
|
|
768
|
+
* Callback for the event.error event, fails the initial connect() and logs the error.
|
|
769
|
+
* @param {Error} err
|
|
770
|
+
* @private
|
|
771
|
+
*/
|
|
772
|
+
#errorCb(err) {
|
|
773
|
+
/* If we get an error in the middle of connecting, reject the promise later with this error. */
|
|
774
|
+
if (this.#state < ConsumerState.CONNECTED) {
|
|
775
|
+
if (!this.#connectionError)
|
|
776
|
+
this.#connectionError = err;
|
|
777
|
+
}
|
|
778
|
+
this.#logger.error(`Error: ${err.message}`, this.#createConsumerBindingMessageMetadata());
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
|
|
782
|
+
/**
|
|
783
|
+
* Callback for the event.stats event, if defined.
|
|
784
|
+
* @private
|
|
785
|
+
*/
|
|
786
|
+
#statsCb = null;
|
|
787
|
+
|
|
788
|
+
/**
|
|
789
|
+
* Converts headers returned by node-rdkafka into a format that can be used by the eachMessage/eachBatch callback.
|
|
790
|
+
* @param {import("../..").MessageHeader[] | undefined} messageHeaders
|
|
791
|
+
* @returns {import("../../types/kafkajs").IHeaders}
|
|
792
|
+
* @private
|
|
793
|
+
*/
|
|
794
|
+
#createHeaders(messageHeaders) {
|
|
795
|
+
let headers;
|
|
796
|
+
if (messageHeaders) {
|
|
797
|
+
headers = {};
|
|
798
|
+
for (const header of messageHeaders) {
|
|
799
|
+
for (const [key, value] of Object.entries(header)) {
|
|
800
|
+
if (!Object.hasOwn(headers, key)) {
|
|
801
|
+
headers[key] = value;
|
|
802
|
+
} else if (headers[key].constructor === Array) {
|
|
803
|
+
headers[key].push(value);
|
|
804
|
+
} else {
|
|
805
|
+
headers[key] = [headers[key], value];
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
return headers;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
/**
|
|
814
|
+
* Converts a message returned by node-rdkafka into a message that can be used by the eachMessage callback.
|
|
815
|
+
* @param {import("../..").Message} message
|
|
816
|
+
* @returns {import("../../types/kafkajs").EachMessagePayload}
|
|
817
|
+
* @private
|
|
818
|
+
*/
|
|
819
|
+
#createPayload(message, worker) {
|
|
820
|
+
let key = message.key;
|
|
821
|
+
if (typeof key === 'string') {
|
|
822
|
+
key = Buffer.from(key);
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
let timestamp = message.timestamp ? String(message.timestamp) : '';
|
|
826
|
+
const headers = this.#createHeaders(message.headers);
|
|
827
|
+
|
|
828
|
+
return {
|
|
829
|
+
topic: message.topic,
|
|
830
|
+
partition: message.partition,
|
|
831
|
+
message: {
|
|
832
|
+
key,
|
|
833
|
+
value: message.value,
|
|
834
|
+
timestamp,
|
|
835
|
+
attributes: 0,
|
|
836
|
+
offset: String(message.offset),
|
|
837
|
+
size: message.size,
|
|
838
|
+
leaderEpoch: message.leaderEpoch,
|
|
839
|
+
headers
|
|
840
|
+
},
|
|
841
|
+
heartbeat: async () => { /* no op */ },
|
|
842
|
+
pause: this.pause.bind(this, [{ topic: message.topic, partitions: [message.partition] }]),
|
|
843
|
+
_worker: worker,
|
|
844
|
+
};
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
/**
|
|
848
|
+
* Method used by #createBatchPayload to resolve offsets.
|
|
849
|
+
* Resolution stores the offset into librdkafka if needed, and into the lastConsumedOffsets map
|
|
850
|
+
* that we use for seeking to the last consumed offset when forced to clear cache.
|
|
851
|
+
*
|
|
852
|
+
* @param {*} payload The payload we're creating. This is a method attached to said object.
|
|
853
|
+
* @param {*} offsetToResolve The offset to resolve.
|
|
854
|
+
* @param {*} leaderEpoch The leader epoch of the message (optional). We expect users to provide it, but for API-compatibility reasons, it's optional.
|
|
855
|
+
* @private
|
|
856
|
+
*/
|
|
857
|
+
#eachBatchPayload_resolveOffsets(payload, offsetToResolve, leaderEpoch = -1) {
|
|
858
|
+
const offset = +offsetToResolve;
|
|
859
|
+
|
|
860
|
+
if (isNaN(offset)) {
|
|
861
|
+
/* Not much we can do but throw and log an error. */
|
|
862
|
+
const e = new error.KafkaJSError(`Invalid offset to resolve: ${offsetToResolve}`, { code: error.ErrorCodes.ERR__INVALID_ARG });
|
|
863
|
+
throw e;
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
/* The user might resolve offset N (< M) after resolving offset M. Given that in librdkafka we can only
|
|
867
|
+
* store one offset, store the last possible one. */
|
|
868
|
+
if (offset <= payload._lastResolvedOffset.offset)
|
|
869
|
+
return;
|
|
870
|
+
|
|
871
|
+
const topic = payload.batch.topic;
|
|
872
|
+
const partition = payload.batch.partition;
|
|
873
|
+
|
|
874
|
+
payload._lastResolvedOffset = { offset, leaderEpoch };
|
|
875
|
+
|
|
876
|
+
try {
|
|
877
|
+
this.#internalClient._offsetsStoreSingle(
|
|
878
|
+
topic,
|
|
879
|
+
partition,
|
|
880
|
+
offset + 1,
|
|
881
|
+
leaderEpoch);
|
|
882
|
+
} catch (e) {
|
|
883
|
+
/* Not much we can do, except log the error. */
|
|
884
|
+
this.#logger.error(`Consumer encountered error while storing offset. Error details: ${e}:${e.stack}`, this.#createConsumerBindingMessageMetadata());
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
/**
|
|
889
|
+
* Method used by #createBatchPayload to commit offsets.
|
|
890
|
+
* @private
|
|
891
|
+
*/
|
|
892
|
+
async #eachBatchPayload_commitOffsetsIfNecessary() {
|
|
893
|
+
if (this.#autoCommit) {
|
|
894
|
+
/* librdkafka internally handles committing of whatever we store.
|
|
895
|
+
* We don't worry about it here. */
|
|
896
|
+
return;
|
|
897
|
+
}
|
|
898
|
+
/* If the offsets are being resolved by the user, they've already called resolveOffset() at this point
|
|
899
|
+
* We just need to commit the offsets that we've stored. */
|
|
900
|
+
await this.commitOffsets();
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
/**
|
|
904
|
+
* Converts a list of messages returned by node-rdkafka into a message that can be used by the eachBatch callback.
|
|
905
|
+
* @param {import("../..").Message[]} messages - must not be empty. Must contain messages from the same topic and partition.
|
|
906
|
+
* @returns {import("../../types/kafkajs").EachBatchPayload}
|
|
907
|
+
* @private
|
|
908
|
+
*/
|
|
909
|
+
#createBatchPayload(messages, worker) {
|
|
910
|
+
const topic = messages[0].topic;
|
|
911
|
+
const partition = messages[0].partition;
|
|
912
|
+
let watermarkOffsets = {};
|
|
913
|
+
let highWatermark = '-1001';
|
|
914
|
+
let offsetLag_ = -1;
|
|
915
|
+
let offsetLagLow_ = -1;
|
|
916
|
+
|
|
917
|
+
try {
|
|
918
|
+
watermarkOffsets = this.#internalClient.getWatermarkOffsets(topic, partition);
|
|
919
|
+
} catch (e) {
|
|
920
|
+
/* Only warn. The batch as a whole remains valid but for the fact that the highwatermark won't be there. */
|
|
921
|
+
this.#logger.warn(`Could not get watermark offsets for batch: ${e}`, this.#createConsumerBindingMessageMetadata());
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
/* Keep default values if it's not a real offset (OFFSET_INVALID: -1001). */
|
|
925
|
+
if (Number.isInteger(watermarkOffsets.highOffset) &&
|
|
926
|
+
watermarkOffsets.highOffset >= 0) {
|
|
927
|
+
highWatermark = watermarkOffsets.highOffset.toString();
|
|
928
|
+
/* While calculating lag, we subtract 1 from the high offset
|
|
929
|
+
* for compatibility reasons with KafkaJS's API */
|
|
930
|
+
offsetLag_ = (watermarkOffsets.highOffset - 1) - messages[messages.length - 1].offset;
|
|
931
|
+
offsetLagLow_ = (watermarkOffsets.highOffset - 1) - messages[0].offset;
|
|
932
|
+
|
|
933
|
+
/* In any case don't return a negative lag but a zero lag instead. */
|
|
934
|
+
offsetLag_ = offsetLag_ > 0 ? offsetLag_ : 0;
|
|
935
|
+
offsetLagLow_ = offsetLagLow_ > 0 ? offsetLagLow_ : 0;
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
const messagesConverted = [];
|
|
939
|
+
for (let i = 0; i < messages.length; i++) {
|
|
940
|
+
const message = messages[i];
|
|
941
|
+
let key = message.key;
|
|
942
|
+
if (typeof key === 'string') {
|
|
943
|
+
key = Buffer.from(key);
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
let timestamp = message.timestamp ? String(message.timestamp) : '';
|
|
947
|
+
const headers = this.#createHeaders(message.headers);
|
|
948
|
+
|
|
949
|
+
const messageConverted = {
|
|
950
|
+
key,
|
|
951
|
+
value: message.value,
|
|
952
|
+
timestamp,
|
|
953
|
+
attributes: 0,
|
|
954
|
+
offset: String(message.offset),
|
|
955
|
+
size: message.size,
|
|
956
|
+
leaderEpoch: message.leaderEpoch,
|
|
957
|
+
headers
|
|
958
|
+
};
|
|
959
|
+
|
|
960
|
+
messagesConverted.push(messageConverted);
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
const batch = {
|
|
964
|
+
topic,
|
|
965
|
+
partition,
|
|
966
|
+
highWatermark,
|
|
967
|
+
messages: messagesConverted,
|
|
968
|
+
isEmpty: () => false,
|
|
969
|
+
firstOffset: () => (messagesConverted[0].offset).toString(),
|
|
970
|
+
lastOffset: () => (messagesConverted[messagesConverted.length - 1].offset).toString(),
|
|
971
|
+
offsetLag: () => offsetLag_.toString(),
|
|
972
|
+
offsetLagLow: () => offsetLagLow_.toString(),
|
|
973
|
+
};
|
|
974
|
+
|
|
975
|
+
const returnPayload = {
|
|
976
|
+
batch,
|
|
977
|
+
_stale: false,
|
|
978
|
+
_seeked: false,
|
|
979
|
+
_lastResolvedOffset: { offset: -1, leaderEpoch: -1 },
|
|
980
|
+
_worker: worker,
|
|
981
|
+
heartbeat: async () => { /* no op */ },
|
|
982
|
+
pause: this.pause.bind(this, [{ topic, partitions: [partition] }]),
|
|
983
|
+
commitOffsetsIfNecessary: this.#eachBatchPayload_commitOffsetsIfNecessary.bind(this),
|
|
984
|
+
isRunning: () => this.#running,
|
|
985
|
+
isStale: () => returnPayload._stale,
|
|
986
|
+
/* NOTE: Probably never to be implemented. Not sure exactly how we'd compute this
|
|
987
|
+
* inexpensively. */
|
|
988
|
+
uncommittedOffsets: () => notImplemented(),
|
|
989
|
+
};
|
|
990
|
+
|
|
991
|
+
returnPayload.resolveOffset = this.#eachBatchPayload_resolveOffsets.bind(this, returnPayload);
|
|
992
|
+
|
|
993
|
+
return returnPayload;
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
#roundUpToNearestMultipleOfMaxBatchesSize(value) {
|
|
997
|
+
return Math.ceil(
|
|
998
|
+
value
|
|
999
|
+
/ this.#maxBatchesSize
|
|
1000
|
+
) * this.#maxBatchesSize;
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
#updateMaxMessageCacheSize() {
|
|
1004
|
+
if (this.#maxBatchSize === -1) {
|
|
1005
|
+
// In case of unbounded max batch size it returns all available messages
|
|
1006
|
+
// for a partition in each batch. Cache is unbounded given that
|
|
1007
|
+
// it takes only one call to process each partition.
|
|
1008
|
+
return;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
const nowNs = hrtime.bigint();
|
|
1012
|
+
if (this.#lastFetchedMessageCnt > 0 && this.#lastFetchClockNs > 0n &&
|
|
1013
|
+
nowNs > this.#lastFetchClockNs) {
|
|
1014
|
+
const consumptionDurationMilliseconds = Number(nowNs - this.#lastFetchClockNs) / 1e6;
|
|
1015
|
+
const messagesPerMillisecondSingleWorker = this.#lastFetchedMessageCnt / this.#lastFetchedConcurrency / consumptionDurationMilliseconds;
|
|
1016
|
+
// Keep enough messages in the cache for this.#maxCacheSizePerWorkerMs of concurrent consumption by all workers.
|
|
1017
|
+
// Round up to the nearest multiple of `#maxBatchesSize`.
|
|
1018
|
+
this.#messageCacheMaxSize = this.#roundUpToNearestMultipleOfMaxBatchesSize(
|
|
1019
|
+
Math.ceil(
|
|
1020
|
+
// Messages needed for `#maxCacheSizePerWorkerMs` milliseconds of consumption
|
|
1021
|
+
// per worker. Take the ceil.
|
|
1022
|
+
this.#maxCacheSizePerWorkerMs *
|
|
1023
|
+
messagesPerMillisecondSingleWorker
|
|
1024
|
+
) * this.#concurrency
|
|
1025
|
+
);
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
#saveFetchStats(messages) {
|
|
1030
|
+
this.#lastFetchClockNs = hrtime.bigint();
|
|
1031
|
+
const partitionsNum = new Map();
|
|
1032
|
+
for (const msg of messages) {
|
|
1033
|
+
const key = partitionKey(msg);
|
|
1034
|
+
partitionsNum.set(key, 1);
|
|
1035
|
+
if (partitionsNum.size >= this.#concurrency) {
|
|
1036
|
+
break;
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
this.#lastFetchedConcurrency = partitionsNum.size;
|
|
1040
|
+
this.#lastFetchedMessageCnt = messages.length;
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
|
|
1044
|
+
async #fetchAndResolveWith(takeFromCache, size) {
|
|
1045
|
+
if (this.#fetchInProgress) {
|
|
1046
|
+
await this.#fetchInProgress;
|
|
1047
|
+
/* Restart with the checks as we might have
|
|
1048
|
+
* a new fetch in progress already. */
|
|
1049
|
+
return null;
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
if (this.#nonEmpty) {
|
|
1053
|
+
await this.#nonEmpty;
|
|
1054
|
+
/* Restart with the checks as we might have
|
|
1055
|
+
* a new fetch in progress already. */
|
|
1056
|
+
return null;
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
if (this.#workerTerminationScheduled.resolved) {
|
|
1060
|
+
/* Return without fetching. */
|
|
1061
|
+
return null;
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
let err, messages, processedRebalance = false;
|
|
1065
|
+
try {
|
|
1066
|
+
this.#fetchInProgress = new DeferredPromise();
|
|
1067
|
+
const fetchResult = new DeferredPromise();
|
|
1068
|
+
this.#logger.debug(`Attempting to fetch ${size} messages to the message cache`,
|
|
1069
|
+
this.#createConsumerBindingMessageMetadata());
|
|
1070
|
+
|
|
1071
|
+
this.#updateMaxMessageCacheSize();
|
|
1072
|
+
this.#internalClient.consume(size, (err, messages) =>
|
|
1073
|
+
fetchResult.resolve([err, messages]));
|
|
1074
|
+
|
|
1075
|
+
[err, messages] = await fetchResult;
|
|
1076
|
+
if (this.#rebalanceCbInProgress) {
|
|
1077
|
+
processedRebalance = true;
|
|
1078
|
+
await this.#rebalanceCbInProgress;
|
|
1079
|
+
this.#rebalanceCbInProgress = null;
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
if (err) {
|
|
1083
|
+
throw createKafkaJsErrorFromLibRdKafkaError(err);
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
this.#messageCache.addMessages(messages);
|
|
1087
|
+
const res = takeFromCache();
|
|
1088
|
+
this.#saveFetchStats(messages);
|
|
1089
|
+
this.#maxPollIntervalRestart.resolve();
|
|
1090
|
+
return res;
|
|
1091
|
+
} finally {
|
|
1092
|
+
this.#fetchInProgress.resolve();
|
|
1093
|
+
this.#fetchInProgress = null;
|
|
1094
|
+
if (!err && !processedRebalance && this.#messageCache.assignedSize === 0)
|
|
1095
|
+
this.#nonEmpty = new DeferredPromise();
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
/**
|
|
1100
|
+
* Consumes a single message from the internal consumer.
|
|
1101
|
+
* @param {PerPartitionCache} ppc Per partition cache to use or null|undefined .
|
|
1102
|
+
* @returns {Promise<import("../..").Message | null>} a promise that resolves to a single message or null.
|
|
1103
|
+
* @note this method caches messages as well, but returns only a single message.
|
|
1104
|
+
* @private
|
|
1105
|
+
*/
|
|
1106
|
+
async #consumeSingleCached(ppc) {
|
|
1107
|
+
const msg = this.#messageCache.next(ppc);
|
|
1108
|
+
if (msg) {
|
|
1109
|
+
return msg;
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
/* It's possible that we get msg = null, but that's because partitionConcurrency
|
|
1113
|
+
* exceeds the number of partitions containing messages. So
|
|
1114
|
+
* we should wait for a new partition to be available.
|
|
1115
|
+
*/
|
|
1116
|
+
if (!msg && this.#messageCache.assignedSize !== 0) {
|
|
1117
|
+
await this.#messageCache.availablePartitions();
|
|
1118
|
+
/* Restart with the checks as we might have
|
|
1119
|
+
* the cache full. */
|
|
1120
|
+
return null;
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
return this.#fetchAndResolveWith(() => this.#messageCache.next(),
|
|
1124
|
+
this.#messageCacheMaxSize);
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
/**
|
|
1128
|
+
* Consumes a single message from the internal consumer.
|
|
1129
|
+
* @param {number} savedIndex - the index of the message in the cache to return.
|
|
1130
|
+
* @param {number} size - the number of messages to fetch.
|
|
1131
|
+
* @returns {Promise<import("../..").Message[] | null>} a promise that resolves to a list of messages or null.
|
|
1132
|
+
* @note this method caches messages as well.
|
|
1133
|
+
* @sa #consumeSingleCached
|
|
1134
|
+
* @private
|
|
1135
|
+
*/
|
|
1136
|
+
async #consumeCachedN(ppc, size) {
|
|
1137
|
+
const msgs = this.#messageCache.nextN(ppc, size);
|
|
1138
|
+
if (msgs) {
|
|
1139
|
+
return msgs;
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
/* It's possible that we get msgs = null, but that's because partitionConcurrency
|
|
1143
|
+
* exceeds the number of partitions containing messages. So
|
|
1144
|
+
* we should wait for a new partition to be available.
|
|
1145
|
+
*/
|
|
1146
|
+
if (!msgs && this.#messageCache.assignedSize !== 0) {
|
|
1147
|
+
await this.#messageCache.availablePartitions();
|
|
1148
|
+
/* Restart with the checks as we might have
|
|
1149
|
+
* the cache full. */
|
|
1150
|
+
return null;
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
return this.#fetchAndResolveWith(() =>
|
|
1154
|
+
this.#messageCache.nextN(null, size),
|
|
1155
|
+
this.#messageCacheMaxSize);
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
/**
|
|
1159
|
+
* Flattens a list of topics with partitions into a list of topic, partition.
|
|
1160
|
+
* @param {Array<({topic: string, partitions: Array<number>}|{topic: string, partition: number})>} topics
|
|
1161
|
+
* @returns {import("../../types/rdkafka").TopicPartition[]} a list of (topic, partition).
|
|
1162
|
+
* @private
|
|
1163
|
+
*/
|
|
1164
|
+
#flattenTopicPartitions(topics) {
|
|
1165
|
+
const ret = [];
|
|
1166
|
+
for (const topic of topics) {
|
|
1167
|
+
if (typeof topic.partition === 'number')
|
|
1168
|
+
ret.push({
|
|
1169
|
+
topic: topic.topic,
|
|
1170
|
+
partition: topic.partition
|
|
1171
|
+
});
|
|
1172
|
+
else {
|
|
1173
|
+
for (const partition of topic.partitions) {
|
|
1174
|
+
ret.push({ topic: topic.topic, partition });
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
return ret;
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
/**
|
|
1182
|
+
* Set up the client and connect to the bootstrap brokers.
|
|
1183
|
+
*
|
|
1184
|
+
* This method can be called only once for a consumer instance, and must be
|
|
1185
|
+
* called before doing any other operations.
|
|
1186
|
+
*
|
|
1187
|
+
* @returns {Promise<void>} a promise that resolves when the consumer is connected.
|
|
1188
|
+
*/
|
|
1189
|
+
async connect() {
|
|
1190
|
+
if (this.#state !== ConsumerState.INIT) {
|
|
1191
|
+
throw new error.KafkaJSError('Connect has already been called elsewhere.', { code: error.ErrorCodes.ERR__STATE });
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
const rdKafkaConfig = this.#config();
|
|
1195
|
+
this.#state = ConsumerState.CONNECTING;
|
|
1196
|
+
rdKafkaConfig.queue_non_empty_cb = this.#queueNonEmptyCb.bind(this);
|
|
1197
|
+
this.#internalClient = new RdKafka.KafkaConsumer(rdKafkaConfig);
|
|
1198
|
+
this.#internalClient.on('ready', this.#readyCb.bind(this));
|
|
1199
|
+
this.#internalClient.on('error', this.#errorCb.bind(this));
|
|
1200
|
+
this.#internalClient.on('event.error', this.#errorCb.bind(this));
|
|
1201
|
+
this.#internalClient.on('event.log', (msg) => loggerTrampoline(msg, this.#logger));
|
|
1202
|
+
if (this.#statsCb) {
|
|
1203
|
+
this.#internalClient.on('event.stats', this.#statsCb.bind(this));
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
return new Promise((resolve, reject) => {
|
|
1207
|
+
this.#connectPromiseFunc = { resolve, reject };
|
|
1208
|
+
this.#internalClient.connect(null, (err) => {
|
|
1209
|
+
if (err) {
|
|
1210
|
+
this.#state = ConsumerState.DISCONNECTED;
|
|
1211
|
+
const rejectionError = this.#connectionError ? this.#connectionError : err;
|
|
1212
|
+
reject(createKafkaJsErrorFromLibRdKafkaError(rejectionError));
|
|
1213
|
+
}
|
|
1214
|
+
});
|
|
1215
|
+
});
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
/**
|
|
1219
|
+
* Subscribes the consumer to the given topics.
|
|
1220
|
+
* @param {object} subscription - An object containing the topic(s) to subscribe to - one of `topic` or `topics` must be present.
|
|
1221
|
+
* @param {string?} subscription.topic - The topic to subscribe to.
|
|
1222
|
+
* @param {Array<string>?} subscription.topics - The topics to subscribe to.
|
|
1223
|
+
* @param {boolean?} subscription.replace - Whether to replace the existing subscription, or to add to it. Adds by default.
|
|
1224
|
+
*/
|
|
1225
|
+
async subscribe(subscription) {
|
|
1226
|
+
if (this.#state !== ConsumerState.CONNECTED) {
|
|
1227
|
+
throw new error.KafkaJSError('Subscribe can only be called while connected.', { code: error.ErrorCodes.ERR__STATE });
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
if (typeof subscription.fromBeginning === 'boolean') {
|
|
1231
|
+
throw new error.KafkaJSError(
|
|
1232
|
+
CompatibilityErrorMessages.subscribeOptionsFromBeginning(),
|
|
1233
|
+
{ code: error.ErrorCodes.ERR__INVALID_ARG });
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
if (!Object.hasOwn(subscription, 'topics') && !Object.hasOwn(subscription, 'topic')) {
|
|
1237
|
+
throw new error.KafkaJSError(CompatibilityErrorMessages.subscribeOptionsMandatoryMissing(), { code: error.ErrorCodes.ERR__INVALID_ARG });
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
let topics = [];
|
|
1241
|
+
if (subscription.topic) {
|
|
1242
|
+
topics.push(subscription.topic);
|
|
1243
|
+
} else if (Array.isArray(subscription.topics)) {
|
|
1244
|
+
topics = subscription.topics;
|
|
1245
|
+
} else {
|
|
1246
|
+
throw new error.KafkaJSError(CompatibilityErrorMessages.subscribeOptionsMandatoryMissing(), { code: error.ErrorCodes.ERR__INVALID_ARG });
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
topics = topics.map(topic => {
|
|
1250
|
+
if (typeof topic === 'string') {
|
|
1251
|
+
return topic;
|
|
1252
|
+
} else if (topic instanceof RegExp) {
|
|
1253
|
+
// Flags are not supported, and librdkafka only considers a regex match if the first character of the regex is ^.
|
|
1254
|
+
if (topic.flags) {
|
|
1255
|
+
throw new error.KafkaJSError(CompatibilityErrorMessages.subscribeOptionsRegexFlag(), { code: error.ErrorCodes.ERR__INVALID_ARG });
|
|
1256
|
+
}
|
|
1257
|
+
const regexSource = topic.source;
|
|
1258
|
+
if (regexSource.charAt(0) !== '^')
|
|
1259
|
+
throw new error.KafkaJSError(CompatibilityErrorMessages.subscribeOptionsRegexStart(), { code: error.ErrorCodes.ERR__INVALID_ARG });
|
|
1260
|
+
|
|
1261
|
+
return regexSource;
|
|
1262
|
+
} else {
|
|
1263
|
+
throw new error.KafkaJSError('Invalid topic ' + topic + ' (' + typeof topic + '), the topic name has to be a String or a RegExp', { code: error.ErrorCodes.ERR__INVALID_ARG });
|
|
1264
|
+
}
|
|
1265
|
+
});
|
|
1266
|
+
|
|
1267
|
+
this.#storedSubscriptions = subscription.replace ? topics : this.#storedSubscriptions.concat(topics);
|
|
1268
|
+
this.#logger.debug(`${subscription.replace ? 'Replacing' : 'Adding'} topics [${topics.join(', ')}] to subscription`, this.#createConsumerBindingMessageMetadata());
|
|
1269
|
+
this.#internalClient.subscribe(this.#storedSubscriptions);
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
async stop() {
|
|
1273
|
+
notImplemented();
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
/**
|
|
1277
|
+
* Starts consumer polling. This method returns immediately.
|
|
1278
|
+
* @param {object} config - The configuration for running the consumer.
|
|
1279
|
+
* @param {function?} config.eachMessage - The function to call for processing each message.
|
|
1280
|
+
* @param {function?} config.eachBatch - The function to call for processing each batch of messages - can only be set if eachMessage is not set.
|
|
1281
|
+
* @param {boolean?} config.eachBatchAutoResolve - Whether to automatically resolve offsets for each batch (only applicable if eachBatch is set, true by default).
|
|
1282
|
+
* @param {number?} config.partitionsConsumedConcurrently - The limit to the number of partitions consumed concurrently (1 by default).
|
|
1283
|
+
*/
|
|
1284
|
+
async run(config) {
|
|
1285
|
+
if (this.#state !== ConsumerState.CONNECTED) {
|
|
1286
|
+
throw new error.KafkaJSError('Run must be called after a successful connect().', { code: error.ErrorCodes.ERR__STATE });
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
if (Object.hasOwn(config, 'autoCommit')) {
|
|
1290
|
+
throw new error.KafkaJSError(CompatibilityErrorMessages.runOptionsAutoCommit(), { code: error.ErrorCodes.ERR__INVALID_ARG });
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
if (Object.hasOwn(config, 'autoCommitInterval')) {
|
|
1294
|
+
throw new error.KafkaJSError(CompatibilityErrorMessages.runOptionsAutoCommitInterval(), { code: error.ErrorCodes.ERR__INVALID_ARG });
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
if (Object.hasOwn(config, 'autoCommitThreshold')) {
|
|
1298
|
+
throw new error.KafkaJSError(CompatibilityErrorMessages.runOptionsAutoCommitThreshold(), { code: error.ErrorCodes.ERR__NOT_IMPLEMENTED });
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
if (this.#running) {
|
|
1302
|
+
throw new error.KafkaJSError('Consumer is already running.', { code: error.ErrorCodes.ERR__STATE });
|
|
1303
|
+
}
|
|
1304
|
+
this.#running = true;
|
|
1305
|
+
|
|
1306
|
+
/* We're going to add keys to the configuration, so make a copy */
|
|
1307
|
+
const configCopy = Object.assign({}, config);
|
|
1308
|
+
|
|
1309
|
+
/* Batches are auto resolved by default. */
|
|
1310
|
+
if (!Object.hasOwn(config, 'eachBatchAutoResolve')) {
|
|
1311
|
+
configCopy.eachBatchAutoResolve = true;
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
if (!Object.hasOwn(config, 'partitionsConsumedConcurrently')) {
|
|
1315
|
+
configCopy.partitionsConsumedConcurrently = 1;
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
this.#messageCache = new MessageCache(this.#logger);
|
|
1319
|
+
/* We deliberately don't await this because we want to return from this method immediately. */
|
|
1320
|
+
this.#runInternal(configCopy);
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
/**
|
|
1324
|
+
* Processes a single message.
|
|
1325
|
+
*
|
|
1326
|
+
* @param m Message as obtained from #consumeSingleCached.
|
|
1327
|
+
* @param config Config as passed to run().
|
|
1328
|
+
* @returns {Promise<number>} The cache index of the message that was processed.
|
|
1329
|
+
* @private
|
|
1330
|
+
*/
|
|
1331
|
+
async #messageProcessor(m, config, worker) {
|
|
1332
|
+
let ppc;
|
|
1333
|
+
[m, ppc] = m;
|
|
1334
|
+
let key = partitionKey(m);
|
|
1335
|
+
let eachMessageProcessed = false;
|
|
1336
|
+
const payload = this.#createPayload(m, worker);
|
|
1337
|
+
|
|
1338
|
+
try {
|
|
1339
|
+
this.#lastConsumedOffsets.set(key, m);
|
|
1340
|
+
await config.eachMessage(payload);
|
|
1341
|
+
eachMessageProcessed = true;
|
|
1342
|
+
} catch (e) {
|
|
1343
|
+
/* It's not only possible, but expected that an error will be thrown by eachMessage.
|
|
1344
|
+
* This is especially true since the pattern of pause() followed by throwing an error
|
|
1345
|
+
* is encouraged. To meet the API contract, we seek one offset backward (which
|
|
1346
|
+
* means seeking to the message offset).
|
|
1347
|
+
* However, we don't do this inside the catch, but just outside it. This is because throwing an
|
|
1348
|
+
* error is not the only case where we might want to seek back.
|
|
1349
|
+
*
|
|
1350
|
+
* So - do nothing but a log, but at this point eachMessageProcessed is false.
|
|
1351
|
+
* TODO: log error only if error type is not KafkaJSError and if no pause() has been called, else log debug.
|
|
1352
|
+
*/
|
|
1353
|
+
this.#logger.error(
|
|
1354
|
+
`Consumer encountered error while processing message. Error details: ${e}: ${e.stack}. The same message may be reprocessed.`,
|
|
1355
|
+
this.#createConsumerBindingMessageMetadata());
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
/* If the message is unprocessed, due to an error, or because the user has not resolved it, we seek back. */
|
|
1359
|
+
if (!eachMessageProcessed) {
|
|
1360
|
+
this.seek({
|
|
1361
|
+
topic: m.topic,
|
|
1362
|
+
partition: m.partition,
|
|
1363
|
+
offset: m.offset,
|
|
1364
|
+
leaderEpoch: m.leaderEpoch,
|
|
1365
|
+
});
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
/* Store the offsets we need to store, or at least record them for cache invalidation reasons. */
|
|
1369
|
+
if (eachMessageProcessed) {
|
|
1370
|
+
try {
|
|
1371
|
+
this.#internalClient._offsetsStoreSingle(m.topic, m.partition, Number(m.offset) + 1, m.leaderEpoch);
|
|
1372
|
+
} catch (e) {
|
|
1373
|
+
/* Not much we can do, except log the error. */
|
|
1374
|
+
this.#logger.error(`Consumer encountered error while storing offset. Error details: ${JSON.stringify(e)}`, this.#createConsumerBindingMessageMetadata());
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
|
|
1379
|
+
return ppc;
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
/**
|
|
1383
|
+
* Processes a batch of messages.
|
|
1384
|
+
*
|
|
1385
|
+
* @param {object} ms Messages as obtained from #consumeCachedN (ms.length !== 0).
|
|
1386
|
+
* This is of the form [Message[], PerPartitionCache].
|
|
1387
|
+
* @param config Config as passed to run().
|
|
1388
|
+
* @returns {Promise<PerPartitionCache>} the PPC corresponding to
|
|
1389
|
+
* the passed batch.
|
|
1390
|
+
* @private
|
|
1391
|
+
*/
|
|
1392
|
+
async #batchProcessor(ms, config, worker) {
|
|
1393
|
+
let ppc;
|
|
1394
|
+
[ms, ppc] = ms;
|
|
1395
|
+
const key = partitionKey(ms[0]);
|
|
1396
|
+
const payload = this.#createBatchPayload(ms, worker);
|
|
1397
|
+
|
|
1398
|
+
this.#topicPartitionToBatchPayload.set(key, payload);
|
|
1399
|
+
|
|
1400
|
+
let lastOffsetProcessed = { offset: -1, leaderEpoch: -1 };
|
|
1401
|
+
const firstMessage = ms[0];
|
|
1402
|
+
const lastMessage = ms[ms.length - 1];
|
|
1403
|
+
const lastOffset = +(lastMessage.offset);
|
|
1404
|
+
const lastLeaderEpoch = lastMessage.leaderEpoch;
|
|
1405
|
+
try {
|
|
1406
|
+
await config.eachBatch(payload);
|
|
1407
|
+
|
|
1408
|
+
/* If the user isn't resolving offsets, we resolve them here. It's significant here to call this method
|
|
1409
|
+
* because besides updating `payload._lastResolvedOffset`, this method is also storing the offsets to
|
|
1410
|
+
* librdkafka, and accounting for any cache invalidations.
|
|
1411
|
+
* Don't bother resolving offsets if payload became stale at some point. We can't know when the payload
|
|
1412
|
+
* became stale, so either the user has been nice enough to keep resolving messages, or we must seek to
|
|
1413
|
+
* the first offset to ensure no message loss. */
|
|
1414
|
+
if (config.eachBatchAutoResolve && !payload._stale) {
|
|
1415
|
+
payload.resolveOffset(lastOffset, lastLeaderEpoch);
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
lastOffsetProcessed = payload._lastResolvedOffset;
|
|
1419
|
+
} catch (e) {
|
|
1420
|
+
/* It's not only possible, but expected that an error will be thrown by eachBatch.
|
|
1421
|
+
* This is especially true since the pattern of pause() followed by throwing an error
|
|
1422
|
+
* is encouraged. To meet the API contract, we seek one offset backward (which
|
|
1423
|
+
* means seeking to the message offset).
|
|
1424
|
+
* However, we don't do this inside the catch, but just outside it. This is because throwing an
|
|
1425
|
+
* error is not the only case where we might want to seek back. We might want to seek back
|
|
1426
|
+
* if the user has not called `resolveOffset` manually in case of using eachBatch without
|
|
1427
|
+
* eachBatchAutoResolve being set.
|
|
1428
|
+
*
|
|
1429
|
+
* So - do nothing but a log, but at this point eachMessageProcessed needs to be false unless
|
|
1430
|
+
* the user has explicitly marked it as true.
|
|
1431
|
+
* TODO: log error only if error type is not KafkaJSError and if no pause() has been called, else log debug.
|
|
1432
|
+
*/
|
|
1433
|
+
this.#logger.error(
|
|
1434
|
+
`Consumer encountered error while processing message. Error details: ${e}: ${e.stack}. The same message may be reprocessed.`,
|
|
1435
|
+
this.#createConsumerBindingMessageMetadata());
|
|
1436
|
+
|
|
1437
|
+
/* The value of eachBatchAutoResolve is not important. The only place where a message is marked processed
|
|
1438
|
+
* despite an error is if the user says so, and the user can use resolveOffset for both the possible
|
|
1439
|
+
* values eachBatchAutoResolve can take. */
|
|
1440
|
+
lastOffsetProcessed = payload._lastResolvedOffset;
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
this.#topicPartitionToBatchPayload.delete(key);
|
|
1444
|
+
|
|
1445
|
+
/* If any message is unprocessed, either due to an error or due to the user not marking it processed, we must seek
|
|
1446
|
+
* back to get it so it can be reprocessed. */
|
|
1447
|
+
if (!payload._seeked && lastOffsetProcessed.offset !== lastOffset) {
|
|
1448
|
+
const offsetToSeekTo = lastOffsetProcessed.offset === -1 ? firstMessage.offset : (lastOffsetProcessed.offset + 1);
|
|
1449
|
+
const leaderEpoch = lastOffsetProcessed.offset === -1 ? firstMessage.leaderEpoch : lastOffsetProcessed.leaderEpoch;
|
|
1450
|
+
this.seek({
|
|
1451
|
+
topic: firstMessage.topic,
|
|
1452
|
+
partition: firstMessage.partition,
|
|
1453
|
+
offset: offsetToSeekTo,
|
|
1454
|
+
leaderEpoch: leaderEpoch,
|
|
1455
|
+
});
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
return ppc;
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
#returnMessages(ms) {
|
|
1462
|
+
let m = ms[0];
|
|
1463
|
+
// ppc could have been change we must return it as well.
|
|
1464
|
+
let ppc = ms[1];
|
|
1465
|
+
const messagesToReturn = m.constructor === Array ? m : [m];
|
|
1466
|
+
const firstMessage = messagesToReturn[0];
|
|
1467
|
+
if (firstMessage && !this.#lastConsumedOffsets.has(ppc.key)) {
|
|
1468
|
+
this.#lastConsumedOffsets.set(ppc.key, {
|
|
1469
|
+
topic: firstMessage.topic,
|
|
1470
|
+
partition: firstMessage.partition,
|
|
1471
|
+
offset: firstMessage.offset - 1,
|
|
1472
|
+
});
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
this.#messageCache.returnMessages(messagesToReturn);
|
|
1476
|
+
return ppc;
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
#notifyNonEmpty() {
|
|
1480
|
+
if (this.#nonEmpty) {
|
|
1481
|
+
this.#nonEmpty.resolve();
|
|
1482
|
+
this.#nonEmpty = null;
|
|
1483
|
+
}
|
|
1484
|
+
if (this.#messageCache)
|
|
1485
|
+
this.#messageCache.notifyAvailablePartitions();
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
#queueNonEmptyCb() {
|
|
1489
|
+
const nonEmptyAction = async () => {
|
|
1490
|
+
if (this.#fetchInProgress)
|
|
1491
|
+
await this.#fetchInProgress;
|
|
1492
|
+
|
|
1493
|
+
this.#notifyNonEmpty();
|
|
1494
|
+
};
|
|
1495
|
+
nonEmptyAction().catch((e) => {
|
|
1496
|
+
this.#logger.error(`Error in queueNonEmptyCb: ${e}`,
|
|
1497
|
+
this.#createConsumerBindingMessageMetadata());
|
|
1498
|
+
});
|
|
1499
|
+
}
|
|
1500
|
+
/**
|
|
1501
|
+
* Starts a worker to fetch messages/batches from the internal consumer and process them.
|
|
1502
|
+
*
|
|
1503
|
+
* A worker runs until it's told to stop.
|
|
1504
|
+
* Conditions where the worker is told to stop:
|
|
1505
|
+
* 1. Cache globally stale
|
|
1506
|
+
* 2. Disconnected initiated
|
|
1507
|
+
* 3. Rebalance
|
|
1508
|
+
* 4. Some other worker has started terminating.
|
|
1509
|
+
*
|
|
1510
|
+
* Worker termination acts as a async barrier.
|
|
1511
|
+
* @private
|
|
1512
|
+
*/
|
|
1513
|
+
async #worker(config, perMessageProcessor, fetcher) {
|
|
1514
|
+
let ppc = null;
|
|
1515
|
+
let workerId = Math.random().toString().slice(2);
|
|
1516
|
+
while (!this.#workerTerminationScheduled.resolved) {
|
|
1517
|
+
try {
|
|
1518
|
+
const ms = await fetcher(ppc);
|
|
1519
|
+
if (!ms)
|
|
1520
|
+
continue;
|
|
1521
|
+
|
|
1522
|
+
if (this.#pendingOperations.length) {
|
|
1523
|
+
/*
|
|
1524
|
+
* Don't process messages anymore, execute the operations first.
|
|
1525
|
+
* Return the messages to the cache that will be cleared if needed.
|
|
1526
|
+
* `ppc` could have been changed, we must return it as well.
|
|
1527
|
+
*/
|
|
1528
|
+
ppc = this.#returnMessages(ms);
|
|
1529
|
+
} else {
|
|
1530
|
+
ppc = await perMessageProcessor(ms, config, workerId);
|
|
1531
|
+
}
|
|
1532
|
+
} catch (e) {
|
|
1533
|
+
/* Since this error cannot be exposed to the user in the current situation, just log and retry.
|
|
1534
|
+
* This is due to restartOnFailure being set to always true. */
|
|
1535
|
+
if (this.#logger)
|
|
1536
|
+
this.#logger.error(`Consumer encountered error while consuming. Retrying. Error details: ${e} : ${e.stack}`, this.#createConsumerBindingMessageMetadata());
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
if (ppc)
|
|
1541
|
+
this.#messageCache.return(ppc);
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
async #checkMaxPollIntervalNotExceeded(now) {
|
|
1545
|
+
const maxPollExpiration = this.#lastFetchClockNs +
|
|
1546
|
+
BigInt((this.#cacheExpirationTimeoutMs + this.#maxPollIntervalMs)
|
|
1547
|
+
* 1e6);
|
|
1548
|
+
|
|
1549
|
+
let interval = Number(maxPollExpiration - now) / 1e6;
|
|
1550
|
+
if (interval < 1)
|
|
1551
|
+
interval = 1;
|
|
1552
|
+
await Timer.withTimeout(interval,
|
|
1553
|
+
this.#maxPollIntervalRestart);
|
|
1554
|
+
now = hrtime.bigint();
|
|
1555
|
+
|
|
1556
|
+
if (now > (maxPollExpiration - 1000000n)) {
|
|
1557
|
+
this.#markBatchPayloadsStale(this.assignment());
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
/**
|
|
1562
|
+
* Clears the cache and resets the positions when
|
|
1563
|
+
* the internal client hasn't been polled for more than
|
|
1564
|
+
* max poll interval since the last fetch.
|
|
1565
|
+
* After that it waits until barrier is reached or
|
|
1566
|
+
* max poll interval is reached. In the latter case it
|
|
1567
|
+
* marks the batch payloads as stale.
|
|
1568
|
+
* @private
|
|
1569
|
+
*/
|
|
1570
|
+
async #cacheExpirationLoop() {
|
|
1571
|
+
const cacheExpirationInterval = BigInt(this.#cacheExpirationTimeoutMs * 1e6);
|
|
1572
|
+
const maxFetchInterval = BigInt(1000 * 1e6);
|
|
1573
|
+
while (!this.#workerTerminationScheduled.resolved) {
|
|
1574
|
+
let now = hrtime.bigint();
|
|
1575
|
+
const cacheExpirationTimeout = this.#lastFetchClockNs +
|
|
1576
|
+
cacheExpirationInterval;
|
|
1577
|
+
const maxFetchTimeout = this.#lastFetchClockNs +
|
|
1578
|
+
maxFetchInterval;
|
|
1579
|
+
|
|
1580
|
+
if (now > cacheExpirationTimeout) {
|
|
1581
|
+
this.#addPendingOperation(() =>
|
|
1582
|
+
this.#clearCacheAndResetPositions());
|
|
1583
|
+
await this.#checkMaxPollIntervalNotExceeded(now);
|
|
1584
|
+
break;
|
|
1585
|
+
}
|
|
1586
|
+
if (now > maxFetchTimeout) {
|
|
1587
|
+
/* We need to continue fetching even when we're
|
|
1588
|
+
* not getting any messages, for example when all partitions are
|
|
1589
|
+
* paused. */
|
|
1590
|
+
this.#notifyNonEmpty();
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
const awakeTime = maxFetchTimeout < cacheExpirationTimeout ?
|
|
1594
|
+
maxFetchTimeout : cacheExpirationTimeout;
|
|
1595
|
+
|
|
1596
|
+
let interval = Number(awakeTime - now) / 1e6;
|
|
1597
|
+
if (interval < 100)
|
|
1598
|
+
interval = 100;
|
|
1599
|
+
await Timer.withTimeout(interval, this.#maxPollIntervalRestart);
|
|
1600
|
+
if (this.#maxPollIntervalRestart.resolved)
|
|
1601
|
+
this.#maxPollIntervalRestart = new DeferredPromise();
|
|
1602
|
+
}
|
|
1603
|
+
if (this.#maxPollIntervalRestart.resolved)
|
|
1604
|
+
this.#maxPollIntervalRestart = new DeferredPromise();
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
/**
|
|
1608
|
+
* Executes all pending operations and clears the list.
|
|
1609
|
+
* @private
|
|
1610
|
+
*/
|
|
1611
|
+
async #executePendingOperations() {
|
|
1612
|
+
// Execute all pending operations, they could add more operations.
|
|
1613
|
+
while (this.#pendingOperations.length > 0) {
|
|
1614
|
+
const op = this.#pendingOperations.shift();
|
|
1615
|
+
await op();
|
|
1616
|
+
}
|
|
1617
|
+
this.#pendingOperations = [];
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
#resolveWorkerTerminationScheduled() {
|
|
1621
|
+
if (this.#workerTerminationScheduled) {
|
|
1622
|
+
this.#workerTerminationScheduled.resolve();
|
|
1623
|
+
this.#queueNonEmptyCb();
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1627
|
+
/**
|
|
1628
|
+
* Internal polling loop.
|
|
1629
|
+
* Spawns and awaits workers until disconnect is initiated.
|
|
1630
|
+
* @private
|
|
1631
|
+
*/
|
|
1632
|
+
async #runInternal(config) {
|
|
1633
|
+
const perMessageProcessor = config.eachMessage ? this.#messageProcessor : this.#batchProcessor;
|
|
1634
|
+
const fetcher = config.eachMessage
|
|
1635
|
+
? (savedIdx) => this.#consumeSingleCached(savedIdx)
|
|
1636
|
+
: (savedIdx) => this.#consumeCachedN(savedIdx, this.#maxBatchSize);
|
|
1637
|
+
|
|
1638
|
+
await this.#lock.write(async () => {
|
|
1639
|
+
this.#workers = [];
|
|
1640
|
+
this.#concurrency = config.partitionsConsumedConcurrently;
|
|
1641
|
+
this.#maxBatchesSize = (
|
|
1642
|
+
config.eachBatch && this.#maxBatchSize > 0 ?
|
|
1643
|
+
this.#maxBatchSize :
|
|
1644
|
+
1) * this.#concurrency;
|
|
1645
|
+
|
|
1646
|
+
while (!this.#disconnectStarted) {
|
|
1647
|
+
if (this.#maxPollIntervalRestart.resolved)
|
|
1648
|
+
this.#maxPollIntervalRestart = new DeferredPromise();
|
|
1649
|
+
|
|
1650
|
+
this.#workerTerminationScheduled = new DeferredPromise();
|
|
1651
|
+
this.#lastFetchClockNs = hrtime.bigint();
|
|
1652
|
+
this.#lastFetchedMessageCnt = 0;
|
|
1653
|
+
this.#lastFetchedConcurrency = 0;
|
|
1654
|
+
if (this.#pendingOperations.length === 0) {
|
|
1655
|
+
const workersToSpawn = Math.max(1, Math.min(this.#concurrency, this.#partitionCount));
|
|
1656
|
+
const cacheExpirationLoop = this.#cacheExpirationLoop();
|
|
1657
|
+
this.#logger.debug(`Spawning ${workersToSpawn} workers`, this.#createConsumerBindingMessageMetadata());
|
|
1658
|
+
this.#workers =
|
|
1659
|
+
Array(workersToSpawn)
|
|
1660
|
+
.fill()
|
|
1661
|
+
.map((_, i) =>
|
|
1662
|
+
this.#worker(config, perMessageProcessor.bind(this), fetcher.bind(this))
|
|
1663
|
+
.catch(e => {
|
|
1664
|
+
if (this.#logger)
|
|
1665
|
+
this.#logger.error(`Worker ${i} encountered an error: ${e}:${e.stack}`);
|
|
1666
|
+
}));
|
|
1667
|
+
|
|
1668
|
+
/* Best we can do is log errors on worker issues - handled by the catch block above. */
|
|
1669
|
+
await Promise.allSettled(this.#workers);
|
|
1670
|
+
this.#maxPollIntervalRestart.resolve();
|
|
1671
|
+
await cacheExpirationLoop;
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1674
|
+
await this.#executePendingOperations();
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
});
|
|
1678
|
+
this.#maxPollIntervalRestart.resolve();
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
async #commitOffsetsUntilNoStateErr(offsetsToCommit) {
|
|
1682
|
+
let err = { code: error.ErrorCodes.ERR_NO_ERROR };
|
|
1683
|
+
do {
|
|
1684
|
+
try {
|
|
1685
|
+
await this.commitOffsets(offsetsToCommit);
|
|
1686
|
+
} catch (e) {
|
|
1687
|
+
err = e;
|
|
1688
|
+
}
|
|
1689
|
+
} while (err.code && err.code === error.ErrorCodes.ERR__STATE);
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1692
|
+
/**
|
|
1693
|
+
* Commit offsets for the given topic partitions. If topic partitions are not specified, commits all offsets.
|
|
1694
|
+
* @param {Array<{topic: string, partition: number, offset: string, leaderEpoch: number|null, metadata: string|null}>?} topicPartitions
|
|
1695
|
+
* @returns {Promise<void>} A promise that resolves when the offsets have been committed.
|
|
1696
|
+
*/
|
|
1697
|
+
async commitOffsets(topicPartitions = null) {
|
|
1698
|
+
if (this.#state !== ConsumerState.CONNECTED) {
|
|
1699
|
+
throw new error.KafkaJSError('Commit can only be called while connected.', { code: error.ErrorCodes.ERR__STATE });
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1702
|
+
return new Promise((resolve, reject) => {
|
|
1703
|
+
try {
|
|
1704
|
+
let cb = (e) => {
|
|
1705
|
+
if (e)
|
|
1706
|
+
reject(createKafkaJsErrorFromLibRdKafkaError(e));
|
|
1707
|
+
else
|
|
1708
|
+
resolve();
|
|
1709
|
+
};
|
|
1710
|
+
|
|
1711
|
+
if (topicPartitions)
|
|
1712
|
+
topicPartitions = topicPartitions.map(topicPartitionOffsetMetadataToRdKafka);
|
|
1713
|
+
else
|
|
1714
|
+
topicPartitions = null;
|
|
1715
|
+
this.#internalClient.commitCb(topicPartitions, cb);
|
|
1716
|
+
} catch (e) {
|
|
1717
|
+
if (!e.code || e.code !== error.ErrorCodes.ERR__NO_OFFSET)
|
|
1718
|
+
reject(createKafkaJsErrorFromLibRdKafkaError(e));
|
|
1719
|
+
else
|
|
1720
|
+
resolve();
|
|
1721
|
+
}
|
|
1722
|
+
});
|
|
1723
|
+
}
|
|
1724
|
+
|
|
1725
|
+
/**
|
|
1726
|
+
* Fetch committed offsets for the given topic partitions.
|
|
1727
|
+
*
|
|
1728
|
+
* @param {Array<{topic: string, partition: number}>?} topicPartitions -
|
|
1729
|
+
* The topic partitions to check for committed offsets. Defaults to all assigned partitions.
|
|
1730
|
+
* @param {number} timeout - Timeout in ms. Defaults to infinite (-1).
|
|
1731
|
+
* @returns {Promise<Array<{topic: string, partition: number, offset: string, leaderEpoch: number|null, metadata: string|null}>>} A promise that resolves to the committed offsets.
|
|
1732
|
+
*/
|
|
1733
|
+
async committed(topicPartitions = null, timeout = -1) {
|
|
1734
|
+
if (this.#state !== ConsumerState.CONNECTED) {
|
|
1735
|
+
throw new error.KafkaJSError('Committed can only be called while connected.', { code: error.ErrorCodes.ERR__STATE });
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1738
|
+
if (!topicPartitions) {
|
|
1739
|
+
topicPartitions = this.assignment();
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
const topicPartitionsRdKafka = topicPartitions.map(
|
|
1743
|
+
topicPartitionOffsetToRdKafka);
|
|
1744
|
+
|
|
1745
|
+
return new Promise((resolve, reject) => {
|
|
1746
|
+
this.#internalClient.committed(topicPartitionsRdKafka, timeout, (err, offsets) => {
|
|
1747
|
+
if (err) {
|
|
1748
|
+
reject(createKafkaJsErrorFromLibRdKafkaError(err));
|
|
1749
|
+
return;
|
|
1750
|
+
}
|
|
1751
|
+
resolve(offsets.map(topicPartitionOffsetMetadataToKafkaJS));
|
|
1752
|
+
});
|
|
1753
|
+
});
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
/**
|
|
1757
|
+
* Apply pending seeks to topic partitions we have just obtained as a result of a rebalance.
|
|
1758
|
+
* @param {Array<{topic: string, partition: number}>} assignment The list of topic partitions to check for pending seeks.
|
|
1759
|
+
* @returns {Array<{topic: string, partition: number, offset: number}>} The new assignment with the offsets seeked to, which can be passed to assign().
|
|
1760
|
+
* @private
|
|
1761
|
+
*/
|
|
1762
|
+
#assignAsPerSeekedOffsets(assignment) {
|
|
1763
|
+
for (let i = 0; i < assignment.length; i++) {
|
|
1764
|
+
const topicPartition = assignment[i];
|
|
1765
|
+
const key = partitionKey(topicPartition);
|
|
1766
|
+
if (!this.#pendingSeeks.has(key))
|
|
1767
|
+
continue;
|
|
1768
|
+
|
|
1769
|
+
const tpo = this.#pendingSeeks.get(key);
|
|
1770
|
+
this.#pendingSeeks.delete(key);
|
|
1771
|
+
|
|
1772
|
+
assignment[i].offset = tpo.offset;
|
|
1773
|
+
assignment[i].leaderEpoch = tpo.leaderEpoch;
|
|
1774
|
+
}
|
|
1775
|
+
return assignment;
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1778
|
+
#addPendingOperation(fun) {
|
|
1779
|
+
if (this.#pendingOperations.length === 0) {
|
|
1780
|
+
this.#resolveWorkerTerminationScheduled();
|
|
1781
|
+
}
|
|
1782
|
+
this.#pendingOperations.push(fun);
|
|
1783
|
+
}
|
|
1784
|
+
|
|
1785
|
+
async #seekInternal(topicPartitionOffsets) {
|
|
1786
|
+
if (topicPartitionOffsets.length === 0) {
|
|
1787
|
+
return;
|
|
1788
|
+
}
|
|
1789
|
+
|
|
1790
|
+
// Uncomment to test an additional delay in seek
|
|
1791
|
+
// await Timer.withTimeout(1000);
|
|
1792
|
+
|
|
1793
|
+
const seekedPartitions = [];
|
|
1794
|
+
const pendingSeeks = new Map();
|
|
1795
|
+
const assignmentSet = new Set();
|
|
1796
|
+
for (const topicPartitionOffset of topicPartitionOffsets) {
|
|
1797
|
+
const key = partitionKey(topicPartitionOffset);
|
|
1798
|
+
pendingSeeks.set(key, topicPartitionOffset);
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1801
|
+
const assignment = this.assignment();
|
|
1802
|
+
for (const topicPartition of assignment) {
|
|
1803
|
+
const key = partitionKey(topicPartition);
|
|
1804
|
+
assignmentSet.add(key);
|
|
1805
|
+
if (!pendingSeeks.has(key))
|
|
1806
|
+
continue;
|
|
1807
|
+
seekedPartitions.push([key, pendingSeeks.get(key)]);
|
|
1808
|
+
}
|
|
1809
|
+
|
|
1810
|
+
for (const topicPartitionOffset of topicPartitionOffsets) {
|
|
1811
|
+
const key = partitionKey(topicPartitionOffset);
|
|
1812
|
+
if (!assignmentSet.has(key))
|
|
1813
|
+
this.#pendingSeeks.set(key, topicPartitionOffset);
|
|
1814
|
+
}
|
|
1815
|
+
|
|
1816
|
+
const offsetsToCommit = [];
|
|
1817
|
+
const librdkafkaSeekPromises = [];
|
|
1818
|
+
for (const [key, topicPartitionOffset] of seekedPartitions) {
|
|
1819
|
+
this.#lastConsumedOffsets.delete(key);
|
|
1820
|
+
this.#messageCache.markStale([topicPartitionOffset]);
|
|
1821
|
+
offsetsToCommit.push(topicPartitionOffset);
|
|
1822
|
+
|
|
1823
|
+
const librdkafkaSeekPromise = new DeferredPromise();
|
|
1824
|
+
this.#internalClient.seek(topicPartitionOffset, 1000,
|
|
1825
|
+
(err) => {
|
|
1826
|
+
if (err)
|
|
1827
|
+
this.#logger.error(`Error while calling seek from within seekInternal: ${err}`, this.#createConsumerBindingMessageMetadata());
|
|
1828
|
+
librdkafkaSeekPromise.resolve();
|
|
1829
|
+
});
|
|
1830
|
+
librdkafkaSeekPromises.push(librdkafkaSeekPromise);
|
|
1831
|
+
}
|
|
1832
|
+
await Promise.allSettled(librdkafkaSeekPromises);
|
|
1833
|
+
await Promise.all(librdkafkaSeekPromises);
|
|
1834
|
+
|
|
1835
|
+
for (const [key, ] of seekedPartitions) {
|
|
1836
|
+
this.#pendingSeeks.delete(key);
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
/* Offsets are committed on seek only when in compatibility mode. */
|
|
1840
|
+
if (offsetsToCommit.length !== 0 && this.#internalConfig['enable.auto.commit']) {
|
|
1841
|
+
await this.#commitOffsetsUntilNoStateErr(offsetsToCommit);
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
|
|
1845
|
+
#markBatchPayloadsStale(topicPartitions, isSeek) {
|
|
1846
|
+
for (const topicPartition of topicPartitions) {
|
|
1847
|
+
const key = partitionKey(topicPartition);
|
|
1848
|
+
if (this.#topicPartitionToBatchPayload.has(key)) {
|
|
1849
|
+
const payload = this.#topicPartitionToBatchPayload.get(key);
|
|
1850
|
+
payload._stale = true;
|
|
1851
|
+
if (isSeek)
|
|
1852
|
+
payload._seeked = true;
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1856
|
+
|
|
1857
|
+
async #pauseInternal(topicPartitions) {
|
|
1858
|
+
// Uncomment to test future async pause
|
|
1859
|
+
// await Timer.withTimeout(1000);
|
|
1860
|
+
|
|
1861
|
+
this.#messageCache.markStale(topicPartitions);
|
|
1862
|
+
this.#internalClient.pause(topicPartitions);
|
|
1863
|
+
|
|
1864
|
+
const seekOffsets = [];
|
|
1865
|
+
for (let topicPartition of topicPartitions) {
|
|
1866
|
+
const key = partitionKey(topicPartition);
|
|
1867
|
+
if (this.#lastConsumedOffsets.has(key)) {
|
|
1868
|
+
const seekOffset = this.#lastConsumedOffsets.get(key);
|
|
1869
|
+
const topicPartitionOffset = {
|
|
1870
|
+
topic: topicPartition.topic,
|
|
1871
|
+
partition: topicPartition.partition,
|
|
1872
|
+
offset: seekOffset.offset + 1,
|
|
1873
|
+
leaderEpoch: seekOffset.leaderEpoch,
|
|
1874
|
+
};
|
|
1875
|
+
seekOffsets.push(topicPartitionOffset);
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
if (seekOffsets.length) {
|
|
1879
|
+
await this.#seekInternal(seekOffsets);
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1883
|
+
async #resumeInternal(topicPartitions) {
|
|
1884
|
+
// Uncomment to test future async resume
|
|
1885
|
+
// await Timer.withTimeout(1000);
|
|
1886
|
+
this.#internalClient.resume(topicPartitions);
|
|
1887
|
+
}
|
|
1888
|
+
|
|
1889
|
+
/**
|
|
1890
|
+
* Seek to the given offset for a topic partition.
|
|
1891
|
+
*
|
|
1892
|
+
* This method is completely asynchronous, and does not wait for the seek to complete.
|
|
1893
|
+
* In case any partitions that are seeked to, are not a part of the current assignment, they are stored internally.
|
|
1894
|
+
*
|
|
1895
|
+
* If at any later time, the consumer is assigned the partition, as a part of a rebalance,
|
|
1896
|
+
* the pending seek will be performed.
|
|
1897
|
+
*
|
|
1898
|
+
* Additionally, if the librdkafka property 'enable.auto.commit' or kafkaJS.autoCommit is true,
|
|
1899
|
+
* the consumer will commit the offset seeked.
|
|
1900
|
+
*
|
|
1901
|
+
* @param {object} topicPartitionOffset
|
|
1902
|
+
* @param {string} topicPartitionOffset.topic
|
|
1903
|
+
* @param {number} topicPartitionOffset.partition
|
|
1904
|
+
* @param {string} topicPartitionOffset.offset - The offset to seek to.
|
|
1905
|
+
*/
|
|
1906
|
+
seek(topicPartitionOffset) {
|
|
1907
|
+
if (this.#state !== ConsumerState.CONNECTED) {
|
|
1908
|
+
throw new error.KafkaJSError('Seek can only be called while connected.', { code: error.ErrorCodes.ERR__STATE });
|
|
1909
|
+
}
|
|
1910
|
+
|
|
1911
|
+
const rdKafkaTopicPartitionOffset =
|
|
1912
|
+
topicPartitionOffsetToRdKafka(topicPartitionOffset);
|
|
1913
|
+
|
|
1914
|
+
if (typeof rdKafkaTopicPartitionOffset.topic !== 'string') {
|
|
1915
|
+
throw new error.KafkaJSError('Topic must be a string.', { code: error.ErrorCodes.ERR__INVALID_ARG });
|
|
1916
|
+
}
|
|
1917
|
+
|
|
1918
|
+
if (isNaN(rdKafkaTopicPartitionOffset.offset) || (rdKafkaTopicPartitionOffset.offset < 0 && rdKafkaTopicPartitionOffset.offset !== -2 && rdKafkaTopicPartitionOffset.offset !== -3)) {
|
|
1919
|
+
throw new error.KafkaJSError('Offset must be >= 0, or a special value.', { code: error.ErrorCodes.ERR__INVALID_ARG });
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1922
|
+
/* If anyone's using eachBatch, mark the batch as stale. */
|
|
1923
|
+
this.#markBatchPayloadsStale([rdKafkaTopicPartitionOffset], true);
|
|
1924
|
+
|
|
1925
|
+
this.#addPendingOperation(() =>
|
|
1926
|
+
this.#seekInternal([rdKafkaTopicPartitionOffset]));
|
|
1927
|
+
}
|
|
1928
|
+
|
|
1929
|
+
async describeGroup() {
|
|
1930
|
+
notImplemented();
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1933
|
+
/**
|
|
1934
|
+
* Find the assigned topic partitions for the consumer.
|
|
1935
|
+
* @returns {Array<{topic: string, partitions: Array<number>}>} the current assignment.
|
|
1936
|
+
*/
|
|
1937
|
+
assignment() {
|
|
1938
|
+
if (this.#state !== ConsumerState.CONNECTED) {
|
|
1939
|
+
throw new error.KafkaJSError('Assignment can only be called while connected.', { code: error.ErrorCodes.ERR__STATE });
|
|
1940
|
+
}
|
|
1941
|
+
|
|
1942
|
+
return this.#flattenTopicPartitions(this.#internalClient.assignments());
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1945
|
+
/**
|
|
1946
|
+
* Get the type of rebalance protocol used in the consumer group.
|
|
1947
|
+
*
|
|
1948
|
+
* @returns {string} "NONE" (if not in a group yet), "COOPERATIVE" or "EAGER".
|
|
1949
|
+
*/
|
|
1950
|
+
rebalanceProtocol() {
|
|
1951
|
+
if (this.#state !== ConsumerState.CONNECTED) {
|
|
1952
|
+
return "NONE";
|
|
1953
|
+
}
|
|
1954
|
+
return this.#internalClient.rebalanceProtocol();
|
|
1955
|
+
}
|
|
1956
|
+
|
|
1957
|
+
/**
|
|
1958
|
+
* Fetches all partitions of topic that are assigned to this consumer.
|
|
1959
|
+
* @param {string} topic
|
|
1960
|
+
* @returns {Array<number>} A list of partitions.
|
|
1961
|
+
* @private
|
|
1962
|
+
*/
|
|
1963
|
+
#getAllAssignedPartition(topic) {
|
|
1964
|
+
return this.#internalClient.assignments()
|
|
1965
|
+
.filter((partition) => partition.topic === topic)
|
|
1966
|
+
.map((tpo) => tpo.partition);
|
|
1967
|
+
}
|
|
1968
|
+
|
|
1969
|
+
/**
|
|
1970
|
+
* Pauses the given topic partitions. If partitions are not specified, pauses
|
|
1971
|
+
* all partitions for the given topic.
|
|
1972
|
+
*
|
|
1973
|
+
* If topic partition(s) are already paused this method has no effect.
|
|
1974
|
+
*
|
|
1975
|
+
* @param {Array<{topic: string, partitions: Array<number>|null}>} topics - Topics or topic partitions to pause.
|
|
1976
|
+
* @returns {function} A function that can be called to resume the topic partitions paused by this call.
|
|
1977
|
+
*/
|
|
1978
|
+
pause(topics) {
|
|
1979
|
+
if (this.#state !== ConsumerState.CONNECTED) {
|
|
1980
|
+
throw new error.KafkaJSError('Pause can only be called while connected.', { code: error.ErrorCodes.ERR__STATE });
|
|
1981
|
+
}
|
|
1982
|
+
|
|
1983
|
+
this.#logger.debug(`Pausing ${topics.length} topics`, this.#createConsumerBindingMessageMetadata());
|
|
1984
|
+
|
|
1985
|
+
const toppars = [];
|
|
1986
|
+
for (let topic of topics) {
|
|
1987
|
+
if (typeof topic.topic !== 'string') {
|
|
1988
|
+
throw new error.KafkaJSError('Topic must be a string.', { code: error.ErrorCodes.ERR__INVALID_ARG });
|
|
1989
|
+
}
|
|
1990
|
+
|
|
1991
|
+
const toppar = { topic: topic.topic };
|
|
1992
|
+
|
|
1993
|
+
if (!topic.partitions) {
|
|
1994
|
+
toppar.partitions = this.#getAllAssignedPartition(topic.topic);
|
|
1995
|
+
} else {
|
|
1996
|
+
/* TODO: add a check here to make sure we own each partition */
|
|
1997
|
+
toppar.partitions = [...topic.partitions];
|
|
1998
|
+
}
|
|
1999
|
+
|
|
2000
|
+
toppars.push(toppar);
|
|
2001
|
+
}
|
|
2002
|
+
|
|
2003
|
+
const flattenedToppars = this.#flattenTopicPartitions(toppars);
|
|
2004
|
+
if (flattenedToppars.length === 0) {
|
|
2005
|
+
return;
|
|
2006
|
+
}
|
|
2007
|
+
|
|
2008
|
+
/* If anyone's using eachBatch, mark the batch as stale. */
|
|
2009
|
+
this.#markBatchPayloadsStale(flattenedToppars);
|
|
2010
|
+
|
|
2011
|
+
flattenedToppars.forEach(
|
|
2012
|
+
topicPartition => this.#pausedPartitions.set(
|
|
2013
|
+
partitionKey(topicPartition),
|
|
2014
|
+
topicPartition));
|
|
2015
|
+
|
|
2016
|
+
this.#addPendingOperation(() =>
|
|
2017
|
+
this.#pauseInternal(flattenedToppars));
|
|
2018
|
+
|
|
2019
|
+
/* Note: we don't use flattenedToppars here because resume flattens them again. */
|
|
2020
|
+
return () => this.resume(toppars);
|
|
2021
|
+
}
|
|
2022
|
+
|
|
2023
|
+
/**
|
|
2024
|
+
* Returns the list of paused topic partitions.
|
|
2025
|
+
* @returns {Array<{topic: string, partitions: Array<number>}>} A list of paused topic partitions.
|
|
2026
|
+
*/
|
|
2027
|
+
paused() {
|
|
2028
|
+
const topicToPartitions = Array
|
|
2029
|
+
.from(this.#pausedPartitions.values())
|
|
2030
|
+
.reduce(
|
|
2031
|
+
(acc, { topic, partition }) => {
|
|
2032
|
+
if (!acc[topic]) {
|
|
2033
|
+
acc[topic] = [];
|
|
2034
|
+
}
|
|
2035
|
+
acc[topic].push(partition);
|
|
2036
|
+
return acc;
|
|
2037
|
+
},
|
|
2038
|
+
{});
|
|
2039
|
+
return Array.from(Object.entries(topicToPartitions), ([topic, partitions]) => ({ topic, partitions }));
|
|
2040
|
+
}
|
|
2041
|
+
|
|
2042
|
+
|
|
2043
|
+
/**
|
|
2044
|
+
* Resumes the given topic partitions. If partitions are not specified, resumes
|
|
2045
|
+
* all partitions for the given topic.
|
|
2046
|
+
*
|
|
2047
|
+
* If topic partition(s) are already resumed this method has no effect.
|
|
2048
|
+
* @param {Array<{topic: string, partitions: Array<number>|null}>} topics - Topics or topic partitions to resume.
|
|
2049
|
+
*/
|
|
2050
|
+
resume(topics) {
|
|
2051
|
+
if (this.#state !== ConsumerState.CONNECTED) {
|
|
2052
|
+
throw new error.KafkaJSError('Resume can only be called while connected.', { code: error.ErrorCodes.ERR__STATE });
|
|
2053
|
+
}
|
|
2054
|
+
|
|
2055
|
+
this.#logger.debug(`Resuming ${topics.length} topics`, this.#createConsumerBindingMessageMetadata());
|
|
2056
|
+
|
|
2057
|
+
const toppars = [];
|
|
2058
|
+
for (let topic of topics) {
|
|
2059
|
+
if (typeof topic.topic !== 'string') {
|
|
2060
|
+
throw new error.KafkaJSError('Topic must be a string.', { code: error.ErrorCodes.ERR__INVALID_ARG });
|
|
2061
|
+
}
|
|
2062
|
+
const toppar = { topic: topic.topic };
|
|
2063
|
+
|
|
2064
|
+
if (!topic.partitions) {
|
|
2065
|
+
toppar.partitions = this.#getAllAssignedPartition(topic.topic);
|
|
2066
|
+
} else {
|
|
2067
|
+
toppar.partitions = [...topic.partitions];
|
|
2068
|
+
}
|
|
2069
|
+
|
|
2070
|
+
toppars.push(toppar);
|
|
2071
|
+
}
|
|
2072
|
+
|
|
2073
|
+
const flattenedToppars = this.#flattenTopicPartitions(toppars);
|
|
2074
|
+
if (flattenedToppars.length === 0) {
|
|
2075
|
+
return;
|
|
2076
|
+
}
|
|
2077
|
+
flattenedToppars.map(partitionKey).
|
|
2078
|
+
forEach(key => this.#pausedPartitions.delete(key));
|
|
2079
|
+
|
|
2080
|
+
this.#addPendingOperation(() =>
|
|
2081
|
+
this.#resumeInternal(flattenedToppars));
|
|
2082
|
+
}
|
|
2083
|
+
|
|
2084
|
+
on(/* eventName, listener */) {
|
|
2085
|
+
notImplemented();
|
|
2086
|
+
}
|
|
2087
|
+
|
|
2088
|
+
/**
|
|
2089
|
+
* Get the logger associated to this consumer instance.
|
|
2090
|
+
*
|
|
2091
|
+
* @example
|
|
2092
|
+
* const logger = consumer.logger();
|
|
2093
|
+
* logger.info('Hello world');
|
|
2094
|
+
* logger.setLogLevel(logLevel.ERROR);
|
|
2095
|
+
* @see {@link KafkaJS.logLevel} for available log levels
|
|
2096
|
+
* @returns {Object} The logger instance.
|
|
2097
|
+
*/
|
|
2098
|
+
logger() {
|
|
2099
|
+
return this.#logger;
|
|
2100
|
+
}
|
|
2101
|
+
|
|
2102
|
+
get events() {
|
|
2103
|
+
notImplemented();
|
|
2104
|
+
return null;
|
|
2105
|
+
}
|
|
2106
|
+
|
|
2107
|
+
/**
|
|
2108
|
+
* Disconnects and cleans up the consumer.
|
|
2109
|
+
*
|
|
2110
|
+
* Warning: This cannot be called from within `eachMessage` or `eachBatch` callback of
|
|
2111
|
+
* [Consumer.run]{@link KafkaJS.Consumer#run}.
|
|
2112
|
+
* @returns {Promise<void>} A promise that resolves when the consumer has disconnected.
|
|
2113
|
+
*/
|
|
2114
|
+
async disconnect() {
|
|
2115
|
+
/* Not yet connected - no error. */
|
|
2116
|
+
if (this.#state === ConsumerState.INIT) {
|
|
2117
|
+
return;
|
|
2118
|
+
}
|
|
2119
|
+
|
|
2120
|
+
/* TODO: We should handle a case where we are connecting, we should
|
|
2121
|
+
* await the connection and then schedule a disconnect. */
|
|
2122
|
+
|
|
2123
|
+
/* Already disconnecting, or disconnected. */
|
|
2124
|
+
if (this.#state >= ConsumerState.DISCONNECTING) {
|
|
2125
|
+
return;
|
|
2126
|
+
}
|
|
2127
|
+
if (this.#state >= ConsumerState.DISCONNECTING) {
|
|
2128
|
+
return;
|
|
2129
|
+
}
|
|
2130
|
+
|
|
2131
|
+
this.#disconnectStarted = true;
|
|
2132
|
+
this.#resolveWorkerTerminationScheduled();
|
|
2133
|
+
this.#logger.debug("Signalling disconnection attempt to workers", this.#createConsumerBindingMessageMetadata());
|
|
2134
|
+
await this.#lock.write(async () => {
|
|
2135
|
+
|
|
2136
|
+
this.#state = ConsumerState.DISCONNECTING;
|
|
2137
|
+
|
|
2138
|
+
});
|
|
2139
|
+
|
|
2140
|
+
await new Promise((resolve, reject) => {
|
|
2141
|
+
const cb = (err) => {
|
|
2142
|
+
if (err) {
|
|
2143
|
+
reject(createKafkaJsErrorFromLibRdKafkaError(err));
|
|
2144
|
+
return;
|
|
2145
|
+
}
|
|
2146
|
+
this.#state = ConsumerState.DISCONNECTED;
|
|
2147
|
+
this.#logger.info("Consumer disconnected", this.#createConsumerBindingMessageMetadata());
|
|
2148
|
+
resolve();
|
|
2149
|
+
};
|
|
2150
|
+
this.#internalClient.unsubscribe();
|
|
2151
|
+
this.#internalClient.disconnect(cb);
|
|
2152
|
+
});
|
|
2153
|
+
}
|
|
2154
|
+
}
|
|
2155
|
+
|
|
2156
|
+
module.exports = { Consumer, PartitionAssigners: Object.freeze(PartitionAssigners), };
|