diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.test.ts b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts index 104f03c84d..a992642d61 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.test.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts @@ -19,6 +19,8 @@ import { createExtensionDataRef } from './createExtensionDataRef'; const stringData = createExtensionDataRef('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'); + }); }); diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.ts b/packages/frontend-plugin-api/src/wiring/createExtension.ts index c545fe7fea..575271cb24 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.ts @@ -18,32 +18,44 @@ import { PortableSchema } from '../schema'; import { BackstagePlugin } from './createPlugin'; import { AnyExtensionDataMap, Extension } from './types'; -type OnlyRequiredKeys = { - [K in keyof TOutput]: TOutput[K]['config']['optional'] extends true - ? never - : K; -}[keyof TOutput]; - -type OnlyOptionalKeys = { - [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 = ( - outputs: { - [K in OnlyRequiredKeys]: TOutput[K]['T']; +export type ExtensionDataBind = ( + values: { + [DataName in keyof TMap as TMap[DataName]['config'] extends { + optional: false; + } + ? DataName + : never]: TMap[DataName]['T']; } & { - [K in OnlyOptionalKeys]?: TOutput[K]['T']; + [DataName in keyof TMap as TMap[DataName]['config'] extends { + optional: true; + } + ? DataName + : never]?: TMap[DataName]['T']; }, ) => void; -/** @public */ -export type ExtensionDataValue = { - [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; config: TConfig; - inputs: { - [pointName in keyof TInputs]: ExtensionDataValue< - TInputs[pointName]['extensionData'] - >[]; - }; + inputs: ExtensionDataInputValues; }): 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, }); }, }; diff --git a/packages/frontend-plugin-api/src/wiring/index.ts b/packages/frontend-plugin-api/src/wiring/index.ts index 1a243eb2e3..4830d7dd88 100644 --- a/packages/frontend-plugin-api/src/wiring/index.ts +++ b/packages/frontend-plugin-api/src/wiring/index.ts @@ -19,7 +19,7 @@ export { createExtension, type CreateExtensionOptions, type ExtensionDataBind, - type ExtensionDataValue, + type ExtensionDataInputValues, } from './createExtension'; export { createExtensionDataRef, diff --git a/packages/frontend-plugin-api/src/wiring/types.ts b/packages/frontend-plugin-api/src/wiring/types.ts index f3f5008f27..dbe4633ff5 100644 --- a/packages/frontend-plugin-api/src/wiring/types.ts +++ b/packages/frontend-plugin-api/src/wiring/types.ts @@ -20,7 +20,9 @@ import { ExtensionDataRef } from './createExtensionDataRef'; import { BackstagePlugin } from './createPlugin'; /** @public */ -export type AnyExtensionDataMap = Record>; +export type AnyExtensionDataMap = { + [name in string]: ExtensionDataRef; +}; /** @public */ export interface Extension {