{"_id":"@azghr/decant","name":"@azghr/decant","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@azghr/decant","version":"0.1.0","description":"Parse Server-Sent Events from a streamed LLM response — cross-chunk safe, spec-correct, transport-agnostic, zero deps.","license":"MIT","type":"module","sideEffects":false,"main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"engines":{"node":">=18"},"scripts":{"build":"tsup src/index.ts --format esm,cjs --dts --sourcemap --clean","test":"vitest run","test:watch":"vitest","typecheck":"tsc --noEmit","lint":"eslint src test examples","demo":"tsx examples/demo.ts","check":"npm run typecheck && npm run lint && npm run test && npm run build","prepublishOnly":"npm run check"},"keywords":["sse","server-sent-events","stream","parser","llm","openai","anthropic","eventsource"],"devDependencies":{"@eslint/js":"^9.18.0","eslint":"^9.18.0","tsx":"^4.19.2","typescript":"^5.7.3","typescript-eslint":"^8.19.1","vitest":"^2.1.8","tsup":"^8.3.5"},"gitHead":"15b6f176abe244e50094e9e1bb94128659962253","_id":"@azghr/decant@0.1.0","_nodeVersion":"24.12.0","_npmVersion":"11.6.2","dist":{"integrity":"sha512-FX13mVGaO3GLNm9f1wni1oT0CdAu6eTEbk5ZMijnPObka/SE6LmVfwLrpWGgX7cxnT1LM4DhtjlcnDNIpZeT0Q==","shasum":"95956da9d2ed9a2e15ecc1d4ede76caefca81cb7","tarball":"https://registry.npmjs.org/@azghr/decant/-/decant-0.1.0.tgz","fileCount":10,"unpackedSize":50520,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCICIkhhI1QJPZptsn2sPT6A/nUSm39lc8x58cFDiH0jGjAiEAtmsZwuK0v6e+uNetr7oj6ocr7TE2ZF/CAzktF0Kt1jE="}]},"_npmUser":{"name":"azghr","email":"masgharali.eng@gmail.com"},"directories":{},"maintainers":[{"name":"azghr","email":"masgharali.eng@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/decant_0.1.0_1784978974709_0.7018188871640267"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-25T11:29:34.543Z","0.1.0":"2026-07-25T11:29:34.849Z","modified":"2026-07-25T11:29:35.006Z"},"maintainers":[{"name":"azghr","email":"masgharali.eng@gmail.com"}],"description":"Parse Server-Sent Events from a streamed LLM response — cross-chunk safe, spec-correct, transport-agnostic, zero deps.","keywords":["sse","server-sent-events","stream","parser","llm","openai","anthropic","eventsource"],"license":"MIT","readme":"# @azghr/decant\n\n[![npm](https://img.shields.io/npm/v/@azghr/decant)](https://www.npmjs.org/package/@azghr/decant)\n[![MIT License](https://img.shields.io/npm/l/@azghr/decant)](LICENSE)\n\nParse Server-Sent Events from a streamed LLM response — cross-chunk safe, spec-correct, transport-agnostic, zero deps.\n\n## The problem\n\nOpenAI, Anthropic, and most LLM APIs stream via Server-Sent Events: `data: {...}\\n\\n` frames, a terminal `data: [DONE]`, multi-line `data` fields, comments, and critically — frames split across network chunks. Everyone hand-rolls `chunk.split(\"\\n\")` and gets partial-line buffering wrong, corrupting the last token of a chunk.\n\nThe generic `EventSource` API is browser-only and reconnects on its own (wrong for a POST stream). Hand-rolled parsers miss CRLF handling, the leading-space strip after `:`, multi-line `data` concatenation, and cross-chunk buffering. You need a push-style parser that correctly buffers partial frames and works in any runtime.\n\n## Install\n\n```bash\nnpm install @azghr/decant\n# or\npnpm add @azghr/decant\n# or\nyarn add @azghr/decant\n```\n\n## Use\n\n```typescript\nimport decant from \"@azghr/decant\";\n\nconst parser = decant();\nfor await (const chunk of response.body) {\n  const text = decoder.decode(chunk);\n  for (const evt of parser.feed(text)) {\n    if (evt.data === \"[DONE]\") break;\n    handle(JSON.parse(evt.data));\n  }\n}\n```\n\n## API\n\n### `decant(): Decant`\n\nCreate a new SSE parser instance.\n\n### `Decant.feed(chunk: string): SSEvent[]`\n\nFeed a chunk of text to the parser. Returns an array of fully-parsed events dispatched in order. Maintains internal buffer for partial lines across calls.\n\n### `Decant.end(): SSEvent[]`\n\nFlush any buffered complete event as a lenient tail. Call when stream ends to retrieve final incomplete event.\n\n### `Decant.reset(): void`\n\nClear all buffered state and reset parser to initial state. Useful for reusing a parser instance.\n\n### `SSEvent`\n\n```typescript\ninterface SSEvent {\n  data: string;      // Multi-line data joined with \"\\n\", trailing newline trimmed\n  event?: string;    // Event type from \"event:\" field\n  id?: string;       // Last seen ID from \"id:\" field\n  retry?: number;    // Retry delay in milliseconds from \"retry:\" field (if numeric)\n}\n```\n\n### Field parsing\n\n- Lines starting with `:` are comments (ignored)\n- Text before first `:` is field name, rest is value\n- Single leading space after `:` is stripped from value\n- `data` fields append with `\\n`, trailing newline trimmed on dispatch\n- `event`, `id`, `retry` fields captured; `retry` parsed only if all digits\n- `id` containing NUL is ignored (per WHATWG spec)\n- BOM (`\\uFEFF`) stripped only at stream start\n- Blank line dispatches accumulated fields as event\n\n## Non-goals\n\n`@azghr/decant` only parses SSE — it does NOT:\n\n- **Perform fetch or reconnection** — you own the network layer\n- **Parse JSON from data** — you call `JSON.parse(evt.data)`\n- **Decode bytes** — you call `decoder.decode(chunk)` before `feed()`\n- **Handle Last-Event-ID retry** — you manage reconnection logic\n- **Manage backpressure** — you control stream consumption\n\n```typescript\n// You handle:\nconst response = await fetch(url);\nconst decoder = new TextDecoder();\nconst parser = decant();\n\n// decant only handles:\nconst events = parser.feed(decoder.decode(chunk));\n```\n\n## TypeScript note\n\n`@azghr/decant` is written in TypeScript with strict mode enabled. Full type definitions are included. The `SSEvent` and `Decant` interfaces are exported for type annotations.\n\n```typescript\nimport type { SSEvent, Decant } from \"@azghr/decant\";\nimport decant from \"@azghr/decant\";\n\nconst parser: Decant = decant();\nconst onEvent = (evt: SSEvent) => console.log(evt.data);\n```\n\n## License\n\nMIT\n","readmeFilename":"README.md","_rev":"1-4bc10a9c9571e213415ccbfd30d833a9"}