@wingsky-1/dsh-worktree-sidebar 0.2.5
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 +21 -0
- package/README.en.md +186 -0
- package/README.md +190 -0
- package/cordis.patch.yml +9 -0
- package/lib/client/bindings.d.ts +16 -0
- package/lib/client/index.d.ts +23 -0
- package/lib/client/inject.d.ts +21 -0
- package/lib/client/shared/ports.d.ts +141 -0
- package/lib/client/source.d.ts +22 -0
- package/lib/client/takeover.d.ts +51 -0
- package/lib/client.js +394 -0
- package/lib/index.d.ts +28 -0
- package/lib/index.js +1614 -0
- package/lib/server/api/deps.d.ts +35 -0
- package/lib/server/api/impl/handlers/index.d.ts +24 -0
- package/lib/server/api/impl/route/index.d.ts +25 -0
- package/lib/server/api/impl/service/index.d.ts +31 -0
- package/lib/server/api/interface.d.ts +12 -0
- package/lib/server/binding/deps.d.ts +9 -0
- package/lib/server/binding/impl/model/index.d.ts +16 -0
- package/lib/server/binding/impl/model/type.d.ts +37 -0
- package/lib/server/binding/impl/service/index.d.ts +67 -0
- package/lib/server/binding/impl/store/index.d.ts +6 -0
- package/lib/server/binding/interface.d.ts +24 -0
- package/lib/server/git/deps.d.ts +59 -0
- package/lib/server/git/impl/exec/index.d.ts +3 -0
- package/lib/server/git/impl/inspect/index.d.ts +47 -0
- package/lib/server/git/impl/service/index.d.ts +70 -0
- package/lib/server/git/interface.d.ts +41 -0
- package/lib/server/host/agents.d.ts +39 -0
- package/lib/server/host/sessions.d.ts +41 -0
- package/lib/server/host/typert.d.ts +11 -0
- package/lib/server/scope/deps.d.ts +110 -0
- package/lib/server/scope/impl/inherit/index.d.ts +25 -0
- package/lib/server/scope/impl/own/index.d.ts +21 -0
- package/lib/server/scope/impl/resolve/index.d.ts +40 -0
- package/lib/server/scope/impl/service/index.d.ts +115 -0
- package/lib/server/scope/interface.d.ts +40 -0
- package/lib/server/shared/file-io.d.ts +19 -0
- package/lib/server/shared/interface.d.ts +11 -0
- package/lib/server/shared/paths.d.ts +2 -0
- package/lib/server/shared/type.d.ts +6 -0
- package/lib/server/tools/deps.d.ts +52 -0
- package/lib/server/tools/impl/bind/index.d.ts +62 -0
- package/lib/server/tools/impl/create/index.d.ts +4 -0
- package/lib/server/tools/impl/protocol/index.d.ts +36 -0
- package/lib/server/tools/impl/register/index.d.ts +4 -0
- package/lib/server/tools/impl/remove/index.d.ts +12 -0
- package/lib/server/tools/impl/service/index.d.ts +28 -0
- package/lib/server/tools/impl/session/index.d.ts +18 -0
- package/lib/server/tools/interface.d.ts +14 -0
- package/lib/shared/contract.d.ts +26 -0
- package/lib/shared/interface.d.ts +8 -0
- package/package.json +103 -0
- package/shared/client/ensure-style.d.ts +21 -0
- package/shared/client/i18n.d.ts +15 -0
- package/shared/dsh-home.d.ts +15 -0
- package/shared/host-utils.d.ts +65 -0
- package/shared/loopback.d.ts +21 -0
- package/shared/settings-namespace.d.ts +46 -0
- package/shared/sse-hub.d.ts +76 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 双端共享契约:宿主与浏览器唯一必须一致的两件事——路由路径与绑定查询的响应形状。
|
|
3
|
+
*
|
|
4
|
+
* 两边各写一份的失败形态是静默的:客户端拿着对不上的键去 `__DSH_ROUTES__` 取值,
|
|
5
|
+
* 只会表现成「树没换根」;响应字段名各写一份,表现成「revision 读不到、绑定永远不刷新」。
|
|
6
|
+
* 两者都不会报错指向分歧点。所以它们在这里各定义一次。
|
|
7
|
+
*
|
|
8
|
+
* 存储形状(`bindings.json` 的版本与记录)不在这里:它只有 binding 域与它的测试读,
|
|
9
|
+
* 属域内契约,物理定义在 `server/binding/impl/model/type.ts`。
|
|
10
|
+
*/
|
|
11
|
+
/** 宿主路由清单。键名即 `__DSH_ROUTES__` 的键。 */
|
|
12
|
+
export declare const ROUTES: {
|
|
13
|
+
readonly bindings: "/api/dsh-worktree-sidebar/bindings";
|
|
14
|
+
readonly health: "/api/dsh-worktree-sidebar/health";
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* 绑定查询端点的响应体:宿主 handler 写、客户端读取处读,两边都从这里取字段名。
|
|
18
|
+
*
|
|
19
|
+
* 字段名必须是类型而不是值常量——只有类型层才能让「某一端改回旧字面量」当场编译失败。
|
|
20
|
+
*/
|
|
21
|
+
export interface BindingResponse {
|
|
22
|
+
/** 绑定表修订号;客户端以它判定「宿主侧是否已经变了」。 */
|
|
23
|
+
readonly revision: number;
|
|
24
|
+
/** 该会话当前生效的 worktree 根;null 表示按会话 cwd 走。 */
|
|
25
|
+
readonly worktreePath: string | null;
|
|
26
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@wingsky-1/dsh-worktree-sidebar",
|
|
3
|
+
"version": "0.2.5",
|
|
4
|
+
"description": "把某个 git worktree 登记给当前会话,使该会话的官方右侧栏文件树根指向该 worktree;会话 cwd 不变。官方提供原生 worktree 会话能力即退役的过渡适配层。",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "lib/index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./lib/index.d.ts",
|
|
10
|
+
"default": "./lib/index.js"
|
|
11
|
+
},
|
|
12
|
+
"./client": {
|
|
13
|
+
"types": "./lib/client/index.d.ts",
|
|
14
|
+
"default": "./lib/client.js"
|
|
15
|
+
},
|
|
16
|
+
"./package.json": "./package.json"
|
|
17
|
+
},
|
|
18
|
+
"dsh": {
|
|
19
|
+
"bundle": {
|
|
20
|
+
"patch": "./cordis.patch.yml",
|
|
21
|
+
"bannerJs": "import { createRequire as __worktreeSidebarRequire } from \"node:module\";\nconst require = __worktreeSidebarRequire(import.meta.url);"
|
|
22
|
+
},
|
|
23
|
+
"client": {
|
|
24
|
+
"inject": [
|
|
25
|
+
"@deepseek-ai/dsh-client-ui-sidebar-right"
|
|
26
|
+
],
|
|
27
|
+
"platform": "web"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"lib",
|
|
32
|
+
"shared/**/*.d.ts",
|
|
33
|
+
"cordis.patch.yml",
|
|
34
|
+
"README.md",
|
|
35
|
+
"LICENSE"
|
|
36
|
+
],
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@deepseek-ai/cordis": "4.0.2",
|
|
39
|
+
"@deepseek-ai/dsh-agent": "0.1.5-rc.1",
|
|
40
|
+
"@deepseek-ai/dsh-api-workspace-files": "0.1.5-rc.1",
|
|
41
|
+
"@deepseek-ai/dsh-host-webserver": "0.1.5-rc.1",
|
|
42
|
+
"@deepseek-ai/dsh-session": "0.1.5-rc.1",
|
|
43
|
+
"@deepseek-ai/dsh-tools": "0.1.5-rc.1",
|
|
44
|
+
"@deepseek-ai/dsh-typert-protocol": "0.1.5-rc.1"
|
|
45
|
+
},
|
|
46
|
+
"peerDependencies": {
|
|
47
|
+
"@deepseek-ai/cordis": "4.0.2",
|
|
48
|
+
"@deepseek-ai/dsh-agent": "0.1.5-rc.1",
|
|
49
|
+
"@deepseek-ai/dsh-api-workspace-files": "0.1.5-rc.1",
|
|
50
|
+
"@deepseek-ai/dsh-host-webserver": "0.1.5-rc.1",
|
|
51
|
+
"@deepseek-ai/dsh-tools": "0.1.5-rc.1",
|
|
52
|
+
"@deepseek-ai/dsh-typert-protocol": "0.1.5-rc.1"
|
|
53
|
+
},
|
|
54
|
+
"peerDependenciesMeta": {
|
|
55
|
+
"@deepseek-ai/cordis": {
|
|
56
|
+
"optional": true
|
|
57
|
+
},
|
|
58
|
+
"@deepseek-ai/dsh-agent": {
|
|
59
|
+
"optional": true
|
|
60
|
+
},
|
|
61
|
+
"@deepseek-ai/dsh-api-workspace-files": {
|
|
62
|
+
"optional": true
|
|
63
|
+
},
|
|
64
|
+
"@deepseek-ai/dsh-host-webserver": {
|
|
65
|
+
"optional": true
|
|
66
|
+
},
|
|
67
|
+
"@deepseek-ai/dsh-tools": {
|
|
68
|
+
"optional": true
|
|
69
|
+
},
|
|
70
|
+
"@deepseek-ai/dsh-typert-protocol": {
|
|
71
|
+
"optional": true
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
"engines": {
|
|
75
|
+
"node": ">=20.0.0"
|
|
76
|
+
},
|
|
77
|
+
"license": "MIT",
|
|
78
|
+
"repository": {
|
|
79
|
+
"type": "git",
|
|
80
|
+
"url": "https://github.com/wingsky-1/dsh-plugin-hub.git",
|
|
81
|
+
"directory": "packages/dsh-worktree-sidebar"
|
|
82
|
+
},
|
|
83
|
+
"bugs": {
|
|
84
|
+
"url": "https://github.com/wingsky-1/dsh-plugin-hub/issues"
|
|
85
|
+
},
|
|
86
|
+
"keywords": [
|
|
87
|
+
"dsh",
|
|
88
|
+
"deepseek-harness",
|
|
89
|
+
"plugin",
|
|
90
|
+
"worktree",
|
|
91
|
+
"git",
|
|
92
|
+
"sidebar"
|
|
93
|
+
],
|
|
94
|
+
"author": "wingsky-1",
|
|
95
|
+
"publishConfig": {
|
|
96
|
+
"access": "public"
|
|
97
|
+
},
|
|
98
|
+
"scripts": {
|
|
99
|
+
"build": "node ../../scripts/build/clean-lib.ts && tsc -p tsconfig.json && node ../../scripts/build/bundle-host.ts .",
|
|
100
|
+
"test": "node ../../scripts/test/run-vitest.mjs --min 19",
|
|
101
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** ensureStyle 参数(issue #477 v2 契约)。 */
|
|
2
|
+
export interface EnsureStyleOptions {
|
|
3
|
+
/** <style> 幂等键(package-owned 命名空间,统一 `dsh-<pkg>-style` 前缀隔离)。 */
|
|
4
|
+
id: string;
|
|
5
|
+
/** 样式文本(构建期 text-loader 内联的 style.css 内容)。 */
|
|
6
|
+
cssText: string;
|
|
7
|
+
/** 可选版本号:写入 dataset.version;变化时旧节点 remove 后重建(热更新失效)。 */
|
|
8
|
+
version?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 按 id 幂等注入 <style> 节点(5 包统一样式注入入口,issue #477 收敛)。
|
|
13
|
+
*
|
|
14
|
+
* 行为契约:
|
|
15
|
+
* - head 缺失(document.head == null)静默早退 no-op 不抛;
|
|
16
|
+
* - 同 id 已存在且 version 未变(或未传)→ 不动现有节点;version 变化 → 重建;
|
|
17
|
+
* - 无 version 不写 dataset.version;
|
|
18
|
+
* - 返回 disposer(remove 该 id 节点;节点不存在时 no-op),卸载后再次调用
|
|
19
|
+
* ensureStyle 重新注入。
|
|
20
|
+
*/
|
|
21
|
+
export declare function ensureStyle(options: EnsureStyleOptions): () => void;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** 翻译函数形态(与官方 Translate 一致:key + {name} 占位参数)。 */
|
|
2
|
+
export declare type TranslateFn = (key: string, params?: Record<string, unknown>) => string;
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 当前翻译函数(apply 时由各包 index.ts 经 bindLocale 装配)。
|
|
6
|
+
* 未装配时回落 key 本体(行为零变化)。
|
|
7
|
+
*/
|
|
8
|
+
export declare let t: TranslateFn;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 装配/重绑翻译函数(官方 ctx.locale.bind(ns) 产物;语言切换 subscribe 内重调)。
|
|
12
|
+
* @param locale 宿主 locale 服务(须实现 bind)。
|
|
13
|
+
* @param ns 本插件字典命名空间。
|
|
14
|
+
*/
|
|
15
|
+
export declare function bindLocale(locale: any, ns: string): void;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 用户 home 目录(`~` 的展开目标):`HOME` 非空白原样采用,未设置或空白回落
|
|
3
|
+
* `os.homedir()`;Windows 以 `USERPROFILE` 优先(对齐 libuv)。显式读 env 而非
|
|
4
|
+
* 直接 `os.homedir()`,是因为后者读进程级 environ,在 worker_threads(Stryker
|
|
5
|
+
* 的 vitest-runner 强制 `pool: 'threads'`)下无法被测试的 `process.env.HOME` 隔离;
|
|
6
|
+
* 取值次序与 libuv 一致,默认形态逐字节不变。
|
|
7
|
+
*/
|
|
8
|
+
export declare function userHome(): string;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* DSH home 解析(单一事实源):`DSH_HOME` 非空白原样采用;未设置或空白回落
|
|
12
|
+
* `~/.dsh`(默认形态路径逐字节不变)。语义对齐官方
|
|
13
|
+
* `@deepseek-ai/dsh-home-paths#resolveDshHome`(空白 env 视同未设置)。
|
|
14
|
+
*/
|
|
15
|
+
export declare function dshHome(): string;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
import type { LoopbackOptions } from "./loopback.d.ts";
|
|
3
|
+
|
|
4
|
+
/** 写 JSON 响应(统一带 referrer-policy 头,防 referrer 泄露)。 */
|
|
5
|
+
export declare function writeJson(res: ServerResponse, status: number, payload: unknown): void;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 序列化一帧 SSE data 行(输出形如 `data: <json>\n\n`)。
|
|
9
|
+
* undefined / 含换行 payload 行为对齐三包历史实现、非承诺契约(#472)。
|
|
10
|
+
*/
|
|
11
|
+
export declare function sseData(payload: unknown): string;
|
|
12
|
+
|
|
13
|
+
/** 请求体读不出来的具体成因:供端点在失败文案里说清是哪一种。 */
|
|
14
|
+
export type JsonBodyInvalidReason = "too-large" | "unreadable" | "malformed" | "not-object";
|
|
15
|
+
|
|
16
|
+
/** `readJsonBodyOutcome` 的成因可辨结果:缺席(可选 body 未给)/ 合法对象 / 非法(带具体成因)。 */
|
|
17
|
+
export type JsonBodyOutcome =
|
|
18
|
+
| { kind: "absent" }
|
|
19
|
+
| { kind: "json"; value: object }
|
|
20
|
+
| { kind: "invalid"; reason: JsonBodyInvalidReason };
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 读请求 body(JSON)并保留失败成因:需要 fail-closed 的端点用它(畸形 body 不能被当成「没有 body」,
|
|
24
|
+
* 否则有副作用的端点会拿垃圾输入触发真实动作)。
|
|
25
|
+
* @param limit 字节上限(默认 2MB)。
|
|
26
|
+
*/
|
|
27
|
+
export declare function readJsonBodyOutcome(
|
|
28
|
+
req: IncomingMessage,
|
|
29
|
+
limit?: number,
|
|
30
|
+
): Promise<JsonBodyOutcome>;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 宽松读请求 body(JSON):解析失败或超限返回 undefined(不抛错),由调用方决定响应。
|
|
34
|
+
* 成因不可辨——要区分「缺席」与「非法」用 `readJsonBodyOutcome`。
|
|
35
|
+
* @param limit 字节上限(默认 2MB)。
|
|
36
|
+
*/
|
|
37
|
+
export declare function readJsonBody(
|
|
38
|
+
req: IncomingMessage,
|
|
39
|
+
limit?: number,
|
|
40
|
+
): Promise<object | undefined>;
|
|
41
|
+
|
|
42
|
+
/** 把任意抛出的值转成可读错误消息。 */
|
|
43
|
+
export declare function errorMessage(error: unknown): string;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 读请求 body(JSON,限长防滥用)。
|
|
47
|
+
* 兼容 Node 事件流(data/end)与 async-iterator 桩两种形态。
|
|
48
|
+
* @param limit 字节上限(默认 256KB)。
|
|
49
|
+
*/
|
|
50
|
+
export declare function readBody(req: IncomingMessage, limit?: number): Promise<object>;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Loopback + 方法白名单低阶路由守卫:非 loopback → 403,方法不在白名单 → 405,
|
|
54
|
+
* 否则放行(403 先于 405 为本守卫执行顺序,仅对套守卫端点成立)。
|
|
55
|
+
* @param methods 允许的 HTTP 方法白名单。
|
|
56
|
+
* @param loopbackOptions 透传给 isLoopbackRequest 的可选判定参数(#549:仅
|
|
57
|
+
* serve 资源路由放行 cross-site no-cors 子资源时使用;其余路由不得传)。
|
|
58
|
+
* @returns 是否放行(true 时调用方继续处理请求)。
|
|
59
|
+
*/
|
|
60
|
+
export declare function guardLoopbackMethod(
|
|
61
|
+
req: IncomingMessage,
|
|
62
|
+
res: ServerResponse,
|
|
63
|
+
methods: string[],
|
|
64
|
+
loopbackOptions?: LoopbackOptions,
|
|
65
|
+
): boolean;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { IncomingMessage } from "node:http";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Loopback 围栏判定参数。
|
|
5
|
+
* - allowCrossSiteNoCors(#549):仅 serve 类资源伺服路由使用——放行「显式
|
|
6
|
+
* sec-fetch-mode: no-cors」的跨站子资源请求(sandbox iframe opaque origin
|
|
7
|
+
* 的相对路径 css/js/img 加载);cors fetch/XHR 与 navigate 仍拒绝。
|
|
8
|
+
* 普通 /api 路由必须保持默认(不传 options)拒绝一切 cross-site。
|
|
9
|
+
*/
|
|
10
|
+
export interface LoopbackOptions {
|
|
11
|
+
allowCrossSiteNoCors?: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Loopback 围栏:请求必须来自回环地址 + 回环 Host,且满足跨站/来源约束,
|
|
16
|
+
* 否则拒绝(DNS 重绑定 + 跨站防御)。
|
|
17
|
+
*/
|
|
18
|
+
export declare function isLoopbackRequest(
|
|
19
|
+
request: IncomingMessage,
|
|
20
|
+
options?: LoopbackOptions,
|
|
21
|
+
): boolean;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 宿主端「设置命名空间」注册(单一事实源)。
|
|
3
|
+
*
|
|
4
|
+
* 背景与语义见 settings-namespace.js 顶部注释。要点:DSH rc.7 起设置卡槽
|
|
5
|
+
* `settings.plugin.item` 为 keyed,只有在「宿主 serve 的命名空间 ∩ 卡片 key」
|
|
6
|
+
* 非空时渲染;为让卡片显示,宿主端需用此函数把命名空间注册进 settings 服务。
|
|
7
|
+
* 零包依赖,服务面注入,等值复刻官方 `installSettingsSection`(dsh-settings)。
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** `installSettingsNamespace` 的 hooks 面。 */
|
|
11
|
+
export interface SettingsNamespaceHooks {
|
|
12
|
+
/** 把插件对该命名空间的读取来源指向返回的 scope(`scope.get()`)。 */
|
|
13
|
+
setSource(source: () => unknown): void;
|
|
14
|
+
/** 来源切换或命名空间值变化时触发,插件据此刷新/落盘。 */
|
|
15
|
+
onChange(): void;
|
|
16
|
+
/** 可选自定义校验,透传给 settings.register。 */
|
|
17
|
+
validate?: unknown;
|
|
18
|
+
/**
|
|
19
|
+
* 可选;register 返回 owner scope 后立即回调(先于 setSource)。
|
|
20
|
+
* 供存量配置迁移 / 写路径装配使用;settings 服务缺失时不触发。
|
|
21
|
+
*/
|
|
22
|
+
onScope?(scope: unknown, service: unknown): void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 日志兜底:logger 可能确实没有(极端降级),全部可选调用。
|
|
27
|
+
* 单一事实源(#436):notifier / lan-proxy 曾各复刻一份,现统一引用本导出。
|
|
28
|
+
*/
|
|
29
|
+
export declare function warnLog(ctx: unknown, message: string): void;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 注册插件自有 settings 命名空间(服务面注入,无包依赖)。
|
|
33
|
+
* 等值语义参考官方 `installSettingsSection`(@deepseek-ai/dsh-settings@0.1.0-rc.7)。
|
|
34
|
+
* @param ctx - 插件宿主端 apply 收到的 cordis 上下文。
|
|
35
|
+
* @param ns - 插件自有命名空间(小写 kebab,须唯一)。
|
|
36
|
+
* @param schema - schemastery schema,解析该命名空间的值(通常为插件 Config)。
|
|
37
|
+
* @param entry - 组合层配置,作为命名空间的 `base` 层。
|
|
38
|
+
* @param hooks - source 收藏与变更通知。
|
|
39
|
+
*/
|
|
40
|
+
export declare function installSettingsNamespace(
|
|
41
|
+
ctx: unknown,
|
|
42
|
+
ns: string,
|
|
43
|
+
schema: unknown,
|
|
44
|
+
entry: unknown,
|
|
45
|
+
hooks: SettingsNamespaceHooks,
|
|
46
|
+
): void;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { ServerResponse } from "node:http";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 连接表项状态(stalledAt 缺省 = 未 stalled)。
|
|
5
|
+
* failStreak:连续写抛错次数(≥3 ≈ 3 心跳周期判死,对齐 dsh-notifier 原版)。
|
|
6
|
+
*/
|
|
7
|
+
export interface SseConnState {
|
|
8
|
+
/** 连续写抛错次数。 */
|
|
9
|
+
failStreak: number;
|
|
10
|
+
/** 自上次成功写/心跳以来的"写被拒"起始时刻(ms epoch;缺省 = 未 stalled)。 */
|
|
11
|
+
stalledAt?: number;
|
|
12
|
+
/** 注册时刻(ms epoch;maxAge 轮换依据)。 */
|
|
13
|
+
registeredAt: number;
|
|
14
|
+
/** 最近一次成功写时刻(ms epoch;idle 判定依据)。 */
|
|
15
|
+
lastWriteAt: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** 回收/淘汰原因(health 观测 + 调试)。`dispose` = 卸载时统一关连接那一次。 */
|
|
19
|
+
export type SseEvictReason = "close" | "error" | "stalled" | "maxage" | "destroyed" | "dispose";
|
|
20
|
+
|
|
21
|
+
/** evict 原因计数(health 观测:先量化残留构成再调参)。键集与 `SseEvictReason` 一一对应。 */
|
|
22
|
+
export interface SseEvictStats {
|
|
23
|
+
close: number;
|
|
24
|
+
error: number;
|
|
25
|
+
stalled: number;
|
|
26
|
+
maxage: number;
|
|
27
|
+
destroyed: number;
|
|
28
|
+
dispose: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** 连接健康观测:排障用明细,**故意不进 `/health`**——它随连接数增长,
|
|
32
|
+
* 而 `/health` 是常量大小的聚合面且经 lan-proxy 对局域网可见。真要暴露请单开路由。 */
|
|
33
|
+
export interface SseConnHealth {
|
|
34
|
+
/** 连接已存活时长(ms)。 */
|
|
35
|
+
ageMs: number;
|
|
36
|
+
/** 距最近成功写已过去时长(ms;-1 = 从未写过)。 */
|
|
37
|
+
lastWriteAgoMs: number;
|
|
38
|
+
/** 当前 stalled 持续时长(ms;-1 = 未 stalled)。 */
|
|
39
|
+
stalledMs: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** createSseHub 选项。 */
|
|
43
|
+
export interface SseHubOptions {
|
|
44
|
+
/** 心跳间隔(默认 30s;测试注入短值)。 */
|
|
45
|
+
heartbeatMs?: number;
|
|
46
|
+
/** stalled 回收窗口:连续写被拒超过此时长即 evict(默认 90s)。 */
|
|
47
|
+
stalledTimeoutMs?: number;
|
|
48
|
+
/** maxAge 轮换上限(默认 120min;0 = 关闭轮换)。 */
|
|
49
|
+
maxAgeMs?: number;
|
|
50
|
+
/** maxAge 轮换的"空闲"门槛:距最近成功写超过此时长才算空闲(默认 15min)。 */
|
|
51
|
+
idleTimeoutMs?: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* SSE 连接枢纽(共享核心,取代各包自建连接表)。
|
|
56
|
+
* 连接表 + 心跳 + stalled/maxAge 主动回收(#515)。
|
|
57
|
+
* 广播负载生成留调用方:hub 只向全部连接写现成 text 帧并判死收口;
|
|
58
|
+
* ?since 补拉(notifier 路由层行为)不进 hub。
|
|
59
|
+
*/
|
|
60
|
+
export interface SseHub {
|
|
61
|
+
/** 注册一条 SSE 连接:入表 + 挂 close/error 监听。 */
|
|
62
|
+
register(res: ServerResponse): void;
|
|
63
|
+
/** 向全部连接写一帧 text(调用方生成,通常经 sseData());判死收口在内部。 */
|
|
64
|
+
broadcast(text: string): void;
|
|
65
|
+
/** 当前连接数(语义 = 服务端未释放句柄数)。 */
|
|
66
|
+
size(): number;
|
|
67
|
+
/** evict 原因计数(health 观测)。 */
|
|
68
|
+
evictStats(): SseEvictStats;
|
|
69
|
+
/** 连接健康快照(按注册序;排障用明细,不进 `/health`,理由见 `SseConnHealth`)。 */
|
|
70
|
+
connHealth(now?: number): SseConnHealth[];
|
|
71
|
+
/** 停止心跳定时器。 */
|
|
72
|
+
dispose(): void;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** 创建 SSE 枢纽并启动心跳(unref:不阻止进程退出)。 */
|
|
76
|
+
export declare function createSseHub(options: SseHubOptions): SseHub;
|