[createInterfaceApi] typescript allows mutations to have array params (#36581)

components/ai_chat/resources/page/api/ai_chat_api.ts had trouble with typescript intellisense due to the process file mutation functions now having an array as their first param. Apparently due to contravariance checks
This commit is contained in:
Pete Miller
2026-05-20 14:17:36 -07:00
committed by GitHub
parent edb9d6dcac
commit 5cc0360a83
2 changed files with 51 additions and 1 deletions
@@ -476,6 +476,49 @@ describe('createInterfaceApi', () => {
expect(mutationFn).toHaveBeenCalledTimes(1)
})
it('accepts mutations defined via endpointsFor under strict function types', async () => {
// Regression: previously, the `RawEndpoints` index signature used
// `EndpointDef<any[], any>`, which made tuple-typed mutations like
// `MutationEndpointDefinition<[Uint8Array, string], …>` fail assignability
// under `strictFunctionTypes` because of contravariant `onMutate(variables: P)`
// callbacks. The signature now uses `EndpointDef<any, any>` to side-step
// that.
type UploadedFile = { name: string }
type MyInterface = {
processImageFile: (
fileData: number[],
filename: string,
) => Promise<{ processedFile: UploadedFile | null }>
processPdfFile: (
fileData: number[],
filename: string,
) => Promise<{ processedFile: UploadedFile | null }>
}
const impl: MyInterface = {
processImageFile: (_d, n) =>
Promise.resolve({ processedFile: { name: n } }),
processPdfFile: (_d, n) =>
Promise.resolve({ processedFile: { name: n } }),
}
const api = createInterfaceApi({
actions: {},
endpoints: {
...endpointsFor(impl, {
processImageFile: {
mutationResponse: (result) => result.processedFile,
},
processPdfFile: {
mutationResponse: (result) => result.processedFile,
},
}),
},
})
const result = await api.processImageFile([[1, 2, 3], 'image.png'])
expect(result).toEqual({ name: 'image.png' })
})
it('can create void mutations that have a parameter', async () => {
const mutationFn = jest.fn((hi: string) => {
return Promise.resolve()
@@ -180,7 +180,14 @@ export function createInterfaceApi<
string,
Function | Record<string, Function>
>,
const RawEndpoints extends Record<string, EndpointDef<readonly any[], any>>,
// Use `EndpointDef<any, any>` (not `any[]`) so that specific endpoint
// definitions remain assignable under `strictFunctionTypes`. With `any[]`,
// mutation callbacks like `onMutate: (variables: [a, b]) => …` fail
// contravariance checks against `(variables: any[]) => …` because tuples
// are narrower than `any[]`. Switching to `any` makes the parameter slot
// bivariant and side-steps the variance issue without losing inference on
// the concrete `RawEndpoints` type below.
const RawEndpoints extends Record<string, EndpointDef<any, any>>,
EventDefinitions extends Record<string, EventDef<any[], any>> = {},
>(config: {
/**