{"_id":"@alecvision/trpc-sse-link","name":"@alecvision/trpc-sse-link","dist-tags":{"latest":"0.0.1"},"versions":{"0.0.1":{"name":"@alecvision/trpc-sse-link","version":"0.0.1","description":"A tRPC HTTP Link with support for Server-Sent Events (SSE)","main":"./dist/index.js","module":"./dist/index.mjs","types":"./dist/index.d.ts","enginesStrict":true,"engines":{"node":">=18.0.0"},"scripts":{"dev":"vitest --coverage --ui","test":"vitest run --coverage && vitest typecheck --run","build":"tsup","ci":"npm ci && npm run test && npm run build","release":"npm run ci && npm publish --access=public","clean":"rm -rf dist && rm -rf coverage && rm -rf node_modules"},"repository":{"type":"git","url":"git+https://github.com/alecvision/trpc-sse-link.git"},"keywords":[],"author":{"name":"Alec Helmturner"},"license":"ISC","devDependencies":{"@trpc/server":"^10.29.0","@vitest/coverage-c8":"^0.29.3","@vitest/ui":"^0.29.2","prettier":"^2.8.4","ts-node":"^10.9.1","tsup":"^6.6.3","typescript":"^5.0.2","vitest":"^0.29.3"},"peerDependencies":{"@trpc/client":"^10.0.0"},"prettier":{"semi":true,"trailingComma":"none","arrowParens":"avoid","tabWidth":2},"tsup":{"minify":true,"minifyIdentifiers":true,"noExternal":[],"external":[],"skipNodeModulesBundle":true,"treeshake":true,"tsconfig":"tsconfig.json","define":{"import.meta.vitest":"undefined"},"dts":true,"entry":["./src/index.ts"],"outDir":"./dist","format":["esm","cjs"]},"dependencies":{"eventsource-parser":"^1.0.0"},"gitHead":"53e31ce83000608416b3874fe0052dbecdc39637","bugs":{"url":"https://github.com/alecvision/trpc-sse-link/issues"},"homepage":"https://github.com/alecvision/trpc-sse-link#readme","_id":"@alecvision/trpc-sse-link@0.0.1","_nodeVersion":"18.16.0","_npmVersion":"9.6.7","dist":{"integrity":"sha512-UagvpbP7wNC49uQGYi10HeXl5FfrHxPsulN+c/1fLaidZseiR9+slOSsijcRkaoSHmFsY/gVO86kjeXcC2s2cQ==","shasum":"9531755d2f2dacce26638b6525c8ef6c0365eaf0","tarball":"https://registry.npmjs.org/@alecvision/trpc-sse-link/-/trpc-sse-link-0.0.1.tgz","fileCount":6,"unpackedSize":12744,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQCxO5wAFw5H9THPXNKbr5NEDUIAwjTcXXlK/V4EhIfUZwIgWbt5wGt/VarVsV8BURmu4uf8bdRN8dRu2bkEAqOTDyc="}]},"_npmUser":{"name":"helmturner","email":"alec@helmturner.dev"},"directories":{},"maintainers":[{"name":"helmturner","email":"alec@helmturner.dev"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/trpc-sse-link_0.0.1_1685838861598_0.32857822050405705"},"_hasShrinkwrap":false}},"time":{"created":"2023-06-04T00:34:21.503Z","0.0.1":"2023-06-04T00:34:21.771Z","modified":"2023-06-04T00:34:21.984Z"},"maintainers":[{"name":"helmturner","email":"alec@helmturner.dev"}],"description":"A tRPC HTTP Link with support for Server-Sent Events (SSE)","homepage":"https://github.com/alecvision/trpc-sse-link#readme","keywords":[],"repository":{"type":"git","url":"git+https://github.com/alecvision/trpc-sse-link.git"},"author":{"name":"Alec Helmturner"},"bugs":{"url":"https://github.com/alecvision/trpc-sse-link/issues"},"license":"ISC","readme":"# tRPC Fetch-SSE Link\n\nBecause tRPC transmits data as JSON, sending (and subscribing to) individual Server-Sent event streams is not possible by default (which is handy if, say, you want to use SSE to send chunks of a ChatGPT response as they are generated). This link enables that functionality.\n\n</br>\n\n## Table of Contents\n\n- [Usage](#usage)\n  - [Adding the Link](#adding-the-link)\n  - [Consuming SSE Stream Procedures](#consuming-sse-stream-procedures)\n- [License](#license)\n- [Author](#author)\n\n</br>\n\n## Usage\n\n> See [the `trpc-sse-adapter` package](https://github.com/alecvision/trpc-sse-adapter) for the server-side adapter needed to handle SSE stream requests.\n\nFirst, install the link:\n\n```bash\nnpm install @alecvision/trpc-sse-link\n```\n\nThere are two steps to implementing this link:\n\n1. Add the link in your tRPC client config and tell it which procedures are SSE streams\n2. Consume the SSE subscription using the same API as you would a WebSocket subscription\n\n</br>\n\n### Adding the Link\n\n---\n\nThis link ONLY handles requests for SSE streams. To use this link, you will need to use [`splitLink`](https://trpc.io/docs/links/splitLink). Because tRPC doesn't know the difference between a WebSocket and an SSE stream, you must tell it which procedures are SSE streams route them to the appropriate link. For example, using Next.js:\n\n```ts\nimport { httpBatchLink, loggerLink, splitLink, wsLink } from \"@trpc/client\";\nimport superjson from \"superjson\";\nimport { createTRPCNext } from \"@trpc/next\";\nimport { sseLink } from \"@alecvision/trpc-sse-link\";\nimport type { AppRouter } from \"../server/trpc\";\n\nconst SSE_PROCEDURE_PATTERNS = [\n  /ticker\\.start$/,\n  /chatgpt\\.generate$/,\n  /*\n    prefixes/suffixes are an easy way to arbitrarily define SSE streams by giving\n    them a special name (e.g. `myProcedure.stream_getSomeStreamingData`)\n    */\n  /^.*\\.stream_\\w+$/\n];\n\n// This MUST return the same value as is returned by the equivalent server-side function\nfunction isStreamable(path: string) {\n  return SSE_PROCEDURE_PATTERNS.some(regex => regex.test(path));\n}\n\nexport const api = createTRPCNext<AppRouter>({\n  config() {\n    return {\n      transformer: superjson,\n      links: [\n        loggerLink(/* ... */), // optional\n        splitLink({\n          condition: ({ type }) => type === \"subscription\",\n          // non-subscription requests go through a normal http link\n          false: httpBatchLink(/* ... */), // or httpLink( /* ... */ )\n          // the nested splitLink is only necessary if you have both SSE and WebSocket subscription procedures.\n          // If you only have SSE subscriptions, you can just use sseLink for handling all subscriptions.\n          true: splitLink({\n            condition: ({ path }) => isStreamable(path),\n            true: sseLink(/* ... */),\n            false: wsLink(/* ... */)\n          })\n        })\n      ]\n    };\n  },\n  ssr: false // This link has not been tested with SSR\n});\n```\n\n</br>\n\n### Consuming SSE Stream Procedures\n\n> **Note** >\n>\n> The sum of the chunks sent will NOT be sent by the server when the stream is complete. It is up to you to store the chunks in state and reconstruct the response. This can be done on the client (as shown here) or on the server (e.g. to store the result in a database)\n\nUse the same API as you would for a WebSocket subscription to consume the stream. For example:\n\n```jsx\nimport { useState } from \"react\";\nimport { api } from \"../utils/trpc\";\n\nexport default function App() {\n  const [isStreaming, setIsStreaming] = useState(false);\n  const [isDone, setIsDone] = useState(false);\n  const [streamedContent, setStreamedContent] = useState(\"\");\n\n  api.chat.generate.useSubscription(\n    {\n      model: \"gpt-4\",\n      messages: [\n        { role: \"user\", content: \"What are the five funniest words you know?\" }\n      ],\n      temperature: 1.0\n    },\n    {\n      // Control the stream with this boolean\n      enabled: isStreaming,\n      // This callback is called for each chunk of data sent by the server.\n      onData(data) {\n        // The sum of the chunks will NOT be sent by the server. It is up to you to reconstruct the full response.\n        setStreamedContent(() => streamedContent + data);\n      },\n      // This callback is called when the server sends the first chunk of data\n      onStarted() {\n        setIsDone(false);\n      },\n      // This callback is called when something goes wrong\n      onError(err) {\n        setIsDone(true);\n      }\n    }\n  );\n\n  return (\n    <div>\n      <button disabled={isStreaming} onClick={() => setIsStreaming(true)}>\n        Get Random Words\n      </button>\n      <p>{streamedContent}</p>\n      {/* shows after the first stream is complete */}\n      {isDone && <p>Wow, those are some great words!</p>}\n    </div>\n  );\n}\n```\n\n</br>\n\n## License\n\nISC License (ISC)\n\n</br>\n\n## Author\n\nAlec Helmturner\n","readmeFilename":"README.md"}