Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix async middleware typings #165

Merged
merged 2 commits into from
May 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 1 addition & 1 deletion apps/example/src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export default function Page() {
</a>
</li>
<li>
<a className="link link-primary" href="/api/v2">
<a className="link link-primary" href="/api/v1">
SwaggerUI
</a>
</li>
Expand Down
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
Loading