@stackline/sse 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,24 @@
2
2
 
3
3
  All notable changes are documented in this file.
4
4
 
5
+ ## [1.0.1] - 2026-08-21
6
+
7
+ ### Added
8
+
9
+ - Executable local stream, parser, OpenAI Responses, Anthropic Messages, and
10
+ edge-handler examples.
11
+ - Integration recipes for provider streams, browsers, Node.js, Workers, and
12
+ low-change package aliases.
13
+ - Reproducible parser, encoder, and adversarial-fragmentation methodology.
14
+ - First-party documentation analytics that never records stream contents.
15
+ - Trusted-publishing workflow for provenance-enabled future releases.
16
+
17
+ ### Changed
18
+
19
+ - Package tarballs now include the public guides and examples.
20
+
21
+ No runtime API or declaration behavior changed in this release.
22
+
5
23
  ## [1.0.0] - 2026-08-16
6
24
 
7
25
  ### Added
@@ -20,3 +38,4 @@ All notable changes are documented in this file.
20
38
  - Request header preservation when adding SSE negotiation and resume headers.
21
39
 
22
40
  [1.0.0]: https://github.com/alexandroit/stackline-sse/releases/tag/v1.0.0
41
+ [1.0.1]: https://github.com/alexandroit/stackline-sse/compare/v1.0.0...v1.0.1
package/README.md CHANGED
@@ -335,6 +335,16 @@ TypeScript 3.9, 4.7, 4.9, 5.x, 6.x, and 7.x.
335
335
  See [SECURITY.md](SECURITY.md) for vulnerability reporting and
336
336
  [CONTRIBUTING.md](CONTRIBUTING.md) for development instructions.
337
337
 
