{"_id":"@bymax-one/nest-realtime","_rev":"11-a3b1fa550dfa4f8ffc50c49b6a9d8caa","name":"@bymax-one/nest-realtime","dist-tags":{"latest":"1.2.0"},"versions":{"1.0.0":{"name":"@bymax-one/nest-realtime","version":"1.0.0","author":"Bymax One <support@bymax.one>","license":"MIT","_id":"@bymax-one/nest-realtime@1.0.0","maintainers":[{"name":"bymax.one","email":"bymaxone.core@gmail.com"},{"name":"msalvatti","email":"msalvatti@gmail.com"}],"homepage":"https://github.com/bymaxone/nest-realtime#readme","bugs":{"url":"https://github.com/bymaxone/nest-realtime/issues"},"dist":{"shasum":"79b258bf61a317b2aabf479acaca94a79cf6483b","tarball":"https://registry.npmjs.org/@bymax-one/nest-realtime/-/nest-realtime-1.0.0.tgz","fileCount":16,"integrity":"sha512-nLx6brBpAUwFkc0R4f0hm4TKJdGOirYEcAI36u52g8ghyDQdp1yIuqPypo+J2YLCqI+PEDFLcvsYS2TRBXQqxQ==","signatures":[{"sig":"MEQCICk5VeZ0RcR7xvwrlE0xCxngDn/oCAjIjsEERNQp5ODRAiB8XHvmng5lF0srglPjTRYM3Ys0CHZIvOQzGU8ZSSy9Vw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":393932},"main":"./dist/server/index.cjs","type":"module","types":"./dist/server/index.d.cts","module":"./dist/server/index.mjs","engines":{"node":">=24.0.0"},"exports":{".":{"import":{"types":"./dist/server/index.d.ts","default":"./dist/server/index.mjs"},"require":{"types":"./dist/server/index.d.cts","default":"./dist/server/index.cjs"}},"./react":{"import":{"types":"./dist/react/index.d.ts","default":"./dist/react/index.mjs"},"require":{"types":"./dist/react/index.d.cts","default":"./dist/react/index.cjs"}},"./shared":{"import":{"types":"./dist/shared/index.d.ts","default":"./dist/shared/index.mjs"},"require":{"types":"./dist/shared/index.d.cts","default":"./dist/shared/index.cjs"}},"./package.json":"./package.json"},"scripts":{"lint":"eslint src","size":"node scripts/check-size.mjs","test":"jest","build":"pnpm clean && tsup","clean":"rm -rf dist coverage","release":"pnpm publish --provenance","lint:fix":"eslint src --fix","mutation":"stryker run","test:all":"pnpm test && pnpm test:e2e","test:cov":"jest --config jest.coverage.config.ts --coverage","test:e2e":"jest --config jest.e2e.config.ts","typecheck":"tsc --noEmit && tsc --noEmit -p tsconfig.server.json","test:watch":"jest --watch","test:cov:all":"jest --config jest.coverage.config.ts --coverage","check:exports":"attw --pack . --profile strict","check:published":"node scripts/check-published-surface.mjs","mutation:dry-run":"stryker run --dryRunOnly","mutation:incremental":"stryker run --incremental","test:e2e:cross-instance":"jest --config jest.e2e.cross-instance.config.ts --runInBand"},"_npmUser":{"name":"bymax.one","email":"bymaxone.core@gmail.com"},"repository":{"url":"https://github.com/bymaxone/nest-realtime.git","type":"git"},"description":"Realtime backend → frontend communication for NestJS — dual-transport SSE (default) and WebSocket (opt-in) with a unified server-side API.","directories":{},"sideEffects":false,"_nodeVersion":"24.16.0","publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"typesVersions":{"*":{"react":["./dist/react/index.d.cts"],"shared":["./dist/shared/index.d.cts"]}},"_hasShrinkwrap":false,"devDependencies":{"jest":"^30.0.0","rxjs":"^7.8.0","tsup":"^8.0.0","react":"^19.2.8","tslib":"^2.6.0","eslint":"^10.8.0","ioredis":"^5.0.0","ts-jest":"^29.4.12","ts-node":"^10.9.0","prettier":"^3.9.6","react-dom":"^19.2.8","socket.io":"^4.6.2","supertest":"^7.0.0","typescript":"^5.6.0","@types/jest":"^30.0.0","@types/node":"^26.1.1","eventsource":"^4.1.0","@nestjs/core":"^11.1.28","@types/react":"^19.0.0","ioredis-mock":"^8.0.0","jsonwebtoken":"^9.0.3","@nestjs/common":"^11.1.28","@types/express":"^5.0.0","@nestjs/testing":"^11.1.28","@types/react-dom":"^19.0.0","@types/supertest":"^7.2.1","reflect-metadata":"^0.2.2","socket.io-client":"^4.0.0","@nestjs/websockets":"^11.1.28","@types/ioredis-mock":"^8.2.7","@types/jsonwebtoken":"^9.0.10","eslint-plugin-import":"^2.29.0","@arethetypeswrong/cli":"^0.18.5","@stryker-mutator/core":"^9.6.1","@testing-library/react":"^16.0.0","eslint-config-prettier":"^10.1.8","eslint-plugin-prettier":"^5.0.0","eslint-plugin-security":"^4.0.1","jest-environment-jsdom":"^30.0.0","@nestjs/platform-express":"^11.1.28","@socket.io/redis-adapter":"^8.0.0","@typescript-eslint/parser":"^8.65.0","eslint-plugin-react-hooks":"^7.1.1","@nestjs/platform-socket.io":"^11.1.28","@stryker-mutator/jest-runner":"^9.6.1","@typescript-eslint/eslint-plugin":"^8.65.0","eslint-import-resolver-typescript":"^4.4.5","@stryker-mutator/typescript-checker":"^9.6.1"},"peerDependencies":{"rxjs":"^7.8.0","react":"^19.0.0","ioredis":"^5.0.0","react-dom":"^19.0.0","socket.io":"^4.6.2","@nestjs/core":"^11.1.18","@nestjs/common":"^11.0.16","reflect-metadata":"^0.2.0","socket.io-client":"^4.0.0","@nestjs/websockets":"^11.0.0","@socket.io/redis-adapter":"^8.0.0","@nestjs/platform-socket.io":"^11.0.0"},"peerDependenciesMeta":{"react":{"optional":true},"ioredis":{"optional":true},"react-dom":{"optional":true},"socket.io":{"optional":true},"socket.io-client":{"optional":true},"@nestjs/websockets":{"optional":true},"@socket.io/redis-adapter":{"optional":true},"@nestjs/platform-socket.io":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/nest-realtime_1.0.0_1785675351774_0.3300013583146395","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"@bymax-one/nest-realtime","version":"1.0.1","keywords":["nestjs","realtime","sse","server-sent-events","websocket","socket.io","pubsub","redis","multi-tenant","presence","event-replay","offline-queue","react-hooks","typescript"],"author":{"name":"Bymax One","email":"support@bymax.one"},"license":"MIT","_id":"@bymax-one/nest-realtime@1.0.1","maintainers":[{"name":"bymax.one","email":"bymaxone.core@gmail.com"},{"name":"msalvatti","email":"msalvatti@gmail.com"}],"homepage":"https://github.com/bymaxone/nest-realtime#readme","bugs":{"url":"https://github.com/bymaxone/nest-realtime/issues"},"dist":{"shasum":"2530f990e794568d9f0bf73025bdad5894fc10c8","tarball":"https://registry.npmjs.org/@bymax-one/nest-realtime/-/nest-realtime-1.0.1.tgz","fileCount":16,"integrity":"sha512-iOHsRrEr7psEa/WfOlW0DYUPXOcfdCRa2TzRc9JlsRbVtgq0qO25g1UZg1aB7N1NofGN9O0K6zke2wEvEypw8Q==","signatures":[{"sig":"MEUCIQCuco6rNB5I4kBWNn4YJfOjFMhXL/7/PkU3vqnvQnETOgIgJxL0bsTD7WXmaDDAtYbVpz5iTBl0FUtowoX0kg+7Z4I=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bymax-one%2fnest-realtime@1.0.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":394063},"main":"./dist/server/index.cjs","type":"module","types":"./dist/server/index.d.cts","module":"./dist/server/index.mjs","engines":{"node":">=24.0.0"},"exports":{".":{"import":{"types":"./dist/server/index.d.ts","default":"./dist/server/index.mjs"},"require":{"types":"./dist/server/index.d.cts","default":"./dist/server/index.cjs"}},"./react":{"import":{"types":"./dist/react/index.d.ts","default":"./dist/react/index.mjs"},"require":{"types":"./dist/react/index.d.cts","default":"./dist/react/index.cjs"}},"./shared":{"import":{"types":"./dist/shared/index.d.ts","default":"./dist/shared/index.mjs"},"require":{"types":"./dist/shared/index.d.cts","default":"./dist/shared/index.cjs"}},"./package.json":"./package.json"},"gitHead":"e476e22f8e3f26f677f612b884ec4b7d836a9bc7","scripts":{"lint":"eslint src","size":"node scripts/check-size.mjs","test":"jest","build":"pnpm clean && tsup","clean":"rm -rf dist coverage","release":"pnpm publish --provenance","lint:fix":"eslint src --fix","mutation":"stryker run","test:all":"pnpm test && pnpm test:e2e","test:cov":"jest --config jest.coverage.config.ts --coverage","test:e2e":"jest --config jest.e2e.config.ts","typecheck":"tsc --noEmit && tsc --noEmit -p tsconfig.server.json","test:watch":"jest --watch","test:cov:all":"jest --config jest.coverage.config.ts --coverage","check:exports":"attw --pack . --profile strict","prepublishOnly":"pnpm clean && pnpm typecheck && pnpm lint && pnpm test:cov:all && pnpm build && pnpm check:published","check:published":"node scripts/check-published-surface.mjs","mutation:dry-run":"stryker run --dryRunOnly","mutation:incremental":"stryker run --incremental","test:e2e:cross-instance":"jest --config jest.e2e.cross-instance.config.ts --runInBand"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:b3e65d80-dbcf-4d5e-a432-1b66b30b7aca"}},"repository":{"url":"git+https://github.com/bymaxone/nest-realtime.git","type":"git"},"_npmVersion":"11.16.0","description":"Realtime backend → frontend communication for NestJS — dual-transport SSE (default) and WebSocket (opt-in) with a unified server-side API.","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"typesVersions":{"*":{"react":["./dist/react/index.d.cts"],"shared":["./dist/shared/index.d.cts"]}},"_hasShrinkwrap":false,"packageManager":"pnpm@11.0.0","devDependencies":{"jest":"^30.0.0","rxjs":"^7.8.0","tsup":"^8.0.0","react":"^19.2.8","tslib":"^2.6.0","eslint":"^10.8.0","ioredis":"^5.0.0","ts-jest":"^29.4.12","ts-node":"^10.9.0","prettier":"^3.9.6","react-dom":"^19.2.8","socket.io":"^4.6.2","supertest":"^7.0.0","typescript":"^5.6.0","@types/jest":"^30.0.0","@types/node":"^26.1.1","eventsource":"^4.1.0","@nestjs/core":"^11.1.28","@types/react":"^19.0.0","ioredis-mock":"^8.0.0","jsonwebtoken":"^9.0.3","@nestjs/common":"^11.1.28","@types/express":"^5.0.0","@nestjs/testing":"^11.1.28","@types/react-dom":"^19.0.0","@types/supertest":"^7.2.1","reflect-metadata":"^0.2.2","socket.io-client":"^4.0.0","@nestjs/websockets":"^11.1.28","@types/ioredis-mock":"^8.2.7","@types/jsonwebtoken":"^9.0.10","eslint-plugin-import":"^2.29.0","@arethetypeswrong/cli":"^0.18.5","@stryker-mutator/core":"^9.6.1","@testing-library/react":"^16.0.0","eslint-config-prettier":"^10.1.8","eslint-plugin-prettier":"^5.0.0","eslint-plugin-security":"^4.0.1","jest-environment-jsdom":"^30.0.0","@nestjs/platform-express":"^11.1.28","@socket.io/redis-adapter":"^8.0.0","@typescript-eslint/parser":"^8.65.0","eslint-plugin-react-hooks":"^7.1.1","@nestjs/platform-socket.io":"^11.1.28","@stryker-mutator/jest-runner":"^9.6.1","@typescript-eslint/eslint-plugin":"^8.65.0","eslint-import-resolver-typescript":"^4.4.5","@stryker-mutator/typescript-checker":"^9.6.1"},"peerDependencies":{"rxjs":"^7.8.0","react":"^19.0.0","ioredis":"^5.0.0","react-dom":"^19.0.0","socket.io":"^4.6.2","@nestjs/core":"^11.1.18","@nestjs/common":"^11.0.16","reflect-metadata":"^0.2.0","socket.io-client":"^4.0.0","@nestjs/websockets":"^11.0.0","@socket.io/redis-adapter":"^8.0.0","@nestjs/platform-socket.io":"^11.0.0"},"peerDependenciesMeta":{"react":{"optional":true},"ioredis":{"optional":true},"react-dom":{"optional":true},"socket.io":{"optional":true},"socket.io-client":{"optional":true},"@nestjs/websockets":{"optional":true},"@socket.io/redis-adapter":{"optional":true},"@nestjs/platform-socket.io":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/nest-realtime_1.0.1_1785683066633_0.6428222065583193","host":"s3://npm-registry-packages-npm-production"}},"1.0.2":{"name":"@bymax-one/nest-realtime","version":"1.0.2","keywords":["nestjs","realtime","sse","server-sent-events","websocket","socket.io","pubsub","redis","multi-tenant","presence","event-replay","offline-queue","react-hooks","typescript"],"author":{"name":"Bymax One","email":"support@bymax.one"},"license":"MIT","_id":"@bymax-one/nest-realtime@1.0.2","maintainers":[{"name":"bymax.one","email":"bymaxone.core@gmail.com"},{"name":"msalvatti","email":"msalvatti@gmail.com"}],"homepage":"https://github.com/bymaxone/nest-realtime#readme","bugs":{"url":"https://github.com/bymaxone/nest-realtime/issues"},"dist":{"shasum":"a718f6d0ef0093b6954429107cc44124cb40be4d","tarball":"https://registry.npmjs.org/@bymax-one/nest-realtime/-/nest-realtime-1.0.2.tgz","fileCount":20,"integrity":"sha512-Mycsu4sVdrMGl4mq8hct+EjHI3Zt7tRrgbOYkrejZ/sdY95ZACBqmjjlR0mzPNrs4z0F4hGapBZhHru2z1vTuw==","signatures":[{"sig":"MEUCIQDhqSeNugVhL0TT0vEB+982cPx7y58YMCQqzMXiU+ajQgIgLElz9DxHiN57t2l/vYBdn3Z8uxNLKIUWSF3X4XpwsuM=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bymax-one%2fnest-realtime@1.0.2","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":540319},"main":"./dist/server/index.cjs","type":"module","types":"./dist/server/index.d.cts","module":"./dist/server/index.mjs","engines":{"node":">=24.0.0"},"exports":{".":{"import":{"types":"./dist/server/index.d.ts","default":"./dist/server/index.mjs"},"require":{"types":"./dist/server/index.d.cts","default":"./dist/server/index.cjs"}},"./react":{"import":{"types":"./dist/react/index.d.ts","default":"./dist/react/index.mjs"},"require":{"types":"./dist/react/index.d.cts","default":"./dist/react/index.cjs"}},"./shared":{"import":{"types":"./dist/shared/index.d.ts","default":"./dist/shared/index.mjs"},"require":{"types":"./dist/shared/index.d.cts","default":"./dist/shared/index.cjs"}},"./websocket":{"import":{"types":"./dist/websocket/index.d.ts","default":"./dist/websocket/index.mjs"},"require":{"types":"./dist/websocket/index.d.cts","default":"./dist/websocket/index.cjs"}},"./package.json":"./package.json"},"gitHead":"9307f68fa00c3608ccdcb19cdfb22e0c75803d19","scripts":{"lint":"eslint src","size":"node scripts/check-size.mjs","test":"jest","build":"pnpm clean && tsup","clean":"rm -rf dist coverage","release":"npm publish --provenance --access public","lint:fix":"eslint src --fix","mutation":"stryker run","test:all":"pnpm test && pnpm test:e2e","test:cov":"jest --config jest.coverage.config.ts --coverage","test:e2e":"jest --config jest.e2e.config.ts","typecheck":"tsc --noEmit && tsc --noEmit -p tsconfig.server.json","test:watch":"jest --watch","test:cov:all":"jest --config jest.coverage.config.ts --coverage","check:exports":"attw --pack . --profile strict","prepublishOnly":"pnpm clean && pnpm typecheck && pnpm lint && pnpm test:cov:all && pnpm build && pnpm check:published","check:published":"node scripts/check-published-surface.mjs","mutation:dry-run":"stryker run --dryRunOnly","mutation:incremental":"stryker run --incremental","test:e2e:cross-instance":"jest --config jest.e2e.cross-instance.config.ts --runInBand"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:b3e65d80-dbcf-4d5e-a432-1b66b30b7aca"}},"repository":{"url":"git+https://github.com/bymaxone/nest-realtime.git","type":"git"},"_npmVersion":"11.16.0","description":"Realtime backend → frontend communication for NestJS — dual-transport SSE (default) and WebSocket (opt-in) with a unified server-side API.","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"typesVersions":{"*":{"react":["./dist/react/index.d.cts"],"shared":["./dist/shared/index.d.cts"],"websocket":["./dist/websocket/index.d.cts"]}},"_hasShrinkwrap":false,"packageManager":"pnpm@11.0.0","devDependencies":{"jest":"^30.0.0","rxjs":"^7.8.0","tsup":"^8.0.0","react":"^19.2.8","tslib":"^2.6.0","eslint":"^10.8.0","ioredis":"^5.0.0","ts-jest":"^29.4.12","ts-node":"^10.9.0","prettier":"^3.9.6","react-dom":"^19.2.8","socket.io":"^4.6.2","supertest":"^7.0.0","typescript":"^5.6.0","@types/jest":"^30.0.0","@types/node":"^26.1.1","eventsource":"^4.1.0","@nestjs/core":"^11.1.28","@types/react":"^19.0.0","ioredis-mock":"^8.0.0","jsonwebtoken":"^9.0.3","@nestjs/common":"^11.1.28","@types/express":"^5.0.0","@nestjs/testing":"^11.1.28","@types/react-dom":"^19.0.0","@types/supertest":"^7.2.1","reflect-metadata":"^0.2.2","socket.io-client":"^4.0.0","@nestjs/websockets":"^11.1.28","@types/ioredis-mock":"^8.2.7","@types/jsonwebtoken":"^9.0.10","eslint-plugin-import":"^2.29.0","@arethetypeswrong/cli":"^0.18.5","@stryker-mutator/core":"^9.6.1","@testing-library/react":"^16.0.0","eslint-config-prettier":"^10.1.8","eslint-plugin-prettier":"^5.0.0","eslint-plugin-security":"^4.0.1","jest-environment-jsdom":"^30.0.0","@nestjs/platform-express":"^11.1.28","@socket.io/redis-adapter":"^8.0.0","@typescript-eslint/parser":"^8.65.0","eslint-plugin-react-hooks":"^7.1.1","@nestjs/platform-socket.io":"^11.1.28","@stryker-mutator/jest-runner":"^9.6.1","@typescript-eslint/eslint-plugin":"^8.65.0","eslint-import-resolver-typescript":"^4.4.5","@stryker-mutator/typescript-checker":"^9.6.1"},"peerDependencies":{"rxjs":"^7.8.0","react":"^19.0.0","ioredis":"^5.0.0","react-dom":"^19.0.0","socket.io":"^4.6.2","@nestjs/core":"^11.1.18","@nestjs/common":"^11.0.16","reflect-metadata":"^0.2.0","socket.io-client":"^4.0.0","@nestjs/websockets":"^11.0.0","@socket.io/redis-adapter":"^8.0.0","@nestjs/platform-socket.io":"^11.0.0"},"peerDependenciesMeta":{"react":{"optional":true},"ioredis":{"optional":true},"react-dom":{"optional":true},"socket.io":{"optional":true},"socket.io-client":{"optional":true},"@nestjs/websockets":{"optional":true},"@socket.io/redis-adapter":{"optional":true},"@nestjs/platform-socket.io":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/nest-realtime_1.0.2_1785691873632_0.04256393951245485","host":"s3://npm-registry-packages-npm-production"}},"1.0.3":{"name":"@bymax-one/nest-realtime","version":"1.0.3","keywords":["nestjs","realtime","sse","server-sent-events","websocket","socket.io","pubsub","redis","multi-tenant","presence","event-replay","offline-queue","react-hooks","typescript"],"author":{"name":"Bymax One","email":"support@bymax.one"},"license":"MIT","_id":"@bymax-one/nest-realtime@1.0.3","maintainers":[{"name":"bymax.one","email":"bymaxone.core@gmail.com"},{"name":"msalvatti","email":"msalvatti@gmail.com"}],"homepage":"https://github.com/bymaxone/nest-realtime#readme","bugs":{"url":"https://github.com/bymaxone/nest-realtime/issues"},"dist":{"shasum":"4f7b380491e0016737572fc4657cf5fadbf6cf72","tarball":"https://registry.npmjs.org/@bymax-one/nest-realtime/-/nest-realtime-1.0.3.tgz","fileCount":24,"integrity":"sha512-3gnxc9diJLics8qYoPvJqvimeFwLasD4/nFky31kz0A/lt0tFvBFKMbQEVktb+qC06tJnaeNkFpfsBGdXSz9Dw==","signatures":[{"sig":"MEUCIQC+nwa59jOa5H06HDDI810alyQVXcYMhhd7SNgfXfTXPgIgRtRIjOU/l0MCN9rvrHVe2macSSrU/L1HEIgXqqHRdtQ=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bymax-one%2fnest-realtime@1.0.3","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":443142},"main":"./dist/server/index.cjs","type":"module","types":"./dist/server/index.d.cts","module":"./dist/server/index.mjs","engines":{"node":">=24.0.0"},"exports":{".":{"import":{"types":"./dist/server/index.d.ts","default":"./dist/server/index.mjs"},"require":{"types":"./dist/server/index.d.cts","default":"./dist/server/index.cjs"}},"./react":{"import":{"types":"./dist/react/index.d.ts","default":"./dist/react/index.mjs"},"require":{"types":"./dist/react/index.d.cts","default":"./dist/react/index.cjs"}},"./shared":{"import":{"types":"./dist/shared/index.d.ts","default":"./dist/shared/index.mjs"},"require":{"types":"./dist/shared/index.d.cts","default":"./dist/shared/index.cjs"}},"./internal":{"import":{"types":"./dist/internal/index.d.ts","default":"./dist/internal/index.mjs"},"require":{"types":"./dist/internal/index.d.cts","default":"./dist/internal/index.cjs"}},"./websocket":{"import":{"types":"./dist/websocket/index.d.ts","default":"./dist/websocket/index.mjs"},"require":{"types":"./dist/websocket/index.d.cts","default":"./dist/websocket/index.cjs"}},"./package.json":"./package.json"},"gitHead":"7bd31f7e51041e0b86e9ef8e50d80d820d6df264","scripts":{"lint":"eslint src","size":"node scripts/check-size.mjs","test":"jest","build":"pnpm clean && tsup","clean":"rm -rf dist coverage","release":"npm publish --provenance --access public","lint:fix":"eslint src --fix","mutation":"stryker run","test:all":"pnpm test && pnpm test:e2e","test:cov":"jest --config jest.coverage.config.ts --coverage","test:e2e":"jest --config jest.e2e.config.ts","typecheck":"tsc --noEmit && tsc --noEmit -p tsconfig.server.json","test:watch":"jest --watch","test:cov:all":"jest --config jest.coverage.config.ts --coverage","check:exports":"attw --pack . --profile strict","check:runtime":"node scripts/check-consumer-runtime.mjs","check:surface":"node scripts/check-public-surface.mjs","prepublishOnly":"pnpm clean && pnpm typecheck && pnpm lint && pnpm test:cov:all && pnpm build && pnpm check:published && pnpm check:surface && pnpm check:runtime","check:published":"node scripts/check-published-surface.mjs","mutation:dry-run":"stryker run --dryRunOnly","mutation:incremental":"stryker run --incremental","test:e2e:cross-instance":"jest --config jest.e2e.cross-instance.config.ts --runInBand"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:b3e65d80-dbcf-4d5e-a432-1b66b30b7aca"}},"repository":{"url":"git+https://github.com/bymaxone/nest-realtime.git","type":"git"},"_npmVersion":"11.16.0","description":"Realtime backend → frontend communication for NestJS — dual-transport SSE (default) and WebSocket (opt-in) with a unified server-side API.","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"typesVersions":{"*":{"react":["./dist/react/index.d.cts"],"shared":["./dist/shared/index.d.cts"],"internal":["./dist/internal/index.d.cts"],"websocket":["./dist/websocket/index.d.cts"]}},"_hasShrinkwrap":false,"packageManager":"pnpm@11.0.0","devDependencies":{"jest":"^30.0.0","rxjs":"^7.8.0","tsup":"^8.0.0","react":"^19.2.8","tslib":"^2.6.0","eslint":"^10.8.0","ioredis":"^5.0.0","ts-jest":"^29.4.12","ts-node":"^10.9.0","prettier":"^3.9.6","react-dom":"^19.2.8","socket.io":"^4.6.2","supertest":"^7.0.0","typescript":"^5.6.0","@types/jest":"^30.0.0","@types/node":"^26.1.1","eventsource":"^4.1.0","@nestjs/core":"^11.1.28","@types/react":"^19.0.0","ioredis-mock":"^8.0.0","jsonwebtoken":"^9.0.3","@nestjs/common":"^11.1.28","@types/express":"^5.0.0","@nestjs/testing":"^11.1.28","@types/react-dom":"^19.0.0","@types/supertest":"^7.2.1","reflect-metadata":"^0.2.2","socket.io-client":"^4.0.0","@nestjs/websockets":"^11.1.28","@types/ioredis-mock":"^8.2.7","@types/jsonwebtoken":"^9.0.10","eslint-plugin-import":"^2.29.0","@arethetypeswrong/cli":"^0.18.5","@stryker-mutator/core":"^9.6.1","@testing-library/react":"^16.0.0","eslint-config-prettier":"^10.1.8","eslint-plugin-prettier":"^5.0.0","eslint-plugin-security":"^4.0.1","jest-environment-jsdom":"^30.0.0","@nestjs/platform-express":"^11.1.28","@socket.io/redis-adapter":"^8.0.0","@typescript-eslint/parser":"^8.65.0","eslint-plugin-react-hooks":"^7.1.1","@nestjs/platform-socket.io":"^11.1.28","@stryker-mutator/jest-runner":"^9.6.1","@typescript-eslint/eslint-plugin":"^8.65.0","eslint-import-resolver-typescript":"^4.4.5","@stryker-mutator/typescript-checker":"^9.6.1"},"peerDependencies":{"rxjs":"^7.8.0","react":"^19.0.0","ioredis":"^5.0.0","react-dom":"^19.0.0","socket.io":"^4.6.2","@nestjs/core":"^11.1.18","@nestjs/common":"^11.0.16","reflect-metadata":"^0.2.0","socket.io-client":"^4.0.0","@nestjs/websockets":"^11.0.0","@socket.io/redis-adapter":"^8.0.0","@nestjs/platform-socket.io":"^11.0.0"},"peerDependenciesMeta":{"react":{"optional":true},"ioredis":{"optional":true},"react-dom":{"optional":true},"socket.io":{"optional":true},"socket.io-client":{"optional":true},"@nestjs/websockets":{"optional":true},"@socket.io/redis-adapter":{"optional":true},"@nestjs/platform-socket.io":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/nest-realtime_1.0.3_1785698316431_0.28426899668328587","host":"s3://npm-registry-packages-npm-production"}},"1.0.4":{"name":"@bymax-one/nest-realtime","version":"1.0.4","keywords":["nestjs","realtime","sse","server-sent-events","websocket","socket.io","pubsub","redis","multi-tenant","presence","event-replay","offline-queue","react-hooks","typescript"],"author":{"name":"Bymax One","email":"support@bymax.one"},"license":"MIT","_id":"@bymax-one/nest-realtime@1.0.4","maintainers":[{"name":"bymax.one","email":"bymaxone.core@gmail.com"},{"name":"msalvatti","email":"msalvatti@gmail.com"}],"homepage":"https://github.com/bymaxone/nest-realtime#readme","bugs":{"url":"https://github.com/bymaxone/nest-realtime/issues"},"dist":{"shasum":"f1fcc72bec53c2a00e3e6ffd25eefc090781c643","tarball":"https://registry.npmjs.org/@bymax-one/nest-realtime/-/nest-realtime-1.0.4.tgz","fileCount":24,"integrity":"sha512-VoGkaUvcN4xF4RHhDRpTOEvEB6WbIWKe051UF5+Fbqn6kEfJJs1Ji1oc//NrB2+ALrMALRriSrGgACp2c0sT0g==","signatures":[{"sig":"MEQCICzNBn0hGRkcSR4ya/m7MJXjeJMBUHRVtKqeluWs18aAAiBi2P3/TteZ9M0hlGw+T965jkLXQSF7KF0VkbusCka3Gg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bymax-one%2fnest-realtime@1.0.4","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":449486},"main":"./dist/server/index.cjs","type":"module","types":"./dist/server/index.d.cts","module":"./dist/server/index.mjs","engines":{"node":">=24.0.0"},"exports":{".":{"import":{"types":"./dist/server/index.d.ts","default":"./dist/server/index.mjs"},"require":{"types":"./dist/server/index.d.cts","default":"./dist/server/index.cjs"}},"./react":{"import":{"types":"./dist/react/index.d.ts","default":"./dist/react/index.mjs"},"require":{"types":"./dist/react/index.d.cts","default":"./dist/react/index.cjs"}},"./shared":{"import":{"types":"./dist/shared/index.d.ts","default":"./dist/shared/index.mjs"},"require":{"types":"./dist/shared/index.d.cts","default":"./dist/shared/index.cjs"}},"./internal":{"import":{"types":"./dist/internal/index.d.ts","default":"./dist/internal/index.mjs"},"require":{"types":"./dist/internal/index.d.cts","default":"./dist/internal/index.cjs"}},"./websocket":{"import":{"types":"./dist/websocket/index.d.ts","default":"./dist/websocket/index.mjs"},"require":{"types":"./dist/websocket/index.d.cts","default":"./dist/websocket/index.cjs"}},"./package.json":"./package.json"},"gitHead":"943d16ea45bbafd5f0fed57ec094e7abef6a2359","scripts":{"lint":"eslint src","size":"node scripts/check-size.mjs","test":"jest","build":"pnpm clean && tsup","clean":"rm -rf dist coverage","release":"npm publish --provenance --access public","lint:fix":"eslint src --fix","mutation":"stryker run","test:all":"pnpm test && pnpm test:e2e","test:cov":"jest --config jest.coverage.config.ts --coverage","test:e2e":"jest --config jest.e2e.config.ts","typecheck":"tsc --noEmit && tsc --noEmit -p tsconfig.server.json","test:watch":"jest --watch","test:cov:all":"jest --config jest.coverage.config.ts --coverage","check:exports":"attw --pack . --profile strict","check:runtime":"node scripts/check-consumer-runtime.mjs","check:surface":"node scripts/check-public-surface.mjs","prepublishOnly":"pnpm clean && pnpm typecheck && pnpm lint && pnpm test:cov:all && pnpm build && pnpm check:published && pnpm check:surface && pnpm check:runtime","check:published":"node scripts/check-published-surface.mjs","mutation:dry-run":"stryker run --dryRunOnly","mutation:incremental":"stryker run --incremental","test:e2e:cross-instance":"jest --config jest.e2e.cross-instance.config.ts --runInBand"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:b3e65d80-dbcf-4d5e-a432-1b66b30b7aca"}},"repository":{"url":"git+https://github.com/bymaxone/nest-realtime.git","type":"git"},"_npmVersion":"11.16.0","description":"Realtime backend → frontend communication for NestJS — dual-transport SSE (default) and WebSocket (opt-in) with a unified server-side API.","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"typesVersions":{"*":{"react":["./dist/react/index.d.cts"],"shared":["./dist/shared/index.d.cts"],"internal":["./dist/internal/index.d.cts"],"websocket":["./dist/websocket/index.d.cts"]}},"_hasShrinkwrap":false,"packageManager":"pnpm@11.0.0","devDependencies":{"jest":"^30.0.0","rxjs":"^7.8.0","tsup":"^8.0.0","react":"^19.2.8","tslib":"^2.6.0","eslint":"^10.8.0","ioredis":"^5.0.0","ts-jest":"^29.4.12","ts-node":"^10.9.0","prettier":"^3.9.6","react-dom":"^19.2.8","socket.io":"^4.6.2","supertest":"^7.0.0","typescript":"^5.6.0","@types/jest":"^30.0.0","@types/node":"^26.1.1","eventsource":"^4.1.0","@nestjs/core":"^11.1.28","@types/react":"^19.0.0","ioredis-mock":"^8.0.0","jsonwebtoken":"^9.0.3","@nestjs/common":"^11.1.28","@types/express":"^5.0.0","@nestjs/testing":"^11.1.28","@types/react-dom":"^19.0.0","@types/supertest":"^7.2.1","reflect-metadata":"^0.2.2","socket.io-client":"^4.0.0","@nestjs/websockets":"^11.1.28","@types/ioredis-mock":"^8.2.7","@types/jsonwebtoken":"^9.0.10","eslint-plugin-import":"^2.29.0","@arethetypeswrong/cli":"^0.18.5","@stryker-mutator/core":"^9.6.1","@testing-library/react":"^16.0.0","eslint-config-prettier":"^10.1.8","eslint-plugin-prettier":"^5.0.0","eslint-plugin-security":"^4.0.1","jest-environment-jsdom":"^30.0.0","@nestjs/platform-express":"^11.1.28","@socket.io/redis-adapter":"^8.0.0","@typescript-eslint/parser":"^8.65.0","eslint-plugin-react-hooks":"^7.1.1","@nestjs/platform-socket.io":"^11.1.28","@stryker-mutator/jest-runner":"^9.6.1","@typescript-eslint/eslint-plugin":"^8.65.0","eslint-import-resolver-typescript":"^4.4.5","@stryker-mutator/typescript-checker":"^9.6.1"},"peerDependencies":{"rxjs":"^7.8.0","react":"^19.0.0","ioredis":"^5.0.0","react-dom":"^19.0.0","socket.io":"^4.6.2","@nestjs/core":"^11.1.18","@nestjs/common":"^11.0.16","reflect-metadata":"^0.2.0","socket.io-client":"^4.0.0","@nestjs/websockets":"^11.0.0","@socket.io/redis-adapter":"^8.0.0","@nestjs/platform-socket.io":"^11.0.0"},"peerDependenciesMeta":{"react":{"optional":true},"ioredis":{"optional":true},"react-dom":{"optional":true},"socket.io":{"optional":true},"socket.io-client":{"optional":true},"@nestjs/websockets":{"optional":true},"@socket.io/redis-adapter":{"optional":true},"@nestjs/platform-socket.io":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/nest-realtime_1.0.4_1785701787504_0.23133376972937558","host":"s3://npm-registry-packages-npm-production"}},"1.0.5":{"name":"@bymax-one/nest-realtime","version":"1.0.5","keywords":["nestjs","realtime","sse","server-sent-events","websocket","socket.io","pubsub","redis","multi-tenant","presence","event-replay","offline-queue","react-hooks","typescript"],"author":{"name":"Bymax One","email":"support@bymax.one"},"license":"MIT","_id":"@bymax-one/nest-realtime@1.0.5","maintainers":[{"name":"bymax.one","email":"bymaxone.core@gmail.com"},{"name":"msalvatti","email":"msalvatti@gmail.com"}],"homepage":"https://github.com/bymaxone/nest-realtime#readme","bugs":{"url":"https://github.com/bymaxone/nest-realtime/issues"},"dist":{"shasum":"6bca9e1498b3a64d2bbd19a1fc1ff2142551286e","tarball":"https://registry.npmjs.org/@bymax-one/nest-realtime/-/nest-realtime-1.0.5.tgz","fileCount":24,"integrity":"sha512-OcT14kkKYC4P4d/FvsXzwl2INt8sG3ywkjmHAoLyQtjOjimPD8t84ZZ7gWT+UsTNd3gtO8IhHY8vqPu8GShusg==","signatures":[{"sig":"MEUCIC2bgEPfium+3X6CCr3y/StSj13j6M+mtDS+Qe+uguGgAiEAnK21pSs9ckN9d6NzSIwnadMTV2ORV8Cxydy3nhpZCJY=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bymax-one%2fnest-realtime@1.0.5","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":451842},"main":"./dist/server/index.cjs","type":"module","types":"./dist/server/index.d.cts","module":"./dist/server/index.mjs","engines":{"node":">=24.0.0"},"exports":{".":{"import":{"types":"./dist/server/index.d.ts","default":"./dist/server/index.mjs"},"require":{"types":"./dist/server/index.d.cts","default":"./dist/server/index.cjs"}},"./react":{"import":{"types":"./dist/react/index.d.ts","default":"./dist/react/index.mjs"},"require":{"types":"./dist/react/index.d.cts","default":"./dist/react/index.cjs"}},"./shared":{"import":{"types":"./dist/shared/index.d.ts","default":"./dist/shared/index.mjs"},"require":{"types":"./dist/shared/index.d.cts","default":"./dist/shared/index.cjs"}},"./internal":{"import":{"types":"./dist/internal/index.d.ts","default":"./dist/internal/index.mjs"},"require":{"types":"./dist/internal/index.d.cts","default":"./dist/internal/index.cjs"}},"./websocket":{"import":{"types":"./dist/websocket/index.d.ts","default":"./dist/websocket/index.mjs"},"require":{"types":"./dist/websocket/index.d.cts","default":"./dist/websocket/index.cjs"}},"./package.json":"./package.json"},"gitHead":"ae955d40b23a1e1da402f96b8443aa5ce47e6c44","scripts":{"lint":"eslint src","size":"node scripts/check-size.mjs","test":"jest","build":"pnpm clean && tsup","clean":"rm -rf dist coverage","release":"npm publish --provenance --access public","lint:fix":"eslint src --fix","mutation":"stryker run","test:all":"pnpm test && pnpm test:e2e","test:cov":"jest --config jest.coverage.config.ts --coverage","test:e2e":"jest --config jest.e2e.config.ts","typecheck":"tsc --noEmit && tsc --noEmit -p tsconfig.server.json","test:watch":"jest --watch","test:cov:all":"jest --config jest.coverage.config.ts --coverage","check:exports":"attw --pack . --profile strict","check:runtime":"node scripts/check-consumer-runtime.mjs","check:surface":"node scripts/check-public-surface.mjs","prepublishOnly":"pnpm clean && pnpm typecheck && pnpm lint && pnpm test:cov:all && pnpm build && pnpm check:published && pnpm check:surface && pnpm check:runtime","check:published":"node scripts/check-published-surface.mjs","mutation:dry-run":"stryker run --dryRunOnly","mutation:incremental":"stryker run --incremental","test:e2e:cross-instance":"jest --config jest.e2e.cross-instance.config.ts --runInBand"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:b3e65d80-dbcf-4d5e-a432-1b66b30b7aca"}},"repository":{"url":"git+https://github.com/bymaxone/nest-realtime.git","type":"git"},"_npmVersion":"11.16.0","description":"Realtime backend → frontend communication for NestJS — dual-transport SSE (default) and WebSocket (opt-in) with a unified server-side API.","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"typesVersions":{"*":{"react":["./dist/react/index.d.cts"],"shared":["./dist/shared/index.d.cts"],"internal":["./dist/internal/index.d.cts"],"websocket":["./dist/websocket/index.d.cts"]}},"_hasShrinkwrap":false,"packageManager":"pnpm@11.0.0","devDependencies":{"jest":"^30.0.0","rxjs":"^7.8.0","tsup":"^8.0.0","react":"^19.2.8","tslib":"^2.6.0","eslint":"^10.8.0","ioredis":"^5.0.0","ts-jest":"^29.4.12","ts-node":"^10.9.0","prettier":"^3.9.6","react-dom":"^19.2.8","socket.io":"^4.6.2","supertest":"^7.0.0","typescript":"^5.6.0","@types/jest":"^30.0.0","@types/node":"^26.1.1","eventsource":"^4.1.0","@nestjs/core":"^11.1.28","@types/react":"^19.0.0","ioredis-mock":"^8.0.0","jsonwebtoken":"^9.0.3","@nestjs/common":"^11.1.28","@types/express":"^5.0.0","@nestjs/testing":"^11.1.28","@types/react-dom":"^19.0.0","@types/supertest":"^7.2.1","reflect-metadata":"^0.2.2","socket.io-client":"^4.0.0","@nestjs/websockets":"^11.1.28","@types/ioredis-mock":"^8.2.7","@types/jsonwebtoken":"^9.0.10","eslint-plugin-import":"^2.29.0","@arethetypeswrong/cli":"^0.18.5","@stryker-mutator/core":"^9.6.1","@testing-library/react":"^16.0.0","eslint-config-prettier":"^10.1.8","eslint-plugin-prettier":"^5.0.0","eslint-plugin-security":"^4.0.1","jest-environment-jsdom":"^30.0.0","@nestjs/platform-express":"^11.1.28","@socket.io/redis-adapter":"^8.0.0","@typescript-eslint/parser":"^8.65.0","eslint-plugin-react-hooks":"^7.1.1","@nestjs/platform-socket.io":"^11.1.28","@stryker-mutator/jest-runner":"^9.6.1","@typescript-eslint/eslint-plugin":"^8.65.0","eslint-import-resolver-typescript":"^4.4.5","@stryker-mutator/typescript-checker":"^9.6.1"},"peerDependencies":{"rxjs":"^7.8.0","react":"^19.0.0","ioredis":"^5.0.0","react-dom":"^19.0.0","socket.io":"^4.6.2","@nestjs/core":"^11.1.18","@nestjs/common":"^11.0.16","reflect-metadata":"^0.2.0","socket.io-client":"^4.0.0","@nestjs/websockets":"^11.0.0","@socket.io/redis-adapter":"^8.0.0","@nestjs/platform-socket.io":"^11.0.0"},"peerDependenciesMeta":{"react":{"optional":true},"ioredis":{"optional":true},"react-dom":{"optional":true},"socket.io":{"optional":true},"socket.io-client":{"optional":true},"@nestjs/websockets":{"optional":true},"@socket.io/redis-adapter":{"optional":true},"@nestjs/platform-socket.io":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/nest-realtime_1.0.5_1785858292669_0.21863207506776505","host":"s3://npm-registry-packages-npm-production"}},"1.0.6":{"name":"@bymax-one/nest-realtime","version":"1.0.6","keywords":["nestjs","realtime","sse","server-sent-events","websocket","socket.io","pubsub","redis","multi-tenant","presence","event-replay","offline-queue","react-hooks","typescript"],"author":{"name":"Bymax One","email":"support@bymax.one"},"license":"MIT","_id":"@bymax-one/nest-realtime@1.0.6","maintainers":[{"name":"bymax.one","email":"bymaxone.core@gmail.com"},{"name":"msalvatti","email":"msalvatti@gmail.com"}],"homepage":"https://github.com/bymaxone/nest-realtime#readme","bugs":{"url":"https://github.com/bymaxone/nest-realtime/issues"},"dist":{"shasum":"7bab248fced9d9a2dfa68ad13ed616ce9ff69d23","tarball":"https://registry.npmjs.org/@bymax-one/nest-realtime/-/nest-realtime-1.0.6.tgz","fileCount":24,"integrity":"sha512-F3PJbxbnsW2VZzY+Rrd30s6FTQU0788lprtIZl6QibLVT2BP1o3G1JqU56tIRhj2aWYHolZkbSyshYCbErdPhg==","signatures":[{"sig":"MEUCIHnrq1cNJWVHTy3cOW6DPNYmYD2BAaOIfCtp3Q1OvXjuAiEA3ft0+Rvx0+jFNeiQE6K1qZLsN6ck6jBqb5zW0i71HM0=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bymax-one%2fnest-realtime@1.0.6","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":452630},"main":"./dist/server/index.cjs","type":"module","types":"./dist/server/index.d.cts","module":"./dist/server/index.mjs","engines":{"node":">=24.0.0"},"exports":{".":{"import":{"types":"./dist/server/index.d.ts","default":"./dist/server/index.mjs"},"require":{"types":"./dist/server/index.d.cts","default":"./dist/server/index.cjs"}},"./react":{"import":{"types":"./dist/react/index.d.ts","default":"./dist/react/index.mjs"},"require":{"types":"./dist/react/index.d.cts","default":"./dist/react/index.cjs"}},"./shared":{"import":{"types":"./dist/shared/index.d.ts","default":"./dist/shared/index.mjs"},"require":{"types":"./dist/shared/index.d.cts","default":"./dist/shared/index.cjs"}},"./internal":{"import":{"types":"./dist/internal/index.d.ts","default":"./dist/internal/index.mjs"},"require":{"types":"./dist/internal/index.d.cts","default":"./dist/internal/index.cjs"}},"./websocket":{"import":{"types":"./dist/websocket/index.d.ts","default":"./dist/websocket/index.mjs"},"require":{"types":"./dist/websocket/index.d.cts","default":"./dist/websocket/index.cjs"}},"./package.json":"./package.json"},"gitHead":"39c5918815fcb52dbba336816d0008b02fdd79d8","scripts":{"lint":"eslint src","size":"node scripts/check-size.mjs","test":"jest","build":"pnpm clean && tsup","clean":"rm -rf dist coverage","release":"npm publish --provenance --access public","lint:fix":"eslint src --fix","mutation":"stryker run","test:all":"pnpm test && pnpm test:e2e","test:cov":"jest --config jest.coverage.config.ts --coverage","test:e2e":"jest --config jest.e2e.config.ts","typecheck":"tsc --noEmit && tsc --noEmit -p tsconfig.server.json","test:watch":"jest --watch","test:cov:all":"jest --config jest.coverage.config.ts --coverage","check:exports":"attw --pack . --profile strict","check:runtime":"node scripts/check-consumer-runtime.mjs","check:surface":"node scripts/check-public-surface.mjs","prepublishOnly":"pnpm clean && pnpm typecheck && pnpm lint && pnpm test:cov:all && pnpm build && pnpm check:published && pnpm check:surface && pnpm check:runtime","check:published":"node scripts/check-published-surface.mjs","mutation:dry-run":"stryker run --dryRunOnly","mutation:incremental":"stryker run --incremental","test:e2e:cross-instance":"jest --config jest.e2e.cross-instance.config.ts --runInBand"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:b3e65d80-dbcf-4d5e-a432-1b66b30b7aca"}},"repository":{"url":"git+https://github.com/bymaxone/nest-realtime.git","type":"git"},"_npmVersion":"11.17.0","description":"Realtime backend → frontend communication for NestJS — dual-transport SSE (default) and WebSocket (opt-in) with a unified server-side API.","directories":{},"sideEffects":false,"_nodeVersion":"24.19.0","publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"typesVersions":{"*":{"react":["./dist/react/index.d.cts"],"shared":["./dist/shared/index.d.cts"],"internal":["./dist/internal/index.d.cts"],"websocket":["./dist/websocket/index.d.cts"]}},"_hasShrinkwrap":false,"packageManager":"pnpm@11.20.0","devDependencies":{"jest":"^30.0.0","rxjs":"^7.8.0","tsup":"^8.0.0","react":"^19.2.8","tslib":"^2.6.0","eslint":"^10.8.0","ioredis":"^5.0.0","ts-jest":"^29.4.12","ts-node":"^10.9.0","prettier":"^3.9.6","react-dom":"^19.2.8","socket.io":"^4.6.2","supertest":"^7.0.0","typescript":"^5.6.0","@types/jest":"^30.0.0","@types/node":"^26.1.1","eventsource":"^4.1.0","@nestjs/core":"^11.1.28","@types/react":"^19.0.0","ioredis-mock":"^8.0.0","jsonwebtoken":"^9.0.3","@nestjs/common":"^11.1.28","@types/express":"^5.0.0","@nestjs/testing":"^11.1.28","@types/react-dom":"^19.0.0","@types/supertest":"^7.2.1","reflect-metadata":"^0.2.2","socket.io-client":"^4.0.0","@nestjs/websockets":"^11.1.28","@types/ioredis-mock":"^8.2.7","@types/jsonwebtoken":"^9.0.10","eslint-plugin-import":"^2.29.0","@arethetypeswrong/cli":"^0.18.5","@stryker-mutator/core":"^9.6.1","@testing-library/react":"^16.0.0","eslint-config-prettier":"^10.1.8","eslint-plugin-prettier":"^5.0.0","eslint-plugin-security":"^4.0.1","jest-environment-jsdom":"^30.0.0","@nestjs/platform-express":"^11.1.28","@socket.io/redis-adapter":"^8.0.0","@typescript-eslint/parser":"^8.65.0","eslint-plugin-react-hooks":"^7.1.1","@nestjs/platform-socket.io":"^11.1.28","@stryker-mutator/jest-runner":"^9.6.1","@typescript-eslint/eslint-plugin":"^8.65.0","eslint-import-resolver-typescript":"^4.4.5","@stryker-mutator/typescript-checker":"^9.6.1"},"peerDependencies":{"rxjs":"^7.8.0","react":"^19.0.0","ioredis":"^5.0.0","react-dom":"^19.0.0","socket.io":"^4.6.2","@nestjs/core":"^11.1.18","@nestjs/common":"^11.0.16","reflect-metadata":"^0.2.0","socket.io-client":"^4.0.0","@nestjs/websockets":"^11.0.0","@socket.io/redis-adapter":"^8.0.0","@nestjs/platform-socket.io":"^11.0.0"},"peerDependenciesMeta":{"react":{"optional":true},"ioredis":{"optional":true},"react-dom":{"optional":true},"socket.io":{"optional":true},"socket.io-client":{"optional":true},"@nestjs/websockets":{"optional":true},"@socket.io/redis-adapter":{"optional":true},"@nestjs/platform-socket.io":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/nest-realtime_1.0.6_1786015882897_0.10050651866286553","host":"s3://npm-registry-packages-npm-production"}},"1.1.0":{"name":"@bymax-one/nest-realtime","version":"1.1.0","keywords":["nestjs","realtime","sse","server-sent-events","websocket","socket.io","pubsub","redis","multi-tenant","presence","event-replay","offline-queue","react-hooks","typescript"],"author":{"name":"Bymax One","email":"support@bymax.one"},"license":"MIT","_id":"@bymax-one/nest-realtime@1.1.0","maintainers":[{"name":"bymax.one","email":"bymaxone.core@gmail.com"},{"name":"msalvatti","email":"msalvatti@gmail.com"}],"homepage":"https://github.com/bymaxone/nest-realtime#readme","bugs":{"url":"https://github.com/bymaxone/nest-realtime/issues"},"dist":{"shasum":"22dce29dbae7df17644e319ce2c9a281b73ac5d7","tarball":"https://registry.npmjs.org/@bymax-one/nest-realtime/-/nest-realtime-1.1.0.tgz","fileCount":24,"integrity":"sha512-b3gwy4AFvzi5hly6nAdAlV5JvfA/XFz7/5dL1H0NRwSWWlImYxYrdZSWgeQsj8DS/S0bgBaDPiGowltrvamgiw==","signatures":[{"sig":"MEYCIQDJs3RmusfvsUmyx0wMiwtoRz8KxjYz5nzUBRgVvtMCdgIhAKYSeSdp/0eftDxZmiFnwmXvVGLMrmPYiSUvf2u0z3MG","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bymax-one%2fnest-realtime@1.1.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":453493},"main":"./dist/server/index.cjs","type":"module","types":"./dist/server/index.d.cts","module":"./dist/server/index.mjs","engines":{"node":">=24.0.0"},"exports":{".":{"import":{"types":"./dist/server/index.d.ts","default":"./dist/server/index.mjs"},"require":{"types":"./dist/server/index.d.cts","default":"./dist/server/index.cjs"}},"./react":{"import":{"types":"./dist/react/index.d.ts","default":"./dist/react/index.mjs"},"require":{"types":"./dist/react/index.d.cts","default":"./dist/react/index.cjs"}},"./shared":{"import":{"types":"./dist/shared/index.d.ts","default":"./dist/shared/index.mjs"},"require":{"types":"./dist/shared/index.d.cts","default":"./dist/shared/index.cjs"}},"./internal":{"import":{"types":"./dist/internal/index.d.ts","default":"./dist/internal/index.mjs"},"require":{"types":"./dist/internal/index.d.cts","default":"./dist/internal/index.cjs"}},"./websocket":{"import":{"types":"./dist/websocket/index.d.ts","default":"./dist/websocket/index.mjs"},"require":{"types":"./dist/websocket/index.d.cts","default":"./dist/websocket/index.cjs"}},"./package.json":"./package.json"},"gitHead":"23147b9e749690dab9e2edfc183c6ece715ece48","scripts":{"lint":"eslint src","size":"node scripts/check-size.mjs","test":"jest","build":"pnpm clean && tsup","clean":"rm -rf dist coverage","release":"npm publish --provenance --access public","lint:fix":"eslint src --fix","mutation":"stryker run","test:all":"pnpm test && pnpm test:e2e","test:cov":"jest --config jest.coverage.config.ts --coverage","test:e2e":"jest --config jest.e2e.config.ts","typecheck":"tsc --noEmit && tsc --noEmit -p tsconfig.server.json","test:watch":"jest --watch","test:cov:all":"jest --config jest.coverage.config.ts --coverage","check:exports":"attw --pack . --profile strict","check:mutants":"node scripts/check-mutation-directives.mjs","check:runtime":"node scripts/check-consumer-runtime.mjs","check:surface":"node scripts/check-public-surface.mjs","mutation:full":"node -e \"require('node:fs').rmSync('reports/stryker-incremental.json',{force:true,recursive:true})\" && stryker run","prepublishOnly":"pnpm clean && pnpm typecheck && pnpm lint && pnpm check:mutants && pnpm test:cov:all && pnpm build && pnpm check:published && pnpm check:surface && pnpm check:runtime","check:published":"node scripts/check-published-surface.mjs","mutation:dry-run":"stryker run --dryRunOnly","test:e2e:cross-instance":"jest --config jest.e2e.cross-instance.config.ts --runInBand"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:b3e65d80-dbcf-4d5e-a432-1b66b30b7aca"}},"repository":{"url":"git+https://github.com/bymaxone/nest-realtime.git","type":"git"},"_npmVersion":"11.16.0","description":"Realtime backend → frontend communication for NestJS — dual-transport SSE (default) and WebSocket (opt-in) with a unified server-side API.","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"typesVersions":{"*":{"react":["./dist/react/index.d.cts"],"shared":["./dist/shared/index.d.cts"],"internal":["./dist/internal/index.d.cts"],"websocket":["./dist/websocket/index.d.cts"]}},"_hasShrinkwrap":false,"packageManager":"pnpm@11.20.0","devDependencies":{"jest":"^30.0.0","rxjs":"^7.8.0","tsup":"^8.0.0","react":"^19.2.8","tslib":"^2.6.0","eslint":"^10.8.0","ioredis":"^6.0.0","ts-jest":"^29.4.12","ts-node":"^10.9.0","prettier":"^3.9.6","react-dom":"^19.2.8","socket.io":"^4.6.2","supertest":"^7.0.0","typescript":"^5.6.0","@types/jest":"^30.0.0","@types/node":"^26.1.1","eventsource":"^4.1.0","@nestjs/core":"^11.1.28","@types/react":"^19.0.0","ioredis-mock":"^8.0.0","jsonwebtoken":"^9.0.3","@nestjs/common":"^11.1.28","@types/express":"^5.0.0","@nestjs/testing":"^11.1.28","@types/react-dom":"^19.0.0","@types/supertest":"^7.2.1","reflect-metadata":"^0.2.2","socket.io-client":"^4.0.0","@nestjs/websockets":"^11.1.28","@types/ioredis-mock":"^8.2.7","@types/jsonwebtoken":"^9.0.10","eslint-plugin-import":"^2.29.0","@arethetypeswrong/cli":"^0.18.5","@stryker-mutator/core":"^9.6.1","@testing-library/react":"^16.0.0","eslint-config-prettier":"^10.1.8","eslint-plugin-prettier":"^5.0.0","eslint-plugin-security":"^4.0.1","jest-environment-jsdom":"^30.0.0","@nestjs/platform-express":"^11.1.28","@socket.io/redis-adapter":"^8.0.0","@typescript-eslint/parser":"^8.65.0","eslint-plugin-react-hooks":"^7.1.1","@nestjs/platform-socket.io":"^11.1.28","@stryker-mutator/jest-runner":"^9.6.1","@typescript-eslint/eslint-plugin":"^8.65.0","eslint-import-resolver-typescript":"^4.4.5","@stryker-mutator/typescript-checker":"^9.6.1"},"peerDependencies":{"rxjs":"^7.8.0","react":"^19.0.0","ioredis":"^6.0.0","react-dom":"^19.0.0","socket.io":"^4.6.2","@nestjs/core":"^11.1.18","@nestjs/common":"^11.0.16","reflect-metadata":"^0.2.0","socket.io-client":"^4.0.0","@nestjs/websockets":"^11.0.0","@socket.io/redis-adapter":"^8.0.0","@nestjs/platform-socket.io":"^11.0.0"},"peerDependenciesMeta":{"react":{"optional":true},"ioredis":{"optional":true},"react-dom":{"optional":true},"socket.io":{"optional":true},"socket.io-client":{"optional":true},"@nestjs/websockets":{"optional":true},"@socket.io/redis-adapter":{"optional":true},"@nestjs/platform-socket.io":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/nest-realtime_1.1.0_1786446128224_0.6281792422200676","host":"s3://npm-registry-packages-npm-production"}},"1.1.1":{"name":"@bymax-one/nest-realtime","version":"1.1.1","keywords":["nestjs","realtime","sse","server-sent-events","websocket","socket.io","pubsub","redis","multi-tenant","presence","event-replay","offline-queue","react-hooks","typescript"],"author":{"name":"Bymax One","email":"support@bymax.one"},"license":"MIT","_id":"@bymax-one/nest-realtime@1.1.1","maintainers":[{"name":"bymax.one","email":"bymaxone.core@gmail.com"},{"name":"msalvatti","email":"msalvatti@gmail.com"}],"homepage":"https://github.com/bymaxone/nest-realtime#readme","bugs":{"url":"https://github.com/bymaxone/nest-realtime/issues"},"dist":{"shasum":"68920649fc7ec0387bd1bff29f12219b0120bced","tarball":"https://registry.npmjs.org/@bymax-one/nest-realtime/-/nest-realtime-1.1.1.tgz","fileCount":24,"integrity":"sha512-jF+1Ac5dO2K+zwsDc9lW+SIBmDVrb/qjm8fOy9eCBi1LLH4xp6AGHO3evrwral2bEjgiv0D4+0aMJryE4+P74A==","signatures":[{"sig":"MEUCIQDHMKyHDKEpwZU34RvSLc9irp+vhWk9lGckxMO1ZEIf/AIgHy2nkeKdBR3uWvN5xcM0RV9DucuDrbNp8+orEj9+u7E=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bymax-one%2fnest-realtime@1.1.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":461476},"main":"./dist/server/index.cjs","type":"module","types":"./dist/server/index.d.cts","module":"./dist/server/index.mjs","engines":{"node":">=24.0.0"},"exports":{".":{"import":{"types":"./dist/server/index.d.ts","default":"./dist/server/index.mjs"},"require":{"types":"./dist/server/index.d.cts","default":"./dist/server/index.cjs"}},"./react":{"import":{"types":"./dist/react/index.d.ts","default":"./dist/react/index.mjs"},"require":{"types":"./dist/react/index.d.cts","default":"./dist/react/index.cjs"}},"./shared":{"import":{"types":"./dist/shared/index.d.ts","default":"./dist/shared/index.mjs"},"require":{"types":"./dist/shared/index.d.cts","default":"./dist/shared/index.cjs"}},"./internal":{"import":{"types":"./dist/internal/index.d.ts","default":"./dist/internal/index.mjs"},"require":{"types":"./dist/internal/index.d.cts","default":"./dist/internal/index.cjs"}},"./websocket":{"import":{"types":"./dist/websocket/index.d.ts","default":"./dist/websocket/index.mjs"},"require":{"types":"./dist/websocket/index.d.cts","default":"./dist/websocket/index.cjs"}},"./package.json":"./package.json"},"gitHead":"d19c3c2cb0191f852478edbbe3388f2d7ce63370","scripts":{"lint":"eslint src","size":"node scripts/check-size.mjs","test":"jest","build":"pnpm clean && tsup","clean":"rm -rf dist coverage","release":"npm publish --provenance --access public","lint:fix":"eslint src --fix","mutation":"stryker run","test:all":"pnpm test && pnpm test:e2e","test:cov":"jest --config jest.coverage.config.ts --coverage","test:e2e":"jest --config jest.e2e.config.ts","typecheck":"tsc --noEmit && tsc --noEmit -p tsconfig.server.json","test:watch":"jest --watch","test:cov:all":"jest --config jest.coverage.config.ts --coverage","check:exports":"attw --pack . --profile strict","check:mutants":"node scripts/check-mutation-directives.mjs","check:runtime":"node scripts/check-consumer-runtime.mjs","check:surface":"node scripts/check-public-surface.mjs","mutation:full":"node -e \"require('node:fs').rmSync('reports/stryker-incremental.json',{force:true,recursive:true})\" && stryker run","prepublishOnly":"pnpm clean && pnpm typecheck && pnpm lint && pnpm check:mutants && pnpm test:cov:all && pnpm build && pnpm check:published && pnpm check:surface && pnpm check:runtime","check:published":"node scripts/check-published-surface.mjs","mutation:dry-run":"stryker run --dryRunOnly","test:e2e:cross-instance":"jest --config jest.e2e.cross-instance.config.ts --runInBand"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:b3e65d80-dbcf-4d5e-a432-1b66b30b7aca"}},"repository":{"url":"git+https://github.com/bymaxone/nest-realtime.git","type":"git"},"_npmVersion":"11.17.0","description":"Realtime backend → frontend communication for NestJS — dual-transport SSE (default) and WebSocket (opt-in) with a unified server-side API.","directories":{},"sideEffects":false,"_nodeVersion":"24.19.0","publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"typesVersions":{"*":{"react":["./dist/react/index.d.cts"],"shared":["./dist/shared/index.d.cts"],"internal":["./dist/internal/index.d.cts"],"websocket":["./dist/websocket/index.d.cts"]}},"_hasShrinkwrap":false,"packageManager":"pnpm@11.20.0","devDependencies":{"jest":"^30.0.0","rxjs":"^7.8.0","tsup":"^8.0.0","react":"^19.2.8","tslib":"^2.6.0","eslint":"^10.8.0","ioredis":"^6.0.0","ts-jest":"^29.4.12","ts-node":"^10.9.0","prettier":"^3.9.6","react-dom":"^19.2.8","socket.io":"^4.6.2","supertest":"^7.0.0","typescript":"^5.6.0","@types/jest":"^30.0.0","@types/node":"^26.1.1","eventsource":"^5.1.0","@nestjs/core":"^11.1.28","@types/react":"^19.0.0","ioredis-mock":"^8.0.0","jsonwebtoken":"^9.0.3","@nestjs/common":"^11.1.28","@types/express":"^5.0.0","@nestjs/testing":"^11.1.28","@types/react-dom":"^19.0.0","@types/supertest":"^7.2.1","reflect-metadata":"^0.2.2","socket.io-client":"^4.0.0","@nestjs/websockets":"^11.1.28","@types/ioredis-mock":"^8.2.7","@types/jsonwebtoken":"^9.0.10","eslint-plugin-import":"^2.29.0","@arethetypeswrong/cli":"^0.18.5","@stryker-mutator/core":"^9.6.1","@testing-library/react":"^16.0.0","eslint-config-prettier":"^10.1.8","eslint-plugin-prettier":"^5.0.0","eslint-plugin-security":"^4.0.1","jest-environment-jsdom":"^30.0.0","@nestjs/platform-express":"^11.1.28","@socket.io/redis-adapter":"^8.0.0","@typescript-eslint/parser":"^8.65.0","eslint-plugin-react-hooks":"^7.1.1","@nestjs/platform-socket.io":"^11.1.28","@stryker-mutator/jest-runner":"^9.6.1","@typescript-eslint/eslint-plugin":"^8.65.0","eslint-import-resolver-typescript":"^4.4.5","@stryker-mutator/typescript-checker":"^9.6.1"},"peerDependencies":{"rxjs":"^7.8.0","react":"^19.0.0","ioredis":"^6.0.0","react-dom":"^19.0.0","socket.io":"^4.6.2","@nestjs/core":"^11.1.18","@nestjs/common":"^11.0.16","reflect-metadata":"^0.2.0","socket.io-client":"^4.0.0","@nestjs/websockets":"^11.0.0","@socket.io/redis-adapter":"^8.0.0","@nestjs/platform-socket.io":"^11.0.0"},"peerDependenciesMeta":{"react":{"optional":true},"ioredis":{"optional":true},"react-dom":{"optional":true},"socket.io":{"optional":true},"socket.io-client":{"optional":true},"@nestjs/websockets":{"optional":true},"@socket.io/redis-adapter":{"optional":true},"@nestjs/platform-socket.io":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/nest-realtime_1.1.1_1787304418991_0.3351503113845724","host":"s3://npm-registry-packages-npm-production"}},"1.1.2":{"name":"@bymax-one/nest-realtime","version":"1.1.2","keywords":["nestjs","realtime","sse","server-sent-events","websocket","socket.io","pubsub","redis","multi-tenant","presence","event-replay","offline-queue","react-hooks","typescript"],"author":{"name":"Bymax One","email":"support@bymax.one"},"license":"MIT","_id":"@bymax-one/nest-realtime@1.1.2","maintainers":[{"name":"bymax.one","email":"bymaxone.core@gmail.com"},{"name":"msalvatti","email":"msalvatti@gmail.com"}],"homepage":"https://github.com/bymaxone/nest-realtime#readme","bugs":{"url":"https://github.com/bymaxone/nest-realtime/issues"},"dist":{"shasum":"82ef7cbe7e85c7ba356def030151191f67f9fbdc","tarball":"https://registry.npmjs.org/@bymax-one/nest-realtime/-/nest-realtime-1.1.2.tgz","fileCount":24,"integrity":"sha512-00PWOuDKSvwUbOhZX+zn3INBEBN1Oztqv7VoSjxob6HpO+miiqTgrF/jTDT11s8cf+M9Mx34BozyNZs0wSmqeg==","signatures":[{"sig":"MEUCIGlU9HPJ8ENhRP5Y95hF/y4RhRwA1zlYBgLaENTowioXAiEAkIRkQRp8SKp4EUoOYv5CniEpgfnDP9cw04xYPe02AaM=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bymax-one%2fnest-realtime@1.1.2","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":465131},"main":"./dist/server/index.cjs","type":"module","types":"./dist/server/index.d.cts","module":"./dist/server/index.mjs","engines":{"node":">=24.0.0"},"exports":{".":{"import":{"types":"./dist/server/index.d.ts","default":"./dist/server/index.mjs"},"require":{"types":"./dist/server/index.d.cts","default":"./dist/server/index.cjs"}},"./react":{"import":{"types":"./dist/react/index.d.ts","default":"./dist/react/index.mjs"},"require":{"types":"./dist/react/index.d.cts","default":"./dist/react/index.cjs"}},"./shared":{"import":{"types":"./dist/shared/index.d.ts","default":"./dist/shared/index.mjs"},"require":{"types":"./dist/shared/index.d.cts","default":"./dist/shared/index.cjs"}},"./internal":{"import":{"types":"./dist/internal/index.d.ts","default":"./dist/internal/index.mjs"},"require":{"types":"./dist/internal/index.d.cts","default":"./dist/internal/index.cjs"}},"./websocket":{"import":{"types":"./dist/websocket/index.d.ts","default":"./dist/websocket/index.mjs"},"require":{"types":"./dist/websocket/index.d.cts","default":"./dist/websocket/index.cjs"}},"./package.json":"./package.json"},"gitHead":"5733312212b94940cac2a6605c832553e96294d8","scripts":{"lint":"eslint src","size":"node scripts/check-size.mjs","test":"jest","build":"pnpm clean && tsup","clean":"rm -rf dist coverage","release":"npm publish --provenance --access public","lint:fix":"eslint src --fix","mutation":"stryker run","test:all":"pnpm test && pnpm test:e2e","test:cov":"jest --config jest.coverage.config.ts --coverage","test:e2e":"jest --config jest.e2e.config.ts","typecheck":"tsc --noEmit && tsc --noEmit -p tsconfig.server.json","test:watch":"jest --watch","test:cov:all":"jest --config jest.coverage.config.ts --coverage","check:exports":"attw --pack . --profile strict","check:mutants":"node scripts/check-mutation-directives.mjs","check:runtime":"node scripts/check-consumer-runtime.mjs","check:surface":"node scripts/check-public-surface.mjs","mutation:full":"node -e \"require('node:fs').rmSync('reports/stryker-incremental.json',{force:true,recursive:true})\" && stryker run","prepublishOnly":"pnpm clean && pnpm typecheck && pnpm lint && pnpm check:mutants && pnpm test:cov:all && pnpm build && pnpm check:published && pnpm check:surface && pnpm check:runtime","check:published":"node scripts/check-published-surface.mjs","mutation:dry-run":"stryker run --dryRunOnly","test:e2e:cross-instance":"jest --config jest.e2e.cross-instance.config.ts --runInBand"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:b3e65d80-dbcf-4d5e-a432-1b66b30b7aca"}},"repository":{"url":"git+https://github.com/bymaxone/nest-realtime.git","type":"git"},"_npmVersion":"11.17.0","description":"Realtime backend → frontend communication for NestJS — dual-transport SSE (default) and WebSocket (opt-in) with a unified server-side API.","directories":{},"sideEffects":false,"_nodeVersion":"24.19.0","publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"typesVersions":{"*":{"react":["./dist/react/index.d.cts"],"shared":["./dist/shared/index.d.cts"],"internal":["./dist/internal/index.d.cts"],"websocket":["./dist/websocket/index.d.cts"]}},"_hasShrinkwrap":false,"packageManager":"pnpm@11.20.0","devDependencies":{"jest":"^30.0.0","rxjs":"^7.8.0","tsup":"^8.0.0","react":"^19.2.8","tslib":"^2.6.0","eslint":"^10.8.0","ioredis":"^6.0.0","ts-jest":"^29.4.12","ts-node":"^10.9.0","prettier":"^3.9.6","react-dom":"^19.2.8","socket.io":"^4.6.2","supertest":"^7.0.0","typescript":"^5.6.0","@types/jest":"^30.0.0","@types/node":"^26.1.1","eventsource":"^5.1.0","@nestjs/core":"^11.1.28","@types/react":"^19.0.0","ioredis-mock":"^8.0.0","jsonwebtoken":"^9.0.3","@nestjs/common":"^11.1.28","@types/express":"^5.0.0","@nestjs/testing":"^11.1.28","@types/react-dom":"^19.0.0","@types/supertest":"^7.2.1","reflect-metadata":"^0.2.2","socket.io-client":"^4.0.0","@nestjs/websockets":"^11.1.28","@types/ioredis-mock":"^8.2.7","@types/jsonwebtoken":"^9.0.10","eslint-plugin-import":"^2.29.0","@arethetypeswrong/cli":"^0.18.5","@stryker-mutator/core":"^9.6.1","@testing-library/react":"^16.0.0","eslint-config-prettier":"^10.1.8","eslint-plugin-prettier":"^5.0.0","eslint-plugin-security":"^4.0.1","jest-environment-jsdom":"^30.0.0","@nestjs/platform-express":"^11.1.28","@socket.io/redis-adapter":"^8.0.0","@typescript-eslint/parser":"^8.65.0","eslint-plugin-react-hooks":"^7.1.1","@nestjs/platform-socket.io":"^11.1.28","@stryker-mutator/jest-runner":"^9.6.1","@typescript-eslint/eslint-plugin":"^8.65.0","eslint-import-resolver-typescript":"^4.4.5","@stryker-mutator/typescript-checker":"^9.6.1"},"peerDependencies":{"rxjs":"^7.8.0","react":"^19.0.0","ioredis":"^6.0.0","react-dom":"^19.0.0","socket.io":"^4.6.2","@nestjs/core":"^11.1.18","@nestjs/common":"^11.0.16","reflect-metadata":"^0.2.0","socket.io-client":"^4.0.0","@nestjs/websockets":"^11.0.0","@socket.io/redis-adapter":"^8.0.0","@nestjs/platform-socket.io":"^11.0.0"},"peerDependenciesMeta":{"react":{"optional":true},"ioredis":{"optional":true},"react-dom":{"optional":true},"socket.io":{"optional":true},"socket.io-client":{"optional":true},"@nestjs/websockets":{"optional":true},"@socket.io/redis-adapter":{"optional":true},"@nestjs/platform-socket.io":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/nest-realtime_1.1.2_1787340873366_0.7507419073808115","host":"s3://npm-registry-packages-npm-production"}},"1.2.0":{"name":"@bymax-one/nest-realtime","version":"1.2.0","description":"Realtime backend → frontend communication for NestJS — dual-transport SSE (default) and WebSocket (opt-in) with a unified server-side API.","author":{"name":"Bymax One","email":"support@bymax.one"},"license":"MIT","homepage":"https://github.com/bymaxone/nest-realtime#readme","repository":{"type":"git","url":"git+https://github.com/bymaxone/nest-realtime.git"},"bugs":{"url":"https://github.com/bymaxone/nest-realtime/issues"},"type":"module","sideEffects":false,"main":"./dist/server/index.cjs","module":"./dist/server/index.mjs","types":"./dist/server/index.d.cts","exports":{".":{"import":{"types":"./dist/server/index.d.ts","default":"./dist/server/index.mjs"},"require":{"types":"./dist/server/index.d.cts","default":"./dist/server/index.cjs"}},"./shared":{"import":{"types":"./dist/shared/index.d.ts","default":"./dist/shared/index.mjs"},"require":{"types":"./dist/shared/index.d.cts","default":"./dist/shared/index.cjs"}},"./react":{"import":{"types":"./dist/react/index.d.ts","default":"./dist/react/index.mjs"},"require":{"types":"./dist/react/index.d.cts","default":"./dist/react/index.cjs"}},"./websocket":{"import":{"types":"./dist/websocket/index.d.ts","default":"./dist/websocket/index.mjs"},"require":{"types":"./dist/websocket/index.d.cts","default":"./dist/websocket/index.cjs"}},"./internal":{"import":{"types":"./dist/internal/index.d.ts","default":"./dist/internal/index.mjs"},"require":{"types":"./dist/internal/index.d.cts","default":"./dist/internal/index.cjs"}},"./package.json":"./package.json"},"typesVersions":{"*":{"shared":["./dist/shared/index.d.cts"],"react":["./dist/react/index.d.cts"],"websocket":["./dist/websocket/index.d.cts"],"internal":["./dist/internal/index.d.cts"]}},"scripts":{"build":"pnpm clean && tsup","check:exports":"attw --pack . --profile strict","check:mutants":"node scripts/check-mutation-directives.mjs","check:published":"node scripts/check-published-surface.mjs","check:runtime":"node scripts/check-consumer-runtime.mjs","check:surface":"node scripts/check-public-surface.mjs","clean":"rm -rf dist coverage","lint":"eslint src","lint:fix":"eslint src --fix","mutation":"stryker run","mutation:full":"node -e \"require('node:fs').rmSync('reports/stryker-incremental.json',{force:true,recursive:true})\" && stryker run","mutation:dry-run":"stryker run --dryRunOnly","prepublishOnly":"pnpm clean && pnpm typecheck && pnpm lint && pnpm check:mutants && pnpm test:cov:all && pnpm build && pnpm check:published && pnpm check:surface && pnpm check:runtime","release":"npm publish --provenance --access public","size":"node scripts/check-size.mjs","test":"jest","test:all":"pnpm test && pnpm test:e2e","test:cov":"jest --config jest.coverage.config.ts --coverage","test:cov:all":"jest --config jest.coverage.config.ts --coverage","test:e2e":"jest --config jest.e2e.config.ts","test:e2e:cross-instance":"jest --config jest.e2e.cross-instance.config.ts --runInBand","test:watch":"jest --watch","typecheck":"tsc --noEmit && tsc --noEmit -p tsconfig.server.json"},"peerDependencies":{"@nestjs/common":"^11.0.16","@nestjs/core":"^11.1.18","@nestjs/platform-socket.io":"^11.0.0","@nestjs/websockets":"^11.0.0","@socket.io/redis-adapter":"^8.0.0","ioredis":"^6.0.0","react":"^19.0.0","react-dom":"^19.0.0","reflect-metadata":"^0.2.0","rxjs":"^7.8.0","socket.io":"^4.6.2","socket.io-client":"^4.0.0"},"peerDependenciesMeta":{"@nestjs/websockets":{"optional":true},"@nestjs/platform-socket.io":{"optional":true},"socket.io":{"optional":true},"@socket.io/redis-adapter":{"optional":true},"ioredis":{"optional":true},"react":{"optional":true},"react-dom":{"optional":true},"socket.io-client":{"optional":true}},"devDependencies":{"@arethetypeswrong/cli":"^0.18.5","@nestjs/common":"^11.1.28","@nestjs/core":"^11.1.28","@nestjs/platform-express":"^11.1.28","@nestjs/platform-socket.io":"^11.1.28","@nestjs/testing":"^11.1.28","@nestjs/websockets":"^11.1.28","@socket.io/redis-adapter":"^8.0.0","@stryker-mutator/core":"^9.6.1","@stryker-mutator/jest-runner":"^9.6.1","@stryker-mutator/typescript-checker":"^9.6.1","@testing-library/react":"^16.0.0","@types/express":"^5.0.0","@types/ioredis-mock":"^8.2.7","@types/jest":"^30.0.0","@types/jsonwebtoken":"^9.0.10","@types/node":"^26.1.1","@types/react":"^19.0.0","@types/react-dom":"^19.0.0","@types/supertest":"^7.2.1","@typescript-eslint/eslint-plugin":"^8.65.0","@typescript-eslint/parser":"^8.65.0","eslint":"^10.8.0","eslint-config-prettier":"^10.1.8","eslint-import-resolver-typescript":"^4.4.5","eslint-plugin-import":"^2.29.0","eslint-plugin-prettier":"^5.0.0","eslint-plugin-react-hooks":"^7.1.1","eslint-plugin-security":"^4.0.1","eventsource":"^5.1.0","ioredis":"^6.0.0","ioredis-mock":"^8.0.0","jest":"^30.0.0","jest-environment-jsdom":"^30.0.0","jsonwebtoken":"^9.0.3","prettier":"^3.9.6","react":"^19.2.8","react-dom":"^19.2.8","reflect-metadata":"^0.2.2","rxjs":"^7.8.0","socket.io":"^4.6.2","socket.io-client":"^4.0.0","supertest":"^7.0.0","ts-jest":"^29.4.12","ts-node":"^10.9.0","tslib":"^2.6.0","tsup":"^8.0.0","typescript":"^5.6.0"},"packageManager":"pnpm@11.20.0","engines":{"node":">=24.0.0"},"keywords":["nestjs","realtime","sse","server-sent-events","websocket","socket.io","pubsub","redis","multi-tenant","presence","event-replay","offline-queue","react-hooks","typescript"],"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"gitHead":"1503499c3761d88e461bf159d465879b6f3099dd","_id":"@bymax-one/nest-realtime@1.2.0","_nodeVersion":"24.19.0","_npmVersion":"11.17.0","dist":{"integrity":"sha512-EeM4s3pjg1mym78dsxl6CoZNBPoVBYqMJm68cz55cXr5EtD7pLRnynLSZBJPjB+mQpIAVUFlu/oEiQNBP4gyrw==","shasum":"9b2420b76a0b19d81709a40692251e12284af7a2","tarball":"https://registry.npmjs.org/@bymax-one/nest-realtime/-/nest-realtime-1.2.0.tgz","fileCount":24,"unpackedSize":484960,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bymax-one%2fnest-realtime@1.2.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQD2ZcTPCxw57JrHS7B9IIicBLmNGNxVgONw8NK61vIhrwIhAOIq4bKkG347Gh+7SvGlf23hFcTUke+2WFskYC0H055T"}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:b3e65d80-dbcf-4d5e-a432-1b66b30b7aca"}},"directories":{},"maintainers":[{"name":"bymax.one","email":"bymaxone.core@gmail.com"},{"name":"msalvatti","email":"msalvatti@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/nest-realtime_1.2.0_1788090389999_0.7216214094505262"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-02T12:55:51.523Z","modified":"2026-08-30T11:46:30.488Z","1.0.0":"2026-08-02T12:55:51.910Z","1.0.1":"2026-08-02T15:04:26.796Z","1.0.2":"2026-08-02T17:31:13.771Z","1.0.3":"2026-08-02T19:18:36.578Z","1.0.4":"2026-08-02T20:16:27.647Z","1.0.5":"2026-08-04T15:44:52.813Z","1.0.6":"2026-08-06T11:31:23.061Z","1.1.0":"2026-08-11T11:02:08.384Z","1.1.1":"2026-08-21T09:26:59.123Z","1.1.2":"2026-08-21T19:34:33.515Z","1.2.0":"2026-08-30T11:46:30.153Z"},"bugs":{"url":"https://github.com/bymaxone/nest-realtime/issues"},"author":{"name":"Bymax One","email":"support@bymax.one"},"license":"MIT","homepage":"https://github.com/bymaxone/nest-realtime#readme","keywords":["nestjs","realtime","sse","server-sent-events","websocket","socket.io","pubsub","redis","multi-tenant","presence","event-replay","offline-queue","react-hooks","typescript"],"repository":{"type":"git","url":"git+https://github.com/bymaxone/nest-realtime.git"},"description":"Realtime backend → frontend communication for NestJS — dual-transport SSE (default) and WebSocket (opt-in) with a unified server-side API.","maintainers":[{"name":"bymax.one","email":"bymaxone.core@gmail.com"},{"name":"msalvatti","email":"msalvatti@gmail.com"}],"readme":"<p align=\"center\">\n  <img src=\"https://img.shields.io/badge/%40bymax--one-nest--realtime-000000?style=for-the-badge&logo=nestjs&logoColor=E0234E\" alt=\"@bymax-one/nest-realtime\" />\n</p>\n\n<h1 align=\"center\">@bymax-one/nest-realtime</h1>\n\n<p align=\"center\">\n  <strong>Dual-transport realtime for NestJS & React</strong><br />\n  <sub>SSE by Default · WebSocket Opt-In · Multi-Tenant Rooms · Auth Inversion · Zero Runtime Dependencies</sub>\n</p>\n\n<p align=\"center\">\n  <a href=\"https://www.npmjs.com/package/@bymax-one/nest-realtime\"><img src=\"https://img.shields.io/npm/v/@bymax-one/nest-realtime?style=flat-square&colorA=000000&colorB=000000\" alt=\"npm version\" /></a>\n  <a href=\"https://www.npmjs.com/package/@bymax-one/nest-realtime\"><img src=\"https://img.shields.io/npm/dm/@bymax-one/nest-realtime?style=flat-square&colorA=000000&colorB=000000\" alt=\"npm downloads\" /></a>\n  <a href=\"https://github.com/bymaxone/nest-realtime/actions/workflows/ci.yml\"><img src=\"https://img.shields.io/github/actions/workflow/status/bymaxone/nest-realtime/ci.yml?branch=main&style=flat-square&colorA=000000&label=CI\" alt=\"CI status\" /></a>\n  <a href=\"https://github.com/bymaxone/nest-realtime/actions/workflows/ci.yml\"><img src=\"https://img.shields.io/badge/coverage-100%25-brightgreen?style=flat-square&colorA=000000\" alt=\"coverage\" /></a>\n  <a href=\"https://github.com/bymaxone/nest-realtime/blob/main/docs/mutation_testing_results.md\"><img src=\"https://img.shields.io/badge/mutation-100.00%25-brightgreen?style=flat-square&colorA=000000\" alt=\"mutation score\" /></a>\n  <a href=\"https://scorecard.dev/viewer/?uri=github.com/bymaxone/nest-realtime\"><img src=\"https://api.scorecard.dev/projects/github.com/bymaxone/nest-realtime/badge?style=flat-square\" alt=\"OpenSSF Scorecard\" /></a>\n  <a href=\"https://github.com/bymaxone/nest-realtime/blob/main/LICENSE\"><img src=\"https://img.shields.io/github/license/bymaxone/nest-realtime?style=flat-square&colorA=000000&colorB=000000\" alt=\"license\" /></a>\n  <a href=\"https://www.typescriptlang.org/\"><img src=\"https://img.shields.io/badge/TypeScript-strict-3178C6?style=flat-square&logo=typescript&logoColor=white\" alt=\"TypeScript\" /></a>\n  <a href=\"https://nodejs.org/\"><img src=\"https://img.shields.io/badge/Node.js-24%2B-339933?style=flat-square&logo=node.js&logoColor=white\" alt=\"Node.js\" /></a>\n</p>\n\n<p align=\"center\">\n  <a href=\"https://github.com/bymaxone/nest-realtime\">GitHub</a> ·\n  <a href=\"https://github.com/bymaxone/nest-realtime/issues\">Issues</a> ·\n  <a href=\"#-quick-start\">Quick Start</a> ·\n  <a href=\"#-api-reference\">API Reference</a> ·\n  <a href=\"https://github.com/bymaxone/nest-realtime-example\">Example App</a>\n</p>\n\n---\n\n## ✨ Overview\n\n`@bymax-one/nest-realtime` is a **transport-agnostic realtime channel** shipped as a single npm package with **4 subpath exports** — covering the SSE server module, the WebSocket module, the shared type contracts, and the React hooks that consume them.\n\nThe default transport is **Server-Sent Events**: browser-native, plain HTTP, no client library to ship. WebSocket via Socket.IO is one config flag away, and `'both'` runs them side by side. Your application code calls the same `RealtimeService` either way — the transport is a deployment decision, not an architectural one.\n\n### Why nest-realtime?\n\n- **🎯 One API, any transport** — `emitToUser`, `emitToTenant`, `emitToRoom`, `broadcast`, and `disconnect` behave identically on SSE, WebSocket, or both. Migrating between transports changes a config line, not your call sites.\n- **📡 SSE first, and that is the point** — SSE reconnects natively, replays through `Last-Event-ID`, and traverses proxies as ordinary HTTP. Most apps only push server → client, and for those WebSocket is a dependency you pay for without using.\n- **🔌 Auth stays yours** — The library never verifies a JWT, never imports an auth package. You implement `IConnectionAuthenticator`; cookie, ticket, and bearer patterns are all supported. This is enforced structurally, not by convention.\n- **🏢 Multi-tenant by construction** — Every connection auto-joins `user:{id}` and, when present, `tenant:{id}`. Tenant scoping is a room convention, not an afterthought bolted onto a chat library.\n- **🪶 Nothing in your bundle you didn't ask for** — `\"dependencies\": {}` on the server, and `socket.io-client` is reached through `await import()` so an SSE-only frontend ships **2.26 KB brotli** instead of ~80 KB.\n\n```\npnpm add @bymax-one/nest-realtime\n```\n\n---\n\n## 🔥 Features\n\n### 📡 Transports\n\n- ✅ **SSE (default)** — native browser reconnect, `Last-Event-ID` replay, `: keepalive` heartbeat tuned for real proxies\n- ✅ **WebSocket (opt-in)** — Socket.IO gateway behind the `/websocket` subpath, with full-duplex `emit`; an SSE app never installs it\n- ✅ **Composite mode** — `transport: 'both'`, also on the `/websocket` subpath, fans a single emit out to clients on either transport\n- ✅ **Unified contract** — every transport implements `ITransport`, so `RealtimeService` never branches\n\n### 🔐 Security & Auth\n\n- ✅ **Auth inversion** — the consumer plugs `IConnectionAuthenticator`; the library owns no credentials\n- ✅ **Zero auth imports** — `src/` contains no reference to `@nestjs/jwt`, `passport-*`, or any auth package, verified in CI\n- ✅ **Three handshake patterns** — HttpOnly cookie (SSE-safe), one-time ticket, and bearer header (WebSocket only)\n- ✅ **Periodic re-authentication** — revalidate credentials on an interval, with a positive-result cache\n- ✅ **Tenant from the token, never the body** — `tenantId` comes from the authenticated result via `tenantResolver`\n\n### 🏢 Multi-Tenant & Delivery\n\n- ✅ **Room conventions** — `user:{id}`, `tenant:{id}`, `resource:{type}:{id}`, with the first two auto-joined\n- ✅ **Offline queue** — `IOfflineQueueStorage` holds events for absent users and flushes them on reconnect\n- ✅ **Replay buffer** — per-user ring buffer answers `Last-Event-ID` after a dropped SSE stream\n- ✅ **FIFO connection limits** — the oldest connection is evicted, so a new tab never gets a 429\n- ✅ **Presence** — optional `IPresenceStorage` powers `presence:online` / `presence:offline`\n\n### 🧩 Developer Experience\n\n- ✅ **4 subpath exports** — server, WebSocket, shared types, and React; tree-shakeable, ESM + CJS dual output\n- ✅ **Dynamic module** — `forRoot()` and `forRootAsync()` with `useFactory` / `useClass` / `useExisting`\n- ✅ **Strict TypeScript, zero `any`** — `noUncheckedIndexedAccess` and `exactOptionalPropertyTypes` on\n- ✅ **Horizontal scaling** — `IRealtimePubSub` for SSE fan-out, `@socket.io/redis-adapter` for WebSocket\n- ✅ **Bundle gate in CI** — the SSE-only React bundle is measured on every build and fails over budget\n\n---\n\n## 📦 Subpath Exports\n\nOne package, four entry points — import only what your app needs:\n\n| Subpath       | Import                               | Purpose                                        |                           Dependencies                            |\n| ------------- | ------------------------------------ | ---------------------------------------------- | :---------------------------------------------------------------: |\n| **Server**    | `@bymax-one/nest-realtime`           | NestJS module for SSE, services, contracts     |                          NestJS 11, rxjs                          |\n| **WebSocket** | `@bymax-one/nest-realtime/websocket` | NestJS module for `'websocket'` and `'both'`   | + `@nestjs/websockets`, `@nestjs/platform-socket.io`, `socket.io` |\n| **Shared**    | `@bymax-one/nest-realtime/shared`    | Types, room prefixes, event names, error codes |                               None                                |\n| **React**     | `@bymax-one/nest-realtime/react`     | Hooks & `RealtimeProvider`                     |                             React 19                              |\n\n```\n       shared (zero deps)\n      ↗        ↑        ↖\n server    websocket    react\n```\n\nThe `shared` subpath carries no Node or NestJS import, so the same event names and error codes are used on both sides of the wire without duplicating a constant.\n\n**The `websocket` subpath exists so the server one costs nothing extra.** Everything that touches `@nestjs/websockets`, `@nestjs/platform-socket.io` and `socket.io` is reachable only through it, so an application on SSE — the default transport — never installs the Socket.IO stack. Importing it is what opts in, in the install as much as in the configuration.\n\nThe package also exposes `@bymax-one/nest-realtime/internal`. It is **not public API** and carries no compatibility promise — it is the shared runtime the two server subpaths import so that a service registered through one and injected through the other is the same class. Import the four above; nothing you need is only there.\n\n---\n\n> [!TIP]\n> Prefer to learn from a working app? See the [nest-realtime-example](https://github.com/bymaxone/nest-realtime-example) — a NestJS + React project wired with this library.\n\n## 🚀 Quick Start\n\n### 1. Install\n\n```bash\n# Using pnpm (recommended)\npnpm add @bymax-one/nest-realtime\n\n# Using npm\nnpm install @bymax-one/nest-realtime\n\n# Using yarn\nyarn add @bymax-one/nest-realtime\n```\n\n> [!IMPORTANT]\n> You must also install the **peer dependencies** for the subpaths you use. The library declares `\"dependencies\": {}` — nothing is installed on your behalf.\n\n```bash\n# Server subpath (required)\npnpm add @nestjs/common @nestjs/core rxjs reflect-metadata\n\n# WebSocket subpath (optional — only when you import @bymax-one/nest-realtime/websocket)\npnpm add @nestjs/websockets @nestjs/platform-socket.io socket.io\n\n# Horizontal scaling (optional)\npnpm add ioredis @socket.io/redis-adapter\n\n# React subpath (optional)\npnpm add react react-dom\n\n# WebSocket on the frontend (optional)\npnpm add socket.io-client\n```\n\n### 2. Implement the Authenticator Interface\n\nThis is the only interface you are required to provide. The library calls it once per connection and never inspects the credential itself.\n\n```typescript\nimport type {\n  IConnectionAuthenticator,\n  ConnectionAuthContext,\n  AuthenticationResult,\n} from '@bymax-one/nest-realtime'\nimport { verifyAccessToken } from './jwt'\n\nexport class CookieAuthenticator implements IConnectionAuthenticator {\n  async authenticate(ctx: ConnectionAuthContext): Promise<AuthenticationResult | null> {\n    const token = ctx.cookies['access_token']\n    if (!token) return null\n\n    try {\n      const claims = await verifyAccessToken(token)\n      return { userId: claims.sub, tenantId: claims.tid }\n    } catch {\n      return null // null rejects the connection\n    }\n  }\n}\n```\n\n> [!NOTE]\n> Returning `null` rejects the connection — throwing is not required. See [Auth Inversion](#-security-model) for the ticket and bearer patterns.\n\n### 3. Register the Module\n\n```typescript\n// app.module.ts\nimport { Module } from '@nestjs/common'\nimport { BymaxRealtimeModule } from '@bymax-one/nest-realtime'\nimport { CookieAuthenticator } from './auth/cookie-authenticator'\n\n@Module({\n  imports: [\n    BymaxRealtimeModule.forRoot({\n      transport: 'sse',\n      authenticator: new CookieAuthenticator(),\n    }),\n  ],\n})\nexport class AppModule {}\n```\n\nClients now connect to `GET /realtime/sse` (configurable via `sse.endpoint`).\n\n### 4. Emit From Anywhere\n\nInject `RealtimeService` into any provider or controller:\n\n```typescript\nimport { Controller, Post, Body, Param } from '@nestjs/common'\nimport { RealtimeService } from '@bymax-one/nest-realtime'\n\n@Controller('invoices')\nexport class InvoicesController {\n  constructor(private readonly realtime: RealtimeService) {}\n\n  @Post(':id/pay')\n  async pay(@Param('id') invoiceId: string, @Body() body: { userId: string; amount: number }) {\n    await this.realtime.emitToUser(body.userId, 'invoice.paid', {\n      invoiceId,\n      amount: body.amount,\n    })\n  }\n}\n```\n\n> [!IMPORTANT]\n> The emit signature is `(target, eventName, data)` — three arguments. The event name is a separate string, not a field inside the payload.\n\n### 5. Consume in React\n\nWrap the subtree once, then read from it with hooks:\n\n```tsx\n// App.tsx\nimport { RealtimeProvider } from '@bymax-one/nest-realtime/react'\n\nexport default function App() {\n  return (\n    <RealtimeProvider options={{ url: '/realtime/sse', eventNames: ['invoice.paid'] }}>\n      <InvoiceList />\n    </RealtimeProvider>\n  )\n}\n```\n\n```tsx\n// InvoiceList.tsx\nimport { useRealtimeContext } from '@bymax-one/nest-realtime/react'\n\nexport function InvoiceList() {\n  const { events, connected } = useRealtimeContext()\n\n  return (\n    <>\n      <span>{connected ? '🟢 live' : '🔴 disconnected'}</span>\n      <ul>\n        {events.map((e, i) => (\n          <li key={i}>\n            {String(e.type)} — {JSON.stringify(e.data)}\n          </li>\n        ))}\n      </ul>\n    </>\n  )\n}\n```\n\n> [!NOTE]\n> Events carry an `id` on the SSE branch only — the WebSocket branch has no per-event id, so the shared context type does not expose one. Narrow on `transport === 'sse'` if you need it.\n\n> [!IMPORTANT]\n> `EventSource` delivers a **named** event only to a listener registered for that exact name. Application-level names such as `invoice.paid` must be listed in `eventNames`, or they never reach the hook. Reserved and presence events are subscribed automatically.\n\n### 6. Scale Across Instances\n\nSSE connections live on the instance that accepted them. To fan an emit out to every instance, provide a pub/sub backend:\n\n```typescript\nimport {\n  BymaxRealtimeModule,\n  RedisRealtimePubSub,\n  RedisOfflineQueue,\n} from '@bymax-one/nest-realtime'\nimport Redis from 'ioredis'\n\nconst redis = new Redis(process.env.REDIS_URL ?? 'redis://localhost:6379')\n\nBymaxRealtimeModule.forRoot({\n  transport: 'sse',\n  authenticator: new CookieAuthenticator(),\n  pubsub: new RedisRealtimePubSub({ client: redis }),\n  offlineQueue: new RedisOfflineQueue({ client: redis }),\n  sse: { heartbeatMs: 25_000, replayBufferSize: 100, maxConnectionsPerUser: 5 },\n})\n```\n\nFor WebSocket, pass an ioredis client to `websocket.redisAdapter.pubClient` and `@socket.io/redis-adapter` is registered for you.\n\n---\n\n## ⚙️ Configuration\n\nAll options are passed to `forRoot()` / `forRootAsync()`. Only `transport` and `authenticator` are required.\n\n| Group                      | Key Options                                                                                                                                 | Default                                      |\n| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- |\n| **transport**              | `'sse'` \\| `'websocket'` \\| `'both'`                                                                                                        | — (**required**)                             |\n| **authenticator**          | `IConnectionAuthenticator`                                                                                                                  | — (**required**)                             |\n| **tenantResolver**         | `(auth) => string \\| undefined` — derives `tenantId` from the auth result                                                                   | `auth.tenantId`                              |\n| **hooks**                  | `onConnect`, `onDisconnect`, `onError`, `onReauthenticationFailed` (fire-and-forget)                                                        | —                                            |\n| **pubsub**                 | `IRealtimePubSub` — cross-instance fan-out for SSE                                                                                          | `InMemoryPubSub`                             |\n| **offlineQueue**           | `IOfflineQueueStorage` — events held while a user is away                                                                                   | — (disabled)                                 |\n| **presence**               | `IPresenceStorage` — online-user tracking                                                                                                   | — (disabled)                                 |\n| **sse**                    | `endpoint`, `heartbeatMs`, `replayBufferSize`, `maxConnectionsPerUser`, `emitConnectionEvent`                                               | `/realtime/sse`, `30000`, `100`, `5`, `true` |\n| **websocket**              | `namespace`, `cors`, `maxHttpBufferSize`, `pingIntervalMs`, `pingTimeoutMs`, `maxConnectionsPerUser`, `emitConnectionEvent`, `redisAdapter` | `'/'`, — (Socket.IO defaults)                |\n| **reauthenticationPolicy** | `intervalSeconds`, `onFailure` (`'disconnect'` \\| `'event'`), `cacheTtlMs`                                                                  | `300`, `'disconnect'`, `60000`               |\n\n> [!NOTE]\n> `websocket.maxConnectionsPerUser` is **opt-in** and unlimited unless set to a positive number, while `sse.maxConnectionsPerUser` defaults to `5`. The two transports differ here on purpose: an SSE stream holds an HTTP connection open, a Socket.IO client multiplexes.\n\n> [!IMPORTANT]\n> **`forRootAsync` takes the SSE route on the registration, not from the factory.** NestJS\n> registers controllers at decoration time, before any factory has run, so `sse.endpoint`\n> cannot move the route there. Declare it as `sseEndpoint` alongside `transport`:\n>\n> ```typescript\n> BymaxRealtimeModule.forRootAsync({\n>   transport: 'sse',\n>   sseEndpoint: '/realtime/sse', // defaults to '/events'\n>   inject: [ConfigService],\n>   useFactory: (cfg: ConfigService) => ({ transport: 'sse', authenticator: ... }),\n> })\n> ```\n>\n> The async default is `'/events'`, not the `forRoot` default of `'/realtime/sse'`. A factory\n> that returns a different `sse.endpoint` is **rejected at bootstrap** rather than ignored, and\n> `REALTIME_OPTIONS_TOKEN` reports the route that was actually bound — so a health check or an\n> OpenAPI document built from the options names the path that exists.\n\nThe full reference, including `forRootAsync` with `useFactory` / `useClass` / `useExisting`, is in [docs/technical_specification.md](./docs/technical_specification.md) §4.\n\n---\n\n## 🏗️ Architecture\n\nThe package runs **inside** your NestJS application as a dynamic module — not as a separate realtime server:\n\n```\n┌────────────────────────────────────────────────────┐\n│              Your NestJS Application               │\n│                                                    │\n│  ┌──────────────────────────────────────────────┐  │\n│  │         @bymax-one/nest-realtime             │  │\n│  │                                              │  │\n│  │   RealtimeService  ──→  ITransport           │  │\n│  │         │                 ├── SseTransport   │  │\n│  │         │                 ├── WebSocket…     │  │\n│  │         │                 └── Composite…     │  │\n│  │         ↓                                    │  │\n│  │   ConnectionRegistry ←→ RoomRegistry         │  │\n│  └────────┬──────────────┬──────────────┬───────┘  │\n│           │              │              │          │\n│   ┌───────▼──────┐ ┌─────▼──────┐ ┌─────▼───────┐  │\n│   │ IConnection  │ │ IRealtime  │ │ IOffline    │  │\n│   │ Authenticator│ │ PubSub     │ │ QueueStorage│  │\n│   │   (yours)    │ │ (optional) │ │ (optional)  │  │\n│   └──────────────┘ └────────────┘ └─────────────┘  │\n└────────────────────────────────────────────────────┘\n              │                        │\n       SSE (HTTP stream)        WebSocket (Socket.IO)\n              └────────→ Browser ←─────┘\n```\n\n### Design Principles\n\n| Principle                 | Description                                                                                                                              |\n| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |\n| **🔌 Interface-Driven**   | Authentication, pub/sub, offline queue, and presence are contracts you implement — Redis reference implementations ship, none are forced |\n| **🔀 Transport-Agnostic** | `RealtimeService` talks to `ITransport`; adding or swapping a transport never touches application code                                   |\n| **🛡️ Auth Inversion**     | The library holds no credential logic, so an auth vulnerability cannot originate here                                                    |\n| **🪶 Zero Runtime Deps**  | `\"dependencies\": {}` — every dependency is a peer, and optional ones are marked in `peerDependenciesMeta`                                |\n| **🌳 Tree-Shakeable**     | `sideEffects: false`, subpath exports, and a dynamic `import()` that keeps `socket.io-client` out of the SSE bundle                      |\n\n### Cross-Instance Emit Flow\n\nAn emit delivers locally **and** publishes once. Every other instance's subscriber re-emits through local-only paths (`emitToUserLocal`, …), so a message is never republished and cannot loop. Connection revocation crosses instances as an `op: 'disconnect'` message on the same channel.\n\nSizing guidance and adapter trade-offs are in [docs/architecture/scaling-cheatsheet.md](./docs/architecture/scaling-cheatsheet.md).\n\n---\n\n## 🔐 Security Model\n\n### Auth Inversion\n\nThis library **never** verifies a JWT, hashes a password, or imports an authentication package. It calls `IConnectionAuthenticator.authenticate()` once per connection and trusts the `AuthenticationResult` you return.\n\nThis is a **structural guarantee**, not a guideline — `src/` is audited in CI for any reference to `@bymax-one/nest-auth`, `@nestjs/jwt`, or `passport-*`:\n\n```bash\ngrep -rE \"@nestjs/jwt|@bymax-one/nest-auth|passport\" src/   # must return zero\n```\n\nThe consequence is worth stating plainly: a credential-handling vulnerability cannot originate in this package, because the code that would contain it does not exist here. It also means **you** are responsible for the bridge — vulnerabilities in a bridge implementation belong to that project, as [SECURITY.md](./SECURITY.md) records.\n\n### Handshake Patterns\n\n| Pattern             | Transport | Credential source              | Notes                                                             |\n| ------------------- | --------- | ------------------------------ | ----------------------------------------------------------------- |\n| **HttpOnly cookie** | SSE + WS  | `ctx.cookies`                  | Recommended. The only pattern where the browser sends it for you. |\n| **One-time ticket** | SSE + WS  | `ctx.query['ticket']`          | Short-lived, single-use. The gateway normalizes it to one string. |\n| **Bearer header**   | WS only   | `ctx.headers['authorization']` | `EventSource` cannot send custom headers — SSE must not use this. |\n\n```typescript\nexport class TicketAuthenticator implements IConnectionAuthenticator {\n  constructor(private readonly tickets: TicketStore) {}\n\n  async authenticate(ctx: ConnectionAuthContext): Promise<AuthenticationResult | null> {\n    const ticket = ctx.query['ticket']\n    if (!ticket || Array.isArray(ticket)) return null\n    return this.tickets.consume(ticket) // null when expired or already used\n  }\n}\n```\n\nA complete `@bymax-one/nest-auth` bridge is in [docs/examples/nest-auth-bridge.md](./docs/examples/nest-auth-bridge.md); the per-transport handshake differences are in [docs/architecture/auth-handshake-differences.md](./docs/architecture/auth-handshake-differences.md).\n\n### Re-Authentication\n\nLong-lived connections outlive short-lived tokens. `reauthenticationPolicy` revalidates on an interval and either drops the connection or emits `connection:reauthentication-failed`, depending on `onFailure`. Successful checks are cached for `cacheTtlMs` so a busy connection is not revalidated on every event.\n\n### Security Checklist\n\nWhen integrating `@bymax-one/nest-realtime` in production, verify each of the following:\n\n- The SSE endpoint is served over HTTPS — `Last-Event-ID` replays past events to whoever reconnects\n- `tenantId` is derived from the authenticated result, never from a query parameter or request body\n- Tickets are single-use and short-lived; `consume()` must be atomic under concurrency\n- `websocket.cors.origin` is an explicit allowlist, not `true`, in any browser-facing deployment\n- `maxConnectionsPerUser` is set for WebSocket if clients are untrusted — it is unlimited by default\n- Reserved event names are not reused for application events (see the [catalog](#reserved-events))\n\n---\n\n## 🛡️ Security Table\n\n| Layer                 | Implementation                                                                 |\n| --------------------- | ------------------------------------------------------------------------------ |\n| Credential validation | Delegated entirely to consumer-supplied `IConnectionAuthenticator`             |\n| Auth library imports  | None — enforced by a CI grep over `src/`                                       |\n| Tenant isolation      | Room-scoped (`tenant:{id}`), resolved from the authenticated result            |\n| Connection revocation | `disconnect(connectionId)`, propagated cross-instance via `op: 'disconnect'`   |\n| Credential expiry     | Interval re-authentication with `disconnect` or event on failure               |\n| Connection flooding   | FIFO eviction per user — oldest closed, new admitted, never a 429              |\n| SSE transport headers | `X-Accel-Buffering: no` and `Cache-Control: no-cache, no-transform` by default |\n| WebSocket origin      | Explicit `cors` config passed straight to Socket.IO                            |\n\n> [!IMPORTANT]\n> This package holds **zero credential logic**. That is its central security property — and it means the strength of your realtime auth is exactly the strength of the authenticator you plug in.\n\n---\n\n## 🧱 Tech Stack\n\n<p>\n  <img src=\"https://img.shields.io/badge/NestJS-11-E0234E?style=flat-square&logo=nestjs&logoColor=white\" alt=\"NestJS\" />\n  <img src=\"https://img.shields.io/badge/TypeScript-strict-3178C6?style=flat-square&logo=typescript&logoColor=white\" alt=\"TypeScript\" />\n  <img src=\"https://img.shields.io/badge/React-19-61DAFB?style=flat-square&logo=react&logoColor=black\" alt=\"React\" />\n  <img src=\"https://img.shields.io/badge/RxJS-7-B7178C?style=flat-square&logo=reactivex&logoColor=white\" alt=\"RxJS\" />\n  <img src=\"https://img.shields.io/badge/Socket.IO-4-010101?style=flat-square&logo=socket.io&logoColor=white\" alt=\"Socket.IO\" />\n  <img src=\"https://img.shields.io/badge/Node.js-24%2B-339933?style=flat-square&logo=node.js&logoColor=white\" alt=\"Node.js\" />\n  <img src=\"https://img.shields.io/badge/Redis-optional-DC382D?style=flat-square&logo=redis&logoColor=white\" alt=\"Redis\" />\n  <img src=\"https://img.shields.io/badge/Jest-30-C21325?style=flat-square&logo=jest&logoColor=white\" alt=\"Jest\" />\n</p>\n\n---\n\n## 🧪 Testing & Quality\n\nA realtime channel fails in ways that are hard to reproduce — dropped streams, races between reconnect and revocation, one instance not seeing another's emit. The suite is built so those failures are caught by a test, not by a user.\n\n- ✅ **100% coverage** — statements, branches, functions, and lines, enforced as a release gate\n- ✅ **100.00% mutation score** — 655 killed and 7 timed out, zero survivors, verified with [Stryker](https://stryker-mutator.io/) (break threshold 100)\n- ✅ **626 tests** — 593 unit and integration across 37 suites, plus 33 end-to-end across 6\n- ✅ **Cross-instance e2e** — real Redis pub/sub fan-out between two live instances, not a mocked channel\n- ✅ **Bundle-size gate** — the SSE-only React bundle is measured on every build and fails over budget\n\n```bash\npnpm test          # unit + integration\npnpm test:e2e      # end-to-end (single instance, no Redis needed)\npnpm test:cov:all  # 100% coverage gate\npnpm mutation      # Stryker mutation testing (~15-25 min)\npnpm build && pnpm size\n```\n\nCross-instance e2e needs a Redis instance:\n\n```bash\nREDIS_URL=redis://localhost:6379 pnpm test:e2e\n```\n\n> [!NOTE]\n> Line coverage proves a line _executed_ under test; mutation testing proves a test _would fail_ if that line were wrong. The full methodology and per-area breakdown are in [docs/mutation_testing_results.md](./docs/mutation_testing_results.md).\n\n---\n\n## 📖 API Reference\n\n### RealtimeService\n\nThe unified emit API. Identical on every transport.\n\n| Method                                | Description                               |\n| ------------------------------------- | ----------------------------------------- |\n| `emitToUser(userId, event, data)`     | Deliver to every connection of one user   |\n| `emitToTenant(tenantId, event, data)` | Deliver to every connection in a tenant   |\n| `emitToRoom(roomId, event, data)`     | Deliver to an arbitrary room              |\n| `broadcast(event, data)`              | Deliver to every connected client         |\n| `joinRoom(connectionId, roomId)`      | Add a connection to a room                |\n| `leaveRoom(connectionId, roomId)`     | Remove a connection from a room           |\n| `disconnect(connectionId, reason?)`   | Revoke a connection, cross-instance aware |\n\nAll methods return `Promise<void>`.\n\n### ConnectionRegistry\n\nRead-side view of live connections — useful for admin endpoints and metrics.\n\n| Method                           | Returns                                          |\n| -------------------------------- | ------------------------------------------------ |\n| `byUser(userId, transport?)`     | `ConnectionRecord[]` for one user                |\n| `byTenant(tenantId, transport?)` | `ConnectionRecord[]` for one tenant              |\n| `allByTransport(transport)`      | `ConnectionRecord[]` on `'sse'` or `'websocket'` |\n| `get(connectionId)`              | `ConnectionRecord \\| undefined`                  |\n| `count()` / `countUsers()`       | Live connection count / distinct user count      |\n\n### Contracts\n\n| Interface                   | Required | Purpose                                                     |\n| --------------------------- | :------: | ----------------------------------------------------------- |\n| `IConnectionAuthenticator`  |    ✅    | Validate a connection, return `userId` / `tenantId`         |\n| `IRealtimePubSub`           |    —     | Cross-instance fan-out for SSE                              |\n| `IOfflineQueueStorage`      |    —     | Hold events for disconnected users                          |\n| `IPresenceStorage`          |    —     | Track who is online                                         |\n| `IConnectionLifecycleHooks` |    —     | Fire-and-forget connect / disconnect / error callbacks      |\n| `ITransport`                |    —     | Implemented by the library; the seam every transport shares |\n\nReference implementations shipped: `InMemoryPubSub` (default), `RedisRealtimePubSub`, `RedisOfflineQueue`.\n\n#### Carrying handshake context into the hooks\n\n`AuthenticationResult.metadata` is a free-form bag the library carries verbatim: it reaches\n`ConnectionEventMeta.metadata` in the three hooks that receive that type — `onConnect`,\n`onDisconnect` and `onReauthenticationFailed` — and is handed back to `revalidate` as part of\nthe original result. It is the only channel that crosses that boundary — `authenticate` sees\nthe request headers but no `connectionId` yet, and those hooks see the `connectionId` but not\nthe headers.\n\n`onError` is the exception and takes a narrower payload (`connectionId?`, `error`, `transport`):\nit can fire before authentication resolves, so there may be no connection to describe, which is\nalso why its `connectionId` is optional. It carries neither `roles` nor `metadata`.\n\n```typescript\nclass TracingAuthenticator implements IConnectionAuthenticator {\n  async authenticate(ctx: ConnectionAuthContext): Promise<AuthenticationResult | null> {\n    const claims = await verify(ctx.cookies['access_token'])\n    if (!claims) return null\n    // ctx.headers keys are lowercased; this is the only place the headers are visible.\n    return { userId: claims.sub, metadata: { traceparent: ctx.headers['traceparent'] } }\n  }\n}\n\nconst hooks: IConnectionLifecycleHooks = {\n  onConnect: (meta) => tracer.recordConnection(meta.connectionId, meta.metadata?.['traceparent']),\n}\n```\n\nLike `roles`, it is a **connect-time snapshot**: a `revalidate` that keeps the connection alive\ndoes not refresh it. The library never reads a key.\n\n### Rooms\n\n| Room ID                | Used for                                        | Auto-joined                   |\n| ---------------------- | ----------------------------------------------- | ----------------------------- |\n| `user:{userId}`        | Every connection belonging to one user          | ✅ Always                     |\n| `tenant:{tenantId}`    | Every connection in a tenant                    | ✅ When `tenantId` is present |\n| `resource:{type}:{id}` | Per-resource events, e.g. `resource:invoice:42` | Manual — call `joinRoom`      |\n\n```typescript\nimport { composeRoomId } from '@bymax-one/nest-realtime'\n\ncomposeRoomId('USER', userId) // → \"user:u_abc\"\ncomposeRoomId('TENANT', tenantId) // → \"tenant:t_acme\"\ncomposeRoomId('RESOURCE', 'invoice', invoiceId) // → \"resource:invoice:42\"\n```\n\n#### Role-scoped delivery\n\nThere is no `emitToRole`. Roles are the consumer's authorization vocabulary, not\nthe transport's — the library carries them through without interpreting them (see\n[Auth Inversion](#auth-inversion)). To deliver to a subset of connections by role,\nbuild a room for it in `onConnect`, where `ConnectionEventMeta.roles` carries the\nsnapshot the authenticator produced:\n\n```typescript\nimport { ModuleRef } from '@nestjs/core'\nimport { BymaxRealtimeModule, RealtimeService } from '@bymax-one/nest-realtime'\n\nBymaxRealtimeModule.forRootAsync({\n  transport: 'sse',\n  inject: [ModuleRef],\n  useFactory: (moduleRef: ModuleRef) => ({\n    transport: 'sse' as const,\n    authenticator: new CookieAuthenticator(),\n    hooks: {\n      onConnect: async (meta) => {\n        if (!meta.roles?.includes('admin')) return\n        // Resolved lazily: the hook runs per connection, long after bootstrap, so\n        // `RealtimeService` — provided by this very module — cannot be injected\n        // into the factory that configures it.\n        const realtime = moduleRef.get(RealtimeService)\n        await realtime.joinRoom(meta.connectionId, 'role:admin')\n      },\n    },\n  }),\n})\n```\n\nThen deliver to exactly those connections from any provider that injects\n`RealtimeService`:\n\n```typescript\nawait this.realtime.emitToRoom('role:admin', 'audit.log', entry)\n```\n\n`meta.roles` is a snapshot taken at connect time; a later `revalidate` that keeps\nthe connection alive does not refresh it. When a role can be revoked mid-session,\nmake `revalidate` return `false` so the connection is torn down and re-established\nwith the new roles.\n\n##### Make the hook testable: depend on the capability, not the injector\n\nThe snippet above works, and it still walks a careful consumer into a wall: a\n`moduleRef.get()` call inside the hook means unit-testing that hook requires\nfaking `ModuleRef`, which in practice means `as unknown as ModuleRef` — a\nsuppression many codebases block outright.\n\nGive the hook the two capabilities it actually uses instead. `ModuleRef` and\n`RealtimeService` satisfy these shapes **structurally**, so the module passes the\nreal objects and a test passes plain ones, with no cast at either end:\n\n```typescript\ninterface RoomJoiner {\n  joinRoom(connectionId: string, roomId: string): Promise<void>\n}\n\ninterface ServiceResolver {\n  get(token: typeof RealtimeService): RoomJoiner\n}\n\nfunction joinRoleRoom(resolver: ServiceResolver, role: string, roomId: string) {\n  return async (meta: ConnectionEventMeta): Promise<void> => {\n    if (!meta.roles?.includes(role)) return\n    await resolver.get(RealtimeService).joinRoom(meta.connectionId, roomId)\n  }\n}\n```\n\nWire it with the real injector:\n\n```typescript\ninject: [ModuleRef],\nuseFactory: (moduleRef: ModuleRef) => ({\n  transport: 'sse' as const,\n  authenticator: new CookieAuthenticator(),\n  hooks: { onConnect: joinRoleRoom(moduleRef, 'admin', 'role:admin') },\n}),\n```\n\nand test it without booting NestJS:\n\n```typescript\nconst joinRoom = jest.fn()\nconst onConnect = joinRoleRoom({ get: () => ({ joinRoom }) }, 'admin', 'role:admin')\n```\n\n> [!NOTE]\n> Match roles by exact membership. `roles?.includes('admin')` on a `readonly string[]`\n> compares whole elements; reaching for `roles?.some((r) => r.includes('admin'))` —\n> for case-insensitivity, say — admits `administrator`, `admin-readonly` and\n> `superadmin`. That failure is an authorization leak, it fails open, and it only\n> shows up for role names nobody declared.\n\n### Reserved Events\n\nEmitted by the library. Do not reuse these names for application events.\n\n| Event                                  | Meaning                                                 |\n| -------------------------------------- | ------------------------------------------------------- |\n| `connection:established`               | Handshake accepted (suppress via `emitConnectionEvent`) |\n| `connection:reauthentication-failed`   | Credentials no longer valid                             |\n| `connection:credential-expiring`       | Credential nearing expiry                               |\n| `room:joined` / `room:left`            | Room membership changed                                 |\n| `error`                                | Transport-level error                                   |\n| `presence:online` / `presence:offline` | Presence changed (requires `IPresenceStorage`)          |\n\n> [!NOTE]\n> The SSE heartbeat is a `: keepalive` **comment line**, not a named event. It never appears in the `Last-Event-ID` id-space and never reaches a client listener.\n\n### Error Codes\n\n`REALTIME_INVALID_OPTIONS` · `REALTIME_NO_AUTHENTICATOR` · `REALTIME_AUTH_FAILED` · `REALTIME_REAUTHENTICATION_FAILED` · `REALTIME_TOO_MANY_CONNECTIONS` · `REALTIME_INVALID_TICKET` · `REALTIME_PUBSUB_UNAVAILABLE` · `REALTIME_PAYLOAD_TOO_LARGE` · `REALTIME_REPLAY_BUFFER_MISS`\n\n### DI Tokens\n\n`REALTIME_OPTIONS_TOKEN` · `REALTIME_TRANSPORT_TOKEN` · `REALTIME_AUTHENTICATOR_TOKEN` · `REALTIME_PUBSUB_TOKEN` · `REALTIME_OFFLINE_QUEUE_TOKEN` · `REALTIME_PRESENCE_TOKEN` · `REALTIME_HOOKS_TOKEN` · `REALTIME_INSTANCE_ID_TOKEN`\n\n### React Hooks\n\n| Hook                             | Returns                                                                                                         |\n| -------------------------------- | --------------------------------------------------------------------------------------------------------------- |\n| `useRealtime(options)`           | `{ transport, connected, events, lastEvent, error, reconnectAttempts, reconnect, emit }` — opens the connection |\n| `useRealtimeContext()`           | The same value, from the nearest `<RealtimeProvider>` — throws when used outside one                            |\n| `useRealtimeConnection(options)` | `{ connected, error, reconnectAttempts, reconnect }` — status only, no events array                             |\n| `usePresence()`                  | `{ onlineUserIds, isOnline(userId), count }` — reads presence events from the provider                          |\n\n`RealtimeProvider` takes a single `options` prop of type `UseRealtimeOptions`:\n\n| Option               | Type                             | Default       | Notes                                               |\n| -------------------- | -------------------------------- | ------------- | --------------------------------------------------- |\n| `url`                | `string`                         | — (required)  | `ws://` or `wss://` selects WebSocket automatically |\n| `transport`          | `'auto' \\| 'sse' \\| 'websocket'` | `'auto'`      | Overrides URL-scheme detection                      |\n| `eventNames`         | `readonly string[]`              | —             | **SSE only** — application event names to subscribe |\n| `withCredentials`    | `boolean`                        | `false`       | **SSE only** — send cookies cross-origin            |\n| `auth`               | `{ ticket?, token? }`            | —             | **WebSocket only** — handshake credentials          |\n| `path`               | `string`                         | `/socket.io`  | **WebSocket only** — Socket.IO path                 |\n| `reconnectInitialMs` | `number`                         | `1000`        | **SSE only** — initial backoff                      |\n| `reconnectMaxMs`     | `number`                         | `30000`       | **SSE only** — backoff ceiling                      |\n| `maxAttempts`        | `number`                         | — (unlimited) | **SSE only** — give up and close after N failures   |\n\n> [!NOTE]\n> `emit` is WebSocket-exclusive — SSE is server → client only. `reconnectAttempts` is always `0` on WebSocket, where Socket.IO owns the retry policy and exposes no count.\n\n---\n\n## 🚧 Deployment Notes\n\nSSE connections are long-lived HTTP responses, and several common proxy defaults break them silently:\n\n| Concern                  | Fix                                                                                                    |\n| ------------------------ | ------------------------------------------------------------------------------------------------------ |\n| **Response compression** | Disable on the SSE endpoint — `Content-Encoding: gzip` buffers the body and defeats streaming          |\n| **Proxy buffering**      | Nginx `proxy_buffering off`. The library already sends `X-Accel-Buffering: no`                         |\n| **CDN caching**          | `Cache-Control: no-cache, no-transform` — set by the library on the SSE response                       |\n| **Idle timeout**         | Must exceed `sse.heartbeatMs` (default 30 s). AWS ALB's 60 s default is compatible                     |\n| **WebSocket + polling**  | Enable sticky sessions. The Redis adapter syncs messages between instances, but not handshake affinity |\n\nPlatform-specific configuration is in [docs/proxies-cheat-sheet.md](./docs/proxies-cheat-sheet.md).\n\n---\n\n## 🤝 Contributing\n\nContributions are welcome! Please open an issue to discuss substantial changes before submitting a pull request.\n\n```bash\n# Clone the repository\ngit clone https://github.com/bymaxone/nest-realtime.git\ncd nest-realtime\n\n# Install dependencies\npnpm install\n\n# Run tests\npnpm test\n\n# Build\npnpm build\n\n# Type check\npnpm typecheck\n```\n\nEvery change must keep the full gate green:\n\n```bash\npnpm typecheck && pnpm lint && pnpm test && pnpm build && pnpm size\n```\n\n---\n\n## 🔒 Security Policy\n\nIf you discover a security vulnerability, please **do not** open a public issue. Instead, email us at **support@bymax.one** with details. We take security seriously and will respond promptly. Full policy and scope: [SECURITY.md](./SECURITY.md).\n\n---\n\n## 📄 License\n\n[MIT](./LICENSE) © [Bymax One](https://github.com/bymaxone)\n\n---\n\n<p align=\"center\">\n  <sub>Built with ❤️ by <a href=\"https://github.com/bymaxone\">Bymax One</a></sub>\n</p>\n","readmeFilename":"README.md"}