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:
@@ -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>;
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user