@yuhere/js-jsonrpc2 0.0.0-stage → 2.1.1

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.
Files changed (174) hide show
  1. package/README.md +277 -2
  2. package/SKILL.md +237 -0
  3. package/dist/cli/cli.d.ts +3 -0
  4. package/dist/cli/cli.d.ts.map +1 -0
  5. package/dist/cli/cli.js +132 -0
  6. package/dist/cli/cli.js.map +1 -0
  7. package/dist/cli/demo-methods.d.ts +10 -0
  8. package/dist/cli/demo-methods.d.ts.map +1 -0
  9. package/dist/cli/demo-methods.js +477 -0
  10. package/dist/cli/demo-methods.js.map +1 -0
  11. package/dist/cli/express.d.ts +2 -0
  12. package/dist/cli/express.d.ts.map +1 -0
  13. package/dist/cli/express.js +42 -0
  14. package/dist/cli/express.js.map +1 -0
  15. package/dist/cli/stdio.d.ts +2 -0
  16. package/dist/cli/stdio.d.ts.map +1 -0
  17. package/dist/cli/stdio.js +5 -0
  18. package/dist/cli/stdio.js.map +1 -0
  19. package/dist/core/Repository.d.ts +24 -0
  20. package/dist/core/Repository.d.ts.map +1 -0
  21. package/dist/core/Repository.js +153 -0
  22. package/dist/core/Repository.js.map +1 -0
  23. package/dist/core/convert.d.ts +4 -0
  24. package/dist/core/convert.d.ts.map +1 -0
  25. package/dist/core/convert.js +29 -0
  26. package/dist/core/convert.js.map +1 -0
  27. package/dist/core/descriptor.d.ts +27 -0
  28. package/dist/core/descriptor.d.ts.map +1 -0
  29. package/dist/core/descriptor.js +83 -0
  30. package/dist/core/descriptor.js.map +1 -0
  31. package/dist/core/index.d.ts +4 -0
  32. package/dist/core/index.d.ts.map +1 -0
  33. package/dist/core/index.js +3 -0
  34. package/dist/core/index.js.map +1 -0
  35. package/dist/core/permission.d.ts +4 -0
  36. package/dist/core/permission.d.ts.map +1 -0
  37. package/dist/core/permission.js +18 -0
  38. package/dist/core/permission.js.map +1 -0
  39. package/dist/core/spec-check.d.ts +3 -0
  40. package/dist/core/spec-check.d.ts.map +1 -0
  41. package/dist/core/spec-check.js +11 -0
  42. package/dist/core/spec-check.js.map +1 -0
  43. package/dist/core/system-methods.d.ts +3 -0
  44. package/dist/core/system-methods.d.ts.map +1 -0
  45. package/dist/core/system-methods.js +76 -0
  46. package/dist/core/system-methods.js.map +1 -0
  47. package/dist/errors/RpcError.d.ts +9 -0
  48. package/dist/errors/RpcError.d.ts.map +1 -0
  49. package/dist/errors/RpcError.js +23 -0
  50. package/dist/errors/RpcError.js.map +1 -0
  51. package/dist/errors/codes.d.ts +7 -0
  52. package/dist/errors/codes.d.ts.map +1 -0
  53. package/dist/errors/codes.js +7 -0
  54. package/dist/errors/codes.js.map +1 -0
  55. package/dist/errors/index.d.ts +3 -0
  56. package/dist/errors/index.d.ts.map +1 -0
  57. package/dist/errors/index.js +3 -0
  58. package/dist/errors/index.js.map +1 -0
  59. package/dist/index.d.ts +18 -0
  60. package/dist/index.d.ts.map +1 -0
  61. package/dist/index.js +17 -0
  62. package/dist/index.js.map +1 -0
  63. package/dist/schema/index.d.ts +4 -0
  64. package/dist/schema/index.d.ts.map +1 -0
  65. package/dist/schema/index.js +4 -0
  66. package/dist/schema/index.js.map +1 -0
  67. package/dist/schema/json/index.d.ts +19 -0
  68. package/dist/schema/json/index.d.ts.map +1 -0
  69. package/dist/schema/json/index.js +6 -0
  70. package/dist/schema/json/index.js.map +1 -0
  71. package/dist/schema/json/marker.d.ts +23 -0
  72. package/dist/schema/json/marker.d.ts.map +1 -0
  73. package/dist/schema/json/marker.js +27 -0
  74. package/dist/schema/json/marker.js.map +1 -0
  75. package/dist/schema/json/primitives.d.ts +202 -0
  76. package/dist/schema/json/primitives.d.ts.map +1 -0
  77. package/dist/schema/json/primitives.js +598 -0
  78. package/dist/schema/json/primitives.js.map +1 -0
  79. package/dist/schema/primitives.d.ts +25 -0
  80. package/dist/schema/primitives.d.ts.map +1 -0
  81. package/dist/schema/primitives.js +153 -0
  82. package/dist/schema/primitives.js.map +1 -0
  83. package/dist/schema/rpc.d.ts +21 -0
  84. package/dist/schema/rpc.d.ts.map +1 -0
  85. package/dist/schema/rpc.js +17 -0
  86. package/dist/schema/rpc.js.map +1 -0
  87. package/dist/schema/z/protocol.d.ts +9 -0
  88. package/dist/schema/z/protocol.d.ts.map +1 -0
  89. package/dist/schema/z/protocol.js +9 -0
  90. package/dist/schema/z/protocol.js.map +1 -0
  91. package/dist/schema/z/register.d.ts +9 -0
  92. package/dist/schema/z/register.d.ts.map +1 -0
  93. package/dist/schema/z/register.js +21 -0
  94. package/dist/schema/z/register.js.map +1 -0
  95. package/dist/stream/index.d.ts +3 -0
  96. package/dist/stream/index.d.ts.map +1 -0
  97. package/dist/stream/index.js +2 -0
  98. package/dist/stream/index.js.map +1 -0
  99. package/dist/stream/stringify-stream.d.ts +15 -0
  100. package/dist/stream/stringify-stream.d.ts.map +1 -0
  101. package/dist/stream/stringify-stream.js +322 -0
  102. package/dist/stream/stringify-stream.js.map +1 -0
  103. package/dist/stream/stringify.d.ts +19 -0
  104. package/dist/stream/stringify.d.ts.map +1 -0
  105. package/dist/stream/stringify.js +217 -0
  106. package/dist/stream/stringify.js.map +1 -0
  107. package/dist/transport/express/body.d.ts +9 -0
  108. package/dist/transport/express/body.d.ts.map +1 -0
  109. package/dist/transport/express/body.js +28 -0
  110. package/dist/transport/express/body.js.map +1 -0
  111. package/dist/transport/express/index.d.ts +3 -0
  112. package/dist/transport/express/index.d.ts.map +1 -0
  113. package/dist/transport/express/index.js +2 -0
  114. package/dist/transport/express/index.js.map +1 -0
  115. package/dist/transport/express/middleware.d.ts +12 -0
  116. package/dist/transport/express/middleware.d.ts.map +1 -0
  117. package/dist/transport/express/middleware.js +73 -0
  118. package/dist/transport/express/middleware.js.map +1 -0
  119. package/dist/transport/express/respond.d.ts +4 -0
  120. package/dist/transport/express/respond.d.ts.map +1 -0
  121. package/dist/transport/express/respond.js +125 -0
  122. package/dist/transport/express/respond.js.map +1 -0
  123. package/dist/transport/stdio/index.d.ts +2 -0
  124. package/dist/transport/stdio/index.d.ts.map +1 -0
  125. package/dist/transport/stdio/index.js +2 -0
  126. package/dist/transport/stdio/index.js.map +1 -0
  127. package/dist/transport/stdio/middleware.d.ts +34 -0
  128. package/dist/transport/stdio/middleware.d.ts.map +1 -0
  129. package/dist/transport/stdio/middleware.js +274 -0
  130. package/dist/transport/stdio/middleware.js.map +1 -0
  131. package/dist/transport/ws/index.d.ts +3 -0
  132. package/dist/transport/ws/index.d.ts.map +1 -0
  133. package/dist/transport/ws/index.js +3 -0
  134. package/dist/transport/ws/index.js.map +1 -0
  135. package/dist/transport/ws/middleware.d.ts +25 -0
  136. package/dist/transport/ws/middleware.d.ts.map +1 -0
  137. package/dist/transport/ws/middleware.js +209 -0
  138. package/dist/transport/ws/middleware.js.map +1 -0
  139. package/dist/transport/ws/standalone.d.ts +22 -0
  140. package/dist/transport/ws/standalone.d.ts.map +1 -0
  141. package/dist/transport/ws/standalone.js +39 -0
  142. package/dist/transport/ws/standalone.js.map +1 -0
  143. package/dist/types.d.ts +75 -0
  144. package/dist/types.d.ts.map +1 -0
  145. package/dist/types.js +2 -0
  146. package/dist/types.js.map +1 -0
  147. package/htdoc/assets/index-CQr-lnlD.js +9286 -0
  148. package/htdoc/assets/index-CQr-lnlD.js.map +1 -0
  149. package/htdoc/assets/index-hcP2-9jw.css +729 -0
  150. package/htdoc/index.html +13 -0
  151. package/package.json +89 -3
  152. package/webui/index.html +12 -0
  153. package/webui/src/App.tsx +56 -0
  154. package/webui/src/api/httpTransport.ts +148 -0
  155. package/webui/src/api/paramForm.ts +79 -0
  156. package/webui/src/api/rpc.ts +57 -0
  157. package/webui/src/api/schema.ts +12 -0
  158. package/webui/src/api/types.ts +16 -0
  159. package/webui/src/api/wsTransport.ts +305 -0
  160. package/webui/src/components/MethodDetail.tsx +43 -0
  161. package/webui/src/components/MethodTree.tsx +118 -0
  162. package/webui/src/components/SchemaForm.tsx +696 -0
  163. package/webui/src/components/SchemaView.tsx +151 -0
  164. package/webui/src/components/TestPanel.tsx +279 -0
  165. package/webui/src/components/Toolbar.tsx +70 -0
  166. package/webui/src/hooks/useMethodDetail.ts +61 -0
  167. package/webui/src/hooks/useMethods.ts +27 -0
  168. package/webui/src/main.tsx +13 -0
  169. package/webui/src/styles/geek.css +729 -0
  170. package/webui/src/theme/ThemeProvider.tsx +79 -0
  171. package/webui/src/vite-env.d.ts +1 -0
  172. package/webui/tsconfig.json +34 -0
  173. package/webui/tsconfig.node.json +13 -0
  174. package/webui/vite.config.ts +15 -0
