@motrix/mdxp 0.1.1 → 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
@@ -33,7 +33,11 @@ top of it.
33
33
  - **Transport-agnostic.** Works over anything that implements
34
34
  `MessageReader`/`MessageWriter`.
35
35
  - **Platform RAL entry points.** `./node` and `./browser` install the matching
36
- `vscode-jsonrpc` runtime abstraction layer for you.
36
+ `vscode-jsonrpc` runtime abstraction layer and re-export its transport classes,
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.
37
41
  - **Agent-ready.** A built-in tool registry emits a JSON-Schema tool catalog you
38
42
  can feed straight into an LLM function-calling API.
39
43
  - **Forward-compatible.** Unknown methods and fields are ignored, not rejected.
@@ -46,10 +50,11 @@ npm install @motrix/mdxp
46
50
  ```
47
51
 
48
52
  Runtime dependency: [`vscode-jsonrpc`](https://www.npmjs.com/package/vscode-jsonrpc)
49
- `^9`, a direct dependency installed alongside this package. The examples below
50
- also import reader/writer classes straight from `vscode-jsonrpc/node` and
51
- `vscode-jsonrpc/browser`; if you do the same, declare `vscode-jsonrpc` in your
52
- own dependencies rather than relying on hoisting. ESM-only; requires
53
+ `^9`, installed alongside this package. Its transport classes (reader/writer)
54
+ and the primitives that surface in this package's API — `MessageReader`,
55
+ `MessageConnection`, `CancellationToken`, `CancellationTokenSource`, … — are
56
+ re-exported from `@motrix/mdxp` (see [Entry points](#entry-points)), so you
57
+ rarely need to import `vscode-jsonrpc` directly. ESM-only; requires
53
58
  Node.js ≥ 18 or a modern bundler.
54
59
 
55
60
  ### Entry points
@@ -63,21 +68,20 @@ Node.js ≥ 18 or a modern bundler.
63
68
  `vscode-jsonrpc` v9 requires a runtime abstraction layer (RAL) to be installed
64
69
  before a connection can be created. Importing `@motrix/mdxp/node` or
65
70
  `@motrix/mdxp/browser` installs the right one into the **same** `vscode-jsonrpc`
66
- instance this package uses, and re-exports the entire public API — so a host
67
- imports everything from a single place.
71
+ instance this package uses, and re-exports the entire public API **plus that
72
+ platform's transport classes** (`StreamMessageReader`/`Writer` for Node,
73
+ `BrowserMessageReader`/`Writer` for the browser) — so a host imports everything,
74
+ including its reader/writer, from a single place.
68
75
 
69
76
  ## Quick start
70
77
 
71
78
  ### Node host (over stdio)
72
79
 
73
80
  ```ts
74
- import { createMdxpConnection } from '@motrix/mdxp/node'
75
- import { StreamMessageReader, StreamMessageWriter } from 'vscode-jsonrpc/node'
81
+ import { fromStdio } from '@motrix/mdxp/node'
76
82
 
77
- const conn = createMdxpConnection(
78
- new StreamMessageReader(process.stdin),
79
- new StreamMessageWriter(process.stdout),
80
- )
83
+ // Over process.stdin / process.stdout (the native-messaging / CLI case).
84
+ const conn = fromStdio()
81
85
 
82
86
  // Register handlers BEFORE listen().
