@yuhere/js-jsonrpc2 0.0.0-stage → 2.1.2
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/README.md +277 -2
- package/SKILL.md +237 -0
- package/dist/cli/cli.d.ts +3 -0
- package/dist/cli/cli.d.ts.map +1 -0
- package/dist/cli/cli.js +132 -0
- package/dist/cli/cli.js.map +1 -0
- package/dist/cli/demo-methods.d.ts +10 -0
- package/dist/cli/demo-methods.d.ts.map +1 -0
- package/dist/cli/demo-methods.js +477 -0
- package/dist/cli/demo-methods.js.map +1 -0
- package/dist/cli/express.d.ts +2 -0
- package/dist/cli/express.d.ts.map +1 -0
- package/dist/cli/express.js +42 -0
- package/dist/cli/express.js.map +1 -0
- package/dist/cli/stdio.d.ts +2 -0
- package/dist/cli/stdio.d.ts.map +1 -0
- package/dist/cli/stdio.js +5 -0
- package/dist/cli/stdio.js.map +1 -0
- package/dist/core/Repository.d.ts +24 -0
- package/dist/core/Repository.d.ts.map +1 -0
- package/dist/core/Repository.js +153 -0
- package/dist/core/Repository.js.map +1 -0
- package/dist/core/convert.d.ts +4 -0
- package/dist/core/convert.d.ts.map +1 -0
- package/dist/core/convert.js +29 -0
- package/dist/core/convert.js.map +1 -0
- package/dist/core/descriptor.d.ts +27 -0
- package/dist/core/descriptor.d.ts.map +1 -0
- package/dist/core/descriptor.js +83 -0
- package/dist/core/descriptor.js.map +1 -0
- package/dist/core/index.d.ts +4 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +3 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/permission.d.ts +4 -0
- package/dist/core/permission.d.ts.map +1 -0
- package/dist/core/permission.js +18 -0
- package/dist/core/permission.js.map +1 -0
- package/dist/core/spec-check.d.ts +3 -0
- package/dist/core/spec-check.d.ts.map +1 -0
- package/dist/core/spec-check.js +11 -0
- package/dist/core/spec-check.js.map +1 -0
- package/dist/core/system-methods.d.ts +3 -0
- package/dist/core/system-methods.d.ts.map +1 -0
- package/dist/core/system-methods.js +76 -0
- package/dist/core/system-methods.js.map +1 -0
- package/dist/errors/RpcError.d.ts +9 -0
- package/dist/errors/RpcError.d.ts.map +1 -0
- package/dist/errors/RpcError.js +23 -0
- package/dist/errors/RpcError.js.map +1 -0
- package/dist/errors/codes.d.ts +7 -0
- package/dist/errors/codes.d.ts.map +1 -0
- package/dist/errors/codes.js +7 -0
- package/dist/errors/codes.js.map +1 -0
- package/dist/errors/index.d.ts +3 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +3 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/schema/index.d.ts +4 -0
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/schema/index.js +4 -0
- package/dist/schema/index.js.map +1 -0
- package/dist/schema/json/index.d.ts +19 -0
- package/dist/schema/json/index.d.ts.map +1 -0
- package/dist/schema/json/index.js +6 -0
- package/dist/schema/json/index.js.map +1 -0
- package/dist/schema/json/marker.d.ts +23 -0
- package/dist/schema/json/marker.d.ts.map +1 -0
- package/dist/schema/json/marker.js +27 -0
- package/dist/schema/json/marker.js.map +1 -0
- package/dist/schema/json/primitives.d.ts +202 -0
- package/dist/schema/json/primitives.d.ts.map +1 -0
- package/dist/schema/json/primitives.js +598 -0
- package/dist/schema/json/primitives.js.map +1 -0
- package/dist/schema/primitives.d.ts +25 -0
- package/dist/schema/primitives.d.ts.map +1 -0
- package/dist/schema/primitives.js +153 -0
- package/dist/schema/primitives.js.map +1 -0
- package/dist/schema/rpc.d.ts +21 -0
- package/dist/schema/rpc.d.ts.map +1 -0
- package/dist/schema/rpc.js +17 -0
- package/dist/schema/rpc.js.map +1 -0
- package/dist/schema/z/protocol.d.ts +9 -0
- package/dist/schema/z/protocol.d.ts.map +1 -0
- package/dist/schema/z/protocol.js +9 -0
- package/dist/schema/z/protocol.js.map +1 -0
- package/dist/schema/z/register.d.ts +9 -0
- package/dist/schema/z/register.d.ts.map +1 -0
- package/dist/schema/z/register.js +21 -0
- package/dist/schema/z/register.js.map +1 -0
- package/dist/stream/index.d.ts +3 -0
- package/dist/stream/index.d.ts.map +1 -0
- package/dist/stream/index.js +2 -0
- package/dist/stream/index.js.map +1 -0
- package/dist/stream/stringify-stream.d.ts +15 -0
- package/dist/stream/stringify-stream.d.ts.map +1 -0
- package/dist/stream/stringify-stream.js +322 -0
- package/dist/stream/stringify-stream.js.map +1 -0
- package/dist/stream/stringify.d.ts +19 -0
- package/dist/stream/stringify.d.ts.map +1 -0
- package/dist/stream/stringify.js +217 -0
- package/dist/stream/stringify.js.map +1 -0
- package/dist/transport/express/body.d.ts +9 -0
- package/dist/transport/express/body.d.ts.map +1 -0
- package/dist/transport/express/body.js +28 -0
- package/dist/transport/express/body.js.map +1 -0
- package/dist/transport/express/index.d.ts +3 -0
- package/dist/transport/express/index.d.ts.map +1 -0
- package/dist/transport/express/index.js +2 -0
- package/dist/transport/express/index.js.map +1 -0
- package/dist/transport/express/middleware.d.ts +12 -0
- package/dist/transport/express/middleware.d.ts.map +1 -0
- package/dist/transport/express/middleware.js +73 -0
- package/dist/transport/express/middleware.js.map +1 -0
- package/dist/transport/express/respond.d.ts +4 -0
- package/dist/transport/express/respond.d.ts.map +1 -0
- package/dist/transport/express/respond.js +125 -0
- package/dist/transport/express/respond.js.map +1 -0
- package/dist/transport/stdio/index.d.ts +2 -0
- package/dist/transport/stdio/index.d.ts.map +1 -0
- package/dist/transport/stdio/index.js +2 -0
- package/dist/transport/stdio/index.js.map +1 -0
- package/dist/transport/stdio/middleware.d.ts +34 -0
- package/dist/transport/stdio/middleware.d.ts.map +1 -0
- package/dist/transport/stdio/middleware.js +274 -0
- package/dist/transport/stdio/middleware.js.map +1 -0
- package/dist/transport/ws/index.d.ts +3 -0
- package/dist/transport/ws/index.d.ts.map +1 -0
- package/dist/transport/ws/index.js +3 -0
- package/dist/transport/ws/index.js.map +1 -0
- package/dist/transport/ws/middleware.d.ts +25 -0
- package/dist/transport/ws/middleware.d.ts.map +1 -0
- package/dist/transport/ws/middleware.js +209 -0
- package/dist/transport/ws/middleware.js.map +1 -0
- package/dist/transport/ws/standalone.d.ts +22 -0
- package/dist/transport/ws/standalone.d.ts.map +1 -0
- package/dist/transport/ws/standalone.js +39 -0
- package/dist/transport/ws/standalone.js.map +1 -0
- package/dist/types.d.ts +75 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/htdoc/assets/index-CQr-lnlD.js +9286 -0
- package/htdoc/assets/index-CQr-lnlD.js.map +1 -0
- package/htdoc/assets/index-hcP2-9jw.css +729 -0
- package/htdoc/index.html +13 -0
- package/package.json +89 -3
- package/webui/index.html +12 -0
- package/webui/src/App.tsx +56 -0
- package/webui/src/api/httpTransport.ts +148 -0
- package/webui/src/api/paramForm.ts +79 -0
- package/webui/src/api/rpc.ts +57 -0
- package/webui/src/api/schema.ts +12 -0
- package/webui/src/api/types.ts +16 -0
- package/webui/src/api/wsTransport.ts +305 -0
- package/webui/src/components/MethodDetail.tsx +43 -0
- package/webui/src/components/MethodTree.tsx +118 -0
- package/webui/src/components/SchemaForm.tsx +696 -0
- package/webui/src/components/SchemaView.tsx +151 -0
- package/webui/src/components/TestPanel.tsx +279 -0
- package/webui/src/components/Toolbar.tsx +70 -0
- package/webui/src/hooks/useMethodDetail.ts +61 -0
- package/webui/src/hooks/useMethods.ts +27 -0
- package/webui/src/main.tsx +13 -0
- package/webui/src/styles/geek.css +729 -0
- package/webui/src/theme/ThemeProvider.tsx +79 -0
- package/webui/src/vite-env.d.ts +1 -0
- package/webui/tsconfig.json +34 -0
- package/webui/tsconfig.node.json +13 -0
- package/webui/vite.config.ts +15 -0
package/README.md
CHANGED
|
@@ -1,3 +1,278 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @yuhere/js-jsonrpc2
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A TypeScript JSON-RPC 2.0 server library with schema validation, authorization, dependency injection, and multiple transports.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
### Deviations from the JSON-RPC 2.0 spec
|
|
8
|
+
|
|
9
|
+
- **No named parameters.** `params` must be a positional array (or `null`).
|
|
10
|
+
- **No notifications.** Every request must carry an `id`.
|
|
11
|
+
- **No batch requests.** A batch payload is rejected with `INVALID_REQUEST`.
|
|
12
|
+
|
|
13
|
+
### Extensions
|
|
14
|
+
|
|
15
|
+
- **Permission checks.** Declare `grantTo` on a method and provide a `perm_check`
|
|
16
|
+
injectable; calls without permission fail with `PERMISSION_DENIED (-32604)`.
|
|
17
|
+
- **`system.*` methods.** `system.listMethods`, `system.methodSignature`,
|
|
18
|
+
`system.methodHelp` are registered automatically.
|
|
19
|
+
- **Global error transform.** Map arbitrary thrown errors to `RPCError` via the
|
|
20
|
+
`errorTransform` option.
|
|
21
|
+
- **Streaming JSON serializer.** Responses are serialized with a streaming
|
|
22
|
+
stringifier that supports `Promise`, `Generator` / `AsyncGenerator`, and
|
|
23
|
+
`Buffer` (internal; not exported from the package root).
|
|
24
|
+
|
|
25
|
+
## Install
|
|
26
|
+
|
|
27
|
+
```shell
|
|
28
|
+
npm install @yuhere/js-jsonrpc2
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
`express` is a peer dependency (v4 or v5). Node.js 18+ is required.
|
|
32
|
+
|
|
33
|
+
## Quick start
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import express from 'express';
|
|
37
|
+
import { createRepository, createExpressRouter, s } from '@yuhere/js-jsonrpc2';
|
|
38
|
+
|
|
39
|
+
const repository = createRepository({ withSystemMethods: true });
|
|
40
|
+
|
|
41
|
+
repository.register(
|
|
42
|
+
{
|
|
43
|
+
namespace: 'add',
|
|
44
|
+
document: 'addition of 2 numbers',
|
|
45
|
+
signature: [s.number(), s.number(), s.number()],
|
|
46
|
+
},
|
|
47
|
+
(a: number, b: number) => a + b,
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
const app = express();
|
|
51
|
+
app.use('/', createExpressRouter(repository, {}));
|
|
52
|
+
app.listen(5000);
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Calling it:
|
|
56
|
+
|
|
57
|
+
```shell
|
|
58
|
+
curl -s http://localhost:5000/ -H 'Content-Type: application/json' \
|
|
59
|
+
-d '{"jsonrpc":"2.0","method":"add","params":[1,2],"id":1}'
|
|
60
|
+
# => {"jsonrpc":"2.0","id":1,"result":3}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Registering methods
|
|
64
|
+
|
|
65
|
+
`Repository.register(descriptor, handler)` takes a descriptor and a handler.
|
|
66
|
+
The descriptor has three fields:
|
|
67
|
+
|
|
68
|
+
| Field | Type | Meaning |
|
|
69
|
+
| --- | --- | --- |
|
|
70
|
+
| `namespace` | `string` | Method name (non-empty). |
|
|
71
|
+
| `document` | `string \| (d) => string \| Promise<string>` | Optional help text for `system.methodHelp`. |
|
|
72
|
+
| `grantTo` | `string[]` | Optional list of permissions required to call the method. |
|
|
73
|
+
| `signature` | `[returnSchema, ...parameterSchema[]]` | Return type first, then parameters. |
|
|
74
|
+
|
|
75
|
+
`signature[0]` is the return schema (`s.Schema` or `s.void()`).
|
|
76
|
+
Every later slot is either a `s.Schema` (client-supplied) or
|
|
77
|
+
`s.injectable(key)` (server-injected). Injectable slots may appear at any
|
|
78
|
+
position except index `0`.
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
import { s } from '@yuhere/js-jsonrpc2';
|
|
82
|
+
|
|
83
|
+
repository.register(
|
|
84
|
+
{
|
|
85
|
+
namespace: 'whoami',
|
|
86
|
+
document: 'returns the caller user agent',
|
|
87
|
+
signature: [
|
|
88
|
+
s.object({ userAgent: s.string().nullable() }),
|
|
89
|
+
s.injectable('request'),
|
|
90
|
+
],
|
|
91
|
+
},
|
|
92
|
+
(req: { headers: Record<string, string> }) => ({
|
|
93
|
+
userAgent: req.headers['user-agent'] ?? null,
|
|
94
|
+
}),
|
|
95
|
+
);
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Schemas
|
|
99
|
+
|
|
100
|
+
Schemas are built with the `s` factory. They validate at runtime and derive
|
|
101
|
+
TypeScript types.
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
s.string() // string
|
|
105
|
+
s.string().min(1).max(64) // length-constrained string
|
|
106
|
+
s.number() // finite number
|
|
107
|
+
s.boolean() // boolean
|
|
108
|
+
s.literal('2.0') // exact literal
|
|
109
|
+
s.array(s.string()) // string[]
|
|
110
|
+
s.object({ id: s.number() }) // open object
|
|
111
|
+
s.object({ id: s.number() }).strict() // all props required, no extras
|
|
112
|
+
s.record(s.number()) // Record<string, number>
|
|
113
|
+
s.union([s.string(), s.number()])
|
|
114
|
+
s.tuple([s.string(), s.number()])
|
|
115
|
+
s.enum(['pending', 'success', 'failed'])
|
|
116
|
+
s.unknown() // any JSON value
|
|
117
|
+
s.void() // RPC-only: no return value
|
|
118
|
+
s.injectable('request') // RPC-only: server-injected parameter
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Chainable modifiers: `.min()`, `.max()`, `.nullable()`, `.strict()`,
|
|
122
|
+
`.describe(text)`. Validation: `.parse(input)` (throws
|
|
123
|
+
`SchemaValidationError`) and `.safeParse(input)` (returns a result object).
|
|
124
|
+
Introspection: `.toJSON()` returns a `SchemaDescription`.
|
|
125
|
+
|
|
126
|
+
## Transports
|
|
127
|
+
|
|
128
|
+
### Express
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
import { createExpressRouter } from '@yuhere/js-jsonrpc2';
|
|
132
|
+
|
|
133
|
+
app.use('/', createExpressRouter(repository, {
|
|
134
|
+
bodyLimit: '1mb',
|
|
135
|
+
useHtdoc: true,
|
|
136
|
+
mkInjectable: (req, res, repo) => ({ request: req, response: res, repository: repo }),
|
|
137
|
+
errorTransform: (error) => /* return RPCError | undefined */ undefined,
|
|
138
|
+
}));
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
The router handles `POST /` only. `useHtdoc` serves the bundled web UI
|
|
142
|
+
(static assets under `htdoc/`).
|
|
143
|
+
|
|
144
|
+
### WebSocket
|
|
145
|
+
|
|
146
|
+
Attach to an existing HTTP server:
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
import { createServer } from 'node:http';
|
|
150
|
+
import { attachWebSocket } from '@yuhere/js-jsonrpc2';
|
|
151
|
+
|
|
152
|
+
const server = createServer(app);
|
|
153
|
+
attachWebSocket(server, repository, {
|
|
154
|
+
path: '/',
|
|
155
|
+
mkInjectable: (ws, req, repo) => ({ socket: ws, request: req, repository: repo }),
|
|
156
|
+
});
|
|
157
|
+
server.listen(5000);
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Or create a standalone server:
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
import { createWebSocketServer } from '@yuhere/js-jsonrpc2';
|
|
164
|
+
|
|
165
|
+
const { server, wss, close } = await createWebSocketServer({
|
|
166
|
+
port: 8080,
|
|
167
|
+
host: '127.0.0.1',
|
|
168
|
+
});
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### stdio (NDJSON)
|
|
172
|
+
|
|
173
|
+
```ts
|
|
174
|
+
import { serveStdio } from '@yuhere/js-jsonrpc2';
|
|
175
|
+
|
|
176
|
+
await serveStdio({ repository, framing: 'lines' });
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
`framing: 'lines'` reads one JSON value per line; `framing: 'json'` accepts
|
|
180
|
+
concatenated JSON values.
|
|
181
|
+
|
|
182
|
+
## Invoking directly
|
|
183
|
+
|
|
184
|
+
`Repository.invoke(input, options?)` returns an `RPCResponse`
|
|
185
|
+
(`RPCSuccess | RPCFailure`). Useful for tests or custom transports.
|
|
186
|
+
|
|
187
|
+
```ts
|
|
188
|
+
const response = await repository.invoke(
|
|
189
|
+
{ jsonrpc: '2.0', method: 'add', params: [1, 2], id: 1 },
|
|
190
|
+
{ injections: { request: fakeReq } },
|
|
191
|
+
);
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Injectables
|
|
195
|
+
|
|
196
|
+
An injectable factory is passed to the transport as `mkInjectable`. The
|
|
197
|
+
resulting object is available to any signature slot declared as
|
|
198
|
+
`s.injectable(key)`. Built-in defaults:
|
|
199
|
+
|
|
200
|
+
| Transport | Default injectables |
|
|
201
|
+
| --- | --- |
|
|
202
|
+
| Express | `request`, `session`, `response`, `repository` |
|
|
203
|
+
| WebSocket | `socket`, `request`, `repository` |
|
|
204
|
+
| stdio | `repository`, `input`, `output` |
|
|
205
|
+
|
|
206
|
+
Values may be plain values or functions; functions are awaited before
|
|
207
|
+
injection.
|
|
208
|
+
|
|
209
|
+
## Permission checks
|
|
210
|
+
|
|
211
|
+
When a descriptor declares `grantTo`, the injected `perm_check(grantTo,
|
|
212
|
+
descriptor)` is called:
|
|
213
|
+
|
|
214
|
+
- returns `true` → allowed
|
|
215
|
+
- returns an `RPCError` → re-thrown as-is
|
|
216
|
+
- anything else → `PERMISSION_DENIED (-32604)`
|
|
217
|
+
|
|
218
|
+
```ts
|
|
219
|
+
app.use('/', createExpressRouter(repository, {
|
|
220
|
+
mkInjectable: (req, res, repo) => ({
|
|
221
|
+
request: req,
|
|
222
|
+
repository: repo,
|
|
223
|
+
perm_check: async (grantTo: string[]) => {
|
|
224
|
+
const roles = (req as any).session?.user?.roles ?? [];
|
|
225
|
+
return roles.some((r: string) => grantTo.includes(r));
|
|
226
|
+
},
|
|
227
|
+
}),
|
|
228
|
+
}));
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## Error transform
|
|
232
|
+
|
|
233
|
+
`errorTransform(error)` may return an `RPCError` to override the default
|
|
234
|
+
`INTERNAL_ERROR` mapping. Return `undefined` to fall back to the default
|
|
235
|
+
behaviour. It is applied only to errors thrown by the method handler, not
|
|
236
|
+
by request validation.
|
|
237
|
+
|
|
238
|
+
```ts
|
|
239
|
+
createExpressRouter(repository, {
|
|
240
|
+
errorTransform: (error) =>
|
|
241
|
+
error instanceof MyDomainError
|
|
242
|
+
? new RPCError(-32000, 'Domain error', error.message)
|
|
243
|
+
: undefined,
|
|
244
|
+
});
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
## Built-in methods
|
|
248
|
+
|
|
249
|
+
| Method | Description |
|
|
250
|
+
| --- | --- |
|
|
251
|
+
| `system.listMethods` | Returns `string[]` of registered method names. |
|
|
252
|
+
| `system.methodSignature` | Returns the schema list for a method. |
|
|
253
|
+
| `system.methodHelp` | Returns the method's `document` (or `null`). |
|
|
254
|
+
|
|
255
|
+
Disable them with `createRepository({ withSystemMethods: false })`.
|
|
256
|
+
|
|
257
|
+
## Error codes
|
|
258
|
+
|
|
259
|
+
| Code | Meaning |
|
|
260
|
+
| --- | --- |
|
|
261
|
+
| -32700 | Parse error |
|
|
262
|
+
| -32600 | Invalid Request |
|
|
263
|
+
| -32601 | Method not found |
|
|
264
|
+
| -32602 | Invalid params |
|
|
265
|
+
| -32603 | Internal error |
|
|
266
|
+
| -32604 | Permission denied |
|
|
267
|
+
| -32000..-32099 | Implementation-defined server errors |
|
|
268
|
+
|
|
269
|
+
`codes` is exported from the package root:
|
|
270
|
+
|
|
271
|
+
```ts
|
|
272
|
+
import { codes } from '@yuhere/js-jsonrpc2';
|
|
273
|
+
codes.INVALID_PARAMS; // -32602
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
## License
|
|
277
|
+
|
|
278
|
+
MIT
|
package/SKILL.md
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: js-jsonrpc2
|
|
3
|
+
description: Use when building or modifying a JSON-RPC 2.0 server with @yuhere/js-jsonrpc2 — registering methods with schema signatures, wiring the Express/WebSocket/stdio transport, streaming returns, or debugging params, permissions, and error mapping.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# @yuhere/js-jsonrpc2
|
|
7
|
+
|
|
8
|
+
JSON-RPC 2.0 server middleware. Package name `@yuhere/js-jsonrpc2`. Node 18+.
|
|
9
|
+
|
|
10
|
+
Hard constraints (by design, do not work around):
|
|
11
|
+
|
|
12
|
+
- No named params. `params` MUST be a positional array or `null`.
|
|
13
|
+
- No notifications. Every request MUST carry an `id`.
|
|
14
|
+
- No batch. A batch payload returns `-32600 Invalid Request`.
|
|
15
|
+
- No request-side streaming. Only method **return values** can be streamed.
|
|
16
|
+
|
|
17
|
+
## Register a method
|
|
18
|
+
|
|
19
|
+
1. Create the repository: `createRepository({ withSystemMethods: true })`.
|
|
20
|
+
Omit the option to get the same default. Pass `false` only to drop `system.*`.
|
|
21
|
+
2. Call `repository.register(descriptor, handler)`.
|
|
22
|
+
3. Set `descriptor.namespace` to a non-empty string.
|
|
23
|
+
4. Set `descriptor.signature` to `[returnSchema, ...parameterSchema[]]`.
|
|
24
|
+
- `signature[0]` MUST be `s.Schema`, `s.void()`, or `s.stream(inner)`.
|
|
25
|
+
Never an injectable.
|
|
26
|
+
- Each later slot MUST be `s.Schema` (client param) or `s.injectable(key)`.
|
|
27
|
+
5. Write `handler` so its positional args match the non-injectable slots in order;
|
|
28
|
+
injectable slots are filled at their declared positions.
|
|
29
|
+
6. Use `s.*` factories for schemas: `s.string()`, `s.number()`, `s.boolean()`,
|
|
30
|
+
`s.literal(v)`, `s.array(item)`, `s.object(shape)`, `s.record(v)`,
|
|
31
|
+
`s.union([...])`, `s.tuple([...])`, `s.enum([...])`, `s.unknown()`.
|
|
32
|
+
7. Chain modifiers before registering: `.min()`, `.max()`, `.nullable()`,
|
|
33
|
+
`.strict()`, `.describe(text)`.
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import { createRepository, s } from '@yuhere/js-jsonrpc2';
|
|
37
|
+
|
|
38
|
+
const repository = createRepository();
|
|
39
|
+
repository.register(
|
|
40
|
+
{ namespace: 'add', document: 'a + b', signature: [s.number(), s.number(), s.number()] },
|
|
41
|
+
(a: number, b: number) => a + b,
|
|
42
|
+
);
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Stream a return value
|
|
46
|
+
|
|
47
|
+
Declare `s.stream(inner)` as `signature[0]`. The handler MUST return an
|
|
48
|
+
`AsyncGenerator`; anything else fails with `INTERNAL_ERROR` before any chunk is
|
|
49
|
+
sent.
|
|
50
|
+
|
|
51
|
+
1. Set `signature[0]` to `s.stream(inner)`. `inner` is any `s.Schema`
|
|
52
|
+
(including `s.unknown()`). No stream-of-stream, and `s.stream()` is not
|
|
53
|
+
valid in a parameter slot.
|
|
54
|
+
2. Make the handler an `async function*` that yields values matching `inner`.
|
|
55
|
+
3. Do NOT rely on `inner` for runtime chunk validation — chunks are **not**
|
|
56
|
+
validated. `inner` is documentation only; the WebUI uses it to render types.
|
|
57
|
+
4. Expect frames of shape `RPCStreamChunk`, each a **complete** JSON-RPC-shaped
|
|
58
|
+
object (not nested under `result`):
|
|
59
|
+
`{ jsonrpc, id, done, index?, value?, error? }`.
|
|
60
|
+
- Normal data frame: `{ done: false, index, value }`.
|
|
61
|
+
- Normal end: `{ done: true, index }`.
|
|
62
|
+
- Mid-stream throw: `{ done: true, index?, error: { code, message } }`.
|
|
63
|
+
`id` is always the request id (never `null` for streams).
|
|
64
|
+
5. The server yields exactly one terminal frame (`done: true`) per stream,
|
|
65
|
+
whether it finished normally or threw. A transport that receives the
|
|
66
|
+
terminal frame ends the response cleanly; it does not treat a terminal
|
|
67
|
+
`error` frame as a transport failure.
|
|
68
|
+
6. Put cleanup in a `finally` inside the generator; the server calls
|
|
69
|
+
`source.return()` when the client disconnects, so `finally` runs.
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
repository.register(
|
|
73
|
+
{
|
|
74
|
+
namespace: 'logs.tail',
|
|
75
|
+
document: 'stream log lines',
|
|
76
|
+
signature: [s.stream(s.string()), s.string()],
|
|
77
|
+
},
|
|
78
|
+
async function* tail(path: string) {
|
|
79
|
+
for await (const line of follow(path)) yield line;
|
|
80
|
+
},
|
|
81
|
+
);
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Reference implementations for every stream shape (finite, infinite, throwing,
|
|
85
|
+
empty, with injectables, `s.unknown()`) live in `dev/express.ts` under the
|
|
86
|
+
`stream.*` namespace.
|
|
87
|
+
|
|
88
|
+
## Wire a transport
|
|
89
|
+
|
|
90
|
+
Pick exactly one transport per server process.
|
|
91
|
+
|
|
92
|
+
- **Express**: call `app.use('/', createExpressRouter(repository, opts))`.
|
|
93
|
+
The router only handles `POST /`. Options: `mkInjectable`, `errorTransform`,
|
|
94
|
+
`bodyLimit`, `useHtdoc`, `streamFormat`.
|
|
95
|
+
- **WebSocket on existing server**: call `attachWebSocket(server, repository, opts)`.
|
|
96
|
+
Options: `path`, `mkInjectable`, `errorTransform`, `allowOrigin`, `maxPayload`,
|
|
97
|
+
`onConnection`.
|
|
98
|
+
- **Standalone WebSocket**: `await createWebSocketServer({ port, host })`;
|
|
99
|
+
it returns `{ server, wss, close() }`.
|
|
100
|
+
- **stdio**: `await serveStdio({ repository, framing: 'lines' })`.
|
|
101
|
+
Use `framing: 'json'` for concatenated JSON values.
|
|
102
|
+
|
|
103
|
+
All transports support stream returns.
|
|
104
|
+
|
|
105
|
+
- Express emits one `RPCStreamChunk` per NDJSON line (`streamFormat: 'ndjson'`,
|
|
106
|
+
the default). `streamFormat: 'sse'` is reserved for `text/event-stream`;
|
|
107
|
+
currently the NDJSON path is the implemented one — do not depend on SSE
|
|
108
|
+
unless the transport source shows it.
|
|
109
|
+
- WS and stdio emit one `RPCStreamChunk` per message/line.
|
|
110
|
+
- Non-stream responses are serialized with a streaming JSON writer. If
|
|
111
|
+
serialization fails partway, the transport aborts the connection rather than
|
|
112
|
+
sending a truncated body with `200 OK`; if it fails before writing any byte,
|
|
113
|
+
the transport emits an `INTERNAL_ERROR` response instead.
|
|
114
|
+
|
|
115
|
+
Supply `mkInjectable` when the handler needs request/socket context. Defaults:
|
|
116
|
+
|
|
117
|
+
| Transport | Default injectables |
|
|
118
|
+
| --- | --- |
|
|
119
|
+
| Express | `request`, `session`, `response`, `repository` |
|
|
120
|
+
| WebSocket | `socket`, `request`, `repository` |
|
|
121
|
+
| stdio | `repository`, `input`, `output` |
|
|
122
|
+
|
|
123
|
+
Injectable values may be plain values or functions; functions are awaited.
|
|
124
|
+
|
|
125
|
+
## Enforce permissions
|
|
126
|
+
|
|
127
|
+
1. Add `grantTo: ['role', ...]` to the descriptor.
|
|
128
|
+
2. Inject `perm_check(grantTo, descriptor)` via `mkInjectable`.
|
|
129
|
+
3. Return `true` to allow, return an `RPCError` to re-throw it verbatim, or
|
|
130
|
+
return anything else to get `PERMISSION_DENIED (-32604)`.
|
|
131
|
+
4. Do not rely on `grantTo` alone — without `perm_check`, the check is skipped.
|
|
132
|
+
|
|
133
|
+
## Map domain errors
|
|
134
|
+
|
|
135
|
+
Call `errorTransform(error)` inside the transport options. Return an `RPCError`
|
|
136
|
+
to override `INTERNAL_ERROR`, return `undefined` to fall back to the default.
|
|
137
|
+
It applies only to errors thrown by the handler **before** streaming starts,
|
|
138
|
+
not to request validation. Errors thrown **after** the first chunk has been
|
|
139
|
+
sent cannot become `RPCFailure`; they surface as a `{ done: true, error }`
|
|
140
|
+
terminal chunk instead.
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
createExpressRouter(repository, {
|
|
144
|
+
errorTransform: (e) => (e instanceof MyDomainError ? new RPCError(-32000, 'Domain', e.message) : undefined),
|
|
145
|
+
});
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Invoke directly
|
|
149
|
+
|
|
150
|
+
Call `repository.invoke(input, { injections?, errorTransform? })`. It returns
|
|
151
|
+
`RPCResponse` (`RPCSuccess | RPCStreamSuccess | RPCFailure`) and never throws
|
|
152
|
+
for protocol errors.
|
|
153
|
+
|
|
154
|
+
For streaming methods the result is `RPCStreamSuccess`:
|
|
155
|
+
`{ jsonrpc, id, chunks: AsyncGenerator<RPCStreamChunk> }`. It has no `result`
|
|
156
|
+
and no boolean `stream` flag — branch on the presence of `chunks`:
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
const r = await repository.invoke(input);
|
|
160
|
+
if ('chunks' in r) for await (const frame of r.chunks) { /* frame is RPCStreamChunk */ }
|
|
161
|
+
else if ('error' in r) { /* RPCFailure */ }
|
|
162
|
+
else { /* RPCSuccess, r.result */ }
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Consume `chunks` yourself, or hand it to a transport. If a client disconnects
|
|
166
|
+
mid-stream, the transport calls `chunks.return()`, which triggers the handler's
|
|
167
|
+
`finally`.
|
|
168
|
+
|
|
169
|
+
## Client (`webui`)
|
|
170
|
+
|
|
171
|
+
`RpcClient.call<T>(method, ...params)` returns
|
|
172
|
+
`CallResult<T> = Promise<T> | AsyncGenerator<T>`. It inspects the first
|
|
173
|
+
frame: a `done` field means stream, otherwise the resolved value is the
|
|
174
|
+
ordinary `result`. Use `isStreamResult(x)` to narrow.
|
|
175
|
+
|
|
176
|
+
- `const r = await rpc.call('add', 1, 2)` → `r` is `3`.
|
|
177
|
+
- `const r = await rpc.call('logs.tail', '/var/log/app.log')` → `r` is an
|
|
178
|
+
`AsyncGenerator`; `for await (const line of r) ...`.
|
|
179
|
+
- HTTP transport calls `reader.cancel()` only on early exit (`break` / throw /
|
|
180
|
+
bad first frame), so the connection is aborted and the server's
|
|
181
|
+
`res.on('close')` fires. A clean read lets the connection return to the pool;
|
|
182
|
+
it does not cancel.
|
|
183
|
+
- WS transport routes the first message via a one-shot waiter, then streams
|
|
184
|
+
subsequent frames per `id`.
|
|
185
|
+
|
|
186
|
+
## Handle errors
|
|
187
|
+
|
|
188
|
+
Read error codes from the exported `codes` object. `RPCError` carries
|
|
189
|
+
`code`, `message`, `data`. Common codes: `-32700` parse, `-32600` invalid
|
|
190
|
+
request, `-32601` method not found, `-32602` invalid params, `-32603` internal,
|
|
191
|
+
`-32604` permission denied.
|
|
192
|
+
|
|
193
|
+
For streams, a mid-stream `RPCError` becomes a terminal chunk with
|
|
194
|
+
`error: { code, message }`; clients re-throw it as `RPCError`.
|
|
195
|
+
|
|
196
|
+
## Use built-in methods
|
|
197
|
+
|
|
198
|
+
Keep `withSystemMethods` on (default) to expose `system.listMethods`,
|
|
199
|
+
`system.methodSignature`, `system.methodHelp`. Set `document` on a descriptor
|
|
200
|
+
so `system.methodHelp` returns useful text.
|
|
201
|
+
|
|
202
|
+
`system.methodSignature` reports a stream return as
|
|
203
|
+
`{ kind: 'stream', children: { inner: <schema> } }`.
|
|
204
|
+
|
|
205
|
+
## Common mistakes
|
|
206
|
+
|
|
207
|
+
- **Passing named params or a bare object as `params`.** `convertParams` rejects
|
|
208
|
+
anything that is not an array or `null` with `INVALID_PARAMS`. Always send
|
|
209
|
+
`params: [...]`.
|
|
210
|
+
- **Omitting `id`.** `RPCRequestSchema` requires it; the request fails with
|
|
211
|
+
`INVALID_REQUEST`. Notifications are not supported.
|
|
212
|
+
- **Naming descriptor fields `sign` / `doc`.** The schema is strict; registration
|
|
213
|
+
throws `TypeError`. Use `signature` and `document`.
|
|
214
|
+
- **Putting `s.injectable()` at `signature[0]`.** `buildDescriptor` throws; the
|
|
215
|
+
first slot is the return type.
|
|
216
|
+
- **Forgetting `mkInjectable` for a `s.injectable(key)` slot.** `convertParams`
|
|
217
|
+
throws `INVALID_PARAMS` for the missing key.
|
|
218
|
+
- **Expecting `errorTransform` to catch validation errors.** It only wraps the
|
|
219
|
+
handler call.
|
|
220
|
+
- **Declaring `s.stream(inner)` but returning a plain value from the handler.**
|
|
221
|
+
The server throws `INTERNAL_ERROR` before sending any chunk.
|
|
222
|
+
- **Expecting chunks to be validated against `inner`.** They are not. `inner`
|
|
223
|
+
is documentation only.
|
|
224
|
+
- **Trying to stream `params`.** Not supported; only return values can stream.
|
|
225
|
+
- **Nesting `s.stream()` inside `s.stream()` or inside a parameter slot.**
|
|
226
|
+
`s.stream()` is only valid at `signature[0]`.
|
|
227
|
+
- **Forgetting `finally` in a streaming handler.** When the client disconnects
|
|
228
|
+
the server calls `source.return()`, so `finally` is the only place to close
|
|
229
|
+
DB cursors, files, etc.
|
|
230
|
+
- **Calling `rpc.call()` and assuming `Promise`.** For stream methods it
|
|
231
|
+
resolves to an `AsyncGenerator`. Branch with `isStreamResult(r)`.
|
|
232
|
+
- **Assuming stream frames are nested under `result` or carry a `stream: true`
|
|
233
|
+
flag.** Each frame is a complete `RPCStreamChunk` with `jsonrpc` and `id`.
|
|
234
|
+
Branch on `done` (and `error`), not on a wrapper field.
|
|
235
|
+
- **Treating a terminal `error` frame as a broken connection.** A mid-stream
|
|
236
|
+
throw is delivered as `{ done: true, error }`; the HTTP/WS/stdio response
|
|
237
|
+
itself ends cleanly. Only transport-level failures abort the connection.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/cli/cli.ts"],"names":[],"mappings":""}
|
package/dist/cli/cli.js
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// src/cli.ts
|
|
3
|
+
//
|
|
4
|
+
// `jsonrpc2-demo` 命令入口:启动带完整 demo 方法的 HTTP + WebSocket 服务,
|
|
5
|
+
// 并自动打开 WebUI(默认 http://localhost:5000/)。
|
|
6
|
+
//
|
|
7
|
+
// 环境变量:
|
|
8
|
+
// PORT 监听端口(默认 5000;被占用时自动换随机端口)
|
|
9
|
+
// NO_OPEN 设为 1 时不自动打开浏览器
|
|
10
|
+
// HOST 监听地址(默认 0.0.0.0)
|
|
11
|
+
import { createServer } from 'node:http';
|
|
12
|
+
import { spawn } from 'node:child_process';
|
|
13
|
+
import { existsSync } from 'node:fs';
|
|
14
|
+
import path from 'node:path';
|
|
15
|
+
import { fileURLToPath } from 'node:url';
|
|
16
|
+
import express from 'express';
|
|
17
|
+
import { createRepository } from '../core/Repository';
|
|
18
|
+
import { installDemoMethods } from './demo-methods';
|
|
19
|
+
import { createExpressRouter } from '../transport/express';
|
|
20
|
+
import { attachWebSocket } from '../transport/ws';
|
|
21
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
22
|
+
function htdocDir() {
|
|
23
|
+
// dist/cli.js → 包根/htdoc
|
|
24
|
+
return path.resolve(here, '..', 'htdoc');
|
|
25
|
+
}
|
|
26
|
+
function openBrowser(url) {
|
|
27
|
+
let cmd;
|
|
28
|
+
let args;
|
|
29
|
+
if (process.platform === 'darwin') {
|
|
30
|
+
cmd = 'open';
|
|
31
|
+
args = [url];
|
|
32
|
+
}
|
|
33
|
+
else if (process.platform === 'win32') {
|
|
34
|
+
// start 的第一个参数会被当作窗口标题,传空字符串占位。
|
|
35
|
+
cmd = 'cmd';
|
|
36
|
+
args = ['/c', 'start', '""', url];
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
cmd = 'xdg-open';
|
|
40
|
+
args = [url];
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
const child = spawn(cmd, args, { stdio: 'ignore', detached: true });
|
|
44
|
+
child.on('error', () => {
|
|
45
|
+
console.log(`(无法自动打开浏览器,请手动访问 ${url})`);
|
|
46
|
+
});
|
|
47
|
+
child.unref();
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
console.log(`(无法自动打开浏览器,请手动访问 ${url})`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function listen(server, host, port) {
|
|
54
|
+
return new Promise((resolve, reject) => {
|
|
55
|
+
const onError = (err) => {
|
|
56
|
+
server.off('listening', onListening);
|
|
57
|
+
if (err.code === 'EADDRINUSE' && port !== 0) {
|
|
58
|
+
console.warn(`端口 ${port} 已被占用,改用随机端口重试。`);
|
|
59
|
+
server.once('error', reject);
|
|
60
|
+
server.once('listening', onListening);
|
|
61
|
+
server.listen(0, host);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
reject(err);
|
|
65
|
+
};
|
|
66
|
+
const onListening = () => {
|
|
67
|
+
server.off('error', onError);
|
|
68
|
+
const addr = server.address();
|
|
69
|
+
const actual = typeof addr === 'object' && addr ? addr.port : port;
|
|
70
|
+
resolve(actual);
|
|
71
|
+
};
|
|
72
|
+
server.once('error', onError);
|
|
73
|
+
server.once('listening', onListening);
|
|
74
|
+
server.listen(port, host);
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
async function main() {
|
|
78
|
+
const repository = createRepository({ withSystemMethods: true });
|
|
79
|
+
installDemoMethods(repository);
|
|
80
|
+
const app = express();
|
|
81
|
+
app.use('/', createExpressRouter(repository, { useHtdoc: true }));
|
|
82
|
+
const server = createServer(app);
|
|
83
|
+
attachWebSocket(server, repository, {
|
|
84
|
+
path: '/',
|
|
85
|
+
mkInjectable: (ws, req, repo) => ({
|
|
86
|
+
socket: ws,
|
|
87
|
+
request: req,
|
|
88
|
+
repository: repo,
|
|
89
|
+
requestId: `ws-${Math.random().toString(36).slice(2, 8)}`,
|
|
90
|
+
}),
|
|
91
|
+
});
|
|
92
|
+
const host = process.env['HOST'] ?? '0.0.0.0';
|
|
93
|
+
const requestedPort = Number(process.env['PORT'] ?? 5000);
|
|
94
|
+
const port = await listen(server, host, requestedPort);
|
|
95
|
+
const url = `http://localhost:${port}/`;
|
|
96
|
+
const wsUrl = `ws://localhost:${port}/`;
|
|
97
|
+
const hasWebUI = existsSync(path.join(htdocDir(), 'index.html'));
|
|
98
|
+
console.log('');
|
|
99
|
+
console.log(' js-jsonrpc2 demo');
|
|
100
|
+
console.log(' ────────────────────────────────────────');
|
|
101
|
+
console.log(` HTTP (POST /) ${url}`);
|
|
102
|
+
console.log(` WebSocket ${wsUrl}`);
|
|
103
|
+
if (hasWebUI) {
|
|
104
|
+
console.log(` WebUI ${url}`);
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
console.log(' WebUI (未构建:请先运行 `npm run build:webui`)');
|
|
108
|
+
}
|
|
109
|
+
console.log('');
|
|
110
|
+
console.log(' 按 Ctrl+C 停止。');
|
|
111
|
+
console.log('');
|
|
112
|
+
if (hasWebUI && process.env['NO_OPEN'] !== '1') {
|
|
113
|
+
openBrowser(url);
|
|
114
|
+
}
|
|
115
|
+
let shuttingDown = false;
|
|
116
|
+
const shutdown = () => {
|
|
117
|
+
if (shuttingDown)
|
|
118
|
+
return;
|
|
119
|
+
shuttingDown = true;
|
|
120
|
+
console.log('\n正在关闭…');
|
|
121
|
+
server.close(() => process.exit(0));
|
|
122
|
+
// 兜底:3 秒内没关完就强退
|
|
123
|
+
setTimeout(() => process.exit(0), 3000).unref();
|
|
124
|
+
};
|
|
125
|
+
process.once('SIGINT', shutdown);
|
|
126
|
+
process.once('SIGTERM', shutdown);
|
|
127
|
+
}
|
|
128
|
+
main().catch((err) => {
|
|
129
|
+
console.error('启动失败:', err);
|
|
130
|
+
process.exit(1);
|
|
131
|
+
});
|
|
132
|
+
//# sourceMappingURL=cli.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/cli/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Repository } from '../core/Repository';
|
|
2
|
+
/**
|
|
3
|
+
* 注册一整套用于演示/调试的 RPC 方法。
|
|
4
|
+
* 覆盖:基础类型、字面量/枚举、数组/元组、对象、Record、联合、
|
|
5
|
+
* nullable、unknown、注入参数、各种返回形态、深层嵌套、混合大杂烩、流式返回。
|
|
6
|
+
*
|
|
7
|
+
* 被 dev/express.ts 与 src/cli.ts 共用。
|
|
8
|
+
*/
|
|
9
|
+
export declare function installDemoMethods(repository: Repository): void;
|
|
10
|
+
//# sourceMappingURL=demo-methods.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"demo-methods.d.ts","sourceRoot":"","sources":["../../src/cli/demo-methods.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAErD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,UAAU,GAAG,IAAI,CAimB/D"}
|