package/package.json CHANGED
@@ -1,6 +1,92 @@
1
1
  {
2
2
  "name": "@yuhere/js-jsonrpc2",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "2.1.1",
4
+ "description": "A TypeScript JSON-RPC 2.0 server library with schema validation, authorization, dependency injection, and multiple transports.",
5
+ "homepage": "https://github.com/yuhere/js-jsonrpc2#readme",
6
+ "license": "MIT",
7
+ "engines": {
8
+ "node": ">=20"
9
+ },
10
+ "author": {
11
+ "name": "yuhere@gmail.com"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/yuhere/js-jsonrpc2/issues"
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "htdoc",
19
+ "webui",
20
+ "dev",
21
+ "SKILL.md",
22
+ "README.md"
23
+ ],
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "https://github.com/yuhere/js-jsonrpc2.git"
27
+ },
28
+ "type": "module",
29
+ "main": "./dist/index.cjs",
30
+ "module": "./dist/index.js",
31
+ "types": "./dist/index.d.ts",
32
+ "exports": {
33
+ ".": {
34
+ "types": "./dist/index.d.ts",
35
+ "import": "./dist/index.js"
36
+ },
37
+ "./package.json": "./package.json"
38
+ },
39
+ "bin": {
40
+ "js-jsonrpc2": "./dist/cli.js"
41
+ },
42
+ "scripts": {
43
+ "build": "npm run build:lib && npm run build:webui",
44
+ "build:lib": "tsc -p tsconfig.build.json",
45
+ "build:webui": "tsc -p webui//tsconfig.json && vite build --config webui/vite.config.ts",
46
+ "dev": "run-p dev:lib dev:webui",
47
+ "dev:lib": "tsx src/cli/express.ts",
48
+ "dev:webui": "vite build --config webui/vite.config.ts --watch --mode development",
49
+ "typecheck": "npm run typecheck:lib && npm run typecheck:webui",
50
+ "typecheck:lib": "tsc --noEmit",
51
+ "typecheck:webui": "tsc -p webui/tsconfig.json --noEmit",
52
+ "test": "vitest run",
53
+ "test:watch": "vitest",
54
+ "test:coverage": "vitest run --coverage",
55
+ "clean": "rm -rf dist htdoc/assets htdoc/index.html"
56
+ },
57
+ "keywords": [
58
+ "json-rpc",
59
+ "jsonrpc",
60
+ "jsonrpc2",
61
+ "express",
62
+ "middleware",
63
+ "typescript",
64
+ "zod"
65
+ ],
66
+ "devDependencies": {
67
+ "@types/body-parser": "^1.19.6",
68
+ "@types/express": "^5.0.6",
69
+ "@types/node": "^26.6.5",
70
+ "@types/react": "^18.3.12",
71
+ "@types/react-dom": "^18.3.1",
72
+ "@types/supertest": "^7.2.1",
73
+ "@types/type-is": "^1.6.7",
74
+ "@types/ws": "^8.18.2",
75
+ "@vitejs/plugin-react": "^6.1.2",
76
+ "@vitest/coverage-v8": "^5.0.5",
77
+ "express": "^5.3.0",
78
+ "npm-run-all": "^4.1.5",
79
+ "react": "^18.3.1",
80
+ "react-dom": "^18.3.1",
81
+ "supertest": "^7.3.1",
82
+ "tsx": "^4.23.15",
83
+ "typescript": "^7.0.2",
84
+ "vite": "^8.3.5",
85
+ "vitest": "^5.0.5",
86
+ "ws": "^8.22.0"
87
+ },
88
+ "dependencies": {
89
+ "body-parser": "^2.3.0",
90
+ "zod": "^4.6.7"
91
+ }
6
92
  }
