{"_id":"@dcsv-io/d2-messaging-rabbitmq","_rev":"2-1d63b319b623bed3f9d15c4161d53664","name":"@dcsv-io/d2-messaging-rabbitmq","dist-tags":{"latest":"0.1.2"},"versions":{"0.1.1":{"name":"@dcsv-io/d2-messaging-rabbitmq","version":"0.1.1","_id":"@dcsv-io/d2-messaging-rabbitmq@0.1.1","maintainers":[{"name":"dcsv-tristan","email":"tristan@dcsv.io"}],"dist":{"shasum":"78de7854351cd71cef36001dec7988618d1554bf","tarball":"https://registry.npmjs.org/@dcsv-io/d2-messaging-rabbitmq/-/d2-messaging-rabbitmq-0.1.1.tgz","fileCount":99,"integrity":"sha512-i7x88bQ3oXNxTWAFfomfxg6iAhQYGvQEjSX798k7c8m0eZzV8v8AfCgp85dRS8zMczBW6wB3OVk50M64VGHW+A==","signatures":[{"sig":"MEYCIQCRDM23VOp50mf4K/ox3QHnDI9m8dqCKupB8Y1qCe693QIhAPTCmnWdkagV+YfjPEg35F+6lmkD/qkJRpGy1RsIfp0a","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":193157},"main":"./dist/index.js","type":"module","_from":"file:bundle/npm/dcsv-io-d2-messaging-rabbitmq-0.1.1.tgz","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"scripts":{"test":"vitest run","build":"tsc -b","test:coverage":"vitest run --coverage","type-check:test":"tsc -p tsconfig.test.json","test:integration":"vitest run --config vitest.integration.config.ts"},"_npmUser":{"name":"dcsv-tristan","email":"tristan@dcsv.io"},"_resolved":"/home/runner/work/D2-Public/D2-Public/bundle/npm/dcsv-io-d2-messaging-rabbitmq-0.1.1.tgz","_integrity":"sha512-i7x88bQ3oXNxTWAFfomfxg6iAhQYGvQEjSX798k7c8m0eZzV8v8AfCgp85dRS8zMczBW6wB3OVk50M64VGHW+A==","_npmVersion":"11.16.0","description":"<!-- Copyright (c) DCSV. Licensed under the Apache License, Version 2.0. -->","directories":{},"_nodeVersion":"24.18.0","dependencies":{"rabbitmq-client":"5.0.8","@dcsv-io/d2-result":"0.1.1","@opentelemetry/api":"1.9.0","@dcsv-io/d2-logging":"0.1.1","@dcsv-io/d2-telemetry":"0.1.1","@dcsv-io/d2-utilities":"0.1.1","@dcsv-io/d2-encryption":"0.1.1","@dcsv-io/d2-headers-amqp":"0.1.1","@dcsv-io/d2-messaging-abstractions":"0.1.1","@dcsv-io/d2-encryption-abstractions":"0.1.1","@dcsv-io/d2-request-context-abstractions":"0.1.1"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"4.0.18","typescript":"5.9.3","testcontainers":"11.14.0","@vitest/coverage-v8":"4.0.18","@testcontainers/rabbitmq":"11.14.0"},"_npmOperationalInternal":{"tmp":"tmp/d2-messaging-rabbitmq_0.1.1_1784262843223_0.43151524131284225","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"name":"@dcsv-io/d2-messaging-rabbitmq","version":"0.1.2","type":"module","main":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"dependencies":{"@opentelemetry/api":"1.9.0","rabbitmq-client":"5.0.8","@dcsv-io/d2-encryption":"0.1.2","@dcsv-io/d2-headers-amqp":"0.1.2","@dcsv-io/d2-logging":"0.1.2","@dcsv-io/d2-encryption-abstractions":"0.1.2","@dcsv-io/d2-request-context-abstractions":"0.1.2","@dcsv-io/d2-result":"0.1.2","@dcsv-io/d2-telemetry":"0.1.2","@dcsv-io/d2-utilities":"0.1.2","@dcsv-io/d2-messaging-abstractions":"0.1.2"},"devDependencies":{"@testcontainers/rabbitmq":"11.14.0","@vitest/coverage-v8":"4.0.18","testcontainers":"11.14.0","typescript":"5.9.3","vitest":"4.0.18"},"scripts":{"build":"tsc -b","test":"vitest run","test:coverage":"vitest run --coverage","test:integration":"vitest run --config vitest.integration.config.ts","type-check:test":"tsc -p tsconfig.test.json"},"_id":"@dcsv-io/d2-messaging-rabbitmq@0.1.2","description":"<!-- Copyright (c) DCSV. Licensed under the Apache License, Version 2.0. -->","_integrity":"sha512-whIPhcXEsB75JWB+HBUbIcrGBTdPnXknuK/1FzWbDs+l3gl5IEgQOwrrY/MbNfGqEqQR5hhxD6aqCHploIOi3Q==","_resolved":"/home/runner/work/D2-Public/D2-Public/bundle/npm/dcsv-io-d2-messaging-rabbitmq-0.1.2.tgz","_from":"file:bundle/npm/dcsv-io-d2-messaging-rabbitmq-0.1.2.tgz","_nodeVersion":"24.18.0","_npmVersion":"11.16.0","dist":{"integrity":"sha512-whIPhcXEsB75JWB+HBUbIcrGBTdPnXknuK/1FzWbDs+l3gl5IEgQOwrrY/MbNfGqEqQR5hhxD6aqCHploIOi3Q==","shasum":"38e048ba0e43501e1224ba8af59b47da553ef06e","tarball":"https://registry.npmjs.org/@dcsv-io/d2-messaging-rabbitmq/-/d2-messaging-rabbitmq-0.1.2.tgz","fileCount":99,"unpackedSize":193109,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIE3qb7+svc+7Tn1mHXTd714nsiD2YsFzytJfYS1FUm79AiEAhQ1su2C8ub6OyBZrRf/QqH6TrJPN30SyfpdYoaxxHPg="}]},"_npmUser":{"name":"dcsv-tristan","email":"tristan@dcsv.io"},"directories":{},"maintainers":[{"name":"dcsv-tristan","email":"tristan@dcsv.io"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/d2-messaging-rabbitmq_0.1.2_1784287036161_0.01647382718167134"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-17T04:34:03.091Z","modified":"2026-07-17T11:17:16.475Z","0.1.1":"2026-07-17T04:34:03.369Z","0.1.2":"2026-07-17T11:17:16.303Z"},"description":"<!-- Copyright (c) DCSV. Licensed under the Apache License, Version 2.0. -->","maintainers":[{"name":"dcsv-tristan","email":"tristan@dcsv.io"}],"readme":"<!--\nCopyright (c) DCSV. Licensed under the Apache License, Version 2.0.\n-->\n\n# @dcsv-io/d2-messaging-rabbitmq\n\nThe TypeScript **CONSUMER** runtime twin of the .NET\n`DcsvIo.D2.Messaging.RabbitMq`\nconsumer path. A service author building a Node service that consumes messages\na .NET service publishes uses this package: a service-agnostic RabbitMQ\nsubscriber with the same topology, same DLQ convention, and same cross-hop\ncontext and trace propagation — so a Node consumer and a .NET consumer are\ninterchangeable on the wire.\n\nBuilt on `rabbitmq-client`\n(zero-dep, auto-reconnecting), pinned exact.\n\n---\n\n## Install\n\n```bash\npnpm add @dcsv-io/d2-messaging-rabbitmq\n```\n\n## Publish/encrypt fusion\n\nPublishing and encryption are **structurally fused** — the TS twin of .NET's\nspec-driven composer + DI. `createPublisher({ crypto })` binds a compile-time\ntype witness: `publish(key, message)` accepts only a message whose encryption\ndomain is `plaintext` or was wired into `crypto`. Publishing to an unwired\nencrypted domain is a **compile error**, and there is no raw-bytes publish\noverload — the composer for a domain is the only path to the socket for that\ndomain. A sealed domain's slot only accepts an `IPayloadSealer`; a symmetric\nslot only an `IPayloadCrypto` (mode-branded by the generated\n`EncryptionDomainModes` literals). The compile witness is pinned by\n`tests/publisher-type-witness.compile.ts` (`@ts-expect-error` proofs under the\ntype-check gate).\n\nA **runtime default-deny** second lock (`composeBody`) covers dynamic / fixture\npaths: the descriptor's domain mode is consulted unconditionally, and a missing\ncomposer for an encrypted domain, or an unknown domain, fails loud before any\nsocket write. The body is composed once (a resend reuses the exact bytes — no\nre-encrypt under a fresh nonce). The host wires KC-backed composer instances\ninto `createPublisher({ crypto })` (and `CryptoBodyOpener` on the consume path)\nusing `@dcsv-io/d2-encryption` sealer / opener / symmetric ports.\n\nOn the consume side, `CryptoBodyOpener` (sealed / symmetric) plugs the real\ncrypto into the body-decompose seam: a wrong-version frame, a plaintext body on\nan encrypted domain, tampering, or an unknown kid all DLQ with `DECRYPT_FAILURE`\n(never a silent mis-decode); `assertOpenerMatchesDomain` is the consumer-side\nsubscriber-vs-opener cross-check.\n\n---\n\n## Quick start\n\n```ts\nimport {\n  createConnection,\n  subscribe,\n  QueuePattern,\n  InMemoryMessageIdempotencyStore,\n} from \"@dcsv-io/d2-messaging-rabbitmq\";\n\nconst connection = createConnection({\n  connectionUri: process.env.D2_RABBITMQ_URI!, // secret — never logged whole\n  clientProvidedName: \"audit-svc\",\n});\n\nconst sub = subscribe({\n  connection,\n  logger,\n  store: new InMemoryMessageIdempotencyStore(),\n  descriptor: {\n    queueName: \"audit.key-rotated\",\n    exchange: \"d2.security.key-rotated\",\n    exchangeType: \"fanout\",\n    pattern: QueuePattern.DurableShared,\n    routingKeyBinding: \"\",\n    prefetch: 8,\n    idempotency: true,\n    nackedBy: \"audit-svc\",\n  },\n  handler: (message, ctx) => {\n    // `message` = the decoded body; `ctx.propagated` = the per-message\n    // operational context (requestId / fingerprints / callPath — never identity).\n    return doWork(message);\n  },\n});\n\nawait sub.ready;\n```\n\nThe handler returns a `D2Result`. A failed result dead-letters the message\n(`HANDLER_RESULT_FAILURE`); a thrown error dead-letters it\n(`HANDLER_EXCEPTION`). Success acks — after writing the idempotency mark.\n\n---\n\n## What the runtime does (per delivery)\n\n1. **Trace linkage** — parses the producer's `traceparent` and starts a\n `Consumer`-kind span `receive {queue}` whose parent is the publish span, so\n the trace assembles across runtimes. A missing / malformed header starts a\n root span (never a reject). Span tags come from the spec-emitted\n `MessagingActivityTags` closed set (same values the .NET consumer emits).\n2. **Per-message context** — decodes the `x-d2-context` header\n (base64url-of-JSON, exactly what the .NET `PropagatedContextSerializer.Encode`\n and the gRPC interceptor produce) via the shared\n `@dcsv-io/d2-request-context-abstractions` serializer and applies the operational\n subset (request id / path / fingerprints / WhoIs hash / locale-tier fields /\n `callPath`) onto a fresh per-message context. **Identity is never taken from\n the wire**, and **`RequestOrigin` is never wire-reconstructed** — those\n slots do not exist on the applied shape (authority-grade origin is established\n only from local transport evidence, never from a forwarded header). A\n malformed header is fail-safe (empty context, message still processed).\n3. **Idempotency** (opt-in) — a precise 5-point contract mirroring .NET: a\n seen `message-id` is **ack-and-skipped, never dead-lettered**; a read-path\n store outage fails **open** (process anyway); the mark is written only on\n the success path **before** the ack; a mark-write failure NACKs to the DLQ\n (never leave the dedup window unguarded); failure paths never mark.\n4. **Body decompose** — an injectable opener seam. The default handles\n plaintext (raw UTF-8 JSON) and **fail-louds** any body whose first byte is a\n known encryption-frame version (1 or 2) → `DECRYPT_FAILURE` → DLQ, never a\n silent mis-parse.\n5. **Dead-lettering** — on failure the original body is republished to\n `{queue}.dlx` with an `x-d2-failure-reason` header (`DlqFailureMetadata`:\n `cause` / `errorCode` / `detail` / `attemptCount` / `traceId` / `nackedBy`,\n PII-safe) then the original is acked; a republish failure falls back to\n NACK-no-requeue. Producer headers (`traceparent`, `x-d2-context`, ...) ride\n forward on the DLQ copy.\n\n---\n\n## Topology\n\n`subscribe` declares the exact .NET topology (see\n`DlqNaming`):\n\n- primary queue with `x-dead-letter-exchange = {queue}.dlx`\n- `{queue}.dlx` fanout DLX → `{queue}.dlq` durable DLQ\n- optional retry tiers (`{queue}.retry.{i}` + `{queue}.retry.return`)\n\nQueue patterns: `CompetingConsumer`, `DurableShared`,\n`FanoutExclusiveAutoDelete` (auto-suffixed per process to avoid the exclusive\nqueue lock).\n\n---\n\n## Testing\n\n- **Unit** (`pnpm test`) — the full delivery matrix against injected seams;\n 100% `src/**` coverage.\n- **Integration** (`pnpm test:integration`) — a Testcontainer RabbitMQ replaying\n **real .NET-emitted golden messages** (emitted by\n `DcsvIo.D2.Tests` `Integration/ContractFixtures/MqGoldenMessageFixtureEmitter`\n into `contract-tests/fixtures/mq-messages-golden/`): wire-contract consume,\n encrypted-frame → DLQ, handler-failure DLQ metadata, idempotency dedup, and\n competing consumers.\n- **Descriptor mirror** — `MqMessages` / `MqMessagesRegistry` (in\n `@dcsv-io/d2-messaging-abstractions`, generated from the messaging specs; sources committed)\n is asserted byte-equal to the .NET `MqMessagesRegistry` by\n `contract-tests/tests/mq-messages.parity.test.ts`.\n\n---\n\n## Dependencies\n\n- `rabbitmq-client` — the only vendor dep (transport).\n- `@dcsv-io/d2-headers-amqp` — AMQP header wire-value constants.\n- `@dcsv-io/d2-messaging-abstractions` — `DlqFailureMetadataFields` / `DlqFailureCauses`\n + the `MqMessages` descriptor mirror.\n- `@dcsv-io/d2-request-context-abstractions` — `PropagatedContextSerializer` +\n `IPropagatedContext`.\n- `@dcsv-io/d2-encryption-abstractions` — frame-version constants (fail-loud guard).\n- `@dcsv-io/d2-telemetry` — the `MessagingActivityTags` span-tag catalog.\n- `@dcsv-io/d2-result`, `@dcsv-io/d2-logging`, `@dcsv-io/d2-utilities` — cross-cutting.\n","readmeFilename":"README.md"}