@zhin.js/middleware 1.1.12 → 1.1.14

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.
@@ -10,11 +10,19 @@ export type MiddlewarePhase = 'before-dispatch' | 'after-dispatch';
10
10
  export type MiddlewareTarget = 'inbound' | 'outbound';
11
11
  export type MiddlewareNext = () => Promise<MiddlewareContinuation>;
12
12
  export type OutboundMiddlewareNext = () => Promise<void>;
13
+ export interface InboundDispatchSummary {
14
+ readonly matched: boolean;
15
+ readonly path: 'button' | 'command' | 'handler' | 'none';
16
+ readonly handlerId?: string;
17
+ readonly replied: boolean;
18
+ }
13
19
  export interface MiddlewareContext<TInput = unknown, TConfig = unknown, TAdapter extends string | undefined = undefined> extends CapabilityContext<TConfig> {
14
20
  readonly input: TInput;
15
21
  /** Lazily resolved native Client. Without `adapter`, its static type is `unknown`. */
16
22
  readonly $client: AdapterClient<TAdapter>;
17
23
  readonly session: OperationSession<TAdapter> | undefined;
24
+ readonly dispatch?: InboundDispatchSummary;
25
+ onAfterDispatch?(listener: (summary: InboundDispatchSummary) => void | Promise<void>): void;
18
26
  }
19
27
  /** Shared metadata for the public inbound and outbound definition contracts. */
