@hydranium/data-client-theia 1.0.0-next.19 → 1.0.0-next.192

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 (64) hide show
  1. package/README.md +35 -27
  2. package/lib/browser/channel-connection.d.ts +2 -2
  3. package/lib/browser/channel-connection.js.map +1 -1
  4. package/lib/browser/channel-data-port.d.ts +83 -0
  5. package/lib/browser/channel-data-port.d.ts.map +1 -0
  6. package/lib/browser/channel-data-port.js +179 -0
  7. package/lib/browser/channel-data-port.js.map +1 -0
  8. package/lib/browser/data-session-stop-contribution.d.ts +64 -0
  9. package/lib/browser/data-session-stop-contribution.d.ts.map +1 -0
  10. package/lib/browser/data-session-stop-contribution.js +117 -0
  11. package/lib/browser/data-session-stop-contribution.js.map +1 -0
  12. package/lib/browser/host-diagnostics-frontend.js.map +1 -1
  13. package/lib/browser/index.d.ts +2 -3
  14. package/lib/browser/index.d.ts.map +1 -1
  15. package/lib/browser/index.js +2 -3
  16. package/lib/browser/index.js.map +1 -1
  17. package/lib/browser/workspace-gate.js.map +1 -1
  18. package/lib/common/emitter-data-client.d.ts +13 -1
  19. package/lib/common/emitter-data-client.d.ts.map +1 -1
  20. package/lib/common/emitter-data-client.js +18 -0
  21. package/lib/common/emitter-data-client.js.map +1 -1
  22. package/lib/node/connection-container-module.d.ts +5 -4
  23. package/lib/node/connection-container-module.d.ts.map +1 -1
  24. package/lib/node/connection-container-module.js +5 -4
  25. package/lib/node/connection-container-module.js.map +1 -1
  26. package/lib/node/data-server-connection-handler.d.ts +9 -9
  27. package/lib/node/data-server-connection-handler.d.ts.map +1 -1
  28. package/lib/node/data-server-connection-handler.js +8 -12
  29. package/lib/node/data-server-connection-handler.js.map +1 -1
  30. package/lib/node/host-diagnostics-server.d.ts.map +1 -1
  31. package/lib/node/host-diagnostics-server.js.map +1 -1
  32. package/lib/node/index.d.ts +0 -1
  33. package/lib/node/index.d.ts.map +1 -1
  34. package/lib/node/index.js +0 -1
  35. package/lib/node/index.js.map +1 -1
  36. package/package.json +25 -24
  37. package/src/browser/channel-connection.ts +2 -2
  38. package/src/browser/channel-data-port.ts +173 -0
  39. package/src/browser/data-session-stop-contribution.ts +117 -0
  40. package/src/browser/index.ts +2 -3
  41. package/src/common/emitter-data-client.ts +27 -0
  42. package/src/node/connection-container-module.ts +5 -4
  43. package/src/node/data-server-connection-handler.ts +10 -13
  44. package/src/node/index.ts +0 -1
  45. package/lib/browser/data-service-frontend.d.ts +0 -154
  46. package/lib/browser/data-service-frontend.d.ts.map +0 -1
  47. package/lib/browser/data-service-frontend.js +0 -169
  48. package/lib/browser/data-service-frontend.js.map +0 -1
  49. package/lib/browser/diagnostics-data-service-frontend.d.ts +0 -35
  50. package/lib/browser/diagnostics-data-service-frontend.d.ts.map +0 -1
  51. package/lib/browser/diagnostics-data-service-frontend.js +0 -54
  52. package/lib/browser/diagnostics-data-service-frontend.js.map +0 -1
  53. package/lib/browser/references-data-service-frontend.d.ts +0 -45
  54. package/lib/browser/references-data-service-frontend.d.ts.map +0 -1
  55. package/lib/browser/references-data-service-frontend.js +0 -55
  56. package/lib/browser/references-data-service-frontend.js.map +0 -1
  57. package/lib/node/socket-channel-forwarder.d.ts +0 -34
  58. package/lib/node/socket-channel-forwarder.d.ts.map +0 -1
  59. package/lib/node/socket-channel-forwarder.js +0 -63
  60. package/lib/node/socket-channel-forwarder.js.map +0 -1
  61. package/src/browser/data-service-frontend.ts +0 -208
  62. package/src/browser/diagnostics-data-service-frontend.ts +0 -70
  63. package/src/browser/references-data-service-frontend.ts +0 -70
  64. package/src/node/socket-channel-forwarder.ts +0 -64
