backend-plugin-api: refactor service factory config to support TS 4.9

Co-authored-by: blam <ben@blam.sh>
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2023-01-13 15:18:38 +01:00
parent 388a3316dc
commit 60f606736a
2 changed files with 276 additions and 65 deletions
@@ -20,13 +20,19 @@ const ref = createServiceRef<string>({ id: 'x' });
const rootDep = createServiceRef<number>({ id: 'y', scope: 'root' });
const pluginDep = createServiceRef<boolean>({ 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));
@@ -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<unknown> },
> {
service: ServiceRef<TService, TScope>;
service: ServiceRef<TService, 'root'>;
deps: TDeps;
factory(deps: ServiceRefsToInstances<TDeps, 'root'>): Promise<TImpl>;
}
/** @public */
export interface PluginServiceFactoryConfig<
TService,
TContext,
TImpl extends TService,
TDeps extends { [name in string]: ServiceRef<unknown> },
> {
service: ServiceRef<TService, 'plugin'>;
deps: TDeps;
rootFactory(deps: ServiceRefsToInstances<TDeps, 'root'>): Promise<TContext>;
factory(
deps: ServiceRefsToInstances<TDeps, 'root'>,
): TScope extends 'root'
? Promise<TImpl>
: Promise<(deps: ServiceRefsToInstances<TDeps>) => Promise<TImpl>>;
deps: ServiceRefsToInstances<TDeps>,
context: TContext,
): Promise<TImpl>;
}
/**
* 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<unknown> },
TOpts extends [options?: object] = [],
TOpts extends object | undefined = undefined,
>(
config: RootServiceFactoryConfig<TService, TImpl, TDeps>,
): (params: TOpts) => ServiceFactory<TService>;
/**
* 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<unknown> },
TOpts extends object | undefined = undefined,
>(
config: (options?: TOpts) => RootServiceFactoryConfig<TService, TImpl, TDeps>,
): (options?: TOpts) => ServiceFactory<TService>;
/**
* 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<unknown> },
TOpts extends object | undefined = undefined,
>(
config: (options: TOpts) => RootServiceFactoryConfig<TService, TImpl, TDeps>,
): (options: TOpts) => ServiceFactory<TService>;
/**
* 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<unknown> },
TOpts extends object | undefined = undefined,
>(
config: PluginServiceFactoryConfig<TService, TContext, TImpl, TDeps>,
): () => ServiceFactory<TService>;
/**
* 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<unknown> },
TOpts extends object | undefined = undefined,
>(
config: (
options?: TOpts,
) => PluginServiceFactoryConfig<TService, TContext, TImpl, TDeps>,
): (options?: TOpts) => ServiceFactory<TService>;
export function createServiceFactory<
TService,
TContext,
TImpl extends TService,
TDeps extends { [name in string]: ServiceRef<unknown> },
TOpts extends object | undefined = undefined,
>(
config:
| ServiceFactoryConfig<TService, TScope, TImpl, TDeps>
| PluginServiceFactoryConfig<TService, TContext, TImpl, TDeps>
| ((
...options: TOpts
) => ServiceFactoryConfig<TService, TScope, TImpl, TDeps>),
): (...params: TOpts) => ServiceFactory<TService> {
if (typeof config === 'function') {
return (...opts: TOpts) => {
const c = config(...opts);
return { ...c, scope: c.service.scope } as ServiceFactory<TService>;
};
}
return () =>
({
...config,
scope: config.service.scope,
} as ServiceFactory<TService>);
options: TOpts,
) => PluginServiceFactoryConfig<TService, TContext, TImpl, TDeps>),
): (options: TOpts) => ServiceFactory<TService>;
export function createServiceFactory<
TService,
TContext,
TImpl extends TService,
TDeps extends { [name in string]: ServiceRef<unknown> },
TOpts extends object | undefined = undefined,
>(
config:
| RootServiceFactoryConfig<TService, TImpl, TDeps>
| PluginServiceFactoryConfig<TService, TContext, TImpl, TDeps>
| ((options: TOpts) => RootServiceFactoryConfig<TService, TImpl, TDeps>)
| ((
options: TOpts,
) => PluginServiceFactoryConfig<TService, TContext, TImpl, TDeps>)
| (() => RootServiceFactoryConfig<TService, TImpl, TDeps>)
| (() => PluginServiceFactoryConfig<TService, TContext, TImpl, TDeps>),
): (options: TOpts) => ServiceFactory<TService> {
const configCallback = typeof config === 'function' ? config : () => config;
return (options: TOpts) => {
const c = configCallback(options);
return { ...c, scope: c.service.scope } as ServiceFactory<TService>;
};
}