Skip to content

Commit

Permalink
Fix async middleware typings
Browse files Browse the repository at this point in the history
This fixes the TS error when using an async
middleware by fixing the return types of the
middleware function. This also exports the typings
of `TypedNextRerquest`, `TypedNextApiRequest` and
`TypedNextApiResponse` so that they can be used for
custom abstractions.
  • Loading branch information
blomqma committed May 4, 2024
1 parent 8e03917 commit 1751340
Show file tree
Hide file tree
Showing 5 changed files with 53 additions and 57 deletions.
59 changes: 25 additions & 34 deletions packages/next-rest-framework/src/app-router/route-operation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,41 +20,36 @@ import { NextResponse, type NextRequest } from 'next/server';
import { type ZodSchema, type z } from 'zod';
import { type ValidMethod } from '../constants';
import { type I18NConfig } from 'next/dist/server/config-shared';
import { type ResponseCookies } from 'next/dist/server/web/spec-extension/cookies';
import { type NextURL } from 'next/dist/server/web/next-url';
import { type OpenAPIV3_1 } from 'openapi-types';
import { type ResponseCookies } from 'next/dist/compiled/@edge-runtime/cookies';

export type TypedNextRequest<
Method = keyof typeof ValidMethod,
interface TypedSearchParams<Query = BaseQuery> extends URLSearchParams {
get: <K extends keyof Query & string>(key: K) => string | null;
getAll: <K extends keyof Query & string>(key: K) => string[];
}

interface TypedNextURL<Query = BaseQuery> extends NextURL {
searchParams: TypedSearchParams<Query>;
}

export interface TypedNextRequest<
Method extends string = keyof typeof ValidMethod,
ContentType = BaseContentType,
Body = unknown,
Query = BaseQuery
> = Modify<
NextRequest,
{
method: Method;
/*! Prevent parsing JSON body for GET requests. Form requests return parsed form data as JSON when the form schema is defined. */
json: Method extends 'GET' ? never : () => Promise<Body>;
/*! Prevent parsing form data for GET and non-form requests. */
formData: Method extends 'GET'
? never
: ContentType extends FormDataContentType
? () => Promise<TypedFormData<Body>>
: never;
nextUrl: Modify<
NextURL,
{
searchParams: Modify<
URLSearchParams,
{
get: (key: keyof Query) => string | null;
getAll: (key: keyof Query) => string[];
}
>;
}
>;
}
>;
> extends NextRequest {
method: Method;
/*! Prevent parsing JSON body for GET requests. Form requests return parsed form data as JSON when the form schema is defined. */
json: Method extends 'GET' ? never : () => Promise<Body>;
/*! Prevent parsing form data for GET and non-form requests. */
formData: Method extends 'GET'
? never
: ContentType extends FormDataContentType
? () => Promise<TypedFormData<Body>>
: never;
nextUrl: TypedNextURL<Query>;
}

type TypedHeaders<ContentType extends BaseContentType> = Modify<
Record<string, string>,
Expand Down Expand Up @@ -165,11 +160,7 @@ type RouteMiddleware<
req: NextRequest,
context: { params: BaseParams },
options: InputOptions
) =>
| Promise<TypedResponse>
| TypedResponse
| Promise<OutputOptions>
| OutputOptions;
) => Promise<TypedResponse | OutputOptions> | TypedResponse | OutputOptions;

type TypedRouteHandler<
Method extends keyof typeof ValidMethod = keyof typeof ValidMethod,
Expand Down
5 changes: 4 additions & 1 deletion packages/next-rest-framework/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@ export {
docsApiRoute,
apiRoute,
apiRouteOperation,
rpcApiRoute
rpcApiRoute,
type TypedNextApiRequest,
type TypedNextApiResponse
} from './pages-router';
export {
docsRoute,
route,
routeOperation,
rpcRoute,
type TypedNextRequest,
TypedNextResponse
} from './app-router';
export { rpcOperation } from './shared';
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export type TypedNextApiRequest<
}
>;

