mq-bridge-py-basic 0.4.1__tar.gz → 0.4.3__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 (290) hide show
  1. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/Cargo.lock +6 -6
  2. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/Cargo.toml +2 -2
  3. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/PKG-INFO +1 -1
  4. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/docs/ARCHITECTURE.md +13 -0
  5. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/docs/DELIVERY.md +62 -10
  6. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/mq-bridge.schema.json +12 -2
  7. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/mq_bridge/config.pyi +2 -0
  8. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/command_handler.rs +4 -0
  9. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/endpoints/file/mod.rs +78 -5
  10. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/endpoints/file/tests.rs +257 -6
  11. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/endpoints/http/mod.rs +22 -3
  12. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/endpoints/http/tests.rs +351 -0
  13. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/endpoints/mod.rs +127 -15
  14. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/endpoints/mongodb/readers.rs +85 -18
  15. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/endpoints/mongodb/tests.rs +46 -6
  16. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/endpoints/object_store.rs +115 -11
  17. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/endpoints/sqlx/mod.rs +88 -3
  18. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/endpoints/sqlx/tests.rs +229 -0
  19. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/endpoints/structural/fanout.rs +5 -0
  20. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/endpoints/structural/request.rs +4 -0
  21. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/endpoints/structural/switch.rs +8 -0
  22. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/middleware/buffer.rs +4 -0
  23. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/middleware/compression.rs +4 -0
  24. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/middleware/cookie_jar.rs +4 -0
  25. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/middleware/delay.rs +4 -0
  26. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/middleware/dlq.rs +8 -0
  27. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/middleware/encryption.rs +4 -0
  28. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/middleware/limiter.rs +4 -0
  29. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/middleware/metrics.rs +4 -0
  30. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/middleware/random_panic.rs +4 -0
  31. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/middleware/retry.rs +4 -0
  32. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/middleware/transform/mod.rs +4 -0
  33. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/models/builders.rs +1 -0
  34. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/models.rs +10 -2
  35. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/plugin/middleware.rs +4 -0
  36. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/route.rs +237 -9
  37. mq_bridge_py_basic-0.4.3/src/support/source_ranges.rs +801 -0
  38. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/traits.rs +38 -0
  39. mq_bridge_py_basic-0.4.3/tests/integration/docker-compose/mosquitto_persistence.conf +20 -0
  40. mq_bridge_py_basic-0.4.1/src/support/source_ranges.rs +0 -448
  41. mq_bridge_py_basic-0.4.1/tests/integration/docker-compose/mosquitto_persistence.conf +0 -18
  42. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  43. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  44. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  45. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/.github/dependabot.yml +0 -0
  46. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/.github/workflows/benchmark.yml +0 -0
  47. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/.github/workflows/ci.yml +0 -0
  48. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/.github/workflows/ibm-mq.yml +0 -0
  49. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/.github/workflows/node.yml +0 -0
  50. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/.github/workflows/publish-node.yml +0 -0
  51. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/.github/workflows/publish-python.yml +0 -0
  52. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/.github/workflows/python.yml +0 -0
  53. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/.github/workflows/release.yml +0 -0
  54. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/.github/workflows/test-matrix.yml +0 -0
  55. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/.gitignore +0 -0
  56. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/CHANGELOG.md +0 -0
  57. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/CLAUDE.md +0 -0
  58. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/CONTRIBUTING.md +0 -0
  59. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/LICENSE +0 -0
  60. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/LICENSE.python +0 -0
  61. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/README.md +0 -0
  62. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/SECURITY.md +0 -0
  63. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/benches/ETL_BENCHMARKS.md +0 -0
  64. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/benches/performance_bench.rs +0 -0
  65. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/benches/router_bench.rs +0 -0
  66. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/benches/zeromq_backends.rs +0 -0
  67. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/bindings-common/Cargo.toml +0 -0
  68. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/bindings-common/src/lib.rs +0 -0
  69. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/bindings-common/src/logging.rs +0 -0
  70. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/build.rs +0 -0
  71. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/deny.toml +0 -0
  72. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/docs/CONFIGURATION.md +0 -0
  73. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/docs/EXTENDING.md +0 -0
  74. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/docs/PLUGINS.md +0 -0
  75. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/docs/REFERENCE.md +0 -0
  76. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/examples/ipc_worker_queue.yaml +0 -0
  77. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/mq_bridge/__init__.py +0 -0
  78. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/mq_bridge/__init__.pyi +0 -0
  79. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/mq_bridge/config.py +0 -0
  80. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/mq_bridge/plugin_packaging.py +0 -0
  81. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/mq_bridge/py.typed +0 -0
  82. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/package-lock.json +0 -0
  83. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/pyproject.toml +0 -0
  84. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/python/mq-bridge-py/Cargo.toml +0 -0
  85. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/python/mq-bridge-py/LICENSE.python +0 -0
  86. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/python/mq-bridge-py/README.md +0 -0
  87. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/python/mq-bridge-py/analysis/bench_http_native.py +0 -0
  88. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/python/mq-bridge-py/example.py +0 -0
  89. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/python/mq-bridge-py/examples/bench_memory.py +0 -0
  90. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/python/mq-bridge-py/examples/bench_memory.yaml +0 -0
  91. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/python/mq-bridge-py/examples/consume_pull.py +0 -0
  92. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/python/mq-bridge-py/examples/dlt_source.py +0 -0
  93. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/python/mq-bridge-py/examples/json_route.py +0 -0
  94. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/python/mq-bridge-py/examples/memory.yaml +0 -0
  95. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/python/mq-bridge-py/examples/raw_route.py +0 -0
  96. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/python/mq-bridge-py/pyproject-basic.toml +0 -0
  97. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/python/mq-bridge-py/pyproject-reduced.toml +0 -0
  98. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/python/mq-bridge-py/scripts/gen_config_types.py +0 -0
  99. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/python/mq-bridge-py/src/lib.rs +0 -0
  100. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/python/mq-bridge-py/tests/test_config_types.py +0 -0
  101. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/python/mq-bridge-py/tests/test_custom_endpoint.py +0 -0
  102. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/python/mq-bridge-py/tests/test_logging.py +0 -0
  103. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/python/mq-bridge-py/tests/test_packaging.py +0 -0
  104. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/python/mq-bridge-py/tests/test_performance_smoke.py +0 -0
  105. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/python/mq-bridge-py/tests/test_plugin_package.py +0 -0
  106. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/python/mq-bridge-py/tests/test_public_api.py +0 -0
  107. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/python/mq-bridge-py/tests/test_routes.py +0 -0
  108. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/python/mq-bridge-py/uv.lock +0 -0
  109. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/scripts/analysis/benches/metadata_bench.rs +0 -0
  110. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/scripts/analysis/debug_ibm_mq_tls.rs +0 -0
  111. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/scripts/analysis/http/analyze_xctrace_time_profile.py +0 -0
  112. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/scripts/analysis/http/http_flamegraph.py +0 -0
  113. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/scripts/analysis/http/mq_bridge_http_profile.rs +0 -0
  114. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/scripts/analysis/http/run_http_ladder.sh +0 -0
  115. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/scripts/ci/format_bench_output.sh +0 -0
  116. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/scripts/httparena/README.md +0 -0
  117. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/scripts/httparena/frameworks/mq-bridge-py/Dockerfile +0 -0
  118. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/scripts/httparena/frameworks/mq-bridge-py/meta.json +0 -0
  119. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/scripts/httparena/frameworks/mq-bridge-py/server.py +0 -0
  120. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/scripts/techempower/Python/mq-bridge-py/README.md +0 -0
  121. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/scripts/techempower/Python/mq-bridge-py/benchmark_config.json +0 -0
  122. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/scripts/techempower/Python/mq-bridge-py/mq-bridge-py.dockerfile +0 -0
  123. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/scripts/techempower/Python/mq-bridge-py/server.py +0 -0
  124. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/scripts/techempower/README.md +0 -0
  125. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/scripts/techempower/postgres.yml +0 -0
  126. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/scripts/techempower/seed.sql +0 -0
  127. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/scripts/techempower/verify.sh +0 -0
  128. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/scripts/the-benchmarker/README.md +0 -0
  129. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/scripts/the-benchmarker/python/mq-bridge-py/config.yaml +0 -0
  130. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/scripts/the-benchmarker/python/mq-bridge-py/pyproject.toml +0 -0
  131. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/scripts/the-benchmarker/python/mq-bridge-py/server.py +0 -0
  132. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/canonical_message.rs +0 -0
  133. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/checkpoint.rs +0 -0
  134. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/endpoints/amqp.rs +0 -0
  135. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/endpoints/aws.rs +0 -0
  136. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/endpoints/clickhouse.rs +0 -0
  137. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/endpoints/grpc.proto +0 -0
  138. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/endpoints/grpc.rs +0 -0
  139. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/endpoints/http/publisher.rs +0 -0
  140. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/endpoints/http/stream.rs +0 -0
  141. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/endpoints/ibm_mq.rs +0 -0
  142. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/endpoints/kafka.rs +0 -0
  143. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/endpoints/memory/endpoint.rs +0 -0
  144. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/endpoints/memory/framed.rs +0 -0
  145. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/endpoints/memory/ipc_unix.rs +0 -0
  146. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/endpoints/memory/ipc_windows.rs +0 -0
  147. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/endpoints/memory/memory_transport.rs +0 -0
  148. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/endpoints/memory/mod.rs +0 -0
  149. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/endpoints/memory/transport.rs +0 -0
  150. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/endpoints/mongodb/consumer.rs +0 -0
  151. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/endpoints/mongodb/dedup.rs +0 -0
  152. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/endpoints/mongodb/mod.rs +0 -0
  153. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/endpoints/mongodb/publisher.rs +0 -0
  154. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/endpoints/mqtt.rs +0 -0
  155. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/endpoints/nats.rs +0 -0
  156. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/endpoints/poll.rs +0 -0
  157. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/endpoints/postgres/VENDORED.md +0 -0
  158. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/endpoints/postgres/mod.rs +0 -0
  159. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/endpoints/postgres/pgoutput/decoder.rs +0 -0
  160. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/endpoints/postgres/pgoutput/messages.rs +0 -0
  161. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/endpoints/postgres/pgoutput/mod.rs +0 -0
  162. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/endpoints/postgres/pgoutput/registry.rs +0 -0
  163. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/endpoints/postgres/pgoutput/values.rs +0 -0
  164. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/endpoints/postgres/replication.rs +0 -0
  165. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/endpoints/postgres/state.rs +0 -0
  166. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/endpoints/redis_streams.rs +0 -0
  167. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/endpoints/sled.rs +0 -0
  168. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/endpoints/sqlx/dedup.rs +0 -0
  169. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/endpoints/structural/mod.rs +0 -0
  170. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/endpoints/structural/null.rs +0 -0
  171. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/endpoints/structural/reader.rs +0 -0
  172. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/endpoints/structural/response.rs +0 -0
  173. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/endpoints/structural/static_endpoint.rs +0 -0
  174. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/endpoints/structural/stream_buffer.rs +0 -0
  175. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/endpoints/websocket.rs +0 -0
  176. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/endpoints/zeromq/codec.rs +0 -0
  177. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/endpoints/zeromq/mod.rs +0 -0
  178. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/endpoints/zeromq/omq.rs +0 -0
  179. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/endpoints/zeromq/zmq.rs +0 -0
  180. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/errors.rs +0 -0
  181. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/event_handler.rs +0 -0
  182. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/event_store.rs +0 -0
  183. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/extensions.rs +0 -0
  184. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/lib.rs +0 -0
  185. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/middleware/deduplication.rs +0 -0
  186. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/middleware/id.rs +0 -0
  187. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/middleware/mod.rs +0 -0
  188. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/middleware/transform/coerce.rs +0 -0
  189. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/middleware/transform/compiled.rs +0 -0
  190. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/middleware/transform/error.rs +0 -0
  191. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/middleware/transform/path.rs +0 -0
  192. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/middleware/transform/schema.rs +0 -0
  193. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/middleware/transform/tests.rs +0 -0
  194. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/middleware/weak_join.rs +0 -0
  195. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/models/defaults.rs +0 -0
  196. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/models/secrets.rs +0 -0
  197. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/models/serde_support.rs +0 -0
  198. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/models/tests.rs +0 -0
  199. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/outcomes.rs +0 -0
  200. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/plugin/conformance.rs +0 -0
  201. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/plugin/endpoint.rs +0 -0
  202. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/plugin/message.rs +0 -0
  203. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/plugin/mod.rs +0 -0
  204. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/plugin/sdk.rs +0 -0
  205. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/plugin/test_support.rs +0 -0
  206. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/publisher.rs +0 -0
  207. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/response.rs +0 -0
  208. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/support/base64_engine.rs +0 -0
  209. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/support/compression.rs +0 -0
  210. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/support/connection_registry.rs +0 -0
  211. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/support/crypto.rs +0 -0
  212. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/support/crypto_envelope.rs +0 -0
  213. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/support/interpolation.rs +0 -0
  214. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/support/mod.rs +0 -0
  215. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/support/plugin_abi.rs +0 -0
  216. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/test_utils.rs +0 -0
  217. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/src/type_handler.rs +0 -0
  218. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/README.md +0 -0
  219. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/armature_integration.rs +0 -0
  220. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/integration/amqp.rs +0 -0
  221. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/integration/aws.rs +0 -0
  222. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/integration/clickhouse.rs +0 -0
  223. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/integration/config.toml +0 -0
  224. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/integration/docker-compose/amqp.yml +0 -0
  225. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/integration/docker-compose/aws.yml +0 -0
  226. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/integration/docker-compose/clickhouse.yml +0 -0
  227. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/integration/docker-compose/ibm-mq-certs/client.crl +0 -0
  228. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/integration/docker-compose/ibm-mq-certs/client.kdb +0 -0
  229. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/integration/docker-compose/ibm-mq-certs/client.p12 +0 -0
  230. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/integration/docker-compose/ibm-mq-certs/client.rdb +0 -0
  231. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/integration/docker-compose/ibm-mq-certs/client.sth +0 -0
  232. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/integration/docker-compose/ibm_mq.yml +0 -0
  233. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/integration/docker-compose/ibm_mq_tls.yml +0 -0
  234. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/integration/docker-compose/kafka-tls.yml +0 -0
  235. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/integration/docker-compose/kafka.yml +0 -0
  236. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/integration/docker-compose/mariadb.yml +0 -0
  237. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/integration/docker-compose/mongodb-replica.yml +0 -0
  238. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/integration/docker-compose/mongodb-tls.yml +0 -0
  239. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/integration/docker-compose/mongodb.yml +0 -0
  240. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/integration/docker-compose/mosquitto.conf +0 -0
  241. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/integration/docker-compose/mosquitto_performance.conf +0 -0
  242. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/integration/docker-compose/mq_init.mqsc +0 -0
  243. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/integration/docker-compose/mq_init_tls.mqsc +0 -0
  244. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/integration/docker-compose/mqtt.yml +0 -0
  245. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/integration/docker-compose/mqtt_performance.yml +0 -0
  246. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/integration/docker-compose/mysql.yml +0 -0
  247. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/integration/docker-compose/nats.yml +0 -0
  248. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/integration/docker-compose/object_store.yml +0 -0
  249. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/integration/docker-compose/postgres.yml +0 -0
  250. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/integration/docker-compose/postgres_cdc.yml +0 -0
  251. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/integration/docker-compose/redis.yml +0 -0
  252. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/integration/file.rs +0 -0
  253. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/integration/grpc.rs +0 -0
  254. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/integration/grpc_tls.rs +0 -0
  255. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/integration/http.rs +0 -0
  256. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/integration/http_tls.rs +0 -0
  257. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/integration/ibm_mq.rs +0 -0
  258. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/integration/ibm_mq_tls.rs +0 -0
  259. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/integration/ipc.rs +0 -0
  260. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/integration/kafka.rs +0 -0
  261. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/integration/kafka_tls.rs +0 -0
  262. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/integration/logic_test.rs +0 -0
  263. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/integration/mariadb.rs +0 -0
  264. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/integration/memory.rs +0 -0
  265. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/integration/mod.rs +0 -0
  266. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/integration/mongodb.rs +0 -0
  267. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/integration/mongodb_raw.rs +0 -0
  268. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/integration/mongodb_tls.rs +0 -0
  269. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/integration/mqtt.rs +0 -0
  270. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/integration/mysql.rs +0 -0
  271. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/integration/nats.rs +0 -0
  272. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/integration/object_store.rs +0 -0
  273. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/integration/performance_static.rs +0 -0
  274. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/integration/postgres.rs +0 -0
  275. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/integration/postgres_cdc.rs +0 -0
  276. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/integration/redis_streams.rs +0 -0
  277. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/integration/route.rs +0 -0
  278. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/integration/scripts/gen_certs.sh +0 -0
  279. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/integration/sqlite.rs +0 -0
  280. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/integration/tls_helpers.rs +0 -0
  281. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/integration/websocket.rs +0 -0
  282. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/integration/zeromq.rs +0 -0
  283. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/integration_test.rs +0 -0
  284. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/memory_leak_test.rs +0 -0
  285. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/ref_test.rs +0 -0
  286. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/reference_docs_test.rs +0 -0
  287. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/sqlite_test.rs +0 -0
  288. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/tls_example.rs +0 -0
  289. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/unit_tests.md +0 -0
  290. {mq_bridge_py_basic-0.4.1 → mq_bridge_py_basic-0.4.3}/tests/websocket_test.rs +0 -0
