mq-bridge-py-basic 0.3.7__tar.gz → 0.3.8__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 (261) hide show
  1. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/CLAUDE.md +1 -1
  2. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/Cargo.lock +4 -4
  3. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/Cargo.toml +1 -1
  4. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/PKG-INFO +1 -1
  5. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/REFERENCE.md +38 -0
  6. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/mq-bridge.schema.json +34 -0
  7. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/mq_bridge/config.py +2 -0
  8. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/mq_bridge/config.pyi +7 -0
  9. mq_bridge_py_basic-0.3.8/src/middleware/compression.rs +314 -0
  10. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/middleware/mod.rs +8 -0
  11. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/models.rs +129 -1
  12. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/reference_docs_test.rs +2 -0
  13. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  14. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  15. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  16. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/.github/workflows/benchmark.yml +0 -0
  17. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/.github/workflows/ci.yml +0 -0
  18. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/.github/workflows/ibm-mq.yml +0 -0
  19. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/.github/workflows/node.yml +0 -0
  20. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/.github/workflows/publish-node.yml +0 -0
  21. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/.github/workflows/publish-python.yml +0 -0
  22. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/.github/workflows/python.yml +0 -0
  23. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/.github/workflows/release.yml +0 -0
  24. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/.github/workflows/test-matrix.yml +0 -0
  25. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/.gitignore +0 -0
  26. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/ARCHITECTURE.md +0 -0
  27. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/CONFIGURATION.md +0 -0
  28. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/CONTRIBUTING.md +0 -0
  29. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/LICENSE +0 -0
  30. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/LICENSE.python +0 -0
  31. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/README.md +0 -0
  32. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/benches/ETL_BENCHMARKS.md +0 -0
  33. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/benches/performance_bench.rs +0 -0
  34. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/benches/router_bench.rs +0 -0
  35. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/benches/zeromq_backends.rs +0 -0
  36. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/bindings-common/Cargo.toml +0 -0
  37. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/bindings-common/src/lib.rs +0 -0
  38. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/bindings-common/src/logging.rs +0 -0
  39. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/build.rs +0 -0
  40. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/examples/ipc_worker_queue.yaml +0 -0
  41. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/mq_bridge/__init__.py +0 -0
  42. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/mq_bridge/__init__.pyi +0 -0
  43. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/mq_bridge/py.typed +0 -0
  44. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/package-lock.json +0 -0
  45. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/pyproject.toml +0 -0
  46. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/python/mq-bridge-py/Cargo.toml +0 -0
  47. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/python/mq-bridge-py/LICENSE.python +0 -0
  48. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/python/mq-bridge-py/README.md +0 -0
  49. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/python/mq-bridge-py/analysis/bench_http_native.py +0 -0
  50. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/python/mq-bridge-py/example.py +0 -0
  51. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/python/mq-bridge-py/examples/bench_memory.py +0 -0
  52. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/python/mq-bridge-py/examples/bench_memory.yaml +0 -0
  53. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/python/mq-bridge-py/examples/consume_pull.py +0 -0
  54. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/python/mq-bridge-py/examples/dlt_source.py +0 -0
  55. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/python/mq-bridge-py/examples/json_route.py +0 -0
  56. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/python/mq-bridge-py/examples/memory.yaml +0 -0
  57. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/python/mq-bridge-py/examples/raw_route.py +0 -0
  58. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/python/mq-bridge-py/pyproject-basic.toml +0 -0
  59. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/python/mq-bridge-py/pyproject-reduced.toml +0 -0
  60. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/python/mq-bridge-py/scripts/gen_config_types.py +0 -0
  61. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/python/mq-bridge-py/src/lib.rs +0 -0
  62. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/python/mq-bridge-py/tests/test_config_types.py +0 -0
  63. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/python/mq-bridge-py/tests/test_logging.py +0 -0
  64. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/python/mq-bridge-py/tests/test_packaging.py +0 -0
  65. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/python/mq-bridge-py/tests/test_performance_smoke.py +0 -0
  66. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/python/mq-bridge-py/tests/test_public_api.py +0 -0
  67. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/python/mq-bridge-py/tests/test_routes.py +0 -0
  68. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/python/mq-bridge-py/uv.lock +0 -0
  69. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/scripts/analysis/benches/metadata_bench.rs +0 -0
  70. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/scripts/analysis/debug_ibm_mq_tls.rs +0 -0
  71. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/scripts/analysis/http/analyze_xctrace_time_profile.py +0 -0
  72. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/scripts/analysis/http/http_flamegraph.py +0 -0
  73. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/scripts/analysis/http/mq_bridge_http_profile.rs +0 -0
  74. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/scripts/analysis/http/run_http_ladder.sh +0 -0
  75. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/scripts/ci/format_bench_output.sh +0 -0
  76. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/scripts/httparena/README.md +0 -0
  77. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/scripts/httparena/frameworks/mq-bridge-py/Dockerfile +0 -0
  78. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/scripts/httparena/frameworks/mq-bridge-py/meta.json +0 -0
  79. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/scripts/httparena/frameworks/mq-bridge-py/server.py +0 -0
  80. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/scripts/techempower/Python/mq-bridge-py/README.md +0 -0
  81. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/scripts/techempower/Python/mq-bridge-py/benchmark_config.json +0 -0
  82. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/scripts/techempower/Python/mq-bridge-py/mq-bridge-py.dockerfile +0 -0
  83. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/scripts/techempower/Python/mq-bridge-py/server.py +0 -0
  84. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/scripts/techempower/README.md +0 -0
  85. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/scripts/techempower/postgres.yml +0 -0
  86. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/scripts/techempower/seed.sql +0 -0
  87. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/scripts/techempower/verify.sh +0 -0
  88. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/scripts/the-benchmarker/README.md +0 -0
  89. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/scripts/the-benchmarker/python/mq-bridge-py/config.yaml +0 -0
  90. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/scripts/the-benchmarker/python/mq-bridge-py/pyproject.toml +0 -0
  91. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/scripts/the-benchmarker/python/mq-bridge-py/server.py +0 -0
  92. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/canonical_message.rs +0 -0
  93. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/checkpoint.rs +0 -0
  94. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/command_handler.rs +0 -0
  95. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/amqp.rs +0 -0
  96. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/aws.rs +0 -0
  97. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/clickhouse.rs +0 -0
  98. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/file/mod.rs +0 -0
  99. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/file/tests.rs +0 -0
  100. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/grpc.proto +0 -0
  101. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/grpc.rs +0 -0
  102. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/http/mod.rs +0 -0
  103. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/http/publisher.rs +0 -0
  104. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/http/stream.rs +0 -0
  105. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/http/tests.rs +0 -0
  106. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/ibm_mq.rs +0 -0
  107. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/kafka.rs +0 -0
  108. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/memory/endpoint.rs +0 -0
  109. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/memory/framed.rs +0 -0
  110. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/memory/ipc_unix.rs +0 -0
  111. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/memory/ipc_windows.rs +0 -0
  112. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/memory/memory_transport.rs +0 -0
  113. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/memory/mod.rs +0 -0
  114. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/memory/transport.rs +0 -0
  115. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/mod.rs +0 -0
  116. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/mongodb/consumer.rs +0 -0
  117. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/mongodb/dedup.rs +0 -0
  118. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/mongodb/mod.rs +0 -0
  119. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/mongodb/publisher.rs +0 -0
  120. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/mongodb/readers.rs +0 -0
  121. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/mongodb/tests.rs +0 -0
  122. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/mqtt.rs +0 -0
  123. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/nats.rs +0 -0
  124. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/object_store.rs +0 -0
  125. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/poll.rs +0 -0
  126. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/postgres/VENDORED.md +0 -0
  127. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/postgres/mod.rs +0 -0
  128. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/postgres/pgoutput/decoder.rs +0 -0
  129. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/postgres/pgoutput/messages.rs +0 -0
  130. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/postgres/pgoutput/mod.rs +0 -0
  131. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/postgres/pgoutput/registry.rs +0 -0
  132. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/postgres/pgoutput/values.rs +0 -0
  133. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/postgres/replication.rs +0 -0
  134. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/postgres/state.rs +0 -0
  135. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/redis_streams.rs +0 -0
  136. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/sled.rs +0 -0
  137. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/sqlx/dedup.rs +0 -0
  138. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/sqlx/mod.rs +0 -0
  139. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/sqlx/tests.rs +0 -0
  140. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/structural/fanout.rs +0 -0
  141. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/structural/mod.rs +0 -0
  142. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/structural/null.rs +0 -0
  143. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/structural/reader.rs +0 -0
  144. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/structural/request.rs +0 -0
  145. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/structural/response.rs +0 -0
  146. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/structural/static_endpoint.rs +0 -0
  147. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/structural/stream_buffer.rs +0 -0
  148. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/structural/switch.rs +0 -0
  149. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/websocket.rs +0 -0
  150. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/zeromq/codec.rs +0 -0
  151. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/zeromq/mod.rs +0 -0
  152. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/zeromq/omq.rs +0 -0
  153. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/endpoints/zeromq/zmq.rs +0 -0
  154. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/errors.rs +0 -0
  155. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/event_handler.rs +0 -0
  156. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/event_store.rs +0 -0
  157. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/extensions.rs +0 -0
  158. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/lib.rs +0 -0
  159. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/middleware/buffer.rs +0 -0
  160. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/middleware/cookie_jar.rs +0 -0
  161. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/middleware/deduplication.rs +0 -0
  162. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/middleware/delay.rs +0 -0
  163. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/middleware/dlq.rs +0 -0
  164. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/middleware/encryption.rs +0 -0
  165. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/middleware/limiter.rs +0 -0
  166. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/middleware/metrics.rs +0 -0
  167. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/middleware/random_panic.rs +0 -0
  168. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/middleware/retry.rs +0 -0
  169. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/middleware/transform/coerce.rs +0 -0
  170. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/middleware/transform/compiled.rs +0 -0
  171. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/middleware/transform/error.rs +0 -0
  172. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/middleware/transform/mod.rs +0 -0
  173. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/middleware/transform/path.rs +0 -0
  174. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/middleware/transform/schema.rs +0 -0
  175. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/middleware/transform/tests.rs +0 -0
  176. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/middleware/weak_join.rs +0 -0
  177. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/outcomes.rs +0 -0
  178. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/publisher.rs +0 -0
  179. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/response.rs +0 -0
  180. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/route.rs +0 -0
  181. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/support/compression.rs +0 -0
  182. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/support/connection_registry.rs +0 -0
  183. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/support/crypto.rs +0 -0
  184. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/support/interpolation.rs +0 -0
  185. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/support/mod.rs +0 -0
  186. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/test_utils.rs +0 -0
  187. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/traits.rs +0 -0
  188. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/src/type_handler.rs +0 -0
  189. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/README.md +0 -0
  190. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/armature_integration.rs +0 -0
  191. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/amqp.rs +0 -0
  192. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/aws.rs +0 -0
  193. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/clickhouse.rs +0 -0
  194. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/config.toml +0 -0
  195. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/docker-compose/amqp.yml +0 -0
  196. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/docker-compose/aws.yml +0 -0
  197. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/docker-compose/clickhouse.yml +0 -0
  198. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/docker-compose/ibm-mq-certs/client.crl +0 -0
  199. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/docker-compose/ibm-mq-certs/client.kdb +0 -0
  200. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/docker-compose/ibm-mq-certs/client.p12 +0 -0
  201. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/docker-compose/ibm-mq-certs/client.rdb +0 -0
  202. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/docker-compose/ibm-mq-certs/client.sth +0 -0
  203. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/docker-compose/ibm_mq.yml +0 -0
  204. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/docker-compose/ibm_mq_tls.yml +0 -0
  205. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/docker-compose/kafka-tls.yml +0 -0
  206. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/docker-compose/kafka.yml +0 -0
  207. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/docker-compose/mariadb.yml +0 -0
  208. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/docker-compose/mongodb-replica.yml +0 -0
  209. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/docker-compose/mongodb-tls.yml +0 -0
  210. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/docker-compose/mongodb.yml +0 -0
  211. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/docker-compose/mosquitto.conf +0 -0
  212. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/docker-compose/mosquitto_performance.conf +0 -0
  213. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/docker-compose/mosquitto_persistence.conf +0 -0
  214. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/docker-compose/mq_init.mqsc +0 -0
  215. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/docker-compose/mq_init_tls.mqsc +0 -0
  216. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/docker-compose/mqtt.yml +0 -0
  217. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/docker-compose/mqtt_performance.yml +0 -0
  218. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/docker-compose/mysql.yml +0 -0
  219. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/docker-compose/nats.yml +0 -0
  220. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/docker-compose/object_store.yml +0 -0
  221. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/docker-compose/postgres.yml +0 -0
  222. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/docker-compose/postgres_cdc.yml +0 -0
  223. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/docker-compose/redis.yml +0 -0
  224. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/file.rs +0 -0
  225. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/grpc.rs +0 -0
  226. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/grpc_tls.rs +0 -0
  227. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/http.rs +0 -0
  228. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/http_tls.rs +0 -0
  229. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/ibm_mq.rs +0 -0
  230. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/ibm_mq_tls.rs +0 -0
  231. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/ipc.rs +0 -0
  232. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/kafka.rs +0 -0
  233. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/kafka_tls.rs +0 -0
  234. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/logic_test.rs +0 -0
  235. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/mariadb.rs +0 -0
  236. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/memory.rs +0 -0
  237. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/mod.rs +0 -0
  238. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/mongodb.rs +0 -0
  239. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/mongodb_raw.rs +0 -0
  240. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/mongodb_tls.rs +0 -0
  241. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/mqtt.rs +0 -0
  242. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/mysql.rs +0 -0
  243. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/nats.rs +0 -0
  244. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/object_store.rs +0 -0
  245. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/performance_static.rs +0 -0
  246. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/postgres.rs +0 -0
  247. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/postgres_cdc.rs +0 -0
  248. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/redis_streams.rs +0 -0
  249. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/route.rs +0 -0
  250. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/scripts/gen_certs.sh +0 -0
  251. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/sqlite.rs +0 -0
  252. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/tls_helpers.rs +0 -0
  253. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/websocket.rs +0 -0
  254. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration/zeromq.rs +0 -0
  255. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/integration_test.rs +0 -0
  256. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/memory_leak_test.rs +0 -0
  257. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/ref_test.rs +0 -0
  258. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/sqlite_test.rs +0 -0
  259. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/tls_example.rs +0 -0
  260. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/unit_tests.md +0 -0
  261. {mq_bridge_py_basic-0.3.7 → mq_bridge_py_basic-0.3.8}/tests/websocket_test.rs +0 -0
