mq-bridge-py-basic 0.2.21__tar.gz → 0.3.1__tar.gz

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 (203) hide show
  1. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/.github/workflows/ci.yml +54 -79
  2. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/.github/workflows/publish-node.yml +56 -10
  3. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/CONFIGURATION.md +80 -0
  4. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/Cargo.lock +17 -4
  5. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/Cargo.toml +2 -2
  6. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/PKG-INFO +94 -7
  7. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/README.md +25 -3
  8. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/benches/performance_bench.rs +1 -1
  9. mq_bridge_py_basic-0.3.1/bindings-common/Cargo.toml +19 -0
  10. mq_bridge_py_basic-0.3.1/bindings-common/src/lib.rs +308 -0
  11. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/mq-bridge.schema.json +48 -0
  12. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/mq_bridge/__init__.py +2 -0
  13. mq_bridge_py_basic-0.3.1/mq_bridge/__init__.pyi +227 -0
  14. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/mq_bridge/config.pyi +8 -1
  15. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/python/mq-bridge-py/Cargo.toml +1 -0
  16. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/python/mq-bridge-py/README.md +93 -6
  17. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/python/mq-bridge-py/analysis/bench_http_native.py +7 -9
  18. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/python/mq-bridge-py/example.py +2 -2
  19. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/python/mq-bridge-py/examples/bench_memory.py +2 -2
  20. mq_bridge_py_basic-0.3.1/python/mq-bridge-py/examples/consume_pull.py +39 -0
  21. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/python/mq-bridge-py/examples/json_route.py +2 -2
  22. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/python/mq-bridge-py/examples/raw_route.py +2 -2
  23. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/python/mq-bridge-py/scripts/gen_config_types.py +1 -1
  24. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/python/mq-bridge-py/src/lib.rs +432 -167
  25. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/python/mq-bridge-py/tests/test_config_types.py +7 -2
  26. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/python/mq-bridge-py/tests/test_routes.py +108 -3
  27. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/scripts/httparena/README.md +1 -1
  28. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/src/canonical_message.rs +33 -0
  29. mq_bridge_py_basic-0.3.1/src/connection_registry.rs +280 -0
  30. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/src/endpoints/amqp.rs +21 -17
  31. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/src/endpoints/aws.rs +5 -0
  32. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/src/endpoints/file.rs +3 -0
  33. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/src/endpoints/grpc.rs +50 -4
  34. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/src/endpoints/http.rs +66 -31
  35. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/src/endpoints/ibm_mq.rs +3 -0
  36. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/src/endpoints/kafka.rs +88 -19
  37. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/src/endpoints/memory/endpoint.rs +176 -48
  38. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/src/endpoints/mongodb.rs +33 -1
  39. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/src/endpoints/mqtt.rs +16 -2
  40. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/src/endpoints/nats.rs +39 -5
  41. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/src/endpoints/sled.rs +5 -0
  42. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/src/endpoints/sqlx.rs +41 -1
  43. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/src/endpoints/static_endpoint.rs +4 -0
  44. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/src/endpoints/stream_buffer.rs +3 -0
  45. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/src/endpoints/websocket.rs +13 -31
  46. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/src/endpoints/zeromq.rs +5 -0
  47. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/src/event_store.rs +3 -0
  48. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/src/lib.rs +1 -0
  49. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/src/middleware/buffer.rs +3 -0
  50. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/src/middleware/cookie_jar.rs +3 -0
  51. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/src/middleware/deduplication.rs +3 -0
  52. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/src/middleware/delay.rs +3 -0
  53. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/src/middleware/limiter.rs +3 -0
  54. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/src/middleware/metrics.rs +3 -0
  55. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/src/middleware/random_panic.rs +3 -0
  56. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/src/middleware/weak_join.rs +3 -0
  57. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/src/models.rs +49 -0
  58. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/src/route.rs +213 -13
  59. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/src/test_utils.rs +10 -1
  60. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/src/traits.rs +191 -43
  61. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/tests/integration/http_tls.rs +1 -1
  62. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/tests/integration/kafka.rs +3 -3
  63. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/tests/integration/route.rs +35 -10
  64. mq_bridge_py_basic-0.3.1/tests/memory_leak_test.rs +134 -0
  65. mq_bridge_py_basic-0.2.21/mq_bridge/__init__.pyi +0 -146
  66. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  67. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  68. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  69. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/.github/workflows/benchmark.yml +0 -0
  70. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/.github/workflows/ibm-mq.yml +0 -0
  71. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/.github/workflows/node.yml +0 -0
  72. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/.github/workflows/publish-python.yml +0 -0
  73. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/.github/workflows/python.yml +0 -0
  74. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/.github/workflows/release.yml +0 -0
  75. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/.github/workflows/test-matrix.yml +0 -0
  76. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/.gitignore +0 -0
  77. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/ARCHITECTURE.md +0 -0
  78. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/CONTRIBUTING.md +0 -0
  79. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/LICENSE +0 -0
  80. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/LICENSE.python +0 -0
  81. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/benches/router_bench.rs +0 -0
  82. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/build.rs +0 -0
  83. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/claude.md +0 -0
  84. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/examples/ipc_worker_queue.yaml +0 -0
  85. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/mq_bridge/config.py +0 -0
  86. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/mq_bridge/py.typed +0 -0
  87. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/package-lock.json +0 -0
  88. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/pyproject.toml +0 -0
  89. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/python/mq-bridge-py/LICENSE.python +0 -0
  90. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/python/mq-bridge-py/examples/bench_memory.yaml +0 -0
  91. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/python/mq-bridge-py/examples/memory.yaml +0 -0
  92. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/python/mq-bridge-py/pyproject-basic.toml +0 -0
  93. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/python/mq-bridge-py/tests/test_packaging.py +0 -0
  94. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/python/mq-bridge-py/tests/test_performance_smoke.py +0 -0
  95. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/python/mq-bridge-py/tests/test_public_api.py +0 -0
  96. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/python/mq-bridge-py/uv.lock +0 -0
  97. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/scripts/analysis/HTTP_STREAM_ENDPOINT_PLAN.md +0 -0
  98. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/scripts/analysis/benches/metadata_bench.rs +0 -0
  99. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/scripts/analysis/debug_ibm_mq_tls.rs +0 -0
  100. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/scripts/analysis/http/analyze_xctrace_time_profile.py +0 -0
  101. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/scripts/analysis/http/http_flamegraph.py +0 -0
  102. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/scripts/analysis/http/mq_bridge_http_profile.rs +0 -0
  103. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/scripts/analysis/http/run_http_ladder.sh +0 -0
  104. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/scripts/httparena/frameworks/mq-bridge-py/Dockerfile +0 -0
  105. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/scripts/httparena/frameworks/mq-bridge-py/meta.json +0 -0
  106. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/scripts/httparena/frameworks/mq-bridge-py/server.py +0 -0
  107. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/scripts/techempower/Python/mq-bridge-py/README.md +0 -0
  108. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/scripts/techempower/Python/mq-bridge-py/benchmark_config.json +0 -0
  109. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/scripts/techempower/Python/mq-bridge-py/mq-bridge-py.dockerfile +0 -0
  110. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/scripts/techempower/Python/mq-bridge-py/server.py +0 -0
  111. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/scripts/techempower/README.md +0 -0
  112. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/scripts/techempower/postgres.yml +0 -0
  113. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/scripts/techempower/seed.sql +0 -0
  114. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/scripts/techempower/verify.sh +0 -0
  115. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/scripts/the-benchmarker/README.md +0 -0
  116. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/scripts/the-benchmarker/python/mq-bridge-py/config.yaml +0 -0
  117. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/scripts/the-benchmarker/python/mq-bridge-py/pyproject.toml +0 -0
  118. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/scripts/the-benchmarker/python/mq-bridge-py/server.py +0 -0
  119. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/src/command_handler.rs +0 -0
  120. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/src/endpoints/fanout.rs +0 -0
  121. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/src/endpoints/grpc.proto +0 -0
  122. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/src/endpoints/http_stream.rs +0 -0
  123. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/src/endpoints/memory/ipc_unix.rs +0 -0
  124. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/src/endpoints/memory/ipc_windows.rs +0 -0
  125. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/src/endpoints/memory/memory_transport.rs +0 -0
  126. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/src/endpoints/memory/mod.rs +0 -0
  127. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/src/endpoints/memory/transport.rs +0 -0
  128. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/src/endpoints/mod.rs +0 -0
  129. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/src/endpoints/null.rs +0 -0
  130. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/src/endpoints/reader.rs +0 -0
  131. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/src/endpoints/response.rs +0 -0
  132. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/src/endpoints/switch.rs +0 -0
  133. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/src/errors.rs +0 -0
  134. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/src/event_handler.rs +0 -0
  135. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/src/extensions.rs +0 -0
  136. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/src/middleware/dlq.rs +0 -0
  137. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/src/middleware/mod.rs +0 -0
  138. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/src/middleware/retry.rs +0 -0
  139. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/src/outcomes.rs +0 -0
  140. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/src/publisher.rs +0 -0
  141. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/src/response.rs +0 -0
  142. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/src/type_handler.rs +0 -0
  143. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/tests/README.md +0 -0
  144. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/tests/armature_integration.rs +0 -0
  145. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/tests/integration/amqp.rs +0 -0
  146. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/tests/integration/aws.rs +0 -0
  147. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/tests/integration/config.toml +0 -0
  148. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/tests/integration/docker-compose/amqp.yml +0 -0
  149. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/tests/integration/docker-compose/aws.yml +0 -0
  150. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/tests/integration/docker-compose/ibm-mq-certs/client.crl +0 -0
  151. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/tests/integration/docker-compose/ibm-mq-certs/client.kdb +0 -0
  152. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/tests/integration/docker-compose/ibm-mq-certs/client.p12 +0 -0
  153. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/tests/integration/docker-compose/ibm-mq-certs/client.rdb +0 -0
  154. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/tests/integration/docker-compose/ibm-mq-certs/client.sth +0 -0
  155. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/tests/integration/docker-compose/ibm_mq.yml +0 -0
  156. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/tests/integration/docker-compose/ibm_mq_tls.yml +0 -0
  157. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/tests/integration/docker-compose/kafka-tls.yml +0 -0
  158. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/tests/integration/docker-compose/kafka.yml +0 -0
  159. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/tests/integration/docker-compose/mariadb.yml +0 -0
  160. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/tests/integration/docker-compose/mongodb-replica.yml +0 -0
  161. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/tests/integration/docker-compose/mongodb-tls.yml +0 -0
  162. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/tests/integration/docker-compose/mongodb.yml +0 -0
  163. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/tests/integration/docker-compose/mosquitto.conf +0 -0
  164. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/tests/integration/docker-compose/mosquitto_performance.conf +0 -0
  165. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/tests/integration/docker-compose/mosquitto_persistence.conf +0 -0
  166. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/tests/integration/docker-compose/mq_init.mqsc +0 -0
  167. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/tests/integration/docker-compose/mq_init_tls.mqsc +0 -0
  168. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/tests/integration/docker-compose/mqtt.yml +0 -0
  169. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/tests/integration/docker-compose/mqtt_performance.yml +0 -0
  170. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/tests/integration/docker-compose/mysql.yml +0 -0
  171. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/tests/integration/docker-compose/nats.yml +0 -0
  172. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/tests/integration/docker-compose/postgres.yml +0 -0
  173. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/tests/integration/file.rs +0 -0
  174. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/tests/integration/grpc.rs +0 -0
  175. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/tests/integration/grpc_tls.rs +0 -0
  176. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/tests/integration/http.rs +0 -0
  177. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/tests/integration/ibm_mq.rs +0 -0
  178. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/tests/integration/ibm_mq_tls.rs +0 -0
  179. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/tests/integration/ipc.rs +0 -0
  180. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/tests/integration/kafka_tls.rs +0 -0
  181. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/tests/integration/logic_test.rs +0 -0
  182. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/tests/integration/mariadb.rs +0 -0
  183. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/tests/integration/memory.rs +0 -0
  184. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/tests/integration/mod.rs +0 -0
  185. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/tests/integration/mongodb.rs +0 -0
  186. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/tests/integration/mongodb_raw.rs +0 -0
  187. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/tests/integration/mongodb_tls.rs +0 -0
  188. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/tests/integration/mqtt.rs +0 -0
  189. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/tests/integration/mysql.rs +0 -0
  190. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/tests/integration/nats.rs +0 -0
  191. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/tests/integration/performance_static.rs +0 -0
  192. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/tests/integration/postgres.rs +0 -0
  193. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/tests/integration/scripts/gen_certs.sh +0 -0
  194. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/tests/integration/sqlite.rs +0 -0
  195. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/tests/integration/tls_helpers.rs +0 -0
  196. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/tests/integration/websocket.rs +0 -0
  197. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/tests/integration/zeromq.rs +0 -0
  198. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/tests/integration_test.rs +0 -0
  199. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/tests/ref_test.rs +0 -0
  200. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/tests/sqlite_test.rs +0 -0
  201. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/tests/tls_example.rs +0 -0
  202. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/tests/unit_tests.md +0 -0
  203. {mq_bridge_py_basic-0.2.21 → mq_bridge_py_basic-0.3.1}/tests/websocket_test.rs +0 -0
