@eclipse-glsp/protocol 2.7.0-next.24 → 2.7.0-next.25

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 (44) hide show
  1. package/lib/action-protocol/model-saving.d.ts +140 -5
  2. package/lib/action-protocol/model-saving.d.ts.map +1 -1
  3. package/lib/action-protocol/model-saving.js +40 -1
  4. package/lib/action-protocol/model-saving.js.map +1 -1
  5. package/lib/action-protocol/viewport.d.ts +22 -1
  6. package/lib/action-protocol/viewport.d.ts.map +1 -1
  7. package/lib/action-protocol/viewport.js +17 -1
  8. package/lib/action-protocol/viewport.js.map +1 -1
  9. package/lib/client-server-protocol/base-glsp-client.d.ts +2 -2
  10. package/lib/client-server-protocol/base-glsp-client.d.ts.map +1 -1
  11. package/lib/client-server-protocol/base-glsp-client.js +3 -2
  12. package/lib/client-server-protocol/base-glsp-client.js.map +1 -1
  13. package/lib/client-server-protocol/glsp-client.d.ts +7 -2
  14. package/lib/client-server-protocol/glsp-client.d.ts.map +1 -1
  15. package/lib/client-server-protocol/glsp-client.js.map +1 -1
  16. package/lib/client-server-protocol/glsp-server.d.ts +18 -1
  17. package/lib/client-server-protocol/glsp-server.d.ts.map +1 -1
  18. package/lib/client-server-protocol/glsp-server.js +3 -2
  19. package/lib/client-server-protocol/glsp-server.js.map +1 -1
  20. package/lib/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.d.ts +1 -1
  21. package/lib/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.d.ts.map +1 -1
  22. package/lib/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.js +4 -3
  23. package/lib/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.js.map +1 -1
  24. package/lib/client-server-protocol/mcp.d.ts +225 -0
  25. package/lib/client-server-protocol/mcp.d.ts.map +1 -0
  26. package/lib/client-server-protocol/mcp.js +75 -0
  27. package/lib/client-server-protocol/mcp.js.map +1 -0
  28. package/lib/index.d.ts +1 -0
  29. package/lib/index.d.ts.map +1 -1
  30. package/lib/index.js +1 -0
  31. package/lib/index.js.map +1 -1
  32. package/package.json +2 -2
  33. package/src/action-protocol/model-saving.spec.ts +126 -1
  34. package/src/action-protocol/model-saving.ts +170 -6
  35. package/src/action-protocol/viewport.spec.ts +37 -1
  36. package/src/action-protocol/viewport.ts +33 -1
  37. package/src/client-server-protocol/base-glsp-client.spec.ts +21 -6
  38. package/src/client-server-protocol/base-glsp-client.ts +3 -2
  39. package/src/client-server-protocol/glsp-client.ts +7 -3
  40. package/src/client-server-protocol/glsp-server.ts +19 -1
  41. package/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.spec.ts +10 -1
  42. package/src/client-server-protocol/jsonrpc/base-jsonrpc-glsp-client.ts +4 -3
  43. package/src/client-server-protocol/mcp.ts +256 -0
  44. package/src/index.ts +1 -0
@@ -1,5 +1,5 @@
1
1
  /********************************************************************************
2
- * Copyright (c) 2021-2024 STMicroelectronics and others.
2
+ * Copyright (c) 2021-2026 STMicroelectronics and others.
3
3
  *
4
4
  * This program and the accompanying materials are made available under the
5
5
  * terms of the Eclipse Public License v. 2.0 which is available at
@@ -14,7 +14,7 @@
14
14
  * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15
15
  ********************************************************************************/
16
16
  import * as sprotty from 'sprotty-protocol/lib/actions';
17
- import { hasBooleanProp, hasStringProp } from '../utils/type-util';
17
+ import { ProposalString, hasBooleanProp, hasStringProp } from '../utils/type-util';
18
18
  import { Action, RequestAction, ResponseAction } from './base-protocol';
19
19
 
20
20
  /**
@@ -84,9 +84,12 @@ export namespace SetDirtyStateAction {
84
84
  }
85
85
 
86
86
  /**
87
- * A `RequestExportSvgAction` is sent by the client (or the server) to initiate the SVG export of the current diagram.
87
+ * A {@link RequestExportSvgAction} is sent by the client (or the server) to initiate the SVG export of the current diagram.
88
88
  * The handler of this action is expected to retrieve the diagram SVG and should send a {@link ExportSvgAction} as response.
89
89
  * Typically the {@link ExportSvgAction} is handled directly on client side.
90
+ *
91
+ * @deprecated Use the unified {@link RequestExportAction} (with `format: 'svg'`) and
92
+ * {@link ExportResultAction} pair instead.
90
93
  */
