UNPKG

@data-client/rest

Version:

Quickly define typed REST resources and endpoints

850 lines (803 loc) 28.5 kB
import type { Denormalize, EndpointExtraOptions, EndpointInstanceInterface, Schema, FetchFunction, ResolveType, } from '@data-client/endpoint'; import type { ExtractCollection } from './extractCollection.js'; import { OptionsToBodyArgument, OptionsToFunction, } from './OptionsToFunction.js'; import { PathArgs, SoftPathArgs } from './pathTypes.js'; import { EndpointUpdateFunction } from './RestEndpointTypeHelp.js'; export type ContentType = 'json' | 'blob' | 'text' | 'arrayBuffer' | 'stream'; interface ContentTypeMap { blob: Blob; text: string; arrayBuffer: ArrayBuffer; stream: ReadableStream<Uint8Array>; json: any; } type ContentReturnType<C extends ContentType> = ContentTypeMap[C]; type ContentSchemaGuard<O> = O extends { content: 'blob' | 'text' | 'arrayBuffer' | 'stream' } ? { schema?: undefined } : {}; export interface RestInstanceBase< F extends FetchFunction = FetchFunction, S extends Schema | undefined = any, M extends boolean | undefined = boolean | undefined, O extends { path: string; body?: any; searchParams?: any; method?: string; } = { path: string }, > extends EndpointInstanceInterface<F, S, M> { /** @see https://dataclient.io/rest/api/RestEndpoint#body */ readonly body?: 'body' extends keyof O ? O['body'] : any; /** @see https://dataclient.io/rest/api/RestEndpoint#searchParams */ readonly searchParams?: 'searchParams' extends keyof O ? O['searchParams'] : // unknown is identity with '&' type operator unknown; /** Pattern to construct url based on Url Parameters * @see https://dataclient.io/rest/api/RestEndpoint#path */ readonly path: O['path']; /** Prepended to all urls * @see https://dataclient.io/rest/api/RestEndpoint#urlPrefix */ readonly urlPrefix: string; readonly requestInit: RequestInit; /** HTTP request method * @see https://dataclient.io/rest/api/RestEndpoint#method */ readonly method: (O & { method: string })['method']; readonly signal: AbortSignal | undefined; /** @see https://dataclient.io/rest/api/RestEndpoint#paginationField */ readonly paginationField?: string; /** @see https://dataclient.io/rest/api/RestEndpoint#content */ readonly content?: ContentType; /* fetch lifecycles */ /* before-fetch */ /** Builds the URL to fetch * @see https://dataclient.io/rest/api/RestEndpoint#url */ url(...args: Parameters<F>): string; /** Encode the searchParams component of the url * @see https://dataclient.io/rest/api/RestEndpoint#searchToString */ searchToString(searchParams: Record<string, any>): string; /** Prepares RequestInit used in fetch. This is sent to fetchResponse() * @see https://dataclient.io/rest/api/RestEndpoint#getRequestInit */ getRequestInit( this: any, body?: RequestInit['body'] | Record<string, unknown>, ): Promise<RequestInit> | RequestInit; /** Called by getRequestInit to determine HTTP Headers * @see https://dataclient.io/rest/api/RestEndpoint#getHeaders */ getHeaders(headers: HeadersInit): Promise<HeadersInit> | HeadersInit; /* after-fetch */ /** Performs the fetch call * @see https://dataclient.io/rest/api/RestEndpoint#fetchResponse */ fetchResponse(input: RequestInfo, init: RequestInit): Promise<Response>; /** Takes the Response and parses via .text() or .json() * @see https://dataclient.io/rest/api/RestEndpoint#parseResponse */ parseResponse(response: Response): Promise<any>; /** Perform any transforms with the parsed result. * @see https://dataclient.io/rest/api/RestEndpoint#process */ process(value: any, ...args: Parameters<F>): ResolveType<F>; /* utilities */ /** Returns true if the provided (fetch) key matches this endpoint. * @see https://dataclient.io/rest/api/RestEndpoint#testKey */ testKey(key: string): boolean; /* extenders */ // TODO: figure out better way than wrapping whole options in Readonly<> + making O extend from {} // this is just a hack to handle when no members of PartialRestGenerics are present // Note: Using overloading (like paginated did) struggles because typescript does not have a clear way of distinguishing one // should be used from the other (due to same problem with every member being partial) /** Creates a child endpoint that inherits from this while overriding provided `options`. * @see https://dataclient.io/rest/api/RestEndpoint#extend */ extend< E extends RestInstanceBase, ExtendOptions extends PartialRestGenerics | {}, >( this: E, options: Readonly< RestEndpointExtendOptions<ExtendOptions, E, F> & ExtendOptions > & ContentSchemaGuard<ExtendOptions>, ): RestExtendedEndpoint<ExtendOptions, E>; } export interface RestInstance< F extends FetchFunction = FetchFunction, S extends Schema | undefined = any, M extends boolean | undefined = boolean | undefined, O extends { path: string; body?: any; searchParams?: any; method?: string; paginationField?: string; } = { path: string }, > extends RestInstanceBase<F, S, M, O> { /** Creates an Endpoint to append the next page extending a list for pagination * @see https://dataclient.io/rest/api/RestEndpoint#paginated */ paginated< E extends RestInstanceBase<FetchFunction, any, undefined>, A extends any[], >( this: E, removeCursor: (...args: A) => readonly [...Parameters<E>], ): PaginationEndpoint<E, A>; paginated< E extends RestInstanceBase<FetchFunction, any, undefined>, C extends string, >( this: E, cursorField: C, ): PaginationFieldEndpoint<E, C>; /** Concatinate the next page of results (GET) * @see https://dataclient.io/rest/api/RestEndpoint#getPage */ getPage: 'paginationField' extends keyof O ? O['paginationField'] extends string ? PaginationFieldEndpoint< F & { schema: S; sideEffect: M } & O, O['paginationField'] > : undefined : undefined; /** Create a new item (POST) and `push` to the end * @see https://dataclient.io/rest/api/RestEndpoint#push */ push: AddEndpoint< F, ExtractCollection<S>['push'], Omit<O, 'body' | 'method'> & { body: | OptionsToAdderBodyArgument<O, ExtractCollection<S>['push']> | OptionsToAdderBodyArgument<O, ExtractCollection<S>['push']>[] | FormData; } >; /** Create a new item (POST) and `unshift` to the beginning * @see https://dataclient.io/rest/api/RestEndpoint#unshift */ unshift: AddEndpoint< F, ExtractCollection<S>['unshift'], Omit<O, 'body' | 'method'> & { body: | OptionsToAdderBodyArgument<O, ExtractCollection<S>['unshift']> | OptionsToAdderBodyArgument<O, ExtractCollection<S>['unshift']>[] | FormData; } >; /** Create new item(s) (POST) and `Object.assign` merge * @see https://dataclient.io/rest/api/RestEndpoint#assign */ assign: AddEndpoint< F, ExtractCollection<S>, Omit<O, 'body' | 'method'> & { body: | Record< string, OptionsToAdderBodyArgument<O, ExtractCollection<S>['assign']> > | FormData; } >; /** Remove item(s) (PATCH) from collection * @see https://dataclient.io/rest/api/RestEndpoint#remove */ remove: RemoveEndpoint< F, ExtractCollection<S>['remove'], Omit<O, 'body' | 'method'> & { body: | Partial<OptionsToAdderBodyArgument<O, ExtractCollection<S>['remove']>> | Partial< OptionsToAdderBodyArgument<O, ExtractCollection<S>['remove']> >[] | FormData; } >; /** Move item between collections (PATCH) - removes from old, adds to new * @see https://dataclient.io/rest/api/RestEndpoint#move */ move: MoveEndpoint< F, ExtractCollection<S>['move'], { path: 'movePath' extends keyof O ? O['movePath'] & string : O['path']; body: | Partial<OptionsToAdderBodyArgument<O, ExtractCollection<S>['move']>> | FormData; } >; } export type RestEndpointExtendOptions< O extends PartialRestGenerics, E extends { body?: any; path?: string; schema?: Schema; method?: string }, F extends FetchFunction, > = RestEndpointOptions< OptionsToFunction<O, E, F>, 'schema' extends keyof O ? Extract<O['schema'], Schema | undefined> : E['schema'] > & Partial< Omit< E, KeyofRestEndpoint | keyof PartialRestGenerics | keyof RestEndpointOptions > >; type OptionsToRestEndpoint< O extends PartialRestGenerics, E extends RestInstanceBase & { body?: any; paginationField?: string }, F extends FetchFunction, > = 'path' extends keyof O ? RestType< 'searchParams' extends keyof O ? [O['searchParams']] extends [undefined] ? PathArgs<Exclude<O['path'], undefined>> : O['searchParams'] & PathArgs<Exclude<O['path'], undefined>> : PathArgs<Exclude<O['path'], undefined>>, OptionsToBodyArgument< 'body' extends keyof O ? O : E, 'method' extends keyof O ? O['method'] : E['method'] >, 'schema' extends keyof O ? O['schema'] : E['schema'], 'sideEffect' extends keyof O ? Extract<O['sideEffect'], boolean | undefined> : 'method' extends keyof O ? MethodToSide<O['method']> : E['sideEffect'], O['process'] extends {} ? ReturnType<O['process']> : 'content' extends keyof O ? ContentReturnType<O['content'] & ContentType> : ResolveType<F>, { path: Exclude<O['path'], undefined>; body: 'body' extends keyof O ? O['body'] : E['body']; searchParams: 'searchParams' extends keyof O ? O['searchParams'] : E['searchParams']; method: 'method' extends keyof O ? O['method'] : E['method']; paginationField: 'paginationField' extends keyof O ? O['paginationField'] : E['paginationField']; } > : 'body' extends keyof O ? RestType< 'searchParams' extends keyof O ? [O['searchParams']] extends [undefined] ? PathArgs<Exclude<O['path'], undefined>> : O['searchParams'] & PathArgs<Exclude<E['path'], undefined>> : PathArgs<Exclude<E['path'], undefined>>, OptionsToBodyArgument< O, 'method' extends keyof O ? O['method'] : E['method'] >, 'schema' extends keyof O ? O['schema'] : E['schema'], 'sideEffect' extends keyof O ? Extract<O['sideEffect'], boolean | undefined> : 'method' extends keyof O ? MethodToSide<O['method']> : E['sideEffect'], O['process'] extends {} ? ReturnType<O['process']> : 'content' extends keyof O ? ContentReturnType<O['content'] & ContentType> : ResolveType<F>, { path: E['path']; body: O['body']; searchParams: 'searchParams' extends keyof O ? O['searchParams'] : E['searchParams']; method: 'method' extends keyof O ? O['method'] : E['method']; paginationField: 'paginationField' extends keyof O ? O['paginationField'] : Extract<E['paginationField'], string>; } > : 'searchParams' extends keyof O ? RestType< [O['searchParams']] extends [undefined] ? PathArgs<Exclude<O['path'], undefined>> : O['searchParams'] & PathArgs<Exclude<E['path'], undefined>>, OptionsToBodyArgument< E, 'method' extends keyof O ? O['method'] : E['method'] >, 'schema' extends keyof O ? O['schema'] : E['schema'], 'sideEffect' extends keyof O ? Extract<O['sideEffect'], boolean | undefined> : 'method' extends keyof O ? MethodToSide<O['method']> : E['sideEffect'], O['process'] extends {} ? ReturnType<O['process']> : 'content' extends keyof O ? ContentReturnType<O['content'] & ContentType> : ResolveType<F>, { path: E['path']; body: E['body']; searchParams: O['searchParams']; method: 'method' extends keyof O ? O['method'] : E['method']; paginationField: 'paginationField' extends keyof O ? O['paginationField'] : Extract<E['paginationField'], string>; } > : RestInstance< F, 'schema' extends keyof O ? O['schema'] : E['schema'], 'sideEffect' extends keyof O ? Extract<O['sideEffect'], boolean | undefined> : 'method' extends keyof O ? MethodToSide<O['method']> : E['sideEffect'], { path: 'path' extends keyof O ? Exclude<O['path'], undefined> : E['path']; body: 'body' extends keyof O ? O['body'] : E['body']; searchParams: 'searchParams' extends keyof O ? O['searchParams'] : E['searchParams']; method: 'method' extends keyof O ? O['method'] : E['method']; paginationField: 'paginationField' extends keyof O ? O['paginationField'] : E['paginationField']; } >; export type RestExtendedEndpoint< O extends PartialRestGenerics, E extends RestInstanceBase & { getPage?: unknown }, > = OptionsToRestEndpoint< O, E & (E extends { getPage: { paginationField: string } } ? { paginationField: E['getPage']['paginationField'] } : unknown), RestInstance< (...args: Parameters<E>) => O['process'] extends {} ? Promise<ReturnType<O['process']>> : 'content' extends keyof O ? Promise<ContentReturnType<O['content'] & ContentType>> : ReturnType<E>, 'schema' extends keyof O ? O['schema'] : E['schema'], 'sideEffect' extends keyof O ? Extract<O['sideEffect'], boolean | undefined> : 'method' extends keyof O ? MethodToSide<O['method']> : E['sideEffect'] > > & Omit<O, KeyofRestEndpoint> & Omit<E, KeyofRestEndpoint | keyof O>; export interface PartialRestGenerics { /** @see https://dataclient.io/rest/api/RestEndpoint#path */ readonly path?: string; /** @see https://dataclient.io/rest/api/RestEndpoint#schema */ readonly schema?: Schema | undefined; /** @see https://dataclient.io/rest/api/RestEndpoint#method */ readonly method?: string; /** Only used for types */ /** @see https://dataclient.io/rest/api/RestEndpoint#body */ body?: any; /** Only used for types */ /** @see https://dataclient.io/rest/api/RestEndpoint#searchParams */ searchParams?: any; /** @see https://dataclient.io/rest/api/RestEndpoint#paginationfield */ readonly paginationField?: string; /** @see https://dataclient.io/rest/api/RestEndpoint#process */ process?(value: any, ...args: any): any; /** @see https://dataclient.io/rest/api/RestEndpoint#content */ readonly content?: ContentType; } /** Generic types when constructing a RestEndpoint * * @see https://dataclient.io/rest/api/RestEndpoint#inheritance */ export interface RestGenerics extends PartialRestGenerics { readonly path: string; } export type PaginationEndpoint< E extends FetchFunction & RestGenerics & { sideEffect?: boolean | undefined }, A extends any[], > = RestInstanceBase< ParamFetchNoBody<A[0], ResolveType<E>>, E['schema'], E['sideEffect'], Pick<E, 'path' | 'searchParams' | 'body'> & { searchParams: Omit<A[0], keyof PathArgs<E['path']>>; } >; /** Merge pagination field C into body, making it required */ type PaginationIntoBody<Body, C extends string> = Body & { [K in C]: string | number | boolean; }; /** Paginated searchParams type */ type PaginatedSearchParams< E extends { searchParams?: any; path?: string }, C extends string, > = { [K in C]: string | number | boolean; } & E['searchParams'] & PathArgs<Exclude<E['path'], undefined>>; /** searchParams version: pagination in searchParams, optional body support */ type PaginationFieldInSearchParams< E extends FetchFunction & RestGenerics & { sideEffect?: boolean | undefined }, C extends string, > = RestInstanceBase< // Union allows calling with just searchParams or with searchParams + body | ParamFetchNoBody<PaginatedSearchParams<E, C>, ResolveType<E>> | ParamFetchWithBody< PaginatedSearchParams<E, C>, NonNullable<E['body']>, ResolveType<E> >, E['schema'], E['sideEffect'], Pick<E, 'path' | 'searchParams' | 'body'> & { searchParams: { [K in C]: string | number | boolean; } & E['searchParams']; } > & { paginationField: C }; /** body version: pagination field is in body (body required) */ type PaginationFieldInBody< E extends FetchFunction & RestGenerics & { sideEffect?: boolean | undefined }, C extends string, > = RestInstanceBase< ParamFetchWithBody< E['searchParams'] & PathArgs<Exclude<E['path'], undefined>>, PaginationIntoBody<E['body'], C>, ResolveType<E> >, E['schema'], E['sideEffect'], Pick<E, 'path' | 'searchParams'> & { body: PaginationIntoBody<E['body'], C>; } > & { paginationField: C }; /** Retrieves the next page of results by pagination field */ export type PaginationFieldEndpoint< E extends FetchFunction & RestGenerics & { sideEffect?: boolean | undefined }, C extends string, > = // If body can be undefined or pagination field not in body, use searchParams undefined extends E['body'] ? PaginationFieldInSearchParams<E, C> : // If pagination field C is a key of body, merge into body C extends keyof E['body'] ? PaginationFieldInBody<E, C> : // Otherwise use searchParams PaginationFieldInSearchParams<E, C>; export type AddEndpoint< F extends FetchFunction = FetchFunction, S extends Schema | undefined = any, O extends { path: string; body: any; searchParams?: any; } = { path: string; body: any }, > = RestInstanceBase< RestFetch< 'searchParams' extends keyof O ? [O['searchParams']] extends [undefined] ? PathArgs<Exclude<O['path'], undefined>> : O['searchParams'] & PathArgs<Exclude<O['path'], undefined>> : PathArgs<Exclude<O['path'], undefined>>, O['body'], ResolveType<F> >, S, true, Omit<O, 'method'> & { method: 'POST' } >; export type RemoveEndpoint< F extends FetchFunction = FetchFunction, S extends Schema | undefined = any, O extends { path: string; body: any; searchParams?: any; } = { path: string; body: any }, > = RestInstanceBase< RestFetch< 'searchParams' extends keyof O ? [O['searchParams']] extends [undefined] ? PathArgs<Exclude<O['path'], undefined>> : O['searchParams'] & PathArgs<Exclude<O['path'], undefined>> : PathArgs<Exclude<O['path'], undefined>>, O['body'], ResolveType<F> >, S, true, Omit<O, 'method'> & { method: 'PATCH' } >; export type MoveEndpoint< F extends FetchFunction = FetchFunction, S extends Schema | undefined = any, O extends { path: string; body: any; } = { path: string; body: any }, > = RestInstanceBase< RestFetch<PathArgs<Exclude<O['path'], undefined>>, O['body'], ResolveType<F>>, S, true, Omit<O, 'method' | 'searchParams'> & { method: 'PATCH' } >; type OptionsBodyDefault<O extends RestGenerics> = 'body' extends keyof O ? O : O['method'] extends 'POST' | 'PUT' | 'PATCH' ? O & { body: any } : O & { body: undefined }; /** When `method` is omitted from `O`, infer it (must stay aligned with `OptionsToBodyArgument`). */ type InferRestMethodWhenOmitted<O extends RestGenerics> = O extends { sideEffect: true } ? 'POST' : 'body' extends keyof O ? [O['body']] extends [undefined] ? 'GET' : 'POST' : 'GET'; type MethodArgForBodyInference<O extends RestGenerics> = 'method' extends keyof O ? O['method'] : InferRestMethodWhenOmitted<O>; type OptionsToAdderBodyArgument<O extends { body?: any }, EntitySchema = any> = 'body' extends keyof O ? O['body'] : Partial<Denormalize<EntitySchema>>; export interface RestEndpointOptions< F extends FetchFunction = FetchFunction, S extends Schema | undefined = undefined, > extends EndpointExtraOptions<F> { /** Prepended to all urls * @see https://dataclient.io/rest/api/RestEndpoint#urlPrefix */ urlPrefix?: string; requestInit?: RequestInit; /** Called by getRequestInit to determine HTTP Headers * @see https://dataclient.io/rest/api/RestEndpoint#getHeaders */ getHeaders?(headers: HeadersInit): Promise<HeadersInit> | HeadersInit; /** Prepares RequestInit used in fetch. This is sent to fetchResponse() * @see https://dataclient.io/rest/api/RestEndpoint#getRequestInit */ getRequestInit?(body: any): Promise<RequestInit> | RequestInit; /** Performs the fetch call * @see https://dataclient.io/rest/api/RestEndpoint#fetchResponse */ fetchResponse?(input: RequestInfo, init: RequestInit): Promise<any>; /** Takes the Response and parses via .text() or .json() * @see https://dataclient.io/rest/api/RestEndpoint#parseResponse */ parseResponse?(response: Response): Promise<any>; /** @see https://dataclient.io/rest/api/RestEndpoint#content */ content?: ContentType; sideEffect?: boolean | undefined; name?: string; signal?: AbortSignal; fetch?: F; key?(...args: Parameters<F>): string; url?(...args: Parameters<F>): string; update?: EndpointUpdateFunction<F, S>; } // When subclassing RestEndpoint with `O extends RestGenerics = any`, O defaults // to `any`. The `unknown extends O ? any` guard catches O=any before it reaches // PathArgs (see #3782). SoftPathArgs collapses PathArgs<string> to `unknown` // when a concrete body is present, preventing union overloads that break // getOptimisticResponse callbacks. Method inference treats explicit body as POST. export type RestEndpointConstructorOptions<O extends RestGenerics = any> = RestEndpointOptions< RestFetch< unknown extends O ? any : 'searchParams' extends keyof O ? [O['searchParams']] extends [undefined] ? SoftPathArgs<O['path']> : O['searchParams'] & SoftPathArgs<O['path']> : SoftPathArgs<O['path']>, OptionsToBodyArgument<O, MethodArgForBodyInference<O>>, O['process'] extends {} ? ReturnType<O['process']> : 'content' extends keyof O ? ContentReturnType<O['content'] & ContentType> : any /*Denormalize<O['schema']>*/ >, O['schema'] >; /** Simplifies endpoint definitions that follow REST patterns * * @see https://dataclient.io/rest/api/RestEndpoint */ export interface RestEndpoint< O extends RestGenerics = any, > extends RestInstance< RestFetch< unknown extends O ? any : 'searchParams' extends keyof O ? [O['searchParams']] extends [undefined] ? PathArgs<O['path']> : O['searchParams'] & PathArgs<O['path']> : PathArgs<O['path']>, OptionsToBodyArgument<O, MethodArgForBodyInference<O>>, O['process'] extends {} ? ReturnType<O['process']> : 'content' extends keyof O ? ContentReturnType<O['content'] & ContentType> : any /*Denormalize<O['schema']>*/ >, 'schema' extends keyof O ? O['schema'] : undefined, 'sideEffect' extends keyof O ? Extract<O['sideEffect'], boolean | undefined> : MethodToSide<MethodArgForBodyInference<O>>, 'method' extends keyof O ? O : O & { method: InferRestMethodWhenOmitted<O>; } > {} export interface RestEndpointConstructor { /** Simplifies endpoint definitions that follow REST patterns * * @see https://dataclient.io/rest/api/RestEndpoint */ new <O extends RestGenerics = any>({ method, sideEffect, name, ...options }: RestEndpointConstructorOptions<O> & Readonly<O> & ContentSchemaGuard<O>): RestEndpoint<O>; readonly prototype: RestInstanceBase; } export type MethodToSide<M> = M extends string ? M extends 'GET' ? undefined : true : undefined; /** RestEndpoint types simplified */ export type RestType< UrlParams = any, Body = any, S extends Schema | undefined = Schema | undefined, M extends boolean | undefined = boolean | undefined, R = any, O extends { path: string; body?: any; searchParams?: any; paginationField?: string; } = { path: string; paginationField: string }, > = IfTypeScriptLooseNull< RestInstance<RestFetch<UrlParams, Body, R>, S, M, O>, Body extends {} ? RestTypeWithBody<UrlParams, S, M, Body, R, O> : RestTypeNoBody<UrlParams, S, M, R, O> >; export type RestTypeWithBody< UrlParams = any, S extends Schema | undefined = Schema | undefined, M extends boolean | undefined = boolean | undefined, Body = any, R = any /*Denormalize<S>*/, O extends { path: string; body?: any; searchParams?: any; } = { path: string; body: any }, > = RestInstance<ParamFetchWithBody<UrlParams, Body, R>, S, M, O>; export type RestTypeNoBody< UrlParams = any, S extends Schema | undefined = Schema | undefined, M extends boolean | undefined = boolean | undefined, R = any /*Denormalize<S>*/, O extends { path: string; body?: undefined; searchParams?: any; } = { path: string; body: undefined }, > = RestInstance<ParamFetchNoBody<UrlParams, R>, S, M, O>; /** Simple parameters, and body fetch functions */ export type RestFetch< UrlParams, Body = {}, Resolve = any, > = IfTypeScriptLooseNull< | ParamFetchNoBody<UrlParams, Resolve> | ParamFetchWithBody<UrlParams, Body, Resolve>, Body extends {} ? ParamFetchWithBody<UrlParams, Body, Resolve> : ParamFetchNoBody<UrlParams, Resolve> >; export type ParamFetchWithBody<P, B = {}, R = any> = // we must always allow undefined in a union and give it a type without params P extends undefined ? (this: EndpointInstanceInterface, body: B) => Promise<R> : // even with loose null, this will only be true when all members are optional {} extends P ? // this safely handles PathArgs with no members that results in a simple `unknown` type keyof P extends never ? (this: EndpointInstanceInterface, body: B) => Promise<R> : | ((this: EndpointInstanceInterface, params: P, body: B) => Promise<R>) | ((this: EndpointInstanceInterface, body: B) => Promise<R>) : (this: EndpointInstanceInterface, params: P, body: B) => Promise<R>; export type ParamFetchNoBody<P, R = any> = // we must always allow undefined in a union and give it a type without params P extends undefined ? (this: EndpointInstanceInterface) => Promise<R> : // even with loose null, this will only be true when all members are optional {} extends P ? // this safely handles PathArgs with no members that results in a simple `unknown` type keyof P extends never ? (this: EndpointInstanceInterface) => Promise<R> : | ((this: EndpointInstanceInterface, params: P) => Promise<R>) | ((this: EndpointInstanceInterface) => Promise<R>) : (this: EndpointInstanceInterface, params: P) => Promise<R>; // same algorithm, but for Args (aka readonly any[]) export type ParamToArgs<P> = P extends undefined ? [] : {} extends P ? keyof P extends never ? [] : [] | [P] : [P]; type IfTypeScriptLooseNull<Y, N> = 1 | undefined extends 1 ? Y : N; export type KeyofRestEndpoint = keyof RestInstance; export type FromFallBack<K extends keyof E, O, E> = K extends keyof O ? O[K] : E[K]; export type FetchMutate< A extends readonly any[] = [any, {}] | [{}], R = any, > = (this: RestInstance, ...args: A) => Promise<R>; export type FetchGet<A extends readonly any[] = [any], R = any> = ( this: RestInstance, ...args: A ) => Promise<R>; export type Defaults<O, D> = { [K in keyof O | keyof D]: K extends keyof O ? Exclude<O[K], undefined> : D[Extract<K, keyof D>]; }; export type GetEndpoint< O extends { readonly path: string; readonly schema: Schema; /** Only used for types */ readonly searchParams?: any; readonly paginationField?: string; } = { path: string; schema: Schema; }, > = RestTypeNoBody< 'searchParams' extends keyof O ? [O['searchParams']] extends [undefined] ? PathArgs<O['path']> : O['searchParams'] & PathArgs<O['path']> : PathArgs<O['path']>, O['schema'], undefined, any, O & { method: 'GET' } >; export type MutateEndpoint< O extends { readonly path: string; readonly schema: Schema; /** Only used for types */ readonly searchParams?: any; /** Only used for types */ readonly body?: any; } = { path: string; body: any; schema: Schema; }, > = RestTypeWithBody< 'searchParams' extends keyof O ? [O['searchParams']] extends [undefined] ? PathArgs<O['path']> : O['searchParams'] & PathArgs<O['path']> : PathArgs<O['path']>, O['schema'], true, O['body'], any, O & { body: any; method: 'POST' | 'PUT' | 'PATCH' | 'DELETE' } >;