@eclipse-glsp/server 2.7.0-next.6 → 2.7.0-next.9

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 (61) hide show
  1. package/lib/common/actions/action-dispatcher.d.ts.map +1 -1
  2. package/lib/common/actions/action-dispatcher.js +6 -0
  3. package/lib/common/actions/action-dispatcher.js.map +1 -1
  4. package/lib/common/actions/global-action-provider.d.ts.map +1 -1
  5. package/lib/common/actions/global-action-provider.js +1 -1
  6. package/lib/common/actions/global-action-provider.js.map +1 -1
  7. package/lib/common/di/client-session-module.d.ts +8 -1
  8. package/lib/common/di/client-session-module.d.ts.map +1 -1
  9. package/lib/common/di/client-session-module.js +9 -1
  10. package/lib/common/di/client-session-module.js.map +1 -1
  11. package/lib/common/di/server-module.d.ts +3 -2
  12. package/lib/common/di/server-module.d.ts.map +1 -1
  13. package/lib/common/di/server-module.js +5 -1
  14. package/lib/common/di/server-module.js.map +1 -1
  15. package/lib/common/launch/jsonrpc-server-launcher.d.ts +1 -1
  16. package/lib/common/launch/jsonrpc-server-launcher.d.ts.map +1 -1
  17. package/lib/common/launch/jsonrpc-server-launcher.js +4 -1
  18. package/lib/common/launch/jsonrpc-server-launcher.js.map +1 -1
  19. package/lib/common/operations/create-operation-handler.d.ts +8 -2
  20. package/lib/common/operations/create-operation-handler.d.ts.map +1 -1
  21. package/lib/common/operations/create-operation-handler.js +16 -2
  22. package/lib/common/operations/create-operation-handler.js.map +1 -1
  23. package/lib/common/protocol/glsp-server.d.ts +7 -2
  24. package/lib/common/protocol/glsp-server.d.ts.map +1 -1
  25. package/lib/common/protocol/glsp-server.js +23 -2
  26. package/lib/common/protocol/glsp-server.js.map +1 -1
  27. package/lib/common/session/client-session-manager.d.ts +8 -1
  28. package/lib/common/session/client-session-manager.d.ts.map +1 -1
  29. package/lib/common/session/client-session-manager.js +4 -1
  30. package/lib/common/session/client-session-manager.js.map +1 -1
  31. package/package.json +3 -3
  32. package/src/common/actions/action-dispatcher.ts +6 -0
  33. package/src/common/actions/global-action-provider.ts +2 -3
  34. package/src/common/di/client-session-module.ts +9 -1
  35. package/src/common/di/server-module.ts +9 -2
  36. package/src/common/launch/jsonrpc-server-launcher.ts +4 -1
  37. package/src/common/operations/create-operation-handler.ts +16 -3
  38. package/src/common/protocol/glsp-server.ts +23 -1
  39. package/src/common/session/client-session-manager.ts +12 -1
  40. package/src/common/test/mock-util.ts +4 -0
  41. package/src/node/actions/action-dispatcher.spec.ts +39 -17
  42. package/lib/browser/di/browser-dispatch-context.d.ts +0 -39
  43. package/lib/browser/di/browser-dispatch-context.d.ts.map +0 -1
  44. package/lib/browser/di/browser-dispatch-context.js +0 -57
  45. package/lib/browser/di/browser-dispatch-context.js.map +0 -1
  46. package/lib/browser/di/sync-dispatch-context.d.ts +0 -39
  47. package/lib/browser/di/sync-dispatch-context.d.ts.map +0 -1
  48. package/lib/browser/di/sync-dispatch-context.js +0 -57
  49. package/lib/browser/di/sync-dispatch-context.js.map +0 -1
  50. package/lib/common/utils/action-channel.d.ts +0 -58
  51. package/lib/common/utils/action-channel.d.ts.map +0 -1
  52. package/lib/common/utils/action-channel.js +0 -99
  53. package/lib/common/utils/action-channel.js.map +0 -1
  54. package/lib/node/di/async-dispatch-context.d.ts +0 -26
  55. package/lib/node/di/async-dispatch-context.d.ts.map +0 -1
  56. package/lib/node/di/async-dispatch-context.js +0 -35
  57. package/lib/node/di/async-dispatch-context.js.map +0 -1
  58. package/lib/node/di/node-dispatch-context.d.ts +0 -26
  59. package/lib/node/di/node-dispatch-context.d.ts.map +0 -1
  60. package/lib/node/di/node-dispatch-context.js +0 -35
  61. package/lib/node/di/node-dispatch-context.js.map +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eclipse-glsp/server",