@@ -0,0 +1,117 @@
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 { DataConnection, DataServerProtocol, DataSession, DiagnosticOf, TransferElement } from '@hydranium/protocol';
11
+ // Type-only: the `@theia/core/lib/browser` barrel touches DOM globals at module
12
+ // load, which the node-environment unit tests cannot provide.
13
+ import type { FrontendApplicationContribution, OnWillStopAction } from '@theia/core/lib/browser';
14
+ import { injectable } from '@theia/core/shared/inversify';
15
+
16
+ /**
17
+ * Ends a Theia frontend's data sessions with its page, and holds the page
18
+ * while one of them is still saving. Bind it as a
19
+ * `FrontendApplicationContribution` in singleton scope and {@link track} each
20
+ * data connection the frontend binds, once.
21
+ *
22
+ * **Stop.** {@link onStop} disposes every tracked session, which sends its
23
+ * `closeSession`, so the server ends it as closed: each document it was the
24
+ * last to hold reverts at once. Otherwise the server learns of the page only
25
+ * when its connection goes, which Theia may hold open for its reconnect
26
+ * timeout, and then ends the sessions as lost, so their documents wait out the
27
+ * revert grace. The close is best effort: under load the page's last frames
28
+ * can be lost on the way, and the server then ends the sessions as lost. A
29
+ * session with calls still in flight at the stop closes after them, which is
30
+ * too late for a page going away, and is left to the server's connection-close
31
+ * cleanup. A call a session makes in the same tick as the stop is refused and
32
+ * never sent.
33
+ *
34
+ * **Veto.** While a tracked session has a save in flight, {@link onWillStop}
35
+ * vetoes, including a session disposed meanwhile. In Electron, Theia awaits
36
+ * the veto's action, which waits for the saves to answer, a save started during
37
+ * the wait included, up to {@link maxSettlePasses} times. The browser runs no
38
+ * action before unload, so there the veto shows the leave-page prompt and
39
+ * nothing more. Theia ignores every veto under
40
+ * `application.confirmExit: 'never'`, so there a save in flight at the stop
41
+ * may be cut off.
42
+ */
43
+ @injectable()
44
+ export class DataSessionStopContribution implements FrontendApplicationContribution {
45
+ /** The sessions to end at the stop, and to wait for while they save. */
46
+ protected readonly sessions = new Set<DataSession<TransferElement>>();
47
+ /**
48
+ * How many times the veto's action waits for the saves in flight before it
49
+ * lets the stop go. Each wait ends by the session's own bound, so a save
50
+ * that never answers holds the stop for a few bounds, not for good.
51
+ */
52
+ protected readonly maxSettlePasses: number = 3;
53
+
54
+ /**
55
+ * Take every session of `connection` into the stop: the ones it has
56
+ * started already, and each one it starts from now on. Generic, so a
57
+ * connection of any transfer root, server and client fits.
58
+ */
59
+ track<TTransfer extends TransferElement, TServer extends DataServerProtocol<TTransfer, DiagnosticOf<TServer>>, TClient extends object>(
60
+ connection: DataConnection<TTransfer, TServer, TClient>
61
+ ): void {
62
+ connection.liveSessions.forEach(session => this.trackSession(session));
63
+ connection.onDidCreateSession(session => this.trackSession(session));
64
+ }
65
+
66
+ /**
67
+ * Take `session` into the stop. It leaves once it has ended and its saves
68
+ * have answered, so a session disposed with a save in flight still holds
69
+ * the page.
70
+ */
71
+ protected trackSession(session: DataSession<TransferElement>): void {
72
+ this.sessions.add(session);
73
+ // A save can outlast the wait's bound, so the session stays until none
74
+ // is left in flight.
75
+ const release = (): void =>
76
+ void session.whenSavesSettled().then(() => (session.hasSavesInFlight ? release() : this.sessions.delete(session)));
77
+ // An ended session has fired its event already, and never fires it again.
78
+ if (session.isDisposed) {
79
+ release();
80
+ } else {
81
+ session.onDidDispose(release);
82
+ }
83
+ }
84
+
85
+ onWillStop(): OnWillStopAction | undefined {
86
+ if (this.savingSessions().length === 0) {
87
+ return undefined;
88
+ }
89
+ return {
90
+ // Theia logs the reason and shows it nowhere, so it is not translated.
91
+ reason: 'Data sessions are saving',
92
+ action: async () => {
93
+ // The page stays usable while Theia awaits this, so a save can
94
+ // start during a wait; each pass waits for the saves then in flight.
95
+ for (let pass = 0; pass < this.maxSettlePasses; pass++) {
96
+ const saving = this.savingSessions();
97
+ if (saving.length === 0) {
98
+ break;
99
+ }
100
+ await Promise.all(saving.map(session => session.whenSavesSettled()));
101
+ }
102
+ return true;
103
+ }
104
+ };
105
+ }
106
+
107
+ /** The tracked sessions with a save in flight. */
108
+ protected savingSessions(): DataSession<TransferElement>[] {
109
+ return [...this.sessions].filter(session => session.hasSavesInFlight);
110
+ }
111
+
112
+ onStop(): void {
113
+ for (const session of this.sessions) {
114
+ session.dispose();
115
+ }
116
+ }
117
+ }
@@ -8,8 +8,7 @@
8
8
  ********************************************************************************/