@@ -23,17 +23,13 @@ jobs:
23
23
  with:
24
24
  components: rustfmt, clippy
25
25
 
26
- - name: Cache cargo registry
27
- uses: actions/cache@v4
26
+ - name: Cache cargo build
27
+ uses: Swatinem/rust-cache@v2
28
28
  with:
29
- path: |
30
- ~/.cargo/bin/
31
- ~/.cargo/registry/cache/
32
- ~/.cargo/git/db/
33
- target/
34
- key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
35
- restore-keys: |
36
- ${{ runner.os }}-cargo-
29
+ # Differentiates the matrix `check` job per feature set; evaluates to an
30
+ # empty string (no-op) in non-matrix jobs. rust-cache already keys on the
31
+ # job id, OS, rustc version, and lockfiles automatically.
32
+ key: ${{ matrix.features }}
37
33
 
38
34
  - name: Check formatting
39
35
  run: cargo fmt --all -- --check
@@ -65,24 +61,20 @@ jobs:
65
61
  - name: Install Rust toolchain
66
62
  uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
67
63
 
68
- - name: Cache cargo registry
69
- uses: actions/cache@v4
64
+ - name: Cache cargo build
65
+ uses: Swatinem/rust-cache@v2
70
66
  with:
71
- path: |
72
- ~/.cargo/bin/
73
- ~/.cargo/registry/cache/
74
- ~/.cargo/git/db/
75
- target/
76
- key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
77
- restore-keys: |
78
- ${{ runner.os }}-cargo-
67
+ # Differentiates the matrix `check` job per feature set; evaluates to an
68
+ # empty string (no-op) in non-matrix jobs. rust-cache already keys on the
69
+ # job id, OS, rustc version, and lockfiles automatically.
70
+ key: ${{ matrix.features }}
79
71
 