91
94
  export interface RequestExportSvgAction extends RequestAction<ExportSvgAction>, sprotty.RequestExportSvgAction {
92
95
  kind: typeof RequestExportSvgAction.KIND;
@@ -108,18 +111,21 @@ export namespace RequestExportSvgAction {
108
111
  }
109
112
  }
110
113
 
111
- /** Configuration options for the {@link RequestExportSvgAction */
114
+ /** Configuration options for the {@link RequestExportSvgAction} */
112
115
  export interface ExportSvgOptions extends sprotty.ExportSvgOptions {
113
116
  // If set to false applied diagram styles will not be copied to the exported SVG
114
117
  skipCopyStyles?: boolean;
115
118
  }
116
119
 
117
120
  /**
118
- * The client sends an `ExportSvgAction` to indicate that the diagram, which represents the current model state,
121
+ * The client sends an {@link ExportSvgAction} to indicate that the diagram, which represents the current model state,
119
122
  * should be exported in SVG format. The action only provides the diagram SVG as plain string. The expected result of executing
120
- * an `ExportSvgAction` is a new file in SVG-format on the underlying filesystem. However, other details like the target destination,
123
+ * an {@link ExportSvgAction} is a new file in SVG-format on the underlying filesystem. However, other details like the target destination,
121
124
  * concrete file name, file extension etc. are not specified in the protocol. So it is the responsibility of the action handler to
122
125
  * process this information accordingly and export the result to the underlying filesystem.
126
+ *
127
+ * @deprecated Use {@link ExportResultAction} as the response to a unified
128
+ * {@link RequestExportAction} instead.
123
129
  */
124
130
  export interface ExportSvgAction extends ResponseAction, sprotty.ExportSvgAction {
125
131
  kind: typeof ExportSvgAction.KIND;
@@ -143,3 +149,161 @@ export namespace ExportSvgAction {
143
149
  };
144
150
  }
145
151
  }
