UNPKG

nuxt

Version:

Nuxt is a free and open-source framework with an intuitive and extendable way to create type-safe, performant and production-grade full-stack web applications and websites with Vue.js.

259 lines • 19.3 kB
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 };