3
- "version": "2.7.0-next.6+2d275ee",
3
+ "version": "2.7.0-next.9+a58ea45",
4
4
  "description": "A js server framework for Eclipse GLSP",
5
5
  "keywords": [
6
6
  "eclipse",
@@ -58,7 +58,7 @@
58
58
  "watch": "tsc -w"
59
59
  },
60
60
  "dependencies": {
61
- "@eclipse-glsp/graph": "2.7.0-next.6+2d275ee",
61
+ "@eclipse-glsp/graph": "2.7.0-next.9+a58ea45",
62
62
  "@eclipse-glsp/protocol": "next",
63
63
  "@types/uuid": "8.3.1",
64
64
  "commander": "^8.3.0",
@@ -78,5 +78,5 @@
78
78
  "publishConfig": {
79
79
  "access": "public"
80
80
  },
81
- "gitHead": "2d275ee9c4ea080f5fd99171e5c760f53ad34f49"
81
+ "gitHead": "a58ea45de80be3d2fcf46e0934af141b213f6187"
82
82
  }
@@ -219,6 +219,12 @@ export class DefaultActionDispatcher implements ActionDispatcher, Disposable {
219
219
 
220
220
  const actionHandlers = this.actionHandlerRegistry.get(action.kind);
221
221
  if (!handledOnClient && actionHandlers.length === 0) {
222
+ // Stale response (e.g. late `RejectAction`) for a pending request that's already
223
+ // gone — protocol signal, not a handler invocation; drop silently.
224
+ if (ResponseAction.is(action)) {
225
+ this.logger.debug(`Stale response '${action.kind}' (responseId='${action.responseId}') has no handler; dropping.`);
226
+ return;
227
+ }
222
228
  throw new GLSPServerError(`No handler registered for action kind: ${action.kind}`);
223
229
  }
224
230
 
@@ -15,7 +15,7 @@
15
15
  ********************************************************************************/
16
16
  import { distinctAdd } from '@eclipse-glsp/protocol';
17
17
  import { Container, ContainerModule, inject, injectable } from 'inversify';
18
- import { createClientSessionModule } from '../di/client-session-module';
18
+ import { TEMPORARY_CLIENT_ID, createClientSessionModule } from '../di/client-session-module';
19
19
  import { DiagramModules, InjectionContainer } from '../di/service-identifiers';
20
20
  import { ClientSessionInitializer } from '../session/client-session-initializer';
21
21
  import { ActionHandlerRegistry } from './action-handler-registry';
@@ -50,8 +50,7 @@ export class DefaultGlobalActionProvider implements GlobalActionProvider {
50
50
  createDiagramContainer(serverContainer: Container, modules: ContainerModule[]): Container {
51
51
  const container = serverContainer.createChild();
52
52
  const clientSessionModule = createClientSessionModule({
53
- clientId: 'tempId',
54
-
53
+ clientId: TEMPORARY_CLIENT_ID,
55
54
  glspClient: { process: () => {} },
56
55
  clientActionKinds: []
57
56
  });
@@ -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
@@ -24,6 +24,14 @@ export interface ClientSessionModuleOptions {
24
24
  clientActionKinds: string[];
25
25
  }
26
26
 
27
+ /**
28
+ * Synthetic `ClientId` used by framework probes that load diagram modules onto a throwaway
29
+ * container — e.g. {@link DefaultGlobalActionProvider} scraping action-kind metadata at server
30
+ * startup. Adopters can detect this id in their `ClientSessionInitializer`s to suppress
31
+ * setup-time diagnostics that don't apply when no real GLSP client is connected.
32
+ */
33
+ export const TEMPORARY_CLIENT_ID = 'tempId';
34
+
27
35
  /**
28
36
  * Creates the DI module that binds client session specific configuration
29
37
  */
@@ -1,5 +1,5 @@
1
1
  /********************************************************************************
2
- * Copyright (c) 2022-2023 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
@@ -13,7 +13,7 @@
13
13
  *
14
14
  * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15
15
  ********************************************************************************/
16
- import { GLSPServer, GLSPServerListener } from '@eclipse-glsp/protocol';
16
+ import { GLSPServer, GLSPServerInitializer, GLSPServerListener } from '@eclipse-glsp/protocol';
17
17
  import { ContainerModule, interfaces } from 'inversify';
18
18
  import { DefaultGlobalActionProvider, GlobalActionProvider } from '../actions/global-action-provider';
19
19
  import { DefaultGLSPServer } from '../protocol/glsp-server';
@@ -77,6 +77,9 @@ export class ServerModule extends GLSPModule {
77
77
  this.configureMultiBinding(new MultiBinding<GLSPServerListener>(GLSPServerListener), binding =>
78
78
  this.configureGLSPServerListeners(binding)
79
79
  );
80
+ this.configureMultiBinding(new MultiBinding<GLSPServerInitializer>(GLSPServerInitializer), binding =>
81
+ this.configureGLSPServerInitializers(binding)
82
+ );
80
83
 
81
84
  applyBindingTarget(context, GlobalActionProvider, this.bindGlobalActionProvider()).inSingletonScope();
82
85
 
@@ -112,4 +115,8 @@ export class ServerModule extends GLSPModule {
112
115
  protected configureGLSPServerListeners(binding: MultiBinding<GLSPServerListener>): void {
113
116
  binding.add({ service: ClientSessionManager });
114
117
  }
118
+
119
+ protected configureGLSPServerInitializers(binding: MultiBinding<GLSPServerInitializer>): void {
120
+ // Can be overridden to add contributions
121
+ }
115
122
  }
@@ -1,5 +1,5 @@
1
1
  /********************************************************************************
2
- * Copyright (c) 2023 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
@@ -83,6 +83,9 @@ export abstract class JsonRpcGLSPServerLauncher<T> extends GLSPServerLauncher<T>
83
83
  serverInstance.clientConnection.onNotification(JsonrpcGLSPClient.ShutdownNotification, () =>
84
84
  this.disposeServerInstance(serverInstance)
85
85
  );
86
+ // A connection may be unceremoniously be closed (e.g., closing/reloading the browser) in which
87
+ // case the server must still be disposed
88
+ serverInstance.clientConnection.onClose(() => this.disposeServerInstance(serverInstance));
86
89
 
87
90
  this.logger.info('Starting GLSP server connection');
88
91
  }
@@ -1,5 +1,5 @@
1
1
  /********************************************************************************
2
- * Copyright (c) 2022-2023 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
@@ -38,6 +38,12 @@ export interface CreateEdgeOperationHandler extends OperationHandler {
38
38
  createCommand(operation: CreateEdgeOperation): MaybePromise<Command | undefined>;
39
39
  }
40
40
 
41
+ export namespace CreateEdgeOperationHandler {
42
+ export function is(object: unknown): object is CreateEdgeOperationHandler {
43
+ return CreateOperationHandler.is(object) && object.operationType === CreateEdgeOperation.KIND;
44
+ }
45
+ }
46
+
41
47
  export interface CreateNodeOperationHandler extends OperationHandler {
42
48
  readonly label: string;
43
49
  elementTypeIds: string[];
@@ -45,14 +51,19 @@ export interface CreateNodeOperationHandler extends OperationHandler {
45
51
  getTriggerActions(): TriggerNodeCreationAction[];
46
52
  createCommand(operation: CreateNodeOperation): MaybePromise<Command | undefined>;
47
53
  }
54
+
55
+ export namespace CreateNodeOperationHandler {
56
+ export function is(object: unknown): object is CreateNodeOperationHandler {
57
+ return CreateOperationHandler.is(object) && object.operationType === CreateNodeOperation.KIND;
58
+ }
59
+ }
60
+
48
61
  /**
49
62
  * A special {@link OperationHandler} that is responsible for the handling of a node or edge creation operation. Depending on its
50
63
  * operation type the triggered actions are {@link TriggerNodeCreationAction} or {@link TriggerEdgeCreationAction}s.
51
64
  */
52
65
  export type CreateOperationHandler = CreateNodeOperationHandler | CreateEdgeOperationHandler;
53
66
 
54
- export type CreateOperationKind = typeof CreateNodeOperation.KIND | typeof CreateEdgeOperation.KIND;
55
-
56
67
  export namespace CreateOperationHandler {
57
68
  export function is(object: unknown): object is CreateOperationHandler {
58
69
  return (
@@ -64,3 +75,5 @@ export namespace CreateOperationHandler {
64
75
  );
65
76
  }
66
77
  }
78
+
79
+ export type CreateOperationKind = typeof CreateNodeOperation.KIND | typeof CreateEdgeOperation.KIND;
@@ -19,6 +19,7 @@ import {
19
19
  DisposeClientSessionParameters,
20
20
  GLSPClientProxy,
21
21
  GLSPServer,
22
+ GLSPServerInitializer,
22
23
  GLSPServerListener,
23
24
  InitializeClientSessionParameters,
24
25
  InitializeParameters,
@@ -63,7 +64,10 @@ export class DefaultGLSPServer implements GLSPServer {
63
64
  protected clientSessions: Map<string, ClientSession>;
64
65
  protected serverListeners: GLSPServerListener[] = [];
65
66
 
66
- constructor(@multiInject(GLSPServerListener) @optional() serverListeners: GLSPServerListener[] = []) {
67
+ constructor(
68
+ @multiInject(GLSPServerListener) @optional() serverListeners: GLSPServerListener[] = [],
69
+ @multiInject(GLSPServerInitializer) @optional() protected initializers: GLSPServerInitializer[] = []
70
+ ) {
67
71
  this.clientSessions = new Map<string, ClientSession>();
68
72
  serverListeners.forEach(listener => this.addListener(listener));
69
73
  }
@@ -103,12 +107,30 @@ export class DefaultGLSPServer implements GLSPServer {
103
107
 
104
108
  let result = { protocolVersion: DefaultGLSPServer.PROTOCOL_VERSION, serverActions };
105
109
 
110
+ result = await this.initializeServer(params, result);
111
+ // keep for backwards compatibility
112
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
106
113
  result = await this.handleInitializeArgs(result, params.args);
107
114
  this.getListenersToNotify('serverInitialized').forEach((listener: GLSPServerListener) => listener.serverInitialized!(this));
108
115
  this.initializeResult = result;
109
116
  return result;
110
117
  }
111
118
 
119
+ protected async initializeServer(params: InitializeParameters, result: InitializeResult): Promise<InitializeResult> {
120
+ for (const initializer of this.initializers) {
121
+ try {
122
+ result = await initializer.initializeServer(this, params, result);
123
+ } catch (error: unknown) {
124
+ this.logger.error(`Error during server initialization from ${initializer.constructor.name}:`, error);
125
+ throw error;
126
+ }
127
+ }
128
+ return result;
129
+ }
130
+
131
+ /**
132
+ * @deprecated Register a `GLSPServerInitializer` instead.
133
+ */
112
134
  protected handleInitializeArgs(result: InitializeResult, args: Args | undefined): MaybePromise<InitializeResult> {
113
135
  return result;
114
136
  }
@@ -1,5 +1,5 @@
1
1
  /********************************************************************************
2
- * Copyright (c) 2022-2023 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
@@ -47,6 +47,13 @@ export interface ClientSessionManager {
47
47
  */
48
48
  getSession(clientSessionId: string): ClientSession | undefined;
49
49
 
50
+ /**
51
+ * Return all currently active {@link ClientSession}s.
52
+ *
53
+ * @returns An array of all currently active {@link ClientSession}s.
54
+ */
55
+ getSessions(): Array<ClientSession>;
56
+
50
57
  /**
51
58
  * Return all currently active {@link ClientSession}s for the given diagram type.
52
59
  *
@@ -129,6 +136,10 @@ export class DefaultClientSessionManager implements ClientSessionManager, GLSPSe
129
136
  return this.clientSessions.get(clientSessionId);
130
137
  }
131
138
 
139
+ getSessions(): ClientSession[] {
140
+ return Array.from(this.clientSessions.values());
141
+ }
142
+
132
143
  getSessionsByType(diagramType: string): ClientSession[] {
133
144
  return Array.from(this.clientSessions.values()).filter(session => session.diagramType === diagramType);
134
145
  }
@@ -169,6 +169,10 @@ export class StubClientSessionManager implements ClientSessionManager {
169
169
  return undefined;
170
170
  }
171
171
 
172
+ getSessions(): ClientSession[] {
173
+ return [];
174
+ }
175
+
172
176
  getSessionsByType(diagramType: string): ClientSession[] {
173
177
  return [];
174
178
  }
@@ -13,7 +13,7 @@
13
13
  *
14
14
  * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15
15
  ********************************************************************************/
16
- import { Action, Deferred, RequestAction, ResponseAction, UpdateModelAction } from '@eclipse-glsp/protocol';
16
+ import { Action, Deferred, RejectAction, RequestAction, ResponseAction, UpdateModelAction } from '@eclipse-glsp/protocol';
17
17
  import { expect } from 'chai';
18
18
  import { Container, ContainerModule } from 'inversify';
19
19
  import * as sinon from 'sinon';
@@ -95,6 +95,13 @@ describe('test DefaultActionDispatcher', () => {
95
95
  const spy_handler1_execute = sinon.stub(handler1, 'execute').returns([]);
96
96
  const spy_handler2_execute = sinon.stub(handler2, 'execute').returns([]);
97
97
  const spy_handler3_execute = sinon.stub(handler3, 'execute').returns([]);
98
+ // Stubs created at the describe-level survive sandbox.restore(), so call counts
99
+ // would accumulate across tests and break `.calledOnce` assertions.
100
+ beforeEach(() => {
101
+ spy_handler1_execute.resetHistory();
102
+ spy_handler2_execute.resetHistory();
103
+ spy_handler3_execute.resetHistory();
104
+ });
98
105
  const handlerMockImpl = (kind: string): ActionHandler[] => {
99
106
  switch (kind) {
100
107
  case action1:
@@ -129,9 +136,9 @@ describe('test DefaultActionDispatcher', () => {
129
136
  // Test execution
130
137
  await actionDispatcher.dispatchAll([{ kind: action1 }, { kind: action2 }, { kind: action3 }]);
131
138
  // Check if all handlers have been called
132
- expect(spy_handler1_execute.calledOnce);
133
- expect(spy_handler2_execute.calledOnce);
134
- expect(spy_handler3_execute.calledOnce);
139
+ expect(spy_handler1_execute.calledOnce).to.be.true;
140
+ expect(spy_handler2_execute.calledOnce).to.be.true;
141
+ expect(spy_handler3_execute.calledOnce).to.be.true;
135
142
  // Check if all handlers have been called in the right order
136
143
  sinon.assert.callOrder(spy_handler1_execute, spy_handler2_execute, spy_handler3_execute);
137
144
  });
@@ -156,9 +163,9 @@ describe('test DefaultActionDispatcher', () => {
156
163
  actionDispatcher.dispatch({ kind: action2 });
157
164
  await actionDispatcher.dispatch({ kind: action3 });
158
165
  // Check if all handlers have been called
159
- expect(spy_handler1_execute.calledOnce);
160
- expect(spy_handler2_execute.calledOnce);
161
- expect(spy_handler3_execute.calledOnce);
166
+ expect(spy_handler1_execute.calledOnce).to.be.true;
167
+ expect(spy_handler2_execute.calledOnce).to.be.true;
168
+ expect(spy_handler3_execute.calledOnce).to.be.true;
162
169
  // Check if all handlers have been called in the right order
163
170
  sinon.assert.callOrder(spy_handler1_execute, spy_handler2_execute, spy_handler3_execute);
164
171
  });
@@ -176,8 +183,8 @@ describe('test DefaultActionDispatcher', () => {
176
183
  const spy_handler2_execute = sinon.spy(handler2, 'execute');
177
184
  // Test execution
178
185
  await actionDispatcher.dispatch({ kind: action1 });
179
- expect(spy_handler1_execute.calledOnce);
180
- expect(spy_handler2_execute.calledOnce);
186
+ expect(spy_handler1_execute.calledOnce).to.be.true;
187
+ expect(spy_handler2_execute.calledOnce).to.be.true;
181
188
  sinon.assert.callOrder(spy_handler1_execute, spy_handler2_execute);
182
189
  });
183
190
  });
@@ -208,8 +215,8 @@ describe('test DefaultActionDispatcher', () => {
208
215
  // Add a delay so that the action dispatcher has time to dispatch the handler response
209
216
  await mock.delay(200);
210
217
  // Check if all handlers have been called
211
- expect(spy_requestHandler_execute.calledOnce);
212
- expect(spy_responseHandler_execute.calledOnce);
218
+ expect(spy_requestHandler_execute.calledOnce).to.be.true;
219
+ expect(spy_responseHandler_execute.calledOnce).to.be.true;
213
220
  });
214
221
 
215
222
  it('dispatch - multiple actions & multiple response', async () => {
@@ -249,10 +256,10 @@ describe('test DefaultActionDispatcher', () => {
249
256
  // Add a delay so that the action dispatcher has time to dispatch the handler response
250
257
  await mock.delay(100);
251
258
  // Check if all handlers have been called correctly
252
- expect(spy_requestHandler1_execute.calledOnce);
253
- expect(spy_requestHandler2_execute.calledOnce);
254
- expect(spy_responseHandler1_execute.calledOnce);
255
- expect(spy_responseHandler2_execute.calledThrice);
259
+ expect(spy_requestHandler1_execute.calledOnce).to.be.true;
260
+ expect(spy_requestHandler2_execute.calledOnce).to.be.true;
261
+ expect(spy_responseHandler1_execute.calledOnce).to.be.true;
262
+ expect(spy_responseHandler2_execute.calledThrice).to.be.true;
256
263
  // Check if all handlers have been called in the right order
257
264
  sinon.assert.callOrder(spy_requestHandler1_execute, spy_requestHandler2_execute);
258
265
  sinon.assert.callOrder(spy_responseHandler1_execute, spy_responseHandler2_execute);
@@ -286,10 +293,10 @@ describe('test DefaultActionDispatcher', () => {
286
293
  await actionDispatcher.dispatch({ kind: intermediateAction });
287
294
  expect(spy_postUpdateHandler_execute.called).to.be.false;
288
295
  await actionDispatcher.dispatch(updateModelAction);
289
- expect(spy_postUpdateHandler_execute.calledOnce);
296
+ expect(spy_postUpdateHandler_execute.calledOnce).to.be.true;
290
297
  // Check that action does not get dispatched again
291
298
  await actionDispatcher.dispatch(updateModelAction);
292
- expect(spy_postUpdateHandler_execute.calledOnce);
299
+ expect(spy_postUpdateHandler_execute.calledOnce).to.be.true;
293
300
  });
294
301
  });
295
302
 
@@ -502,6 +509,21 @@ describe('test DefaultActionDispatcher', () => {
502
509
  expect(lateResponse.responseId).to.equal('');
503
510
  });
504
511
 
512
+ it('dispatch - drops a stale ResponseAction with no matching pending request and no handler', async () => {
513
+ // Late `RejectAction`s (and any other ResponseAction that arrives after the matching
514
+ // pending request is gone) used to throw "No handler registered" because the dispatcher
515
+ // tried to route them as regular actions. They are protocol signals, not handler
516
+ // invocations — `doDispatch` swallows them with a debug log instead.
517
+ clientActionForwarderStub.shouldForwardToClient.returns(false);
518
+ clientActionForwarderStub.handle.returns(false);
519
+ registry_get_stub.callsFake(() => []);
520
+
521
+ const lateReject = RejectAction.create('late reject', { responseId: 'never-pending' });
522
+
523
+ // No throw, no client-forward — the dispatch resolves cleanly.
524
+ await actionDispatcher.dispatch(lateReject);
525
+ });
526
+
505
527
  it('request - resolves when response intercept happens from inside doDispatch', async () => {
506
528
  // A local handler for the request kind returns the matching response action directly.
507
529
  // The response is dispatched via dispatchResponses() -> dispatch() and intercepted
@@ -1,39 +0,0 @@
1
- /********************************************************************************
2
- * Copyright (c) 2026 EclipseSource and others.
3
- *
4
- * This program and the accompanying materials are made available under the
5
- * terms of the Eclipse Public License v. 2.0 which is available at
6
- * http://www.eclipse.org/legal/epl-2.0.
7
- *
8
- * This Source Code may also be made available under the following Secondary
9
- * Licenses when the conditions for such availability set forth in the Eclipse
10
- * Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
- * with the GNU Classpath Exception which is available at
12
- * https://www.gnu.org/software/classpath/license.html.
13
- *
14
- * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15
- ********************************************************************************/
16
- import { Action } from '@eclipse-glsp/protocol';
17
- import { ActionDispatchContext } from '../../common/actions/action-dispatcher';
18
- /**
19
- * Browser-compatible {@link ActionDispatchContext} that uses a simple flag instead of
20
- * `AsyncLocalStorage`. V8's `await` uses the spec's `PerformPromiseThen` which bypasses
21
- * patched `Promise.prototype.then`, breaking polyfill-based `AsyncLocalStorage` context
22
- * propagation across `await` boundaries. This causes deadlocks in the action dispatcher's
23
- * consumer loop.
24
- *
25
- * Since web workers are single-threaded with a single consumer loop, a flag-based approach
26
- * is sufficient. The flag is set before processing and cleared via `promise.finally()` so
27
- * it persists across the entire async chain.
28
- *
29
- * Unlike native `AsyncLocalStorage`, the flag cannot distinguish "within the handler's async
30
- * continuation" from "new event that arrived during an await gap." To preserve sequential
31
- * processing guarantees, {@link isInContext} always returns `false` for client-originated
32
- * actions, ensuring they are queued rather than dispatched inline.
33
- */
34
- export declare class BrowserDispatchContext implements ActionDispatchContext {
35
- protected active: boolean;
36
- run<R>(callback: () => R): R;
37
- isInContext(action: Action): boolean;
38
- }
39
- //# sourceMappingURL=browser-dispatch-context.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"browser-dispatch-context.d.ts","sourceRoot":"","sources":["../../../src/browser/di/browser-dispatch-context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;kFAckF;AAElF,OAAO,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAChD,OAAO,EAAE,qBAAqB,EAAE,MAAM,wCAAwC,CAAC;AAG/E;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,sBAAuB,YAAW,qBAAqB;IAChE,SAAS,CAAC,MAAM,UAAS;IAEzB,GAAG,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,GAAG,CAAC;IAa5B,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;CAGvC"}
@@ -1,57 +0,0 @@
1
- "use strict";
2
- /********************************************************************************
3
- * Copyright (c) 2026 EclipseSource and others.
4
- *
5
- * This program and the accompanying materials are made available under the
6
- * terms of the Eclipse Public License v. 2.0 which is available at
7
- * http://www.eclipse.org/legal/epl-2.0.
8
- *
9
- * This Source Code may also be made available under the following Secondary
10
- * Licenses when the conditions for such availability set forth in the Eclipse
11
- * Public License v. 2.0 are satisfied: GNU General Public License, version 2
12
- * with the GNU Classpath Exception which is available at
13
- * https://www.gnu.org/software/classpath/license.html.
14
- *
15
- * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
16
- ********************************************************************************/
17
- Object.defineProperty(exports, "__esModule", { value: true });
18
- exports.BrowserDispatchContext = void 0;
19
- const client_action_1 = require("../../common/protocol/client-action");
20
- /**
21
- * Browser-compatible {@link ActionDispatchContext} that uses a simple flag instead of
22
- * `AsyncLocalStorage`. V8's `await` uses the spec's `PerformPromiseThen` which bypasses
23
- * patched `Promise.prototype.then`, breaking polyfill-based `AsyncLocalStorage` context
24
- * propagation across `await` boundaries. This causes deadlocks in the action dispatcher's
25
- * consumer loop.
26
- *
27
- * Since web workers are single-threaded with a single consumer loop, a flag-based approach
28
- * is sufficient. The flag is set before processing and cleared via `promise.finally()` so
29
- * it persists across the entire async chain.
30
- *
31
- * Unlike native `AsyncLocalStorage`, the flag cannot distinguish "within the handler's async
32
- * continuation" from "new event that arrived during an await gap." To preserve sequential
33
- * processing guarantees, {@link isInContext} always returns `false` for client-originated
34
- * actions, ensuring they are queued rather than dispatched inline.
35
- */
36
- class BrowserDispatchContext {
37
- constructor() {
38
- this.active = false;
39
- }
40
- run(callback) {
41
- const prior = this.active;
42
- this.active = true;
43
- const result = callback();
44
- if (result instanceof Promise) {
45
- return result.finally(() => {
46
- this.active = prior;
47
- });
48
- }
49
- this.active = prior;
50
- return result;
51
- }
52
- isInContext(action) {
53
- return this.active && !client_action_1.ClientAction.is(action);
54
- }
55
- }
56
- exports.BrowserDispatchContext = BrowserDispatchContext;
57
- //# sourceMappingURL=browser-dispatch-context.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"browser-dispatch-context.js","sourceRoot":"","sources":["../../../src/browser/di/browser-dispatch-context.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;kFAckF;;;AAIlF,uEAAmE;AAEnE;;;;;;;;;;;;;;;GAeG;AACH,MAAa,sBAAsB;IAAnC;QACc,WAAM,GAAG,KAAK,CAAC;IAkB7B,CAAC;IAhBG,GAAG,CAAI,QAAiB;QACpB,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC;QAC1B,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;QACnB,MAAM,MAAM,GAAG,QAAQ,EAAE,CAAC;QAC1B,IAAI,MAAM,YAAY,OAAO,EAAE,CAAC;YAC5B,OAAO,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE;gBACvB,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;YACxB,CAAC,CAAiB,CAAC;QACvB,CAAC;QACD,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;QACpB,OAAO,MAAM,CAAC;IAClB,CAAC;IAED,WAAW,CAAC,MAAc;QACtB,OAAO,IAAI,CAAC,MAAM,IAAI,CAAC,4BAAY,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC;IACnD,CAAC;CACJ;AAnBD,wDAmBC"}
@@ -1,39 +0,0 @@
1
- /********************************************************************************
2
- * Copyright (c) 2026 EclipseSource and others.
3
- *
4
- * This program and the accompanying materials are made available under the
5
- * terms of the Eclipse Public License v. 2.0 which is available at
6
- * http://www.eclipse.org/legal/epl-2.0.
7
- *
8
- * This Source Code may also be made available under the following Secondary
9
- * Licenses when the conditions for such availability set forth in the Eclipse
10
- * Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
- * with the GNU Classpath Exception which is available at
12
- * https://www.gnu.org/software/classpath/license.html.
13
- *
14
- * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15
- ********************************************************************************/
16
- import { Action } from '@eclipse-glsp/protocol';
17
- import { ActionDispatchContext } from '../../common/actions/action-dispatcher';
18
- /**
19
- * Browser-compatible {@link ActionDispatchContext} that uses a simple flag instead of
20
- * `AsyncLocalStorage`. V8's `await` uses the spec's `PerformPromiseThen` which bypasses
21
- * patched `Promise.prototype.then`, breaking polyfill-based `AsyncLocalStorage` context
22
- * propagation across `await` boundaries. This causes deadlocks in the action dispatcher's
23
- * consumer loop.
24
- *
25
- * Since web workers are single-threaded with a single consumer loop, a flag-based approach
26
- * is sufficient. The flag is set before processing and cleared via `promise.finally()` so
27
- * it persists across the entire async chain.
28
- *
29
- * Unlike native `AsyncLocalStorage`, the flag cannot distinguish "within the handler's async
30
- * continuation" from "new event that arrived during an await gap." To preserve sequential
31
- * processing guarantees, {@link isInContext} always returns `false` for client-originated
32
- * actions, ensuring they are queued rather than dispatched inline.
33
- */
34
- export declare class SyncDispatchContext implements ActionDispatchContext {
35
- protected active: boolean;
36
- run<R>(callback: () => R): R;
37
- isInContext(action: Action): boolean;
38
- }
39
- //# sourceMappingURL=sync-dispatch-context.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"sync-dispatch-context.d.ts","sourceRoot":"","sources":["../../../src/browser/di/sync-dispatch-context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;kFAckF;AAElF,OAAO,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAChD,OAAO,EAAE,qBAAqB,EAAE,MAAM,wCAAwC,CAAC;AAG/E;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,mBAAoB,YAAW,qBAAqB;IAC7D,SAAS,CAAC,MAAM,UAAS;IAEzB,GAAG,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,GAAG,CAAC;IAa5B,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;CAGvC"}
@@ -1,57 +0,0 @@
1
- "use strict";
2
- /********************************************************************************
3
- * Copyright (c) 2026 EclipseSource and others.
4
- *
5
- * This program and the accompanying materials are made available under the
6
- * terms of the Eclipse Public License v. 2.0 which is available at
7
- * http://www.eclipse.org/legal/epl-2.0.
8
- *
9
- * This Source Code may also be made available under the following Secondary
10
- * Licenses when the conditions for such availability set forth in the Eclipse
11
- * Public License v. 2.0 are satisfied: GNU General Public License, version 2
12
- * with the GNU Classpath Exception which is available at
13
- * https://www.gnu.org/software/classpath/license.html.
14
- *
15
- * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
16
- ********************************************************************************/
17
- Object.defineProperty(exports, "__esModule", { value: true });
18
- exports.SyncDispatchContext = void 0;
19
- const client_action_1 = require("../../common/protocol/client-action");
20
- /**
21
- * Browser-compatible {@link ActionDispatchContext} that uses a simple flag instead of
22
- * `AsyncLocalStorage`. V8's `await` uses the spec's `PerformPromiseThen` which bypasses
23
- * patched `Promise.prototype.then`, breaking polyfill-based `AsyncLocalStorage` context
24
- * propagation across `await` boundaries. This causes deadlocks in the action dispatcher's
25
- * consumer loop.
26
- *
27
- * Since web workers are single-threaded with a single consumer loop, a flag-based approach
28
- * is sufficient. The flag is set before processing and cleared via `promise.finally()` so
29
- * it persists across the entire async chain.
30
- *
31
- * Unlike native `AsyncLocalStorage`, the flag cannot distinguish "within the handler's async
32
- * continuation" from "new event that arrived during an await gap." To preserve sequential
33
- * processing guarantees, {@link isInContext} always returns `false` for client-originated
34
- * actions, ensuring they are queued rather than dispatched inline.
35
- */
36
- class SyncDispatchContext {
37
- constructor() {
38
- this.active = false;
39
- }
40
- run(callback) {
41
- const prior = this.active;
42
- this.active = true;
43
- const result = callback();
44
- if (result instanceof Promise) {
45
- return result.finally(() => {
46
- this.active = prior;
47
- });
48
- }
49
- this.active = prior;
50
- return result;
51
- }
52
- isInContext(action) {
53
- return this.active && !client_action_1.ClientAction.is(action);
54
- }
55
- }
56
- exports.SyncDispatchContext = SyncDispatchContext;
57
- //# sourceMappingURL=sync-dispatch-context.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"sync-dispatch-context.js","sourceRoot":"","sources":["../../../src/browser/di/sync-dispatch-context.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;kFAckF;;;AAIlF,uEAAmE;AAEnE;;;;;;;;;;;;;;;GAeG;AACH,MAAa,mBAAmB;IAAhC;QACc,WAAM,GAAG,KAAK,CAAC;IAkB7B,CAAC;IAhBG,GAAG,CAAI,QAAiB;QACpB,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC;QAC1B,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;QACnB,MAAM,MAAM,GAAG,QAAQ,EAAE,CAAC;QAC1B,IAAI,MAAM,YAAY,OAAO,EAAE,CAAC;YAC5B,OAAO,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE;gBACvB,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;YACxB,CAAC,CAAiB,CAAC;QACvB,CAAC;QACD,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;QACpB,OAAO,MAAM,CAAC;IAClB,CAAC;IAED,WAAW,CAAC,MAAc;QACtB,OAAO,IAAI,CAAC,MAAM,IAAI,CAAC,4BAAY,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC;IACnD,CAAC;CACJ;AAnBD,kDAmBC"}