152
+
153
+ /**
154
+ * Format identifier for a diagram export. The package ships `'svg'` and `'png'`;
155
+ * adopters add formats by registering a `DiagramExporter` strategy keyed on any string.
156
+ */
157
+ export type ExportFormat = ProposalString<'svg' | 'png'>;
158
+
159
+ /** MIME type returned alongside an export. */
160
+ export type ExportMimeType = ProposalString<'image/svg+xml' | 'image/png'>;
161
+
162
+ /**
163
+ * Encoding of the bytes carried in {@link ExportResultAction.data}. The package ships
164
+ * `'text'` (UTF-8 markup, e.g. SVG) and `'base64'` (binary blobs, e.g. PNG); adopters
165
+ * registering a strategy with a different runtime declare their own encoding tag.
166
+ * Receivers must reject encodings they don't understand explicitly; silently treating an
167
+ * unknown value as one of the shipped ones corrupts payloads.
168
+ */
169
+ export type ExportEncoding = ProposalString<'text' | 'base64'>;
170
+
171
+ /**
172
+ * Options shared by every export strategy that renders through sprotty's SVG export pipeline.
173
+ * Today honoured by SVG output and the canvas-based PNG rasteriser; future strategies that
174
+ * also raster from SVG (PDF, PPTX, ...) would extend this interface for the same knob.
175
+ */
176
+ export interface SvgRenderOptions {
177
+ /**
178
+ * Skip the per-element style-copy step. The default copy walk is expensive on large
179
+ * diagrams; setting this flag skips it at the cost of losing CSS-driven styling in the
180
+ * exported output.
181
+ */
182
+ skipCopyStyles?: boolean;
183
+ }
184
+
185
+ /**
186
+ * SVG-specific options carried inside {@link RequestExportAction.formatOptions} for the SVG
187
+ * export strategy. Mirrors {@link ExportSvgOptions} so callers migrating from the legacy
188
+ * {@link RequestExportSvgAction} retain feature parity.
189
+ */
190
+ export interface SvgExportOptions extends SvgRenderOptions {}
191
+
192
+ /**
193
+ * PNG-specific options carried inside {@link RequestExportAction.formatOptions} for the PNG
194
+ * export strategy. All fields optional — strategies fall back to sensible defaults. Inherits
195
+ * `skipCopyStyles` from {@link SvgRenderOptions} since PNG rasterises from SVG.
196
+ */
197
+ export interface PngExportOptions extends SvgRenderOptions {
198
+ /** Output width in px. */
199
+ width?: number;
200
+ /** Output height in px. If omitted, height is derived to preserve the rendered aspect ratio. */
201
+ height?: number;
202
+ /** CSS colour painted as the canvas background before drawing the SVG. */
203
+ background?: string;
204
+ }
205
+
206
+ /**
207
+ * Generic, format-agnostic export request. Sent client-to-self for UI-driven export
208
+ * flows, or server-to-client for server-orchestrated flows (e.g. an MCP tool requesting
209
+ * a PNG snapshot of the active diagram). The expected response is an
210
+ * {@link ExportResultAction} carrying the rendered bytes.
211
+ *
212
+ * `formatOptions` is opaque to the protocol — typed as `unknown` so the format-specific
213
+ * overloads of {@link RequestExportAction.create} can narrow it to {@link SvgExportOptions},
214
+ * {@link PngExportOptions}, or an adopter's own shape without an index-signature dance. The
215
+ * `DiagramExporter` for the given `format` validates the value at the strategy boundary.
216
+ *
217
+ * Coexists with the legacy {@link RequestExportSvgAction} under strict separation:
218
+ * legacy kind (`requestExportSvg`) → legacy action ({@link ExportSvgAction}) only; new kind
219
+ * (`requestExport`) → new action ({@link ExportResultAction}) only; never crossed.
220
+ */
221
+ export interface RequestExportAction extends RequestAction<ExportResultAction> {
222
+ kind: typeof RequestExportAction.KIND;
223
+ format: ExportFormat;
224
+ formatOptions?: unknown;
225
+ }
226
+ export namespace RequestExportAction {
227
+ export const KIND = 'requestExport';
228
+
229
+ export function is(object: unknown): object is RequestExportAction {
230
+ return RequestAction.hasKind(object, KIND) && hasStringProp(object, 'format');
231
+ }
232
+
233
+ /** Typed overload for the shipped `'svg'` format — autocomplete on `formatOptions`. */
234
+ export function create(format: 'svg', options?: { formatOptions?: SvgExportOptions; requestId?: string }): RequestExportAction;
235
+ /** Typed overload for the shipped `'png'` format — autocomplete on `formatOptions`. */
236
+ export function create(format: 'png', options?: { formatOptions?: PngExportOptions; requestId?: string }): RequestExportAction;
237
+ /** General overload for adopter-registered formats; `formatOptions` is opaque. */
238
+ export function create(format: ExportFormat, options?: { formatOptions?: unknown; requestId?: string }): RequestExportAction;
239
+ export function create(format: ExportFormat, options: { formatOptions?: unknown; requestId?: string } = {}): RequestExportAction {
240
+ return {
241
+ kind: KIND,
242
+ format,
243
+ requestId: '',
244
+ ...options
245
+ };
246
+ }
247
+ }
248
+
249
+ /**
250
+ * Response to a {@link RequestExportAction} carrying the rendered diagram. Text-encoded
251
+ * payloads (e.g. SVG markup) ride in `data` directly; binary payloads (e.g. PNG) are
252
+ * base64-encoded with `encoding: 'base64'` so the action stays JSON-safe.
253
+ */
254
+ export interface ExportResultAction extends ResponseAction {
255
+ kind: typeof ExportResultAction.KIND;
256
+ /** Echoes the requested format. */
257
+ format: ExportFormat;
258
+ mimeType: ExportMimeType;
259
+ encoding: ExportEncoding;
260
+ /** SVG markup (`encoding: 'text'`) or base64-encoded bytes (`encoding: 'base64'`). */
261
+ data: string;
262
+ /** Echoes the request's `formatOptions` so receivers can correlate result fields back to the request. */
263
+ formatOptions?: unknown;
264
+ }
265
+ export namespace ExportResultAction {
266
+ export const KIND = 'exportResult';
267
+
268
+ export function is(object: unknown): object is ExportResultAction {
269
+ return (
270
+ Action.hasKind(object, KIND) &&
271
+ hasStringProp(object, 'format') &&
272
+ hasStringProp(object, 'data') &&
273
+ hasStringProp(object, 'mimeType') &&
274
+ hasStringProp(object, 'encoding')
275
+ );
276
+ }
277
+
278
+ /** Typed overload for the shipped `'svg'` format — typed echoed `formatOptions`. */
279
+ export function create(
280
+ format: 'svg',
281
+ data: string,
282
+ options: { mimeType: ExportMimeType; encoding: ExportEncoding; responseId?: string; formatOptions?: SvgExportOptions }
283
+ ): ExportResultAction;
284
+ /** Typed overload for the shipped `'png'` format — typed echoed `formatOptions`. */
285
+ export function create(
286
+ format: 'png',
287
+ data: string,
288
+ options: { mimeType: ExportMimeType; encoding: ExportEncoding; responseId?: string; formatOptions?: PngExportOptions }
289
+ ): ExportResultAction;
290
+ /** General overload for adopter-registered formats; echoed `formatOptions` is opaque. */
291
+ export function create(
292
+ format: ExportFormat,
293
+ data: string,
294
+ options: { mimeType: ExportMimeType; encoding: ExportEncoding; responseId?: string; formatOptions?: unknown }
295
+ ): ExportResultAction;
296
+ export function create(
297
+ format: ExportFormat,
298
+ data: string,
299
+ options: { mimeType: ExportMimeType; encoding: ExportEncoding; responseId?: string; formatOptions?: unknown }
300
+ ): ExportResultAction {
301
+ return {
302
+ kind: KIND,
303
+ format,
304
+ data,
305
+ responseId: '',
306
+ ...options
307
+ };
308
+ }
309
+ }
@@ -15,7 +15,7 @@
15
15
  ********************************************************************************/
