@suveren/gateway 0.7.2 → 0.7.4

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.
Files changed (51) hide show
  1. package/README.md +10 -1
  2. package/content/integrations/gmail.json +3 -6
  3. package/dist/control-plane/index.mjs +111 -10
  4. package/dist/mcp-server/http.mjs +59 -8
  5. package/dist/ui/assets/index-0Q2eNytX.js +110 -0
  6. package/dist/ui/index.html +1 -1
  7. package/node_modules/@hono/node-server/dist/index.cjs +151 -9
  8. package/node_modules/@hono/node-server/dist/index.mjs +151 -9
  9. package/node_modules/@hono/node-server/package.json +1 -1
  10. package/node_modules/@modelcontextprotocol/sdk/node_modules/body-parser/node_modules/content-type/README.md +2 -0
  11. package/node_modules/@modelcontextprotocol/sdk/node_modules/body-parser/node_modules/content-type/dist/index.d.ts +20 -0
  12. package/node_modules/@modelcontextprotocol/sdk/node_modules/body-parser/node_modules/content-type/dist/index.js +20 -14
  13. package/node_modules/@modelcontextprotocol/sdk/node_modules/body-parser/node_modules/content-type/dist/index.js.map +1 -1
  14. package/node_modules/@modelcontextprotocol/sdk/node_modules/body-parser/node_modules/content-type/package.json +1 -1
  15. package/node_modules/@modelcontextprotocol/sdk/node_modules/type-is/node_modules/content-type/README.md +2 -0
  16. package/node_modules/@modelcontextprotocol/sdk/node_modules/type-is/node_modules/content-type/dist/index.d.ts +20 -0
  17. package/node_modules/@modelcontextprotocol/sdk/node_modules/type-is/node_modules/content-type/dist/index.js +20 -14
  18. package/node_modules/@modelcontextprotocol/sdk/node_modules/type-is/node_modules/content-type/dist/index.js.map +1 -1
  19. package/node_modules/@modelcontextprotocol/sdk/node_modules/type-is/node_modules/content-type/package.json +1 -1
  20. package/node_modules/eventsource-parser/dist/index.cjs +2 -2
  21. package/node_modules/eventsource-parser/dist/index.cjs.map +1 -1
  22. package/node_modules/eventsource-parser/dist/index.js +2 -2
  23. package/node_modules/eventsource-parser/dist/index.js.map +1 -1
  24. package/node_modules/eventsource-parser/package.json +1 -1
  25. package/node_modules/eventsource-parser/src/parse.ts +4 -2
  26. package/node_modules/hono/dist/cjs/client/client.js +3 -3
  27. package/node_modules/hono/dist/cjs/jsx/intrinsic-element/components.js +1 -1
  28. package/node_modules/hono/dist/cjs/middleware/cors/index.js +15 -12
  29. package/node_modules/hono/dist/cjs/middleware/etag/digest.js +47 -1
  30. package/node_modules/hono/dist/cjs/middleware/secure-headers/secure-headers.js +6 -3
  31. package/node_modules/hono/dist/cjs/utils/url.js +2 -2
  32. package/node_modules/hono/dist/client/client.js +3 -3
  33. package/node_modules/hono/dist/jsx/intrinsic-element/components.js +1 -1
  34. package/node_modules/hono/dist/middleware/cors/index.js +15 -12
  35. package/node_modules/hono/dist/middleware/etag/digest.js +47 -1
  36. package/node_modules/hono/dist/middleware/secure-headers/secure-headers.js +6 -3
  37. package/node_modules/hono/dist/types/middleware/secure-headers/permissions-policy.d.ts +3 -3
  38. package/node_modules/hono/dist/utils/url.js +2 -2
  39. package/node_modules/hono/package.json +1 -1
  40. package/node_modules/ip-address/README.md +134 -134
  41. package/node_modules/ip-address/dist/address-error.d.ts +11 -0
  42. package/node_modules/ip-address/dist/address-error.js.map +1 -1
  43. package/node_modules/ip-address/dist/ipv4.d.ts +22 -6
  44. package/node_modules/ip-address/dist/ipv4.js +22 -6
  45. package/node_modules/ip-address/dist/ipv4.js.map +1 -1
  46. package/node_modules/ip-address/dist/ipv6.d.ts +10 -3
  47. package/node_modules/ip-address/dist/ipv6.js +26 -17
  48. package/node_modules/ip-address/dist/ipv6.js.map +1 -1
  49. package/node_modules/ip-address/package.json +1 -1
  50. package/package.json +2 -2
  51. package/dist/ui/assets/index-DHT9QCrE.js +0 -110
