@motrix/mdxp 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -35,6 +35,9 @@ top of it.
35
35
  - **Platform RAL entry points.** `./node` and `./browser` install the matching
36
36
  `vscode-jsonrpc` runtime abstraction layer and re-export its transport classes,
37
37
  so you import everything — connection helper and reader/writer — from one place.
38
+ - **One-call constructors.** `fromWebSocket`, `fromStdio`, and `fromWorker` build
39
+ a connection over a common transport in a single call; `createMdxpConnection`
40
+ stays the generic escape hatch.
38
41
  - **Agent-ready.** A built-in tool registry emits a JSON-Schema tool catalog you
39
42
  can feed straight into an LLM function-calling API.
40
43
  - **Forward-compatible.** Unknown methods and fields are ignored, not rejected.
@@ -75,16 +78,10 @@ including its reader/writer, from a single place.
75
78
  ### Node host (over stdio)
76
79
 
77
80
  ```ts
78
- import {
79
- createMdxpConnection,
80
- StreamMessageReader,
81
- StreamMessageWriter,
82
- } from '@motrix/mdxp/node'
83
-
84
- const conn = createMdxpConnection(
85
- new StreamMessageReader(process.stdin),
86
- new StreamMessageWriter(process.stdout),
87
- )
81
+ import { fromStdio } from '@motrix/mdxp/node'
82
+
83
+ // Over process.stdin / process.stdout (the native-messaging / CLI case).
84
+ const conn = fromStdio()
88
85
 
89
86
  // Register handlers BEFORE listen().
90
87
  conn.onNotification('$/task/progress', (p) => {
@@ -95,24 +92,31 @@ conn.onNotification('$/task/progress', (p) => {
95
92
  conn.listen()
96
93
  ```
97
94
 
98
- ### Browser host
95
+ ### Browser host (over WebSocket)
99
96
 
100
97
  ```ts
101
- // Installs the browser RAL and re-exports the full API + transport classes.
102
- import {
103
- createMdxpConnection,
104
- BrowserMessageReader,
105
- BrowserMessageWriter,
106
- } from '@motrix/mdxp/browser'
107
-
108
- // e.g. a MessagePort / Worker; adapt your WebSocket to reader/writer as needed.
109
- const conn = createMdxpConnection(
110
- new BrowserMessageReader(worker),
111
- new BrowserMessageWriter(worker),
112
- )
98
+ import { fromWebSocket } from '@motrix/mdxp/browser'
99
+
100
+ const conn = fromWebSocket(new WebSocket('ws://127.0.0.1:16650/v1'))
101
+ conn.onNotification('$/task/progress', (p) => {})
113
102
  conn.listen()
114
103
  ```
115
104
 
105
+ ### Convenience constructors
106
+
107
+ `createMdxpConnection(reader, writer)` is the generic entry point — bring any
108
+ `vscode-jsonrpc` reader/writer. For the common transports, skip the boilerplate:
109
+
110
+ | Constructor | Entry | Transport |
111
+ | --- | --- | --- |
112
+ | `fromWebSocket(ws)` | `./node` · `./browser` | A browser `WebSocket` or a Node `ws` socket |
113
+ | `fromStdio(opts?)` | `./node` | `process.stdin` / `process.stdout`, or given streams |
114
+ | `fromWorker(port)` | `./browser` | A `Worker` or `MessagePort` |
115
+
116
+ Each returns a ready `MdxpConnection` — you still register handlers and call
117
+ `listen()`. For any other transport, build the reader/writer and call
118
+ `createMdxpConnection` directly.
119
+
116
120
  ## Core concepts
117
121
 
118
122
  **Server vs. client.** The Motrix desktop app is the **server** — it owns the
@@ -321,6 +325,7 @@ const tools = toAgentToolCatalog()
321
325
  | Export | Kind | Purpose |
322
326
  | --- | --- | --- |
323
327
  | `createMdxpConnection(reader, writer)` | function | Wrap a reader/writer pair in a typed `MdxpConnection`. |
328
+ | `fromWebSocket` · `fromStdio` · `fromWorker` | function | One-call constructors over a WebSocket / stdio / Worker (from `./node` · `./browser`). |
324
329
  | `MdxpConnection` | type | The connection interface (`sendRequest`, `onRequest`, `sendNotification`, `onNotification`, `dispose`, `raw`). |
325
330
  | `MdxpRequestMap` / `MdxpNotificationMap` | type | Method/notification name → params/result type maps. |
326
331
  | `Methods` / `Notifications` | const | Wire-name constants (`Methods.DownloadAdd === 'download/add'`). |
package/README.zh-CN.md CHANGED
@@ -30,6 +30,8 @@
30
30
  - **平台 RAL 入口**:`./node` 与 `./browser` 会替你装好对应的 `vscode-jsonrpc`
31
31
  runtime abstraction layer,并 re-export 它的 transport class —— 连接封装与
32
32
  reader/writer 都从同一处 import。
33
+ - **一步构造**:`fromWebSocket`、`fromStdio`、`fromWorker` 一行即可在常见 transport
34
+ 上建好连接;`createMdxpConnection` 仍是通用的底层入口。
33
35
  - **面向 agent**:内置的 tool registry 可产出一份 JSON-Schema tool catalog,
34
36
  能直接对接 LLM 的 function-calling API。
35
37
  - **forward-compatible**:遇到未知的 method 或字段选择忽略,而非 reject。
