chore: establish YCode replication baseline and TODO

This commit is contained in:
xiaohan.yuan committed 2026-10-17 02:43:49 +08:00
commit 6dc59f89ba
1118 files changed
+196002

No files matched your search

+69
View File
@@ -0,0 +1,69 @@
/**
* 极简 SSE(text/event-stream)解析器。
*
* 只依赖 Web 标准的 ReadableStream,可直接消费 fetch 响应体。
* 上游既可能是标准 SSE,也可能在末尾缺少空行,因此以「块边界」为准做收尾。
*/
export interface SseEvent {
event?: string
data: string
}
export async function* parseSse(
stream: ReadableStream<Uint8Array>
): AsyncGenerator<SseEvent, void, void> {
const reader = stream.getReader()
const decoder = new TextDecoder()
let buffer = ''
try {
for (;;) {
const { done, value } = await reader.read()
if (done) break
buffer += decoder.decode(value, { stream: true })
let index: number
// 事件之间以空行分隔,兼容 \r\n 与 \n
while ((index = findBoundary(buffer)) !== -1) {
const raw = buffer.slice(0, index)
buffer = buffer.slice(index).replace(/^(\r?\n){2}/, '')
const parsed = parseBlock(raw)
if (parsed) yield parsed
}
}
// 收尾:流结束时可能残留最后一个事件
buffer += decoder.decode()
const tail = parseBlock(buffer)
if (tail) yield tail
} finally {
reader.releaseLock()
}
}
function findBoundary(buffer: string): number {
const a = buffer.indexOf('\n\n')
const b = buffer.indexOf('\r\n\r\n')
if (a === -1) return b
if (b === -1) return a
return Math.min(a, b)
}
function parseBlock(block: string): SseEvent | null {
const lines = block.split(/\r?\n/)
const dataLines: string[] = []
let event: string | undefined
for (const line of lines) {
if (!line || line.startsWith(':')) continue
if (line.startsWith('event:')) {
event = line.slice(6).trim()
} else if (line.startsWith('data:')) {
dataLines.push(line.slice(5).replace(/^ /, ''))
}
}
if (dataLines.length === 0) return null
return { event, data: dataLines.join('\n') }
}