From b86efa2d04a4e8475aeca0845beafdd9bd4ddd71 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 8 Feb 2023 11:13:58 +0100 Subject: [PATCH] backend-plugin-api: make ServiceFactory opaque Signed-off-by: Patrik Oldsberg --- .changeset/quiet-pets-count.md | 6 ++ .changeset/wild-melons-shout.md | 5 + packages/backend-app-api/api-report.md | 60 +++++++++--- .../httpRouterServiceFactory.test.ts | 11 +-- .../tokenManagerServiceFactory.test.ts | 11 +-- .../src/wiring/ServiceRegistry.ts | 82 +++++++++++------ packages/backend-plugin-api/api-report.md | 54 ++++------- .../src/services/system/types.ts | 91 ++++++++++++------- packages/backend-test-utils/api-report.md | 30 +++--- .../src/next/services/mockServices.ts | 12 ++- 10 files changed, 212 insertions(+), 150 deletions(-) create mode 100644 .changeset/quiet-pets-count.md create mode 100644 .changeset/wild-melons-shout.md diff --git a/.changeset/quiet-pets-count.md b/.changeset/quiet-pets-count.md new file mode 100644 index 0000000000..bc7da8f3e7 --- /dev/null +++ b/.changeset/quiet-pets-count.md @@ -0,0 +1,6 @@ +--- +'@backstage/backend-test-utils': patch +'@backstage/backend-app-api': patch +--- + +Updated usage of `ServiceFactory`. diff --git a/.changeset/wild-melons-shout.md b/.changeset/wild-melons-shout.md new file mode 100644 index 0000000000..5a79612f22 --- /dev/null +++ b/.changeset/wild-melons-shout.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-plugin-api': minor +--- + +Switch `ServiceFactory` to be an opaque type, keeping only the `service` field as public API, but also adding a type parameter for the service scope. diff --git a/packages/backend-app-api/api-report.md b/packages/backend-app-api/api-report.md index e4c3e34751..9329610a4c 100644 --- a/packages/backend-app-api/api-report.md +++ b/packages/backend-app-api/api-report.md @@ -48,7 +48,10 @@ export interface Backend { } // @public (undocumented) -export const cacheServiceFactory: () => ServiceFactory; +export const cacheServiceFactory: () => ServiceFactory< + PluginCacheManager, + 'plugin' +>; // @public (undocumented) export interface ConfigFactoryOptions { @@ -59,7 +62,7 @@ export interface ConfigFactoryOptions { // @public (undocumented) export const configServiceFactory: ( options?: ConfigFactoryOptions | undefined, -) => ServiceFactory; +) => ServiceFactory; // @public (undocumented) export function createConfigSecretEnumerator(options: { @@ -88,7 +91,10 @@ export interface CreateSpecializedBackendOptions { } // @public (undocumented) -export const databaseServiceFactory: () => ServiceFactory; +export const databaseServiceFactory: () => ServiceFactory< + PluginDatabaseManager, + 'plugin' +>; // @public export class DefaultRootHttpRouter implements RootHttpRouterService { @@ -106,7 +112,10 @@ export interface DefaultRootHttpRouterOptions { } // @public (undocumented) -export const discoveryServiceFactory: () => ServiceFactory; +export const discoveryServiceFactory: () => ServiceFactory< + PluginEndpointDiscovery, + 'plugin' +>; // @public export interface ExtendedHttpServer extends http.Server { @@ -126,7 +135,7 @@ export interface HttpRouterFactoryOptions { // @public (undocumented) export const httpRouterServiceFactory: ( options?: HttpRouterFactoryOptions | undefined, -) => ServiceFactory; +) => ServiceFactory; // @public export type HttpServerCertificateOptions = @@ -160,10 +169,13 @@ export type IdentityFactoryOptions = { // @public (undocumented) export const identityServiceFactory: ( options?: IdentityFactoryOptions | undefined, -) => ServiceFactory; +) => ServiceFactory; // @public -export const lifecycleServiceFactory: () => ServiceFactory; +export const lifecycleServiceFactory: () => ServiceFactory< + LifecycleService, + 'plugin' +>; // @public export function loadBackendConfig(options: { @@ -174,7 +186,10 @@ export function loadBackendConfig(options: { }>; // @public (undocumented) -export const loggerServiceFactory: () => ServiceFactory; +export const loggerServiceFactory: () => ServiceFactory< + LoggerService, + 'plugin' +>; // @public export class MiddlewareFactory { @@ -202,7 +217,10 @@ export interface MiddlewareFactoryOptions { } // @public (undocumented) -export const permissionsServiceFactory: () => ServiceFactory; +export const permissionsServiceFactory: () => ServiceFactory< + PermissionsService, + 'plugin' +>; // @public export function readCorsOptions(config?: Config): CorsOptions; @@ -238,22 +256,34 @@ export type RootHttpRouterFactoryOptions = { // @public (undocumented) export const rootHttpRouterServiceFactory: ( options?: RootHttpRouterFactoryOptions | undefined, -) => ServiceFactory; +) => ServiceFactory; // @public -export const rootLifecycleServiceFactory: () => ServiceFactory; +export const rootLifecycleServiceFactory: () => ServiceFactory< + RootLifecycleService, + 'root' +>; // @public (undocumented) -export const rootLoggerServiceFactory: () => ServiceFactory; +export const rootLoggerServiceFactory: () => ServiceFactory< + RootLoggerService, + 'root' +>; // @public (undocumented) -export const schedulerServiceFactory: () => ServiceFactory; +export const schedulerServiceFactory: () => ServiceFactory< + SchedulerService, + 'plugin' +>; // @public (undocumented) -export const tokenManagerServiceFactory: () => ServiceFactory; +export const tokenManagerServiceFactory: () => ServiceFactory< + TokenManagerService, + 'plugin' +>; // @public (undocumented) -export const urlReaderServiceFactory: () => ServiceFactory; +export const urlReaderServiceFactory: () => ServiceFactory; // @public export class WinstonLogger implements RootLoggerService { diff --git a/packages/backend-app-api/src/services/implementations/httpRouter/httpRouterServiceFactory.test.ts b/packages/backend-app-api/src/services/implementations/httpRouter/httpRouterServiceFactory.test.ts index 2e124c98b6..e52b4c9a36 100644 --- a/packages/backend-app-api/src/services/implementations/httpRouter/httpRouterServiceFactory.test.ts +++ b/packages/backend-app-api/src/services/implementations/httpRouter/httpRouterServiceFactory.test.ts @@ -14,19 +14,12 @@ * limitations under the License. */ -import { - HttpRouterService, - ServiceFactory, -} from '@backstage/backend-plugin-api'; import { httpRouterServiceFactory } from './httpRouterServiceFactory'; describe('httpRouterFactory', () => { it('should register plugin paths', async () => { const rootHttpRouter = { use: jest.fn() }; - const factory = httpRouterServiceFactory() as Exclude< - ServiceFactory, - { scope: 'root' } - >; + const factory = httpRouterServiceFactory() as any; const handler1 = () => {}; const router1 = await factory.factory( @@ -57,7 +50,7 @@ describe('httpRouterFactory', () => { const rootHttpRouter = { use: jest.fn() }; const factory = httpRouterServiceFactory({ getPath: id => `/some/${id}/path`, - }) as Exclude, { scope: 'root' }>; + }) as any; const handler1 = () => {}; const router1 = await factory.factory( diff --git a/packages/backend-app-api/src/services/implementations/tokenManager/tokenManagerServiceFactory.test.ts b/packages/backend-app-api/src/services/implementations/tokenManager/tokenManagerServiceFactory.test.ts index 6c33829967..aca5211e2e 100644 --- a/packages/backend-app-api/src/services/implementations/tokenManager/tokenManagerServiceFactory.test.ts +++ b/packages/backend-app-api/src/services/implementations/tokenManager/tokenManagerServiceFactory.test.ts @@ -14,11 +14,7 @@ * limitations under the License. */ -import { - LoggerService, - ServiceFactory, - TokenManagerService, -} from '@backstage/backend-plugin-api'; +import { LoggerService } from '@backstage/backend-plugin-api'; import { ConfigReader } from '@backstage/config'; import { tokenManagerServiceFactory } from './tokenManagerServiceFactory'; @@ -26,10 +22,7 @@ describe('tokenManagerFactory', () => { it('should create managers that can share tokens in development', async () => { (process.env as { NODE_ENV?: string }).NODE_ENV = 'development'; - const factory = tokenManagerServiceFactory() as Exclude< - ServiceFactory, - { scope: 'root' } - >; + const factory = tokenManagerServiceFactory() as any; const deps = { config: new ConfigReader({}), logger: { warn() {} } as unknown as LoggerService, diff --git a/packages/backend-app-api/src/wiring/ServiceRegistry.ts b/packages/backend-app-api/src/wiring/ServiceRegistry.ts index 68dff3f7c3..b5b55f67cf 100644 --- a/packages/backend-app-api/src/wiring/ServiceRegistry.ts +++ b/packages/backend-app-api/src/wiring/ServiceRegistry.ts @@ -18,36 +18,66 @@ import { ServiceFactory, ServiceRef, coreServices, + createServiceFactory, } from '@backstage/backend-plugin-api'; import { stringifyError } from '@backstage/errors'; import { EnumerableServiceHolder } from './types'; +// Direct internal import to avoid duplication +// eslint-disable-next-line @backstage/no-forbidden-package-imports +import { InternalServiceFactory } from '@backstage/backend-plugin-api/src/services/system/types'; /** * Keep in sync with `@backstage/backend-plugin-api/src/services/system/types.ts` * @internal */ -export type InternalServiceRef = ServiceRef & { +export type InternalServiceRef = ServiceRef & { __defaultFactory?: ( - service: ServiceRef, - ) => Promise | (() => ServiceFactory)>; + service: ServiceRef, + ) => Promise ServiceFactory)>; }; +function toInternalServiceFactory( + factory: ServiceFactory, +): InternalServiceFactory { + const f = factory as InternalServiceFactory; + if (f.$$type !== '@backstage/ServiceFactory') { + throw new Error(`Invalid service factory, bad type '${f.$$type}'`); + } + if (f.version !== 'v1') { + throw new Error(`Invalid service factory, bad version '${f.version}'`); + } + return f; +} + +const pluginMetadataServiceFactory = createServiceFactory( + (options: { pluginId: string }) => ({ + service: coreServices.pluginMetadata, + deps: {}, + factory: async () => ({ getId: () => options.pluginId }), + }), +); + export class ServiceRegistry implements EnumerableServiceHolder { - readonly #providedFactories: Map; - readonly #loadedDefaultFactories: Map>; + readonly #providedFactories: Map; + readonly #loadedDefaultFactories: Map< + Function, + Promise + >; readonly #implementations: Map< - ServiceFactory, + InternalServiceFactory, { context: Promise; byPlugin: Map>; } >; readonly #rootServiceImplementations = new Map< - ServiceFactory, + InternalServiceFactory, Promise >(); - constructor(factories: Array>) { - this.#providedFactories = new Map(factories.map(f => [f.service.id, f])); + constructor(factories: Array) { + this.#providedFactories = new Map( + factories.map(sf => [sf.service.id, toInternalServiceFactory(sf)]), + ); this.#loadedDefaultFactories = new Map(); this.#implementations = new Map(); } @@ -55,23 +85,19 @@ export class ServiceRegistry implements EnumerableServiceHolder { #resolveFactory( ref: ServiceRef, pluginId: string, - ): Promise | undefined { + ): Promise | 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< - ServiceFactory - >({ - scope: 'plugin', - service: coreServices.pluginMetadata, - deps: {}, - factory: async () => ({ getId: () => pluginId }), - }); + return Promise.resolve( + toInternalServiceFactory(pluginMetadataServiceFactory({ pluginId })), + ); } - let resolvedFactory: Promise | ServiceFactory | undefined = - this.#providedFactories.get(ref.id); - const { __defaultFactory: defaultFactory } = - ref as InternalServiceRef; + let resolvedFactory: + | Promise + | InternalServiceFactory + | undefined = this.#providedFactories.get(ref.id); + const { __defaultFactory: defaultFactory } = ref as InternalServiceRef; if (!resolvedFactory && !defaultFactory) { return undefined; } @@ -82,8 +108,8 @@ export class ServiceRegistry implements EnumerableServiceHolder { loadedFactory = Promise.resolve() .then(() => defaultFactory!(ref)) .then(f => - typeof f === 'function' ? f() : f, - ) as Promise; + toInternalServiceFactory(typeof f === 'function' ? f() : f), + ); this.#loadedDefaultFactories.set(defaultFactory!, loadedFactory); } resolvedFactory = loadedFactory.catch(error => { @@ -100,7 +126,7 @@ export class ServiceRegistry implements EnumerableServiceHolder { return Promise.resolve(resolvedFactory); } - #checkForMissingDeps(factory: ServiceFactory, pluginId: string) { + #checkForMissingDeps(factory: InternalServiceFactory, pluginId: string) { const missingDeps = Object.values(factory.deps).filter(ref => { if (ref.id === coreServices.pluginMetadata.id) { return false; @@ -109,7 +135,7 @@ export class ServiceRegistry implements EnumerableServiceHolder { return false; } - return !(ref as InternalServiceRef).__defaultFactory; + return !(ref as InternalServiceRef).__defaultFactory; }); if (missingDeps.length) { @@ -126,7 +152,7 @@ export class ServiceRegistry implements EnumerableServiceHolder { get(ref: ServiceRef, pluginId: string): Promise | undefined { return this.#resolveFactory(ref, pluginId)?.then(factory => { - if (factory.scope === 'root') { + if (factory.service.scope === 'root') { let existing = this.#rootServiceImplementations.get(factory); if (!existing) { this.#checkForMissingDeps(factory, pluginId); @@ -143,7 +169,7 @@ export class ServiceRegistry implements EnumerableServiceHolder { } existing = Promise.all(rootDeps).then(entries => - factory.factory(Object.fromEntries(entries)), + factory.factory(Object.fromEntries(entries), undefined), ); this.#rootServiceImplementations.set(factory, existing); } diff --git a/packages/backend-plugin-api/api-report.md b/packages/backend-plugin-api/api-report.md index 1219bdaf3f..6a0b5e51e7 100644 --- a/packages/backend-plugin-api/api-report.md +++ b/packages/backend-plugin-api/api-report.md @@ -145,7 +145,7 @@ export function createServiceFactory< TOpts extends object | undefined = undefined, >( config: RootServiceFactoryConfig, -): () => ServiceFactory; +): () => ServiceFactory; // @public export function createServiceFactory< @@ -157,7 +157,7 @@ export function createServiceFactory< TOpts extends object | undefined = undefined, >( config: (options?: TOpts) => RootServiceFactoryConfig, -): (options?: TOpts) => ServiceFactory; +): (options?: TOpts) => ServiceFactory; // @public export function createServiceFactory< @@ -169,7 +169,7 @@ export function createServiceFactory< TOpts extends object | undefined = undefined, >( config: (options: TOpts) => RootServiceFactoryConfig, -): (options: TOpts) => ServiceFactory; +): (options: TOpts) => ServiceFactory; // @public export function createServiceFactory< @@ -182,7 +182,7 @@ export function createServiceFactory< TOpts extends object | undefined = undefined, >( config: PluginServiceFactoryConfig, -): () => ServiceFactory; +): () => ServiceFactory; // @public export function createServiceFactory< @@ -197,7 +197,7 @@ export function createServiceFactory< config: ( options?: TOpts, ) => PluginServiceFactoryConfig, -): (options?: TOpts) => ServiceFactory; +): (options?: TOpts) => ServiceFactory; // @public export function createServiceFactory< @@ -214,7 +214,7 @@ export function createServiceFactory< | (( options: TOpts, ) => PluginServiceFactoryConfig), -): (options: TOpts) => ServiceFactory; +): (options: TOpts) => ServiceFactory; // @public export function createServiceRef( @@ -427,38 +427,18 @@ export type SearchResponseFile = { }; // @public (undocumented) -export type ServiceFactory = - | { - scope: 'root'; - service: ServiceRef; - deps: { - [key in string]: ServiceRef; - }; - factory(deps: { - [key in string]: unknown; - }): Promise; - } - | { - scope: 'plugin'; - service: ServiceRef; - deps: { - [key in string]: ServiceRef; - }; - createRootContext?(deps: { - [key in string]: unknown; - }): Promise; - factory( - deps: { - [key in string]: unknown; - }, - context: unknown, - ): Promise; - }; +export interface ServiceFactory< + TService = unknown, + TScope extends 'plugin' | 'root' = 'plugin' | 'root', +> { + // (undocumented) + $$type: '@backstage/ServiceFactory'; + // (undocumented) + service: ServiceRef; +} // @public -export type ServiceFactoryOrFunction = - | ServiceFactory - | (() => ServiceFactory); +export type ServiceFactoryOrFunction = ServiceFactory | (() => ServiceFactory); // @public export type ServiceRef< @@ -477,7 +457,7 @@ export interface ServiceRefConfig { // (undocumented) defaultFactory?: ( service: ServiceRef, - ) => Promise>; + ) => Promise; // (undocumented) id: string; // (undocumented) diff --git a/packages/backend-plugin-api/src/services/system/types.ts b/packages/backend-plugin-api/src/services/system/types.ts index ea151e00b9..190ef7c13e 100644 --- a/packages/backend-plugin-api/src/services/system/types.ts +++ b/packages/backend-plugin-api/src/services/system/types.ts @@ -48,34 +48,35 @@ export type ServiceRef< }; /** @public */ -export type ServiceFactory = - | { - // 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; - deps: { [key in string]: ServiceRef }; - factory(deps: { [key in string]: unknown }): Promise; - } - | { - scope: 'plugin'; - service: ServiceRef; - deps: { [key in string]: ServiceRef }; - createRootContext?(deps: { [key in string]: unknown }): Promise; - factory( - deps: { [key in string]: unknown }, - context: unknown, - ): Promise; - }; +export interface ServiceFactory< + TService = unknown, + TScope extends 'plugin' | 'root' = 'plugin' | 'root', +> { + $$type: '@backstage/ServiceFactory'; + + service: ServiceRef; +} + +/** @internal */ +export interface InternalServiceFactory< + TService = unknown, + TScope extends 'plugin' | 'root' = 'plugin' | 'root', +> extends ServiceFactory { + version: 'v1'; + deps: { [key in string]: ServiceRef }; + createRootContext?(deps: { [key in string]: unknown }): Promise; + factory( + deps: { [key in string]: unknown }, + context: unknown, + ): Promise; +} /** * Represents either a {@link ServiceFactory} or a function that returns one. * * @public */ -export type ServiceFactoryOrFunction = - | ServiceFactory - | (() => ServiceFactory); +export type ServiceFactoryOrFunction = ServiceFactory | (() => ServiceFactory); /** @public */ export interface ServiceRefConfig { @@ -83,7 +84,7 @@ export interface ServiceRefConfig { scope?: TScope; defaultFactory?: ( service: ServiceRef, - ) => Promise>; + ) => Promise; } /** @@ -175,7 +176,7 @@ export function createServiceFactory< TOpts extends object | undefined = undefined, >( config: RootServiceFactoryConfig, -): () => ServiceFactory; +): () => ServiceFactory; /** * Creates a root scoped service factory with optional options. * @@ -189,7 +190,7 @@ export function createServiceFactory< TOpts extends object | undefined = undefined, >( config: (options?: TOpts) => RootServiceFactoryConfig, -): (options?: TOpts) => ServiceFactory; +): (options?: TOpts) => ServiceFactory; /** * Creates a root scoped service factory with required options. * @@ -203,7 +204,7 @@ export function createServiceFactory< TOpts extends object | undefined = undefined, >( config: (options: TOpts) => RootServiceFactoryConfig, -): (options: TOpts) => ServiceFactory; +): (options: TOpts) => ServiceFactory; /** * Creates a plugin scoped service factory without options. * @@ -218,7 +219,7 @@ export function createServiceFactory< TOpts extends object | undefined = undefined, >( config: PluginServiceFactoryConfig, -): () => ServiceFactory; +): () => ServiceFactory; /** * Creates a plugin scoped service factory with optional options. * @@ -235,7 +236,7 @@ export function createServiceFactory< config: ( options?: TOpts, ) => PluginServiceFactoryConfig, -): (options?: TOpts) => ServiceFactory; +): (options?: TOpts) => ServiceFactory; /** * Creates a plugin scoped service factory with required options. * @@ -254,7 +255,7 @@ export function createServiceFactory< | (( options: TOpts, ) => PluginServiceFactoryConfig), -): (options: TOpts) => ServiceFactory; +): (options: TOpts) => ServiceFactory; export function createServiceFactory< TService, TImpl extends TService, @@ -271,20 +272,40 @@ export function createServiceFactory< ) => PluginServiceFactoryConfig) | (() => RootServiceFactoryConfig) | (() => PluginServiceFactoryConfig), -): (options: TOpts) => ServiceFactory { +): (options: TOpts) => ServiceFactory { const configCallback = typeof config === 'function' ? config : () => config; - return (options: TOpts) => { - const c = configCallback(options); + return ( + options: TOpts, + ): InternalServiceFactory => { + const anyConf = configCallback(options); + if (anyConf.service.scope === 'root') { + const c = anyConf as RootServiceFactoryConfig; + return { + $$type: '@backstage/ServiceFactory', + version: 'v1', + service: c.service, + deps: c.deps, + factory: async (deps: TDeps) => c.factory(deps), + }; + } + const c = anyConf as PluginServiceFactoryConfig< + TService, + TContext, + TImpl, + TDeps + >; return { - ...c, + $$type: '@backstage/ServiceFactory', + version: 'v1', + service: c.service, ...('createRootContext' in c ? { createRootContext: async (deps: TDeps) => c?.createRootContext?.(deps), } : {}), + deps: c.deps, factory: async (deps: TDeps, ctx: TContext) => c.factory(deps, ctx), - scope: c.service.scope, - } as ServiceFactory; + }; }; } diff --git a/packages/backend-test-utils/api-report.md b/packages/backend-test-utils/api-report.md index d99cf51866..0b3d7dd843 100644 --- a/packages/backend-test-utils/api-report.md +++ b/packages/backend-test-utils/api-report.md @@ -33,7 +33,7 @@ export namespace mockServices { // (undocumented) export namespace cache { const // (undocumented) - factory: () => ServiceFactory; + factory: () => ServiceFactory; } // (undocumented) export function config(options?: config.Options): ConfigService; @@ -44,46 +44,48 @@ export namespace mockServices { data?: JsonObject; }; const // (undocumented) - factory: (options?: Options | undefined) => ServiceFactory; + factory: ( + options?: Options | undefined, + ) => ServiceFactory; } // (undocumented) export namespace database { const // (undocumented) - factory: () => ServiceFactory; + factory: () => ServiceFactory; } // (undocumented) export namespace httpRouter { const // (undocumented) factory: ( options?: HttpRouterFactoryOptions | undefined, - ) => ServiceFactory; + ) => ServiceFactory; } // (undocumented) export function identity(): IdentityService; // (undocumented) export namespace identity { const // (undocumented) - factory: () => ServiceFactory; + factory: () => ServiceFactory; } // (undocumented) export namespace lifecycle { const // (undocumented) - factory: () => ServiceFactory; + factory: () => ServiceFactory; } // (undocumented) export namespace logger { const // (undocumented) - factory: () => ServiceFactory; + factory: () => ServiceFactory; } // (undocumented) export namespace permissions { const // (undocumented) - factory: () => ServiceFactory; + factory: () => ServiceFactory; } // (undocumented) export namespace rootLifecycle { const // (undocumented) - factory: () => ServiceFactory; + factory: () => ServiceFactory; } // (undocumented) export function rootLogger(options?: rootLogger.Options): LoggerService; @@ -94,24 +96,26 @@ export namespace mockServices { level?: 'none' | 'error' | 'warn' | 'info' | 'debug'; }; const // (undocumented) - factory: (options?: Options | undefined) => ServiceFactory; + factory: ( + options?: Options | undefined, + ) => ServiceFactory; } // (undocumented) export namespace scheduler { const // (undocumented) - factory: () => ServiceFactory; + factory: () => ServiceFactory; } // (undocumented) export function tokenManager(): TokenManagerService; // (undocumented) export namespace tokenManager { const // (undocumented) - factory: () => ServiceFactory; + factory: () => ServiceFactory; } // (undocumented) export namespace urlReader { const // (undocumented) - factory: () => ServiceFactory; + factory: () => ServiceFactory; } } diff --git a/packages/backend-test-utils/src/next/services/mockServices.ts b/packages/backend-test-utils/src/next/services/mockServices.ts index e5b3b32be7..2e97cc63fb 100644 --- a/packages/backend-test-utils/src/next/services/mockServices.ts +++ b/packages/backend-test-utils/src/next/services/mockServices.ts @@ -40,17 +40,21 @@ import { JsonObject } from '@backstage/types'; import { MockIdentityService } from './MockIdentityService'; import { MockRootLoggerService } from './MockRootLoggerService'; -function simpleFactory( - ref: ServiceRef, +function simpleFactory< + TService, + TScope extends 'root' | 'plugin', + TOptions extends [options?: object] = [], +>( + ref: ServiceRef, factory: (...options: TOptions) => TService, -): (...options: TOptions) => ServiceFactory { +): (...options: TOptions) => ServiceFactory { return createServiceFactory((options: unknown) => ({ service: ref as ServiceRef, deps: {}, async factory() { return (factory as any)(options); }, - })); + })) as (...options: TOptions) => ServiceFactory; } /**