@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.
Files changed (628) hide show
  1. package/CHANGELOG.md +457 -0
  2. package/LICENSE.kafkajs +31 -0
  3. package/LICENSE.node-rdkafka +25 -0
  4. package/LICENSE.txt +20 -0
  5. package/README.md +133 -0
  6. package/binding.gyp +184 -0
  7. package/build/Release/confluent-kafka-javascript.node +0 -0
  8. package/build/deps/librdkafka++.so.1 +0 -0
  9. package/build/deps/librdkafka.Makefile +6 -0
  10. package/build/deps/librdkafka.so.1 +0 -0
  11. package/build/deps/librdkafka.target.mk +55 -0
  12. package/deps/librdkafka/.clang-format +136 -0
  13. package/deps/librdkafka/.clang-format-cpp +103 -0
  14. package/deps/librdkafka/.dir-locals.el +10 -0
  15. package/deps/librdkafka/.formatignore +35 -0
  16. package/deps/librdkafka/.gdbmacros +19 -0
  17. package/deps/librdkafka/CHANGELOG.md +2477 -0
  18. package/deps/librdkafka/CMakeLists.txt +291 -0
  19. package/deps/librdkafka/CODE_OF_CONDUCT.md +46 -0
  20. package/deps/librdkafka/CONFIGURATION.md +212 -0
  21. package/deps/librdkafka/CONTRIBUTING.md +431 -0
  22. package/deps/librdkafka/Doxyfile +2375 -0
  23. package/deps/librdkafka/INTRODUCTION.md +2752 -0
  24. package/deps/librdkafka/LICENSE +26 -0
  25. package/deps/librdkafka/LICENSE.cjson +22 -0
  26. package/deps/librdkafka/LICENSE.crc32c +28 -0
  27. package/deps/librdkafka/LICENSE.fnv1a +18 -0
  28. package/deps/librdkafka/LICENSE.hdrhistogram +27 -0
  29. package/deps/librdkafka/LICENSE.lz4 +26 -0
  30. package/deps/librdkafka/LICENSE.murmur2 +25 -0
  31. package/deps/librdkafka/LICENSE.nanopb +22 -0
  32. package/deps/librdkafka/LICENSE.opentelemetry +203 -0
  33. package/deps/librdkafka/LICENSE.pycrc +23 -0
  34. package/deps/librdkafka/LICENSE.queue +31 -0
  35. package/deps/librdkafka/LICENSE.regexp +5 -0
  36. package/deps/librdkafka/LICENSE.snappy +36 -0
  37. package/deps/librdkafka/LICENSE.tinycthread +26 -0
  38. package/deps/librdkafka/LICENSE.wingetopt +49 -0
  39. package/deps/librdkafka/LICENSES.txt +625 -0
  40. package/deps/librdkafka/Makefile +125 -0
  41. package/deps/librdkafka/README.md +201 -0
  42. package/deps/librdkafka/README.win32 +26 -0
  43. package/deps/librdkafka/STATISTICS.md +624 -0
  44. package/deps/librdkafka/configure +214 -0
  45. package/deps/librdkafka/configure.self +341 -0
  46. package/deps/librdkafka/dev-conf.sh +123 -0
  47. package/deps/librdkafka/lds-gen.py +73 -0
  48. package/deps/librdkafka/mainpage.doxy +40 -0
  49. package/deps/librdkafka/mklove/Makefile.base +329 -0
  50. package/deps/librdkafka/mklove/modules/configure.atomics +144 -0
  51. package/deps/librdkafka/mklove/modules/configure.base +2484 -0
  52. package/deps/librdkafka/mklove/modules/configure.builtin +70 -0
  53. package/deps/librdkafka/mklove/modules/configure.cc +186 -0
  54. package/deps/librdkafka/mklove/modules/configure.cxx +8 -0
  55. package/deps/librdkafka/mklove/modules/configure.fileversion +65 -0
  56. package/deps/librdkafka/mklove/modules/configure.gitversion +29 -0
  57. package/deps/librdkafka/mklove/modules/configure.good_cflags +18 -0
  58. package/deps/librdkafka/mklove/modules/configure.host +132 -0
  59. package/deps/librdkafka/mklove/modules/configure.lib +49 -0
  60. package/deps/librdkafka/mklove/modules/configure.libcurl +99 -0
  61. package/deps/librdkafka/mklove/modules/configure.libsasl2 +36 -0
  62. package/deps/librdkafka/mklove/modules/configure.libssl +147 -0
  63. package/deps/librdkafka/mklove/modules/configure.libzstd +58 -0
  64. package/deps/librdkafka/mklove/modules/configure.parseversion +95 -0
  65. package/deps/librdkafka/mklove/modules/configure.pic +16 -0
  66. package/deps/librdkafka/mklove/modules/configure.socket +20 -0
  67. package/deps/librdkafka/mklove/modules/configure.zlib +61 -0
  68. package/deps/librdkafka/mklove/modules/patches/README.md +8 -0
  69. package/deps/librdkafka/mklove/modules/patches/libcurl.0000-no-runtime-linking-check.patch +11 -0
  70. package/deps/librdkafka/mklove/modules/patches/libssl.0000-osx-rand-include-fix-OpenSSL-PR16409.patch +56 -0
  71. package/deps/librdkafka/service.yml +182 -0
  72. package/deps/librdkafka/src/CMakeLists.txt +379 -0
  73. package/deps/librdkafka/src/Makefile +105 -0
  74. package/deps/librdkafka/src/README.lz4.md +30 -0
  75. package/deps/librdkafka/src/cJSON.c +3191 -0
  76. package/deps/librdkafka/src/cJSON.h +306 -0
  77. package/deps/librdkafka/src/crc32c.c +430 -0
  78. package/deps/librdkafka/src/crc32c.h +38 -0
  79. package/deps/librdkafka/src/generate_proto.sh +66 -0
  80. package/deps/librdkafka/src/librdkafka_cgrp_synch.png +0 -0
  81. package/deps/librdkafka/src/lz4.c +2727 -0
  82. package/deps/librdkafka/src/lz4.h +842 -0
  83. package/deps/librdkafka/src/lz4frame.c +2078 -0
  84. package/deps/librdkafka/src/lz4frame.h +692 -0
  85. package/deps/librdkafka/src/lz4frame_static.h +47 -0
  86. package/deps/librdkafka/src/lz4hc.c +1631 -0
  87. package/deps/librdkafka/src/lz4hc.h +413 -0
  88. package/deps/librdkafka/src/nanopb/pb.h +917 -0
  89. package/deps/librdkafka/src/nanopb/pb_common.c +388 -0
  90. package/deps/librdkafka/src/nanopb/pb_common.h +49 -0
  91. package/deps/librdkafka/src/nanopb/pb_decode.c +1727 -0
  92. package/deps/librdkafka/src/nanopb/pb_decode.h +193 -0
  93. package/deps/librdkafka/src/nanopb/pb_encode.c +1000 -0
  94. package/deps/librdkafka/src/nanopb/pb_encode.h +185 -0
  95. package/deps/librdkafka/src/opentelemetry/common.pb.c +32 -0
  96. package/deps/librdkafka/src/opentelemetry/common.pb.h +170 -0
  97. package/deps/librdkafka/src/opentelemetry/metrics.options +2 -0
  98. package/deps/librdkafka/src/opentelemetry/metrics.pb.c +67 -0
  99. package/deps/librdkafka/src/opentelemetry/metrics.pb.h +966 -0
  100. package/deps/librdkafka/src/opentelemetry/resource.pb.c +12 -0
  101. package/deps/librdkafka/src/opentelemetry/resource.pb.h +58 -0
  102. package/deps/librdkafka/src/queue.h +850 -0
  103. package/deps/librdkafka/src/rd.h +592 -0
  104. package/deps/librdkafka/src/rdaddr.c +255 -0
  105. package/deps/librdkafka/src/rdaddr.h +202 -0
  106. package/deps/librdkafka/src/rdatomic.h +262 -0
  107. package/deps/librdkafka/src/rdavg.h +260 -0
  108. package/deps/librdkafka/src/rdavl.c +210 -0
  109. package/deps/librdkafka/src/rdavl.h +250 -0
  110. package/deps/librdkafka/src/rdbase64.c +200 -0
  111. package/deps/librdkafka/src/rdbase64.h +43 -0
  112. package/deps/librdkafka/src/rdbuf.c +1884 -0
  113. package/deps/librdkafka/src/rdbuf.h +375 -0
  114. package/deps/librdkafka/src/rdcrc32.c +114 -0
  115. package/deps/librdkafka/src/rdcrc32.h +170 -0
  116. package/deps/librdkafka/src/rddl.c +179 -0
  117. package/deps/librdkafka/src/rddl.h +43 -0
  118. package/deps/librdkafka/src/rdendian.h +175 -0
  119. package/deps/librdkafka/src/rdfloat.h +67 -0
  120. package/deps/librdkafka/src/rdfnv1a.c +113 -0
  121. package/deps/librdkafka/src/rdfnv1a.h +35 -0
  122. package/deps/librdkafka/src/rdgz.c +120 -0
  123. package/deps/librdkafka/src/rdgz.h +46 -0
  124. package/deps/librdkafka/src/rdhdrhistogram.c +721 -0
  125. package/deps/librdkafka/src/rdhdrhistogram.h +87 -0
  126. package/deps/librdkafka/src/rdhttp.c +831 -0
  127. package/deps/librdkafka/src/rdhttp.h +101 -0
  128. package/deps/librdkafka/src/rdinterval.h +177 -0
  129. package/deps/librdkafka/src/rdkafka.c +7287 -0
  130. package/deps/librdkafka/src/rdkafka.h +11795 -0
  131. package/deps/librdkafka/src/rdkafka_admin.c +10165 -0
  132. package/deps/librdkafka/src/rdkafka_admin.h +658 -0
  133. package/deps/librdkafka/src/rdkafka_assignment.c +1282 -0
  134. package/deps/librdkafka/src/rdkafka_assignment.h +73 -0
  135. package/deps/librdkafka/src/rdkafka_assignor.c +1800 -0
  136. package/deps/librdkafka/src/rdkafka_assignor.h +402 -0
  137. package/deps/librdkafka/src/rdkafka_aux.c +409 -0
  138. package/deps/librdkafka/src/rdkafka_aux.h +174 -0
  139. package/deps/librdkafka/src/rdkafka_background.c +222 -0
  140. package/deps/librdkafka/src/rdkafka_broker.c +6736 -0
  141. package/deps/librdkafka/src/rdkafka_broker.h +851 -0
  142. package/deps/librdkafka/src/rdkafka_buf.c +543 -0
  143. package/deps/librdkafka/src/rdkafka_buf.h +1525 -0
  144. package/deps/librdkafka/src/rdkafka_cert.c +576 -0
  145. package/deps/librdkafka/src/rdkafka_cert.h +62 -0
  146. package/deps/librdkafka/src/rdkafka_cgrp.c +8310 -0
  147. package/deps/librdkafka/src/rdkafka_cgrp.h +589 -0
  148. package/deps/librdkafka/src/rdkafka_conf.c +5177 -0
  149. package/deps/librdkafka/src/rdkafka_conf.h +746 -0
  150. package/deps/librdkafka/src/rdkafka_confval.h +97 -0
  151. package/deps/librdkafka/src/rdkafka_coord.c +623 -0
  152. package/deps/librdkafka/src/rdkafka_coord.h +132 -0
  153. package/deps/librdkafka/src/rdkafka_error.c +228 -0
  154. package/deps/librdkafka/src/rdkafka_error.h +80 -0
  155. package/deps/librdkafka/src/rdkafka_event.c +502 -0
  156. package/deps/librdkafka/src/rdkafka_event.h +126 -0
  157. package/deps/librdkafka/src/rdkafka_feature.c +898 -0
  158. package/deps/librdkafka/src/rdkafka_feature.h +104 -0
  159. package/deps/librdkafka/src/rdkafka_fetcher.c +3820 -0
  160. package/deps/librdkafka/src/rdkafka_fetcher.h +62 -0
  161. package/deps/librdkafka/src/rdkafka_header.c +220 -0
  162. package/deps/librdkafka/src/rdkafka_header.h +76 -0
  163. package/deps/librdkafka/src/rdkafka_idempotence.c +807 -0
  164. package/deps/librdkafka/src/rdkafka_idempotence.h +144 -0
  165. package/deps/librdkafka/src/rdkafka_int.h +1505 -0
  166. package/deps/librdkafka/src/rdkafka_interceptor.c +842 -0
  167. package/deps/librdkafka/src/rdkafka_interceptor.h +104 -0
  168. package/deps/librdkafka/src/rdkafka_lz4.c +450 -0
  169. package/deps/librdkafka/src/rdkafka_lz4.h +49 -0
  170. package/deps/librdkafka/src/rdkafka_metadata.c +2347 -0
  171. package/deps/librdkafka/src/rdkafka_metadata.h +350 -0
  172. package/deps/librdkafka/src/rdkafka_metadata_cache.c +1183 -0
  173. package/deps/librdkafka/src/rdkafka_mock.c +4514 -0
  174. package/deps/librdkafka/src/rdkafka_mock.h +867 -0
  175. package/deps/librdkafka/src/rdkafka_mock_cgrp.c +1877 -0
  176. package/deps/librdkafka/src/rdkafka_mock_handlers.c +5255 -0
  177. package/deps/librdkafka/src/rdkafka_mock_int.h +1005 -0
  178. package/deps/librdkafka/src/rdkafka_mock_sharegrp.c +1207 -0
  179. package/deps/librdkafka/src/rdkafka_msg.c +2619 -0
  180. package/deps/librdkafka/src/rdkafka_msg.h +629 -0
  181. package/deps/librdkafka/src/rdkafka_msgbatch.h +62 -0
  182. package/deps/librdkafka/src/rdkafka_msgset.h +105 -0
  183. package/deps/librdkafka/src/rdkafka_msgset_reader.c +2099 -0
  184. package/deps/librdkafka/src/rdkafka_msgset_writer.c +1474 -0
  185. package/deps/librdkafka/src/rdkafka_offset.c +1565 -0
  186. package/deps/librdkafka/src/rdkafka_offset.h +150 -0
  187. package/deps/librdkafka/src/rdkafka_op.c +1219 -0
  188. package/deps/librdkafka/src/rdkafka_op.h +1065 -0
  189. package/deps/librdkafka/src/rdkafka_partition.c +5109 -0
  190. package/deps/librdkafka/src/rdkafka_partition.h +1215 -0
  191. package/deps/librdkafka/src/rdkafka_pattern.c +228 -0
  192. package/deps/librdkafka/src/rdkafka_pattern.h +70 -0
  193. package/deps/librdkafka/src/rdkafka_plugin.c +213 -0
  194. package/deps/librdkafka/src/rdkafka_plugin.h +41 -0
  195. package/deps/librdkafka/src/rdkafka_proto.h +739 -0
  196. package/deps/librdkafka/src/rdkafka_protocol.h +131 -0
  197. package/deps/librdkafka/src/rdkafka_queue.c +1351 -0
  198. package/deps/librdkafka/src/rdkafka_queue.h +1235 -0
  199. package/deps/librdkafka/src/rdkafka_range_assignor.c +1748 -0
  200. package/deps/librdkafka/src/rdkafka_request.c +7298 -0
  201. package/deps/librdkafka/src/rdkafka_request.h +761 -0
  202. package/deps/librdkafka/src/rdkafka_roundrobin_assignor.c +123 -0
  203. package/deps/librdkafka/src/rdkafka_sasl.c +589 -0
  204. package/deps/librdkafka/src/rdkafka_sasl.h +63 -0
  205. package/deps/librdkafka/src/rdkafka_sasl_cyrus.c +722 -0
  206. package/deps/librdkafka/src/rdkafka_sasl_int.h +89 -0
  207. package/deps/librdkafka/src/rdkafka_sasl_oauthbearer.c +1833 -0
  208. package/deps/librdkafka/src/rdkafka_sasl_oauthbearer.h +52 -0
  209. package/deps/librdkafka/src/rdkafka_sasl_oauthbearer_oidc.c +1827 -0
  210. package/deps/librdkafka/src/rdkafka_sasl_oauthbearer_oidc.h +52 -0
  211. package/deps/librdkafka/src/rdkafka_sasl_plain.c +142 -0
  212. package/deps/librdkafka/src/rdkafka_sasl_scram.c +858 -0
  213. package/deps/librdkafka/src/rdkafka_sasl_win32.c +550 -0
  214. package/deps/librdkafka/src/rdkafka_share_acknowledgement.c +1445 -0
  215. package/deps/librdkafka/src/rdkafka_share_acknowledgement.h +373 -0
  216. package/deps/librdkafka/src/rdkafka_ssl.c +2235 -0
  217. package/deps/librdkafka/src/rdkafka_ssl.h +86 -0
  218. package/deps/librdkafka/src/rdkafka_sticky_assignor.c +4785 -0
  219. package/deps/librdkafka/src/rdkafka_subscription.c +411 -0
  220. package/deps/librdkafka/src/rdkafka_telemetry.c +761 -0
  221. package/deps/librdkafka/src/rdkafka_telemetry.h +52 -0
  222. package/deps/librdkafka/src/rdkafka_telemetry_decode.c +1307 -0
  223. package/deps/librdkafka/src/rdkafka_telemetry_decode.h +59 -0
  224. package/deps/librdkafka/src/rdkafka_telemetry_encode.c +1309 -0
  225. package/deps/librdkafka/src/rdkafka_telemetry_encode.h +459 -0
  226. package/deps/librdkafka/src/rdkafka_timer.c +407 -0
  227. package/deps/librdkafka/src/rdkafka_timer.h +117 -0
  228. package/deps/librdkafka/src/rdkafka_topic.c +2382 -0
  229. package/deps/librdkafka/src/rdkafka_topic.h +369 -0
  230. package/deps/librdkafka/src/rdkafka_transport.c +1309 -0
  231. package/deps/librdkafka/src/rdkafka_transport.h +99 -0
  232. package/deps/librdkafka/src/rdkafka_transport_int.h +100 -0
  233. package/deps/librdkafka/src/rdkafka_txnmgr.c +3256 -0
  234. package/deps/librdkafka/src/rdkafka_txnmgr.h +171 -0
  235. package/deps/librdkafka/src/rdkafka_zstd.c +226 -0
  236. package/deps/librdkafka/src/rdkafka_zstd.h +57 -0
  237. package/deps/librdkafka/src/rdlist.c +593 -0
  238. package/deps/librdkafka/src/rdlist.h +444 -0
  239. package/deps/librdkafka/src/rdlog.c +89 -0
  240. package/deps/librdkafka/src/rdlog.h +41 -0
  241. package/deps/librdkafka/src/rdmap.c +508 -0
  242. package/deps/librdkafka/src/rdmap.h +492 -0
  243. package/deps/librdkafka/src/rdmurmur2.c +167 -0
  244. package/deps/librdkafka/src/rdmurmur2.h +35 -0
  245. package/deps/librdkafka/src/rdports.c +61 -0
  246. package/deps/librdkafka/src/rdports.h +38 -0
  247. package/deps/librdkafka/src/rdposix.h +250 -0
  248. package/deps/librdkafka/src/rdrand.c +151 -0
  249. package/deps/librdkafka/src/rdrand.h +72 -0
  250. package/deps/librdkafka/src/rdregex.c +156 -0
  251. package/deps/librdkafka/src/rdregex.h +43 -0
  252. package/deps/librdkafka/src/rdsignal.h +57 -0
  253. package/deps/librdkafka/src/rdstring.c +645 -0
  254. package/deps/librdkafka/src/rdstring.h +98 -0
  255. package/deps/librdkafka/src/rdsysqueue.h +404 -0
  256. package/deps/librdkafka/src/rdtime.h +356 -0
  257. package/deps/librdkafka/src/rdtypes.h +86 -0
  258. package/deps/librdkafka/src/rdunittest.c +564 -0
  259. package/deps/librdkafka/src/rdunittest.h +232 -0
  260. package/deps/librdkafka/src/rdunittest_acknowledge.c +1177 -0
  261. package/deps/librdkafka/src/rdunittest_fetcher.c +1211 -0
  262. package/deps/librdkafka/src/rdunittest_msgset_errors.c +1521 -0
  263. package/deps/librdkafka/src/rdvarint.c +134 -0
  264. package/deps/librdkafka/src/rdvarint.h +165 -0
  265. package/deps/librdkafka/src/rdwin32.h +382 -0
  266. package/deps/librdkafka/src/rdxxhash.c +1030 -0
  267. package/deps/librdkafka/src/rdxxhash.h +328 -0
  268. package/deps/librdkafka/src/regexp.c +1352 -0
  269. package/deps/librdkafka/src/regexp.h +41 -0
  270. package/deps/librdkafka/src/snappy.c +1866 -0
  271. package/deps/librdkafka/src/snappy.h +62 -0
  272. package/deps/librdkafka/src/snappy_compat.h +138 -0
  273. package/deps/librdkafka/src/statistics_schema.json +444 -0
  274. package/deps/librdkafka/src/tinycthread.c +932 -0
  275. package/deps/librdkafka/src/tinycthread.h +503 -0
  276. package/deps/librdkafka/src/tinycthread_extra.c +210 -0
  277. package/deps/librdkafka/src/tinycthread_extra.h +218 -0
  278. package/deps/librdkafka/src/win32_config.h +58 -0
  279. package/deps/librdkafka/src-cpp/CMakeLists.txt +90 -0
  280. package/deps/librdkafka/src-cpp/ConfImpl.cpp +84 -0
  281. package/deps/librdkafka/src-cpp/ConsumerImpl.cpp +244 -0
  282. package/deps/librdkafka/src-cpp/HandleImpl.cpp +436 -0
  283. package/deps/librdkafka/src-cpp/HeadersImpl.cpp +48 -0
  284. package/deps/librdkafka/src-cpp/KafkaConsumerImpl.cpp +296 -0
  285. package/deps/librdkafka/src-cpp/Makefile +55 -0
  286. package/deps/librdkafka/src-cpp/MessageImpl.cpp +38 -0
  287. package/deps/librdkafka/src-cpp/MetadataImpl.cpp +170 -0
  288. package/deps/librdkafka/src-cpp/ProducerImpl.cpp +197 -0
  289. package/deps/librdkafka/src-cpp/QueueImpl.cpp +70 -0
  290. package/deps/librdkafka/src-cpp/README.md +16 -0
  291. package/deps/librdkafka/src-cpp/RdKafka.cpp +59 -0
  292. package/deps/librdkafka/src-cpp/TopicImpl.cpp +124 -0
  293. package/deps/librdkafka/src-cpp/TopicPartitionImpl.cpp +57 -0
  294. package/deps/librdkafka/src-cpp/rdkafkacpp.h +3797 -0
  295. package/deps/librdkafka/src-cpp/rdkafkacpp_int.h +1641 -0
  296. package/deps/librdkafka/tests/0000-unittests.c +72 -0
  297. package/deps/librdkafka/tests/0001-multiobj.c +102 -0
  298. package/deps/librdkafka/tests/0002-unkpart.c +244 -0
  299. package/deps/librdkafka/tests/0003-msgmaxsize.c +173 -0
  300. package/deps/librdkafka/tests/0004-conf.c +934 -0
  301. package/deps/librdkafka/tests/0005-order.c +133 -0
  302. package/deps/librdkafka/tests/0006-symbols.c +163 -0
  303. package/deps/librdkafka/tests/0007-autotopic.c +136 -0
  304. package/deps/librdkafka/tests/0008-reqacks.c +179 -0
  305. package/deps/librdkafka/tests/0009-mock_cluster.c +272 -0
  306. package/deps/librdkafka/tests/0011-produce_batch.c +753 -0
  307. package/deps/librdkafka/tests/0012-produce_consume.c +537 -0
  308. package/deps/librdkafka/tests/0013-null-msgs.c +473 -0
  309. package/deps/librdkafka/tests/0014-reconsume-191.c +512 -0
  310. package/deps/librdkafka/tests/0015-offset_seeks.c +172 -0
  311. package/deps/librdkafka/tests/0016-client_swname.c +181 -0
  312. package/deps/librdkafka/tests/0017-compression.c +140 -0
  313. package/deps/librdkafka/tests/0018-cgrp_term.c +444 -0
  314. package/deps/librdkafka/tests/0019-list_groups.c +289 -0
  315. package/deps/librdkafka/tests/0020-destroy_hang.c +162 -0
  316. package/deps/librdkafka/tests/0021-rkt_destroy.c +72 -0
  317. package/deps/librdkafka/tests/0022-consume_batch.c +331 -0
  318. package/deps/librdkafka/tests/0025-timers.c +147 -0
  319. package/deps/librdkafka/tests/0026-consume_pause.c +547 -0
  320. package/deps/librdkafka/tests/0028-long_topicnames.c +79 -0
  321. package/deps/librdkafka/tests/0029-assign_offset.c +202 -0
  322. package/deps/librdkafka/tests/0030-offset_commit.c +589 -0
  323. package/deps/librdkafka/tests/0031-get_offsets.c +235 -0
  324. package/deps/librdkafka/tests/0033-regex_subscribe.c +536 -0
  325. package/deps/librdkafka/tests/0034-offset_reset.c +398 -0
  326. package/deps/librdkafka/tests/0035-api_version.c +73 -0
  327. package/deps/librdkafka/tests/0036-partial_fetch.c +87 -0
  328. package/deps/librdkafka/tests/0037-destroy_hang_local.c +85 -0
  329. package/deps/librdkafka/tests/0038-performance.c +121 -0
  330. package/deps/librdkafka/tests/0039-event.c +284 -0
  331. package/deps/librdkafka/tests/0040-io_event.c +257 -0
  332. package/deps/librdkafka/tests/0041-fetch_max_bytes.c +97 -0
  333. package/deps/librdkafka/tests/0042-many_topics.c +252 -0
  334. package/deps/librdkafka/tests/0043-no_connection.c +77 -0
  335. package/deps/librdkafka/tests/0044-partition_cnt.c +94 -0
  336. package/deps/librdkafka/tests/0045-subscribe_update.c +1010 -0
  337. package/deps/librdkafka/tests/0046-rkt_cache.c +65 -0
  338. package/deps/librdkafka/tests/0047-partial_buf_tmout.c +98 -0
  339. package/deps/librdkafka/tests/0048-partitioner.c +283 -0
  340. package/deps/librdkafka/tests/0049-consume_conn_close.c +162 -0
  341. package/deps/librdkafka/tests/0050-subscribe_adds.c +145 -0
  342. package/deps/librdkafka/tests/0051-assign_adds.c +126 -0
  343. package/deps/librdkafka/tests/0052-msg_timestamps.c +238 -0
  344. package/deps/librdkafka/tests/0053-stats_cb.cpp +527 -0
  345. package/deps/librdkafka/tests/0054-offset_time.cpp +236 -0
  346. package/deps/librdkafka/tests/0055-producer_latency.c +539 -0
  347. package/deps/librdkafka/tests/0056-balanced_group_mt.c +315 -0
  348. package/deps/librdkafka/tests/0057-invalid_topic.cpp +112 -0
  349. package/deps/librdkafka/tests/0058-log.cpp +123 -0
  350. package/deps/librdkafka/tests/0059-bsearch.cpp +241 -0
  351. package/deps/librdkafka/tests/0060-op_prio.cpp +163 -0
  352. package/deps/librdkafka/tests/0061-consumer_lag.cpp +295 -0
  353. package/deps/librdkafka/tests/0062-stats_event.c +126 -0
  354. package/deps/librdkafka/tests/0063-clusterid.cpp +180 -0
  355. package/deps/librdkafka/tests/0064-interceptors.c +529 -0
  356. package/deps/librdkafka/tests/0065-yield.cpp +140 -0
  357. package/deps/librdkafka/tests/0066-plugins.cpp +143 -0
  358. package/deps/librdkafka/tests/0067-empty_topic.cpp +151 -0
  359. package/deps/librdkafka/tests/0068-produce_timeout.c +136 -0
  360. package/deps/librdkafka/tests/0069-consumer_add_parts.c +119 -0
  361. package/deps/librdkafka/tests/0070-null_empty.cpp +197 -0
  362. package/deps/librdkafka/tests/0072-headers_ut.c +448 -0
  363. package/deps/librdkafka/tests/0073-headers.c +381 -0
  364. package/deps/librdkafka/tests/0074-producev.c +87 -0
  365. package/deps/librdkafka/tests/0075-retry.c +290 -0
  366. package/deps/librdkafka/tests/0076-produce_retry.c +452 -0
  367. package/deps/librdkafka/tests/0077-compaction.c +366 -0
  368. package/deps/librdkafka/tests/0078-c_from_cpp.cpp +96 -0
  369. package/deps/librdkafka/tests/0079-fork.c +93 -0
  370. package/deps/librdkafka/tests/0080-admin_ut.c +3095 -0
  371. package/deps/librdkafka/tests/0081-admin.c +5633 -0
  372. package/deps/librdkafka/tests/0082-fetch_max_bytes.cpp +137 -0
  373. package/deps/librdkafka/tests/0083-cb_event.c +233 -0
  374. package/deps/librdkafka/tests/0084-destroy_flags.c +208 -0
  375. package/deps/librdkafka/tests/0085-headers.cpp +392 -0
  376. package/deps/librdkafka/tests/0086-purge.c +368 -0
  377. package/deps/librdkafka/tests/0088-produce_metadata_timeout.c +162 -0
  378. package/deps/librdkafka/tests/0089-max_poll_interval.c +511 -0
  379. package/deps/librdkafka/tests/0090-idempotence.c +171 -0
  380. package/deps/librdkafka/tests/0091-max_poll_interval_timeout.c +295 -0
  381. package/deps/librdkafka/tests/0092-mixed_msgver.c +103 -0
  382. package/deps/librdkafka/tests/0093-holb.c +200 -0
  383. package/deps/librdkafka/tests/0094-idempotence_msg_timeout.c +231 -0
  384. package/deps/librdkafka/tests/0095-all_brokers_down.cpp +122 -0
  385. package/deps/librdkafka/tests/0097-ssl_verify.cpp +674 -0
  386. package/deps/librdkafka/tests/0098-consumer-txn.cpp +1218 -0
  387. package/deps/librdkafka/tests/0099-commit_metadata.c +194 -0
  388. package/deps/librdkafka/tests/0100-thread_interceptors.cpp +195 -0
  389. package/deps/librdkafka/tests/0101-fetch-from-follower.cpp +446 -0
  390. package/deps/librdkafka/tests/0102-static_group_rebalance.c +836 -0
  391. package/deps/librdkafka/tests/0103-transactions.c +1383 -0
  392. package/deps/librdkafka/tests/0104-fetch_from_follower_mock.c +625 -0
  393. package/deps/librdkafka/tests/0105-transactions_mock.c +3930 -0
  394. package/deps/librdkafka/tests/0106-cgrp_sess_timeout.c +318 -0
  395. package/deps/librdkafka/tests/0107-topic_recreate.c +259 -0
  396. package/deps/librdkafka/tests/0109-auto_create_topics.cpp +278 -0
  397. package/deps/librdkafka/tests/0110-batch_size.cpp +182 -0
  398. package/deps/librdkafka/tests/0111-delay_create_topics.cpp +127 -0
  399. package/deps/librdkafka/tests/0112-assign_unknown_part.c +87 -0
  400. package/deps/librdkafka/tests/0113-cooperative_rebalance.cpp +3473 -0
  401. package/deps/librdkafka/tests/0114-sticky_partitioning.cpp +176 -0
  402. package/deps/librdkafka/tests/0115-producer_auth.cpp +182 -0
  403. package/deps/librdkafka/tests/0116-kafkaconsumer_close.cpp +216 -0
  404. package/deps/librdkafka/tests/0117-mock_errors.c +331 -0
  405. package/deps/librdkafka/tests/0118-commit_rebalance.c +154 -0
  406. package/deps/librdkafka/tests/0119-consumer_auth.cpp +167 -0
  407. package/deps/librdkafka/tests/0120-asymmetric_subscription.c +185 -0
  408. package/deps/librdkafka/tests/0121-clusterid.c +115 -0
  409. package/deps/librdkafka/tests/0122-buffer_cleaning_after_rebalance.c +227 -0
  410. package/deps/librdkafka/tests/0123-connections_max_idle.c +98 -0
  411. package/deps/librdkafka/tests/0124-openssl_invalid_engine.c +69 -0
  412. package/deps/librdkafka/tests/0125-immediate_flush.c +144 -0
  413. package/deps/librdkafka/tests/0126-oauthbearer_oidc.c +1164 -0
  414. package/deps/librdkafka/tests/0127-fetch_queue_backoff.cpp +165 -0
  415. package/deps/librdkafka/tests/0128-sasl_callback_queue.cpp +217 -0
  416. package/deps/librdkafka/tests/0129-fetch_aborted_msgs.c +79 -0
  417. package/deps/librdkafka/tests/0130-store_offsets.c +178 -0
  418. package/deps/librdkafka/tests/0131-connect_timeout.c +81 -0
  419. package/deps/librdkafka/tests/0132-strategy_ordering.c +179 -0
  420. package/deps/librdkafka/tests/0133-ssl_keys.c +150 -0
  421. package/deps/librdkafka/tests/0134-ssl_provider.c +92 -0
  422. package/deps/librdkafka/tests/0135-sasl_credentials.cpp +279 -0
  423. package/deps/librdkafka/tests/0136-resolve_cb.c +181 -0
  424. package/deps/librdkafka/tests/0137-barrier_batch_consume.c +752 -0
  425. package/deps/librdkafka/tests/0138-admin_mock.c +281 -0
  426. package/deps/librdkafka/tests/0139-offset_validation_mock.c +950 -0
  427. package/deps/librdkafka/tests/0140-commit_metadata.cpp +108 -0
  428. package/deps/librdkafka/tests/0142-reauthentication.c +931 -0
  429. package/deps/librdkafka/tests/0143-exponential_backoff_mock.c +552 -0
  430. package/deps/librdkafka/tests/0144-idempotence_mock.c +373 -0
  431. package/deps/librdkafka/tests/0145-pause_resume_mock.c +119 -0
  432. package/deps/librdkafka/tests/0146-metadata_mock.c +505 -0
  433. package/deps/librdkafka/tests/0147-consumer_group_consumer_mock.c +1210 -0
  434. package/deps/librdkafka/tests/0148-offset_fetch_commit_error_mock.c +569 -0
  435. package/deps/librdkafka/tests/0149-broker-same-host-port.c +140 -0
  436. package/deps/librdkafka/tests/0150-telemetry_mock.c +651 -0
  437. package/deps/librdkafka/tests/0151-purge-brokers.c +566 -0
  438. package/deps/librdkafka/tests/0152-rebootstrap.c +59 -0
  439. package/deps/librdkafka/tests/0153-memberid.c +128 -0
  440. package/deps/librdkafka/tests/0155-share_group_heartbeat_mock.c +2695 -0
  441. package/deps/librdkafka/tests/0156-share_consumer_fetch_mock.c +1551 -0
  442. package/deps/librdkafka/tests/0157-share_consumer_ack_mock.c +1788 -0
  443. package/deps/librdkafka/tests/0158-share_consumer_transactions_mock.c +467 -0
  444. package/deps/librdkafka/tests/0170-share_consumer_subscription.c +2192 -0
  445. package/deps/librdkafka/tests/0171-share_consumer_consume.c +2150 -0
  446. package/deps/librdkafka/tests/0172-share_consumer_acknowledge.c +1919 -0
  447. package/deps/librdkafka/tests/0173-share_consumer_commit_async.c +2735 -0
  448. package/deps/librdkafka/tests/0174-share_consumer_concurrency.c +1434 -0
  449. package/deps/librdkafka/tests/0175-share_consumer_groups.c +574 -0
  450. package/deps/librdkafka/tests/0176-share_consumer_commit_sync.c +2986 -0
  451. package/deps/librdkafka/tests/0177-share_consumer_transactions.c +1815 -0
  452. package/deps/librdkafka/tests/0178-share_consumer_close.c +2102 -0
  453. package/deps/librdkafka/tests/0179-share_consumer_destroy.c +2482 -0
  454. package/deps/librdkafka/tests/0180-share_consumer_config.c +1677 -0
  455. package/deps/librdkafka/tests/0181-share_consumer_topic_delete.c +354 -0
  456. package/deps/librdkafka/tests/0182-share_consumer_error_handling_mock.c +3358 -0
  457. package/deps/librdkafka/tests/0183-share_consumer_leader_change_mock.c +1234 -0
  458. package/deps/librdkafka/tests/0184-share_consumer_topic_recreate.c +1356 -0
  459. package/deps/librdkafka/tests/0185-share_consumer_max_poll_interval.c +557 -0
  460. package/deps/librdkafka/tests/0186-share_consumer_fatal_error.c +360 -0
  461. package/deps/librdkafka/tests/0190-share_consumer_telemetry.c +387 -0
  462. package/deps/librdkafka/tests/1000-unktopic.c +164 -0
  463. package/deps/librdkafka/tests/8000-idle.cpp +60 -0
  464. package/deps/librdkafka/tests/8001-fetch_from_follower_mock_manual.c +113 -0
  465. package/deps/librdkafka/tests/CMakeLists.txt +193 -0
  466. package/deps/librdkafka/tests/LibrdkafkaTestApp.py +291 -0
  467. package/deps/librdkafka/tests/Makefile +182 -0
  468. package/deps/librdkafka/tests/README.md +514 -0
  469. package/deps/librdkafka/tests/autotest.sh +33 -0
  470. package/deps/librdkafka/tests/backtrace.gdb +30 -0
  471. package/deps/librdkafka/tests/broker_version_tests.py +315 -0
  472. package/deps/librdkafka/tests/buildbox.sh +17 -0
  473. package/deps/librdkafka/tests/cleanup-checker-tests.sh +20 -0
  474. package/deps/librdkafka/tests/cluster_testing.py +203 -0
  475. package/deps/librdkafka/tests/delete-test-topics.sh +56 -0
  476. package/deps/librdkafka/tests/fixtures/oauthbearer/jwt_assertion_template.json +10 -0
  477. package/deps/librdkafka/tests/fixtures/ssl/Makefile +8 -0
  478. package/deps/librdkafka/tests/fixtures/ssl/README.md +13 -0
  479. package/deps/librdkafka/tests/fixtures/ssl/client.keystore.intermediate.p12 +0 -0
  480. package/deps/librdkafka/tests/fixtures/ssl/client.keystore.p12 +0 -0
  481. package/deps/librdkafka/tests/fixtures/ssl/client2.certificate.intermediate.pem +72 -0
  482. package/deps/librdkafka/tests/fixtures/ssl/client2.certificate.pem +50 -0
  483. package/deps/librdkafka/tests/fixtures/ssl/client2.intermediate.key +46 -0
  484. package/deps/librdkafka/tests/fixtures/ssl/client2.key +46 -0
  485. package/deps/librdkafka/tests/fixtures/ssl/create_keys.sh +168 -0
  486. package/deps/librdkafka/tests/fuzzers/Makefile +12 -0
  487. package/deps/librdkafka/tests/fuzzers/README.md +31 -0
  488. package/deps/librdkafka/tests/fuzzers/fuzz_regex.c +74 -0
  489. package/deps/librdkafka/tests/fuzzers/helpers.h +90 -0
  490. package/deps/librdkafka/tests/gen-ssl-certs.sh +165 -0
  491. package/deps/librdkafka/tests/interactive_broker_version.py +170 -0
  492. package/deps/librdkafka/tests/interceptor_test/CMakeLists.txt +16 -0
  493. package/deps/librdkafka/tests/interceptor_test/Makefile +22 -0
  494. package/deps/librdkafka/tests/interceptor_test/interceptor_test.c +314 -0
  495. package/deps/librdkafka/tests/interceptor_test/interceptor_test.h +54 -0
  496. package/deps/librdkafka/tests/java/IncrementalRebalanceCli.java +97 -0
  497. package/deps/librdkafka/tests/java/Makefile +13 -0
  498. package/deps/librdkafka/tests/java/Murmur2Cli.java +46 -0
  499. package/deps/librdkafka/tests/java/README.md +14 -0
  500. package/deps/librdkafka/tests/java/TransactionProducerCli.java +162 -0
  501. package/deps/librdkafka/tests/java/run-class.sh +11 -0
  502. package/deps/librdkafka/tests/librdkafka.suppressions +483 -0
  503. package/deps/librdkafka/tests/lz4_manual_test.sh +59 -0
  504. package/deps/librdkafka/tests/multi-broker-version-test.sh +50 -0
  505. package/deps/librdkafka/tests/parse-refcnt.sh +43 -0
  506. package/deps/librdkafka/tests/performance_plot.py +115 -0
  507. package/deps/librdkafka/tests/plugin_test/Makefile +19 -0
  508. package/deps/librdkafka/tests/plugin_test/plugin_test.c +58 -0
  509. package/deps/librdkafka/tests/requirements.txt +2 -0
  510. package/deps/librdkafka/tests/run-all-tests.sh +81 -0
  511. package/deps/librdkafka/tests/run-consumer-tests.sh +16 -0
  512. package/deps/librdkafka/tests/run-producer-tests.sh +16 -0
  513. package/deps/librdkafka/tests/run-test-batches.py +157 -0
  514. package/deps/librdkafka/tests/run-test.sh +141 -0
  515. package/deps/librdkafka/tests/rusage.c +249 -0
  516. package/deps/librdkafka/tests/sasl_test.py +289 -0
  517. package/deps/librdkafka/tests/scenarios/README.md +6 -0
  518. package/deps/librdkafka/tests/scenarios/ak23.json +6 -0
  519. package/deps/librdkafka/tests/scenarios/default.json +5 -0
  520. package/deps/librdkafka/tests/scenarios/noautocreate.json +5 -0
  521. package/deps/librdkafka/tests/share_telemetry_verify/share-otel-collector-config.yaml +28 -0
  522. package/deps/librdkafka/tests/sockem.c +801 -0
  523. package/deps/librdkafka/tests/sockem.h +85 -0
  524. package/deps/librdkafka/tests/sockem_ctrl.c +145 -0
  525. package/deps/librdkafka/tests/sockem_ctrl.h +61 -0
  526. package/deps/librdkafka/tests/test.c +8376 -0
  527. package/deps/librdkafka/tests/test.conf.example +27 -0
  528. package/deps/librdkafka/tests/test.h +1150 -0
  529. package/deps/librdkafka/tests/testcpp.cpp +131 -0
  530. package/deps/librdkafka/tests/testcpp.h +388 -0
  531. package/deps/librdkafka/tests/testshared.h +416 -0
  532. package/deps/librdkafka/tests/tools/README.md +4 -0
  533. package/deps/librdkafka/tests/tools/stats/README.md +21 -0
  534. package/deps/librdkafka/tests/tools/stats/filter.jq +42 -0
  535. package/deps/librdkafka/tests/tools/stats/graph.py +150 -0
  536. package/deps/librdkafka/tests/tools/stats/requirements.txt +3 -0
  537. package/deps/librdkafka/tests/tools/stats/to_csv.py +124 -0
  538. package/deps/librdkafka/tests/trivup/trivup-0.15.0.tar.gz +0 -0
  539. package/deps/librdkafka/tests/until-fail.sh +87 -0
  540. package/deps/librdkafka/tests/xxxx-assign_partition.c +122 -0
  541. package/deps/librdkafka/tests/xxxx-metadata.cpp +159 -0
  542. package/deps/librdkafka/vcpkg.json +23 -0
  543. package/deps/librdkafka/win32/README.md +5 -0
  544. package/deps/librdkafka/win32/build-package.bat +3 -0
  545. package/deps/librdkafka/win32/build.bat +19 -0
  546. package/deps/librdkafka/win32/common.vcxproj +84 -0
  547. package/deps/librdkafka/win32/interceptor_test/interceptor_test.vcxproj +87 -0
  548. package/deps/librdkafka/win32/librdkafka.autopkg.template +54 -0
  549. package/deps/librdkafka/win32/librdkafka.master.testing.targets +13 -0
  550. package/deps/librdkafka/win32/librdkafka.sln +226 -0
  551. package/deps/librdkafka/win32/librdkafka.vcxproj +282 -0
  552. package/deps/librdkafka/win32/librdkafkacpp/librdkafkacpp.vcxproj +104 -0
  553. package/deps/librdkafka/win32/msbuild.ps1 +58 -0
  554. package/deps/librdkafka/win32/openssl_engine_example/openssl_engine_example.vcxproj +132 -0
  555. package/deps/librdkafka/win32/package-zip.ps1 +46 -0
  556. package/deps/librdkafka/win32/packages/repositories.config +4 -0
  557. package/deps/librdkafka/win32/push-package.bat +4 -0
  558. package/deps/librdkafka/win32/rdkafka_complex_consumer_example_cpp/rdkafka_complex_consumer_example_cpp.vcxproj +67 -0
  559. package/deps/librdkafka/win32/rdkafka_example/rdkafka_example.vcxproj +97 -0
  560. package/deps/librdkafka/win32/rdkafka_performance/rdkafka_performance.vcxproj +97 -0
  561. package/deps/librdkafka/win32/setup-msys2.ps1 +47 -0
  562. package/deps/librdkafka/win32/setup-vcpkg.ps1 +34 -0
  563. package/deps/librdkafka/win32/tests/test.conf.example +25 -0
  564. package/deps/librdkafka/win32/tests/tests.vcxproj +278 -0
  565. package/deps/librdkafka/win32/win_ssl_cert_store/win_ssl_cert_store.vcxproj +132 -0
  566. package/deps/librdkafka/win32/wingetopt.c +564 -0
  567. package/deps/librdkafka/win32/wingetopt.h +101 -0
  568. package/deps/librdkafka/win32/wintime.h +33 -0
  569. package/deps/librdkafka.gyp +83 -0
  570. package/deps/windows-install.py +80 -0
  571. package/index.d.ts +3 -0
  572. package/lib/admin.js +732 -0
  573. package/lib/client.js +679 -0
  574. package/lib/error.js +501 -0
  575. package/lib/index.js +8 -0
  576. package/lib/kafka-consumer-stream.js +399 -0
  577. package/lib/kafka-consumer.js +798 -0
  578. package/lib/kafkajs/_admin.js +1052 -0
  579. package/lib/kafkajs/_common.js +887 -0
  580. package/lib/kafkajs/_consumer.js +2156 -0
  581. package/lib/kafkajs/_consumer_cache.js +310 -0
  582. package/lib/kafkajs/_error.js +298 -0
  583. package/lib/kafkajs/_kafka.js +125 -0
  584. package/lib/kafkajs/_linked-list.js +220 -0
  585. package/lib/kafkajs/_producer.js +863 -0
  586. package/lib/kafkajs/index.js +5 -0
  587. package/lib/producer/high-level-producer.js +400 -0
  588. package/lib/producer-stream.js +337 -0
  589. package/lib/producer.js +405 -0
  590. package/lib/rdkafka.js +44 -0
  591. package/lib/tools/ref-counter.js +53 -0
  592. package/lib/topic-partition.js +91 -0
  593. package/lib/topic.js +43 -0
  594. package/lib/util.js +55 -0
  595. package/librdkafka.js +12 -0
  596. package/package.json +85 -0
  597. package/shim/source_location +54 -0
  598. package/src/admin.cc +1597 -0
  599. package/src/admin.h +112 -0
  600. package/src/binding.cc +76 -0
  601. package/src/binding.h +25 -0
  602. package/src/callbacks.cc +676 -0
  603. package/src/callbacks.h +302 -0
  604. package/src/common.cc +1628 -0
  605. package/src/common.h +192 -0
  606. package/src/config.cc +219 -0
  607. package/src/config.h +61 -0
  608. package/src/connection.cc +703 -0
  609. package/src/connection.h +117 -0
  610. package/src/errors.cc +119 -0
  611. package/src/errors.h +60 -0
  612. package/src/kafka-consumer.cc +1539 -0
  613. package/src/kafka-consumer.h +147 -0
  614. package/src/producer.cc +917 -0
  615. package/src/producer.h +124 -0
  616. package/src/topic.cc +173 -0
  617. package/src/topic.h +54 -0
  618. package/src/workers.cc +1714 -0
  619. package/src/workers.h +690 -0
  620. package/types/config.d.ts +1269 -0
  621. package/types/errors.d.ts +373 -0
  622. package/types/kafkajs.d.ts +568 -0
  623. package/types/rdkafka.d.ts +571 -0
  624. package/util/configure.js +32 -0
  625. package/util/generate-docs.sh +18 -0
  626. package/util/get-env.js +6 -0
  627. package/util/test-compile.js +11 -0
  628. 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), };