@eclipse-glsp/server 2.8.0-next.0 → 2.8.0-next.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/lib/browser/launch/worker-server-launcher.js +3 -5
  2. package/lib/browser/launch/worker-server-launcher.js.map +1 -1
  3. package/lib/common/actions/action-dispatcher.js +5 -8
  4. package/lib/common/actions/action-dispatcher.js.map +1 -1
  5. package/lib/common/actions/action-handler-registry.js +1 -1
  6. package/lib/common/actions/action-handler-registry.js.map +1 -1
  7. package/lib/common/actions/global-action-provider.js +1 -2
  8. package/lib/common/actions/global-action-provider.js.map +1 -1
  9. package/lib/common/command/command.js +1 -2
  10. package/lib/common/command/command.js.map +1 -1
  11. package/lib/common/command/recording-command.js +3 -6
  12. package/lib/common/command/recording-command.js.map +1 -1
  13. package/lib/common/di/diagram-module.js +5 -6
  14. package/lib/common/di/diagram-module.js.map +1 -1
  15. package/lib/common/di/glsp-module.js +1 -1
  16. package/lib/common/di/glsp-module.js.map +1 -1
  17. package/lib/common/di/server-module.js +1 -1
  18. package/lib/common/di/server-module.js.map +1 -1
  19. package/lib/common/features/directediting/context-edit-validator.js +3 -3
  20. package/lib/common/features/directediting/context-edit-validator.js.map +1 -1
  21. package/lib/common/features/directediting/label-edit-validator.js +1 -1
  22. package/lib/common/features/directediting/label-edit-validator.js.map +1 -1
  23. package/lib/common/features/layout/computed-bounds-action-handler.d.ts +16 -1
  24. package/lib/common/features/layout/computed-bounds-action-handler.d.ts.map +1 -1
  25. package/lib/common/features/layout/computed-bounds-action-handler.js +41 -4
  26. package/lib/common/features/layout/computed-bounds-action-handler.js.map +1 -1
  27. package/lib/common/features/layout/layout-operation-handler.js +1 -2
  28. package/lib/common/features/layout/layout-operation-handler.js.map +1 -1
  29. package/lib/common/features/model/gmodel-index.js +3 -5
  30. package/lib/common/features/model/gmodel-index.js.map +1 -1
  31. package/lib/common/features/model/gmodel-serializer.js +1 -2
  32. package/lib/common/features/model/gmodel-serializer.js.map +1 -1
  33. package/lib/common/features/model/model-submission-handler.d.ts +1 -2
  34. package/lib/common/features/model/model-submission-handler.d.ts.map +1 -1
  35. package/lib/common/features/model/model-submission-handler.js +3 -6
  36. package/lib/common/features/model/model-submission-handler.js.map +1 -1
  37. package/lib/common/features/model/request-model-action-handler.js +4 -6
  38. package/lib/common/features/model/request-model-action-handler.js.map +1 -1
  39. package/lib/common/features/navigation/navigation-target-resolution.js +1 -1
  40. package/lib/common/features/navigation/navigation-target-resolution.js.map +1 -1
  41. package/lib/common/features/progress/progress-service.d.ts.map +1 -1
  42. package/lib/common/features/progress/progress-service.js +1 -2
  43. package/lib/common/features/progress/progress-service.js.map +1 -1
  44. package/lib/common/features/validation/request-markers-handler.js +1 -2
  45. package/lib/common/features/validation/request-markers-handler.js.map +1 -1
  46. package/lib/common/gmodel/change-routing-points-operation-handler.js +1 -2
  47. package/lib/common/gmodel/change-routing-points-operation-handler.js.map +1 -1
  48. package/lib/common/gmodel/gmodel-create-node-operation-handler.js +3 -5
  49. package/lib/common/gmodel/gmodel-create-node-operation-handler.js.map +1 -1
  50. package/lib/common/gmodel/paste-operation-handler.d.ts +1 -1
  51. package/lib/common/gmodel/paste-operation-handler.d.ts.map +1 -1
  52. package/lib/common/gmodel/paste-operation-handler.js +8 -11
  53. package/lib/common/gmodel/paste-operation-handler.js.map +1 -1
  54. package/lib/common/launch/glsp-server-launcher.d.ts +8 -1
  55. package/lib/common/launch/glsp-server-launcher.d.ts.map +1 -1
  56. package/lib/common/launch/glsp-server-launcher.js +11 -1
  57. package/lib/common/launch/glsp-server-launcher.js.map +1 -1
  58. package/lib/common/launch/jsonrpc-server-launcher.d.ts +51 -2
  59. package/lib/common/launch/jsonrpc-server-launcher.d.ts.map +1 -1
  60. package/lib/common/launch/jsonrpc-server-launcher.js +82 -3
  61. package/lib/common/launch/jsonrpc-server-launcher.js.map +1 -1
  62. package/lib/common/operations/compound-operation-handler.js +1 -1
  63. package/lib/common/operations/compound-operation-handler.js.map +1 -1
  64. package/lib/common/operations/json-operation-handler.js +2 -3
  65. package/lib/common/operations/json-operation-handler.js.map +1 -1
  66. package/lib/common/protocol/glsp-server.js +6 -7
  67. package/lib/common/protocol/glsp-server.js.map +1 -1
  68. package/lib/common/session/client-session-manager.js +3 -5
  69. package/lib/common/session/client-session-manager.js.map +1 -1
  70. package/lib/common/utils/action-queue.js +2 -4
  71. package/lib/common/utils/action-queue.js.map +1 -1
  72. package/lib/common/utils/client-options-util.js +2 -2
  73. package/lib/common/utils/client-options-util.js.map +1 -1
  74. package/lib/common/utils/glsp-server-error.d.ts +2 -3
  75. package/lib/common/utils/glsp-server-error.d.ts.map +1 -1
  76. package/lib/common/utils/glsp-server-error.js +1 -1
  77. package/lib/common/utils/glsp-server-error.js.map +1 -1
  78. package/lib/common/utils/layout-util.d.ts +5 -5
  79. package/lib/common/utils/layout-util.d.ts.map +1 -1
  80. package/lib/common/utils/layout-util.js +17 -19
  81. package/lib/common/utils/layout-util.js.map +1 -1
  82. package/lib/common/utils/logger.js +1 -2
  83. package/lib/common/utils/logger.js.map +1 -1
  84. package/lib/node/abstract-json-model-storage.js +2 -4
  85. package/lib/node/abstract-json-model-storage.js.map +1 -1
  86. package/lib/node/launch/socket-server-launcher.d.ts +39 -2
  87. package/lib/node/launch/socket-server-launcher.d.ts.map +1 -1
  88. package/lib/node/launch/socket-server-launcher.js +50 -31
  89. package/lib/node/launch/socket-server-launcher.js.map +1 -1
  90. package/lib/node/launch/websocket-server-launcher.d.ts +36 -5
  91. package/lib/node/launch/websocket-server-launcher.d.ts.map +1 -1
  92. package/lib/node/launch/websocket-server-launcher.js +54 -13
  93. package/lib/node/launch/websocket-server-launcher.js.map +1 -1
  94. package/package.json +17 -21
  95. package/src/common/features/directediting/context-edit-validator.ts +2 -2
  96. package/src/common/features/layout/computed-bounds-action-handler.ts +52 -4
  97. package/src/common/features/model/model-submission-handler.ts +3 -2
  98. package/src/common/features/progress/progress-service.ts +2 -3
  99. package/src/common/gmodel/paste-operation-handler.ts +6 -6
  100. package/src/common/launch/glsp-server-launcher.ts +12 -1
  101. package/src/common/launch/jsonrpc-server-launcher.ts +89 -2
  102. package/src/common/utils/glsp-server-error.ts +3 -5
  103. package/src/common/utils/layout-util.ts +13 -12
  104. package/src/node/launch/socket-server-launcher.ts +54 -31
  105. package/src/node/launch/websocket-server-launcher.ts +72 -14
  106. package/src/browser/di/browser-action-dispatch-scope.spec.ts +0 -75
  107. package/src/common/actions/action-handler-registry.spec.ts +0 -52
  108. package/src/common/actions/global-action-provider.spec.ts +0 -68
  109. package/src/common/command/command-stack.spec.ts +0 -239
  110. package/src/common/command/command.spec.ts +0 -92
  111. package/src/common/command/recording-command.spec.ts +0 -86
  112. package/src/common/di/binding-target.spec.ts +0 -147
  113. package/src/common/di/multi-bindings.spec.ts +0 -133
  114. package/src/common/features/contextactions/context-actions-provider-registry.spec.ts +0 -69
  115. package/src/common/features/contextactions/request-context-actions-handler.spec.ts +0 -75
  116. package/src/common/features/contextactions/tool-palette-item-provider.spec.ts +0 -46
  117. package/src/common/features/directediting/context-edit-validator-registry.spec.ts +0 -54
  118. package/src/common/features/directediting/request-edit-validation-handler.spec.ts +0 -131
  119. package/src/common/features/model/gmodel-serializer.spec.ts +0 -141
  120. package/src/common/features/type-hints/request-type-hints-action-handler.spec.ts +0 -59
  121. package/src/common/operations/operation-handler-registry.spec.ts +0 -50
  122. package/src/common/protocol/glsp-server.spec.ts +0 -154
  123. package/src/common/session/client-session-factory.spec.ts +0 -60
  124. package/src/common/session/client-session-manager.spec.ts +0 -83
  125. package/src/common/test/mock-util.ts +0 -281
  126. package/src/common/utils/action-queue.spec.ts +0 -133
  127. package/src/common/utils/promise-queue.spec.ts +0 -142
  128. package/src/common/utils/registry.spec.ts +0 -225
  129. package/src/node/actions/action-dispatcher.spec.ts +0 -678
  130. package/src/node/launch/cli-parser.spec.ts +0 -61
  131. package/src/node/launch/socket-cli-parser.spec.ts +0 -73
  132. package/src/node/launch/socket-server-launcher.spec.ts +0 -74