@@ -67,16 +69,10 @@ transport class**(Node 的 `StreamMessageReader`/`Writer`、浏览器的
67
69
  ### Node host(走 stdio)
68
70
 
69
71
  ```ts
70
- import {
71
- createMdxpConnection,
72
- StreamMessageReader,
73
- StreamMessageWriter,
74
- } from '@motrix/mdxp/node'
75
-
76
- const conn = createMdxpConnection(
77
- new StreamMessageReader(process.stdin),
78
- new StreamMessageWriter(process.stdout),
79
- )
72
+ import { fromStdio } from '@motrix/mdxp/node'
73
+
74
+ // 走 process.stdin / process.stdout(native-messaging / CLI 场景)。
75
+ const conn = fromStdio()
80
76
 
81
77
  // 务必在 listen() 之前注册 handler。
82
78
  conn.onNotification('$/task/progress', (p) => {
@@ -87,24 +83,30 @@ conn.onNotification('$/task/progress', (p) => {
87
83
  conn.listen()
88
84
  ```
89
85
 
90
- ### 浏览器 host
86
+ ### 浏览器 host(走 WebSocket)
91
87
 
92
88
  ```ts
93
- // 装好 browser RAL,并 re-export 全量 API + transport class。
94
- import {
95
- createMdxpConnection,
96
- BrowserMessageReader,
97
- BrowserMessageWriter,
98
- } from '@motrix/mdxp/browser'
99
-
100
- // 例如一个 MessagePort / Worker;若用 WebSocket,需自行适配成 reader/writer。
101
- const conn = createMdxpConnection(
102
- new BrowserMessageReader(worker),
103
- new BrowserMessageWriter(worker),
104
- )
89
+ import { fromWebSocket } from '@motrix/mdxp/browser'
90
+
91
+ const conn = fromWebSocket(new WebSocket('ws://127.0.0.1:16650/v1'))
92
+ conn.onNotification('$/task/progress', (p) => {})
105
93
  conn.listen()
106
94
  ```
107
95
 
96
+ ### 便捷构造器
97
+
98
+ `createMdxpConnection(reader, writer)` 是通用入口 —— 传入任意 `vscode-jsonrpc` 的
99
+ reader/writer。常见 transport 则可直接省去样板:
100
+
101
+ | 构造器 | 入口 | Transport |
102
+ | --- | --- | --- |
103
+ | `fromWebSocket(ws)` | `./node` · `./browser` | 浏览器 `WebSocket` 或 Node 的 `ws` socket |
104
+ | `fromStdio(opts?)` | `./node` | `process.stdin` / `process.stdout`,或指定的流 |
105
+ | `fromWorker(port)` | `./browser` | 一个 `Worker` 或 `MessagePort` |
106
+
107
+ 每个都返回开箱即用的 `MdxpConnection` —— 你仍需注册 handler 并调用 `listen()`。
108
+ 其它 transport 则自行构造 reader/writer,再直接调用 `createMdxpConnection`。
109
+
108
110
  ## 核心概念
109
111
 
110
112
  **server 与 client**:Motrix 桌面端是 **server** —— 下载引擎由它持有。**client**
@@ -305,6 +307,7 @@ const tools = toAgentToolCatalog()
305
307
  | 导出 | 类型 | 作用 |
306
308
  | --- | --- | --- |
307
309
  | `createMdxpConnection(reader, writer)` | function | 把一对 reader/writer 封装成类型化的 `MdxpConnection`。 |
310
+ | `fromWebSocket` · `fromStdio` · `fromWorker` | function | 在 WebSocket / stdio / Worker 上的一步构造器(来自 `./node` · `./browser`)。 |
308
311
  | `MdxpConnection` | type | 连接接口(`sendRequest`、`onRequest`、`sendNotification`、`onNotification`、`dispose`、`raw`)。 |
309
312
  | `MdxpRequestMap` / `MdxpNotificationMap` | type | method / notification 名 → params/result 类型的映射表。 |
310
313
  | `Methods` / `Notifications` | const | wire 名常量(`Methods.DownloadAdd === 'download/add'`)。 |
package/dist/browser.d.ts CHANGED
@@ -1,4 +1,8 @@
1
1
  import 'vscode-jsonrpc/browser';
2
2
  export { BrowserMessageReader, BrowserMessageWriter, } from 'vscode-jsonrpc/browser';
3
3
  export * from './index.js';
4
+ export type { WorkerLike } from './transports/worker.js';
5
+ export { fromWorker } from './transports/worker.js';
6
+ export type { WebSocketLike } from './transports/ws.js';
7
+ export { fromWebSocket } from './transports/ws.js';
4
8
  //# sourceMappingURL=browser.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAWA,OAAO,wBAAwB,CAAA;AAM/B,OAAO,EACL,oBAAoB,EACpB,oBAAoB,GACrB,MAAM,wBAAwB,CAAA;AAC/B,cAAc,YAAY,CAAA"}
1
+ {"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAWA,OAAO,wBAAwB,CAAA;AAM/B,OAAO,EACL,oBAAoB,EACpB,oBAAoB,GACrB,MAAM,wBAAwB,CAAA;AAC/B,cAAc,YAAY,CAAA;AAC1B,YAAY,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AACxD,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AACnD,YAAY,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAEvD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA"}
package/dist/browser.js CHANGED
@@ -16,4 +16,7 @@ import 'vscode-jsonrpc/browser';
16
16
  // installed the RAL into.
17
17
  export { BrowserMessageReader, BrowserMessageWriter, } from 'vscode-jsonrpc/browser';
18
18
  export * from './index.js';
19
+ export { fromWorker } from './transports/worker.js';
20
+ // High-level convenience constructors for browser transports.
21
+ export { fromWebSocket } from './transports/ws.js';
19
22
  //# sourceMappingURL=browser.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"browser.js","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAAA,0BAA0B;AAC1B,EAAE;AACF,8EAA8E;AAC9E,0EAA0E;AAC1E,gFAAgF;AAChF,8EAA8E;AAC9E,mCAAmC;AACnC,EAAE;AACF,uEAAuE;AACvE,iFAAiF;AACjF,4EAA4E;AAC5E,OAAO,wBAAwB,CAAA;AAE/B,uEAAuE;AACvE,4EAA4E;AAC5E,0EAA0E;AAC1E,0BAA0B;AAC1B,OAAO,EACL,oBAAoB,EACpB,oBAAoB,GACrB,MAAM,wBAAwB,CAAA;AAC/B,cAAc,YAAY,CAAA"}
1
+ {"version":3,"file":"browser.js","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAAA,0BAA0B;AAC1B,EAAE;AACF,8EAA8E;AAC9E,0EAA0E;AAC1E,gFAAgF;AAChF,8EAA8E;AAC9E,mCAAmC;AACnC,EAAE;AACF,uEAAuE;AACvE,iFAAiF;AACjF,4EAA4E;AAC5E,OAAO,wBAAwB,CAAA;AAE/B,uEAAuE;AACvE,4EAA4E;AAC5E,0EAA0E;AAC1E,0BAA0B;AAC1B,OAAO,EACL,oBAAoB,EACpB,oBAAoB,GACrB,MAAM,wBAAwB,CAAA;AAC/B,cAAc,YAAY,CAAA;AAE1B,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AAEnD,8DAA8D;AAC9D,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA"}
package/dist/node.d.ts CHANGED
@@ -1,4 +1,8 @@
1
1
  import 'vscode-jsonrpc/node';
2
2
  export { IPCMessageReader, IPCMessageWriter, PortMessageReader, PortMessageWriter, SocketMessageReader, SocketMessageWriter, StreamMessageReader, StreamMessageWriter, } from 'vscode-jsonrpc/node';
3
3
  export * from './index.js';
4
+ export type { StdioOptions } from './transports/stdio.js';
5
+ export { fromStdio } from './transports/stdio.js';
6
+ export type { WebSocketLike } from './transports/ws.js';
7
+ export { fromWebSocket } from './transports/ws.js';
4
8
  //# sourceMappingURL=node.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../src/node.ts"],"names":[],"mappings":"AAYA,OAAO,qBAAqB,CAAA;AAK5B,OAAO,EACL,gBAAgB,EAChB,gBAAgB,EAChB,iBAAiB,EACjB,iBAAiB,EACjB,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,GACpB,MAAM,qBAAqB,CAAA;AAC5B,cAAc,YAAY,CAAA"}
1
+ {"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../src/node.ts"],"names":[],"mappings":"AAYA,OAAO,qBAAqB,CAAA;AAK5B,OAAO,EACL,gBAAgB,EAChB,gBAAgB,EAChB,iBAAiB,EACjB,iBAAiB,EACjB,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,GACpB,MAAM,qBAAqB,CAAA;AAC5B,cAAc,YAAY,CAAA;AAC1B,YAAY,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AAEzD,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;AACjD,YAAY,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA"}
package/dist/node.js CHANGED
@@ -16,4 +16,7 @@ import 'vscode-jsonrpc/node';
16
16
  // import, and guaranteed to be the instance this package installed the RAL into.
