From 60f606736a0208dc0a6eb88964ec1c7be5b69880 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 13 Jan 2023 15:18:38 +0100 Subject: [PATCH] backend-plugin-api: refactor service factory config to support TS 4.9 Co-authored-by: blam Signed-off-by: Patrik Oldsberg --- .../src/services/system/types.test.ts | 200 ++++++++++++++---- .../src/services/system/types.ts | 141 +++++++++--- 2 files changed, 276 insertions(+), 65 deletions(-) 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 9cbc45151d..7c0942a4c3 100644 --- a/packages/backend-plugin-api/src/services/system/types.test.ts +++ b/packages/backend-plugin-api/src/services/system/types.test.ts @@ -20,13 +20,19 @@ const ref = createServiceRef({ id: 'x' }); const rootDep = createServiceRef({ id: 'y', scope: 'root' }); const pluginDep = createServiceRef({ id: 'z' }); +interface TestOptions { + x: number; +} +function unused(..._any: any[]) {} + describe('createServiceFactory', () => { it('should create a meta factory with no options', () => { const metaFactory = createServiceFactory({ service: ref, deps: {}, + async rootFactory() {}, async factory(_deps) { - return async () => 'x'; + return 'x'; }, }); expect(metaFactory).toEqual(expect.any(Function)); @@ -49,8 +55,9 @@ describe('createServiceFactory', () => { const metaFactory = createServiceFactory((_opts?: { x: number }) => ({ service: ref, deps: {}, + async rootFactory() {}, async factory() { - return async () => 'x'; + return 'x'; }, })); expect(metaFactory).toEqual(expect.any(Function)); @@ -72,8 +79,9 @@ describe('createServiceFactory', () => { const metaFactory = createServiceFactory((_opts: { x: number }) => ({ service: ref, deps: {}, + async rootFactory() {}, async factory() { - return async () => 'x'; + return 'x'; }, })); expect(metaFactory).toEqual(expect.any(Function)); @@ -94,14 +102,12 @@ describe('createServiceFactory', () => { }); it('should create a meta factory with optional options as interface', () => { - interface TestOptions { - x: number; - } const metaFactory = createServiceFactory((_opts?: TestOptions) => ({ service: ref, deps: {}, + async rootFactory() {}, async factory() { - return async () => 'x'; + return 'x'; }, })); expect(metaFactory).toEqual(expect.any(Function)); @@ -120,14 +126,12 @@ describe('createServiceFactory', () => { }); it('should create a meta factory with required options as interface', () => { - interface TestOptions { - x: number; - } const metaFactory = createServiceFactory((_opts: TestOptions) => ({ service: ref, deps: {}, + async rootFactory() {}, async factory() { - return async () => 'x'; + return 'x'; }, })); expect(metaFactory).toEqual(expect.any(Function)); @@ -147,15 +151,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 +162,134 @@ 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)); + }); + + 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 root scoped factory with dependencies and required 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); + // @ts-expect-error + metaFactory(undefined); + // @ts-expect-error + metaFactory(); + }); + + it('should create factory with dependencies', () => { + const metaFactory = createServiceFactory({ + service: createServiceRef({ id: 'derp' }), + deps: { + root: rootDep, + plugin: pluginDep, + }, + async rootFactory({ 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)); + + // @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 rootFactory({ 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 +310,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 rootFactory({ 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)); diff --git a/packages/backend-plugin-api/src/services/system/types.ts b/packages/backend-plugin-api/src/services/system/types.ts index 34c0a50005..71359bad68 100644 --- a/packages/backend-plugin-api/src/services/system/types.ts +++ b/packages/backend-plugin-api/src/services/system/types.ts @@ -135,46 +135,139 @@ type ServiceRefsToInstances< }; /** @public */ -export interface ServiceFactoryConfig< +export interface RootServiceFactoryConfig< TService, - TScope extends 'root' | 'plugin', TImpl extends TService, TDeps extends { [name in string]: ServiceRef }, > { - service: ServiceRef; + service: ServiceRef; deps: TDeps; + factory(deps: ServiceRefsToInstances): Promise; +} + +/** @public */ +export interface PluginServiceFactoryConfig< + TService, + TContext, + TImpl extends TService, + TDeps extends { [name in string]: ServiceRef }, +> { + service: ServiceRef; + deps: TDeps; + rootFactory(deps: ServiceRefsToInstances): Promise; factory( - deps: ServiceRefsToInstances, - ): TScope extends 'root' - ? Promise - : Promise<(deps: ServiceRefsToInstances) => Promise>; + deps: ServiceRefsToInstances, + context: TContext, + ): Promise; } /** + * Creates a root scoped service factory. + * * @public + * @param config */ export function createServiceFactory< TService, - TScope extends 'root' | 'plugin', TImpl extends TService, TDeps extends { [name in string]: ServiceRef }, - TOpts extends [options?: object] = [], + TOpts extends object | undefined = undefined, +>( + config: RootServiceFactoryConfig, +): (params: TOpts) => ServiceFactory; +/** + * Creates a root scoped service factory with optional options. + * + * @public + * @param config + */ +export function createServiceFactory< + TService, + TImpl extends TService, + TDeps extends { [name in string]: ServiceRef }, + TOpts extends object | undefined = undefined, +>( + config: (options?: TOpts) => RootServiceFactoryConfig, +): (options?: TOpts) => ServiceFactory; +/** + * Creates a plugin scoped service factory with required options. + * + * @public + * @param config + */ +export function createServiceFactory< + TService, + TImpl extends TService, + TDeps extends { [name in string]: ServiceRef }, + TOpts extends object | undefined = undefined, +>( + config: (options: TOpts) => RootServiceFactoryConfig, +): (options: TOpts) => ServiceFactory; +/** + * Creates a plugin scoped service factory with optional options. + * + * @public + * @param config + */ +export function createServiceFactory< + TService, + TContext, + TImpl extends TService, + TDeps extends { [name in string]: ServiceRef }, + TOpts extends object | undefined = undefined, +>( + config: PluginServiceFactoryConfig, +): () => ServiceFactory; +/** + * Creates a plugin scoped service factory with required options. + * + * @public + * @param config + */ +export function createServiceFactory< + TService, + TContext, + TImpl extends TService, + TDeps extends { [name in string]: ServiceRef }, + TOpts extends object | undefined = undefined, +>( + config: ( + options?: TOpts, + ) => PluginServiceFactoryConfig, +): (options?: TOpts) => ServiceFactory; +export function createServiceFactory< + TService, + TContext, + TImpl extends TService, + TDeps extends { [name in string]: ServiceRef }, + TOpts extends object | undefined = undefined, >( config: - | ServiceFactoryConfig + | PluginServiceFactoryConfig | (( - ...options: TOpts - ) => ServiceFactoryConfig), -): (...params: TOpts) => ServiceFactory { - if (typeof config === 'function') { - return (...opts: TOpts) => { - const c = config(...opts); - return { ...c, scope: c.service.scope } as ServiceFactory; - }; - } - return () => - ({ - ...config, - scope: config.service.scope, - } as ServiceFactory); + options: TOpts, + ) => PluginServiceFactoryConfig), +): (options: TOpts) => ServiceFactory; +export function createServiceFactory< + TService, + TContext, + TImpl extends TService, + TDeps extends { [name in string]: ServiceRef }, + TOpts extends object | undefined = undefined, +>( + config: + | RootServiceFactoryConfig + | PluginServiceFactoryConfig + | ((options: TOpts) => RootServiceFactoryConfig) + | (( + options: TOpts, + ) => PluginServiceFactoryConfig) + | (() => RootServiceFactoryConfig) + | (() => PluginServiceFactoryConfig), +): (options: TOpts) => ServiceFactory { + const configCallback = typeof config === 'function' ? config : () => config; + return (options: TOpts) => { + const c = configCallback(options); + return { ...c, scope: c.service.scope } as ServiceFactory; + }; }