@yuhere/js-jsonrpc2 2.1.11 → 2.1.13
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/dist/cli/cli.d.ts +0 -1
- package/dist/cli/cli.js +0 -1
- package/dist/cli/demo-methods.d.ts +0 -1
- package/dist/cli/demo-methods.js +0 -1
- package/dist/cli/express.d.ts +0 -1
- package/dist/cli/express.js +0 -1
- package/dist/cli/stdio.d.ts +0 -1
- package/dist/cli/stdio.js +0 -1
- package/dist/core/Repository.d.ts +0 -1
- package/dist/core/Repository.js +0 -1
- package/dist/core/convert.d.ts +0 -1
- package/dist/core/convert.js +0 -1
- package/dist/core/descriptor.d.ts +0 -1
- package/dist/core/descriptor.js +0 -1
- package/dist/core/index.d.ts +0 -1
- package/dist/core/index.js +0 -1
- package/dist/core/llm-tools.d.ts +0 -1
- package/dist/core/llm-tools.js +89 -3
- package/dist/core/permission.d.ts +0 -1
- package/dist/core/permission.js +0 -1
- package/dist/core/spec-check.d.ts +0 -1
- package/dist/core/spec-check.js +0 -1
- package/dist/core/system-methods.d.ts +0 -1
- package/dist/core/system-methods.js +12 -7
- package/dist/errors/RPCError.d.ts +0 -1
- package/dist/errors/RPCError.js +0 -1
- package/dist/errors/codes.d.ts +0 -1
- package/dist/errors/codes.js +0 -1
- package/dist/errors/index.d.ts +0 -1
- package/dist/errors/index.js +0 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/metrics/memory.d.ts +0 -1
- package/dist/metrics/memory.js +0 -1
- package/dist/metrics/prom.d.ts +0 -1
- package/dist/metrics/prom.js +0 -1
- package/dist/schema/json/index.d.ts +0 -1
- package/dist/schema/json/index.js +0 -1
- package/dist/schema/json/marker.d.ts +0 -1
- package/dist/schema/json/marker.js +0 -1
- package/dist/schema/json/primitives.d.ts +0 -1
- package/dist/schema/json/primitives.js +0 -1
- package/dist/schema/z/protocol.d.ts +0 -1
- package/dist/schema/z/protocol.js +0 -1
- package/dist/schema/z/register.d.ts +0 -1
- package/dist/schema/z/register.js +0 -1
- package/dist/stream/stringify-stream.d.ts +0 -1
- package/dist/stream/stringify-stream.js +94 -70
- package/dist/transport/express/body.d.ts +0 -1
- package/dist/transport/express/body.js +0 -1
- package/dist/transport/express/index.d.ts +0 -1
- package/dist/transport/express/index.js +0 -1
- package/dist/transport/express/middleware.d.ts +0 -1
- package/dist/transport/express/middleware.js +8 -6
- package/dist/transport/express/respond.d.ts +0 -1
- package/dist/transport/express/respond.js +0 -1
- package/dist/transport/stdio/index.d.ts +0 -1
- package/dist/transport/stdio/index.js +0 -1
- package/dist/transport/stdio/middleware.d.ts +0 -1
- package/dist/transport/stdio/middleware.js +7 -5
- package/dist/transport/ws/index.d.ts +0 -1
- package/dist/transport/ws/index.js +0 -1
- package/dist/transport/ws/middleware.d.ts +0 -1
- package/dist/transport/ws/middleware.js +7 -5
- package/dist/transport/ws/standalone.d.ts +0 -1
- package/dist/transport/ws/standalone.js +0 -1
- package/dist/types.d.ts +0 -1
- package/dist/types.js +0 -1
- package/dist/webclient/webui/src/client/httpTransport.d.ts +8 -0
- package/dist/webclient/webui/src/client/httpTransport.js +114 -0
- package/dist/webclient/webui/src/client/index.d.ts +4 -0
- package/dist/webclient/webui/src/client/index.js +2 -0
- package/dist/webclient/webui/src/client/rpc.d.ts +24 -0
- package/dist/webclient/webui/src/client/rpc.js +32 -0
- package/dist/webclient/webui/src/client/types.d.ts +14 -0
- package/dist/webclient/webui/src/client/types.js +1 -0
- package/dist/webclient/webui/src/client/wsTransport.d.ts +8 -0
- package/{webui/src/api/wsTransport.ts → dist/webclient/webui/src/client/wsTransport.js} +263 -305
- package/htdoc/assets/favicon-CXdFuLlv.png +0 -0
- package/htdoc/assets/{index-Cr0Jnrgb.js → index-CqI7vRAS.js} +1191 -327
- package/htdoc/assets/index-CqI7vRAS.js.map +1 -0
- package/htdoc/assets/{index-Ck-39Igh.css → index-Rq1yIwo2.css} +58 -0
- package/htdoc/index.html +4 -2
- package/package.json +10 -7
- package/dist/cli/cli.d.ts.map +0 -1
- package/dist/cli/cli.js.map +0 -1
- package/dist/cli/demo-methods.d.ts.map +0 -1
- package/dist/cli/demo-methods.js.map +0 -1
- package/dist/cli/express.d.ts.map +0 -1
- package/dist/cli/express.js.map +0 -1
- package/dist/cli/stdio.d.ts.map +0 -1
- package/dist/cli/stdio.js.map +0 -1
- package/dist/core/Repository.d.ts.map +0 -1
- package/dist/core/Repository.js.map +0 -1
- package/dist/core/convert.d.ts.map +0 -1
- package/dist/core/convert.js.map +0 -1
- package/dist/core/descriptor.d.ts.map +0 -1
- package/dist/core/descriptor.js.map +0 -1
- package/dist/core/index.d.ts.map +0 -1
- package/dist/core/index.js.map +0 -1
- package/dist/core/llm-tools.d.ts.map +0 -1
- package/dist/core/llm-tools.js.map +0 -1
- package/dist/core/permission.d.ts.map +0 -1
- package/dist/core/permission.js.map +0 -1
- package/dist/core/spec-check.d.ts.map +0 -1
- package/dist/core/spec-check.js.map +0 -1
- package/dist/core/system-methods.d.ts.map +0 -1
- package/dist/core/system-methods.js.map +0 -1
- package/dist/errors/RPCError.d.ts.map +0 -1
- package/dist/errors/RPCError.js.map +0 -1
- package/dist/errors/codes.d.ts.map +0 -1
- package/dist/errors/codes.js.map +0 -1
- package/dist/errors/index.d.ts.map +0 -1
- package/dist/errors/index.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/metrics/memory.d.ts.map +0 -1
- package/dist/metrics/memory.js.map +0 -1
- package/dist/metrics/prom.d.ts.map +0 -1
- package/dist/metrics/prom.js.map +0 -1
- package/dist/schema/json/index.d.ts.map +0 -1
- package/dist/schema/json/index.js.map +0 -1
- package/dist/schema/json/marker.d.ts.map +0 -1
- package/dist/schema/json/marker.js.map +0 -1
- package/dist/schema/json/primitives.d.ts.map +0 -1
- package/dist/schema/json/primitives.js.map +0 -1
- package/dist/schema/z/protocol.d.ts.map +0 -1
- package/dist/schema/z/protocol.js.map +0 -1
- package/dist/schema/z/register.d.ts.map +0 -1
- package/dist/schema/z/register.js.map +0 -1
- package/dist/stream/stringify-stream.d.ts.map +0 -1
- package/dist/stream/stringify-stream.js.map +0 -1
- package/dist/transport/express/body.d.ts.map +0 -1
- package/dist/transport/express/body.js.map +0 -1
- package/dist/transport/express/index.d.ts.map +0 -1
- package/dist/transport/express/index.js.map +0 -1
- package/dist/transport/express/middleware.d.ts.map +0 -1
- package/dist/transport/express/middleware.js.map +0 -1
- package/dist/transport/express/respond.d.ts.map +0 -1
- package/dist/transport/express/respond.js.map +0 -1
- package/dist/transport/stdio/index.d.ts.map +0 -1
- package/dist/transport/stdio/index.js.map +0 -1
- package/dist/transport/stdio/middleware.d.ts.map +0 -1
- package/dist/transport/stdio/middleware.js.map +0 -1
- package/dist/transport/ws/index.d.ts.map +0 -1
- package/dist/transport/ws/index.js.map +0 -1
- package/dist/transport/ws/middleware.d.ts.map +0 -1
- package/dist/transport/ws/middleware.js.map +0 -1
- package/dist/transport/ws/standalone.d.ts.map +0 -1
- package/dist/transport/ws/standalone.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
- package/htdoc/assets/index-Cr0Jnrgb.js.map +0 -1
- package/webui/index.html +0 -12
- package/webui/src/App.tsx +0 -164
- package/webui/src/api/docGen.ts +0 -188
- package/webui/src/api/glob.ts +0 -90
- package/webui/src/api/httpTransport.ts +0 -148
- package/webui/src/api/metrics.ts +0 -135
- package/webui/src/api/paramForm.ts +0 -80
- package/webui/src/api/rpc.ts +0 -57
- package/webui/src/api/schema.ts +0 -12
- package/webui/src/api/types.ts +0 -16
- package/webui/src/components/MethodDetail.tsx +0 -43
- package/webui/src/components/MethodTree.tsx +0 -118
- package/webui/src/components/MetricsPanel.md +0 -126
- package/webui/src/components/MetricsPanel.tsx +0 -290
- package/webui/src/components/SchemaForm.tsx +0 -697
- package/webui/src/components/SchemaView.tsx +0 -151
- package/webui/src/components/TestPanel.tsx +0 -322
- package/webui/src/components/Toolbar.tsx +0 -82
- package/webui/src/hooks/useMethodDetail.ts +0 -61
- package/webui/src/hooks/useMethods.ts +0 -27
- package/webui/src/hooks/useMetrics.ts +0 -91
- package/webui/src/main.tsx +0 -13
- package/webui/src/styles/geek.css +0 -963
- package/webui/src/theme/ThemeProvider.tsx +0 -79
- package/webui/src/vite-env.d.ts +0 -1
- package/webui/tsconfig.json +0 -34
- package/webui/tsconfig.node.json +0 -13
- package/webui/vite.config.ts +0 -15
package/webui/index.html
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="zh-CN">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8" />
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
-
<title>JSON-RPC 2.0</title>
|
|
7
|
-
</head>
|
|
8
|
-
<body>
|
|
9
|
-
<div id="root"></div>
|
|
10
|
-
<script type="module" src="/src/main.tsx"></script>
|
|
11
|
-
</body>
|
|
12
|
-
</html>
|
package/webui/src/App.tsx
DELETED
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
2
|
-
import { createRpc, type TransportKind } from './api/rpc';
|
|
3
|
-
import { useMethods } from './hooks/useMethods';
|
|
4
|
-
import { useMethodDetail } from './hooks/useMethodDetail';
|
|
5
|
-
import { useMetrics } from './hooks/useMetrics';
|
|
6
|
-
import { Toolbar } from './components/Toolbar';
|
|
7
|
-
import { MethodTree } from './components/MethodTree';
|
|
8
|
-
import { MethodDetail } from './components/MethodDetail';
|
|
9
|
-
import { TestPanel } from './components/TestPanel';
|
|
10
|
-
import { MetricsPanel } from './components/MetricsPanel';
|
|
11
|
-
import { compileGlobFilter, filterSnapshotMethods } from './api/glob';
|
|
12
|
-
import type { MetricsSnapshot } from './api/metrics';
|
|
13
|
-
import {
|
|
14
|
-
collectApiDoc,
|
|
15
|
-
downloadText,
|
|
16
|
-
renderMarkdown,
|
|
17
|
-
} from './api/docGen';
|
|
18
|
-
|
|
19
|
-
const ENDPOINT = '';
|
|
20
|
-
type View = 'methods' | 'metrics';
|
|
21
|
-
|
|
22
|
-
export default function App() {
|
|
23
|
-
const [transport, setTransport] = useState<TransportKind>('http');
|
|
24
|
-
const [selected, setSelected] = useState('');
|
|
25
|
-
const [generatingDocs, setGeneratingDocs] = useState(false);
|
|
26
|
-
const [view, setView] = useState<View>('methods');
|
|
27
|
-
const [methodFilter, setMethodFilter] = useState('');
|
|
28
|
-
|
|
29
|
-
const rpc = useMemo(() => createRpc({ transport, endpoint: ENDPOINT }), [transport]);
|
|
30
|
-
|
|
31
|
-
useEffect(() => () => rpc.close(), [rpc]);
|
|
32
|
-
|
|
33
|
-
const { methods, loading, error, refresh } = useMethods(rpc);
|
|
34
|
-
const detail = useMethodDetail(rpc, selected);
|
|
35
|
-
const metrics = useMetrics(rpc, view === 'metrics');
|
|
36
|
-
|
|
37
|
-
const compiledFilter = useMemo(() => compileGlobFilter(methodFilter), [methodFilter]);
|
|
38
|
-
|
|
39
|
-
// 过滤后的快照:不改变拉取范围,只裁剪 methods。
|
|
40
|
-
const filteredSnapshot = useMemo<MetricsSnapshot | undefined>(() => {
|
|
41
|
-
if (!metrics.snapshot) return undefined;
|
|
42
|
-
if (compiledFilter.passthrough) return metrics.snapshot;
|
|
43
|
-
return {
|
|
44
|
-
...metrics.snapshot,
|
|
45
|
-
methods: filterSnapshotMethods(compiledFilter, metrics.snapshot.methods) as MetricsSnapshot['methods'],
|
|
46
|
-
};
|
|
47
|
-
}, [metrics.snapshot, compiledFilter]);
|
|
48
|
-
|
|
49
|
-
const generateDocs = useCallback(async () => {
|
|
50
|
-
if (methods.length === 0) {
|
|
51
|
-
alert('方法列表为空,请先刷新方法列表。');
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
setGeneratingDocs(true);
|
|
55
|
-
try {
|
|
56
|
-
const doc = await collectApiDoc(rpc, methods);
|
|
57
|
-
const md = renderMarkdown(doc);
|
|
58
|
-
const ts = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
|
59
|
-
downloadText(`jsonrpc-api-doc-${ts}.md`, md, 'text/markdown');
|
|
60
|
-
} catch (e) {
|
|
61
|
-
alert(`生成文档失败:${e instanceof Error ? e.message : String(e)}`);
|
|
62
|
-
} finally {
|
|
63
|
-
setGeneratingDocs(false);
|
|
64
|
-
}
|
|
65
|
-
}, [rpc, methods]);
|
|
66
|
-
|
|
67
|
-
return (
|
|
68
|
-
<div className="app">
|
|
69
|
-
<Toolbar
|
|
70
|
-
transport={transport}
|
|
71
|
-
onTransportChange={setTransport}
|
|
72
|
-
onRefresh={refresh}
|
|
73
|
-
loading={loading}
|
|
74
|
-
error={error}
|
|
75
|
-
onGenerateDocs={generateDocs}
|
|
76
|
-
generatingDocs={generatingDocs}
|
|
77
|
-
/>
|
|
78
|
-
<main>
|
|
79
|
-
<aside className="sidebar">
|
|
80
|
-
<div className="view-switch segmented" role="group" aria-label="视图">
|
|
81
|
-
<button
|
|
82
|
-
type="button"
|
|
83
|
-
className={view === 'methods' ? 'active' : ''}
|
|
84
|
-
onClick={() => setView('methods')}
|
|
85
|
-
>
|
|
86
|
-
Methods
|
|
87
|
-
</button>
|
|
88
|
-
<button
|
|
89
|
-
type="button"
|
|
90
|
-
className={view === 'metrics' ? 'active' : ''}
|
|
91
|
-
onClick={() => setView('metrics')}
|
|
92
|
-
>
|
|
93
|
-
Metrics
|
|
94
|
-
</button>
|
|
95
|
-
</div>
|
|
96
|
-
|
|
97
|
-
{view === 'methods' ? (
|
|
98
|
-
<>
|
|
99
|
-
<h2>Methods</h2>
|
|
100
|
-
<MethodTree methods={methods} selected={selected} onSelect={setSelected} />
|
|
101
|
-
</>
|
|
102
|
-
) : (
|
|
103
|
-
<div className="metrics-sidebar">
|
|
104
|
-
<h2>Metrics</h2>
|
|
105
|
-
<p className="hint" style={{ margin: '0 0 8px' }}>
|
|
106
|
-
📊 服务端 RPC 调用统计。
|
|
107
|
-
</p>
|
|
108
|
-
<label className="metrics-filter">
|
|
109
|
-
<span className="metrics-filter-label">Filter</span>
|
|
110
|
-
<input
|
|
111
|
-
className="sf-input"
|
|
112
|
-
type="text"
|
|
113
|
-
placeholder="user.*, !system.*"
|
|
114
|
-
value={methodFilter}
|
|
115
|
-
onChange={(e) => setMethodFilter(e.target.value)}
|
|
116
|
-
spellCheck={false}
|
|
117
|
-
/>
|
|
118
|
-
</label>
|
|
119
|
-
<p className="hint metrics-filter-help">
|
|
120
|
-
支持 <code>*</code> / <code>?</code> / <code>!</code>,多个模式用 <code>,</code> 分隔。
|
|
121
|
-
<br />
|
|
122
|
-
例:<code>user.*</code>、<code>*.get</code>、<code>!system.*</code>、<code>user.*,!user.secret</code>
|
|
123
|
-
</p>
|
|
124
|
-
{metrics.snapshot && (
|
|
125
|
-
<p className="hint metrics-filter-stat">
|
|
126
|
-
匹配 {Object.keys(filteredSnapshot?.methods ?? {}).length} /{' '}
|
|
127
|
-
{Object.keys(metrics.snapshot.methods).length} 个方法
|
|
128
|
-
</p>
|
|
129
|
-
)}
|
|
130
|
-
</div>
|
|
131
|
-
)}
|
|
132
|
-
</aside>
|
|
133
|
-
<div className="content">
|
|
134
|
-
{view === 'methods' ? (
|
|
135
|
-
<>
|
|
136
|
-
<MethodDetail method={selected} detail={detail} />
|
|
137
|
-
<TestPanel
|
|
138
|
-
method={selected}
|
|
139
|
-
detail={detail}
|
|
140
|
-
rpc={rpc}
|
|
141
|
-
transport={transport}
|
|
142
|
-
endpoint={ENDPOINT}
|
|
143
|
-
/>
|
|
144
|
-
</>
|
|
145
|
-
) : (
|
|
146
|
-
<MetricsPanel
|
|
147
|
-
snapshot={filteredSnapshot}
|
|
148
|
-
previous={metrics.previous}
|
|
149
|
-
fetchedAt={metrics.fetchedAt}
|
|
150
|
-
loading={metrics.loading}
|
|
151
|
-
error={metrics.error}
|
|
152
|
-
available={metrics.available}
|
|
153
|
-
onRefresh={() => void metrics.refresh()}
|
|
154
|
-
autoRefresh={metrics.autoRefresh}
|
|
155
|
-
onAutoRefreshChange={metrics.setAutoRefresh}
|
|
156
|
-
intervalMs={metrics.intervalMs}
|
|
157
|
-
onIntervalChange={metrics.setIntervalMs}
|
|
158
|
-
/>
|
|
159
|
-
)}
|
|
160
|
-
</div>
|
|
161
|
-
</main>
|
|
162
|
-
</div>
|
|
163
|
-
);
|
|
164
|
-
}
|
package/webui/src/api/docGen.ts
DELETED
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
import type { RpcClient } from './rpc';
|
|
2
|
-
import type { SchemaDescription } from './schema';
|
|
3
|
-
|
|
4
|
-
export interface MethodDoc {
|
|
5
|
-
name: string;
|
|
6
|
-
help: string;
|
|
7
|
-
signature: SchemaDescription[];
|
|
8
|
-
error?: string;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export interface ApiDoc {
|
|
12
|
-
generatedAt: string;
|
|
13
|
-
methods: MethodDoc[];
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function isSchema(v: unknown): v is SchemaDescription {
|
|
17
|
-
return !!v && typeof v === 'object' && typeof (v as SchemaDescription).kind === 'string';
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/** 逐个方法拉取 help + signature */
|
|
21
|
-
export async function collectApiDoc(
|
|
22
|
-
rpc: RpcClient,
|
|
23
|
-
methods: string[],
|
|
24
|
-
onProgress?: (done: number, total: number) => void,
|
|
25
|
-
): Promise<ApiDoc> {
|
|
26
|
-
const out: MethodDoc[] = [];
|
|
27
|
-
for (let i = 0; i < methods.length; i++) {
|
|
28
|
-
const name = methods[i];
|
|
29
|
-
try {
|
|
30
|
-
const [help, sig] = await Promise.all([
|
|
31
|
-
rpc.call<string>('system.methodHelp', name).catch(() => ''),
|
|
32
|
-
rpc.call<unknown[]>('system.methodSignature', name).catch(() => []),
|
|
33
|
-
]);
|
|
34
|
-
out.push({
|
|
35
|
-
name,
|
|
36
|
-
help: typeof help === 'string' ? help : '',
|
|
37
|
-
signature: Array.isArray(sig) ? sig.filter(isSchema) : [],
|
|
38
|
-
});
|
|
39
|
-
} catch (e) {
|
|
40
|
-
out.push({
|
|
41
|
-
name,
|
|
42
|
-
help: '',
|
|
43
|
-
signature: [],
|
|
44
|
-
error: e instanceof Error ? e.message : String(e),
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
onProgress?.(i + 1, methods.length);
|
|
48
|
-
}
|
|
49
|
-
return { generatedAt: new Date().toISOString(), methods: out };
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// ─────────────────────────────────────────────────────────────
|
|
53
|
-
// Markdown 渲染
|
|
54
|
-
// ─────────────────────────────────────────────────────────────
|
|
55
|
-
|
|
56
|
-
function renderSchema(schema: SchemaDescription): string {
|
|
57
|
-
// 简单的单行描述:kind + constraints + nullable
|
|
58
|
-
let s = schema.kind;
|
|
59
|
-
if (schema.nullable) s += ' | null';
|
|
60
|
-
const c = schema.constraints;
|
|
61
|
-
if (c && Object.keys(c).length > 0) {
|
|
62
|
-
const parts = Object.entries(c)
|
|
63
|
-
.filter(([, v]) => v !== undefined)
|
|
64
|
-
.map(([k, v]) => `${k}=${JSON.stringify(v)}`);
|
|
65
|
-
if (parts.length) s += ` { ${parts.join(', ')} }`;
|
|
66
|
-
}
|
|
67
|
-
return s;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function renderSchemaTree(schema: SchemaDescription, indent = 0): string[] {
|
|
71
|
-
const pad = ' '.repeat(indent);
|
|
72
|
-
const lines: string[] = [];
|
|
73
|
-
let head = `${pad}- \`${schema.kind}\``;
|
|
74
|
-
if (schema.nullable) head += ' _(nullable)_';
|
|
75
|
-
if (schema.description) head += ` — ${schema.description}`;
|
|
76
|
-
const c = schema.constraints;
|
|
77
|
-
if (c && Object.keys(c).length > 0) {
|
|
78
|
-
const parts = Object.entries(c)
|
|
79
|
-
.filter(([, v]) => v !== undefined)
|
|
80
|
-
.map(([k, v]) => `${k}=${JSON.stringify(v)}`);
|
|
81
|
-
if (parts.length) head += ` \`{ ${parts.join(', ')} }\``;
|
|
82
|
-
}
|
|
83
|
-
lines.push(head);
|
|
84
|
-
|
|
85
|
-
const ch = schema.children ?? {};
|
|
86
|
-
const pushChild = (label: string, child: SchemaDescription) => {
|
|
87
|
-
const sub = renderSchemaTree(child, indent + 1);
|
|
88
|
-
if (sub.length) {
|
|
89
|
-
sub[0] = `${' '.repeat(indent + 1)}- **${label}**: ${sub[0].trim().replace(/^- /, '')}`;
|
|
90
|
-
lines.push(...sub);
|
|
91
|
-
}
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
if (isSchema(ch.inner)) pushChild('inner', ch.inner);
|
|
95
|
-
if (Array.isArray(ch.items)) {
|
|
96
|
-
(ch.items as unknown[]).forEach((it, i) => {
|
|
97
|
-
if (isSchema(it)) pushChild(`[${i}]`, it);
|
|
98
|
-
});
|
|
99
|
-
} else if (isSchema(ch.items)) {
|
|
100
|
-
pushChild('items', ch.items);
|
|
101
|
-
}
|
|
102
|
-
if (Array.isArray(ch.options)) {
|
|
103
|
-
(ch.options as unknown[]).forEach((opt, i) => {
|
|
104
|
-
if (isSchema(opt)) pushChild(`option[${i}]`, opt);
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
if (ch.properties && typeof ch.properties === 'object') {
|
|
108
|
-
for (const [k, v] of Object.entries(ch.properties)) {
|
|
109
|
-
if (isSchema(v)) pushChild(k, v);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
if (isSchema(ch.values)) pushChild('values', ch.values);
|
|
113
|
-
|
|
114
|
-
return lines;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
export function renderMarkdown(doc: ApiDoc): string {
|
|
118
|
-
const lines: string[] = [];
|
|
119
|
-
lines.push('# JSON-RPC API 文档');
|
|
120
|
-
lines.push('');
|
|
121
|
-
lines.push(`> 生成时间:${doc.generatedAt}`);
|
|
122
|
-
lines.push(`> 方法总数:${doc.methods.length}`);
|
|
123
|
-
lines.push('');
|
|
124
|
-
lines.push('## 目录');
|
|
125
|
-
lines.push('');
|
|
126
|
-
for (const m of doc.methods) {
|
|
127
|
-
const anchor = m.name.toLowerCase().replace(/[^\w]+/g, '-');
|
|
128
|
-
lines.push(`- [\`${m.name}\`](#${anchor})`);
|
|
129
|
-
}
|
|
130
|
-
lines.push('');
|
|
131
|
-
|
|
132
|
-
for (const m of doc.methods) {
|
|
133
|
-
lines.push(`## \`${m.name}\``);
|
|
134
|
-
lines.push('');
|
|
135
|
-
if (m.error) {
|
|
136
|
-
lines.push(`> ⚠️ 获取失败:${m.error}`);
|
|
137
|
-
lines.push('');
|
|
138
|
-
continue;
|
|
139
|
-
}
|
|
140
|
-
lines.push('**帮助**');
|
|
141
|
-
lines.push('');
|
|
142
|
-
lines.push(m.help ? m.help : '_(无帮助信息)_');
|
|
143
|
-
lines.push('');
|
|
144
|
-
|
|
145
|
-
if (m.signature.length === 0) {
|
|
146
|
-
lines.push('**签名**:_(无)_');
|
|
147
|
-
lines.push('');
|
|
148
|
-
continue;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
const [ret, ...params] = m.signature;
|
|
152
|
-
lines.push('**返回类型**');
|
|
153
|
-
lines.push('');
|
|
154
|
-
lines.push(...renderSchemaTree(ret, 0));
|
|
155
|
-
lines.push('');
|
|
156
|
-
lines.push(`**参数**(${params.length} 个)`);
|
|
157
|
-
lines.push('');
|
|
158
|
-
if (params.length === 0) {
|
|
159
|
-
lines.push('_(无参数)_');
|
|
160
|
-
} else {
|
|
161
|
-
params.forEach((p, i) => {
|
|
162
|
-
const sub = renderSchemaTree(p, 0);
|
|
163
|
-
if (sub.length) {
|
|
164
|
-
sub[0] = `- **参数 [${i}]**: ${sub[0].trim().replace(/^- /, '')}`;
|
|
165
|
-
lines.push(...sub);
|
|
166
|
-
}
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
lines.push('');
|
|
170
|
-
lines.push('---');
|
|
171
|
-
lines.push('');
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
return lines.join('\n');
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/** 触发浏览器下载 */
|
|
178
|
-
export function downloadText(filename: string, text: string, mime = 'text/plain') {
|
|
179
|
-
const blob = new Blob([text], { type: `${mime};charset=utf-8` });
|
|
180
|
-
const url = URL.createObjectURL(blob);
|
|
181
|
-
const a = document.createElement('a');
|
|
182
|
-
a.href = url;
|
|
183
|
-
a.download = filename;
|
|
184
|
-
document.body.appendChild(a);
|
|
185
|
-
a.click();
|
|
186
|
-
document.body.removeChild(a);
|
|
187
|
-
setTimeout(() => URL.revokeObjectURL(url), 1000);
|
|
188
|
-
}
|
package/webui/src/api/glob.ts
DELETED
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 极简 glob 匹配 + 多模式过滤。
|
|
3
|
-
*
|
|
4
|
-
* 语法:
|
|
5
|
-
* - `*` 任意字符(含 `.`)
|
|
6
|
-
* - `?` 单个字符
|
|
7
|
-
* - `**` 与 `*` 等价(兼容常见 glob 方言)
|
|
8
|
-
* - `!pat` 取反;多个 `!` 之间是「且」
|
|
9
|
-
* - 多个模式用 `,` 分隔;正向模式之间是「或」,取反模式与整体是「且」
|
|
10
|
-
* - 空串或纯空白 → 不过滤
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
export interface CompiledFilter {
|
|
14
|
-
/** 正向模式(OR);为空表示「全部」。 */
|
|
15
|
-
includes: RegExp[];
|
|
16
|
-
/** 取反模式(AND NOT)。 */
|
|
17
|
-
excludes: RegExp[];
|
|
18
|
-
/** 原始输入,便于回显。 */
|
|
19
|
-
raw: string;
|
|
20
|
-
/** 是否等价于「不过滤」。 */
|
|
21
|
-
passthrough: boolean;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function globToRegExp(glob: string): RegExp {
|
|
25
|
-
let re = '^';
|
|
26
|
-
for (let i = 0; i < glob.length; i++) {
|
|
27
|
-
const ch = glob[i]!;
|
|
28
|
-
if (ch === '*') {
|
|
29
|
-
// 吞掉连续 *(** 视为 *)
|
|
30
|
-
while (i + 1 < glob.length && glob[i + 1] === '*') i++;
|
|
31
|
-
re += '.*';
|
|
32
|
-
} else if (ch === '?') {
|
|
33
|
-
re += '.';
|
|
34
|
-
} else if ('\\^$+{}()|[]'.includes(ch)) {
|
|
35
|
-
re += '\\' + ch;
|
|
36
|
-
} else {
|
|
37
|
-
re += ch;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
re += '$';
|
|
41
|
-
return new RegExp(re);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export function compileGlobFilter(input: string): CompiledFilter {
|
|
45
|
-
const raw = input ?? '';
|
|
46
|
-
const trimmed = raw.trim();
|
|
47
|
-
if (trimmed.length === 0) {
|
|
48
|
-
return { includes: [], excludes: [], raw, passthrough: true };
|
|
49
|
-
}
|
|
50
|
-
const includes: RegExp[] = [];
|
|
51
|
-
const excludes: RegExp[] = [];
|
|
52
|
-
for (const part of trimmed.split(',')) {
|
|
53
|
-
const pat = part.trim();
|
|
54
|
-
if (pat.length === 0) continue;
|
|
55
|
-
if (pat.startsWith('!')) {
|
|
56
|
-
const body = pat.slice(1).trim();
|
|
57
|
-
if (body.length > 0) excludes.push(globToRegExp(body));
|
|
58
|
-
} else {
|
|
59
|
-
includes.push(globToRegExp(pat));
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
// 只有 !pat 时,includes 为空 → 视为「从全部里排除」
|
|
63
|
-
return { includes, excludes, raw, passthrough: false };
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export function matchMethod(filter: CompiledFilter, method: string): boolean {
|
|
67
|
-
if (filter.passthrough) return true;
|
|
68
|
-
if (filter.excludes.some((re) => re.test(method))) return false;
|
|
69
|
-
if (filter.includes.length === 0) return true; // 只有 !pat 时,保留所有未排除的
|
|
70
|
-
return filter.includes.some((re) => re.test(method));
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/** 对一组 method 名做过滤;返回过滤后的列表(保持原顺序)。 */
|
|
74
|
-
export function filterMethods<T extends string>(filter: CompiledFilter, methods: T[]): T[] {
|
|
75
|
-
if (filter.passthrough) return methods;
|
|
76
|
-
return methods.filter((m) => matchMethod(filter, m));
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/** 便捷:直接对 MetricsSnapshot 的 methods 做过滤,返回浅拷贝。 */
|
|
80
|
-
export function filterSnapshotMethods(
|
|
81
|
-
filter: CompiledFilter,
|
|
82
|
-
methods: Record<string, unknown>,
|
|
83
|
-
): Record<string, unknown> {
|
|
84
|
-
if (filter.passthrough) return methods;
|
|
85
|
-
const out: Record<string, unknown> = {};
|
|
86
|
-
for (const [method, stats] of Object.entries(methods)) {
|
|
87
|
-
if (matchMethod(filter, method)) out[method] = stats;
|
|
88
|
-
}
|
|
89
|
-
return out;
|
|
90
|
-
}
|
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
import type { RpcResponse } from './types';
|
|
2
|
-
import {
|
|
3
|
-
RPCError,
|
|
4
|
-
type CallResult,
|
|
5
|
-
type RpcClient,
|
|
6
|
-
} from './rpc';
|
|
7
|
-
|
|
8
|
-
export interface HttpRpcOptions {
|
|
9
|
-
endpoint?: string;
|
|
10
|
-
headers?: Record<string, string>;
|
|
11
|
-
fetchImpl?: typeof fetch;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
let idCounter = 0;
|
|
15
|
-
|
|
16
|
-
interface StreamFrame {
|
|
17
|
-
jsonrpc: '2.0';
|
|
18
|
-
id: number | string;
|
|
19
|
-
done: boolean;
|
|
20
|
-
index?: number;
|
|
21
|
-
value?: unknown;
|
|
22
|
-
error?: { code: number; message: string };
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
async function readLine(
|
|
26
|
-
reader: ReadableStreamDefaultReader<Uint8Array>,
|
|
27
|
-
decoder: TextDecoder,
|
|
28
|
-
state: { buf: string },
|
|
29
|
-
): Promise<string | undefined> {
|
|
30
|
-
while (true) {
|
|
31
|
-
const nl = state.buf.indexOf('\n');
|
|
32
|
-
if (nl >= 0) {
|
|
33
|
-
const line = state.buf.slice(0, nl).trim();
|
|
34
|
-
state.buf = state.buf.slice(nl + 1);
|
|
35
|
-
if (line) return line;
|
|
36
|
-
continue;
|
|
37
|
-
}
|
|
38
|
-
const { done, value } = await reader.read();
|
|
39
|
-
if (done) {
|
|
40
|
-
const trailing = state.buf.trim();
|
|
41
|
-
state.buf = '';
|
|
42
|
-
return trailing.length > 0 ? trailing : undefined;
|
|
43
|
-
}
|
|
44
|
-
state.buf += decoder.decode(value, { stream: true });
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function isStreamFrame(v: unknown): v is StreamFrame {
|
|
49
|
-
return !!v && typeof v === 'object' && typeof (v as StreamFrame).done === 'boolean';
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export function createHttpRpc(options: HttpRpcOptions = {}): RpcClient {
|
|
53
|
-
const {
|
|
54
|
-
endpoint = '',
|
|
55
|
-
headers = {},
|
|
56
|
-
fetchImpl = fetch,
|
|
57
|
-
} = options;
|
|
58
|
-
|
|
59
|
-
function call<T = unknown>(method: string, ...params: unknown[]): CallResult<T> {
|
|
60
|
-
const id = ++idCounter;
|
|
61
|
-
const body = { jsonrpc: '2.0', method, params, id };
|
|
62
|
-
|
|
63
|
-
return (async () => {
|
|
64
|
-
const res = await fetchImpl(endpoint, {
|
|
65
|
-
method: 'POST',
|
|
66
|
-
headers: {
|
|
67
|
-
'Content-Type': 'application/json',
|
|
68
|
-
'Accept': 'application/x-ndjson',
|
|
69
|
-
...headers,
|
|
70
|
-
},
|
|
71
|
-
body: JSON.stringify(body),
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
if (!res.ok) {
|
|
75
|
-
throw new Error(`HTTP ${res.status} ${res.statusText}`);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
if (!res.body) {
|
|
79
|
-
const payload = (await res.json()) as RpcResponse<T>;
|
|
80
|
-
if (payload.error) throw new RPCError(payload.error);
|
|
81
|
-
return payload.result as T;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const reader = res.body.getReader();
|
|
85
|
-
const decoder = new TextDecoder();
|
|
86
|
-
const state = { buf: '' };
|
|
87
|
-
|
|
88
|
-
const firstLine = await readLine(reader, decoder, state);
|
|
89
|
-
if (firstLine === undefined) {
|
|
90
|
-
await reader.cancel().catch(() => { /* ignore */ });
|
|
91
|
-
throw new Error(`Empty response for ${method}`);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
let first: StreamFrame | RpcResponse<T>;
|
|
95
|
-
try {
|
|
96
|
-
first = JSON.parse(firstLine);
|
|
97
|
-
} catch (e) {
|
|
98
|
-
await reader.cancel().catch(() => { /* ignore */ });
|
|
99
|
-
throw new Error(
|
|
100
|
-
`Invalid JSON response for ${method}: ${e instanceof Error ? e.message : String(e)}`,
|
|
101
|
-
);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if (!isStreamFrame(first)) {
|
|
105
|
-
const payload = first as RpcResponse<T>;
|
|
106
|
-
await reader.cancel().catch(() => { /* ignore */ });
|
|
107
|
-
if (payload.error) throw new RPCError(payload.error);
|
|
108
|
-
return payload.result as T;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
return (async function* (): AsyncGenerator<T, void, unknown> {
|
|
112
|
-
let drained = false;
|
|
113
|
-
try {
|
|
114
|
-
let chunk = first;
|
|
115
|
-
while (true) {
|
|
116
|
-
if (chunk.error) throw new RPCError(chunk.error);
|
|
117
|
-
if (chunk.done) { drained = true; return; }
|
|
118
|
-
if (chunk.value !== undefined) yield chunk.value as T;
|
|
119
|
-
const line = await readLine(reader, decoder, state);
|
|
120
|
-
if (line === undefined) {
|
|
121
|
-
throw new Error('Stream ended without terminal frame');
|
|
122
|
-
}
|
|
123
|
-
chunk = JSON.parse(line);
|
|
124
|
-
}
|
|
125
|
-
} finally {
|
|
126
|
-
if (!drained) {
|
|
127
|
-
await reader.cancel().catch(() => { /* ignore */ });
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
})();
|
|
131
|
-
})();
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
return {
|
|
135
|
-
call,
|
|
136
|
-
close() {
|
|
137
|
-
},
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
export function absoluteEndpoint(endpoint?: string): string {
|
|
142
|
-
const raw = endpoint && endpoint.trim().length > 0 ? endpoint : '/';
|
|
143
|
-
try {
|
|
144
|
-
return new URL(raw, window.location.href).toString();
|
|
145
|
-
} catch {
|
|
146
|
-
return raw;
|
|
147
|
-
}
|
|
148
|
-
}
|