20
28
  export interface MiddlewareDefinitionBase<TAdapter extends string | undefined = string | undefined> {
@@ -46,16 +54,17 @@ declare module '@zhin.js/plugin-runtime' {
46
54
  */
47
55
  type AuthoringForAdapter<TInput, TConfig, TAdapter extends string | undefined> = Omit<MiddlewareDefinitionBase<TAdapter>, '$feature' | 'phase' | 'order'> & {
48
56
  readonly phase?: MiddlewarePhase;
57
+ readonly stage?: MiddlewarePhase;
49
58
  readonly order?: number;
50
59
  } & ({
51
60
  readonly target?: 'inbound';
52
- handle(context: MiddlewareContext<TInput, TConfig, TAdapter>, next: MiddlewareNext): unknown;
61
+ handle(this: MiddlewareContext<TInput, TConfig, TAdapter>, context: MiddlewareContext<TInput, TConfig, TAdapter>, next: MiddlewareNext): unknown;
53
62
  } | {
54
63
  readonly target: 'outbound';
55
64
  /** Opt in to cumulative prefixes. Previously published text, format and addressing must stay stable;
56
65
  * do not repeat approvals, external sends or other side effects. */
57
66
  readonly incremental?: true;
58
- handle(context: MiddlewareContext<TInput, TConfig, TAdapter>, next: OutboundMiddlewareNext): void | Promise<void>;
67
+ handle(this: MiddlewareContext<TInput, TConfig, TAdapter>, context: MiddlewareContext<TInput, TConfig, TAdapter>, next: OutboundMiddlewareNext): void | Promise<void>;
59
68
  });
60
69
  /** @internal Shape shared with the typed IM facade; not a separate authoring API. */
61
70
  export type MiddlewareAuthoringDefinition<TInput, TConfig> = AuthoringForAdapter<TInput, TConfig, undefined> | {
@@ -63,6 +72,24 @@ export type MiddlewareAuthoringDefinition<TInput, TConfig> = AuthoringForAdapter
63
72
  readonly adapter: TAdapter;
64
73
  };
65
74
  }[RegisteredAdapterName];
75
+ /**
76
+ * Returned by `defineMiddleware.withConfig<Config>()`: fixes the plugin config type while
77
+ * keeping inbound/outbound input inference intact.
78
+ *
79
+ * @public
80
+ */
81
+ export interface MiddlewareDefiner<TConfig> {
82
+ <TInput = unknown>(definition: Extract<MiddlewareAuthoringDefinition<TInput, TConfig>, {
83
+ readonly target: 'outbound';
84
+ }>): Readonly<Extract<MiddlewareDefinition<TInput, TConfig>, {
85
+ readonly target: 'outbound';
86
+ }>>;
87
+ <TInput = unknown>(definition: Extract<MiddlewareAuthoringDefinition<TInput, TConfig>, {
88
+ readonly target?: 'inbound';
89
+ }>): Readonly<Extract<MiddlewareDefinition<TInput, TConfig>, {
90
+ readonly target: 'inbound';
91
+ }>>;
92
+ }
66
93
  export declare function defineMiddleware<TInput = unknown, TConfig = unknown>(definition: Extract<MiddlewareAuthoringDefinition<TInput, TConfig>, {
67
94
  readonly target: 'outbound';
68
95
  }>): Readonly<Extract<MiddlewareDefinition<TInput, TConfig>, {
@@ -73,5 +100,8 @@ export declare function defineMiddleware<TInput = unknown, TConfig = unknown>(de
73
100
  }>): Readonly<Extract<MiddlewareDefinition<TInput, TConfig>, {
74
101
  readonly target: 'inbound';
75
102
  }>>;
103
+ export declare namespace defineMiddleware {
104
+ var withConfig: <TConfig = unknown>() => MiddlewareDefiner<TConfig>;
105
+ }
76
106
  /** @internal Runtime validation for convention-discovered modules. */
77
107
  export declare function parseMiddlewareDefinition(value: unknown): MiddlewareDefinition;
package/lib/definition.js CHANGED
@@ -8,7 +8,7 @@ export function defineMiddleware(definition) {
8
8
  && (typeof adapter !== 'string' || adapter.trim() === '')) {
9
9
  throw new TypeError('Middleware adapter must be a non-empty string');
10
10
  }
11
- const phase = definition.phase ?? 'before-dispatch';
11
+ const phase = definition.phase ?? definition.stage ?? 'before-dispatch';
12
12
  if (phase !== 'before-dispatch' && phase !== 'after-dispatch') {
13
13
  throw new TypeError(`Invalid Middleware phase: ${String(phase)}`);
14
14
  }
@@ -21,6 +21,12 @@ export function defineMiddleware(definition) {
21
21
  throw new TypeError('Middleware order must be a safe integer');
22
22
  return Object.freeze({ ...definition, $feature: middlewareBrand, phase, target, order });
23
23
  }
24
+ /**
25
+ * Fix the plugin config type first, then define the middleware.
26
+ */
27
+ defineMiddleware.withConfig = function withConfig() {
28
+ return ((definition) => defineMiddleware(definition));
29
+ };
24
30
  /** @internal Runtime validation for convention-discovered modules. */
25
31
  export function parseMiddlewareDefinition(value) {
26
32
  if (!value || typeof value !== 'object')
@@ -1,9 +1,14 @@
1
1
  import type { CapabilitySlot, PluginId, RuntimeSnapshot } from '@zhin.js/plugin-runtime';
2
- import type { MiddlewareDefinition, MiddlewarePhase, MiddlewareTarget, OutboundMiddlewareNext } from './definition.js';
2
+ import type { InboundDispatchSummary, MiddlewareDefinition, MiddlewarePhase, MiddlewareTarget, OutboundMiddlewareNext } from './definition.js';
3
+ /** @internal Terminal control passed to inbound terminal callbacks to publish dispatch summary before after-dispatch unwinds. */
4
+ export interface InboundTerminalControl {
5
+ reportDispatch(summary: InboundDispatchSummary): Promise<void>;
6
+ }
3
7
  /** @internal Transmission-neutral result policy supplied by the composition layer. */
4
8
  export interface MiddlewareResultPolicy<TResult> {
5
9
  stop(owner: PluginId): TResult;
6
10
  replace(owner: PluginId, value: unknown, downstream: TResult | undefined): TResult;
11
+ summarize?(result: TResult): InboundDispatchSummary | undefined;
7
12
  }
8
13
  /** @internal Runtime continuation result. */
9
14
  export interface MiddlewareRunResult {
@@ -32,7 +37,7 @@ export declare class MiddlewareIndex {
32
37
  run<TInput>(input: TInput, terminal: OutboundMiddlewareNext | undefined, target: 'outbound', operationSnapshot?: RuntimeSnapshot): Promise<void>;
33
38
  run<TInput>(input: TInput, terminal: OutboundMiddlewareNext | undefined, target: MiddlewareTarget, operationSnapshot?: RuntimeSnapshot): Promise<MiddlewareRunResult | void>;
34
39
  /** Unwinds one inbound operation before its caller performs any automatic reply. */
35
- runInbound<TInput, TResult>(input: TInput, terminal: () => Promise<TResult>, results: MiddlewareResultPolicy<TResult>, operationSnapshot?: RuntimeSnapshot): Promise<TResult>;
40
+ runInbound<TInput, TResult>(input: TInput, terminal: (control: InboundTerminalControl) => Promise<TResult>, results: MiddlewareResultPolicy<TResult>, operationSnapshot?: RuntimeSnapshot): Promise<TResult>;
36
41
  }
37
42
  /** @internal Runtime projection guard. */
38
43
  export declare function isMiddlewareIndex(value: unknown): value is MiddlewareIndex;
@@ -42,7 +42,7 @@ export class MiddlewareIndex {
42
42
  return terminal();
43
43
  const authorInput = bindOperationInput(input, record.owner, operationSnapshot);
44
44
  const context = {
45
- ...createCapabilityContext(operationSnapshot, record.owner, undefined, readOperationSession(authorInput, record.adapter)?.signal),
45
+ ...createCapabilityContext({ snapshot: operationSnapshot, ownerId: record.owner, admission: undefined, signal: readOperationSession(authorInput, record.adapter)?.signal }),
46
46
  input: authorInput,
47
47
  };
48
48
  Object.defineProperty(context, 'session', {
@@ -56,7 +56,7 @@ export class MiddlewareIndex {
56
56
  const definition = record.slot.definition;
57
57
  if (definition.target !== 'outbound')
58
58
  throw new Error('Invalid outbound Middleware');
59
- const result = await definition.handle(context, () => dispatch(index + 1));
59
+ const result = await definition.handle.call(context, context, () => dispatch(index + 1));
60
60
  if (result !== undefined)
61
61
  throw new TypeError('Outbound Middleware must use input.replace(), not return content');
62
62
  };
@@ -67,16 +67,41 @@ export class MiddlewareIndex {
67
67
  const clientAdapter = operationClientAdapter(input);
68
68
  const records = this.#records.filter((record) => record.target === 'inbound'
69
69
  && (!record.adapter || record.adapter === clientAdapter));
70
+ let dispatchSummary;
71
+ let reported = false;
72
+ const afterDispatchListeners = [];
73
+ const reportDispatch = async (summary) => {
74
+ dispatchSummary = Object.freeze({ ...summary });
75
+ if (reported)
76
+ return;
77
+ reported = true;
78
+ for (const listener of afterDispatchListeners.splice(0)) {
79
+ await listener(dispatchSummary);
80
+ }
81
+ };
70
82
  const dispatch = async (index) => {
71
83
  const record = records[index];
72
- if (!record)
73
- return terminal();
84
+ if (!record) {
85
+ const terminalResult = await terminal({ reportDispatch });
86
+ if (!reported) {
87
+ const inferred = results.summarize?.(terminalResult)
88
+ ?? { matched: false, path: 'none', replied: false };
89
+ await reportDispatch(inferred);
90
+ }
91
+ return terminalResult;
92
+ }
74
93
  const definition = record.slot.definition;
75
94
  if (definition.target !== 'inbound')
76
95
  throw new Error('Invalid inbound Middleware');
77
96
  const authorInput = bindOperationInput(input, record.owner, operationSnapshot);
78
97
  const context = {
79
- ...createCapabilityContext(operationSnapshot, record.owner, undefined, readOperationSession(authorInput, record.adapter)?.signal), input: authorInput,
98
+ ...createCapabilityContext({ snapshot: operationSnapshot, ownerId: record.owner, admission: undefined, signal: readOperationSession(authorInput, record.adapter)?.signal }), input: authorInput,
99
+ onAfterDispatch(listener) {
100
+ if (typeof listener !== 'function') {
101
+ throw new TypeError('Middleware onAfterDispatch listener must be a function');
102
+ }
103
+ afterDispatchListeners.push(listener);
104
+ },
80
105
  };
81
106
  Object.defineProperty(context, 'session', {
82
107
  enumerable: true, get: () => readOperationSession(authorInput, record.adapter),
@@ -84,6 +109,9 @@ export class MiddlewareIndex {
84
109
  Object.defineProperty(context, '$client', {
85
110
  enumerable: true, get: () => readOperationClient(authorInput, record.adapter),
86
111
  });
112
+ Object.defineProperty(context, 'dispatch', {
113
+ enumerable: true, get: () => dispatchSummary,
114
+ });
87
115
  Object.freeze(context);
88
116
  let active = true;
89
117
  let nextCalled = false;
@@ -107,7 +135,7 @@ export class MiddlewareIndex {
107
135
  };
108
136
  let returned;
109
137
  try {
110
- returned = await definition.handle(context, next);
138
+ returned = await definition.handle.call(context, context, next);
111
139
  }
112
140
  catch (error) {
113
141
  // Drain an admitted child before the owning generation lease can close.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/middleware",
3
- "version": "1.1.12",
3
+ "version": "1.1.14",
4
4
  "description": "Convention-based Middleware Feature for Zhin Plugin Runtime",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -16,8 +16,8 @@
16
16
  "src"
17
17
  ],
18
18
  "dependencies": {
19
- "@zhin.js/feature-kit": "1.1.11",
20
- "@zhin.js/plugin-runtime": "1.1.19"
19
+ "@zhin.js/plugin-runtime": "1.1.21",
20
+ "@zhin.js/feature-kit": "1.1.13"
21
21
  },
22
22
  "devDependencies": {
23
23
  "@types/node": "^26.1.2",
package/src/definition.ts CHANGED
@@ -18,6 +18,13 @@ export type MiddlewareTarget = 'inbound' | 'outbound';
18
18
  export type MiddlewareNext = () => Promise<MiddlewareContinuation>;
19
19
  export type OutboundMiddlewareNext = () => Promise<void>;
20
20
 
21
+ export interface InboundDispatchSummary {
22
+ readonly matched: boolean;
23
+ readonly path: 'button' | 'command' | 'handler' | 'none';
24
+ readonly handlerId?: string;
25
+ readonly replied: boolean;
26
+ }
27
+
21
28
  export interface MiddlewareContext<
22
29
  TInput = unknown,
23
30
  TConfig = unknown,
@@ -28,6 +35,8 @@ export interface MiddlewareContext<
28
35
  /** Lazily resolved native Client. Without `adapter`, its static type is `unknown`. */
29
36
  readonly $client: AdapterClient<TAdapter>;
30
37
  readonly session: OperationSession<TAdapter> | undefined;
38
+ readonly dispatch?: InboundDispatchSummary;
39
+ onAfterDispatch?(listener: (summary: InboundDispatchSummary) => void | Promise<void>): void;
31
40
  }
32
41
 
33
42
  /** Shared metadata for the public inbound and outbound definition contracts. */
@@ -71,15 +80,16 @@ declare module '@zhin.js/plugin-runtime' {
71
80
  type AuthoringForAdapter<TInput, TConfig, TAdapter extends string | undefined> =
72
81
  Omit<MiddlewareDefinitionBase<TAdapter>, '$feature' | 'phase' | 'order'> & {
73
82
  readonly phase?: MiddlewarePhase;
83
+ readonly stage?: MiddlewarePhase;
74
84
  readonly order?: number;
75
85
  } & (
76
- | { readonly target?: 'inbound'; handle(context: MiddlewareContext<TInput, TConfig, TAdapter>, next: MiddlewareNext): unknown }
86
+ | { readonly target?: 'inbound'; handle(this: MiddlewareContext<TInput, TConfig, TAdapter>, context: MiddlewareContext<TInput, TConfig, TAdapter>, next: MiddlewareNext): unknown }
77
87
  | {
78
88
  readonly target: 'outbound';
79
89
  /** Opt in to cumulative prefixes. Previously published text, format and addressing must stay stable;
80
90
  * do not repeat approvals, external sends or other side effects. */
81
91
  readonly incremental?: true;
82
- handle(context: MiddlewareContext<TInput, TConfig, TAdapter>, next: OutboundMiddlewareNext): void | Promise<void>;
92
+ handle(this: MiddlewareContext<TInput, TConfig, TAdapter>, context: MiddlewareContext<TInput, TConfig, TAdapter>, next: OutboundMiddlewareNext): void | Promise<void>;
83
93
  }
84
94
  );
85
95
 
@@ -92,6 +102,21 @@ export type MiddlewareAuthoringDefinition<TInput, TConfig> =
92
102
  }
93
103
  }[RegisteredAdapterName];
94
104
 
105
+ /**
106
+ * Returned by `defineMiddleware.withConfig<Config>()`: fixes the plugin config type while
107
+ * keeping inbound/outbound input inference intact.
108
+ *
109
+ * @public
110
+ */
111
+ export interface MiddlewareDefiner<TConfig> {
112
+ <TInput = unknown>(
113
+ definition: Extract<MiddlewareAuthoringDefinition<TInput, TConfig>, { readonly target: 'outbound' }>,
114
+ ): Readonly<Extract<MiddlewareDefinition<TInput, TConfig>, { readonly target: 'outbound' }>>;
115
+ <TInput = unknown>(
116
+ definition: Extract<MiddlewareAuthoringDefinition<TInput, TConfig>, { readonly target?: 'inbound' }>,
117
+ ): Readonly<Extract<MiddlewareDefinition<TInput, TConfig>, { readonly target: 'inbound' }>>;
118
+ }
119
+
95
120
  export function defineMiddleware<TInput = unknown, TConfig = unknown>(
96
121
  definition: Extract<MiddlewareAuthoringDefinition<TInput, TConfig>, { readonly target: 'outbound' }>,
97
122
  ): Readonly<Extract<MiddlewareDefinition<TInput, TConfig>, { readonly target: 'outbound' }>>;
@@ -109,7 +134,7 @@ export function defineMiddleware<TInput = unknown, TConfig = unknown>(
109
134
  && (typeof adapter !== 'string' || adapter.trim() === '')) {
110
135
  throw new TypeError('Middleware adapter must be a non-empty string');
111
136
  }
112
- const phase = definition.phase ?? 'before-dispatch';
137
+ const phase = definition.phase ?? (definition as { readonly stage?: MiddlewarePhase }).stage ?? 'before-dispatch';
113
138
  if (phase !== 'before-dispatch' && phase !== 'after-dispatch') {
114
139
  throw new TypeError(`Invalid Middleware phase: ${String(phase)}`);
115
140
  }
@@ -124,6 +149,14 @@ export function defineMiddleware<TInput = unknown, TConfig = unknown>(
124
149
  >;
125
150
  }
126
151
 
152
+ /**
153
+ * Fix the plugin config type first, then define the middleware.
154
+ */
155
+ defineMiddleware.withConfig = function withConfig<TConfig = unknown>(): MiddlewareDefiner<TConfig> {
156
+ return ((definition: MiddlewareAuthoringDefinition<unknown, TConfig>) =>
157
+ defineMiddleware(definition as never)) as MiddlewareDefiner<TConfig>;
158
+ };
159
+
127
160
  /** @internal Runtime validation for convention-discovered modules. */
128
161
  export function parseMiddlewareDefinition(value: unknown): MiddlewareDefinition {
129
162
  if (!value || typeof value !== 'object') throw invalidMiddleware();
@@ -7,6 +7,7 @@ import {
7
7
  readOperationSession,
8
8
  } from '@zhin.js/feature-kit';
9
9
  import type {
10
+ InboundDispatchSummary,
10
11
  MiddlewareContext,
11
12
  MiddlewareDefinition,
12
13
  MiddlewarePhase,
@@ -15,10 +16,16 @@ import type {
15
16
  } from './definition.js';
16
17
  import { continuationBrand, type MiddlewareContinuation } from './continuation.js';
17
18
 
19
+ /** @internal Terminal control passed to inbound terminal callbacks to publish dispatch summary before after-dispatch unwinds. */
20
+ export interface InboundTerminalControl {
21
+ reportDispatch(summary: InboundDispatchSummary): Promise<void>;
22
+ }
23
+
18
24
  /** @internal Transmission-neutral result policy supplied by the composition layer. */
19
25
  export interface MiddlewareResultPolicy<TResult> {
20
26
  stop(owner: PluginId): TResult;
21
27
  replace(owner: PluginId, value: unknown, downstream: TResult | undefined): TResult;
28
+ summarize?(result: TResult): InboundDispatchSummary | undefined;
22
29
  }
23
30
 
24
31
  /** @internal Runtime continuation result. */
@@ -96,7 +103,7 @@ export class MiddlewareIndex {
96
103
  if (!record) return terminal();
97
104
  const authorInput = bindOperationInput(input, record.owner, operationSnapshot);
98
105
  const context = {
99
- ...createCapabilityContext(operationSnapshot, record.owner, undefined, readOperationSession(authorInput, record.adapter)?.signal),
106
+ ...createCapabilityContext({ snapshot: operationSnapshot, ownerId: record.owner, admission: undefined, signal: readOperationSession(authorInput, record.adapter)?.signal }),
100
107
  input: authorInput,
101
108
  } as MiddlewareContext<TInput>;
102
109
  Object.defineProperty(context, 'session', {
@@ -109,7 +116,7 @@ export class MiddlewareIndex {
109
116
  Object.freeze(context);
110
117
  const definition = record.slot.definition;
111
118
  if (definition.target !== 'outbound') throw new Error('Invalid outbound Middleware');
112
- const result = await definition.handle(context, () => dispatch(index + 1));
119
+ const result = await definition.handle.call(context, context, () => dispatch(index + 1));
113
120
  if (result !== undefined) throw new TypeError('Outbound Middleware must use input.replace(), not return content');
114
121
  };
115
122
  await dispatch(0);
@@ -118,21 +125,46 @@ export class MiddlewareIndex {
118
125
  /** Unwinds one inbound operation before its caller performs any automatic reply. */
119
126
  async runInbound<TInput, TResult>(
120
127
  input: TInput,
121
- terminal: () => Promise<TResult>,
128
+ terminal: (control: InboundTerminalControl) => Promise<TResult>,
122
129
  results: MiddlewareResultPolicy<TResult>,
123
130
  operationSnapshot: RuntimeSnapshot = this.snapshot,
124
131
  ): Promise<TResult> {
125
132
  const clientAdapter = operationClientAdapter(input);
126
133
  const records = this.#records.filter((record) => record.target === 'inbound'
127
134
  && (!record.adapter || record.adapter === clientAdapter));
135
+ let dispatchSummary: InboundDispatchSummary | undefined;
136
+ let reported = false;
137
+ const afterDispatchListeners: Array<(summary: InboundDispatchSummary) => void | Promise<void>> = [];
138
+ const reportDispatch = async (summary: InboundDispatchSummary): Promise<void> => {
139
+ dispatchSummary = Object.freeze({ ...summary });
140
+ if (reported) return;
141
+ reported = true;
142
+ for (const listener of afterDispatchListeners.splice(0)) {
143
+ await listener(dispatchSummary);
144
+ }
145
+ };
128
146
  const dispatch = async (index: number): Promise<TResult> => {
129
147
  const record = records[index];
130
- if (!record) return terminal();
148
+ if (!record) {
149
+ const terminalResult = await terminal({ reportDispatch });
150
+ if (!reported) {
151
+ const inferred = results.summarize?.(terminalResult)
152
+ ?? { matched: false, path: 'none' as const, replied: false };
153
+ await reportDispatch(inferred);
154
+ }
155
+ return terminalResult;
156
+ }
131
157
  const definition = record.slot.definition;
132
158
  if (definition.target !== 'inbound') throw new Error('Invalid inbound Middleware');
133
159
  const authorInput = bindOperationInput(input, record.owner, operationSnapshot);
134
160
  const context = {
135
- ...createCapabilityContext(operationSnapshot, record.owner, undefined, readOperationSession(authorInput, record.adapter)?.signal), input: authorInput,
161
+ ...createCapabilityContext({ snapshot: operationSnapshot, ownerId: record.owner, admission: undefined, signal: readOperationSession(authorInput, record.adapter)?.signal }), input: authorInput,
162
+ onAfterDispatch(listener: (summary: InboundDispatchSummary) => void | Promise<void>) {
163
+ if (typeof listener !== 'function') {
164
+ throw new TypeError('Middleware onAfterDispatch listener must be a function');
165
+ }
166
+ afterDispatchListeners.push(listener);
167
+ },
136
168
  } as MiddlewareContext<TInput>;
137
169
  Object.defineProperty(context, 'session', {
138
170
  enumerable: true, get: () => readOperationSession(authorInput, record.adapter),
@@ -140,6 +172,9 @@ export class MiddlewareIndex {
140
172
  Object.defineProperty(context, '$client', {
141
173
  enumerable: true, get: () => readOperationClient(authorInput, record.adapter),
142
174
  });
175
+ Object.defineProperty(context, 'dispatch', {
176
+ enumerable: true, get: () => dispatchSummary,
177
+ });
143
178
  Object.freeze(context);
144
179
  let active = true;
145
180
  let nextCalled = false;
@@ -161,7 +196,7 @@ export class MiddlewareIndex {
161
196
  };
162
197
  let returned: unknown;
163
198
  try {
164
- returned = await definition.handle(context, next);
199
+ returned = await definition.handle.call(context, context, next);
165
200
  } catch (error) {
166
201
  // Drain an admitted child before the owning generation lease can close.
167
202
  await nextTask?.catch(() => undefined);