{"_id":"@emmett-community/emmett-google-pubsub","_rev":"5-ec34480779688bc778dad105a3b8421b","name":"@emmett-community/emmett-google-pubsub","dist-tags":{"latest":"0.5.0"},"versions":{"0.1.0":{"name":"@emmett-community/emmett-google-pubsub","version":"0.1.0","keywords":["emmett","event-sourcing","google-pubsub","message-bus","cqrs","event-driven","ddd"],"author":{"name":"Andre X Costa"},"license":"MIT","_id":"@emmett-community/emmett-google-pubsub@0.1.0","maintainers":[{"name":"axcosta","email":"axcosta@gmail.com"}],"homepage":"https://github.com/emmett-community/emmett-google-pubsub#readme","bugs":{"url":"https://github.com/emmett-community/emmett-google-pubsub/issues"},"dist":{"shasum":"a5ccf8fee23b31ecf5d6addffb83f90fa3ecd905","tarball":"https://registry.npmjs.org/@emmett-community/emmett-google-pubsub/-/emmett-google-pubsub-0.1.0.tgz","fileCount":15,"integrity":"sha512-viGK/E9TCWFXqctwgLHOXPcvPxZb+Ot73xD3Znlkok3cicLKbqWydudskzZMIOshXO/dS316oaaI9Mn8WiLPVw==","signatures":[{"sig":"MEYCIQDEukjU7GWbAh0uq+VuRw3g1O2hOPoqs58cLLhK4ZEhrgIhAJakAW/rkuP9KNWLqNn5PwuJqNf6LsExKD58aNl/WwbA","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":211557},"main":"./dist/index.js","types":"./dist/index.d.ts","module":"./dist/index.mjs","engines":{"node":">=18.0.0"},"exports":{".":{"import":{"types":"./dist/index.d.mts","default":"./dist/index.mjs"},"require":{"types":"./dist/index.d.ts","default":"./dist/index.js"}},"./testing":{"import":{"types":"./dist/testing/index.d.mts","default":"./dist/testing/index.mjs"},"require":{"types":"./dist/testing/index.d.ts","default":"./dist/testing/index.js"}}},"gitHead":"addcd96f7ee803f9456c526a3198e73487d0eeb6","scripts":{"lint":"eslint . --ext .ts","test":"jest","build":"tsup","clean":"rm -rf dist","format":"prettier --write \"src/**/*.ts\" \"test/**/*.ts\"","build:ts":"tsc --noEmit","lint:fix":"eslint . --ext .ts --fix","test:unit":"jest test/unit","typecheck":"tsc --noEmit","test:watch":"jest --watch","format:check":"prettier --check \"src/**/*.ts\" \"test/**/*.ts\"","test:coverage":"jest --coverage","prepublishOnly":"npm run clean && npm run build","test:integration":"jest test/integration"},"_npmUser":{"name":"axcosta","email":"axcosta@gmail.com"},"repository":{"url":"git+https://github.com/emmett-community/emmett-google-pubsub.git","type":"git"},"_npmVersion":"11.1.0","description":"Google Cloud Pub/Sub message bus for Emmett - Event Sourcing development made simple","directories":{},"_nodeVersion":"22.11.0","dependencies":{"@google-cloud/pubsub":"^4.8.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^29.7.0","tsup":"^8.3.5","eslint":"^9.16.0","ts-jest":"^29.2.5","ts-node":"^10.9.2","prettier":"^3.4.2","typescript":"^5.7.2","@types/jest":"^29.5.14","@types/node":"^22.10.1","eslint-config-prettier":"^9.1.0","eslint-plugin-prettier":"^5.2.1","@event-driven-io/emmett":"^0.39.1","@typescript-eslint/parser":"^8.17.0","@typescript-eslint/eslint-plugin":"^8.17.0"},"peerDependencies":{"@event-driven-io/emmett":"^0.39.0"},"_npmOperationalInternal":{"tmp":"tmp/emmett-google-pubsub_0.1.0_1766096548319_0.17982118852357432","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@emmett-community/emmett-google-pubsub","version":"0.2.0","keywords":["emmett","event-sourcing","google-pubsub","message-bus","cqrs","event-driven","ddd"],"author":{"name":"Andre X Costa"},"license":"MIT","_id":"@emmett-community/emmett-google-pubsub@0.2.0","maintainers":[{"name":"axcosta","email":"axcosta@gmail.com"}],"homepage":"https://github.com/emmett-community/emmett-google-pubsub#readme","bugs":{"url":"https://github.com/emmett-community/emmett-google-pubsub/issues"},"dist":{"shasum":"4661b1aed0e7fdbbe6c7d499f5aafd564ef00a7c","tarball":"https://registry.npmjs.org/@emmett-community/emmett-google-pubsub/-/emmett-google-pubsub-0.2.0.tgz","fileCount":9,"integrity":"sha512-Lyyq880I7+Ty8ELOi1B2yZPPA80njk5ROB8TxDFAYqSUmOR7Ek3KjpyFY27NUyx4HmmfySoOiT42z5V60Zcspw==","signatures":[{"sig":"MEUCIQC2RXAByoqtcwVJn+3WrEt8+vguMbNN0+pDG4Ug5ovObwIgFuH+G5KSRCasjCyAw2iEUs6mcgzpReX4FtMuH5oQne0=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@emmett-community%2femmett-google-pubsub@0.2.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":212582},"main":"./dist/index.js","types":"./dist/index.d.ts","module":"./dist/index.mjs","engines":{"node":">=18.0.0"},"exports":{".":{"import":{"types":"./dist/index.d.mts","default":"./dist/index.mjs"},"require":{"types":"./dist/index.d.ts","default":"./dist/index.js"}}},"gitHead":"f8cacc60b4e918b4d6e6ee7f88e320ff428f9727","scripts":{"lint":"eslint . --ext .ts","test":"npm run test:unit && npm run test:int && npm run test:e2e","build":"tsup","clean":"rm -rf dist","format":"prettier --write \"src/**/*.ts\" \"test/**/*.ts\"","build:ts":"tsc --noEmit","lint:fix":"eslint . --ext .ts --fix","test:e2e":"jest --testMatch \"**/*.e2e.spec.ts\" --watchman=false","test:int":"jest --testMatch \"**/*.int.spec.ts\" --watchman=false","test:unit":"jest --testMatch \"**/*.unit.spec.ts\" --watchman=false","typecheck":"tsc --noEmit","test:watch":"jest --watch --watchman=false","format:check":"prettier --check \"src/**/*.ts\" \"test/**/*.ts\"","test:coverage":"jest --coverage --watchman=false","prepublishOnly":"npm run clean && npm run build","test:integration":"npm run test:int"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:89399104-18f8-4a62-9ef7-f29ee040737c"}},"repository":{"url":"git+https://github.com/emmett-community/emmett-google-pubsub.git","type":"git"},"_npmVersion":"11.6.2","description":"Google Cloud Pub/Sub message bus for Emmett - Event Sourcing development made simple","directories":{},"_nodeVersion":"24.12.0","dependencies":{"@google-cloud/pubsub":"^4.8.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^29.7.0","tsup":"^8.3.5","eslint":"^9.16.0","ts-jest":"^29.2.5","ts-node":"^10.9.2","prettier":"^3.4.2","typescript":"^5.7.2","@types/jest":"^29.5.14","@types/node":"^22.10.1","testcontainers":"^10.28.0","eslint-config-prettier":"^9.1.0","eslint-plugin-prettier":"^5.2.1","@event-driven-io/emmett":"^0.39.1","@typescript-eslint/parser":"^8.17.0","@typescript-eslint/eslint-plugin":"^8.17.0"},"peerDependencies":{"@event-driven-io/emmett":"^0.39.0"},"_npmOperationalInternal":{"tmp":"tmp/emmett-google-pubsub_0.2.0_1766876889190_0.7893019000360371","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@emmett-community/emmett-google-pubsub","version":"0.3.0","keywords":["emmett","event-sourcing","google-pubsub","message-bus","cqrs","event-driven","ddd"],"author":{"name":"Andre X Costa"},"license":"MIT","_id":"@emmett-community/emmett-google-pubsub@0.3.0","maintainers":[{"name":"axcosta","email":"axcosta@gmail.com"}],"homepage":"https://github.com/emmett-community/emmett-google-pubsub#readme","bugs":{"url":"https://github.com/emmett-community/emmett-google-pubsub/issues"},"dist":{"shasum":"524959fb431a8efe5f96f14a48bd6a04f19ef387","tarball":"https://registry.npmjs.org/@emmett-community/emmett-google-pubsub/-/emmett-google-pubsub-0.3.0.tgz","fileCount":9,"integrity":"sha512-XXAGY/iK/RqXwrYRjGvNl0/Q/fmpdXHS4OwOPd3LPWNL0wULpvo0IwhL8DF2jIxwF/Y9B3bSnh/vL8ougtID1A==","signatures":[{"sig":"MEUCIQDmjH6liIccFtvOS6lH1QrXk/AHIABBxbQlnum2NbDu7QIgCRZrnhnPaXpu1ehCtRZIyZboLgkc5UW9AwOglZKOjVQ=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@emmett-community%2femmett-google-pubsub@0.3.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":234894},"main":"./dist/index.js","types":"./dist/index.d.ts","module":"./dist/index.mjs","engines":{"node":">=18.0.0"},"exports":{".":{"import":{"types":"./dist/index.d.mts","default":"./dist/index.mjs"},"require":{"types":"./dist/index.d.ts","default":"./dist/index.js"}}},"gitHead":"cc01f6c9f906346b8c2b7986902b2528da581476","scripts":{"lint":"eslint . --ext .ts","test":"npm run test:unit && npm run test:int && npm run test:e2e","build":"tsup","clean":"rm -rf dist","format":"prettier --write \"src/**/*.ts\" \"test/**/*.ts\"","build:ts":"tsc --noEmit","lint:fix":"eslint . --ext .ts --fix","test:e2e":"jest --testMatch \"**/*.e2e.spec.ts\" --watchman=false","test:int":"jest --testMatch \"**/*.int.spec.ts\" --watchman=false","test:unit":"jest --testMatch \"**/*.unit.spec.ts\" --watchman=false","typecheck":"tsc --noEmit","test:watch":"jest --watch --watchman=false","format:check":"prettier --check \"src/**/*.ts\" \"test/**/*.ts\"","test:coverage":"jest --coverage --watchman=false","prepublishOnly":"npm run clean && npm run build","test:integration":"npm run test:int"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:89399104-18f8-4a62-9ef7-f29ee040737c"}},"repository":{"url":"git+https://github.com/emmett-community/emmett-google-pubsub.git","type":"git"},"_npmVersion":"11.6.2","description":"Google Cloud Pub/Sub message bus for Emmett - Event Sourcing development made simple","directories":{},"_nodeVersion":"24.12.0","dependencies":{"@opentelemetry/api":"^1.7.0","@google-cloud/pubsub":"^4.8.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^29.7.0","tsup":"^8.3.5","eslint":"^9.16.0","ts-jest":"^29.2.5","ts-node":"^10.9.2","prettier":"^3.4.2","typescript":"^5.7.2","@types/jest":"^29.5.14","@types/node":"^22.10.1","testcontainers":"^10.28.0","eslint-config-prettier":"^9.1.0","eslint-plugin-prettier":"^5.2.1","@event-driven-io/emmett":"^0.39.1","@typescript-eslint/parser":"^8.17.0","@typescript-eslint/eslint-plugin":"^8.17.0"},"peerDependencies":{"@event-driven-io/emmett":"^0.39.0"},"_npmOperationalInternal":{"tmp":"tmp/emmett-google-pubsub_0.3.0_1767310344260_0.18438147533461668","host":"s3://npm-registry-packages-npm-production"}},"0.4.0":{"name":"@emmett-community/emmett-google-pubsub","version":"0.4.0","keywords":["emmett","event-sourcing","google-pubsub","message-bus","cqrs","event-driven","ddd"],"author":{"name":"Andre X Costa"},"license":"MIT","_id":"@emmett-community/emmett-google-pubsub@0.4.0","maintainers":[{"name":"axcosta","email":"axcosta@gmail.com"}],"homepage":"https://github.com/emmett-community/emmett-google-pubsub#readme","bugs":{"url":"https://github.com/emmett-community/emmett-google-pubsub/issues"},"dist":{"shasum":"d1834eb4a6b330d830a79ad8081160c941cb0bf5","tarball":"https://registry.npmjs.org/@emmett-community/emmett-google-pubsub/-/emmett-google-pubsub-0.4.0.tgz","fileCount":9,"integrity":"sha512-/QLqW8vFn9ErN1ksZivMWc+FVRX/pbO5EoF+eJg0mpTKdz9KL2lJQA++10cqa4tLyAIn4On9z4FKkffDtGypJQ==","signatures":[{"sig":"MEQCIEHwXJK7D/g097cGtuksPZHy+OWHO5XCrwWoty6AYxcWAiB1pgZvP+OcnRkbzAuWzymWnYYv0txZgqvTgXH7RO5ZWg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@emmett-community%2femmett-google-pubsub@0.4.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":234894},"main":"./dist/index.js","types":"./dist/index.d.ts","module":"./dist/index.mjs","engines":{"node":">=18.0.0"},"exports":{".":{"import":{"types":"./dist/index.d.mts","default":"./dist/index.mjs"},"require":{"types":"./dist/index.d.ts","default":"./dist/index.js"}}},"gitHead":"694a1eca2e29215adfaa1aac3b058903bf9f663b","scripts":{"lint":"eslint . --ext .ts","test":"npm run test:unit && npm run test:int && npm run test:e2e","build":"tsup","clean":"rm -rf dist","format":"prettier --write \"src/**/*.ts\" \"test/**/*.ts\"","build:ts":"tsc --noEmit","lint:fix":"eslint . --ext .ts --fix","test:e2e":"jest --testMatch \"**/*.e2e.spec.ts\" --watchman=false","test:int":"jest --testMatch \"**/*.int.spec.ts\" --watchman=false","test:unit":"jest --testMatch \"**/*.unit.spec.ts\" --watchman=false","typecheck":"tsc --noEmit","test:watch":"jest --watch --watchman=false","format:check":"prettier --check \"src/**/*.ts\" \"test/**/*.ts\"","test:coverage":"jest --coverage --watchman=false","prepublishOnly":"npm run clean && npm run build","test:integration":"npm run test:int"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:89399104-18f8-4a62-9ef7-f29ee040737c"}},"repository":{"url":"git+https://github.com/emmett-community/emmett-google-pubsub.git","type":"git"},"_npmVersion":"11.6.2","description":"Google Cloud Pub/Sub message bus for Emmett - Event Sourcing development made simple","directories":{},"_nodeVersion":"24.12.0","dependencies":{"@opentelemetry/api":"^1.7.0","@google-cloud/pubsub":"^4.8.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^29.7.0","tsup":"^8.3.5","eslint":"^9.16.0","ts-jest":"^29.2.5","ts-node":"^10.9.2","prettier":"^3.4.2","typescript":"^5.7.2","@types/jest":"^29.5.14","@types/node":"^22.10.1","testcontainers":"^10.28.0","eslint-config-prettier":"^9.1.0","eslint-plugin-prettier":"^5.2.1","@event-driven-io/emmett":"^0.39.1","@typescript-eslint/parser":"^8.17.0","@typescript-eslint/eslint-plugin":"^8.17.0"},"peerDependencies":{"@event-driven-io/emmett":"^0.39.0"},"_npmOperationalInternal":{"tmp":"tmp/emmett-google-pubsub_0.4.0_1768511999626_0.1293986185337308","host":"s3://npm-registry-packages-npm-production"}},"0.5.0":{"_id":"@emmett-community/emmett-google-pubsub@0.5.0","bugs":{"url":"https://github.com/emmett-community/emmett-google-pubsub/issues"},"dist":{"shasum":"92d150ac7934e6da6ab8b3509ef496113db5a1fe","tarball":"https://registry.npmjs.org/@emmett-community/emmett-google-pubsub/-/emmett-google-pubsub-0.5.0.tgz","fileCount":9,"integrity":"sha512-gcGBzKfwCI1nkNF6tBJ4ISQcapRSdbmbaiyobsySYUXs+NSSGWGaNcHGmgH942jD92+Lse2qZFte+BJnHHk4wg==","signatures":[{"sig":"MEQCIDqsMeKSATqF3nHNxqu8PgOE7rda3jPeGJoKBs5kISD7AiBsZ64c7zwfEOt0kETTqIjghoJmhqhsm8yET7dGZqfkdQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCICm6c44K2R8oqlnq/Lsi4n/xuy5s5iTcEvosQ0xE1a1IAiEAp5i/R8Ns92S2pqI5DLjD9ND8dteBdNlOr5SQQ10Uc1A="}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@emmett-community%2femmett-google-pubsub@0.5.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":266811},"main":"./dist/index.js","name":"@emmett-community/emmett-google-pubsub","types":"./dist/index.d.ts","author":{"name":"Andre X Costa"},"module":"./dist/index.mjs","engines":{"node":">=18.0.0"},"exports":{".":{"import":{"types":"./dist/index.d.mts","default":"./dist/index.mjs"},"require":{"types":"./dist/index.d.ts","default":"./dist/index.js"}}},"gitHead":"15f9cbcec1ea6141428b7334a699e629321a4cf4","license":"MIT","scripts":{"lint":"eslint . --ext .ts","test":"npm run test:unit && npm run test:int && npm run test:e2e","build":"tsup","clean":"rm -rf dist","format":"prettier --write \"src/**/*.ts\" \"test/**/*.ts\"","build:ts":"tsc --noEmit","lint:fix":"eslint . --ext .ts --fix","test:e2e":"jest --testMatch \"**/*.e2e.spec.ts\" --watchman=false","test:int":"jest --testMatch \"**/*.int.spec.ts\" --watchman=false","test:unit":"jest --testMatch \"**/*.unit.spec.ts\" --watchman=false","typecheck":"tsc --noEmit","test:watch":"jest --watch --watchman=false","format:check":"prettier --check \"src/**/*.ts\" \"test/**/*.ts\"","test:coverage":"jest --coverage --watchman=false","prepublishOnly":"npm run clean && npm run build","test:integration":"npm run test:int"},"version":"0.5.0","_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:89399104-18f8-4a62-9ef7-f29ee040737c"}},"homepage":"https://github.com/emmett-community/emmett-google-pubsub#readme","keywords":["emmett","event-sourcing","google-pubsub","message-bus","cqrs","event-driven","ddd"],"repository":{"url":"git+https://github.com/emmett-community/emmett-google-pubsub.git","type":"git"},"_npmVersion":"11.19.0","description":"Google Cloud Pub/Sub message bus for Emmett - Event Sourcing development made simple","directories":{},"maintainers":[{"name":"axcosta","email":"axcosta@gmail.com"}],"_nodeVersion":"24.21.0","dependencies":{"@opentelemetry/api":"^1.7.0","@google-cloud/pubsub":"^4.8.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^29.7.0","tsup":"^8.3.5","eslint":"^9.16.0","ts-jest":"^29.2.5","ts-node":"^10.9.2","prettier":"^3.4.2","typescript":"^5.7.2","@types/jest":"^29.5.14","@types/node":"^22.10.1","testcontainers":"^10.28.0","eslint-config-prettier":"^9.1.0","eslint-plugin-prettier":"^5.2.1","@event-driven-io/emmett":"^0.39.1","@typescript-eslint/parser":"^8.17.0","@typescript-eslint/eslint-plugin":"^8.17.0"},"peerDependencies":{"@event-driven-io/emmett":"^0.39.0"},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/emmett-google-pubsub_0.5.0_1790476989557_0.3953351235406919"}}},"time":{"created":"2025-12-18T22:22:28.187Z","modified":"2026-09-27T02:43:09.971Z","0.1.0":"2025-12-18T22:22:28.463Z","0.2.0":"2025-12-27T23:08:09.341Z","0.3.0":"2026-01-01T23:32:24.428Z","0.4.0":"2026-01-15T21:19:59.934Z","0.5.0":"2026-09-27T02:43:09.646Z"},"bugs":{"url":"https://github.com/emmett-community/emmett-google-pubsub/issues"},"author":{"name":"Andre X Costa"},"license":"MIT","homepage":"https://github.com/emmett-community/emmett-google-pubsub#readme","keywords":["emmett","event-sourcing","google-pubsub","message-bus","cqrs","event-driven","ddd"],"repository":{"url":"git+https://github.com/emmett-community/emmett-google-pubsub.git","type":"git"},"description":"Google Cloud Pub/Sub message bus for Emmett - Event Sourcing development made simple","maintainers":[{"name":"axcosta","email":"axcosta@gmail.com"}],"readme":"# @emmett-community/emmett-google-pubsub\n\nGoogle Cloud Pub/Sub message bus implementation for [Emmett](https://event-driven-io.github.io/emmett/), the Node.js event sourcing framework.\n\n[![npm version](https://img.shields.io/npm/v/@emmett-community/emmett-google-pubsub.svg)](https://www.npmjs.com/package/@emmett-community/emmett-google-pubsub) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n\n## Features\n\n- ✅ **Distributed Message Bus** - Scale command/event handling across multiple instances\n- ✅ **Type-Safe** - Full TypeScript support with comprehensive types\n- ✅ **Automatic Topic Management** - Auto-creates topics and subscriptions\n- ✅ **Message Scheduling** - Schedule commands/events for future execution\n- ✅ **Error Handling** - Built-in retry logic and dead letter queue support\n- ✅ **Connection Recovery** - Watchdog detects and recreates subscriptions that go silently dead\n- ✅ **Emulator Support** - Local development with PubSub emulator\n- ✅ **Emmett Compatible** - Drop-in replacement for in-memory message bus\n- ✅ **Producer-Only Mode** - Use without starting consumers\n\n## Installation\n\n```bash\nnpm install @emmett-community/emmett-google-pubsub @google-cloud/pubsub\n```\n\n### Peer Dependencies\n\n- `@event-driven-io/emmett` ^0.39.0\n\n## Quick Start\n\n```typescript\nimport { PubSub } from '@google-cloud/pubsub';\nimport { getPubSubMessageBus } from '@emmett-community/emmett-google-pubsub';\n\n// Initialize PubSub client\nconst pubsub = new PubSub({ projectId: 'your-project-id' });\n\n// Create message bus\nconst messageBus = getPubSubMessageBus({ pubsub });\n\n// Register command handler\nmessageBus.handle(async (command) => {\n  console.log('Processing:', command.type, command.data);\n}, 'AddProductItem');\n\n// Subscribe to events\nmessageBus.subscribe(async (event) => {\n  console.log('Received:', event.type, event.data);\n}, 'ProductItemAdded');\n\n// Start listening\nawait messageBus.start();\n\n// Send commands and publish events\nawait messageBus.send({\n  type: 'AddProductItem',\n  data: { productId: '123', quantity: 2 },\n});\n\nawait messageBus.publish({\n  type: 'ProductItemAdded',\n  data: { productId: '123', quantity: 2 },\n});\n```\n\n## How It Works\n\n### Topic/Subscription Strategy\n\nThe message bus uses a **topic-per-type** strategy:\n\n```\nCommands (1-to-1):\n  Topic: {prefix}-cmd-{CommandType}\n  Subscription: {prefix}-cmd-{CommandType}-{instanceId}\n  → Only ONE handler processes each command\n\nEvents (1-to-many):\n  Topic: {prefix}-evt-{EventType}\n  Subscription: {prefix}-evt-{EventType}-{subscriberId}\n  → ALL subscribers receive each event\n```\n\n**Example topic names:**\n\n```\nemmett-cmd-AddProductItem\nemmett-cmd-AddProductItem-instance-abc123\n\nemmett-evt-ProductItemAdded\nemmett-evt-ProductItemAdded-subscriber-xyz789\n```\n\n### Message Lifecycle\n\n```\n1. REGISTRATION     2. STARTUP          3. RUNTIME           4. SHUTDOWN\n   handle()            start()             send/publish         close()\n   subscribe()         → Create topics     → Route messages     → Stop listeners\n                       → Create subs       → Execute handlers   → Cleanup\n                       → Attach listeners  → Ack/Nack\n```\n\n### Producer-Only Mode\n\nYou can use the message bus to only produce messages without consuming:\n\n```typescript\nconst messageBus = getPubSubMessageBus({ pubsub });\n\n// No handlers, no start() needed\nawait messageBus.send({ type: 'MyCommand', data: {} });\nawait messageBus.publish({ type: 'MyEvent', data: {} });\n```\n\n### Connection Recovery\n\nThe underlying streaming-pull connection can go silently dead after a network interruption (e.g. an idle NAT/firewall timeout) without ever emitting the subscription's `error` event. Left unhandled, this stops message delivery permanently until the process is restarted.\n\nA watchdog runs alongside every subscription, periodically checking it is still reachable and transparently recreating it (and its topic, if that was lost too) when a check fails. It's enabled by default:\n\n```typescript\nconst messageBus = getPubSubMessageBus({\n  pubsub,\n  subscriptionOptions: {\n    watchdog: {\n      enabled: true,          // default: true\n      checkIntervalMs: 30000, // default: 30000\n      checkTimeoutMs: 5000,   // default: 5000\n    },\n  },\n});\n```\n\nThe same failure mode can also strike the initial topic/subscription setup performed during `start()` - if the connection is already dead when the message bus starts, setup calls have no timeout of their own and could hang indefinitely. `setupTimeoutMs` (default: 10000) bounds those calls too.\n\n## API Reference\n\n### `getPubSubMessageBus(config)`\n\nCreates a message bus instance.\n\n```typescript\nconst messageBus = getPubSubMessageBus({\n  pubsub,                          // Required: PubSub client\n  topicPrefix: 'myapp',            // Topic name prefix (default: \"emmett\")\n  instanceId: 'worker-1',          // Instance ID (default: auto-generated)\n  useEmulator: true,               // Emulator mode (default: false)\n  autoCreateResources: true,       // Auto-create topics/subs (default: true)\n  cleanupOnClose: false,           // Delete subs on close (default: false)\n  closePubSubClient: true,         // Close PubSub on close (default: true)\n  subscriptionOptions: {           // Subscription config\n    ackDeadlineSeconds: 60,\n    retryPolicy: {\n      minimumBackoff: { seconds: 10 },\n      maximumBackoff: { seconds: 600 },\n    },\n    deadLetterPolicy: {\n      deadLetterTopic: 'projects/.../topics/dead-letters',\n      maxDeliveryAttempts: 5,\n    },\n    watchdog: { enabled: true },   // Recreate dead subscriptions (default: enabled)\n    setupTimeoutMs: 10000,         // Timeout for topic/subscription setup calls\n  },\n});\n```\n\n### Methods\n\n| Method | Description |\n|--------|-------------|\n| `send(command)` | Send a command (1-to-1) |\n| `publish(event)` | Publish an event (1-to-many) |\n| `handle(handler, ...types)` | Register command handler |\n| `subscribe(handler, ...types)` | Subscribe to events |\n| `schedule(message, options)` | Schedule for future delivery |\n| `dequeue()` | Get scheduled messages (emulator only) |\n| `start()` | Start listening for messages |\n| `close()` | Graceful shutdown |\n| `isStarted()` | Check if running |\n\nSee [docs/API.md](./docs/API.md) for complete API documentation.\n\n## Configuration\n\n### Basic Configuration\n\n```typescript\nconst messageBus = getPubSubMessageBus({\n  pubsub: new PubSub({ projectId: 'my-project' }),\n  topicPrefix: 'orders',\n});\n```\n\n### Emulator Configuration\n\n```typescript\n// Set environment variable\nprocess.env.PUBSUB_EMULATOR_HOST = 'localhost:8085';\n\nconst pubsub = new PubSub({ projectId: 'demo-project' });\nconst messageBus = getPubSubMessageBus({\n  pubsub,\n  useEmulator: true,  // Enables in-memory scheduling\n});\n```\n\n### Production Configuration\n\n```typescript\nconst pubsub = new PubSub({\n  projectId: process.env.GCP_PROJECT_ID,\n  // Uses Application Default Credentials or Workload Identity\n});\n\nconst messageBus = getPubSubMessageBus({\n  pubsub,\n  topicPrefix: 'prod-myapp',\n  subscriptionOptions: {\n    ackDeadlineSeconds: 120,\n    retryPolicy: {\n      minimumBackoff: { seconds: 5 },\n      maximumBackoff: { seconds: 300 },\n    },\n  },\n});\n```\n\n## Testing\n\n### Testing Utilities\n\n```typescript\nimport { PubSub } from '@google-cloud/pubsub';\nimport { getPubSubMessageBus } from '@emmett-community/emmett-google-pubsub';\n\ndescribe('My Tests', () => {\n  let pubsub: PubSub;\n  let messageBus: ReturnType<typeof getPubSubMessageBus>;\n\n  beforeAll(() => {\n    pubsub = new PubSub({ projectId: 'test-project' });\n  });\n\n  beforeEach(() => {\n    messageBus = getPubSubMessageBus({\n      pubsub,\n      useEmulator: true,\n      topicPrefix: `test-${Date.now()}`,\n      cleanupOnClose: true,\n      closePubSubClient: false,\n    });\n  });\n\n  afterEach(async () => {\n    await messageBus.close();\n  });\n\n  afterAll(async () => {\n    await pubsub.close();\n  });\n\n  it('should handle commands', async () => {\n    const received: unknown[] = [];\n\n    messageBus.handle(async (cmd) => {\n      received.push(cmd);\n    }, 'TestCommand');\n\n    await messageBus.start();\n\n    await messageBus.send({\n      type: 'TestCommand',\n      data: { value: 42 },\n    });\n\n    // Wait for async delivery\n    await new Promise((r) => setTimeout(r, 500));\n\n    expect(received).toHaveLength(1);\n  });\n});\n```\n\n### Running Tests\n\n```bash\n# Unit tests\nnpm run test:unit\n\n# Integration tests (in-memory)\nnpm run test:int\n\n# E2E tests (PubSub emulator via Testcontainers, requires Docker)\nnpm run test:e2e\n\n# All tests\nnpm test\n```\n\nE2E tests start the emulator automatically via Testcontainers.\n\n## Examples\n\n### Complete Shopping Cart Example\n\nSee [examples/shopping-cart](./examples/shopping-cart) for a full application including:\n\n- Event-sourced shopping cart with Firestore\n- Express.js API with OpenAPI spec\n- Docker Compose setup with all emulators\n- Unit, integration, and E2E tests\n\n```bash\ncd examples/shopping-cart\ndocker-compose up\n\n# API: http://localhost:3000\n# Firebase UI: http://localhost:4000\n# PubSub UI: http://localhost:4001\n```\n\n### Multiple Event Subscribers\n\n```typescript\n// Analytics service\nmessageBus.subscribe(async (event) => {\n  await analytics.track(event);\n}, 'OrderCreated');\n\n// Notification service\nmessageBus.subscribe(async (event) => {\n  await email.sendConfirmation(event.data.customerId);\n}, 'OrderCreated');\n\n// Inventory service\nmessageBus.subscribe(async (event) => {\n  await inventory.reserve(event.data.items);\n}, 'OrderCreated');\n\n// All three receive every OrderCreated event\n```\n\n### Scheduled Messages\n\n```typescript\n// Schedule for future\nmessageBus.schedule(\n  { type: 'SendReminder', data: { userId: '123' } },\n  { afterInMs: 24 * 60 * 60 * 1000 }  // 24 hours\n);\n\n// Schedule for specific time\nmessageBus.schedule(\n  { type: 'SendReminder', data: { userId: '123' } },\n  { at: new Date('2024-12-25T10:00:00Z') }\n);\n```\n\nSee [docs/EXAMPLES.md](./docs/EXAMPLES.md) for more examples.\n\n## Architecture\n\n### Message Format\n\nMessages are wrapped in an envelope for transport:\n\n```typescript\ninterface PubSubMessageEnvelope {\n  type: string;           // Message type name\n  kind: 'command' | 'event';\n  data: unknown;          // Serialized data\n  metadata?: unknown;     // Optional metadata\n  timestamp: string;      // ISO 8601\n  messageId: string;      // UUID for idempotency\n}\n```\n\n### Date Serialization\n\nJavaScript `Date` objects are preserved through serialization:\n\n```typescript\n// Original\n{ createdAt: new Date('2024-01-15T10:00:00Z') }\n\n// Serialized\n{ createdAt: { __type: 'Date', value: '2024-01-15T10:00:00.000Z' } }\n\n// Deserialized (restored as Date object)\n{ createdAt: Date('2024-01-15T10:00:00Z') }\n```\n\n### Error Handling\n\n| Scenario | Behavior |\n|----------|----------|\n| Handler succeeds | Message acknowledged |\n| Transient error | Message nack'd, retried with backoff |\n| Permanent error | Message ack'd, logged |\n| No handler | Message nack'd for retry |\n\nSee [docs/ARCHITECTURE.md](./docs/ARCHITECTURE.md) for design decisions.\n\n## Observability\n\nThe package supports optional observability through structured logging and OpenTelemetry tracing.\n\n### Logging\n\nLogging is opt-in and completely silent by default. To enable logging, provide a logger that implements the canonical `(context, message)` contract:\n\n```typescript\nconst messageBus = getPubSubMessageBus({\n  pubsub,\n  observability: {\n    logger: {\n      debug: (context, message) => console.debug(message, context),\n      info: (context, message) => console.info(message, context),\n      warn: (context, message) => console.warn(message, context),\n      error: (context, message) => console.error(message, context),\n    },\n  },\n});\n```\n\n**Logger Contract:**\n\nThe logger MUST implement the canonical `(context, message)` contract:\n\n- `context`: Structured data as `Record<string, unknown>` (first parameter)\n- `message`: Human-readable log message (second parameter, optional)\n\nPino is natively compatible. For Winston, use an adapter.\n\n**Log Levels:**\n\n- `info` - Lifecycle events (start, stop)\n- `debug` - External I/O operations (publish, subscribe)\n- `warn` - Recoverable failures (timeouts, retries)\n- `error` - Failures (with Error objects in `{ err: error }` format)\n\n### Tracing (OpenTelemetry)\n\nThe package creates OpenTelemetry spans for key operations. Tracing is passive - the `@opentelemetry/api` is a no-op by default.\n\nTo enable tracing, configure OpenTelemetry in your application:\n\n```typescript\nimport { NodeSDK } from '@opentelemetry/sdk-node';\nconst sdk = new NodeSDK({ /* config */ });\nsdk.start();\n\n// Spans from emmett-google-pubsub are now captured\nconst messageBus = getPubSubMessageBus({ pubsub });\n```\n\n**Notes:**\n\n- The package never initializes OpenTelemetry\n- No tracing flags needed - spans are always created (no-op if SDK not initialized)\n- Message types and payloads are never included in spans or logs\n\n## Compatibility\n\n- **Node.js**: >= 18.0.0\n- **Emmett**: ^0.39.0\n- **@google-cloud/pubsub**: ^4.8.0\n\n## Contributing\n\nContributions are welcome! Please:\n\n1. Fork the repository\n2. Create a feature branch\n3. Add tests for new functionality\n4. Ensure all tests pass\n5. Submit a pull request\n\n## Development\n\n```bash\n# Install dependencies\nnpm install\n\n# Build\nnpm run build\n\n# Run tests\nnpm test\n\n# Run unit tests only\nnpm run test:unit\n\n# Run integration tests (in-memory)\nnpm run test:int\n\n# Run E2E tests (requires Docker)\nnpm run test:e2e\n\n# Lint\nnpm run lint\n\n# Format\nnpm run format\n```\n\n## License\n\nMIT\n\n## Related Packages\n\n- [@event-driven-io/emmett](https://github.com/event-driven-io/emmett) - Core Emmett framework\n- [@emmett-community/emmett-google-firestore](https://github.com/emmett-community/emmett-google-firestore) - Firestore event store\n- [@emmett-community/emmett-google-realtime-db](https://github.com/emmett-community/emmett-google-realtime-db) - Realtime Database inline projections\n- [@event-driven-io/emmett-mongodb](https://github.com/event-driven-io/emmett/tree/main/src/packages/emmett-mongodb) - MongoDB event store\n\n## Support\n\n- [GitHub Issues](https://github.com/emmett-community/emmett-google-pubsub/issues)\n- [Emmett Documentation](https://event-driven-io.github.io/emmett/)\n\n---\n\nMade with ❤️ by the Emmett Community\n","readmeFilename":"README.md"}