17
17
  export { IPCMessageReader, IPCMessageWriter, PortMessageReader, PortMessageWriter, SocketMessageReader, SocketMessageWriter, StreamMessageReader, StreamMessageWriter, } from 'vscode-jsonrpc/node';
18
18
  export * from './index.js';
19
+ // High-level convenience constructors for Node transports.
20
+ export { fromStdio } from './transports/stdio.js';
21
+ export { fromWebSocket } from './transports/ws.js';
19
22
  //# sourceMappingURL=node.js.map
package/dist/node.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"node.js","sourceRoot":"","sources":["../src/node.ts"],"names":[],"mappings":"AAAA,uBAAuB;AACvB,EAAE;AACF,8EAA8E;AAC9E,0EAA0E;AAC1E,gFAAgF;AAChF,2EAA2E;AAC3E,gFAAgF;AAChF,4BAA4B;AAC5B,EAAE;AACF,mFAAmF;AACnF,4EAA4E;AAC5E,yEAAyE;AACzE,OAAO,qBAAqB,CAAA;AAE5B,+EAA+E;AAC/E,+EAA+E;AAC/E,iFAAiF;AACjF,OAAO,EACL,gBAAgB,EAChB,gBAAgB,EAChB,iBAAiB,EACjB,iBAAiB,EACjB,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,GACpB,MAAM,qBAAqB,CAAA;AAC5B,cAAc,YAAY,CAAA"}
1
+ {"version":3,"file":"node.js","sourceRoot":"","sources":["../src/node.ts"],"names":[],"mappings":"AAAA,uBAAuB;AACvB,EAAE;AACF,8EAA8E;AAC9E,0EAA0E;AAC1E,gFAAgF;AAChF,2EAA2E;AAC3E,gFAAgF;AAChF,4BAA4B;AAC5B,EAAE;AACF,mFAAmF;AACnF,4EAA4E;AAC5E,yEAAyE;AACzE,OAAO,qBAAqB,CAAA;AAE5B,+EAA+E;AAC/E,+EAA+E;AAC/E,iFAAiF;AACjF,OAAO,EACL,gBAAgB,EAChB,gBAAgB,EAChB,iBAAiB,EACjB,iBAAiB,EACjB,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,GACpB,MAAM,qBAAqB,CAAA;AAC5B,cAAc,YAAY,CAAA;AAE1B,2DAA2D;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;AAEjD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA"}
@@ -0,0 +1,19 @@
1
+ import type { MdxpConnection } from '../connection.js';
2
+ /** Streams for {@link fromStdio}; defaults to the process's stdio. */
3
+ export interface StdioOptions {
4
+ input?: NodeJS.ReadableStream;
5
+ output?: NodeJS.WritableStream;
6
+ }
7
+ /**
8
+ * Create an {@link MdxpConnection} over Node streams — `process.stdin` /
9
+ * `process.stdout` by default. This is the native-messaging-host / CLI case:
10
+ * the parent process pipes JSON-RPC over the child's stdio.
11
+ *
12
+ * ```ts
13
+ * const conn = fromStdio()
14
+ * conn.onRequest('url/resolve', async (params) => {})
15
+ * conn.listen()
16
+ * ```
17
+ */
18
+ export declare function fromStdio(options?: StdioOptions): MdxpConnection;
19
+ //# sourceMappingURL=stdio.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stdio.d.ts","sourceRoot":"","sources":["../../src/transports/stdio.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAGtD,sEAAsE;AACtE,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC,cAAc,CAAA;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC,cAAc,CAAA;CAC/B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,SAAS,CAAC,OAAO,GAAE,YAAiB,GAAG,cAAc,CAKpE"}
@@ -0,0 +1,17 @@
1
+ import { StreamMessageReader, StreamMessageWriter } from 'vscode-jsonrpc/node';
2
+ import { createMdxpConnection } from '../connection.js';
3
+ /**
4
+ * Create an {@link MdxpConnection} over Node streams — `process.stdin` /
5
+ * `process.stdout` by default. This is the native-messaging-host / CLI case:
6
+ * the parent process pipes JSON-RPC over the child's stdio.
7
+ *
8
+ * ```ts
9
+ * const conn = fromStdio()
10
+ * conn.onRequest('url/resolve', async (params) => {})
11
+ * conn.listen()
12
+ * ```
13
+ */
14
+ export function fromStdio(options = {}) {
15
+ return createMdxpConnection(new StreamMessageReader(options.input ?? process.stdin), new StreamMessageWriter(options.output ?? process.stdout));
16
+ }
17
+ //# sourceMappingURL=stdio.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stdio.js","sourceRoot":"","sources":["../../src/transports/stdio.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAA;AAE9E,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAA;AAQvD;;;;;;;;;;GAUG;AACH,MAAM,UAAU,SAAS,CAAC,UAAwB,EAAE;IAClD,OAAO,oBAAoB,CACzB,IAAI,mBAAmB,CAAC,OAAO,CAAC,KAAK,IAAI,OAAO,CAAC,KAAK,CAAC,EACvD,IAAI,mBAAmB,CAAC,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC,CAC1D,CAAA;AACH,CAAC"}
@@ -0,0 +1,27 @@
1
+ import type { MdxpConnection } from '../connection.js';
2
+ /**
3
+ * Minimal structural subset of a `MessagePort` / `Worker` /
4
+ * `DedicatedWorkerGlobalScope` — anything the browser RAL's reader/writer
5
+ * accept. Consumers pass their worker or port as-is.
6
+ */
7
+ export interface WorkerLike {
8
+ postMessage(message: unknown): void;
9
+ addEventListener(type: 'message', listener: (event: {
10
+ readonly data: unknown;
11
+ }) => void): void;
12
+ removeEventListener(type: 'message', listener: (event: {
13
+ readonly data: unknown;
14
+ }) => void): void;
15
+ }
16
+ /**
17
+ * Create an {@link MdxpConnection} over a Worker or `MessagePort` (the browser
18
+ * `postMessage` transport).
19
+ *
20
+ * ```ts
21
+ * const conn = fromWorker(self) // inside a Web Worker
22
+ * conn.onNotification('$/task/progress', (p) => {})
23
+ * conn.listen()
24
+ * ```
25
+ */
26
+ export declare function fromWorker(port: WorkerLike): MdxpConnection;
27
+ //# sourceMappingURL=worker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../../src/transports/worker.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAGtD;;;;GAIG;AACH,MAAM,WAAW,UAAU;IACzB,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAA;IACnC,gBAAgB,CACd,IAAI,EAAE,SAAS,EACf,QAAQ,EAAE,CAAC,KAAK,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,GACpD,IAAI,CAAA;IACP,mBAAmB,CACjB,IAAI,EAAE,SAAS,EACf,QAAQ,EAAE,CAAC,KAAK,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,GACpD,IAAI,CAAA;CACR;AAaD;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,UAAU,GAAG,cAAc,CAE3D"}
@@ -0,0 +1,22 @@
1
+ import { BrowserMessageReader, BrowserMessageWriter, } from 'vscode-jsonrpc/browser';
2
+ import { createMdxpConnection } from '../connection.js';
3
+ // vscode-jsonrpc types the port as `MessagePort | Worker | DedicatedWorkerGlobalScope`,
4
+ // which are DOM / WebWorker globals. This package targets `lib: ES2022` (no DOM),
5
+ // so re-type the constructors against the structural `WorkerLike` instead of
6
+ // pulling the DOM libs into every consumer.
7
+ const Reader = BrowserMessageReader;
8
+ const Writer = BrowserMessageWriter;
9
+ /**
10
+ * Create an {@link MdxpConnection} over a Worker or `MessagePort` (the browser
11
+ * `postMessage` transport).
12
+ *
13
+ * ```ts
14
+ * const conn = fromWorker(self) // inside a Web Worker
15
+ * conn.onNotification('$/task/progress', (p) => {})
16
+ * conn.listen()
17
+ * ```
18
+ */
19
+ export function fromWorker(port) {
20
+ return createMdxpConnection(new Reader(port), new Writer(port));
21
+ }
22
+ //# sourceMappingURL=worker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worker.js","sourceRoot":"","sources":["../../src/transports/worker.ts"],"names":[],"mappings":"AACA,OAAO,EACL,oBAAoB,EACpB,oBAAoB,GACrB,MAAM,wBAAwB,CAAA;AAE/B,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAA;AAmBvD,wFAAwF;AACxF,kFAAkF;AAClF,6EAA6E;AAC7E,4CAA4C;AAC5C,MAAM,MAAM,GAAG,oBAEG,CAAA;AAClB,MAAM,MAAM,GAAG,oBAEG,CAAA;AAElB;;;;;;;;;GASG;AACH,MAAM,UAAU,UAAU,CAAC,IAAgB;IACzC,OAAO,oBAAoB,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,CAAA;AACjE,CAAC"}
@@ -0,0 +1,35 @@
1
+ import type { MdxpConnection } from '../connection.js';
2
+ /**
3
+ * Minimal structural subset of a WebSocket shared by the browser `WebSocket`
4
+ * and the Node [`ws`](https://www.npmjs.com/package/ws) package. Consumers pass
5
+ * their socket as-is — no wrapping required.
6
+ */
7
+ export interface WebSocketLike {
8
+ readonly readyState: number;
9
+ send(data: string): void;
10
+ close(code?: number, reason?: string): void;
11
+ addEventListener(type: 'message' | 'close' | 'error', listener: (event: {
12
+ readonly data?: unknown;
13
+ }) => void): void;
14
+ removeEventListener(type: 'message' | 'close' | 'error', listener: (event: {
15
+ readonly data?: unknown;
16
+ }) => void): void;
17
+ }
18
+ /**
19
+ * Create an {@link MdxpConnection} over a WebSocket.
20
+ *
21
+ * Each WebSocket message carries exactly one complete JSON-RPC 2.0 message —
22
+ * the WebSocket protocol already frames messages, so there is no Content-Length
23
+ * framing. Works with the browser `WebSocket` and the Node `ws` package alike
24
+ * (both satisfy {@link WebSocketLike}).
25
+ *
26
+ * Register handlers first, then call `listen()`:
27
+ *
28
+ * ```ts
29
+ * const conn = fromWebSocket(ws)
30
+ * conn.onNotification('$/task/progress', (p) => {})
31
+ * conn.listen()
32
+ * ```
33
+ */
34
+ export declare function fromWebSocket(ws: WebSocketLike): MdxpConnection;
35
+ //# sourceMappingURL=ws.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ws.d.ts","sourceRoot":"","sources":["../../src/transports/ws.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAMtD;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3C,gBAAgB,CACd,IAAI,EAAE,SAAS,GAAG,OAAO,GAAG,OAAO,EACnC,QAAQ,EAAE,CAAC,KAAK,EAAE;QAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,GACrD,IAAI,CAAA;IACP,mBAAmB,CACjB,IAAI,EAAE,SAAS,GAAG,OAAO,GAAG,OAAO,EACnC,QAAQ,EAAE,CAAC,KAAK,EAAE;QAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,GACrD,IAAI,CAAA;CACR;AAsID;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,aAAa,CAAC,EAAE,EAAE,aAAa,GAAG,cAAc,CAK/D"}
@@ -0,0 +1,144 @@
1
+ import { Emitter } from 'vscode-jsonrpc';
2
+ import { createMdxpConnection } from '../connection.js';
3
+ /** `WebSocket.readyState` value for an open socket (browser and `ws` agree). */
4
+ const OPEN = 1;
5
+ function toText(data) {
6
+ if (typeof data === 'string')
7
+ return data;
8
+ if (data instanceof ArrayBuffer)
9
+ return new TextDecoder().decode(data);
10
+ // A typed array / DataView / Node Buffer. TS 6's generic `ArrayBufferView`
11
+ // typing is stricter than `TextDecoder.decode`'s runtime contract (which
12
+ // accepts any view), so widen to satisfy the compiler.
13
+ if (ArrayBuffer.isView(data)) {
14
+ return new TextDecoder().decode(data);
15
+ }
16
+ throw new Error('unsupported WebSocket message data type');
17
+ }
18
+ class WebSocketMessageReader {
19
+ ws;
20
+ errorEmitter = new Emitter();
21
+ closeEmitter = new Emitter();
22
+ partialEmitter = new Emitter();
23
+ callback = null;
24
+ attached = false;
25
+ constructor(ws) {
26
+ this.ws = ws;
27
+ }
28
+ get onError() {
29
+ return this.errorEmitter.event;
30
+ }
31
+ get onClose() {
32
+ return this.closeEmitter.event;
33
+ }
34
+ get onPartialMessage() {
35
+ return this.partialEmitter.event;
36
+ }
37
+ onMessage = (event) => {
38
+ if (!this.callback)
39
+ return;
40
+ let message;
41
+ try {
42
+ message = JSON.parse(toText(event.data));
43
+ }
44
+ catch (error) {
45
+ this.errorEmitter.fire(error instanceof Error ? error : new Error(String(error)));
46
+ return;
47
+ }
48
+ this.callback(message);
49
+ };
50
+ onCloseEvent = () => {
51
+ this.closeEmitter.fire(undefined);
52
+ };
53
+ onErrorEvent = () => {
54
+ this.errorEmitter.fire(new Error('WebSocket error'));
55
+ };
56
+ listen(callback) {
57
+ if (this.attached) {
58
+ throw new Error('WebSocketMessageReader is already listening');
59
+ }
60
+ this.callback = callback;
61
+ this.ws.addEventListener('message', this.onMessage);
62
+ this.ws.addEventListener('close', this.onCloseEvent);
63
+ this.ws.addEventListener('error', this.onErrorEvent);
64
+ this.attached = true;
65
+ return { dispose: () => this.dispose() };
66
+ }
67
+ dispose() {
68
+ if (!this.attached)
69
+ return;
70
+ this.ws.removeEventListener('message', this.onMessage);
71
+ this.ws.removeEventListener('close', this.onCloseEvent);
72
+ this.ws.removeEventListener('error', this.onErrorEvent);
73
+ this.attached = false;
74
+ this.callback = null;
75
+ this.errorEmitter.dispose();
76
+ this.closeEmitter.dispose();
77
+ this.partialEmitter.dispose();
78
+ }
79
+ }
80
+ class WebSocketMessageWriter {
81
+ ws;
82
+ errorEmitter = new Emitter();
83
+ closeEmitter = new Emitter();
84
+ attached = false;
85
+ onCloseEvent = () => {
86
+ this.closeEmitter.fire(undefined);
87
+ };
88
+ constructor(ws) {
89
+ this.ws = ws;
90
+ this.ws.addEventListener('close', this.onCloseEvent);
91
+ this.attached = true;
92
+ }
93
+ get onError() {
94
+ return this.errorEmitter.event;
95
+ }
96
+ get onClose() {
97
+ return this.closeEmitter.event;
98
+ }
99
+ async write(message) {
100
+ if (this.ws.readyState !== OPEN) {
101
+ throw new Error(`WebSocket is not open (readyState=${this.ws.readyState})`);
102
+ }
103
+ try {
104
+ this.ws.send(JSON.stringify(message));
105
+ }
106
+ catch (error) {
107
+ const err = error instanceof Error ? error : new Error(String(error));
108
+ this.errorEmitter.fire([err, message, undefined]);
109
+ throw err;
110
+ }
111
+ }
112
+ end() {
113
+ if (this.ws.readyState === OPEN)
114
+ this.ws.close();
115
+ }
116
+ dispose() {
117
+ if (!this.attached)
118
+ return;
119
+ this.ws.removeEventListener('close', this.onCloseEvent);
120
+ this.attached = false;
121
+ this.errorEmitter.dispose();
122
+ this.closeEmitter.dispose();
123
+ }
124
+ }
125
+ /**
126
+ * Create an {@link MdxpConnection} over a WebSocket.
127
+ *
128
+ * Each WebSocket message carries exactly one complete JSON-RPC 2.0 message —
129
+ * the WebSocket protocol already frames messages, so there is no Content-Length
130
+ * framing. Works with the browser `WebSocket` and the Node `ws` package alike
131
+ * (both satisfy {@link WebSocketLike}).
132
+ *
133
+ * Register handlers first, then call `listen()`:
134
+ *
135
+ * ```ts
136
+ * const conn = fromWebSocket(ws)
137
+ * conn.onNotification('$/task/progress', (p) => {})
138
+ * conn.listen()
139
+ * ```
140
+ */
141
+ export function fromWebSocket(ws) {
142
+ return createMdxpConnection(new WebSocketMessageReader(ws), new WebSocketMessageWriter(ws));
143
+ }
144
+ //# sourceMappingURL=ws.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ws.js","sourceRoot":"","sources":["../../src/transports/ws.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@motrix/mdxp",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Motrix Download eXchange Protocol — JSON-RPC 2.0 wire types and connection helpers",
5
5
  "license": "MIT",
