frontend-plugin-api: simply output mapping types + add inputs mapped types

Co-authored-by: Fredrik Adelöw <freben@gmail.com>
Co-authored-by: Johan Haals <johan.haals@gmail.com>
Co-authored-by: Philipp Hugenroth <philipph@spotify.com>
Co-authored-by: Camila Belo <camilaibs@gmail.com>
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2023-09-05 12:17:55 +02:00
parent f2bacd19db
commit 44dcc3b591
4 changed files with 104 additions and 32 deletions
@@ -19,6 +19,8 @@ import { createExtensionDataRef } from './createExtensionDataRef';
const stringData = createExtensionDataRef<string>('string');
function unused(..._any: any[]) {}
describe('createExtension', () => {
it('should create an extension with a simple output', () => {
const extension = createExtension({
@@ -87,4 +89,68 @@ describe('createExtension', () => {
});
expect(extension.id).toBe('test');
});
it('should create an extension with input', () => {
const extension = createExtension({
id: 'test',
at: 'root',
inputs: {
mixed: {
extensionData: {
required: stringData,
optional: stringData.optional(),
},
},
onlyRequired: {
extensionData: {
required: stringData,
},
},
onlyOptional: {
extensionData: {
optional: stringData.optional(),
},
},
},
output: {
foo: stringData,
},
factory({ bind, inputs }) {
const a1: string = inputs.mixed?.[0].required;
// @ts-expect-error
const a2: number = inputs.mixed?.[0].required;
// @ts-expect-error
const a3: any = inputs.mixed?.[0].nonExistent;
unused(a1, a2, a3);
const b1: string | undefined = inputs.mixed?.[0].optional;
// @ts-expect-error
const b2: string = inputs.mixed?.[0].optional;
// @ts-expect-error
const b3: number = inputs.mixed?.[0].optional;
// @ts-expect-error
const b4: number | undefined = inputs.mixed?.[0].optional;
unused(b1, b2, b3, b4);
const c1: string = inputs.onlyRequired?.[0].required;
// @ts-expect-error
const c2: number = inputs.onlyRequired?.[0].required;
unused(c1, c2);
const d1: string | undefined = inputs.onlyOptional?.[0].optional;
// @ts-expect-error
const d2: string = inputs.onlyOptional?.[0].optional;
// @ts-expect-error
const d3: number = inputs.onlyOptional?.[0].optional;
// @ts-expect-error
const d4: number | undefined = inputs.onlyOptional?.[0].optional;
unused(d1, d2, d3, d4);
bind({
foo: 'bar',
});
},
});
expect(extension.id).toBe('test');
});
});
@@ -18,32 +18,44 @@ import { PortableSchema } from '../schema';
import { BackstagePlugin } from './createPlugin';
import { AnyExtensionDataMap, Extension } from './types';
type OnlyRequiredKeys<TOutput extends AnyExtensionDataMap> = {
[K in keyof TOutput]: TOutput[K]['config']['optional'] extends true
? never
: K;
}[keyof TOutput];
type OnlyOptionalKeys<TOutput extends AnyExtensionDataMap> = {
[K in keyof TOutput]: TOutput[K]['config']['optional'] extends false
? never
: K;
}[keyof TOutput];
/** @public */
export type ExtensionDataInputValues<
TInputs extends { [name in string]: { extensionData: AnyExtensionDataMap } },
> = {
[InputName in keyof TInputs]: Array<
{
[DataName in keyof TInputs[InputName]['extensionData'] as TInputs[InputName]['extensionData'][DataName]['config'] extends {
optional: true;
}
? DataName
: never]?: TInputs[InputName]['extensionData'][DataName]['T'];
} & {
[DataName in keyof TInputs[InputName]['extensionData'] as TInputs[InputName]['extensionData'][DataName]['config'] extends {
optional: false;
}
? DataName
: never]: TInputs[InputName]['extensionData'][DataName]['T'];
}
>;
};
/** @public */
export type ExtensionDataBind<TOutput extends AnyExtensionDataMap> = (
outputs: {
[K in OnlyRequiredKeys<TOutput>]: TOutput[K]['T'];
export type ExtensionDataBind<TMap extends AnyExtensionDataMap> = (
values: {
[DataName in keyof TMap as TMap[DataName]['config'] extends {
optional: false;
}
? DataName
: never]: TMap[DataName]['T'];
} & {
[K in OnlyOptionalKeys<TOutput>]?: TOutput[K]['T'];
[DataName in keyof TMap as TMap[DataName]['config'] extends {
optional: true;
}
? DataName
: never]?: TMap[DataName]['T'];
},
) => void;
/** @public */
export type ExtensionDataValue<TData extends AnyExtensionDataMap> = {
[K in keyof TData]: TData[K]['T'];
};
/** @public */
export interface CreateExtensionOptions<
TOutput extends AnyExtensionDataMap,
@@ -60,11 +72,7 @@ export interface CreateExtensionOptions<
source?: BackstagePlugin;
bind: ExtensionDataBind<TOutput>;
config: TConfig;
inputs: {
[pointName in keyof TInputs]: ExtensionDataValue<
TInputs[pointName]['extensionData']
>[];
};
inputs: ExtensionDataInputValues<TInputs>;
}): void;
}
@@ -86,11 +94,7 @@ export function createExtension<
return options.factory({
bind,
config,
inputs: inputs as {
[pointName in keyof TInputs]: ExtensionDataValue<
TInputs[pointName]['extensionData']
>[];
},
inputs: inputs as ExtensionDataInputValues<TInputs>,
});
},
};
@@ -19,7 +19,7 @@ export {
createExtension,
type CreateExtensionOptions,
type ExtensionDataBind,
type ExtensionDataValue,
type ExtensionDataInputValues,
} from './createExtension';
export {
createExtensionDataRef,
@@ -20,7 +20,9 @@ import { ExtensionDataRef } from './createExtensionDataRef';
import { BackstagePlugin } from './createPlugin';
/** @public */
export type AnyExtensionDataMap = Record<string, ExtensionDataRef<any, any>>;
export type AnyExtensionDataMap = {
[name in string]: ExtensionDataRef<any, any>;
};
/** @public */
export interface Extension<TConfig> {