• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

microsoft / botbuilder-js / 6469114986

10 Oct 2023 12:15PM UTC coverage: 84.766% (-0.1%) from 84.903%
6469114986

Pull #4543

github

web-flow
Merge a1e0e4e49 into 123cc8831
Pull Request #4543: fix: [#2782] Migrate to MSAL from adal-node - Add MSAL support

9959 of 13006 branches covered (0.0%)

Branch coverage included in aggregate %.

46 of 46 new or added lines in 4 files covered. (100.0%)

20271 of 22657 relevant lines covered (89.47%)

7159.51 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

69.57
/libraries/botbuilder-dialogs-adaptive-runtime-core/src/serviceCollection.ts
1
// Copyright (c) Microsoft Corporation.
2
// Licensed under the MIT License.
3

4
import assert from 'assert';
2✔
5
import { DepGraph } from 'dependency-graph';
2✔
6
import { ok } from 'assert';
2✔
7
import { stringify } from './util';
2✔
8

9
/**
10
 * Factory describes a generic factory function signature. The type is generic over a few parameters:
11
 *
12
 * @template Type type the factory produces
13
 * @template Initial true if the `initialValue` passed to the factory must be defined
14
 */
15
export type Factory<Type, Initial extends boolean> = (
16
    initialValue: Initial extends true ? Type : Type | undefined
17
) => Type;
18

19
/**
20
 * DependencyFactory is a function signature that produces an instance that depends on a set of
21
 * other services. The type is generic over a few parameters:
22
 *
23
 * @template Type type the factory produces
24
 * @template Dependencies the services this factory function depends on
25
 * @template Initial true if the `initialValue` passed to the factory must be defined
26
 */
27
export type DependencyFactory<Type, Dependencies, Initial extends boolean> = (
28
    dependencies: Dependencies,
29
    initialValue: Initial extends true ? Type : Type | undefined
30
) => Type;
31

32
/**
33
 * ServiceCollection is an interface that describes a set of methods to register services. This, in a lighter way,
34
 * mimics the .NET dependency injection service collection functionality, except for instances rather than types.
35
 */
