Latest Threat Research:SANDWORM_MODE: Shai-Hulud-Style npm Worm Hijacks CI Workflows and Poisons AI Toolchains.Details
Socket
Book a DemoSign in
Socket

eventsource-parser

Package Overview
Dependencies
Maintainers
1
Versions
18
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

eventsource-parser - npm Package Compare versions

Comparing version
3.0.1
to
3.0.2
+9
-10
package.json
{
"name": "eventsource-parser",
"version": "3.0.1",
"version": "3.0.2",
"description": "Streaming, source-agnostic EventSource/Server-Sent Events parser",

@@ -8,3 +8,3 @@ "sideEffects": false,

"types": "./dist/index.d.ts",
"module": "./dist/index.esm.js",
"module": "./dist/index.js",
"main": "./dist/index.cjs",

@@ -45,4 +45,3 @@ "exports": {

"dist",
"src",
"stream.js"
"src"
],

@@ -68,3 +67,3 @@ "scripts": {

"devDependencies": {
"@sanity/pkg-utils": "^6.11.4",
"@sanity/pkg-utils": "^7.2.2",
"@sanity/semantic-release-preset": "^5.0.0",

@@ -77,9 +76,9 @@ "@types/node": "^18.0.0",

"eslint-config-sanity": "^7.1.2",
"eventsource-encoder": "^1.0.0",
"prettier": "^3.3.3",
"eventsource-encoder": "^1.0.1",
"prettier": "^3.5.3",
"rimraf": "^6.0.1",
"rollup-plugin-visualizer": "^5.12.0",
"semantic-release": "^24.0.0",
"typescript": "^5.5.4",
"vitest": "^2.0.5"
"semantic-release": "^24.2.3",
"typescript": "^5.8.3",
"vitest": "^3.1.3"
},

@@ -86,0 +85,0 @@ "homepage": "https://github.com/rexxars/eventsource-parser#readme",

class ParseError extends Error {
constructor(message, options) {
super(message), this.name = "ParseError", this.type = options.type, this.field = options.field, this.value = options.value, this.line = options.line;
}
}
function noop(_arg) {
}
function createParser(callbacks) {
if (typeof callbacks == "function")
throw new TypeError(
"`callbacks` must be an object, got a function instead. Did you mean `{onEvent: fn}`?"
);
const { onEvent = noop, onError = noop, onRetry = noop, onComment } = callbacks;
let incompleteLine = "", isFirstChunk = !0, id, data = "", eventType = "";
function feed(newChunk) {
const chunk = isFirstChunk ? newChunk.replace(/^\xEF\xBB\xBF/, "") : newChunk, [complete, incomplete] = splitLines(`${incompleteLine}${chunk}`);
for (const line of complete)
parseLine(line);
incompleteLine = incomplete, isFirstChunk = !1;
}
function parseLine(line) {
if (line === "") {
dispatchEvent();
return;
}
if (line.startsWith(":")) {
onComment && onComment(line.slice(line.startsWith(": ") ? 2 : 1));
return;
}
const fieldSeparatorIndex = line.indexOf(":");
if (fieldSeparatorIndex !== -1) {
const field = line.slice(0, fieldSeparatorIndex), offset = line[fieldSeparatorIndex + 1] === " " ? 2 : 1, value = line.slice(fieldSeparatorIndex + offset);
processField(field, value, line);
return;
}
processField(line, "", line);
}
function processField(field, value, line) {
switch (field) {
case "event":
eventType = value;
break;
case "data":
data = `${data}${value}
`;
break;
case "id":
id = value.includes("\0") ? void 0 : value;
break;
case "retry":
/^\d+$/.test(value) ? onRetry(parseInt(value, 10)) : onError(
new ParseError(`Invalid \`retry\` value: "${value}"`, {
type: "invalid-retry",
value,
line
})
);
break;
default:
onError(
new ParseError(
`Unknown field "${field.length > 20 ? `${field.slice(0, 20)}\u2026` : field}"`,
{ type: "unknown-field", field, value, line }
)
);
break;
}
}
function dispatchEvent() {
data.length > 0 && onEvent({
id,
event: eventType || void 0,
// If the data buffer's last character is a U+000A LINE FEED (LF) character,
// then remove the last character from the data buffer.
data: data.endsWith(`
`) ? data.slice(0, -1) : data
}), id = void 0, data = "", eventType = "";
}
function reset(options = {}) {
incompleteLine && options.consume && parseLine(incompleteLine), isFirstChunk = !0, id = void 0, data = "", eventType = "", incompleteLine = "";
}
return { feed, reset };
}
function splitLines(chunk) {
const lines = [];
let incompleteLine = "", searchIndex = 0;
for (; searchIndex < chunk.length; ) {
const crIndex = chunk.indexOf("\r", searchIndex), lfIndex = chunk.indexOf(`
`, searchIndex);
let lineEnd = -1;
if (crIndex !== -1 && lfIndex !== -1 ? lineEnd = Math.min(crIndex, lfIndex) : crIndex !== -1 ? lineEnd = crIndex : lfIndex !== -1 && (lineEnd = lfIndex), lineEnd === -1) {
incompleteLine = chunk.slice(searchIndex);
break;
} else {
const line = chunk.slice(searchIndex, lineEnd);
lines.push(line), searchIndex = lineEnd + 1, chunk[searchIndex - 1] === "\r" && chunk[searchIndex] === `
` && searchIndex++;
}
}
return [lines, incompleteLine];
}
export {
ParseError,
createParser
};
//# sourceMappingURL=index.esm.js.map
{"version":3,"file":"index.esm.js","sources":["../src/errors.ts","../src/parse.ts"],"sourcesContent":["/**\n * The type of error that occurred.\n * @public\n */\nexport type ErrorType = 'invalid-retry' | 'unknown-field'\n\n/**\n * Error thrown when encountering an issue during parsing.\n *\n * @public\n */\nexport class ParseError extends Error {\n /**\n * The type of error that occurred.\n */\n type: ErrorType\n\n /**\n * In the case of an unknown field encountered in the stream, this will be the field name.\n */\n field?: string\n\n /**\n * In the case of an unknown field encountered in the stream, this will be the value of the field.\n */\n value?: string\n\n /**\n * The line that caused the error, if available.\n */\n line?: string\n\n constructor(\n message: string,\n options: {type: ErrorType; field?: string; value?: string; line?: string},\n ) {\n super(message)\n this.name = 'ParseError'\n this.type = options.type\n this.field = options.field\n this.value = options.value\n this.line = options.line\n }\n}\n","/**\n * EventSource/Server-Sent Events parser\n * @see https://html.spec.whatwg.org/multipage/server-sent-events.html\n */\nimport {ParseError} from './errors.ts'\nimport type {EventSourceParser, ParserCallbacks} from './types.ts'\n\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nfunction noop(_arg: unknown) {\n // intentional noop\n}\n\n/**\n * Creates a new EventSource parser.\n *\n * @param callbacks - Callbacks to invoke on different parsing events:\n * - `onEvent` when a new event is parsed\n * - `onError` when an error occurs\n * - `onRetry` when a new reconnection interval has been sent from the server\n * - `onComment` when a comment is encountered in the stream\n *\n * @returns A new EventSource parser, with `parse` and `reset` methods.\n * @public\n */\nexport function createParser(callbacks: ParserCallbacks): EventSourceParser {\n if (typeof callbacks === 'function') {\n throw new TypeError(\n '`callbacks` must be an object, got a function instead. Did you mean `{onEvent: fn}`?',\n )\n }\n\n const {onEvent = noop, onError = noop, onRetry = noop, onComment} = callbacks\n\n let incompleteLine = ''\n\n let isFirstChunk = true\n let id: string | undefined\n let data = ''\n let eventType = ''\n\n function feed(newChunk: string) {\n // Strip any UTF8 byte order mark (BOM) at the start of the stream\n const chunk = isFirstChunk ? newChunk.replace(/^\\xEF\\xBB\\xBF/, '') : newChunk\n\n // If there was a previous incomplete line, append it to the new chunk,\n // so we may process it together as a new (hopefully complete) chunk.\n const [complete, incomplete] = splitLines(`${incompleteLine}${chunk}`)\n\n for (const line of complete) {\n parseLine(line)\n }\n\n incompleteLine = incomplete\n isFirstChunk = false\n }\n\n function parseLine(line: string) {\n // If the line is empty (a blank line), dispatch the event\n if (line === '') {\n dispatchEvent()\n return\n }\n\n // If the line starts with a U+003A COLON character (:), ignore the line.\n if (line.startsWith(':')) {\n if (onComment) {\n onComment(line.slice(line.startsWith(': ') ? 2 : 1))\n }\n return\n }\n\n // If the line contains a U+003A COLON character (:)\n const fieldSeparatorIndex = line.indexOf(':')\n if (fieldSeparatorIndex !== -1) {\n // Collect the characters on the line before the first U+003A COLON character (:),\n // and let `field` be that string.\n const field = line.slice(0, fieldSeparatorIndex)\n\n // Collect the characters on the line after the first U+003A COLON character (:),\n // and let `value` be that string. If value starts with a U+0020 SPACE character,\n // remove it from value.\n const offset = line[fieldSeparatorIndex + 1] === ' ' ? 2 : 1\n const value = line.slice(fieldSeparatorIndex + offset)\n\n processField(field, value, line)\n return\n }\n\n // Otherwise, the string is not empty but does not contain a U+003A COLON character (:)\n // Process the field using the whole line as the field name, and an empty string as the field value.\n // 👆 This is according to spec. That means that a line that has the value `data` will result in\n // a newline being added to the current `data` buffer, for instance.\n processField(line, '', line)\n }\n\n function processField(field: string, value: string, line: string) {\n // Field names must be compared literally, with no case folding performed.\n switch (field) {\n case 'event':\n // Set the `event type` buffer to field value\n eventType = value\n break\n case 'data':\n // Append the field value to the `data` buffer, then append a single U+000A LINE FEED(LF)\n // character to the `data` buffer.\n data = `${data}${value}\\n`\n break\n case 'id':\n // If the field value does not contain U+0000 NULL, then set the `ID` buffer to\n // the field value. Otherwise, ignore the field.\n id = value.includes('\\0') ? undefined : value\n break\n case 'retry':\n // If the field value consists of only ASCII digits, then interpret the field value as an\n // integer in base ten, and set the event stream's reconnection time to that integer.\n // Otherwise, ignore the field.\n if (/^\\d+$/.test(value)) {\n onRetry(parseInt(value, 10))\n } else {\n onError(\n new ParseError(`Invalid \\`retry\\` value: \"${value}\"`, {\n type: 'invalid-retry',\n value,\n line,\n }),\n )\n }\n break\n default:\n // Otherwise, the field is ignored.\n onError(\n new ParseError(\n `Unknown field \"${field.length > 20 ? `${field.slice(0, 20)}…` : field}\"`,\n {type: 'unknown-field', field, value, line},\n ),\n )\n break\n }\n }\n\n function dispatchEvent() {\n const shouldDispatch = data.length > 0\n if (shouldDispatch) {\n onEvent({\n id,\n event: eventType || undefined,\n // If the data buffer's last character is a U+000A LINE FEED (LF) character,\n // then remove the last character from the data buffer.\n data: data.endsWith('\\n') ? data.slice(0, -1) : data,\n })\n }\n\n // Reset for the next event\n id = undefined\n data = ''\n eventType = ''\n }\n\n function reset(options: {consume?: boolean} = {}) {\n if (incompleteLine && options.consume) {\n parseLine(incompleteLine)\n }\n\n isFirstChunk = true\n id = undefined\n data = ''\n eventType = ''\n incompleteLine = ''\n }\n\n return {feed, reset}\n}\n\n/**\n * For the given `chunk`, split it into lines according to spec, and return any remaining incomplete line.\n *\n * @param chunk - The chunk to split into lines\n * @returns A tuple containing an array of complete lines, and any remaining incomplete line\n * @internal\n */\nfunction splitLines(chunk: string): [complete: Array<string>, incomplete: string] {\n /**\n * According to the spec, a line is terminated by either:\n * - U+000D CARRIAGE RETURN U+000A LINE FEED (CRLF) character pair\n * - a single U+000A LINE FEED(LF) character not preceded by a U+000D CARRIAGE RETURN(CR) character\n * - a single U+000D CARRIAGE RETURN(CR) character not followed by a U+000A LINE FEED(LF) character\n */\n const lines: Array<string> = []\n let incompleteLine = ''\n let searchIndex = 0\n\n while (searchIndex < chunk.length) {\n // Find next line terminator\n const crIndex = chunk.indexOf('\\r', searchIndex)\n const lfIndex = chunk.indexOf('\\n', searchIndex)\n\n // Determine line end\n let lineEnd = -1\n if (crIndex !== -1 && lfIndex !== -1) {\n // CRLF case\n lineEnd = Math.min(crIndex, lfIndex)\n } else if (crIndex !== -1) {\n lineEnd = crIndex\n } else if (lfIndex !== -1) {\n lineEnd = lfIndex\n }\n\n // Extract line if terminator found\n if (lineEnd === -1) {\n // No terminator found, rest is incomplete\n incompleteLine = chunk.slice(searchIndex)\n break\n } else {\n const line = chunk.slice(searchIndex, lineEnd)\n lines.push(line)\n\n // Move past line terminator\n searchIndex = lineEnd + 1\n if (chunk[searchIndex - 1] === '\\r' && chunk[searchIndex] === '\\n') {\n searchIndex++\n }\n }\n }\n\n return [lines, incompleteLine]\n}\n"],"names":[],"mappings":"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"}
import { createParser } from "./index.esm.js";
import { ParseError } from "./index.esm.js";
class EventSourceParserStream extends TransformStream {
constructor({ onError, onRetry, onComment } = {}) {
let parser;
super({
start(controller) {
parser = createParser({
onEvent: (event) => {
controller.enqueue(event);
},
onError(error) {
onError === "terminate" ? controller.error(error) : typeof onError == "function" && onError(error);
},
onRetry,
onComment
});
},
transform(chunk) {
parser.feed(chunk);
}
});
}
}
export {
EventSourceParserStream,
ParseError
};
//# sourceMappingURL=stream.esm.js.map
{"version":3,"file":"stream.esm.js","sources":["../src/stream.ts"],"sourcesContent":["import {createParser} from './parse.ts'\nimport type {EventSourceMessage, EventSourceParser} from './types.ts'\n\n/**\n * Options for the EventSourceParserStream.\n *\n * @public\n */\nexport interface StreamOptions {\n /**\n * Behavior when a parsing error occurs.\n *\n * - A custom function can be provided to handle the error.\n * - `'terminate'` will error the stream and stop parsing.\n * - Any other value will ignore the error and continue parsing.\n *\n * @defaultValue `undefined`\n */\n onError?: 'terminate' | ((error: Error) => void)\n\n /**\n * Callback for when a reconnection interval is sent from the server.\n *\n * @param retry - The number of milliseconds to wait before reconnecting.\n */\n onRetry?: (retry: number) => void\n\n /**\n * Callback for when a comment is encountered in the stream.\n *\n * @param comment - The comment encountered in the stream.\n */\n onComment?: (comment: string) => void\n}\n\n/**\n * A TransformStream that ingests a stream of strings and produces a stream of `EventSourceMessage`.\n *\n * @example Basic usage\n * ```\n * const eventStream =\n * response.body\n * .pipeThrough(new TextDecoderStream())\n * .pipeThrough(new EventSourceParserStream())\n * ```\n *\n * @example Terminate stream on parsing errors\n * ```\n * const eventStream =\n * response.body\n * .pipeThrough(new TextDecoderStream())\n * .pipeThrough(new EventSourceParserStream({terminateOnError: true}))\n * ```\n *\n * @public\n */\nexport class EventSourceParserStream extends TransformStream<string, EventSourceMessage> {\n constructor({onError, onRetry, onComment}: StreamOptions = {}) {\n let parser!: EventSourceParser\n\n super({\n start(controller) {\n parser = createParser({\n onEvent: (event) => {\n controller.enqueue(event)\n },\n onError(error) {\n if (onError === 'terminate') {\n controller.error(error)\n } else if (typeof onError === 'function') {\n onError(error)\n }\n\n // Ignore by default\n },\n onRetry,\n onComment,\n })\n },\n transform(chunk) {\n parser.feed(chunk)\n },\n })\n }\n}\n\nexport {type ErrorType, ParseError} from './errors.ts'\nexport type {EventSourceMessage} from './types.ts'\n"],"names":[],"mappings":";;AAwDO,MAAM,gCAAgC,gBAA4C;AAAA,EACvF,YAAY,EAAC,SAAS,SAAS,UAAS,IAAmB,CAAA,GAAI;AACzD,QAAA;AAEE,UAAA;AAAA,MACJ,MAAM,YAAY;AAChB,iBAAS,aAAa;AAAA,UACpB,SAAS,CAAC,UAAU;AAClB,uBAAW,QAAQ,KAAK;AAAA,UAC1B;AAAA,UACA,QAAQ,OAAO;AACT,wBAAY,cACd,WAAW,MAAM,KAAK,IACb,OAAO,WAAY,cAC5B,QAAQ,KAAK;AAAA,UAIjB;AAAA,UACA;AAAA,UACA;AAAA,QAAA,CACD;AAAA,MACH;AAAA,MACA,UAAU,OAAO;AACf,eAAO,KAAK,KAAK;AAAA,MACnB;AAAA,IAAA,CACD;AAAA,EACH;AACF;"}
// AUTO-GENERATED – DO NOT EDIT
export * from './dist/stream.esm'

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet