@@ -26,7 +26,7 @@ import {
2626 flatPush
2727} from '@eclipse-glsp/protocol' ;
2828import { inject , injectable , postConstruct } from 'inversify' ;
29- import { ActionDispatchContext , ClientId } from '../di/service-identifiers' ;
29+ import { ClientId } from '../di/service-identifiers' ;
3030import { ActionChannel } from '../utils/action-channel' ;
3131import { GLSPServerError } from '../utils/glsp-server-error' ;
3232import { Logger } from '../utils/logger' ;
@@ -113,6 +113,36 @@ export interface ActionDispatcher {
113113 ) : Promise < Res | undefined > ;
114114}
115115
116+ export const ActionDispatchContext = Symbol ( 'ActionDispatchContext' ) ;
117+
118+ /**
119+ * Scope marker that lets the {@link ActionDispatcher} know whether a call to `dispatch()`
120+ * originates from inside a running handler (reentrant) or from outside (external).
121+ *
122+ * The consumer loop wraps each action in {@link run} so that reentrant `dispatch()` calls
123+ * (handler responses, injected dispatcher calls) can be recognized via {@link isInContext}
124+ * and executed inline instead of being queued.
125+ *
126+ * Used by the {@link DefaultActionDispatcher} implementation.
127+ */
128+ export interface ActionDispatchContext {
129+ /**
130+ * Executes the callback inside the dispatch context. While the callback (and its full
131+ * async continuation) is running, {@link isInContext} returns `true` for reentrant calls.
132+ */
133+ run < R > ( callback : ( ) => R ) : R ;
134+
135+ /**
136+ * Returns `true` if the caller is executing inside a {@link run} callback, meaning the
137+ * dispatch is reentrant (e.g. a handler response or an injected dispatcher call) and
138+ * should run inline rather than being queued.
139+ *
140+ * Implementations may inspect the action to apply additional guards, e.g. to ensure
141+ * client-originated actions are always queued regardless of context state.
142+ */
143+ isInContext ( action : Action ) : boolean ;
144+ }
145+
116146/**
117147 * Default {@link ActionDispatcher}. External dispatches are queued and processed one at a
118148 * time; dispatches made from within a running handler run inline with the containing action.
@@ -156,7 +186,7 @@ export class DefaultActionDispatcher implements ActionDispatcher, Disposable {
156186 return Promise . resolve ( ) ;
157187 }
158188 // Reentrant dispatches run inline to preserve ordering with the containing action.
159- if ( this . dispatchContext . getStore ( ) ) {
189+ if ( this . dispatchContext . isInContext ( action ) ) {
160190 return this . doDispatch ( action ) ;
161191 }
162192 // External dispatches are queued and processed sequentially.
@@ -167,7 +197,7 @@ export class DefaultActionDispatcher implements ActionDispatcher, Disposable {
167197 // Run each action inside the dispatch context so reentrant dispatch() calls are recognized.
168198 for await ( const entry of this . channel . consume ( ) ) {
169199 try {
170- await this . dispatchContext . run ( true , ( ) => this . doDispatch ( entry . item ) ) ;
200+ await this . dispatchContext . run ( ( ) => this . doDispatch ( entry . item ) ) ;
171201 entry . resolve ( ) ;
172202 } catch ( error ) {
173203 entry . reject ( error ) ;
0 commit comments