@@ -1,678 +0,0 @@
1
- /********************************************************************************
2
- * Copyright (c) 2022-2026 STMicroelectronics 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, Deferred, RejectAction, RequestAction, ResponseAction, UpdateModelAction } from '@eclipse-glsp/protocol';
17
- import { expect } from 'chai';
18
- import { Container, ContainerModule } from 'inversify';
19
- import * as sinon from 'sinon';
20
- import { ActionDispatchScope, DefaultActionDispatcher } from '../../common/actions/action-dispatcher';
21
- import { ActionHandler } from '../../common/actions/action-handler';
22
- import { ActionHandlerRegistry } from '../../common/actions/action-handler-registry';
23
- import { ClientActionForwarder } from '../../common/actions/client-action-handler';
24
- import { ClientActionKinds, ClientId } from '../../common/di/service-identifiers';
25
- import { ClientSessionManager } from '../../common/session/client-session-manager';
26
- import * as mock from '../../common/test/mock-util';
27
- import { Logger } from '../../common/utils/logger';
28
- import { NodeActionDispatchScope } from '../di/node-action-dispatch-scope';
29
-
30
- function waitSync(timeInMillis: number): void {
31
- const start = Date.now();
32
- let now = start;
33
- while (now - start < timeInMillis) {
34
- now = Date.now();
35
- }
36
- }
37
-
38
- describe('test DefaultActionDispatcher', () => {
39
- const container = new Container();
40
- const clientId = 'myClientId';
41
- const actionHandlerRegistry = new ActionHandlerRegistry();
42
- let registry_get_stub: sinon.SinonStub<[string], ActionHandler[]>;
43
- const sandbox = sinon.createSandbox();
44
-
45
- const clientActionForwarderStub = sinon.createStubInstance(ClientActionForwarder);
46
-
47
- container.load(
48
- new ContainerModule(bind => {
49
- bind(Logger).toConstantValue(new mock.StubLogger());
50
- bind(ClientSessionManager).toConstantValue(new mock.StubClientSessionManager());
51
- bind(ClientId).toConstantValue(clientId);
52
- bind(ActionHandlerRegistry).toConstantValue(actionHandlerRegistry);
53
- bind(ClientActionKinds).toConstantValue(new Set(['response', 'response1', 'response2']));
54
- bind(ClientActionForwarder).toConstantValue(clientActionForwarderStub);
55
- bind(ActionDispatchScope).to(NodeActionDispatchScope);
56
- })
57
- );
58
- const actionDispatcher = container.resolve(DefaultActionDispatcher);
59
-
60
- beforeEach(() => {
61
- registry_get_stub = sandbox.stub(actionHandlerRegistry, 'get');
62
- });
63
-
64
- afterEach(() => {
65
- sandbox.restore();
66
- });
67
-
68
- describe('test with one-way actions (no response actions)', () => {
69
- it('dispatch- unhandled action', async () => {
70
- mock.expectToThrowAsync(() => actionDispatcher.dispatch({ kind: 'unhandled' }));
71
- });
72
-
73
- it('dispatch - one action', async () => {
74
- // Mock setup
75
- const action = 'action';
76
- const handler = new mock.StubActionHandler([action]);
77
- const getHandler = (kind: string): ActionHandler[] => (kind === action ? [handler] : []);
78
- registry_get_stub.callsFake(getHandler);
79
- const spy_handler_execute = sinon.spy(handler, 'execute');
80
- // Test execution
81
- await actionDispatcher.dispatch({ kind: action });
82
- expect(spy_handler_execute.calledOnce).true;
83
- });
84
-
85
- describe('test multi dispatch with single-handled actions', () => {
86
- // Mock Setup
87
- const action1 = 'a1';
88
- const action2 = 'a2';
89
- const action3 = 'a3';
90
-
91
- const handler1 = new mock.StubActionHandler([action1]);
92
- const handler2 = new mock.StubActionHandler([action2]);
93
- const handler3 = new mock.StubActionHandler([action3]);
94
-
95
- const spy_handler1_execute = sinon.stub(handler1, 'execute').returns([]);
96
- const spy_handler2_execute = sinon.stub(handler2, 'execute').returns([]);
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
- });
105
- const handlerMockImpl = (kind: string): ActionHandler[] => {
106
- switch (kind) {
107
- case action1:
108
- return [handler1];
109
- case action2:
110
- return [handler2];
111
- case action3:
112
- return [handler3];
113
- default:
114
- return [];
115
- }
116
- };
117
-
118
- it('dispatch - multiple actions', async () => {
119
- // Mock setup
120
- registry_get_stub.callsFake(handlerMockImpl);
121
- // Test execution
122
- actionDispatcher.dispatch({ kind: action1 });
123
- actionDispatcher.dispatch({ kind: action2 });
124
- await actionDispatcher.dispatch({ kind: action3 });
125
- // Check if all handlers have been called
126
- expect(spy_handler1_execute.called).true;
127
- expect(spy_handler2_execute.called).true;
128
- expect(spy_handler3_execute.called).true;
129
- // Check if all handlers have been called in the right order
130
- sinon.assert.callOrder(spy_handler1_execute, spy_handler2_execute, spy_handler3_execute);
131
- });
132
-
133
- it('dispatchAll- multiple actions', async () => {
134
- // Mock setup
135
- registry_get_stub.callsFake(handlerMockImpl);
136
- // Test execution
137
- await actionDispatcher.dispatchAll([{ kind: action1 }, { kind: action2 }, { kind: action3 }]);
138
- // Check if all handlers have been called
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;
142
- // Check if all handlers have been called in the right order
143
- sinon.assert.callOrder(spy_handler1_execute, spy_handler2_execute, spy_handler3_execute);
144
- });
145
-
146
- it('dispatch - multiple actions (racing execution times)', async () => {
147
- // Mock setup
148
- registry_get_stub.callsFake(handlerMockImpl);
149
- spy_handler1_execute.callsFake((_action: Action) => {
150
- waitSync(500);
151
- return [];
152
- });
153
- spy_handler2_execute.callsFake((_action: Action) => {
154
- waitSync(200);
155
- return [];
156
- });
157
- spy_handler3_execute.callsFake((_action: Action) => {
158
- waitSync(100);
159
- return [];
160
- });
161
- // Test execution
162
- actionDispatcher.dispatch({ kind: action1 });
163
- actionDispatcher.dispatch({ kind: action2 });
164
- await actionDispatcher.dispatch({ kind: action3 });
165
- // Check if all handlers have been called
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;
169
- // Check if all handlers have been called in the right order
170
- sinon.assert.callOrder(spy_handler1_execute, spy_handler2_execute, spy_handler3_execute);
171
- });
172
- });
173
-
174
- it('dispatch- one action & multiple handlers', async () => {
175
- // Mock setup
176
- const action1 = 'a1';
177
-
178
- const handler1 = new mock.StubActionHandler([action1]);
179
- const handler2 = new mock.StubActionHandler([action1]);
180
-
181
- registry_get_stub.callsFake((kind: string) => (kind === action1 ? [handler1, handler2] : []));
182
- const spy_handler1_execute = sinon.spy(handler1, 'execute');
183
- const spy_handler2_execute = sinon.spy(handler2, 'execute');
184
- // Test execution
185
- await actionDispatcher.dispatch({ kind: action1 });
186
- expect(spy_handler1_execute.calledOnce).to.be.true;
187
- expect(spy_handler2_execute.calledOnce).to.be.true;
188
- sinon.assert.callOrder(spy_handler1_execute, spy_handler2_execute);
189
- });
190
- });
191
-
192
- describe('test with handler response actions ', () => {
193
- it('dispatch - one action & one handler response action', async () => {
194
- // Mock setup
195
- const request = 'request';
196
- const response = 'response';
197
-
198
- const requestHandler = new mock.StubActionHandler([request]);
199
- const responseHandler = new mock.StubActionHandler([response]);
200
-
201
- const spy_requestHandler_execute = sinon.stub(requestHandler, 'execute').returns([{ kind: response }]);
202
- const spy_responseHandler_execute = sinon.spy(responseHandler, 'execute');
203
- registry_get_stub.callsFake((kind: string) => {
204
- switch (kind) {
205
- case request:
206
- return [requestHandler];
207
- case response:
208
- return [responseHandler];
209
- default:
210
- return [];
211
- }
212
- });
213
- // Test execution
214
- await actionDispatcher.dispatch({ kind: request });
215
- // Add a delay so that the action dispatcher has time to dispatch the handler response
216
- await mock.delay(200);
217
- // Check if all handlers have been called
218
- expect(spy_requestHandler_execute.calledOnce).to.be.true;
219
- expect(spy_responseHandler_execute.calledOnce).to.be.true;
220
- });
221
-
222
- it('dispatch - multiple actions & multiple response', async () => {
223
- // Mock setup
224
- const request1 = 'request1';
225
- const request2 = 'request2';
226
- const response1 = 'response1';
227
- const response2 = 'response2';
228
-
229
- const responseHandler1 = new mock.StubActionHandler([response1]);
230
- const responseHandler2 = new mock.StubActionHandler([response2]);
231
- const requestHandler1 = new mock.StubActionHandler([request1]);
232
- const requestHandler2 = new mock.StubActionHandler([request2]);
233
-
234
- const spy_requestHandler1_execute = sinon.stub(requestHandler1, 'execute').returns([{ kind: response1 }, { kind: response2 }]);
235
- const spy_requestHandler2_execute = sinon.stub(requestHandler2, 'execute').returns([{ kind: response2 }]);
236
- const spy_responseHandler1_execute = sinon.spy(responseHandler1, 'execute');
237
- const spy_responseHandler2_execute = sinon.spy(responseHandler2, 'execute');
238
- registry_get_stub.callsFake((kind: string) => {
239
- switch (kind) {
240
- case request1:
241
- return [requestHandler1];
242
- case request2:
243
- return [requestHandler2];
244
- case response1:
245
- return [responseHandler1, responseHandler2];
246
- case response2:
247
- return [responseHandler2];
248
- default:
249
- return [];
250
- }
251
- });
252
- // Test execution
253
- actionDispatcher.dispatch({ kind: request1 });
254
- await actionDispatcher.dispatch({ kind: request2 });
255
-
256
- // Add a delay so that the action dispatcher has time to dispatch the handler response
257
- await mock.delay(100);
258
- // Check if all handlers have been called correctly
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;
263
- // Check if all handlers have been called in the right order
264
- sinon.assert.callOrder(spy_requestHandler1_execute, spy_requestHandler2_execute);
265
- sinon.assert.callOrder(spy_responseHandler1_execute, spy_responseHandler2_execute);
266
- });
267
- });
268
-
269
- describe('test dispatch after next update', () => {
270
- it('dispatchAfterNextUpdate', async () => {
271
- // Mock setup
272
- const updateModelAction = UpdateModelAction.create({ id: 'newRoot', type: 'myType' });
273
- const intermediateAction = 'intermediate';
274
- const postUpdateAction = 'postUpdate';
275
- const handler = new mock.StubActionHandler([updateModelAction.kind, intermediateAction]);
276
- const postUpdateHandler = new mock.StubActionHandler([postUpdateAction]);
277
-
278
- const getHandler = (kind: string): ActionHandler[] => {
279
- if (kind === updateModelAction.kind || kind === intermediateAction) {
280
- return [handler];
281
- } else if (kind === postUpdateAction) {
282
- return [postUpdateHandler];
283
- }
284
-
285
- return [];
286
- };
287
- registry_get_stub.callsFake(getHandler);
288
- const spy_postUpdateHandler_execute = sinon.spy(postUpdateHandler, 'execute');
289
-
290
- // Test execution
291
- actionDispatcher.dispatchAfterNextUpdate({ kind: postUpdateAction });
292
- expect(spy_postUpdateHandler_execute.called).to.be.false;
293
- await actionDispatcher.dispatch({ kind: intermediateAction });
294
- expect(spy_postUpdateHandler_execute.called).to.be.false;
295
- await actionDispatcher.dispatch(updateModelAction);
296
- expect(spy_postUpdateHandler_execute.calledOnce).to.be.true;
297
- // Check that action does not get dispatched again
298
- await actionDispatcher.dispatch(updateModelAction);
299
- expect(spy_postUpdateHandler_execute.calledOnce).to.be.true;
300
- });
301
- });
302
-
303
- describe('test request/response', () => {
304
- it('request - resolves when matching response is dispatched', async () => {
305
- const requestAction: RequestAction<ResponseAction> = {
306
- kind: 'testRequest',
307
- requestId: 'req_1'
308
- };
309
- const responseAction: ResponseAction = {
310
- kind: 'testResponse',
311
- responseId: 'req_1'
312
- };
313
-
314
- // Configure forwarder: testRequest is forwarded to the client
315
- clientActionForwarderStub.shouldForwardToClient.callsFake(action => action.kind === 'testRequest');
316
- clientActionForwarderStub.handle.callsFake(action => action.kind === 'testRequest');
317
- registry_get_stub.callsFake(() => []);
318
-
319
- const responsePromise = actionDispatcher.request(requestAction);
320
- await actionDispatcher.dispatch(responseAction);
321
-
322
- const result = await responsePromise;
323
- expect(result.responseId).to.equal('req_1');
324
- });
325
-
326
- it('request - response bypasses queue even when queue is busy', async () => {
327
- const requestAction: RequestAction<ResponseAction> = {
328
- kind: 'testRequest',
329
- requestId: 'req_deadlock'
330
- };
331
- const responseAction: ResponseAction = {
332
- kind: 'testResponse',
333
- responseId: 'req_deadlock'
334
- };
335
-
336
- clientActionForwarderStub.shouldForwardToClient.callsFake(action => action.kind === 'testRequest');
337
- clientActionForwarderStub.handle.callsFake(action => action.kind === 'testRequest');
338
-
339
- const handlerRunning = new Deferred<void>();
340
-
341
- const slowAction = 'slowAction';
342
- const slowHandler = new mock.StubActionHandler([slowAction]);
343
- slowHandler.execute = async () => {
344
- const resultPromise = actionDispatcher.request(requestAction);
345
- handlerRunning.resolve();
346
- const result = await resultPromise;
347
- expect(result.responseId).to.equal('req_deadlock');
348
- return [];
349
- };
350
- registry_get_stub.callsFake((kind: string) => (kind === slowAction ? [slowHandler] : []));
351
-
352
- const dispatchPromise = actionDispatcher.dispatch({ kind: slowAction });
353
- await handlerRunning.promise;
354
-
355
- // Response must resolve even though the queue is busy
356
- await actionDispatcher.dispatch(responseAction);
357
- await dispatchPromise;
358
- });
359
-
360
- it('request - resolves for locally handled request (server→server)', async () => {
361
- const localRequestKind = 'localRequest';
362
- const localResponseKind = 'localResponse';
363
-
364
- const handler = new mock.StubActionHandler([localRequestKind]);
365
- sinon.stub(handler, 'execute').callsFake(() => {
366
- const response: ResponseAction = { kind: localResponseKind, responseId: '' };
367
- return [response];
368
- });
369
- registry_get_stub.callsFake((kind: string) => (kind === localRequestKind ? [handler] : []));
370
-
371
- const requestAction: RequestAction<ResponseAction> = {
372
- kind: localRequestKind,
373
- requestId: ''
374
- };
375
-
376
- const result = await actionDispatcher.request(requestAction);
377
- expect(result).to.exist;
378
- expect(result.responseId).to.equal(requestAction.requestId);
379
- });
380
-
381
- it('request - resolves for locally handled request called from inside a handler', async () => {
382
- const innerRequestKind = 'innerRequest';
383
- const innerResponseKind = 'innerResponse';
384
- const outerActionKind = 'outerAction';
385
-
386
- const innerHandler = new mock.StubActionHandler([innerRequestKind]);
387
- sinon.stub(innerHandler, 'execute').callsFake(() => {
388
- const response: ResponseAction = { kind: innerResponseKind, responseId: '' };
389
- return [response];
390
- });
391
-
392
- const outerHandler = new mock.StubActionHandler([outerActionKind]);
393
- outerHandler.execute = async () => {
394
- const innerRequest: RequestAction<ResponseAction> = {
395
- kind: innerRequestKind,
396
- requestId: ''
397
- };
398
- const result = await actionDispatcher.request(innerRequest);
399
- expect(result).to.exist;
400
- return [];
401
- };
402
-
403
- registry_get_stub.callsFake((kind: string) => {
404
- if (kind === outerActionKind) return [outerHandler];
405
- if (kind === innerRequestKind) return [innerHandler];
406
- return [];
407
- });
408
-
409
- // This must not deadlock
410
- await actionDispatcher.dispatch({ kind: outerActionKind });
411
- });
412
-
413
- it('requestUntil - rejects on timeout when rejectOnTimeout is true', async () => {
414
- const requestAction: RequestAction<ResponseAction> = {
415
- kind: 'testRequest',
416
- requestId: 'req_hard'
417
- };
418
-
419
- clientActionForwarderStub.shouldForwardToClient.callsFake(action => action.kind === 'testRequest');
420
- clientActionForwarderStub.handle.callsFake(action => action.kind === 'testRequest');
421
- registry_get_stub.callsFake(() => []);
422
-
423
- try {
424
- await actionDispatcher.requestUntil(requestAction, 100, true);
425
- expect.fail('Should have thrown');
426
- } catch (error: unknown) {
427
- expect((error as Error).message).to.include('timed out');
428
- }
429
- });
430
-
431
- it('requestUntil - resolves undefined on timeout when rejectOnTimeout is false', async () => {
432
- const requestAction: RequestAction<ResponseAction> = {
433
- kind: 'testRequest',
434
- requestId: 'req_soft'
435
- };
436
-
437
- clientActionForwarderStub.shouldForwardToClient.callsFake(action => action.kind === 'testRequest');
438
- clientActionForwarderStub.handle.callsFake(action => action.kind === 'testRequest');
439
- registry_get_stub.callsFake(() => []);
440
-
441
- const result = await actionDispatcher.requestUntil(requestAction, 100, false);
442
- expect(result).to.be.undefined;
443
- });
444
-
445
- it('request - auto-generates requestId when empty', async () => {
446
- const requestAction: RequestAction<ResponseAction> = {
447
- kind: 'testRequest',
448
- requestId: ''
449
- };
450
-
451
- clientActionForwarderStub.shouldForwardToClient.callsFake(action => action.kind === 'testRequest');
452
- clientActionForwarderStub.handle.callsFake(action => action.kind === 'testRequest');
453
- registry_get_stub.callsFake(() => []);
454
-
455
- const responsePromise = actionDispatcher.request(requestAction);
456
- expect(requestAction.requestId).to.match(/^server_.*_\d+$/);
457
-
458
- await actionDispatcher.dispatch({
459
- kind: 'testResponse',
460
- responseId: requestAction.requestId
461
- } as ResponseAction);
462
-
463
- const result = await responsePromise;
464
- expect(result).to.exist;
465
- });
466
-
467
- it('request - rejects when dispatch fails (no handler, not a client action)', async () => {
468
- const requestAction: RequestAction<ResponseAction> = {
469
- kind: 'unknownRequest',
470
- requestId: 'req_fail'
471
- };
472
-
473
- // NOT forwarded to client, no handler registered → dispatch throws
474
- clientActionForwarderStub.shouldForwardToClient.returns(false);
475
- clientActionForwarderStub.handle.returns(false);
476
- registry_get_stub.callsFake(() => []);
477
-
478
- try {
479
- await actionDispatcher.request(requestAction);
480
- expect.fail('Should have thrown');
481
- } catch (error: unknown) {
482
- expect((error as Error).message).to.include('No handler registered');
483
- }
484
- });
485
-
486
- it('requestUntil - late response after timeout has responseId cleared', async () => {
487
- const requestAction: RequestAction<ResponseAction> = {
488
- kind: 'testRequest',
489
- requestId: 'req_late'
490
- };
491
-
492
- clientActionForwarderStub.shouldForwardToClient.callsFake(action => action.kind === 'testRequest');
493
- clientActionForwarderStub.handle.callsFake(action => action.kind === 'testRequest');
494
- // Register a no-op handler for testResponse so the late response can be dispatched normally
495
- const noopHandler = new mock.StubActionHandler(['testResponse']);
496
- registry_get_stub.callsFake((kind: string) => (kind === 'testResponse' ? [noopHandler] : []));
497
-
498
- // Request times out
499
- try {
500
- await actionDispatcher.requestUntil(requestAction, 50, true);
501
- expect.fail('Should have thrown');
502
- } catch (error: unknown) {
503
- expect((error as Error).message).to.include('timed out');
504
- }
505
-
506
- // Late response arrives — responseId should be cleared, dispatched as normal action
507
- const lateResponse: ResponseAction = { kind: 'testResponse', responseId: 'req_late' };
508
- await actionDispatcher.dispatch(lateResponse);
509
- expect(lateResponse.responseId).to.equal('');
510
- });
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
-
527
- it('request - resolves when response intercept happens from inside doDispatch', async () => {
528
- // A local handler for the request kind returns the matching response action directly.
529
- // The response is dispatched via dispatchResponses() -> dispatch() and intercepted
530
- // via the reentrant path (not via the external dispatch() entry).
531
- const requestKind = 'inlineRequest';
532
- const responseKind = 'inlineResponse';
533
-
534
- const handler = new mock.StubActionHandler([requestKind]);
535
- sinon.stub(handler, 'execute').callsFake(() => [{ kind: responseKind, responseId: '' } as ResponseAction]);
536
- registry_get_stub.callsFake((kind: string) => (kind === requestKind ? [handler] : []));
537
-
538
- const requestAction: RequestAction<ResponseAction> = { kind: requestKind, requestId: '' };
539
- const result = await actionDispatcher.request(requestAction);
540
-
541
- expect(result.responseId).to.equal(requestAction.requestId);
542
- });
543
- });
544
-
545
- describe('test reentrancy and ordering', () => {
546
- it('dispatch from within a handler runs inline before the next queued action', async () => {
547
- const outerKind = 'reentrantOuter';
548
- const innerKind = 'reentrantInner';
549
- const followerKind = 'reentrantFollower';
550
-
551
- const events: string[] = [];
552
-
553
- const innerHandler = new mock.StubActionHandler([innerKind]);
554
- sinon.stub(innerHandler, 'execute').callsFake(() => {
555
- events.push('inner');
556
- return [];
557
- });
558
-
559
- const outerHandler = new mock.StubActionHandler([outerKind]);
560
- outerHandler.execute = async () => {
561
- events.push('outer-start');
562
- await actionDispatcher.dispatch({ kind: innerKind });
563
- events.push('outer-end');
564
- return [];
565
- };
566
-
567
- const followerHandler = new mock.StubActionHandler([followerKind]);
568
- sinon.stub(followerHandler, 'execute').callsFake(() => {
569
- events.push('follower');
570
- return [];
571
- });
572
-
573
- registry_get_stub.callsFake((kind: string) => {
574
- if (kind === outerKind) return [outerHandler];
575
- if (kind === innerKind) return [innerHandler];
576
- if (kind === followerKind) return [followerHandler];
577
- return [];
578
- });
579
-
580
- actionDispatcher.dispatch({ kind: outerKind });
581
- await actionDispatcher.dispatch({ kind: followerKind });
582
-
583
- expect(events).to.deep.equal(['outer-start', 'inner', 'outer-end', 'follower']);
584
- });
585
-
586
- it('handler response actions are dispatched in order without an ephemeral queue', async () => {
587
- const requestKind = 'orderedRequest';
588
- const firstResponse = 'orderedResponse1';
589
- const secondResponse = 'orderedResponse2';
590
- const order: string[] = [];
591
-
592
- const requestHandler = new mock.StubActionHandler([requestKind]);
593
- sinon.stub(requestHandler, 'execute').callsFake(() => [{ kind: firstResponse }, { kind: secondResponse }]);
594
-
595
- const firstHandler = new mock.StubActionHandler([firstResponse]);
596
- sinon.stub(firstHandler, 'execute').callsFake(async () => {
597
- await mock.delay(20);
598
- order.push(firstResponse);
599
- return [];
600
- });
601
-
602
- const secondHandler = new mock.StubActionHandler([secondResponse]);
603
- sinon.stub(secondHandler, 'execute').callsFake(() => {
604
- order.push(secondResponse);
605
- return [];
606
- });
607
-
608
- registry_get_stub.callsFake((kind: string) => {
609
- if (kind === requestKind) return [requestHandler];
610
- if (kind === firstResponse) return [firstHandler];
611
- if (kind === secondResponse) return [secondHandler];
612
- return [];
613
- });
614
-
615
- await actionDispatcher.dispatch({ kind: requestKind });
616
- expect(order).to.deep.equal([firstResponse, secondResponse]);
617
- });
618
- });
619
-
620
- describe('test dispose', () => {
621
- it('dispose - rejects all pending requests', async () => {
622
- const requestAction: RequestAction<ResponseAction> = {
623
- kind: 'testRequest',
624
- requestId: 'req_dispose'
625
- };
626
-
627
- clientActionForwarderStub.shouldForwardToClient.callsFake(action => action.kind === 'testRequest');
628
- clientActionForwarderStub.handle.callsFake(action => action.kind === 'testRequest');
629
- registry_get_stub.callsFake(() => []);
630
-
631
- const responsePromise = actionDispatcher.request(requestAction);
632
- actionDispatcher.dispose();
633
-
634
- try {
635
- await responsePromise;
636
- expect.fail('Should have thrown');
637
- } catch (error: unknown) {
638
- expect((error as Error).message).to.include('cancelled');
639
- expect((error as Error).message).to.include('req_dispose');
640
- }
641
- });
642
-
643
- it('dispose - rejects queued dispatch() promises instead of orphaning them', async () => {
644
- // Use a fresh dispatcher so the shared one is not affected.
645
- const localDispatcher = container.resolve(DefaultActionDispatcher);
646
- const slowKind = 'slowDispose';
647
- const queuedKind = 'queuedDispose';
648
-
649
- const slowHandler = new mock.StubActionHandler([slowKind]);
650
- const slowStarted = new Deferred<void>();
651
- const releaseSlow = new Deferred<void>();
652
- slowHandler.execute = async () => {
653
- slowStarted.resolve();
654
- await releaseSlow.promise;
655
- return [];
656
- };
657
-
658
- registry_get_stub.callsFake((kind: string) => (kind === slowKind ? [slowHandler] : []));
659
-
660
- const slowPromise = localDispatcher.dispatch({ kind: slowKind });
661
- await slowStarted.promise;
662
- const queuedPromise = localDispatcher.dispatch({ kind: queuedKind });
663
-
664
- localDispatcher.dispose();
665
-
666
- try {
667
- await queuedPromise;
668
- expect.fail('Queued dispatch should have rejected');
669
- } catch (error: unknown) {
670
- expect((error as Error).message).to.include('ActionDispatcher disposed');
671
- }
672
-
673
- // Let the slow handler finish so the local dispatcher's consumer loop can exit cleanly.
674
- releaseSlow.resolve();
675
- await slowPromise;
676
- });
677
- });
678
- });