mq-bridge-py-basic 0.4.6__tar.gz → 0.4.7__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 (291) hide show
  1. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/CHANGELOG.md +117 -0
  2. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/Cargo.lock +4 -4
  3. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/Cargo.toml +1 -1
  4. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/PKG-INFO +1 -1
  5. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/plugin/endpoint.rs +46 -5
  6. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/route.rs +147 -24
  7. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  8. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  9. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  10. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/.github/dependabot.yml +0 -0
  11. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/.github/workflows/benchmark.yml +0 -0
  12. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/.github/workflows/ci.yml +0 -0
  13. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/.github/workflows/ibm-mq.yml +0 -0
  14. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/.github/workflows/node.yml +0 -0
  15. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/.github/workflows/publish-node.yml +0 -0
  16. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/.github/workflows/publish-python.yml +0 -0
  17. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/.github/workflows/python.yml +0 -0
  18. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/.github/workflows/release.yml +0 -0
  19. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/.github/workflows/test-matrix.yml +0 -0
  20. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/.gitignore +0 -0
  21. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/CLAUDE.md +0 -0
  22. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/CONTRIBUTING.md +0 -0
  23. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/LICENSE +0 -0
  24. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/LICENSE.python +0 -0
  25. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/README.md +0 -0
  26. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/SECURITY.md +0 -0
  27. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/benches/ETL_BENCHMARKS.md +0 -0
  28. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/benches/performance_bench.rs +0 -0
  29. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/benches/router_bench.rs +0 -0
  30. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/benches/zeromq_backends.rs +0 -0
  31. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/bindings-common/Cargo.toml +0 -0
  32. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/bindings-common/src/lib.rs +0 -0
  33. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/bindings-common/src/logging.rs +0 -0
  34. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/build.rs +0 -0
  35. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/deny.toml +0 -0
  36. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/docs/ARCHITECTURE.md +0 -0
  37. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/docs/CONFIGURATION.md +0 -0
  38. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/docs/DELIVERY.md +0 -0
  39. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/docs/EXTENDING.md +0 -0
  40. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/docs/PLUGINS.md +0 -0
  41. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/docs/REFERENCE.md +0 -0
  42. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/examples/ipc_worker_queue.yaml +0 -0
  43. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/mq-bridge.schema.json +0 -0
  44. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/mq_bridge/__init__.py +0 -0
  45. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/mq_bridge/__init__.pyi +0 -0
  46. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/mq_bridge/config.py +0 -0
  47. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/mq_bridge/config.pyi +0 -0
  48. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/mq_bridge/plugin_packaging.py +0 -0
  49. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/mq_bridge/py.typed +0 -0
  50. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/package-lock.json +0 -0
  51. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/pyproject.toml +0 -0
  52. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/python/mq-bridge-py/Cargo.toml +0 -0
  53. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/python/mq-bridge-py/LICENSE.python +0 -0
  54. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/python/mq-bridge-py/README.md +0 -0
  55. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/python/mq-bridge-py/analysis/bench_http_native.py +0 -0
  56. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/python/mq-bridge-py/example.py +0 -0
  57. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/python/mq-bridge-py/examples/bench_memory.py +0 -0
  58. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/python/mq-bridge-py/examples/bench_memory.yaml +0 -0
  59. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/python/mq-bridge-py/examples/consume_pull.py +0 -0
  60. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/python/mq-bridge-py/examples/dlt_source.py +0 -0
  61. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/python/mq-bridge-py/examples/json_route.py +0 -0
  62. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/python/mq-bridge-py/examples/memory.yaml +0 -0
  63. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/python/mq-bridge-py/examples/raw_route.py +0 -0
  64. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/python/mq-bridge-py/pyproject-basic.toml +0 -0
  65. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/python/mq-bridge-py/pyproject-reduced.toml +0 -0
  66. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/python/mq-bridge-py/scripts/gen_config_types.py +0 -0
  67. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/python/mq-bridge-py/src/lib.rs +0 -0
  68. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/python/mq-bridge-py/tests/test_config_types.py +0 -0
  69. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/python/mq-bridge-py/tests/test_custom_endpoint.py +0 -0
  70. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/python/mq-bridge-py/tests/test_logging.py +0 -0
  71. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/python/mq-bridge-py/tests/test_packaging.py +0 -0
  72. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/python/mq-bridge-py/tests/test_performance_smoke.py +0 -0
  73. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/python/mq-bridge-py/tests/test_plugin_package.py +0 -0
  74. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/python/mq-bridge-py/tests/test_public_api.py +0 -0
  75. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/python/mq-bridge-py/tests/test_routes.py +0 -0
  76. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/python/mq-bridge-py/uv.lock +0 -0
  77. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/scripts/analysis/benches/metadata_bench.rs +0 -0
  78. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/scripts/analysis/debug_ibm_mq_tls.rs +0 -0
  79. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/scripts/analysis/http/analyze_xctrace_time_profile.py +0 -0
  80. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/scripts/analysis/http/http_flamegraph.py +0 -0
  81. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/scripts/analysis/http/mq_bridge_http_profile.rs +0 -0
  82. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/scripts/analysis/http/run_http_ladder.sh +0 -0
  83. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/scripts/ci/format_bench_output.sh +0 -0
  84. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/scripts/httparena/README.md +0 -0
  85. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/scripts/httparena/frameworks/mq-bridge-py/Dockerfile +0 -0
  86. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/scripts/httparena/frameworks/mq-bridge-py/meta.json +0 -0
  87. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/scripts/httparena/frameworks/mq-bridge-py/server.py +0 -0
  88. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/scripts/techempower/Python/mq-bridge-py/README.md +0 -0
  89. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/scripts/techempower/Python/mq-bridge-py/benchmark_config.json +0 -0
  90. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/scripts/techempower/Python/mq-bridge-py/mq-bridge-py.dockerfile +0 -0
  91. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/scripts/techempower/Python/mq-bridge-py/server.py +0 -0
  92. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/scripts/techempower/README.md +0 -0
  93. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/scripts/techempower/postgres.yml +0 -0
  94. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/scripts/techempower/seed.sql +0 -0
  95. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/scripts/techempower/verify.sh +0 -0
  96. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/scripts/the-benchmarker/README.md +0 -0
  97. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/scripts/the-benchmarker/python/mq-bridge-py/config.yaml +0 -0
  98. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/scripts/the-benchmarker/python/mq-bridge-py/pyproject.toml +0 -0
  99. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/scripts/the-benchmarker/python/mq-bridge-py/server.py +0 -0
  100. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/canonical_message.rs +0 -0
  101. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/checkpoint.rs +0 -0
  102. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/command_handler.rs +0 -0
  103. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/endpoints/amqp.rs +0 -0
  104. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/endpoints/aws.rs +0 -0
  105. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/endpoints/clickhouse.rs +0 -0
  106. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/endpoints/file/mod.rs +0 -0
  107. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/endpoints/file/tests.rs +0 -0
  108. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/endpoints/grpc.proto +0 -0
  109. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/endpoints/grpc.rs +0 -0
  110. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/endpoints/http/mod.rs +0 -0
  111. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/endpoints/http/publisher.rs +0 -0
  112. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/endpoints/http/stream.rs +0 -0
  113. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/endpoints/http/tests.rs +0 -0
  114. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/endpoints/ibm_mq.rs +0 -0
  115. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/endpoints/kafka.rs +0 -0
  116. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/endpoints/memory/endpoint.rs +0 -0
  117. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/endpoints/memory/framed.rs +0 -0
  118. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/endpoints/memory/ipc_unix.rs +0 -0
  119. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/endpoints/memory/ipc_windows.rs +0 -0
  120. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/endpoints/memory/memory_transport.rs +0 -0
  121. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/endpoints/memory/mod.rs +0 -0
  122. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/endpoints/memory/transport.rs +0 -0
  123. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/endpoints/mod.rs +0 -0
  124. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/endpoints/mongodb/consumer.rs +0 -0
  125. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/endpoints/mongodb/dedup.rs +0 -0
  126. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/endpoints/mongodb/mod.rs +0 -0
  127. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/endpoints/mongodb/publisher.rs +0 -0
  128. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/endpoints/mongodb/readers.rs +0 -0
  129. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/endpoints/mongodb/tests.rs +0 -0
  130. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/endpoints/mqtt.rs +0 -0
  131. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/endpoints/nats.rs +0 -0
  132. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/endpoints/object_store.rs +0 -0
  133. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/endpoints/poll.rs +0 -0
  134. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/endpoints/postgres/VENDORED.md +0 -0
  135. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/endpoints/postgres/mod.rs +0 -0
  136. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/endpoints/postgres/pgoutput/decoder.rs +0 -0
  137. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/endpoints/postgres/pgoutput/messages.rs +0 -0
  138. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/endpoints/postgres/pgoutput/mod.rs +0 -0
  139. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/endpoints/postgres/pgoutput/registry.rs +0 -0
  140. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/endpoints/postgres/pgoutput/values.rs +0 -0
  141. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/endpoints/postgres/replication.rs +0 -0
  142. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/endpoints/postgres/state.rs +0 -0
  143. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/endpoints/redis_streams.rs +0 -0
  144. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/endpoints/sled.rs +0 -0
  145. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/endpoints/sqlx/dedup.rs +0 -0
  146. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/endpoints/sqlx/mod.rs +0 -0
  147. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/endpoints/sqlx/tests.rs +0 -0
  148. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/endpoints/structural/fanout.rs +0 -0
  149. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/endpoints/structural/mod.rs +0 -0
  150. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/endpoints/structural/null.rs +0 -0
  151. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/endpoints/structural/reader.rs +0 -0
  152. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/endpoints/structural/request.rs +0 -0
  153. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/endpoints/structural/response.rs +0 -0
  154. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/endpoints/structural/static_endpoint.rs +0 -0
  155. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/endpoints/structural/stream_buffer.rs +0 -0
  156. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/endpoints/structural/switch.rs +0 -0
  157. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/endpoints/websocket.rs +0 -0
  158. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/endpoints/zeromq/codec.rs +0 -0
  159. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/endpoints/zeromq/mod.rs +0 -0
  160. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/endpoints/zeromq/omq.rs +0 -0
  161. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/endpoints/zeromq/zmq.rs +0 -0
  162. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/errors.rs +0 -0
  163. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/event_handler.rs +0 -0
  164. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/event_store.rs +0 -0
  165. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/extensions.rs +0 -0
  166. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/lib.rs +0 -0
  167. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/middleware/buffer.rs +0 -0
  168. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/middleware/compression.rs +0 -0
  169. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/middleware/cookie_jar.rs +0 -0
  170. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/middleware/deduplication.rs +0 -0
  171. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/middleware/deferred_commit.rs +0 -0
  172. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/middleware/delay.rs +0 -0
  173. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/middleware/dlq.rs +0 -0
  174. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/middleware/encryption.rs +0 -0
  175. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/middleware/filter.rs +0 -0
  176. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/middleware/id.rs +0 -0
  177. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/middleware/limiter.rs +0 -0
  178. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/middleware/metrics.rs +0 -0
  179. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/middleware/mod.rs +0 -0
  180. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/middleware/random_panic.rs +0 -0
  181. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/middleware/raw_json.rs +0 -0
  182. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/middleware/retry.rs +0 -0
  183. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/middleware/transform/coerce.rs +0 -0
  184. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/middleware/transform/compiled.rs +0 -0
  185. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/middleware/transform/error.rs +0 -0
  186. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/middleware/transform/mod.rs +0 -0
  187. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/middleware/transform/path.rs +0 -0
  188. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/middleware/transform/schema.rs +0 -0
  189. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/middleware/transform/tests.rs +0 -0
  190. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/middleware/weak_join.rs +0 -0
  191. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/models/builders.rs +0 -0
  192. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/models/defaults.rs +0 -0
  193. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/models/secrets.rs +0 -0
  194. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/models/serde_support.rs +0 -0
  195. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/models/tests.rs +0 -0
  196. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/models.rs +0 -0
  197. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/outcomes.rs +0 -0
  198. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/plugin/conformance.rs +0 -0
  199. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/plugin/message.rs +0 -0
  200. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/plugin/middleware.rs +0 -0
  201. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/plugin/mod.rs +0 -0
  202. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/plugin/sdk.rs +0 -0
  203. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/plugin/test_support.rs +0 -0
  204. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/publisher.rs +0 -0
  205. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/response.rs +0 -0
  206. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/support/base64_engine.rs +0 -0
  207. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/support/compression.rs +0 -0
  208. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/support/connection_registry.rs +0 -0
  209. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/support/crypto.rs +0 -0
  210. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/support/crypto_envelope.rs +0 -0
  211. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/support/interpolation.rs +0 -0
  212. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/support/mod.rs +0 -0
  213. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/support/plugin_abi.rs +0 -0
  214. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/support/source_ranges.rs +0 -0
  215. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/test_utils.rs +0 -0
  216. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/traits.rs +0 -0
  217. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/src/type_handler.rs +0 -0
  218. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/README.md +0 -0
  219. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/armature_integration.rs +0 -0
  220. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/integration/amqp.rs +0 -0
  221. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/integration/aws.rs +0 -0
  222. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/integration/clickhouse.rs +0 -0
  223. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/integration/config.toml +0 -0
  224. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/integration/docker-compose/amqp.yml +0 -0
  225. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/integration/docker-compose/aws.yml +0 -0
  226. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/integration/docker-compose/clickhouse.yml +0 -0
  227. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/integration/docker-compose/ibm-mq-certs/client.crl +0 -0
  228. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/integration/docker-compose/ibm-mq-certs/client.kdb +0 -0
  229. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/integration/docker-compose/ibm-mq-certs/client.p12 +0 -0
  230. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/integration/docker-compose/ibm-mq-certs/client.rdb +0 -0
  231. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/integration/docker-compose/ibm-mq-certs/client.sth +0 -0
  232. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/integration/docker-compose/ibm_mq.yml +0 -0
  233. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/integration/docker-compose/ibm_mq_tls.yml +0 -0
  234. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/integration/docker-compose/kafka-tls.yml +0 -0
  235. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/integration/docker-compose/kafka.yml +0 -0
  236. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/integration/docker-compose/mariadb.yml +0 -0
  237. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/integration/docker-compose/mongodb-replica.yml +0 -0
  238. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/integration/docker-compose/mongodb-tls.yml +0 -0
  239. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/integration/docker-compose/mongodb.yml +0 -0
  240. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/integration/docker-compose/mosquitto.conf +0 -0
  241. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/integration/docker-compose/mosquitto_performance.conf +0 -0
  242. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/integration/docker-compose/mosquitto_persistence.conf +0 -0
  243. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/integration/docker-compose/mq_init.mqsc +0 -0
  244. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/integration/docker-compose/mq_init_tls.mqsc +0 -0
  245. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/integration/docker-compose/mqtt.yml +0 -0
  246. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/integration/docker-compose/mqtt_performance.yml +0 -0
  247. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/integration/docker-compose/mysql.yml +0 -0
  248. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/integration/docker-compose/nats.yml +0 -0
  249. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/integration/docker-compose/object_store.yml +0 -0
  250. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/integration/docker-compose/postgres.yml +0 -0
  251. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/integration/docker-compose/postgres_cdc.yml +0 -0
  252. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/integration/docker-compose/redis.yml +0 -0
  253. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/integration/file.rs +0 -0
  254. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/integration/grpc.rs +0 -0
  255. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/integration/grpc_tls.rs +0 -0
  256. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/integration/http.rs +0 -0
  257. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/integration/http_tls.rs +0 -0
  258. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/integration/ibm_mq.rs +0 -0
  259. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/integration/ibm_mq_tls.rs +0 -0
  260. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/integration/ipc.rs +0 -0
  261. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/integration/kafka.rs +0 -0
  262. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/integration/kafka_tls.rs +0 -0
  263. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/integration/logic_test.rs +0 -0
  264. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/integration/mariadb.rs +0 -0
  265. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/integration/memory.rs +0 -0
  266. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/integration/mod.rs +0 -0
  267. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/integration/mongodb.rs +0 -0
  268. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/integration/mongodb_raw.rs +0 -0
  269. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/integration/mongodb_tls.rs +0 -0
  270. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/integration/mqtt.rs +0 -0
  271. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/integration/mysql.rs +0 -0
  272. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/integration/nats.rs +0 -0
  273. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/integration/object_store.rs +0 -0
  274. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/integration/performance_static.rs +0 -0
  275. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/integration/postgres.rs +0 -0
  276. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/integration/postgres_cdc.rs +0 -0
  277. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/integration/redis_streams.rs +0 -0
  278. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/integration/route.rs +0 -0
  279. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/integration/scripts/gen_certs.sh +0 -0
  280. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/integration/sqlite.rs +0 -0
  281. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/integration/tls_helpers.rs +0 -0
  282. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/integration/websocket.rs +0 -0
  283. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/integration/zeromq.rs +0 -0
  284. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/integration_test.rs +0 -0
  285. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/memory_leak_test.rs +0 -0
  286. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/ref_test.rs +0 -0
  287. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/reference_docs_test.rs +0 -0
  288. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/sqlite_test.rs +0 -0
  289. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/tls_example.rs +0 -0
  290. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/unit_tests.md +0 -0
  291. {mq_bridge_py_basic-0.4.6 → mq_bridge_py_basic-0.4.7}/tests/websocket_test.rs +0 -0