338
+ ## Adoption resources
339
+
340
+ - [OpenAI, Anthropic, browser, Node.js, and edge recipes](docs/INTEGRATIONS.md)
341
+ - [Reproducible parser and fragmentation benchmarks](docs/BENCHMARKS.md)
342
+ - [Executable examples](examples)
343
+ - [Stackline open-source catalog](https://alexandro.net/docs/open-source/)
344
+
345
+ The examples ship in the npm tarball. Network examples expose functions and do
346
+ not send requests during installation or import.
347
+
338
348
  ## License
339
349
 
340
350
  [MIT](LICENSE) Copyright 2026 Alexandro Paixao Marques.
package/dist/index.cjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! @stackline/sse v1.0.0 | MIT */
1
+ /*! @stackline/sse v1.0.1 | MIT */
2
2
  var __defProp = Object.defineProperty;
3
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- /*! @stackline/sse v1.0.0 | MIT */
1
+ /*! @stackline/sse v1.0.1 | MIT */
2
2
 
3
3
  // src/errors.js
4
4
  var SSEError = class extends Error {
package/dist/index.min.js CHANGED
@@ -1,4 +1,4 @@
1
- /*! @stackline/sse v1.0.0 | MIT */
1
+ /*! @stackline/sse v1.0.1 | MIT */
2
2
  var StacklineSSE=(()=>{var Le=Object.defineProperty;var Ze=Object.getOwnPropertyDescriptor;var Ge=Object.getOwnPropertyNames;var et=Object.prototype.hasOwnProperty;var tt=(e,t)=>{for(var r in t)Le(e,r,{get:t[r],enumerable:!0})},rt=(e,t,r,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let a of Ge(t))!et.call(e,a)&&a!==r&&Le(e,a,{get:()=>t[a],enumerable:!(n=Ze(t,a))||n.enumerable});return e};var nt=e=>rt(Le({},"__esModule",{value:!0}),e);var Ut={};tt(Ut,{EVENT_STREAM_CONTENT_TYPE:()=>U,EventStreamContentType:()=>ye,SSEEncodeError:()=>g,SSEError:()=>_,SSEHTTPError:()=>H,SSEParseError:()=>O,SSEReplayError:()=>q,SSERetryError:()=>J,SSETimeoutError:()=>M,consumeSSE:()=>le,createDecoderStream:()=>Re,createEncoderStream:()=>be,createParser:()=>G,createSSEChannel:()=>Ae,decodeJSON:()=>Te,decodeSSE:()=>ee,default:()=>Mt,encode:()=>Se,encodeComment:()=>we,encodeJSON:()=>W,encodeSSE:()=>D,eventStreamResponse:()=>Ne,fetchEventSource:()=>_e,fetchSSE:()=>de,toAsyncIterable:()=>fe});var _=class extends Error{constructor(t,r,n){super(t),this.name=new.target.name,this.code=r,n&&n.cause!==void 0&&(this.cause=n.cause)}},O=class extends _{constructor(t,r={}){super(t,r.code||"ERR_SSE_PARSE",r),this.fatal=r.fatal===!0,r.field!==void 0&&(this.field=r.field),r.line!==void 0&&(this.line=r.line),r.limit!==void 0&&(this.limit=r.limit)}},g=class extends _{constructor(t,r,n){super(t,"ERR_SSE_ENCODE",{cause:n}),this.field=r}},H=class extends _{constructor(t,r,n="ERR_SSE_HTTP"){super(t,n),this.response=r,this.status=r&&typeof r.status=="number"?r.status:0}},M=class extends _{constructor(t,r,n){super(`SSE ${t} timeout after ${r}ms`,"ERR_SSE_TIMEOUT",{cause:n}),this.phase=t,this.timeout=r}},J=class extends _{constructor(t,r){super(`SSE retry budget exhausted after ${t} attempt${t===1?"":"s"}`,"ERR_SSE_RETRY",{cause:r}),this.attempts=t}},q=class extends _{constructor(){super("The request body cannot be replayed. Provide bodyFactory or disable reconnection.","ERR_SSE_BODY_REPLAY")}};var ot=1024*1024,at=1024*1024,he=10,Fe=13,Z=32,it=65279,De="\0",ce=()=>{};function G(e={}){if(typeof e=="function"&&(e={onEvent:e}),!e||typeof e!="object")throw new TypeError("Parser configuration must be an object or event callback");let t=typeof e.onEvent=="function"?e.onEvent:ce,r=typeof e.onComment=="function"?e.onComment:ce,n=typeof e.onRetry=="function"?e.onRetry:ce,a=typeof e.onError=="function"?e.onError:ce,i=typeof e.onId=="function"?e.onId:ce,f=e.strict===!0,m=e.fatalUTF8===!0,u=$e(e.maxEventSize===void 0?e.maxBufferSize:e.maxEventSize,ot,"maxEventSize"),y=$e(e.maxLineLength,at,"maxLineLength"),p,l,T=[],h=0,S=!1,v=!0,I=[],A=0,w,E=ke(e.lastEventId),b=E,x=!1,L=!1,F=0,d=0,$=0,B=0,ae=0;function P(o){if(L)throw new O("Parser is terminated; call reset() before feeding more data",{code:"ERR_SSE_TERMINATED",fatal:!0});if(typeof o=="string"){if(l==="bytes")throw j("Cannot mix string and byte chunks in one parser stream","ERR_SSE_CHUNK_TYPE");return l="string",d+=o.length,ie(o),ne}if(!(o instanceof Uint8Array))throw new TypeError("SSE parser chunks must be strings or Uint8Array values");if(l==="string")throw j("Cannot mix string and byte chunks in one parser stream","ERR_SSE_CHUNK_TYPE");l="bytes",F+=o.byteLength;let c;try{c=Xe().decode(o,{stream:!0})}catch(s){throw s instanceof O?s:j("Invalid UTF-8 in the SSE stream","ERR_SSE_UTF8",{cause:s})}return d+=c.length,ie(c),ne}function K(){if(L)return ne;if(p)try{let o=p.decode();d+=o.length,ie(o)}catch(o){throw j("Invalid UTF-8 at the end of the SSE stream","ERR_SSE_UTF8",{cause:o})}return Qe(),re(),ne}function Oe(o={}){o&&o.consume&&h>0&&N(te());let s=!!(o&&o.preserveLastEventId)?b:ke(e.lastEventId);return p=void 0,l=void 0,T=[],h=0,S=!1,v=!0,I=[],A=0,w=void 0,E=s,b=s,x=!1,L=!1,F=0,d=0,$=0,B=0,ae=0,ne}function ie(o){let c=o;if(v&&c.length>0&&(v=!1,c.charCodeAt(0)===it&&(c=c.slice(1))),c.length===0)return;if(!S&&c.indexOf("\r")===-1){Ee(c);return}let s=0,C=0;for(S&&(S=!1,c.charCodeAt(0)===he&&(s=1,C=1));s<c.length;s++){let R=c.charCodeAt(s);R!==he&&R!==Fe||(Y(c.slice(C,s)),N(te()),re(),R===Fe&&(c.charCodeAt(s+1)===he?s++:s+1===c.length&&(S=!0)),C=s+1)}Y(c.slice(C))}function Ee(o){let c=0,s=o.indexOf(`
3
3
  `);for(;s!==-1;){if(h===0&&I.length===0&&o.charCodeAt(s+1)===he&&ft(o,c)){let C=o.charCodeAt(c+5)===Z?c+6:c+5,R=o.slice(C,s);if(s-c>y)throw j(`SSE line exceeded ${y} characters`,"ERR_SSE_LINE_LIMIT",{limit:y});Q(R.length+1),Ke(R),c=s+2,s=o.indexOf(`
4
4
  `,c);continue}if(h>0)Y(o.slice(c,s)),N(te()),re();else{if(s-c>y)throw j(`SSE line exceeded ${y} characters`,"ERR_SSE_LINE_LIMIT",{limit:y});N(o.slice(c,s))}c=s+1,s=o.indexOf(`
@@ -0,0 +1,71 @@
1
+ # Architecture
2
+
3
+ ## Goals
4
+
5
+ `@stackline/sse` treats SSE as one protocol with five composable surfaces:
6
+
7
+ 1. incremental wire parser;
8
+ 2. async iterable and TransformStream adapters;
9
+ 3. injection-resistant encoder;
10
+ 4. reconnecting Fetch client;
11
+ 5. backpressure-aware server output.
12
+
13
+ The runtime has no package dependencies and no Node-specific imports.
14
+
15
+ ## Parser state machine
16
+
17
+ The parser stores line fragments rather than repeatedly concatenating an
18
+ unterminated line. This keeps one-character chunk fragmentation linear. LF-only
19
+ input uses an `indexOf` fast path; CR and CRLF use the complete state machine.
20
+
21
+ State is separated into:
22
+
23
+ - pending line fragments;
24
+ - current data lines and event type;
25
+ - pending ID field;
26
+ - committed `lastEventId`;
27
+ - counters and configured limits.
28
+
29
+ An ID is committed at a blank line even if the block has no data. Incomplete
30
+ EOF data is discarded as required by the WHATWG algorithm.
31
+
32
+ ## Backpressure
33
+
34
+ `decodeSSE` reads one source chunk only when the consumer advances its async
35
+ iterator. Large source chunks are admitted in 16 KiB slices and callback bursts
36
+ are bounded. The server iterable helper asks its source for one item per stream
37
+ pull. Push channels expose `send(): boolean` and `ready`.
38
+
39
+ ## Client lifecycle
40
+
41
+ Each connection attempt owns an AbortController. External cancellation,
42
+ connect timeout, idle timeout, and total timeout converge on that controller.
43
+ Rejected response bodies are cancelled before retry. A user callback failure is
44
+ wrapped as `ERR_SSE_CALLBACK` and is never classified as a network failure.
45
+ Custom Fetch implementations, body factories, and iterators are raced against
46
+ the attempt signal so a non-cooperative adapter cannot hold a timeout open. A
47
+ response that resolves after timeout is cancelled on arrival.
48
+
49
+ Resume state is advanced only by committed parser IDs. Retry delay precedence
50
+ is HTTP `Retry-After`, server `retry:`, then client exponential backoff.
51
+
52
+ Streaming request bodies are not replayed. Applications can provide a
53
+ `bodyFactory` to create one body per attempt; the factory receives the attempt
54
+ signal. Headers from a Request input are preserved unless explicitly replaced.
55
+
56
+ ## Encoder boundary
57
+
58
+ Data and comments can span lines and are encoded one field per line. Event names
59
+ cannot contain CR or LF. IDs also cannot contain NUL, matching the value space
60
+ of `Last-Event-ID`. These checks prevent field and header injection.
61
+
62
+ ## Build outputs
63
+
64
+ One source graph produces:
65
+
66
+ - `dist/index.js` for ESM;
67
+ - `dist/index.cjs` for CommonJS;
68
+ - `dist/index.min.js` for browser globals;
69
+ - declarations for old and conditional-export-aware TypeScript versions.
70
+
71
+ All outputs are generated from the same commit and packed once for release.
@@ -0,0 +1,20 @@
1
+ # Benchmark Methodology
2
+
3
+ The checked-in benchmark compares the incremental parser with
4
+ `eventsource-parser@3.1.1` using the same 50,000-event document. It measures a
5
+ single document, 16 KiB chunks, encoding throughput, and an adversarial stream
6
+ split one character at a time.
7
+
8
+ ```bash
9
+ npm ci
10
+ npm run benchmark
11
+ ```
12
+
13
+ Both parsers are warmed before measurement and must produce the expected event
14
+ count. The adversarial case is reported separately because fragmented input is
15
+ a complexity and memory-safety concern, not only a throughput concern.
16
+
17
+ Results depend on Node.js, CPU, chunk shape, event size, callbacks, and enabled
18
+ limits. Publish the complete command, environment, and raw JSON output. Do not
19
+ compare unlike parser, client, encoder, and server contracts as if they were
20
+ the same operation.
@@ -0,0 +1,57 @@
1
+ # Compatibility
2
+
3
+ ## JavaScript runtimes
4
+
5
+ Parser and encoder APIs require JavaScript ES2018 and `TextDecoder` only when
6
+ byte chunks are used. The declared Node.js floor is 14.17.
7
+
8
+ Fetch client requirements:
9
+
10
+ - `fetch` and `AbortController`;
11
+ - a response body exposed as Web Stream or async iterable.
12
+
13
+ Server helper requirements:
14
+
15
+ - `ReadableStream` and `TextEncoder`;
16
+ - `Response` when creating HTTP responses.
17
+
18
+ Node.js 14 and 16 users can inject a Fetch implementation. Parser and encoder
19
+ usage does not require a polyfill.
20
+
21
+ ## TypeScript
22
+
23
+ CI compiles clean install fixtures with TypeScript 3.9.10, 4.7.4, 4.9.5,
24
+ 5.9.3, 6.0.2, and 7.0.2. Declarations avoid requiring DOM library types by
25
+ using structural Fetch and Web Stream interfaces.
26
+
27
+ ## eventsource-parser surface
28
+
29
+ Supported familiar fields:
30
+
31
+ - `createParser({ onEvent, onRetry, onComment, onError })`;
32
+ - `feed(chunk)` and `reset({ consume })`;
33
+ - `maxBufferSize` as an alias for the event limit;
34
+ - event `data`, `event`, and `id` fields.
35
+
36
+ Differences:
37
+
38
+ - byte chunks are accepted directly;
39
+ - security limits are enabled by default;
40
+ - `lastEventId` is included and ID-only blocks advance resume state;
41
+ - fatal limits throw immediately and terminate the parser.
42
+ - unknown fields and invalid retries call `onError` only with `strict: true`;
43
+ - `maxBufferSize` aliases the accumulated event limit, while line length has a
44
+ separate `maxLineLength` limit.
45
+
46
+ ## @microsoft/fetch-event-source surface
47
+
48
+ Supported callback names are `onopen`, `onmessage`, `onclose`, and `onerror`.
49
+ Fetch options, custom methods, headers, bodies, and custom Fetch functions pass
50
+ through. The package does not automatically disconnect when a document becomes
51
+ hidden, so `openWhenHidden` has no behavioral effect.
52
+
53
+ ## Protocol limits
54
+
55
+ SSE is UTF-8 only. This package does not implement WebSocket framing, HTTP
56
+ server transports, compression, JSON schema validation, or provider-specific
57
+ AI event models.
@@ -0,0 +1,46 @@
1
+ # Integration Recipes
2
+
3
+ ## OpenAI Responses
4
+
5
+ Use `fetchSSE` when the application wants direct control over streamed
6
+ Responses without adding a provider SDK. Pass the bearer token, JSON request,
7
+ finite retry budget, and an idle timeout. See
8
+ [`examples/openai-responses.mjs`](../examples/openai-responses.mjs).
9
+
10
+ ## Anthropic Messages
11
+
12
+ Anthropic emits named SSE events. Preserve `event.event` alongside the parsed
13
+ JSON payload so message-start, content-block, delta, and stop events remain
14
+ distinguishable. See
15
+ [`examples/anthropic-messages.mjs`](../examples/anthropic-messages.mjs).
16
+
17
+ ## Browser or Node client
18
+
19
+ `fetchSSE` accepts standard Fetch options plus retry, connect, idle, and total
20
+ timeouts. Node.js 18 and newer provide Fetch. Node.js 14 and 16 can inject a
21
+ compatible implementation with `options.fetch`.
22
+
23
+ ## Cloudflare Workers and edge handlers
24
+
25
+ Return `eventStreamResponse(events())` from a Fetch-style handler. The helper
26
+ sets the event-stream content type and cache headers while consuming an async
27
+ iterable with backpressure. See
28
+ [`examples/server-edge.mjs`](../examples/server-edge.mjs).
29
+
30
+ ## Existing parser imports
31
+
32
+ For a staged migration that keeps `eventsource-parser` imports:
33
+
34
+ ```bash
35
+ npm install eventsource-parser@npm:@stackline/sse
36
+ ```
37
+
38
+ For a staged migration that keeps `@microsoft/fetch-event-source` imports:
39
+
40
+ ```bash
41
+ npm install @microsoft/fetch-event-source@npm:@stackline/sse
42
+ ```
43
+
44
+ Commit the changed lockfile and run the complete consumer suite. Compatibility
45
+ aliases reduce source changes but do not remove the need to review documented
46
+ hardening differences and finite retry policies.
@@ -0,0 +1,84 @@
1
+ # Market Research
2
+
3
+ Research date: 2026-08-16.
4
+
5
+ ## Decision
6
+
7
+ The selected need is a unified, safe Server-Sent Events toolkit. SSE is used by
8
+ AI response streaming, live dashboards, notifications, build logs, and edge
9
+ applications. Existing adoption is large, while the common solution remains
10
+ fragmented across parser, polyfill/client, encoder, and custom retry code.
11
+
12
+ ## Download evidence
13
+
14
+ Counts below come from the public npm downloads API for two fixed 30-day
15
+ windows. They measure package download activity, including CI and transitive
16
+ installs. They are not unique users and must not be treated as a download
17
+ forecast for this package.
18
+
19
+ | Package | 2026-07-17 to 2026-08-15 | Prior 30 days | Change |
20
+ | --- | ---: | ---: | ---: |
21
+ | `eventsource-parser` | 239,814,210 | 213,287,465 | +12.44% |
22
+ | `eventsource` | 203,093,267 | 176,499,445 | +15.07% |
23
+ | `eventsource-client` | 809,871 | 832,191 | -2.68% |
24
+ | `eventsource-encoder` | 186,321 | 121,896 | +52.85% |
25
+ | `@microsoft/fetch-event-source` | 11,218,110 | 10,963,830 | +2.32% |
26
+ | `eventsource-polyfill` | 757,091 | 749,470 | +1.02% |
27
+ | Combined activity | 455,878,870 | 402,454,297 | +13.27% |
28
+
29
+ The combined row contains overlap because applications may install several of
30
+ these packages. The overlap is part of the product observation: users often
31
+ need more than one package to cover the protocol lifecycle.
32
+
33
+ API source examples:
34
+
35
+ - `https://api.npmjs.org/downloads/point/2026-07-17:2026-08-15/eventsource-parser`
36
+ - `https://api.npmjs.org/downloads/point/2026-07-17:2026-08-15/eventsource`
37
+ - scoped names use URL encoding in the same endpoint.
38
+
39
+ ## Standards and demand signals
40
+
41
+ - The WHATWG HTML Living Standard defines UTF-8 decoding, CR/LF handling,
42
+ field processing, resume IDs, reconnect behavior, and HTTP 204 termination.
43
+ - OpenAI documents server-sent events as the transport for streamed Responses.
44
+ - Native browser EventSource cannot send POST bodies or arbitrary auth headers.
45
+ - `@microsoft/fetch-event-source` addresses Fetch flexibility but its published
46
+ 2.0.1 release is five years old at the research date.
47
+ - `eventsource-parser` is current and fast, but intentionally only a parser.
48
+
49
+ Primary references:
50
+
51
+ - [WHATWG Server-Sent Events](https://html.spec.whatwg.org/multipage/server-sent-events.html)
52
+ - [OpenAI streaming events](https://platform.openai.com/docs/api-reference/responses-streaming)
53
+ - [npm eventsource-parser](https://www.npmjs.com/package/eventsource-parser)
54
+ - [npm eventsource](https://www.npmjs.com/package/eventsource)
55
+ - [npm @microsoft/fetch-event-source](https://www.npmjs.com/package/@microsoft/fetch-event-source)
56
+
57
+ ## Competitor analysis
58
+
59
+ | Capability | Parser package | EventSource client | Microsoft Fetch client | `@stackline/sse` |
60
+ | --- | --- | --- | --- | --- |
61
+ | Incremental parser | Yes | Dependency | Internal | Yes |
62
+ | Encoder | Separate package | No | No | Yes |
63
+ | Async iterator | Stream adapter | Yes | No | Yes |
64
+ | POST and custom headers | Retrieval-agnostic | Yes | Yes | Yes |
65
+ | Resume after ID-only block | Not exposed | Limited by parser events | Limited | Yes |
66
+ | Default memory limits | Optional | Parser-dependent | No | Yes |
67
+ | Retry-After and jitter | No | Basic reconnect | Custom callback | Yes |
68
+ | Connect, idle, total timeout | No | No | Custom | Yes |
69
+ | Non-replayable body guard | No | No | No | Yes |
70
+ | Safe encoder field validation | Separate | No | No | Yes |
71
+ | Server Response helper | No | No | No | Yes |
72
+ | Runtime dependencies | 0 | 1 | 0 | 0 |
73
+
74
+ This table describes documented public behavior at the research date. It is not
75
+ a claim that competing projects are unsafe or unsuitable.
76
+
77
+ ## Product thesis
78
+
79
+ The opportunity is not another EventSource polyfill. It is one standards-based
80
+ protocol toolkit with consistent safety, cancellation, observability hooks,
81
+ backpressure, and migration paths on both sides of the HTTP connection.
82
+
83
+ Success requires documentation and ecosystem trust. Download volume is a
84
+ market signal, not a guarantee of adoption.
@@ -0,0 +1,60 @@
1
+ # Releasing
2
+
3
+ ## Preconditions
4
+
5
+ - `main` is the only development branch.
6
+ - Version, changelog, documentation, and browser metadata agree.
7
+ - Runtime dependencies remain zero or an exception is documented.
8
+ - Local tests, GitHub CI, and CodeQL pass.
9
+ - npm and Verdaccio authentication are verified.
10
+
11
+ ## Release gate
12
+
13
+ ```bash
14
+ npm ci
15
+ npm test
16
+ npm run test:attw
17
+ npm run audit:dependencies
18
+ npm pack --dry-run
19
+ npm run benchmark
20
+ ```
21
+
22
+ ## Immutable artifact
23
+
24
+ Build and pack once. Publish the same bytes everywhere.
25
+
26
+ ```bash
27
+ version="$(node -p "require('./package.json').version")"
28
+ mkdir -p "release/$version"
29
+ npm pack --ignore-scripts --pack-destination "release/$version"
30
+ (cd "release/$version" && sha512sum *.tgz > SHA512SUMS)
31
+ npm sbom --omit=dev --sbom-format cyclonedx > "release/$version/sbom.cdx.json"
32
+ ```
33
+
34
+ Run `scripts/smoke-install.mjs` against the retained tarball.
35
+
36
+ ## Registry sequence
37
+
38
+ 1. Publish the retained tarball to Verdaccio.
39
+ 2. Download it anonymously and compare SHA-512.
40
+ 3. Smoke install directly and through both documented aliases.
41
+ 4. Run `publish.yml` with the expected SHA-512. The trusted workflow rebuilds
42
+ the reviewed commit and stops unless its tarball is byte-identical.
43
+ 5. Let the workflow publish with npm provenance, then download anonymously and
44
+ compare SHA-512 again.
45
+ 6. Verify `latest`, registry signatures, audit output, and package visibility.
46
+
47
+ Do not rebuild between publications.
48
+
49
+ ## GitHub and documentation
50
+
51
+ Tag the exact tested commit. Attach tarball, checksum, and CycloneDX SBOM to the
52
+ GitHub release. Deploy only the project documentation directory under
53
+ `/docs/vanilla/sse/`; do not delete unrelated documentation trees. Verify the
54
+ interactive site on desktop and mobile before announcing the release.
55
+
56
+ ## Rollback
57
+
58
+ npm releases are immutable. Deprecate a defective version with a precise
59
+ message and publish a patch from the last known-good source. Unpublish only for
60
+ a compelling legal or security emergency.
@@ -0,0 +1,28 @@
1
+ import { fetchSSE } from '@stackline/sse';
2
+
3
+ const received = [];
4
+ for await (const event of fetchSSE('https://api.example.com/stream', {
5
+ method: 'POST',
6
+ headers: {
7
+ Authorization: `Bearer ${process.env.API_TOKEN}`,
8
+ 'Content-Type': 'application/json'
9
+ },
10
+ body: JSON.stringify({ stream: true }),
11
+ fetch: async () => ({
12
+ body: chunks('data: {"delta":"Hello"}\n\ndata: [DONE]\n\n'),
13
+ headers: { get: (name) => name.toLowerCase() === 'content-type' ? 'text/event-stream' : null },
14
+ status: 200
15
+ }),
16
+ retry: { retries: 0 }
17
+ })) {
18
+ if (event.data === '[DONE]') break;
19
+ received.push(JSON.parse(event.data));
20
+ }
21
+
22
+ console.log(received);
23
+
24
+ async function* chunks(document) {
25
+ const bytes = new TextEncoder().encode(document);
26
+ yield bytes.subarray(0, 13);
27
+ yield bytes.subarray(13);
28
+ }
@@ -0,0 +1,24 @@
1
+ import { fetchSSE } from '@stackline/sse';
2
+
3
+ export async function* streamAnthropic(prompt, apiKey = process.env.ANTHROPIC_API_KEY) {
4
+ if (!apiKey) throw new Error('Set ANTHROPIC_API_KEY before calling streamAnthropic');
5
+
6
+ for await (const event of fetchSSE('https://api.anthropic.com/v1/messages', {
7
+ method: 'POST',
8
+ headers: {
9
+ 'anthropic-version': '2023-06-01',
10
+ 'Content-Type': 'application/json',
11
+ 'x-api-key': apiKey
12
+ },
13
+ body: JSON.stringify({
14
+ max_tokens: 1024,
15
+ messages: [{ role: 'user', content: prompt }],
16
+ model: 'your-model',
17
+ stream: true
18
+ }),
19
+ idleTimeout: 45_000,
20
+ retry: { retries: 3 }
21
+ })) {
22
+ yield { event: event.event, payload: JSON.parse(event.data) };
23
+ }
24
+ }
@@ -0,0 +1,18 @@
1
+ import { fetchSSE } from '@stackline/sse';
2
+
3
+ export async function* streamOpenAI(input, apiKey = process.env.OPENAI_API_KEY) {
4
+ if (!apiKey) throw new Error('Set OPENAI_API_KEY before calling streamOpenAI');
5
+
6
+ for await (const event of fetchSSE('https://api.openai.com/v1/responses', {
7
+ method: 'POST',
8
+ headers: {
9
+ Authorization: `Bearer ${apiKey}`,
10
+ 'Content-Type': 'application/json'
11
+ },
12
+ body: JSON.stringify({ model: 'your-model', input, stream: true }),
13
+ idleTimeout: 45_000,
14
+ retry: { retries: 3 }
15
+ })) {
16
+ yield JSON.parse(event.data);
17
+ }
18
+ }
@@ -0,0 +1,10 @@
1
+ import { createParser } from '@stackline/sse';
2
+
3
+ const parser = createParser({
4
+ onEvent(event) {
5
+ console.log(event);
6
+ }
7
+ });
8
+
9
+ parser.feed('id: 1\ndata: hello\n');
10
+ parser.feed('data: world\n\n');
@@ -0,0 +1,10 @@
1
+ import { eventStreamResponse } from '@stackline/sse';
2
+
3
+ async function* events() {
4
+ yield { event: 'ready', data: 'connected', id: '1' };
5
+ yield { event: 'message', data: 'hello', id: '2' };
6
+ }
7
+
8
+ export function GET() {
9
+ return eventStreamResponse(events());
10
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stackline/sse",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Universal, spec-correct and memory-safe Server-Sent Events toolkit for AI streaming, browsers, servers and edge runtimes",
5
5
  "keywords": [
6
6
  "sse",
@@ -61,6 +61,8 @@
61
61
  },
62
62
  "files": [
63
63
  "dist",
64
+ "docs",
65
+ "examples",
64
66
  "CHANGELOG.md",
65
67
  "CONTRIBUTING.md",
66
68
  "LICENSE",
@@ -75,10 +77,11 @@
75
77
  "clean": "node scripts/clean.mjs",
76
78
  "build": "node scripts/build.mjs",
77
79
  "lint": "eslint . && node scripts/check-markdown.mjs",
78
- "test": "npm run build && npm run lint && npm run test:coverage && npm run test:types && npm run test:package && npm run test:install && npm run test:docs",
80
+ "test": "npm run build && npm run lint && npm run test:coverage && npm run test:types && npm run test:examples && npm run test:package && npm run test:install && npm run test:docs",
79
81
  "test:unit": "node --test --test-reporter=spec test/*.test.mjs",
80
82
  "test:coverage": "c8 --all --src src --check-coverage --lines 100 --functions 100 --statements 100 --branches 95 node --test test/parser.test.mjs test/encoder.test.mjs test/iterable.test.mjs test/client.test.mjs test/server.test.mjs test/security.test.mjs",
81
83
  "test:types": "node scripts/test-types.mjs",
84
+ "test:examples": "node examples/parser.mjs && node examples/ai-client.mjs && node examples/server-edge.mjs && node examples/openai-responses.mjs && node examples/anthropic-messages.mjs",
82
85
  "test:package": "node --test test/package.test.mjs && node scripts/check-dist.mjs && publint",
83
86
  "test:install": "node scripts/smoke-install.mjs",
84
87
  "test:docs": "npm run docs:build && node scripts/check-docs.mjs",