frontend-plugin-api: add configSchema option and deprecate config.schema

Introduce a new top-level `configSchema` option for `createExtension`,
`createExtensionBlueprint`, `override`, and `makeWithOverrides` that
accepts Standard Schema values directly. The old `config.schema` form
is deprecated via function/method overloads so that the entire call
site gets editor strikethrough when matched.

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
Made-with: Cursor
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
Made-with: Cursor
This commit is contained in:
Patrik Oldsberg
2026-04-10 08:48:25 +02:00
parent 787200224e
commit 4c7905d22c
7 changed files with 907 additions and 118 deletions
+2 -2
View File
@@ -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;
};
@@ -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<UParentInputs> &
@@ -194,6 +198,10 @@ export interface ExtensionBlueprint<
string}' is already defined in parent definition`;
};
output?: Array<UNewOutput>;
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<NonNullable<T['params']>>,
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<UNewOutput>;
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<T extends ZodType>(factory: (zImpl: typeof z) => T): T;
// (No @packageDocumentation comment for this package)
```
+397 -19
View File
@@ -469,7 +469,69 @@ export function createApiRef<T>(): {
};
};
// 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<any, any>,
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<UParentInputs> &
VerifyExtensionAttachTo<UOutput, UParentInputs>;
disabled?: boolean;
if?: FilterPredicate;
inputs?: TInputs;
output: Array<UOutput>;
config?: never;
configSchema?: TNewConfigSchema;
factory(context: {
node: AppNode;
apis: ApiHolder;
config: {
[key in keyof TNewConfigSchema]: NonNullable<
TNewConfigSchema[key]['~standard']['types']
>['output'];
};
inputs: Expand<ResolvedExtensionInputs<TInputs>>;
}): Iterable<UFactoryOutput>;
} & VerifyExtensionFactoryOutput<UOutput, UFactoryOutput>,
): 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<IData, IId, IConfig>
: 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<UParentInputs> &
VerifyExtensionAttachTo<UOutput, UParentInputs>;
disabled?: boolean;
if?: FilterPredicate;
inputs?: TInputs;
output: Array<UOutput>;
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<ResolvedExtensionInputs<TInputs>>;
}): Iterable<UFactoryOutput>;
} & VerifyExtensionFactoryOutput<UOutput, UFactoryOutput>,
): 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<any, any>,
TKind extends string,
UParentInputs extends ExtensionDataRef,
TDataRefs extends {
[name in string]: ExtensionDataRef;
} = never,
TNewConfigSchema extends {
[key in string]: StandardSchemaV1;
} = {},
>(
options: {
kind: TKind;
attachTo: ExtensionDefinitionAttachTo<UParentInputs> &
VerifyExtensionAttachTo<UOutput, UParentInputs>;
disabled?: boolean;
if?: FilterPredicate;
inputs?: TInputs;
output: Array<UOutput>;
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<TParams>['T']
: TParams,
context: {
node: AppNode;
apis: ApiHolder;
config: {
[key in keyof TNewConfigSchema]: NonNullable<
TNewConfigSchema[key]['~standard']['types']
>['output'];
};
inputs: Expand<ResolvedExtensionInputs<TInputs>>;
},
): Iterable<UFactoryOutput>;
dataRefs?: TDataRefs;
} & VerifyExtensionFactoryOutput<UOutput, UFactoryOutput>,
): ExtensionBlueprint<{
kind: TKind;
params: TParams;
output: UOutput extends ExtensionDataRef<
infer IData,
infer IId,
infer IConfig
>
? ExtensionDataRef<IData, IId, IConfig>
: 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<UParentInputs> &
VerifyExtensionAttachTo<UOutput, UParentInputs>;
disabled?: boolean;
if?: FilterPredicate;
inputs?: TInputs;
output: Array<UOutput>;
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<TParams>['T']
: TParams,
context: {
node: AppNode;
apis: ApiHolder;
config: {
[key in keyof TConfigSchema]: z.infer<
ReturnType<((...args: any[]) => any) & TConfigSchema[key]>
>;
};
inputs: Expand<ResolvedExtensionInputs<TInputs>>;
},
): Iterable<UFactoryOutput>;
dataRefs?: TDataRefs;
} & VerifyExtensionFactoryOutput<UOutput, UFactoryOutput>,
): 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<UParentInputs> &
@@ -603,6 +778,7 @@ export type CreateExtensionBlueprintOptions<
if?: FilterPredicate;
inputs?: TInputs;
output: Array<UOutput>;
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<any, any>,
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<UOutput>;
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<any, any>,
UNewOutput extends ExtensionDataRef,
UParentInputs extends ExtensionDataRef,
TExtraInputs extends {
[inputName in string]: ExtensionInput;
} = {},
TNewExtensionConfigSchema extends {
[key in string]: StandardSchemaV1;
} = {},
>(args: {
name?: TName;
attachTo?: ExtensionDefinitionAttachTo<UParentInputs> &
VerifyExtensionAttachTo<
ExtensionDataRef extends UNewOutput
? NonNullable<T['output']>
: 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<UNewOutput>;
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<NonNullable<T['params']>>,
>(
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(<params>))`'
: T['params'],
context?: {
config?: T['config'];
inputs?: ResolvedInputValueOverrides<NonNullable<T['inputs']>>;
},
) => ExtensionDataContainer<NonNullable<T['output']>>,
context: {
node: AppNode;
apis: ApiHolder;
config: T['config'] & {
[key in keyof TNewExtensionConfigSchema]: NonNullable<
TNewExtensionConfigSchema[key]['~standard']['types']
>['output'];
};
inputs: Expand<ResolvedExtensionInputs<T['inputs'] & TExtraInputs>>;
},
): Iterable<UFactoryOutput> &
VerifyExtensionFactoryOutput<
ExtensionDataRef extends UNewOutput
? NonNullable<T['output']>
: 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<T['inputs'] & TExtraInputs>;
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<UNewOutput>;
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<any, any>,
UNewOutput extends ExtensionDataRef,
TExtraInputs extends {
[inputName in string]: ExtensionInput;
},
TParamsInput extends AnyParamsInput<NonNullable<T['params']>>,
UParentInputs extends ExtensionDataRef,
TNewExtensionConfigSchema extends {
[key in string]: StandardSchemaV1;
} = {},
>(
args: Expand<
{
attachTo?: ExtensionDefinitionAttachTo<UParentInputs> &
VerifyExtensionAttachTo<
ExtensionDataRef extends UNewOutput
? NonNullable<T['output']>
: 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<UNewOutput>;
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<T['params']>
>,
>(
context?: Expand<
{
config?: T['config'];
inputs?: ResolvedInputValueOverrides<NonNullable<T['inputs']>>;
} & ([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(<params>))`'
: Partial<T['params']>;
})
>,
) => ExtensionDataContainer<NonNullable<T['output']>>,
context: {
node: AppNode;
apis: ApiHolder;
config: T['config'] & {
[key in keyof TNewExtensionConfigSchema]: NonNullable<
TNewExtensionConfigSchema[key]['~standard']['types']
>['output'];
};
inputs: Expand<ResolvedExtensionInputs<T['inputs'] & TExtraInputs>>;
},
): Iterable<UFactoryOutput>;
} & ([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(<params>))`'
: Partial<T['params']>;
})
> &
VerifyExtensionFactoryOutput<
ExtensionDataRef extends UNewOutput
? NonNullable<T['output']>
: 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<UNewOutput>;
configSchema?: never;
config?: {
schema: TExtensionConfigSchema & {
[KName in keyof T['config']]?: `Error: Config key '${KName &
@@ -2519,4 +2892,9 @@ export function withApis<T extends {}>(
(props: PropsWithChildren<Omit<TProps, keyof T>>): 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
```
@@ -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.',
);
}
}
@@ -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<any, any>,
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<UOutput>;
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<any, any>,
UNewOutput extends ExtensionDataRef,
TExtraInputs extends { [inputName in string]: ExtensionInput },
TParamsInput extends AnyParamsInput<NonNullable<T['params']>>,
UParentInputs extends ExtensionDataRef,
TNewExtensionConfigSchema extends {
[key in string]: StandardSchemaV1;
} = {},
>(
args: Expand<
{
attachTo?: ExtensionDefinitionAttachTo<UParentInputs> &
VerifyExtensionAttachTo<
ExtensionDataRef extends UNewOutput
? NonNullable<T['output']>
: 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<UNewOutput>;
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<T['params']>
>,
>(
context?: Expand<
{
config?: T['config'];
inputs?: ResolvedInputValueOverrides<NonNullable<T['inputs']>>;
} & ([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(<params>))`'
: Partial<T['params']>;
})
>,
) => ExtensionDataContainer<NonNullable<T['output']>>,
context: {
node: AppNode;
apis: ApiHolder;
config: T['config'] & {
[key in keyof TNewExtensionConfigSchema]: NonNullable<
TNewExtensionConfigSchema[key]['~standard']['types']
>['output'];
};
inputs: Expand<ResolvedExtensionInputs<T['inputs'] & TExtraInputs>>;
},
): Iterable<UFactoryOutput>;
} & ([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(<params>))`'
: Partial<T['params']>;
})
> &
VerifyExtensionFactoryOutput<
ExtensionDataRef extends UNewOutput
? NonNullable<T['output']>
: 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<UNewOutput>;
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<any, any>,
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<UParentInputs> &
VerifyExtensionAttachTo<UOutput, UParentInputs>;
disabled?: boolean;
if?: FilterPredicate;
inputs?: TInputs;
output: Array<UOutput>;
config?: never;
configSchema?: TNewConfigSchema;
factory(context: {
node: AppNode;
apis: ApiHolder;
config: {
[key in keyof TNewConfigSchema]: NonNullable<
TNewConfigSchema[key]['~standard']['types']
>['output'];
};
inputs: Expand<ResolvedExtensionInputs<TInputs>>;
}): Iterable<UFactoryOutput>;
} & VerifyExtensionFactoryOutput<UOutput, UFactoryOutput>,
): 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<IData, IId, IConfig>
: 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<UParentInputs> &
VerifyExtensionAttachTo<UOutput, UParentInputs>;
disabled?: boolean;
if?: FilterPredicate;
inputs?: TInputs;
output: Array<UOutput>;
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<ResolvedExtensionInputs<TInputs>>;
}): Iterable<UFactoryOutput>;
} & VerifyExtensionFactoryOutput<UOutput, UFactoryOutput>,
): 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<any> {
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<UOutput> => {
return createExtensionDataContainer<UOutput>(
(innerContext: any): ExtensionDataContainer<any> => {
return createExtensionDataContainer<any>(
options.factory({
node,
apis,
@@ -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(),
},
},
});
@@ -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<any, any>,
TDataRefs extends { [name in string]: ExtensionDataRef },
UParentInputs extends ExtensionDataRef,
TNewConfigSchema extends { [key in string]: StandardSchemaV1 } = {},
> = {
kind: TKind;
attachTo: ExtensionDefinitionAttachTo<UParentInputs> &
@@ -119,7 +121,14 @@ export type CreateExtensionBlueprintOptions<
if?: FilterPredicate;
inputs?: TInputs;
output: Array<UOutput>;
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<any, any>,
UNewOutput extends ExtensionDataRef,
UParentInputs extends ExtensionDataRef,
TExtraInputs extends { [inputName in string]: ExtensionInput } = {},
TNewExtensionConfigSchema extends {
[key in string]: StandardSchemaV1;
} = {},
>(args: {
name?: TName;
attachTo?: ExtensionDefinitionAttachTo<UParentInputs> &
VerifyExtensionAttachTo<
ExtensionDataRef extends UNewOutput
? NonNullable<T['output']>
: 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<UNewOutput>;
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<NonNullable<T['params']>>,
>(
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(<params>))`'
: T['params'],
context?: {
config?: T['config'];
inputs?: ResolvedInputValueOverrides<NonNullable<T['inputs']>>;
},
) => ExtensionDataContainer<NonNullable<T['output']>>,
context: {
node: AppNode;
apis: ApiHolder;
config: T['config'] & {
[key in keyof TNewExtensionConfigSchema]: NonNullable<
TNewExtensionConfigSchema[key]['~standard']['types']
>['output'];
};
inputs: Expand<ResolvedExtensionInputs<T['inputs'] & TExtraInputs>>;
},
): Iterable<UFactoryOutput> &
VerifyExtensionFactoryOutput<
ExtensionDataRef extends UNewOutput
? NonNullable<T['output']>
: 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<T['inputs'] & TExtraInputs>;
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<UNewOutput>;
configSchema?: never;
config?: {
schema: TExtensionConfigSchema & {
[KName in keyof T['config']]?: `Error: Config key '${KName &
@@ -462,6 +562,74 @@ function unwrapParams<TParams extends object>(
* ```
* @public
*/
export function createExtensionBlueprint<
TParams extends object | ExtensionBlueprintDefineParams,
UOutput extends ExtensionDataRef,
TInputs extends { [inputName in string]: ExtensionInput },
UFactoryOutput extends ExtensionDataValue<any, any>,
TKind extends string,
UParentInputs extends ExtensionDataRef,
TDataRefs extends { [name in string]: ExtensionDataRef } = never,
TNewConfigSchema extends { [key in string]: StandardSchemaV1 } = {},
>(
options: {
kind: TKind;
attachTo: ExtensionDefinitionAttachTo<UParentInputs> &
VerifyExtensionAttachTo<UOutput, UParentInputs>;
disabled?: boolean;
if?: FilterPredicate;
inputs?: TInputs;
output: Array<UOutput>;
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<TParams>['T']
: TParams,
context: {
node: AppNode;
apis: ApiHolder;
config: {
[key in keyof TNewConfigSchema]: NonNullable<
TNewConfigSchema[key]['~standard']['types']
>['output'];
};
inputs: Expand<ResolvedExtensionInputs<TInputs>>;
},
): Iterable<UFactoryOutput>;
dataRefs?: TDataRefs;
} & VerifyExtensionFactoryOutput<UOutput, UFactoryOutput>,
): ExtensionBlueprint<{
kind: TKind;
params: TParams;
output: UOutput extends ExtensionDataRef<
infer IData,
infer IId,
infer IConfig
>
? ExtensionDataRef<IData, IId, IConfig>
: 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<UParentInputs> &
VerifyExtensionAttachTo<UOutput, UParentInputs>;
disabled?: boolean;
if?: FilterPredicate;
inputs?: TInputs;
output: Array<UOutput>;
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<TParams>['T']
: TParams,
context: {
node: AppNode;
apis: ApiHolder;
config: {
[key in keyof TConfigSchema]: z.infer<
ReturnType<((...args: any[]) => any) & TConfigSchema[key]>
>;
};
inputs: Expand<ResolvedExtensionInputs<TInputs>>;
},
): Iterable<UFactoryOutput>;
dataRefs?: TDataRefs;
} & VerifyExtensionFactoryOutput<UOutput, UFactoryOutput>,
): 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<ExtensionDataValue<any, any>>,
}) 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<IData, IId, IConfig>
: never
>(
(innerParams: any, innerContext: any) => {
return createExtensionDataContainer<any>(
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<any>;
}