@declaro/core 2.0.0-beta.99 → 2.1.0
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.
- package/dist/browser/index.js +21 -27
- package/dist/browser/index.js.map +37 -27
- package/dist/browser/scope/index.js +1 -2
- package/dist/browser/scope/index.js.map +1 -1
- package/dist/bun/index.js +19011 -0
- package/dist/bun/index.js.map +132 -0
- package/dist/bun/scope/index.js +4 -0
- package/dist/bun/scope/index.js.map +9 -0
- package/dist/node/index.cjs +2581 -874
- package/dist/node/index.cjs.map +36 -27
- package/dist/node/index.js +2572 -868
- package/dist/node/index.js.map +36 -27
- package/dist/node/scope/index.cjs +31 -10
- package/dist/node/scope/index.cjs.map +1 -1
- package/dist/node/scope/index.js +1 -27
- package/dist/node/scope/index.js.map +1 -1
- package/dist/ts/context/async-context.d.ts +54 -0
- package/dist/ts/context/async-context.d.ts.map +1 -0
- package/dist/ts/context/async-context.test.d.ts +2 -0
- package/dist/ts/context/async-context.test.d.ts.map +1 -0
- package/dist/ts/context/context.circular-deps.test.d.ts +2 -0
- package/dist/ts/context/context.circular-deps.test.d.ts.map +1 -0
- package/dist/ts/context/context.d.ts +297 -38
- package/dist/ts/context/context.d.ts.map +1 -1
- package/dist/ts/http/request-context.d.ts.map +1 -1
- package/dist/ts/index.d.ts +2 -0
- package/dist/ts/index.d.ts.map +1 -1
- package/dist/ts/schema/json-schema.d.ts +9 -1
- package/dist/ts/schema/json-schema.d.ts.map +1 -1
- package/dist/ts/schema/model.d.ts +6 -1
- package/dist/ts/schema/model.d.ts.map +1 -1
- package/dist/ts/schema/test/mock-model.d.ts +2 -2
- package/dist/ts/schema/test/mock-model.d.ts.map +1 -1
- package/dist/ts/shared/utils/schema-utils.d.ts +3 -0
- package/dist/ts/shared/utils/schema-utils.d.ts.map +1 -0
- package/dist/ts/shared/utils/schema-utils.test.d.ts +2 -0
- package/dist/ts/shared/utils/schema-utils.test.d.ts.map +1 -0
- package/dist/ts/shims/async-local-storage.d.ts +36 -0
- package/dist/ts/shims/async-local-storage.d.ts.map +1 -0
- package/dist/ts/shims/async-local-storage.test.d.ts +2 -0
- package/dist/ts/shims/async-local-storage.test.d.ts.map +1 -0
- package/package.json +17 -9
- package/src/context/async-context.test.ts +348 -0
- package/src/context/async-context.ts +129 -0
- package/src/context/context.circular-deps.test.ts +1047 -0
- package/src/context/context.test.ts +150 -0
- package/src/context/context.ts +493 -55
- package/src/http/request-context.ts +1 -3
- package/src/index.ts +2 -0
- package/src/schema/json-schema.ts +14 -1
- package/src/schema/model-schema.test.ts +155 -1
- package/src/schema/model.ts +34 -3
- package/src/schema/test/mock-model.ts +6 -2
- package/src/shared/utils/schema-utils.test.ts +33 -0
- package/src/shared/utils/schema-utils.ts +17 -0
- package/src/shims/async-local-storage.test.ts +258 -0
- package/src/shims/async-local-storage.ts +82 -0
- package/dist/ts/schema/entity-schema.test.d.ts +0 -1
- package/dist/ts/schema/entity-schema.test.d.ts.map +0 -1
- package/src/schema/entity-schema.test.ts +0 -0
package/src/context/context.ts
CHANGED
|
@@ -5,19 +5,39 @@ import type { AllNodeMiddleware } from '../http/request-context'
|
|
|
5
5
|
import type { Class, PromiseOrValue, UnwrapPromise } from '../typescript'
|
|
6
6
|
import { validate, validateAny, type Validator } from '../validation'
|
|
7
7
|
import { ContextConsumer } from './context-consumer'
|
|
8
|
+
import { useContext, withContext } from './async-context'
|
|
8
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Global interface for declaring dependencies available across all contexts.
|
|
12
|
+
* Extend this interface using declaration merging to add type-safe dependencies.
|
|
13
|
+
*/
|
|
9
14
|
export interface DeclaroDependencies {}
|
|
10
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Base scope interface for contexts with request and node middleware.
|
|
18
|
+
*/
|
|
11
19
|
export interface DeclaroScope {
|
|
20
|
+
/** Middleware that runs on request contexts */
|
|
12
21
|
requestMiddleware: ContextMiddleware<Context>[]
|
|
22
|
+
/** Node.js-compatible middleware */
|
|
13
23
|
nodeMiddleware: AllNodeMiddleware[]
|
|
14
24
|
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Scope interface for request-specific contexts, extending the base scope with request data.
|
|
28
|
+
*/
|
|
15
29
|
export interface DeclaroRequestScope extends DeclaroScope {
|
|
30
|
+
/** The HTTP request object */
|
|
16
31
|
request: Request
|
|
32
|
+
/** Incoming HTTP headers */
|
|
17
33
|
headers: IncomingHttpHeaders
|
|
34
|
+
/** Helper function to retrieve a specific header value */
|
|
18
35
|
header: <K extends keyof IncomingHttpHeaders>(header: K) => IncomingHttpHeaders[K] | undefined
|
|
19
36
|
}
|
|
20
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Extracts the scope type from a Context type.
|
|
40
|
+
*/
|
|
21
41
|
export type ExtractScope<T extends Context<any>> = T extends Context<infer S> ? S : never
|
|
22
42
|
|
|
23
43
|
/**
|
|
@@ -32,53 +52,194 @@ export type NarrowContext<TContext extends Context<any>, TNarrowScope extends ob
|
|
|
32
52
|
: never
|
|
33
53
|
: never
|
|
34
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Middleware function that can modify or extend a context.
|
|
57
|
+
*/
|
|
35
58
|
export type ContextMiddleware<C extends Context = Context> = (context: Context<ExtractScope<C>>) => any | Promise<any>
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Represents the state storage for a context, mapping keys to their attributes.
|
|
62
|
+
*/
|
|
36
63
|
export type ContextState<TContext extends Context> = Record<PropertyKey, ContextAttribute<TContext, StateValue<any>>>
|
|
37
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Function that resolves a value from a context.
|
|
67
|
+
*/
|
|
38
68
|
export type ContextResolver<T> = (context: Context) => StateValue<T>
|
|
39
69
|
|
|
70
|
+
/**
|
|
71
|
+
* Wrapper type for state values.
|
|
72
|
+
*/
|
|
40
73
|
export type StateValue<T> = T
|
|
41
74
|
|
|
75
|
+
/**
|
|
76
|
+
* Types of dependencies that can be registered in a context.
|
|
77
|
+
*/
|
|
42
78
|
export enum DependencyType {
|
|
79
|
+
/** A literal value dependency */
|
|
43
80
|
VALUE = 'VALUE',
|
|
81
|
+
/** A factory function that creates the dependency */
|
|
44
82
|
FACTORY = 'FACTORY',
|
|
83
|
+
/** A class constructor that instantiates the dependency */
|
|
45
84
|
CLASS = 'CLASS',
|
|
46
85
|
}
|
|
47
86
|
|
|
87
|
+
/**
|
|
88
|
+
* Factory function type that takes arguments and returns a value.
|
|
89
|
+
*/
|
|
48
90
|
export type FactoryFn<T, A extends any[]> = (...args: A) => T
|
|
49
|
-
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Function that loads a value from a context with optional resolution options.
|
|
94
|
+
*/
|
|
95
|
+
export type ValueLoader<C extends Context, T> = (context: C, resolutionOptions?: ResolveOptions) => T
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Filters object keys to only those whose values match a specific type.
|
|
99
|
+
*/
|
|
50
100
|
export type FilterKeysByType<TScope, TValue> = {
|
|
51
101
|
[Key in keyof TScope]: TScope[Key] extends TValue ? Key : never
|
|
52
102
|
}[keyof TScope]
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Filters object keys to only those whose values match a specific type or Promise of that type.
|
|
106
|
+
*/
|
|
53
107
|
export type FilterKeysByAsyncType<TScope, TValue> = {
|
|
54
108
|
[Key in keyof TScope]: TScope[Key] extends PromiseOrValue<TValue> ? Key : never
|
|
55
109
|
}[keyof TScope]
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Maps an array of argument types to their corresponding scope keys.
|
|
113
|
+
*/
|
|
56
114
|
export type FilterArgsByType<TScope, TArgs extends any[]> = {
|
|
57
115
|
[Key in keyof TArgs]: FilterKeysByType<TScope, TArgs[Key]>
|
|
58
116
|
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Maps an array of argument types to their corresponding scope keys for async values.
|
|
120
|
+
*/
|
|
59
121
|
export type FilterAsyncArgsByType<TScope, TArgs extends any[]> = {
|
|
60
122
|
[Key in keyof TArgs]: FilterKeysByAsyncType<TScope, TArgs[Key]>
|
|
61
123
|
}
|
|
62
124
|
|
|
125
|
+
/**
|
|
126
|
+
* Metadata describing how a dependency is registered and resolved in a context.
|
|
127
|
+
*/
|
|
63
128
|
export type ContextAttribute<TContext extends Context<any>, TValue> = {
|
|
129
|
+
/** The key under which this dependency is registered */
|
|
64
130
|
key: PropertyKey
|
|
131
|
+
/** Function that loads the value from the context */
|
|
65
132
|
value?: ValueLoader<TContext, TValue>
|
|
133
|
+
/** The type of dependency (value, factory, or class) */
|
|
66
134
|
type: DependencyType
|
|
135
|
+
/** Options controlling how this dependency is resolved */
|
|
67
136
|
resolveOptions?: ResolveOptions
|
|
137
|
+
/** Cached value for singleton or eager dependencies */
|
|
68
138
|
cachedValue?: TValue
|
|
139
|
+
/** Keys of other dependencies that this dependency requires */
|
|
69
140
|
inject: PropertyKey[]
|
|
70
141
|
}
|
|
71
142
|
|
|
143
|
+
/**
|
|
144
|
+
* Type-safe scope key extraction.
|
|
145
|
+
*/
|
|
72
146
|
export type ScopeKey<S extends object> = keyof S
|
|
73
147
|
|
|
148
|
+
/**
|
|
149
|
+
* Listener function that responds to events in a context.
|
|
150
|
+
*/
|
|
74
151
|
export type ContextListener<C extends Context, E extends IEvent> = (context: C, event: E) => any
|
|
75
152
|
|
|
76
|
-
|
|
153
|
+
/**
|
|
154
|
+
* Interface for circular dependency proxies that defer to a real target once resolved.
|
|
155
|
+
* These proxies are created during circular dependency resolution to allow references
|
|
156
|
+
* to objects that haven't been fully constructed yet.
|
|
157
|
+
*/
|
|
158
|
+
export interface ResolveProxy<T = any> {
|
|
159
|
+
/**
|
|
160
|
+
* Identifies this object as a circular proxy.
|
|
161
|
+
* @internal
|
|
162
|
+
*/
|
|
163
|
+
readonly __isProxy: true
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Sets the real target object that this proxy should delegate to.
|
|
167
|
+
* Called internally once the circular dependency is resolved.
|
|
168
|
+
* @internal
|
|
169
|
+
*/
|
|
170
|
+
readonly __resolve: (target: T) => void
|
|
171
|
+
|
|
172
|
+
/** Indicates whether the proxy has been resolved to a real target. */
|
|
173
|
+
readonly __isResolved: boolean
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Returns the real target object if resolved, otherwise returns the proxy itself.
|
|
177
|
+
* This allows using the proxy transparently before and after resolution.
|
|
178
|
+
*/
|
|
179
|
+
readonly valueOf: () => T
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Type guard to check if a value is a circular dependency proxy.
|
|
184
|
+
*
|
|
185
|
+
* @param value - The value to check
|
|
186
|
+
* @returns True if the value is a ResolveProxy
|
|
187
|
+
*/
|
|
188
|
+
export function isProxy(value: any): value is ResolveProxy {
|
|
189
|
+
return value && typeof value === 'object' && value.__isProxy === true
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export interface ResolveOptions {
|
|
193
|
+
/**
|
|
194
|
+
* If true, an error will be thrown if the dependency is not found. If false, undefined will be returned if the dependency is not found.
|
|
195
|
+
* @default false
|
|
196
|
+
*/
|
|
77
197
|
strict?: boolean
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* If true, the dependency will be resolved immediately when the context is initialized. This is useful for dependencies that need to perform setup work.
|
|
201
|
+
* @default false
|
|
202
|
+
*/
|
|
78
203
|
eager?: boolean
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* If true, the dependency will be a singleton, and the same instance will be returned for every request. If false, a new instance will be created each time the dependency is resolved.
|
|
207
|
+
* @default false
|
|
208
|
+
*/
|
|
79
209
|
singleton?: boolean
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* An optional resolution context that can be used to track resolution state across multiple `resolve` calls. This is primarily used internally to track circular dependencies, but can be useful for advanced use cases.
|
|
213
|
+
*/
|
|
214
|
+
resolutionContext?: Map<PropertyKey, any>
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Extracts nested resolution options, preserving resolution state across calls.
|
|
219
|
+
*
|
|
220
|
+
* @param options - The resolve options to extract from
|
|
221
|
+
* @returns Internal resolution options with resolution stack and context
|
|
222
|
+
*/
|
|
223
|
+
export function getNestedResolveOptions(options?: ResolveOptions | InternalResolveOptions): InternalResolveOptions {
|
|
224
|
+
return {
|
|
225
|
+
resolutionStack: (options as InternalResolveOptions)?.resolutionStack,
|
|
226
|
+
resolutionContext: options?.resolutionContext,
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Internal interface extending ResolveOptions with resolution tracking.
|
|
232
|
+
*/
|
|
233
|
+
export interface InternalResolveOptions extends ResolveOptions {
|
|
234
|
+
/** Stack tracking the current resolution chain to detect circular dependencies */
|
|
235
|
+
resolutionStack: Set<PropertyKey>
|
|
80
236
|
}
|
|
81
237
|
|
|
238
|
+
/**
|
|
239
|
+
* Returns the default resolution options for dependencies.
|
|
240
|
+
*
|
|
241
|
+
* @returns Default resolve options with strict, eager, and singleton all set to false
|
|
242
|
+
*/
|
|
82
243
|
export function defaultResolveOptions(): ResolveOptions {
|
|
83
244
|
return {
|
|
84
245
|
strict: false,
|
|
@@ -87,18 +248,43 @@ export function defaultResolveOptions(): ResolveOptions {
|
|
|
87
248
|
}
|
|
88
249
|
}
|
|
89
250
|
|
|
251
|
+
/**
|
|
252
|
+
* Helper function to define type-safe context middleware.
|
|
253
|
+
*
|
|
254
|
+
* @param middleware - The middleware function to define
|
|
255
|
+
* @returns The same middleware function with proper typing
|
|
256
|
+
*/
|
|
257
|
+
export function defineContextMiddleware<C extends Context>(middleware: ContextMiddleware<C>): ContextMiddleware<C> {
|
|
258
|
+
return middleware
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Configuration options for creating a context.
|
|
263
|
+
*/
|
|
90
264
|
export type ContextOptions = {
|
|
265
|
+
/** Default options to use when resolving dependencies */
|
|
91
266
|
defaultResolveOptions?: ResolveOptions
|
|
92
267
|
}
|
|
93
268
|
|
|
269
|
+
/**
|
|
270
|
+
* Core dependency injection container that manages application dependencies and their lifecycle.
|
|
271
|
+
* Supports values, factories, and classes with automatic dependency resolution and circular dependency handling.
|
|
272
|
+
*/
|
|
94
273
|
export class Context<Scope extends object = any> {
|
|
95
274
|
private readonly state: ContextState<this> = {}
|
|
96
275
|
private readonly emitter = new EventManager()
|
|
97
276
|
|
|
277
|
+
/** The scope object providing typed access to all registered dependencies */
|
|
98
278
|
public readonly scope: Scope = {} as Scope
|
|
99
279
|
|
|
280
|
+
/** Default options used when resolving dependencies if not overridden */
|
|
100
281
|
protected readonly defaultResolveOptions: ResolveOptions
|
|
101
282
|
|
|
283
|
+
/**
|
|
284
|
+
* Creates a new context instance.
|
|
285
|
+
*
|
|
286
|
+
* @param options - Configuration options for the context
|
|
287
|
+
*/
|
|
102
288
|
constructor(options?: ContextOptions) {
|
|
103
289
|
this.defaultResolveOptions = {
|
|
104
290
|
...defaultResolveOptions(),
|
|
@@ -106,10 +292,19 @@ export class Context<Scope extends object = any> {
|
|
|
106
292
|
}
|
|
107
293
|
}
|
|
108
294
|
|
|
295
|
+
/**
|
|
296
|
+
* Gets the event manager for this context.
|
|
297
|
+
*
|
|
298
|
+
* @returns The event manager instance
|
|
299
|
+
*/
|
|
109
300
|
get events() {
|
|
110
301
|
return this.emitter
|
|
111
302
|
}
|
|
112
303
|
|
|
304
|
+
/**
|
|
305
|
+
* Initializes all dependencies marked as eager.
|
|
306
|
+
* Should be called after all dependencies are registered to trigger eager initialization.
|
|
307
|
+
*/
|
|
113
308
|
async initializeEagerDependencies() {
|
|
114
309
|
await Promise.all(
|
|
115
310
|
Object.entries(this.state)
|
|
@@ -123,8 +318,8 @@ export class Context<Scope extends object = any> {
|
|
|
123
318
|
/**
|
|
124
319
|
* Set a value in context, to be injected later.
|
|
125
320
|
*
|
|
126
|
-
* @param key
|
|
127
|
-
* @param payload
|
|
321
|
+
* @param key - The scope key to register the value under
|
|
322
|
+
* @param payload - The value to register
|
|
128
323
|
* @deprecated Use `provideValue` instead, or you can register the same dependency as a factory with `provideFactory` or class with `provideClass`.
|
|
129
324
|
*/
|
|
130
325
|
provide<K extends ScopeKey<Scope>>(key: K, payload: Scope[K]) {
|
|
@@ -165,6 +360,10 @@ export class Context<Scope extends object = any> {
|
|
|
165
360
|
|
|
166
361
|
/**
|
|
167
362
|
* Add a dependency to the context.
|
|
363
|
+
*
|
|
364
|
+
* @param key - The scope key to register under
|
|
365
|
+
* @param dep - The dependency attribute to add
|
|
366
|
+
* @returns The registered dependency attribute
|
|
168
367
|
*/
|
|
169
368
|
protected addDep<K extends ScopeKey<Scope>>(key: K, dep: ContextAttribute<this, Scope[K]>) {
|
|
170
369
|
this.state[key] = dep
|
|
@@ -181,8 +380,10 @@ export class Context<Scope extends object = any> {
|
|
|
181
380
|
/**
|
|
182
381
|
* Register a value in context scope.
|
|
183
382
|
*
|
|
184
|
-
* @param key The key to register the dependency under
|
|
185
|
-
* @param value The value to register
|
|
383
|
+
* @param key - The key to register the dependency under
|
|
384
|
+
* @param value - The value to register
|
|
385
|
+
* @param defaultResolveOptions - Optional resolution options
|
|
386
|
+
* @returns The context instance for chaining
|
|
186
387
|
*/
|
|
187
388
|
registerValue<K extends ScopeKey<Scope>>(key: K, value: Scope[K], defaultResolveOptions?: ResolveOptions) {
|
|
188
389
|
const attribute: ContextAttribute<this, Scope[K]> = {
|
|
@@ -201,9 +402,10 @@ export class Context<Scope extends object = any> {
|
|
|
201
402
|
/**
|
|
202
403
|
* Register a dependency as a factory in context scope.
|
|
203
404
|
*
|
|
204
|
-
* @param key The key to register the dependency under
|
|
205
|
-
* @param factory A factory function that will be called to generate the value when it is requested
|
|
206
|
-
* @param inject An array of keys to use when injecting factory args
|
|
405
|
+
* @param key - The key to register the dependency under
|
|
406
|
+
* @param factory - A factory function that will be called to generate the value when it is requested
|
|
407
|
+
* @param inject - An array of keys to use when injecting factory args
|
|
408
|
+
* @param defaultResolveOptions - Optional resolution options
|
|
207
409
|
* @returns A chainable instance of context
|
|
208
410
|
*/
|
|
209
411
|
registerFactory<K extends ScopeKey<Scope>, A extends any[]>(
|
|
@@ -213,8 +415,10 @@ export class Context<Scope extends object = any> {
|
|
|
213
415
|
defaultResolveOptions?: ResolveOptions,
|
|
214
416
|
) {
|
|
215
417
|
const attribute: ContextAttribute<this, Scope[K]> = {
|
|
216
|
-
value: (context) => {
|
|
217
|
-
const args = (inject?.map((key) =>
|
|
418
|
+
value: (context, resolveOptions) => {
|
|
419
|
+
const args = (inject?.map((key) =>
|
|
420
|
+
context._resolveValue(key, getNestedResolveOptions(resolveOptions)),
|
|
421
|
+
) ?? []) as A
|
|
218
422
|
|
|
219
423
|
return factory(...args)
|
|
220
424
|
},
|
|
@@ -229,6 +433,16 @@ export class Context<Scope extends object = any> {
|
|
|
229
433
|
return this
|
|
230
434
|
}
|
|
231
435
|
|
|
436
|
+
/**
|
|
437
|
+
* Register an async factory in context scope.
|
|
438
|
+
* The factory function and its dependencies can be asynchronous.
|
|
439
|
+
*
|
|
440
|
+
* @param key - The key to register the dependency under
|
|
441
|
+
* @param factory - An async factory function that will be called to generate the value
|
|
442
|
+
* @param inject - An array of keys to use when injecting factory args
|
|
443
|
+
* @param defaultResolveOptions - Optional resolution options
|
|
444
|
+
* @returns A chainable instance of context
|
|
445
|
+
*/
|
|
232
446
|
registerAsyncFactory<K extends FilterKeysByType<Scope, Promise<any>>, A extends any[]>(
|
|
233
447
|
key: K,
|
|
234
448
|
factory: FactoryFn<Scope[K], A>,
|
|
@@ -236,8 +450,10 @@ export class Context<Scope extends object = any> {
|
|
|
236
450
|
defaultResolveOptions?: ResolveOptions,
|
|
237
451
|
) {
|
|
238
452
|
const attribute: ContextAttribute<this, Scope[K]> = {
|
|
239
|
-
value: (async (context) => {
|
|
240
|
-
const args = (await Promise.all(
|
|
453
|
+
value: (async (context, resolveOptions) => {
|
|
454
|
+
const args = (await Promise.all(
|
|
455
|
+
(inject?.map((key) => context.resolve(key, getNestedResolveOptions(resolveOptions))) as A) ?? [],
|
|
456
|
+
)) as A
|
|
241
457
|
|
|
242
458
|
return await factory(...args)
|
|
243
459
|
}) as ValueLoader<this, Scope[K]>,
|
|
@@ -252,6 +468,16 @@ export class Context<Scope extends object = any> {
|
|
|
252
468
|
return this
|
|
253
469
|
}
|
|
254
470
|
|
|
471
|
+
/**
|
|
472
|
+
* Register a class constructor in context scope.
|
|
473
|
+
* The class will be instantiated with the specified dependencies.
|
|
474
|
+
*
|
|
475
|
+
* @param key - The key to register the dependency under
|
|
476
|
+
* @param Class - The class constructor
|
|
477
|
+
* @param inject - An array of keys to use when injecting constructor arguments
|
|
478
|
+
* @param defaultResolveOptions - Optional resolution options
|
|
479
|
+
* @returns A chainable instance of context
|
|
480
|
+
*/
|
|
255
481
|
registerClass<
|
|
256
482
|
K extends FilterKeysByType<Scope, InstanceType<T>>,
|
|
257
483
|
T extends Class<Scope[K] extends {} ? Scope[K] : never>,
|
|
@@ -262,8 +488,8 @@ export class Context<Scope extends object = any> {
|
|
|
262
488
|
defaultResolveOptions?: ResolveOptions,
|
|
263
489
|
) {
|
|
264
490
|
const attribute: ContextAttribute<this, Scope[K]> = {
|
|
265
|
-
value: (context) => {
|
|
266
|
-
const args = inject?.map((key) => context.resolve(key)) ?? []
|
|
491
|
+
value: (context, resolveOptions) => {
|
|
492
|
+
const args = inject?.map((key) => context.resolve(key, getNestedResolveOptions(resolveOptions))) ?? []
|
|
267
493
|
|
|
268
494
|
return new (Class as any)(...(args as any))
|
|
269
495
|
},
|
|
@@ -278,6 +504,16 @@ export class Context<Scope extends object = any> {
|
|
|
278
504
|
return this
|
|
279
505
|
}
|
|
280
506
|
|
|
507
|
+
/**
|
|
508
|
+
* Register an async class constructor in context scope.
|
|
509
|
+
* The class constructor can have async dependencies.
|
|
510
|
+
*
|
|
511
|
+
* @param key - The key to register the dependency under
|
|
512
|
+
* @param Class - The class constructor
|
|
513
|
+
* @param inject - An array of keys to use when injecting constructor arguments
|
|
514
|
+
* @param defaultResolveOptions - Optional resolution options
|
|
515
|
+
* @returns A chainable instance of context
|
|
516
|
+
*/
|
|
281
517
|
registerAsyncClass<
|
|
282
518
|
K extends FilterKeysByType<Scope, InstanceType<any>>,
|
|
283
519
|
T extends Class<UnwrapPromise<Scope[K]> extends {} ? UnwrapPromise<Scope[K]> : never>,
|
|
@@ -288,9 +524,10 @@ export class Context<Scope extends object = any> {
|
|
|
288
524
|
defaultResolveOptions?: ResolveOptions,
|
|
289
525
|
) {
|
|
290
526
|
const attribute: ContextAttribute<this, Scope[K]> = {
|
|
291
|
-
value: (async (context) => {
|
|
527
|
+
value: (async (context, resolveOptions) => {
|
|
292
528
|
const args = (await Promise.all(
|
|
293
|
-
(inject?.map((key) => context.resolve(key)) ??
|
|
529
|
+
(inject?.map((key) => context.resolve(key, getNestedResolveOptions(resolveOptions))) ??
|
|
530
|
+
[]) as ConstructorParameters<T>,
|
|
294
531
|
)) as ConstructorParameters<T>
|
|
295
532
|
|
|
296
533
|
return new (Class as any)(...(args as any))
|
|
@@ -306,6 +543,12 @@ export class Context<Scope extends object = any> {
|
|
|
306
543
|
return this
|
|
307
544
|
}
|
|
308
545
|
|
|
546
|
+
/**
|
|
547
|
+
* Gets all dependencies required by a specific dependency.
|
|
548
|
+
*
|
|
549
|
+
* @param key - The key of the dependency to inspect
|
|
550
|
+
* @returns Array of all direct and transitive dependencies
|
|
551
|
+
*/
|
|
309
552
|
getAllDependencies<K extends ScopeKey<Scope>>(key: K): ContextAttribute<this, any>[] {
|
|
310
553
|
const attribute = this.state[key]
|
|
311
554
|
|
|
@@ -323,26 +566,59 @@ export class Context<Scope extends object = any> {
|
|
|
323
566
|
return dependencies
|
|
324
567
|
}
|
|
325
568
|
|
|
326
|
-
|
|
569
|
+
/**
|
|
570
|
+
* Gets all dependencies that depend on a specific dependency.
|
|
571
|
+
* Useful for cache invalidation when a dependency changes.
|
|
572
|
+
*
|
|
573
|
+
* @param key - The key of the dependency to inspect
|
|
574
|
+
* @param visited - Internal tracking set to prevent infinite recursion
|
|
575
|
+
* @returns Array of all direct and transitive dependents
|
|
576
|
+
*/
|
|
577
|
+
getAllDependents<K extends ScopeKey<Scope>>(key: K, visited = new Set<any>()): ContextAttribute<this, any>[] {
|
|
578
|
+
if (visited.has(key)) {
|
|
579
|
+
return []
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
visited.add(key)
|
|
583
|
+
|
|
327
584
|
const dependents = Object.entries(this.state)
|
|
328
585
|
.filter(([_, attribute]) => attribute.inject?.includes(key))
|
|
329
586
|
.map(([key, attribute]) => attribute)
|
|
330
587
|
|
|
331
588
|
dependents.forEach((dependent) => {
|
|
332
|
-
const nestedDependents = this.getAllDependents(dependent.key as any)
|
|
589
|
+
const nestedDependents = this.getAllDependents(dependent.key as any, visited)
|
|
333
590
|
dependents.push(...nestedDependents)
|
|
334
591
|
})
|
|
335
592
|
|
|
336
593
|
return dependents
|
|
337
594
|
}
|
|
338
595
|
|
|
596
|
+
/**
|
|
597
|
+
* Introspects a dependency to get its metadata.
|
|
598
|
+
*
|
|
599
|
+
* @param key - The key of the dependency to inspect
|
|
600
|
+
* @returns The context attribute for the dependency, or undefined if not found
|
|
601
|
+
*/
|
|
339
602
|
introspect<K extends ScopeKey<Scope>>(key: K) {
|
|
340
603
|
const attribute = this.state[key]
|
|
341
604
|
|
|
342
605
|
return attribute
|
|
343
606
|
}
|
|
344
607
|
|
|
345
|
-
|
|
608
|
+
/**
|
|
609
|
+
* Checks if a cached value is still valid for a dependency.
|
|
610
|
+
* A cache is invalid if the dependency or any of its transitive dependencies have been invalidated.
|
|
611
|
+
*
|
|
612
|
+
* @param key - The key of the dependency to check
|
|
613
|
+
* @param visited - Internal tracking set to prevent infinite recursion in circular dependencies
|
|
614
|
+
* @returns True if the cache is valid, false otherwise
|
|
615
|
+
*/
|
|
616
|
+
protected _cacheIsValid<K extends ScopeKey<Scope>>(key: K, visited = new Set<PropertyKey>()): boolean {
|
|
617
|
+
// Prevent infinite recursion in circular dependencies
|
|
618
|
+
if (visited.has(key)) {
|
|
619
|
+
return true
|
|
620
|
+
}
|
|
621
|
+
|
|
346
622
|
const attribute = this.state[key]
|
|
347
623
|
|
|
348
624
|
const needsCache = attribute?.resolveOptions?.singleton || attribute?.resolveOptions?.eager
|
|
@@ -357,17 +633,123 @@ export class Context<Scope extends object = any> {
|
|
|
357
633
|
return false
|
|
358
634
|
}
|
|
359
635
|
|
|
360
|
-
|
|
636
|
+
visited.add(key)
|
|
637
|
+
const result = hasCachedValue && attribute.inject?.every((depKey) => this._cacheIsValid(depKey as any, visited))
|
|
638
|
+
visited.delete(key)
|
|
639
|
+
|
|
640
|
+
return result
|
|
361
641
|
}
|
|
362
642
|
|
|
363
|
-
|
|
364
|
-
|
|
643
|
+
/**
|
|
644
|
+
* Creates a proxy object for handling circular dependencies.
|
|
645
|
+
* The proxy initially acts as a placeholder and is later resolved to the real target.
|
|
646
|
+
*
|
|
647
|
+
* @returns A proxy that can be resolved later
|
|
648
|
+
*/
|
|
649
|
+
protected createProxy<T>(): ResolveProxy<T> {
|
|
650
|
+
let realTarget: any = null
|
|
651
|
+
let isResolved = false
|
|
652
|
+
|
|
653
|
+
const proxy = new Proxy({} as any, {
|
|
654
|
+
get: (target, prop, receiver) => {
|
|
655
|
+
if (prop === '__isProxy') {
|
|
656
|
+
return true
|
|
657
|
+
}
|
|
658
|
+
if (prop === '__resolve') {
|
|
659
|
+
return (newTarget: any) => {
|
|
660
|
+
realTarget = newTarget
|
|
661
|
+
isResolved = true
|
|
662
|
+
// Copy any properties that were set on the proxy to the real target
|
|
663
|
+
Object.keys(target).forEach((key) => {
|
|
664
|
+
if (!(key in newTarget)) {
|
|
665
|
+
newTarget[key] = target[key]
|
|
666
|
+
}
|
|
667
|
+
})
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
if (prop === '__isResolved') {
|
|
672
|
+
return isResolved
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
if (prop === 'valueOf') {
|
|
676
|
+
return () => realTarget ?? proxy
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
if (isResolved && realTarget) {
|
|
680
|
+
return Reflect.get(realTarget, prop, realTarget)
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
return Reflect.get(target, prop, receiver)
|
|
684
|
+
},
|
|
685
|
+
|
|
686
|
+
set: (target, prop, value, receiver) => {
|
|
687
|
+
if (isResolved && realTarget) {
|
|
688
|
+
return Reflect.set(realTarget, prop, value, realTarget)
|
|
689
|
+
}
|
|
690
|
+
return Reflect.set(target, prop, value, receiver)
|
|
691
|
+
},
|
|
692
|
+
|
|
693
|
+
has: (target, prop) => {
|
|
694
|
+
if (isResolved && realTarget) {
|
|
695
|
+
return Reflect.has(realTarget, prop)
|
|
696
|
+
}
|
|
697
|
+
return Reflect.has(target, prop)
|
|
698
|
+
},
|
|
699
|
+
|
|
700
|
+
ownKeys: (target) => {
|
|
701
|
+
if (isResolved && realTarget) {
|
|
702
|
+
return Reflect.ownKeys(realTarget)
|
|
703
|
+
}
|
|
704
|
+
return Reflect.ownKeys(target)
|
|
705
|
+
},
|
|
365
706
|
|
|
366
|
-
|
|
707
|
+
getOwnPropertyDescriptor: (target, prop) => {
|
|
708
|
+
if (isResolved && realTarget) {
|
|
709
|
+
return Reflect.getOwnPropertyDescriptor(realTarget, prop)
|
|
710
|
+
}
|
|
711
|
+
return Reflect.getOwnPropertyDescriptor(target, prop)
|
|
712
|
+
},
|
|
713
|
+
|
|
714
|
+
getPrototypeOf: (target) => {
|
|
715
|
+
if (isResolved && realTarget) {
|
|
716
|
+
return Reflect.getPrototypeOf(realTarget)
|
|
717
|
+
}
|
|
718
|
+
return Reflect.getPrototypeOf(target)
|
|
719
|
+
},
|
|
720
|
+
})
|
|
721
|
+
|
|
722
|
+
return proxy as ResolveProxy<T>
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
/**
|
|
726
|
+
* Internal method to resolve a dependency value with circular dependency handling.
|
|
727
|
+
* This is the core resolution logic that handles caching, proxies, and dependency injection.
|
|
728
|
+
*
|
|
729
|
+
* @param key - The key of the dependency to resolve
|
|
730
|
+
* @param resolveOptions - Options controlling the resolution behavior
|
|
731
|
+
* @returns The resolved dependency value
|
|
732
|
+
*/
|
|
733
|
+
protected _resolveValue<K extends ScopeKey<Scope>>(key: K, resolveOptions?: InternalResolveOptions): Scope[K] {
|
|
734
|
+
const attributeResolveOptions: InternalResolveOptions = {
|
|
367
735
|
...this.defaultResolveOptions,
|
|
368
|
-
...
|
|
736
|
+
...this.state[key]?.resolveOptions,
|
|
369
737
|
...resolveOptions,
|
|
738
|
+
resolutionStack: resolveOptions?.resolutionStack ?? new Set<PropertyKey>(),
|
|
370
739
|
}
|
|
740
|
+
const isRoot = attributeResolveOptions.resolutionStack.size === 0
|
|
741
|
+
attributeResolveOptions.resolutionStack.add(key)
|
|
742
|
+
let resolutionContext: Map<PropertyKey, any> = new Map<PropertyKey, any>()
|
|
743
|
+
if (attributeResolveOptions.resolutionContext) {
|
|
744
|
+
// Import existing resolution context if provided, but define a new object to avoid mutating the original
|
|
745
|
+
resolutionContext = new Map(attributeResolveOptions.resolutionContext)
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
// Update the resolve options to use the local copy of the resolution context
|
|
749
|
+
// so that recursive calls don't mutate the original
|
|
750
|
+
attributeResolveOptions.resolutionContext = resolutionContext
|
|
751
|
+
|
|
752
|
+
const attribute = this.state[key]
|
|
371
753
|
|
|
372
754
|
if (!attribute && attributeResolveOptions.strict) {
|
|
373
755
|
throw new Error(`Dependency ${key?.toString()} not found.`)
|
|
@@ -378,10 +760,48 @@ export class Context<Scope extends object = any> {
|
|
|
378
760
|
const serveFromCache = attributeResolveOptions.singleton || attributeResolveOptions.eager
|
|
379
761
|
const dependenciesValid = attribute?.inject?.every((key) => this._cacheIsValid(key as any))
|
|
380
762
|
|
|
381
|
-
|
|
763
|
+
// Check instance cache for resolution context (including non-singletons)
|
|
764
|
+
const resolutionContextValue = resolutionContext.get(key)
|
|
765
|
+
if (!isRoot && resolutionContextValue) {
|
|
766
|
+
value = resolutionContextValue
|
|
767
|
+
} else if (serveFromCache && attribute?.cachedValue && dependenciesValid) {
|
|
382
768
|
value = attribute.cachedValue
|
|
383
769
|
} else {
|
|
384
|
-
|
|
770
|
+
const contextValue = resolutionContext.get(key)
|
|
771
|
+
let proxy: ResolveProxy
|
|
772
|
+
// If the context already has a proxy for this key, use it
|
|
773
|
+
if (isProxy(contextValue)) {
|
|
774
|
+
proxy = contextValue as ResolveProxy
|
|
775
|
+
} else {
|
|
776
|
+
// Create a proxy to use as a placeholder during resolution for circular dependencies
|
|
777
|
+
proxy = this.createProxy()
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
resolutionContext.set(key, proxy)
|
|
781
|
+
|
|
782
|
+
if (contextValue && !isProxy(contextValue)) {
|
|
783
|
+
// If the context has a non-proxy value for this key, use it directly
|
|
784
|
+
value = contextValue
|
|
785
|
+
} else if (proxy.__isResolved) {
|
|
786
|
+
// If the proxy has already been resolved, use its real target
|
|
787
|
+
value = proxy.valueOf() as Scope[K]
|
|
788
|
+
} else {
|
|
789
|
+
// Otherwise, resolve the value normally
|
|
790
|
+
value =
|
|
791
|
+
typeof attribute?.value === 'function' ? attribute.value(this, attributeResolveOptions) : undefined
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
if (value instanceof Promise) {
|
|
795
|
+
const valueAsPromise = value as Scope[K] & Promise<unknown>
|
|
796
|
+
value = valueAsPromise.then((resolvedValue) => {
|
|
797
|
+
proxy.__resolve(resolvedValue)
|
|
798
|
+
resolutionContext.set(key, resolvedValue)
|
|
799
|
+
return isProxy(resolvedValue) ? resolvedValue.valueOf() : resolvedValue
|
|
800
|
+
}) as Scope[K]
|
|
801
|
+
} else {
|
|
802
|
+
proxy.__resolve(value)
|
|
803
|
+
resolutionContext.set(key, value)
|
|
804
|
+
}
|
|
385
805
|
}
|
|
386
806
|
|
|
387
807
|
if (serveFromCache) {
|
|
@@ -398,23 +818,38 @@ export class Context<Scope extends object = any> {
|
|
|
398
818
|
/**
|
|
399
819
|
* Extract a value from context.
|
|
400
820
|
*
|
|
401
|
-
* @param key
|
|
402
|
-
* @returns
|
|
821
|
+
* @param key - The scope key to resolve
|
|
822
|
+
* @returns The resolved value or undefined if not found
|
|
403
823
|
* @deprecated Use `resolve` instead
|
|
404
824
|
*/
|
|
405
825
|
inject<T = any>(key: ScopeKey<Scope>): T | undefined {
|
|
406
826
|
return this._resolveValue(key) as T
|
|
407
827
|
}
|
|
408
828
|
|
|
829
|
+
/**
|
|
830
|
+
* Resolves a dependency from the context.
|
|
831
|
+
* This is the primary method for retrieving registered dependencies.
|
|
832
|
+
*
|
|
833
|
+
* @param key - The scope key to resolve
|
|
834
|
+
* @param resolveOptions - Options controlling resolution behavior
|
|
835
|
+
* @returns The resolved dependency value
|
|
836
|
+
*/
|
|
409
837
|
resolve<K extends ScopeKey<Scope>>(key: K, resolveOptions?: ResolveOptions): Scope[K] {
|
|
410
|
-
|
|
838
|
+
// Create a default resolution context for top-level calls if none provided
|
|
839
|
+
const options: InternalResolveOptions = {
|
|
840
|
+
resolutionContext: new Map<PropertyKey, any>(),
|
|
841
|
+
resolutionStack: new Set<PropertyKey>(),
|
|
842
|
+
...resolveOptions,
|
|
843
|
+
}
|
|
844
|
+
return this._resolveValue(key, options)
|
|
411
845
|
}
|
|
412
846
|
|
|
413
847
|
/**
|
|
414
848
|
* Ensure that only one copy of this instance exists in this context. Provides the instance if it doesn't exist yet, otherwise inject the cached instance.
|
|
415
849
|
*
|
|
416
|
-
* @param key
|
|
417
|
-
* @param instance
|
|
850
|
+
* @param key - The scope key to register under
|
|
851
|
+
* @param instance - The instance to register as a singleton
|
|
852
|
+
* @returns The singleton instance (either the provided one or the existing one)
|
|
418
853
|
*/
|
|
419
854
|
singleton<T = any>(key: ScopeKey<Scope>, instance: T) {
|
|
420
855
|
const existing = this.inject<T>(key)
|
|
@@ -427,10 +862,12 @@ export class Context<Scope extends object = any> {
|
|
|
427
862
|
}
|
|
428
863
|
|
|
429
864
|
/**
|
|
430
|
-
* Instantiate a ContextConsumer class
|
|
865
|
+
* Instantiate a ContextConsumer class.
|
|
866
|
+
* ContextConsumer classes have automatic access to the context instance.
|
|
431
867
|
*
|
|
432
|
-
* @param Consumer
|
|
433
|
-
* @
|
|
868
|
+
* @param Consumer - The ContextConsumer class to instantiate
|
|
869
|
+
* @param args - Additional arguments to pass to the constructor
|
|
870
|
+
* @returns A new instance of the ContextConsumer
|
|
434
871
|
*/
|
|
435
872
|
hydrate<T extends ContextConsumer<this, any[]>, A extends any[]>(
|
|
436
873
|
Consumer: new (context: this, ...args: A) => T,
|
|
@@ -440,10 +877,11 @@ export class Context<Scope extends object = any> {
|
|
|
440
877
|
}
|
|
441
878
|
|
|
442
879
|
/**
|
|
443
|
-
* Create a new context from other instance(s) of Context
|
|
880
|
+
* Create a new context from other instance(s) of Context.
|
|
881
|
+
* Dependencies and event listeners from the provided contexts will be merged into this context.
|
|
444
882
|
*
|
|
445
|
-
* @param contexts
|
|
446
|
-
* @returns
|
|
883
|
+
* @param contexts - One or more contexts to extend from
|
|
884
|
+
* @returns The current context instance for chaining
|
|
447
885
|
*/
|
|
448
886
|
extend(...contexts: Context[]): this {
|
|
449
887
|
contexts.forEach((context) => {
|
|
@@ -460,10 +898,10 @@ export class Context<Scope extends object = any> {
|
|
|
460
898
|
}
|
|
461
899
|
|
|
462
900
|
/**
|
|
463
|
-
* Modify context with middleware
|
|
901
|
+
* Modify context with middleware.
|
|
902
|
+
* Middleware can register dependencies, modify configuration, or perform other setup tasks.
|
|
464
903
|
*
|
|
465
|
-
* @param middleware
|
|
466
|
-
* @returns
|
|
904
|
+
* @param middleware - One or more middleware functions to apply
|
|
467
905
|
*/
|
|
468
906
|
async use<TNarrowScope extends Partial<Scope>>(
|
|
469
907
|
...middleware: ContextMiddleware<Context<TNarrowScope>>[]
|
|
@@ -481,17 +919,18 @@ export class Context<Scope extends object = any> {
|
|
|
481
919
|
/**
|
|
482
920
|
* Validate context ensuring all validators are valid.
|
|
483
921
|
*
|
|
484
|
-
* @param validators
|
|
485
|
-
* @returns
|
|
922
|
+
* @param validators - One or more validator functions to run
|
|
923
|
+
* @returns The validation result
|
|
486
924
|
*/
|
|
487
925
|
validate(...validators: Validator<Context>[]) {
|
|
488
926
|
return validate(this, ...validators)
|
|
489
927
|
}
|
|
490
928
|
|
|
491
929
|
/**
|
|
492
|
-
* Validate context ensuring at least one validator is valid
|
|
493
|
-
*
|
|
494
|
-
* @
|
|
930
|
+
* Validate context ensuring at least one validator is valid.
|
|
931
|
+
*
|
|
932
|
+
* @param validators - One or more validator functions to run
|
|
933
|
+
* @returns The validation result
|
|
495
934
|
*/
|
|
496
935
|
validateAny(...validators: Validator<Context>[]) {
|
|
497
936
|
return validateAny(this, ...validators)
|
|
@@ -500,27 +939,26 @@ export class Context<Scope extends object = any> {
|
|
|
500
939
|
/**
|
|
501
940
|
* Add a callback to listen for an event in this context.
|
|
502
941
|
*
|
|
503
|
-
* @param event
|
|
504
|
-
* @param listener
|
|
505
|
-
* @returns
|
|
942
|
+
* @param type - The event type to listen for
|
|
943
|
+
* @param listener - The callback function to invoke when the event is emitted
|
|
944
|
+
* @returns A function to unregister the listener
|
|
506
945
|
*/
|
|
507
946
|
on<E extends IEvent = IEvent>(type: IEvent['type'], listener: ContextListener<this, E>) {
|
|
508
947
|
return this.emitter.on(type, (event) => {
|
|
509
|
-
return listener(this, event as E)
|
|
948
|
+
return listener((useContext() ?? this) as this, event as E)
|
|
510
949
|
})
|
|
511
950
|
}
|
|
512
951
|
|
|
513
952
|
/**
|
|
514
|
-
* Emit an event in this context
|
|
953
|
+
* Emit an event in this context.
|
|
515
954
|
*
|
|
516
|
-
* @param event
|
|
517
|
-
* @
|
|
518
|
-
* @returns
|
|
955
|
+
* @param event - The event type string or event object to emit
|
|
956
|
+
* @returns A promise that resolves when all event listeners have completed
|
|
519
957
|
*/
|
|
520
958
|
async emit(event: string | IEvent) {
|
|
521
959
|
const eventObject = typeof event === 'string' ? { type: event } : event
|
|
522
960
|
|
|
523
|
-
return await this.emitter.emitAsync(eventObject)
|
|
961
|
+
return await withContext(this, () => this.emitter.emitAsync(eventObject))
|
|
524
962
|
}
|
|
525
963
|
|
|
526
964
|
/**
|