@@ -0,0 +1,12 @@
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>
@@ -0,0 +1,56 @@
1
+ import { 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 { Toolbar } from './components/Toolbar';
6
+ import { MethodTree } from './components/MethodTree';
7
+ import { MethodDetail } from './components/MethodDetail';
8
+ import { TestPanel } from './components/TestPanel';
9
+
10
+ const ENDPOINT = '';
11
+ // 如需强制指定 WS 地址,可改为例如 'ws://localhost:3000/'
12
+ // 默认由 wsTransport 从 window.location 推导。
13
+
14
+ export default function App() {
15
+ const [transport, setTransport] = useState<TransportKind>('http');
16
+ const [selected, setSelected] = useState('');
17
+
18
+ const rpc = useMemo(() => {
19
+ return createRpc({ transport, endpoint: ENDPOINT });
20
+ }, [transport]);
21
+
22
+ useEffect(() => {
23
+ return () => rpc.close();
24
+ }, [rpc]);
25
+
26
+ const { methods, loading, error, refresh } = useMethods(rpc);
27
+ const detail = useMethodDetail(rpc, selected);
28
+
29
+ return (
30
+ <div className="app">
31
+ <Toolbar
32
+ transport={transport}
33
+ onTransportChange={setTransport}
34
+ onRefresh={refresh}
35
+ loading={loading}
36
+ error={error}
37
+ />
38
+ <main>
39
+ <aside className="sidebar">
40
+ <h2>Methods</h2>
41
+ <MethodTree methods={methods} selected={selected} onSelect={setSelected} />
42
+ </aside>
43
+ <div className="content">
44
+ <MethodDetail method={selected} detail={detail} />
45
+ <TestPanel
46
+ method={selected}
47
+ detail={detail}
48
+ rpc={rpc}
49
+ transport={transport}
50
+ endpoint={ENDPOINT}
51
+ />
52
+ </div>
53
+ </main>
54
+ </div>
55
+ );
56
+ }
@@ -0,0 +1,148 @@
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
+ }
@@ -0,0 +1,79 @@
1
+ import type { SchemaDescription } from './schema';
2
+
3
+ /** 判断是否为合法 schema 描述(kind 为字符串) */
4
+ export function isSchema(v: unknown): v is SchemaDescription {
5
+ return !!v && typeof v === 'object' && typeof (v as SchemaDescription).kind === 'string';
6
+ }
7
+
8
+ /**
9
+ * 根据 schema 生成一个合理的示例值。
10
+ * - 原始类型返回零值(''、0、false)
11
+ * - literal 返回固定值
12
+ * - enum 返回第一个枚举值
13
+ * - nullable 返回 null
14
+ * - 容器类型递归展开
15
+ * - unknown 返回 null
16
+ */
17
+ export function sampleFromSchema(schema: SchemaDescription): unknown {
18
+ if (schema.nullable) return null;
19
+ const c = schema.children ?? {};
20
+ switch (schema.kind) {
21
+ case 'string':
22
+ return '';
23
+ case 'number':
24
+ return 0;
25
+ case 'boolean':
26
+ return false;
27
+ case 'literal':
28
+ return schema.constraints?.value ?? null;
29
+ case 'enum': {
30
+ const values = schema.constraints?.values;
31
+ return Array.isArray(values) && values.length > 0 ? values[0] : '';
32
+ }
33
+ case 'nullable':
34
+ return isSchema(c.inner) ? sampleFromSchema(c.inner) : null;
35
+ case 'array': {
36
+ const item = isSchema(c.items) ? sampleFromSchema(c.items) : null;
37
+ return [item];
38
+ }
39
+ case 'tuple': {
40
+ const items = Array.isArray(c.items) ? c.items : [];
41
+ return items.map((it) => (isSchema(it) ? sampleFromSchema(it) : null));
42
+ }
43
+ case 'union': {
44
+ const options = Array.isArray(c.options) ? c.options : [];
45
+ return options.length > 0 && isSchema(options[0])
46
+ ? sampleFromSchema(options[0] as SchemaDescription)
47
+ : null;
48
+ }
49
+ case 'object': {
50
+ const props = (c.properties ?? {}) as Record<string, unknown>;
51
+ const out: Record<string, unknown> = {};
52
+ for (const [k, v] of Object.entries(props)) {
53
+ if (isSchema(v)) out[k] = sampleFromSchema(v);
54
+ }
55
+ return out;
56
+ }
57
+ case 'record': {
58
+ const val = isSchema(c.values) ? sampleFromSchema(c.values) : null;
59
+ return { key: val };
60
+ }
61
+ default:
62
+ return null;
63
+ }
64
+ }
65
+
66
+ /** 是否用原生 input 编辑(否则走 JSON textarea / 递归容器) */
67
+ export function isScalarSchema(schema: SchemaDescription): boolean {
68
+ if (schema.nullable) return true;
69
+ switch (schema.kind) {
70
+ case 'string':
71
+ case 'number':
72
+ case 'boolean':
73
+ case 'enum':
74
+ case 'literal':
75
+ return true;
76
+ default:
77
+ return false;
78
+ }
79
+ }
@@ -0,0 +1,57 @@
1
+ import type { RPCErrorPayload } from './types';
2
+ import { createHttpRpc, type HttpRpcOptions } from './httpTransport';
3
+ import { createWsRpc, type WsRpcOptions } from './wsTransport';
4
+
5
+ export type TransportKind = 'http' | 'ws';
6
+
7
+ export type CallResult<T> = Promise<T> | AsyncGenerator<T, void, unknown>;
8
+
9
+ export interface RpcClient {
10
+ call<T = unknown>(method: string, ...params: unknown[]): CallResult<T>;
11
+ close(): void;
12
+ }
13
+
14
+ export interface RpcOptions {
15
+ transport: TransportKind;
16
+ endpoint?: string;
17
+ headers?: Record<string, string>;
18
+ fetchImpl?: typeof fetch;
19
+ connectTimeoutMs?: number;
20
+ callTimeoutMs?: number;
21
+ }
22
+
23
+ export class RPCError extends Error {
24
+ constructor(public payload: RPCErrorPayload) {
25
+ super(payload.message);
26
+ this.name = 'RPCError';
27
+ }
28
+
29
+ get code(): number { return this.payload.code; }
30
+ get data(): unknown { return this.payload.data; }
31
+ }
32
+
33
+ export function isStreamResult<T>(v: CallResult<T>): v is AsyncGenerator<T, void, unknown> {
34
+ return (
35
+ !!v &&
36
+ typeof v === 'object' &&
37
+ typeof (v as AsyncGenerator<T>)[Symbol.asyncIterator] === 'function'
38
+ );
39
+ }
40
+
41
+ export function createRpc(options: RpcOptions): RpcClient {
42
+ if (options.transport === 'ws') {
43
+ const wsOpts: WsRpcOptions = {
44
+ endpoint: options.endpoint,
45
+ connectTimeoutMs: options.connectTimeoutMs,
46
+ callTimeoutMs: options.callTimeoutMs,
47
+ };
48
+ return createWsRpc(wsOpts);
49
+ }
50
+
51
+ const httpOpts: HttpRpcOptions = {
52
+ endpoint: options.endpoint,
53
+ headers: options.headers,
54
+ fetchImpl: options.fetchImpl,
55
+ };
56
+ return createHttpRpc(httpOpts);
57
+ }
@@ -0,0 +1,12 @@
1
+ export type JSONPrimitive = string | number | boolean | null;
2
+ export type JSONValue = JSONPrimitive | JSONValue[] | { [key: string]: JSONValue };
3
+
4
+ export type SchemaKind = string;
5
+
6
+ export interface SchemaDescription {
7
+ kind: SchemaKind;
8
+ description?: string;
9
+ nullable?: boolean;
10
+ constraints?: Record<string, unknown>;
11
+ children?: Record<string, unknown>;
12
+ }
@@ -0,0 +1,16 @@
1
+ export interface RPCErrorPayload {
2
+ code: number;
3
+ message: string;
4
+ data?: unknown;
5
+ }
6
+
7
+ export interface RpcResponse<T = unknown> {
8
+ jsonrpc: '2.0';
9
+ id: number | string | null;
10
+ result?: T;
11
+ error?: RPCErrorPayload;
12
+ }
13
+
14
+ export interface Signature {
15
+ [index: number]: string | string[];
16
+ }