diff --git a/.changeset/metal-weeks-kiss.md b/.changeset/metal-weeks-kiss.md new file mode 100644 index 0000000000..90e453dd5c --- /dev/null +++ b/.changeset/metal-weeks-kiss.md @@ -0,0 +1,7 @@ +--- +'@backstage/backend-app-api': patch +'@backstage/backend-defaults': patch +'@backstage/backend-test-utils': patch +--- + +Updated to support new `ServiceFactory` formats. diff --git a/.changeset/short-mice-explode.md b/.changeset/short-mice-explode.md new file mode 100644 index 0000000000..42ebfa9f5c --- /dev/null +++ b/.changeset/short-mice-explode.md @@ -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. diff --git a/packages/backend-app-api/api-report.md b/packages/backend-app-api/api-report.md index 0f8806870d..ea149a44f2 100644 --- a/packages/backend-app-api/api-report.md +++ b/packages/backend-app-api/api-report.md @@ -28,10 +28,12 @@ export interface Backend { } // @public (undocumented) -export const cacheFactory: ServiceFactory; +export const cacheFactory: ( + options?: undefined, +) => ServiceFactory; // @public (undocumented) -export const configFactory: ServiceFactory; +export const configFactory: (options?: undefined) => ServiceFactory; // @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; +export const databaseFactory: ( + options?: undefined, +) => ServiceFactory; // @public (undocumented) -export const discoveryFactory: ServiceFactory; +export const discoveryFactory: ( + options?: undefined, +) => ServiceFactory; // @public (undocumented) -export const httpRouterFactory: ServiceFactory; +export const httpRouterFactory: ( + options?: undefined, +) => ServiceFactory; // @public (undocumented) -export const loggerFactory: ServiceFactory; +export const loggerFactory: (options?: undefined) => ServiceFactory; // @public (undocumented) -export const permissionsFactory: ServiceFactory< - PermissionAuthorizer | PermissionEvaluator ->; +export const permissionsFactory: ( + options?: undefined, +) => ServiceFactory; // @public (undocumented) -export const schedulerFactory: ServiceFactory; +export const schedulerFactory: ( + options?: undefined, +) => ServiceFactory; // @public (undocumented) export type ServiceOrExtensionPoint = @@ -70,8 +80,12 @@ export type ServiceOrExtensionPoint = | ServiceRef; // @public (undocumented) -export const tokenManagerFactory: ServiceFactory; +export const tokenManagerFactory: ( + options?: undefined, +) => ServiceFactory; // @public (undocumented) -export const urlReaderFactory: ServiceFactory; +export const urlReaderFactory: ( + options?: undefined, +) => ServiceFactory; ``` diff --git a/packages/backend-app-api/src/wiring/ServiceRegistry.ts b/packages/backend-app-api/src/wiring/ServiceRegistry.ts index 45b82aac56..e0509aecde 100644 --- a/packages/backend-app-api/src/wiring/ServiceRegistry.ts +++ b/packages/backend-app-api/src/wiring/ServiceRegistry.ts @@ -26,7 +26,9 @@ import { stringifyError } from '@backstage/errors'; * @internal */ export type InternalServiceRef = ServiceRef & { - __defaultFactory?: (service: ServiceRef) => Promise>; + __defaultFactory?: ( + service: ServiceRef, + ) => Promise | (() => ServiceFactory)>; }; export class ServiceRegistry { @@ -40,8 +42,18 @@ export class ServiceRegistry { } >; - constructor(factories: ServiceFactory[]) { - this.#providedFactories = new Map(factories.map(f => [f.service.id, f])); + constructor( + factories: Array | (() => ServiceFactory)>, + ) { + 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, - ); + loadedFactory = Promise.resolve() + .then(() => defaultFactory!(ref)) + .then(f => + typeof f === 'function' ? f() : f, + ) as Promise; this.#loadedDefaultFactories.set(defaultFactory!, loadedFactory); } // NOTE: This await is safe as long as #providedFactories is not mutated. diff --git a/packages/backend-app-api/src/wiring/types.ts b/packages/backend-app-api/src/wiring/types.ts index 55009edbdf..febc6830c7 100644 --- a/packages/backend-app-api/src/wiring/types.ts +++ b/packages/backend-app-api/src/wiring/types.ts @@ -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)), + ); } /** diff --git a/packages/backend-defaults/api-report.md b/packages/backend-defaults/api-report.md index b5f1e154bb..050ebde759 100644 --- a/packages/backend-defaults/api-report.md +++ b/packages/backend-defaults/api-report.md @@ -12,6 +12,6 @@ export function createBackend(options?: CreateBackendOptions): Backend; // @public (undocumented) export interface CreateBackendOptions { // (undocumented) - services?: ServiceFactory[]; + services?: (ServiceFactory | (() => ServiceFactory))[]; } ``` diff --git a/packages/backend-defaults/src/CreateBackend.ts b/packages/backend-defaults/src/CreateBackend.ts index 819a77ea11..5130d6733b 100644 --- a/packages/backend-defaults/src/CreateBackend.ts +++ b/packages/backend-defaults/src/CreateBackend.ts @@ -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( - 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 ?? [])], }); } diff --git a/packages/backend-plugin-api/api-report.md b/packages/backend-plugin-api/api-report.md index 5ea7f52c4b..30e58237af 100644 --- a/packages/backend-plugin-api/api-report.md +++ b/packages/backend-plugin-api/api-report.md @@ -109,16 +109,28 @@ export function createServiceFactory< TDeps extends { [name in string]: unknown; }, + TOpts extends + | { + [name in string]: unknown; + } + | undefined = undefined, >(factory: { service: ServiceRef; deps: TypesToServiceRef; - factory(deps: DepsToDepFactories): Promise>; -}): ServiceFactory; + factory( + deps: DepsToDepFactories, + options: TOpts, + ): Promise>; +}): undefined extends TOpts + ? (options?: TOpts) => ServiceFactory + : (options: TOpts) => ServiceFactory; // @public (undocumented) export function createServiceRef(options: { id: string; - defaultFactory?: (service: ServiceRef) => Promise>; + defaultFactory?: ( + service: ServiceRef, + ) => Promise | (() => ServiceFactory)>; }): ServiceRef; // @public (undocumented) diff --git a/packages/backend-plugin-api/src/services/system/types.test.ts b/packages/backend-plugin-api/src/services/system/types.test.ts new file mode 100644 index 0000000000..29512d3a58 --- /dev/null +++ b/packages/backend-plugin-api/src/services/system/types.test.ts @@ -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({ 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({ 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({ 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(); + }); +}); diff --git a/packages/backend-plugin-api/src/services/system/types.ts b/packages/backend-plugin-api/src/services/system/types.ts index d622b3917f..a19c17bb9c 100644 --- a/packages/backend-plugin-api/src/services/system/types.ts +++ b/packages/backend-plugin-api/src/services/system/types.ts @@ -41,7 +41,9 @@ export type InternalServiceRef = ServiceRef & { * The default factory that will be used to create service * instances if no other factory is provided. */ - __defaultFactory?: (service: ServiceRef) => Promise>; + __defaultFactory?: ( + service: ServiceRef, + ) => Promise | (() => ServiceFactory)>; }; /** @public */ @@ -67,7 +69,9 @@ export type ServiceFactory = { */ export function createServiceRef(options: { id: string; - defaultFactory?: (service: ServiceRef) => Promise>; + defaultFactory?: ( + service: ServiceRef, + ) => Promise | (() => ServiceFactory)>; }): ServiceRef { 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; deps: TypesToServiceRef; - factory(deps: DepsToDepFactories): Promise>; -}): ServiceFactory { - return factory as ServiceFactory; + factory( + deps: DepsToDepFactories, + options: TOpts, + ): Promise>; +}): undefined extends TOpts + ? (options?: TOpts) => ServiceFactory + : (options: TOpts) => ServiceFactory { + return (options?: TOpts) => ({ + service: factory.service, + deps: factory.deps, + factory(deps: DepsToDepFactories) { + return factory.factory(deps, options!); + }, + }); } diff --git a/packages/backend-test-utils/api-report.md b/packages/backend-test-utils/api-report.md index c0f6a43321..2fe4ed31d3 100644 --- a/packages/backend-test-utils/api-report.md +++ b/packages/backend-test-utils/api-report.md @@ -46,6 +46,7 @@ export interface TestBackendOptions< ...{ [index in keyof TServices]: | ServiceFactory + | (() => ServiceFactory) | [ServiceRef, Partial]; }, ]; diff --git a/packages/backend-test-utils/src/next/wiring/TestBackend.ts b/packages/backend-test-utils/src/next/wiring/TestBackend.ts index 98e979793e..f93b34cdd8 100644 --- a/packages/backend-test-utils/src/next/wiring/TestBackend.ts +++ b/packages/backend-test-utils/src/next/wiring/TestBackend.ts @@ -32,6 +32,7 @@ export interface TestBackendOptions< ...{ [index in keyof TServices]: | ServiceFactory + | (() => ServiceFactory) | [ServiceRef, Partial]; }, ];