frontend-plugin-api: update createExtension to use union-based verification of factory outputs

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2024-07-26 16:49:54 +02:00
parent 0d9c38f1b8
commit f4958c9a72
2 changed files with 86 additions and 75 deletions
@@ -357,7 +357,68 @@ describe('createExtension', () => {
}).toThrow("Missing required value at 'foo'");
});
it('should new form of inputs and outputs', () => {
it('should support new form of outputs', () => {
// @ts-expect-error
createExtension({
namespace: 'test',
attachTo: { id: 'root', input: 'default' },
output: [stringDataRef, numberDataRef],
factory() {
return []; // Missing all outputs
},
});
// @ts-expect-error
createExtension({
namespace: 'test',
attachTo: { id: 'root', input: 'default' },
output: [stringDataRef, numberDataRef],
factory() {
return [stringDataRef('hello')]; // Missing number output
},
});
// Duplicate output, we won't attempt to handle this a compile time and instead error out at runtime
createExtension({
namespace: 'test',
attachTo: { id: 'root', input: 'default' },
output: [stringDataRef],
factory() {
return [stringDataRef('hello'), stringDataRef('hello')];
},
});
createExtension({
namespace: 'test',
attachTo: { id: 'root', input: 'default' },
output: [stringDataRef, numberDataRef],
factory() {
return [stringDataRef('hello'), numberDataRef(4)];
},
});
createExtension({
namespace: 'test',
attachTo: { id: 'root', input: 'default' },
output: [stringDataRef, numberDataRef.optional()],
factory() {
return [stringDataRef('hello'), numberDataRef(4)];
},
});
createExtension({
namespace: 'test',
attachTo: { id: 'root', input: 'default' },
output: [stringDataRef, numberDataRef.optional()],
factory() {
return [stringDataRef('hello')]; // Missing number output, but it's optional so that's allowed
},
});
expect(true).toBe(true);
});
it('should support new form of inputs', () => {
createExtension({
namespace: 'test',
attachTo: { id: 'root', input: 'default' },
@@ -389,55 +450,6 @@ describe('createExtension', () => {
},
});
// Double output
createExtension({
namespace: 'test',
attachTo: { id: 'root', input: 'default' },
output: [stringDataRef],
// @ts-expect-error
factory() {
return [stringDataRef('hello'), stringDataRef('hello')];
},
});
// Missing output
createExtension({
namespace: 'test',
attachTo: { id: 'root', input: 'default' },
output: [stringDataRef, numberDataRef],
// @ts-expect-error
factory() {
return [stringDataRef('hello')];
},
});
createExtension({
namespace: 'test',
attachTo: { id: 'root', input: 'default' },
output: [stringDataRef, numberDataRef],
factory() {
return [stringDataRef('hello'), numberDataRef(4)];
},
});
createExtension({
namespace: 'test',
attachTo: { id: 'root', input: 'default' },
output: [stringDataRef, numberDataRef.optional()],
factory() {
return [stringDataRef('hello'), numberDataRef(4)];
},
});
createExtension({
namespace: 'test',
attachTo: { id: 'root', input: 'default' },
output: [stringDataRef, numberDataRef.optional()],
factory() {
return [stringDataRef('hello')];
},
});
expect(true).toBe(true);
});
});
@@ -143,27 +143,6 @@ export interface LegacyCreateExtensionOptions<
}): Expand<ExtensionDataValues<TOutput>>;
}
// TODO(Rugvip): This creates a permutation of all possible output tuples,
// taking the optionality into account. It's not optimal, since it might hurt
// performance for large outputs, doesn't provide a very clear error message,
// and doesn't allow us to refactor to allow for a generator.
/** @public */
type ExtensionFactoryOutput<
URef extends AnyExtensionDataRef,
TPickRef extends AnyExtensionDataRef = URef,
> = [URef] extends [never]
? []
: TPickRef extends ExtensionDataRef<infer IData, infer IId, infer IConfig>
?
| (IConfig['optional'] extends true
? ExtensionFactoryOutput<Exclude<URef, TPickRef>>
: never)
| [
ExtensionDataValue<IData, IId>,
...ExtensionFactoryOutput<Exclude<URef, TPickRef>>,
]
: never;
/** @public */
export type CreateExtensionOptions<
UOutput extends AnyExtensionDataRef,
@@ -171,6 +150,7 @@ export type CreateExtensionOptions<
TConfig,
TConfigInput,
TConfigSchema extends { [key: string]: (zImpl: typeof z) => z.ZodType },
UFactoryOutput extends ExtensionDataValue<any, any>,
> = {
kind?: string;
namespace?: string;
@@ -195,8 +175,23 @@ export type CreateExtensionOptions<
>;
});
inputs: Expand<ResolvedExtensionInputs<TInputs>>;
}): ExtensionFactoryOutput<UOutput>;
};
}): Iterable<UFactoryOutput>;
} & ((
UOutput extends any
? UOutput['config']['optional'] extends true
? never
: UOutput['id']
: never
) extends infer IRequiredOutputIds
? [IRequiredOutputIds] extends [UFactoryOutput['id']]
? {}
: {
'Error: The extension factory is missing the following outputs': Exclude<
IRequiredOutputIds,
UFactoryOutput['id']
>;
}
: never);
/** @public */
export interface ExtensionDefinition<TConfig, TConfigInput = TConfig> {
@@ -256,13 +251,15 @@ export function createExtension<
TConfig,
TConfigInput,
TConfigSchema extends { [key: string]: (zImpl: typeof z) => z.ZodType },
UFactoryOutput extends ExtensionDataValue<any, any>,
>(
options: CreateExtensionOptions<
UOutput,
TInputs,
TConfig,
TConfigInput,
TConfigSchema
TConfigSchema,
UFactoryOutput
>,
): ExtensionDefinition<
TConfig &
@@ -320,6 +317,7 @@ export function createExtension<
TConfig,
TConfigInput,
TConfigSchema extends { [key: string]: (zImpl: typeof z) => z.ZodType },
UFactoryOutput extends ExtensionDataValue<any, any>,
>(
options:
| CreateExtensionOptions<
@@ -327,7 +325,8 @@ export function createExtension<
TInputs,
TConfig,
TConfigInput,
TConfigSchema
TConfigSchema,
UFactoryOutput
>
| LegacyCreateExtensionOptions<
AnyExtensionDataMap,