frontend-plugin-api: initial optional extension output implementation

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>
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2023-09-04 17:01:50 +02:00
parent d1a1cad0e7
commit 46e36bfad0
4 changed files with 84 additions and 9 deletions
@@ -49,4 +49,42 @@ describe('createExtension', () => {
});
expect(extension.id).toBe('test');
});
it('should create an extension with a some optional output', () => {
const extension = createExtension({
id: 'test',
at: 'root',
output: {
foo: stringData,
bar: stringData.optional(),
},
factory({ bind }) {
bind({
foo: 'bar',
});
bind({
foo: 'bar',
bar: 'baz',
});
bind({
// @ts-expect-error
foo: 3,
});
bind({
foo: 'bar',
// @ts-expect-error
bar: 3,
});
// @ts-expect-error
bind({ bar: 'bar' });
// @ts-expect-error
bind({});
// @ts-expect-error
bind();
// @ts-expect-error
bind('bar');
},
});
expect(extension.id).toBe('test');
});
});
@@ -18,10 +18,26 @@ 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 ExtensionDataBind<TOutput extends AnyExtensionDataMap> = (outputs: {
[K in keyof TOutput]: TOutput[K]['T'];
}) => void;
export type ExtensionDataBind<TOutput extends AnyExtensionDataMap> = (
outputs: {
[K in OnlyRequiredKeys<TOutput>]: TOutput[K]['T'];
} & {
[K in OnlyOptionalKeys<TOutput>]?: TOutput[K]['T'];
},
) => void;
/** @public */
export type ExtensionDataValue<TData extends AnyExtensionDataMap> = {
@@ -15,14 +15,35 @@
*/
/** @public */
export type ExtensionDataRef<T> = {
export type ExtensionDataRef<
TData,
TConfig extends { optional: boolean } = { optional: false },
> = {
id: string;
T: T;
T: TData;
config: TConfig;
$$type: 'extension-data';
};
/** @public */
// TODO: change to options object with ID.
export function createExtensionDataRef<T>(id: string): ExtensionDataRef<T> {
return { id, $$type: 'extension-data' } as ExtensionDataRef<T>;
export interface ConfigurableExtensionDataRef<
TData,
TConfig extends { optional: boolean },
> extends ExtensionDataRef<TData, TConfig> {
optional(): ConfigurableExtensionDataRef<TData, TData & { optional: true }>;
}
/** @public */
// TODO: change to options object with ID.
export function createExtensionDataRef<TData>(
id: string,
): ConfigurableExtensionDataRef<TData, { optional: false }> {
return {
id,
$$type: 'extension-data',
config: { optional: false },
optional() {
return { ...this, config: { ...this.config, optional: true } };
},
} as ConfigurableExtensionDataRef<TData, { optional: false }>;
}
@@ -20,7 +20,7 @@ import { ExtensionDataRef } from './createExtensionDataRef';
import { BackstagePlugin } from './createPlugin';
/** @public */
export type AnyExtensionDataMap = Record<string, ExtensionDataRef<any>>;
export type AnyExtensionDataMap = Record<string, ExtensionDataRef<any, any>>;
/** @public */
export interface Extension<TConfig> {