nuxt
Version:
259 lines • 19.3 kB
TypeScript
import { InternalApiMethods, InternalApiRequest, InternalApiRequestShape, InternalApiResponse } from "./fetch-internal-api.js";
import { AnyHTTPMethod, LooseTypedHeaders, TypedFetchErrorBody, TypedFetchPath, TypedFetchRequestBody, TypedFetchRequestHeaders, TypedFetchRequestQuery, TypedFetchRequires, TypedFetchResponseBody, ValidFetchInput } from "fetchdts";
import { FetchOptions, FetchRequest, FetchResponse } from "ofetch";
import { ErrorBody, Methods, Path, RequestBody, RequestHeaders, RequestQuery, Requires, Response, RouteTypesEngine, StrictFetchPaths, ValidInput } from "#build/server-routes";
//#region src/app/types/fetch.d.ts
/**
* Whether requests are typed from nitro's `InternalApi` interface rather than from the routes the
* server builder reported.
*
* Resolved by the build and emitted with the route types: under `compatibilityVersion: 4` the
* generated route set is written but not consulted, so `$fetch` and `useFetch` resolve exactly as
* they did before the route compiler existed. Each type below that the two engines disagree on
* branches on this.
*
* @internal
*/
type LegacyEngine = RouteTypesEngine extends 'internal-api' ? true : false;
/** A lowercased HTTP method a server route can be registered for. */
export type ServerRouteMethod = 'get' | 'head' | 'patch' | 'post' | 'put' | 'delete' | 'connect' | 'options' | 'trace';
/**
* A method as it may be written at a call site.
*
* A method type parameter has to admit both cases, or the one it does not admit cannot be inferred:
* a parameter constrained to lowercase falls back to its default when a call passes `'POST'`, and
* the call is rejected rather than resolved against the wrong method.
*/
export type AnyServerRouteMethod = ServerRouteMethod | Uppercase<ServerRouteMethod>;
/**
* The constraint on a path argument: a pattern while strict, so that a literal keeps its literal
* type through `ref` and `computed`, and `string & {}` when not, which does the same and also
* accepts a path known only at runtime.
*/
export type TypedFetchPathInput = StrictFetchPaths extends true ? `/${string}` | `${string}://${string}` : (string & {});
/**
* The constraint on `$fetch`'s permissive signature: any string, without widening one built from a
* template.
*
* Plain `string` would accept everything and is what lets `$fetch<Something>(url)` compile, but a
* literal inferred into a parameter constrained by `string` widens - so `` `/api/posts/${id}` ``
* arrives as `string`, matches nothing, and loses the union a parameter would have resolved to. The
* pattern members keep the literal; `string & {}` keeps the signature total.
*/
type AnyFetchPath = `/${string}` | `${string}://${string}` | (string & {});
/**
* `unknown` where the path resolves, where it is an absolute URL, where the request is not a
* string, or where the app opted out; otherwise a type no string satisfies, whose single key names
* the path and method that matched nothing.
*
* An absolute URL is exempt because Nuxt cannot know a third-party API's routes.
*/
export type ValidTypedFetchPath<Request, Method extends AnyServerRouteMethod> = StrictFetchPaths extends false ? unknown : string extends Request ? unknown : Request extends string ? Request extends `${string}://${string}` ? unknown : ValidInput<Request, Uppercase<Method>> : unknown;
/**
* A request accepted by Nuxt's typed `$fetch`: a route the server serves, or any non-string request
* `fetch` accepts.
*
* The path union is emitted as source by the route compiler, so it needs no normalising before it
* is used as a constraint here.
*/
export type TypedFetchRequest = LegacyEngine extends true ? InternalApiRequest : Path | Exclude<FetchRequest, string>;
/**
* The type a request to `Request` resolves to, or `Default` where no route matches.
*
* A `Default` the caller named explicitly wins over the route's own type, because naming it is how
* `$fetch<Something>(url)` has always overridden the response and the request is not inferred at
* such a call site.
*/
export type TypedServerResponse<Request, Default = unknown, Method extends AnyServerRouteMethod = 'get'> = LegacyEngine extends true ? InternalApiResponse<Request, Default, Lowercase<Method>> : Default extends string | boolean | number | null | void | object ? Default : Response<Request, Uppercase<Method>> extends (infer Resolved) ? [Resolved] extends [never] ? Default : Resolved : Default;
/** The body a failed request to `Request` carries. */
export type TypedServerError<Request, Method extends AnyServerRouteMethod = 'get'> = ErrorBody<Request, Uppercase<Method>>;
/** The methods the matching route answers, uppercased as the schema declares them. */
export type ServerRouteMethods<Request> = LegacyEngine extends true ? Uppercase<InternalApiMethods<Request>> : Methods<Request>;
/**
* The methods `Request` can be fetched with.
*
* Not used in `$fetch`'s own signatures: a method the route does not answer is already rejected by
* {@link ValidTypedFetchPath}, and naming this in the options would instantiate it once per call
* site for no additional check. Exported because modules building their own clients want it.
*/
export type AvailableServerRouteMethod<Request> = LegacyEngine extends true ? InternalApiMethods<Request> : Lowercase<Methods<Request> & string>;
/**
* The `body`, `query` and `headers` a request to `Request` with `Method` accepts, according to what
* the matching route validates, falling back to ofetch's own types where it declares nothing.
*
* The keys are written out and only the values are computed. That is load-bearing: an interface may
* extend a route-derived object type only where its key set is statically known, and a generic path
* is enough to break that, which `useFetch`'s options always have. The requiredness of `body`
* therefore lives in {@link RequiredRequestBody}, intersected at the call signature.
*
* A request the route set cannot resolve - a path built at runtime, or a `Request` object - falls
* back to ofetch's own types rather than narrowing to nothing, which the accessors handle.
*/
export type TypedRequestShape<Request, Method extends AnyServerRouteMethod> = LegacyEngine extends true ? InternalApiRequestShape : {
body?: Declared<RequestBody<Request, Uppercase<Method>, never>, FetchOptions['body']>;
query?: Declared<RequestQuery<Request, Uppercase<Method>, never>, FetchOptions['query']>;
headers?: DeclaredHeaders<RequestHeaders<Request, Uppercase<Method>, never>>;
};
/**
* The shape a route declares for a field, or `Fallback` where it declares none.
*
* The accessors are asked with `never` as their fallback so that "declared" and "not declared" stay
* distinguishable: a real fallback is indistinguishable from a declared shape, since a declared
* shape is assignable to it. This also catches a field the builder's extractor answered `never` for
* - which is every route whose handler validates nothing, since the generator emits an extractor
* call per field rather than deciding at build time whether one applies.
*
* @internal
*/
type Declared<Shape, Fallback> = [Shape] extends [never] ? Fallback : Shape;
/**
* As {@link Declared}, but a declared shape widens rather than replacing what ofetch accepts: a
* route is validated for the headers it names, and does not reject a request for carrying more.
*
* @internal
*/
type DeclaredHeaders<Shape> = [Shape] extends [never] ? FetchOptions['headers'] : (Shape & Record<string, string>) | LooseTypedHeaders<Shape> | Array<[string, string]>;
/** Requires `body` where the route declares one that is not optional, and contributes nothing otherwise. */
export type RequiredRequestBody<Request, Method extends AnyServerRouteMethod> = LegacyEngine extends true ? unknown : RequiresBody<Request, Method> extends true ? {
body: TypedRequestShape<Request, Method>['body'];
} : unknown;
/**
* Whether the route declares a body a request to it cannot omit, asking the generated answer first.
*
* A route that declares nothing declares `never`, which is not a body anyone can supply, so it does
* not count as required however the accessor answers.
*
* @internal
*/
type RequiresBody<Request, Method extends AnyServerRouteMethod> = [RequestBody<Request, Uppercase<Method>, never>] extends [never] ? false : Requires<Request, Uppercase<Method>, 'body'>;
/**
* Requires `body` for a request to `Request` with `Method`, without saying what it holds.
*
* `useFetch`'s options are an interface so that modules can extend them, which means its members
* have to be statically known and cannot be made conditionally required. Intersecting this at the
* call signatures instead requires the member where the route declares one, while leaving its type
* to the interface - which also wraps it in the ref and getter forms `useFetch` accepts.
*/
export type RequiredFetchBody<Request, Method extends AnyServerRouteMethod> = LegacyEngine extends true ? unknown : RequiresBody<Request, Method> extends true ? {
body: unknown;
} : unknown;
/**
* Empty where the request resolves, and a trailing argument no call supplies where it does not.
*
* The path union carries no method, so a signature constrained by it alone accepts a path the
* server knows for a method that path does not answer. Requiring an argument that cannot be passed
* takes such a signature out of the running, and the call falls through to the one that validates
* the path, which names the path and method that matched nothing.
*
* The check has to sit here rather than on the parameters that carry it. Intersected onto the
* request it is no longer a contextual type a template literal narrows against, so
* `` `/api/posts/${id}` `` would arrive as `string` and resolve to nothing; folded into the options
* it is unreachable, because they are optional at the signatures a call with no options resolves
* by, and wrapping them in a conditional stops `pick` and `transform` inferring.
*/
export type UnmatchedRouteArgs<Request, Method extends AnyServerRouteMethod> = unknown extends ValidTypedFetchPath<Request, Method> ? [] : [unmatched: ValidTypedFetchPath<Request, Method>];
/**
* The method as written, or `never` where the matching route does not answer it.
*
* Narrowing is conditional on the request resolving at all: with `strictRouteTypes` off, at a path
* known only at runtime, or against an absolute URL, the route set says nothing about which methods
* a path answers, so any method is accepted.
*/
export type AcceptedMethod<Request, Method extends AnyServerRouteMethod> = LegacyEngine extends true ? Extract<Method, InternalApiMethods<Request> | Uppercase<InternalApiMethods<Request>>> : unknown extends ValidTypedFetchPath<Request, Method> ? Method : never;
/**
* The path a request is matched against, with a `baseURL` applied.
*
* A `baseURL` is prepended to the path before the request is made, so it is part of the route being
* requested and has to be part of what the route set is asked about: without it, `'/hello'` with a
* base of `'/api'` names no route, and `'/api/hello'` with the same base type-checks while
* requesting `/api/api/hello`.
*
* Where the base is not statically known - a runtime value, a `ref` or a getter - the path resolves
* to `string`, which the accessors treat as a request they cannot see, so nothing is rejected on the
* strength of a base Nuxt cannot read.
*/
export type ResolvedFetchPath<Request, BaseURL extends string> = LegacyEngine extends true ? Request : BaseURL extends '' ? Request : string extends BaseURL ? string : Request extends `${string}://${string}` ? Request : BaseURL extends `${infer Base}/` ? `${Base}${Request & string}` : `${BaseURL}${Request & string}`;
/**
* The `baseURL` a call is made with: the one it passes, or the one its `$fetch` instance was created
* with, matching how ofetch merges the two.
*/
export type EffectiveBaseURL<Instance extends string, Call extends string> = Call extends '' ? Instance : Call;
/**
* The `baseURL` assumed where none is inferred: `''` while the request is inferred, and `string` once
* it falls back to `Default`. A call that names its type arguments infers none of the others, so a
* base it passes is not inferred either.
*/
export type FallbackBaseURL<Request, Default> = [Default] extends [Request] ? string : '';
/** Options accepted by Nuxt's typed `$fetch`, narrowed to what the matching route validates. */
export type TypedFetchOptions<Request, Method extends AnyServerRouteMethod = 'get', BaseURL extends string = string> = Omit<FetchOptions, 'method' | 'body' | 'query' | 'params' | 'headers' | 'baseURL'> & {
baseURL?: BaseURL;
} & {
method?: AcceptedMethod<Request, Method>;
} & TypedRequestShape<Request, Method> & RequiredRequestBody<Request, Method>;
/**
* A request accepted by a client built for the route set `Schema` describes: one of its paths, a
* path known only at runtime, or any non-string request `fetch` accepts.
*
* The paths are read out of the route set so an editor completes them. `string & {}` keeps a literal
* from widening while still admitting a path built at runtime, which resolves to `unknown` as it
* does against the app's own routes.
*/
export type DeclaredFetchRequest<Schema> = TypedFetchPath<Schema> | (string & {}) | Exclude<FetchRequest, string>;
/**
* The types below mirror those above for a route set a client declares rather than the one the app's
* own server serves. They are a separate family rather than a `Schema` parameter on each of the
* types above, because a parameter means every program would always instantiate the generic accessors.
*/
/** As {@link ValidTypedFetchPath}, resolved against a declared route set. */
export type ValidDeclaredFetchPath<Schema, Request, Method extends AnyServerRouteMethod> = StrictFetchPaths extends false ? unknown : string extends Request ? unknown : Request extends string ? Request extends `${string}://${string}` ? unknown : ValidFetchInput<Schema, Request, Uppercase<Method>> : unknown;
/** As {@link UnmatchedRouteArgs}, resolved against a declared route set. */
export type UnmatchedDeclaredRouteArgs<Schema, Request, Method extends AnyServerRouteMethod> = unknown extends ValidDeclaredFetchPath<Schema, Request, Method> ? [] : [unmatched: ValidDeclaredFetchPath<Schema, Request, Method>];
/** As {@link TypedServerResponse}, resolved against a declared route set. */
export type DeclaredServerResponse<Schema, Request, Default = unknown, Method extends AnyServerRouteMethod = 'get'> = Default extends string | boolean | number | null | void | object ? Default : TypedFetchResponseBody<Schema, Request, Uppercase<Method>> extends (infer Resolved) ? [Resolved] extends [never] ? Default : Resolved : Default;
/** As {@link TypedServerError}, resolved against a declared route set. */
export type DeclaredServerError<Schema, Request, Method extends AnyServerRouteMethod = 'get'> = TypedFetchErrorBody<Schema, Request, Uppercase<Method>>;
/** As {@link TypedRequestShape}, resolved against a declared route set. */
export type DeclaredRequestShape<Schema, Request, Method extends AnyServerRouteMethod> = {
body?: Declared<TypedFetchRequestBody<Schema, Request, Uppercase<Method>, never>, FetchOptions['body']>;
query?: Declared<TypedFetchRequestQuery<Schema, Request, Uppercase<Method>, never>, FetchOptions['query']>;
headers?: DeclaredHeaders<TypedFetchRequestHeaders<Schema, Request, Uppercase<Method>, never>>;
};
/** @internal */
type RequiresDeclaredBody<Schema, Request, Method extends AnyServerRouteMethod> = [TypedFetchRequestBody<Schema, Request, Uppercase<Method>, never>] extends [never] ? false : TypedFetchRequires<Schema, Request, Uppercase<Method>, 'body'>;
/** As {@link RequiredFetchBody}, resolved against a declared route set. */
export type RequiredDeclaredBody<Schema, Request, Method extends AnyServerRouteMethod> = RequiresDeclaredBody<Schema, Request, Method> extends true ? {
body: unknown;
} : unknown;
/** As {@link AcceptedMethod}, resolved against a declared route set. */
export type AcceptedDeclaredMethod<Schema, Request, Method extends AnyServerRouteMethod> = unknown extends ValidDeclaredFetchPath<Schema, Request, Method> ? Method : never;
/**
* A `fetch` implementation whose responses are typed from the routes the server serves.
*
* Two signatures. The first is constrained by the path union, so an editor offers the registered
* paths as completions, and is what a resolving call is answered by; it carries
* {@link UnmatchedRouteArgs}, because that union carries no method and would otherwise accept a
* path the server knows for a method that path does not answer. The second validates the path and
* is what names one that matched nothing.
*
* The second is constrained by `string` rather than by {@link TypedFetchPathInput}, so that naming
* the response type (`$fetch<Something>(url)`) still compiles: doing so turns off inference for the
* request, and a pattern constraint has no default a runtime path would satisfy. The consequence is
* that `$fetch` validates a path it can see and passes one it cannot; `useFetch` keeps the pattern,
* which it needs so a literal survives `ref` and `computed`.
*
* Concrete, never generic over the route set: `$fetch` is declared as a value in generated types
* and returned from `useRequestFetch()`, both of which need a type with no schema parameter.
*/
export interface TypedFetch<DefaultT = unknown, DefaultB extends string = ''> {
<T = DefaultT, R extends TypedFetchRequest = TypedFetchRequest, M extends AnyServerRouteMethod = unknown extends T ? 'get' : AnyServerRouteMethod, const B extends string = FallbackBaseURL<R, TypedFetchRequest>, _R = ResolvedFetchPath<R, EffectiveBaseURL<DefaultB, B>>>(request: R, opts?: TypedFetchOptions<_R, M, B>, ...unmatched: UnmatchedRouteArgs<_R, M>): Promise<TypedServerResponse<_R, T, M>>;
<T = DefaultT, R extends AnyFetchPath = AnyFetchPath, M extends AnyServerRouteMethod = unknown extends T ? 'get' : AnyServerRouteMethod, const B extends string = FallbackBaseURL<R, AnyFetchPath>, _R = ResolvedFetchPath<R, EffectiveBaseURL<DefaultB, B>>>(request: R & ValidTypedFetchPath<_R, M>, opts?: TypedFetchOptions<_R, M, B>): Promise<TypedServerResponse<_R, T, M>>;
raw: {
<T = DefaultT, R extends TypedFetchRequest = TypedFetchRequest, M extends AnyServerRouteMethod = unknown extends T ? 'get' : AnyServerRouteMethod, const B extends string = FallbackBaseURL<R, TypedFetchRequest>, _R = ResolvedFetchPath<R, EffectiveBaseURL<DefaultB, B>>>(request: R, opts?: TypedFetchOptions<_R, M, B>, ...unmatched: UnmatchedRouteArgs<_R, M>): Promise<FetchResponse<TypedServerResponse<_R, T, M>>>;
<T = DefaultT, R extends AnyFetchPath = AnyFetchPath, M extends AnyServerRouteMethod = unknown extends T ? 'get' : AnyServerRouteMethod, const B extends string = FallbackBaseURL<R, AnyFetchPath>, _R = ResolvedFetchPath<R, EffectiveBaseURL<DefaultB, B>>>(request: R & ValidTypedFetchPath<_R, M>, opts?: TypedFetchOptions<_R, M, B>): Promise<FetchResponse<TypedServerResponse<_R, T, M>>>;
};
create: <T = DefaultT, const B extends string = unknown extends T ? DefaultB : string>(defaults: Omit<FetchOptions, 'baseURL'> & {
baseURL?: B;
}) => TypedFetch<T, EffectiveBaseURL<DefaultB, B>>;
}
//#endregion
export type { AnyHTTPMethod };