@@ -52,16 +52,17 @@ function format(obj) {
52
52
  * Parse a `Content-Type` header.
53
53
  */
54
54
  function parse(header, options) {
55
+ const stopChar = options?.comma === true ? COMMA : 65536; // Sentinel for "no stop char".
55
56
  const len = header.length;
56
- let index = skipOWS(header, 0, len);
57
+ let index = skipOWS(header, options?.start ?? 0, len);
57
58
  const valueStart = index;
58
- index = skipValue(header, index, len);
59
+ index = skipValue(header, index, len, stopChar);
59
60
  const valueEnd = trailingOWS(header, valueStart, index);
60
61
  const type = header.slice(valueStart, valueEnd).toLowerCase();
61
- const parameters = options?.parameters === false
62
- ? new NullObject()
63
- : parseParameters(header, index, len);
64
- return { type, parameters };
62
+ if (options?.parameters === false) {
63
+ return { type, index, parameters: new NullObject() };
64
+ }
65
+ return parseParameters(header, type, index, len, stopChar);
65
66
  }
66
67
  const SP = 32; // " "
67
68
  const HTAB = 9; // "\t"
@@ -69,16 +70,21 @@ const SEMI = 59; // ";"
69
70
  const EQ = 61; // "="
70
71
  const DQUOTE = 34; // '"'
71
72
  const BSLASH = 92; // "\\"
73
+ const COMMA = 44; // ","
72
74
  /**
73
75
  * Parses the parameters of a `Content-Type` header starting at the given index.
74
76
  */
75
- function parseParameters(header, index, len) {
77
+ function parseParameters(header, type, index, len, stopChar) {
76
78
  const parameters = new NullObject();
77
79
  parameter: while (index < len) {
80
+ if (header.charCodeAt(index) === stopChar)
81
+ break;
78
82
  index = skipOWS(header, index + 1 /* Skip over ; */, len);
79
83
  const keyStart = index;
80
84
  while (index < len) {
81
85
  const code = header.charCodeAt(index);
86
+ if (code === stopChar)
87
+ break parameter;
82
88
  if (code === SEMI)
83
89
  continue parameter;
84
90
  if (code === EQ) {
@@ -91,7 +97,7 @@ function parseParameters(header, index, len) {
91
97
  while (index < len) {
92
98
  const code = header.charCodeAt(index++);
93
99
  if (code === DQUOTE) {
94
- index = skipValue(header, index, len);
100
+ index = skipValue(header, index, len, stopChar);
95
101
  if (parameters[key] === undefined)
96
102
  parameters[key] = value;
97
103
  break;
@@ -105,7 +111,7 @@ function parseParameters(header, index, len) {
105
111
  continue parameter;
106
112
  }
107
113
  const valueStart = index;
108
- index = skipValue(header, index, len);
114
+ index = skipValue(header, index, len, stopChar);
109
115
  if (parameters[key] === undefined) {
110
116
  const valueEnd = trailingOWS(header, valueStart, index);
111
117
  parameters[key] = header.slice(valueStart, valueEnd);
@@ -115,15 +121,15 @@ function parseParameters(header, index, len) {
115
121
  index++;
116
122
  }
117
123
  }
118
- return parameters;
124
+ return { type, index, parameters };
119
125
  }
120
126
  /**
121
- * Skip over characters until a semicolon.
127
+ * Skip over characters until a semicolon or other exit character.
122
128
  */
123
- function skipValue(str, index, len) {
129
+ function skipValue(str, index, len, stopChar) {
124
130
  while (index < len) {
125
- const char = str.charCodeAt(index);
126
- if (char === SEMI)
131
+ const code = str.charCodeAt(index);
132
+ if (code === SEMI || code === stopChar)
127
133
  break;
128
134
  index++;
129
135
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * content-type\n * Copyright(c) 2015 Douglas Christopher Wilson\n * MIT Licensed\n */\n\nconst TEXT_REGEXP = /^[\\u0009\\u0020-\\u007e\\u0080-\\u00ff]*$/;\nconst TOKEN_REGEXP = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;\n\n/**\n * RegExp to match chars that must be quoted-pair in RFC 9110 sec 5.6.4\n */\nconst QUOTE_REGEXP = /[\\\\\"]/g;\n\n/**\n * RegExp to match type in RFC 9110 sec 8.3.1\n *\n * media-type = type \"/\" subtype\n * type = token\n * subtype = token\n */\nconst TYPE_REGEXP =\n /^[!#$%&'*+.^_`|~0-9A-Za-z-]+\\/[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;\n\n/**\n * Null object perf optimization. Faster than `Object.create(null)` and `{ __proto__: null }`.\n */\nconst NullObject = /* @__PURE__ */ (() => {\n const C = function () {};\n C.prototype = Object.create(null);\n return C;\n})() as unknown as { new (): any };\n\n/**\n * The content type object contains a type string and optional parameters.\n */\nexport interface ContentType {\n type: string;\n parameters: Record<string, string>;\n}\n\n/**\n * Format an object into a `Content-Type` header.\n */\nexport function format(obj: Partial<ContentType>): string {\n const { type, parameters } = obj;\n\n if (!type || !TYPE_REGEXP.test(type)) {\n throw new TypeError(`Invalid type: ${type}`);\n }\n\n let result = type;\n\n if (parameters) {\n for (const param of Object.keys(parameters)) {\n if (!TOKEN_REGEXP.test(param)) {\n throw new TypeError(`Invalid parameter name: ${param}`);\n }\n\n result += `; ${param}=${qstring(parameters[param])}`;\n }\n }\n\n return result;\n}\n\n/**\n * Options for parsing a `Content-Type` header.\n */\nexport interface ParseOptions {\n parameters?: boolean;\n}\n\n/**\n * Parse a `Content-Type` header.\n */\nexport function parse(header: string, options?: ParseOptions): ContentType {\n const len = header.length;\n let index = skipOWS(header, 0, len);\n\n const valueStart = index;\n index = skipValue(header, index, len);\n const valueEnd = trailingOWS(header, valueStart, index);\n const type = header.slice(valueStart, valueEnd).toLowerCase();\n const parameters =\n options?.parameters === false\n ? new NullObject()\n : parseParameters(header, index, len);\n\n return { type, parameters };\n}\n\nconst SP = 32; // \" \"\nconst HTAB = 9; // \"\\t\"\nconst SEMI = 59; // \";\"\nconst EQ = 61; // \"=\"\nconst DQUOTE = 34; // '\"'\nconst BSLASH = 92; // \"\\\\\"\n\n/**\n * Parses the parameters of a `Content-Type` header starting at the given index.\n */\nfunction parseParameters(\n header: string,\n index: number,\n len: number,\n): Record<string, string> {\n const parameters: Record<string, string> = new NullObject();\n\n parameter: while (index < len) {\n index = skipOWS(header, index + 1 /* Skip over ; */, len);\n\n const keyStart = index;\n\n while (index < len) {\n const code = header.charCodeAt(index);\n if (code === SEMI) continue parameter;\n\n if (code === EQ) {\n const keyEnd = trailingOWS(header, keyStart, index);\n const key = header.slice(keyStart, keyEnd).toLowerCase();\n\n index = skipOWS(header, index + 1, len);\n\n if (index < len && header.charCodeAt(index) === DQUOTE) {\n index++;\n\n let value = \"\";\n while (index < len) {\n const code = header.charCodeAt(index++);\n if (code === DQUOTE) {\n index = skipValue(header, index, len);\n if (parameters[key] === undefined) parameters[key] = value;\n break;\n }\n\n if (code === BSLASH && index < len) {\n value += header[index++];\n continue;\n }\n\n value += String.fromCharCode(code);\n }\n\n continue parameter;\n }\n\n const valueStart = index;\n index = skipValue(header, index, len);\n\n if (parameters[key] === undefined) {\n const valueEnd = trailingOWS(header, valueStart, index);\n parameters[key] = header.slice(valueStart, valueEnd);\n }\n\n continue parameter;\n }\n\n index++;\n }\n }\n\n return parameters;\n}\n\n/**\n * Skip over characters until a semicolon.\n */\nfunction skipValue(str: string, index: number, len: number): number {\n while (index < len) {\n const char = str.charCodeAt(index);\n if (char === SEMI) break;\n index++;\n }\n return index;\n}\n\n/**\n * Skip optional whitespace (OWS) in an HTTP header value.\n *\n * OWS is defined in RFC 9110 sec 5.6.3 as SP (\" \") or HTAB (\"\\t\").\n */\nfunction skipOWS(header: string, index: number, len: number): number {\n while (index < len) {\n const char = header.charCodeAt(index);\n if (char !== SP && char !== HTAB) break;\n index++;\n }\n return index;\n}\n\n/**\n * Trim optional whitespace (OWS) from the end of a substring.\n *\n * OWS is defined in RFC 9110 sec 5.6.3 as SP (\" \") or HTAB (\"\\t\").\n */\nfunction trailingOWS(header: string, start: number, end: number): number {\n while (end > start) {\n const char = header.charCodeAt(end - 1);\n if (char !== SP && char !== HTAB) break;\n end--;\n }\n return end;\n}\n\n/**\n * Serialize a parameter value.\n */\nfunction qstring(str: string): string {\n if (TOKEN_REGEXP.test(str)) return str;\n if (TEXT_REGEXP.test(str)) return `\"${str.replace(QUOTE_REGEXP, \"\\\\$&\")}\"`;\n\n throw new TypeError(`Invalid parameter value: ${str}`);\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * content-type\n * Copyright(c) 2015 Douglas Christopher Wilson\n * MIT Licensed\n */\n\nconst TEXT_REGEXP = /^[\\u0009\\u0020-\\u007e\\u0080-\\u00ff]*$/;\nconst TOKEN_REGEXP = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;\n\n/**\n * RegExp to match chars that must be quoted-pair in RFC 9110 sec 5.6.4\n */\nconst QUOTE_REGEXP = /[\\\\\"]/g;\n\n/**\n * RegExp to match type in RFC 9110 sec 8.3.1\n *\n * media-type = type \"/\" subtype\n * type = token\n * subtype = token\n */\nconst TYPE_REGEXP =\n /^[!#$%&'*+.^_`|~0-9A-Za-z-]+\\/[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;\n\n/**\n * Null object perf optimization. Faster than `Object.create(null)` and `{ __proto__: null }`.\n */\nconst NullObject = /* @__PURE__ */ (() => {\n const C = function () {};\n C.prototype = Object.create(null);\n return C;\n})() as unknown as { new (): any };\n\n/**\n * The content type object contains a type string and optional parameters.\n */\nexport interface ContentType {\n type: string;\n index: number;\n parameters: Record<string, string>;\n}\n\n/**\n * Format an object into a `Content-Type` header.\n */\nexport function format(obj: Partial<ContentType>): string {\n const { type, parameters } = obj;\n\n if (!type || !TYPE_REGEXP.test(type)) {\n throw new TypeError(`Invalid type: ${type}`);\n }\n\n let result = type;\n\n if (parameters) {\n for (const param of Object.keys(parameters)) {\n if (!TOKEN_REGEXP.test(param)) {\n throw new TypeError(`Invalid parameter name: ${param}`);\n }\n\n result += `; ${param}=${qstring(parameters[param])}`;\n }\n }\n\n return result;\n}\n\n/**\n * Options for parsing a `Content-Type` header.\n */\nexport interface ParseOptions {\n /**\n * Exit early on the first semicolon, returning only the type.\n * This is useful for parsing the MIME from `Content-Type` headers.\n *\n * @default false\n */\n parameters?: boolean;\n /**\n * Exits early on a comma, returning the first value and parameters.\n * This is useful for parsing `Accept` headers.\n *\n * @default false\n */\n comma?: boolean;\n /**\n * The index to start parsing from.\n *\n * @default 0\n */\n start?: number;\n}\n\n/**\n * Parse a `Content-Type` header.\n */\nexport function parse(header: string, options?: ParseOptions): ContentType {\n const stopChar = options?.comma === true ? COMMA : 65_536; // Sentinel for \"no stop char\".\n const len = header.length;\n let index = skipOWS(header, options?.start ?? 0, len);\n\n const valueStart = index;\n index = skipValue(header, index, len, stopChar);\n const valueEnd = trailingOWS(header, valueStart, index);\n const type = header.slice(valueStart, valueEnd).toLowerCase();\n\n if (options?.parameters === false) {\n return { type, index, parameters: new NullObject() };\n }\n\n return parseParameters(header, type, index, len, stopChar);\n}\n\nconst SP = 32; // \" \"\nconst HTAB = 9; // \"\\t\"\nconst SEMI = 59; // \";\"\nconst EQ = 61; // \"=\"\nconst DQUOTE = 34; // '\"'\nconst BSLASH = 92; // \"\\\\\"\nconst COMMA = 44; // \",\"\n\n/**\n * Parses the parameters of a `Content-Type` header starting at the given index.\n */\nfunction parseParameters(\n header: string,\n type: string,\n index: number,\n len: number,\n stopChar: number,\n): ContentType {\n const parameters: Record<string, string> = new NullObject();\n\n parameter: while (index < len) {\n if (header.charCodeAt(index) === stopChar) break;\n\n index = skipOWS(header, index + 1 /* Skip over ; */, len);\n\n const keyStart = index;\n\n while (index < len) {\n const code = header.charCodeAt(index);\n if (code === stopChar) break parameter;\n\n if (code === SEMI) continue parameter;\n\n if (code === EQ) {\n const keyEnd = trailingOWS(header, keyStart, index);\n const key = header.slice(keyStart, keyEnd).toLowerCase();\n\n index = skipOWS(header, index + 1, len);\n\n if (index < len && header.charCodeAt(index) === DQUOTE) {\n index++;\n\n let value = \"\";\n while (index < len) {\n const code = header.charCodeAt(index++);\n if (code === DQUOTE) {\n index = skipValue(header, index, len, stopChar);\n if (parameters[key] === undefined) parameters[key] = value;\n break;\n }\n\n if (code === BSLASH && index < len) {\n value += header[index++];\n continue;\n }\n\n value += String.fromCharCode(code);\n }\n\n continue parameter;\n }\n\n const valueStart = index;\n index = skipValue(header, index, len, stopChar);\n\n if (parameters[key] === undefined) {\n const valueEnd = trailingOWS(header, valueStart, index);\n parameters[key] = header.slice(valueStart, valueEnd);\n }\n\n continue parameter;\n }\n\n index++;\n }\n }\n\n return { type, index, parameters };\n}\n\n/**\n * Skip over characters until a semicolon or other exit character.\n */\nfunction skipValue(\n str: string,\n index: number,\n len: number,\n stopChar: number,\n): number {\n while (index < len) {\n const code = str.charCodeAt(index);\n if (code === SEMI || code === stopChar) break;\n index++;\n }\n return index;\n}\n\n/**\n * Skip optional whitespace (OWS) in an HTTP header value.\n *\n * OWS is defined in RFC 9110 sec 5.6.3 as SP (\" \") or HTAB (\"\\t\").\n */\nfunction skipOWS(header: string, index: number, len: number): number {\n while (index < len) {\n const char = header.charCodeAt(index);\n if (char !== SP && char !== HTAB) break;\n index++;\n }\n return index;\n}\n\n/**\n * Trim optional whitespace (OWS) from the end of a substring.\n *\n * OWS is defined in RFC 9110 sec 5.6.3 as SP (\" \") or HTAB (\"\\t\").\n */\nfunction trailingOWS(header: string, start: number, end: number): number {\n while (end > start) {\n const char = header.charCodeAt(end - 1);\n if (char !== SP && char !== HTAB) break;\n end--;\n }\n return end;\n}\n\n/**\n * Serialize a parameter value.\n */\nfunction qstring(str: string): string {\n if (TOKEN_REGEXP.test(str)) return str;\n if (TEXT_REGEXP.test(str)) return `\"${str.replace(QUOTE_REGEXP, \"\\\\$&\")}\"`;\n\n throw new TypeError(`Invalid parameter value: ${str}`);\n}\n"]}
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "content-type",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "description": "Create and parse HTTP Content-Type header",
5
5
  "keywords": [
6
6
  "content-type",
@@ -101,7 +101,7 @@ ${value2}`, dataLines++;
101
101
  }
102
102
  if (firstCharCode === 105 && chunk.charCodeAt(start + 1) === 100 && chunk.charCodeAt(start + 2) === 58) {
103
103
  const value2 = chunk.slice(chunk.charCodeAt(start + 3) === SPACE ? start + 4 : start + 3, end);
104
- id = value2.includes("\0") ? void 0 : value2;
104
+ value2.includes("\0") || (id = value2);
105
105
  return;
106
106
  }
107
107
  if (firstCharCode === 58) {
@@ -129,7 +129,7 @@ ${value2}`, dataLines++;
129
129
  ${value}`, dataLines++;
130
130
  break;
131
131
  case "id":
132
- id = value.includes("\0") ? void 0 : value;
132
+ value.includes("\0") || (id = value);
133
133
  break;
134
134
  case "retry":
135
135
  /^\d+$/.test(value) ? onRetry(parseInt(value, 10)) : onError(
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","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' | 'max-buffer-size-exceeded'\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 | undefined\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 | undefined\n\n /**\n * The line that caused the error, if available.\n */\n line?: string | undefined\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, ParserConfig} from './types.ts'\n\n// ASCII codes used in the hot parsing paths.\nconst LF = 10\nconst CR = 13\nconst SPACE = 32\n\n// oxlint-disable-next-line no-unused-vars\nfunction noop(_arg: unknown) {\n // intentional noop\n}\n\n/**\n * Creates a new EventSource parser.\n *\n * @param config - Parser configuration. Accepts callbacks (see {@link ParserCallbacks})\n * and options like `maxBufferSize` (see {@link ParserConfig}).\n *\n * @returns A new EventSource parser, with `feed` and `reset` methods.\n * @public\n */\nexport function createParser(config: ParserConfig): EventSourceParser {\n if (typeof config === 'function') {\n throw new TypeError(\n '`config` must be an object, got a function instead. Did you mean `createParser({onEvent: fn})`?',\n )\n }\n\n const {onEvent = noop, onError = noop, onRetry = noop, onComment, maxBufferSize} = config\n\n // Trailing bytes from prior `feed()` calls that did not yet form a complete line.\n // Stored as an array of fragments and only joined when a line terminator arrives.\n // Concatenating per-feed (`prefix + chunk`) is O(N²) when a single SSE line spans\n // many chunks (e.g. a large `data:` payload streamed in tiny slices, or an MCP-style\n // server that emits one giant content block). Buffering as fragments + joining once\n // makes the same workload linear.\n const pendingFragments: string[] = []\n\n // Running total of `pendingFragments` lengths, kept in sync with the array so the\n // `maxBufferSize` check doesn't have to walk the fragment list on every feed.\n let pendingFragmentsLength = 0\n\n let isFirstChunk = true\n let id: string | undefined\n let data = ''\n let dataLines = 0\n let eventType: string | undefined\n\n // Set after a `maxBufferSize` overflow. Once tripped, `feed()` throws until\n // `reset()` is called — see the comment on `maxBufferSize` in `ParserConfig`.\n let terminated = false\n\n /**\n * Feeds a chunk of the SSE stream to the parser. Any trailing bytes that do\n * not yet form a complete line are held back and prepended to the next chunk,\n * so callers can pass arbitrary slices of the stream without worrying about\n * line boundaries.\n *\n * Per the SSE spec, a UTF-8 BOM (0xEF 0xBB 0xBF) at the start of the very\n * first chunk is stripped before parsing.\n *\n * @see https://html.spec.whatwg.org/multipage/server-sent-events.html#parsing-an-event-stream\n */\n function feed(chunk: string) {\n if (terminated) {\n throw new Error(\n 'Cannot feed parser: it was terminated after exceeding the configured max buffer size. Call `reset()` to resume parsing.',\n )\n }\n\n if (isFirstChunk) {\n isFirstChunk = false\n // Match and strip UTF-8 BOM from the start of the stream, if present.\n // (Per the spec, this is only valid at the very start of the stream)\n if (\n chunk.charCodeAt(0) === 0xef &&\n chunk.charCodeAt(1) === 0xbb &&\n chunk.charCodeAt(2) === 0xbf\n ) {\n chunk = chunk.slice(3)\n }\n }\n\n // Hot path: no buffered prefix from a prior partial line. Hand the chunk\n // straight to `processLines`, exactly like the original implementation.\n // Zero new work in the common case (every chunk ends with `\\n\\n`).\n if (pendingFragments.length === 0) {\n const trailing = processLines(chunk)\n if (trailing !== '') {\n pendingFragments.push(trailing)\n pendingFragmentsLength = trailing.length\n }\n checkBufferSize()\n return\n }\n\n // We have a buffered prefix. If this chunk also has no terminator, append\n // to the buffer without concatenating — that's the O(N²) trap we're\n // avoiding (large single `data:` payload split across many tiny chunks).\n if (chunk.indexOf('\\n') === -1 && chunk.indexOf('\\r') === -1) {\n pendingFragments.push(chunk)\n pendingFragmentsLength += chunk.length\n checkBufferSize()\n return\n }\n\n // Terminator arrived. Join the accumulated fragments + this chunk once,\n // process, and buffer any new trailing partial line.\n pendingFragments.push(chunk)\n const input = pendingFragments.join('')\n pendingFragments.length = 0\n pendingFragmentsLength = 0\n const trailing = processLines(input)\n if (trailing !== '') {\n pendingFragments.push(trailing)\n pendingFragmentsLength = trailing.length\n }\n checkBufferSize()\n }\n\n function checkBufferSize() {\n if (maxBufferSize === undefined) return\n if (pendingFragmentsLength + data.length <= maxBufferSize) return\n\n terminated = true\n pendingFragments.length = 0\n pendingFragmentsLength = 0\n id = undefined\n data = ''\n dataLines = 0\n eventType = undefined\n onError(\n new ParseError(`Buffered data exceeded max buffer size of ${maxBufferSize} characters`, {\n type: 'max-buffer-size-exceeded',\n }),\n )\n }\n\n /**\n * Splits `chunk` into SSE lines and dispatches each to the appropriate handler.\n * Returns any trailing bytes that did not terminate with a line break, so the\n * caller can prepend them to the next chunk.\n *\n * The SSE spec permits three line terminators: `\\n`, `\\r`, and `\\r\\n`. Real-world\n * streams almost always use plain `\\n`, so we take a fast path when no `\\r` is\n * present in the chunk. The slow path is spec-correct but does more work per line.\n */\n function processLines(chunk: string): string {\n let searchIndex = 0\n\n // Fast path: LF-only chunk (the common case for typical SSE servers).\n // We can scan forward with a single `indexOf('\\n')` per line and inline\n // the hot-path branches for `data:` and `event:` without the CR bookkeeping\n // the slow path needs.\n if (chunk.indexOf('\\r') === -1) {\n let lfIndex = chunk.indexOf('\\n', searchIndex)\n while (lfIndex !== -1) {\n // Blank line: end-of-event marker. Dispatch the accumulated event (if any)\n // and reset the buffered fields. This is hoisted out of `parseLine` because\n // it's the single most common line shape after `data:` lines.\n if (searchIndex === lfIndex) {\n if (dataLines > 0) {\n onEvent({id, event: eventType, data})\n }\n id = undefined\n data = ''\n dataLines = 0\n eventType = undefined\n searchIndex = lfIndex + 1\n lfIndex = chunk.indexOf('\\n', searchIndex)\n continue\n }\n const firstCharCode = chunk.charCodeAt(searchIndex)\n if (isDataPrefix(chunk, searchIndex, firstCharCode)) {\n // `data:` line — append the value to the event's data buffer.\n // 'data:'.length === 5, 'data: '.length === 6\n const valueStart =\n chunk.charCodeAt(searchIndex + 5) === SPACE ? searchIndex + 6 : searchIndex + 5\n const value = chunk.slice(valueStart, lfIndex)\n // Fast path within a fast path: if this is the first data line AND the\n // next char is another LF (i.e. `data:foo\\n\\n`), dispatch immediately\n // without ever writing to the `data` buffer. This is the shape of a\n // typical single-line SSE event (ChatGPT-style streams, etc.) and is\n // hot enough to be worth the duplication.\n if (dataLines === 0 && chunk.charCodeAt(lfIndex + 1) === LF) {\n onEvent({id, event: eventType, data: value})\n id = undefined\n data = ''\n eventType = undefined\n searchIndex = lfIndex + 2\n lfIndex = chunk.indexOf('\\n', searchIndex)\n continue\n }\n // Multi-line data: concatenate with newline separator per spec.\n data = dataLines === 0 ? value : `${data}\\n${value}`\n dataLines++\n } else if (isEventPrefix(chunk, searchIndex, firstCharCode)) {\n // `event:` line — set the event type for the next dispatch. Per spec,\n // an empty value resets `event type` to its default (undefined here).\n // 'event:'.length === 6, 'event: '.length === 7\n eventType =\n chunk.slice(\n chunk.charCodeAt(searchIndex + 6) === SPACE ? searchIndex + 7 : searchIndex + 6,\n lfIndex,\n ) || undefined\n } else {\n // Everything else: `id:`, `retry:`, comment lines (`:` prefix), unknown\n // fields, or malformed lines. These are rarer and go through the full\n // per-line parser, which handles the SSE field grammar in detail.\n parseLine(chunk, searchIndex, lfIndex)\n }\n searchIndex = lfIndex + 1\n lfIndex = chunk.indexOf('\\n', searchIndex)\n }\n return chunk.slice(searchIndex)\n }\n\n // Slow path: the chunk contains at least one `\\r`, so lines may be terminated\n // by `\\r`, `\\n`, or `\\r\\n`. We locate the next terminator by looking at both\n // the nearest `\\r` and `\\n` and picking whichever comes first.\n while (searchIndex < chunk.length) {\n const crIndex = chunk.indexOf('\\r', searchIndex)\n const lfIndex = chunk.indexOf('\\n', searchIndex)\n\n let lineEnd = -1\n if (crIndex !== -1 && lfIndex !== -1) {\n lineEnd = crIndex < lfIndex ? crIndex : lfIndex\n } else if (crIndex !== -1) {\n // A trailing `\\r` at the very end of the chunk is ambiguous: it could be\n // a bare-CR terminator, or the first half of a `\\r\\n` whose `\\n` arrives\n // in the next chunk. Defer until we see more input.\n if (crIndex === chunk.length - 1) {\n lineEnd = -1\n } else {\n lineEnd = crIndex\n }\n } else if (lfIndex !== -1) {\n lineEnd = lfIndex\n }\n\n if (lineEnd === -1) {\n break\n }\n\n parseLine(chunk, searchIndex, lineEnd)\n searchIndex = lineEnd + 1\n // If we just consumed a `\\r` and the next char is `\\n`, skip it so the\n // pair is treated as a single terminator rather than an empty line.\n if (chunk.charCodeAt(searchIndex - 1) === CR && chunk.charCodeAt(searchIndex) === LF) {\n searchIndex++\n }\n }\n\n return chunk.slice(searchIndex)\n }\n\n function parseLine(chunk: string, start: number, end: number) {\n if (start === end) {\n dispatchEvent()\n return\n }\n\n const firstCharCode = chunk.charCodeAt(start)\n\n if (isDataPrefix(chunk, start, firstCharCode)) {\n // 'data:'.length === 5, 'data: '.length === 6\n const valueStart = chunk.charCodeAt(start + 5) === SPACE ? start + 6 : start + 5\n const value = chunk.slice(valueStart, end)\n data = dataLines === 0 ? value : `${data}\\n${value}`\n dataLines++\n return\n }\n\n if (isEventPrefix(chunk, start, firstCharCode)) {\n // 'event:'.length === 6, 'event: '.length === 7\n eventType =\n chunk.slice(chunk.charCodeAt(start + 6) === SPACE ? start + 7 : start + 6, end) || undefined\n return\n }\n\n // Fast path for \"id:\" — 'i' = 105, 'd' = 100, ':' = 58\n if (\n firstCharCode === 105 &&\n chunk.charCodeAt(start + 1) === 100 &&\n chunk.charCodeAt(start + 2) === 58\n ) {\n // 'id:'.length === 3, 'id: '.length === 4\n const value = chunk.slice(chunk.charCodeAt(start + 3) === SPACE ? start + 4 : start + 3, end)\n id = value.includes('\\0') ? undefined : value\n return\n }\n\n // Comment line — ':' = 58\n if (firstCharCode === 58) {\n if (onComment) {\n const line = chunk.slice(start, end)\n // skip ':' (+1), or ': ' (+2) when a space follows\n onComment(line.slice(chunk.charCodeAt(start + 1) === SPACE ? 2 : 1))\n }\n return\n }\n\n const line = chunk.slice(start, end)\n const fieldSeparatorIndex = line.indexOf(':')\n if (fieldSeparatorIndex === -1) {\n processField(line, '', line)\n return\n }\n\n const field = line.slice(0, fieldSeparatorIndex)\n // skip ':' (+1), or ': ' (+2) when a space follows\n const offset = line.charCodeAt(fieldSeparatorIndex + 1) === SPACE ? 2 : 1\n const value = line.slice(fieldSeparatorIndex + offset)\n processField(field, value, 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 || undefined\n break\n case 'data':\n data = dataLines === 0 ? value : `${data}\\n${value}`\n dataLines++\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 if (dataLines > 0) {\n onEvent({\n id,\n event: eventType,\n data,\n })\n }\n\n id = undefined\n data = ''\n dataLines = 0\n eventType = undefined\n }\n\n function reset(options: {consume?: boolean} = {}) {\n if (options.consume && pendingFragments.length > 0) {\n const incompleteLine = pendingFragments.join('')\n parseLine(incompleteLine, 0, incompleteLine.length)\n }\n\n isFirstChunk = true\n id = undefined\n data = ''\n dataLines = 0\n eventType = undefined\n pendingFragments.length = 0\n pendingFragmentsLength = 0\n terminated = false\n }\n\n return {feed, reset}\n}\n\n/**\n * Checks if `chunk` starts with the literal `data:` at index `i`.\n *\n * Equivalent to `chunk.startsWith('data:', i)`, but benchmarks show this\n * hand-unrolled char-code comparison is ~20% faster on common event types.\n * The caller passes `firstCharCode` (the code at `i`) so it can be reused\n * across prefix checks.\n *\n * ASCII: 'd' = 100, 'a' = 97, 't' = 116, 'a' = 97, ':' = 58\n */\nfunction isDataPrefix(chunk: string, i: number, firstCharCode: number): boolean {\n return (\n firstCharCode === 100 &&\n chunk.charCodeAt(i + 1) === 97 &&\n chunk.charCodeAt(i + 2) === 116 &&\n chunk.charCodeAt(i + 3) === 97 &&\n chunk.charCodeAt(i + 4) === 58\n )\n}\n\n/**\n * Checks if `chunk` starts with the literal `event:` at index `i`.\n *\n * See {@link isDataPrefix} for why this is hand-unrolled rather than using\n * `String.prototype.startsWith`.\n *\n * ASCII: 'e' = 101, 'v' = 118, 'e' = 101, 'n' = 110, 't' = 116, ':' = 58\n */\nfunction isEventPrefix(chunk: string, i: number, firstCharCode: number): boolean {\n return (\n firstCharCode === 101 &&\n chunk.charCodeAt(i + 1) === 118 &&\n chunk.charCodeAt(i + 2) === 101 &&\n chunk.charCodeAt(i + 3) === 110 &&\n chunk.charCodeAt(i + 4) === 116 &&\n chunk.charCodeAt(i + 5) === 58\n )\n}\n"],"names":["trailing","value","line"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","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' | 'max-buffer-size-exceeded'\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 | undefined\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 | undefined\n\n /**\n * The line that caused the error, if available.\n */\n line?: string | undefined\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, ParserConfig} from './types.ts'\n\n// ASCII codes used in the hot parsing paths.\nconst LF = 10\nconst CR = 13\nconst SPACE = 32\n\n// oxlint-disable-next-line no-unused-vars\nfunction noop(_arg: unknown) {\n // intentional noop\n}\n\n/**\n * Creates a new EventSource parser.\n *\n * @param config - Parser configuration. Accepts callbacks (see {@link ParserCallbacks})\n * and options like `maxBufferSize` (see {@link ParserConfig}).\n *\n * @returns A new EventSource parser, with `feed` and `reset` methods.\n * @public\n */\nexport function createParser(config: ParserConfig): EventSourceParser {\n if (typeof config === 'function') {\n throw new TypeError(\n '`config` must be an object, got a function instead. Did you mean `createParser({onEvent: fn})`?',\n )\n }\n\n const {onEvent = noop, onError = noop, onRetry = noop, onComment, maxBufferSize} = config\n\n // Trailing bytes from prior `feed()` calls that did not yet form a complete line.\n // Stored as an array of fragments and only joined when a line terminator arrives.\n // Concatenating per-feed (`prefix + chunk`) is O(N²) when a single SSE line spans\n // many chunks (e.g. a large `data:` payload streamed in tiny slices, or an MCP-style\n // server that emits one giant content block). Buffering as fragments + joining once\n // makes the same workload linear.\n const pendingFragments: string[] = []\n\n // Running total of `pendingFragments` lengths, kept in sync with the array so the\n // `maxBufferSize` check doesn't have to walk the fragment list on every feed.\n let pendingFragmentsLength = 0\n\n let isFirstChunk = true\n let id: string | undefined\n let data = ''\n let dataLines = 0\n let eventType: string | undefined\n\n // Set after a `maxBufferSize` overflow. Once tripped, `feed()` throws until\n // `reset()` is called — see the comment on `maxBufferSize` in `ParserConfig`.\n let terminated = false\n\n /**\n * Feeds a chunk of the SSE stream to the parser. Any trailing bytes that do\n * not yet form a complete line are held back and prepended to the next chunk,\n * so callers can pass arbitrary slices of the stream without worrying about\n * line boundaries.\n *\n * Per the SSE spec, a UTF-8 BOM (0xEF 0xBB 0xBF) at the start of the very\n * first chunk is stripped before parsing.\n *\n * @see https://html.spec.whatwg.org/multipage/server-sent-events.html#parsing-an-event-stream\n */\n function feed(chunk: string) {\n if (terminated) {\n throw new Error(\n 'Cannot feed parser: it was terminated after exceeding the configured max buffer size. Call `reset()` to resume parsing.',\n )\n }\n\n if (isFirstChunk) {\n isFirstChunk = false\n // Match and strip UTF-8 BOM from the start of the stream, if present.\n // (Per the spec, this is only valid at the very start of the stream)\n if (\n chunk.charCodeAt(0) === 0xef &&\n chunk.charCodeAt(1) === 0xbb &&\n chunk.charCodeAt(2) === 0xbf\n ) {\n chunk = chunk.slice(3)\n }\n }\n\n // Hot path: no buffered prefix from a prior partial line. Hand the chunk\n // straight to `processLines`, exactly like the original implementation.\n // Zero new work in the common case (every chunk ends with `\\n\\n`).\n if (pendingFragments.length === 0) {\n const trailing = processLines(chunk)\n if (trailing !== '') {\n pendingFragments.push(trailing)\n pendingFragmentsLength = trailing.length\n }\n checkBufferSize()\n return\n }\n\n // We have a buffered prefix. If this chunk also has no terminator, append\n // to the buffer without concatenating — that's the O(N²) trap we're\n // avoiding (large single `data:` payload split across many tiny chunks).\n if (chunk.indexOf('\\n') === -1 && chunk.indexOf('\\r') === -1) {\n pendingFragments.push(chunk)\n pendingFragmentsLength += chunk.length\n checkBufferSize()\n return\n }\n\n // Terminator arrived. Join the accumulated fragments + this chunk once,\n // process, and buffer any new trailing partial line.\n pendingFragments.push(chunk)\n const input = pendingFragments.join('')\n pendingFragments.length = 0\n pendingFragmentsLength = 0\n const trailing = processLines(input)\n if (trailing !== '') {\n pendingFragments.push(trailing)\n pendingFragmentsLength = trailing.length\n }\n checkBufferSize()\n }\n\n function checkBufferSize() {\n if (maxBufferSize === undefined) return\n if (pendingFragmentsLength + data.length <= maxBufferSize) return\n\n terminated = true\n pendingFragments.length = 0\n pendingFragmentsLength = 0\n id = undefined\n data = ''\n dataLines = 0\n eventType = undefined\n onError(\n new ParseError(`Buffered data exceeded max buffer size of ${maxBufferSize} characters`, {\n type: 'max-buffer-size-exceeded',\n }),\n )\n }\n\n /**\n * Splits `chunk` into SSE lines and dispatches each to the appropriate handler.\n * Returns any trailing bytes that did not terminate with a line break, so the\n * caller can prepend them to the next chunk.\n *\n * The SSE spec permits three line terminators: `\\n`, `\\r`, and `\\r\\n`. Real-world\n * streams almost always use plain `\\n`, so we take a fast path when no `\\r` is\n * present in the chunk. The slow path is spec-correct but does more work per line.\n */\n function processLines(chunk: string): string {\n let searchIndex = 0\n\n // Fast path: LF-only chunk (the common case for typical SSE servers).\n // We can scan forward with a single `indexOf('\\n')` per line and inline\n // the hot-path branches for `data:` and `event:` without the CR bookkeeping\n // the slow path needs.\n if (chunk.indexOf('\\r') === -1) {\n let lfIndex = chunk.indexOf('\\n', searchIndex)\n while (lfIndex !== -1) {\n // Blank line: end-of-event marker. Dispatch the accumulated event (if any)\n // and reset the buffered fields. This is hoisted out of `parseLine` because\n // it's the single most common line shape after `data:` lines.\n if (searchIndex === lfIndex) {\n if (dataLines > 0) {\n onEvent({id, event: eventType, data})\n }\n id = undefined\n data = ''\n dataLines = 0\n eventType = undefined\n searchIndex = lfIndex + 1\n lfIndex = chunk.indexOf('\\n', searchIndex)\n continue\n }\n const firstCharCode = chunk.charCodeAt(searchIndex)\n if (isDataPrefix(chunk, searchIndex, firstCharCode)) {\n // `data:` line — append the value to the event's data buffer.\n // 'data:'.length === 5, 'data: '.length === 6\n const valueStart =\n chunk.charCodeAt(searchIndex + 5) === SPACE ? searchIndex + 6 : searchIndex + 5\n const value = chunk.slice(valueStart, lfIndex)\n // Fast path within a fast path: if this is the first data line AND the\n // next char is another LF (i.e. `data:foo\\n\\n`), dispatch immediately\n // without ever writing to the `data` buffer. This is the shape of a\n // typical single-line SSE event (ChatGPT-style streams, etc.) and is\n // hot enough to be worth the duplication.\n if (dataLines === 0 && chunk.charCodeAt(lfIndex + 1) === LF) {\n onEvent({id, event: eventType, data: value})\n id = undefined\n data = ''\n eventType = undefined\n searchIndex = lfIndex + 2\n lfIndex = chunk.indexOf('\\n', searchIndex)\n continue\n }\n // Multi-line data: concatenate with newline separator per spec.\n data = dataLines === 0 ? value : `${data}\\n${value}`\n dataLines++\n } else if (isEventPrefix(chunk, searchIndex, firstCharCode)) {\n // `event:` line — set the event type for the next dispatch. Per spec,\n // an empty value resets `event type` to its default (undefined here).\n // 'event:'.length === 6, 'event: '.length === 7\n eventType =\n chunk.slice(\n chunk.charCodeAt(searchIndex + 6) === SPACE ? searchIndex + 7 : searchIndex + 6,\n lfIndex,\n ) || undefined\n } else {\n // Everything else: `id:`, `retry:`, comment lines (`:` prefix), unknown\n // fields, or malformed lines. These are rarer and go through the full\n // per-line parser, which handles the SSE field grammar in detail.\n parseLine(chunk, searchIndex, lfIndex)\n }\n searchIndex = lfIndex + 1\n lfIndex = chunk.indexOf('\\n', searchIndex)\n }\n return chunk.slice(searchIndex)\n }\n\n // Slow path: the chunk contains at least one `\\r`, so lines may be terminated\n // by `\\r`, `\\n`, or `\\r\\n`. We locate the next terminator by looking at both\n // the nearest `\\r` and `\\n` and picking whichever comes first.\n while (searchIndex < chunk.length) {\n const crIndex = chunk.indexOf('\\r', searchIndex)\n const lfIndex = chunk.indexOf('\\n', searchIndex)\n\n let lineEnd = -1\n if (crIndex !== -1 && lfIndex !== -1) {\n lineEnd = crIndex < lfIndex ? crIndex : lfIndex\n } else if (crIndex !== -1) {\n // A trailing `\\r` at the very end of the chunk is ambiguous: it could be\n // a bare-CR terminator, or the first half of a `\\r\\n` whose `\\n` arrives\n // in the next chunk. Defer until we see more input.\n if (crIndex === chunk.length - 1) {\n lineEnd = -1\n } else {\n lineEnd = crIndex\n }\n } else if (lfIndex !== -1) {\n lineEnd = lfIndex\n }\n\n if (lineEnd === -1) {\n break\n }\n\n parseLine(chunk, searchIndex, lineEnd)\n searchIndex = lineEnd + 1\n // If we just consumed a `\\r` and the next char is `\\n`, skip it so the\n // pair is treated as a single terminator rather than an empty line.\n if (chunk.charCodeAt(searchIndex - 1) === CR && chunk.charCodeAt(searchIndex) === LF) {\n searchIndex++\n }\n }\n\n return chunk.slice(searchIndex)\n }\n\n function parseLine(chunk: string, start: number, end: number) {\n if (start === end) {\n dispatchEvent()\n return\n }\n\n const firstCharCode = chunk.charCodeAt(start)\n\n if (isDataPrefix(chunk, start, firstCharCode)) {\n // 'data:'.length === 5, 'data: '.length === 6\n const valueStart = chunk.charCodeAt(start + 5) === SPACE ? start + 6 : start + 5\n const value = chunk.slice(valueStart, end)\n data = dataLines === 0 ? value : `${data}\\n${value}`\n dataLines++\n return\n }\n\n if (isEventPrefix(chunk, start, firstCharCode)) {\n // 'event:'.length === 6, 'event: '.length === 7\n eventType =\n chunk.slice(chunk.charCodeAt(start + 6) === SPACE ? start + 7 : start + 6, end) || undefined\n return\n }\n\n // Fast path for \"id:\" — 'i' = 105, 'd' = 100, ':' = 58\n if (\n firstCharCode === 105 &&\n chunk.charCodeAt(start + 1) === 100 &&\n chunk.charCodeAt(start + 2) === 58\n ) {\n // 'id:'.length === 3, 'id: '.length === 4\n const value = chunk.slice(chunk.charCodeAt(start + 3) === SPACE ? start + 4 : start + 3, end)\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 if (!value.includes('\\0')) id = value\n return\n }\n\n // Comment line — ':' = 58\n if (firstCharCode === 58) {\n if (onComment) {\n const line = chunk.slice(start, end)\n // skip ':' (+1), or ': ' (+2) when a space follows\n onComment(line.slice(chunk.charCodeAt(start + 1) === SPACE ? 2 : 1))\n }\n return\n }\n\n const line = chunk.slice(start, end)\n const fieldSeparatorIndex = line.indexOf(':')\n if (fieldSeparatorIndex === -1) {\n processField(line, '', line)\n return\n }\n\n const field = line.slice(0, fieldSeparatorIndex)\n // skip ':' (+1), or ': ' (+2) when a space follows\n const offset = line.charCodeAt(fieldSeparatorIndex + 1) === SPACE ? 2 : 1\n const value = line.slice(fieldSeparatorIndex + offset)\n processField(field, value, 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 || undefined\n break\n case 'data':\n data = dataLines === 0 ? value : `${data}\\n${value}`\n dataLines++\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 if (!value.includes('\\0')) id = 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 if (dataLines > 0) {\n onEvent({\n id,\n event: eventType,\n data,\n })\n }\n\n id = undefined\n data = ''\n dataLines = 0\n eventType = undefined\n }\n\n function reset(options: {consume?: boolean} = {}) {\n if (options.consume && pendingFragments.length > 0) {\n const incompleteLine = pendingFragments.join('')\n parseLine(incompleteLine, 0, incompleteLine.length)\n }\n\n isFirstChunk = true\n id = undefined\n data = ''\n dataLines = 0\n eventType = undefined\n pendingFragments.length = 0\n pendingFragmentsLength = 0\n terminated = false\n }\n\n return {feed, reset}\n}\n\n/**\n * Checks if `chunk` starts with the literal `data:` at index `i`.\n *\n * Equivalent to `chunk.startsWith('data:', i)`, but benchmarks show this\n * hand-unrolled char-code comparison is ~20% faster on common event types.\n * The caller passes `firstCharCode` (the code at `i`) so it can be reused\n * across prefix checks.\n *\n * ASCII: 'd' = 100, 'a' = 97, 't' = 116, 'a' = 97, ':' = 58\n */\nfunction isDataPrefix(chunk: string, i: number, firstCharCode: number): boolean {\n return (\n firstCharCode === 100 &&\n chunk.charCodeAt(i + 1) === 97 &&\n chunk.charCodeAt(i + 2) === 116 &&\n chunk.charCodeAt(i + 3) === 97 &&\n chunk.charCodeAt(i + 4) === 58\n )\n}\n\n/**\n * Checks if `chunk` starts with the literal `event:` at index `i`.\n *\n * See {@link isDataPrefix} for why this is hand-unrolled rather than using\n * `String.prototype.startsWith`.\n *\n * ASCII: 'e' = 101, 'v' = 118, 'e' = 101, 'n' = 110, 't' = 116, ':' = 58\n */\nfunction isEventPrefix(chunk: string, i: number, firstCharCode: number): boolean {\n return (\n firstCharCode === 101 &&\n chunk.charCodeAt(i + 1) === 118 &&\n chunk.charCodeAt(i + 2) === 101 &&\n chunk.charCodeAt(i + 3) === 110 &&\n chunk.charCodeAt(i + 4) === 116 &&\n chunk.charCodeAt(i + 5) === 58\n )\n}\n"],"names":["trailing","value","line"],"mappings":";;AAWO,MAAM,mBAAmB,MAAM;AAAA,EAqBpC,YACE,SACA,SACA;AACA,UAAM,OAAO,GACb,KAAK,OAAO,cACZ,KAAK,OAAO,QAAQ,MACpB,KAAK,QAAQ,QAAQ,OACrB,KAAK,QAAQ,QAAQ,OACrB,KAAK,OAAO,QAAQ;AAAA,EACtB;AACF;ACnCA,MAAM,KAAK,IACL,KAAK,IACL,QAAQ;AAGd,SAAS,KAAK,MAAe;AAE7B;AAWO,SAAS,aAAa,QAAyC;AACpE,MAAI,OAAO,UAAW;AACpB,UAAM,IAAI;AAAA,MACR;AAAA,IAAA;AAIJ,QAAM,EAAC,UAAU,MAAM,UAAU,MAAM,UAAU,MAAM,WAAW,cAAA,IAAiB,QAQ7E,mBAA6B,CAAA;AAInC,MAAI,yBAAyB,GAEzB,eAAe,IACf,IACA,OAAO,IACP,YAAY,GACZ,WAIA,aAAa;AAajB,WAAS,KAAK,OAAe;AAC3B,QAAI;AACF,YAAM,IAAI;AAAA,QACR;AAAA,MAAA;AAoBJ,QAhBI,iBACF,eAAe,IAIb,MAAM,WAAW,CAAC,MAAM,OACxB,MAAM,WAAW,CAAC,MAAM,OACxB,MAAM,WAAW,CAAC,MAAM,QAExB,QAAQ,MAAM,MAAM,CAAC,KAOrB,iBAAiB,WAAW,GAAG;AACjC,YAAMA,YAAW,aAAa,KAAK;AAC/BA,oBAAa,OACf,iBAAiB,KAAKA,SAAQ,GAC9B,yBAAyBA,UAAS,SAEpC,gBAAA;AACA;AAAA,IACF;AAKA,QAAI,MAAM,QAAQ;AAAA,CAAI,MAAM,MAAM,MAAM,QAAQ,IAAI,MAAM,IAAI;AAC5D,uBAAiB,KAAK,KAAK,GAC3B,0BAA0B,MAAM,QAChC,gBAAA;AACA;AAAA,IACF;AAIA,qBAAiB,KAAK,KAAK;AAC3B,UAAM,QAAQ,iBAAiB,KAAK,EAAE;AACtC,qBAAiB,SAAS,GAC1B,yBAAyB;AACzB,UAAM,WAAW,aAAa,KAAK;AAC/B,iBAAa,OACf,iBAAiB,KAAK,QAAQ,GAC9B,yBAAyB,SAAS,SAEpC,gBAAA;AAAA,EACF;AAEA,WAAS,kBAAkB;AACrB,sBAAkB,WAClB,yBAAyB,KAAK,UAAU,kBAE5C,aAAa,IACb,iBAAiB,SAAS,GAC1B,yBAAyB,GACzB,KAAK,QACL,OAAO,IACP,YAAY,GACZ,YAAY,QACZ;AAAA,MACE,IAAI,WAAW,6CAA6C,aAAa,eAAe;AAAA,QACtF,MAAM;AAAA,MAAA,CACP;AAAA,IAAA;AAAA,EAEL;AAWA,WAAS,aAAa,OAAuB;AAC3C,QAAI,cAAc;AAMlB,QAAI,MAAM,QAAQ,IAAI,MAAM,IAAI;AAC9B,UAAI,UAAU,MAAM,QAAQ;AAAA,GAAM,WAAW;AAC7C,aAAO,YAAY,MAAI;AAIrB,YAAI,gBAAgB,SAAS;AACvB,sBAAY,KACd,QAAQ,EAAC,IAAI,OAAO,WAAW,KAAA,CAAK,GAEtC,KAAK,QACL,OAAO,IACP,YAAY,GACZ,YAAY,QACZ,cAAc,UAAU,GACxB,UAAU,MAAM,QAAQ;AAAA,GAAM,WAAW;AACzC;AAAA,QACF;AACA,cAAM,gBAAgB,MAAM,WAAW,WAAW;AAClD,YAAI,aAAa,OAAO,aAAa,aAAa,GAAG;AAGnD,gBAAM,aACJ,MAAM,WAAW,cAAc,CAAC,MAAM,QAAQ,cAAc,IAAI,cAAc,GAC1E,QAAQ,MAAM,MAAM,YAAY,OAAO;AAM7C,cAAI,cAAc,KAAK,MAAM,WAAW,UAAU,CAAC,MAAM,IAAI;AAC3D,oBAAQ,EAAC,IAAI,OAAO,WAAW,MAAM,MAAA,CAAM,GAC3C,KAAK,QACL,OAAO,IACP,YAAY,QACZ,cAAc,UAAU,GACxB,UAAU,MAAM,QAAQ;AAAA,GAAM,WAAW;AACzC;AAAA,UACF;AAEA,iBAAO,cAAc,IAAI,QAAQ,GAAG,IAAI;AAAA,EAAK,KAAK,IAClD;AAAA,QACF,MAAW,eAAc,OAAO,aAAa,aAAa,IAIxD,YACE,MAAM;AAAA,UACJ,MAAM,WAAW,cAAc,CAAC,MAAM,QAAQ,cAAc,IAAI,cAAc;AAAA,UAC9E;AAAA,QAAA,KACG,SAKP,UAAU,OAAO,aAAa,OAAO;AAEvC,sBAAc,UAAU,GACxB,UAAU,MAAM,QAAQ;AAAA,GAAM,WAAW;AAAA,MAC3C;AACA,aAAO,MAAM,MAAM,WAAW;AAAA,IAChC;AAKA,WAAO,cAAc,MAAM,UAAQ;AACjC,YAAM,UAAU,MAAM,QAAQ,MAAM,WAAW,GACzC,UAAU,MAAM,QAAQ;AAAA,GAAM,WAAW;AAE/C,UAAI,UAAU;AAgBd,UAfI,YAAY,MAAM,YAAY,KAChC,UAAU,UAAU,UAAU,UAAU,UAC/B,YAAY,KAIjB,YAAY,MAAM,SAAS,IAC7B,UAAU,KAEV,UAAU,UAEH,YAAY,OACrB,UAAU,UAGR,YAAY;AACd;AAGF,gBAAU,OAAO,aAAa,OAAO,GACrC,cAAc,UAAU,GAGpB,MAAM,WAAW,cAAc,CAAC,MAAM,MAAM,MAAM,WAAW,WAAW,MAAM,MAChF;AAAA,IAEJ;AAEA,WAAO,MAAM,MAAM,WAAW;AAAA,EAChC;AAEA,WAAS,UAAU,OAAe,OAAe,KAAa;AAC5D,QAAI,UAAU,KAAK;AACjB,oBAAA;AACA;AAAA,IACF;AAEA,UAAM,gBAAgB,MAAM,WAAW,KAAK;AAE5C,QAAI,aAAa,OAAO,OAAO,aAAa,GAAG;AAE7C,YAAM,aAAa,MAAM,WAAW,QAAQ,CAAC,MAAM,QAAQ,QAAQ,IAAI,QAAQ,GACzEC,SAAQ,MAAM,MAAM,YAAY,GAAG;AACzC,aAAO,cAAc,IAAIA,SAAQ,GAAG,IAAI;AAAA,EAAKA,MAAK,IAClD;AACA;AAAA,IACF;AAEA,QAAI,cAAc,OAAO,OAAO,aAAa,GAAG;AAE9C,kBACE,MAAM,MAAM,MAAM,WAAW,QAAQ,CAAC,MAAM,QAAQ,QAAQ,IAAI,QAAQ,GAAG,GAAG,KAAK;AACrF;AAAA,IACF;AAGA,QACE,kBAAkB,OAClB,MAAM,WAAW,QAAQ,CAAC,MAAM,OAChC,MAAM,WAAW,QAAQ,CAAC,MAAM,IAChC;AAEA,YAAMA,SAAQ,MAAM,MAAM,MAAM,WAAW,QAAQ,CAAC,MAAM,QAAQ,QAAQ,IAAI,QAAQ,GAAG,GAAG;AAGvFA,aAAM,SAAS,IAAI,MAAG,KAAKA;AAChC;AAAA,IACF;AAGA,QAAI,kBAAkB,IAAI;AACxB,UAAI,WAAW;AACb,cAAMC,QAAO,MAAM,MAAM,OAAO,GAAG;AAEnC,kBAAUA,MAAK,MAAM,MAAM,WAAW,QAAQ,CAAC,MAAM,QAAQ,IAAI,CAAC,CAAC;AAAA,MACrE;AACA;AAAA,IACF;AAEA,UAAM,OAAO,MAAM,MAAM,OAAO,GAAG,GAC7B,sBAAsB,KAAK,QAAQ,GAAG;AAC5C,QAAI,wBAAwB,IAAI;AAC9B,mBAAa,MAAM,IAAI,IAAI;AAC3B;AAAA,IACF;AAEA,UAAM,QAAQ,KAAK,MAAM,GAAG,mBAAmB,GAEzC,SAAS,KAAK,WAAW,sBAAsB,CAAC,MAAM,QAAQ,IAAI,GAClE,QAAQ,KAAK,MAAM,sBAAsB,MAAM;AACrD,iBAAa,OAAO,OAAO,IAAI;AAAA,EACjC;AAEA,WAAS,aAAa,OAAe,OAAe,MAAc;AAEhE,YAAQ,OAAA;AAAA,MACN,KAAK;AAEH,oBAAY,SAAS;AACrB;AAAA,MACF,KAAK;AACH,eAAO,cAAc,IAAI,QAAQ,GAAG,IAAI;AAAA,EAAK,KAAK,IAClD;AACA;AAAA,MACF,KAAK;AAGE,cAAM,SAAS,IAAI,MAAG,KAAK;AAChC;AAAA,MACF,KAAK;AAIC,gBAAQ,KAAK,KAAK,IACpB,QAAQ,SAAS,OAAO,EAAE,CAAC,IAE3B;AAAA,UACE,IAAI,WAAW,6BAA6B,KAAK,KAAK;AAAA,YACpD,MAAM;AAAA,YACN;AAAA,YACA;AAAA,UAAA,CACD;AAAA,QAAA;AAGL;AAAA,MACF;AAEE;AAAA,UACE,IAAI;AAAA,YACF,kBAAkB,MAAM,SAAS,KAAK,GAAG,MAAM,MAAM,GAAG,EAAE,CAAC,WAAM,KAAK;AAAA,YACtE,EAAC,MAAM,iBAAiB,OAAO,OAAO,KAAA;AAAA,UAAI;AAAA,QAC5C;AAEF;AAAA,IAAA;AAAA,EAEN;AAEA,WAAS,gBAAgB;AACnB,gBAAY,KACd,QAAQ;AAAA,MACN;AAAA,MACA,OAAO;AAAA,MACP;AAAA,IAAA,CACD,GAGH,KAAK,QACL,OAAO,IACP,YAAY,GACZ,YAAY;AAAA,EACd;AAEA,WAAS,MAAM,UAA+B,IAAI;AAChD,QAAI,QAAQ,WAAW,iBAAiB,SAAS,GAAG;AAClD,YAAM,iBAAiB,iBAAiB,KAAK,EAAE;AAC/C,gBAAU,gBAAgB,GAAG,eAAe,MAAM;AAAA,IACpD;AAEA,mBAAe,IACf,KAAK,QACL,OAAO,IACP,YAAY,GACZ,YAAY,QACZ,iBAAiB,SAAS,GAC1B,yBAAyB,GACzB,aAAa;AAAA,EACf;AAEA,SAAO,EAAC,MAAM,MAAA;AAChB;AAYA,SAAS,aAAa,OAAe,GAAW,eAAgC;AAC9E,SACE,kBAAkB,OAClB,MAAM,WAAW,IAAI,CAAC,MAAM,MAC5B,MAAM,WAAW,IAAI,CAAC,MAAM,OAC5B,MAAM,WAAW,IAAI,CAAC,MAAM,MAC5B,MAAM,WAAW,IAAI,CAAC,MAAM;AAEhC;AAUA,SAAS,cAAc,OAAe,GAAW,eAAgC;AAC/E,SACE,kBAAkB,OAClB,MAAM,WAAW,IAAI,CAAC,MAAM,OAC5B,MAAM,WAAW,IAAI,CAAC,MAAM,OAC5B,MAAM,WAAW,IAAI,CAAC,MAAM,OAC5B,MAAM,WAAW,IAAI,CAAC,MAAM,OAC5B,MAAM,WAAW,IAAI,CAAC,MAAM;AAEhC;;;"}
@@ -99,7 +99,7 @@ ${value2}`, dataLines++;
99
99
  }
100
100
  if (firstCharCode === 105 && chunk.charCodeAt(start + 1) === 100 && chunk.charCodeAt(start + 2) === 58) {
101
101
  const value2 = chunk.slice(chunk.charCodeAt(start + 3) === SPACE ? start + 4 : start + 3, end);
102
- id = value2.includes("\0") ? void 0 : value2;
102
+ value2.includes("\0") || (id = value2);
103
103
  return;
104
104
  }
105
105
  if (firstCharCode === 58) {
@@ -127,7 +127,7 @@ ${value2}`, dataLines++;
127
127
  ${value}`, dataLines++;
128
128
  break;
129
129
  case "id":
130
- id = value.includes("\0") ? void 0 : value;
130
+ value.includes("\0") || (id = value);
131
131
  break;
132
132
  case "retry":
133
133
  /^\d+$/.test(value) ? onRetry(parseInt(value, 10)) : onError(
@@ -1 +1 @@
1
- {"version":3,"file":"index.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' | 'max-buffer-size-exceeded'\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 | undefined\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 | undefined\n\n /**\n * The line that caused the error, if available.\n */\n line?: string | undefined\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, ParserConfig} from './types.ts'\n\n// ASCII codes used in the hot parsing paths.\nconst LF = 10\nconst CR = 13\nconst SPACE = 32\n\n// oxlint-disable-next-line no-unused-vars\nfunction noop(_arg: unknown) {\n // intentional noop\n}\n\n/**\n * Creates a new EventSource parser.\n *\n * @param config - Parser configuration. Accepts callbacks (see {@link ParserCallbacks})\n * and options like `maxBufferSize` (see {@link ParserConfig}).\n *\n * @returns A new EventSource parser, with `feed` and `reset` methods.\n * @public\n */\nexport function createParser(config: ParserConfig): EventSourceParser {\n if (typeof config === 'function') {\n throw new TypeError(\n '`config` must be an object, got a function instead. Did you mean `createParser({onEvent: fn})`?',\n )\n }\n\n const {onEvent = noop, onError = noop, onRetry = noop, onComment, maxBufferSize} = config\n\n // Trailing bytes from prior `feed()` calls that did not yet form a complete line.\n // Stored as an array of fragments and only joined when a line terminator arrives.\n // Concatenating per-feed (`prefix + chunk`) is O(N²) when a single SSE line spans\n // many chunks (e.g. a large `data:` payload streamed in tiny slices, or an MCP-style\n // server that emits one giant content block). Buffering as fragments + joining once\n // makes the same workload linear.\n const pendingFragments: string[] = []\n\n // Running total of `pendingFragments` lengths, kept in sync with the array so the\n // `maxBufferSize` check doesn't have to walk the fragment list on every feed.\n let pendingFragmentsLength = 0\n\n let isFirstChunk = true\n let id: string | undefined\n let data = ''\n let dataLines = 0\n let eventType: string | undefined\n\n // Set after a `maxBufferSize` overflow. Once tripped, `feed()` throws until\n // `reset()` is called — see the comment on `maxBufferSize` in `ParserConfig`.\n let terminated = false\n\n /**\n * Feeds a chunk of the SSE stream to the parser. Any trailing bytes that do\n * not yet form a complete line are held back and prepended to the next chunk,\n * so callers can pass arbitrary slices of the stream without worrying about\n * line boundaries.\n *\n * Per the SSE spec, a UTF-8 BOM (0xEF 0xBB 0xBF) at the start of the very\n * first chunk is stripped before parsing.\n *\n * @see https://html.spec.whatwg.org/multipage/server-sent-events.html#parsing-an-event-stream\n */\n function feed(chunk: string) {\n if (terminated) {\n throw new Error(\n 'Cannot feed parser: it was terminated after exceeding the configured max buffer size. Call `reset()` to resume parsing.',\n )\n }\n\n if (isFirstChunk) {\n isFirstChunk = false\n // Match and strip UTF-8 BOM from the start of the stream, if present.\n // (Per the spec, this is only valid at the very start of the stream)\n if (\n chunk.charCodeAt(0) === 0xef &&\n chunk.charCodeAt(1) === 0xbb &&\n chunk.charCodeAt(2) === 0xbf\n ) {\n chunk = chunk.slice(3)\n }\n }\n\n // Hot path: no buffered prefix from a prior partial line. Hand the chunk\n // straight to `processLines`, exactly like the original implementation.\n // Zero new work in the common case (every chunk ends with `\\n\\n`).\n if (pendingFragments.length === 0) {\n const trailing = processLines(chunk)\n if (trailing !== '') {\n pendingFragments.push(trailing)\n pendingFragmentsLength = trailing.length\n }\n checkBufferSize()\n return\n }\n\n // We have a buffered prefix. If this chunk also has no terminator, append\n // to the buffer without concatenating — that's the O(N²) trap we're\n // avoiding (large single `data:` payload split across many tiny chunks).\n if (chunk.indexOf('\\n') === -1 && chunk.indexOf('\\r') === -1) {\n pendingFragments.push(chunk)\n pendingFragmentsLength += chunk.length\n checkBufferSize()\n return\n }\n\n // Terminator arrived. Join the accumulated fragments + this chunk once,\n // process, and buffer any new trailing partial line.\n pendingFragments.push(chunk)\n const input = pendingFragments.join('')\n pendingFragments.length = 0\n pendingFragmentsLength = 0\n const trailing = processLines(input)\n if (trailing !== '') {\n pendingFragments.push(trailing)\n pendingFragmentsLength = trailing.length\n }\n checkBufferSize()\n }\n\n function checkBufferSize() {\n if (maxBufferSize === undefined) return\n if (pendingFragmentsLength + data.length <= maxBufferSize) return\n\n terminated = true\n pendingFragments.length = 0\n pendingFragmentsLength = 0\n id = undefined\n data = ''\n dataLines = 0\n eventType = undefined\n onError(\n new ParseError(`Buffered data exceeded max buffer size of ${maxBufferSize} characters`, {\n type: 'max-buffer-size-exceeded',\n }),\n )\n }\n\n /**\n * Splits `chunk` into SSE lines and dispatches each to the appropriate handler.\n * Returns any trailing bytes that did not terminate with a line break, so the\n * caller can prepend them to the next chunk.\n *\n * The SSE spec permits three line terminators: `\\n`, `\\r`, and `\\r\\n`. Real-world\n * streams almost always use plain `\\n`, so we take a fast path when no `\\r` is\n * present in the chunk. The slow path is spec-correct but does more work per line.\n */\n function processLines(chunk: string): string {\n let searchIndex = 0\n\n // Fast path: LF-only chunk (the common case for typical SSE servers).\n // We can scan forward with a single `indexOf('\\n')` per line and inline\n // the hot-path branches for `data:` and `event:` without the CR bookkeeping\n // the slow path needs.\n if (chunk.indexOf('\\r') === -1) {\n let lfIndex = chunk.indexOf('\\n', searchIndex)\n while (lfIndex !== -1) {\n // Blank line: end-of-event marker. Dispatch the accumulated event (if any)\n // and reset the buffered fields. This is hoisted out of `parseLine` because\n // it's the single most common line shape after `data:` lines.\n if (searchIndex === lfIndex) {\n if (dataLines > 0) {\n onEvent({id, event: eventType, data})\n }\n id = undefined\n data = ''\n dataLines = 0\n eventType = undefined\n searchIndex = lfIndex + 1\n lfIndex = chunk.indexOf('\\n', searchIndex)\n continue\n }\n const firstCharCode = chunk.charCodeAt(searchIndex)\n if (isDataPrefix(chunk, searchIndex, firstCharCode)) {\n // `data:` line — append the value to the event's data buffer.\n // 'data:'.length === 5, 'data: '.length === 6\n const valueStart =\n chunk.charCodeAt(searchIndex + 5) === SPACE ? searchIndex + 6 : searchIndex + 5\n const value = chunk.slice(valueStart, lfIndex)\n // Fast path within a fast path: if this is the first data line AND the\n // next char is another LF (i.e. `data:foo\\n\\n`), dispatch immediately\n // without ever writing to the `data` buffer. This is the shape of a\n // typical single-line SSE event (ChatGPT-style streams, etc.) and is\n // hot enough to be worth the duplication.\n if (dataLines === 0 && chunk.charCodeAt(lfIndex + 1) === LF) {\n onEvent({id, event: eventType, data: value})\n id = undefined\n data = ''\n eventType = undefined\n searchIndex = lfIndex + 2\n lfIndex = chunk.indexOf('\\n', searchIndex)\n continue\n }\n // Multi-line data: concatenate with newline separator per spec.\n data = dataLines === 0 ? value : `${data}\\n${value}`\n dataLines++\n } else if (isEventPrefix(chunk, searchIndex, firstCharCode)) {\n // `event:` line — set the event type for the next dispatch. Per spec,\n // an empty value resets `event type` to its default (undefined here).\n // 'event:'.length === 6, 'event: '.length === 7\n eventType =\n chunk.slice(\n chunk.charCodeAt(searchIndex + 6) === SPACE ? searchIndex + 7 : searchIndex + 6,\n lfIndex,\n ) || undefined\n } else {\n // Everything else: `id:`, `retry:`, comment lines (`:` prefix), unknown\n // fields, or malformed lines. These are rarer and go through the full\n // per-line parser, which handles the SSE field grammar in detail.\n parseLine(chunk, searchIndex, lfIndex)\n }\n searchIndex = lfIndex + 1\n lfIndex = chunk.indexOf('\\n', searchIndex)\n }\n return chunk.slice(searchIndex)\n }\n\n // Slow path: the chunk contains at least one `\\r`, so lines may be terminated\n // by `\\r`, `\\n`, or `\\r\\n`. We locate the next terminator by looking at both\n // the nearest `\\r` and `\\n` and picking whichever comes first.\n while (searchIndex < chunk.length) {\n const crIndex = chunk.indexOf('\\r', searchIndex)\n const lfIndex = chunk.indexOf('\\n', searchIndex)\n\n let lineEnd = -1\n if (crIndex !== -1 && lfIndex !== -1) {\n lineEnd = crIndex < lfIndex ? crIndex : lfIndex\n } else if (crIndex !== -1) {\n // A trailing `\\r` at the very end of the chunk is ambiguous: it could be\n // a bare-CR terminator, or the first half of a `\\r\\n` whose `\\n` arrives\n // in the next chunk. Defer until we see more input.\n if (crIndex === chunk.length - 1) {\n lineEnd = -1\n } else {\n lineEnd = crIndex\n }\n } else if (lfIndex !== -1) {\n lineEnd = lfIndex\n }\n\n if (lineEnd === -1) {\n break\n }\n\n parseLine(chunk, searchIndex, lineEnd)\n searchIndex = lineEnd + 1\n // If we just consumed a `\\r` and the next char is `\\n`, skip it so the\n // pair is treated as a single terminator rather than an empty line.\n if (chunk.charCodeAt(searchIndex - 1) === CR && chunk.charCodeAt(searchIndex) === LF) {\n searchIndex++\n }\n }\n\n return chunk.slice(searchIndex)\n }\n\n function parseLine(chunk: string, start: number, end: number) {\n if (start === end) {\n dispatchEvent()\n return\n }\n\n const firstCharCode = chunk.charCodeAt(start)\n\n if (isDataPrefix(chunk, start, firstCharCode)) {\n // 'data:'.length === 5, 'data: '.length === 6\n const valueStart = chunk.charCodeAt(start + 5) === SPACE ? start + 6 : start + 5\n const value = chunk.slice(valueStart, end)\n data = dataLines === 0 ? value : `${data}\\n${value}`\n dataLines++\n return\n }\n\n if (isEventPrefix(chunk, start, firstCharCode)) {\n // 'event:'.length === 6, 'event: '.length === 7\n eventType =\n chunk.slice(chunk.charCodeAt(start + 6) === SPACE ? start + 7 : start + 6, end) || undefined\n return\n }\n\n // Fast path for \"id:\" — 'i' = 105, 'd' = 100, ':' = 58\n if (\n firstCharCode === 105 &&\n chunk.charCodeAt(start + 1) === 100 &&\n chunk.charCodeAt(start + 2) === 58\n ) {\n // 'id:'.length === 3, 'id: '.length === 4\n const value = chunk.slice(chunk.charCodeAt(start + 3) === SPACE ? start + 4 : start + 3, end)\n id = value.includes('\\0') ? undefined : value\n return\n }\n\n // Comment line — ':' = 58\n if (firstCharCode === 58) {\n if (onComment) {\n const line = chunk.slice(start, end)\n // skip ':' (+1), or ': ' (+2) when a space follows\n onComment(line.slice(chunk.charCodeAt(start + 1) === SPACE ? 2 : 1))\n }\n return\n }\n\n const line = chunk.slice(start, end)\n const fieldSeparatorIndex = line.indexOf(':')\n if (fieldSeparatorIndex === -1) {\n processField(line, '', line)\n return\n }\n\n const field = line.slice(0, fieldSeparatorIndex)\n // skip ':' (+1), or ': ' (+2) when a space follows\n const offset = line.charCodeAt(fieldSeparatorIndex + 1) === SPACE ? 2 : 1\n const value = line.slice(fieldSeparatorIndex + offset)\n processField(field, value, 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 || undefined\n break\n case 'data':\n data = dataLines === 0 ? value : `${data}\\n${value}`\n dataLines++\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 if (dataLines > 0) {\n onEvent({\n id,\n event: eventType,\n data,\n })\n }\n\n id = undefined\n data = ''\n dataLines = 0\n eventType = undefined\n }\n\n function reset(options: {consume?: boolean} = {}) {\n if (options.consume && pendingFragments.length > 0) {\n const incompleteLine = pendingFragments.join('')\n parseLine(incompleteLine, 0, incompleteLine.length)\n }\n\n isFirstChunk = true\n id = undefined\n data = ''\n dataLines = 0\n eventType = undefined\n pendingFragments.length = 0\n pendingFragmentsLength = 0\n terminated = false\n }\n\n return {feed, reset}\n}\n\n/**\n * Checks if `chunk` starts with the literal `data:` at index `i`.\n *\n * Equivalent to `chunk.startsWith('data:', i)`, but benchmarks show this\n * hand-unrolled char-code comparison is ~20% faster on common event types.\n * The caller passes `firstCharCode` (the code at `i`) so it can be reused\n * across prefix checks.\n *\n * ASCII: 'd' = 100, 'a' = 97, 't' = 116, 'a' = 97, ':' = 58\n */\nfunction isDataPrefix(chunk: string, i: number, firstCharCode: number): boolean {\n return (\n firstCharCode === 100 &&\n chunk.charCodeAt(i + 1) === 97 &&\n chunk.charCodeAt(i + 2) === 116 &&\n chunk.charCodeAt(i + 3) === 97 &&\n chunk.charCodeAt(i + 4) === 58\n )\n}\n\n/**\n * Checks if `chunk` starts with the literal `event:` at index `i`.\n *\n * See {@link isDataPrefix} for why this is hand-unrolled rather than using\n * `String.prototype.startsWith`.\n *\n * ASCII: 'e' = 101, 'v' = 118, 'e' = 101, 'n' = 110, 't' = 116, ':' = 58\n */\nfunction isEventPrefix(chunk: string, i: number, firstCharCode: number): boolean {\n return (\n firstCharCode === 101 &&\n chunk.charCodeAt(i + 1) === 118 &&\n chunk.charCodeAt(i + 2) === 101 &&\n chunk.charCodeAt(i + 3) === 110 &&\n chunk.charCodeAt(i + 4) === 116 &&\n chunk.charCodeAt(i + 5) === 58\n )\n}\n"],"names":["trailing","value","line"],"mappings":"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"}
1
+ {"version":3,"file":"index.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' | 'max-buffer-size-exceeded'\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 | undefined\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 | undefined\n\n /**\n * The line that caused the error, if available.\n */\n line?: string | undefined\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, ParserConfig} from './types.ts'\n\n// ASCII codes used in the hot parsing paths.\nconst LF = 10\nconst CR = 13\nconst SPACE = 32\n\n// oxlint-disable-next-line no-unused-vars\nfunction noop(_arg: unknown) {\n // intentional noop\n}\n\n/**\n * Creates a new EventSource parser.\n *\n * @param config - Parser configuration. Accepts callbacks (see {@link ParserCallbacks})\n * and options like `maxBufferSize` (see {@link ParserConfig}).\n *\n * @returns A new EventSource parser, with `feed` and `reset` methods.\n * @public\n */\nexport function createParser(config: ParserConfig): EventSourceParser {\n if (typeof config === 'function') {\n throw new TypeError(\n '`config` must be an object, got a function instead. Did you mean `createParser({onEvent: fn})`?',\n )\n }\n\n const {onEvent = noop, onError = noop, onRetry = noop, onComment, maxBufferSize} = config\n\n // Trailing bytes from prior `feed()` calls that did not yet form a complete line.\n // Stored as an array of fragments and only joined when a line terminator arrives.\n // Concatenating per-feed (`prefix + chunk`) is O(N²) when a single SSE line spans\n // many chunks (e.g. a large `data:` payload streamed in tiny slices, or an MCP-style\n // server that emits one giant content block). Buffering as fragments + joining once\n // makes the same workload linear.\n const pendingFragments: string[] = []\n\n // Running total of `pendingFragments` lengths, kept in sync with the array so the\n // `maxBufferSize` check doesn't have to walk the fragment list on every feed.\n let pendingFragmentsLength = 0\n\n let isFirstChunk = true\n let id: string | undefined\n let data = ''\n let dataLines = 0\n let eventType: string | undefined\n\n // Set after a `maxBufferSize` overflow. Once tripped, `feed()` throws until\n // `reset()` is called — see the comment on `maxBufferSize` in `ParserConfig`.\n let terminated = false\n\n /**\n * Feeds a chunk of the SSE stream to the parser. Any trailing bytes that do\n * not yet form a complete line are held back and prepended to the next chunk,\n * so callers can pass arbitrary slices of the stream without worrying about\n * line boundaries.\n *\n * Per the SSE spec, a UTF-8 BOM (0xEF 0xBB 0xBF) at the start of the very\n * first chunk is stripped before parsing.\n *\n * @see https://html.spec.whatwg.org/multipage/server-sent-events.html#parsing-an-event-stream\n */\n function feed(chunk: string) {\n if (terminated) {\n throw new Error(\n 'Cannot feed parser: it was terminated after exceeding the configured max buffer size. Call `reset()` to resume parsing.',\n )\n }\n\n if (isFirstChunk) {\n isFirstChunk = false\n // Match and strip UTF-8 BOM from the start of the stream, if present.\n // (Per the spec, this is only valid at the very start of the stream)\n if (\n chunk.charCodeAt(0) === 0xef &&\n chunk.charCodeAt(1) === 0xbb &&\n chunk.charCodeAt(2) === 0xbf\n ) {\n chunk = chunk.slice(3)\n }\n }\n\n // Hot path: no buffered prefix from a prior partial line. Hand the chunk\n // straight to `processLines`, exactly like the original implementation.\n // Zero new work in the common case (every chunk ends with `\\n\\n`).\n if (pendingFragments.length === 0) {\n const trailing = processLines(chunk)\n if (trailing !== '') {\n pendingFragments.push(trailing)\n pendingFragmentsLength = trailing.length\n }\n checkBufferSize()\n return\n }\n\n // We have a buffered prefix. If this chunk also has no terminator, append\n // to the buffer without concatenating — that's the O(N²) trap we're\n // avoiding (large single `data:` payload split across many tiny chunks).\n if (chunk.indexOf('\\n') === -1 && chunk.indexOf('\\r') === -1) {\n pendingFragments.push(chunk)\n pendingFragmentsLength += chunk.length\n checkBufferSize()\n return\n }\n\n // Terminator arrived. Join the accumulated fragments + this chunk once,\n // process, and buffer any new trailing partial line.\n pendingFragments.push(chunk)\n const input = pendingFragments.join('')\n pendingFragments.length = 0\n pendingFragmentsLength = 0\n const trailing = processLines(input)\n if (trailing !== '') {\n pendingFragments.push(trailing)\n pendingFragmentsLength = trailing.length\n }\n checkBufferSize()\n }\n\n function checkBufferSize() {\n if (maxBufferSize === undefined) return\n if (pendingFragmentsLength + data.length <= maxBufferSize) return\n\n terminated = true\n pendingFragments.length = 0\n pendingFragmentsLength = 0\n id = undefined\n data = ''\n dataLines = 0\n eventType = undefined\n onError(\n new ParseError(`Buffered data exceeded max buffer size of ${maxBufferSize} characters`, {\n type: 'max-buffer-size-exceeded',\n }),\n )\n }\n\n /**\n * Splits `chunk` into SSE lines and dispatches each to the appropriate handler.\n * Returns any trailing bytes that did not terminate with a line break, so the\n * caller can prepend them to the next chunk.\n *\n * The SSE spec permits three line terminators: `\\n`, `\\r`, and `\\r\\n`. Real-world\n * streams almost always use plain `\\n`, so we take a fast path when no `\\r` is\n * present in the chunk. The slow path is spec-correct but does more work per line.\n */\n function processLines(chunk: string): string {\n let searchIndex = 0\n\n // Fast path: LF-only chunk (the common case for typical SSE servers).\n // We can scan forward with a single `indexOf('\\n')` per line and inline\n // the hot-path branches for `data:` and `event:` without the CR bookkeeping\n // the slow path needs.\n if (chunk.indexOf('\\r') === -1) {\n let lfIndex = chunk.indexOf('\\n', searchIndex)\n while (lfIndex !== -1) {\n // Blank line: end-of-event marker. Dispatch the accumulated event (if any)\n // and reset the buffered fields. This is hoisted out of `parseLine` because\n // it's the single most common line shape after `data:` lines.\n if (searchIndex === lfIndex) {\n if (dataLines > 0) {\n onEvent({id, event: eventType, data})\n }\n id = undefined\n data = ''\n dataLines = 0\n eventType = undefined\n searchIndex = lfIndex + 1\n lfIndex = chunk.indexOf('\\n', searchIndex)\n continue\n }\n const firstCharCode = chunk.charCodeAt(searchIndex)\n if (isDataPrefix(chunk, searchIndex, firstCharCode)) {\n // `data:` line — append the value to the event's data buffer.\n // 'data:'.length === 5, 'data: '.length === 6\n const valueStart =\n chunk.charCodeAt(searchIndex + 5) === SPACE ? searchIndex + 6 : searchIndex + 5\n const value = chunk.slice(valueStart, lfIndex)\n // Fast path within a fast path: if this is the first data line AND the\n // next char is another LF (i.e. `data:foo\\n\\n`), dispatch immediately\n // without ever writing to the `data` buffer. This is the shape of a\n // typical single-line SSE event (ChatGPT-style streams, etc.) and is\n // hot enough to be worth the duplication.\n if (dataLines === 0 && chunk.charCodeAt(lfIndex + 1) === LF) {\n onEvent({id, event: eventType, data: value})\n id = undefined\n data = ''\n eventType = undefined\n searchIndex = lfIndex + 2\n lfIndex = chunk.indexOf('\\n', searchIndex)\n continue\n }\n // Multi-line data: concatenate with newline separator per spec.\n data = dataLines === 0 ? value : `${data}\\n${value}`\n dataLines++\n } else if (isEventPrefix(chunk, searchIndex, firstCharCode)) {\n // `event:` line — set the event type for the next dispatch. Per spec,\n // an empty value resets `event type` to its default (undefined here).\n // 'event:'.length === 6, 'event: '.length === 7\n eventType =\n chunk.slice(\n chunk.charCodeAt(searchIndex + 6) === SPACE ? searchIndex + 7 : searchIndex + 6,\n lfIndex,\n ) || undefined\n } else {\n // Everything else: `id:`, `retry:`, comment lines (`:` prefix), unknown\n // fields, or malformed lines. These are rarer and go through the full\n // per-line parser, which handles the SSE field grammar in detail.\n parseLine(chunk, searchIndex, lfIndex)\n }\n searchIndex = lfIndex + 1\n lfIndex = chunk.indexOf('\\n', searchIndex)\n }\n return chunk.slice(searchIndex)\n }\n\n // Slow path: the chunk contains at least one `\\r`, so lines may be terminated\n // by `\\r`, `\\n`, or `\\r\\n`. We locate the next terminator by looking at both\n // the nearest `\\r` and `\\n` and picking whichever comes first.\n while (searchIndex < chunk.length) {\n const crIndex = chunk.indexOf('\\r', searchIndex)\n const lfIndex = chunk.indexOf('\\n', searchIndex)\n\n let lineEnd = -1\n if (crIndex !== -1 && lfIndex !== -1) {\n lineEnd = crIndex < lfIndex ? crIndex : lfIndex\n } else if (crIndex !== -1) {\n // A trailing `\\r` at the very end of the chunk is ambiguous: it could be\n // a bare-CR terminator, or the first half of a `\\r\\n` whose `\\n` arrives\n // in the next chunk. Defer until we see more input.\n if (crIndex === chunk.length - 1) {\n lineEnd = -1\n } else {\n lineEnd = crIndex\n }\n } else if (lfIndex !== -1) {\n lineEnd = lfIndex\n }\n\n if (lineEnd === -1) {\n break\n }\n\n parseLine(chunk, searchIndex, lineEnd)\n searchIndex = lineEnd + 1\n // If we just consumed a `\\r` and the next char is `\\n`, skip it so the\n // pair is treated as a single terminator rather than an empty line.\n if (chunk.charCodeAt(searchIndex - 1) === CR && chunk.charCodeAt(searchIndex) === LF) {\n searchIndex++\n }\n }\n\n return chunk.slice(searchIndex)\n }\n\n function parseLine(chunk: string, start: number, end: number) {\n if (start === end) {\n dispatchEvent()\n return\n }\n\n const firstCharCode = chunk.charCodeAt(start)\n\n if (isDataPrefix(chunk, start, firstCharCode)) {\n // 'data:'.length === 5, 'data: '.length === 6\n const valueStart = chunk.charCodeAt(start + 5) === SPACE ? start + 6 : start + 5\n const value = chunk.slice(valueStart, end)\n data = dataLines === 0 ? value : `${data}\\n${value}`\n dataLines++\n return\n }\n\n if (isEventPrefix(chunk, start, firstCharCode)) {\n // 'event:'.length === 6, 'event: '.length === 7\n eventType =\n chunk.slice(chunk.charCodeAt(start + 6) === SPACE ? start + 7 : start + 6, end) || undefined\n return\n }\n\n // Fast path for \"id:\" — 'i' = 105, 'd' = 100, ':' = 58\n if (\n firstCharCode === 105 &&\n chunk.charCodeAt(start + 1) === 100 &&\n chunk.charCodeAt(start + 2) === 58\n ) {\n // 'id:'.length === 3, 'id: '.length === 4\n const value = chunk.slice(chunk.charCodeAt(start + 3) === SPACE ? start + 4 : start + 3, end)\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 if (!value.includes('\\0')) id = value\n return\n }\n\n // Comment line — ':' = 58\n if (firstCharCode === 58) {\n if (onComment) {\n const line = chunk.slice(start, end)\n // skip ':' (+1), or ': ' (+2) when a space follows\n onComment(line.slice(chunk.charCodeAt(start + 1) === SPACE ? 2 : 1))\n }\n return\n }\n\n const line = chunk.slice(start, end)\n const fieldSeparatorIndex = line.indexOf(':')\n if (fieldSeparatorIndex === -1) {\n processField(line, '', line)\n return\n }\n\n const field = line.slice(0, fieldSeparatorIndex)\n // skip ':' (+1), or ': ' (+2) when a space follows\n const offset = line.charCodeAt(fieldSeparatorIndex + 1) === SPACE ? 2 : 1\n const value = line.slice(fieldSeparatorIndex + offset)\n processField(field, value, 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 || undefined\n break\n case 'data':\n data = dataLines === 0 ? value : `${data}\\n${value}`\n dataLines++\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 if (!value.includes('\\0')) id = 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 if (dataLines > 0) {\n onEvent({\n id,\n event: eventType,\n data,\n })\n }\n\n id = undefined\n data = ''\n dataLines = 0\n eventType = undefined\n }\n\n function reset(options: {consume?: boolean} = {}) {\n if (options.consume && pendingFragments.length > 0) {\n const incompleteLine = pendingFragments.join('')\n parseLine(incompleteLine, 0, incompleteLine.length)\n }\n\n isFirstChunk = true\n id = undefined\n data = ''\n dataLines = 0\n eventType = undefined\n pendingFragments.length = 0\n pendingFragmentsLength = 0\n terminated = false\n }\n\n return {feed, reset}\n}\n\n/**\n * Checks if `chunk` starts with the literal `data:` at index `i`.\n *\n * Equivalent to `chunk.startsWith('data:', i)`, but benchmarks show this\n * hand-unrolled char-code comparison is ~20% faster on common event types.\n * The caller passes `firstCharCode` (the code at `i`) so it can be reused\n * across prefix checks.\n *\n * ASCII: 'd' = 100, 'a' = 97, 't' = 116, 'a' = 97, ':' = 58\n */\nfunction isDataPrefix(chunk: string, i: number, firstCharCode: number): boolean {\n return (\n firstCharCode === 100 &&\n chunk.charCodeAt(i + 1) === 97 &&\n chunk.charCodeAt(i + 2) === 116 &&\n chunk.charCodeAt(i + 3) === 97 &&\n chunk.charCodeAt(i + 4) === 58\n )\n}\n\n/**\n * Checks if `chunk` starts with the literal `event:` at index `i`.\n *\n * See {@link isDataPrefix} for why this is hand-unrolled rather than using\n * `String.prototype.startsWith`.\n *\n * ASCII: 'e' = 101, 'v' = 118, 'e' = 101, 'n' = 110, 't' = 116, ':' = 58\n */\nfunction isEventPrefix(chunk: string, i: number, firstCharCode: number): boolean {\n return (\n firstCharCode === 101 &&\n chunk.charCodeAt(i + 1) === 118 &&\n chunk.charCodeAt(i + 2) === 101 &&\n chunk.charCodeAt(i + 3) === 110 &&\n chunk.charCodeAt(i + 4) === 116 &&\n chunk.charCodeAt(i + 5) === 58\n )\n}\n"],"names":["trailing","value","line"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eventsource-parser",
3
- "version": "3.1.0",
3
+ "version": "3.1.1",
4
4
  "description": "Streaming, source-agnostic EventSource/Server-Sent Events parser",
5
5
  "keywords": [
6
6
  "eventsource",
@@ -291,7 +291,9 @@ export function createParser(config: ParserConfig): EventSourceParser {
291
291
  ) {
292
292
  // 'id:'.length === 3, 'id: '.length === 4
293
293
  const value = chunk.slice(chunk.charCodeAt(start + 3) === SPACE ? start + 4 : start + 3, end)
294
- id = value.includes('\0') ? undefined : value
294
+ // If the field value does not contain U+0000 NULL, then set the `ID` buffer to
295
+ // the field value. Otherwise, ignore the field.
296
+ if (!value.includes('\0')) id = value
295
297
  return
296
298
  }
297
299
 
@@ -333,7 +335,7 @@ export function createParser(config: ParserConfig): EventSourceParser {
333
335
  case 'id':
334
336
  // If the field value does not contain U+0000 NULL, then set the `ID` buffer to
335
337
  // the field value. Otherwise, ignore the field.
336
- id = value.includes('\0') ? undefined : value
338
+ if (!value.includes('\0')) id = value
337
339
  break
338
340
  case 'retry':
339
341
  // If the field value consists of only ASCII digits, then interpret the field value as an
@@ -74,7 +74,7 @@ class ClientRequestImpl {
74
74
  }
75
75
  this.rBody = form;
76
76
  }
77
- if (args.json) {
77
+ if (args.json !== void 0) {
78
78
  this.rBody = JSON.stringify(args.json);
79
79
  this.cType = "application/json";
80
80
  }
@@ -90,9 +90,9 @@ class ClientRequestImpl {
90
90
  if (args?.cookie) {
91
91
  const cookies = [];
92
92
  for (const [key, value] of Object.entries(args.cookie)) {
93
- cookies.push((0, import_cookie.serialize)(key, value, { path: "/" }));
93
+ cookies.push((0, import_cookie.serialize)(key, value));
94
94
  }
95
- headerValues["Cookie"] = cookies.join(",");
95
+ headerValues["Cookie"] = cookies.join("; ");
96
96
  }
97
97
  if (this.cType) {
98
98
  headerValues["Content-Type"] = this.cType;
@@ -101,7 +101,7 @@ const documentMetadataTag = (tag, children, props, sort) => {
101
101
  const string = new import_base.JSXNode(tag, restProps, (0, import_children.toArray)(children || [])).toString();
102
102
  if (string instanceof Promise) {
103
103
  return string.then(
104
- (resString) => (0, import_html.raw)(string, [
104
+ (resString) => (0, import_html.raw)(resString, [
105
105
  ...resString.callbacks || [],
106
106
  insertIntoHead(tag, resString, restProps, precedence)
107
107
  ])
@@ -28,6 +28,8 @@ const cors = (options) => {
28
28
  exposeHeaders: [],
29
29
  ...options
30
30
  };
31
+ const exposeHeadersStr = opts.exposeHeaders?.length ? opts.exposeHeaders.join(",") : void 0;
32
+ const allowHeadersStr = opts.allowHeaders?.length ? opts.allowHeaders.join(",") : void 0;
31
33
  const findAllowOrigin = ((optsOrigin) => {
32
34
  if (typeof optsOrigin === "string") {
33
35
  if (optsOrigin === "*") {
@@ -43,11 +45,12 @@ const cors = (options) => {
43
45
  })(opts.origin);
44
46
  const findAllowMethods = ((optsAllowMethods) => {
45
47
  if (typeof optsAllowMethods === "function") {
46
- return optsAllowMethods;
48
+ return async (origin, c) => (await optsAllowMethods(origin, c)).join(",");
47
49
  } else if (Array.isArray(optsAllowMethods)) {
48
- return () => optsAllowMethods;
50
+ const methodsStr = optsAllowMethods.join(",");
51
+ return () => methodsStr;
49
52
  } else {
50
- return () => [];
53
+ return () => "";
51
54
  }
52
55
  })(opts.allowMethods);
53
56
  return async function cors2(c, next) {
@@ -61,8 +64,8 @@ const cors = (options) => {
61
64
  if (opts.credentials) {
62
65
  set("Access-Control-Allow-Credentials", "true");
63
66
  }
64
- if (opts.exposeHeaders?.length) {
65
- set("Access-Control-Expose-Headers", opts.exposeHeaders.join(","));
67
+ if (exposeHeadersStr) {
68
+ set("Access-Control-Expose-Headers", exposeHeadersStr);
66
69
  }
67
70
  if (c.req.method === "OPTIONS") {
68
71
  if (opts.origin !== "*") {
@@ -72,18 +75,18 @@ const cors = (options) => {
72
75
  set("Access-Control-Max-Age", opts.maxAge.toString());
73
76
  }
74
77
  const allowMethods = await findAllowMethods(c.req.header("origin") || "", c);
75
- if (allowMethods.length) {
76
- set("Access-Control-Allow-Methods", allowMethods.join(","));
78
+ if (allowMethods) {
79
+ set("Access-Control-Allow-Methods", allowMethods);
77
80
  }
78
- let headers = opts.allowHeaders;
79
- if (!headers?.length) {
81
+ let headersStr = allowHeadersStr;
82
+ if (!headersStr) {
80
83
  const requestHeaders = c.req.header("Access-Control-Request-Headers");
81
84
  if (requestHeaders) {
82
- headers = requestHeaders.split(",").map((h) => h.trim());
85
+ headersStr = requestHeaders.split(",").map((h) => h.trim()).join(",");
83
86
  }
84
87
  }
85
- if (headers?.length) {
86
- set("Access-Control-Allow-Headers", headers.join(","));
88
+ if (headersStr) {
89
+ set("Access-Control-Allow-Headers", headersStr);
87
90
  c.res.headers.append("Vary", "Access-Control-Request-Headers");
88
91
  }
89
92
  c.res.headers.delete("Content-Length");
@@ -31,18 +31,64 @@ const mergeBuffers = (buffer1, buffer2) => {
31
31
  merged.set(buffer2, buffer1.byteLength);
32
32
  return merged;
33
33
  };
34
+ const CHUNK_SIZE = 256 * 1024;
34
35
  const generateDigest = async (stream, generator) => {
35
36
  if (!stream) {
36
37
  return null;
37
38
  }
38
39
  let result = void 0;
40
+ let chunk;
41
+ let chunkLength = 0;
42
+ const digest = async (body) => {
43
+ result = await generator(mergeBuffers(result, body));
44
+ };
39
45
  const reader = stream.getReader();
40
46
  for (; ; ) {
41
47
  const { value, done } = await reader.read();
42
48
  if (done) {
43
49
  break;
44
50
  }
45
- result = await generator(mergeBuffers(result, value));
51
+ let offset = 0;
52
+ while (offset < value.byteLength) {
53
+ const remaining = value.byteLength - offset;
54
+ if (chunkLength === 0 && remaining >= CHUNK_SIZE) {
55
+ await digest(value.subarray(offset, offset + CHUNK_SIZE));
56
+ offset += CHUNK_SIZE;
57
+ continue;
58
+ }
59
+ const requiredLength = chunkLength + remaining;
60
+ if (requiredLength < CHUNK_SIZE) {
61
+ if (!chunk) {
62
+ chunk = value.subarray(offset);
63
+ } else {
64
+ if (chunk.byteLength < requiredLength) {
65
+ const nextChunk = new Uint8Array(
66
+ new ArrayBuffer(Math.min(CHUNK_SIZE, Math.max(requiredLength, chunk.byteLength * 2)))
67
+ );
68
+ nextChunk.set(chunk.subarray(0, chunkLength));
69
+ chunk = nextChunk;
70
+ }
71
+ chunk.set(value.subarray(offset), chunkLength);
72
+ }
73
+ chunkLength = requiredLength;
74
+ break;
75
+ }
76
+ const length = CHUNK_SIZE - chunkLength;
77
+ if (chunk?.byteLength !== CHUNK_SIZE) {
78
+ const nextChunk = new Uint8Array(new ArrayBuffer(CHUNK_SIZE));
79
+ if (chunk) {
80
+ nextChunk.set(chunk.subarray(0, chunkLength));
81
+ }
82
+ chunk = nextChunk;
83
+ }
84
+ chunk.set(value.subarray(offset, offset + length), chunkLength);
85
+ await digest(chunk);
86
+ chunkLength = 0;
87
+ offset += length;
88
+ }
89
+ }
90
+ if (chunk && chunkLength > 0) {
91
+ await digest(chunk.subarray(0, chunkLength));
46
92
  }
47
93
  if (!result) {
48
94
  return null;