Merge pull request #15748 from backstage/mob/new-factories
backend-plugin-api: rework service factory definitions
This commit is contained in:
@@ -152,7 +152,9 @@ export type HttpServerOptions = {
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export const identityFactory: () => ServiceFactory<IdentityService>;
|
||||
export const identityFactory: (
|
||||
options?: IdentityFactoryOptions | undefined,
|
||||
) => ServiceFactory<IdentityService>;
|
||||
|
||||
// @public
|
||||
export type IdentityFactoryOptions = {
|
||||
|
||||
+5
-5
@@ -27,10 +27,10 @@ export const cacheFactory = createServiceFactory({
|
||||
config: coreServices.config,
|
||||
plugin: coreServices.pluginMetadata,
|
||||
},
|
||||
async factory({ config }) {
|
||||
const cacheManager = CacheManager.fromConfig(config);
|
||||
return async ({ plugin }) => {
|
||||
return cacheManager.forPlugin(plugin.getId());
|
||||
};
|
||||
async createRootContext({ config }) {
|
||||
return CacheManager.fromConfig(config);
|
||||
},
|
||||
async factory({ plugin }, manager) {
|
||||
return manager.forPlugin(plugin.getId());
|
||||
},
|
||||
});
|
||||
|
||||
@@ -28,8 +28,8 @@ export const databaseFactory = createServiceFactory({
|
||||
config: coreServices.config,
|
||||
plugin: coreServices.pluginMetadata,
|
||||
},
|
||||
async factory({ config }) {
|
||||
const databaseManager = config.getOptional('backend.database')
|
||||
async createRootContext({ config }) {
|
||||
return config.getOptional('backend.database')
|
||||
? DatabaseManager.fromConfig(config)
|
||||
: DatabaseManager.fromConfig(
|
||||
new ConfigReader({
|
||||
@@ -38,9 +38,8 @@ export const databaseFactory = createServiceFactory({
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
return async ({ plugin }) => {
|
||||
return databaseManager.forPlugin(plugin.getId());
|
||||
};
|
||||
},
|
||||
async factory({ plugin }, databaseManager) {
|
||||
return databaseManager.forPlugin(plugin.getId());
|
||||
},
|
||||
});
|
||||
|
||||
@@ -27,9 +27,6 @@ export const discoveryFactory = createServiceFactory({
|
||||
config: coreServices.config,
|
||||
},
|
||||
async factory({ config }) {
|
||||
const discovery = SingleHostDiscovery.fromConfig(config);
|
||||
return async () => {
|
||||
return discovery;
|
||||
};
|
||||
return SingleHostDiscovery.fromConfig(config);
|
||||
},
|
||||
});
|
||||
|
||||
+28
-6
@@ -27,16 +27,27 @@ describe('httpRouterFactory', () => {
|
||||
ServiceFactory<HttpRouterService>,
|
||||
{ scope: 'root' }
|
||||
>;
|
||||
const innerFactory = await factory.factory({ rootHttpRouter });
|
||||
|
||||
const handler1 = () => {};
|
||||
const router1 = await innerFactory({ plugin: { getId: () => 'test1' } });
|
||||
const router1 = await factory.factory(
|
||||
{
|
||||
rootHttpRouter,
|
||||
plugin: { getId: () => 'test1' },
|
||||
},
|
||||
undefined,
|
||||
);
|
||||
router1.use(handler1);
|
||||
expect(rootHttpRouter.use).toHaveBeenCalledTimes(1);
|
||||
expect(rootHttpRouter.use).toHaveBeenCalledWith('/api/test1', handler1);
|
||||
|
||||
const handler2 = () => {};
|
||||
const router2 = await innerFactory({ plugin: { getId: () => 'test2' } });
|
||||
const router2 = await factory.factory(
|
||||
{
|
||||
rootHttpRouter,
|
||||
plugin: { getId: () => 'test2' },
|
||||
},
|
||||
undefined,
|
||||
);
|
||||
router2.use(handler2);
|
||||
expect(rootHttpRouter.use).toHaveBeenCalledTimes(2);
|
||||
expect(rootHttpRouter.use).toHaveBeenCalledWith('/api/test2', handler2);
|
||||
@@ -47,10 +58,15 @@ describe('httpRouterFactory', () => {
|
||||
const factory = httpRouterFactory({
|
||||
getPath: id => `/some/${id}/path`,
|
||||
}) as Exclude<ServiceFactory<HttpRouterService>, { scope: 'root' }>;
|
||||
const innerFactory = await factory.factory({ rootHttpRouter });
|
||||
|
||||
const handler1 = () => {};
|
||||
const router1 = await innerFactory({ plugin: { getId: () => 'test1' } });
|
||||
const router1 = await factory.factory(
|
||||
{
|
||||
rootHttpRouter,
|
||||
plugin: { getId: () => 'test1' },
|
||||
},
|
||||
undefined,
|
||||
);
|
||||
router1.use(handler1);
|
||||
expect(rootHttpRouter.use).toHaveBeenCalledTimes(1);
|
||||
expect(rootHttpRouter.use).toHaveBeenCalledWith(
|
||||
@@ -59,7 +75,13 @@ describe('httpRouterFactory', () => {
|
||||
);
|
||||
|
||||
const handler2 = () => {};
|
||||
const router2 = await innerFactory({ plugin: { getId: () => 'test2' } });
|
||||
const router2 = await factory.factory(
|
||||
{
|
||||
rootHttpRouter,
|
||||
plugin: { getId: () => 'test2' },
|
||||
},
|
||||
undefined,
|
||||
);
|
||||
router2.use(handler2);
|
||||
expect(rootHttpRouter.use).toHaveBeenCalledTimes(2);
|
||||
expect(rootHttpRouter.use).toHaveBeenCalledWith(
|
||||
|
||||
+6
-9
@@ -38,16 +38,13 @@ export const httpRouterFactory = createServiceFactory(
|
||||
plugin: coreServices.pluginMetadata,
|
||||
rootHttpRouter: coreServices.rootHttpRouter,
|
||||
},
|
||||
async factory({ rootHttpRouter }) {
|
||||
async factory({ plugin, rootHttpRouter }) {
|
||||
const getPath = options?.getPath ?? (id => `/api/${id}`);
|
||||
|
||||
return async ({ plugin }) => {
|
||||
const path = getPath(plugin.getId());
|
||||
return {
|
||||
use(handler: Handler) {
|
||||
rootHttpRouter.use(path, handler);
|
||||
},
|
||||
};
|
||||
const path = getPath(plugin.getId());
|
||||
return {
|
||||
use(handler: Handler) {
|
||||
rootHttpRouter.use(path, handler);
|
||||
},
|
||||
};
|
||||
},
|
||||
}),
|
||||
|
||||
@@ -34,17 +34,14 @@ export type IdentityFactoryOptions = {
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export const identityFactory = createServiceFactory({
|
||||
service: coreServices.identity,
|
||||
deps: {
|
||||
config: coreServices.config,
|
||||
discovery: coreServices.discovery,
|
||||
tokenManager: coreServices.tokenManager,
|
||||
},
|
||||
|
||||
async factory({}, options?: IdentityFactoryOptions) {
|
||||
return async ({ discovery }) => {
|
||||
export const identityFactory = createServiceFactory(
|
||||
(options?: IdentityFactoryOptions) => ({
|
||||
service: coreServices.identity,
|
||||
deps: {
|
||||
discovery: coreServices.discovery,
|
||||
},
|
||||
async factory({ discovery }) {
|
||||
return DefaultIdentityClient.create({ discovery, ...options });
|
||||
};
|
||||
},
|
||||
});
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
+9
-11
@@ -29,18 +29,16 @@ export const lifecycleFactory = createServiceFactory({
|
||||
rootLifecycle: coreServices.rootLifecycle,
|
||||
pluginMetadata: coreServices.pluginMetadata,
|
||||
},
|
||||
async factory({ rootLifecycle }) {
|
||||
return async ({ logger, pluginMetadata }) => {
|
||||
const plugin = pluginMetadata.getId();
|
||||
return {
|
||||
addShutdownHook(options: LifecycleServiceShutdownHook): void {
|
||||
rootLifecycle.addShutdownHook({
|
||||
...options,
|
||||
async factory({ rootLifecycle, logger, pluginMetadata }) {
|
||||
const plugin = pluginMetadata.getId();
|
||||
return {
|
||||
addShutdownHook(options: LifecycleServiceShutdownHook): void {
|
||||
rootLifecycle.addShutdownHook({
|
||||
...options,
|
||||
|
||||
logger: options.logger?.child({ plugin }) ?? logger,
|
||||
});
|
||||
},
|
||||
};
|
||||
logger: options.logger?.child({ plugin }) ?? logger,
|
||||
});
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
@@ -26,9 +26,7 @@ export const loggerFactory = createServiceFactory({
|
||||
rootLogger: coreServices.rootLogger,
|
||||
plugin: coreServices.pluginMetadata,
|
||||
},
|
||||
async factory({ rootLogger }) {
|
||||
return async ({ plugin }) => {
|
||||
return rootLogger.child({ plugin: plugin.getId() });
|
||||
};
|
||||
factory({ rootLogger, plugin }) {
|
||||
return rootLogger.child({ plugin: plugin.getId() });
|
||||
},
|
||||
});
|
||||
|
||||
+5
-7
@@ -28,12 +28,10 @@ export const permissionsFactory = createServiceFactory({
|
||||
discovery: coreServices.discovery,
|
||||
tokenManager: coreServices.tokenManager,
|
||||
},
|
||||
async factory({ config }) {
|
||||
return async ({ discovery, tokenManager }) => {
|
||||
return ServerPermissionClient.fromConfig(config, {
|
||||
discovery,
|
||||
tokenManager,
|
||||
});
|
||||
};
|
||||
async factory({ config, discovery, tokenManager }) {
|
||||
return ServerPermissionClient.fromConfig(config, {
|
||||
discovery,
|
||||
tokenManager,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -29,13 +29,11 @@ export const schedulerFactory = createServiceFactory({
|
||||
databaseManager: coreServices.database,
|
||||
logger: coreServices.logger,
|
||||
},
|
||||
async factory() {
|
||||
return async ({ plugin, databaseManager, logger }) => {
|
||||
return TaskScheduler.forPlugin({
|
||||
pluginId: plugin.getId(),
|
||||
databaseManager,
|
||||
logger: loggerToWinstonLogger(logger),
|
||||
});
|
||||
};
|
||||
async factory({ plugin, databaseManager, logger }) {
|
||||
return TaskScheduler.forPlugin({
|
||||
pluginId: plugin.getId(),
|
||||
databaseManager,
|
||||
logger: loggerToWinstonLogger(logger),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
LoggerService,
|
||||
ServiceFactory,
|
||||
TokenManagerService,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { tokenManagerFactory } from './tokenManagerFactory';
|
||||
|
||||
describe('tokenManagerFactory', () => {
|
||||
it('should create managers that can share tokens in development', async () => {
|
||||
(process.env as { NODE_ENV?: string }).NODE_ENV = 'development';
|
||||
|
||||
const factory = tokenManagerFactory() as Exclude<
|
||||
ServiceFactory<TokenManagerService>,
|
||||
{ scope: 'root' }
|
||||
>;
|
||||
const deps = {
|
||||
config: new ConfigReader({}),
|
||||
logger: { warn() {} } as unknown as LoggerService,
|
||||
};
|
||||
|
||||
const ctx = await factory.createRootContext?.(deps);
|
||||
const manager1 = await factory.factory!(deps, ctx);
|
||||
const manager2 = await factory.factory!(deps, ctx);
|
||||
|
||||
const { token } = await manager1.getToken();
|
||||
await expect(manager2.authenticate(token)).resolves.toBeUndefined();
|
||||
});
|
||||
});
|
||||
+5
-3
@@ -27,10 +27,12 @@ export const tokenManagerFactory = createServiceFactory({
|
||||
config: coreServices.config,
|
||||
logger: coreServices.rootLogger,
|
||||
},
|
||||
async factory({ config, logger }) {
|
||||
const tokenManager = ServerTokenManager.fromConfig(config, {
|
||||
createRootContext({ config, logger }) {
|
||||
return ServerTokenManager.fromConfig(config, {
|
||||
logger,
|
||||
});
|
||||
return async () => tokenManager;
|
||||
},
|
||||
async factory(_deps, tokenManager) {
|
||||
return tokenManager;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -27,12 +27,10 @@ export const urlReaderFactory = createServiceFactory({
|
||||
config: coreServices.config,
|
||||
logger: coreServices.logger,
|
||||
},
|
||||
async factory() {
|
||||
return async ({ config, logger }) => {
|
||||
return UrlReaders.default({
|
||||
config,
|
||||
logger: loggerToWinstonLogger(logger),
|
||||
});
|
||||
};
|
||||
async factory({ config, logger }) {
|
||||
return UrlReaders.default({
|
||||
config,
|
||||
logger: loggerToWinstonLogger(logger),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -29,9 +29,7 @@ const sf1 = createServiceFactory({
|
||||
service: ref1,
|
||||
deps: {},
|
||||
async factory() {
|
||||
return async () => {
|
||||
return { x: 1 };
|
||||
};
|
||||
return { x: 1 };
|
||||
},
|
||||
})();
|
||||
|
||||
@@ -61,7 +59,7 @@ const refDefault1 = createServiceRef<{ x: number }>({
|
||||
service,
|
||||
deps: {},
|
||||
async factory() {
|
||||
return async () => ({ x: 10 });
|
||||
return { x: 10 };
|
||||
},
|
||||
})(),
|
||||
});
|
||||
@@ -73,7 +71,7 @@ const refDefault2a = createServiceRef<{ x: number }>({
|
||||
service,
|
||||
deps: {},
|
||||
async factory() {
|
||||
return async () => ({ x: 20 });
|
||||
return { x: 20 };
|
||||
},
|
||||
}),
|
||||
});
|
||||
@@ -85,7 +83,7 @@ const refDefault2b = createServiceRef<{ x: number }>({
|
||||
service,
|
||||
deps: {},
|
||||
async factory() {
|
||||
return async () => ({ x: 220 });
|
||||
return { x: 220 };
|
||||
},
|
||||
}),
|
||||
});
|
||||
@@ -144,7 +142,7 @@ describe('ServiceRegistry', () => {
|
||||
service: ref1,
|
||||
deps: { rootDep: ref2 },
|
||||
factory: async ({ rootDep }) => {
|
||||
return async () => ({ x: rootDep.x });
|
||||
return { x: rootDep.x };
|
||||
},
|
||||
});
|
||||
const registry = new ServiceRegistry([factory(), sf2]);
|
||||
@@ -173,8 +171,8 @@ describe('ServiceRegistry', () => {
|
||||
const factory = createServiceFactory({
|
||||
service: ref,
|
||||
deps: { meta: coreServices.pluginMetadata },
|
||||
async factory() {
|
||||
return async ({ meta }) => ({ pluginId: meta.getId() });
|
||||
async factory({ meta }) {
|
||||
return { pluginId: meta.getId() };
|
||||
},
|
||||
});
|
||||
const registry = new ServiceRegistry([factory()]);
|
||||
@@ -224,13 +222,11 @@ describe('ServiceRegistry', () => {
|
||||
});
|
||||
|
||||
it('should only call each default factory loader once', async () => {
|
||||
const factoryLoader = jest.fn(async (service: ServiceRef<void>) =>
|
||||
const factoryLoader = jest.fn(async (service: ServiceRef<void, 'plugin'>) =>
|
||||
createServiceFactory({
|
||||
service,
|
||||
deps: {},
|
||||
async factory() {
|
||||
return async () => {};
|
||||
},
|
||||
async factory() {},
|
||||
}),
|
||||
);
|
||||
const ref = createServiceRef<void>({
|
||||
@@ -247,10 +243,31 @@ describe('ServiceRegistry', () => {
|
||||
});
|
||||
|
||||
it('should not call factory functions more than once', async () => {
|
||||
const innerFactory = jest.fn(async () => {
|
||||
return { x: 1 };
|
||||
const createRootContext = jest.fn(async () => ({ x: 1 }));
|
||||
const factory = jest.fn(async () => ({ x: 1 }));
|
||||
const myFactory = createServiceFactory({
|
||||
service: ref1,
|
||||
deps: {},
|
||||
createRootContext,
|
||||
factory,
|
||||
});
|
||||
const factory = jest.fn(async () => innerFactory);
|
||||
|
||||
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')!,
|
||||
]);
|
||||
|
||||
expect(createRootContext).toHaveBeenCalledTimes(1);
|
||||
expect(factory).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('should not call factory functions more than once without root context', async () => {
|
||||
const factory = jest.fn(async () => ({ x: 1 }));
|
||||
const myFactory = createServiceFactory({
|
||||
service: ref1,
|
||||
deps: {},
|
||||
@@ -267,8 +284,7 @@ describe('ServiceRegistry', () => {
|
||||
registry.get(ref1, 'scaffolder')!,
|
||||
]);
|
||||
|
||||
expect(factory).toHaveBeenCalledTimes(1);
|
||||
expect(innerFactory).toHaveBeenCalledTimes(2);
|
||||
expect(factory).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('should throw if dependencies are not available', async () => {
|
||||
@@ -296,9 +312,7 @@ describe('ServiceRegistry', () => {
|
||||
const factoryA = createServiceFactory({
|
||||
service: refA,
|
||||
deps: { b: refB },
|
||||
async factory() {
|
||||
return async ({ b }) => b;
|
||||
},
|
||||
factory: async ({ b }) => b,
|
||||
});
|
||||
|
||||
const factoryB = createServiceFactory({
|
||||
@@ -320,15 +334,18 @@ describe('ServiceRegistry', () => {
|
||||
const myFactory = createServiceFactory({
|
||||
service: ref1,
|
||||
deps: {},
|
||||
factory() {
|
||||
createRootContext() {
|
||||
throw new Error('top-level error');
|
||||
},
|
||||
factory() {
|
||||
throw new Error(`error in plugin`);
|
||||
},
|
||||
});
|
||||
|
||||
const registry = new ServiceRegistry([myFactory()]);
|
||||
|
||||
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",
|
||||
"Failed to instantiate service '1' because createRootContext threw an error, Error: top-level error",
|
||||
);
|
||||
});
|
||||
|
||||
@@ -337,9 +354,7 @@ describe('ServiceRegistry', () => {
|
||||
service: ref1,
|
||||
deps: {},
|
||||
async factory() {
|
||||
return () => {
|
||||
throw new Error(`error in plugin`);
|
||||
};
|
||||
throw new Error(`error in plugin`);
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -37,12 +37,14 @@ export class ServiceRegistry implements EnumerableServiceHolder {
|
||||
readonly #implementations: Map<
|
||||
ServiceFactory,
|
||||
{
|
||||
factoryFunc: Promise<
|
||||
(deps: { [name in string]: unknown }) => Promise<unknown>
|
||||
>;
|
||||
context: Promise<unknown>;
|
||||
byPlugin: Map<string, Promise<unknown>>;
|
||||
}
|
||||
>;
|
||||
readonly #rootServiceImplementations = new Map<
|
||||
ServiceFactory,
|
||||
Promise<unknown>
|
||||
>();
|
||||
|
||||
constructor(factories: Array<ServiceFactory<unknown>>) {
|
||||
this.#providedFactories = new Map(factories.map(f => [f.service.id, f]));
|
||||
@@ -56,15 +58,13 @@ export class ServiceRegistry implements EnumerableServiceHolder {
|
||||
): Promise<ServiceFactory> | undefined {
|
||||
// Special case handling of the plugin metadata service, generating a custom factory for it each time
|
||||
if (ref.id === coreServices.pluginMetadata.id) {
|
||||
return Promise.resolve({
|
||||
return Promise.resolve<
|
||||
ServiceFactory<typeof coreServices.pluginMetadata.T>
|
||||
>({
|
||||
scope: 'plugin',
|
||||
service: coreServices.pluginMetadata,
|
||||
deps: {},
|
||||
factory: async () => async () => ({
|
||||
getId() {
|
||||
return pluginId;
|
||||
},
|
||||
}),
|
||||
factory: async () => ({ getId: () => pluginId }),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -100,8 +100,6 @@ export class ServiceRegistry implements EnumerableServiceHolder {
|
||||
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 === coreServices.pluginMetadata.id) {
|
||||
@@ -129,7 +127,7 @@ export class ServiceRegistry implements EnumerableServiceHolder {
|
||||
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);
|
||||
let existing = this.#rootServiceImplementations.get(factory);
|
||||
if (!existing) {
|
||||
this.#checkForMissingDeps(factory, pluginId);
|
||||
const rootDeps = new Array<Promise<[name: string, impl: unknown]>>();
|
||||
@@ -147,7 +145,7 @@ export class ServiceRegistry implements EnumerableServiceHolder {
|
||||
existing = Promise.all(rootDeps).then(entries =>
|
||||
factory.factory(Object.fromEntries(entries)),
|
||||
);
|
||||
this.#separateMapForTheRootService.set(factory, existing);
|
||||
this.#rootServiceImplementations.set(factory, existing);
|
||||
}
|
||||
return existing as Promise<T>;
|
||||
}
|
||||
@@ -165,12 +163,14 @@ export class ServiceRegistry implements EnumerableServiceHolder {
|
||||
}
|
||||
|
||||
implementation = {
|
||||
factoryFunc: Promise.all(rootDeps)
|
||||
.then(entries => factory.factory(Object.fromEntries(entries)))
|
||||
context: Promise.all(rootDeps)
|
||||
.then(entries =>
|
||||
factory.createRootContext?.(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, ${cause}`,
|
||||
`Failed to instantiate service '${ref.id}' because createRootContext threw an error, ${cause}`,
|
||||
);
|
||||
}),
|
||||
byPlugin: new Map(),
|
||||
@@ -188,10 +188,10 @@ export class ServiceRegistry implements EnumerableServiceHolder {
|
||||
allDeps.push(target.then(impl => [name, impl]));
|
||||
}
|
||||
|
||||
result = implementation.factoryFunc
|
||||
.then(func =>
|
||||
result = implementation.context
|
||||
.then(context =>
|
||||
Promise.all(allDeps).then(entries =>
|
||||
func(Object.fromEntries(entries)),
|
||||
factory.factory(Object.fromEntries(entries), context),
|
||||
),
|
||||
)
|
||||
.catch(error => {
|
||||
|
||||
@@ -53,7 +53,7 @@ describe('createSpecializedBackend', () => {
|
||||
createServiceFactory({
|
||||
service: coreServices.pluginMetadata,
|
||||
deps: {},
|
||||
factory: async () => async () => ({ getId: () => 'test' }),
|
||||
factory: async () => ({ getId: () => 'test' }),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
|
||||
@@ -63,7 +63,7 @@ describe('createBackend', () => {
|
||||
createServiceFactory({
|
||||
service: coreServices.pluginMetadata,
|
||||
deps: {},
|
||||
factory: async () => async () => ({ getId: () => 'test' }),
|
||||
factory: async () => ({ getId: () => 'test' }),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
|
||||
@@ -126,22 +126,86 @@ export function createExtensionPoint<T>(
|
||||
config: ExtensionPointConfig,
|
||||
): ExtensionPoint<T>;
|
||||
|
||||
// @public (undocumented)
|
||||
// @public
|
||||
export function createServiceFactory<
|
||||
TService,
|
||||
TScope extends 'root' | 'plugin',
|
||||
TImpl extends TService,
|
||||
TDeps extends {
|
||||
[name in string]: ServiceRef<unknown>;
|
||||
},
|
||||
TOpts extends [options?: object] = [],
|
||||
TOpts extends object | undefined = undefined,
|
||||
>(
|
||||
config: RootServiceFactoryConfig<TService, TImpl, TDeps>,
|
||||
): () => ServiceFactory<TService>;
|
||||
|
||||
// @public
|
||||
export function createServiceFactory<
|
||||
TService,
|
||||
TImpl extends TService,
|
||||
TDeps extends {
|
||||
[name in string]: ServiceRef<unknown>;
|
||||
},
|
||||
TOpts extends object | undefined = undefined,
|
||||
>(
|
||||
config: (options?: TOpts) => RootServiceFactoryConfig<TService, TImpl, TDeps>,
|
||||
): (options?: TOpts) => ServiceFactory<TService>;
|
||||
|
||||
// @public
|
||||
export function createServiceFactory<
|
||||
TService,
|
||||
TImpl extends TService,
|
||||
TDeps extends {
|
||||
[name in string]: ServiceRef<unknown>;
|
||||
},
|
||||
TOpts extends object | undefined = undefined,
|
||||
>(
|
||||
config: (options: TOpts) => RootServiceFactoryConfig<TService, TImpl, TDeps>,
|
||||
): (options: TOpts) => ServiceFactory<TService>;
|
||||
|
||||
// @public
|
||||
export function createServiceFactory<
|
||||
TService,
|
||||
TImpl extends TService,
|
||||
TDeps extends {
|
||||
[name in string]: ServiceRef<unknown>;
|
||||
},
|
||||
TContext = undefined,
|
||||
TOpts extends object | undefined = undefined,
|
||||
>(
|
||||
config: PluginServiceFactoryConfig<TService, TContext, TImpl, TDeps>,
|
||||
): () => ServiceFactory<TService>;
|
||||
|
||||
// @public
|
||||
export function createServiceFactory<
|
||||
TService,
|
||||
TImpl extends TService,
|
||||
TDeps extends {
|
||||
[name in string]: ServiceRef<unknown>;
|
||||
},
|
||||
TContext = undefined,
|
||||
TOpts extends object | undefined = undefined,
|
||||
>(
|
||||
config: (
|
||||
options?: TOpts,
|
||||
) => PluginServiceFactoryConfig<TService, TContext, TImpl, TDeps>,
|
||||
): (options?: TOpts) => ServiceFactory<TService>;
|
||||
|
||||
// @public
|
||||
export function createServiceFactory<
|
||||
TService,
|
||||
TImpl extends TService,
|
||||
TDeps extends {
|
||||
[name in string]: ServiceRef<unknown>;
|
||||
},
|
||||
TContext = undefined,
|
||||
TOpts extends object | undefined = undefined,
|
||||
>(
|
||||
config:
|
||||
| ServiceFactoryConfig<TService, TScope, TImpl, TDeps>
|
||||
| PluginServiceFactoryConfig<TService, TContext, TImpl, TDeps>
|
||||
| ((
|
||||
...options: TOpts
|
||||
) => ServiceFactoryConfig<TService, TScope, TImpl, TDeps>),
|
||||
): (...params: TOpts) => ServiceFactory<TService>;
|
||||
options: TOpts,
|
||||
) => PluginServiceFactoryConfig<TService, TContext, TImpl, TDeps>),
|
||||
): (options: TOpts) => ServiceFactory<TService>;
|
||||
|
||||
// @public
|
||||
export function createServiceRef<TService>(
|
||||
@@ -229,6 +293,30 @@ export interface PluginMetadataService {
|
||||
getId(): string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface PluginServiceFactoryConfig<
|
||||
TService,
|
||||
TContext,
|
||||
TImpl extends TService,
|
||||
TDeps extends {
|
||||
[name in string]: ServiceRef<unknown>;
|
||||
},
|
||||
> {
|
||||
// (undocumented)
|
||||
createRootContext?(
|
||||
deps: ServiceRefsToInstances<TDeps, 'root'>,
|
||||
): TContext | Promise<TContext>;
|
||||
// (undocumented)
|
||||
deps: TDeps;
|
||||
// (undocumented)
|
||||
factory(
|
||||
deps: ServiceRefsToInstances<TDeps>,
|
||||
context: TContext,
|
||||
): TImpl | Promise<TImpl>;
|
||||
// (undocumented)
|
||||
service: ServiceRef<TService, 'plugin'>;
|
||||
}
|
||||
|
||||
// @public
|
||||
export type ReadTreeOptions = {
|
||||
filter?(
|
||||
@@ -284,6 +372,22 @@ export interface RootLifecycleService extends LifecycleService {}
|
||||
// @public (undocumented)
|
||||
export interface RootLoggerService extends LoggerService {}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface RootServiceFactoryConfig<
|
||||
TService,
|
||||
TImpl extends TService,
|
||||
TDeps extends {
|
||||
[name in string]: ServiceRef<unknown>;
|
||||
},
|
||||
> {
|
||||
// (undocumented)
|
||||
deps: TDeps;
|
||||
// (undocumented)
|
||||
factory(deps: ServiceRefsToInstances<TDeps, 'root'>): TImpl | Promise<TImpl>;
|
||||
// (undocumented)
|
||||
service: ServiceRef<TService, 'root'>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface SchedulerService extends PluginTaskScheduler {}
|
||||
|
||||
@@ -323,36 +427,17 @@ export type ServiceFactory<TService = unknown> =
|
||||
deps: {
|
||||
[key in string]: ServiceRef<unknown>;
|
||||
};
|
||||
factory(deps: {
|
||||
createRootContext?(deps: {
|
||||
[key in string]: unknown;
|
||||
}): Promise<
|
||||
(deps: {
|
||||
}): Promise<unknown>;
|
||||
factory(
|
||||
deps: {
|
||||
[key in string]: unknown;
|
||||
}) => Promise<TService>
|
||||
>;
|
||||
},
|
||||
context: unknown,
|
||||
): Promise<TService>;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export interface ServiceFactoryConfig<
|
||||
TService,
|
||||
TScope extends 'root' | 'plugin',
|
||||
TImpl extends TService,
|
||||
TDeps extends {
|
||||
[name in string]: ServiceRef<unknown>;
|
||||
},
|
||||
> {
|
||||
// (undocumented)
|
||||
deps: TDeps;
|
||||
// (undocumented)
|
||||
factory(
|
||||
deps: ServiceRefsToInstances<TDeps, 'root'>,
|
||||
): TScope extends 'root'
|
||||
? Promise<TImpl>
|
||||
: Promise<(deps: ServiceRefsToInstances<TDeps>) => Promise<TImpl>>;
|
||||
// (undocumented)
|
||||
service: ServiceRef<TService, TScope>;
|
||||
}
|
||||
|
||||
// @public
|
||||
export type ServiceFactoryOrFunction<TService = unknown> =
|
||||
| ServiceFactory<TService>
|
||||
|
||||
@@ -19,7 +19,8 @@ export type {
|
||||
ServiceRefConfig,
|
||||
TypesToServiceRef,
|
||||
ServiceFactory,
|
||||
ServiceFactoryConfig,
|
||||
PluginServiceFactoryConfig,
|
||||
RootServiceFactoryConfig,
|
||||
ServiceFactoryOrFunction,
|
||||
} from './types';
|
||||
export { createServiceRef, createServiceFactory } from './types';
|
||||
|
||||
@@ -20,13 +20,19 @@ const ref = createServiceRef<string>({ id: 'x' });
|
||||
const rootDep = createServiceRef<number>({ id: 'y', scope: 'root' });
|
||||
const pluginDep = createServiceRef<boolean>({ id: 'z' });
|
||||
|
||||
interface TestOptions {
|
||||
x: number;
|
||||
}
|
||||
function unused(..._any: any[]) {}
|
||||
|
||||
describe('createServiceFactory', () => {
|
||||
it('should create a meta factory with no options', () => {
|
||||
it('should create a sync factory with no options', () => {
|
||||
const metaFactory = createServiceFactory({
|
||||
service: ref,
|
||||
deps: {},
|
||||
async factory(_deps) {
|
||||
return async () => 'x';
|
||||
createRootContext() {},
|
||||
factory(_deps) {
|
||||
return 'x';
|
||||
},
|
||||
});
|
||||
expect(metaFactory).toEqual(expect.any(Function));
|
||||
@@ -45,12 +51,62 @@ describe('createServiceFactory', () => {
|
||||
metaFactory();
|
||||
});
|
||||
|
||||
it('should create a meta factory with optional options', () => {
|
||||
it('should create a sync root factory with no options', () => {
|
||||
const metaFactory = createServiceFactory({
|
||||
service: rootDep,
|
||||
deps: {},
|
||||
factory(_deps) {
|
||||
return 0;
|
||||
},
|
||||
});
|
||||
expect(metaFactory).toEqual(expect.any(Function));
|
||||
expect(metaFactory().service).toBe(rootDep);
|
||||
|
||||
// @ts-expect-error
|
||||
metaFactory('string');
|
||||
// @ts-expect-error
|
||||
metaFactory({});
|
||||
// @ts-expect-error
|
||||
metaFactory({ x: 1 });
|
||||
// @ts-expect-error
|
||||
metaFactory(null);
|
||||
// @ts-expect-error
|
||||
metaFactory(undefined);
|
||||
metaFactory();
|
||||
});
|
||||
|
||||
it('should create a factory with no options', () => {
|
||||
const metaFactory = createServiceFactory({
|
||||
service: ref,
|
||||
deps: {},
|
||||
async createRootContext() {},
|
||||
async factory(_deps) {
|
||||
return 'x';
|
||||
},
|
||||
});
|
||||
expect(metaFactory).toEqual(expect.any(Function));
|
||||
expect(metaFactory().service).toBe(ref);
|
||||
|
||||
// @ts-expect-error
|
||||
metaFactory('string');
|
||||
// @ts-expect-error
|
||||
metaFactory({});
|
||||
// @ts-expect-error
|
||||
metaFactory({ x: 1 });
|
||||
// @ts-expect-error
|
||||
metaFactory(null);
|
||||
// @ts-expect-error
|
||||
metaFactory(undefined);
|
||||
metaFactory();
|
||||
});
|
||||
|
||||
it('should create a factory with optional options', () => {
|
||||
const metaFactory = createServiceFactory((_opts?: { x: number }) => ({
|
||||
service: ref,
|
||||
deps: {},
|
||||
async createRootContext() {},
|
||||
async factory() {
|
||||
return async () => 'x';
|
||||
return 'x';
|
||||
},
|
||||
}));
|
||||
expect(metaFactory).toEqual(expect.any(Function));
|
||||
@@ -68,12 +124,13 @@ describe('createServiceFactory', () => {
|
||||
metaFactory();
|
||||
});
|
||||
|
||||
it('should create a meta factory with required options', () => {
|
||||
it('should create a factory with required options', () => {
|
||||
const metaFactory = createServiceFactory((_opts: { x: number }) => ({
|
||||
service: ref,
|
||||
deps: {},
|
||||
async createRootContext() {},
|
||||
async factory() {
|
||||
return async () => 'x';
|
||||
return 'x';
|
||||
},
|
||||
}));
|
||||
expect(metaFactory).toEqual(expect.any(Function));
|
||||
@@ -93,15 +150,13 @@ describe('createServiceFactory', () => {
|
||||
metaFactory();
|
||||
});
|
||||
|
||||
it('should create a meta factory with optional options as interface', () => {
|
||||
interface TestOptions {
|
||||
x: number;
|
||||
}
|
||||
it('should create a factory with optional options as interface', () => {
|
||||
const metaFactory = createServiceFactory((_opts?: TestOptions) => ({
|
||||
service: ref,
|
||||
deps: {},
|
||||
async createRootContext() {},
|
||||
async factory() {
|
||||
return async () => 'x';
|
||||
return 'x';
|
||||
},
|
||||
}));
|
||||
expect(metaFactory).toEqual(expect.any(Function));
|
||||
@@ -119,15 +174,13 @@ describe('createServiceFactory', () => {
|
||||
metaFactory();
|
||||
});
|
||||
|
||||
it('should create a meta factory with required options as interface', () => {
|
||||
interface TestOptions {
|
||||
x: number;
|
||||
}
|
||||
it('should create a factory with required options as interface', () => {
|
||||
const metaFactory = createServiceFactory((_opts: TestOptions) => ({
|
||||
service: ref,
|
||||
deps: {},
|
||||
async createRootContext() {},
|
||||
async factory() {
|
||||
return async () => 'x';
|
||||
return 'x';
|
||||
},
|
||||
}));
|
||||
expect(metaFactory).toEqual(expect.any(Function));
|
||||
@@ -147,15 +200,9 @@ describe('createServiceFactory', () => {
|
||||
metaFactory();
|
||||
});
|
||||
|
||||
it('should create factory with required options and dependencies', () => {
|
||||
interface TestOptions {
|
||||
x: number;
|
||||
}
|
||||
|
||||
function unused(..._any: any[]) {}
|
||||
|
||||
const metaFactory = createServiceFactory((_opts: TestOptions) => ({
|
||||
service: ref,
|
||||
it('should create root scoped factory with dependencies', () => {
|
||||
const metaFactory = createServiceFactory({
|
||||
service: createServiceRef({ id: 'foo', scope: 'root' }),
|
||||
deps: {
|
||||
root: rootDep,
|
||||
plugin: pluginDep,
|
||||
@@ -164,13 +211,143 @@ describe('createServiceFactory', () => {
|
||||
const root1: number = root;
|
||||
// @ts-expect-error
|
||||
const root2: string = root;
|
||||
return async ({ plugin }) => {
|
||||
const plugin3: boolean = plugin;
|
||||
// @ts-expect-error
|
||||
const plugin4: number = plugin;
|
||||
unused(root1, root2, plugin3, plugin4);
|
||||
return 'x';
|
||||
};
|
||||
unused(root1, root2);
|
||||
return 0;
|
||||
},
|
||||
});
|
||||
expect(metaFactory).toEqual(expect.any(Function));
|
||||
|
||||
// @ts-expect-error
|
||||
metaFactory({});
|
||||
// @ts-expect-error
|
||||
metaFactory(null);
|
||||
// @ts-expect-error
|
||||
metaFactory(undefined);
|
||||
metaFactory();
|
||||
});
|
||||
|
||||
it('should create root scoped factory with dependencies and optional options', () => {
|
||||
const metaFactory = createServiceFactory((_options?: TestOptions) => ({
|
||||
service: createServiceRef({ id: 'foo', scope: 'root' }),
|
||||
deps: {
|
||||
root: rootDep,
|
||||
plugin: pluginDep,
|
||||
},
|
||||
async factory({ root }) {
|
||||
const root1: number = root;
|
||||
// @ts-expect-error
|
||||
const root2: string = root;
|
||||
unused(root1, root2);
|
||||
return 0;
|
||||
},
|
||||
}));
|
||||
expect(metaFactory).toEqual(expect.any(Function));
|
||||
|
||||
// @ts-expect-error
|
||||
metaFactory('string');
|
||||
// @ts-expect-error
|
||||
metaFactory({});
|
||||
metaFactory({ x: 1 });
|
||||
// @ts-expect-error
|
||||
metaFactory({ x: 1, y: 2 });
|
||||
// @ts-expect-error
|
||||
metaFactory(null);
|
||||
metaFactory(undefined);
|
||||
metaFactory();
|
||||
});
|
||||
|
||||
it('should create factory with dependencies', () => {
|
||||
const metaFactory = createServiceFactory({
|
||||
service: createServiceRef({ id: 'derp' }),
|
||||
deps: {
|
||||
root: rootDep,
|
||||
plugin: pluginDep,
|
||||
},
|
||||
async createRootContext({ root }) {
|
||||
const root1: number = root;
|
||||
// @ts-expect-error
|
||||
const root2: string = root;
|
||||
unused(root1, root2);
|
||||
return { root };
|
||||
},
|
||||
async factory({ plugin, root: rootB }, { root }) {
|
||||
const root1: number = root;
|
||||
// @ts-expect-error
|
||||
const root2: string = root;
|
||||
const root3: number = rootB;
|
||||
// @ts-expect-error
|
||||
const root4: string = rootB;
|
||||
const plugin3: boolean = plugin;
|
||||
// @ts-expect-error
|
||||
const plugin4: number = plugin;
|
||||
unused(root1, root2, root3, root4, plugin3, plugin4);
|
||||
return 'x';
|
||||
},
|
||||
});
|
||||
expect(metaFactory).toEqual(expect.any(Function));
|
||||
|
||||
// @ts-expect-error
|
||||
metaFactory({});
|
||||
// @ts-expect-error
|
||||
metaFactory(null);
|
||||
// @ts-expect-error
|
||||
metaFactory(undefined);
|
||||
metaFactory();
|
||||
});
|
||||
|
||||
it('should create factory with dependencies with optional derpFactory', () => {
|
||||
const metaFactory = createServiceFactory({
|
||||
service: createServiceRef({ id: 'derp' }),
|
||||
deps: {
|
||||
root: rootDep,
|
||||
plugin: pluginDep,
|
||||
},
|
||||
async factory({ root, plugin }) {
|
||||
const root1: number = root;
|
||||
// @ts-expect-error
|
||||
const root2: string = root;
|
||||
const plugin3: boolean = plugin;
|
||||
// @ts-expect-error
|
||||
const plugin4: number = plugin;
|
||||
unused(root1, root2, plugin3, plugin4);
|
||||
return 'x';
|
||||
},
|
||||
});
|
||||
|
||||
expect(metaFactory).toEqual(expect.any(Function));
|
||||
|
||||
// @ts-expect-error
|
||||
metaFactory({});
|
||||
// @ts-expect-error
|
||||
metaFactory(null);
|
||||
// @ts-expect-error
|
||||
metaFactory(undefined);
|
||||
metaFactory();
|
||||
});
|
||||
|
||||
it('should create factory with required options and dependencies', () => {
|
||||
const metaFactory = createServiceFactory((_opts: TestOptions) => ({
|
||||
service: ref,
|
||||
deps: {
|
||||
root: rootDep,
|
||||
plugin: pluginDep,
|
||||
},
|
||||
async createRootContext({ root }) {
|
||||
const root1: number = root;
|
||||
// @ts-expect-error
|
||||
const root2: string = root;
|
||||
unused(root1, root2);
|
||||
return { root };
|
||||
},
|
||||
async factory({ plugin }, { root }) {
|
||||
const root1: number = root;
|
||||
// @ts-expect-error
|
||||
const root2: string = root;
|
||||
const plugin3: boolean = plugin;
|
||||
// @ts-expect-error
|
||||
const plugin4: number = plugin;
|
||||
unused(root1, root2, plugin3, plugin4);
|
||||
return 'x';
|
||||
},
|
||||
}));
|
||||
expect(metaFactory).toEqual(expect.any(Function));
|
||||
@@ -191,29 +368,28 @@ describe('createServiceFactory', () => {
|
||||
});
|
||||
|
||||
it('should create factory with optional options and dependencies', () => {
|
||||
interface TestOptions {
|
||||
x: number;
|
||||
}
|
||||
|
||||
function unused(..._any: any[]) {}
|
||||
|
||||
const metaFactory = createServiceFactory((_opts?: TestOptions) => ({
|
||||
service: ref,
|
||||
deps: {
|
||||
root: rootDep,
|
||||
plugin: pluginDep,
|
||||
},
|
||||
async factory({ root }) {
|
||||
async createRootContext({ root }) {
|
||||
const root1: number = root;
|
||||
// @ts-expect-error
|
||||
const root2: string = root;
|
||||
return async ({ plugin }) => {
|
||||
const plugin3: boolean = plugin;
|
||||
// @ts-expect-error
|
||||
const plugin4: number = plugin;
|
||||
unused(root1, root2, plugin3, plugin4);
|
||||
return 'x';
|
||||
};
|
||||
unused(root1, root2);
|
||||
return { root };
|
||||
},
|
||||
async factory({ plugin }, { root }) {
|
||||
const root1: number = root;
|
||||
// @ts-expect-error
|
||||
const root2: string = root;
|
||||
const plugin3: boolean = plugin;
|
||||
// @ts-expect-error
|
||||
const plugin4: number = plugin;
|
||||
unused(root1, root2, plugin3, plugin4);
|
||||
return 'x';
|
||||
},
|
||||
}));
|
||||
expect(metaFactory).toEqual(expect.any(Function));
|
||||
|
||||
@@ -64,9 +64,11 @@ export type ServiceFactory<TService = unknown> =
|
||||
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>
|
||||
>;
|
||||
createRootContext?(deps: { [key in string]: unknown }): Promise<unknown>;
|
||||
factory(
|
||||
deps: { [key in string]: unknown },
|
||||
context: unknown,
|
||||
): Promise<TService>;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -135,46 +137,157 @@ type ServiceRefsToInstances<
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export interface ServiceFactoryConfig<
|
||||
export interface RootServiceFactoryConfig<
|
||||
TService,
|
||||
TScope extends 'root' | 'plugin',
|
||||
TImpl extends TService,
|
||||
TDeps extends { [name in string]: ServiceRef<unknown> },
|
||||
> {
|
||||
service: ServiceRef<TService, TScope>;
|
||||
service: ServiceRef<TService, 'root'>;
|
||||
deps: TDeps;
|
||||
factory(
|
||||
factory(deps: ServiceRefsToInstances<TDeps, 'root'>): TImpl | Promise<TImpl>;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export interface PluginServiceFactoryConfig<
|
||||
TService,
|
||||
TContext,
|
||||
TImpl extends TService,
|
||||
TDeps extends { [name in string]: ServiceRef<unknown> },
|
||||
> {
|
||||
service: ServiceRef<TService, 'plugin'>;
|
||||
deps: TDeps;
|
||||
createRootContext?(
|
||||
deps: ServiceRefsToInstances<TDeps, 'root'>,
|
||||
): TScope extends 'root'
|
||||
? Promise<TImpl>
|
||||
: Promise<(deps: ServiceRefsToInstances<TDeps>) => Promise<TImpl>>;
|
||||
): TContext | Promise<TContext>;
|
||||
factory(
|
||||
deps: ServiceRefsToInstances<TDeps>,
|
||||
context: TContext,
|
||||
): TImpl | Promise<TImpl>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a root scoped service factory without options.
|
||||
*
|
||||
* @public
|
||||
* @param config - The service factory configuration.
|
||||
*/
|
||||
export function createServiceFactory<
|
||||
TService,
|
||||
TScope extends 'root' | 'plugin',
|
||||
TImpl extends TService,
|
||||
TDeps extends { [name in string]: ServiceRef<unknown> },
|
||||
TOpts extends [options?: object] = [],
|
||||
TOpts extends object | undefined = undefined,
|
||||
>(
|
||||
config: RootServiceFactoryConfig<TService, TImpl, TDeps>,
|
||||
): () => ServiceFactory<TService>;
|
||||
/**
|
||||
* Creates a root scoped service factory with optional options.
|
||||
*
|
||||
* @public
|
||||
* @param config - The service factory configuration.
|
||||
*/
|
||||
export function createServiceFactory<
|
||||
TService,
|
||||
TImpl extends TService,
|
||||
TDeps extends { [name in string]: ServiceRef<unknown> },
|
||||
TOpts extends object | undefined = undefined,
|
||||
>(
|
||||
config: (options?: TOpts) => RootServiceFactoryConfig<TService, TImpl, TDeps>,
|
||||
): (options?: TOpts) => ServiceFactory<TService>;
|
||||
/**
|
||||
* Creates a root scoped service factory with required options.
|
||||
*
|
||||
* @public
|
||||
* @param config - The service factory configuration.
|
||||
*/
|
||||
export function createServiceFactory<
|
||||
TService,
|
||||
TImpl extends TService,
|
||||
TDeps extends { [name in string]: ServiceRef<unknown> },
|
||||
TOpts extends object | undefined = undefined,
|
||||
>(
|
||||
config: (options: TOpts) => RootServiceFactoryConfig<TService, TImpl, TDeps>,
|
||||
): (options: TOpts) => ServiceFactory<TService>;
|
||||
/**
|
||||
* Creates a plugin scoped service factory without options.
|
||||
*
|
||||
* @public
|
||||
* @param config - The service factory configuration.
|
||||
*/
|
||||
export function createServiceFactory<
|
||||
TService,
|
||||
TImpl extends TService,
|
||||
TDeps extends { [name in string]: ServiceRef<unknown> },
|
||||
TContext = undefined,
|
||||
TOpts extends object | undefined = undefined,
|
||||
>(
|
||||
config: PluginServiceFactoryConfig<TService, TContext, TImpl, TDeps>,
|
||||
): () => ServiceFactory<TService>;
|
||||
/**
|
||||
* Creates a plugin scoped service factory with optional options.
|
||||
*
|
||||
* @public
|
||||
* @param config - The service factory configuration.
|
||||
*/
|
||||
export function createServiceFactory<
|
||||
TService,
|
||||
TImpl extends TService,
|
||||
TDeps extends { [name in string]: ServiceRef<unknown> },
|
||||
TContext = undefined,
|
||||
TOpts extends object | undefined = undefined,
|
||||
>(
|
||||
config: (
|
||||
options?: TOpts,
|
||||
) => PluginServiceFactoryConfig<TService, TContext, TImpl, TDeps>,
|
||||
): (options?: TOpts) => ServiceFactory<TService>;
|
||||
/**
|
||||
* Creates a plugin scoped service factory with required options.
|
||||
*
|
||||
* @public
|
||||
* @param config - The service factory configuration.
|
||||
*/
|
||||
export function createServiceFactory<
|
||||
TService,
|
||||
TImpl extends TService,
|
||||
TDeps extends { [name in string]: ServiceRef<unknown> },
|
||||
TContext = undefined,
|
||||
TOpts extends object | undefined = undefined,
|
||||
>(
|
||||
config:
|
||||
| ServiceFactoryConfig<TService, TScope, TImpl, TDeps>
|
||||
| PluginServiceFactoryConfig<TService, TContext, TImpl, TDeps>
|
||||
| ((
|
||||
...options: TOpts
|
||||
) => ServiceFactoryConfig<TService, TScope, TImpl, TDeps>),
|
||||
): (...params: TOpts) => ServiceFactory<TService> {
|
||||
if (typeof config === 'function') {
|
||||
return (...opts: TOpts) => {
|
||||
const c = config(...opts);
|
||||
return { ...c, scope: c.service.scope } as ServiceFactory<TService>;
|
||||
};
|
||||
}
|
||||
return () =>
|
||||
({
|
||||
...config,
|
||||
scope: config.service.scope,
|
||||
} as ServiceFactory<TService>);
|
||||
options: TOpts,
|
||||
) => PluginServiceFactoryConfig<TService, TContext, TImpl, TDeps>),
|
||||
): (options: TOpts) => ServiceFactory<TService>;
|
||||
export function createServiceFactory<
|
||||
TService,
|
||||
TImpl extends TService,
|
||||
TDeps extends { [name in string]: ServiceRef<unknown> },
|
||||
TContext,
|
||||
TOpts extends object | undefined = undefined,
|
||||
>(
|
||||
config:
|
||||
| RootServiceFactoryConfig<TService, TImpl, TDeps>
|
||||
| PluginServiceFactoryConfig<TService, TContext, TImpl, TDeps>
|
||||
| ((options: TOpts) => RootServiceFactoryConfig<TService, TImpl, TDeps>)
|
||||
| ((
|
||||
options: TOpts,
|
||||
) => PluginServiceFactoryConfig<TService, TContext, TImpl, TDeps>)
|
||||
| (() => RootServiceFactoryConfig<TService, TImpl, TDeps>)
|
||||
| (() => PluginServiceFactoryConfig<TService, TContext, TImpl, TDeps>),
|
||||
): (options: TOpts) => ServiceFactory<TService> {
|
||||
const configCallback = typeof config === 'function' ? config : () => config;
|
||||
return (options: TOpts) => {
|
||||
const c = configCallback(options);
|
||||
return {
|
||||
...c,
|
||||
...('createRootContext' in c
|
||||
? {
|
||||
createRootContext: async (deps: TDeps) =>
|
||||
c?.createRootContext?.(deps),
|
||||
}
|
||||
: {}),
|
||||
factory: async (deps: TDeps, ctx: TContext) => c.factory(deps, ctx),
|
||||
scope: c.service.scope,
|
||||
} as ServiceFactory<TService>;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -35,8 +35,6 @@ export const mockTokenManagerFactory = createServiceFactory({
|
||||
service: coreServices.tokenManager,
|
||||
deps: {},
|
||||
async factory() {
|
||||
return async () => {
|
||||
return new TokenManagerMock();
|
||||
};
|
||||
return new TokenManagerMock();
|
||||
},
|
||||
});
|
||||
|
||||
@@ -106,7 +106,7 @@ describe('TestBackend', () => {
|
||||
deps: {},
|
||||
service: testRef,
|
||||
factory: async () => {
|
||||
return async () => testFn;
|
||||
return testFn;
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -168,7 +168,7 @@ export async function startTestBackend<
|
||||
backend: { baseUrl: `http://localhost:${port}`, listen: { port } },
|
||||
}),
|
||||
);
|
||||
return async () => discovery;
|
||||
return discovery;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -179,13 +179,13 @@ export async function startTestBackend<
|
||||
const [ref, impl] = serviceDef;
|
||||
if (ref.scope === 'plugin') {
|
||||
return createServiceFactory({
|
||||
service: ref,
|
||||
service: ref as ServiceRef<unknown, 'plugin'>,
|
||||
deps: {},
|
||||
factory: async () => async () => impl,
|
||||
factory: async () => impl,
|
||||
})();
|
||||
}
|
||||
return createServiceFactory({
|
||||
service: ref,
|
||||
service: ref as ServiceRef<unknown, 'root'>,
|
||||
deps: {},
|
||||
factory: async () => impl,
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user