backend-{plugin,app}-api: introduce service scopes and update registry to support

Co-authored-by: Fredrik Adelöw <freben@gmail.com>
Co-authored-by: blam <ben@blam.sh>
Co-authored-by: Johan Haals <johan.haals@gmail.com>
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2022-09-08 15:46:16 +02:00
parent 541ea937d2
commit 229a1fa927
4 changed files with 284 additions and 194 deletions
@@ -21,28 +21,28 @@ import {
} from '@backstage/backend-plugin-api';
import { ServiceRegistry } from './ServiceRegistry';
const ref1 = createServiceRef<{ x: number; pluginId: string }>({
const ref1 = createServiceRef<{ x: number }>({
id: '1',
});
const sf1 = createServiceFactory({
service: ref1,
deps: {},
factory: async () => {
return async pluginId => {
return { x: 1, pluginId };
return async () => {
return { x: 1 };
};
},
});
const ref2 = createServiceRef<{ x: number; pluginId: string }>({
const ref2 = createServiceRef<{ x: number }>({
id: '2',
});
const sf2 = createServiceFactory({
service: ref2,
deps: {},
factory: async () => {
return async pluginId => {
return { x: 2, pluginId };
return async () => {
return { x: 2 };
};
},
});
@@ -50,126 +50,115 @@ const sf2b = createServiceFactory({
service: ref2,
deps: {},
factory: async () => {
return async pluginId => {
return { x: 22, pluginId };
return async () => {
return { x: 22 };
};
},
});
const refDefault1 = createServiceRef<{ x: number; pluginId: string }>({
const refDefault1 = createServiceRef<{ x: number }>({
id: '1',
defaultFactory: async service =>
createServiceFactory({
service,
deps: {},
factory: async () => async pluginId => ({ x: 10, pluginId }),
factory: async () => async () => ({ x: 10 }),
}),
});
const refDefault2a = createServiceRef<{ x: number; pluginId: string }>({
const refDefault2a = createServiceRef<{ x: number }>({
id: '2a',
defaultFactory: async service =>
createServiceFactory({
service,
deps: {},
factory: async () => async pluginId => ({ x: 20, pluginId }),
factory: async () => async () => ({ x: 20 }),
}),
});
const refDefault2b = createServiceRef<{ x: number; pluginId: string }>({
const refDefault2b = createServiceRef<{ x: number }>({
id: '2b',
defaultFactory: async service =>
createServiceFactory({
service,
deps: {},
factory: async () => async pluginId => ({ x: 220, pluginId }),
factory: async () => async () => ({ x: 220 }),
}),
});
describe('ServiceRegistry', () => {
it('should return undefined if there is no factory defined', async () => {
const registry = new ServiceRegistry([]);
expect(registry.get(ref1)).toBe(undefined);
expect(registry.get(ref1, 'catalog')).toBe(undefined);
});
it('should return a factory for a registered ref', async () => {
it('should return an implementation for a registered ref', async () => {
const registry = new ServiceRegistry([sf1]);
const factory = registry.get(ref1)!;
expect(factory).toEqual(expect.any(Function));
await expect(factory('catalog')).resolves.toEqual({
x: 1,
pluginId: 'catalog',
});
await expect(factory('scaffolder')).resolves.toEqual({
x: 1,
pluginId: 'scaffolder',
});
expect(await factory('catalog')).toBe(await factory('catalog'));
await expect(registry.get(ref1, 'catalog')).resolves.toEqual({ x: 1 });
await expect(registry.get(ref1, 'scaffolder')).resolves.toEqual({ x: 1 });
expect(await registry.get(ref1, 'catalog')).toBe(
await registry.get(ref1, 'catalog'),
);
expect(await registry.get(ref1, 'scaffolder')).toBe(
await registry.get(ref1, 'scaffolder'),
);
expect(await registry.get(ref1, 'catalog')).not.toBe(
await registry.get(ref1, 'scaffolder'),
);
});
it('should handle multiple factories with different serviceRefs', async () => {
const registry = new ServiceRegistry([sf1, sf2]);
const factory1 = registry.get(ref1)!;
const factory2 = registry.get(ref2)!;
expect(factory1).toEqual(expect.any(Function));
expect(factory2).toEqual(expect.any(Function));
await expect(factory1('catalog')).resolves.toEqual({
await expect(registry.get(ref1, 'catalog')).resolves.toEqual({
x: 1,
pluginId: 'catalog',
});
await expect(factory2('catalog')).resolves.toEqual({
await expect(registry.get(ref2, 'catalog')).resolves.toEqual({
x: 2,
pluginId: 'catalog',
});
expect(await factory1('catalog')).not.toBe(await factory2('catalog'));
expect(await registry.get(ref1, 'catalog')).not.toBe(
await registry.get(ref2, 'catalog'),
);
});
it('should use the last factory for each ref', async () => {
const registry = new ServiceRegistry([sf2, sf2b]);
const factory2 = registry.get(ref2)!;
await expect(factory2('catalog')).resolves.toEqual({
await expect(registry.get(ref2, 'catalog')).resolves.toEqual({
x: 22,
pluginId: 'catalog',
});
});
it('should return the defaultFactory from the ref if not provided to the registry', async () => {
it('should use the defaultFactory from the ref if not provided to the registry', async () => {
const registry = new ServiceRegistry([]);
const factory = registry.get(refDefault1)!;
expect(factory).toEqual(expect.any(Function));
await expect(factory('catalog')).resolves.toEqual({
await expect(registry.get(refDefault1, 'catalog')).resolves.toEqual({
x: 10,
pluginId: 'catalog',
});
});
it('should not return the defaultFactory from the ref if provided to the registry', async () => {
it('should not use the defaultFactory from the ref if provided to the registry', async () => {
const registry = new ServiceRegistry([sf1]);
const factory = registry.get(refDefault1)!;
expect(factory).toEqual(expect.any(Function));
await expect(factory('catalog')).resolves.toEqual({
await expect(registry.get(refDefault1, 'catalog')).resolves.toEqual({
x: 1,
pluginId: 'catalog',
});
});
it('should handle duplicate defaultFactories by duplicating the implementations', async () => {
const registry = new ServiceRegistry([]);
const factoryA = registry.get(refDefault2a)!;
const factoryB = registry.get(refDefault2b)!;
expect(factoryA).toEqual(expect.any(Function));
expect(factoryB).toEqual(expect.any(Function));
await expect(factoryA('catalog')).resolves.toEqual({
await expect(registry.get(refDefault2a, 'catalog')).resolves.toEqual({
x: 20,
pluginId: 'catalog',
});
await expect(factoryB('catalog')).resolves.toEqual({
await expect(registry.get(refDefault2b, 'catalog')).resolves.toEqual({
x: 220,
pluginId: 'catalog',
});
expect(await factoryA('catalog')).toBe(await factoryA('catalog'));
expect(await factoryB('catalog')).toBe(await factoryB('catalog'));
expect(await factoryA('catalog')).not.toBe(await factoryB('catalog'));
expect(await registry.get(refDefault2a, 'catalog')).toBe(
await registry.get(refDefault2a, 'catalog'),
);
expect(await registry.get(refDefault2b, 'catalog')).toBe(
await registry.get(refDefault2b, 'catalog'),
);
expect(await registry.get(refDefault2a, 'catalog')).not.toBe(
await registry.get(refDefault2b, 'catalog'),
);
});
it('should only call each default factory loader once', async () => {
@@ -186,17 +175,16 @@ describe('ServiceRegistry', () => {
});
const registry = new ServiceRegistry([]);
const factory = registry.get(ref)!;
await Promise.all([
expect(factory('catalog')).resolves.toBeUndefined(),
expect(factory('catalog')).resolves.toBeUndefined(),
expect(registry.get(ref, 'catalog')).resolves.toBeUndefined(),
expect(registry.get(ref, 'catalog')).resolves.toBeUndefined(),
]);
expect(factoryLoader).toHaveBeenCalledTimes(1);
});
it('should not call factory functions more than once', async () => {
const innerFactory = jest.fn(async (pluginId: string) => {
return { x: 1, pluginId };
const innerFactory = jest.fn(async () => {
return { x: 1 };
});
const factory = jest.fn(async () => innerFactory);
const myFactory = createServiceFactory({
@@ -208,17 +196,15 @@ describe('ServiceRegistry', () => {
const registry = new ServiceRegistry([myFactory]);
await Promise.all([
registry.get(ref1)!('catalog')!,
registry.get(ref1)!('catalog')!,
registry.get(ref1)!('catalog')!,
registry.get(ref1)!('scaffolder')!,
registry.get(ref1)!('scaffolder')!,
registry.get(ref1, 'catalog')!,
registry.get(ref1, 'catalog')!,
registry.get(ref1, 'catalog')!,
registry.get(ref1, 'scaffolder')!,
registry.get(ref1, 'scaffolder')!,
]);
expect(factory).toHaveBeenCalledTimes(1);
expect(innerFactory).toHaveBeenCalledTimes(2);
expect(innerFactory).toHaveBeenCalledWith('catalog');
expect(innerFactory).toHaveBeenCalledWith('scaffolder');
});
it('should throw if dependencies are not available', async () => {
@@ -231,9 +217,8 @@ describe('ServiceRegistry', () => {
});
const registry = new ServiceRegistry([myFactory]);
const factory = registry.get(ref1)!;
await expect(factory('catalog')).rejects.toThrow(
await expect(registry.get(ref1, 'catalog')).rejects.toThrow(
"Failed to instantiate service '1' for 'catalog' because the following dependent services are missing: '2'",
);
});
@@ -247,8 +232,8 @@ describe('ServiceRegistry', () => {
const factoryA = createServiceFactory({
service: refA,
deps: { b: refB },
async factory({ b }) {
return async pluginId => b(pluginId);
async factory() {
return async ({ b }) => b;
},
});
@@ -261,9 +246,8 @@ describe('ServiceRegistry', () => {
});
const registry = new ServiceRegistry([factoryA, factoryB]);
const factory = registry.get(refA)!;
await expect(factory('catalog')).rejects.toThrow(
await expect(registry.get(refA, 'catalog')).rejects.toThrow(
"Failed to instantiate service 'a' for 'catalog' because the factory function threw an error, Error: Failed to instantiate service 'b' for 'catalog' because the following dependent services are missing: 'c', 'd'",
);
});
@@ -278,9 +262,8 @@ describe('ServiceRegistry', () => {
});
const registry = new ServiceRegistry([myFactory]);
const factory = registry.get(ref1)!;
await expect(factory('catalog')).rejects.toThrow(
await expect(registry.get(ref1, 'catalog')).rejects.toThrow(
"Failed to instantiate service '1' because the top-level factory function threw an error, Error: top-level error",
);
});
@@ -290,17 +273,16 @@ describe('ServiceRegistry', () => {
service: ref1,
deps: {},
async factory() {
return pluginId => {
throw new Error(`error in plugin ${pluginId}`);
return () => {
throw new Error(`error in plugin`);
};
},
});
const registry = new ServiceRegistry([myFactory]);
const factory = registry.get(ref1)!;
await expect(factory('catalog')).rejects.toThrow(
"Failed to instantiate service '1' for 'catalog' because the factory function threw an error, Error: error in plugin catalog",
await expect(registry.get(ref1, 'catalog')).rejects.toThrow(
"Failed to instantiate service '1' for 'catalog' because the factory function threw an error, Error: error in plugin",
);
});
@@ -313,9 +295,8 @@ describe('ServiceRegistry', () => {
});
const registry = new ServiceRegistry([]);
const factory = registry.get(ref)!;
await expect(factory('catalog')).rejects.toThrow(
await expect(registry.get(ref, 'catalog')).rejects.toThrow(
"Failed to instantiate service '1' because the default factory loader threw an error, Error: default factory error",
);
});
@@ -16,8 +16,8 @@
import {
ServiceFactory,
FactoryFunc,
ServiceRef,
pluginMetadataServiceRef,
} from '@backstage/backend-plugin-api';
import { stringifyError } from '@backstage/errors';
@@ -37,7 +37,9 @@ export class ServiceRegistry {
readonly #implementations: Map<
ServiceFactory,
{
factoryFunc: Promise<FactoryFunc<unknown>>;
factoryFunc: Promise<
(deps: { [name in string]: unknown }) => Promise<unknown>
>;
byPlugin: Map<string, Promise<unknown>>;
}
>;
@@ -58,67 +60,123 @@ export class ServiceRegistry {
this.#implementations = new Map();
}
get<T>(ref: ServiceRef<T>): FactoryFunc<T> | undefined {
let factory = this.#providedFactories.get(ref.id);
const { __defaultFactory: defaultFactory } = ref as InternalServiceRef<T>;
if (!factory && !defaultFactory) {
#resolveFactory(
ref: ServiceRef<unknown>,
pluginId: string,
): Promise<ServiceFactory> | undefined {
// Special case handling of the plugin metadata service, generating a custom factory for it each time
if (ref.id === pluginMetadataServiceRef.id) {
return Promise.resolve({
scope: 'plugin',
service: pluginMetadataServiceRef,
deps: {},
factory: async () => async () => ({
getId() {
return pluginId;
},
}),
});
}
let resolvedFactory: Promise<ServiceFactory> | ServiceFactory | undefined =
this.#providedFactories.get(ref.id);
const { __defaultFactory: defaultFactory } =
ref as InternalServiceRef<unknown>;
if (!resolvedFactory && !defaultFactory) {
return undefined;
}
return async (pluginId: string): Promise<T> => {
if (!factory) {
let loadedFactory = this.#loadedDefaultFactories.get(defaultFactory!);
if (!loadedFactory) {
loadedFactory = Promise.resolve()
.then(() => defaultFactory!(ref))
.then(f =>
typeof f === 'function' ? f() : f,
) as Promise<ServiceFactory>;
this.#loadedDefaultFactories.set(defaultFactory!, loadedFactory);
}
// NOTE: This await is safe as long as #providedFactories is not mutated.
factory = await loadedFactory.catch(error => {
throw new Error(
`Failed to instantiate service '${
ref.id
}' because the default factory loader threw an error, ${stringifyError(
error,
)}`,
if (!resolvedFactory) {
let loadedFactory = this.#loadedDefaultFactories.get(defaultFactory!);
if (!loadedFactory) {
loadedFactory = Promise.resolve()
.then(() => defaultFactory!(ref))
.then(f =>
typeof f === 'function' ? f() : f,
) as Promise<ServiceFactory>;
this.#loadedDefaultFactories.set(defaultFactory!, loadedFactory);
}
resolvedFactory = loadedFactory.catch(error => {
throw new Error(
`Failed to instantiate service '${
ref.id
}' because the default factory loader threw an error, ${stringifyError(
error,
)}`,
);
});
}
return Promise.resolve(resolvedFactory);
}
#separateMapForTheRootService = new Map<ServiceFactory, Promise<unknown>>();
#checkForMissingDeps(factory: ServiceFactory, pluginId: string) {
const missingDeps = Object.values(factory.deps).filter(ref => {
if (ref.id === pluginMetadataServiceRef.id) {
return false;
}
if (this.#providedFactories.get(ref.id)) {
return false;
}
return !(ref as InternalServiceRef<unknown>).__defaultFactory;
});
if (missingDeps.length) {
const missing = missingDeps.map(r => `'${r.id}'`).join(', ');
throw new Error(
`Failed to instantiate service '${factory.service.id}' for '${pluginId}' because the following dependent services are missing: ${missing}`,
);
}
}
get<T>(ref: ServiceRef<T>, pluginId: string): Promise<T> | undefined {
return this.#resolveFactory(ref, pluginId)?.then(factory => {
if (factory.scope === 'root') {
let existing = this.#separateMapForTheRootService.get(factory);
if (!existing) {
this.#checkForMissingDeps(factory, pluginId);
const rootDeps = new Array<Promise<[name: string, impl: unknown]>>();
for (const [name, serviceRef] of Object.entries(factory.deps)) {
if (serviceRef.scope !== 'root') {
throw new Error(
`Failed to instantiate 'root' scoped service '${ref.id}' because it depends on '${serviceRef.scope}' scoped service '${serviceRef.id}'.`,
);
}
const target = this.get(serviceRef, pluginId)!;
rootDeps.push(target.then(impl => [name, impl]));
}
existing = Promise.all(rootDeps).then(entries =>
factory.factory(Object.fromEntries(entries)),
);
});
this.#separateMapForTheRootService.set(factory, existing);
}
return existing as Promise<T>;
}
let implementation = this.#implementations.get(factory);
if (!implementation) {
const missingRefs = new Array<ServiceRef<unknown>>();
const factoryDeps: { [name in string]: FactoryFunc<unknown> } = {};
this.#checkForMissingDeps(factory, pluginId);
const rootDeps = new Array<Promise<[name: string, impl: unknown]>>();
for (const [name, serviceRef] of Object.entries(factory.deps)) {
const target = this.get(serviceRef);
if (!target) {
missingRefs.push(serviceRef);
} else {
factoryDeps[name] = target;
if (serviceRef.scope === 'root') {
const target = this.get(serviceRef, pluginId)!;
rootDeps.push(target.then(impl => [name, impl]));
}
}
if (missingRefs.length) {
const missing = missingRefs.map(r => `'${r.id}'`).join(', ');
throw new Error(
`Failed to instantiate service '${ref.id}' for '${pluginId}' because the following dependent services are missing: ${missing}`,
);
}
implementation = {
factoryFunc: Promise.resolve()
.then(() => factory!.factory(factoryDeps))
factoryFunc: Promise.all(rootDeps)
.then(entries => factory.factory(Object.fromEntries(entries)))
.catch(error => {
const cause = stringifyError(error);
throw new Error(
`Failed to instantiate service '${
ref.id
}' because the top-level factory function threw an error, ${stringifyError(
error,
)}`,
`Failed to instantiate service '${ref.id}' because the top-level factory function threw an error, ${cause}`,
);
}),
byPlugin: new Map(),
@@ -129,24 +187,29 @@ export class ServiceRegistry {
let result = implementation.byPlugin.get(pluginId) as Promise<any>;
if (!result) {
result = implementation.factoryFunc.then(func =>
Promise.resolve()
.then(() => func(pluginId))
.catch(error => {
throw new Error(
`Failed to instantiate service '${
ref.id
}' for '${pluginId}' because the factory function threw an error, ${stringifyError(
error,
)}`,
);
}),
);
const allDeps = new Array<Promise<[name: string, impl: unknown]>>();
for (const [name, serviceRef] of Object.entries(factory.deps)) {
const target = this.get(serviceRef, pluginId)!;
allDeps.push(target.then(impl => [name, impl]));
}
result = implementation.factoryFunc
.then(func =>
Promise.all(allDeps).then(entries =>
func(Object.fromEntries(entries)),
),
)
.catch(error => {
const cause = stringifyError(error);
throw new Error(
`Failed to instantiate service '${ref.id}' for '${pluginId}' because the factory function threw an error, ${cause}`,
);
});
implementation.byPlugin.set(pluginId, result);
}
return result;
};
});
}
}
+1 -2
View File
@@ -18,7 +18,6 @@ import {
ServiceFactory,
BackendFeature,
ExtensionPoint,
FactoryFunc,
ServiceRef,
} from '@backstage/backend-plugin-api';
import { BackstageBackend } from './BackstageBackend';
@@ -47,7 +46,7 @@ export interface CreateSpecializedBackendOptions {
}
export type ServiceHolder = {
get<T>(api: ServiceRef<T>): FactoryFunc<T> | undefined;
get<T>(api: ServiceRef<T>, pluginId: string): Promise<T> | undefined;
};
/**
@@ -19,63 +19,84 @@
*
* @public
*/
export type ServiceRef<T> = {
export type ServiceRef<
TService,
TScope extends 'root' | 'plugin' = 'root' | 'plugin',
> = {
id: string;
/**
* This determines the scope at which this service is available.
*
* Root scoped services are available to all other services but
* may only depend on other root scoped services.
*
* Plugin scoped services are only available to other plugin scoped
* services but may depend on all other services.
*/
scope: TScope;
/**
* Utility for getting the type of the service, using `typeof serviceRef.T`.
* Attempting to actually read this value will result in an exception.
*/
T: T;
T: TService;
toString(): string;
$$ref: 'service';
};
/**
* @internal
*/
export type InternalServiceRef<T> = ServiceRef<T> & {
/**
* The default factory that will be used to create service
* instances if no other factory is provided.
*/
__defaultFactory?: (
service: ServiceRef<T>,
) => Promise<ServiceFactory<T> | (() => ServiceFactory<T>)>;
};
/** @public */
export type TypesToServiceRef<T> = { [key in keyof T]: ServiceRef<T[key]> };
/** @public */
export type DepsToDepFactories<T> = {
[key in keyof T]: (pluginId: string) => Promise<T[key]>;
};
/** @public */
export type FactoryFunc<Impl> = (pluginId: string) => Promise<Impl>;
/** @public */
export type ServiceFactory<TService = unknown> = {
service: ServiceRef<TService>;
deps: { [key in string]: ServiceRef<unknown> };
factory(deps: { [key in string]: unknown }): Promise<FactoryFunc<TService>>;
};
export type ServiceFactory<TService = unknown> =
| {
// This scope prop is needed in addition to the service ref, as TypeScript
// can't properly discriminate the two factory types otherwise.
scope: 'root';
service: ServiceRef<TService, 'root'>;
deps: { [key in string]: ServiceRef<unknown> };
factory(deps: { [key in string]: unknown }): Promise<TService>;
}
| {
scope: 'plugin';
service: ServiceRef<TService, 'plugin'>;
deps: { [key in string]: ServiceRef<unknown> };
factory(deps: { [key in string]: unknown }): Promise<
(deps: { [key in string]: unknown }) => Promise<TService>
>;
};
/**
* @public
*/
export function createServiceRef<T>(options: {
id: string;
scope?: 'plugin';
defaultFactory?: (
service: ServiceRef<T>,
) => Promise<ServiceFactory<T> | (() => ServiceFactory<T>)>;
}): ServiceRef<T> {
const { id, defaultFactory } = options;
}): ServiceRef<T, 'plugin'>;
export function createServiceRef<T>(options: {
id: string;
scope: 'root';
defaultFactory?: (
service: ServiceRef<T>,
) => Promise<ServiceFactory<T> | (() => ServiceFactory<T>)>;
}): ServiceRef<T, 'root'>;
export function createServiceRef<T>(options: {
id: string;
scope?: 'plugin' | 'root';
defaultFactory?: (
service: ServiceRef<T>,
) => Promise<ServiceFactory<T> | (() => ServiceFactory<T>)>;
}): ServiceRef<T, 'plugin' | 'root'> {
const { id, scope = 'plugin', defaultFactory } = options;
return {
id,
scope,
get T(): T {
throw new Error(`tried to read ServiceRef.T of ${this}`);
},
@@ -84,32 +105,58 @@ export function createServiceRef<T>(options: {
},
$$ref: 'service', // TODO: declare
__defaultFactory: defaultFactory,
} as InternalServiceRef<T>;
} as ServiceRef<T, typeof scope> & {
__defaultFactory?: (
service: ServiceRef<T>,
) => Promise<ServiceFactory<T> | (() => ServiceFactory<T>)>;
};
}
type OnlyRootScopeDependencies<
TDeps extends { [key in string]: ServiceRef<unknown> },
> = Pick<
TDeps,
{
[name in keyof TDeps]: TDeps[name]['scope'] extends 'root' ? name : never;
}[keyof TDeps]
>;
type DependencyRefsToInstances<
T extends { [key in string]: ServiceRef<unknown> },
> = {
[key in keyof T]: T[key] extends ServiceRef<infer TImpl> ? TImpl : never;
};
/**
* @public
*/
export function createServiceFactory<
TService,
TScope extends 'root' | 'plugin',
TImpl extends TService,
TDeps extends { [name in string]: unknown },
TDeps extends { [name in string]: ServiceRef<unknown> },
TOpts extends { [name in string]: unknown } | undefined = undefined,
>(factory: {
service: ServiceRef<TService>;
deps: TypesToServiceRef<TDeps>;
>(config: {
service: ServiceRef<TService, TScope>;
deps: TDeps;
factory(
deps: DepsToDepFactories<TDeps>,
deps: DependencyRefsToInstances<OnlyRootScopeDependencies<TDeps>>,
options: TOpts,
): Promise<FactoryFunc<TImpl>>;
): TScope extends 'root'
? Promise<TImpl>
: Promise<(deps: DependencyRefsToInstances<TDeps>) => Promise<TImpl>>;
}): undefined extends TOpts
? (options?: TOpts) => ServiceFactory<TService>
: (options: TOpts) => ServiceFactory<TService> {
return (options?: TOpts) => ({
service: factory.service,
deps: factory.deps,
factory(deps: DepsToDepFactories<TDeps>) {
return factory.factory(deps, options!);
},
});
return (options?: TOpts) =>
({
scope: config.service.scope,
service: config.service,
deps: config.deps,
factory(
deps: DependencyRefsToInstances<OnlyRootScopeDependencies<TDeps>>,
) {
return config.factory(deps, options!);
},
} as ServiceFactory<TService>);
}