@@ -1900,9 +1900,9 @@ dependencies = [
1900
1900
 
1901
1901
  [[package]]
1902
1902
  name = "fast-uuid-v7"
1903
- version = "0.1.8"
1903
+ version = "0.1.9"
1904
1904
  source = "registry+https://github.com/rust-lang/crates.io-index"
1905
- checksum = "65d7a30239eba18ae6a60dc3c3df98af18f00bba69d2b1d5f2816a831e83bff3"
1905
+ checksum = "dc759201eb9b09178fb2174087a451d19fd46a8b8b4f5f6103de83b9f5949f52"
1906
1906
  dependencies = [
1907
1907
  "rand 0.10.2",
1908
1908
  ]
@@ -3348,7 +3348,7 @@ dependencies = [
3348
3348
 
3349
3349
  [[package]]
3350
3350
  name = "mq-bridge"
3351
- version = "0.4.1"
3351
+ version = "0.4.3"
3352
3352
  dependencies = [
3353
3353
  "aes-gcm",
3354
3354
  "anyhow",
@@ -3431,7 +3431,7 @@ dependencies = [
3431
3431
 
3432
3432
  [[package]]
3433
3433
  name = "mq-bridge-bindings-common"
3434
- version = "0.4.1"
3434
+ version = "0.4.3"
3435
3435
  dependencies = [
3436
3436
  "anyhow",
3437
3437
  "fast-uuid-v7",
@@ -3445,7 +3445,7 @@ dependencies = [
3445
3445
 
3446
3446
  [[package]]
3447
3447
  name = "mq-bridge-node"
3448
- version = "0.4.1"
3448
+ version = "0.4.3"
3449
3449
  dependencies = [
3450
3450
  "anyhow",
3451
3451
  "async-trait",
@@ -3487,7 +3487,7 @@ dependencies = [
3487
3487
 
3488
3488
  [[package]]
3489
3489
  name = "mq-bridge-py"
3490
- version = "0.4.1"
3490
+ version = "0.4.3"
3491
3491
  dependencies = [
3492
3492
  "anyhow",
3493
3493
  "async-trait",
@@ -16,13 +16,13 @@ members = ["bindings-common", "python/mq-bridge-py"]
16
16
  resolver = "2"
17
17
 
18
18
  [workspace.package]
19
- version = "0.4.1"
19
+ version = "0.4.3"
20
20
 
21
21
  [workspace.dependencies]
22
22
  anyhow = "1.0"
23
23
  async-trait = "0.1"
24
24
  bytes = "1"
25
- fast-uuid-v7 = "0.1.8"
25
+ fast-uuid-v7 = "0.1.9"
26
26
  serde = "1.0"
27
27
  serde_json = { version = "1.0", features = ["raw_value"] }
28
28
  serde_yaml_ng = "0.10"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mq-bridge-py-basic
3
- Version: 0.4.1
3
+ Version: 0.4.3
4
4
  Classifier: Development Status :: 3 - Alpha
5
5
  Classifier: License :: OSI Approved :: MIT License
6
6
  Classifier: Programming Language :: Python :: 3
@@ -150,6 +150,19 @@ Batch processing is a core concept in mq-bridge and is required for all endpoint
150
150
  - Each route can be configured with a `concurrency` parameter, which determines how many worker tasks will process batches in parallel.
151
151
  - Batch size is also configurable per route.
152
152
 
153
+ #### Ordering
154
+ Two independent guarantees, each decided once per route by the endpoint itself:
155
+
156
+ - **Commits** follow `MessageConsumer::commit_requires_order()` (default `true`). Cumulative-ack sources such as Kafka funnel their commits through one sequencer; individually-acking sources commit concurrently, bounded by `commit_concurrency_limit`.
157
+ - **Publishing** follows `MessagePublisher::requires_ordered_publish()` (default `false`). Above `concurrency: 1` workers call `send_batch` in parallel, so whole batches can reach the sink out of source order — rows keep their order *within* a batch. The `file` sink declares `true` and the route sequences the `send_batch` calls; batch prep and commits stay parallel, so the ordered path measures within noise of an unordered one.
158
+
159
+ Only `file` declares it, for two reasons that any other candidate has to clear:
160
+
161
+ - **The cost must be low.** The file sink already holds a write lock across the whole batch, so sequencing changes who writes, not how many write at once. Sequencing a sink that ends in a network round trip instead costs the whole concurrency factor — one batch in flight instead of N.
162
+ - **The guarantee must be reachable.** `object_store` does *not* declare it: read order there is object-key order, and the uuidv7 key randomises everything below the millisecond, so ordering the writes would buy nothing. Ordered cloud export needs a source-sequenced key first.
163
+
164
+ Broker sinks keep it off. NATS (both Core and JetStream) publishes a batch through `send_batch_helper`, which keeps up to `SEND_BATCH_CONCURRENCY` publishes in flight, so wire order inside one batch is already unordered — only the results are re-sorted. Kafka and AMQP preserve order within a batch (they submit the whole batch, then await confirms), but for them the order-critical part is the cheap submit loop, not the round trip, so a useful fix would have to release the sequence after submit rather than after `send_batch`. For per-key Kafka ordering today, use `concurrency: 1`.
165
+
153
166
  ### Helper Utilities
154
167
  mq-bridge provides several helper functions to make implementing batching easier, especially if your endpoint only supports single-message operations:
155
168
 
@@ -22,7 +22,7 @@ sink writes data.
22
22
  | MongoDB | `id_field` set to a payload field or replay-stable template such as `${metadata:mqb.id}` |
23
23
  | PostgreSQL / SQLite | `sqlx.insert_query` uses a unique key with `ON CONFLICT` |
24
24
  | MySQL / MariaDB | `sqlx.insert_query` uses a unique key with `ON DUPLICATE KEY` |
25
- | File / object store | `idempotency: true` with a replayable Kafka or Postgres CDC source |
25
+ | File / object store | `idempotency: true` with a replayable Kafka, Postgres CDC, SQL cursor, MongoDB CDC or file source |
26
26
 
27
27
  For a source without stable identity, derive one once and consume it at the sink:
28
28
 
@@ -143,7 +143,7 @@ one identity to every message missing the field, so it is dropped instead.
143
143
  | `mongodb` | Unique `_id` index; dup-key (11000) is treated as an idempotent success | `id_field` |
144
144
  | `sqlx` (PostgreSQL / MySQL / SQLite) | The table's own `UNIQUE`/`PRIMARY KEY` | `ON CONFLICT` / `ON DUPLICATE KEY` in `insert_query` |
145
145
  | `clickhouse` | `ReplacingMergeTree` collapses at merge time | Table DDL — no `mq-bridge` config |
146
- | `file`, `object_store` | Deterministic part names + covered-range recovery | `idempotency: true` (needs a replayable source) |
146
+ | `file`, `object_store` | Deterministic, sortable part names + covered-range recovery | `idempotency: true` (needs a replayable source) |
147
147
  | `kafka` | `enable.idempotence` dedups **producer retries within one session** — this is *not* exactly-once semantics | On by default |
148
148
  | `nats`, `amqp`, `mqtt`, `redis_streams`, `aws`, `ibm_mq`, `zeromq` | None | Deduplicate at the next consumer instead |
149
149
 
@@ -359,6 +359,13 @@ but before the branch's downstream send committed, the replay hits a duplicate k
359
359
 
360
360
  ### Files & object storage — `idempotency`
361
361
 
362
+ > **Check first whether you need any of this.** If your records already carry a business key — an
363
+ > `id` field in the payload, or `mqb.id` from the [`id` middleware](#giving-a-source-an-identity) —
364
+ > then a key-addressed sink (Mongo `id_field`, SQL `ON CONFLICT`) already gives you effective
365
+ > exactly-once, and the order objects happen to land in does not matter. Positional naming below is
366
+ > for the case where the *sink itself* has to recognise a replay, or where a downstream reader
367
+ > depends on replay **order**. Turning it on when you do not need it only adds restrictions.
368
+
362
369
  A filesystem has no unique constraint, so the `file` and `object_store` sinks get replay safety a
363
370
  different way: **deterministic names plus covered-range recovery**. Set `idempotency: true` and the
364
371
  sink stops writing UUID-named objects. Instead it groups each batch into runs of consecutive source
@@ -371,7 +378,7 @@ kafka_to_s3:
371
378
  output:
372
379
  object_store:
373
380
  url: "s3://my-bucket/orders"
374
- idempotency: true # → s3://my-bucket/orders/part-orders-0-0-1023.jsonl
381
+ idempotency: true # → part-orders-<partition>-<start>-<end>.jsonl (zero-padded)
375
382
  ```
376
383
 
377
384
  On startup the sink lists what is already there, parses the ranges out of the part names, and drops
@@ -381,13 +388,58 @@ checkpoint protocol. Local files stage to a temp name, `fsync`, then `rename` (a
381
388
  filesystem); object stores have no atomic rename, so a single PUT under the final name *is* the
382
389
  commit.
383
390
 
384
- This needs a replayable source position, which today means **Kafka** (topic/partition/offset) or
385
- **`postgres_cdc`** (commit LSN plus in-transaction ordinal; `sqlx` with `publication` maps onto the
386
- same consumer). A route with an idempotent output turns `source_metadata` on for its input
387
- automatically; set it explicitly only when you want the `mqb.src.*` keys for something else. Any
388
- other input is rejected when the route starts. NATS, AMQP and MongoDB CDC also accept
389
- `source_metadata` and emit provenance keys, but a subject or routing key is not a replayable offset,
390
- so they cannot drive an idempotent sink.
391
+ This needs a replayable source position. Today that means:
392
+
393
+ | Source | Position | Restriction |
394
+ | --- | --- | --- |
395
+ | `kafka` | topic / partition / offset | — |
396
+ | `postgres_cdc` | commit LSN + in-transaction ordinal | `sqlx` with `publication` maps onto the same consumer |
397
+ | `mongodb` | cluster time + ordinal; the initial snapshot uses its `_id` scan index | `capture_new` / `capture_all` only — `consumer` and `snapshot` have no position |
398
+ | `file` | record index within the file | all modes; only `consume` deduplicates across runs (see below) |
399
+ | `sqlx` | the `cursor_column` value | cursor polling only; the column must be a unique, non-negative integer |
400
+
401
+ A route with an idempotent output turns `source_metadata` on for its input automatically; set it
402
+ explicitly only when you want the `mqb.src.*` keys for something else. Any other input is rejected
403
+ when the route starts. NATS and AMQP also accept `source_metadata` and emit provenance keys, but a
404
+ subject or routing key is not a replayable offset, so they cannot drive an idempotent sink.
405
+
406
+ **Without `idempotency: true`, an `object_store` sink names objects in write order**, and key order
407
+ is the only order a bucket has. At `concurrency: 1` that is source order; above it the name is minted
408
+ inside the worker pool, so it is arrival order, and replaying a change stream through the bucket can
409
+ reorder updates to the same key. The route logs a warning when it starts in that configuration.
410
+
411
+ **Numbers inside a part name are zero-padded** so that ASCII sort equals numeric sort — a bucket is
412
+ listed lexicographically and that listing order *is* the replay order. A bucket written by a version
413
+ before this padding contains unpadded names; the two forms do not sort correctly against each other,
414
+ so do not mix them in one prefix.
415
+
416
+ **MongoDB `capture_all`** reads the collection, then streams changes. Both phases are numbered into
417
+ the key so the snapshot always sorts ahead of the changes; a change can never replay before the
418
+ document it modifies. The snapshot does not resume across runs — it re-scans from the start — but the
419
+ numbering is deterministic, so a restart reproduces the same names and the covered-range recovery
420
+ skips them. The exception is inserts or deletes landing in the collection between the two runs: those
421
+ shift the numbering, and some documents are then written twice.
422
+
423
+ **The `sqlx` cursor source** uses the `cursor_column` value itself as the position — the same shape
424
+ Kafka Connect's S3 sink gives a Kafka offset. That only works for a unique, non-negative integer
425
+ column: a text cursor pages fine but has no contiguous numeric order, and a repeated value would
426
+ resolve two rows to one position and drop one of them. The reader rejects both at read time rather
427
+ than naming records wrongly. Consecutive ids coalesce into one part file; gaps split it.
428
+
429
+ **The `file` source** numbers records by their index in the file, not their byte offset (byte offsets
430
+ are not consecutive, so every record would become its own part).
431
+
432
+ That index repeats across runs only in `consume` mode, which always reads from byte 0 — and repeating
433
+ is the point: a re-read produces the same names, so the sink recognises them as already covered and
434
+ writes nothing. `subscribe` starts at the current end of the file and `group_subscribe` resumes at a
435
+ stored byte offset, so their index restarts at 0 over records an earlier run already numbered. Those
436
+ two modes therefore carry a **run epoch** in the object name.
437
+
438
+ The epoch keeps names distinct and keeps runs in order (a later run reads later records, so its
439
+ objects sort after the earlier run's). What it does not give you is deduplication across a restart:
440
+ records re-read after a crash are written again under new names. That is ordinary at-least-once, and
441
+ it is the honest guarantee for a source with no durable per-record position — these modes are
442
+ allowed, not rejected, because that guarantee is fine for plenty of pipelines.
391
443
 
392
444
  For the `file` sink, `idempotency: true` changes what `path` means: it is the directory that receives
393
445
  the part files, not the file that is appended to. The sink creates it on startup, so pointing it at an
@@ -1012,13 +1012,18 @@
1012
1012
  "default": "normal"
1013
1013
  },
1014
1014
  "idempotency": {
1015
- "description": "Write replay-safe source ranges as immutable part files under this directory.\nRequires Kafka source metadata or postgres_cdc commit LSN plus transaction ordinal metadata.",
1015
+ "description": "Write replay-safe source ranges as immutable part files under this directory.\nRequires a source that stamps a replayable position: kafka, postgres_cdc, mongodb CDC, sqlx or file.",
1016
1016
  "type": "boolean",
1017
1017
  "default": false
1018
1018
  },
1019
1019
  "path": {
1020
1020
  "description": "Path to the file, or to the directory holding the part files when `idempotency` is true.",
1021
1021
  "type": "string"
1022
+ },
1023
+ "source_metadata": {
1024
+ "description": "(Consumer only) Include authoritative `mqb.src.file_*` source positions; only `consume` mode reproduces them across restarts. Defaults to false.",
1025
+ "type": "boolean",
1026
+ "default": false
1022
1027
  }
1023
1028
  },
1024
1029
  "anyOf": [
@@ -2972,7 +2977,7 @@
2972
2977
  "minimum": 1
2973
2978
  },
2974
2979
  "concurrency": {
2975
- "description": "(Optional) Number of concurrent processing tasks for this route. While it improves throughput for high-latency\nhandlers, it adds synchronization overhead for ordered commits and may lead to out-of-order processing\nin the handler. Defaults to 1.",
2980
+ "description": "(Optional) Number of concurrent processing tasks for this route. While it improves throughput for high-latency\nhandlers, it adds synchronization overhead for ordered commits and may lead to out-of-order processing\nin the handler. Above 1, whole batches may also reach the sink out of source order (rows keep their order\nwithin a batch) unless the sink declares itself order-sensitive, as `file` does.\nDefaults to 1.",
2976
2981
  "type": "integer",
2977
2982
  "format": "uint",
2978
2983
  "default": 1,
@@ -3217,6 +3222,11 @@
3217
3222
  "null"
3218
3223
  ]
3219
3224
  },
3225
+ "source_metadata": {
3226
+ "description": "(Consumer only) Include authoritative `mqb.src.sqlx_*` source positions; `cursor_column` must then be a unique integer. Defaults to false.",
3227
+ "type": "boolean",
3228
+ "default": false
3229
+ },
3220
3230
  "table": {
3221
3231
  "description": "The table to interact with.",
3222
3232
  "type": "string"
@@ -154,6 +154,7 @@ class FileConfig(TypedDict, total=False):
154
154
  format: FileFormat
155
155
  idempotency: bool
156
156
  path: Required[str]
157
+ source_metadata: bool
157
158
 
158
159
 
159
160
  class GrpcConfig(TypedDict, total=False):
@@ -476,6 +477,7 @@ class SqlxConfig(TypedDict, total=False):
476
477
  select_query: Optional[str]
477
478
  shared: Optional[bool]
478
479
  slot_name: Optional[str]
480
+ source_metadata: bool
479
481
  table: Required[str]
480
482
  test_before_acquire: Optional[bool]
481
483
  tls: TlsConfig
@@ -165,6 +165,10 @@ impl MessagePublisher for CommandPublisher {
165
165
  self.inner.status().await
166
166
  }
167
167
 
168
+ fn requires_ordered_publish(&self) -> bool {
169
+ self.inner.requires_ordered_publish()
170
+ }
171
+
168
172
  fn as_any(&self) -> &dyn Any {
169
173
  self
170
174
  }
@@ -19,7 +19,7 @@ use std::any::Any;
19
19
  use std::collections::HashMap;
20
20
  use std::io::Seek;
21
21
  use std::path::Path;
22
- use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
22
+ use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering};
23
23
  use std::sync::{Arc, Mutex as StdMutex, Weak};
24
24
  use std::time::{Duration, SystemTime};
25
25
  use tokio::fs::{self, File, OpenOptions};
@@ -1017,6 +1017,12 @@ impl MessagePublisher for FilePublisher {
1017
1017
  Ok(())
1018
1018
  }
1019
1019
 
1020
+ /// A file is an ordered log by construction: appending batches in the order they
1021
+ /// were read is the whole point of exporting to JSONL/CSV.
1022
+ fn requires_ordered_publish(&self) -> bool {
1023
+ true
1024
+ }
1025
+
1020
1026
  fn as_any(&self) -> &dyn Any {
1021
1027
  self
1022
1028
  }
@@ -2007,15 +2013,44 @@ pub struct FileConsumer {
2007
2013
  /// A live tail keeps waiting for the file to appear; a drain (`exit_on_empty`)
2008
2014
  /// cannot, so it fails with this instead of blocking forever.
2009
2015
  startup_open_error: Option<String>,
2016
+ /// Stamp `mqb.src.file_*` so an idempotent sink can name records in source order.
2017
+ source_metadata: bool,
2018
+ /// Index of the next record in this file.
2019
+ next_record: u64,
2020
+ /// Set only for the modes that do not start at byte 0, so their record indexes cannot
2021
+ /// collide with a previous run's. `None` for `consume`, whose names must repeat.
2022
+ run_epoch: Option<u64>,
2010
2023
  exit_on_empty: bool,
2011
2024
  }
2012
2025
 
2026
+ /// Hands out a strictly increasing run epoch. Seeded from wall-clock millis so epochs still
2027
+ /// sort across process restarts, but never repeats or goes backwards within one process —
2028
+ /// consumers built in the same millisecond, or across a clock step back, stay distinct.
2029
+ fn next_run_epoch() -> u64 {
2030
+ static LAST: AtomicU64 = AtomicU64::new(0);
2031
+ let now = SystemTime::now()
2032
+ .duration_since(std::time::UNIX_EPOCH)
2033
+ .map(|d| d.as_millis() as u64)
2034
+ .unwrap_or(0);
2035
+ let mut last = LAST.load(Ordering::Relaxed);
2036
+ loop {
2037
+ let epoch = now.max(last + 1);
2038
+ match LAST.compare_exchange_weak(last, epoch, Ordering::Relaxed, Ordering::Relaxed) {
2039
+ Ok(_) => return epoch,
2040
+ Err(observed) => last = observed,
2041
+ }
2042
+ }
2043
+ }
2044
+
2013
2045
  impl FileConsumer {
2014
2046
  fn wrap(backend: ConsumerBackend) -> Self {
2015
2047
  Self {
2016
2048
  backend,
2017
2049
  path: String::new(),
2018
2050
  startup_open_error: None,
2051
+ source_metadata: false,
2052
+ next_record: 0,
2053
+ run_epoch: None,
2019
2054
  exit_on_empty: false,
2020
2055
  }
2021
2056
  }
@@ -2025,6 +2060,17 @@ impl FileConsumer {
2025
2060
  let mut consumer = Self::new_backend(config).await?;
2026
2061
  consumer.path = config.path.clone();
2027
2062
  consumer.startup_open_error = startup_open_error;
2063
+ consumer.source_metadata = config.source_metadata;
2064
+ // `consume` always reads from byte 0, so its record index is reproducible and two
2065
+ // runs deliberately produce the same names — that is what makes the sink idempotent.
2066
+ // `subscribe` starts at the current end and `group_subscribe` resumes at a stored
2067
+ // byte offset, so their index restarts at 0 over records a previous run already
2068
+ // numbered. Without an epoch those names would collide and the sink would discard
2069
+ // the new records as already-covered; with one they stay distinct and ordered, at
2070
+ // the cost of cross-restart deduplication.
2071
+ consumer.run_epoch = (config.source_metadata
2072
+ && !matches!(&config.mode, None | Some(FileConsumerMode::Consume { .. })))
2073
+ .then(next_run_epoch);
2028
2074
  Ok(consumer)
2029
2075
  }
2030
2076
 
@@ -2338,6 +2384,37 @@ impl MessageConsumer for FileConsumer {
2338
2384
  // a cumulative byte offset (the max acked `file_offset`), so out-of-order
2339
2385
  // commits could advance the offset past un-acked messages and lose them.
2340
2386
  async fn receive_batch(&mut self, max_messages: usize) -> Result<ReceivedBatch, ConsumerError> {
2387
+ let mut batch = self.receive_batch_inner(max_messages).await?;
2388
+ if self.source_metadata {
2389
+ for message in &mut batch.messages {
2390
+ message
2391
+ .metadata
2392
+ .insert("mqb.src.file_path".to_string(), self.path.clone());
2393
+ message.metadata.insert(
2394
+ "mqb.src.file_record".to_string(),
2395
+ self.next_record.to_string(),
2396
+ );
2397
+ if let Some(epoch) = self.run_epoch {
2398
+ message
2399
+ .metadata
2400
+ .insert("mqb.src.file_epoch".to_string(), epoch.to_string());
2401
+ }
2402
+ self.next_record += 1;
2403
+ }
2404
+ }
2405
+ Ok(batch)
2406
+ }
2407
+
2408
+ fn as_any(&self) -> &dyn Any {
2409
+ self
2410
+ }
2411
+ }
2412
+
2413
+ impl FileConsumer {
2414
+ async fn receive_batch_inner(
2415
+ &mut self,
2416
+ max_messages: usize,
2417
+ ) -> Result<ReceivedBatch, ConsumerError> {
2341
2418
  // The path was unopenable at startup. A live tail waits for it (rotation,
2342
2419
  // a writer that hasn't created it yet), but a drain has nothing to wait
2343
2420
  // for, so re-probe and fail rather than block forever on a typo'd path.
@@ -2560,10 +2637,6 @@ impl MessageConsumer for FileConsumer {
2560
2637
  }
2561
2638
  }
2562
2639
  }
2563
-
2564
- fn as_any(&self) -> &dyn Any {
2565
- self
2566
- }
2567
2640
  }
2568
2641
 
2569
2642
  /// Wraps a message body for the Json/Text file formats. Generic over the payload type