{"_id":"stream-diffs","_rev":"2-1c8204c5e98f49a1db0f4e852bd58b80","name":"stream-diffs","dist-tags":{"latest":"0.0.2"},"versions":{"0.0.1":{"name":"stream-diffs","version":"0.0.1","author":{"name":"Simon He"},"license":"MIT","_id":"stream-diffs@0.0.1","maintainers":[{"name":"simon_he","email":"13917107469@163.com"}],"homepage":"https://github.com/Simon-He95/stream-diffs#readme","bugs":{"url":"https://github.com/Simon-He95/stream-diffs/issues"},"dist":{"shasum":"18ed09325c84c9230af30de838a61f60d594e084","tarball":"https://registry.npmjs.org/stream-diffs/-/stream-diffs-0.0.1.tgz","fileCount":14,"integrity":"sha512-ljw+IPcxj3dj9zyKtv9QOxloByi72fVuprHahO8pIhqk787sLmNvygMONSs07NNjUvknLZN9sePKh+LYGDuoQQ==","signatures":[{"sig":"MEYCIQD9b/iNrVqhYOhsf6nME2lnhDEjdJQtCopJwCJhBmo+xgIhAJ1uU9AAk5GyZcmfFeguZ0RJeE4D8Y7/cpk36yvLV07n","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":118758},"type":"module","_from":"file:stream-diffs-0.0.1.tgz","types":"./dist/index.d.mts","module":"./dist/index.mjs","exports":{".":{"types":"./dist/index.d.mts","import":"./dist/index.mjs"},"./vue":{"types":"./dist/vue.d.mts","import":"./dist/vue.mjs"},"./pierre":{"types":"./dist/pierre.d.mts","import":"./dist/pierre.mjs"}},"scripts":{"dev":"tsdown --watch","test":"vitest --run","build":"tsdown","example":"vite examples","release":"bumpp --execute \"pnpm release:publish\"","benchmark":"vitest bench --run benchmarks/streaming.bench.ts","typecheck":"tsc --noEmit","size:check":"node scripts/check-size.mjs","example:build":"vite build examples --outDir ../.tmp/example --emptyOutDir","release:verify":"pnpm typecheck && pnpm test && pnpm build && pnpm size:check && pnpm example:build && node scripts/check-package.mjs","release:publish":"pnpm build && pnpm publish --no-git-checks"},"_npmUser":{"name":"simon_he","email":"13917107469@163.com"},"_resolved":"/private/var/folders/z8/w1qvd0cd46z6k8_swl1n33d00000gn/T/cbc52632bcacdb48bfc1f7a08bfdb53e/stream-diffs-0.0.1.tgz","_integrity":"sha512-ljw+IPcxj3dj9zyKtv9QOxloByi72fVuprHahO8pIhqk787sLmNvygMONSs07NNjUvknLZN9sePKh+LYGDuoQQ==","repository":{"url":"git+https://github.com/Simon-He95/stream-diffs.git","type":"git"},"_npmVersion":"10.9.2","description":"Lightweight, stream-native code and diff rendering for AI output, powered by @pierre/diffs and Shiki.","directories":{},"sideEffects":false,"_nodeVersion":"23.11.0","dependencies":{"@pierre/diffs":"^1.2.12"},"_hasShrinkwrap":false,"devDependencies":{"vue":"^3.5.39","vite":"^7.3.5","bumpp":"^11.1.0","jsdom":"^26.1.0","tsdown":"^0.22.7","vitest":"^3.2.7","typescript":"^5.9.3","@types/node":"^24.13.3"},"peerDependencies":{"vue":"^3.3.0"},"peerDependenciesMeta":{"vue":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/stream-diffs_0.0.1_1784013467085_0.33054795814740845","host":"s3://npm-registry-packages-npm-production"}},"0.0.2":{"name":"stream-diffs","type":"module","version":"0.0.2","description":"Lightweight, stream-native code and diff rendering for AI output, powered by @pierre/diffs and Shiki.","author":{"name":"Simon He"},"license":"MIT","homepage":"https://github.com/Simon-He95/stream-diffs#readme","repository":{"type":"git","url":"git+https://github.com/Simon-He95/stream-diffs.git"},"sideEffects":false,"exports":{".":{"types":"./dist/index.d.mts","import":"./dist/index.mjs"},"./markstream":{"types":"./dist/markstream.d.mts","import":"./dist/markstream.mjs"},"./vue":{"types":"./dist/vue.d.mts","import":"./dist/vue.mjs"},"./pierre":{"types":"./dist/pierre.d.mts","import":"./dist/pierre.mjs"}},"module":"./dist/index.mjs","types":"./dist/index.d.mts","dependencies":{"@pierre/diffs":"^1.2.12"},"peerDependencies":{"vue":"^3.3.0"},"peerDependenciesMeta":{"vue":{"optional":true}},"devDependencies":{"@types/node":"^24.13.3","bumpp":"^11.1.0","jsdom":"^26.1.0","tsdown":"^0.22.7","typescript":"^5.9.3","vite":"^7.3.5","vitest":"^3.2.7","vue":"^3.5.39"},"scripts":{"build":"tsdown","dev":"tsdown --watch","example":"vite examples","example:build":"vite build examples --outDir ../.tmp/example --emptyOutDir","test":"vitest --run","benchmark":"vitest bench --run benchmarks/streaming.bench.ts","typecheck":"tsc --noEmit","size:check":"node scripts/check-size.mjs","release:verify":"pnpm typecheck && pnpm test && pnpm build && pnpm size:check && pnpm example:build && node scripts/check-package.mjs","release":"bumpp --execute \"pnpm release:publish\"","release:publish":"pnpm build && pnpm publish --no-git-checks"},"_id":"stream-diffs@0.0.2","bugs":{"url":"https://github.com/Simon-He95/stream-diffs/issues"},"_integrity":"sha512-mYH+kBCmrN2ebVPNkvQIFUSPemFxUPPPk+NT4H/wEzZ9NCt5Eaa8OE05f/1MA5HofZ64LYbTOliQTU6VghHeVA==","_resolved":"/private/var/folders/z8/w1qvd0cd46z6k8_swl1n33d00000gn/T/5f3d439741c11ccef73f87457dcd304b/stream-diffs-0.0.2.tgz","_from":"file:stream-diffs-0.0.2.tgz","_nodeVersion":"23.11.0","_npmVersion":"10.9.2","dist":{"integrity":"sha512-mYH+kBCmrN2ebVPNkvQIFUSPemFxUPPPk+NT4H/wEzZ9NCt5Eaa8OE05f/1MA5HofZ64LYbTOliQTU6VghHeVA==","shasum":"c3e6935c9c9e1966ce1cc7d3e865d6e96e94205a","tarball":"https://registry.npmjs.org/stream-diffs/-/stream-diffs-0.0.2.tgz","fileCount":17,"unpackedSize":130500,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDt3ND3IA2OyPJDU4Ok5RClu0nmDSLTA98Uelv0verZNwIhAPaC9P+gTtXa2Xn5gH10/HLGWjY69S8wRmVldHiyDkS0"}]},"_npmUser":{"name":"simon_he","email":"13917107469@163.com"},"directories":{},"maintainers":[{"name":"simon_he","email":"13917107469@163.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/stream-diffs_0.0.2_1784298022529_0.18572397285668418"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-14T07:17:47.026Z","modified":"2026-07-17T14:20:22.810Z","0.0.1":"2026-07-14T07:17:47.257Z","0.0.2":"2026-07-17T14:20:22.678Z"},"bugs":{"url":"https://github.com/Simon-He95/stream-diffs/issues"},"author":{"name":"Simon He"},"license":"MIT","homepage":"https://github.com/Simon-He95/stream-diffs#readme","repository":{"type":"git","url":"git+https://github.com/Simon-He95/stream-diffs.git"},"description":"Lightweight, stream-native code and diff rendering for AI output, powered by @pierre/diffs and Shiki.","maintainers":[{"name":"simon_he","email":"13917107469@163.com"}],"readme":"# stream-diffs\n\n面向 AI 输出的轻量、只读代码与 Diff 渲染库。底层使用 [`@pierre/diffs`](https://diffs.com/) 与 Shiki；流式阶段不创建 Monaco model 和 worker，结束后可切换为可交互的 File、Diff 或 Git 冲突解决界面。\n\n## 安装\n\n```bash\npnpm add stream-diffs\n```\n\nVue 组件入口需要 Vue 3：\n\n```bash\npnpm add stream-diffs vue\n```\n\n## 流式代码\n\n```ts\nimport { createCodeStream } from 'stream-diffs'\n\nconst output = createCodeStream({\n  fileName: 'answer.ts',\n  language: 'typescript',\n  theme: { dark: 'github-dark', light: 'github-light' },\n  maxHeight: 600,\n  lineNumbers: true,\n  autoScroll: 'near-bottom',\n})\n\nawait output.mount(document.querySelector('#code')!)\noutput.append('export const ')\noutput.append('answer = 42\\n')\nawait output.finalize({ view: 'file' })\n```\n\n实时高亮默认保护阈值为 10,000 行或 1,000,000 字符。超过任一阈值后会终止持续增长的高亮 DOM，继续累积文本并使用只追加纯文本显示；最终仍能生成准确的 File 或 Diff：\n\n```ts\nconst output = createCodeStream({\n  limits: {\n    maxStreamingLines: 20_000,\n    maxStreamingChars: 2_000_000,\n    overflowBehavior: 'stop-highlighting',\n  },\n  onOverflow: stats => console.warn('已停止实时高亮', stats),\n})\n```\n\n这些是 `stream-diffs` 的安全默认值，不是 Diffs 官方上限。宿主已限制输出长度时可传入 `limits: false`。\n\n`append()` 是性能最佳的增量路径。对于每次返回完整文本的 SDK，可以使用：\n\n```ts\noutput.updateSnapshot('export')\noutput.updateSnapshot('export const')\noutput.updateSnapshot('export const answer = 42')\n```\n\n也可以直接消费 `ReadableStream<string>` 或异步迭代器：\n\n```ts\nawait output.consume(response.body!)\nawait output.finalize({\n  view: 'diff',\n  original: previousSource,\n  diffStyle: 'split',\n})\n```\n\n## 单列与双列 Diff\n\n```ts\nimport { createDiffSurface } from 'stream-diffs'\n\nconst review = createDiffSurface({\n  kind: 'diff',\n  oldFile: { name: 'src/app.ts', contents: before },\n  newFile: { name: 'src/app.ts', contents: after },\n  options: {\n    diffStyle: 'unified', // 单列\n    // diffStyle: 'split', // 双列\n    diffIndicators: 'bars',\n    lineDiffType: 'word-alt',\n    enableLineSelection: true,\n  },\n})\n\nawait review.mount(document.querySelector('#diff')!)\nreview.setSelectedLines({ start: 8, end: 12, side: 'additions' })\n```\n\n`kind: 'file'` 渲染静态代码，`kind: 'merge-conflict'` 渲染 Git 冲突解决 UI。\n\n原始 Git patch 与预解析 `FileDiffMetadata` 也可以直接渲染：\n\n```ts\ncreateDiffSurface({ kind: 'patch', patch: unifiedPatch, fileIndex: 0 })\ncreateDiffSurface({ kind: 'diff', fileDiff: parsedMetadata })\n```\n\n## 在 markstream-vue 中自动使用\n\n`markstream-vue` 会动态优先选择已安装的 `stream-diffs`，现有 CodeBlock 用法无需改变：\n\n```bash\npnpm add markstream-vue stream-diffs\n```\n\n```vue\n<MarkdownRender :content=\"markdown\" :loading=\"streaming\" />\n```\n\n高级配置继续通过已有的 `codeBlockProps.monacoOptions` 传入；保留这个属性名是为了兼容现有 API：\n\n```ts\nconst codeBlockProps = {\n  monacoOptions: {\n    diffStyle: 'split',\n    enableLineSelection: true,\n    onLineSelected: range => console.log(range),\n    onTokenEnter: ({ tokenText, tokenElement }) => {\n      showHover(tokenText, tokenElement)\n    },\n    lineAnnotations: comments,\n    renderAnnotation: renderComment,\n    onController: controller => codeController = controller,\n  },\n}\n```\n\n流式阶段 markstream 保留自己的 `<pre>`。代码块完整且进入可视区域后，它只创建一次最终的静态 `File` 或 `FileDiff`，等待最新渲染稳定后再原子切换；完整冲突标记会进入冲突解决 UI。两种可选渲染器同时安装时优先 `stream-diffs`；移除它即可显式使用 `stream-monaco`。\n\n首次冷启动仍需异步初始化共享的 Shiki highlighter，因此 markstream 会保留 `<pre>`，直到 Diffs surface ready；它不创建 Monaco worker 或 model。实测 Chrome 中冷启动 fallback 中位数为 72.4 ms，stream-monaco 为 913.0 ms；1、12、24 threads 压测均未检测到容器高度回落。方法和复现命令见[性能与包体积](./docs/performance.md#browser-streaming-benchmark)。\n\n兼容运行时提供 `whenVisualReady()`。只有它返回 `true` 时适配层才移除 `<pre>`；返回 `false` 表示创建已过期或被取消，必须继续保留 fallback。\n\n## 交互能力\n\n- Custom headers：`renderHeaderPrefix`、`renderHeaderMetadata`、`renderCustomHeader`。\n- Token Hover：`onTokenEnter`、`onTokenLeave`、`onTokenClick`。\n- 行选择：`enableLineSelection`、`onLineSelected`、`setSelectedLines()`。\n- Comments & Annotations：`annotations`、`renderAnnotation`、`setAnnotations()`。\n- Accept/Reject：`acceptReject(hunkIndex, 'accept' | 'reject' | 'both')`，也支持指定 `changeIndex`。\n- Merge：`mergeConflictActionsType`、`onMergeConflictResolve`、`resolveConflict()`。\n- 完整原生能力：`getNativeInstance()` 返回当前 `File`、`FileDiff` 或 `UnresolvedFile`；`getResolvedFile()` 返回 Accept/Reject 后的完整文件。\n\n示例：\n\n```ts\nreview.acceptReject(0, 'accept')\nreview.setAnnotations([\n  { side: 'additions', lineNumber: 14, metadata: { body: '这里需要校验输入' } },\n])\n\nconst merge = createDiffSurface({\n  kind: 'merge-conflict',\n  file: { name: 'app.ts', contents: conflictedSource },\n  options: {\n    mergeConflictActionsType: 'default',\n    onMergeConflictResolve(file) {\n      save(file.contents)\n    },\n  },\n})\n```\n\n当前底层 Diffs 版本把 `UnresolvedFile`、merge resolution 和 token hooks 标记为实验性能力，`stream-diffs` 会如实保留这个状态与原生类型。\n\n## Vue 组件\n\n```vue\n<script setup lang=\"ts\">\nimport { StreamCode, StreamDiff, StreamMergeConflict } from 'stream-diffs/vue'\n</script>\n\n<template>\n  <StreamCode :code=\"generated\" language=\"typescript\" :loading=\"streaming\" />\n  <StreamDiff :original=\"before\" :modified=\"after\" diff-style=\"split\" />\n  <StreamMergeConflict :code=\"conflictedSource\" language=\"typescript\" />\n</template>\n```\n\n完整说明见 [API 文档](./docs/api.md)、[性能与包体积](./docs/performance.md) 与 [markstream-vue 接入文档](./docs/markstream-vue.md)。\n\n运行 `pnpm example` 可以打开真实浏览器能力实验页，同页覆盖 unified/split review、annotations、token hover、行选择、accept/reject、merge actions 和 streaming-to-file 转换。\n\n同步的 Pierre 底层工具使用独立入口，避免根入口提前加载渲染依赖：\n\n```ts\nimport { parseDiffFromFile, parsePatchFiles } from 'stream-diffs/pierre'\n```\n\n## 设计边界\n\n- 流式阶段只读、只追加。\n- delta append 的性能优于 cumulative snapshot。\n- 流式阶段使用 `FileStream`，最终 Diff 基于完整 snapshot 计算，保证准确性。\n- 超长输出会继续以只追加纯文本显示，并仍可最终生成准确 File 或 Diff。\n- 不包含 cursor editing、undo/redo、Monaco model、补全、LSP 与诊断运行时。\n- `@pierre/diffs` 在首次 mount 时动态加载，SSR import 安全。\n\n## License\n\nMIT\n","readmeFilename":"README.zh-CN.md"}