chore: more work towards supporting extensions
Co-authored-by: Patrik Oldsberg <poldsberg@gmail.com> Co-authored-by: Fredrik Adelöw <freben@users.noreply.github.com> Co-authored-by: Camila Belo <camilaibs@users.noreply.github.com> Signed-off-by: blam <ben@blam.sh>
This commit is contained in:
@@ -48,6 +48,7 @@ describe('createApiExtension', () => {
|
||||
},
|
||||
factory: expect.any(Function),
|
||||
toString: expect.any(Function),
|
||||
override: expect.any(Function),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -85,6 +86,7 @@ describe('createApiExtension', () => {
|
||||
},
|
||||
factory: expect.any(Function),
|
||||
toString: expect.any(Function),
|
||||
override: expect.any(Function),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -41,6 +41,7 @@ describe('createAppRootElementExtension', () => {
|
||||
},
|
||||
factory: expect.any(Function),
|
||||
toString: expect.any(Function),
|
||||
override: expect.any(Function),
|
||||
});
|
||||
|
||||
createExtensionTester(extension).render();
|
||||
@@ -88,6 +89,7 @@ describe('createAppRootElementExtension', () => {
|
||||
},
|
||||
factory: expect.any(Function),
|
||||
toString: expect.any(Function),
|
||||
override: expect.any(Function),
|
||||
});
|
||||
|
||||
createExtensionTester(extension, { config: { name: 'Robin' } })
|
||||
|
||||
@@ -42,6 +42,7 @@ describe('createAppRootWrapperExtension', () => {
|
||||
},
|
||||
factory: expect.any(Function),
|
||||
toString: expect.any(Function),
|
||||
override: expect.any(Function),
|
||||
});
|
||||
|
||||
createExtensionTester(
|
||||
@@ -94,6 +95,7 @@ describe('createAppRootWrapperExtension', () => {
|
||||
component: expect.anything(),
|
||||
},
|
||||
factory: expect.any(Function),
|
||||
override: expect.any(Function),
|
||||
toString: expect.any(Function),
|
||||
});
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ describe('createNavLogoExtension', () => {
|
||||
logos: expect.anything(),
|
||||
},
|
||||
factory: expect.any(Function),
|
||||
override: expect.any(Function),
|
||||
toString: expect.any(Function),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -56,6 +56,7 @@ describe('createPageExtension', () => {
|
||||
},
|
||||
factory: expect.any(Function),
|
||||
toString: expect.any(Function),
|
||||
override: expect.any(Function),
|
||||
});
|
||||
|
||||
expect(
|
||||
@@ -78,6 +79,7 @@ describe('createPageExtension', () => {
|
||||
kind: 'page',
|
||||
attachTo: { id: 'other', input: 'place' },
|
||||
configSchema: expect.anything(),
|
||||
override: expect.any(Function),
|
||||
disabled: true,
|
||||
inputs: {
|
||||
first: createExtensionInput({
|
||||
@@ -115,6 +117,7 @@ describe('createPageExtension', () => {
|
||||
},
|
||||
factory: expect.any(Function),
|
||||
toString: expect.any(Function),
|
||||
override: expect.any(Function),
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ describe('createRouterExtension', () => {
|
||||
component: expect.anything(),
|
||||
},
|
||||
factory: expect.any(Function),
|
||||
override: expect.any(Function),
|
||||
toString: expect.any(Function),
|
||||
});
|
||||
|
||||
@@ -119,6 +120,7 @@ describe('createRouterExtension', () => {
|
||||
component: expect.anything(),
|
||||
},
|
||||
factory: expect.any(Function),
|
||||
override: expect.any(Function),
|
||||
toString: expect.any(Function),
|
||||
});
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ describe('createTranslationExtension', () => {
|
||||
namespace: 'test',
|
||||
attachTo: { id: 'app', input: 'translations' },
|
||||
disabled: false,
|
||||
override: expect.any(Function),
|
||||
inputs: {},
|
||||
output: {
|
||||
resource: createTranslationExtension.translationDataRef,
|
||||
@@ -84,6 +85,7 @@ describe('createTranslationExtension', () => {
|
||||
namespace: 'test',
|
||||
attachTo: { id: 'app', input: 'translations' },
|
||||
disabled: false,
|
||||
override: expect.any(Function),
|
||||
inputs: {},
|
||||
output: {
|
||||
resource: createTranslationExtension.translationDataRef,
|
||||
@@ -120,6 +122,7 @@ describe('createTranslationExtension', () => {
|
||||
version: 'v1',
|
||||
kind: 'translation',
|
||||
namespace: 'test',
|
||||
override: expect.any(Function),
|
||||
name: 'sv',
|
||||
attachTo: { id: 'app', input: 'translations' },
|
||||
disabled: false,
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
|
||||
import { createExtensionTester } from '@backstage/frontend-test-utils';
|
||||
import { coreExtensionData } from './coreExtensionData';
|
||||
import { createExtension } from './createExtension';
|
||||
import { createExtensionDataRef } from './createExtensionDataRef';
|
||||
import { createExtensionInput } from './createExtensionInput';
|
||||
@@ -306,9 +305,7 @@ describe('createExtension', () => {
|
||||
baz: z => z.string().optional(),
|
||||
},
|
||||
},
|
||||
output: {
|
||||
foo: stringDataRef,
|
||||
},
|
||||
output: [stringDataRef],
|
||||
factory({ config }) {
|
||||
const a1: string = config.foo;
|
||||
const a2: string = config.bar;
|
||||
@@ -322,12 +319,10 @@ describe('createExtension', () => {
|
||||
const c3: number = config.baz;
|
||||
unused(a1, a2, a3, c1, c2, c3);
|
||||
|
||||
return {
|
||||
foo: 'bar',
|
||||
};
|
||||
return [stringDataRef('bar')];
|
||||
},
|
||||
});
|
||||
expect(extension).toMatchObject({ version: 'v1', namespace: 'test' });
|
||||
expect(extension).toMatchObject({ version: 'v2', namespace: 'test' });
|
||||
expect(String(extension)).toBe(
|
||||
'ExtensionDefinition{namespace=test,attachTo=root@default}',
|
||||
);
|
||||
@@ -614,18 +609,7 @@ describe('createExtension', () => {
|
||||
},
|
||||
});
|
||||
|
||||
const override = testExtension.override({
|
||||
output: [numberDataRef],
|
||||
factory(_, { inputs }) {
|
||||
return [
|
||||
numberDataRef(inputs.test.get(stringDataRef).length),
|
||||
stringDataRef('default'),
|
||||
];
|
||||
},
|
||||
});
|
||||
|
||||
// @ts-expect-error - should fail as the string output from previous is not provided
|
||||
const override2 = testExtension.override({
|
||||
const override1 = testExtension.override({
|
||||
output: [numberDataRef],
|
||||
factory(_, { inputs }) {
|
||||
return [numberDataRef(inputs.test.get(stringDataRef).length)];
|
||||
@@ -633,14 +617,14 @@ describe('createExtension', () => {
|
||||
});
|
||||
|
||||
// @ts-expect-error - this should fail because string output should be merged?
|
||||
const override3 = testExtension.override({
|
||||
const override2 = testExtension.override({
|
||||
output: [numberDataRef],
|
||||
factory(_, { inputs }) {
|
||||
return [stringDataRef(inputs.test.get(stringDataRef))];
|
||||
},
|
||||
});
|
||||
|
||||
unused(override, override2, override3);
|
||||
unused(override1, override2);
|
||||
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
|
||||
@@ -14,10 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { over } from 'lodash';
|
||||
import { AppNode } from '../apis';
|
||||
import { PortableSchema, createSchemaFromZod } from '../schema';
|
||||
import { Expand } from '../types';
|
||||
import { createDataContainer } from './createExtensionBlueprint';
|
||||
import {
|
||||
AnyExtensionDataRef,
|
||||
ExtensionDataRef,
|
||||
@@ -128,7 +128,6 @@ export interface LegacyCreateExtensionOptions<
|
||||
TInputs extends AnyExtensionInputMap,
|
||||
TConfig,
|
||||
TConfigInput,
|
||||
TConfigSchema extends { [key: string]: (zImpl: typeof z) => z.ZodType },
|
||||
> {
|
||||
kind?: string;
|
||||
namespace?: string;
|
||||
@@ -137,21 +136,10 @@ export interface LegacyCreateExtensionOptions<
|
||||
disabled?: boolean;
|
||||
inputs?: TInputs;
|
||||
output: TOutput;
|
||||
/** @deprecated - use `config.schema` instead */
|
||||
configSchema?: PortableSchema<TConfig, TConfigInput>;
|
||||
config?: {
|
||||
schema: TConfigSchema;
|
||||
};
|
||||
factory(context: {
|
||||
node: AppNode;
|
||||
config: TConfig &
|
||||
(string extends keyof TConfigSchema
|
||||
? {}
|
||||
: {
|
||||
[key in keyof TConfigSchema]: z.infer<
|
||||
ReturnType<TConfigSchema[key]>
|
||||
>;
|
||||
});
|
||||
config: TConfig;
|
||||
inputs: Expand<ResolvedExtensionInputs<TInputs>>;
|
||||
}): Expand<ExtensionDataValues<TOutput>>;
|
||||
}
|
||||
@@ -193,8 +181,6 @@ export type CreateExtensionOptions<
|
||||
{ optional: boolean; singleton: boolean }
|
||||
>;
|
||||
},
|
||||
TConfig,
|
||||
TConfigInput,
|
||||
TConfigSchema extends { [key: string]: (zImpl: typeof z) => z.ZodType },
|
||||
UFactoryOutput extends ExtensionDataValue<any, any>,
|
||||
> = {
|
||||
@@ -205,27 +191,30 @@ export type CreateExtensionOptions<
|
||||
disabled?: boolean;
|
||||
inputs?: TInputs;
|
||||
output: Array<UOutput>;
|
||||
/** @deprecated - use `config.schema` instead */
|
||||
configSchema?: PortableSchema<TConfig, TConfigInput>;
|
||||
config?: {
|
||||
schema: TConfigSchema;
|
||||
};
|
||||
factory(context: {
|
||||
node: AppNode;
|
||||
config: TConfig &
|
||||
(string extends keyof TConfigSchema
|
||||
? {}
|
||||
: {
|
||||
[key in keyof TConfigSchema]: z.infer<
|
||||
ReturnType<TConfigSchema[key]>
|
||||
>;
|
||||
});
|
||||
config: {
|
||||
[key in keyof TConfigSchema]: z.infer<ReturnType<TConfigSchema[key]>>;
|
||||
};
|
||||
inputs: Expand<ResolvedExtensionInputs<TInputs>>;
|
||||
}): Iterable<UFactoryOutput>;
|
||||
} & VerifyExtensionFactoryOutput<UOutput, UFactoryOutput>;
|
||||
|
||||
/** @public */
|
||||
export interface ExtensionDefinition<TConfig, TConfigInput = TConfig> {
|
||||
export interface ExtensionDefinition<
|
||||
TConfig,
|
||||
TConfigInput = TConfig,
|
||||
UOutput extends AnyExtensionDataRef = AnyExtensionDataRef,
|
||||
TInputs extends {
|
||||
[inputName in string]: ExtensionInput<
|
||||
AnyExtensionDataRef,
|
||||
{ optional: boolean; singleton: boolean }
|
||||
>;
|
||||
} = {},
|
||||
> {
|
||||
$$type: '@backstage/ExtensionDefinition';
|
||||
readonly kind?: string;
|
||||
readonly namespace?: string;
|
||||
@@ -233,102 +222,71 @@ export interface ExtensionDefinition<TConfig, TConfigInput = TConfig> {
|
||||
readonly attachTo: { id: string; input: string };
|
||||
readonly disabled: boolean;
|
||||
readonly configSchema?: PortableSchema<TConfig, TConfigInput>;
|
||||
}
|
||||
|
||||
export type OverrideExtensionOptions<
|
||||
UOutput extends AnyExtensionDataRef,
|
||||
TInputs extends {
|
||||
[inputName in string]: ExtensionInput<
|
||||
AnyExtensionDataRef,
|
||||
{ optional: boolean; singleton: boolean }
|
||||
>;
|
||||
},
|
||||
TConfig,
|
||||
TConfigInput,
|
||||
TConfigSchema extends { [key: string]: (zImpl: typeof z) => z.ZodType },
|
||||
TConfigSchemaOverrides extends {
|
||||
[key: string]: (zImpl: typeof z) => z.ZodType;
|
||||
},
|
||||
UOutputOverrides extends AnyExtensionDataRef,
|
||||
UFactoryOverrideOutput extends ExtensionDataValue<any, any>,
|
||||
> = {
|
||||
kind?: string;
|
||||
namespace?: string;
|
||||
name?: string;
|
||||
attachTo?: { id: string; input: string };
|
||||
disabled?: boolean;
|
||||
inputs?: TInputs;
|
||||
output?: Array<UOutputOverrides>;
|
||||
/** @deprecated - use `config.schema` instead */
|
||||
configSchema?: PortableSchema<TConfig, TConfigInput>;
|
||||
config?: {
|
||||
schema: TConfigSchemaOverrides & {
|
||||
[KName in keyof TConfig]?: `Error: Config key '${KName &
|
||||
string}' is already defined in parent schema`;
|
||||
};
|
||||
};
|
||||
factory?(
|
||||
originalFactory: (context?: {
|
||||
config?: {
|
||||
[key in keyof TConfigSchema]: z.infer<ReturnType<TConfigSchema[key]>>;
|
||||
};
|
||||
inputs?: Expand<ResolvedExtensionInputs<TInputs>>;
|
||||
}) => ExtensionDataContainer<UOutput>,
|
||||
context: {
|
||||
node: AppNode;
|
||||
config: TConfig &
|
||||
(string extends keyof TConfigSchema
|
||||
? {}
|
||||
: {
|
||||
[key in keyof TConfigSchemaOverrides]: z.infer<
|
||||
ReturnType<TConfigSchemaOverrides[key]>
|
||||
>;
|
||||
} & {
|
||||
[key in keyof TConfigSchema]: z.infer<
|
||||
ReturnType<TConfigSchema[key]>
|
||||
>;
|
||||
});
|
||||
inputs: Expand<ResolvedExtensionInputs<TInputs>>;
|
||||
},
|
||||
): Iterable<UFactoryOverrideOutput>;
|
||||
// todo(blam): need to verify that the outputs are merged and verified properly.
|
||||
} & VerifyExtensionFactoryOutput<
|
||||
UOutput & UOutputOverrides,
|
||||
UFactoryOverrideOutput
|
||||
>;
|
||||
|
||||
/** @public */
|
||||
export type OverridableExtension<
|
||||
UOutput extends AnyExtensionDataRef,
|
||||
TInputs extends {
|
||||
[inputName in string]: ExtensionInput<
|
||||
AnyExtensionDataRef,
|
||||
{ optional: boolean; singleton: boolean }
|
||||
>;
|
||||
},
|
||||
TConfig,
|
||||
TConfigInput,
|
||||
TConfigSchema extends { [key: string]: (zImpl: typeof z) => z.ZodType },
|
||||
> = {
|
||||
override<
|
||||
TConfigSchemaOverrides extends {
|
||||
TExtensionConfigSchema extends {
|
||||
[key in string]: (zImpl: typeof z) => z.ZodType;
|
||||
},
|
||||
UOutputOverrides extends AnyExtensionDataRef,
|
||||
UFactoryOverrideOutput extends ExtensionDataValue<any, any>,
|
||||
UFactoryOutput extends ExtensionDataValue<any, any>,
|
||||
UNewOutput extends AnyExtensionDataRef,
|
||||
TExtraInputs extends {
|
||||
[inputName in string]: ExtensionInput<
|
||||
AnyExtensionDataRef,
|
||||
{ optional: boolean; singleton: boolean }
|
||||
>;
|
||||
},
|
||||
>(
|
||||
options: OverrideExtensionOptions<
|
||||
UOutput,
|
||||
TInputs,
|
||||
TConfig,
|
||||
TConfigInput,
|
||||
TConfigSchema,
|
||||
TConfigSchemaOverrides,
|
||||
UOutputOverrides,
|
||||
UFactoryOverrideOutput
|
||||
args: {
|
||||
attachTo?: { id: string; input: string };
|
||||
disabled?: boolean;
|
||||
inputs?: TExtraInputs & {
|
||||
[KName in keyof TInputs]?: `Error: Input '${KName &
|
||||
string}' is already defined in parent definition`;
|
||||
};
|
||||
output?: Array<UNewOutput>;
|
||||
config?: {
|
||||
schema: TExtensionConfigSchema & {
|
||||
[KName in keyof TConfig]?: `Error: Config key '${KName &
|
||||
string}' is already defined in parent schema`;
|
||||
};
|
||||
};
|
||||
factory(
|
||||
originalFactory: (context?: {
|
||||
config?: TConfig;
|
||||
inputs?: Expand<ResolvedExtensionInputs<TInputs>>;
|
||||
}) => ExtensionDataContainer<UOutput>,
|
||||
context: {
|
||||
node: AppNode;
|
||||
config: TConfig & {
|
||||
[key in keyof TExtensionConfigSchema]: z.infer<
|
||||
ReturnType<TExtensionConfigSchema[key]>
|
||||
>;
|
||||
};
|
||||
inputs: Expand<ResolvedExtensionInputs<TInputs & TExtraInputs>>;
|
||||
},
|
||||
): Iterable<UFactoryOutput>;
|
||||
} & VerifyExtensionFactoryOutput<
|
||||
AnyExtensionDataRef extends UNewOutput ? UOutput : UNewOutput,
|
||||
UFactoryOutput
|
||||
>,
|
||||
): ExtensionDefinition<TConfig, TConfigInput>;
|
||||
};
|
||||
): ExtensionDefinition<
|
||||
{
|
||||
[key in keyof TExtensionConfigSchema]: z.infer<
|
||||
ReturnType<TExtensionConfigSchema[key]>
|
||||
>;
|
||||
} & TConfig,
|
||||
z.input<
|
||||
z.ZodObject<{
|
||||
[key in keyof TExtensionConfigSchema]: ReturnType<
|
||||
TExtensionConfigSchema[key]
|
||||
>;
|
||||
}>
|
||||
> &
|
||||
TConfigInput,
|
||||
AnyExtensionDataRef extends UNewOutput ? UOutput : UNewOutput,
|
||||
TInputs & TExtraInputs
|
||||
>;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export type InternalExtensionDefinition<TConfig, TConfigInput> =
|
||||
@@ -397,36 +355,27 @@ export function createExtension<
|
||||
{ optional: boolean; singleton: boolean }
|
||||
>;
|
||||
},
|
||||
TConfig,
|
||||
TConfigInput,
|
||||
TConfigSchema extends { [key: string]: (zImpl: typeof z) => z.ZodType },
|
||||
UFactoryOutput extends ExtensionDataValue<any, any>,
|
||||
>(
|
||||
options: CreateExtensionOptions<
|
||||
UOutput,
|
||||
TInputs,
|
||||
TConfig,
|
||||
TConfigInput,
|
||||
TConfigSchema,
|
||||
UFactoryOutput
|
||||
>,
|
||||
): ExtensionDefinition<
|
||||
TConfig &
|
||||
(string extends keyof TConfigSchema
|
||||
? {}
|
||||
: {
|
||||
[key in keyof TConfigSchema]: z.infer<ReturnType<TConfigSchema[key]>>;
|
||||
}),
|
||||
TConfigInput &
|
||||
(string extends keyof TConfigSchema
|
||||
? {}
|
||||
: z.input<
|
||||
z.ZodObject<{
|
||||
[key in keyof TConfigSchema]: ReturnType<TConfigSchema[key]>;
|
||||
}>
|
||||
>)
|
||||
> &
|
||||
OverridableExtension<UOutput, TInputs, TConfig, TConfigInput, TConfigSchema>;
|
||||
{
|
||||
[key in keyof TConfigSchema]: z.infer<ReturnType<TConfigSchema[key]>>;
|
||||
},
|
||||
z.input<
|
||||
z.ZodObject<{
|
||||
[key in keyof TConfigSchema]: ReturnType<TConfigSchema[key]>;
|
||||
}>
|
||||
>,
|
||||
UOutput,
|
||||
TInputs
|
||||
>;
|
||||
/**
|
||||
* @public
|
||||
* @deprecated - use the array format of `output` instead, see TODO-doc-link
|
||||
@@ -436,31 +385,14 @@ export function createExtension<
|
||||
TInputs extends AnyExtensionInputMap,
|
||||
TConfig,
|
||||
TConfigInput,
|
||||
TConfigSchema extends { [key: string]: (zImpl: typeof z) => z.ZodType },
|
||||
>(
|
||||
options: LegacyCreateExtensionOptions<
|
||||
TOutput,
|
||||
TInputs,
|
||||
TConfig,
|
||||
TConfigInput,
|
||||
TConfigSchema
|
||||
TConfigInput
|
||||
>,
|
||||
): ExtensionDefinition<
|
||||
TConfig &
|
||||
(string extends keyof TConfigSchema
|
||||
? {}
|
||||
: {
|
||||
[key in keyof TConfigSchema]: z.infer<ReturnType<TConfigSchema[key]>>;
|
||||
}),
|
||||
TConfigInput &
|
||||
(string extends keyof TConfigSchema
|
||||
? {}
|
||||
: z.input<
|
||||
z.ZodObject<{
|
||||
[key in keyof TConfigSchema]: ReturnType<TConfigSchema[key]>;
|
||||
}>
|
||||
>)
|
||||
>;
|
||||
): ExtensionDefinition<TConfig, TConfigInput, never, never>;
|
||||
export function createExtension<
|
||||
UOutput extends AnyExtensionDataRef,
|
||||
TInputs extends {
|
||||
@@ -476,20 +408,12 @@ export function createExtension<
|
||||
UFactoryOutput extends ExtensionDataValue<any, any>,
|
||||
>(
|
||||
options:
|
||||
| CreateExtensionOptions<
|
||||
UOutput,
|
||||
TInputs,
|
||||
TConfig,
|
||||
TConfigInput,
|
||||
TConfigSchema,
|
||||
UFactoryOutput
|
||||
>
|
||||
| CreateExtensionOptions<UOutput, TInputs, TConfigSchema, UFactoryOutput>
|
||||
| LegacyCreateExtensionOptions<
|
||||
AnyExtensionDataMap,
|
||||
TLegacyInputs,
|
||||
TConfig,
|
||||
TConfigInput,
|
||||
TConfigSchema
|
||||
TConfigInput
|
||||
>,
|
||||
): ExtensionDefinition<
|
||||
TConfig &
|
||||
@@ -505,22 +429,29 @@ export function createExtension<
|
||||
z.ZodObject<{
|
||||
[key in keyof TConfigSchema]: ReturnType<TConfigSchema[key]>;
|
||||
}>
|
||||
>)
|
||||
> &
|
||||
OverridableExtension<UOutput, TInputs, TConfig, TConfigInput, TConfigSchema> {
|
||||
const newConfigSchema = options.config?.schema;
|
||||
if (newConfigSchema && options.configSchema) {
|
||||
>),
|
||||
UOutput,
|
||||
TInputs
|
||||
> {
|
||||
if ('configSchema' in options && 'config' in options) {
|
||||
throw new Error(`Cannot provide both configSchema and config.schema`);
|
||||
}
|
||||
const configSchema = newConfigSchema
|
||||
? createSchemaFromZod(innerZ =>
|
||||
let configSchema: PortableSchema<any, any> | undefined;
|
||||
if ('configSchema' in options) {
|
||||
configSchema = options.configSchema;
|
||||
}
|
||||
if ('config' in options) {
|
||||
const newConfigSchema = options.config?.schema;
|
||||
configSchema =
|
||||
newConfigSchema &&
|
||||
createSchemaFromZod(innerZ =>
|
||||
innerZ.object(
|
||||
Object.fromEntries(
|
||||
Object.entries(newConfigSchema).map(([k, v]) => [k, v(innerZ)]),
|
||||
),
|
||||
),
|
||||
)
|
||||
: options.configSchema;
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
$$type: '@backstage/ExtensionDefinition',
|
||||
@@ -548,89 +479,133 @@ export function createExtension<
|
||||
parts.push(`attachTo=${options.attachTo.id}@${options.attachTo.input}`);
|
||||
return `ExtensionDefinition{${parts.join(',')}}`;
|
||||
},
|
||||
override<
|
||||
TConfigSchemaOverrides extends {
|
||||
override: <
|
||||
TExtensionConfigSchema extends {
|
||||
[key in string]: (zImpl: typeof z) => z.ZodType;
|
||||
},
|
||||
UOutputOverrides extends AnyExtensionDataRef,
|
||||
UFactoryOverrideOutput extends ExtensionDataValue<any, any>,
|
||||
>(
|
||||
overrideOptions: OverrideExtensionOptions<
|
||||
UOverrideFactoryOutput extends ExtensionDataValue<any, any>,
|
||||
UNewOutput extends AnyExtensionDataRef,
|
||||
TExtraInputs extends {
|
||||
[inputName in string]: ExtensionInput<
|
||||
AnyExtensionDataRef,
|
||||
{ optional: boolean; singleton: boolean }
|
||||
>;
|
||||
},
|
||||
>(overrideOptions: {
|
||||
attachTo?: { id: string; input: string };
|
||||
disabled?: boolean;
|
||||
inputs?: TExtraInputs;
|
||||
output?: Array<UNewOutput>;
|
||||
config?: {
|
||||
schema: TExtensionConfigSchema;
|
||||
};
|
||||
factory(
|
||||
originalFactory: (context?: {
|
||||
config?: {
|
||||
[key in keyof TConfigSchema]: z.infer<
|
||||
ReturnType<TConfigSchema[key]>
|
||||
>;
|
||||
};
|
||||
inputs?: Expand<ResolvedExtensionInputs<TInputs>>;
|
||||
}) => ExtensionDataContainer<UOutput>,
|
||||
context: {
|
||||
node: AppNode;
|
||||
config: {
|
||||
[key in keyof TExtensionConfigSchema]: z.infer<
|
||||
ReturnType<TExtensionConfigSchema[key]>
|
||||
>;
|
||||
} & {
|
||||
[key in keyof TConfigSchema]: z.infer<
|
||||
ReturnType<TConfigSchema[key]>
|
||||
>;
|
||||
};
|
||||
inputs: Expand<ResolvedExtensionInputs<TInputs & TExtraInputs>>;
|
||||
},
|
||||
): Iterable<UOverrideFactoryOutput>;
|
||||
}): ExtensionDefinition<
|
||||
{
|
||||
[key in keyof TExtensionConfigSchema]: z.infer<
|
||||
ReturnType<TExtensionConfigSchema[key]>
|
||||
>;
|
||||
} & {
|
||||
[key in keyof TConfigSchema]: z.infer<ReturnType<TConfigSchema[key]>>;
|
||||
},
|
||||
z.input<
|
||||
z.ZodObject<
|
||||
{
|
||||
[key in keyof TExtensionConfigSchema]: ReturnType<
|
||||
TExtensionConfigSchema[key]
|
||||
>;
|
||||
} & {
|
||||
[key in keyof TConfigSchema]: ReturnType<TConfigSchema[key]>;
|
||||
}
|
||||
>
|
||||
>,
|
||||
AnyExtensionDataRef extends UNewOutput ? UOutput : UNewOutput,
|
||||
TInputs & TExtraInputs
|
||||
> => {
|
||||
if (!Array.isArray(options.output)) {
|
||||
throw new Error(
|
||||
'Cannot override an extension that is not declared using the new format with outputs as an array',
|
||||
);
|
||||
}
|
||||
const newOptions = options as CreateExtensionOptions<
|
||||
UOutput,
|
||||
TInputs,
|
||||
TConfig,
|
||||
TConfigInput,
|
||||
TConfigSchema,
|
||||
TConfigSchemaOverrides,
|
||||
UOutputOverrides,
|
||||
UFactoryOverrideOutput
|
||||
>,
|
||||
) {
|
||||
UFactoryOutput
|
||||
>;
|
||||
const overrideNewConfigSchema = overrideOptions.config?.schema;
|
||||
if (overrideNewConfigSchema && overrideOptions.configSchema) {
|
||||
throw new Error(`Cannot provide both configSchema and config.schema`);
|
||||
}
|
||||
|
||||
const mergedConfigSchema = {
|
||||
...options.config?.schema,
|
||||
...overrideOptions.config?.schema,
|
||||
};
|
||||
|
||||
const overrideConfigSchema = mergedConfigSchema
|
||||
? createSchemaFromZod(innerZ =>
|
||||
innerZ.object(
|
||||
Object.fromEntries(
|
||||
Object.entries(mergedConfigSchema).map(([k, v]) => [
|
||||
k,
|
||||
v(innerZ),
|
||||
]),
|
||||
),
|
||||
),
|
||||
)
|
||||
: overrideOptions.configSchema;
|
||||
|
||||
const buildDataContainer = (outputs): ExtensionDataContainer<UOutput> => {
|
||||
const dataMap = new Map<string, unknown>();
|
||||
if (Symbol.iterator in outputs) {
|
||||
for (const output of outputs) {
|
||||
dataMap.set(output.id, output.value);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
get(ref) {
|
||||
return dataMap.get(ref.id);
|
||||
},
|
||||
} as ExtensionDataContainer<UOutput>;
|
||||
};
|
||||
const schema = {
|
||||
...newOptions.config?.schema,
|
||||
...overrideNewConfigSchema,
|
||||
} as TConfigSchema & TExtensionConfigSchema;
|
||||
|
||||
return createExtension({
|
||||
attachTo: options.attachTo,
|
||||
output: overrideOptions.output ?? options.output,
|
||||
configSchema: overrideConfigSchema,
|
||||
kind: newOptions.kind,
|
||||
namespace: newOptions.namespace,
|
||||
name: newOptions.name,
|
||||
attachTo: overrideOptions.attachTo ?? newOptions.attachTo,
|
||||
disabled: overrideOptions.disabled ?? newOptions.disabled,
|
||||
inputs: { ...overrideOptions.inputs, ...newOptions.inputs },
|
||||
output: overrideOptions.output ?? newOptions.output,
|
||||
config: Object.keys(schema).length === 0 ? undefined : { schema },
|
||||
factory: ({ node, config, inputs }) => {
|
||||
if (overrideOptions.factory) {
|
||||
return overrideOptions.factory(
|
||||
innerCtx => {
|
||||
const originalFactoryResponse = options.factory({
|
||||
node,
|
||||
config: innerCtx?.config ?? config,
|
||||
inputs: innerCtx?.inputs ?? inputs,
|
||||
});
|
||||
|
||||
return buildDataContainer(originalFactoryResponse);
|
||||
},
|
||||
{ node, config, inputs },
|
||||
);
|
||||
if (!overrideOptions.factory) {
|
||||
return newOptions.factory({
|
||||
node,
|
||||
config,
|
||||
inputs: inputs as unknown as Expand<
|
||||
ResolvedExtensionInputs<TInputs>
|
||||
>,
|
||||
});
|
||||
}
|
||||
return options.factory(originalFactory, context);
|
||||
return overrideOptions.factory(
|
||||
(innerContext?: {
|
||||
config?: {
|
||||
[key in keyof TConfigSchema]: z.infer<
|
||||
ReturnType<TConfigSchema[key]>
|
||||
>;
|
||||
};
|
||||
inputs?: Expand<ResolvedExtensionInputs<TInputs>>;
|
||||
}): ExtensionDataContainer<UOutput> => {
|
||||
return createDataContainer<UOutput>(
|
||||
newOptions.factory({
|
||||
node,
|
||||
config: innerContext?.config ?? config,
|
||||
inputs: (innerContext?.inputs ?? inputs) as any, // TODO: Fix the way input values are overridden
|
||||
}) as Iterable<any>,
|
||||
);
|
||||
},
|
||||
{
|
||||
node,
|
||||
config,
|
||||
inputs,
|
||||
},
|
||||
);
|
||||
},
|
||||
disabled: overrideOptions.disabled ?? options.disabled,
|
||||
inputs: overrideOptions.inputs ?? options.inputs,
|
||||
kind: overrideOptions.kind ?? options.kind,
|
||||
name: overrideOptions.name ?? options.name,
|
||||
namespace: overrideOptions.namespace ?? options.namespace,
|
||||
});
|
||||
} as CreateExtensionOptions<AnyExtensionDataRef extends UNewOutput ? UOutput : UNewOutput, TInputs & TExtraInputs, TConfigSchema & TExtensionConfigSchema, UOverrideFactoryOutput>);
|
||||
},
|
||||
} as InternalExtensionDefinition<
|
||||
TConfig &
|
||||
|
||||
@@ -69,6 +69,7 @@ describe('createExtensionBlueprint', () => {
|
||||
output: [coreExtensionData.reactElement],
|
||||
factory: expect.any(Function),
|
||||
toString: expect.any(Function),
|
||||
override: expect.any(Function),
|
||||
version: 'v2',
|
||||
});
|
||||
|
||||
@@ -108,6 +109,7 @@ describe('createExtensionBlueprint', () => {
|
||||
output: [coreExtensionData.reactElement],
|
||||
factory: expect.any(Function),
|
||||
toString: expect.any(Function),
|
||||
override: expect.any(Function),
|
||||
version: 'v2',
|
||||
});
|
||||
|
||||
@@ -389,6 +391,7 @@ describe('createExtensionBlueprint', () => {
|
||||
"output": [
|
||||
[Function],
|
||||
],
|
||||
"override": [Function],
|
||||
"toString": [Function],
|
||||
"version": "v2",
|
||||
}
|
||||
|
||||
@@ -171,7 +171,7 @@ export interface ExtensionBlueprint<
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
function createDataContainer<UData extends AnyExtensionDataRef>(
|
||||
export function createDataContainer<UData extends AnyExtensionDataRef>(
|
||||
values: Iterable<
|
||||
UData extends ExtensionDataRef<infer IData, infer IId>
|
||||
? ExtensionDataValue<IData, IId>
|
||||
@@ -241,10 +241,7 @@ class ExtensionBlueprintImpl<
|
||||
name?: string;
|
||||
attachTo?: { id: string; input: string };
|
||||
disabled?: boolean;
|
||||
inputs?: TExtraInputs & {
|
||||
[KName in keyof TInputs]?: `Error: Input '${KName &
|
||||
string}' is already defined in parent definition`;
|
||||
};
|
||||
inputs?: TExtraInputs;
|
||||
output?: Array<UNewOutput>;
|
||||
params?: TParams;
|
||||
config?: {
|
||||
@@ -363,9 +360,7 @@ class ExtensionBlueprintImpl<
|
||||
}
|
||||
throw new Error('Either params or factory must be provided');
|
||||
},
|
||||
} as CreateExtensionOptions<
|
||||
UOutput,
|
||||
TInputs & TExtraInputs,
|
||||
} as CreateExtensionOptions<UOutput, TInputs & TExtraInputs, TConfigSchema & TExtensionConfigSchema, UFactoryOutput>) as ExtensionDefinition<
|
||||
{
|
||||
[key in keyof TExtensionConfigSchema]: z.infer<
|
||||
ReturnType<TExtensionConfigSchema[key]>
|
||||
@@ -383,10 +378,8 @@ class ExtensionBlueprintImpl<
|
||||
[key in keyof TConfigSchema]: ReturnType<TConfigSchema[key]>;
|
||||
}
|
||||
>
|
||||
>,
|
||||
TConfigSchema,
|
||||
UFactoryOutput
|
||||
>);
|
||||
>
|
||||
>;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user