Merge pull request #13510 from backstage/mob/factories
backend-next: incorporate options into createServiceFactory
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/backend-app-api': patch
|
||||
'@backstage/backend-defaults': patch
|
||||
'@backstage/backend-test-utils': patch
|
||||
---
|
||||
|
||||
Updated to support new `ServiceFactory` formats.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/backend-plugin-api': patch
|
||||
---
|
||||
|
||||
The `createServiceFactory` method has been updated to return a higher-order factory that can accept options.
|
||||
@@ -28,10 +28,12 @@ export interface Backend {
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const cacheFactory: ServiceFactory<PluginCacheManager>;
|
||||
export const cacheFactory: (
|
||||
options?: undefined,
|
||||
) => ServiceFactory<PluginCacheManager>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const configFactory: ServiceFactory<Config>;
|
||||
export const configFactory: (options?: undefined) => ServiceFactory<Config>;
|
||||
|
||||
// @public (undocumented)
|
||||
export function createSpecializedBackend(
|
||||
@@ -41,28 +43,36 @@ export function createSpecializedBackend(
|
||||
// @public (undocumented)
|
||||
export interface CreateSpecializedBackendOptions {
|
||||
// (undocumented)
|
||||
services: ServiceFactory[];
|
||||
services: (ServiceFactory | (() => ServiceFactory))[];
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const databaseFactory: ServiceFactory<PluginDatabaseManager>;
|
||||
export const databaseFactory: (
|
||||
options?: undefined,
|
||||
) => ServiceFactory<PluginDatabaseManager>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const discoveryFactory: ServiceFactory<PluginEndpointDiscovery>;
|
||||
export const discoveryFactory: (
|
||||
options?: undefined,
|
||||
) => ServiceFactory<PluginEndpointDiscovery>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const httpRouterFactory: ServiceFactory<HttpRouterService>;
|
||||
export const httpRouterFactory: (
|
||||
options?: undefined,
|
||||
) => ServiceFactory<HttpRouterService>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const loggerFactory: ServiceFactory<Logger>;
|
||||
export const loggerFactory: (options?: undefined) => ServiceFactory<Logger>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const permissionsFactory: ServiceFactory<
|
||||
PermissionAuthorizer | PermissionEvaluator
|
||||
>;
|
||||
export const permissionsFactory: (
|
||||
options?: undefined,
|
||||
) => ServiceFactory<PermissionAuthorizer | PermissionEvaluator>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const schedulerFactory: ServiceFactory<PluginTaskScheduler>;
|
||||
export const schedulerFactory: (
|
||||
options?: undefined,
|
||||
) => ServiceFactory<PluginTaskScheduler>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type ServiceOrExtensionPoint<T = unknown> =
|
||||
@@ -70,8 +80,12 @@ export type ServiceOrExtensionPoint<T = unknown> =
|
||||
| ServiceRef<T>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const tokenManagerFactory: ServiceFactory<TokenManager>;
|
||||
export const tokenManagerFactory: (
|
||||
options?: undefined,
|
||||
) => ServiceFactory<TokenManager>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const urlReaderFactory: ServiceFactory<UrlReader>;
|
||||
export const urlReaderFactory: (
|
||||
options?: undefined,
|
||||
) => ServiceFactory<UrlReader>;
|
||||
```
|
||||
|
||||
@@ -26,7 +26,9 @@ import { stringifyError } from '@backstage/errors';
|
||||
* @internal
|
||||
*/
|
||||
export type InternalServiceRef<T> = ServiceRef<T> & {
|
||||
__defaultFactory?: (service: ServiceRef<T>) => Promise<ServiceFactory<T>>;
|
||||
__defaultFactory?: (
|
||||
service: ServiceRef<T>,
|
||||
) => Promise<ServiceFactory<T> | (() => ServiceFactory<T>)>;
|
||||
};
|
||||
|
||||
export class ServiceRegistry {
|
||||
@@ -40,8 +42,18 @@ export class ServiceRegistry {
|
||||
}
|
||||
>;
|
||||
|
||||
constructor(factories: ServiceFactory<any>[]) {
|
||||
this.#providedFactories = new Map(factories.map(f => [f.service.id, f]));
|
||||
constructor(
|
||||
factories: Array<ServiceFactory<unknown> | (() => ServiceFactory<unknown>)>,
|
||||
) {
|
||||
this.#providedFactories = new Map(
|
||||
factories.map(f => {
|
||||
if (typeof f === 'function') {
|
||||
const cf = f();
|
||||
return [cf.service.id, cf];
|
||||
}
|
||||
return [f.service.id, f];
|
||||
}),
|
||||
);
|
||||
this.#loadedDefaultFactories = new Map();
|
||||
this.#implementations = new Map();
|
||||
}
|
||||
@@ -57,9 +69,11 @@ export class ServiceRegistry {
|
||||
if (!factory) {
|
||||
let loadedFactory = this.#loadedDefaultFactories.get(defaultFactory!);
|
||||
if (!loadedFactory) {
|
||||
loadedFactory = Promise.resolve().then(
|
||||
() => defaultFactory!(ref) as Promise<ServiceFactory>,
|
||||
);
|
||||
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.
|
||||
|
||||
@@ -43,7 +43,7 @@ export interface BackendRegisterInit {
|
||||
* @public
|
||||
*/
|
||||
export interface CreateSpecializedBackendOptions {
|
||||
services: ServiceFactory[];
|
||||
services: (ServiceFactory | (() => ServiceFactory))[];
|
||||
}
|
||||
|
||||
export type ServiceHolder = {
|
||||
@@ -56,7 +56,9 @@ export type ServiceHolder = {
|
||||
export function createSpecializedBackend(
|
||||
options: CreateSpecializedBackendOptions,
|
||||
): Backend {
|
||||
return new BackstageBackend(options.services);
|
||||
return new BackstageBackend(
|
||||
options.services.map(s => (typeof s === 'function' ? s() : s)),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -12,6 +12,6 @@ export function createBackend(options?: CreateBackendOptions): Backend;
|
||||
// @public (undocumented)
|
||||
export interface CreateBackendOptions {
|
||||
// (undocumented)
|
||||
services?: ServiceFactory[];
|
||||
services?: (ServiceFactory | (() => ServiceFactory))[];
|
||||
}
|
||||
```
|
||||
|
||||
@@ -47,24 +47,14 @@ export const defaultServiceFactories = [
|
||||
* @public
|
||||
*/
|
||||
export interface CreateBackendOptions {
|
||||
services?: ServiceFactory[];
|
||||
services?: (ServiceFactory | (() => ServiceFactory))[];
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export function createBackend(options?: CreateBackendOptions): Backend {
|
||||
const services = new Map<string, ServiceFactory>(
|
||||
defaultServiceFactories.map(sf => [sf.service.id, sf as ServiceFactory]),
|
||||
);
|
||||
|
||||
if (options?.services) {
|
||||
for (const sf of options.services) {
|
||||
services.set(sf.service.id, sf);
|
||||
}
|
||||
}
|
||||
|
||||
return createSpecializedBackend({
|
||||
services: Array.from(services.values()),
|
||||
services: [...defaultServiceFactories, ...(options?.services ?? [])],
|
||||
});
|
||||
}
|
||||
|
||||
@@ -109,16 +109,28 @@ export function createServiceFactory<
|
||||
TDeps extends {
|
||||
[name in string]: unknown;
|
||||
},
|
||||
TOpts extends
|
||||
| {
|
||||
[name in string]: unknown;
|
||||
}
|
||||
| undefined = undefined,
|
||||
>(factory: {
|
||||
service: ServiceRef<TService>;
|
||||
deps: TypesToServiceRef<TDeps>;
|
||||
factory(deps: DepsToDepFactories<TDeps>): Promise<FactoryFunc<TImpl>>;
|
||||
}): ServiceFactory<TService>;
|
||||
factory(
|
||||
deps: DepsToDepFactories<TDeps>,
|
||||
options: TOpts,
|
||||
): Promise<FactoryFunc<TImpl>>;
|
||||
}): undefined extends TOpts
|
||||
? (options?: TOpts) => ServiceFactory<TService>
|
||||
: (options: TOpts) => ServiceFactory<TService>;
|
||||
|
||||
// @public (undocumented)
|
||||
export function createServiceRef<T>(options: {
|
||||
id: string;
|
||||
defaultFactory?: (service: ServiceRef<T>) => Promise<ServiceFactory<T>>;
|
||||
defaultFactory?: (
|
||||
service: ServiceRef<T>,
|
||||
) => Promise<ServiceFactory<T> | (() => ServiceFactory<T>)>;
|
||||
}): ServiceRef<T>;
|
||||
|
||||
// @public (undocumented)
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* 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 { createServiceFactory, createServiceRef } from './types';
|
||||
|
||||
describe('createServiceFactory', () => {
|
||||
it('should create a meta factory with no options', () => {
|
||||
const ref = createServiceRef<string>({ id: 'x' });
|
||||
const metaFactory = createServiceFactory({
|
||||
service: ref,
|
||||
deps: {},
|
||||
async factory(_deps) {
|
||||
return async () => '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);
|
||||
metaFactory(undefined);
|
||||
metaFactory();
|
||||
});
|
||||
|
||||
it('should create a meta factory with optional options', () => {
|
||||
const ref = createServiceRef<string>({ id: 'x' });
|
||||
const metaFactory = createServiceFactory({
|
||||
service: ref,
|
||||
deps: {},
|
||||
async factory(_deps, _opts?: { x: number }) {
|
||||
return async () => 'x';
|
||||
},
|
||||
});
|
||||
expect(metaFactory).toEqual(expect.any(Function));
|
||||
|
||||
// @ts-expect-error
|
||||
metaFactory('string');
|
||||
// @ts-expect-error
|
||||
metaFactory({});
|
||||
metaFactory({ x: 1 });
|
||||
// @ts-expect-error
|
||||
metaFactory(null);
|
||||
metaFactory(undefined);
|
||||
metaFactory();
|
||||
});
|
||||
|
||||
it('should create a meta factory with required options', () => {
|
||||
const ref = createServiceRef<string>({ id: 'x' });
|
||||
const metaFactory = createServiceFactory({
|
||||
service: ref,
|
||||
deps: {},
|
||||
async factory(_deps, _opts: { x: number }) {
|
||||
return async () => 'x';
|
||||
},
|
||||
});
|
||||
expect(metaFactory).toEqual(expect.any(Function));
|
||||
|
||||
// @ts-expect-error
|
||||
metaFactory('string');
|
||||
// @ts-expect-error
|
||||
metaFactory({});
|
||||
metaFactory({ x: 1 });
|
||||
// @ts-expect-error
|
||||
metaFactory(null);
|
||||
// @ts-expect-error
|
||||
metaFactory(undefined);
|
||||
// @ts-expect-error
|
||||
metaFactory();
|
||||
});
|
||||
});
|
||||
@@ -41,7 +41,9 @@ 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>>;
|
||||
__defaultFactory?: (
|
||||
service: ServiceRef<T>,
|
||||
) => Promise<ServiceFactory<T> | (() => ServiceFactory<T>)>;
|
||||
};
|
||||
|
||||
/** @public */
|
||||
@@ -67,7 +69,9 @@ export type ServiceFactory<TService = unknown> = {
|
||||
*/
|
||||
export function createServiceRef<T>(options: {
|
||||
id: string;
|
||||
defaultFactory?: (service: ServiceRef<T>) => Promise<ServiceFactory<T>>;
|
||||
defaultFactory?: (
|
||||
service: ServiceRef<T>,
|
||||
) => Promise<ServiceFactory<T> | (() => ServiceFactory<T>)>;
|
||||
}): ServiceRef<T> {
|
||||
const { id, defaultFactory } = options;
|
||||
return {
|
||||
@@ -90,10 +94,22 @@ export function createServiceFactory<
|
||||
TService,
|
||||
TImpl extends TService,
|
||||
TDeps extends { [name in string]: unknown },
|
||||
TOpts extends { [name in string]: unknown } | undefined = undefined,
|
||||
>(factory: {
|
||||
service: ServiceRef<TService>;
|
||||
deps: TypesToServiceRef<TDeps>;
|
||||
factory(deps: DepsToDepFactories<TDeps>): Promise<FactoryFunc<TImpl>>;
|
||||
}): ServiceFactory<TService> {
|
||||
return factory as ServiceFactory<TService>;
|
||||
factory(
|
||||
deps: DepsToDepFactories<TDeps>,
|
||||
options: TOpts,
|
||||
): Promise<FactoryFunc<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!);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ export interface TestBackendOptions<
|
||||
...{
|
||||
[index in keyof TServices]:
|
||||
| ServiceFactory<TServices[index]>
|
||||
| (() => ServiceFactory<TServices[index]>)
|
||||
| [ServiceRef<TServices[index]>, Partial<TServices[index]>];
|
||||
},
|
||||
];
|
||||
|
||||
@@ -32,6 +32,7 @@ export interface TestBackendOptions<
|
||||
...{
|
||||
[index in keyof TServices]:
|
||||
| ServiceFactory<TServices[index]>
|
||||
| (() => ServiceFactory<TServices[index]>)
|
||||
| [ServiceRef<TServices[index]>, Partial<TServices[index]>];
|
||||
},
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user