diff --git a/packages/app-example-plugin/report.api.md b/packages/app-example-plugin/report.api.md index 432174da68..2269e5c3b3 100644 --- a/packages/app-example-plugin/report.api.md +++ b/packages/app-example-plugin/report.api.md @@ -22,11 +22,11 @@ const examplePlugin: OverridableFrontendPlugin< 'page:example': OverridableExtensionDefinition<{ kind: 'page'; name: undefined; - config: { + config: {} & { path: string | undefined; title: string | undefined; }; - configInput: { + configInput: {} & { title?: string | undefined; path?: string | undefined; }; diff --git a/packages/frontend-plugin-api/report-alpha.api.md b/packages/frontend-plugin-api/report-alpha.api.md index 4df3233871..0ec2a76218 100644 --- a/packages/frontend-plugin-api/report-alpha.api.md +++ b/packages/frontend-plugin-api/report-alpha.api.md @@ -87,9 +87,10 @@ export interface AppTree { } // @public -export type ConfigFieldSchema = - | StandardSchemaV1 - | ((zImpl: typeof z) => ZodType); +export type ConfigFieldSchema = StandardSchemaV1 | ConfigFieldSchemaFactory; + +// @public @deprecated +export type ConfigFieldSchemaFactory = (zImpl: typeof z) => ZodType; // @public (undocumented) export interface ConfigurableExtensionDataRef< @@ -178,6 +179,9 @@ export interface ExtensionBlueprint< TExtraInputs extends { [inputName in string]: ExtensionInput; } = {}, + TNewExtensionConfigSchema extends { + [key in string]: StandardSchemaV1; + } = {}, >(args: { name?: TName; attachTo?: ExtensionDefinitionAttachTo & @@ -194,6 +198,10 @@ export interface ExtensionBlueprint< string}' is already defined in parent definition`; }; output?: Array; + configSchema?: TNewExtensionConfigSchema & { + [KName in keyof T['config']]?: `Error: Config key '${KName & + string}' is already defined in parent schema`; + }; config?: { schema: TExtensionConfigSchema & { [KName in keyof T['config']]?: `Error: Config key '${KName & @@ -218,6 +226,10 @@ export interface ExtensionBlueprint< node: AppNode; apis: ApiHolder; config: T['config'] & { + [key in keyof TNewExtensionConfigSchema]: NonNullable< + TNewExtensionConfigSchema[key]['~standard']['types'] + >['output']; + } & { [key in keyof TExtensionConfigSchema]: z.infer< ReturnType<((...args: any[]) => any) & TExtensionConfigSchema[key]> >; @@ -233,7 +245,11 @@ export interface ExtensionBlueprint< >; }): OverridableExtensionDefinition<{ config: Expand< - (string extends keyof TExtensionConfigSchema + { + [key in keyof TNewExtensionConfigSchema]: NonNullable< + TNewExtensionConfigSchema[key]['~standard']['types'] + >['output']; + } & (string extends keyof TExtensionConfigSchema ? {} : { [key in keyof TExtensionConfigSchema]: z.infer< @@ -245,7 +261,11 @@ export interface ExtensionBlueprint< T['config'] >; configInput: Expand< - (string extends keyof TExtensionConfigSchema + { + [key in keyof TNewExtensionConfigSchema]?: NonNullable< + TNewExtensionConfigSchema[key]['~standard']['types'] + >['input']; + } & (string extends keyof TExtensionConfigSchema ? {} : z.input< z.ZodObject<{ @@ -491,6 +511,9 @@ export interface OverridableExtensionDefinition< }, TParamsInput extends AnyParamsInput_2>, UParentInputs extends ExtensionDataRef, + TNewExtensionConfigSchema extends { + [key in string]: StandardSchemaV1; + } = {}, >( args: Expand< { @@ -508,6 +531,10 @@ export interface OverridableExtensionDefinition< string}' is already defined in parent definition`; }; output?: Array; + configSchema?: TNewExtensionConfigSchema & { + [KName in keyof T['config']]?: `Error: Config key '${KName & + string}' is already defined in parent schema`; + }; config?: { schema: TExtensionConfigSchema & { [KName in keyof T['config']]?: `Error: Config key '${KName & @@ -539,6 +566,10 @@ export interface OverridableExtensionDefinition< node: AppNode; apis: ApiHolder; config: T['config'] & { + [key in keyof TNewExtensionConfigSchema]: NonNullable< + TNewExtensionConfigSchema[key]['~standard']['types'] + >['output']; + } & { [key in keyof TExtensionConfigSchema]: z.infer< ReturnType< ((...args: any[]) => any) & TExtensionConfigSchema[key] @@ -570,12 +601,19 @@ export interface OverridableExtensionDefinition< output: ExtensionDataRef extends UNewOutput ? T['output'] : UNewOutput; inputs: T['inputs'] & TExtraInputs; config: T['config'] & { + [key in keyof TNewExtensionConfigSchema]: NonNullable< + TNewExtensionConfigSchema[key]['~standard']['types'] + >['output']; + } & { [key in keyof TExtensionConfigSchema]: z.infer< ReturnType<((...args: any[]) => any) & TExtensionConfigSchema[key]> >; }; - configInput: T['configInput'] & - z.input< + configInput: T['configInput'] & { + [key in keyof TNewExtensionConfigSchema]?: NonNullable< + TNewExtensionConfigSchema[key]['~standard']['types'] + >['input']; + } & z.input< z.ZodObject<{ [key in keyof TExtensionConfigSchema]: ReturnType< ((...args: any[]) => any) & TExtensionConfigSchema[key] @@ -669,5 +707,8 @@ export interface SubRouteRef< readonly T: TParams; } +// @public @deprecated +export function zodField(factory: (zImpl: typeof z) => T): T; + // (No @packageDocumentation comment for this package) ``` diff --git a/packages/frontend-plugin-api/report.api.md b/packages/frontend-plugin-api/report.api.md index b975da2e14..42156875e4 100644 --- a/packages/frontend-plugin-api/report.api.md +++ b/packages/frontend-plugin-api/report.api.md @@ -469,7 +469,69 @@ export function createApiRef(): { }; }; +// Warning: (ae-forgotten-export) The symbol "VerifyExtensionFactoryOutput" needs to be exported by the entry point index.d.ts +// // @public +export function createExtension< + UOutput extends ExtensionDataRef, + TInputs extends { + [inputName in string]: ExtensionInput; + }, + UFactoryOutput extends ExtensionDataValue, + const TKind extends string | undefined = undefined, + const TName extends string | undefined = undefined, + UParentInputs extends ExtensionDataRef = ExtensionDataRef, + TNewConfigSchema extends { + [key: string]: StandardSchemaV1; + } = {}, +>( + options: { + kind?: TKind; + name?: TName; + attachTo: ExtensionDefinitionAttachTo & + VerifyExtensionAttachTo; + disabled?: boolean; + if?: FilterPredicate; + inputs?: TInputs; + output: Array; + config?: never; + configSchema?: TNewConfigSchema; + factory(context: { + node: AppNode; + apis: ApiHolder; + config: { + [key in keyof TNewConfigSchema]: NonNullable< + TNewConfigSchema[key]['~standard']['types'] + >['output']; + }; + inputs: Expand>; + }): Iterable; + } & VerifyExtensionFactoryOutput, +): OverridableExtensionDefinition<{ + config: { + [key in keyof TNewConfigSchema]: NonNullable< + TNewConfigSchema[key]['~standard']['types'] + >['output']; + }; + configInput: { + [key in keyof TNewConfigSchema]?: NonNullable< + TNewConfigSchema[key]['~standard']['types'] + >['input']; + }; + output: UOutput extends ExtensionDataRef< + infer IData, + infer IId, + infer IConfig + > + ? ExtensionDataRef + : never; + inputs: TInputs; + params: never; + kind: string | undefined extends TKind ? undefined : TKind; + name: string | undefined extends TName ? undefined : TName; +}>; + +// @public @deprecated (undocumented) export function createExtension< UOutput extends ExtensionDataRef, TInputs extends { @@ -483,15 +545,30 @@ export function createExtension< const TName extends string | undefined = undefined, UParentInputs extends ExtensionDataRef = ExtensionDataRef, >( - options: CreateExtensionOptions< - TKind, - TName, - UOutput, - TInputs, - TConfigSchema, - UFactoryOutput, - UParentInputs - >, + options: { + kind?: TKind; + name?: TName; + attachTo: ExtensionDefinitionAttachTo & + VerifyExtensionAttachTo; + disabled?: boolean; + if?: FilterPredicate; + inputs?: TInputs; + output: Array; + configSchema?: never; + config?: { + schema: TConfigSchema; + }; + factory(context: { + node: AppNode; + apis: ApiHolder; + config: { + [key in keyof TConfigSchema]: z.infer< + ReturnType<((...args: any[]) => any) & TConfigSchema[key]> + >; + }; + inputs: Expand>; + }): Iterable; + } & VerifyExtensionFactoryOutput, ): OverridableExtensionDefinition<{ config: string extends keyof TConfigSchema ? {} @@ -522,7 +599,80 @@ export function createExtension< name: string | undefined extends TName ? undefined : TName; }>; +// Warning: (ae-unresolved-link) The @link reference could not be resolved: The reference is ambiguous because "createExtension" has more than one declaration; you need to add a TSDoc member reference selector +// // @public +export function createExtensionBlueprint< + TParams extends object | ExtensionBlueprintDefineParams, + UOutput extends ExtensionDataRef, + TInputs extends { + [inputName in string]: ExtensionInput; + }, + UFactoryOutput extends ExtensionDataValue, + TKind extends string, + UParentInputs extends ExtensionDataRef, + TDataRefs extends { + [name in string]: ExtensionDataRef; + } = never, + TNewConfigSchema extends { + [key in string]: StandardSchemaV1; + } = {}, +>( + options: { + kind: TKind; + attachTo: ExtensionDefinitionAttachTo & + VerifyExtensionAttachTo; + disabled?: boolean; + if?: FilterPredicate; + inputs?: TInputs; + output: Array; + config?: never; + configSchema?: TNewConfigSchema; + defineParams?: TParams extends ExtensionBlueprintDefineParams + ? TParams + : 'The defineParams option must be a function if provided, see the docs for details'; + factory( + params: TParams extends ExtensionBlueprintDefineParams + ? ReturnType['T'] + : TParams, + context: { + node: AppNode; + apis: ApiHolder; + config: { + [key in keyof TNewConfigSchema]: NonNullable< + TNewConfigSchema[key]['~standard']['types'] + >['output']; + }; + inputs: Expand>; + }, + ): Iterable; + dataRefs?: TDataRefs; + } & VerifyExtensionFactoryOutput, +): ExtensionBlueprint<{ + kind: TKind; + params: TParams; + output: UOutput extends ExtensionDataRef< + infer IData, + infer IId, + infer IConfig + > + ? ExtensionDataRef + : never; + inputs: string extends keyof TInputs ? {} : TInputs; + config: { + [key in keyof TNewConfigSchema]: NonNullable< + TNewConfigSchema[key]['~standard']['types'] + >['output']; + }; + configInput: { + [key in keyof TNewConfigSchema]?: NonNullable< + TNewConfigSchema[key]['~standard']['types'] + >['input']; + }; + dataRefs: TDataRefs; +}>; + +// @public @deprecated (undocumented) export function createExtensionBlueprint< TParams extends object | ExtensionBlueprintDefineParams, UOutput extends ExtensionDataRef, @@ -539,16 +689,38 @@ export function createExtensionBlueprint< [name in string]: ExtensionDataRef; } = never, >( - options: CreateExtensionBlueprintOptions< - TKind, - TParams, - UOutput, - TInputs, - TConfigSchema, - UFactoryOutput, - TDataRefs, - UParentInputs - >, + options: { + kind: TKind; + attachTo: ExtensionDefinitionAttachTo & + VerifyExtensionAttachTo; + disabled?: boolean; + if?: FilterPredicate; + inputs?: TInputs; + output: Array; + configSchema?: never; + config?: { + schema: TConfigSchema; + }; + defineParams?: TParams extends ExtensionBlueprintDefineParams + ? TParams + : 'The defineParams option must be a function if provided, see the docs for details'; + factory( + params: TParams extends ExtensionBlueprintDefineParams + ? ReturnType['T'] + : TParams, + context: { + node: AppNode; + apis: ApiHolder; + config: { + [key in keyof TConfigSchema]: z.infer< + ReturnType<((...args: any[]) => any) & TConfigSchema[key]> + >; + }; + inputs: Expand>; + }, + ): Iterable; + dataRefs?: TDataRefs; + } & VerifyExtensionFactoryOutput, ): ExtensionBlueprint<{ kind: TKind; params: TParams; @@ -595,6 +767,9 @@ export type CreateExtensionBlueprintOptions< [name in string]: ExtensionDataRef; }, UParentInputs extends ExtensionDataRef, + TNewConfigSchema extends { + [key in string]: StandardSchemaV1; + } = {}, > = { kind: TKind; attachTo: ExtensionDefinitionAttachTo & @@ -603,6 +778,7 @@ export type CreateExtensionBlueprintOptions< if?: FilterPredicate; inputs?: TInputs; output: Array; + configSchema?: TNewConfigSchema; config?: { schema: TConfigSchema; }; @@ -617,6 +793,10 @@ export type CreateExtensionBlueprintOptions< node: AppNode; apis: ApiHolder; config: { + [key in keyof TNewConfigSchema]: NonNullable< + TNewConfigSchema[key]['~standard']['types'] + >['output']; + } & { [key in keyof TConfigSchema]: z.infer< ReturnType<((...args: any[]) => any) & TConfigSchema[key]> >; @@ -683,6 +863,9 @@ export type CreateExtensionOptions< }, UFactoryOutput extends ExtensionDataValue, UParentInputs extends ExtensionDataRef, + TNewConfigSchema extends { + [key: string]: StandardSchemaV1; + } = {}, > = { kind?: TKind; name?: TName; @@ -692,6 +875,7 @@ export type CreateExtensionOptions< if?: FilterPredicate; inputs?: TInputs; output: Array; + configSchema?: TNewConfigSchema; config?: { schema: TConfigSchema; }; @@ -699,6 +883,10 @@ export type CreateExtensionOptions< node: AppNode; apis: ApiHolder; config: { + [key in keyof TNewConfigSchema]: NonNullable< + TNewConfigSchema[key]['~standard']['types'] + >['output']; + } & { [key in keyof TConfigSchema]: z.infer< ReturnType<((...args: any[]) => any) & TConfigSchema[key]> >; @@ -1063,6 +1251,91 @@ export interface ExtensionBlueprint< inputs: T['inputs']; params: T['params']; }>; + makeWithOverrides< + TName extends string | undefined, + UFactoryOutput extends ExtensionDataValue, + UNewOutput extends ExtensionDataRef, + UParentInputs extends ExtensionDataRef, + TExtraInputs extends { + [inputName in string]: ExtensionInput; + } = {}, + TNewExtensionConfigSchema extends { + [key in string]: StandardSchemaV1; + } = {}, + >(args: { + name?: TName; + attachTo?: ExtensionDefinitionAttachTo & + VerifyExtensionAttachTo< + ExtensionDataRef extends UNewOutput + ? NonNullable + : UNewOutput, + UParentInputs + >; + disabled?: boolean; + if?: FilterPredicate; + inputs?: TExtraInputs & { + [KName in keyof T['inputs']]?: `Error: Input '${KName & + string}' is already defined in parent definition`; + }; + output?: Array; + config?: never; + configSchema?: TNewExtensionConfigSchema & { + [KName in keyof T['config']]?: `Error: Config key '${KName & + string}' is already defined in parent schema`; + }; + factory( + originalFactory: < + TParamsInput extends AnyParamsInput_2>, + >( + params: TParamsInput extends ExtensionBlueprintDefineParams + ? TParamsInput + : T['params'] extends ExtensionBlueprintDefineParams + ? 'Error: This blueprint uses advanced parameter types and requires you to pass parameters as using the following callback syntax: `originalFactory(defineParams => defineParams())`' + : T['params'], + context?: { + config?: T['config']; + inputs?: ResolvedInputValueOverrides>; + }, + ) => ExtensionDataContainer>, + context: { + node: AppNode; + apis: ApiHolder; + config: T['config'] & { + [key in keyof TNewExtensionConfigSchema]: NonNullable< + TNewExtensionConfigSchema[key]['~standard']['types'] + >['output']; + }; + inputs: Expand>; + }, + ): Iterable & + VerifyExtensionFactoryOutput< + ExtensionDataRef extends UNewOutput + ? NonNullable + : UNewOutput, + UFactoryOutput + >; + }): OverridableExtensionDefinition<{ + config: Expand< + { + [key in keyof TNewExtensionConfigSchema]: NonNullable< + TNewExtensionConfigSchema[key]['~standard']['types'] + >['output']; + } & T['config'] + >; + configInput: Expand< + { + [key in keyof TNewExtensionConfigSchema]?: NonNullable< + TNewExtensionConfigSchema[key]['~standard']['types'] + >['input']; + } & T['configInput'] + >; + output: ExtensionDataRef extends UNewOutput ? T['output'] : UNewOutput; + inputs: Expand; + kind: T['kind']; + name: string | undefined extends TName ? undefined : TName; + params: T['params']; + }>; + // @deprecated (undocumented) makeWithOverrides< TName extends string | undefined, TExtensionConfigSchema extends { @@ -1090,6 +1363,7 @@ export interface ExtensionBlueprint< string}' is already defined in parent definition`; }; output?: Array; + configSchema?: never; config?: { schema: TExtensionConfigSchema & { [KName in keyof T['config']]?: `Error: Config key '${KName & @@ -1706,6 +1980,104 @@ export interface OverridableExtensionDefinition< >; }; // (undocumented) + override< + UFactoryOutput extends ExtensionDataValue, + UNewOutput extends ExtensionDataRef, + TExtraInputs extends { + [inputName in string]: ExtensionInput; + }, + TParamsInput extends AnyParamsInput>, + UParentInputs extends ExtensionDataRef, + TNewExtensionConfigSchema extends { + [key in string]: StandardSchemaV1; + } = {}, + >( + args: Expand< + { + attachTo?: ExtensionDefinitionAttachTo & + VerifyExtensionAttachTo< + ExtensionDataRef extends UNewOutput + ? NonNullable + : UNewOutput, + UParentInputs + >; + disabled?: boolean; + if?: FilterPredicate; + inputs?: TExtraInputs & { + [KName in keyof T['inputs']]?: `Error: Input '${KName & + string}' is already defined in parent definition`; + }; + output?: Array; + config?: never; + configSchema?: TNewExtensionConfigSchema & { + [KName in keyof T['config']]?: `Error: Config key '${KName & + string}' is already defined in parent schema`; + }; + factory?( + originalFactory: < + TFactoryParamsReturn extends AnyParamsInput< + NonNullable + >, + >( + context?: Expand< + { + config?: T['config']; + inputs?: ResolvedInputValueOverrides>; + } & ([T['params']] extends [never] + ? {} + : { + params?: TFactoryParamsReturn extends ExtensionBlueprintDefineParams + ? TFactoryParamsReturn + : T['params'] extends ExtensionBlueprintDefineParams + ? 'Error: This blueprint uses advanced parameter types and requires you to pass parameters as using the following callback syntax: `originalFactory(defineParams => defineParams())`' + : Partial; + }) + >, + ) => ExtensionDataContainer>, + context: { + node: AppNode; + apis: ApiHolder; + config: T['config'] & { + [key in keyof TNewExtensionConfigSchema]: NonNullable< + TNewExtensionConfigSchema[key]['~standard']['types'] + >['output']; + }; + inputs: Expand>; + }, + ): Iterable; + } & ([T['params']] extends [never] + ? {} + : { + params?: TParamsInput extends ExtensionBlueprintDefineParams + ? TParamsInput + : T['params'] extends ExtensionBlueprintDefineParams + ? 'Error: This blueprint uses advanced parameter types and requires you to pass parameters as using the following callback syntax: `originalFactory(defineParams => defineParams())`' + : Partial; + }) + > & + VerifyExtensionFactoryOutput< + ExtensionDataRef extends UNewOutput + ? NonNullable + : UNewOutput, + UFactoryOutput + >, + ): OverridableExtensionDefinition<{ + kind: T['kind']; + name: T['name']; + output: ExtensionDataRef extends UNewOutput ? T['output'] : UNewOutput; + inputs: T['inputs'] & TExtraInputs; + config: T['config'] & { + [key in keyof TNewExtensionConfigSchema]: NonNullable< + TNewExtensionConfigSchema[key]['~standard']['types'] + >['output']; + }; + configInput: T['configInput'] & { + [key in keyof TNewExtensionConfigSchema]?: NonNullable< + TNewExtensionConfigSchema[key]['~standard']['types'] + >['input']; + }; + }>; + // @deprecated (undocumented) override< TExtensionConfigSchema extends { [key in string]: ConfigFieldSchema; @@ -1734,6 +2106,7 @@ export interface OverridableExtensionDefinition< string}' is already defined in parent definition`; }; output?: Array; + configSchema?: never; config?: { schema: TExtensionConfigSchema & { [KName in keyof T['config']]?: `Error: Config key '${KName & @@ -2519,4 +2892,9 @@ export function withApis( (props: PropsWithChildren>): JSX_3.Element; displayName: string; }; + +// Warnings were encountered during analysis: +// +// src/wiring/createExtension.d.ts:280:5 - (ae-forgotten-export) The symbol "VerifyExtensionAttachTo" needs to be exported by the entry point index.d.ts +// src/wiring/createExtension.d.ts:293:9 - (ae-forgotten-export) The symbol "ResolvedExtensionInputs" needs to be exported by the entry point index.d.ts ``` diff --git a/packages/frontend-plugin-api/src/schema/createPortableSchema.ts b/packages/frontend-plugin-api/src/schema/createPortableSchema.ts index eab87ad562..26d4835e0f 100644 --- a/packages/frontend-plugin-api/src/schema/createPortableSchema.ts +++ b/packages/frontend-plugin-api/src/schema/createPortableSchema.ts @@ -46,7 +46,7 @@ import { type StandardSchemaV1 } from '@standard-schema/spec'; /** * A single config field schema. Accepts any Standard Schema implementation, - * or the legacy factory form for backward compat and zod-based composition. + * or the legacy factory form for backward compat. * @public */ export type ConfigFieldSchema = @@ -422,3 +422,22 @@ function formatStandardIssue( : fieldKey; return `${message} at '${path}'`; } + +// --------------------------------------------------------------------------- +// Deprecation warning +// --------------------------------------------------------------------------- + +let hasWarnedConfigSchemaProp = false; + +/** @internal */ +export function warnConfigSchemaPropDeprecation() { + if (!hasWarnedConfigSchemaProp) { + hasWarnedConfigSchemaProp = true; + // eslint-disable-next-line no-console + console.warn( + 'DEPRECATION WARNING: The `config.schema` option for extension config is deprecated. ' + + 'Use the `configSchema` option instead with Standard Schema values, for example ' + + '`configSchema: { title: z.string() }` using zod v3.25+ or v4.', + ); + } +} diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.ts b/packages/frontend-plugin-api/src/wiring/createExtension.ts index 735438cdd7..5cd8f987f8 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.ts @@ -29,6 +29,8 @@ import { ExtensionInput } from './createExtensionInput'; import type { z } from 'zod/v3'; import { createSchemaFromZod } from '../schema/createSchemaFromZod'; import type { ConfigFieldSchema } from '../schema/createPortableSchema'; +import { warnConfigSchemaPropDeprecation } from '../schema/createPortableSchema'; +import { type StandardSchemaV1 } from '@standard-schema/spec'; import { OpaqueExtensionDefinition } from '@internal/frontend'; import { ExtensionDataContainer } from './types'; import { @@ -170,6 +172,7 @@ export type CreateExtensionOptions< TConfigSchema extends { [key: string]: ConfigFieldSchema }, UFactoryOutput extends ExtensionDataValue, UParentInputs extends ExtensionDataRef, + TNewConfigSchema extends { [key: string]: StandardSchemaV1 } = {}, > = { kind?: TKind; name?: TName; @@ -179,13 +182,24 @@ export type CreateExtensionOptions< if?: FilterPredicate; inputs?: TInputs; output: Array; + configSchema?: TNewConfigSchema; + /** + * @deprecated Use {@link CreateExtensionOptions.configSchema} instead. + */ config?: { + /** + * @deprecated Use {@link CreateExtensionOptions.configSchema} instead. + */ schema: TConfigSchema; }; factory(context: { node: AppNode; apis: ApiHolder; config: { + [key in keyof TNewConfigSchema]: NonNullable< + TNewConfigSchema[key]['~standard']['types'] + >['output']; + } & { [key in keyof TConfigSchema]: z.infer< ReturnType<((...args: any[]) => any) & TConfigSchema[key]> >; @@ -240,6 +254,105 @@ export interface OverridableExtensionDefinition< >; }; + override< + UFactoryOutput extends ExtensionDataValue, + UNewOutput extends ExtensionDataRef, + TExtraInputs extends { [inputName in string]: ExtensionInput }, + TParamsInput extends AnyParamsInput>, + UParentInputs extends ExtensionDataRef, + TNewExtensionConfigSchema extends { + [key in string]: StandardSchemaV1; + } = {}, + >( + args: Expand< + { + attachTo?: ExtensionDefinitionAttachTo & + VerifyExtensionAttachTo< + ExtensionDataRef extends UNewOutput + ? NonNullable + : UNewOutput, + UParentInputs + >; + disabled?: boolean; + if?: FilterPredicate; + inputs?: TExtraInputs & { + [KName in keyof T['inputs']]?: `Error: Input '${KName & + string}' is already defined in parent definition`; + }; + output?: Array; + config?: never; + configSchema?: TNewExtensionConfigSchema & { + [KName in keyof T['config']]?: `Error: Config key '${KName & + string}' is already defined in parent schema`; + }; + factory?( + originalFactory: < + TFactoryParamsReturn extends AnyParamsInput< + NonNullable + >, + >( + context?: Expand< + { + config?: T['config']; + inputs?: ResolvedInputValueOverrides>; + } & ([T['params']] extends [never] + ? {} + : { + params?: TFactoryParamsReturn extends ExtensionBlueprintDefineParams + ? TFactoryParamsReturn + : T['params'] extends ExtensionBlueprintDefineParams + ? 'Error: This blueprint uses advanced parameter types and requires you to pass parameters as using the following callback syntax: `originalFactory(defineParams => defineParams())`' + : Partial; + }) + >, + ) => ExtensionDataContainer>, + context: { + node: AppNode; + apis: ApiHolder; + config: T['config'] & { + [key in keyof TNewExtensionConfigSchema]: NonNullable< + TNewExtensionConfigSchema[key]['~standard']['types'] + >['output']; + }; + inputs: Expand>; + }, + ): Iterable; + } & ([T['params']] extends [never] + ? {} + : { + params?: TParamsInput extends ExtensionBlueprintDefineParams + ? TParamsInput + : T['params'] extends ExtensionBlueprintDefineParams + ? 'Error: This blueprint uses advanced parameter types and requires you to pass parameters as using the following callback syntax: `originalFactory(defineParams => defineParams())`' + : Partial; + }) + > & + VerifyExtensionFactoryOutput< + ExtensionDataRef extends UNewOutput + ? NonNullable + : UNewOutput, + UFactoryOutput + >, + ): OverridableExtensionDefinition<{ + kind: T['kind']; + name: T['name']; + output: ExtensionDataRef extends UNewOutput ? T['output'] : UNewOutput; + inputs: T['inputs'] & TExtraInputs; + config: T['config'] & { + [key in keyof TNewExtensionConfigSchema]: NonNullable< + TNewExtensionConfigSchema[key]['~standard']['types'] + >['output']; + }; + configInput: T['configInput'] & { + [key in keyof TNewExtensionConfigSchema]?: NonNullable< + TNewExtensionConfigSchema[key]['~standard']['types'] + >['input']; + }; + }>; + + /** + * @deprecated Use the `configSchema` option instead of `config.schema`. + */ override< TExtensionConfigSchema extends { [key in string]: ConfigFieldSchema; @@ -266,6 +379,7 @@ export interface OverridableExtensionDefinition< string}' is already defined in parent definition`; }; output?: Array; + configSchema?: never; config?: { schema: TExtensionConfigSchema & { [KName in keyof T['config']]?: `Error: Config key '${KName & @@ -402,6 +516,65 @@ function bindInputs( * * @public */ +export function createExtension< + UOutput extends ExtensionDataRef, + TInputs extends { [inputName in string]: ExtensionInput }, + UFactoryOutput extends ExtensionDataValue, + const TKind extends string | undefined = undefined, + const TName extends string | undefined = undefined, + UParentInputs extends ExtensionDataRef = ExtensionDataRef, + TNewConfigSchema extends { [key: string]: StandardSchemaV1 } = {}, +>( + options: { + kind?: TKind; + name?: TName; + attachTo: ExtensionDefinitionAttachTo & + VerifyExtensionAttachTo; + disabled?: boolean; + if?: FilterPredicate; + inputs?: TInputs; + output: Array; + config?: never; + configSchema?: TNewConfigSchema; + factory(context: { + node: AppNode; + apis: ApiHolder; + config: { + [key in keyof TNewConfigSchema]: NonNullable< + TNewConfigSchema[key]['~standard']['types'] + >['output']; + }; + inputs: Expand>; + }): Iterable; + } & VerifyExtensionFactoryOutput, +): OverridableExtensionDefinition<{ + config: { + [key in keyof TNewConfigSchema]: NonNullable< + TNewConfigSchema[key]['~standard']['types'] + >['output']; + }; + configInput: { + [key in keyof TNewConfigSchema]?: NonNullable< + TNewConfigSchema[key]['~standard']['types'] + >['input']; + }; + output: UOutput extends ExtensionDataRef< + infer IData, + infer IId, + infer IConfig + > + ? ExtensionDataRef + : never; + inputs: TInputs; + params: never; + kind: string | undefined extends TKind ? undefined : TKind; + name: string | undefined extends TName ? undefined : TName; +}>; + +/** + * @deprecated Use the top-level `configSchema` option instead of `config.schema`. + * @public + */ export function createExtension< UOutput extends ExtensionDataRef, TInputs extends { [inputName in string]: ExtensionInput }, @@ -411,15 +584,30 @@ export function createExtension< const TName extends string | undefined = undefined, UParentInputs extends ExtensionDataRef = ExtensionDataRef, >( - options: CreateExtensionOptions< - TKind, - TName, - UOutput, - TInputs, - TConfigSchema, - UFactoryOutput, - UParentInputs - >, + options: { + kind?: TKind; + name?: TName; + attachTo: ExtensionDefinitionAttachTo & + VerifyExtensionAttachTo; + disabled?: boolean; + if?: FilterPredicate; + inputs?: TInputs; + output: Array; + configSchema?: never; + config?: { + schema: TConfigSchema; + }; + factory(context: { + node: AppNode; + apis: ApiHolder; + config: { + [key in keyof TConfigSchema]: z.infer< + ReturnType<((...args: any[]) => any) & TConfigSchema[key]> + >; + }; + inputs: Expand>; + }): Iterable; + } & VerifyExtensionFactoryOutput, ): OverridableExtensionDefinition<{ config: string extends keyof TConfigSchema ? {} @@ -448,9 +636,18 @@ export function createExtension< params: never; kind: string | undefined extends TKind ? undefined : TKind; name: string | undefined extends TName ? undefined : TName; -}> { - const schemaDeclaration = options.config?.schema; - const configSchema = +}>; + +/** @internal */ +export function createExtension( + options: any, +): OverridableExtensionDefinition { + const schemaDeclaration = + options.configSchema ?? (options.config?.schema as any); + if (options.config?.schema) { + warnConfigSchemaPropDeprecation(); + } + const resolvedConfigSchema = schemaDeclaration && createSchemaFromZod(innerZ => innerZ.object( @@ -464,28 +661,7 @@ export function createExtension< ); return OpaqueExtensionDefinition.createInstance('v2', { - T: undefined as unknown as { - config: string extends keyof TConfigSchema - ? {} - : { - [key in keyof TConfigSchema]: z.infer< - ReturnType<((...args: any[]) => any) & TConfigSchema[key]> - >; - }; - configInput: string extends keyof TConfigSchema - ? {} - : z.input< - z.ZodObject<{ - [key in keyof TConfigSchema]: ReturnType< - ((...args: any[]) => any) & TConfigSchema[key] - >; - }> - >; - output: UOutput; - inputs: TInputs; - kind: string | undefined extends TKind ? undefined : TKind; - name: string | undefined extends TName ? undefined : TName; - }, + T: undefined as any, kind: options.kind, name: options.name, attachTo: options.attachTo, @@ -493,7 +669,7 @@ export function createExtension< if: options.if, inputs: bindInputs(options.inputs, options.kind, options.name), output: options.output, - configSchema, + configSchema: resolvedConfigSchema, factory: options.factory, toString() { const parts: string[] = []; @@ -540,7 +716,7 @@ export function createExtension< parts.push(`attachTo=${attachTo}`); return `ExtensionDefinition{${parts.join(',')}}`; }, - override(overrideOptions) { + override(overrideOptions: any) { 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', @@ -585,11 +761,16 @@ export function createExtension< output: (overrideOptions.output ?? options.output) as ExtensionDataRef[], config: - options.config || overrideOptions.config + options.config || + options.configSchema || + overrideOptions.config || + overrideOptions.configSchema ? { schema: { ...options.config?.schema, + ...options.configSchema, ...overrideOptions.config?.schema, + ...overrideOptions.configSchema, }, } : undefined, @@ -604,8 +785,8 @@ export function createExtension< }); } const parentResult = overrideOptions.factory( - (innerContext): ExtensionDataContainer => { - return createExtensionDataContainer( + (innerContext: any): ExtensionDataContainer => { + return createExtensionDataContainer( options.factory({ node, apis, diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx index 7f3ef5cb88..a76fafac89 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx @@ -248,6 +248,7 @@ describe('createExtensionBlueprint', () => { }, }); + // @ts-expect-error: overlapping config key 'text' TestExtensionBlueprint.makeWithOverrides({ name: 'my-extension', params: { @@ -255,9 +256,8 @@ describe('createExtensionBlueprint', () => { }, config: { schema: { - // @ts-expect-error - text: z => z.number(), - something: z => z.string(), + text: (z: any) => z.number(), + something: (z: any) => z.string(), }, }, }); diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts index 80c78ea24a..9be7a51f9f 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts @@ -29,6 +29,7 @@ import { import type { z } from 'zod/v3'; import { ExtensionInput } from './createExtensionInput'; import type { ConfigFieldSchema } from '../schema/createPortableSchema'; +import { type StandardSchemaV1 } from '@standard-schema/spec'; import { ExtensionDataRef, ExtensionDataValue } from './createExtensionDataRef'; import { createExtensionDataContainer } from '@internal/frontend'; import { @@ -111,6 +112,7 @@ export type CreateExtensionBlueprintOptions< UFactoryOutput extends ExtensionDataValue, TDataRefs extends { [name in string]: ExtensionDataRef }, UParentInputs extends ExtensionDataRef, + TNewConfigSchema extends { [key in string]: StandardSchemaV1 } = {}, > = { kind: TKind; attachTo: ExtensionDefinitionAttachTo & @@ -119,7 +121,14 @@ export type CreateExtensionBlueprintOptions< if?: FilterPredicate; inputs?: TInputs; output: Array; + configSchema?: TNewConfigSchema; + /** + * @deprecated Use {@link CreateExtensionBlueprintOptions.configSchema} instead. + */ config?: { + /** + * @deprecated Use {@link CreateExtensionBlueprintOptions.configSchema} instead. + */ schema: TConfigSchema; }; /** @@ -171,6 +180,10 @@ export type CreateExtensionBlueprintOptions< node: AppNode; apis: ApiHolder; config: { + [key in keyof TNewConfigSchema]: NonNullable< + TNewConfigSchema[key]['~standard']['types'] + >['output']; + } & { [key in keyof TConfigSchema]: z.infer< ReturnType<((...args: any[]) => any) & TConfigSchema[key]> >; @@ -248,6 +261,92 @@ export interface ExtensionBlueprint< * You must either pass `params` directly, or define a `factory` that can * optionally call the original factory with the same params. */ + makeWithOverrides< + TName extends string | undefined, + UFactoryOutput extends ExtensionDataValue, + UNewOutput extends ExtensionDataRef, + UParentInputs extends ExtensionDataRef, + TExtraInputs extends { [inputName in string]: ExtensionInput } = {}, + TNewExtensionConfigSchema extends { + [key in string]: StandardSchemaV1; + } = {}, + >(args: { + name?: TName; + attachTo?: ExtensionDefinitionAttachTo & + VerifyExtensionAttachTo< + ExtensionDataRef extends UNewOutput + ? NonNullable + : UNewOutput, + UParentInputs + >; + disabled?: boolean; + if?: FilterPredicate; + inputs?: TExtraInputs & { + [KName in keyof T['inputs']]?: `Error: Input '${KName & + string}' is already defined in parent definition`; + }; + output?: Array; + config?: never; + configSchema?: TNewExtensionConfigSchema & { + [KName in keyof T['config']]?: `Error: Config key '${KName & + string}' is already defined in parent schema`; + }; + factory( + originalFactory: < + TParamsInput extends AnyParamsInput>, + >( + params: TParamsInput extends ExtensionBlueprintDefineParams + ? TParamsInput + : T['params'] extends ExtensionBlueprintDefineParams + ? 'Error: This blueprint uses advanced parameter types and requires you to pass parameters as using the following callback syntax: `originalFactory(defineParams => defineParams())`' + : T['params'], + context?: { + config?: T['config']; + inputs?: ResolvedInputValueOverrides>; + }, + ) => ExtensionDataContainer>, + context: { + node: AppNode; + apis: ApiHolder; + config: T['config'] & { + [key in keyof TNewExtensionConfigSchema]: NonNullable< + TNewExtensionConfigSchema[key]['~standard']['types'] + >['output']; + }; + inputs: Expand>; + }, + ): Iterable & + VerifyExtensionFactoryOutput< + ExtensionDataRef extends UNewOutput + ? NonNullable + : UNewOutput, + UFactoryOutput + >; + }): OverridableExtensionDefinition<{ + config: Expand< + { + [key in keyof TNewExtensionConfigSchema]: NonNullable< + TNewExtensionConfigSchema[key]['~standard']['types'] + >['output']; + } & T['config'] + >; + configInput: Expand< + { + [key in keyof TNewExtensionConfigSchema]?: NonNullable< + TNewExtensionConfigSchema[key]['~standard']['types'] + >['input']; + } & T['configInput'] + >; + output: ExtensionDataRef extends UNewOutput ? T['output'] : UNewOutput; + inputs: Expand; + kind: T['kind']; + name: string | undefined extends TName ? undefined : TName; + params: T['params']; + }>; + + /** + * @deprecated Use the `configSchema` option instead of `config.schema`. + */ makeWithOverrides< TName extends string | undefined, TExtensionConfigSchema extends { @@ -273,6 +372,7 @@ export interface ExtensionBlueprint< string}' is already defined in parent definition`; }; output?: Array; + configSchema?: never; config?: { schema: TExtensionConfigSchema & { [KName in keyof T['config']]?: `Error: Config key '${KName & @@ -462,6 +562,74 @@ function unwrapParams( * ``` * @public */ +export function createExtensionBlueprint< + TParams extends object | ExtensionBlueprintDefineParams, + UOutput extends ExtensionDataRef, + TInputs extends { [inputName in string]: ExtensionInput }, + UFactoryOutput extends ExtensionDataValue, + TKind extends string, + UParentInputs extends ExtensionDataRef, + TDataRefs extends { [name in string]: ExtensionDataRef } = never, + TNewConfigSchema extends { [key in string]: StandardSchemaV1 } = {}, +>( + options: { + kind: TKind; + attachTo: ExtensionDefinitionAttachTo & + VerifyExtensionAttachTo; + disabled?: boolean; + if?: FilterPredicate; + inputs?: TInputs; + output: Array; + config?: never; + configSchema?: TNewConfigSchema; + defineParams?: TParams extends ExtensionBlueprintDefineParams + ? TParams + : 'The defineParams option must be a function if provided, see the docs for details'; + factory( + params: TParams extends ExtensionBlueprintDefineParams + ? ReturnType['T'] + : TParams, + context: { + node: AppNode; + apis: ApiHolder; + config: { + [key in keyof TNewConfigSchema]: NonNullable< + TNewConfigSchema[key]['~standard']['types'] + >['output']; + }; + inputs: Expand>; + }, + ): Iterable; + dataRefs?: TDataRefs; + } & VerifyExtensionFactoryOutput, +): ExtensionBlueprint<{ + kind: TKind; + params: TParams; + output: UOutput extends ExtensionDataRef< + infer IData, + infer IId, + infer IConfig + > + ? ExtensionDataRef + : never; + inputs: string extends keyof TInputs ? {} : TInputs; + config: { + [key in keyof TNewConfigSchema]: NonNullable< + TNewConfigSchema[key]['~standard']['types'] + >['output']; + }; + configInput: { + [key in keyof TNewConfigSchema]?: NonNullable< + TNewConfigSchema[key]['~standard']['types'] + >['input']; + }; + dataRefs: TDataRefs; +}>; + +/** + * @deprecated Use the top-level `configSchema` option instead of `config.schema`. + * @public + */ export function createExtensionBlueprint< TParams extends object | ExtensionBlueprintDefineParams, UOutput extends ExtensionDataRef, @@ -472,20 +640,41 @@ export function createExtensionBlueprint< UParentInputs extends ExtensionDataRef, TDataRefs extends { [name in string]: ExtensionDataRef } = never, >( - options: CreateExtensionBlueprintOptions< - TKind, - TParams, - UOutput, - TInputs, - TConfigSchema, - UFactoryOutput, - TDataRefs, - UParentInputs - >, + options: { + kind: TKind; + attachTo: ExtensionDefinitionAttachTo & + VerifyExtensionAttachTo; + disabled?: boolean; + if?: FilterPredicate; + inputs?: TInputs; + output: Array; + configSchema?: never; + config?: { + schema: TConfigSchema; + }; + defineParams?: TParams extends ExtensionBlueprintDefineParams + ? TParams + : 'The defineParams option must be a function if provided, see the docs for details'; + factory( + params: TParams extends ExtensionBlueprintDefineParams + ? ReturnType['T'] + : TParams, + context: { + node: AppNode; + apis: ApiHolder; + config: { + [key in keyof TConfigSchema]: z.infer< + ReturnType<((...args: any[]) => any) & TConfigSchema[key]> + >; + }; + inputs: Expand>; + }, + ): Iterable; + dataRefs?: TDataRefs; + } & VerifyExtensionFactoryOutput, ): ExtensionBlueprint<{ kind: TKind; params: TParams; - // This inference and remapping back to ExtensionDataRef eliminates any occurrences ConfigurationExtensionDataRef output: UOutput extends ExtensionDataRef< infer IData, infer IId, @@ -511,7 +700,10 @@ export function createExtensionBlueprint< }> >; dataRefs: TDataRefs; -}> { +}>; + +/** @internal */ +export function createExtensionBlueprint(options: any): any { const defineParams = options.defineParams as | ExtensionBlueprintDefineParams | undefined; @@ -529,14 +721,15 @@ export function createExtensionBlueprint< inputs: options.inputs, output: options.output as ExtensionDataRef[], config: options.config, + configSchema: options.configSchema as any, factory: ctx => options.factory( unwrapParams(args.params, ctx, defineParams, options.kind), - ctx, + ctx as any, ) as Iterable>, }) as OverridableExtensionDefinition; }, - makeWithOverrides(args) { + makeWithOverrides(args: any) { return createExtension({ kind: options.kind, name: args.name, @@ -555,19 +748,18 @@ export function createExtensionBlueprint< }, } : undefined, + configSchema: + options.configSchema || args.configSchema + ? { + ...options.configSchema, + ...args.configSchema, + } + : (undefined as any), factory: ctx => { const { node, config, inputs, apis } = ctx; return args.factory( - (innerParams, innerContext) => { - return createExtensionDataContainer< - UOutput extends ExtensionDataRef< - infer IData, - infer IId, - infer IConfig - > - ? ExtensionDataRef - : never - >( + (innerParams: any, innerContext: any) => { + return createExtensionDataContainer( options.factory( unwrapParams(innerParams, ctx, defineParams, options.kind), { @@ -595,27 +787,5 @@ export function createExtensionBlueprint< }, }) as OverridableExtensionDefinition; }, - } as ExtensionBlueprint<{ - kind: TKind; - params: TParams; - output: any; - inputs: string extends keyof TInputs ? {} : TInputs; - config: string extends keyof TConfigSchema - ? {} - : { - [key in keyof TConfigSchema]: z.infer< - ReturnType<((...args: any[]) => any) & TConfigSchema[key]> - >; - }; - configInput: string extends keyof TConfigSchema - ? {} - : z.input< - z.ZodObject<{ - [key in keyof TConfigSchema]: ReturnType< - ((...args: any[]) => any) & TConfigSchema[key] - >; - }> - >; - dataRefs: TDataRefs; - }>; + } as ExtensionBlueprint; }