@composed-di/core 0.5.3-alpha → 0.7.0-alpha
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/errors.d.ts +0 -2
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +4 -4
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -2
- package/dist/index.js.map +1 -1
- package/dist/serviceFactory.d.ts.map +1 -1
- package/dist/serviceFactory.js.map +1 -1
- package/dist/serviceKey.d.ts +6 -0
- package/dist/serviceKey.d.ts.map +1 -1
- package/dist/serviceKey.js.map +1 -1
- package/dist/serviceModule.d.ts +1 -11
- package/dist/serviceModule.d.ts.map +1 -1
- package/dist/serviceModule.js +3 -146
- package/dist/serviceModule.js.map +1 -1
- package/dist/serviceScope.d.ts.map +1 -1
- package/dist/serviceScope.js.map +1 -1
- package/dist/serviceSelector.d.ts.map +1 -1
- package/dist/serviceSelector.js.map +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js.map +1 -1
- package/package.json +17 -17
- package/src/errors.ts +2 -10
- package/src/index.ts +7 -9
- package/src/serviceFactory.ts +33 -33
- package/src/serviceKey.ts +11 -4
- package/src/serviceModule.ts +53 -217
- package/src/serviceScope.ts +2 -2
- package/src/serviceSelector.ts +4 -4
- package/src/utils.ts +103 -103
- package/dist/redactingEventListener.d.ts +0 -119
- package/dist/redactingEventListener.d.ts.map +0 -1
- package/dist/redactingEventListener.js +0 -169
- package/dist/redactingEventListener.js.map +0 -1
- package/dist/serviceEventListener.d.ts +0 -133
- package/dist/serviceEventListener.d.ts.map +0 -1
- package/dist/serviceEventListener.js +0 -3
- package/dist/serviceEventListener.js.map +0 -1
- package/dist/serviceModuleListener.d.ts +0 -133
- package/dist/serviceModuleListener.d.ts.map +0 -1
- package/dist/serviceModuleListener.js +0 -3
- package/dist/serviceModuleListener.js.map +0 -1
- package/src/redactingEventListener.ts +0 -256
- package/src/serviceModuleListener.ts +0 -139
package/src/serviceFactory.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { ServiceKey, ServiceSelectorKey } from './serviceKey'
|
|
2
|
-
import { ServiceScope } from './serviceScope'
|
|
3
|
-
import { ServiceSelector } from './serviceSelector'
|
|
1
|
+
import { ServiceKey, ServiceSelectorKey } from './serviceKey'
|
|
2
|
+
import { ServiceScope } from './serviceScope'
|
|
3
|
+
import { ServiceSelector } from './serviceSelector'
|
|
4
4
|
|
|
5
5
|
// Helper types to extract the type from ServiceKey or ServiceSelectorKey
|
|
6
6
|
type ServiceType<T> =
|
|
@@ -8,22 +8,22 @@ type ServiceType<T> =
|
|
|
8
8
|
? ServiceSelector<U>
|
|
9
9
|
: T extends ServiceKey<infer U>
|
|
10
10
|
? U
|
|
11
|
-
: never
|
|
11
|
+
: never
|
|
12
12
|
|
|
13
13
|
// Helper types to convert an array/tuple of ServiceKey to tuple of their types
|
|
14
14
|
type DependencyTypes<T extends readonly ServiceKey<unknown>[]> = {
|
|
15
|
-
[K in keyof T]: ServiceType<T[K]
|
|
16
|
-
}
|
|
15
|
+
[K in keyof T]: ServiceType<T[K]>
|
|
16
|
+
}
|
|
17
17
|
|
|
18
18
|
export abstract class ServiceFactory<
|
|
19
19
|
const T,
|
|
20
20
|
const D extends readonly ServiceKey<unknown>[] = [],
|
|
21
21
|
> {
|
|
22
|
-
abstract provides: ServiceKey<T
|
|
23
|
-
abstract dependsOn: D
|
|
24
|
-
abstract scope?: ServiceScope
|
|
25
|
-
abstract initialize: (...dependencies: DependencyTypes<D>) => T | Promise<T
|
|
26
|
-
abstract dispose?: () => void
|
|
22
|
+
abstract provides: ServiceKey<T>
|
|
23
|
+
abstract dependsOn: D
|
|
24
|
+
abstract scope?: ServiceScope
|
|
25
|
+
abstract initialize: (...dependencies: DependencyTypes<D>) => T | Promise<T>
|
|
26
|
+
abstract dispose?: () => void
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
29
|
* Creates a singleton service factory that ensures a single instance of the provided service is initialized
|
|
@@ -39,14 +39,14 @@ export abstract class ServiceFactory<
|
|
|
39
39
|
initialize,
|
|
40
40
|
dispose = () => {},
|
|
41
41
|
}: {
|
|
42
|
-
scope?: ServiceScope
|
|
43
|
-
provides: ServiceKey<T
|
|
44
|
-
dependsOn?: D
|
|
45
|
-
initialize: (...dependencies: DependencyTypes<D>) => T | Promise<T
|
|
46
|
-
dispose?: (instance: T) => void
|
|
42
|
+
scope?: ServiceScope
|
|
43
|
+
provides: ServiceKey<T>
|
|
44
|
+
dependsOn?: D
|
|
45
|
+
initialize: (...dependencies: DependencyTypes<D>) => T | Promise<T>
|
|
46
|
+
dispose?: (instance: T) => void
|
|
47
47
|
}): ServiceFactory<T, D> {
|
|
48
|
-
let promisedInstance: Promise<T> | undefined
|
|
49
|
-
let resolvedInstance: T | undefined
|
|
48
|
+
let promisedInstance: Promise<T> | undefined
|
|
49
|
+
let resolvedInstance: T | undefined
|
|
50
50
|
|
|
51
51
|
return {
|
|
52
52
|
scope,
|
|
@@ -54,32 +54,32 @@ export abstract class ServiceFactory<
|
|
|
54
54
|
dependsOn,
|
|
55
55
|
async initialize(...dependencies: DependencyTypes<D>): Promise<T> {
|
|
56
56
|
if (resolvedInstance !== undefined) {
|
|
57
|
-
return resolvedInstance
|
|
57
|
+
return resolvedInstance
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
if (promisedInstance !== undefined) {
|
|
61
|
-
return promisedInstance
|
|
61
|
+
return promisedInstance
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
// Store the reference to the promise so that concurrent requests can wait for it
|
|
65
65
|
promisedInstance = (async () => {
|
|
66
66
|
try {
|
|
67
|
-
resolvedInstance = await initialize(...dependencies)
|
|
68
|
-
return resolvedInstance
|
|
67
|
+
resolvedInstance = await initialize(...dependencies)
|
|
68
|
+
return resolvedInstance
|
|
69
69
|
} finally {
|
|
70
|
-
promisedInstance = undefined
|
|
70
|
+
promisedInstance = undefined
|
|
71
71
|
}
|
|
72
|
-
})()
|
|
73
|
-
return promisedInstance
|
|
72
|
+
})()
|
|
73
|
+
return promisedInstance
|
|
74
74
|
},
|
|
75
75
|
dispose(): void {
|
|
76
76
|
if (resolvedInstance !== undefined) {
|
|
77
|
-
dispose(resolvedInstance)
|
|
78
|
-
resolvedInstance = undefined
|
|
77
|
+
dispose(resolvedInstance)
|
|
78
|
+
resolvedInstance = undefined
|
|
79
79
|
}
|
|
80
|
-
promisedInstance = undefined
|
|
80
|
+
promisedInstance = undefined
|
|
81
81
|
},
|
|
82
|
-
}
|
|
82
|
+
}
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
/**
|
|
@@ -91,14 +91,14 @@ export abstract class ServiceFactory<
|
|
|
91
91
|
dependsOn,
|
|
92
92
|
initialize,
|
|
93
93
|
}: {
|
|
94
|
-
provides: ServiceKey<T
|
|
95
|
-
dependsOn: D
|
|
96
|
-
initialize: (...dependencies: DependencyTypes<D>) => T | Promise<T
|
|
94
|
+
provides: ServiceKey<T>
|
|
95
|
+
dependsOn: D
|
|
96
|
+
initialize: (...dependencies: DependencyTypes<D>) => T | Promise<T>
|
|
97
97
|
}): ServiceFactory<T, D> {
|
|
98
98
|
return {
|
|
99
99
|
provides,
|
|
100
100
|
dependsOn,
|
|
101
101
|
initialize,
|
|
102
|
-
}
|
|
102
|
+
}
|
|
103
103
|
}
|
|
104
104
|
}
|
package/src/serviceKey.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// @ts-ignore
|
|
2
|
-
import { ServiceSelector } from './serviceSelector'
|
|
2
|
+
import { ServiceSelector } from './serviceSelector'
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* A typed token used to identify and retrieve a service from a ServiceModule.
|
|
@@ -30,11 +30,18 @@ import { ServiceSelector } from './serviceSelector';
|
|
|
30
30
|
* ```
|
|
31
31
|
*/
|
|
32
32
|
export class ServiceKey<T> {
|
|
33
|
+
/**
|
|
34
|
+
* Phantom field that brands this key with the service type `T`.
|
|
35
|
+
* Exists only at the type level (`declare` emits no runtime code) and
|
|
36
|
+
* prevents keys of different service types from being interchangeable.
|
|
37
|
+
*/
|
|
38
|
+
declare private readonly _type: T
|
|
39
|
+
|
|
33
40
|
/**
|
|
34
41
|
* A unique symbol that identifies this service key.
|
|
35
42
|
* Used internally for identity comparison between keys.
|
|
36
43
|
*/
|
|
37
|
-
public readonly symbol: symbol
|
|
44
|
+
public readonly symbol: symbol
|
|
38
45
|
|
|
39
46
|
/**
|
|
40
47
|
* Creates a new ServiceKey with the given name.
|
|
@@ -42,7 +49,7 @@ export class ServiceKey<T> {
|
|
|
42
49
|
* @param name A human-readable name for the service, used in error messages and debugging.
|
|
43
50
|
*/
|
|
44
51
|
constructor(public readonly name: string) {
|
|
45
|
-
this.symbol = Symbol(name)
|
|
52
|
+
this.symbol = Symbol(name)
|
|
46
53
|
}
|
|
47
54
|
}
|
|
48
55
|
|
|
@@ -90,6 +97,6 @@ export class ServiceSelectorKey<T> extends ServiceKey<ServiceSelector<T>> {
|
|
|
90
97
|
* All keys must be registered in the ServiceModule for dependency validation to pass.
|
|
91
98
|
*/
|
|
92
99
|
constructor(readonly values: ServiceKey<T>[]) {
|
|
93
|
-
super(`ServiceSelector[${values}]`)
|
|
100
|
+
super(`ServiceSelector[${values}]`)
|
|
94
101
|
}
|
|
95
102
|
}
|
package/src/serviceModule.ts
CHANGED
|
@@ -1,15 +1,11 @@
|
|
|
1
|
-
import { ServiceKey, ServiceSelectorKey } from './serviceKey'
|
|
2
|
-
import { ServiceFactory } from './serviceFactory'
|
|
3
|
-
import { ServiceScope } from './serviceScope'
|
|
4
|
-
import { ServiceSelector } from './serviceSelector'
|
|
5
|
-
import { ServiceFactoryNotFoundError, ServiceModuleInitError } from './errors'
|
|
6
|
-
import type {
|
|
7
|
-
EventSpan,
|
|
8
|
-
ServiceModuleListener,
|
|
9
|
-
} from './serviceModuleListener';
|
|
1
|
+
import { ServiceKey, ServiceSelectorKey } from './serviceKey'
|
|
2
|
+
import { ServiceFactory } from './serviceFactory'
|
|
3
|
+
import { ServiceScope } from './serviceScope'
|
|
4
|
+
import { ServiceSelector } from './serviceSelector'
|
|
5
|
+
import { ServiceFactoryNotFoundError, ServiceModuleInitError } from './errors'
|
|
10
6
|
|
|
11
|
-
type GenericFactory = ServiceFactory<unknown, readonly ServiceKey<any>[]
|
|
12
|
-
type GenericKey = ServiceKey<any
|
|
7
|
+
type GenericFactory = ServiceFactory<unknown, readonly ServiceKey<any>[]>
|
|
8
|
+
type GenericKey = ServiceKey<any>
|
|
13
9
|
|
|
14
10
|
/**
|
|
15
11
|
* ServiceModule is a container for service factories and manages dependency resolution.
|
|
@@ -26,10 +22,10 @@ export class ServiceModule {
|
|
|
26
22
|
* @param factories An array of service factories that this module will manage.
|
|
27
23
|
*/
|
|
28
24
|
private constructor(readonly factories: GenericFactory[]) {
|
|
29
|
-
checkCircularDependencies(this.factories)
|
|
25
|
+
checkCircularDependencies(this.factories)
|
|
30
26
|
factories.forEach((factory) => {
|
|
31
|
-
checkMissingDependencies(factory, this.factories)
|
|
32
|
-
})
|
|
27
|
+
checkMissingDependencies(factory, this.factories)
|
|
28
|
+
})
|
|
33
29
|
}
|
|
34
30
|
|
|
35
31
|
/**
|
|
@@ -40,15 +36,15 @@ export class ServiceModule {
|
|
|
40
36
|
* @throws {ServiceFactoryNotFoundError} If no suitable factory is found for the given key.
|
|
41
37
|
*/
|
|
42
38
|
public async get<T>(key: ServiceKey<T>): Promise<T> {
|
|
43
|
-
const factory = this.factories.find((
|
|
44
|
-
return isSuitable(key,
|
|
45
|
-
})
|
|
39
|
+
const factory = this.factories.find((candidate: GenericFactory) => {
|
|
40
|
+
return isSuitable(key, candidate)
|
|
41
|
+
})
|
|
46
42
|
|
|
47
43
|
// Check if a factory to supply the requested key was not found
|
|
48
44
|
if (!factory) {
|
|
49
45
|
throw new ServiceFactoryNotFoundError(
|
|
50
46
|
`Could not find a suitable factory for ${key.name}`,
|
|
51
|
-
)
|
|
47
|
+
)
|
|
52
48
|
}
|
|
53
49
|
|
|
54
50
|
// Resolve all dependencies first
|
|
@@ -56,14 +52,14 @@ export class ServiceModule {
|
|
|
56
52
|
factory.dependsOn.map((dependencyKey: ServiceKey<unknown>) => {
|
|
57
53
|
// If the dependency is a ServiceSelectorKey, create a ServiceSelector instance
|
|
58
54
|
if (dependencyKey instanceof ServiceSelectorKey) {
|
|
59
|
-
return new ServiceSelector(this, dependencyKey)
|
|
55
|
+
return new ServiceSelector(this, dependencyKey)
|
|
60
56
|
}
|
|
61
|
-
return this.get(dependencyKey)
|
|
57
|
+
return this.get(dependencyKey)
|
|
62
58
|
}),
|
|
63
|
-
)
|
|
59
|
+
)
|
|
64
60
|
|
|
65
61
|
// Call the factory to retrieve the dependency
|
|
66
|
-
return factory.initialize(...dependencies)
|
|
62
|
+
return factory.initialize(...dependencies)
|
|
67
63
|
}
|
|
68
64
|
|
|
69
65
|
/**
|
|
@@ -74,12 +70,12 @@ export class ServiceModule {
|
|
|
74
70
|
*/
|
|
75
71
|
public async getOrNull<T>(key: ServiceKey<T>): Promise<T | null> {
|
|
76
72
|
try {
|
|
77
|
-
return await this.get(key)
|
|
73
|
+
return await this.get(key)
|
|
78
74
|
} catch (error) {
|
|
79
75
|
if (error instanceof ServiceFactoryNotFoundError) {
|
|
80
|
-
return null
|
|
76
|
+
return null
|
|
81
77
|
}
|
|
82
|
-
throw error
|
|
78
|
+
throw error
|
|
83
79
|
}
|
|
84
80
|
}
|
|
85
81
|
|
|
@@ -96,9 +92,9 @@ export class ServiceModule {
|
|
|
96
92
|
public dispose(scope?: ServiceScope) {
|
|
97
93
|
const factories = scope
|
|
98
94
|
? this.factories.filter((f) => f.scope === scope)
|
|
99
|
-
: this.factories
|
|
95
|
+
: this.factories
|
|
100
96
|
|
|
101
|
-
factories.forEach((factory) => factory.dispose?.())
|
|
97
|
+
factories.forEach((factory) => factory.dispose?.())
|
|
102
98
|
}
|
|
103
99
|
|
|
104
100
|
/**
|
|
@@ -107,44 +103,24 @@ export class ServiceModule {
|
|
|
107
103
|
* If multiple factories provide the same
|
|
108
104
|
* ServiceKey, the last one in the list takes precedence.
|
|
109
105
|
*
|
|
110
|
-
* When a listener is provided, every factory is wrapped with instrumentation:
|
|
111
|
-
* the listener is notified when a service is initialized or disposed and when
|
|
112
|
-
* a method is called on a service instance, and may return an EventSpan per
|
|
113
|
-
* operation to observe its completion. Service instances are wrapped in a
|
|
114
|
-
* Proxy to observe method calls, and errors are rethrown after being
|
|
115
|
-
* reported, so behavior is otherwise unchanged.
|
|
116
|
-
*
|
|
117
106
|
* @param entries - An array of ServiceModule or GenericFactory
|
|
118
107
|
* instances to be processed into a single ServiceModule.
|
|
119
|
-
* @param listener - An optional ServiceEventListener notified of service
|
|
120
|
-
* lifecycle events and method calls in the resulting module.
|
|
121
108
|
* @return A new ServiceModule containing the deduplicated factories.
|
|
122
109
|
* @throws {ServiceModuleInitError} If circular or missing dependencies are detected during module creation.
|
|
123
110
|
*/
|
|
124
|
-
static from(
|
|
125
|
-
entries: (ServiceModule | GenericFactory)[],
|
|
126
|
-
listener?: ServiceModuleListener,
|
|
127
|
-
): ServiceModule {
|
|
111
|
+
static from(entries: (ServiceModule | GenericFactory)[]): ServiceModule {
|
|
128
112
|
// Flatten entries and keep only the last factory for each ServiceKey
|
|
129
113
|
const flattened = entries.flatMap((e) =>
|
|
130
114
|
e instanceof ServiceModule ? e.factories : [e],
|
|
131
|
-
)
|
|
115
|
+
)
|
|
132
116
|
|
|
133
|
-
const byKey = new Map<symbol, GenericFactory>()
|
|
117
|
+
const byKey = new Map<symbol, GenericFactory>()
|
|
134
118
|
// Later factories overwrite earlier ones (last-wins)
|
|
135
119
|
for (const f of flattened) {
|
|
136
|
-
byKey.set(f.provides.symbol, f)
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
if (listener) {
|
|
140
|
-
return new ServiceModule(
|
|
141
|
-
Array.from(byKey.values()).map((factory) => {
|
|
142
|
-
return makeObservable(listener, factory);
|
|
143
|
-
}),
|
|
144
|
-
);
|
|
120
|
+
byKey.set(f.provides.symbol, f)
|
|
145
121
|
}
|
|
146
122
|
|
|
147
|
-
return new ServiceModule(Array.from(byKey.values()))
|
|
123
|
+
return new ServiceModule(Array.from(byKey.values()))
|
|
148
124
|
}
|
|
149
125
|
}
|
|
150
126
|
|
|
@@ -155,48 +131,48 @@ export class ServiceModule {
|
|
|
155
131
|
* @throws {ServiceModuleInitError} If a circular dependency is detected.
|
|
156
132
|
*/
|
|
157
133
|
function checkCircularDependencies(factories: GenericFactory[]) {
|
|
158
|
-
const factoryMap = new Map<symbol, GenericFactory>()
|
|
134
|
+
const factoryMap = new Map<symbol, GenericFactory>()
|
|
159
135
|
for (const f of factories) {
|
|
160
|
-
factoryMap.set(f.provides.symbol, f)
|
|
136
|
+
factoryMap.set(f.provides.symbol, f)
|
|
161
137
|
}
|
|
162
138
|
|
|
163
|
-
const visited = new Set<symbol>()
|
|
164
|
-
const stack = new Set<symbol>()
|
|
139
|
+
const visited = new Set<symbol>()
|
|
140
|
+
const stack = new Set<symbol>()
|
|
165
141
|
|
|
166
142
|
function walk(factory: GenericFactory, path: string[]) {
|
|
167
|
-
const symbol = factory.provides.symbol
|
|
143
|
+
const symbol = factory.provides.symbol
|
|
168
144
|
|
|
169
145
|
if (stack.has(symbol)) {
|
|
170
|
-
const cyclePath = [...path, factory.provides.name].join(' -> ')
|
|
146
|
+
const cyclePath = [...path, factory.provides.name].join(' -> ')
|
|
171
147
|
throw new ServiceModuleInitError(
|
|
172
148
|
`Circular dependency detected: ${cyclePath}`,
|
|
173
|
-
)
|
|
149
|
+
)
|
|
174
150
|
}
|
|
175
151
|
|
|
176
152
|
if (visited.has(symbol)) {
|
|
177
|
-
return
|
|
153
|
+
return
|
|
178
154
|
}
|
|
179
155
|
|
|
180
|
-
visited.add(symbol)
|
|
181
|
-
stack.add(symbol)
|
|
156
|
+
visited.add(symbol)
|
|
157
|
+
stack.add(symbol)
|
|
182
158
|
|
|
183
159
|
for (const depKey of factory.dependsOn) {
|
|
184
160
|
const keysToCheck =
|
|
185
|
-
depKey instanceof ServiceSelectorKey ? depKey.values : [depKey]
|
|
161
|
+
depKey instanceof ServiceSelectorKey ? depKey.values : [depKey]
|
|
186
162
|
|
|
187
163
|
for (const key of keysToCheck) {
|
|
188
|
-
const depFactory = factoryMap.get(key.symbol)
|
|
164
|
+
const depFactory = factoryMap.get(key.symbol)
|
|
189
165
|
if (depFactory) {
|
|
190
|
-
walk(depFactory, [...path, factory.provides.name])
|
|
166
|
+
walk(depFactory, [...path, factory.provides.name])
|
|
191
167
|
}
|
|
192
168
|
}
|
|
193
169
|
}
|
|
194
170
|
|
|
195
|
-
stack.delete(symbol)
|
|
171
|
+
stack.delete(symbol)
|
|
196
172
|
}
|
|
197
173
|
|
|
198
174
|
for (const factory of factories) {
|
|
199
|
-
walk(factory, [])
|
|
175
|
+
walk(factory, [])
|
|
200
176
|
}
|
|
201
177
|
}
|
|
202
178
|
|
|
@@ -211,31 +187,31 @@ function checkMissingDependencies(
|
|
|
211
187
|
factory: GenericFactory,
|
|
212
188
|
factories: GenericFactory[],
|
|
213
189
|
) {
|
|
214
|
-
const missingDependencies: GenericKey[] = []
|
|
190
|
+
const missingDependencies: GenericKey[] = []
|
|
215
191
|
|
|
216
192
|
factory.dependsOn.forEach((dependencyKey: GenericKey) => {
|
|
217
193
|
// For ServiceSelectorKey, check all contained keys are registered
|
|
218
194
|
if (dependencyKey instanceof ServiceSelectorKey) {
|
|
219
195
|
dependencyKey.values.forEach((key) => {
|
|
220
196
|
if (!isRegistered(key, factories)) {
|
|
221
|
-
missingDependencies.push(key)
|
|
197
|
+
missingDependencies.push(key)
|
|
222
198
|
}
|
|
223
|
-
})
|
|
199
|
+
})
|
|
224
200
|
} else if (!isRegistered(dependencyKey, factories)) {
|
|
225
|
-
missingDependencies.push(dependencyKey)
|
|
201
|
+
missingDependencies.push(dependencyKey)
|
|
226
202
|
}
|
|
227
|
-
})
|
|
203
|
+
})
|
|
228
204
|
|
|
229
205
|
if (missingDependencies.length === 0) {
|
|
230
|
-
return
|
|
206
|
+
return
|
|
231
207
|
}
|
|
232
208
|
|
|
233
209
|
const dependencyList = missingDependencies
|
|
234
210
|
.map((dependencyKey) => ` -> ${dependencyKey.name}`)
|
|
235
|
-
.join('\n')
|
|
211
|
+
.join('\n')
|
|
236
212
|
throw new ServiceModuleInitError(
|
|
237
213
|
`${factory.provides.name} will fail because it depends on:\n ${dependencyList}`,
|
|
238
|
-
)
|
|
214
|
+
)
|
|
239
215
|
}
|
|
240
216
|
|
|
241
217
|
/**
|
|
@@ -246,7 +222,7 @@ function checkMissingDependencies(
|
|
|
246
222
|
* @returns True if a factory provides the given key, false otherwise.
|
|
247
223
|
*/
|
|
248
224
|
function isRegistered(key: GenericKey, factories: GenericFactory[]) {
|
|
249
|
-
return factories.some((factory) => factory.provides?.symbol === key?.symbol)
|
|
225
|
+
return factories.some((factory) => factory.provides?.symbol === key?.symbol)
|
|
250
226
|
}
|
|
251
227
|
|
|
252
228
|
/**
|
|
@@ -260,145 +236,5 @@ function isSuitable<T, D extends readonly ServiceKey<any>[]>(
|
|
|
260
236
|
key: ServiceKey<T>,
|
|
261
237
|
factory: ServiceFactory<any, D>,
|
|
262
238
|
): factory is ServiceFactory<T, D> {
|
|
263
|
-
return factory?.provides?.symbol === key?.symbol
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
/**
|
|
267
|
-
* Wraps a given service factory with instrumentation to notify a listener of
|
|
268
|
-
* lifecycle events and method calls.
|
|
269
|
-
*
|
|
270
|
-
* For each of initialize, dispose, and method calls, the listener is invoked
|
|
271
|
-
* at the start of the operation and may return an EventSpan whose `end` is
|
|
272
|
-
* called with the outcome when the operation finishes. Errors are rethrown
|
|
273
|
-
* after being reported.
|
|
274
|
-
*
|
|
275
|
-
* @param listener The listener notified of lifecycle and method call events.
|
|
276
|
-
* @param delegate The original service factory to be instrumented.
|
|
277
|
-
* @return A new service factory that provides the same dependencies but includes event notification logic.
|
|
278
|
-
*/
|
|
279
|
-
function makeObservable<T, D extends readonly ServiceKey<any>[]>(
|
|
280
|
-
listener: ServiceModuleListener,
|
|
281
|
-
delegate: ServiceFactory<any, D>,
|
|
282
|
-
): ServiceFactory<T, D> {
|
|
283
|
-
const key = delegate.provides;
|
|
284
|
-
|
|
285
|
-
return ServiceFactory.singleton({
|
|
286
|
-
scope: delegate.scope,
|
|
287
|
-
provides: delegate.provides,
|
|
288
|
-
dependsOn: delegate.dependsOn,
|
|
289
|
-
dispose: () => {
|
|
290
|
-
const dispose = delegate.dispose;
|
|
291
|
-
if (dispose) {
|
|
292
|
-
const span = listener.onDispose?.({ key });
|
|
293
|
-
try {
|
|
294
|
-
invokeWithin(span, dispose);
|
|
295
|
-
} catch (error) {
|
|
296
|
-
span?.end?.({ type: 'failure', error });
|
|
297
|
-
throw error;
|
|
298
|
-
}
|
|
299
|
-
span?.end?.({ type: 'success', value: undefined });
|
|
300
|
-
}
|
|
301
|
-
},
|
|
302
|
-
initialize: async (...args) => {
|
|
303
|
-
const span = listener.onInitialize?.({ key });
|
|
304
|
-
try {
|
|
305
|
-
const instance = observeMethodCalls(
|
|
306
|
-
await invokeWithin(span, () => delegate.initialize(...args)),
|
|
307
|
-
listener,
|
|
308
|
-
key,
|
|
309
|
-
);
|
|
310
|
-
span?.end?.({ type: 'success', value: instance });
|
|
311
|
-
return instance;
|
|
312
|
-
} catch (error) {
|
|
313
|
-
span?.end?.({ type: 'failure', error });
|
|
314
|
-
throw error;
|
|
315
|
-
}
|
|
316
|
-
},
|
|
317
|
-
});
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
/**
|
|
321
|
-
* Wraps an object with a Proxy to notify the listener of method calls.
|
|
322
|
-
*
|
|
323
|
-
* Methods returning a promise report their outcome when the promise
|
|
324
|
-
* settles, not when the method returns.
|
|
325
|
-
*
|
|
326
|
-
* @param thing The object whose method calls need to be observed.
|
|
327
|
-
* @param listener The listener notified of method call events.
|
|
328
|
-
* @param key The service key used to identify the service in events.
|
|
329
|
-
* @return A Proxy wrapping the input object, with all method calls being reported.
|
|
330
|
-
*/
|
|
331
|
-
function observeMethodCalls(
|
|
332
|
-
thing: any,
|
|
333
|
-
listener: ServiceModuleListener,
|
|
334
|
-
key: ServiceKey<unknown>,
|
|
335
|
-
): any {
|
|
336
|
-
if (typeof thing !== 'object' || thing === null) {
|
|
337
|
-
return thing;
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
const className = classNameOf(thing);
|
|
341
|
-
|
|
342
|
-
return new Proxy(thing, {
|
|
343
|
-
get(target, prop) {
|
|
344
|
-
const value = Reflect.get(target, prop);
|
|
345
|
-
if (typeof value === 'function' && typeof prop === 'string') {
|
|
346
|
-
return (...args: unknown[]) => {
|
|
347
|
-
const span = listener.onMethodCall?.({
|
|
348
|
-
key,
|
|
349
|
-
className,
|
|
350
|
-
functionName: prop,
|
|
351
|
-
args,
|
|
352
|
-
});
|
|
353
|
-
try {
|
|
354
|
-
const result = invokeWithin(span, () => value.apply(target, args));
|
|
355
|
-
if (result instanceof Promise) {
|
|
356
|
-
return result.then(
|
|
357
|
-
(resolved) => {
|
|
358
|
-
span?.end?.({ type: 'success', value: resolved });
|
|
359
|
-
return resolved;
|
|
360
|
-
},
|
|
361
|
-
(error) => {
|
|
362
|
-
span?.end?.({ type: 'failure', error });
|
|
363
|
-
throw error;
|
|
364
|
-
},
|
|
365
|
-
);
|
|
366
|
-
}
|
|
367
|
-
span?.end?.({ type: 'success', value: result });
|
|
368
|
-
return result;
|
|
369
|
-
} catch (error) {
|
|
370
|
-
span?.end?.({ type: 'failure', error });
|
|
371
|
-
throw error;
|
|
372
|
-
}
|
|
373
|
-
};
|
|
374
|
-
}
|
|
375
|
-
return value;
|
|
376
|
-
},
|
|
377
|
-
});
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
/**
|
|
381
|
-
* Invokes an operation through the EventSpan's `run` wrapper when the
|
|
382
|
-
* listener provided one, so it can establish ambient state (tracing
|
|
383
|
-
* context) around the operation; invokes the operation directly otherwise.
|
|
384
|
-
*
|
|
385
|
-
* @param span The EventSpan returned by the listener, if any.
|
|
386
|
-
* @param fn The thunk performing the operation.
|
|
387
|
-
* @returns The value returned by `fn`.
|
|
388
|
-
*/
|
|
389
|
-
function invokeWithin<T>(span: EventSpan | void, fn: () => T): T {
|
|
390
|
-
return span?.run ? span.run(fn) : fn();
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
/**
|
|
394
|
-
* Resolves the class name of a service instance, or undefined for values
|
|
395
|
-
* that are not instances of a named class (plain object literals,
|
|
396
|
-
* null-prototype objects).
|
|
397
|
-
*
|
|
398
|
-
* @param thing The service instance to inspect.
|
|
399
|
-
* @returns The constructor name, or undefined when there is none to report.
|
|
400
|
-
*/
|
|
401
|
-
function classNameOf(thing: object): string | undefined {
|
|
402
|
-
const name = thing.constructor?.name;
|
|
403
|
-
return name && name !== 'Object' ? name : undefined;
|
|
239
|
+
return factory?.provides?.symbol === key?.symbol
|
|
404
240
|
}
|
package/src/serviceScope.ts
CHANGED
package/src/serviceSelector.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { ServiceKey, ServiceSelectorKey } from './serviceKey'
|
|
2
|
-
import { ServiceModule } from './serviceModule'
|
|
1
|
+
import { ServiceKey, ServiceSelectorKey } from './serviceKey'
|
|
2
|
+
import { ServiceModule } from './serviceModule'
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* A runtime selector that provides access to multiple service implementations of the same type.
|
|
@@ -63,6 +63,6 @@ export class ServiceSelector<T> {
|
|
|
63
63
|
* ```
|
|
64
64
|
*/
|
|
65
65
|
get(key: ServiceKey<T>): Promise<T> {
|
|
66
|
-
return this.serviceModule.get(key)
|
|
66
|
+
return this.serviceModule.get(key)
|
|
67
67
|
}
|
|
68
|
-
}
|
|
68
|
+
}
|