@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
@@ -0,0 +1,30 @@
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
+
10
+ import { HostMemoryDiagnosticsService } from '@hydranium/client-theia/lib/browser';
11
+ import { HOST_DIAGNOSTICS_PATH, type HostDiagnosticsProtocol } from '@hydranium/protocol';
12
+ import { RemoteConnectionProvider, type ServiceConnectionProvider } from '@theia/core/lib/browser/messaging/service-connection-provider';
13
+ import { type interfaces } from '@theia/core/shared/inversify';
14
+
15
+ /**
16
+ * Bind the host-process diagnostics frontend proxy to
17
+ * {@link HostMemoryDiagnosticsService} so `MemoryDiagnosticsContribution`'s
18
+ * "Dump Backend State" / "Write Heap Snapshot (Backend)" commands light up. The
19
+ * proxy targets {@link HOST_DIAGNOSTICS_PATH}, served in the Theia backend by
20
+ * `createHostDiagnosticsBackendModule` (see `../node`). Call from a Theia
21
+ * frontend `ContainerModule`, alongside `bindMemoryDiagnostics`.
22
+ */
23
+ export function bindHostDiagnostics(bind: interfaces.Bind): void {
24
+ bind(HostMemoryDiagnosticsService)
25
+ .toDynamicValue(context => {
26
+ const provider = context.container.get<ServiceConnectionProvider>(RemoteConnectionProvider);
27
+ return provider.createProxy<HostDiagnosticsProtocol>(HOST_DIAGNOSTICS_PATH);
28
+ })
29
+ .inSingletonScope();
30
+ }
@@ -0,0 +1,15 @@
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
+
10
+ export * from './channel-connection';
11
+ export * from './data-service-frontend';
12
+ export * from './diagnostics-data-service-frontend';
13
+ export * from './host-diagnostics-frontend';
14
+ export * from './references-data-service-frontend';
15
+ export * from './workspace-gate';
@@ -0,0 +1,70 @@
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
+
10
+ import type {
11
+ FindNextNameArgs,
12
+ ReferenceCandidate,
13
+ ReferenceContext,
14
+ ReferenceRequest,
15
+ ReferenceServerProtocol,
16
+ ReferenceTarget,
17
+ TransferElement
18
+ } from '@hydranium/protocol';
19
+ import { AbstractDataServiceFrontend } from './data-service-frontend';
20
+
21
+ /**
22
+ * {@link AbstractDataServiceFrontend} specialised for a server that also
23
+ * composes the framework {@link ReferenceServerProtocol} fragment. It
24
+ * implements each reference method as a readiness-gated pass-through —
25
+ * `await this.ensureConnected()`, then delegate to `this.server` — so an
26
+ * adopter frontend gets the candidate / resolve / next-name surface without
27
+ * hand-writing identical one-liner bodies.
28
+ *
29
+ * The sibling of `AbstractDiagnosticsDataServiceFrontend`, and the same
30
+ * shape for the same reason: `this.server` is already a forwarding proxy, but
31
+ * it queues only on the channel being live, not on the server's `waitForReady`
32
+ * gate — so the gate has to be interposed per method.
33
+ *
34
+ * Extend this instead of {@link AbstractDataServiceFrontend} whenever the
35
+ * head's `DataServer` registers the reference fragment (typically via a
36
+ * subclass's `additionalMethods`, since
37
+ * `REFERENCE_SERVER_PROTOCOL_METHODS` is deliberately NOT part of
38
+ * `DATA_SERVER_PROTOCOL_METHODS`). The `TServer` bound carries
39
+ * `ReferenceServerProtocol`, so the delegates are type-checked without casts.
40
+ *
41
+ * A frontend needing both fragments extends the diagnostics base and mixes
42
+ * these three in, or vice versa — TypeScript allows only one base class, so
43
+ * the two bases are deliberately independent rather than chained. Chaining
44
+ * them would force every reference-fragment adopter to also carry the
45
+ * diagnostics surface, which a head that dropped those methods via
46
+ * `DataServerOptions.excludedMethods` cannot satisfy.
47
+ */
48
+ export abstract class AbstractReferencesDataServiceFrontend<
49
+ TTransfer extends TransferElement,
50
+ TServer extends { waitForReady(): Promise<void> } & ReferenceServerProtocol<TTransfer>,
51
+ TClient extends object
52
+ >
53
+ extends AbstractDataServiceFrontend<TServer, TClient>
54
+ implements ReferenceServerProtocol<TTransfer>
55
+ {
56
+ async findReferenceCandidates(ctx: ReferenceContext): Promise<ReferenceCandidate[]> {
57
+ await this.ensureConnected();
58
+ return this.server.findReferenceCandidates(ctx);
59
+ }
60
+
61
+ async resolveReference(ref: ReferenceRequest): Promise<ReferenceTarget<TTransfer> | undefined> {
62
+ await this.ensureConnected();
63
+ return this.server.resolveReference(ref);
64
+ }
65
+
66
+ async findNextName(args: FindNextNameArgs): Promise<string> {
67
+ await this.ensureConnected();
68
+ return this.server.findNextName(args);
69
+ }
70
+ }
@@ -0,0 +1,35 @@
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
+
10
+ import { type WorkspaceService } from '@theia/workspace/lib/browser';
11
+
12
+ /**
13
+ * Resolve once a workspace is open (one or more roots present). Resolves
14
+ * immediately if a workspace is already open, otherwise on the first
15
+ * `onWorkspaceChanged` that reports roots — the listener disposes itself.
16
+ *
17
+ * The common Theia gate for a data-server head: the model-server only starts
18
+ * once the LSP launches for a workspace, so connecting earlier would hang on
19
+ * port discovery. Pass the returned promise as
20
+ * `OpenChannelConnectionOptions.whenReady`.
21
+ */
22
+ export function whenWorkspaceOpen(workspaceService: WorkspaceService): Promise<void> {
23
+ const roots = workspaceService.tryGetRoots();
24
+ if (roots && roots.length > 0) {
25
+ return Promise.resolve();
26
+ }
27
+ return new Promise<void>(resolve => {
28
+ const disposable = workspaceService.onWorkspaceChanged(changedRoots => {
29
+ if (changedRoots && changedRoots.length > 0) {
30
+ disposable.dispose();
31
+ resolve();
32
+ }
33
+ });
34
+ });
35
+ }
@@ -0,0 +1,64 @@
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
+
10
+ import type {
11
+ DataClientProtocol,
12
+ Project,
13
+ ProjectsChangedEvent,
14
+ TransferDiagnostic,
15
+ TransferDocumentSavedEvent,
16
+ TransferDocumentUpdatedEvent,
17
+ TransferElement
18
+ } from '@hydranium/protocol';
19
+ import { Emitter, type Event } from '@theia/core';
20
+
21
+ /**
22
+ * Default {@link DataClientProtocol} implementation for a Theia frontend:
23
+ * each inbound `on*` notification is fanned out to a Theia {@link Event} so
24
+ * multiple local listeners can subscribe (the framework deliberately leaves
25
+ * fan-out to the consumer — see `DataClientProtocol`).
26
+ *
27
+ * Bind an instance as the `localTarget` of the frontend's `createRpcProxy`
28
+ * (with `DATA_CLIENT_PROTOCOL_METHODS` as `localMethods`); incoming
29
+ * `<namespace>onDocumentUpdated` etc. dispatch to the matching method here,
30
+ * which fires the paired `Event`.
31
+ *
32
+ * Adopters subclass to add signals that are not wire notifications, or to
33
+ * expose domain-renamed aliases of these channels — the event getters stay
34
+ * accessible to the subclass.
35
+ */
36
+ export class EmitterDataClient<
37
+ TTransfer extends TransferElement,
38
+ TDiagnostic extends TransferDiagnostic = TransferDiagnostic,
39
+ TProject extends Project = Project
40
+ > implements DataClientProtocol<TTransfer, TDiagnostic, TProject> {
41
+ protected readonly onDocumentUpdatedEmitter = new Emitter<TransferDocumentUpdatedEvent<TTransfer, TDiagnostic>>();
42
+ /** Fires for each inbound {@link onDocumentUpdated} notification. */
43
+ readonly onDidUpdateDocument: Event<TransferDocumentUpdatedEvent<TTransfer, TDiagnostic>> = this.onDocumentUpdatedEmitter.event;
44
+
45
+ protected readonly onDocumentSavedEmitter = new Emitter<TransferDocumentSavedEvent<TTransfer, TDiagnostic>>();
46
+ /** Fires for each inbound {@link onDocumentSaved} notification. */
47
+ readonly onDidSaveDocument: Event<TransferDocumentSavedEvent<TTransfer, TDiagnostic>> = this.onDocumentSavedEmitter.event;
48
+
49
+ protected readonly onProjectsChangedEmitter = new Emitter<ProjectsChangedEvent<TProject>>();
50
+ /** Fires for each inbound {@link onProjectsChanged} notification. */
51
+ readonly onDidChangeProjects: Event<ProjectsChangedEvent<TProject>> = this.onProjectsChangedEmitter.event;
52
+
53
+ onDocumentUpdated(event: TransferDocumentUpdatedEvent<TTransfer, TDiagnostic>): void {
54
+ this.onDocumentUpdatedEmitter.fire(event);
55
+ }
56
+
57
+ onDocumentSaved(event: TransferDocumentSavedEvent<TTransfer, TDiagnostic>): void {
58
+ this.onDocumentSavedEmitter.fire(event);
59
+ }
60
+
61
+ onProjectsChanged(event: ProjectsChangedEvent<TProject>): void {
62
+ this.onProjectsChangedEmitter.fire(event);
63
+ }
64
+ }
@@ -0,0 +1,21 @@
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
+
10
+ // Environment-agnostic shared surface for the data-server-head Theia
11
+ // integration. A module lands here only when its whole dependency closure —
12
+ // values, types and relative imports alike — is either neutral or Theia's
13
+ // COMMON tier; `@theia/core`'s root entry is that tier, while anything reaching
14
+ // `@theia/*/lib/browser` or `/lib/node` belongs in the tier it names, even when
15
+ // the import is type-only and erases at runtime.
16
+ //
17
+ // This tier deliberately holds no error-reconstruction bridge, and that is a
18
+ // property of the transport rather than an omission: the direct vscode-jsonrpc
19
+ // connection carries a typed error such as `ConflictError` across the channel
20
+ // relay natively, with no Theia msgpack hop to drop its data.
21
+ export * from './emitter-data-client';
package/src/index.ts ADDED
@@ -0,0 +1,18 @@
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
+
10
+ // Root barrel, deliberately empty: this package integrates a host whose own
11
+ // packages declare no root entry, so a consumer names a tier. The
12
+ // environment-agnostic surface is `@hydranium/data-client-theia/common`,
13
+ // browser-side primitives (channel transport / proxy wiring) are
14
+ // `.../browser`, and node-side ones (connection bootstrap) are `.../node`, so
15
+ // env-specific imports cannot leak into the wrong bundle. Re-exporting `common/`
16
+ // from here would make the root a partial surface that hides which tier a
17
+ // symbol lives on, which is the ambiguity the split exists to remove.
18
+ export {};
@@ -0,0 +1,51 @@
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
+
10
+ import { ConnectionHandler } from '@theia/core';
11
+ import { ConnectionContainerModule } from '@theia/core/lib/node/messaging/connection-container-module';
12
+ import { ContainerModule, type interfaces } from '@theia/core/shared/inversify';
13
+
14
+ /**
15
+ * Creates the frontend-scoped Theia backend module that registers a
16
+ * data-server {@link ConnectionHandler} (a `DataServerConnectionHandler`
17
+ * subclass) per browser-frontend connection. Returns a ready-to-export
18
+ * `ContainerModule` so adopters can
19
+ * `export default createDataServerConnectionContainerModule(MyHandler)` from
20
+ * their backend-module entry point.
21
+ *
22
+ * Sibling of `@hydranium/glsp-client-theia`'s
23
+ * `createGlspConnectionContainerModule` — the `ContainerModule +
24
+ * ConnectionContainerModule.create` boilerplate every frontend-scoped Theia
25
+ * connection adopter writes by hand, lifted so adopters declare only the
26
+ * handler class.
27
+ *
28
+ * **More than one handler is the normal case, not an exotic one.** Theia keys a
29
+ * frontend channel by its service path and refuses a second channel on a path
30
+ * already open, so every frontend abstraction reaching the data head needs its
31
+ * own path and therefore its own handler — a host-neutral `DataPort` beside a
32
+ * Theia `AbstractDataServiceFrontend` is exactly that shape. They still forward
33
+ * to the SAME data server: the path distinguishes the channel, the shared
34
+ * `portCommand` names the one process behind it.
35
+ */
36
+ export function createDataServerConnectionContainerModule(...handlerClasses: interfaces.Newable<ConnectionHandler>[]): ContainerModule {
37
+ const frontendScopedConnectionModule = ConnectionContainerModule.create(({ bind }) => {
38
+ for (const handlerClass of handlerClasses) {
39
+ bind(handlerClass).toSelf().inSingletonScope();
40
+ // `toDynamicValue` over `to(handlerClass)`: `ConnectionHandler` is
41
+ // multi-bound here, and resolving through the container keeps each
42
+ // entry the SAME singleton the adopter can inject by its own class.
43
+ bind(ConnectionHandler)
44
+ .toDynamicValue(context => context.container.get(handlerClass))
45
+ .inSingletonScope();
46
+ }
47
+ });
48
+ return new ContainerModule(bind => {
49
+ bind(ConnectionContainerModule).toConstantValue(frontendScopedConnectionModule);
50
+ });
51
+ }
@@ -0,0 +1,76 @@
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
+
10
+ import { AbstractSocketForwardingConnectionHandler } from '@hydranium/client-theia/lib/node';
11
+ import { DATA_SERVER_PATH, DATA_SERVER_PORT_COMMAND } from '@hydranium/protocol';
12
+ import { type Channel, type Disposable } from '@theia/core';
13
+ import { injectable, unmanaged } from '@theia/core/shared/inversify';
14
+ import type * as net from 'node:net';
15
+ import { SocketChannelForwarder } from './socket-channel-forwarder';
16
+
17
+ /** Options for {@link DataServerConnectionHandler}. `servicePath` is the Theia
18
+ * service path the browser frontend opens a channel to; `portCommand` is the
19
+ * command id whose return value is the data-server's listening port (the
20
+ * data-server publishes it on the LSP connection at startup, the same handshake
21
+ * GLSP uses). */
22
+ export interface DataServerConnectionHandlerOptions {
23
+ /** Theia service path the browser frontend opens a channel to. Defaults to
24
+ * the framework `DATA_SERVER_PATH`; override for an adopter-specific path,
25
+ * and NECESSARILY when a second frontend reaches the same head — Theia
26
+ * refuses a second channel on a path already open, so paths are per
27
+ * FRONTEND while `portCommand` stays per server. */
28
+ readonly servicePath?: string;
29
+ /** Command id whose return value is the data-server's listening port.
30
+ * Defaults to the framework `DATA_SERVER_PORT_COMMAND`; override to match
31
+ * an adopter's established id. */
32
+ readonly portCommand?: string;
33
+ readonly findPortTimeout?: number;
34
+ readonly findPortAttempts?: number;
35
+ readonly connectTimeoutMs?: number;
36
+ /**
37
+ * Optional diagnostic hook called immediately after the outbound
38
+ * `net.Socket` to the data-server is created, BEFORE `socket.connect()` is
39
+ * invoked. Adopters attach `'data'` / `'close'` listeners here for
40
+ * byte-level observability when debugging wire-level issues; attaching
41
+ * before `connect()` guarantees the very first bytes are observed.
42
+ */
43
+ readonly onSocketCreated?: (socket: net.Socket) => void;
44
+ }
45
+
46
+ /**
47
+ * Bridges a Theia browser-frontend channel to a data-server's TCP socket — the
48
+ * backend half of the data-server head's transport.
49
+ *
50
+ * The port-discovery + buffer-and-replay race fix + connect orchestration live
51
+ * on the cross-head {@link AbstractSocketForwardingConnectionHandler} base; this
52
+ * subclass supplies only the data-server defaults (service path / port command /
53
+ * log labels) and plugs in {@link SocketChannelForwarder} as the byte relay —
54
+ * its own forwarder, so the data head carries no GLSP dependency. Sibling of
55
+ * `@hydranium/glsp-client-theia`'s `GlspServerConnectionHandler`, which
56
+ * subclasses the same base with `@eclipse-glsp`'s `SocketConnectionForwarder`.
57
+ */
58
+ @injectable()
59
+ export class DataServerConnectionHandler extends AbstractSocketForwardingConnectionHandler {
60
+ constructor(@unmanaged() options: DataServerConnectionHandlerOptions = {}) {
61
+ super({
62
+ path: options.servicePath ?? DATA_SERVER_PATH,
63
+ portCommand: options.portCommand ?? DATA_SERVER_PORT_COMMAND,
64
+ logComponent: 'DataServer',
65
+ serverName: 'Model Server',
66
+ findPortTimeout: options.findPortTimeout,
67
+ findPortAttempts: options.findPortAttempts,
68
+ connectTimeoutMs: options.connectTimeoutMs,
69
+ onSocketCreated: options.onSocketCreated
70
+ });
71
+ }
72
+
73
+ protected forwardToSocketConnection(clientChannel: Channel, socket: net.Socket): Disposable {
74
+ return new SocketChannelForwarder(clientChannel, socket);
75
+ }
76
+ }
@@ -0,0 +1,57 @@
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
+
10
+ import { formatProcessMemory, writeHeapSnapshotToDir } from '@hydranium/core/lib/node';
11
+ import {
12
+ HOST_DIAGNOSTICS_PATH,
13
+ type DumpHostStateArgs,
14
+ type HostDiagnosticsProtocol,
15
+ type WriteHostHeapSnapshotArgs
16
+ } from '@hydranium/protocol';
17
+ import { ConnectionHandler, RpcConnectionHandler } from '@theia/core';
18
+ import { ContainerModule, injectable } from '@theia/core/shared/inversify';
19
+
20
+ /**
21
+ * Host-process diagnostics, computed IN the Theia backend process this service
22
+ * runs in (the parent / host, distinct from the data-server child that holds the
23
+ * model store). Universal — `process.memoryUsage()` always works — so the
24
+ * framework wires it by default. Each result is also written to the backend's
25
+ * stdout so it reaches the pod log (`kubectl logs`), not only the RPC caller.
26
+ */
27
+ @injectable()
28
+ export class HostDiagnosticsServer implements HostDiagnosticsProtocol {
29
+ async dumpHostState(args: DumpHostStateArgs): Promise<string> {
30
+ const snapshot = formatProcessMemory(args.label ? `Host backend state (${args.label})` : 'Host backend state');
31
+ console.info(snapshot);
32
+ return snapshot;
33
+ }
34
+
35
+ async writeHostHeapSnapshot(args: WriteHostHeapSnapshotArgs): Promise<string> {
36
+ const filePath = writeHeapSnapshotToDir(args.directory, args.label ?? 'backend', 'heap-backend');
37
+ console.info(`Host heap snapshot written to ${filePath}`);
38
+ return filePath;
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Theia backend module that exposes {@link HostDiagnosticsServer} as an ordinary
44
+ * in-process RPC service at {@link HOST_DIAGNOSTICS_PATH} (not the socket-
45
+ * forwarded data-server transport). The frontend reaches it with
46
+ * `bindHostDiagnostics` (see `../browser`). Adopters
47
+ * `export default createHostDiagnosticsBackendModule()` from a backend-module
48
+ * entry point.
49
+ */
50
+ export function createHostDiagnosticsBackendModule(): ContainerModule {
51
+ return new ContainerModule(bind => {
52
+ bind(HostDiagnosticsServer).toSelf().inSingletonScope();
53
+ bind(ConnectionHandler)
54
+ .toDynamicValue(context => new RpcConnectionHandler(HOST_DIAGNOSTICS_PATH, () => context.container.get(HostDiagnosticsServer)))
55
+ .inSingletonScope();
56
+ });
57
+ }
@@ -0,0 +1,13 @@
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
+
10
+ export * from './connection-container-module';
11
+ export * from './data-server-connection-handler';
12
+ export * from './host-diagnostics-server';
13
+ export * from './socket-channel-forwarder';
@@ -0,0 +1,64 @@
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
+
10
+ import { type Channel, Disposable, DisposableCollection, type MessageProvider } from '@theia/core';
11
+ import type * as net from 'node:net';
12
+ import { createMessageConnection, type Message, SocketMessageReader, SocketMessageWriter } from 'vscode-jsonrpc/node';
13
+
14
+ /**
15
+ * Relays JSON-RPC messages between a Theia browser-frontend {@link Channel} and
16
+ * a data-server's TCP socket — the backend half of the data-server head's
17
+ * transport. The model-server speaks vscode-jsonrpc (Content-Length framed) on
18
+ * the socket; the frontend speaks the same protocol over the channel (via
19
+ * `createChannelConnection`). This forwarder decodes each side and re-emits on
20
+ * the other, so the two ends share one protocol with no semantic re-proxy in
21
+ * between.
22
+ *
23
+ * Framework-owned equivalent of `@eclipse-glsp/theia-integration`'s
24
+ * `SocketConnectionForwarder` — reproduced here (rather than imported) so the
25
+ * data-server head carries no GLSP dependency.
26
+ */
27
+ export class SocketChannelForwarder implements Disposable {
28
+ protected readonly toDispose = new DisposableCollection();
29
+
30
+ constructor(
31
+ protected readonly channel: Channel,
32
+ protected readonly socket: net.Socket
33
+ ) {
34
+ const reader = new SocketMessageReader(socket);
35
+ const writer = new SocketMessageWriter(socket);
36
+ const connection = createMessageConnection(reader, writer);
37
+ this.toDispose.pushAll([
38
+ connection.onClose(() => socket.destroy()),
39
+ reader.listen(message => this.writeToChannel(message)),
40
+ channel.onMessage(provider => void writer.write(this.decodeChannelMessage(provider))),
41
+ channel.onClose(() => connection.dispose()),
42
+ connection.onClose(() => channel.close()),
43
+ Disposable.create(() => {
44
+ channel.close();
45
+ connection.dispose();
46
+ })
47
+ ]);
48
+ }
49
+
50
+ protected decodeChannelMessage(provider: MessageProvider): Message {
51
+ const buffer = provider().readBytes();
52
+ return JSON.parse(new TextDecoder().decode(buffer)) as Message;
53
+ }
54
+
55
+ protected writeToChannel(message: Message): void {
56
+ const writeBuffer = this.channel.getWriteBuffer();
57
+ writeBuffer.writeBytes(new TextEncoder().encode(JSON.stringify(message)));
58
+ writeBuffer.commit();
59
+ }
60
+
61
+ dispose(): void {
62
+ this.toDispose.dispose();
63
+ }
64
+ }