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

microsoft / botbuilder-js / 12259066809

10 Dec 2024 03:12PM UTC coverage: 84.625% (+0.3%) from 84.277%
12259066809

push

github

web-flow
fix: [#4684] ESLint issues in botbuilder-dialogs-adaptive-runtime-integration libraries (#4811)

* Fix issues in runtime-integration-azure-functions

* Fix issues in runtime-integration-express

* Fix issues in runtime-integration-restify

8185 of 10821 branches covered (75.64%)

Branch coverage included in aggregate %.

20513 of 23091 relevant lines covered (88.84%)

7378.25 hits per line

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

131
        this.graph.addNode(key, factories.concat(factory) as any);
26,988✔
132

133
        return this;
26,988✔
134
    }
135

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

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

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

175
                    return maybeFactory(dependencies, value);
×
176
                },
177
            );
178
        } else {
179
            ok(typeof depsOrFactory === 'function', 'illegal invocation with undefined factory');
26,988✔
180

181
            return this.addFactory<InstanceType>(key, (value) => {
26,988✔
182
                ok(value, `unable to create ${key}, initial value undefined`);
7,128✔
183

184
                return depsOrFactory(value);
7,128✔
185
            });
186
        }
187
    }
188

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

201
        // Generate nodes after registering dependencies so ordering is correct
202
        const nodes = generateNodes();
7,012✔
203

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

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

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

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

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

232
                return assignValue(instance);
7,012✔
233
            },
234
            <Record<string, unknown>>{},
235
        );
236

237
        // Cache results for subsequent invocations that may desire pre-constructed instances
238
        Object.assign(this.cache, services);
7,012✔
239

240
        return services as ReturnType;
7,012✔
241
    }
242

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

258
        const services = this.buildNodes<Record<string, InstanceType | undefined>>(
7,012✔
259
            () => this.graph.dependenciesOf(key).concat(key),
7,012✔
260
            initialServices,
261
        );
262

263
        return services[key];
7,012✔
264
    }
265

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

277
        return instance;
7,012✔
278
    }
279

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

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

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

304
        return instances;
×
305
    }
306
}
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

© 2026 Coveralls, Inc