16
16
 
17
17
  import { expect } from 'chai';
18
- import { CenterAction, FitToScreenAction, MoveViewportAction } from './viewport';
18
+ import { CenterAction, FitToScreenAction, MoveViewportAction, OriginViewportAction } from './viewport';
19
19
 
20
20
  describe('Viewport Actions', () => {
21
21
  describe('CenterAction', () => {
@@ -133,4 +133,40 @@ describe('Viewport Actions', () => {
133
133
  });
134
134
  });
135
135
  });
136
+
137
+ describe('OriginViewportAction', () => {
138
+ describe('is', () => {
139
+ it('should return true for an object having the correct type and a value for all required interface properties', () => {
140
+ const action: OriginViewportAction = {
141
+ kind: 'originViewport',
142
+ animate: true
143
+ };
144
+ expect(OriginViewportAction.is(action)).to.be.true;
145
+ });
146
+ it('should return false for `undefined`', () => {
147
+ expect(OriginViewportAction.is(undefined)).to.be.false;
148
+ });
149
+ it('should return false for an object that does not have all required interface properties', () => {
150
+ expect(OriginViewportAction.is({ kind: 'notTheRightOne' })).to.be.false;
151
+ });
152
+ });
153
+
154
+ describe('create', () => {
155
+ it('should return an object conforming to the interface with default values for the optional arguments', () => {
156
+ const expected: OriginViewportAction = {
157
+ kind: 'originViewport',
158
+ animate: true
159
+ };
160
+ expect(OriginViewportAction.create()).to.deep.equals(expected);
161
+ });
162
+ it('should return an object conforming to the interface with matching properties for the given optional arguments', () => {
163
+ const expected: OriginViewportAction = {
164
+ kind: 'originViewport',
165
+ animate: false
166
+ };
167
+ const { animate } = expected;
168
+ expect(OriginViewportAction.create({ animate })).to.deep.equals(expected);
169
+ });
170
+ });
171
+ });
136
172
  });