83
87
  conn.onNotification('$/task/progress', (p) => {
@@ -88,21 +92,31 @@ conn.onNotification('$/task/progress', (p) => {
88
92
  conn.listen()
89
93
  ```
90
94
 
91
- ### Browser host
95
+ ### Browser host (over WebSocket)
92
96
 
93
97
  ```ts
94
- // Installs the browser RAL, then re-exports the full API.
95
- import { createMdxpConnection } from '@motrix/mdxp/browser'
96
- import { BrowserMessageReader, BrowserMessageWriter } from 'vscode-jsonrpc/browser'
97
-
98
- // e.g. a MessagePort / Worker; adapt your WebSocket to reader/writer as needed.
99
- const conn = createMdxpConnection(
100
- new BrowserMessageReader(worker),
101
- new BrowserMessageWriter(worker),
102
- )
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) => {})
103
102
  conn.listen()
104
103
  ```
105
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
+
106
120
  ## Core concepts
107
121
 
108
122
  **Server vs. client.** The Motrix desktop app is the **server** — it owns the
@@ -245,7 +259,7 @@ conn.onNotification('$/task/error', (p) => {
245
259
  handler observes `token.isCancellationRequested`.
246
260
 
247
261
  ```ts
248
- import { CancellationTokenSource } from 'vscode-jsonrpc'
262
+ import { CancellationTokenSource } from '@motrix/mdxp'
249
263
 
250
264
  const cts = new CancellationTokenSource()
251
265
  const pending = conn.sendRequest('url/resolve', { url }, cts.token)
@@ -311,6 +325,7 @@ const tools = toAgentToolCatalog()
311
325
  | Export | Kind | Purpose |
312
326
  | --- | --- | --- |
313
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`). |
314
329
  | `MdxpConnection` | type | The connection interface (`sendRequest`, `onRequest`, `sendNotification`, `onNotification`, `dispose`, `raw`). |
315
330
  | `MdxpRequestMap` / `MdxpNotificationMap` | type | Method/notification name → params/result type maps. |
316
331
  | `Methods` / `Notifications` | const | Wire-name constants (`Methods.DownloadAdd === 'download/add'`). |
@@ -321,6 +336,7 @@ const tools = toAgentToolCatalog()
321
336
  | `toAgentToolCatalog()` | function | The `agentFacing` subset as JSON-Schema tools. |
322
337
  | `SERVER_INITIATED_METHODS` | const | Methods the server calls on the client (`url/probe`, `url/resolve`). |
323
338
  | `*Schema` | Zod schema | Every wire shape, for runtime validation. |
339
+ | `MessageReader` · `MessageWriter` · `MessageConnection` · `CancellationToken` · `CancellationTokenSource` · `Disposable` | re-export | `vscode-jsonrpc` primitives used across the API. Platform transport classes (`StreamMessageReader`/`Writer`, `BrowserMessageReader`/`Writer`) are re-exported from `./node` and `./browser`. |
324
340
 
325
341
  ### Methods
326
342
 
package/README.zh-CN.md CHANGED
@@ -28,7 +28,10 @@
28
28
  调用处无需任何 cast。
29
29
  - **transport-agnostic**:只要实现了 `MessageReader`/`MessageWriter`,任何双工流都能用。
30
30
  - **平台 RAL 入口**:`./node` 与 `./browser` 会替你装好对应的 `vscode-jsonrpc`
31
- runtime abstraction layer。
31
+ runtime abstraction layer,并 re-export 它的 transport class —— 连接封装与
32
+ reader/writer 都从同一处 import。
33
+ - **一步构造**:`fromWebSocket`、`fromStdio`、`fromWorker` 一行即可在常见 transport
34
+ 上建好连接;`createMdxpConnection` 仍是通用的底层入口。
32
35
  - **面向 agent**:内置的 tool registry 可产出一份 JSON-Schema tool catalog,
33
36
  能直接对接 LLM 的 function-calling API。
34
37
  - **forward-compatible**:遇到未知的 method 或字段选择忽略,而非 reject。
@@ -41,10 +44,11 @@ npm install @motrix/mdxp
41
44
  ```
42
45
 
43
46
  运行时依赖:[`vscode-jsonrpc`](https://www.npmjs.com/package/vscode-jsonrpc)
44
- `^9`,是本包的直接 `dependency`,会随本包一并装上。下面的示例还直接从
45
- `vscode-jsonrpc/node` 和 `vscode-jsonrpc/browser` import reader/writer 类;若你也
46
- 这样用,请在自己的 dependencies 里显式声明 `vscode-jsonrpc`,不要依赖 hoisting。
47
- 仅 ESM;需要 Node.js ≥ 18 或现代 bundler。
47
+ `^9`,随本包一并装上。它的 transport class(reader/writer),以及在本包 API 中出现的
48
+ 那些 primitive —— `MessageReader`、`MessageConnection`、`CancellationToken`、
49
+ `CancellationTokenSource` 等 —— 都已从 `@motrix/mdxp` re-export(见
50
+ [入口点](#入口点)),因此你几乎无需直接 import `vscode-jsonrpc`。仅 ESM;需要
51
+ Node.js ≥ 18 或现代 bundler。
48
52
 
49
53
  ### 入口点
50
54
 
@@ -56,21 +60,19 @@ npm install @motrix/mdxp
56
60
 
57
61
  `vscode-jsonrpc` v9 要求先装好一层 runtime abstraction layer(RAL),才能创建连接。
58
62
  import `@motrix/mdxp/node` 或 `@motrix/mdxp/browser` 会把对应的 RAL 装进本包所用的
59
- **同一个** `vscode-jsonrpc` 实例,并 re-export 完整的公开 API —— 于是 host 只需从
60
- 一个入口 import 一切。
63
+ **同一个** `vscode-jsonrpc` 实例,并 re-export 完整的公开 API,**外加该平台的
64
+ transport class**(Node 的 `StreamMessageReader`/`Writer`、浏览器的
65
+ `BrowserMessageReader`/`Writer`)—— 于是 host 连同 reader/writer 都只需从一个入口 import。
61
66
 
62
67
  ## 快速开始
63
68
 
64
69
  ### Node host(走 stdio)
65
70
 
66
71
  ```ts
67
- import { createMdxpConnection } from '@motrix/mdxp/node'
68
- import { StreamMessageReader, StreamMessageWriter } from 'vscode-jsonrpc/node'
72
+ import { fromStdio } from '@motrix/mdxp/node'
69
73
 
70
- const conn = createMdxpConnection(
71
- new StreamMessageReader(process.stdin),
72
- new StreamMessageWriter(process.stdout),
73
- )
74
+ // 走 process.stdin / process.stdout(native-messaging / CLI 场景)。
75
+ const conn = fromStdio()
74
76
 
75
77
  // 务必在 listen() 之前注册 handler。
76
78
  conn.onNotification('$/task/progress', (p) => {
@@ -81,21 +83,30 @@ conn.onNotification('$/task/progress', (p) => {
81
83
  conn.listen()
82
84
  ```
83
85
 
84
- ### 浏览器 host
86
+ ### 浏览器 host(走 WebSocket)
85
87
 
86
88
  ```ts
87
- // 装好 browser RAL,并 re-export 全量 API。
88
- import { createMdxpConnection } from '@motrix/mdxp/browser'
89
- import { BrowserMessageReader, BrowserMessageWriter } from 'vscode-jsonrpc/browser'
90
-
91
- // 例如一个 MessagePort / Worker;若用 WebSocket,需自行适配成 reader/writer。
92
- const conn = createMdxpConnection(
93
- new BrowserMessageReader(worker),
94
- new BrowserMessageWriter(worker),
95
- )
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) => {})
96
93
  conn.listen()
97
94
  ```
98
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
+
99
110
  ## 核心概念
100
111
 
101
112
  **server 与 client**:Motrix 桌面端是 **server** —— 下载引擎由它持有。**client**
@@ -232,7 +243,7 @@ conn.onNotification('$/task/error', (p) => {
232
243
  `token.isCancellationRequested` 即可响应。
233
244
 
234
245
  ```ts
235
- import { CancellationTokenSource } from 'vscode-jsonrpc'
246
+ import { CancellationTokenSource } from '@motrix/mdxp'
236
247
 
237
248
  const cts = new CancellationTokenSource()
238
249
  const pending = conn.sendRequest('url/resolve', { url }, cts.token)
@@ -296,6 +307,7 @@ const tools = toAgentToolCatalog()
296
307
  | 导出 | 类型 | 作用 |
297
308
  | --- | --- | --- |
298
309
  | `createMdxpConnection(reader, writer)` | function | 把一对 reader/writer 封装成类型化的 `MdxpConnection`。 |
310
+ | `fromWebSocket` · `fromStdio` · `fromWorker` | function | 在 WebSocket / stdio / Worker 上的一步构造器(来自 `./node` · `./browser`)。 |
299
311
  | `MdxpConnection` | type | 连接接口(`sendRequest`、`onRequest`、`sendNotification`、`onNotification`、`dispose`、`raw`)。 |
300
312
  | `MdxpRequestMap` / `MdxpNotificationMap` | type | method / notification 名 → params/result 类型的映射表。 |
301
313
  | `Methods` / `Notifications` | const | wire 名常量(`Methods.DownloadAdd === 'download/add'`)。 |
@@ -306,6 +318,7 @@ const tools = toAgentToolCatalog()
306
318
  | `toAgentToolCatalog()` | function | 取 `agentFacing` 子集,转成 JSON-Schema tools。 |
307
319
  | `SERVER_INITIATED_METHODS` | const | 由 server 向 client 发起的 method(`url/probe`、`url/resolve`)。 |
308
320
  | `*Schema` | Zod schema | 全部 wire 形态,供运行时校验。 |
321
+ | `MessageReader` · `MessageWriter` · `MessageConnection` · `CancellationToken` · `CancellationTokenSource` · `Disposable` | re-export | 本包 API 中用到的 `vscode-jsonrpc` primitive。平台 transport class(`StreamMessageReader`/`Writer`、`BrowserMessageReader`/`Writer`)从 `./node` 与 `./browser` re-export。 |
309
322
 
310
323
  ### Methods
311
324
 
package/dist/browser.d.ts CHANGED
@@ -1,3 +1,8 @@
1
1
  import 'vscode-jsonrpc/browser';
2
+ export { BrowserMessageReader, BrowserMessageWriter, } from 'vscode-jsonrpc/browser';
2
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';
3
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;AAE/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
@@ -10,5 +10,13 @@
10
10
  // `@motrix/mdxp/browser` once at startup. The full public API is re-exported, so
11
11
  // `import { createMdxpConnection } from '@motrix/mdxp/browser'` also works.
12
12
  import 'vscode-jsonrpc/browser';
13
+ // Browser transport classes, re-exported so a browser host imports its
14
+ // reader/writer from the same place as `createMdxpConnection` — no separate
15
+ // `vscode-jsonrpc` import, and guaranteed to be the instance this package
16
+ // installed the RAL into.
17
+ export { BrowserMessageReader, BrowserMessageWriter, } from 'vscode-jsonrpc/browser';
13
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';
14
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,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/index.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ export type { Disposable, MessageConnection, MessageReader, MessageWriter, } from 'vscode-jsonrpc';
2
+ export { CancellationToken, CancellationTokenSource } from 'vscode-jsonrpc';
1
3
  export type { MdxpConnection, MdxpNotificationMap, MdxpRequestMap, } from './connection.js';
2
4
  export { createMdxpConnection } from './connection.js';
3
5
  export type { ErrorCode, MdxpError, MdxpErrorData } from './errors.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,YAAY,EACV,cAAc,EACd,mBAAmB,EACnB,cAAc,GACf,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAA;AACtD,YAAY,EAAE,SAAS,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AACtE,OAAO,EACL,UAAU,EACV,aAAa,EACb,eAAe,EACf,aAAa,GACd,MAAM,aAAa,CAAA;AACpB,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AACxD,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAGrD,cAAc,oBAAoB,CAAA;AAClC,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAGxD,OAAO,EACL,wBAAwB,EACxB,KAAK,EACL,kBAAkB,GACnB,MAAM,YAAY,CAAA;AAEnB,mBAAmB,YAAY,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAOA,YAAY,EACV,UAAU,EACV,iBAAiB,EACjB,aAAa,EACb,aAAa,GACd,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAA;AAC3E,YAAY,EACV,cAAc,EACd,mBAAmB,EACnB,cAAc,GACf,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAA;AACtD,YAAY,EAAE,SAAS,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AACtE,OAAO,EACL,UAAU,EACV,aAAa,EACb,eAAe,EACf,aAAa,GACd,MAAM,aAAa,CAAA;AACpB,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AACxD,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAGrD,cAAc,oBAAoB,CAAA;AAClC,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAGxD,OAAO,EACL,wBAAwB,EACxB,KAAK,EACL,kBAAkB,GACnB,MAAM,YAAY,CAAA;AAEnB,mBAAmB,YAAY,CAAA"}
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  // MDXP — Motrix Download eXchange Protocol
2
2
  // Public API surface for both Motrix desktop and the browser extension.
3
+ export { CancellationToken, CancellationTokenSource } from 'vscode-jsonrpc';
3
4
  export { createMdxpConnection } from './connection.js';
4
5
  export { ErrorCodes, isMotrixError, isProtocolError, makeMdxpError, } from './errors.js';
5
6
  export { Methods, Notifications } from './methods.js';
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,2CAA2C;AAC3C,wEAAwE;AAOxE,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAA;AAEtD,OAAO,EACL,UAAU,EACV,aAAa,EACb,eAAe,EACf,aAAa,GACd,MAAM,aAAa,CAAA;AAEpB,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAErD,qDAAqD;AACrD,cAAc,oBAAoB,CAAA;AAGlC,wCAAwC;AACxC,OAAO,EACL,wBAAwB,EACxB,KAAK,EACL,kBAAkB,GACnB,MAAM,YAAY,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,2CAA2C;AAC3C,wEAAwE;AAYxE,OAAO,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAA;AAM3E,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAA;AAEtD,OAAO,EACL,UAAU,EACV,aAAa,EACb,eAAe,EACf,aAAa,GACd,MAAM,aAAa,CAAA;AAEpB,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAErD,qDAAqD;AACrD,cAAc,oBAAoB,CAAA;AAGlC,wCAAwC;AACxC,OAAO,EACL,wBAAwB,EACxB,KAAK,EACL,kBAAkB,GACnB,MAAM,YAAY,CAAA"}
package/dist/node.d.ts CHANGED
@@ -1,3 +1,8 @@
1
1
  import 'vscode-jsonrpc/node';
2
+ export { IPCMessageReader, IPCMessageWriter, PortMessageReader, PortMessageWriter, SocketMessageReader, SocketMessageWriter, StreamMessageReader, StreamMessageWriter, } from 'vscode-jsonrpc/node';
2
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';
3
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;AAE5B,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
@@ -11,5 +11,12 @@
11
11
  // once at bootstrap. The full public API is re-exported for convenience, so
12
12
  // `import { createMdxpConnection } from '@motrix/mdxp/node'` also works.
13
13
  import 'vscode-jsonrpc/node';
14
+ // Node transport classes, re-exported so a Node host imports its reader/writer
15
+ // from the same place as `createMdxpConnection` — no separate `vscode-jsonrpc`
16
+ // import, and guaranteed to be the instance this package installed the RAL into.
17
+ export { IPCMessageReader, IPCMessageWriter, PortMessageReader, PortMessageWriter, SocketMessageReader, SocketMessageWriter, StreamMessageReader, StreamMessageWriter, } from 'vscode-jsonrpc/node';
14
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';
15
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,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.1.1",
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
@@ -11,4 +11,17 @@
11
11
  // `import { createMdxpConnection } from '@motrix/mdxp/browser'` also works.
12
12
  import 'vscode-jsonrpc/browser'
13
13
 
14
+ // Browser transport classes, re-exported so a browser host imports its
15
+ // reader/writer from the same place as `createMdxpConnection` — no separate
16
+ // `vscode-jsonrpc` import, and guaranteed to be the instance this package
17
+ // installed the RAL into.
18
+ export {
19
+ BrowserMessageReader,
20
+ BrowserMessageWriter,
21
+ } from 'vscode-jsonrpc/browser'
14
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/index.ts CHANGED
@@ -1,6 +1,17 @@
1
1
  // MDXP — Motrix Download eXchange Protocol
2
2
  // Public API surface for both Motrix desktop and the browser extension.
3
3
 
4
+ // vscode-jsonrpc primitives that appear in this package's public API surface,
5
+ // re-exported so consumers import them from `@motrix/mdxp` instead of reaching
6
+ // for a second package. Platform transport classes (StreamMessageReader,
7
+ // BrowserMessageReader, …) are re-exported from `./node` and `./browser`.
8
+ export type {
9
+ Disposable,
10
+ MessageConnection,
11
+ MessageReader,
12
+ MessageWriter,
13
+ } from 'vscode-jsonrpc'
14
+ export { CancellationToken, CancellationTokenSource } from 'vscode-jsonrpc'
4
15
  export type {
5
16
  MdxpConnection,
6
17
  MdxpNotificationMap,
package/src/node.ts CHANGED
@@ -12,4 +12,22 @@
12
12
  // `import { createMdxpConnection } from '@motrix/mdxp/node'` also works.
13
13
  import 'vscode-jsonrpc/node'
14
14
 
15
+ // Node transport classes, re-exported so a Node host imports its reader/writer
16
+ // from the same place as `createMdxpConnection` — no separate `vscode-jsonrpc`
17
+ // import, and guaranteed to be the instance this package installed the RAL into.
18
+ export {
19
+ IPCMessageReader,
20
+ IPCMessageWriter,
21
+ PortMessageReader,
22
+ PortMessageWriter,
23
+ SocketMessageReader,
24
+ SocketMessageWriter,
25
+ StreamMessageReader,
26
+ StreamMessageWriter,
27
+ } from 'vscode-jsonrpc/node'
15
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
+ }