80
- - name: Build
72
+ - name: Check
81
73
  run: |
82
74
  if [ -z "${{ matrix.features }}" ]; then
83
- cargo build --release
75
+ cargo check --all-targets
84
76
  else
85
- cargo build --release --features "${{ matrix.features }}"
77
+ cargo check --all-targets --features "${{ matrix.features }}"
86
78
  fi
87
79
 
88
80
  # Unit tests
@@ -95,24 +87,26 @@ jobs:
95
87
  - name: Install Rust toolchain
96
88
  uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
97
89
 
98
- - name: Cache cargo registry
99
- uses: actions/cache@v4
90
+ - name: Cache cargo build
91
+ uses: Swatinem/rust-cache@v2
100
92
  with:
101
- path: |
102
- ~/.cargo/bin/
103
- ~/.cargo/registry/cache/
104
- ~/.cargo/git/db/
105
- target/
106
- key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
107
- restore-keys: |
108
- ${{ runner.os }}-cargo-
93
+ # Differentiates the matrix `check` job per feature set; evaluates to an
94
+ # empty string (no-op) in non-matrix jobs. rust-cache already keys on the
95
+ # job id, OS, rustc version, and lockfiles automatically.
96
+ key: ${{ matrix.features }}
109
97
 
110
98
  - name: Run unit tests
111
- run: cargo test --lib --features=full
99
+ run: cargo test --lib --features=full
112
100
  # currently - just random panic test
113
101
  # - name: Run long-running unit tests
114
102
  # run: cargo test --lib --features=full -- --ignored --nocapture
115
103
 
104
+ - name: Run non-Docker integration tests
105
+ run: cargo test --test ref_test --test sqlite_test --test tls_example --test websocket_test --features=full
106
+
107
+ - name: Memory leak soak (route commit-task JoinSet)
108
+ run: cargo test --test memory_leak_test --features=full -- --ignored --nocapture
109
+
116
110
  # Integration tests (requires Docker) split into parallel jobs
117
111
  integration-others:
118
112
  name: Integration — Others
@@ -127,17 +121,13 @@ jobs:
127
121
  - name: Install Rust toolchain
128
122
  uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
129
123
 
130
- - name: Cache cargo registry
131
- uses: actions/cache@v4
124
+ - name: Cache cargo build
125
+ uses: Swatinem/rust-cache@v2
132
126
  with:
133
- path: |
134
- ~/.cargo/bin/
135
- ~/.cargo/registry/cache/
136
- ~/.cargo/git/db/
137
- target/
138
- key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
139
- restore-keys: |
140
- ${{ runner.os }}-cargo-
127
+ # Differentiates the matrix `check` job per feature set; evaluates to an
128
+ # empty string (no-op) in non-matrix jobs. rust-cache already keys on the
129
+ # job id, OS, rustc version, and lockfiles automatically.
130
+ key: ${{ matrix.features }}
141
131
 
142
132
  - name: Pre-pull docker images
143
133
  run: |
@@ -175,17 +165,13 @@ jobs:
175
165
  - name: Install Rust toolchain
176
166
  uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
177
167
 