9
9
 
10
10
  export * from './channel-connection';
11
- export * from './data-service-frontend';
12
- export * from './diagnostics-data-service-frontend';
11
+ export * from './channel-data-port';
12
+ export * from './data-session-stop-contribution';
13
13
  export * from './host-diagnostics-frontend';
14
- export * from './references-data-service-frontend';
15
14
  export * from './workspace-gate';
@@ -12,7 +12,10 @@ import type {
12
12
  Project,
13
13
  ProjectsChangedEvent,
14
14
  TransferDiagnostic,
15
+ TransferDocumentDeletedEvent,
16
+ TransferDocumentDirtyChangedEvent,
15
17
  TransferDocumentSavedEvent,
18
+ TransferDocumentsBuiltEvent,
16
19
  TransferDocumentUpdatedEvent,
17
20
  TransferElement
18
21
  } from '@hydranium/protocol';
@@ -46,6 +49,18 @@ export class EmitterDataClient<
46
49
  /** Fires for each inbound {@link onDocumentSaved} notification. */
47
50
  readonly onDidSaveDocument: Event<TransferDocumentSavedEvent<TTransfer, TDiagnostic>> = this.onDocumentSavedEmitter.event;
48
51
 
52
+ protected readonly onDocumentDirtyChangedEmitter = new Emitter<TransferDocumentDirtyChangedEvent>();
53
+ /** Fires for each inbound {@link onDocumentDirtyChanged} notification. */
54
+ readonly onDidChangeDocumentDirty: Event<TransferDocumentDirtyChangedEvent> = this.onDocumentDirtyChangedEmitter.event;
55
+
56
+ protected readonly onDocumentDeletedEmitter = new Emitter<TransferDocumentDeletedEvent>();
57
+ /** Fires for each inbound {@link onDocumentDeleted} notification. */
58
+ readonly onDidDeleteDocument: Event<TransferDocumentDeletedEvent> = this.onDocumentDeletedEmitter.event;
59
+
60
+ protected readonly onDocumentsBuiltEmitter = new Emitter<TransferDocumentsBuiltEvent>();
61
+ /** Fires for each inbound {@link onDocumentsBuilt} notification. */
62
+ readonly onDidBuildDocuments: Event<TransferDocumentsBuiltEvent> = this.onDocumentsBuiltEmitter.event;
63
+
49
64
  protected readonly onProjectsChangedEmitter = new Emitter<ProjectsChangedEvent<TProject>>();
50
65
  /** Fires for each inbound {@link onProjectsChanged} notification. */
51
66
  readonly onDidChangeProjects: Event<ProjectsChangedEvent<TProject>> = this.onProjectsChangedEmitter.event;
@@ -58,6 +73,18 @@ export class EmitterDataClient<
58
73
  this.onDocumentSavedEmitter.fire(event);
59
74
  }
60
75
 
76
+ onDocumentDirtyChanged(event: TransferDocumentDirtyChangedEvent): void {
77
+ this.onDocumentDirtyChangedEmitter.fire(event);
78
+ }
79
+
80
+ onDocumentDeleted(event: TransferDocumentDeletedEvent): void {
81
+ this.onDocumentDeletedEmitter.fire(event);
82
+ }
83
+
84
+ onDocumentsBuilt(event: TransferDocumentsBuiltEvent): void {
85
+ this.onDocumentsBuiltEmitter.fire(event);
86
+ }
87
+
61
88
  onProjectsChanged(event: ProjectsChangedEvent<TProject>): void {
62
89
  this.onProjectsChangedEmitter.fire(event);
63
90
  }
@@ -27,11 +27,12 @@ import { ContainerModule, type interfaces } from '@theia/core/shared/inversify';
27
27
  *
28
28
  * **More than one handler is the normal case, not an exotic one.** Theia keys a
29
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
30
+ * already open, so every frontend abstraction reaching the data head on its own
31
+ * channel needs its own path and therefore its own handler. They still forward
33
32
  * to the SAME data server: the path distinguishes the channel, the shared
34
- * `portCommand` names the one process behind it.
33
+ * `portCommand` names the one process behind it. Several participants over ONE
34
+ * channel need no second handler — that is what `DataConnection`'s sessions are
35
+ * for, and it is the cheaper arrangement.
35
36
  */
