Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 69 additions & 9 deletions examples/todolist/src/router.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { createRouter, FromSchema, Response } from 'fets';
import { TypedRequest, TypedResponse } from 'fets/src/typed-fetch';

const TodoSchema = {
type: 'object',
Expand Down Expand Up @@ -42,7 +43,9 @@ export const router = createRouter({
},
},
} as const,
handler: () => Response.json(todos),
handler: () => {
return Response.json(todos);
},
})
.route({
description: 'Get a todo',
Expand Down Expand Up @@ -72,7 +75,18 @@ export const router = createRouter({
},
},
} as const,
handler: async request => {
handler: async (
request: TypedRequest<
Comment thread
JustFly1984 marked this conversation as resolved.
Outdated
any,
Record<string, FormDataEntryValue | undefined>,
Record<string, string>,
'GET',
Record<string, string | string[]>,
{
id: string;
}
>,
) => {
const { id } = request.params;
const todo = todos.find(todo => todo.id === id);
if (!todo) {
Expand Down Expand Up @@ -109,7 +123,18 @@ export const router = createRouter({
},
},
} as const,
handler: async request => {
handler: async (
request: TypedRequest<
any,
Record<string, FormDataEntryValue | undefined>,
Record<string, string>,
'PUT',
Record<string, string | string[]>,
{
id: string;
}
>,
) => {
const input = await request.json();
const todo: Todo = {
id: crypto.randomUUID(),
Expand Down Expand Up @@ -153,7 +178,18 @@ export const router = createRouter({
},
},
} as const,
handler: async request => {
handler: async (
request: TypedRequest<
any,
Record<string, FormDataEntryValue | undefined>,
Record<string, string>,
'DELETE',
Record<string, string | string[]>,
{
id: string;
}
>,
) => {
const { id } = request.params;
const index = todos.findIndex(todo => todo.id === id);
if (index === -1) {
Expand Down Expand Up @@ -193,7 +229,7 @@ export const router = createRouter({
required: ['file'],
additionalProperties: false,
},
},
} as const,
responses: {
200: {
type: 'object',
Expand All @@ -209,16 +245,40 @@ export const router = createRouter({
},
},
} as const,
handler: async request => {
handler: async (
request: TypedRequest<
any,
any,
Record<string, string>,
'POST',
Record<string, string | string[]>,
Record<never, string>
>,
): Promise<
TypedResponse<
{
readonly name: string;
readonly description: string;
readonly type: string;
readonly size: number;
readonly lastModified: number;
},
Record<string, string>,
200
>
> => {
const body = await request.formData();
const file = body.get('file');
const description = body.get('description');
return Response.json({

const json = {
name: file.name,
description,
description: description || '',
type: file.type,
size: file.size,
lastModified: file.lastModified,
});
} as const;

return Response.json(json);
},
});
17 changes: 17 additions & 0 deletions packages/fets/src/createRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -291,9 +291,26 @@ export function createRouter<
plugins,
};
const routerBaseObject = createRouterBase(finalOpts, openAPIDocument as OpenAPIDocument);

// Argument of type 'RouterOptions<TServerContext, TComponents>' is not assignable to parameter of type 'ServerAdapterOptions<TServerContext>' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties.
// Types of property 'plugins' are incompatible.
// Type 'RouterPlugin<TServerContext>[] | undefined' is not assignable to type 'ServerAdapterPlugin<TServerContext>[]'.
// Type 'undefined' is not assignable to type 'ServerAdapterPlugin<TServerContext>[]'.
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const router = createServerAdapter(routerBaseObject, finalOpts);

for (const onRouterInitHook of routerBaseObject.__onRouterInitHooks) {
// Argument of type 'ServerAdapter<TServerContext, ServerAdapterBaseObject<TServerContext, ServerAdapterRequestHandler<TServerContext>>>' is not assignable to parameter of type 'Router<any, any, any>'.
// Type 'ServerAdapter<TServerContext, ServerAdapterBaseObject<TServerContext, ServerAdapterRequestHandler<TServerContext>>>' is not assignable to type 'RouterBaseObject<any, any, any>'.
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
onRouterInitHook(router);
}

// Type 'ServerAdapter<TServerContext, ServerAdapterBaseObject<TServerContext, ServerAdapterRequestHandler<TServerContext>>>' is not assignable to type 'Router<TServerContext, TComponents, TRouterSDK>'.
// Type 'ServerAdapter<TServerContext, ServerAdapterBaseObject<TServerContext, ServerAdapterRequestHandler<TServerContext>>>' is not assignable to type 'RouterBaseObject<TServerContext, TComponents, TRouterSDK>'.
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
return router;
}
8 changes: 6 additions & 2 deletions packages/fets/src/plugins/ajv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,14 +167,18 @@ export function useAjv({
contentType?.includes('application/x-www-form-urlencoded')
) {
const formData = await request.formData();
const formDataObj: Record<string, FormDataEntryValue> = {};
const formDataObj: Record<string, FormDataEntryValue | undefined> = {};
const jobs: Promise<void>[] = [];
formData.forEach((value, key) => {
if (typeof value === 'undefined') {
return;
}

if (typeof value === 'string') {
formDataObj[key] = value;
} else {
jobs.push(
value.arrayBuffer().then(buffer => {
value.arrayBuffer().then((buffer: ArrayBuffer): void => {
const typedArray = new Uint8Array(buffer);
const binaryStrParts: string[] = [];
typedArray.forEach((byte, index) => {
Expand Down
16 changes: 11 additions & 5 deletions packages/fets/src/typed-fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export type NotOkStatusCode = Exclude<StatusCode, OkStatusCode>;

export type TypedBody<
TJSON,
TFormData extends Record<string, FormDataEntryValue>,
TFormData extends Record<string, FormDataEntryValue | undefined>,
THeaders extends Record<string, string>,
> = Omit<Body, 'json' | 'formData' | 'headers'> & {
/**
Expand Down Expand Up @@ -314,7 +314,7 @@ export type HTTPMethod =
export type TypedRequestInit<
THeaders extends Record<string, string>,
TMethod extends HTTPMethod,
TFormData extends Record<string, FormDataEntryValue>,
TFormData extends Record<string, FormDataEntryValue | undefined>,
> = Omit<RequestInit, 'method' | 'headers' | 'body'> & {
method: TMethod;
headers: TypedHeaders<THeaders>;
Expand All @@ -323,7 +323,10 @@ export type TypedRequestInit<

export type TypedRequest<
TJSON = any,
TFormData extends Record<string, FormDataEntryValue> = Record<string, FormDataEntryValue>,
TFormData extends Record<string, FormDataEntryValue | undefined> = Record<
string,
FormDataEntryValue | undefined
>,
THeaders extends Record<string, string> = Record<string, string>,
TMethod extends HTTPMethod = HTTPMethod,
TQueryParams extends Record<string, string | string[]> = Record<string, string | string[]>,
Expand All @@ -340,7 +343,7 @@ export type TypedRequestCtor = new <
THeaders extends Record<string, string>,
TMethod extends HTTPMethod,
TQueryParams extends Record<string, string | string[]>,
TFormData extends Record<string, FormDataEntryValue>,
TFormData extends Record<string, FormDataEntryValue | undefined>,
>(
input: string | TypedURL<TQueryParams>,
init?: TypedRequestInit<THeaders, TMethod, TFormData>,
Expand Down Expand Up @@ -391,7 +394,10 @@ export type TypedURLCtor = new <TQueryParams extends Record<string, string | str
) => TypedURL<TQueryParams>;

export interface TypedFormData<
TMap extends Record<string, FormDataEntryValue> = Record<string, FormDataEntryValue>,
TMap extends Record<string, FormDataEntryValue | undefined> = Record<
string,
FormDataEntryValue | undefined
>,
> {
append<TName extends keyof TMap>(
name: TName,
Expand Down
17 changes: 10 additions & 7 deletions packages/fets/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,10 @@ export type StatusCodeMap<T> = {
export type TypedRouterHandlerTypeConfig<
TPath extends string,
TRequestJSON = any,
TRequestFormData extends Record<string, FormDataEntryValue> = Record<string, FormDataEntryValue>,
TRequestFormData extends Record<string, FormDataEntryValue | undefined> = Record<
string,
FormDataEntryValue | undefined
>,
TRequestHeaders extends Record<string, string> = Record<string, string>,
TRequestQueryParams extends Record<string, string | string[]> = Record<string, string | string[]>,
TRequestPathParams extends Record<string, any> = Record<
Expand Down Expand Up @@ -233,7 +236,7 @@ export type TypedRequestFromTypeConfig<
: never
: TypedRequest<
any,
Record<string, FormDataEntryValue>,
Record<string, FormDataEntryValue | undefined>,
Record<string, string>,
TMethod,
Record<string, string | string[]>,
Expand Down Expand Up @@ -443,10 +446,10 @@ export type TypedRequestFromRouteSchemas<
? FromSchemaWithComponents<
TComponents,
TRouteSchemas['request']['formData']
> extends Record<string, FormDataEntryValue>
> extends Record<string, FormDataEntryValue | undefined>
? FromSchemaWithComponents<TComponents, TRouteSchemas['request']['formData']>
: Record<string, FormDataEntryValue>
: Record<string, FormDataEntryValue>,
: Record<string, FormDataEntryValue | undefined>
: Record<string, FormDataEntryValue | undefined>,
TRouteSchemas['request'] extends { headers: JSONSchema }
? FromSchemaWithComponents<TComponents, TRouteSchemas['request']['headers']> extends Record<
string,
Expand Down Expand Up @@ -475,7 +478,7 @@ export type TypedRequestFromRouteSchemas<
>
: TypedRequest<
any,
Record<string, FormDataEntryValue>,
Record<string, FormDataEntryValue | undefined>,
Record<string, string>,
TMethod,
Record<string, string | string[]>,
Expand Down Expand Up @@ -511,7 +514,7 @@ export type AddRouteWithTypesOpts<
TPath extends string,
TTypedRequest extends TypedRequest<
any,
Record<string, FormDataEntryValue>,
Record<string, FormDataEntryValue | undefined>,
Record<string, string>,
TMethod,
Record<string, string | string[]>,
Expand Down
11 changes: 8 additions & 3 deletions packages/fets/src/zod/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,14 @@ export type RouteZodSchemas = {
export type TypedRequestFromRouteZodSchemas<
TRouteZodSchemas extends RouteZodSchemas,
TMethod extends HTTPMethod,
> = TRouteZodSchemas extends { request: Required<RouteZodSchemas>['request'] }
> = TRouteZodSchemas extends { request: RouteZodSchemas['request'] }
? TypedRequest<
TRouteZodSchemas['request'] extends { json: ZodType }
? InferZodType<TRouteZodSchemas['request']['json']>
: any,
TRouteZodSchemas['request'] extends { formData: ZodType }
? InferZodType<TRouteZodSchemas['request']['formData']>
: Record<string, FormDataEntryValue>,
: Record<string, FormDataEntryValue | undefined>,
TRouteZodSchemas['request'] extends { headers: ZodType }
? InferZodType<TRouteZodSchemas['request']['headers']>
: Record<string, string>,
Expand All @@ -45,7 +45,12 @@ export type TypedRequestFromRouteZodSchemas<
? InferZodType<TRouteZodSchemas['request']['params']>
: Record<string, any>
>
: TypedRequest<any, Record<string, FormDataEntryValue>, Record<string, string>, TMethod>;
: TypedRequest<
any,
Record<string, FormDataEntryValue | undefined>,
Record<string, string>,
TMethod
>;

export type TypedResponseFromRouteZodSchemas<TRouteZodSchemas extends RouteZodSchemas> =
TRouteZodSchemas extends { responses: StatusCodeMap<ZodType> }
Expand Down