type TypedNextApiResponse<Body, Status, ContentType> = Modify<
export type TypedNextApiResponse<Body, Status, ContentType> = Modify<
NextApiResponse<Body>,
{
status: (status: Status) => TypedNextApiResponse<Body, Status, ContentType>;
Expand Down
33 changes: 18 additions & 15 deletions packages/next-rest-framework/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,21 +179,24 @@ export type FormDataContentType =
| 'application/x-www-form-urlencoded'
| 'multipart/form-data';

export type TypedFormData<T> = Modify<
FormData,
{
append: <K extends keyof T>(name: K, value: T[K] | Blob) => void;
delete: <K extends keyof T>(name: K) => void;
get: <K extends keyof T>(name: K) => T[K];
getAll: <K extends keyof T>(name: K) => Array<T[K]>;
has: <K extends keyof T>(name: K) => boolean;
set: <K extends keyof T>(name: K, value: T[K] | Blob) => void;
forEach: <K extends keyof T>(
callbackfn: (value: T[K], key: T, parent: TypedFormData<T>) => void,
thisArg?: any
) => void;
}
>;
export interface TypedFormData<T> extends FormData {
append: <K extends keyof T>(name: K, value: Blob | string) => void;
delete: <K extends keyof T & string>(name: K) => void;
get: <K extends keyof T & string>(name: K) => T[K] & FormDataEntryValue;
getAll: <K extends keyof T & string>(
name: K
) => Array<T[K]> & FormDataEntryValue[];
has: <K extends keyof T & string>(name: K) => boolean;
set: <K extends keyof T>(name: K, value: Blob | string) => void;
forEach: <K extends keyof T & string>(
callbackfn: (
value: T[K] & FormDataEntryValue,
key: K,
parent: TypedFormData<T>
) => void,
thisArg?: any
) => void;
}

interface FormDataLikeInput {
[Symbol.iterator]: () => IterableIterator<[string, FormDataEntryValue]>;
Expand Down
11 changes: 5 additions & 6 deletions packages/next-rest-framework/tests/app-router/route.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { z } from 'zod';
import { TypedNextResponse, route, routeOperation } from '../../src/app-router';
import { DEFAULT_ERRORS, ValidMethod } from '../../src/constants';
import { createMockRouteRequest } from '../utils';
import { NextResponse } from 'next/server';
import { validateSchema } from '../../src/shared';
import { zfd } from 'zod-form-data';

Expand All @@ -25,7 +24,7 @@ describe('route', () => {
body: z.array(z.string())
}
])
.handler(() => NextResponse.json(data));
.handler(() => TypedNextResponse.json(data));

const res = await route({
testGet: getOperation('GET'),
Expand Down Expand Up @@ -353,7 +352,7 @@ describe('route', () => {
])
.handler(async (req) => {
const { foo } = await req.json();
return NextResponse.json({ foo });
return TypedNextResponse.json({ foo });
})
}).POST(req, context);

Expand Down Expand Up @@ -542,7 +541,7 @@ describe('route', () => {
const res = await route({
test: routeOperation({ method: 'GET' })
.middleware(() => {
return NextResponse.json({ foo: 'bar' }, { status: 200 });
return TypedNextResponse.json({ foo: 'bar' }, { status: 200 });
})
.handler(() => {
console.log('foo');
Expand Down Expand Up @@ -591,7 +590,7 @@ describe('route', () => {
console.log({ options: true, ...options });
console.log({ 'x-foo': req.headers.get('x-foo') });
console.log({ 'x-bar': req.headers.get('x-bar') });
return NextResponse.json(options);
return TypedNextResponse.json(options);
})
}).POST(req, context);

Expand Down Expand Up @@ -656,7 +655,7 @@ describe('route', () => {
})
.handler((_req, _ctx, options) => {
console.log('handler');
return NextResponse.json(options);
return TypedNextResponse.json(options);
})
}).GET(req, context);

Expand Down

0 comments on commit 1751340

Please sign in to comment.