36
export class ServiceCollection {
2✔
37
    // We store the full set of dependencies as a workaround to the fact that `DepGraph` throws an error if you
38
    // attempt to register a dependency to a node that does not yet exist.
39
    private readonly dependencies = new Map<string, string[]>();
13,462✔
40

41
    /**
42
     * `DepGraph` is a dependency graph data structure. In our case, the services we support are encoded as a
43
     * dependency graph where nodes are named with a key and store a list of factory methods.
44
     */
45
    private readonly graph = new DepGraph<Array<DependencyFactory<unknown, Record<string, unknown>, true>>>();
13,462✔
46

47
    /**
48
     * Cache constructed instances for reuse
49
     */
50
    private cache: Record<string, unknown> = {};
13,462✔
51

52
    /**
53
     * Construct a Providers instance
54
     *
55
     * @template S services interface
56
     * @param defaultServices default set of services
57
     */
58
    constructor(defaultServices: Record<string, unknown> = {}) {
×
59
        Object.entries(defaultServices).forEach(([key, instance]) => {
13,462✔
60
            this.addInstance(key, instance);
26,820✔
61
        });
62
    }
63

64
    /**
65
     * Register an instance by key. This will overwrite existing instances.
66
     *
67
     * @param key key of the instance being provided
68
     * @param instance instance to provide
69
     * @returns this for chaining
70
     */
71
    addInstance<InstanceType>(key: string, instance: InstanceType): this {
72
        if (this.graph.hasNode(key)) {
26,820!
73
            this.graph.removeNode(key);
×
74
        }
75

76
        this.graph.addNode(key, [() => instance]);
26,820✔
77
        return this;
26,820✔
78
    }
79

80
    /**
81
     * Register a factory for a key.
82
     *
83
     * @param key key that factory will provide
84
     * @param factory function that creates an instance to provide
85
     * @returns this for chaining
86
     */
87
    addFactory<InstanceType>(key: string, factory: Factory<InstanceType, false>): this;
88

89
    /**
90
     * Register a factory for a key with a set of dependencies.
91
     *
92
     * @param key key that factory will provide
93
     * @param dependencies set of things this instance depends on. Will be provided to factory function via `services`.
94
     * @param factory function that creates an instance to provide
95
     * @returns this for chaining
96
     */
97
    addFactory<InstanceType, Dependencies>(
98
        key: string,
99
        dependencies: string[],
100
        factory: DependencyFactory<InstanceType, Dependencies, false>
101
    ): this;
102

103
    /**
104
     * @internal
105
     */
106
    addFactory<InstanceType, Dependencies>(
107
        key: string,
108
        depsOrFactory: string[] | Factory<InstanceType, false>,
109
        maybeFactory?: DependencyFactory<InstanceType, Dependencies, false>
110
    ): this {
111
        const dependencies = Array.isArray(depsOrFactory) ? depsOrFactory : undefined;
26,936!
112

113
        let factory: DependencyFactory<InstanceType, Dependencies, false> | undefined = maybeFactory;
26,936✔
114
        if (!factory && typeof depsOrFactory === 'function') {
26,936!
115
            factory = (_services, value) => depsOrFactory(value);
26,936✔
116
        }
117

118
        // Asserts factory is not undefined
119
        ok(factory, 'illegal invocation with undefined factory');
26,936✔
120

121
        if (dependencies) {
26,936!
122
            this.dependencies.set(key, dependencies);
×
123
        }
124

125
        // If the graph already has this key, fetch its data and remove it (to be replaced)
126
        let factories: unknown[] = [];
26,936✔
127
        if (this.graph.hasNode(key)) {
26,936!
128
            factories = this.graph.getNodeData(key);
26,936✔
129
            this.graph.removeNode(key);
26,936✔
130
        }
131

132
        // Note: we have done the type checking above, so disabling no-explicit-any is okay.
133
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
134
        this.graph.addNode(key, factories.concat(factory) as any);
26,936✔
135

136
        return this;
26,936✔
137
    }
138

139
    /**
140
     * Register a factory (that expects the initial value that is not undefined) for a key.
141
     *
142
     * @param key key of the instance being provided
143
     * @param instance instance to provide
144
     * @returns this for chaining
145
     */
146
    composeFactory<InstanceType>(key: string, factory: Factory<InstanceType, true>): this;
147

148
    /**
149
     * Register a factory (that expects an initial value that is not undefined) for a key
150
     * with a set of dependencies.
151
     *
152
     * @param key key that factory will provide
153
     * @param dependencies set of things this instance depends on. Will be provided to factory function via `services`.
154
     * @param factory function that creates an instance to provide
155
     * @returns this for chaining
156
     */
157
    composeFactory<InstanceType, Dependencies>(
158
        key: string,
159
        dependencies: string[],
160
        factory: DependencyFactory<InstanceType, Dependencies, true>
161
    ): this;
162

163
    /**
164
     * @internal
165
     */
166
    composeFactory<InstanceType, Dependencies>(
167
        key: string,
168
        depsOrFactory: string[] | Factory<InstanceType, true>,
169
        maybeFactory?: DependencyFactory<InstanceType, Dependencies, true>
170
    ): this {
171
        if (maybeFactory) {
26,936!
172
            return this.addFactory<InstanceType, Dependencies>(
×
173
                key,
174
                Array.isArray(depsOrFactory) ? depsOrFactory : [],
×
175
                (dependencies, value) => {
176
                    ok(value, `unable to create ${key}, initial value undefined`);
×
177

178
                    return maybeFactory(dependencies, value);
×
179
                }
180
            );
181
        } else {
182
            ok(typeof depsOrFactory === 'function', 'illegal invocation with undefined factory');
26,936✔
183

184
            return this.addFactory<InstanceType>(key, (value) => {
26,936✔
185
                ok(value, `unable to create ${key}, initial value undefined`);
7,116✔
186

187
                return depsOrFactory(value);
7,116✔
188
            });
189
        }
190
    }
191

192
    // Register dependencies and then build nodes. Note: `nodes` is a function because ordering may
193
    // depend on results of dependency registration
194
    private buildNodes<ReturnType = Record<string, unknown>>(
195
        generateNodes: () => string[],
196
        reuseServices: Record<string, unknown> = {}
×
197
    ): ReturnType {
198
        // Consume all dependencies and then reset so updating registrations without re-registering
199
        // dependencies works
200
        this.dependencies.forEach((dependencies, node) =>
7,000✔
201
            dependencies.forEach((dependency) => this.graph.addDependency(node, stringify(dependency)))
×
202
        );
203

204
        // Generate nodes after registering dependencies so ordering is correct
205
        const nodes = generateNodes();
7,000✔
206

207
        const services = nodes.reduce((services, service) => {
7,000✔
208
            // Extra precaution
209
            if (!this.graph.hasNode(service)) {
7,000!
210
                return services;
×
211
            }
212

213
            // Helper to generate return value
214
            const assignValue = (value: unknown) => ({
7,000✔
215
                ...services,
216
                [service]: value,
217
            });
218

219
            // Optionally reuse existing service
220
            const reusedService = reuseServices[service];
7,000✔
221
            if (reusedService !== undefined) {
7,000!
222
                return assignValue(reusedService);
×
223
            }
224

225
            // Each node stores a list of factory methods.
226
            const factories = this.graph.getNodeData(service);
7,000✔
227

228
            // Produce the instance by reducing those factories, passing the instance along for composition.
229
            const instance = factories.reduce((value, factory) => factory(services, value), <unknown>services[service]);
14,116✔
230

231
            return assignValue(instance);
7,000✔
232
        }, <Record<string, unknown>>{});
233

234
        // Cache results for subsequent invocations that may desire pre-constructed instances
235
        Object.assign(this.cache, services);
7,000✔
236

237
        return services as ReturnType;
7,000✔
238
    }
239

240
    /**
241
     * Build a single service.
242
     *
243
     * @param key service to build
244
     * @param deep reconstruct all dependencies
245
     * @returns the service instance, or undefined
246
     */
247
    makeInstance<InstanceType = unknown>(key: string, deep = false): InstanceType | undefined {
×
248
        // If this is not a deep reconstruction, reuse any services that `key` depends on
249
        let initialServices: Record<string, unknown> | undefined;
250
        if (!deep) {
7,000!
251
            const { [key]: _, ...cached } = this.cache;
7,000!
252
            initialServices = cached;
7,000✔
253
        }
254

255
        const services = this.buildNodes<Record<string, InstanceType | undefined>>(
7,000✔
256
            () => this.graph.dependenciesOf(key).concat(key),
7,000✔
257
            initialServices
258
        );
259

260
        return services[key];
7,000✔
261
    }
262

263
    /**
264
     * Build a single service and assert that it is not undefined.
265
     *
266
     * @param key service to build
267
     * @param deep reconstruct all dependencies
268
     * @returns the service instance
269
     */
270
    mustMakeInstance<InstanceType = unknown>(key: string, deep = false): InstanceType {
7,000✔
271
        const instance = this.makeInstance<InstanceType>(key, deep);
7,000✔
272
        assert.ok(instance, `\`${key}\` instance undefined!`);
7,000✔
273

274
        return instance;
7,000✔
275
    }
276

277
    /**
278
     * Build the full set of services.
279
     *
280
     * @returns all resolved services
281
     */
282
    makeInstances<InstancesType>(): InstancesType {
283
        return this.buildNodes<InstancesType>(() => this.graph.overallOrder());
×
284
    }
285

286
    /**
287
     * Build the full set of services, asserting that the specified keys are not undefined.
288
     *
289
     * @param keys instances that must be not undefined
290
     * @returns all resolve services
291
     */
292
    mustMakeInstances<InstancesType extends Record<string, unknown> = Record<string, unknown>>(
293
        ...keys: string[]
294
    ): InstancesType {
295
        const instances = this.makeInstances<InstancesType>();
×
296

297
        keys.forEach((key) => {
×
298
            assert.ok(instances[key], `\`${key}\` instance undefined!`);
×
299
        });
300

301
        return instances;
×
302
    }
303
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc