@hydranium/data-client-theia 1.0.0-next.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +126 -0
  3. package/lib/browser/channel-connection.d.ts +174 -0
  4. package/lib/browser/channel-connection.d.ts.map +1 -0
  5. package/lib/browser/channel-connection.js +264 -0
  6. package/lib/browser/channel-connection.js.map +1 -0
  7. package/lib/browser/data-service-frontend.d.ts +154 -0
  8. package/lib/browser/data-service-frontend.d.ts.map +1 -0
  9. package/lib/browser/data-service-frontend.js +169 -0
  10. package/lib/browser/data-service-frontend.js.map +1 -0
  11. package/lib/browser/diagnostics-data-service-frontend.d.ts +35 -0
  12. package/lib/browser/diagnostics-data-service-frontend.d.ts.map +1 -0
  13. package/lib/browser/diagnostics-data-service-frontend.js +54 -0
  14. package/lib/browser/diagnostics-data-service-frontend.js.map +1 -0
  15. package/lib/browser/host-diagnostics-frontend.d.ts +19 -0
  16. package/lib/browser/host-diagnostics-frontend.d.ts.map +1 -0
  17. package/lib/browser/host-diagnostics-frontend.js +31 -0
  18. package/lib/browser/host-diagnostics-frontend.js.map +1 -0
  19. package/lib/browser/index.d.ts +15 -0
  20. package/lib/browser/index.d.ts.map +1 -0
  21. package/lib/browser/index.js +31 -0
  22. package/lib/browser/index.js.map +1 -0
  23. package/lib/browser/references-data-service-frontend.d.ts +45 -0
  24. package/lib/browser/references-data-service-frontend.d.ts.map +1 -0
  25. package/lib/browser/references-data-service-frontend.js +55 -0
  26. package/lib/browser/references-data-service-frontend.js.map +1 -0
  27. package/lib/browser/workspace-gate.d.ts +21 -0
  28. package/lib/browser/workspace-gate.d.ts.map +1 -0
  29. package/lib/browser/workspace-gate.js +36 -0
  30. package/lib/browser/workspace-gate.js.map +1 -0
  31. package/lib/common/emitter-data-client.d.ts +40 -0
  32. package/lib/common/emitter-data-client.d.ts.map +1 -0
  33. package/lib/common/emitter-data-client.js +49 -0
  34. package/lib/common/emitter-data-client.js.map +1 -0
  35. package/lib/common/index.d.ts +10 -0
  36. package/lib/common/index.d.ts.map +1 -0
  37. package/lib/common/index.js +37 -0
  38. package/lib/common/index.js.map +1 -0
  39. package/lib/index.d.ts +10 -0
  40. package/lib/index.d.ts.map +1 -0
  41. package/lib/index.js +11 -0
  42. package/lib/index.js.map +1 -0
  43. package/lib/node/connection-container-module.d.ts +34 -0
  44. package/lib/node/connection-container-module.d.ts.map +1 -0
  45. package/lib/node/connection-container-module.js +53 -0
  46. package/lib/node/connection-container-module.js.map +1 -0
  47. package/lib/node/data-server-connection-handler.d.ts +56 -0
  48. package/lib/node/data-server-connection-handler.d.ts.map +1 -0
  49. package/lib/node/data-server-connection-handler.js +63 -0
  50. package/lib/node/data-server-connection-handler.js.map +1 -0
  51. package/lib/node/host-diagnostics-server.d.ts +31 -0
  52. package/lib/node/host-diagnostics-server.d.ts.map +1 -0
  53. package/lib/node/host-diagnostics-server.js +62 -0
  54. package/lib/node/host-diagnostics-server.js.map +1 -0
  55. package/lib/node/index.d.ts +13 -0
  56. package/lib/node/index.d.ts.map +1 -0
  57. package/lib/node/index.js +29 -0
  58. package/lib/node/index.js.map +1 -0
  59. package/lib/node/socket-channel-forwarder.d.ts +34 -0
  60. package/lib/node/socket-channel-forwarder.d.ts.map +1 -0
  61. package/lib/node/socket-channel-forwarder.js +63 -0
  62. package/lib/node/socket-channel-forwarder.js.map +1 -0
  63. package/package.json +101 -0
  64. package/src/browser/channel-connection.ts +413 -0
  65. package/src/browser/data-service-frontend.ts +208 -0
  66. package/src/browser/diagnostics-data-service-frontend.ts +70 -0
  67. package/src/browser/host-diagnostics-frontend.ts +30 -0
  68. package/src/browser/index.ts +15 -0
  69. package/src/browser/references-data-service-frontend.ts +70 -0
  70. package/src/browser/workspace-gate.ts +35 -0
  71. package/src/common/emitter-data-client.ts +64 -0
  72. package/src/common/index.ts +21 -0
  73. package/src/index.ts +18 -0
  74. package/src/node/connection-container-module.ts +51 -0
  75. package/src/node/data-server-connection-handler.ts +76 -0
  76. package/src/node/host-diagnostics-server.ts +57 -0
  77. package/src/node/index.ts +13 -0
  78. package/src/node/socket-channel-forwarder.ts +64 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 CrossBreeze, EclipseSource and others.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,126 @@
