Skip to content
This repository was archived by the owner on Apr 23, 2025. It is now read-only.

Commit f79902a

Browse files
authored
feat: copy nextjs adapter over to server package (zenstackhq#420)
1 parent 3ee7821 commit f79902a

File tree

5 files changed

+450
-0
lines changed

5 files changed

+450
-0
lines changed

packages/server/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
"fastify-plugin": "^4.5.0",
5353
"isomorphic-fetch": "^3.0.0",
5454
"jest": "^29.5.0",
55+
"next": "^12.3.1",
5556
"rimraf": "^3.0.2",
5657
"supertest": "^6.3.3",
5758
"ts-jest": "^29.0.5",

packages/server/src/next/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { default as NextRequestHandler } from './request-handler';
2+
export * from './request-handler';
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
import {
2+
DbClientContract,
3+
DbOperations,
4+
isPrismaClientKnownRequestError,
5+
isPrismaClientUnknownRequestError,
6+
isPrismaClientValidationError,
7+
} from '@zenstackhq/runtime';
8+
import { NextApiRequest, NextApiResponse } from 'next';
9+
import { logError } from '../api/utils';
10+
import { AdapterBaseOptions } from '../types';
11+
import { marshalToObject, unmarshalFromObject, unmarshalFromString } from '../utils';
12+
13+
/**
14+
* Options for initializing a Next.js API endpoint request handler.
15+
* @see requestHandler
16+
*/
17+
export interface RequestHandlerOptions extends AdapterBaseOptions {
18+
/**
19+
* Callback method for getting a Prisma instance for the given request/response pair.
20+
*/
21+
getPrisma: (req: NextApiRequest, res: NextApiResponse) => Promise<unknown> | unknown;
22+
}
23+
24+
/**
25+
* Creates a Next.js API endpoint request handler which encapsulates Prisma CRUD operations.
26+
*
27+
* @param options Options for initialization
28+
* @returns An API endpoint request handler
29+
*/
30+
export default function requestHandler(
31+
options: RequestHandlerOptions
32+
): (req: NextApiRequest, res: NextApiResponse) => Promise<void> {
33+
return async (req: NextApiRequest, res: NextApiResponse) => {
34+
const prisma = await options.getPrisma(req, res);
35+
if (!prisma) {
36+
sendResponse(
37+
res,
38+
500,
39+
{
40+
error: 'unable to get prisma from request context',
41+
},
42+
options.useSuperJson === true
43+
);
44+
return;
45+
}
46+
return handleRequest(req, res, prisma as DbClientContract, options);
47+
};
48+
}
49+
50+
async function handleRequest(
51+
req: NextApiRequest,
52+
res: NextApiResponse,
53+
prisma: DbClientContract,
54+
options: RequestHandlerOptions
55+
): Promise<void> {
56+
const [model, op] = req.query.path as string[];
57+
58+
const dbOp = op as keyof DbOperations;
59+
let args: unknown;
60+
let resCode = 200;
61+
const useSuperJson = options.useSuperJson === true;
62+
63+
switch (dbOp) {
64+
case 'create':
65+
case 'createMany':
66+
case 'upsert':
67+
if (req.method !== 'POST') {
68+
sendResponse(res, 400, { error: 'invalid http method' }, useSuperJson);
69+
return;
70+
}
71+
args = unmarshalFromObject(req.body, options.useSuperJson);
72+
// TODO: upsert's status code should be conditional
73+
resCode = 201;
74+
break;
75+
76+
case 'findFirst':
77+
case 'findUnique':
78+
case 'findMany':
79+
case 'aggregate':
80+
case 'groupBy':
81+
case 'count':
82+
if (req.method !== 'GET') {
83+
sendResponse(res, 400, { error: 'invalid http method' }, useSuperJson);
84+
return;
85+
}
86+
args = req.query.q ? unmarshalFromString(req.query.q as string, options.useSuperJson) : {};
87+
break;
88+
89+
case 'update':
90+
case 'updateMany':
91+
if (req.method !== 'PUT' && req.method !== 'PATCH') {
92+
sendResponse(res, 400, { error: 'invalid http method' }, useSuperJson);
93+
return;
94+
}
95+
args = unmarshalFromObject(req.body, options.useSuperJson);
96+
break;
97+
98+
case 'delete':
99+
case 'deleteMany':
100+
if (req.method !== 'DELETE') {
101+
sendResponse(res, 400, { error: 'invalid http method' }, useSuperJson);
102+
return;
103+
}
104+
args = req.query.q ? unmarshalFromString(req.query.q as string, options.useSuperJson) : {};
105+
break;
106+
107+
default:
108+
sendResponse(res, 400, { error: `unknown method name: ${op}` }, useSuperJson);
109+
return;
110+
}
111+
112+
try {
113+
if (!prisma[model]) {
114+
sendResponse(res, 400, { error: `unknown model name: ${model}` }, useSuperJson);
115+
return;
116+
}
117+
const result = await prisma[model][dbOp](args);
118+
sendResponse(res, resCode, result, useSuperJson);
119+
} catch (err) {
120+
if (isPrismaClientKnownRequestError(err)) {
121+
logError(options.logger, err.message, err.code);
122+
if (err.code === 'P2004') {
123+
// rejected by policy
124+
sendResponse(
125+
res,
126+
403,
127+
{
128+
prisma: true,
129+
rejectedByPolicy: true,
130+
code: err.code,
131+
message: err.message,
132+
reason: err.meta?.reason,
133+
},
134+
useSuperJson
135+
);
136+
} else {
137+
sendResponse(
138+
res,
139+
400,
140+
{
141+
prisma: true,
142+
code: err.code,
143+
message: err.message,
144+
reason: err.meta?.reason,
145+
},
146+
useSuperJson
147+
);
148+
}
149+
} else if (isPrismaClientUnknownRequestError(err) || isPrismaClientValidationError(err)) {
150+
logError(options.logger, err.message);
151+
sendResponse(
152+
res,
153+
400,
154+
{
155+
prisma: true,
156+
message: err.message,
157+
},
158+
useSuperJson
159+
);
160+
} else {
161+
const _err = err as Error;
162+
logError(options.logger, _err.message + (_err.stack ? '\n' + _err.stack : ''));
163+
sendResponse(
164+
res,
165+
500,
166+
{
167+
message: (err as Error).message,
168+
},
169+
useSuperJson
170+
);
171+
}
172+
}
173+
}
174+
175+
function sendResponse(res: NextApiResponse, status: number, data: unknown, useSuperJson: boolean): void {
176+
res.status(status).send(marshalToObject(data, useSuperJson));
177+
}

0 commit comments

Comments
 (0)