178
- - name: Cache cargo registry
179
- uses: actions/cache@v4
168
+ - name: Cache cargo build
169
+ uses: Swatinem/rust-cache@v2
180
170
  with:
181
- path: |
182
- ~/.cargo/bin/
183
- ~/.cargo/registry/cache/
184
- ~/.cargo/git/db/
185
- target/
186
- key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
187
- restore-keys: |
188
- ${{ runner.os }}-cargo-
171
+ # Differentiates the matrix `check` job per feature set; evaluates to an
172
+ # empty string (no-op) in non-matrix jobs. rust-cache already keys on the
173
+ # job id, OS, rustc version, and lockfiles automatically.
174
+ key: ${{ matrix.features }}
189
175
 
190
176
  - name: Pre-pull docker images
191
177
  run: |
@@ -223,17 +209,13 @@ jobs:
223
209
  - name: Install Rust toolchain
224
210
  uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
225
211
 
226
- - name: Cache cargo registry
227
- uses: actions/cache@v4
212
+ - name: Cache cargo build
213
+ uses: Swatinem/rust-cache@v2
228
214
  with:
229
- path: |
230
- ~/.cargo/bin/
231
- ~/.cargo/registry/cache/
232
- ~/.cargo/git/db/
233
- target/
234
- key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
235
- restore-keys: |
236
- ${{ runner.os }}-cargo-
215
+ # Differentiates the matrix `check` job per feature set; evaluates to an
216
+ # empty string (no-op) in non-matrix jobs. rust-cache already keys on the
217
+ # job id, OS, rustc version, and lockfiles automatically.
218
+ key: ${{ matrix.features }}
237
219
 
238
220
  - name: Pre-pull docker images
239
221
  run: |
@@ -264,20 +246,13 @@ jobs:
264
246
  - name: Install Rust toolchain
265
247
  uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
266
248
 
267
- - name: Cache cargo registry
268
- uses: actions/cache@v4
249
+ - name: Cache cargo build
250
+ uses: Swatinem/rust-cache@v2
269
251
  with:
270
- path: |
271
- ~/.cargo/bin/
272
- ~/.cargo/registry/cache/
273
- ~/.cargo/git/db/
274
- target/
275
- key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
276
- restore-keys: |
277
- ${{ runner.os }}-cargo-
278
-
279
- - name: Build documentation
280
- run: cargo doc --all-features --no-deps
252
+ # Differentiates the matrix `check` job per feature set; evaluates to an
253
+ # empty string (no-op) in non-matrix jobs. rust-cache already keys on the
254
+ # job id, OS, rustc version, and lockfiles automatically.
255
+ key: ${{ matrix.features }}
281
256
 
282
- - name: Check for broken links
283
- run: cargo doc --all-features --no-deps 2>&1 | grep -i "warning\|error" && exit 1 || exit 0
257
+ - name: Build documentation (deny warnings)
258
+ run: RUSTDOCFLAGS="-D warnings" cargo doc --all-features --no-deps
@@ -10,11 +10,12 @@ name: Publish Node Package
10
10
  # RHEL 9 (glibc 2.34), Debian 11/12 and Ubuntu 20.04+ — covering the priority
11
11
  # server targets. macOS/Windows build natively.
12
12
  #
13
- # Auth: token-or-OIDC. If the `NPM_TOKEN` secret is set it is used (needed for the
14
- # one-time bootstrap that creates the 5 platform packages, since npm has no
15
- # pending-publisher concept). Once all six packages exist and trusted publishing
16
- # is configured for them, remove the secret and publishing continues via OIDC
17
- # (id-token) with provenance no token.
13
+ # Auth: OIDC trusted publishing. The one-time bootstrap (which used an NPM_TOKEN
14
+ # secret to create the 5 platform packages, since npm has no pending-publisher
15
+ # concept) is done; all six packages exist with trusted publishing configured, so
16
+ # publishing runs via OIDC (id-token) with provenance no token. We deliberately
17
+ # do NOT read NPM_TOKEN: a present-but-expired secret would be sent to the registry
18
+ # and 401 instead of falling back to OIDC, so the credential is left out entirely.
18
19
 
19
20
  on:
20
21
  release:
@@ -67,6 +68,9 @@ jobs:
67
68
  env:
68
69
  CARGO_NET_RETRY: "5"
69
70
  CARGO_NET_GIT_FETCH_WITH_CLI: "true"
71
+ # curl's HTTP/2 multiplexing flakes on Windows runners ("[55] Failed
72
+ # sending data to the peer"); force HTTP/1.1 so fetches are reliable.
73
+ CARGO_HTTP_MULTIPLEXING: "false"
70
74
  run: >-
71
75
  npx napi build --platform --release --strip
72
76
  --target ${{ matrix.target }}
@@ -129,6 +133,9 @@ jobs:
129
133
  env:
130
134
  CARGO_NET_RETRY: "5"
131
135
  CARGO_NET_GIT_FETCH_WITH_CLI: "true"
136
+ # curl's HTTP/2 multiplexing flakes on Windows runners ("[55] Failed
137
+ # sending data to the peer"); force HTTP/1.1 so fetches are reliable.
138
+ CARGO_HTTP_MULTIPLEXING: "false"
132
139
  run: >-
133
140
  npx napi build --platform --release --strip
134
141
  --target ${{ matrix.target }}
@@ -195,17 +202,56 @@ jobs:
195
202
  working-directory: node/mq-bridge-node
196
203
  run: ls -R npm
197
204
 
