@hydranium/data-client-vscode 1.0.0-next.322 → 1.0.0-next.323

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
@@ -16,6 +16,8 @@ to it over a `vscode-messenger` hop, and the webview's data port reads it.
16
16
  - The webview's `DataPort`, for the connection and sessions from
17
17
  `@hydranium/protocol`, which rejects pending requests when the extension host
18
18
  reports the relay lost.
19
+ - A `DataPort` over a socket for code in the extension host itself, such as a
20
+ command, which looks the port up again for every connection.
19
21
 
20
22
  ## Install
21
23
 
@@ -78,6 +80,22 @@ messenger.start();
78
80
  const connection = new DataConnectionWithEvents(port);
79
81
  ```
80
82
 
83
+ For code in the extension host itself, give the connection a `SocketDataPort`
84
+ instead, which opens the socket there; nothing connects until the first
85
+ request:
86
+
87
+ <!-- snippet-preamble
88
+ declare function findDataServerPort(): Promise<number>;
89
+ -->
90
+
91
+ ```ts
92
+ import { SocketDataPort } from '@hydranium/data-client-vscode/node';
93
+ import { DataConnectionWithEvents } from '@hydranium/protocol';
94
+
95
+ const port = new SocketDataPort({ findPort: findDataServerPort });
96
+ const connection = new DataConnectionWithEvents(port);
97
+ ```
98
+
81
99
  To test a webview's data path in Node, `makeMessengerHarness` from `./testing`
82
100
  joins the real extension-side and webview-side messengers through a fake
83
101
  panel and a fake `window`, so the test runs their real routing.
@@ -90,7 +108,7 @@ For the whole setup, see *Host in VS Code* in
90
108
  | Subpath | Use it for | Runs in |
91
109
  | ----------- | --------------------------------------------- | -------------------------- |
92
110
  | `.` | The messenger channel both ends build on | anywhere |
93
- | `./node` | The relay from the data-server's socket | the VS Code extension host |
111
+ | `./node` | The relay, and the extension host's own port | the VS Code extension host |
94
112
  | `./browser` | The webview's data port | a webview |
95
113
  | `./testing` | The real messenger pair joined in a Node test | a test |
96
114
 
@@ -7,4 +7,5 @@
7
7
  * SPDX-License-Identifier: MIT
8
8
  ********************************************************************************/
9
9
  export * from './data-server-connection';
10
+ export * from './socket-data-port';
10
11
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/node/index.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAElF,cAAc,0BAA0B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/node/index.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAElF,cAAc,0BAA0B,CAAC;AACzC,cAAc,oBAAoB,CAAC"}
package/lib/node/index.js CHANGED
@@ -23,4 +23,5 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
23
23
  };
24
24
  Object.defineProperty(exports, "__esModule", { value: true });
25
25
  __exportStar(require("./data-server-connection"), exports);
26
+ __exportStar(require("./socket-data-port"), exports);
26
27
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/node/index.ts"],"names":[],"mappings":";AAAA;;;;;;;kFAOkF;;;;;;;;;;;;;;;;AAElF,2DAAyC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/node/index.ts"],"names":[],"mappings":";AAAA;;;;;;;kFAOkF;;;;;;;;;;;;;;;;AAElF,2DAAyC;AACzC,qDAAmC"}
@@ -0,0 +1,54 @@
1
+ /********************************************************************************
2
+ * Copyright (c) 2026 EclipseSource and others.
3
+ *
4
+ * This program and the accompanying materials are made available under the
5
+ * terms of the MIT License which is available in the project root.
6
+ *
7
+ * SPDX-License-Identifier: MIT
8
+ ********************************************************************************/
9
+ import { type DataPort, type Logger, type ResolvedMessage } from '@hydranium/protocol';
10
+ import { Emitter } from 'vscode-jsonrpc';
11
+ import { type MessageConnection } from 'vscode-jsonrpc/node';
12
+ /** What {@link SocketDataPort} needs from the host. */
13
+ export interface SocketDataPortOptions {
14
+ /**
15
+ * Resolve the data-server's listening port. Called once per connection
16
+ * generation, so a restarted server is re-discovered rather than cached.
17
+ */
18
+ readonly findPort: () => Promise<number>;
19
+ /** The data-server's host. Defaults to the loopback address. */
20
+ readonly host?: string;
21
+ /** Where the connection logs its protocol faults, and what it does on its own. */
22
+ readonly logger?: Logger;
23
+ /** Surface a failure to the user. Defaults to the logger, else the console. */
24
+ readonly reportError?: (error: unknown, reported: ResolvedMessage) => void;
25
+ }
26
+ /**
27
+ * A {@link DataPort} over a socket to the data-server, for code in the
28
+ * extension host itself, such as a command. A webview has no `net`; it takes
29
+ * `WebviewDataPort` over `connectDataServer` instead.
30
+ *
31
+ * Each generation opens its own socket and closes the one before, without
32
+ * firing {@link onDispose} for it; of two overlapping calls, the one that
33
+ * settles last keeps its socket. A socket the server ends under the current
34
+ * generation fires it, so `DataConnection` reconnects to the port
35
+ * {@link SocketDataPortOptions.findPort} finds next.
36
+ */
37
+ export declare class SocketDataPort implements DataPort {
38
+ protected readonly options: SocketDataPortOptions;
39
+ protected readonly disposeEmitter: Emitter<void>;
40
+ readonly onDispose: import("vscode-jsonrpc").Event<void>;
41
+ readonly logger?: Logger;
42
+ protected disposed: boolean;
43
+ protected current?: {
44
+ readonly connection: MessageConnection;
45
+ readonly dispose: () => void;
46
+ };
47
+ constructor(options: SocketDataPortOptions);
48
+ connect(): Promise<MessageConnection>;
49
+ reportError(error: unknown, reported: ResolvedMessage): void;
50
+ /** The consumer hears it first; then the current socket closes. */
51
+ dispose(): void;
52
+ protected closeCurrent(): void;
53
+ }
54
+ //# sourceMappingURL=socket-data-port.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"socket-data-port.d.ts","sourceRoot":"","sources":["../../src/node/socket-data-port.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAElF,OAAO,EAA0B,KAAK,QAAQ,EAAE,KAAK,MAAM,EAAE,KAAK,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC/G,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AACzC,OAAO,EAA2B,KAAK,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAGtF,uDAAuD;AACvD,MAAM,WAAW,qBAAqB;IACnC;;;OAGG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IACzC,gEAAgE;IAChE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,kFAAkF;IAClF,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,+EAA+E;IAC/E,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,eAAe,KAAK,IAAI,CAAC;CAC7E;AAED;;;;;;;;;;GAUG;AACH,qBAAa,cAAe,YAAW,QAAQ;IAOhC,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,qBAAqB;IAN7D,SAAS,CAAC,QAAQ,CAAC,cAAc,gBAAuB;IACxD,QAAQ,CAAC,SAAS,uCAA6B;IAC/C,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,SAAS,CAAC,QAAQ,UAAS;IAC3B,SAAS,CAAC,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,UAAU,EAAE,iBAAiB,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAA;KAAE,CAAC;IAE7F,YAA+B,OAAO,EAAE,qBAAqB,EAE5D;IAEK,OAAO,IAAI,OAAO,CAAC,iBAAiB,CAAC,CA6B1C;IAED,WAAW,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,eAAe,GAAG,IAAI,CAQ3D;IAED,mEAAmE;IACnE,OAAO,IAAI,IAAI,CAQd;IAED,SAAS,CAAC,YAAY,IAAI,IAAI,CAI7B;CACH"}
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+ /********************************************************************************
3
+ * Copyright (c) 2026 EclipseSource and others.
4
+ *
5
+ * This program and the accompanying materials are made available under the
6
+ * terms of the MIT License which is available in the project root.
7
+ *
8
+ * SPDX-License-Identifier: MIT
9
+ ********************************************************************************/
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.SocketDataPort = void 0;
12
+ const protocol_1 = require("@hydranium/protocol");
13
+ const vscode_jsonrpc_1 = require("vscode-jsonrpc");
14
+ const node_1 = require("vscode-jsonrpc/node");
15
+ const data_server_connection_1 = require("./data-server-connection");
16
+ /**
17
+ * A {@link DataPort} over a socket to the data-server, for code in the
18
+ * extension host itself, such as a command. A webview has no `net`; it takes
19
+ * `WebviewDataPort` over `connectDataServer` instead.
20
+ *
21
+ * Each generation opens its own socket and closes the one before, without
22
+ * firing {@link onDispose} for it; of two overlapping calls, the one that
23
+ * settles last keeps its socket. A socket the server ends under the current
24
+ * generation fires it, so `DataConnection` reconnects to the port
25
+ * {@link SocketDataPortOptions.findPort} finds next.
26
+ */
27
+ class SocketDataPort {
28
+ options;
29
+ disposeEmitter = new vscode_jsonrpc_1.Emitter();
30
+ onDispose = this.disposeEmitter.event;
31
+ logger;
32
+ disposed = false;
33
+ current;
34
+ constructor(options) {
35
+ this.options = options;
36
+ this.logger = options.logger;
37
+ }
38
+ async connect() {
39
+ if (this.disposed) {
40
+ throw new Error('SocketDataPort: the port is disposed');
41
+ }
42
+ const transport = await (0, data_server_connection_1.openSocketTransport)(await this.options.findPort(), this.options.host);
43
+ // Disposed while the socket opened: nothing would close this one.
44
+ if (this.disposed) {
45
+ transport.dispose?.();
46
+ throw new Error('SocketDataPort: the port is disposed');
47
+ }
48
+ // The one before, or an overlapping call's that was installed meanwhile.
49
+ this.closeCurrent();
50
+ const connection = (0, node_1.createMessageConnection)(transport.reader, transport.writer, this.logger);
51
+ const current = {
52
+ connection,
53
+ dispose: () => {
54
+ connection.dispose();
55
+ transport.dispose?.();
56
+ }
57
+ };
58
+ this.current = current;
59
+ connection.onClose(() => {
60
+ if (this.current === current) {
61
+ this.closeCurrent();
62
+ this.disposeEmitter.fire();
63
+ }
64
+ });
65
+ connection.listen();
66
+ return connection;
67
+ }
68
+ reportError(error, reported) {
69
+ if (this.options.reportError) {
70
+ this.options.reportError(error, reported);
71
+ }
72
+ else if (this.logger) {
73
+ this.logger.error((0, protocol_1.renderFrameworkMessage)(reported), error);
74
+ }
75
+ else {
76
+ console.error(`[data head] ${(0, protocol_1.renderFrameworkMessage)(reported)}`, error);
77
+ }
78
+ }
79
+ /** The consumer hears it first; then the current socket closes. */
80
+ dispose() {
81
+ if (this.disposed) {
82
+ return;
83
+ }
84
+ this.disposed = true;
85
+ this.disposeEmitter.fire();
86
+ this.disposeEmitter.dispose();
87
+ this.closeCurrent();
88
+ }
89
+ closeCurrent() {
90
+ const current = this.current;
91
+ this.current = undefined;
92
+ current?.dispose();
93
+ }
94
+ }
95
+ exports.SocketDataPort = SocketDataPort;
96
+ //# sourceMappingURL=socket-data-port.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"socket-data-port.js","sourceRoot":"","sources":["../../src/node/socket-data-port.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hydranium/data-client-vscode",
3
- "version": "1.0.0-next.322",
3
+ "version": "1.0.0-next.323",
4
4
  "description": "VS Code client primitives for the hydranium data-server protocol head; companion of @hydranium/data-server. The extension host relays the data-server's socket to a webview over a vscode-messenger hop, and the webview's data port reads it, so DataConnection and DataSession run unchanged in a webview.",
5
5
  "keywords": [
6
6
  "hydranium",
@@ -63,7 +63,7 @@
63
63
  "watch": "tsc -b -w --preserveWatchOutput"
64
64
  },
65
65
  "devDependencies": {
66
- "@hydranium/protocol": "1.0.0-next.322",
66
+ "@hydranium/protocol": "1.0.0-next.323",
67
67
  "@types/node": "^22.0.0",
68
68
  "@types/vscode": "^1.101.0",
69
69
  "rimraf": "^5.0.0",
@@ -74,7 +74,7 @@
74
74
  "vscode-messenger-webview": "^0.4.5"
75
75
  },
76
76
  "peerDependencies": {
77
- "@hydranium/protocol": "1.0.0-next.322",
77
+ "@hydranium/protocol": "1.0.0-next.323",
78
78
  "vscode-jsonrpc": "^9.0.0",
79
79
  "vscode-messenger": "^0.4.5",
80
80
  "vscode-messenger-common": "^0.4.5",
package/src/node/index.ts CHANGED
@@ -8,3 +8,4 @@
8
8
  ********************************************************************************/
9
9
 
10
10
  export * from './data-server-connection';
11
+ export * from './socket-data-port';
@@ -0,0 +1,109 @@
1
+ /********************************************************************************
2
+ * Copyright (c) 2026 EclipseSource and others.
3
+ *
4
+ * This program and the accompanying materials are made available under the
5
+ * terms of the MIT License which is available in the project root.
6
+ *
7
+ * SPDX-License-Identifier: MIT
8
+ ********************************************************************************/
9
+
10
+ import { renderFrameworkMessage, type DataPort, type Logger, type ResolvedMessage } from '@hydranium/protocol';
11
+ import { Emitter } from 'vscode-jsonrpc';
12
+ import { createMessageConnection, type MessageConnection } from 'vscode-jsonrpc/node';
13
+ import { openSocketTransport } from './data-server-connection';
14
+
15
+ /** What {@link SocketDataPort} needs from the host. */
16
+ export interface SocketDataPortOptions {
17
+ /**
18
+ * Resolve the data-server's listening port. Called once per connection
19
+ * generation, so a restarted server is re-discovered rather than cached.
20
+ */
21
+ readonly findPort: () => Promise<number>;
22
+ /** The data-server's host. Defaults to the loopback address. */
23
+ readonly host?: string;
24
+ /** Where the connection logs its protocol faults, and what it does on its own. */
25
+ readonly logger?: Logger;
26
+ /** Surface a failure to the user. Defaults to the logger, else the console. */
27
+ readonly reportError?: (error: unknown, reported: ResolvedMessage) => void;
28
+ }
29
+
30
+ /**
31
+ * A {@link DataPort} over a socket to the data-server, for code in the
32
+ * extension host itself, such as a command. A webview has no `net`; it takes
33
+ * `WebviewDataPort` over `connectDataServer` instead.
34
+ *
35
+ * Each generation opens its own socket and closes the one before, without
36
+ * firing {@link onDispose} for it; of two overlapping calls, the one that
37
+ * settles last keeps its socket. A socket the server ends under the current
38
+ * generation fires it, so `DataConnection` reconnects to the port
39
+ * {@link SocketDataPortOptions.findPort} finds next.
40
+ */
41
+ export class SocketDataPort implements DataPort {
42
+ protected readonly disposeEmitter = new Emitter<void>();
43
+ readonly onDispose = this.disposeEmitter.event;
44
+ readonly logger?: Logger;
45
+ protected disposed = false;
46
+ protected current?: { readonly connection: MessageConnection; readonly dispose: () => void };
47
+
48
+ constructor(protected readonly options: SocketDataPortOptions) {
49
+ this.logger = options.logger;
50
+ }
51
+
52
+ async connect(): Promise<MessageConnection> {
53
+ if (this.disposed) {
54
+ throw new Error('SocketDataPort: the port is disposed');
55
+ }
56
+ const transport = await openSocketTransport(await this.options.findPort(), this.options.host);
57
+ // Disposed while the socket opened: nothing would close this one.
58
+ if (this.disposed) {
59
+ transport.dispose?.();
60
+ throw new Error('SocketDataPort: the port is disposed');
61
+ }
62
+ // The one before, or an overlapping call's that was installed meanwhile.
63
+ this.closeCurrent();
64
+ const connection = createMessageConnection(transport.reader, transport.writer, this.logger);
65
+ const current = {
66
+ connection,
67
+ dispose: () => {
68
+ connection.dispose();
69
+ transport.dispose?.();
70
+ }
71
+ };
72
+ this.current = current;
73
+ connection.onClose(() => {
74
+ if (this.current === current) {
75
+ this.closeCurrent();
76
+ this.disposeEmitter.fire();
77
+ }
78
+ });
79
+ connection.listen();
80
+ return connection;
81
+ }
82
+
83
+ reportError(error: unknown, reported: ResolvedMessage): void {
84
+ if (this.options.reportError) {
85
+ this.options.reportError(error, reported);
86
+ } else if (this.logger) {
87
+ this.logger.error(renderFrameworkMessage(reported), error);
88
+ } else {
89
+ console.error(`[data head] ${renderFrameworkMessage(reported)}`, error);
90
+ }
91
+ }
92
+
93
+ /** The consumer hears it first; then the current socket closes. */
94
+ dispose(): void {
95
+ if (this.disposed) {
96
+ return;
97
+ }
98
+ this.disposed = true;
99
+ this.disposeEmitter.fire();
100
+ this.disposeEmitter.dispose();
101
+ this.closeCurrent();
102
+ }
103
+
104
+ protected closeCurrent(): void {
105
+ const current = this.current;
106
+ this.current = undefined;
107
+ current?.dispose();
108
+ }
109
+ }