@@ -1,5 +1,5 @@
1
1
  /********************************************************************************
2
- * Copyright (c) 2021-2025 STMicroelectronics and others.
2
+ * Copyright (c) 2021-2026 STMicroelectronics and others.
3
3
  *
4
4
  * This program and the accompanying materials are made available under the
5
5
  * terms of the Eclipse Public License v. 2.0 which is available at
@@ -111,6 +111,38 @@ export namespace FitToScreenAction {
111
111
  }
112
112
  }
113
113
 
114
+ /**
115
+ * Resets the viewport to its origin (zoom 1, scroll 0). This is typically dispatched by the
116
+ * client (e.g., from the tool palette) but can also be sent by the server to reset the viewport
117
+ * remotely.
118
+ * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks
119
+ * and creating new `OriginViewportActions`.
120
+ */
121
+ export interface OriginViewportAction extends Action {
122
+ kind: typeof OriginViewportAction.KIND;
123
+
124
+ /**
125
+ * Indicate if the modification of the viewport should be realized with or without support of animations.
126
+ */
127
+ animate: boolean;
128
+ }
129
+
130
+ export namespace OriginViewportAction {
131
+ export const KIND = 'originViewport';
132
+
133
+ export function is(object: unknown): object is OriginViewportAction {
134
+ return Action.hasKind(object, KIND) && hasBooleanProp(object, 'animate');
135
+ }
136
+
137
+ export function create(options: { animate?: boolean } = {}): OriginViewportAction {
138
+ return {
139
+ kind: KIND,
140
+ animate: true,
141
+ ...options
142
+ };
143
+ }
144
+ }
145
+
114
146
  /**
115
147
  * Moves the diagram canvas.
116
148
  * The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks
@@ -220,20 +220,35 @@ describe('Node GLSP Client', () => {
220
220
  });
221
221
 
222
222
  describe('shutdownServer', () => {
223
- it('should fail if server is not configured', () => {
223
+ it('should fail if server is not configured', async () => {
224
224
  resetClient(false);
225
- expect(() => client.shutdownServer()).to.throw();
225
+ // `shutdownServer` is now async; guard failures surface as a rejected promise.
226
+ let rejection: unknown;
227
+ try {
228
+ await client.shutdownServer();
229
+ } catch (err) {
230
+ rejection = err;
231
+ }
232
+ expect(rejection).to.be.instanceOf(Error);
233
+ expect((rejection as Error).message).to.match(/not in 'Running' state/);
226
234
  expect(server.shutdown.called).to.be.false;
227
235
  });
228
- it('should fail if client is not running', () => {
236
+ it('should fail if client is not running', async () => {
229
237
  resetClient(false);
230
238
  client.configureServer(server);
231
- expect(() => client.shutdownServer()).to.throw();
239
+ let rejection: unknown;
240
+ try {
241
+ await client.shutdownServer();
242
+ } catch (err) {
243
+ rejection = err;
244
+ }
245
+ expect(rejection).to.be.instanceOf(Error);
246
+ expect((rejection as Error).message).to.match(/not in 'Running' state/);
232
247
  expect(server.shutdown.called).to.be.false;
233
248
  });
234
- it('should invoke the corresponding server method', () => {
249
+ it('should invoke the corresponding server method', async () => {
235
250
  resetClient();
236
- client.shutdownServer();
251
+ await client.shutdownServer();
237
252
  expect(server.shutdown.calledOnce).to.be.true;
238
253
  });
239
254
  });
@@ -1,5 +1,5 @@
1
1
  /********************************************************************************
2
- * Copyright (c) 2023-2024 EclipseSource and others.
2
+ * Copyright (c) 2023-2026 EclipseSource and others.
3
3
  *
4
4
  * This program and the accompanying materials are made available under the
5
5
  * terms of the Eclipse Public License v. 2.0 which is available at
@@ -153,7 +153,8 @@ export class BaseGLSPClient implements GLSPClient {
153
153
  return this.checkedServer.disposeClientSession(params);
154
154
  }
155
155
 
156
- shutdownServer(): void {
156
+ async shutdownServer(): Promise<void> {
157
+ // Fire-and-forget at the proxy boundary; subclasses with a flushable send override.
157
158
  this.checkedServer.shutdown();
158
159
  }
159
160
 
@@ -18,7 +18,7 @@ import { v4 as uuid } from 'uuid';
18
18
  import { ActionMessage } from '../action-protocol/base-protocol';
19
19
  import { Disposable } from '../utils/disposable';
20
20
  import { Event } from '../utils/event';
21
- import { AnyObject, hasStringProp } from '../utils/type-util';
21
+ import { AnyObject, MaybePromise, hasStringProp } from '../utils/type-util';
22
22
  import { DisposeClientSessionParameters, InitializeClientSessionParameters, InitializeParameters, InitializeResult } from './types';
23
23
 
24
24
  export class ApplicationIdProvider {
@@ -131,9 +131,13 @@ export interface GLSPClient {
131
131
  disposeClientSession(params: DisposeClientSessionParameters): Promise<void>;
132
132
 
133
133
  /**
134
- * Send a `shutdown` notification to the server.
134
+ * Send a `shutdown` notification to the server. The return type is {@link MaybePromise} so
135
+ * pre-existing synchronous adopter implementations remain valid; new implementations should
136
+ * return a `Promise<void>` that resolves once the notification has been flushed to the wire,
137
+ * otherwise callers that dispose the connection immediately afterwards may race the pending
138
+ * notification and the server never sees the shutdown signal.
135
139
  */