@@ -3,7 +3,7 @@
3
3
  > **Looking for what middleware or structural endpoints exist, and how to configure them?**
4
4
  > [REFERENCE.md](REFERENCE.md) is the complete, authoritative list — every middleware
5
5
  > (`retry`, `dlq`, `transform`, `deduplication`, `weak_join`, `buffer`, `limiter`, `delay`,
6
- > `cookie_jar`, `encryption`, `metrics`, `random_panic`, `custom`) and every structural endpoint (`ref`,
6
+ > `cookie_jar`, `encryption`, `compression`, `metrics`, `random_panic`, `custom`) and every structural endpoint (`ref`,
7
7
  > `fanout`, `switch`, `request`, `response`, `reader`, `static`, `stream_buffer`, `null`,
8
8
  > `custom`), each with its fields, defaults, and a working YAML example. Do not infer these
9
9
  > from the enum definitions in `models.rs`; the reference records the behaviour and the
@@ -3330,7 +3330,7 @@ dependencies = [
3330
3330
 
3331
3331
  [[package]]
3332
3332
  name = "mq-bridge"
3333
- version = "0.3.7"
3333
+ version = "0.3.8"
3334
3334
  dependencies = [
3335
3335
  "aes-gcm",
3336
3336
  "anyhow",
@@ -3411,7 +3411,7 @@ dependencies = [
3411
3411
 
3412
3412
  [[package]]
3413
3413
  name = "mq-bridge-bindings-common"
3414
- version = "0.3.7"
3414
+ version = "0.3.8"
3415
3415
  dependencies = [
3416
3416
  "anyhow",
3417
3417
  "fast-uuid-v7",
@@ -3425,7 +3425,7 @@ dependencies = [
3425
3425
 
3426
3426
  [[package]]
3427
3427
  name = "mq-bridge-node"
3428
- version = "0.3.7"
3428
+ version = "0.3.8"
3429
3429
  dependencies = [
3430
3430
  "anyhow",
3431
3431
  "async-trait",
@@ -3446,7 +3446,7 @@ dependencies = [
3446
3446
 
3447
3447
  [[package]]
3448
3448
  name = "mq-bridge-py"
3449
- version = "0.3.7"
3449
+ version = "0.3.8"
3450
3450
  dependencies = [
3451
3451
  "anyhow",
3452
3452
  "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.3.7"
19
+ version = "0.3.8"
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.3.7
3
+ Version: 0.3.8
4
4
  Classifier: Development Status :: 3 - Alpha
5
5
  Classifier: License :: OSI Approved :: MIT License
6
6
  Classifier: Programming Language :: Python :: 3
@@ -65,10 +65,21 @@ middlewares:
65
65
  | [`delay`](#delay) | ✅ | ✅ | – | Fixed delay per receive/send |
66
66
  | [`cookie_jar`](#cookie_jar) | ✅ | ✅ | – | Persist HTTP cookies / session values across messages |
67
67
  | [`encryption`](#encryption) | ✅ | ✅ | `encryption` | AEAD-encrypt payloads on send, decrypt on receive |
68
+ | [`compression`](#compression) | ✅ | ✅ | `compression` | Compress payloads on send, decompress on receive |
68
69
  | [`metrics`](#metrics) | ✅ | ✅ | `metrics` | Emit throughput/latency/error metrics |
69
70
  | [`random_panic`](#random_panic) | ✅ | ✅ | – | Fault injection for testing |
70
71
  | [`custom`](#custom-middleware) | ✅ | ✅ | – | Your own middleware via a registered factory |
71
72
 
73
+ > **Two kinds of compression.** The [`compression`](#compression) *middleware* compresses
74
+ > each message **payload** on any transport and decompresses it on the far side. Separately,
75
+ > the batch `compression` *field* on the `file` and `object_store` endpoints (`none` / `gzip`
76
+ > / `lz4` / `zstd`, same `compression` feature) compresses whole write batches so the file
77
+ > stays decodable with `zcat` / `lz4 -d`. Use the field for CLI-readable data at rest, the
78
+ > middleware for over-the-wire payloads. Don't stack either compression with the
79
+ > [`encryption`](#encryption) middleware on the same route — ciphertext does not compress; for
80
+ > compressed-and-encrypted data at rest use the endpoints' own `compression`/`encryption`
81
+ > fields (compress-then-encrypt per batch).
82
+
72
83
  **Putting a middleware on the wrong side behaves in two different ways**, so check the table
73
84
  above rather than assuming:
74
85
 
@@ -421,6 +432,33 @@ sniffing the leading magic bytes, so raw compressed bytes are never emitted as m
421
432
  > `double precision` column can change the last bit. Build with the `float-roundtrip` feature
422
433
  > for bit-exact float parsing across every endpoint (it trades a little parse speed for it).
423
434
 
435
+ ### `compression`
436
+
437
+ Compresses each message **payload** on the output side and decompresses it on the input
438
+ side. Metadata and routing keys are untouched. Input and output. Requires the `compression`
439
+ feature.
440
+
441
+ | Field | Type | Default |
442
+ |---|---|---|
443
+ | `algorithm` | `none` \| `gzip` \| `lz4` \| `zstd` | `zstd` |
444
+ | `max_decompressed_bytes` | integer — reject a payload that decompresses larger than this (bomb guard); consumer side only | unset (no limit) |
445
+
446
+ ```yaml
447
+ - compression: { algorithm: zstd }
448
+ ```
449
+
450
+ Each payload is compressed independently into a single self-contained member, so this works
451
+ over any transport, not just files. `algorithm: none` is a passthrough. A truncated or
452
+ corrupt frame is a **permanent** consumer error (the poison message is not re-read
453
+ indefinitely), as is a payload that exceeds `max_decompressed_bytes`. Put the same
454
+ `algorithm` on both the input and output side of a route.
455
+
456
+ Unlike the `file` / `object_store` batch `compression` field — which keeps whole write
457
+ batches decodable with `zcat` / `lz4 -d` — this middleware frames per message and is only
458
+ readable through a matching consumer. Do not combine it with the [`encryption`](#encryption)
459
+ middleware (ciphertext does not compress); for compressed-and-encrypted data at rest, use the
460
+ endpoints' own `compression`/`encryption` fields instead.
461
+
424
462
  ### `metrics`
425
463
 
426
464
  Emits throughput, latency and error metrics for the endpoint. Input and output. Requires the
@@ -389,6 +389,28 @@
389
389
  }
390
390
  ]
391
391
  },
392
+ "CompressionMiddleware": {
393
+ "description": "Payload-compression middleware configuration.\n\nCompresses each message payload on the output side and decompresses it on the input\nside; metadata and routing keys are left untouched. Requires the `compression` feature.\nDistinct from the `file`/`object_store` batch `compression` field, which stays\nCLI-decodable at rest — this operates per message on any transport.",
394
+ "type": "object",
395
+ "properties": {
396
+ "algorithm": {
397
+ "description": "Algorithm: `none`, `gzip`, `lz4`, or `zstd`. Defaults to `zstd`.",
398
+ "$ref": "#/$defs/Compression",
399
+ "default": "zstd"
400
+ },
401
+ "max_decompressed_bytes": {
402
+ "description": "Reject a decompressed payload larger than this many bytes (decompression-bomb guard).\nConsumer side only; unset means no limit.",
403
+ "type": [
404
+ "integer",
405
+ "null"
406
+ ],
407
+ "format": "uint64",
408
+ "default": null,
409
+ "minimum": 0
410
+ }
411
+ },
412
+ "additionalProperties": false
413
+ },
392
414
  "CookieJarMiddleware": {
393
415
  "description": "Cookie/session jar middleware configuration.\n\nOptimized for HTTP by default: it can read `cookie` and `set-cookie` metadata,\npersist session cookies, and inject them into later outgoing requests.\n\nThe middleware can also capture arbitrary metadata values into the same session store\nand optionally expose stored values back into message metadata.",
394
416
  "type": "object",
@@ -1926,6 +1948,18 @@
1926
1948
  "encryption"
1927
1949
  ]
1928
1950
  },
1951
+ {
1952
+ "type": "object",
1953
+ "properties": {
1954
+ "compression": {
1955
+ "$ref": "#/$defs/CompressionMiddleware"
1956
+ }
1957
+ },
1958
+ "additionalProperties": false,
1959
+ "required": [
1960
+ "compression"
1961
+ ]
1962
+ },
1929
1963
  {
1930
1964
  "type": "object",
1931
1965
  "properties": {
@@ -7,6 +7,7 @@ AmqpConfig = dict
7
7
  AwsConfig = dict
8
8
  BufferMiddleware = dict
9
9
  ClickHouseConfig = dict
10
+ CompressionMiddleware = dict
10
11
  CookieJarMiddleware = dict
11
12
  DeadLetterQueueMiddleware = dict
12
13
  DeduplicationMiddleware = dict
@@ -73,6 +74,7 @@ __all__ = [
73
74
  "CipherKind",
74
75
  "ClickHouseConfig",
75
76
  "Compression",
77
+ "CompressionMiddleware",
76
78
  "ConfigDocument",
77
79
  "CookieJarMiddleware",
78
80
  "DeadLetterQueueMiddleware",
@@ -63,6 +63,12 @@ class ClickHouseConfig(TypedDict, total=False):
63
63
  wait_for_async_insert: Optional[bool]
64
64
 
65
65
 
66
+ class CompressionMiddleware(TypedDict, total=False):
67
+ """Payload-compression middleware configuration."""
68
+ algorithm: Compression
69
+ max_decompressed_bytes: Optional[int]
70
+
71
+
66
72
  class CookieJarMiddleware(TypedDict, total=False):
67
73
  """Cookie/session jar middleware configuration."""
68
74
  capture_metadata_keys: List[str]
@@ -226,6 +232,7 @@ class MetricsMiddleware(TypedDict, total=False):
226
232
  class Middleware(TypedDict, total=False):
227
233
  """An enumeration of all supported middleware types."""
228
234
  buffer: BufferMiddleware
235
+ compression: CompressionMiddleware
229
236
  cookie_jar: CookieJarMiddleware
230
237
  custom: Dict[str, Any]
231
238
  deduplication: DeduplicationMiddleware
@@ -0,0 +1,314 @@
1
+ // mq-bridge
2
+ // © Copyright 2026, by Marco Mengelkoch
3
+ // Licensed under MIT License, see License file for more details
4
+ // git clone https://github.com/marcomq/mq-bridge
5
+ //
6
+ //! Two-way payload compression middleware: the publisher side compresses each
7
+ //! message payload into a single self-contained member, the consumer side
8
+ //! decompresses it. Metadata and routing keys stay untouched. `Compression::None`
9
+ //! is a passthrough.
10
+
11
+ use crate::models::{Compression, CompressionMiddleware};
12
+ use crate::support::compression::{compress_member, decompress_all};
13
+ use crate::traits::{
14
+ BoxFuture, ConsumerError, MessageConsumer, MessagePublisher, PublisherError, Received,
15
+ ReceivedBatch, Sent, SentBatch,
16
+ };
17
+ use crate::CanonicalMessage;
18
+ use async_trait::async_trait;
19
+ use std::any::Any;
20
+
21
+ pub struct CompressionPublisher {
22
+ inner: Box<dyn MessagePublisher>,
23
+ algo: Compression,
24
+ }
25
+
26
+ impl CompressionPublisher {
27
+ pub fn new(inner: Box<dyn MessagePublisher>, config: &CompressionMiddleware) -> Self {
28
+ Self {
29
+ inner,
30
+ algo: config.algorithm,
31
+ }
32
+ }
33
+
34
+ fn compress_message(&self, message: &mut CanonicalMessage) -> Result<(), PublisherError> {
35
+ if self.algo == Compression::None {
36
+ return Ok(());
37
+ }
38
+ let out = compress_member(self.algo, &message.payload)
39
+ .map_err(|e| PublisherError::NonRetryable(e.into()))?;
40
+ message.payload = out.into();
41
+ Ok(())
42
+ }
43
+ }
44
+
45
+ #[async_trait]
46
+ impl MessagePublisher for CompressionPublisher {
47
+ fn on_connect_hook(&self) -> Option<BoxFuture<'_, anyhow::Result<()>>> {
48
+ self.inner.on_connect_hook()
49
+ }
50
+
51
+ fn on_disconnect_hook(&self) -> Option<BoxFuture<'_, anyhow::Result<()>>> {
52
+ self.inner.on_disconnect_hook()
53
+ }
54
+
55
+ async fn send(&self, mut message: CanonicalMessage) -> Result<Sent, PublisherError> {
56
+ self.compress_message(&mut message)?;
57
+ self.inner.send(message).await
58
+ }
59
+
60
+ async fn send_batch(
61
+ &self,
62
+ mut messages: Vec<CanonicalMessage>,
63
+ ) -> Result<SentBatch, PublisherError> {
64
+ // Keep the uncompressed payloads: messages surfaced as failed must go back
65
+ // upstream (retry/dlq) in their original form, or an outer retry would
66
+ // double-compress them.
67
+ let originals: std::collections::HashMap<u128, bytes::Bytes> = messages
68
+ .iter()
69
+ .map(|m| (m.message_id, m.payload.clone()))
70
+ .collect();
71
+ for message in &mut messages {
72
+ self.compress_message(message)?;
73
+ }
74
+ match self.inner.send_batch(messages).await? {
75
+ SentBatch::Ack => Ok(SentBatch::Ack),
76
+ SentBatch::Partial {
77
+ responses,
78
+ mut failed,
79
+ } => {
80
+ for (msg, _) in &mut failed {
81
+ if let Some(original) = originals.get(&msg.message_id) {
82
+ msg.payload = original.clone();
83
+ }
84
+ }
85
+ Ok(SentBatch::Partial { responses, failed })
86
+ }
87
+ }
88
+ }
89
+
90
+ fn as_any(&self) -> &dyn Any {
91
+ self
92
+ }
93
+ }
94
+
95
+ pub struct CompressionConsumer {
96
+ inner: Box<dyn MessageConsumer>,
97
+ algo: Compression,
98
+ max_bytes: Option<u64>,
99
+ }
100
+
101
+ impl CompressionConsumer {
102
+ pub fn new(inner: Box<dyn MessageConsumer>, config: &CompressionMiddleware) -> Self {
103
+ Self {
104
+ inner,
105
+ algo: config.algorithm,
106
+ max_bytes: config.max_decompressed_bytes,
107
+ }
108
+ }
109
+
110
+ fn decompress_message(&self, message: &mut CanonicalMessage) -> Result<(), ConsumerError> {
111
+ if self.algo == Compression::None {
112
+ return Ok(());
113
+ }
114
+ // A malformed/truncated frame will never decode, so it is a permanent failure
115
+ // rather than a reconnectable one — otherwise the poison message would be
116
+ // re-read forever.
117
+ let out = decompress_all(self.algo, &message.payload, self.max_bytes)
118
+ .map_err(|e| ConsumerError::Permanent(e.into()))?;
119
+ message.payload = out.into();
120
+ Ok(())
121
+ }
122
+ }
123
+
124
+ #[async_trait]
125
+ impl MessageConsumer for CompressionConsumer {
126
+ fn set_exit_on_empty(&mut self, exit_on_empty: bool) {
127
+ self.inner.set_exit_on_empty(exit_on_empty);
128
+ }
129
+
130
+ fn commit_requires_order(&self) -> bool {
131
+ self.inner.commit_requires_order()
132
+ }
133
+
134
+ fn on_connect_hook(&self) -> Option<BoxFuture<'_, anyhow::Result<()>>> {
135
+ self.inner.on_connect_hook()
136
+ }
137
+
138
+ fn on_disconnect_hook(&self) -> Option<BoxFuture<'_, anyhow::Result<()>>> {
139
+ self.inner.on_disconnect_hook()
140
+ }
141
+
142
+ async fn receive(&mut self) -> Result<Received, ConsumerError> {
143
+ let mut received = self.inner.receive().await?;
144
+ self.decompress_message(&mut received.message)?;
145
+ Ok(received)
146
+ }
147
+
148
+ async fn receive_batch(&mut self, max_messages: usize) -> Result<ReceivedBatch, ConsumerError> {
149
+ let mut batch = self.inner.receive_batch(max_messages).await?;
150
+ for message in &mut batch.messages {
151
+ self.decompress_message(message)?;
152
+ }
153
+ Ok(batch)
154
+ }
155
+
156
+ fn as_any(&self) -> &dyn Any {
157
+ self
158
+ }
159
+ }
160
+
161
+ #[cfg(test)]
162
+ mod tests {
163
+ use super::*;
164
+ use std::sync::{Arc, Mutex};
165
+
166
+ fn config(algo: Compression) -> CompressionMiddleware {
167
+ CompressionMiddleware {
168
+ algorithm: algo,
169
+ ..Default::default()
170
+ }
171
+ }
172
+
173
+ #[derive(Clone)]
174
+ struct RecordingPublisher {
175
+ sent: Arc<Mutex<Vec<CanonicalMessage>>>,
176
+ }
177
+
178
+ #[async_trait]
179
+ impl MessagePublisher for RecordingPublisher {
180
+ async fn send_batch(
181
+ &self,
182
+ messages: Vec<CanonicalMessage>,
183
+ ) -> Result<SentBatch, PublisherError> {
184
+ self.sent.lock().unwrap().extend(messages);
185
+ Ok(SentBatch::Ack)
186
+ }
187
+
188
+ fn as_any(&self) -> &dyn Any {
189
+ self
190
+ }
191
+ }
192
+
193
+ struct MockConsumer {
194
+ messages: Option<Vec<CanonicalMessage>>,
195
+ }
196
+
197
+ #[async_trait]
198
+ impl MessageConsumer for MockConsumer {
199
+ async fn receive_batch(
200
+ &mut self,
201
+ _max_messages: usize,
202
+ ) -> Result<ReceivedBatch, ConsumerError> {
203
+ Ok(ReceivedBatch {
204
+ messages: self.messages.take().expect("batch already consumed"),
205
+ commit: Box::new(|_| Box::pin(async { Ok(()) })),
206
+ })
207
+ }
208
+
209
+ fn as_any(&self) -> &dyn Any {
210
+ self
211
+ }
212
+ }
213
+
214
+ #[tokio::test]
215
+ async fn publisher_compresses_and_consumer_decompresses() {
216
+ for algo in [Compression::Gzip, Compression::Lz4, Compression::Zstd] {
217
+ let sent = Arc::new(Mutex::new(Vec::new()));
218
+ let publisher = CompressionPublisher::new(
219
+ Box::new(RecordingPublisher { sent: sent.clone() }),
220
+ &config(algo),
221
+ );
222
+
223
+ let plaintext = "compress me ".repeat(64);
224
+ let mut msg = CanonicalMessage::from(plaintext.as_str());
225
+ msg.metadata.insert("kind".to_string(), "note".to_string());
226
+ publisher.send_batch(vec![msg]).await.unwrap();
227
+
228
+ // The wire payload is compressed (smaller) and metadata stays clear.
229
+ let wire = sent.lock().unwrap().clone();
230
+ assert_ne!(
231
+ wire[0].payload.as_ref(),
232
+ plaintext.as_bytes(),
233
+ "algo {algo:?}"
234
+ );
235
+ assert!(wire[0].payload.len() < plaintext.len(), "algo {algo:?}");
236
+ assert_eq!(
237
+ wire[0].metadata.get("kind").map(|s| s.as_str()),
238
+ Some("note")
239
+ );
240
+
241
+ let mut consumer = CompressionConsumer::new(
242
+ Box::new(MockConsumer {
243
+ messages: Some(wire),
244
+ }),
245
+ &config(algo),
246
+ );
247
+ let batch = consumer.receive_batch(10).await.unwrap();
248
+ assert_eq!(
249
+ batch.messages[0].payload.as_ref(),
250
+ plaintext.as_bytes(),
251
+ "algo {algo:?}"
252
+ );
253
+ }
254
+ }
255
+
256
+ #[tokio::test]
257
+ async fn corrupt_frame_fails_consume() {
258
+ let sent = Arc::new(Mutex::new(Vec::new()));
259
+ let publisher = CompressionPublisher::new(
260
+ Box::new(RecordingPublisher { sent: sent.clone() }),
261
+ &config(Compression::Gzip),
262
+ );
263
+ publisher
264
+ .send_batch(vec![CanonicalMessage::from("payload payload payload")])
265
+ .await
266
+ .unwrap();
267
+
268
+ let mut wire = sent.lock().unwrap().clone();
269
+ let mut corrupt = wire[0].payload.to_vec();
270
+ *corrupt.last_mut().unwrap() ^= 0xff;
271
+ wire[0].payload = corrupt.into();
272
+
273
+ let mut consumer = CompressionConsumer::new(
274
+ Box::new(MockConsumer {
275
+ messages: Some(wire),
276
+ }),
277
+ &config(Compression::Gzip),
278
+ );
279
+ assert!(matches!(
280
+ consumer.receive_batch(10).await,
281
+ Err(ConsumerError::Permanent(_))
282
+ ));
283
+ }
284
+
285
+ #[tokio::test]
286
+ async fn decompression_bomb_guard_rejects_oversized() {
287
+ let sent = Arc::new(Mutex::new(Vec::new()));
288
+ let publisher = CompressionPublisher::new(
289
+ Box::new(RecordingPublisher { sent: sent.clone() }),
290
+ &config(Compression::Zstd),
291
+ );
292
+ let big = "x".repeat(64 * 1024);
293
+ publisher
294
+ .send_batch(vec![CanonicalMessage::from(big.as_str())])
295
+ .await
296
+ .unwrap();
297
+ let wire = sent.lock().unwrap().clone();
298
+
299
+ let cfg = CompressionMiddleware {
300
+ algorithm: Compression::Zstd,
301
+ max_decompressed_bytes: Some(1024),
302
+ };
303
+ let mut consumer = CompressionConsumer::new(
304
+ Box::new(MockConsumer {
305
+ messages: Some(wire),
306
+ }),
307
+ &cfg,
308
+ );
309
+ assert!(matches!(
310
+ consumer.receive_batch(10).await,
311
+ Err(ConsumerError::Permanent(_))
312
+ ));
313
+ }
314
+ }
@@ -10,6 +10,8 @@ use anyhow::Result;
10
10
  use std::sync::Arc;
11
11
 
12
12
  mod buffer;
13
+ #[cfg(feature = "compression")]
14
+ mod compression;
13
15
  mod cookie_jar;
14
16
  #[cfg(feature = "dedup")]
15
17
  pub(crate) mod deduplication;
@@ -26,6 +28,8 @@ mod transform;
26
28
  mod weak_join;
27
29
 
28
30
  use buffer::{BufferConsumer, BufferPublisher};
31
+ #[cfg(feature = "compression")]
32
+ use compression::{CompressionConsumer, CompressionPublisher};
29
33
  use cookie_jar::{CookieJarConsumer, CookieJarPublisher};
30
34
  #[cfg(feature = "dedup")]
31
35
  use deduplication::DeduplicationConsumer;
@@ -77,6 +81,8 @@ pub async fn apply_middlewares_to_consumer(
77
81
  Middleware::Transform(cfg) => Box::new(TransformConsumer::new(consumer, cfg)?),
78
82
  #[cfg(feature = "encryption")]
79
83
  Middleware::Encryption(cfg) => Box::new(EncryptionConsumer::new(consumer, cfg)?),
84
+ #[cfg(feature = "compression")]
85
+ Middleware::Compression(cfg) => Box::new(CompressionConsumer::new(consumer, cfg)),
80
86
  Middleware::Custom { name, config } => {
81
87
  let factory = get_middleware_factory(name).ok_or_else(|| {
82
88
  anyhow::anyhow!("Custom middleware factory '{}' not found", name)
@@ -140,6 +146,8 @@ pub async fn apply_middlewares_to_publisher(
140
146
  Middleware::Transform(cfg) => Box::new(TransformPublisher::new(publisher, cfg)?),
141
147
  #[cfg(feature = "encryption")]
142
148
  Middleware::Encryption(cfg) => Box::new(EncryptionPublisher::new(publisher, cfg)?),
149
+ #[cfg(feature = "compression")]
150
+ Middleware::Compression(cfg) => Box::new(CompressionPublisher::new(publisher, cfg)),
143
151
  Middleware::Custom { name, config } => {
144
152
  let factory = get_middleware_factory(name).ok_or_else(|| {
145
153
  anyhow::anyhow!("Custom middleware factory '{}' not found", name)