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