@@ -2,6 +2,123 @@
2
2
 
3
3
  All notable changes to `mq-bridge`. Newest first.
4
4
 
5
+ ## 0.4.7
6
+
7
+ ### Changed
8
+
9
+ - **The startup failure message distinguishes its two causes.** A route that never became ready
10
+ reports `failed to start: did not become ready within {n}ms`; a route whose task ended before
11
+ signalling ready reports `failed to start: ended before it signalled ready (outcome: ...)`.
12
+ The second case returns immediately and never involved the timeout, so reporting it as one
13
+ sent the reader looking for a stall that never happened. Both now carry the cause the
14
+ reconnect loop recorded, which the old message dropped. Code matching on the old
15
+ `failed to start within {n}ms or encountered an error` text needs updating; the substring
16
+ `failed to start` is unchanged.
17
+
18
+ ### Fixed
19
+
20
+ - **A route that ran to completion is no longer reported as one that failed to start.** The run
21
+ task signals ready before its consume loop, so a drain over a small source can reach its
22
+ terminal while that signal is still buffered — leaving the reconnect loop's `select!` with two
23
+ ready branches and free to take the terminal one, dropping the startup channel unread.
24
+ `Route::run()` then returned an error for a drain that had already written its output, and
25
+ `mqb copy --drain` exited non-zero on a successful copy. Both terminal arms now forward a
26
+ ready signal that was already emitted.
27
+
28
+ - **A completed route reports the messages it dropped even after an earlier reconnect failure.**
29
+ The drop report was suppressed whenever `EndpointStatus::error` was already set, including by a
30
+ transient failure the route recovered from. Only a `Failed` route's cause now outranks it.
31
+
32
+ - **A plugin endpoint that rejects its config no longer reconnects forever.** `create_consumer`
33
+ and `create_publisher` flattened the plugin's status into a bare message, and the reconnect
34
+ loop decides by downcasting to `ConsumerError`/`PublisherError` — so every failure to open
35
+ looked like a connection fault worth retrying. A plugin answering `MQB_ERR_INVALID_CONFIG`,
36
+ `MQB_ERR_UNSUPPORTED`, `MQB_ERR_PERMANENT` or `MQB_ERR_PANIC` now stops the route with that
37
+ cause; `MQB_ERR_CONNECTION` and `MQB_ERR_RETRYABLE` still get the reconnect loop.
38
+
39
+ ## 0.4.6
40
+
41
+ ### Added
42
+
43
+ - **`filter` middleware.** Keeps only the messages matching an expression and drops the rest,
44
+ on the input or the output: `- filter: "amount > 100"`. Payload fields are read by bare name
45
+ (`amount`, `order.status`), metadata under `meta.` and always as text, so a numeric comparison
46
+ needs a cast (`number(meta.http_status_code) >= 400`); `&&`/`||` and `and`/`or` both work. A
47
+ metadata-only expression never parses the payload. **Prefer the input** — a filter there drops
48
+ the message before the rest of the route touches it and acks it at the source. Requires the
49
+ new `filter` feature (it pulls the `zen-expression` engine), which is part of `middleware` and
50
+ `full` but **not** of `portable`.
51
+
52
+ - **Predicate mode for `switch`.** `when: [{ if, to }, ...]` routes on the same expression
53
+ language, first match wins, so overlapping thresholds are safe to write and a payload-derived
54
+ key no longer has to be promoted into metadata first. A `switch` uses exactly one of `when` or
55
+ `metadata_key`/`cases`; naming both or neither is a startup error, as is a `when` list in a
56
+ build without the `filter` feature. Value lookup stays the cheaper mode when the key is
57
+ already in metadata.
58
+
59
+ - **`expression` on `transform`.** A Zen expression producing the output document, applied after
60
+ `mapping` and before `schema` — for values that have to be calculated rather than copied.
61
+ Needs the `zen` feature.
62
+
63
+ - **A `fanout` can reply.** If one branch produces a response — a `response` or `static` leg, or
64
+ a `request` whose `forward_to` replies — that response goes back to the caller, so a
65
+ request/reply input can fan a message out and still answer. Only one branch may answer: if
66
+ several do, the first in list order wins, and the route warns once rather than on every
67
+ message. A branch that *fails* nacks the whole fan-out, so the caller gets a `500` rather than
68
+ an answer that hides a lost message; opt out per branch with a `request` whose `forward_to`
69
+ does not reply, or a `dlq` on a plain branch.
70
+
71
+ - **`pass_through_status` on the `http` endpoint.** On a **publisher**, a non-2xx response
72
+ becomes response data instead of a publisher error, so a route can branch on
73
+ `http_status_code` with a `switch` rather than treating every 404 as a failed send. Transport
74
+ failures and unreadable responses are still errors. On a **consumer**, once every output sink
75
+ opts in, a transient sink failure answers the request with `502` instead of stopping and
76
+ reconnecting a non-streaming request/reply route. Composite outputs such as `fanout` need the
77
+ flag on every leaf sink — a mixed output keeps the normal stop-and-reconnect policy — and
78
+ streamable HTTP inputs keep their protocol-specific error frames.
79
+
80
+ ### Changed
81
+
82
+ - **A middleware that empties a batch no longer acknowledges ahead of the route.** `filter` and
83
+ `deduplication` can filter a batch down to nothing, and acking that straight away jumps ahead
84
+ of batches the route is still writing on a source with cumulative acks — the ordered sequencer
85
+ only sees the commits `receive_batch` returns, so a crash in that window lost them. On those
86
+ sources the emptied batch's commit is now held and runs from inside the next retained batch's
87
+ commit, which the sequencer does order.
88
+
89
+ - **An `object_store` sink on `name_by: auto` switches to `write_time` names when a row-dropping
90
+ middleware is on the route.** A source-range name covers one *contiguous* run of positions, so
91
+ a batch with holes punched in it would be written as one object per surviving run — a filter
92
+ keeping 80% of rows turns one upload into roughly a hundred. The route logs one line at
93
+ startup saying it made the switch. This covers `filter`, `deduplication`, `weak_join`,
94
+ `transform` with `on_error: reject`, and a `switch` in `when` mode with no `default`. Set
95
+ `name_by: source_position` explicitly to keep replay-safe names and accept the fragmentation.
96
+
97
+ - **`filter` expressions and a `switch`'s `when` cases are compiled when the config is loaded.**
98
+ An invalid expression is now a startup error naming the expression, instead of a per-message
99
+ failure that only surfaces once traffic arrives.
100
+
101
+ ### Fixed
102
+
103
+ - **`switch` forwards connect and disconnect hooks to its destinations**, `cases` and `default`
104
+ alike, and one failing teardown no longer skips the rest.
105
+
106
+ - **A failed `request` whose `forward_to` answers the caller surfaces the error** instead of
107
+ echoing the original message back as a success. The route then nacks (HTTP `500`) and a
108
+ `retry` or `dlq` on the endpoint sees the failure as usual. Forwarding to a plain sink still
109
+ acks, so routing a status with a `switch` is unchanged.
110
+
111
+ - **Concurrent MongoDB consumers no longer claim documents out from under each other.** Two
112
+ consumers polling within the same second write the same `locked_until`, which is all the claim
113
+ query had to work with, so one poll could read back documents another had just taken. Each
114
+ claim now carries a unique `claim_token`, and the read-back, unlock, ack and delete are all
115
+ scoped to it — an expired lease that was re-claimed elsewhere is no longer deleted from under
116
+ its new owner.
117
+
118
+ ## 0.4.5
119
+
120
+ No library changes: CI workflow timeouts and a version bump.
121
+
5
122
  ## 0.4.4
