|
class ParseError extends Error { |
|
constructor(message, options) { |
|
super(message), this.name = "ParseError", this.type = options.type, this.field = options.field, this.value = options.value, this.line = options.line; |
|
} |
|
} |
|
function noop(_arg) { |
|
} |
|
function createParser(callbacks) { |
|
if (typeof callbacks == "function") |
|
throw new TypeError( |
|
"`callbacks` must be an object, got a function instead. Did you mean `{onEvent: fn}`?" |
|
); |
|
const { onEvent = noop, onError = noop, onRetry = noop, onComment } = callbacks; |
|
let incompleteLine = "", isFirstChunk = !0, id, data = "", eventType = ""; |
|
function feed(newChunk) { |
|
const chunk = isFirstChunk ? newChunk.replace(/^\xEF\xBB\xBF/, "") : newChunk, [complete, incomplete] = splitLines(`${incompleteLine}${chunk}`); |
|
for (const line of complete) |
|
parseLine(line); |
|
incompleteLine = incomplete, isFirstChunk = !1; |
|
} |
|
function parseLine(line) { |
|
if (line === "") { |
|
dispatchEvent(); |
|
return; |
|
} |
|
if (line.startsWith(":")) { |
|
onComment && onComment(line.slice(line.startsWith(": ") ? 2 : 1)); |
|
return; |
|
} |
|
const fieldSeparatorIndex = line.indexOf(":"); |
|
if (fieldSeparatorIndex !== -1) { |
|
const field = line.slice(0, fieldSeparatorIndex), offset = line[fieldSeparatorIndex + 1] === " " ? 2 : 1, value = line.slice(fieldSeparatorIndex + offset); |
|
processField(field, value, line); |
|
return; |
|
} |
|
processField(line, "", line); |
|
} |
|
function processField(field, value, line) { |
|
switch (field) { |
|
case "event": |
|
eventType = value; |
|
break; |
|
case "data": |
|
data = `${data}${value} |
|
`; |
|
break; |
|
case "id": |
|
id = value.includes("\0") ? void 0 : value; |
|
break; |
|
case "retry": |
|
/^\d+$/.test(value) ? onRetry(parseInt(value, 10)) : onError( |
|
new ParseError(`Invalid \`retry\` value: "${value}"`, { |
|
type: "invalid-retry", |
|
value, |
|
line |
|
}) |
|
); |
|
break; |
|
default: |
|
onError( |
|
new ParseError( |
|
`Unknown field "${field.length > 20 ? `${field.slice(0, 20)}\u2026` : field}"`, |
|
{ type: "unknown-field", field, value, line } |
|
) |
|
); |
|
break; |
|
} |
|
} |
|
function dispatchEvent() { |
|
data.length > 0 && onEvent({ |
|
id, |
|
event: eventType || void 0, |
|
|
|
|
|
data: data.endsWith(` |
|
`) ? data.slice(0, -1) : data |
|
}), id = void 0, data = "", eventType = ""; |
|
} |
|
function reset(options = {}) { |
|
incompleteLine && options.consume && parseLine(incompleteLine), isFirstChunk = !0, id = void 0, data = "", eventType = "", incompleteLine = ""; |
|
} |
|
return { feed, reset }; |
|
} |
|
function splitLines(chunk) { |
|
const lines = []; |
|
let incompleteLine = "", searchIndex = 0; |
|
for (; searchIndex < chunk.length; ) { |
|
const crIndex = chunk.indexOf("\r", searchIndex), lfIndex = chunk.indexOf(` |
|
`, searchIndex); |
|
let lineEnd = -1; |
|
if (crIndex !== -1 && lfIndex !== -1 ? lineEnd = Math.min(crIndex, lfIndex) : crIndex !== -1 ? lineEnd = crIndex : lfIndex !== -1 && (lineEnd = lfIndex), lineEnd === -1) { |
|
incompleteLine = chunk.slice(searchIndex); |
|
break; |
|
} else { |
|
const line = chunk.slice(searchIndex, lineEnd); |
|
lines.push(line), searchIndex = lineEnd + 1, chunk[searchIndex - 1] === "\r" && chunk[searchIndex] === ` |
|
` && searchIndex++; |
|
} |
|
} |
|
return [lines, incompleteLine]; |
|
} |
|
export { |
|
ParseError, |
|
createParser |
|
}; |
|
|
|
|