36
37
  export function createDataServerConnectionContainerModule(...handlerClasses: interfaces.Newable<ConnectionHandler>[]): ContainerModule {
37
38
  const frontendScopedConnectionModule = ConnectionContainerModule.create(({ bind }) => {
@@ -9,10 +9,8 @@
9
9
 
10
10
  import { AbstractSocketForwardingConnectionHandler } from '@hydranium/client-theia/lib/node';
11
11
  import { DATA_SERVER_PATH, DATA_SERVER_PORT_COMMAND } from '@hydranium/protocol';
12
- import { type Channel, type Disposable } from '@theia/core';
13
12
  import { injectable, unmanaged } from '@theia/core/shared/inversify';
14
13
  import type * as net from 'node:net';
15
- import { SocketChannelForwarder } from './socket-channel-forwarder';
16
14
 
17
15
  /** Options for {@link DataServerConnectionHandler}. `servicePath` is the Theia
18
16
  * service path the browser frontend opens a channel to; `portCommand` is the
@@ -30,6 +28,8 @@ export interface DataServerConnectionHandlerOptions {
30
28
  * Defaults to the framework `DATA_SERVER_PORT_COMMAND`; override to match
31
29
  * an adopter's established id. */
32
30
  readonly portCommand?: string;
31
+ /** Name of the server in this handler's log lines. */
32
+ readonly serverName?: string;
33
33
  readonly findPortTimeout?: number;
34
34
  readonly findPortAttempts?: number;
35
35
  readonly connectTimeoutMs?: number;
@@ -47,13 +47,10 @@ export interface DataServerConnectionHandlerOptions {
47
47
  * Bridges a Theia browser-frontend channel to a data-server's TCP socket — the
48
48
  * backend half of the data-server head's transport.
49
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`.
50
+ * The port discovery, the buffer-and-replay race fix, the connect orchestration
51
+ * and the byte forwarder live on the cross-head
52
+ * {@link AbstractSocketForwardingConnectionHandler} base; this subclass supplies
53
+ * only the data-server defaults (service path / port command / log labels).
57
54
  */
58
55
  @injectable()
59
56
  export class DataServerConnectionHandler extends AbstractSocketForwardingConnectionHandler {
@@ -62,7 +59,7 @@ export class DataServerConnectionHandler extends AbstractSocketForwardingConnect
62
59
  path: options.servicePath ?? DATA_SERVER_PATH,
63
60
  portCommand: options.portCommand ?? DATA_SERVER_PORT_COMMAND,
64
61
  logComponent: 'DataServer',
65
- serverName: 'Model Server',
62
+ serverName: options.serverName ?? 'Data Server',
66
63
  findPortTimeout: options.findPortTimeout,
67
64
  findPortAttempts: options.findPortAttempts,
68
65
  connectTimeoutMs: options.connectTimeoutMs,
@@ -70,7 +67,7 @@ export class DataServerConnectionHandler extends AbstractSocketForwardingConnect
70
67
  });
71
68
  }
72
69
 
73
- protected forwardToSocketConnection(clientChannel: Channel, socket: net.Socket): Disposable {
74
- return new SocketChannelForwarder(clientChannel, socket);
75
- }
70
+ /** Logged only: the frontend port reports the connection, and a second
71
+ * notification would duplicate it. */
72
+ protected override reportConnectFailure(): void {}
76
73
  }
package/src/node/index.ts CHANGED
@@ -10,4 +10,3 @@
10
10
  export * from './connection-container-module';
11
11
  export * from './data-server-connection-handler';
12
12
  export * from './host-diagnostics-server';
13
- export * from './socket-channel-forwarder';
@@ -1,154 +0,0 @@
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 { ServiceConnectionProvider } from '@theia/core/lib/browser';
10
- import { Deferred } from '@theia/core/lib/common/promise-util';
11
- import type { WorkspaceService } from '@theia/workspace/lib/browser';
12
- import type { MessageConnection } from 'vscode-jsonrpc';
13
- import { type ChannelConnectionHandle } from './channel-connection';
14
- /**
15
- * Base for a Theia frontend that owns the data-server vscode-jsonrpc
16
- * connection directly (the data head's "frontend speaks the model-server's
17
- * protocol over a relayed channel" pattern). Lifts the mechanical wiring —
18
- * the workspace-gated connection, the combined server proxy + inbound client
19
- * binding, and the lazy idempotent init gate — leaving the adopter to supply
20
- * the connection seams and any progress UI / domain caching / request-method
21
- * delegation on top.
22
- *
23
- * Generic over the server protocol `TServer` (must expose `waitForReady`) and
24
- * the local notification target `TClient`. A subclass supplies the abstract
25
- * members below, calls {@link start} from its `@postConstruct`, and awaits
26
- * {@link ensureConnected} before its first `this.server.*` call.
27
- */
28
- export declare abstract class AbstractDataServiceFrontend<TServer extends {
29
- waitForReady(): Promise<void>;
30
- }, TClient extends object> {
31
- /**
32
- * The workspace-gated connection to the backend forwarder. Set by
33
- * {@link start}, and REPLACED whenever the connection is lost and
34
- * {@link reconnectOnConnectionLoss} is on — so read it per use and never
35
- * cache the resolved connection.
36
- */
37
- protected connectionPromise: Promise<MessageConnection>;
38
- /**
39
- * Typed proxy over {@link connectionPromise}, addressing the server under
40
- * {@link methodNamespace}. Set by {@link start}, and replaced alongside
41
- * {@link connectionPromise} on reconnect.
42
- *
43
- * `createRpcProxy` resolves the connection promise once and binds to it for
44
- * good, so a reconnect necessarily means a new proxy. Reading
45
- * `this.server.foo()` per call — rather than hoisting `this.server` into a
46
- * local or a constructor-time field — is what keeps a subclass correct
47
- * across one.
48
- */
49
- protected server: TServer;
50
- /** The channel handle {@link start} opened; owns reconnect and disposal. */
51
- protected channel?: ChannelConnectionHandle;
52
- /** Shared init Deferred so concurrent {@link ensureConnected} callers await one initialization. */
53
- protected initialized?: Deferred<void>;
54
- /** Theia connection provider the channel is opened through. */
55
- protected abstract readonly connectionProvider: ServiceConnectionProvider;
56
- /**
57
- * Optional workspace service. When provided, the default
58
- * {@link connectionReadyGate} waits for a workspace before opening the
59
- * channel; a head that is not workspace-scoped omits it (and may override
60
- * {@link connectionReadyGate} for a different gate).
61
- */
62
- protected abstract readonly workspaceService?: WorkspaceService;
63
- /** Local inbound-notification target bound on the connection (the `localTarget`). */
64
- protected abstract readonly client: TClient;
65
- /**
66
- * Theia service path the backend forwarder is registered under.
67
- *
68
- * **Unique per frontend, not per server.** Theia keys a frontend channel by
69
- * this path and throws `Another channel with the id '<path>' is already open`
70
- * on a second opener — so a subclass sharing the framework default with any
71
- * other consumer of the same head (a host-neutral `DataPort`, a sibling
72
- * service frontend) breaks whichever opens second. The failure is remote from
73
- * its cause: the throw escapes an `openChannelConnection` the other consumer
74
- * awaited, leaving its request permanently unsettled rather than rejected,
75
- * which presents as a view stuck on its loading state with a clean server
76
- * log. Give each frontend its own path and register a forwarder per path;
77
- * they still reach one server, since the shared `portCommand` is what names
78
- * the process.
79
- */
80
- protected abstract readonly servicePath: string;
81
- /** Wire namespace the server + client methods are addressed under. */
82
- protected abstract readonly methodNamespace: string;
83
- /** Allowlist of {@link client} methods to bind as inbound handlers. */
84
- protected abstract readonly clientMethods: readonly (keyof TClient & string)[];
85
- /**
86
- * Rebuild the connection and the proxy when the current connection is lost,
87
- * and re-run initialization against the replacement. Defaults to `true` —
88
- * see `OpenChannelConnectionOptions.reconnect` for why re-opening the
89
- * channel is the only thing that recovers a restarted language server, and
90
- * why a dead connection leaves no alternative worth preserving.
91
- *
92
- * The subclass-facing cost is that {@link doInitialize} runs again per
93
- * connection, so any progress UI it drives reappears. Turn this off for a
94
- * frontend that would rather show nothing than show its warm-up twice, or
95
- * that tears itself down on transport loss.
96
- */
97
- protected readonly reconnectOnConnectionLoss: boolean;
98
- /**
99
- * Readiness gate for the connection — the channel opens only once the
100
- * returned promise settles. Default: waits for a workspace when
101
- * {@link workspaceService} is provided, otherwise opens immediately
102
- * (`undefined`). Override for a different gate (e.g. a fixed model store
103
- * that is always ready, or a custom warm-up).
104
- */
105
- protected connectionReadyGate(): Promise<void> | undefined;
106
- /**
107
- * Open the connection (workspace-gated by default via
108
- * {@link connectionReadyGate}) and build the combined server proxy +
109
- * inbound client binding. Call once (typically from the adopter's
110
- * `@postConstruct`). Outbound calls + inbound notifications queue over the
111
- * connection promise until the channel is live.
112
- */
113
- protected start(): void;
114
- /**
115
- * Point {@link connectionPromise} and {@link server} at the channel's
116
- * current connection. Called by {@link start} and again per reconnect.
117
- */
118
- protected bindConnection(): void;
119
- /**
120
- * Rebind onto the replacement connection and arm initialization to run again.
121
- *
122
- * Clearing {@link initialized} is the load-bearing half. A restarted server
123
- * has an unwarmed workspace, so its `waitForReady` gate has to be awaited
124
- * afresh; leaving the old resolved Deferred in place would let the first
125
- * request after a restart through against a server still walking the
126
- * workspace, and be answered correctly from an empty registry — which reads
127
- * as data loss rather than as a race.
128
- */
129
- protected handleConnectionLost(): void;
130
- /**
131
- * Release the connection and stop tracking the channel. Idempotent.
132
- *
133
- * Subclasses that are Theia `Disposable`s should route their own disposal
134
- * here; nothing calls it automatically, because the base is not bound to a
135
- * lifecycle of its own.
136
- */
137
- dispose(): void;
138
- /**
139
- * Lazily drive initialization, shared across concurrent callers via one
140
- * {@link Deferred}. Request methods `await this.ensureConnected()` before
141
- * their first `this.server.*` call.
142
- */
143
- protected ensureConnected(): Promise<void>;
144
- /**
145
- * Default initialization: await the connection, await the server's readiness
146
- * gate, then resolve the passed Deferred. Initialization completion is
147
- * observable by awaiting {@link ensureConnected} (which returns this same
148
- * Deferred's promise) — there is no separate post-init hook. Override
149
- * wholesale to interleave progress UI / extra warm-up; an override owns
150
- * resolving/rejecting `initialized` (there is no `super` step to call).
151
- */
152
- protected doInitialize(initialized: Deferred<void>): Promise<void>;
153
- }
154
- //# sourceMappingURL=data-service-frontend.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"data-service-frontend.d.ts","sourceRoot":"","sources":["../../src/browser/data-service-frontend.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAGlF,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,yBAAyB,CAAC;AACzE,OAAO,EAAE,QAAQ,EAAE,MAAM,qCAAqC,CAAC;AAC/D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AACrE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,EAAE,KAAK,uBAAuB,EAAyB,MAAM,sBAAsB,CAAC;AAG3F;;;;;;;;;;;;;GAaG;AACH,8BAAsB,2BAA2B,CAAC,OAAO,SAAS;IAAE,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,EAAE,OAAO,SAAS,MAAM;IACxH;;;;;OAKG;IACH,SAAS,CAAC,iBAAiB,EAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;IACzD;;;;;;;;;;OAUG;IACH,SAAS,CAAC,MAAM,EAAG,OAAO,CAAC;IAC3B,4EAA4E;IAC5E,SAAS,CAAC,OAAO,CAAC,EAAE,uBAAuB,CAAC;IAC5C,mGAAmG;IACnG,SAAS,CAAC,WAAW,CAAC,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC;IAEvC,+DAA+D;IAC/D,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,kBAAkB,EAAE,yBAAyB,CAAC;IAC1E;;;;;OAKG;IACH,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IAChE,qFAAqF;IACrF,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IAC5C;;;;;;;;;;;;;;OAcG;IACH,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAChD,sEAAsE;IACtE,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACpD,uEAAuE;IACvE,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,aAAa,EAAE,SAAS,CAAC,MAAM,OAAO,GAAG,MAAM,CAAC,EAAE,CAAC;IAE/E;;;;;;;;;;;OAWG;IACH,SAAS,CAAC,QAAQ,CAAC,yBAAyB,EAAE,OAAO,CAAQ;IAE7D;;;;;;OAMG;IACH,SAAS,CAAC,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,SAAS;IAI1D;;;;;;OAMG;IACH,SAAS,CAAC,KAAK,IAAI,IAAI;IAavB;;;OAGG;IACH,SAAS,CAAC,cAAc,IAAI,IAAI;IAYhC;;;;;;;;;OASG;IACH,SAAS,CAAC,oBAAoB,IAAI,IAAI;IAKtC;;;;;;OAMG;IACH,OAAO,IAAI,IAAI;IAMf;;;;OAIG;IACH,SAAS,CAAC,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ1C;;;;;;;OAOG;cACa,YAAY,CAAC,WAAW,EAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;CAS1E"}
@@ -1,169 +0,0 @@
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.AbstractDataServiceFrontend = void 0;
12
- const protocol_1 = require("@hydranium/protocol");
13
- const promise_util_1 = require("@theia/core/lib/common/promise-util");
14
- const channel_connection_1 = require("./channel-connection");
15
- const workspace_gate_1 = require("./workspace-gate");
16
- /**
17
- * Base for a Theia frontend that owns the data-server vscode-jsonrpc
18
- * connection directly (the data head's "frontend speaks the model-server's
19
- * protocol over a relayed channel" pattern). Lifts the mechanical wiring —
20
- * the workspace-gated connection, the combined server proxy + inbound client
21
- * binding, and the lazy idempotent init gate — leaving the adopter to supply
22
- * the connection seams and any progress UI / domain caching / request-method
23
- * delegation on top.
24
- *
25
- * Generic over the server protocol `TServer` (must expose `waitForReady`) and
26
- * the local notification target `TClient`. A subclass supplies the abstract
27
- * members below, calls {@link start} from its `@postConstruct`, and awaits
28
- * {@link ensureConnected} before its first `this.server.*` call.
29
- */
30
- class AbstractDataServiceFrontend {
31
- /**
32
- * The workspace-gated connection to the backend forwarder. Set by
33
- * {@link start}, and REPLACED whenever the connection is lost and
34
- * {@link reconnectOnConnectionLoss} is on — so read it per use and never
35
- * cache the resolved connection.
36
- */
37
- connectionPromise;
38
- /**
39
- * Typed proxy over {@link connectionPromise}, addressing the server under
40
- * {@link methodNamespace}. Set by {@link start}, and replaced alongside
41
- * {@link connectionPromise} on reconnect.
42
- *
43
- * `createRpcProxy` resolves the connection promise once and binds to it for
44
- * good, so a reconnect necessarily means a new proxy. Reading
45
- * `this.server.foo()` per call — rather than hoisting `this.server` into a
46
- * local or a constructor-time field — is what keeps a subclass correct
47
- * across one.
48
- */
49
- server;
50
- /** The channel handle {@link start} opened; owns reconnect and disposal. */
51
- channel;
52
- /** Shared init Deferred so concurrent {@link ensureConnected} callers await one initialization. */
53
- initialized;
54
- /**
55
- * Rebuild the connection and the proxy when the current connection is lost,
56
- * and re-run initialization against the replacement. Defaults to `true` —
57
- * see `OpenChannelConnectionOptions.reconnect` for why re-opening the
58
- * channel is the only thing that recovers a restarted language server, and
59
- * why a dead connection leaves no alternative worth preserving.
60
- *
61
- * The subclass-facing cost is that {@link doInitialize} runs again per
62
- * connection, so any progress UI it drives reappears. Turn this off for a
63
- * frontend that would rather show nothing than show its warm-up twice, or
64
- * that tears itself down on transport loss.
65
- */
66
- reconnectOnConnectionLoss = true;
67
- /**
68
- * Readiness gate for the connection — the channel opens only once the
69
- * returned promise settles. Default: waits for a workspace when
70
- * {@link workspaceService} is provided, otherwise opens immediately
71
- * (`undefined`). Override for a different gate (e.g. a fixed model store
72
- * that is always ready, or a custom warm-up).
73
- */
74
- connectionReadyGate() {
75
- return this.workspaceService ? (0, workspace_gate_1.whenWorkspaceOpen)(this.workspaceService) : undefined;
76
- }
77
- /**
78
- * Open the connection (workspace-gated by default via
79
- * {@link connectionReadyGate}) and build the combined server proxy +
80
- * inbound client binding. Call once (typically from the adopter's
81
- * `@postConstruct`). Outbound calls + inbound notifications queue over the
82
- * connection promise until the channel is live.
83
- */
84
- start() {
85
- this.channel = (0, channel_connection_1.openChannelConnection)(this.connectionProvider, this.servicePath, {
86
- whenReady: this.connectionReadyGate(),
87
- reconnect: this.reconnectOnConnectionLoss
88
- });
89
- this.bindConnection();
90
- // The LOSS, not the replacement's arrival: rebinding when the channel
91
- // closes points `server` at the queueing replacement promise, so a request
92
- // made during the gap waits for the new server instead of being addressed
93
- // at the dead one and never settling.
94
- this.channel.onDidLoseConnection(() => this.handleConnectionLost());
95
- }
96
- /**
97
- * Point {@link connectionPromise} and {@link server} at the channel's
98
- * current connection. Called by {@link start} and again per reconnect.
99
- */
100
- bindConnection() {
101
- if (!this.channel) {
102
- throw new Error('bindConnection called before start');
103
- }
104
- this.connectionPromise = this.channel.current;
105
- this.server = (0, protocol_1.createRpcProxy)(this.connectionPromise, {
106
- methodNamespace: this.methodNamespace,
107
- localTarget: this.client,
108
- localMethods: this.clientMethods
109
- });
110
- }
111
- /**
112
- * Rebind onto the replacement connection and arm initialization to run again.
113
- *
114
- * Clearing {@link initialized} is the load-bearing half. A restarted server
115
- * has an unwarmed workspace, so its `waitForReady` gate has to be awaited
116
- * afresh; leaving the old resolved Deferred in place would let the first
117
- * request after a restart through against a server still walking the
118
- * workspace, and be answered correctly from an empty registry — which reads
119
- * as data loss rather than as a race.
120
- */
121
- handleConnectionLost() {
122
- this.initialized = undefined;
123
- this.bindConnection();
124
- }
125
- /**
126
- * Release the connection and stop tracking the channel. Idempotent.
127
- *
128
- * Subclasses that are Theia `Disposable`s should route their own disposal
129
- * here; nothing calls it automatically, because the base is not bound to a
130
- * lifecycle of its own.
131
- */
132
- dispose() {
133
- this.channel?.dispose();
134
- this.channel = undefined;
135
- this.initialized = undefined;
136
- }
137
- /**
138
- * Lazily drive initialization, shared across concurrent callers via one
139
- * {@link Deferred}. Request methods `await this.ensureConnected()` before
140
- * their first `this.server.*` call.
141
- */
142
- ensureConnected() {
143
- if (!this.initialized) {
144
- this.initialized = new promise_util_1.Deferred();
145
- void this.doInitialize(this.initialized);
146
- }
147
- return this.initialized.promise;
148
- }
149
- /**
150
- * Default initialization: await the connection, await the server's readiness
151
- * gate, then resolve the passed Deferred. Initialization completion is
152
- * observable by awaiting {@link ensureConnected} (which returns this same
153
- * Deferred's promise) — there is no separate post-init hook. Override
154
- * wholesale to interleave progress UI / extra warm-up; an override owns
155
- * resolving/rejecting `initialized` (there is no `super` step to call).
156
- */
157
- async doInitialize(initialized) {
158
- try {
159
- await this.connectionPromise;
160
- await this.server.waitForReady();
161
- initialized.resolve();
162
- }
163
- catch (error) {
164
- initialized.reject(error instanceof Error ? error : new Error(String(error)));
165
- }
166
- }
167
- }
168
- exports.AbstractDataServiceFrontend = AbstractDataServiceFrontend;
169
- //# sourceMappingURL=data-service-frontend.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"data-service-frontend.js","sourceRoot":"","sources":["../../src/browser/data-service-frontend.ts"],"names":[],"mappings":"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"}
@@ -1,35 +0,0 @@
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 { DataServerDiagnosticsProtocol, DumpServerStateArgs, LatencyReport, StartProfilingArgs, StopProfilingArgs, WriteServerHeapSnapshotArgs } from '@hydranium/protocol';
10
- import { AbstractDataServiceFrontend } from './data-service-frontend';
11
- /**
12
- * {@link AbstractDataServiceFrontend} specialised for a server that also exposes
13
- * the framework {@link DataServerDiagnosticsProtocol} (which the framework
14
- * `DataServer` implements by default). It implements each diagnostics method as
15
- * a readiness-gated pass-through — `await this.ensureConnected()`, then delegate
16
- * to `this.server` — so every adopter frontend gets the memory / state /
17
- * profiling / latency surface without hand-writing identical one-liner bodies.
18
- *
19
- * Extend this instead of {@link AbstractDataServiceFrontend} whenever the head's
20
- * `DataServer` keeps the default diagnostics registration; the `TServer` bound
21
- * carries `DataServerDiagnosticsProtocol`, so the delegates are type-checked
22
- * (no casts). A head that dropped the diagnostics methods via
23
- * `DataServerOptions.excludedMethods` should extend the plain base instead.
24
- */
25
- export declare abstract class AbstractDiagnosticsDataServiceFrontend<TServer extends {
26
- waitForReady(): Promise<void>;
27
- } & DataServerDiagnosticsProtocol, TClient extends object> extends AbstractDataServiceFrontend<TServer, TClient> implements DataServerDiagnosticsProtocol {
28
- dumpServerState(args: DumpServerStateArgs): Promise<string>;
29
- writeHeapSnapshot(args: WriteServerHeapSnapshotArgs): Promise<string>;
30
- dumpPodMemory(): Promise<string>;
31
- startProfiling(args: StartProfilingArgs): Promise<void>;
32
- stopProfiling(args: StopProfilingArgs): Promise<string>;
33
- getLatency(): Promise<LatencyReport>;
34
- }
35
- //# sourceMappingURL=diagnostics-data-service-frontend.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"diagnostics-data-service-frontend.d.ts","sourceRoot":"","sources":["../../src/browser/diagnostics-data-service-frontend.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAElF,OAAO,KAAK,EACT,6BAA6B,EAC7B,mBAAmB,EACnB,aAAa,EACb,kBAAkB,EAClB,iBAAiB,EACjB,2BAA2B,EAC7B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,2BAA2B,EAAE,MAAM,yBAAyB,CAAC;AAEtE;;;;;;;;;;;;;GAaG;AACH,8BAAsB,sCAAsC,CACzD,OAAO,SAAS;IAAE,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,GAAG,6BAA6B,EACjF,OAAO,SAAS,MAAM,CAEtB,SAAQ,2BAA2B,CAAC,OAAO,EAAE,OAAO,CACpD,YAAW,6BAA6B;IAElC,eAAe,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,MAAM,CAAC;IAK3D,iBAAiB,CAAC,IAAI,EAAE,2BAA2B,GAAG,OAAO,CAAC,MAAM,CAAC;IAKrE,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC;IAKhC,cAAc,CAAC,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;IAKvD,aAAa,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,CAAC;IAKvD,UAAU,IAAI,OAAO,CAAC,aAAa,CAAC;CAI5C"}