205
+ # The default per-platform README is ~88 bytes; paired with a 50-67MB
206
+ # binary that low-content signature trips npm's new-package spam filter
207
+ # (E403 "Package name triggered spam detection"). Give each stub a real
208
+ # README plus description/homepage/keywords so it doesn't read as a
209
+ # placeholder. (Server-side heuristic; reduces but cannot guarantee a pass.)
210
+ - name: Enrich platform package metadata
211
+ working-directory: node/mq-bridge-node
212
+ run: |
213
+ set -e
214
+ for dir in npm/*/; do
215
+ name=$(node -p "require('./$dir/package.json').name")
216
+ cat > "$dir/README.md" <<EOF
217
+ # $name
218
+
219
+ Prebuilt native binary for the [\`mq-bridge\`](https://github.com/marcomq/mq-bridge)
220
+ Node.js addon (napi-rs). This is a platform-specific dependency of the
221
+ \`mq-bridge\` package and is selected automatically via optionalDependencies —
222
+ you install \`mq-bridge\`, not this package directly.
223
+ EOF
224
+ node -e "const p='./$dir/package.json',j=require(p);j.description='Prebuilt '+j.name+' binary for the mq-bridge Node.js addon';j.homepage='https://github.com/marcomq/mq-bridge#readme';j.keywords=['mq-bridge','napi','native','prebuilt','addon'];require('fs').writeFileSync(p,JSON.stringify(j,null,2)+'\n')"
225
+ done
226
+
198
227
  - name: Publish platform packages and root
199
228
  working-directory: node/mq-bridge-node
200
229
  env:
201
- # Empty when the secret is unset -> npm falls back to OIDC.
202
- NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
230
+ # No NODE_AUTH_TOKEN: publishing is OIDC-only (trusted publishing).
203
231
  NPM_CONFIG_PROVENANCE: "true"
204
232
  run: |
205
233
  set -e
206
- for dir in npm/*/; do
207
- echo "Publishing $dir"
234
+ publish() {
235
+ local dir="$1" name ver
236
+ name=$(node -p "require('./$dir/package.json').name")
237
+ ver=$(node -p "require('./$dir/package.json').version")
238
+ # Skip if this exact version is already on the registry; a re-run
239
+ # after a partial publish must not fail on already-published dirs.
240
+ if npm view "$name@$ver" version >/dev/null 2>&1; then
241
+ echo "Skipping $name@$ver (already published)"
242
+ return 0
243
+ fi
244
+ echo "Publishing $dir ($name@$ver)"
208
245
  npm publish "$dir" --access public
246
+ }
247
+ for dir in npm/*/; do
248
+ publish "$dir"
209
249
  done
210
250
  echo "Publishing root package"
211
- npm publish --access public
251
+ root_name=$(node -p "require('./package.json').name")
252
+ root_ver=$(node -p "require('./package.json').version")
253
+ if npm view "$root_name@$root_ver" version >/dev/null 2>&1; then
254
+ echo "Skipping $root_name@$root_ver (already published)"
255
+ else
256
+ npm publish --access public
257
+ fi
@@ -161,6 +161,56 @@ input:
161
161
  # ... kafka config
162
162
  ```
163
163
 
164
+ ### TLS & Security Hardening
165
+
166
+ Most endpoints accept a `tls` block. The available fields are:
167
+
168
+ ```yaml
169
+ tls:
170
+ required: true # enable TLS
171
+ ca_file: "./certs/ca.pem" # CA to verify the server
172
+ cert_file: "./certs/client.pem" # client cert (mTLS)
173
+ key_file: "./certs/client.key" # client private key (mTLS)
174
+ cert_password: "secret" # password for an encrypted key (where supported)
175
+ accept_invalid_certs: false # NEVER set true in production
176
+ ```
177
+
178
+ **Hardening checklist (e.g. for PCI-DSS Req 4.2.1):**
179
+
180
+ 1. **Enable TLS on every endpoint carrying sensitive data** (`required: true`) and supply
181
+ a `ca_file`. Use mTLS (`cert_file` + `key_file`) for mutual authentication where the
182
+ broker supports it.
183
+ 2. **Never disable certificate validation.** `accept_invalid_certs` defaults to `false`;
184
+ leaving it that way is required — setting it `true` on a sensitive path defeats TLS.
185
+ 3. **Choose the crypto provider feature.** Build with `rustls-aws-lc` (FIPS-capable, also
186
+ enables post-quantum key exchange) or `rustls-ring`. The rustls-based endpoints — NATS,
187
+ MQTT, HTTP, gRPC, WebSocket, AMQP — only ever negotiate rustls's safe TLS 1.2/1.3 AEAD
188
+ cipher suites; weak/legacy suites (RC4, 3DES, CBC-SHA1, export) cannot be offered, so
189
+ "strong ciphers only" holds without any explicit cipher list.
190
+ 4. **Kafka** (librdkafka/OpenSSL): certificate verification is on by default
191
+ (`enable.ssl.certificate.verification` follows `accept_invalid_certs`). If an auditor
192
+ requires an explicit allowlist, pin it via `producer_options` / `consumer_options`:
193
+ ```yaml
194
+ kafka:
195
+ producer_options: [["ssl.cipher.suites", "ECDHE-RSA-AES256-GCM-SHA384"]]
196
+ consumer_options: [["ssl.cipher.suites", "ECDHE-RSA-AES256-GCM-SHA384"]]
197
+ ```
198
+ 5. **IBM MQ** (native stack): set a strong `cipher_spec` (a TLS 1.2/1.3 CipherSpec) — it is
199
+ required for encrypted connections.
200
+ 6. **Keep sensitive payloads out of logs.** Message payloads are emitted at `trace` level;
201
+ run production above `trace` and confirm no cardholder data (PAN) reaches logs or traces.
202
+ 7. **Do not commit secrets.** Source passwords and tokens from a secrets manager or env vars
203
+ (`MQB__...`) rather than checked-in config.
204
+
205
+ **Notes and boundaries:**
206
+
207
+ - TLS 1.3 alone is sufficient in 2026 (Mozilla "Modern" profile). The rustls endpoints
208
+ currently negotiate **TLS 1.2 and 1.3** (both are PCI-acceptable). A central
209
+ "TLS 1.3-only" toggle is not yet configurable in the library; enforce a minimum protocol
210
+ version on the broker/server side, which is the side that accepts the connection.
211
+ - Kafka and IBM MQ use native TLS stacks, so a library-wide version policy cannot be
212
+ applied to them — configure their minimum TLS version on the broker.
213
+
164
214
  ### HTTP Consumer Fast Path
165
215
 
166
216
  Compatible `http -> response` routes may use an inline response fast path for lower latency. This bypasses the normal route consumer/worker/disposition pipeline, but it still keeps the output publisher chain active, including output handlers and allowed output middlewares.
@@ -183,6 +233,36 @@ input:
183
233
 
184
234
  This is useful when you want stable, explicit semantics regardless of future optimizations, or when you want to avoid the inline path's response behavior differences. In particular, the inline path does not automatically echo unchanged request metadata back as HTTP response headers.
185
235
 
236
+ ### Connection Sharing
237
+
238
+ Publishers that target the same server reuse one underlying transport client by
239
+ default, instead of each opening its own. This consolidates TCP connections, background
240
+ threads, and batching, and follows each driver's own guidance (one shared producer /
241
+ client / pool per application). Sharing applies to **Kafka, NATS, MongoDB, SQLx, HTTP,
242
+ and gRPC**; the client is keyed by its connection-level settings (URL, auth, TLS, and
243
+ client-level options), never by topic/subject/collection. A shared client is released
244
+ once the last publisher using it is dropped.
245
+
246
+ Set `shared: false` on a publisher to give it a dedicated connection:
247
+
248
+ ```yaml
249
+ orders_out:
250
+ output:
251
+ kafka:
252
+ topic: "orders"
253
+ url: "localhost:9092"
254
+ shared: false # dedicated producer — keeps this latency-sensitive topic off a busy producer's queue
255
+ ```
256
+
257
+ * **Kafka**: a single producer serves every topic and is the recommended setup. Use
258
+ `shared: false` to isolate a latency-sensitive topic from a high-throughput one so
259
+ they don't share one internal send queue (head-of-line blocking).
260
+ * **SQLx**: a shared pool means its `max_connections` is a budget shared across every
261
+ route using that database. Use `shared: false` if a route needs its own pool.
262
+ * **gRPC**: a shared channel multiplexes over one HTTP/2 connection; at very high
263
+ concurrency its max-concurrent-streams cap can bottleneck — `shared: false` gives a
264
+ dedicated channel.
265
+
186
266
  ### Specialized Endpoints
187
267
 
188
268
  #### Switch
@@ -3082,7 +3082,7 @@ dependencies = [
3082
3082
 
3083
3083
  [[package]]
3084
3084
  name = "mq-bridge"
3085
- version = "0.2.21"
3085
+ version = "0.3.1"
3086
3086
  dependencies = [
3087
3087
  "anyhow",
3088
3088
  "arc-swap",
@@ -3147,20 +3147,32 @@ dependencies = [
3147
3147
  "zeromq",
3148
3148
  ]
3149
3149
 
3150
+ [[package]]
3151
+ name = "mq-bridge-bindings-common"
3152
+ version = "0.3.1"
3153
+ dependencies = [
3154
+ "anyhow",
3155
+ "fast-uuid-v7",
3156
+ "mq-bridge",
3157
+ "serde",
3158
+ "serde_yaml_ng",
3159
+ "tokio",
3160
+ ]
3161
+
3150
3162
  [[package]]
3151
3163
  name = "mq-bridge-node"
3152
- version = "0.2.21"
3164
+ version = "0.3.1"
3153
3165
  dependencies = [
3154
3166
  "anyhow",
3155
3167
  "async-trait",
3156
3168
  "bytes",
3157
3169
  "fast-uuid-v7",
3158
3170
  "mq-bridge",
3171
+ "mq-bridge-bindings-common",
3159
3172
  "napi",
3160
3173
  "napi-build",
3161
3174
  "napi-derive",
3162
3175
  "schemars",
3163
- "serde",
3164
3176
  "serde_json",
3165
3177
  "serde_yaml_ng",
3166
3178
  "tokio",
@@ -3169,13 +3181,14 @@ dependencies = [
3169
3181
 
3170
3182
  [[package]]
3171
3183
  name = "mq-bridge-py"
3172
- version = "0.2.21"
3184
+ version = "0.3.1"
3173
3185
  dependencies = [
3174
3186
  "anyhow",
3175
3187
  "async-trait",
3176
3188
  "bytes",
3177
3189
  "fast-uuid-v7",
3178
3190
  "mq-bridge",
3191
+ "mq-bridge-bindings-common",
3179
3192
  "pyo3",
3180
3193
  "rustls 0.23.40",
3181
3194
  "schemars",
@@ -12,11 +12,11 @@ keywords = ["messaging", "kafka", "nats", "amqp", "cqrs"]
12
12
  categories = ["asynchronous", "network-programming"]
13
13
 
14
14
  [workspace]
15
- members = ["python/mq-bridge-py"]
15
+ members = ["bindings-common", "python/mq-bridge-py"]
16
16
  resolver = "2"
17
17
 
18
18
  [workspace.package]
19
- version = "0.2.21"
19
+ version = "0.3.1"
20
20
 
21
21
  [workspace.dependencies]
22
22
  anyhow = "1.0"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mq-bridge-py-basic
3
- Version: 0.2.21
3
+ Version: 0.3.1
4
4
  Classifier: Development Status :: 3 - Alpha
5
5
  Classifier: License :: OSI Approved :: MIT License
6
6
  Classifier: Programming Language :: Python :: 3
@@ -38,12 +38,14 @@ Memory and file endpoints are always present in both packages. Use `mq-bridge-py
38
38
 
39
39
  The public API stays close to mq-bridge itself:
40
40
 
41
- - `Route.from_yaml(path, name)` loads one named route from a YAML file
42
- - `Route.from_yaml_str(text, name)` / `Route.from_config(mapping, name)` build a route from an in-memory YAML string or a Python `dict`, no file required
41
+ - `Route.from_file(path, name=None)` loads a route from a YAML/JSON file. The three constructors differ only by source: `from_file` (path), `from_str` (in-memory YAML/JSON string), `from_config` (Python `dict`)
42
+ - The `name` is optional: pass it to pick one entry out of a `routes:`/`publishers:` document, or omit it to treat the whole config as a single bare route/endpoint body
43
43
  - `Route.with_handler(...)` attaches a raw `Message` handler, with lazy `json()`/`text()` readers and `with_json()`/`with_payload()` response helpers
44
44
  - `Route.add_handler(kind, ...)` uses mq-bridge's `kind` dispatch and delivers decoded JSON
45
45
  - `RetryableError` and `NonRetryableError` let Python handlers signal retry intent
46
- - `Publisher.from_yaml(path, name)` (plus `from_yaml_str` / `from_config`) loads one named publisher
46
+ - `Publisher.from_file(path, name=None)` (plus `from_str` / `from_config`) builds a publisher endpoint
47
+
48
+ `from_yaml` / `from_yaml_str` remain as deprecated aliases for `from_file` / `from_str`.
47
49
  - `Publisher.send_json(...)` and `Publisher.request_json(...)` serialize Python JSON values in Rust
48
50
 
49
51
  The Python surface is synchronous and blocking. Tokio, broker I/O, routing, and batching all stay in Rust.
@@ -53,10 +55,10 @@ The Python surface is synchronous and blocking. Tokio, broker I/O, routing, and
53
55
  `mq-bridge-app` can create and test route and endpoint JSON/YAML through its UI.
54
56
  It does not replace your Python code or handlers, but it is useful when you want
55
57
  a known-good connection and route shape before pasting the configuration into
56
- Python. Load the generated config with `Route.from_config`, `Route.from_yaml`,
57
- `Publisher.from_config`, or `Publisher.from_yaml`.
58
+ Python. Load the generated config with `Route.from_config`, `Route.from_file`,
59
+ `Publisher.from_config`, or `Publisher.from_file`.
58
60
 
59
- For the `from_config` / `from_yaml_str` mappings, `mq_bridge.config` ships
61
+ For the `from_config` / `from_str` mappings, `mq_bridge.config` ships
60
62
  `TypedDict` definitions so editors autocomplete the config keys (`input`,
61
63
  `output`, `batch_size`, every transport config, middleware, …):
62
64
 
@@ -117,6 +119,91 @@ with Route.from_config(config, "orders_route").with_handler(handle):
117
119
  Configuration/connection errors surface from `start()` itself, not from a
118
120
  background thread. `run()` remains available for the blocking single-route case.
119
121
 
122
+ ## Pull-based consumer
123
+
124
+ `Route` is push-based: you attach a handler and the route drives it. When you
125
+ instead want to **pull** messages on your own schedule — e.g. to feed a
126
+ generator-style sink such as a [`dlt`](https://dlthub.com) resource — use
127
+ `Consumer`. It wraps any input endpoint and hands batches back to Python:
128
+
129
+ ```python
130
+ from mq_bridge import Consumer
131
+
132
+ consumer = Consumer.from_config({"nats": {"subject": "orders", "url": "nats://localhost:4222"}})
133
+
134
+ while not consumer.exhausted:
135
+ batch = consumer.poll(max=500, timeout_ms=1000) # [] on timeout
136
+ if not batch:
137
+ continue
138
+ for message in batch:
139
+ handle(message.json())
140
+ consumer.commit() # ack only after handling
141
+ ```
142
+
143
+ `poll()` receives up to `max` messages **without** acknowledging them;
144
+ `commit()` acks every batch returned since the last commit, advancing the
145
+ consumer offset (or removing them from the queue). Committing only after the
146
+ downstream write succeeds gives at-least-once delivery: a crash before `commit()`
147
+ re-delivers the batch. `poll()` returns `[]` once `timeout_ms` elapses with
148
+ nothing received (omit it to block until a message arrives), and sets
149
+ `exhausted` once a bounded source (e.g. a file) is fully drained — streaming
150
+ brokers never set it.
151
+
152
+ > **You must call `commit()` — it is not optional.** It is the only thing that
153
+ > tells the broker a batch is done. If you keep polling without committing:
154
+ > - the consumer offset never advances, so every message is **re-delivered** on
155
+ > the next run (and you reprocess from the start);
156
+ > - most brokers stop sending once their unacknowledged/prefetch window fills, so
157
+ > `poll()` eventually **stalls** and returns nothing;
158
+ > - the uncommitted batches are held in memory pending their ack, so the process
159
+ > **grows unbounded**.
160
+ >
161
+ > Commit after each batch you have durably handled (as in the loops above). If a
162
+ > batch fails downstream, simply *don't* commit it — it will be redelivered.
163
+
164
+ `consumer.status()` returns a snapshot dict (`healthy`, `target`, `pending`,
165
+ `capacity`, `error`, `details`). `pending` is the broker backlog/lag where the
166
+ transport reports it — Kafka offset lag, AMQP queue depth, NATS JetStream
167
+ `num_pending` — so `pending == 0` is a precise "caught up" check for a bounded
168
+ drain; it is `None` where the broker exposes no backlog (core NATS, MQTT), where
169
+ you fall back to a `timeout_ms` that returns `[]`. It's a point-in-time snapshot,
170
+ not a guarantee.
171
+
172
+ `consumer.close()` releases the broker connection; it's idempotent, and `poll()`
173
+ /`status()` raise afterwards. Python is garbage-collected, so close explicitly
174
+ (or use the context-manager form, which closes on exit) rather than relying on
175
+ the object being collected:
176
+
177
+ ```python
178
+ with Consumer.from_config(cfg) as consumer:
179
+ batch = consumer.poll(max=500, timeout_ms=1000)
180
+ ...
181
+ consumer.commit()
182
+ # connection released here
183
+ ```
184
+
185
+ The endpoint config decides durability exactly as a route input does: a
186
+ consumer-group config resumes from the last commit, a subscriber config receives
187
+ only new messages. `Consumer.from_file` / `from_str` accept the same shapes,
188
+ plus a named entry under a `consumers:` document section.
189
+
190
+ As a `dlt` resource this is a few lines:
191
+
192
+ ```python
193
+ import dlt
194
+ from mq_bridge import Consumer
195
+
196
+ @dlt.resource(name="orders")
197
+ def orders():
198
+ consumer = Consumer.from_config({"nats": {"subject": "orders", "url": "nats://localhost:4222"}})
199
+ while not consumer.exhausted:
200
+ batch = consumer.poll(max=500, timeout_ms=1000)
201
+ if not batch:
202
+ break # nothing more pending this run
203
+ yield [m.json() for m in batch]
204
+ consumer.commit()
205
+ ```
206
+
120
207
  ## Tuning (environment variables)
121
208
 
122
209
  These knobs are read from the environment at startup:
@@ -20,6 +20,28 @@
20
20
  It is not only a forwarder. A route can transform, filter, fan out, retry, rate-limit, deduplicate, or turn a request into a response before the message reaches the next system. The core is built on Tokio and keeps the transport details at the edge, so application code can mostly work with `CanonicalMessage`s and handlers.
21
21
 
22
22
 
23
+ ## Language Bindings
24
+
25
+ `mq-bridge` is a Rust library, but the same engine ships as native bindings for **Python** and **Node.js**. The Tokio runtime, broker I/O, routing, and batching all stay in Rust; the binding is a thin layer for handlers and configuration.
26
+
27
+ | Language | Package | Install |
28
+ | :--- | :--- | :--- |
29
+ | Rust | [`mq-bridge`](https://crates.io/crates/mq-bridge) | `cargo add mq-bridge` |
30
+ | Python | [`mq-bridge-py`](python/mq-bridge-py/README.md) ([PyPI](https://pypi.org/project/mq-bridge-py/)) | `pip install mq-bridge-py` |
31
+ | Node.js | [`mq-bridge`](node/mq-bridge-node/README.md) ([npm](https://www.npmjs.com/package/mq-bridge)) | `npm install mq-bridge` |
32
+
33
+ The constructor names are kept aligned across languages, so a config loader reads the same in either binding (Python uses `snake_case`, Node uses `camelCase`):
34
+
35
+ - `Route.from_file` / `Route.fromFile` — load a route from a YAML/JSON file
36
+ - `Route.from_str` / `Route.fromStr` — load from an in-memory YAML/JSON string
37
+ - `Route.from_config` / `Route.fromConfig` — load from a dict / JS object
38
+ - the matching `Publisher.*` constructors build a publisher endpoint
39
+
40
+ The `name` argument is optional in both: pass it to select one entry from a `routes:`/`publishers:` document, or omit it to treat the config as a single bare route/endpoint body.
41
+
42
+ The Python binding also holds up well under load on the third-party [http-arena.com](https://www.http-arena.com/#sort=rps:-1&q=python) requests-per-second HTTP benchmark (live leaderboard — rankings shift over time). See [the Python analysis notes](python/mq-bridge-py/README.md#analysis) for the local HTTP comparison harness.
43
+
44
+
23
45
  ## Architecture
24
46
 
25
47
  See [ARCHITECTURE.md](ARCHITECTURE.md) for a detailed overview of the internal design, extensibility, and usage patterns.
@@ -55,7 +77,7 @@ That means `mq-bridge` tries to keep the boring parts boring. Kafka offsets, Rab
55
77
 
56
78
  Batching is a big part of that design. Every endpoint is optimized around batch-shaped APIs, even when the backend itself only has a single-message primitive. Batching is disabled by default (`batch_size: 1`) because it is the safest behavior and easiest to reason about. When throughput matters, increasing `batch_size` is usually the first knob to try. For example via `batch_size: 128` in yaml or `.with_batch_size(128)` for routes.
57
79
 
58
- The error handling follows the same idea. Batch publishing can report partial success, retryable failures, and non-retryable failures. Route commits are sequenced so cumulative-ack brokers do not accidentally acknowledge later messages before earlier batches are resolved. In other words: batching is not just a performance trick bolted onto the side; ack/nack behavior and retry/DLQ handling were built to work with it.
80
+ The error handling follows the same idea. Batch publishing can report partial success, retryable failures, and non-retryable failures. Route commits are sequenced for cumulative-ack brokers so later batches cannot acknowledge earlier unresolved messages; transports with independent acknowledgements can commit batches concurrently. In other words: batching is not just a performance trick bolted onto the side; ack/nack behavior and retry/DLQ handling were built to work with it.
59
81
 
60
82
  What it does not try to be: a domain framework, an actor runtime, or a full stream processor. You can build CQRS-ish flows with it, but the library cares more about transport, routing, and delivery behavior than about prescribing your domain model.
61
83
 
@@ -161,7 +183,7 @@ https://github.com/marcomq/mq-bridge-app
161
183
 
162
184
  `mq-bridge-app` can be used to create and test route and endpoint configurations through a UI. The generated JSON/YAML can then be copied into an application and loaded by the Rust library or the Python bindings.
163
185
 
164
- This does not replace application code or handlers, but it is useful when you want a known-good connection and route shape before pasting the configuration into code. For Python projects, routes and publishers are commonly loaded from JSON/YAML with `Route.from_config`, `Route.from_yaml`, `Publisher.from_config`, or `Publisher.from_yaml`.
186
+ This does not replace application code or handlers, but it is useful when you want a known-good connection and route shape before pasting the configuration into code. For Python and Node projects, routes and publishers are commonly loaded from JSON/YAML with `Route.from_file`/`fromFile` (a file path) and `Route.from_str`/`fromStr` (a JSON/YAML string), while `Route.from_config`/`fromConfig` takes an already-parsed dict/JS object. The matching `Publisher.*` constructors work the same way.
165
187
 
166
188
  ### Programmatic Handlers
167
189
 
@@ -392,7 +414,7 @@ Important route-level knobs:
392
414
 
393
415
  * `batch_size`: maximum messages per route iteration. Defaults to `1`; increase it when throughput matters.
394
416
  * `concurrency`: number of route workers. Defaults to `1`; useful for high-latency handlers or endpoints.
395
- * `commit_concurrency_limit`: maximum queued in-flight commit operations used by ordered commit sequencing. Defaults to `4096`.
417
+ * `commit_concurrency_limit`: maximum in-flight commit operations, whether they are queued through ordered commit sequencing or run concurrently for independent-ack transports. Defaults to `4096`.
396
418
 
397
419
  Middleware can be attached to inputs or outputs. The most commonly used ones are `retry`, `dlq`, `deduplication`, `limiter`, and `cookie_jar`. Retry/DLQ are especially useful with batching because partial failures can be retried or sent to a DLQ without treating the entire batch as equally broken.
398
420