1
+ # `@hydranium/data-client-theia`
2
+
3
+ Theia client primitives for the hydranium **data head** — the companion of
4
+ `@hydranium/data-server` in the
5
+ [Hydranium](https://github.com/eclipse-emfcloud/hydranium) framework.
6
+
7
+ The data head's frontend speaks the data server's own `vscode-jsonrpc` protocol
8
+ over a channel that the Theia backend relays byte-for-byte onto the server's
9
+ socket. This package is both halves of that arrangement: the browser-side
10
+ connection and typed-frontend bases, and the backend-side connection handler and
11
+ forwarder. Install it if a Theia application needs form editors, trees, or
12
+ code-gen driven from the live AST rather than from LSP text edits.
13
+
14
+ ## What it gives you
15
+
16
+ - **`openChannelConnection`** with `createChannelConnection` and
17
+ `ChannelConnectionHandle` — wraps a Theia `Channel` as a `vscode-jsonrpc`
18
+ `MessageConnection`, gated on a `whenReady` promise, and by default
19
+ re-establishes it by **re-opening the channel** when the connection is lost.
20
+ Re-opening rather than rebuilding is what recovers a restarted language server:
21
+ a server restart closes only the multiplexed sub-channel, so no replacement
22
+ channel ever arrives on its own. Retries follow `DEFAULT_RECONNECT_DELAYS`,
23
+ escalating per consecutive loss and resetting after
24
+ `RECONNECT_ESCALATION_RESET_MS`.
25
+ - **`AbstractDataServiceFrontend`** — base for a frontend that owns the
26
+ data-server connection: the workspace-gated connect, the typed server proxy,
27
+ the inbound client binding, and a lazy idempotent init gate (`ensureConnected`).
28
+ You supply the connection seams (`connectionProvider`, `servicePath`,
29
+ `methodNamespace`, `client`, `clientMethods`); progress UI and domain caching
30
+ layer on top.
31
+ - **`AbstractDiagnosticsDataServiceFrontend`** and
32
+ **`AbstractReferencesDataServiceFrontend`** — the same base specialised for the
33
+ two protocol fragments an adopter most often keeps: each diagnostics
34
+ (`dumpServerState`, `writeHeapSnapshot`, `dumpPodMemory`, the profiling and
35
+ latency calls) and each reference method is implemented as a readiness-gated
36
+ pass-through, so you do not hand-write identical one-line bodies. They are
37
+ deliberately independent bases rather than chained.
38
+ - **`EmitterDataClient`** (on `./common`, not `./browser`) — the default
39
+ client-side implementation of the data protocol's inbound notifications,
40
+ fanning each one out to a Theia `Event`: `onDidUpdateDocument`,
41
+ `onDidSaveDocument`, `onDidChangeProjects`. Bind an instance as the
42
+ `localTarget` of the frontend's RPC proxy. It sits on the common tier because
43
+ its only runtime dependency is `@theia/core`'s root entry, which is Theia's own
44
+ common tier, so a backend or a plain-Node consumer can bind it too.
45
+ - **`whenWorkspaceOpen`** — resolves once Theia reports a workspace root. The data
46
+ server only starts once the LSP launches for a workspace, so connecting earlier
47
+ would hang in port discovery; pass this as `whenReady`.
48
+ - **Backend (`./node`)** — `DataServerConnectionHandler` (the socket bridge, with
49
+ its own GLSP-free `SocketChannelForwarder`),
50
+ `createDataServerConnectionContainerModule(...handlers)` for the
51
+ frontend-scoped module boilerplate, and `HostDiagnosticsServer` with
52
+ `createHostDiagnosticsBackendModule()` — which, paired with the browser-side
53
+ `bindHostDiagnostics`, lights up the "Backend" diagnostics commands in
54
+ `@hydranium/client-theia`'s contribution.
55
+
56
+ ## Install
57
+
58
+ ```bash
59
+ npm install @hydranium/data-client-theia
60
+ ```
61
+
62
+ You must already have a Theia application with `@theia/workspace` available, and a
63
+ running hydranium data server to connect to. The declared peer dependencies are:
64
+
65
+ | Peer | Range |
66
+ | ------------------------- | -------------------- |
67
+ | `@hydranium/client-theia` | `^1.0.0-next` |
68
+ | `@hydranium/core` | `^1.0.0-next` |
69
+ | `@hydranium/protocol` | `^1.0.0-next` |
70
+ | `@theia/core` | `^1.71.0` |
71
+ | `@theia/workspace` | `^1.71.0` |
72
+ | `inversify` | `^6.0.0` |
73
+ | `vscode-jsonrpc` | `^8.0.0 \|\| ^9.0.0` |
74
+
75
+ `@hydranium/core` is reached only from the `./node` tier (the host-diagnostics
76
+ service), so a frontend-only consumer never loads it.
77
+
78
+ ## Wiring
79
+
80
+ This package declares no `theiaExtensions` — it is a library your own Theia
81
+ extension builds on. That extension's `package.json` declares the entries, and
82
+ each entry names one frontend/backend module pair:
83
+
84
+ - the **frontend** module binds your `AbstractDataServiceFrontend` subclass (and,
85
+ for the diagnostics commands, calls `bindHostDiagnostics`);
86
+ - the **backend** module is typically a one-liner:
87
+ `export default createDataServerConnectionContainerModule(MyHandler)`, where
88
+ `MyHandler` extends `DataServerConnectionHandler`.
89
+
90
+ More than one handler is the normal case, not an exotic one. Theia keys a
91
+ frontend channel by its service path and refuses a second channel on a path
92
+ already open, so every frontend abstraction reaching the data head needs its own
93
+ `servicePath` — while the shared `portCommand` still names the one server process
94
+ behind them all.
95
+
96
+ The refusal is silent, which is what makes it expensive: the loser's promise is
97
+ left unsettled rather than rejected, so a frontend that shared a path hangs on
98
+ its loading state indefinitely with nothing in the server log to say why.
99
+
100
+ ## Entry points
101
+
102
+ | Subpath | Holds | Environment |
103
+ | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- |
104
+ | `.` | Nothing — the surface is split by environment, so the root barrel stays empty. | browser-neutral (gated) |
105
+ | `./common` | `EmitterDataClient` — a module lands here only when values, types and relative imports are all neutral or Theia COMMON tier. No error-reconstruction bridge, and that is a property of the transport: the direct `vscode-jsonrpc` connection carries a typed error across the relay natively. | browser-neutral (gated) |
106
+ | `./browser` | `openChannelConnection`, `createChannelConnection`, the three `Abstract*DataServiceFrontend` bases, `bindHostDiagnostics`, `whenWorkspaceOpen` | browser / Theia frontend (gated) |
107
+ | `./node` | `DataServerConnectionHandler`, `createDataServerConnectionContainerModule`, `SocketChannelForwarder`, `HostDiagnosticsServer`, `createHostDiagnosticsBackendModule` | Node / Theia backend |
108
+
109
+ Every subpath also has a `./lib/<name>` twin for consumers on
110
+ `moduleResolution: "Node"`. "Gated" means the repository's neutral-bundle check
111
+ enforces that the entry bundles for the browser with no `node:*` import,
112
+ transitive ones included; `./node` is deliberately outside that gate. Also
113
+ worth reading: [what "gated neutral" does and does not promise](../../docs/concepts/browser-hosting.md#a-note-on-what-gated-neutral-does-and-does-not-promise).
114
+
115
+ ## Status
116
+
117
+ Alpha — pre-v0, not yet published. The API is not stable and may change without a
118
+ deprecation cycle. See [`docs/concepts/architecture.md`](../../docs/concepts/architecture.md) for
119
+ the data head's place among the heads, and the
120
+ [repository README](../../README.md) for current status and known limitations.
121
+
122
+ ## License
123
+
124
+ `MIT` — see this package's [`LICENSE`](./LICENSE). Third-party notices for the
125
+ runtime dependency closure are collected in the repository
126
+ [`NOTICE.md`](../../NOTICE.md).
@@ -0,0 +1,174 @@
1
+ /********************************************************************************
2
+ * Copyright (c) 2026 CrossBreeze, 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 Channel, type Event } from '@theia/core';
10
+ import { type ServiceConnectionProvider } from '@theia/core/lib/browser';
11
+ import { type Logger, type MessageConnection } from 'vscode-jsonrpc/browser';
12
+ /**
13
+ * Build a `vscode-jsonrpc` {@link MessageConnection} on top of a Theia
14
+ * {@link Channel}. This is the browser-side transport for the data-server
15
+ * head: the frontend obtains a channel to the backend forwarder (via
16
+ * `connectionProvider.listen`), wraps it here, and drives the typed
17
+ * `createRpcProxy` over the returned connection — so the frontend
18
+ * speaks the same vscode-jsonrpc protocol the model-server speaks, with the
19
+ * backend relaying bytes between the two.
20
+ *
21
+ * Mirrors `@eclipse-glsp/theia-integration`'s `createChannelConnection`, but
22
+ * lives here so the data-server head carries no GLSP dependency.
23
+ */
24
+ export declare function createChannelConnection(channel: Channel, logger?: Logger): MessageConnection;
25
+ /** Options for {@link openChannelConnection}. */
26
+ export interface OpenChannelConnectionOptions {
27
+ /**
28
+ * Resolves when it is safe to open the channel. The handler holds back the
29
+ * `connectionProvider.listen` call until this settles — e.g. pass
30
+ * `whenWorkspaceOpen` so the backend forwarder isn't asked for the
31
+ * model-server port before a workspace (and therefore the LSP launch that
32
+ * publishes the port) exists.
33
+ */
34
+ whenReady?: Promise<void>;
35
+ /** Optional `vscode-jsonrpc` logger threaded into the connection. */
36
+ logger?: Logger;
37
+ /**
38
+ * Re-establish the connection whenever the live one is lost, by RE-OPENING
39
+ * the channel on `path` — not merely by rebuilding over a channel Theia hands
40
+ * back.
41
+ *
42
+ * **Default `true`, and the default is the point.** Re-opening is the only
43
+ * thing that recovers a restarted language server. Theia replays a `listen`
44
+ * handler solely from `ServiceConnectionProvider.handleChannelCreated`, i.e.
45
+ * when the MAIN frontend-backend channel is (re)created — a page reload, a
46
+ * dropped socket, a backend restart. A language-server restart closes just the
47
+ * multiplexed sub-channel for this service path while the main channel stays
48
+ * open, so no replacement channel ever arrives on its own and a handle that
49
+ * only waits for one holds a connection to the dead process forever: its
50
+ * requests neither answer nor reject.
51
+ *
52
+ * **Why on by default, where the older rebuild-on-a-fresh-channel behaviour
53
+ * was opt-in.** That one had a working alternative to weigh against — the
54
+ * existing connection was fine, and opting in only decided whether to swap to
55
+ * a fresher one, so leaving a consumer's readiness gate (and the progress UI it
56
+ * drives) undisturbed was worth more. A CLOSED channel offers no such choice:
57
+ * the alternative to re-opening is a permanently dead handle. Re-running a
58
+ * readiness gate against a live replacement is what a restarted server should
59
+ * cost.
60
+ *
61
+ * Turn it off for a consumer that treats transport loss as terminal and tears
62
+ * itself down instead. Off means build-once: every channel after the first is
63
+ * ignored and {@link ChannelConnectionHandle.onDidLoseConnection} never fires.
64
+ */
65
+ reconnect?: boolean;
66
+ /**
67
+ * Delay before the re-open following each CONSECUTIVE connection loss, in ms;
68
+ * the last entry repeats once the schedule is exhausted. Defaults to
69
+ * {@link DEFAULT_RECONNECT_DELAYS}.
70
+ *
71
+ * A delay rather than an immediate re-open, for two independent reasons.
72
+ * Theia's `ChannelMultiplexer.handleClose` fires the close emitter BEFORE it
73
+ * deletes the id from `openChannels`, so a re-open issued from inside the
74
+ * close listener can still see the id as open — and `open()` throws
75
+ * "Another channel with the id '<id>' is already open" then, which
76
+ * `ServiceConnectionProvider.listen` neither catches nor reports: the handler
77
+ * is simply never invoked, and the consumer hangs rather than failing. And a
78
+ * server that is flapping would otherwise be re-opened against at whatever
79
+ * rate it can close a channel, which is what the escalation is for.
80
+ *
81
+ * The escalation resets once a connection has survived
82
+ * {@link RECONNECT_ESCALATION_RESET_MS}, so an hour-long session with
83
+ * occasional restarts does not converge on the longest delay.
84
+ */
85
+ reconnectDelays?: readonly number[];
86
+ }
87
+ /**
88
+ * Default {@link OpenChannelConnectionOptions.reconnectDelays}.
89
+ *
90
+ * The first entry only has to outlast the synchronous close dispatch, so it is
91
+ * short: the backend forwarder, not this delay, is what absorbs a server that is
92
+ * still down. `AbstractSocketForwardingConnectionHandler` re-runs its port command per
93
+ * connection and retries it indefinitely, so a channel re-opened while the
94
+ * server is restarting parks in `findPort` and connects when the replacement
95
+ * publishes its new port. The later entries exist for the case that machinery
96
+ * cannot absorb — a server that accepts a socket and then dies again.
97
+ */
98
+ export declare const DEFAULT_RECONNECT_DELAYS: readonly number[];
99
+ /** How long a connection must survive before the reconnect escalation resets. */
100
+ export declare const RECONNECT_ESCALATION_RESET_MS = 30000;
101
+ /**
102
+ * A live channel connection plus the two things a bare
103
+ * `Promise<MessageConnection>` cannot express: that the connection can be
104
+ * REPLACED, and that it can be released.
105
+ */
106
+ export interface ChannelConnectionHandle {
107
+ /**
108
+ * The current connection. Read it per use rather than caching it: after a
109
+ * reconnect this returns a NEW promise, and a cached one resolves to the
110
+ * dead connection forever. The same reasoning makes
111
+ * `AbstractDataServiceFrontend` rebuild its proxy rather than hold one.
112
+ */
113
+ readonly current: Promise<MessageConnection>;
114
+ /**
115
+ * Fires when the live connection is gone and a replacement is on the way.
116
+ * {@link current} has ALREADY been repointed at the replacement (still
117
+ * unresolved) by the time listeners run, so a listener re-derives from
118
+ * `current` and whatever it queues waits for the new channel instead of being
119
+ * addressed at the dead one.
120
+ *
121
+ * **This is the event a consumer wants, not {@link onDidReconnect}.** Anything
122
+ * derived from a connection — an `createRpcProxy`, a readiness gate, a
123
+ * `DataSession` generation — is dead from this moment, and a consumer that
124
+ * waits for the replacement to be live instead keeps sending into the dead one
125
+ * for the whole gap. A `DataPort` implementation translates this into the
126
+ * port's own `onDispose`, which is what makes `DataSession` drop its
127
+ * generation.
128
+ *
129
+ * Never fires when {@link OpenChannelConnectionOptions.reconnect} is off.
130
+ */
131
+ readonly onDidLoseConnection: Event<void>;
132
+ /**
133
+ * Fires with each connection built AFTER the first, once it is live. For a
134
+ * consumer that needs the connection object itself; for rebuilding what was
135
+ * derived from the previous one, use {@link onDidLoseConnection} instead —
136
+ * it fires at the start of the gap rather than at its end.
137
+ *
138
+ * Never fires when {@link OpenChannelConnectionOptions.reconnect} is off.
139
+ */
140
+ readonly onDidReconnect: Event<MessageConnection>;
141
+ /**
142
+ * Dispose the live connection and stop tracking the channel. Idempotent.
143
+ *
144
+ * Without it, a frontend being torn down leaves its connection and its
145
+ * inbound handler bindings attached — a leak that accumulates per cycle in
146
+ * tests and in a reloading workbench.
147
+ */
148
+ dispose(): void;
149
+ }
150
+ /**
151
+ * Open a Theia channel to a backend forwarder and wrap it as the
152
+ * `vscode-jsonrpc` {@link MessageConnection} the data-server proxy speaks over.
153
+ *
154
+ * Awaits `options.whenReady` (if given), then registers the channel handler via
155
+ * `connectionProvider.listen` and keeps a live connection to `path` for as long
156
+ * as the handle is undisposed — re-opening the channel whenever the current one
157
+ * closes, which {@link OpenChannelConnectionOptions.reconnect} governs. Callers
158
+ * build their `createRpcProxy` / `bindRpcMethods` over
159
+ * {@link ChannelConnectionHandle.current}, which queues outbound calls and
160
+ * inbound bindings until the channel is live.
161
+ *
162
+ * **The `reconnect` argument to Theia's `listen` is deliberately `false`**, in
163
+ * both modes. Passing `true` there registers the handler for replay from
164
+ * `handleChannelCreated`, and this handle then has two independent re-openers for
165
+ * one path: Theia's replay when the main frontend-backend channel is recreated,
166
+ * and the close-driven re-open below — which also fires then, because
167
+ * `ChannelMultiplexer.onUnderlyingChannelClose` closes every sub-channel. Two
168
+ * `open()` calls for one id means the loser throws "Another channel with the id
169
+ * '<id>' is already open" inside a promise `listen` neither awaits nor reports,
170
+ * so the consumer's request is left unsettled: a hang with a clean log, not an
171
+ * error. Owning the re-open here means owning it exclusively.
172
+ */
173
+ export declare function openChannelConnection(connectionProvider: ServiceConnectionProvider, path: string, options?: OpenChannelConnectionOptions): ChannelConnectionHandle;
174
+ //# sourceMappingURL=channel-connection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel-connection.d.ts","sourceRoot":"","sources":["../../src/browser/channel-connection.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAElF,OAAO,EAAE,KAAK,OAAO,EAAiC,KAAK,KAAK,EAAwB,MAAM,aAAa,CAAC;AAC5G,OAAO,EAAE,KAAK,yBAAyB,EAAE,MAAM,yBAAyB,CAAC;AAEzE,OAAO,EAMJ,KAAK,MAAM,EAEX,KAAK,iBAAiB,EAQxB,MAAM,wBAAwB,CAAC;AAkEhC;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,iBAAiB,CAI5F;AAED,iDAAiD;AACjD,MAAM,WAAW,4BAA4B;IAC1C;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,qEAAqE;IACrE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB;;;;;;;;;;;;;;;;;;OAkBG;IACH,eAAe,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACtC;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,wBAAwB,EAAE,SAAS,MAAM,EAA2C,CAAC;AAElG,iFAAiF;AACjF,eAAO,MAAM,6BAA6B,QAAS,CAAC;AAEpD;;;;GAIG;AACH,MAAM,WAAW,uBAAuB;IACrC;;;;;OAKG;IACH,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAE7C;;;;;;;;;;;;;;;;OAgBG;IACH,QAAQ,CAAC,mBAAmB,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;IAE1C;;;;;;;OAOG;IACH,QAAQ,CAAC,cAAc,EAAE,KAAK,CAAC,iBAAiB,CAAC,CAAC;IAElD;;;;;;OAMG;IACH,OAAO,IAAI,IAAI,CAAC;CAClB;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,qBAAqB,CAClC,kBAAkB,EAAE,yBAAyB,EAC7C,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,4BAAiC,GAC1C,uBAAuB,CA4IzB"}
@@ -0,0 +1,264 @@
1
+ "use strict";
2
+ /********************************************************************************
3
+ * Copyright (c) 2026 CrossBreeze, 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.RECONNECT_ESCALATION_RESET_MS = exports.DEFAULT_RECONNECT_DELAYS = void 0;
12
+ exports.createChannelConnection = createChannelConnection;
13
+ exports.openChannelConnection = openChannelConnection;
14
+ const core_1 = require("@theia/core");
15
+ const promise_util_1 = require("@theia/core/lib/common/promise-util");
16
+ const browser_1 = require("vscode-jsonrpc/browser");
17
+ /**
18
+ * A `vscode-jsonrpc` {@link MessageReader} that reads JSON-RPC messages off a
19
+ * Theia {@link Channel}. Each Theia channel message carries exactly one
20
+ * JSON-RPC message (the channel does its own length framing); the reader
21
+ * decodes the bytes and fires the parsed message.
22
+ */
23
+ class ChannelMessageReader extends browser_1.AbstractMessageReader {
24
+ channel;
25
+ onMessageEmitter = new core_1.Emitter();
26
+ toDispose = new core_1.DisposableCollection();
27
+ constructor(channel) {
28
+ super();
29
+ this.channel = channel;
30
+ this.toDispose.push(this.onMessageEmitter);
31
+ this.toDispose.push(channel.onMessage(provider => this.handleMessage(provider)));
32
+ this.toDispose.push(channel.onClose(() => this.fireClose()));
33
+ }
34
+ handleMessage(provider) {
35
+ const buffer = provider().readBytes();
36
+ const message = JSON.parse(new TextDecoder().decode(buffer));
37
+ this.onMessageEmitter.fire(message);
38
+ }
39
+ listen(callback) {
40
+ return this.onMessageEmitter.event(callback);
41
+ }
42
+ dispose() {
43
+ super.dispose();
44
+ this.toDispose.dispose();
45
+ }
46
+ }
47
+ /**
48
+ * A `vscode-jsonrpc` {@link MessageWriter} that writes JSON-RPC messages onto a
49
+ * Theia {@link Channel} — one channel message per JSON-RPC message, the dual of
50
+ * {@link ChannelMessageReader}. Uses `TextEncoder` (not node `Buffer`) so the
51
+ * writer is browser-native, since the frontend constructs the connection.
52
+ */
53
+ class ChannelMessageWriter extends browser_1.AbstractMessageWriter {
54
+ channel;
55
+ toDispose;
56
+ constructor(channel) {
57
+ super();
58
+ this.channel = channel;
59
+ this.toDispose = channel.onClose(() => this.fireClose());
60
+ }
61
+ write(message) {
62
+ const writeBuffer = this.channel.getWriteBuffer();
63
+ writeBuffer.writeBytes(new TextEncoder().encode(JSON.stringify(message)));
64
+ writeBuffer.commit();
65
+ return Promise.resolve();
66
+ }
67
+ end() {
68
+ this.dispose();
69
+ }
70
+ dispose() {
71
+ super.dispose();
72
+ this.toDispose.dispose();
73
+ }
74
+ }
75
+ /**
76
+ * Build a `vscode-jsonrpc` {@link MessageConnection} on top of a Theia
77
+ * {@link Channel}. This is the browser-side transport for the data-server
78
+ * head: the frontend obtains a channel to the backend forwarder (via
79
+ * `connectionProvider.listen`), wraps it here, and drives the typed
80
+ * `createRpcProxy` over the returned connection — so the frontend
81
+ * speaks the same vscode-jsonrpc protocol the model-server speaks, with the
82
+ * backend relaying bytes between the two.
83
+ *
84
+ * Mirrors `@eclipse-glsp/theia-integration`'s `createChannelConnection`, but
85
+ * lives here so the data-server head carries no GLSP dependency.
86
+ */
87
+ function createChannelConnection(channel, logger) {
88
+ const reader = new ChannelMessageReader(channel);
89
+ const writer = new ChannelMessageWriter(channel);
90
+ return (0, browser_1.createMessageConnection)(reader, writer, logger);
91
+ }
92
+ /**
93
+ * Default {@link OpenChannelConnectionOptions.reconnectDelays}.
94
+ *
95
+ * The first entry only has to outlast the synchronous close dispatch, so it is
96
+ * short: the backend forwarder, not this delay, is what absorbs a server that is
97
+ * still down. `AbstractSocketForwardingConnectionHandler` re-runs its port command per
98
+ * connection and retries it indefinitely, so a channel re-opened while the
99
+ * server is restarting parks in `findPort` and connects when the replacement
100
+ * publishes its new port. The later entries exist for the case that machinery
101
+ * cannot absorb — a server that accepts a socket and then dies again.
102
+ */
103
+ exports.DEFAULT_RECONNECT_DELAYS = [250, 500, 1_000, 2_000, 4_000, 8_000];
104
+ /** How long a connection must survive before the reconnect escalation resets. */
105
+ exports.RECONNECT_ESCALATION_RESET_MS = 30_000;
106
+ /**
107
+ * Open a Theia channel to a backend forwarder and wrap it as the
108
+ * `vscode-jsonrpc` {@link MessageConnection} the data-server proxy speaks over.
109
+ *
110
+ * Awaits `options.whenReady` (if given), then registers the channel handler via
111
+ * `connectionProvider.listen` and keeps a live connection to `path` for as long
112
+ * as the handle is undisposed — re-opening the channel whenever the current one
113
+ * closes, which {@link OpenChannelConnectionOptions.reconnect} governs. Callers
114
+ * build their `createRpcProxy` / `bindRpcMethods` over
115
+ * {@link ChannelConnectionHandle.current}, which queues outbound calls and
116
+ * inbound bindings until the channel is live.
117
+ *
118
+ * **The `reconnect` argument to Theia's `listen` is deliberately `false`**, in
119
+ * both modes. Passing `true` there registers the handler for replay from
120
+ * `handleChannelCreated`, and this handle then has two independent re-openers for
121
+ * one path: Theia's replay when the main frontend-backend channel is recreated,
122
+ * and the close-driven re-open below — which also fires then, because
123
+ * `ChannelMultiplexer.onUnderlyingChannelClose` closes every sub-channel. Two
124
+ * `open()` calls for one id means the loser throws "Another channel with the id
125
+ * '<id>' is already open" inside a promise `listen` neither awaits nor reports,
126
+ * so the consumer's request is left unsettled: a hang with a clean log, not an
127
+ * error. Owning the re-open here means owning it exclusively.
128
+ */
129
+ function openChannelConnection(connectionProvider, path, options = {}) {
130
+ const reconnect = options.reconnect ?? true;
131
+ const reconnectDelays = options.reconnectDelays ?? exports.DEFAULT_RECONNECT_DELAYS;
132
+ const reconnectEmitter = new core_1.Emitter();
133
+ const loseEmitter = new core_1.Emitter();
134
+ let deferred = new promise_util_1.Deferred();
135
+ let live;
136
+ let generation = 0;
137
+ let disposed = false;
138
+ /** Set from the close that repointed `deferred`, cleared by the accept that resolves it. */
139
+ let awaitingReplacement = false;
140
+ /** Consecutive losses, indexing `reconnectDelays`. */
141
+ let losses = 0;
142
+ /** When the live connection was built, for the escalation reset. `0` before the first. */
143
+ let liveSince = 0;
144
+ let reopenTimer;
145
+ // Own synchronous flag, deliberately NOT `deferred.state`. Theia's
146
+ // `Deferred` sets `state` inside a `.then()` on its own promise, so it is
147
+ // still `'unresolved'` for a microtask after `resolve()` returns. Guarding on
148
+ // it means two channels arriving in the SAME synchronous turn both pass the
149
+ // guard: the second connection gets built and starts listening, the
150
+ // first-wins `resolve` keeps the promise pointed at the first, and the second
151
+ // leaks — attached to a channel, reachable by nobody, never disposed. A test
152
+ // that only checks which connection the promise resolves with cannot see
153
+ // that; it takes counting the connections built per channel.
154
+ let built = false;
155
+ const scheduleReopen = () => {
156
+ if (liveSince > 0 && Date.now() - liveSince >= exports.RECONNECT_ESCALATION_RESET_MS) {
157
+ losses = 0;
158
+ }
159
+ const delay = reconnectDelays[Math.min(losses, reconnectDelays.length - 1)];
160
+ losses++;
161
+ reopenTimer = setTimeout(() => {
162
+ reopenTimer = undefined;
163
+ // `awaitingReplacement` is the guard against re-opening a path that
164
+ // already has a channel: a second `open()` for one id is the "already
165
+ // open" throw, and its symptom is a hang rather than an error.
166
+ if (disposed || !awaitingReplacement) {
167
+ return;
168
+ }
169
+ requestChannel();
170
+ }, delay);
171
+ };
172
+ /**
173
+ * The live connection's channel closed. Retire the generation and arm the
174
+ * replacement, in that order — a listener on `onDidLoseConnection` must
175
+ * already see `current` pointing at the replacement.
176
+ */
177
+ const handleChannelClosed = () => {
178
+ if (disposed || awaitingReplacement) {
179
+ return;
180
+ }
181
+ // Dispose rather than merely drop. vscode-jsonrpc rejects pending
182
+ // responses from `dispose`, never from a reader-side close — so a
183
+ // connection left undisposed keeps every in-flight request unsettled
184
+ // forever, which is the failure this whole path exists to end.
185
+ live?.dispose();
186
+ live = undefined;
187
+ deferred = new promise_util_1.Deferred();
188
+ awaitingReplacement = true;
189
+ loseEmitter.fire(undefined);
190
+ scheduleReopen();
191
+ };
192
+ const acceptChannel = (channel) => {
193
+ if (disposed) {
194
+ return;
195
+ }
196
+ if (built && !awaitingReplacement) {
197
+ // Unsolicited: a channel arrived while one is already live and no close
198
+ // asked for a replacement. Exactly one channel is outstanding at a time
199
+ // here, so this is either the build-once case (`reconnect` off) or a
200
+ // handler re-fire, and adopting it would leave the previous connection
201
+ // listening on its own channel, reachable by nobody and never disposed.
202
+ // The close-driven path is the only way a second generation is built.
203
+ return;
204
+ }
205
+ const connection = createChannelConnection(channel, options.logger);
206
+ connection.listen();
207
+ live = connection;
208
+ built = true;
209
+ awaitingReplacement = false;
210
+ generation++;
211
+ liveSince = Date.now();
212
+ if (reconnect) {
213
+ // Registered AFTER `createChannelConnection`, so the connection's own
214
+ // reader and writer see the close first and `connection.onClose` still
215
+ // fires for whoever is listening to it. Disposing before they run would
216
+ // put the connection in `Disposed`, from which vscode-jsonrpc
217
+ // deliberately emits no close event at all.
218
+ const acceptedGeneration = generation;
219
+ channel.onClose(() => {
220
+ if (acceptedGeneration === generation) {
221
+ handleChannelClosed();
222
+ }
223
+ });
224
+ }
225
+ deferred.resolve(connection);
226
+ if (generation > 1) {
227
+ reconnectEmitter.fire(connection);
228
+ }
229
+ };
230
+ /** Register the channel handler for one open attempt on `path`. */
231
+ function requestChannel() {
232
+ connectionProvider.listen(path, (_path, channel) => acceptChannel(channel), false);
233
+ }
234
+ const start = async () => {
235
+ await options.whenReady;
236
+ if (disposed) {
237
+ return;
238
+ }
239
+ requestChannel();
240
+ };
241
+ start();
242
+ return {
243
+ get current() {
244
+ return deferred.promise;
245
+ },
246
+ onDidLoseConnection: loseEmitter.event,
247
+ onDidReconnect: reconnectEmitter.event,
248
+ dispose() {
249
+ if (disposed) {
250
+ return;
251
+ }
252
+ disposed = true;
253
+ if (reopenTimer !== undefined) {
254
+ clearTimeout(reopenTimer);
255
+ reopenTimer = undefined;
256
+ }
257
+ live?.dispose();
258
+ live = undefined;
259
+ loseEmitter.dispose();
260
+ reconnectEmitter.dispose();
261
+ }
262
+ };
263
+ }
264
+ //# sourceMappingURL=channel-connection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel-connection.js","sourceRoot":"","sources":["../../src/browser/channel-connection.ts"],"names":[],"mappings":"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"}