Skip to content

Commit c544729

Browse files
authored
feat!: provide onError, onComment, and onRetry callbacks (#15)
BREAKING CHANGE: The parser now takes an object of callbacks instead of an `onParse` callback. This means you do not have to check the type of the event in the `onEvent` callback, but instead provide separate callbacks for each event type. BREAKING CHANGE: The `ParsedEvent` type has been renamed to `EventSourceMessage` and the `type` attribute has been removed. BREAKING CHANGE: The `EventSourceCallback` type has been removed in favor of the `ParserCallbacks` interface. BREAKING CHNAGE: The `ReconnectInterval` type has been removed in favor of providing the interval directly to the `onRetry` callback. BREAKING CHANGE: The `ParseEvent` type has been removed in favor of providing separate callbacks for each event type. BREAKING CHANGE: The parser has been rewritten to be more specification compliant. Certain _rare_ edge cases may now be handled differently. Mixed CRLF and LF line endings will now be handled correctly. `retry` fields now have to be completely valid integers to be parsed.
1 parent 0436c7e commit c544729

File tree

18 files changed

+4742
-11395
lines changed

18 files changed

+4742
-11395
lines changed

.github/workflows/test.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@ on:
66
bun:
77
description: 'Test on bun'
88
required: true
9-
default: false
9+
default: true
1010
type: boolean
1111
deno:
1212
description: 'Test on deno'
1313
required: true
14-
default: false
14+
default: true
1515
type: boolean
1616

1717
jobs:

MIGRATE-v3.md

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# How to migrate from v2 to v3
2+
3+
## If importing from `eventsource-parser`
4+
5+
The parser now takes an object of callbacks instead of only an `onParse` callback. This means you do not have to check the type of the event in the `onEvent` callback, but instead provide separate callbacks for each event type:
6+
7+
```diff
8+
-import {createParser, type ParsedEvent, type ReconnectInterval} from 'eventsource-parser'
9+
+import {createParser, type EventSourceMessage} from 'eventsource-parser'
10+
11+
-const parser = createParser((event: ParsedEvent | ReconnectInterval) => {
12+
- if (event.type === 'event') {
13+
- // …handle event…
14+
- } else if (event.type === 'reconnect-interval') {
15+
- // …handle retry interval change…
16+
- }
17+
-})
18+
+const parser = createParser({
19+
+ onEvent: (event: EventSourceMessage) => {
20+
+ // …handle event…
21+
+ },
22+
+ onRetry: (interval: number) => {
23+
+ // …handle retry interval change…
24+
+ }
25+
+})
26+
```
27+
28+
The parser also now has a `onError` callback that you can use to handle parse errors, as well as an `onComment` callback that you can use to handle comments:
29+
30+
```ts
31+
const parser = createParser({
32+
onEvent: (event: EventSourceMessage) => {
33+
// …handle event…
34+
},
35+
onRetry: (interval: number) => {
36+
// …handle retry interval change…
37+
},
38+
onError: (error: Error) => {
39+
// …handle parse error…
40+
},
41+
onComment: (comment: string) => {
42+
// …handle comment…
43+
},
44+
})
45+
```
46+
47+
Renamed types:
48+
49+
- `ParsedEvent` => `EventSourceMessage` (and `type` property is removed)
50+
51+
Removed types:
52+
53+
- `EventSourceParseCallback` - replaced with `ParserCallbacks` interface (`onEvent` property)
54+
- `ReconnectInterval` - no longer needed, as the `onRetry` callback now provides the interval directly
55+
- `ParseEvent` - no longer needed - the different event types are now handled by separate callbacks
56+
57+
## If using the `TransformStream` variant
58+
59+
No change is neccessary, but you can now subscribe to changes in the retry interval by providing a callback to the `onRetry` option when creating the stream:
60+
61+
```ts
62+
const stream = new EventSourceParserStream({
63+
onRetry: (interval: number) => {
64+
// …handle retry interval change…
65+
},
66+
})
67+
```
68+
69+
There is also a new option to specify how parse should be handled - by default it will ignore them, but you can choose to terminate the stream or handle it manually:
70+
71+
```ts
72+
const stream = new EventSourceParserStream({
73+
onError: (error: Error) => {
74+
// …handle parse error…
75+
},
76+
})
77+
78+
// …or…
79+
const stream = new EventSourceParserStream({
80+
onError: 'terminate',
81+
})
82+
```
83+
84+
Lastly, if you're interested in any comments that are encountered during parsing, you can provide a callback to the `onComment` option:
85+
86+
```ts
87+
const stream = new EventSourceParserStream({
88+
onComment: (comment: string) => {
89+
// …handle comment…
90+
},
91+
})
92+
```

README.md

Lines changed: 79 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,15 @@ If you are looking for a modern client implementation, see [eventsource-client](
88

99
You create an instance of the parser, and _feed_ it chunks of data - partial or complete, and the parse emits parsed messages once it receives a complete message. A [TransformStream variant](#stream-usage) is also available for environments that support it (modern browsers, Node 18 and higher).
1010

11+
Other modules in the EventSource family:
12+
13+
- [eventsource-client](https://github.com/rexxars/eventsource-client): modern, feature rich eventsource client for browsers, node.js, bun, deno and other modern JavaScript environments.
14+
- [eventsource-encoder](https://github.com/rexxars/eventsource-encoder): encodes messages in the EventSource/Server-Sent Events format.
15+
- [eventsource](https://github.com/eventsource/eventsource): Node.js polyfill for the WhatWG EventSource API.
16+
17+
> [!NOTE]
18+
> Migrating from eventsource-parser 1.x/2.x? See the [migration guide](./MIGRATE-v3.md).
19+
1120
## Installation
1221

1322
```bash
@@ -17,20 +26,16 @@ npm install --save eventsource-parser
1726
## Usage
1827

1928
```ts
20-
import {createParser, type ParsedEvent, type ReconnectInterval} from 'eventsource-parser'
21-
22-
function onParse(event: ParsedEvent | ReconnectInterval) {
23-
if (event.type === 'event') {
24-
console.log('Received event!')
25-
console.log('id: %s', event.id || '<none>')
26-
console.log('name: %s', event.name || '<none>')
27-
console.log('data: %s', event.data)
28-
} else if (event.type === 'reconnect-interval') {
29-
console.log('We should set reconnect interval to %d milliseconds', event.value)
30-
}
29+
import {createParser, type EventSourceMessage} from 'eventsource-parser'
30+
31+
function onEvent(event: EventSourceMessage) {
32+
console.log('Received event!')
33+
console.log('id: %s', event.id || '<none>')
34+
console.log('name: %s', event.name || '<none>')
35+
console.log('data: %s', event.data)
3136
}
3237

33-
const parser = createParser(onParse)
38+
const parser = createParser({onEvent})
3439
const sseStream = getSomeReadableStream()
3540

3641
for await (const chunk of sseStream) {
@@ -42,6 +47,68 @@ parser.reset()
4247
console.log('Done!')
4348
```
4449

50+
### Retry intervals
51+
52+
If the server sends a `retry` field in the event stream, the parser will call any `onRetry` callback specified to the `createParser` function:
53+
54+
```ts
55+
const parser = createParser({
56+
onRetry(retryInterval) {
57+
console.log('Server requested retry interval of %dms', retryInterval)
58+
},
59+
onEvent(event) {
60+
//
61+
},
62+
})
63+
```
64+
65+
### Parse errors
66+
67+
If the parser encounters an error while parsing, it will call any `onError` callback provided to the `createParser` function:
68+
69+
```ts
70+
import {type ParseError} from 'eventsource-parser'
71+
72+
const parser = createParser({
73+
onError(error: ParseError) {
74+
console.error('Error parsing event:', error)
75+
if (error.type === 'invalid-field') {
76+
console.error('Field name:', error.field)
77+
console.error('Field value:', error.value)
78+
console.error('Line:', error.line)
79+
} else if (error.type === 'invalid-retry') {
80+
console.error('Invalid retry interval:', error.value)
81+
}
82+
},
83+
onEvent(event) {
84+
//
85+
},
86+
})
87+
```
88+
89+
Note that `invalid-field` errors will usually be called for any invalid data - not only data shaped as `field: value`. This is because the EventSource specification says to treat anything prior to a `:` as the field name. Use the `error.line` property to get the full line that caused the error.
90+
91+
> [!NOTE]
92+
> When encountering the end of a stream, calling `.reset({consume: true})` on the parser to flush any remaining data and reset the parser state. This will trigger the `onError` callback if the pending data is not a valid event.
93+
94+
### Comments
95+
96+
The parser will ignore comments (lines starting with `:`) by default. If you want to handle comments, you can provide an `onComment` callback to the `createParser` function:
97+
98+
```ts
99+
const parser = createParser({
100+
onComment(comment) {
101+
console.log('Received comment:', comment)
102+
},
103+
onEvent(event) {
104+
//
105+
},
106+
})
107+
```
108+
109+
> [!NOTE]
110+
> Leading whitespace is not stripped from comments, eg `: comment` will give ` comment` as the comment value, not `comment` (note the leading space).
111+
45112
## Stream usage
46113

47114
```ts

0 commit comments

Comments
 (0)