backend-plugin-api: fix type inference of interface options
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/backend-plugin-api': patch
|
||||
---
|
||||
|
||||
Allow interfaces to be used for inferred option types.
|
||||
@@ -73,11 +73,7 @@ export const configServiceRef: ServiceRef<Config, 'root'>;
|
||||
|
||||
// @public (undocumented)
|
||||
export function createBackendModule<
|
||||
TOptions extends
|
||||
| {
|
||||
[name: string]: unknown;
|
||||
}
|
||||
| undefined = undefined,
|
||||
TOptions extends object | undefined = undefined,
|
||||
>(
|
||||
config: BackendModuleConfig<TOptions>,
|
||||
): undefined extends TOptions
|
||||
@@ -86,14 +82,11 @@ export function createBackendModule<
|
||||
|
||||
// @public (undocumented)
|
||||
export function createBackendPlugin<
|
||||
TOptions extends
|
||||
| {
|
||||
[name: string]: unknown;
|
||||
}
|
||||
| undefined = undefined,
|
||||
>(
|
||||
config: BackendPluginConfig<TOptions>,
|
||||
): undefined extends TOptions
|
||||
TOptions extends object | undefined = undefined,
|
||||
>(config: {
|
||||
id: string;
|
||||
register(reg: BackendRegistrationPoints, options: TOptions): void;
|
||||
}): undefined extends TOptions
|
||||
? (options?: TOptions) => BackendFeature
|
||||
: (options: TOptions) => BackendFeature;
|
||||
|
||||
@@ -110,11 +103,7 @@ export function createServiceFactory<
|
||||
TDeps extends {
|
||||
[name in string]: ServiceRef<unknown>;
|
||||
},
|
||||
TOpts extends
|
||||
| {
|
||||
[name in string]: unknown;
|
||||
}
|
||||
| undefined = undefined,
|
||||
TOpts extends object | undefined = undefined,
|
||||
>(config: {
|
||||
service: ServiceRef<TService, TScope>;
|
||||
deps: TDeps;
|
||||
|
||||
@@ -58,6 +58,8 @@ describe('createServiceFactory', () => {
|
||||
metaFactory({});
|
||||
metaFactory({ x: 1 });
|
||||
// @ts-expect-error
|
||||
metaFactory({ x: 1, y: 2 });
|
||||
// @ts-expect-error
|
||||
metaFactory(null);
|
||||
metaFactory(undefined);
|
||||
metaFactory();
|
||||
@@ -80,10 +82,145 @@ describe('createServiceFactory', () => {
|
||||
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 a meta factory with optional options as interface', () => {
|
||||
interface TestOptions {
|
||||
x: number;
|
||||
}
|
||||
const ref = createServiceRef<string>({ id: 'x' });
|
||||
const metaFactory = createServiceFactory({
|
||||
service: ref,
|
||||
deps: {},
|
||||
async factory(_deps, _opts?: TestOptions) {
|
||||
return async () => 'x';
|
||||
},
|
||||
});
|
||||
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 a meta factory with required options as interface', () => {
|
||||
interface TestOptions {
|
||||
x: number;
|
||||
}
|
||||
const ref = createServiceRef<string>({ id: 'x' });
|
||||
const metaFactory = createServiceFactory({
|
||||
service: ref,
|
||||
deps: {},
|
||||
async factory(_deps, _opts: TestOptions) {
|
||||
return async () => 'x';
|
||||
},
|
||||
});
|
||||
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 only allow objects as options', () => {
|
||||
const ref = createServiceRef<string>({ id: 'x' });
|
||||
const metaFactory = createServiceFactory({
|
||||
service: ref,
|
||||
deps: {},
|
||||
// @ts-expect-error
|
||||
async factory(_deps, _opts: string) {
|
||||
return async () => 'x';
|
||||
},
|
||||
});
|
||||
expect(metaFactory).toEqual(expect.any(Function));
|
||||
createServiceFactory({
|
||||
service: ref,
|
||||
deps: {},
|
||||
// @ts-expect-error
|
||||
async factory(_deps, _opts: number) {
|
||||
return async () => 'x';
|
||||
},
|
||||
});
|
||||
createServiceFactory({
|
||||
service: ref,
|
||||
deps: {},
|
||||
// @ts-expect-error
|
||||
async factory(_deps, _opts: symbol) {
|
||||
return async () => 'x';
|
||||
},
|
||||
});
|
||||
createServiceFactory({
|
||||
service: ref,
|
||||
deps: {},
|
||||
// @ts-expect-error
|
||||
async factory(_deps, _opts: bigint) {
|
||||
return async () => 'x';
|
||||
},
|
||||
});
|
||||
createServiceFactory({
|
||||
service: ref,
|
||||
deps: {},
|
||||
// @ts-expect-error
|
||||
async factory(_deps, _opts: 'string') {
|
||||
return async () => 'x';
|
||||
},
|
||||
});
|
||||
createServiceFactory({
|
||||
service: ref,
|
||||
deps: {},
|
||||
// @ts-expect-error
|
||||
async factory(_deps, _opts: Array) {
|
||||
return async () => 'x';
|
||||
},
|
||||
});
|
||||
createServiceFactory({
|
||||
service: ref,
|
||||
deps: {},
|
||||
// @ts-expect-error
|
||||
async factory(_deps, _opts: Map) {
|
||||
return async () => 'x';
|
||||
},
|
||||
});
|
||||
createServiceFactory({
|
||||
service: ref,
|
||||
deps: {},
|
||||
// @ts-expect-error
|
||||
async factory(_deps, _opts: Set) {
|
||||
return async () => 'x';
|
||||
},
|
||||
});
|
||||
createServiceFactory({
|
||||
service: ref,
|
||||
deps: {},
|
||||
// @ts-expect-error
|
||||
async factory(_deps, _opts: null) {
|
||||
return async () => 'x';
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -133,7 +133,7 @@ export function createServiceFactory<
|
||||
TScope extends 'root' | 'plugin',
|
||||
TImpl extends TService,
|
||||
TDeps extends { [name in string]: ServiceRef<unknown> },
|
||||
TOpts extends { [name in string]: unknown } | undefined = undefined,
|
||||
TOpts extends object | undefined = undefined,
|
||||
>(config: {
|
||||
service: ServiceRef<TService, TScope>;
|
||||
deps: TDeps;
|
||||
|
||||
@@ -68,6 +68,38 @@ describe('createBackendPlugin', () => {
|
||||
// @ts-expect-error
|
||||
expect(plugin({})).toBeDefined();
|
||||
});
|
||||
|
||||
it('should create a BackendPlugin with options as interface', () => {
|
||||
interface TestOptions {
|
||||
a: string;
|
||||
}
|
||||
const plugin = createBackendPlugin({
|
||||
id: 'x',
|
||||
register(_reg, _options: TestOptions) {},
|
||||
});
|
||||
expect(plugin).toBeDefined();
|
||||
expect(plugin({ a: 'a' })).toBeDefined();
|
||||
expect(plugin({ a: 'a' }).id).toBe('x');
|
||||
// @ts-expect-error
|
||||
expect(plugin()).toBeDefined();
|
||||
// @ts-expect-error
|
||||
expect(plugin({ b: 'b' })).toBeDefined();
|
||||
});
|
||||
|
||||
it('should create plugins with optional options as interface', () => {
|
||||
interface TestOptions {
|
||||
a: string;
|
||||
}
|
||||
const plugin = createBackendPlugin({
|
||||
id: 'x',
|
||||
register(_reg, _options?: TestOptions) {},
|
||||
});
|
||||
expect(plugin).toBeDefined();
|
||||
expect(plugin({ a: 'a' })).toBeDefined();
|
||||
expect(plugin()).toBeDefined();
|
||||
// @ts-expect-error
|
||||
expect(plugin({ b: 'b' })).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('createBackendModule', () => {
|
||||
@@ -111,4 +143,38 @@ describe('createBackendModule', () => {
|
||||
// @ts-expect-error
|
||||
expect(mod({})).toBeDefined();
|
||||
});
|
||||
|
||||
it('should create a BackendModule as interface', () => {
|
||||
interface TestOptions {
|
||||
a: string;
|
||||
}
|
||||
const mod = createBackendModule({
|
||||
pluginId: 'x',
|
||||
moduleId: 'y',
|
||||
register(_reg, _options: TestOptions) {},
|
||||
});
|
||||
expect(mod).toBeDefined();
|
||||
expect(mod({ a: 'a' })).toBeDefined();
|
||||
expect(mod({ a: 'a' }).id).toBe('x.y');
|
||||
// @ts-expect-error
|
||||
expect(mod()).toBeDefined();
|
||||
// @ts-expect-error
|
||||
expect(mod({ b: 'b' })).toBeDefined();
|
||||
});
|
||||
|
||||
it('should create modules with optional options as interface', () => {
|
||||
interface TestOptions {
|
||||
a: string;
|
||||
}
|
||||
const mod = createBackendModule({
|
||||
pluginId: 'x',
|
||||
moduleId: 'y',
|
||||
register(_reg, _options?: TestOptions) {},
|
||||
});
|
||||
expect(mod).toBeDefined();
|
||||
expect(mod({ a: 'a' })).toBeDefined();
|
||||
expect(mod()).toBeDefined();
|
||||
// @ts-expect-error
|
||||
expect(mod({ b: 'b' })).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -44,10 +44,11 @@ export interface BackendPluginConfig<TOptions> {
|
||||
|
||||
/** @public */
|
||||
export function createBackendPlugin<
|
||||
TOptions extends { [name: string]: unknown } | undefined = undefined,
|
||||
>(
|
||||
config: BackendPluginConfig<TOptions>,
|
||||
): undefined extends TOptions
|
||||
TOptions extends object | undefined = undefined,
|
||||
>(config: {
|
||||
id: string;
|
||||
register(reg: BackendRegistrationPoints, options: TOptions): void;
|
||||
}): undefined extends TOptions
|
||||
? (options?: TOptions) => BackendFeature
|
||||
: (options: TOptions) => BackendFeature {
|
||||
return (options?: TOptions) => ({
|
||||
@@ -70,7 +71,7 @@ export interface BackendModuleConfig<TOptions> {
|
||||
|
||||
/** @public */
|
||||
export function createBackendModule<
|
||||
TOptions extends { [name: string]: unknown } | undefined = undefined,
|
||||
TOptions extends object | undefined = undefined,
|
||||
>(
|
||||
config: BackendModuleConfig<TOptions>,
|
||||
): undefined extends TOptions
|
||||
|
||||
Reference in New Issue
Block a user