From f8c313f80f72f0980748155a866fea2f01c47668 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 14 Jan 2023 12:46:29 +0100 Subject: [PATCH] backend-plugin-api: allow service factories to be sync Signed-off-by: Patrik Oldsberg --- .changeset/real-poets-run.md | 4 +- .../implementations/logger/loggerFactory.ts | 2 +- packages/backend-plugin-api/api-report.md | 6 +- .../src/services/system/types.test.ts | 59 +++++++++++++++++-- .../src/services/system/types.ts | 18 ++++-- 5 files changed, 74 insertions(+), 15 deletions(-) diff --git a/.changeset/real-poets-run.md b/.changeset/real-poets-run.md index f1bef6ed68..d626827b96 100644 --- a/.changeset/real-poets-run.md +++ b/.changeset/real-poets-run.md @@ -2,7 +2,7 @@ '@backstage/backend-plugin-api': patch --- -The `createServiceFactory` function has been updated to no longer use a duplicate callback pattern for plugin scoped services. The outer callback is now replaced by an optional `createRootContext` method. This change was made in order to support TypeScript 4.9, but it also simplifies the API surface a bit, especially for plugin scoped service factories that don't need to create a root context. +The `createServiceFactory` function has been updated to no longer use a duplicate callback pattern for plugin scoped services. The outer callback is now replaced by an optional `createRootContext` method. This change was made in order to support TypeScript 4.9, but it also simplifies the API surface a bit, especially for plugin scoped service factories that don't need to create a root context. In addition, the factory and root context functions can now be synchronous. A factory that previously would have looked like this: @@ -49,7 +49,7 @@ createServiceFactory({ rootLogger: coreServices.rootLogger, plugin: coreServices.pluginMetadata, }, - async factory({ rootLogger, plugin }) { + factory({ rootLogger, plugin }) { return rootLogger.child({ plugin: plugin.getId() }); }, }); diff --git a/packages/backend-app-api/src/services/implementations/logger/loggerFactory.ts b/packages/backend-app-api/src/services/implementations/logger/loggerFactory.ts index 14b6cc5f35..60c91d0ee6 100644 --- a/packages/backend-app-api/src/services/implementations/logger/loggerFactory.ts +++ b/packages/backend-app-api/src/services/implementations/logger/loggerFactory.ts @@ -26,7 +26,7 @@ export const loggerFactory = createServiceFactory({ rootLogger: coreServices.rootLogger, plugin: coreServices.pluginMetadata, }, - async factory({ rootLogger, plugin }) { + factory({ rootLogger, plugin }) { return rootLogger.child({ plugin: plugin.getId() }); }, }); diff --git a/packages/backend-plugin-api/api-report.md b/packages/backend-plugin-api/api-report.md index 59338fdf42..931aba62a6 100644 --- a/packages/backend-plugin-api/api-report.md +++ b/packages/backend-plugin-api/api-report.md @@ -305,14 +305,14 @@ export interface PluginServiceFactoryConfig< // (undocumented) createRootContext?( deps: ServiceRefsToInstances, - ): Promise; + ): TContext | Promise; // (undocumented) deps: TDeps; // (undocumented) factory( deps: ServiceRefsToInstances, context: TContext, - ): Promise; + ): TImpl | Promise; // (undocumented) service: ServiceRef; } @@ -383,7 +383,7 @@ export interface RootServiceFactoryConfig< // (undocumented) deps: TDeps; // (undocumented) - factory(deps: ServiceRefsToInstances): Promise; + factory(deps: ServiceRefsToInstances): TImpl | Promise; // (undocumented) service: ServiceRef; } diff --git a/packages/backend-plugin-api/src/services/system/types.test.ts b/packages/backend-plugin-api/src/services/system/types.test.ts index fab0232380..d3e9b9dd4e 100644 --- a/packages/backend-plugin-api/src/services/system/types.test.ts +++ b/packages/backend-plugin-api/src/services/system/types.test.ts @@ -26,7 +26,56 @@ interface TestOptions { 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: {}, + createRootContext() {}, + 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 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: {}, @@ -51,7 +100,7 @@ describe('createServiceFactory', () => { metaFactory(); }); - it('should create a meta factory with optional options', () => { + it('should create a factory with optional options', () => { const metaFactory = createServiceFactory((_opts?: { x: number }) => ({ service: ref, deps: {}, @@ -75,7 +124,7 @@ 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: {}, @@ -101,7 +150,7 @@ describe('createServiceFactory', () => { metaFactory(); }); - it('should create a meta factory with optional options as interface', () => { + it('should create a factory with optional options as interface', () => { const metaFactory = createServiceFactory((_opts?: TestOptions) => ({ service: ref, deps: {}, @@ -125,7 +174,7 @@ describe('createServiceFactory', () => { metaFactory(); }); - it('should create a meta factory with required options as interface', () => { + it('should create a factory with required options as interface', () => { const metaFactory = createServiceFactory((_opts: TestOptions) => ({ service: ref, deps: {}, diff --git a/packages/backend-plugin-api/src/services/system/types.ts b/packages/backend-plugin-api/src/services/system/types.ts index f4489b168b..2615c5e3a3 100644 --- a/packages/backend-plugin-api/src/services/system/types.ts +++ b/packages/backend-plugin-api/src/services/system/types.ts @@ -144,7 +144,7 @@ export interface RootServiceFactoryConfig< > { service: ServiceRef; deps: TDeps; - factory(deps: ServiceRefsToInstances): Promise; + factory(deps: ServiceRefsToInstances): TImpl | Promise; } /** @public */ @@ -158,11 +158,11 @@ export interface PluginServiceFactoryConfig< deps: TDeps; createRootContext?( deps: ServiceRefsToInstances, - ): Promise; + ): TContext | Promise; factory( deps: ServiceRefsToInstances, context: TContext, - ): Promise; + ): TImpl | Promise; } /** @@ -278,6 +278,16 @@ export function createServiceFactory< const configCallback = typeof config === 'function' ? config : () => config; return (options: TOpts) => { const c = configCallback(options); - return { ...c, scope: c.service.scope } as ServiceFactory; + 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; }; }