136
- shutdownServer(): void;
140
+ shutdownServer(): MaybePromise<void>;
137
141
 
138
142
  /**
139
143
  * Stops the client and disposes unknown resources. During the stop procedure the client is in the `Stopping` state and will
@@ -1,5 +1,5 @@
1
1
  /********************************************************************************
2
- * Copyright (c) 2022-2024 STMicroelectronics and others.
2
+ * Copyright (c) 2022-2026 STMicroelectronics and others.
3
3
  *
4
4
  * This program and the accompanying materials are made available under the
5
5
  * terms of the Eclipse Public License v. 2.0 which is available at
@@ -15,6 +15,7 @@
15
15
  ********************************************************************************/
16
16
 
17
17
  import { ActionMessage } from '../action-protocol/base-protocol';
18
+ import { MaybePromise } from '../utils/type-util';
18
19
  import { DisposeClientSessionParameters, InitializeClientSessionParameters, InitializeParameters, InitializeResult } from './types';
19
20
 
20
21
  /**
@@ -131,6 +132,23 @@ export interface GLSPServerListener {
131
132
  }
132
133
  export const GLSPServerListener = Symbol('GLSPServerListener');
133
134
 
135
+ /**
136
+ * Contribution interface for extending the server initialization process.
137
+ * Implementations can modify the {@link InitializeResult} based on the provided {@link InitializeParameters}.
138
+ */
139
+ export interface GLSPServerInitializer {
140
+ /**
141
+ * Handles the server initialization with the given parameters and allows to modify the initialization result.
142
+ *
143
+ * @param server The {@link GLSPServer} instance that is being initialized.
144
+ * @param params The {@link InitializeParameters} provided by the client.
145
+ * @param result The current {@link InitializeResult} that can be modified by the contribution.
146
+ * @returns The (possibly modified) {@link InitializeResult}.
147
+ */
148
+ initializeServer(server: GLSPServer, params: InitializeParameters, result: InitializeResult): MaybePromise<InitializeResult>;
149
+ }
150
+ export const GLSPServerInitializer = Symbol('GLSPServerInitializer');
151
+
134
152
  /**
135
153
  * Communication proxy interface used by the GLSP servers to send action messages to clients.
136
154
  * The default `JsonrpcClientProxy` used an underlying jsonrpc message connection for sending the action messages.
@@ -262,7 +262,16 @@ describe('Base JSON-RPC GLSP Client', () => {
262
262
  await resetClient(false);
263
263
  const stateChangeHandler = sinon.spy();
264
264
  client.onCurrentStateChanged(stateChangeHandler);
265
- expect(() => client.shutdownServer()).to.throw();
265
+ // `shutdownServer` is now async; the connection-state guard rejects the returned
266
+ // promise rather than throwing synchronously.
267
+ let rejection: unknown;
268
+ try {
269
+ await client.shutdownServer();
270
+ } catch (err) {
271
+ rejection = err;
272
+ }
273
+ expect(rejection).to.be.instanceOf(Error);
274
+ expect((rejection as Error).message).to.equal(JsonrpcGLSPClient.ClientNotReadyMsg);
266
275
  expect(connection.sendNotification.called).to.be.false;
267
276
  expect(stateChangeHandler.called).to.be.false;
268
277
  });
@@ -1,5 +1,5 @@
1
1
  /********************************************************************************
2
- * Copyright (c) 2019-2024 EclipseSource and others.
2
+ * Copyright (c) 2019-2026 EclipseSource and others.
3
3
  *
4
4
  * This program and the accompanying materials are made available under the
5
5
  * terms of the Eclipse Public License v. 2.0 which is available at
@@ -125,8 +125,9 @@ export class BaseJsonrpcGLSPClient implements GLSPClient {
125
125
  this.checkedConnection.sendNotification(JsonrpcGLSPClient.ActionMessageNotification, message);
126
126
  }
127
127
 
128
- shutdownServer(): void {
129
- this.checkedConnection.sendNotification(JsonrpcGLSPClient.ShutdownNotification);
128
+ async shutdownServer(): Promise<void> {
129
+ // Await the send so callers can dispose the connection without racing the wire flush.
130
+ await this.checkedConnection.sendNotification(JsonrpcGLSPClient.ShutdownNotification);
130
131
  }
131
132
 
132
133
  stop(): Promise<void> {