-
-
Notifications
You must be signed in to change notification settings - Fork 805
$fetch type safety for query and body parameters #938
Copy link
Copy link
Closed as not planned
Labels
Description
Describe the feature
$fetch provides type safety for return types which is great. It would be greater if it optionally checks types for query and body parameters for internal API requests.
Below is a rough proposal:
- Server routes optionally export
QuerySchemaandBodySchema.
-> Developer's responsibility - Generate necessary types for those routes
/.nuxt/types/nitro.d.ts.
-> Below is an example. - Add types to
/node_modules/nitropack/dist/index.d.ts
-> Below is a proposal.
/server/api/product.units.ts
export default defineEventHandler((event) => {
const query = getQuery(event)
const body = await readBody(event)
})
export interface QuerySchema {
name: string;
id: number;
}
export interface BodySchema {
content: string
}/.nuxt/types/nitro.d.ts
// It would be better if `InternalApi` and the proposed `InternalApiQuerySchema` and `InternalApiQuerySchema`
// are merged into one interface.
// However separated interfaces are easier to implement for re-using the current code base.
declare module 'nitropack' {
interface InternalApi {
'/api/units': {
'get': Awaited<ReturnType<typeof import('../../server/api/units.get').default>>
}
}
interface InternalApiQuerySchema {
"/api/units": {
get: import("../../server//api/units.get").QuerySchema;
};
}
interface InternalApiBodySchema {
"/api/units": {
get: import("../../server//api/units.get").BodySchema;
};
}
}/node_modules/nitropack/dist/index.d.ts
// ─── Added ───────────────────────────────────────────────────────────────────
type RequestSchema<Base, R extends NitroFetchRequest, M extends AvailableRouterMethod<R> = AvailableRouterMethod<R>> = R extends keyof Base
? M extends keyof Base[R]
? Base[R][M]
: never
: never;
// ─── Modified ────────────────────────────────────────────────────────────────
// Added `query` and `body`
interface NitroFetchOptions<R extends NitroFetchRequest, M extends AvailableRouterMethod<R> = AvailableRouterMethod<R>>
extends Omit<FetchOptions, "query" | "body"> {
method?: Uppercase<M> | M;
query?: RequestSchema<InternalApiQuerySchema, R, M>;
body?: RequestSchema<InternalApiBodySchema, R, M>;
}Problems I stumbled upon:
- Related to TS error
Excessive stack depth comparing typeswhen trying to wrap$fetch#470. I getExcessive stack depth comparing types...error from TypeScript. This error is present even I copy-paste the types without changing them. The problem is caused byAvailableRouterMethod<R>type. If I switch it withRouterMethod, it works. In this case we sacrifice "method" safety. TBH, I preferqueryandpostsafety to the "method"
safety.
a. Query and body parameters are much more error prone compared to a simple method name.
b.AvailableRouterMethod<R>type seems much more expensive compared to simple object types. - I don't know how to generate types
/.nuxt/types/nitro.d.ts. I guess it would be easy to utilize already existing type generation function.
POC
Below is the POC: A composable for Nuxt representing Excessive stack... problem mentioned above.
POC Code
/server/api/units.get.ts
import { useValidatedQuery, useValidatedBody, z } from "h3-zod";
import type { H3Event } from "h3";
const querySchema = z.object({ language: z.string() });
const bodySchema = z.object({ color: z.number() });
export type QuerySchema = z.infer<typeof querySchema>;
export type BodySchema = z.infer<typeof bodySchema>;
export default eventHandler(async (event: H3Event) => {
const { language } = useValidatedQuery(event, querySchema);
const { color } = useValidatedBody(event, bodySchema);
return { color, language };
});/composables/useSafeFetch.ts
import { NitroFetchRequest, TypedInternalResponse, ExtractedRouteMethod, AvailableRouterMethod } from "nitropack";
import { FetchOptions, FetchResponse } from "ofetch";
import type { InternalApiQuerySchema, InternalApiBodySchema } from "internal-api-schema";
// Types from `/node_modules/nitropack/dist/index.d.ts`
// ─── Added ───────────────────────────────────────────────────────────────────
type RequestSchema<Base, R extends NitroFetchRequest, M extends AvailableRouterMethod<R> = AvailableRouterMethod<R>> = R extends keyof Base
? M extends keyof Base[R]
? Base[R][M]
: never
: never;
// ─── Modified ────────────────────────────────────────────────────────────────
// Added `query` and `body`
interface NitroFetchOptions<R extends NitroFetchRequest, M extends AvailableRouterMethod<R> = AvailableRouterMethod<R>>
extends Omit<FetchOptions, "query" | "body"> {
method?: Uppercase<M> | M;
query?: RequestSchema<InternalApiQuerySchema, R, M>;
body?: RequestSchema<InternalApiBodySchema, R, M>;
}
// ─── Not Changed ─────────────────────────────────────────────────────────────
interface $Fetch<DefaultT = unknown, DefaultR extends NitroFetchRequest = NitroFetchRequest> {
<T = DefaultT, R extends NitroFetchRequest = DefaultR, O extends NitroFetchOptions<R> = NitroFetchOptions<R>>(
request: R,
opts?: O
): Promise<TypedInternalResponse<R, T, ExtractedRouteMethod<R, O>>>;
raw<T = DefaultT, R extends NitroFetchRequest = DefaultR, O extends NitroFetchOptions<R> = NitroFetchOptions<R>>(
request: R,
opts?: O
): Promise<FetchResponse<TypedInternalResponse<R, T, ExtractedRouteMethod<R, O>>>>;
create<T = DefaultT, R extends NitroFetchRequest = DefaultR>(defaults: FetchOptions): $Fetch<T, R>;
}
const useSafeFetch: $Fetch = (request, opts) => $fetch(request, opts);
useSafeFetch.raw = (request, opts) => $fetch.raw(request, opts);
useSafeFetch.create = (defaults) => $fetch.create(defaults);
export default useSafeFetch;/.nuxt/types/nitro.d.ts
declare module "internal-api-schema" {
interface InternalApiQuerySchema {
"/api/units": {
get: import("../../server/api/units.get").QuerySchema;
};
}
interface InternalApiBodySchema {
"/api/units": {
get: import("../../server/api/units.get").BodySchema;
};
}
}Additional information
- Would you be willing to help implement this feature?
Reactions are currently unavailable