6
123
 
7
124
  ### Breaking
@@ -3439,7 +3439,7 @@ dependencies = [
3439
3439
 
3440
3440
  [[package]]
3441
3441
  name = "mq-bridge"
3442
- version = "0.4.6"
3442
+ version = "0.4.7"
3443
3443
  dependencies = [
3444
3444
  "aes-gcm",
3445
3445
  "anyhow",
@@ -3524,7 +3524,7 @@ dependencies = [
3524
3524
 
3525
3525
  [[package]]
3526
3526
  name = "mq-bridge-bindings-common"
3527
- version = "0.4.6"
3527
+ version = "0.4.7"
3528
3528
  dependencies = [
3529
3529
  "anyhow",
3530
3530
  "fast-uuid-v7",
@@ -3538,7 +3538,7 @@ dependencies = [
3538
3538
 
3539
3539
  [[package]]
3540
3540
  name = "mq-bridge-node"
3541
- version = "0.4.6"
3541
+ version = "0.4.7"
3542
3542
  dependencies = [
3543
3543
  "anyhow",
3544
3544
  "async-trait",
@@ -3580,7 +3580,7 @@ dependencies = [
3580
3580
 
3581
3581
  [[package]]
3582
3582
  name = "mq-bridge-py"
3583
- version = "0.4.6"
3583
+ version = "0.4.7"
3584
3584
  dependencies = [
3585
3585
  "anyhow",
3586
3586
  "async-trait",
@@ -16,7 +16,7 @@ members = ["bindings-common", "python/mq-bridge-py"]
16
16
  resolver = "2"
17
17
 
18
18
  [workspace.package]
19
- version = "0.4.6"
19
+ version = "0.4.7"
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.4.6
3
+ Version: 0.4.7
4
4
  Classifier: Development Status :: 3 - Alpha
5
5
  Classifier: License :: OSI Approved :: MIT License
6
6
  Classifier: Programming Language :: Python :: 3
@@ -99,11 +99,16 @@ impl CustomEndpointFactory for PluginEndpointFactory {
99
99
  if status == MQB_OK {
100
100
  Ok(AssertSend(out))
101
101
  } else {
102
- Err(anyhow!(
102
+ // Classify rather than flattening to a string: a plugin that
103
+ // rejected its config reports MQB_ERR_INVALID_CONFIG, and the
104
+ // route only breaks out of its reconnect loop for the error
105
+ // classes it can downcast back out of this `anyhow`.
106
+ let cause = anyhow!(
103
107
  "endpoint plugin `{}` could not open an input for route `{route}`: {}",
104
108
  plugin.name(),
105
109
  plugin.take_error(err)
106
- ))
110
+ );
111
+ Err(anyhow::Error::new(consumer_error_from(status, cause)))
107
112
  }
108
113
  })
109
114
  .await
@@ -146,11 +151,13 @@ impl CustomEndpointFactory for PluginEndpointFactory {
146
151
  if status == MQB_OK {
147
152
  Ok(AssertSend(out))
148
153
  } else {
149
- Err(anyhow!(
154
+ // Classified for the same reason as the consumer side above.
155
+ let cause = anyhow!(
150
156
  "endpoint plugin `{}` could not open an output for route `{route}`: {}",
151
157
  plugin.name(),
152
158
  plugin.take_error(err)
153
- ))
159
+ );
160
+ Err(anyhow::Error::new(publisher_error_from(status, cause)))
154
161
  }
155
162
  })
156
163
  .await
@@ -513,7 +520,12 @@ fn publisher_error(
513
520
  err: MqbBuffer,
514
521
  action: &str,
515
522
  ) -> PublisherError {
516
- let cause = plugin_cause(plugin, err, action);
523
+ publisher_error_from(status, plugin_cause(plugin, err, action))
524
+ }
525
+
526
+ /// The status-to-class half of [`publisher_error`], for callers that already
527
+ /// turned the plugin's error buffer into a message.
528
+ pub(super) fn publisher_error_from(status: MqbStatus, cause: anyhow::Error) -> PublisherError {
517
529
  match status {
518
530
  MQB_ERR_RETRYABLE => PublisherError::Retryable(cause),
519
531
  MQB_ERR_CONNECTION => PublisherError::Connection(cause),
@@ -545,4 +557,33 @@ mod tests {
545
557
  MQB_DISPOSITION_ACK
546
558
  );
547
559
  }
560
+
561
+ // A plugin that rejected its config must not look like a transient fault:
562
+ // `create_consumer`/`create_publisher` classify with these, and the route
563
+ // only stops reconnecting for the permanent classes.
564
+ #[test]
565
+ fn a_rejected_plugin_config_is_permanent_not_retryable() {
566
+ assert!(matches!(
567
+ consumer_error_from(MQB_ERR_INVALID_CONFIG, anyhow!("bad field")),
568
+ ConsumerError::Permanent(_)
569
+ ));
570
+ assert!(matches!(
571
+ publisher_error_from(MQB_ERR_INVALID_CONFIG, anyhow!("bad field")),
572
+ PublisherError::NonRetryable(_)
573
+ ));
574
+ }
575
+
576
+ // The other half of the contract: a connection failure stays retryable, so
577
+ // a broker that is merely down still gets the reconnect loop.
578
+ #[test]
579
+ fn a_plugin_connection_failure_stays_retryable() {
580
+ assert!(matches!(
581
+ consumer_error_from(MQB_ERR_CONNECTION, anyhow!("broker down")),
582
+ ConsumerError::Connection(_)
583
+ ));
584
+ assert!(matches!(
585
+ publisher_error_from(MQB_ERR_CONNECTION, anyhow!("broker down")),
586
+ PublisherError::Connection(_)
587
+ ));
588
+ }
548
589
  }
@@ -101,13 +101,14 @@ impl Drop for OutcomeGuard {
101
101
  RouteOutcome::Failed
102
102
  });
103
103
  // A route that discarded data did not run clean, whatever its outcome.
104
- // Never overwrite a recorded failure cause: that one explains why the
105
- // route stopped, which is the more urgent of the two.
104
+ // Only a `Failed` route's cause is kept over the drop report: it explains
105
+ // why the route stopped. On any other outcome a lingering `error` is a
106
+ // stale reconnect failure the route recovered from, so the drops win.
106
107
  {
107
108
  let drops = recover_read_lock(&self.drops, "route_drop_report");
108
109
  if drops.count > 0 {
109
110
  let mut s = recover_write_lock(&self.status, "route_handle_status");
110
- if s.error.is_none() {
111
+ if s.error.is_none() || !matches!(outcome, RouteOutcome::Failed) {
111
112
  s.error = Some(format!(
112
113
  "dropped {} message(s): sink rejected them permanently and no dlq middleware is configured: {}",
113
114
  drops.count,
@@ -370,6 +371,25 @@ const DRAIN_MAX_RECONNECT_ATTEMPTS: usize = 10;
370
371
  /// never reported healthy on the strength of the connect alone.
371
372
  const STABLE_RUN: std::time::Duration = std::time::Duration::from_secs(30);
372
373
 
374
+ /// Forward a ready signal the run task already emitted but the reconnect loop
375
+ /// never observed.
376
+ ///
377
+ /// The inner task signals ready onto a `bounded(1)` channel *before* its consume
378
+ /// loop, so the signal sits buffered. A drain over a small source can then reach
379
+ /// its terminal within the same scheduling window, leaving `select!` with two
380
+ /// ready branches and free to pick the terminal one at random. Without this, a
381
+ /// route that ran to completion is reported to `run()` as one that never started.
382
+ async fn forward_buffered_ready(
383
+ startup_notified: &mut bool,
384
+ iter_ready_rx: &async_channel::Receiver<()>,
385
+ ready_tx: &Sender<()>,
386
+ ) {
387
+ if !*startup_notified && iter_ready_rx.try_recv().is_ok() {
388
+ let _ = ready_tx.send(()).await;
389
+ *startup_notified = true;
390
+ }
391
+ }
392
+
373
393
  async fn pause_after_empty_batch(delay_ms: u64) {
374
394
  if delay_ms > 0 {
375
395
  tokio::time::sleep(std::time::Duration::from_millis(delay_ms)).await;
@@ -1094,6 +1114,7 @@ impl Route {
1094
1114
  let _ = internal_shutdown_tx.send(()).await;
1095
1115
  // Wait for the inner task to finish gracefully.
1096
1116
  let _ = run_task.await;
1117
+ forward_buffered_ready(&mut startup_notified, &iter_ready_rx, &ready_tx).await;
1097
1118
  outcome_guard.set(RouteOutcome::Stopped);
1098
1119
  break 'reconnect;
1099
1120
  }
@@ -1126,6 +1147,7 @@ impl Route {
1126
1147
  // Keep waiting for the run result / shutdown.
1127
1148
  }
1128
1149
  res = &mut run_task => {
1150
+ forward_buffered_ready(&mut startup_notified, &iter_ready_rx, &ready_tx).await;
1129
1151
  match res {
1130
1152
  Ok(Ok(should_continue)) if !should_continue => {
1131
1153
  info!("Route '{}' completed gracefully. Shutting down.", name);
@@ -1226,32 +1248,49 @@ impl Route {
1226
1248
  }
1227
1249
  });
1228
1250
 
1229
- match tokio::time::timeout(startup_timeout, ready_rx.recv()).await {
1230
- Ok(Ok(_)) => Ok(RouteHandle {
1251
+ let started = tokio::time::timeout(startup_timeout, ready_rx.recv()).await;
1252
+ if let Ok(Ok(_)) = started {
1253
+ return Ok(RouteHandle {
1231
1254
  handle,
1232
1255
  shutdown_tx,
1233
1256
  status,
1234
1257
  outcome,
1235
- }),
1236
- _ => {
1237
- handle.abort();
1238
- // The startup failure itself stays inside the reconnect loop, so
1239
- // surface the cause it recorded rather than a bare timeout.
1240
- // "connecting" is the initial marker, not a recorded failure.
1241
- let cause = recover_read_lock(&status, "route_handle_status")
1242
- .error
1243
- .clone()
1244
- .filter(|e| e != "connecting")
1245
- .map(|e| format!(": {e}"))
1246
- .unwrap_or_default();
1247
- Err(anyhow::anyhow!(
1248
- "Route '{}' failed to start within {}ms or encountered an error{}",
1258
+ });
1259
+ }
1260
+ handle.abort();
1261
+ // The startup failure itself stays inside the reconnect loop, so
1262
+ // surface the cause it recorded rather than a bare timeout.
1263
+ // "connecting" is the initial marker, not a recorded failure.
1264
+ let cause = recover_read_lock(&status, "route_handle_status")
1265
+ .error
1266
+ .clone()
1267
+ .filter(|e| e != "connecting")
1268
+ .map(|e| format!(": {e}"))
1269
+ .unwrap_or_default();
1270
+ Err(match started {
1271
+ // Every `ready_tx` was dropped: the route task ended without ever
1272
+ // signalling ready. That returns immediately, so calling it a timeout
1273
+ // sends the reader after a stall that never happened.
1274
+ Ok(Err(_)) => {
1275
+ let terminal = *recover_read_lock(&outcome, "route_handle_outcome");
1276
+ anyhow::anyhow!(
1277
+ "Route '{}' failed to start: ended before it signalled ready (outcome: {}){}",
1249
1278
  name_str,
1250
- startup_timeout.as_millis(),
1279
+ terminal.map_or("unknown", |o| match o {
1280
+ RouteOutcome::Completed => "completed",
1281
+ RouteOutcome::Stopped => "stopped",
1282
+ RouteOutcome::Failed => "failed",
1283
+ }),
1251
1284
  cause
1252
- ))
1285
+ )
1253
1286
  }
1254
- }
1287
+ _ => anyhow::anyhow!(
1288
+ "Route '{}' failed to start: did not become ready within {}ms{}",
1289
+ name_str,
1290
+ startup_timeout.as_millis(),
1291
+ cause
1292
+ ),
1293
+ })
1255
1294
  }
1256
1295
 
1257
1296
  /// The core logic of running the route, designed to be called within a reconnect loop.
@@ -3556,6 +3595,44 @@ mod tests {
3556
3595
  assert_eq!(state.publisher_disconnects.load(Ordering::SeqCst), 1);
3557
3596
  }
3558
3597
 
3598
+ #[tokio::test]
3599
+ async fn test_forward_buffered_ready_delivers_a_signal_the_loop_never_observed() {
3600
+ // The terminal arms of the reconnect loop can win the `select!` race against a
3601
+ // ready signal the run task already buffered. Forwarding it from there is what
3602
+ // keeps a route that ran to completion from being reported as one that never
3603
+ // started.
3604
+ let (iter_ready_tx, iter_ready_rx) = bounded::<()>(1);
3605
+ let (ready_tx, ready_rx) = bounded::<()>(1);
3606
+ iter_ready_tx.send(()).await.unwrap();
3607
+
3608
+ let mut startup_notified = false;
3609
+ forward_buffered_ready(&mut startup_notified, &iter_ready_rx, &ready_tx).await;
3610
+ assert!(startup_notified);
3611
+ assert!(
3612
+ ready_rx.try_recv().is_ok(),
3613
+ "buffered ready must reach `run()`"
3614
+ );
3615
+
3616
+ // `run()` consumes the startup channel once, so a second send would sit unread.
3617
+ iter_ready_tx.send(()).await.unwrap();
3618
+ forward_buffered_ready(&mut startup_notified, &iter_ready_rx, &ready_tx).await;
3619
+ assert!(
3620
+ ready_rx.try_recv().is_err(),
3621
+ "startup must not be notified twice"
3622
+ );
3623
+ }
3624
+
3625
+ #[tokio::test]
3626
+ async fn test_forward_buffered_ready_keeps_a_never_ready_route_a_failure() {
3627
+ let (_iter_ready_tx, iter_ready_rx) = bounded::<()>(1);
3628
+ let (ready_tx, ready_rx) = bounded::<()>(1);
3629
+
3630
+ let mut startup_notified = false;
3631
+ forward_buffered_ready(&mut startup_notified, &iter_ready_rx, &ready_tx).await;
3632
+ assert!(!startup_notified);
3633
+ assert!(ready_rx.try_recv().is_err());
3634
+ }
3635
+
3559
3636
  #[tokio::test]
3560
3637
  async fn test_start_fails_on_unavailable_endpoint() {
3561
3638
  // tokio::time::pause();
@@ -3582,8 +3659,54 @@ mod tests {
3582
3659
  // The route should fail to start because the input endpoint fails to create.
3583
3660
  // The run() method waits for a ready signal which never comes.
3584
3661
  let result = route.run("test_start_fail").await;
3585
- assert!(result.is_err());
3586
- assert!(result.unwrap_err().to_string().contains("failed to start"));
3662
+ let err = result.expect_err("must fail").to_string();
3663
+ // A stalled connect is a timeout, not a route that ended: the two now read
3664
+ // differently, and the recorded cause rides along.
3665
+ assert!(
3666
+ err.contains("failed to start: did not become ready within 5000ms")
3667
+ && err.contains("Endpoint unavailable"),
3668
+ "got: {err}"
3669
+ );
3670
+ }
3671
+
3672
+ #[tokio::test]
3673
+ async fn test_start_failure_distinguishes_an_ended_route_from_a_timeout() {
3674
+ // A drain gives up after DRAIN_MAX_RECONNECT_ATTEMPTS, so the route task ends
3675
+ // long before the startup timeout. `ready_rx` then fails immediately, and
3676
+ // calling that a 5000ms timeout sends the reader after a stall that never
3677
+ // happened.
3678
+ let factory_name = format!("ended_before_ready_{}", fast_uuid_v7::gen_id());
3679
+ register_endpoint_factory(
3680
+ &factory_name,
3681
+ Arc::new(MockEndpointFactory {
3682
+ create_consumer_fail: true,
3683
+ ..MockEndpointFactory::new()
3684
+ }),
3685
+ )
3686
+ .unwrap();
3687
+
3688
+ let input = Endpoint {
3689
+ endpoint_type: EndpointType::Custom {
3690
+ name: factory_name,
3691
+ config: serde_json::Value::Null,
3692
+ },
3693
+ middlewares: vec![],
3694
+ handler: None,
3695
+ };
3696
+ let route = Route::new(input, Endpoint::new_memory("out", 10))
3697
+ .with_exit_on_empty(true)
3698
+ .with_reconnect_interval_ms(1);
3699
+
3700
+ let err = route
3701
+ .run("test_ended_before_ready")
3702
+ .await
3703
+ .expect_err("must fail")
3704
+ .to_string();
3705
+ assert!(
3706
+ err.contains("failed to start: ended before it signalled ready")
3707
+ && err.contains("outcome: failed"),
3708
+ "got: {err}"
3709
+ );
3587
3710
  }
3588
3711
 
3589
3712
  #[tokio::test]