{"_id":"@alecvision/trpc-sse-adapter","name":"@alecvision/trpc-sse-adapter","dist-tags":{"latest":"0.0.1"},"versions":{"0.0.1":{"name":"@alecvision/trpc-sse-adapter","version":"0.0.1","description":"A tRPC Fetch Adapter 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-adapter.git"},"keywords":[],"author":{"name":"Alec Helmturner"},"license":"ISC","devDependencies":{"@trpc/client":"^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/server":"^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"]},"gitHead":"8ad1df669857fed3f47fb5c91916033f4507d551","bugs":{"url":"https://github.com/alecvision/trpc-sse-adapter/issues"},"homepage":"https://github.com/alecvision/trpc-sse-adapter#readme","_id":"@alecvision/trpc-sse-adapter@0.0.1","_nodeVersion":"18.16.0","_npmVersion":"9.6.7","dist":{"integrity":"sha512-Z/vWhm2ZEa1rcANfR/4H0qiyl4TsAb++gYbkjHGiPakpEBS2ErV6rllrt5W33j0uLEX7yjD9Ol6dCHjZXjx9Sw==","shasum":"fb3e70a384c18c171c50d8455c372b894f7e9567","tarball":"https://registry.npmjs.org/@alecvision/trpc-sse-adapter/-/trpc-sse-adapter-0.0.1.tgz","fileCount":6,"unpackedSize":16739,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQCS2BNzKn+Ci+16dGSsjmiruuvoxdBT4PsKtPsx0PnrigIgV7btTW2DMp62wHr8gXoRHBKS/XkjJ8FJG/D8Zk+z7Ts="}]},"_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-adapter_0.0.1_1685823521099_0.9435845811466936"},"_hasShrinkwrap":false}},"time":{"created":"2023-06-03T20:18:41.027Z","0.0.1":"2023-06-03T20:18:41.293Z","modified":"2023-06-03T20:18:41.486Z"},"maintainers":[{"name":"helmturner","email":"alec@helmturner.dev"}],"description":"A tRPC Fetch Adapter with support for Server-Sent Events (SSE)","homepage":"https://github.com/alecvision/trpc-sse-adapter#readme","keywords":[],"repository":{"type":"git","url":"git+https://github.com/alecvision/trpc-sse-adapter.git"},"author":{"name":"Alec Helmturner"},"bugs":{"url":"https://github.com/alecvision/trpc-sse-adapter/issues"},"license":"ISC","readme":"# tRPC Fetch-SSE Adapter\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 adapter enables that functionality.\n\n</br>\n\n## Usage\n\n> See [the `trpc-sse-link` package](https://github.com/alecvision/trpc-sse-link) for the client-side link needed to consume SSE streams.\n\nFirst, install the adapter:\n\n```bash\nnpm install @alecvision/trpc-sse-adapter\n```\n\nThere are two steps to implementing this adapter:\n\n1. Add the adapter to your server and tell it which procedures are SSE streams\n2. Create `subscription` procedures for your SSE streams\n\n</br>\n\n### Adding the Adapter\n-----\nThis adapter ONLY handles requests for SSE streams. Batching of SSE Stream requests is not supported. Creating an SSE stream is as simple as creating a subscription procedure, just as you would with WebSockets - but tRPC doesn't know the difference between a WebSocket and an SSE stream. You must tell it which procedures are SSE streams and handle them accordingly. For example, using Next.js:\n\n```ts\nimport type { NextRequest } from \"next/server\";\nimport { fetchRequestHandler } from \"@trpc/server/adapters/fetch\";\nimport { sseRequestHandler } from \"@alecvision/trpc-sse-adapter\";\nimport { appRouter, createTRPCContext } from \"../../../server\";\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\nfunction isStreamable(path: string) {\n  return SSE_PROCEDURE_PATTERNS.some((regex) => regex.test(path));\n}\n\n// Vercel only supports SSE on the edge runtime (WebSockets are not supported at all)\nexport const config = {\n  runtime: \"edge\",\n};\n\nexport default async function handler(req: NextRequest) {\n  \n  if (isStreamable(req.nextUrl.pathname)) {\n    // Accepts a subset of the options for the fetch adapter\n    return sseRequestHandler({\n      endpoint: \"/api/trpc\",\n      router: appRouter,\n      req,\n      createContext: createTRPCContext,\n    });\n  }\n\n  return fetchRequestHandler({\n    endpoint: \"/api/trpc\",\n    router: appRouter,\n    req,\n    createContext: createTRPCContext,\n  });\n}\n\nexport default handler;\n```\n\n</br>\n\n### Creating SSE Stream Procedures\n-----\n```ts\nimport { observable } from \"@trpc/server/observable\";\nimport { OpenAI } from \"openai-streams\";\nimport { z } from \"zod\";\nimport { createTRPCRouter, publicProcedure } from \".\";\n\nexport const chatRouter = createTRPCRouter({\n  generate: publicProcedure\n    .input(\n      z.object({\n        model: z.string(),\n        messages: z.array(\n          z.object({\n            role: z.enum([\"user\", \"system\", \"assistant\"]),\n            content: z.string(),\n          }),\n        ),\n        temperature: z.number().nullish(),\n        top_p: z.number().nullish(),\n        frequency_penalty: z.number().nullish(),\n        presence_penalty: z.number().nullish(),\n        max_tokens: z.number().default(4096),\n        n: z.number().nullish(),\n        logit_bias: z\n          .record(z.string(), z.number().min(-100).max(100))\n          .nullish(),\n        stop: z.array(z.string()).nullish(),\n        user: z.string().nullish(),\n      }),\n    )\n    .subscription(\n      ({ input }) => {\n        return observable<string>((observer) => {\n          const abortController = new AbortController();\n\n          void OpenAI(\"chat\", input, {\n            controller: abortController,\n            apiKey: process.env.OPEN_AI_API_KEY,\n            onParse(token) {\n              observer.next(token);\n            },\n            onDone() {\n              observer.complete();\n            },\n          }).catch((err) => {\n            observer.error(err);\n          });\n\n          return () => {\n            abortController.abort();\n            observer.complete();\n          };\n        });\n      },\n    ),\n});\n```\n\n</br>\n\nISC License (ISC)\n\n```\n\n","readmeFilename":"README.md"}