6
6
  "author": "Motrix",
package/src/browser.ts CHANGED
@@ -20,3 +20,8 @@ export {
20
20
  BrowserMessageWriter,
21
21
  } from 'vscode-jsonrpc/browser'
22
22
  export * from './index.js'
23
+ export type { WorkerLike } from './transports/worker.js'
24
+ export { fromWorker } from './transports/worker.js'
25
+ export type { WebSocketLike } from './transports/ws.js'
26
+ // High-level convenience constructors for browser transports.
27
+ export { fromWebSocket } from './transports/ws.js'
package/src/node.ts CHANGED
@@ -26,3 +26,8 @@ export {
26
26
  StreamMessageWriter,
27
27
  } from 'vscode-jsonrpc/node'
28
28
  export * from './index.js'
29
+ export type { StdioOptions } from './transports/stdio.js'
30
+ // High-level convenience constructors for Node transports.
31
+ export { fromStdio } from './transports/stdio.js'
32
+ export type { WebSocketLike } from './transports/ws.js'
33
+ export { fromWebSocket } from './transports/ws.js'
@@ -0,0 +1,27 @@
1
+ import { StreamMessageReader, StreamMessageWriter } from 'vscode-jsonrpc/node'
2
+ import type { MdxpConnection } from '../connection.js'
3
+ import { createMdxpConnection } from '../connection.js'
4
+
5
+ /** Streams for {@link fromStdio}; defaults to the process's stdio. */
6
+ export interface StdioOptions {
7
+ input?: NodeJS.ReadableStream
8
+ output?: NodeJS.WritableStream
9
+ }
10
+
11
+ /**
12
+ * Create an {@link MdxpConnection} over Node streams — `process.stdin` /
13
+ * `process.stdout` by default. This is the native-messaging-host / CLI case:
14
+ * the parent process pipes JSON-RPC over the child's stdio.
15
+ *
16
+ * ```ts
17
+ * const conn = fromStdio()
18
+ * conn.onRequest('url/resolve', async (params) => {})
19
+ * conn.listen()
20
+ * ```
21
+ */
22
+ export function fromStdio(options: StdioOptions = {}): MdxpConnection {
23
+ return createMdxpConnection(
24
+ new StreamMessageReader(options.input ?? process.stdin),
25
+ new StreamMessageWriter(options.output ?? process.stdout)
26
+ )
27
+ }
@@ -0,0 +1,49 @@
1
+ import type { MessageReader, MessageWriter } from 'vscode-jsonrpc'
2
+ import {
3
+ BrowserMessageReader,
4
+ BrowserMessageWriter,
5
+ } from 'vscode-jsonrpc/browser'
6
+ import type { MdxpConnection } from '../connection.js'
7
+ import { createMdxpConnection } from '../connection.js'
8
+
9
+ /**
10
+ * Minimal structural subset of a `MessagePort` / `Worker` /
11
+ * `DedicatedWorkerGlobalScope` — anything the browser RAL's reader/writer
12
+ * accept. Consumers pass their worker or port as-is.
13
+ */
14
+ export interface WorkerLike {
15
+ postMessage(message: unknown): void
16
+ addEventListener(
17
+ type: 'message',
18
+ listener: (event: { readonly data: unknown }) => void
19
+ ): void
20
+ removeEventListener(
21
+ type: 'message',
22
+ listener: (event: { readonly data: unknown }) => void
23
+ ): void
24
+ }
25
+
26
+ // vscode-jsonrpc types the port as `MessagePort | Worker | DedicatedWorkerGlobalScope`,
27
+ // which are DOM / WebWorker globals. This package targets `lib: ES2022` (no DOM),
28
+ // so re-type the constructors against the structural `WorkerLike` instead of
29
+ // pulling the DOM libs into every consumer.
30
+ const Reader = BrowserMessageReader as unknown as new (
31
+ port: WorkerLike
32
+ ) => MessageReader
33
+ const Writer = BrowserMessageWriter as unknown as new (
34
+ port: WorkerLike
35
+ ) => MessageWriter
36
+
37
+ /**
38
+ * Create an {@link MdxpConnection} over a Worker or `MessagePort` (the browser
39
+ * `postMessage` transport).
40
+ *
41
+ * ```ts
42
+ * const conn = fromWorker(self) // inside a Web Worker
43
+ * conn.onNotification('$/task/progress', (p) => {})
44
+ * conn.listen()
45
+ * ```
46
+ */
47
+ export function fromWorker(port: WorkerLike): MdxpConnection {
48
+ return createMdxpConnection(new Reader(port), new Writer(port))
49
+ }
@@ -0,0 +1,188 @@
1
+ import type {
2
+ DataCallback,
3
+ Disposable,
4
+ Message,
5
+ MessageReader,
6
+ MessageWriter,
7
+ PartialMessageInfo,
8
+ } from 'vscode-jsonrpc'
9
+ import { Emitter } from 'vscode-jsonrpc'
10
+ import type { MdxpConnection } from '../connection.js'
11
+ import { createMdxpConnection } from '../connection.js'
12
+
13
+ /** `WebSocket.readyState` value for an open socket (browser and `ws` agree). */
14
+ const OPEN = 1
15
+
16
+ /**
17
+ * Minimal structural subset of a WebSocket shared by the browser `WebSocket`
18
+ * and the Node [`ws`](https://www.npmjs.com/package/ws) package. Consumers pass
19
+ * their socket as-is — no wrapping required.
20
+ */
21
+ export interface WebSocketLike {
22
+ readonly readyState: number
23
+ send(data: string): void
24
+ close(code?: number, reason?: string): void
25
+ addEventListener(
26
+ type: 'message' | 'close' | 'error',
27
+ listener: (event: { readonly data?: unknown }) => void
28
+ ): void
29
+ removeEventListener(
30
+ type: 'message' | 'close' | 'error',
31
+ listener: (event: { readonly data?: unknown }) => void
32
+ ): void
33
+ }
34
+
35
+ function toText(data: unknown): string {
36
+ if (typeof data === 'string') return data
37
+ if (data instanceof ArrayBuffer) return new TextDecoder().decode(data)
38
+ // A typed array / DataView / Node Buffer. TS 6's generic `ArrayBufferView`
39
+ // typing is stricter than `TextDecoder.decode`'s runtime contract (which
40
+ // accepts any view), so widen to satisfy the compiler.
41
+ if (ArrayBuffer.isView(data)) {
42
+ return new TextDecoder().decode(data as unknown as Uint8Array)
43
+ }
44
+ throw new Error('unsupported WebSocket message data type')
45
+ }
46
+
47
+ class WebSocketMessageReader implements MessageReader {
48
+ private readonly errorEmitter = new Emitter<Error>()
49
+ private readonly closeEmitter = new Emitter<void>()
50
+ private readonly partialEmitter = new Emitter<PartialMessageInfo>()
51
+ private callback: DataCallback | null = null
52
+ private attached = false
53
+
54
+ constructor(private readonly ws: WebSocketLike) {}
55
+
56
+ get onError() {
57
+ return this.errorEmitter.event
58
+ }
59
+
60
+ get onClose() {
61
+ return this.closeEmitter.event
62
+ }
63
+
64
+ get onPartialMessage() {
65
+ return this.partialEmitter.event
66
+ }
67
+
68
+ private readonly onMessage = (event: { readonly data?: unknown }): void => {
69
+ if (!this.callback) return
70
+ let message: Message
71
+ try {
72
+ message = JSON.parse(toText(event.data)) as Message
73
+ } catch (error) {
74
+ this.errorEmitter.fire(
75
+ error instanceof Error ? error : new Error(String(error))
76
+ )
77
+ return
78
+ }
79
+ this.callback(message)
80
+ }
81
+
82
+ private readonly onCloseEvent = (): void => {
83
+ this.closeEmitter.fire(undefined)
84
+ }
85
+
86
+ private readonly onErrorEvent = (): void => {
87
+ this.errorEmitter.fire(new Error('WebSocket error'))
88
+ }
89
+
90
+ listen(callback: DataCallback): Disposable {
91
+ if (this.attached) {
92
+ throw new Error('WebSocketMessageReader is already listening')
93
+ }
94
+ this.callback = callback
95
+ this.ws.addEventListener('message', this.onMessage)
96
+ this.ws.addEventListener('close', this.onCloseEvent)
97
+ this.ws.addEventListener('error', this.onErrorEvent)
98
+ this.attached = true
99
+ return { dispose: () => this.dispose() }
100
+ }
101
+
102
+ dispose(): void {
103
+ if (!this.attached) return
104
+ this.ws.removeEventListener('message', this.onMessage)
105
+ this.ws.removeEventListener('close', this.onCloseEvent)
106
+ this.ws.removeEventListener('error', this.onErrorEvent)
107
+ this.attached = false
108
+ this.callback = null
109
+ this.errorEmitter.dispose()
110
+ this.closeEmitter.dispose()
111
+ this.partialEmitter.dispose()
112
+ }
113
+ }
114
+
115
+ class WebSocketMessageWriter implements MessageWriter {
116
+ private readonly errorEmitter = new Emitter<
117
+ [Error, Message | undefined, number | undefined]
118
+ >()
119
+ private readonly closeEmitter = new Emitter<void>()
120
+ private attached = false
121
+
122
+ private readonly onCloseEvent = (): void => {
123
+ this.closeEmitter.fire(undefined)
124
+ }
125
+
126
+ constructor(private readonly ws: WebSocketLike) {
127
+ this.ws.addEventListener('close', this.onCloseEvent)
128
+ this.attached = true
129
+ }
130
+
131
+ get onError() {
132
+ return this.errorEmitter.event
133
+ }
134
+
135
+ get onClose() {
136
+ return this.closeEmitter.event
137
+ }
138
+
139
+ async write(message: Message): Promise<void> {
140
+ if (this.ws.readyState !== OPEN) {
141
+ throw new Error(
142
+ `WebSocket is not open (readyState=${this.ws.readyState})`
143
+ )
144
+ }
145
+ try {
146
+ this.ws.send(JSON.stringify(message))
147
+ } catch (error) {
148
+ const err = error instanceof Error ? error : new Error(String(error))
149
+ this.errorEmitter.fire([err, message, undefined])
150
+ throw err
151
+ }
152
+ }
153
+
154
+ end(): void {
155
+ if (this.ws.readyState === OPEN) this.ws.close()
156
+ }
157
+
158
+ dispose(): void {
159
+ if (!this.attached) return
160
+ this.ws.removeEventListener('close', this.onCloseEvent)
161
+ this.attached = false
162
+ this.errorEmitter.dispose()
163
+ this.closeEmitter.dispose()
164
+ }
165
+ }
166
+
167
+ /**
168
+ * Create an {@link MdxpConnection} over a WebSocket.
169
+ *
170
+ * Each WebSocket message carries exactly one complete JSON-RPC 2.0 message —
171
+ * the WebSocket protocol already frames messages, so there is no Content-Length
172
+ * framing. Works with the browser `WebSocket` and the Node `ws` package alike
173
+ * (both satisfy {@link WebSocketLike}).
174
+ *
175
+ * Register handlers first, then call `listen()`:
176
+ *
177
+ * ```ts
178
+ * const conn = fromWebSocket(ws)
179
+ * conn.onNotification('$/task/progress', (p) => {})
180
+ * conn.listen()
181
+ * ```
182
+ */
183
+ export function fromWebSocket(ws: WebSocketLike): MdxpConnection {
184
+ return createMdxpConnection(
185
+ new WebSocketMessageReader(ws),
186
+ new WebSocketMessageWriter(ws)
187
+ )
188
+ }