sse-stream-plugin-sdk 1.0.0

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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ryan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,318 @@
1
+ # sse-stream-plugin-sdk
2
+
3
+ 插件化跨端 SSE 核心 SDK。框架无关,零运行时依赖,提供可插拔的传输层、解析器、请求/事件中间件和统一插件契约。
4
+
5
+ ## 安装
6
+
7
+ ```bash
8
+ npm install sse-stream-plugin-sdk
9
+ # 或
10
+ pnpm add sse-stream-plugin-sdk
11
+ ```
12
+
13
+ ## 快速开始
14
+
15
+ ### 浏览器(自动探测 fetch)
16
+
17
+ ```ts
18
+ import { PluginSSE, recommendedPlugins } from 'sse-stream-plugin-sdk'
19
+
20
+ const sse = new PluginSSE({
21
+ url: 'https://api.example.com/stream',
22
+ method: 'POST',
23
+ body: JSON.stringify({ prompt: '你好' }),
24
+ headers: { 'Content-Type': 'application/json' },
25
+ plugins: recommendedPlugins(), // 重连 + 心跳 + 节流
26
+ })
27
+
28
+ sse.on('message', (data) => console.log('收到:', data))
29
+ sse.on('error', (err) => console.error(err))
30
+ sse.on('end', () => console.log('流结束'))
31
+
32
+ sse.connect()
33
+ ```
34
+
35
+ ### 小程序(通过 `createMpTransport` 注入平台 API)
36
+
37
+ ```ts
38
+ import { PluginSSE, createMpTransport, recommendedPlugins } from 'sse-stream-plugin-sdk'
39
+
40
+ const sse = new PluginSSE({
41
+ url: 'https://api.example.com/stream',
42
+ method: 'POST',
43
+ body: JSON.stringify({ prompt: '你好' }),
44
+ // 微信:wx;抖音:tt;快手:ks;百度:swan;QQ:qq
45
+ transport: createMpTransport(() => wx),
46
+ plugins: recommendedPlugins(),
47
+ })
48
+
49
+ sse.on('message', (data) => { /* 处理流式文本 */ })
50
+ sse.connect()
51
+ ```
52
+
53
+ ## 核心 API
54
+
55
+ ### `PluginSSE`
56
+
57
+ ```ts
58
+ new PluginSSE(options: PluginSSEOptions)
59
+ ```
60
+
61
+ #### 配置项 `PluginSSEOptions`
62
+
63
+ | 字段 | 类型 | 说明 |
64
+ |------|------|------|
65
+ | `url` | `string` | 请求地址(必填) |
66
+ | `method` | `'GET' \| 'POST'` | 请求方法,默认 `GET` |
67
+ | `body` | `string` | 请求体字符串,POST 时发送 |
68
+ | `headers` | `Record<string, string>` | 自定义请求头 |
69
+ | `transport` | `string \| TransportFactory` | 传输层:名称(`web`/`mock` 或插件注册的名称)或工厂;省略则自动探测 `fetch` |
70
+ | `plugins` | `SSEPlugin[]` | 初始插件列表 |
71
+ | `parser` | `ParserFactory` | 自定义解析器,默认标准 SSE |
72
+
73
+ #### 实例方法
74
+
75
+ | 方法 | 说明 |
76
+ |------|------|
77
+ | `connect()` | 建立连接 |
78
+ | `destroy()` | 永久销毁:中止连接、释放插件资源、清空订阅 |
79
+ | `use(plugin)` | 注册插件(链式调用) |
80
+ | `on(event, listener)` | 订阅事件,返回取消订阅函数 |
81
+ | `once(event, listener)` | 订阅一次 |
82
+ | `off(event, listener)` | 取消订阅 |
83
+
84
+ #### 事件
85
+
86
+ | 事件 | 参数 | 说明 |
87
+ |------|------|------|
88
+ | `connecting` | `{ session }` | 开始连接 |
89
+ | `open` | `{ transport }` | 连接已打开,传输层名称 |
90
+ | `response` | `{ status, headers? }` | 收到响应头 |
91
+ | `data` | `chunk: string` | 收到原始文本分片 |
92
+ | `event` | `ParsedEvent` | 解析出的结构化事件 |
93
+ | `message` | `data: string, event: ParsedEvent` | `message` 事件(默认事件类型) |
94
+ | `end` | — | 流正常结束 |
95
+ | `error` | `err: unknown` | 连接/读取错误 |
96
+ | `reconnect` | `{ attempt, inMs }` | 即将重连 |
97
+ | `reconnect-failed` | `err` | 达到最大重试次数 |
98
+ | `heartbeat-lost` | — | 心跳超时 |
99
+ | `closed` | — | 实例已销毁 |
100
+
101
+ ### 推荐插件组合
102
+
103
+ ```ts
104
+ import { recommendedPlugins } from 'sse-stream-plugin-sdk'
105
+
106
+ // 自动重连 + 心跳检测 + 消息节流
107
+ const plugins = recommendedPlugins()
108
+ ```
109
+
110
+ ## 传输层
111
+
112
+ ### 内置传输层
113
+
114
+ | 名称 | 说明 |
115
+ |------|------|
116
+ | `web` | 基于 `fetch` + `ReadableStream`,浏览器/H5 环境 |
117
+ | `mock` | 按脚本定时产出 SSE 文本,用于测试和演示 |
118
+
119
+ ### 小程序传输层(工厂模式)
120
+
121
+ core 不绑定任何具体小程序平台,通过 `createMpTransport` 工厂注入平台 `request` API:
122
+
123
+ ```ts
124
+ import { createMpTransport } from 'sse-stream-plugin-sdk'
125
+
126
+ // 微信
127
+ createMpTransport(() => wx)
128
+ // 抖音
129
+ createMpTransport(() => tt)
130
+ // 快手
131
+ createMpTransport(() => ks)
132
+ // 百度
133
+ createMpTransport(() => swan)
134
+ // QQ
135
+ createMpTransport(() => qq)
136
+ ```
137
+
138
+ > 微信/抖音/快手/百度/QQ 的 `request` API 同构,均支持 `enableChunked` + `onChunkReceived`,因此一个工厂覆盖全部。
139
+
140
+ ### 作为插件注册(命名引用)
141
+
142
+ ```ts
143
+ import { PluginSSE, createMpTransport } from 'sse-stream-plugin-sdk'
144
+
145
+ const sse = new PluginSSE({ url })
146
+
147
+ sse.use({
148
+ name: 'wx-transport',
149
+ install(ctx) {
150
+ ctx.registerTransport('wx', createMpTransport(() => wx))
151
+ },
152
+ })
153
+
154
+ // 之后用名称引用
155
+ const sse2 = new PluginSSE({ url, transport: 'wx' })
156
+ ```
157
+
158
+ ### 自定义传输层
159
+
160
+ 实现 `Transport` 接口即可接入任何运行环境:
161
+
162
+ ```ts
163
+ import type { Transport, TransportFactory, TransportEvents, RequestConfig } from 'sse-stream-plugin-sdk'
164
+
165
+ const myTransport: TransportFactory = (): Transport => ({
166
+ name: 'my-transport',
167
+ connect(req: RequestConfig, events: TransportEvents) {
168
+ // 发起请求,通过 events.data / events.end / events.error 回调
169
+ },
170
+ abort() {
171
+ // 中止连接
172
+ },
173
+ })
174
+ ```
175
+
176
+ ## 内置插件
177
+
178
+ ### `reconnectPlugin` — 自动重连 + 断线续传
179
+
180
+ ```ts
181
+ reconnectPlugin({
182
+ maxRetries: 3, // 最大重试次数,默认 3
183
+ delay: 1500, // 重试等待毫秒,默认 1500
184
+ })
185
+ ```
186
+
187
+ 自动记忆最近事件 `id`,重连时注入 `Last-Event-ID` 请求头实现续传。
188
+
189
+ ### `heartbeatPlugin` — 心跳检测
190
+
191
+ ```ts
192
+ heartbeatPlugin({
193
+ interval: 10000, // 心跳超时毫秒,默认 10000
194
+ })
195
+ ```
196
+
197
+ 任意数据到达即续命;超时未收到数据则触发 `heartbeat-lost` 并走错误流程(可由重连插件接管)。
198
+
199
+ ### `throttlePlugin` — 消息节流
200
+
201
+ ```ts
202
+ throttlePlugin({
203
+ windowMs: 100, // 合并窗口毫秒,默认 100
204
+ })
205
+ ```
206
+
207
+ 按时间窗口合并 `message` 事件数据,高频流式场景下显著减少渲染次数。被合并事件保留最后一个 `id`,续传位点不丢失。
208
+
209
+ ### `authPlugin` — 鉴权注入与自动刷新
210
+
211
+ ```ts
212
+ authPlugin({
213
+ getToken: () => localStorage.getItem('token') || '',
214
+ refreshToken: async () => {
215
+ const res = await fetch('/api/refresh')
216
+ const { token } = await res.json()
217
+ localStorage.setItem('token', token)
218
+ return token
219
+ },
220
+ header: 'Authorization', // 默认 Authorization
221
+ scheme: 'Bearer', // 默认 Bearer;传空串表示不加前缀
222
+ })
223
+ ```
224
+
225
+ 每次连接前注入鉴权头;收到 401 时自动刷新令牌并重连(仅刷新一次防止死循环)。
226
+
227
+ ### `loggerPlugin` — 生命周期日志
228
+
229
+ ```ts
230
+ loggerPlugin({
231
+ sink: (msg) => console.log(msg), // 默认 console.log
232
+ })
233
+ ```
234
+
235
+ ### `statsPlugin` — 统计(自定义插件示例)
236
+
237
+ ```ts
238
+ statsPlugin({
239
+ keyword: '✅', // 命中关键字时派发 keyword 事件,默认 ✅
240
+ })
241
+ ```
242
+
243
+ 统计消息条数与字符数,通过 `stats` 事件广播;是"第三方风格"插件的范例。
244
+
245
+ ## 开发自定义插件
246
+
247
+ 插件统一实现 `SSEPlugin` 接口,通过 `PluginContext` 扩展能力:
248
+
249
+ ```ts
250
+ import type { SSEPlugin, PluginContext } from 'sse-stream-plugin-sdk'
251
+
252
+ const myPlugin: SSEPlugin = {
253
+ name: 'my-plugin',
254
+
255
+ install(ctx: PluginContext) {
256
+ // 1. 注册请求中间件(改写请求:加头、改 URL 等)
257
+ ctx.addRequestMiddleware(async (req, next) => {
258
+ req.headers['X-Custom'] = 'value'
259
+ return next(req)
260
+ })
261
+
262
+ // 2. 注册事件中间件(改写/拦截事件)
263
+ ctx.addEventMiddleware((event, next) => {
264
+ if (event.name === 'ping') return // 丢弃 ping 事件
265
+ next(event)
266
+ })
267
+
268
+ // 3. 注册命名传输层
269
+ ctx.registerTransport('custom', myTransportFactory)
270
+
271
+ // 4. 替换解析器
272
+ ctx.setParser(myParserFactory)
273
+
274
+ // 5. 订阅事件总线
275
+ ctx.emitter.on('message', (data) => { /* ... */ })
276
+
277
+ // 6. 主动发起重连
278
+ // ctx.start()
279
+
280
+ // 7. 主动上报错误
281
+ // ctx.fail(new Error('...'))
282
+
283
+ // 8. 注册销毁回调
284
+ ctx.onDispose(() => { /* 释放资源 */ })
285
+ },
286
+ }
287
+ ```
288
+
289
+ `PluginContext` 完整能力:
290
+
291
+ | 方法/属性 | 说明 |
292
+ |-----------|------|
293
+ | `emitter` | 事件总线 |
294
+ | `request` | 当前请求配置(只读) |
295
+ | `addRequestMiddleware(mw)` | 注册请求中间件 |
296
+ | `addEventMiddleware(mw)` | 注册事件中间件 |
297
+ | `setParser(factory)` | 替换流解析器 |
298
+ | `registerTransport(name, factory)` | 注册命名传输层 |
299
+ | `start()` | 发起(重)连接 |
300
+ | `fail(err)` | 上报错误并走错误流程 |
301
+ | `getState()` | 获取内核状态 |
302
+ | `isDead()` | 是否已销毁 |
303
+ | `onDispose(fn)` | 注册销毁回调 |
304
+
305
+ ## 平台兼容性
306
+
307
+ | 平台 | 传输层 | 流式支持 |
308
+ |------|--------|---------|
309
+ | 浏览器 / H5 | `webFetchTransport`(fetch) | ✅ |
310
+ | 微信小程序 | `createMpTransport(() => wx)` | ✅ |
311
+ | 抖音小程序 | `createMpTransport(() => tt)` | ✅ |
312
+ | 快手小程序 | `createMpTransport(() => ks)` | ✅ |
313
+ | 百度小程序 | `createMpTransport(() => swan)` | ✅ |
314
+ | QQ 小程序 | `createMpTransport(() => qq)` | ✅ |
315
+
316
+ ## License
317
+
318
+ MIT