@zenstackhq/server 2.2.4 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/api/base.js +1 -1
- package/api/base.js.map +1 -1
- package/api/rest/index.js +482 -510
- package/api/rest/index.js.map +1 -1
- package/api/rpc/index.js +145 -168
- package/api/rpc/index.js.map +1 -1
- package/api/utils.js +7 -8
- package/api/utils.js.map +1 -1
- package/express/middleware.js +4 -13
- package/express/middleware.js.map +1 -1
- package/fastify/plugin.d.ts +1 -1
- package/fastify/plugin.js +6 -16
- package/fastify/plugin.js.map +1 -1
- package/nestjs/zenstack.module.d.ts +1 -1
- package/nestjs/zenstack.module.js +9 -19
- package/nestjs/zenstack.module.js.map +1 -1
- package/next/app-route-handler.js +7 -16
- package/next/app-route-handler.js.map +1 -1
- package/next/index.js +1 -2
- package/next/index.js.map +1 -1
- package/next/pages-route-handler.js +5 -14
- package/next/pages-route-handler.js.map +1 -1
- package/nuxt/handler.js +8 -19
- package/nuxt/handler.js.map +1 -1
- package/package.json +4 -4
- package/shared.js +6 -8
- package/shared.js.map +1 -1
- package/sveltekit/handler.js +10 -19
- package/sveltekit/handler.js.map +1 -1
package/api/rest/index.js
CHANGED
|
@@ -1,18 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
2
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
4
|
};
|
|
14
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
exports.
|
|
6
|
+
exports.default = makeHandler;
|
|
7
|
+
exports.RestApiHandler = makeHandler;
|
|
8
|
+
exports.default = makeHandler;
|
|
9
|
+
exports.RestApiHandler = makeHandler;
|
|
16
10
|
const runtime_1 = require("@zenstackhq/runtime");
|
|
17
11
|
const change_case_1 = require("change-case");
|
|
18
12
|
const lower_case_first_1 = require("lower-case-first");
|
|
@@ -167,328 +161,316 @@ class RequestHandler extends base_1.APIHandlerBase {
|
|
|
167
161
|
data: zod_1.default.array(zod_1.default.object({ type: zod_1.default.string(), id: zod_1.default.union([zod_1.default.string(), zod_1.default.number()]) })),
|
|
168
162
|
});
|
|
169
163
|
}
|
|
170
|
-
handleRequest(
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
return yield this.processSingleRead(prisma, match.type, match.id, query);
|
|
193
|
-
}
|
|
194
|
-
match = urlPatterns.fetchRelationship.match(path);
|
|
195
|
-
if (match) {
|
|
196
|
-
// fetch related resource(s)
|
|
197
|
-
return yield this.processFetchRelated(prisma, match.type, match.id, match.relationship, query);
|
|
198
|
-
}
|
|
199
|
-
match = urlPatterns.relationship.match(path);
|
|
200
|
-
if (match) {
|
|
201
|
-
// read relationship
|
|
202
|
-
return yield this.processReadRelationship(prisma, match.type, match.id, match.relationship, query, modelMeta);
|
|
203
|
-
}
|
|
204
|
-
match = urlPatterns.collection.match(path);
|
|
205
|
-
if (match) {
|
|
206
|
-
// collection read
|
|
207
|
-
return yield this.processCollectionRead(prisma, match.type, query);
|
|
208
|
-
}
|
|
209
|
-
return this.makeError('invalidPath');
|
|
164
|
+
async handleRequest({ prisma, method, path, query, requestBody, logger, modelMeta, zodSchemas, }) {
|
|
165
|
+
modelMeta = modelMeta ?? this.defaultModelMeta;
|
|
166
|
+
if (!modelMeta) {
|
|
167
|
+
throw new Error('Model metadata is not provided or loaded from default location');
|
|
168
|
+
}
|
|
169
|
+
if (!this.serializers) {
|
|
170
|
+
this.buildSerializers(modelMeta);
|
|
171
|
+
}
|
|
172
|
+
if (!this.typeMap) {
|
|
173
|
+
this.buildTypeMap(logger, modelMeta);
|
|
174
|
+
}
|
|
175
|
+
method = method.toUpperCase();
|
|
176
|
+
if (!path.startsWith('/')) {
|
|
177
|
+
path = '/' + path;
|
|
178
|
+
}
|
|
179
|
+
try {
|
|
180
|
+
switch (method) {
|
|
181
|
+
case 'GET': {
|
|
182
|
+
let match = urlPatterns.single.match(path);
|
|
183
|
+
if (match) {
|
|
184
|
+
// single resource read
|
|
185
|
+
return await this.processSingleRead(prisma, match.type, match.id, query);
|
|
210
186
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
let match = urlPatterns.collection.match(path);
|
|
216
|
-
if (match) {
|
|
217
|
-
// resource creation
|
|
218
|
-
return yield this.processCreate(prisma, match.type, query, requestBody, zodSchemas);
|
|
219
|
-
}
|
|
220
|
-
match = urlPatterns.relationship.match(path);
|
|
221
|
-
if (match) {
|
|
222
|
-
// relationship creation (collection relationship only)
|
|
223
|
-
return yield this.processRelationshipCRUD(prisma, 'create', match.type, match.id, match.relationship, query, requestBody);
|
|
224
|
-
}
|
|
225
|
-
return this.makeError('invalidPath');
|
|
187
|
+
match = urlPatterns.fetchRelationship.match(path);
|
|
188
|
+
if (match) {
|
|
189
|
+
// fetch related resource(s)
|
|
190
|
+
return await this.processFetchRelated(prisma, match.type, match.id, match.relationship, query);
|
|
226
191
|
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
return this.makeError('invalidPayload');
|
|
232
|
-
}
|
|
233
|
-
let match = urlPatterns.single.match(path);
|
|
234
|
-
if (match) {
|
|
235
|
-
// resource update
|
|
236
|
-
return yield this.processUpdate(prisma, match.type, match.id, query, requestBody, zodSchemas);
|
|
237
|
-
}
|
|
238
|
-
match = urlPatterns.relationship.match(path);
|
|
239
|
-
if (match) {
|
|
240
|
-
// relationship update
|
|
241
|
-
return yield this.processRelationshipCRUD(prisma, 'update', match.type, match.id, match.relationship, query, requestBody);
|
|
242
|
-
}
|
|
243
|
-
return this.makeError('invalidPath');
|
|
192
|
+
match = urlPatterns.relationship.match(path);
|
|
193
|
+
if (match) {
|
|
194
|
+
// read relationship
|
|
195
|
+
return await this.processReadRelationship(prisma, match.type, match.id, match.relationship, query, modelMeta);
|
|
244
196
|
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
return yield this.processDelete(prisma, match.type, match.id);
|
|
250
|
-
}
|
|
251
|
-
match = urlPatterns.relationship.match(path);
|
|
252
|
-
if (match) {
|
|
253
|
-
// relationship deletion (collection relationship only)
|
|
254
|
-
return yield this.processRelationshipCRUD(prisma, 'delete', match.type, match.id, match.relationship, query, requestBody);
|
|
255
|
-
}
|
|
256
|
-
return this.makeError('invalidPath');
|
|
197
|
+
match = urlPatterns.collection.match(path);
|
|
198
|
+
if (match) {
|
|
199
|
+
// collection read
|
|
200
|
+
return await this.processCollectionRead(prisma, match.type, query);
|
|
257
201
|
}
|
|
258
|
-
|
|
259
|
-
return this.makeError('invalidPath');
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
catch (err) {
|
|
263
|
-
if (err instanceof InvalidValueError) {
|
|
264
|
-
return this.makeError('invalidValue', err.message);
|
|
202
|
+
return this.makeError('invalidPath');
|
|
265
203
|
}
|
|
266
|
-
|
|
267
|
-
|
|
204
|
+
case 'POST': {
|
|
205
|
+
if (!requestBody) {
|
|
206
|
+
return this.makeError('invalidPayload');
|
|
207
|
+
}
|
|
208
|
+
let match = urlPatterns.collection.match(path);
|
|
209
|
+
if (match) {
|
|
210
|
+
// resource creation
|
|
211
|
+
return await this.processCreate(prisma, match.type, query, requestBody, zodSchemas);
|
|
212
|
+
}
|
|
213
|
+
match = urlPatterns.relationship.match(path);
|
|
214
|
+
if (match) {
|
|
215
|
+
// relationship creation (collection relationship only)
|
|
216
|
+
return await this.processRelationshipCRUD(prisma, 'create', match.type, match.id, match.relationship, query, requestBody);
|
|
217
|
+
}
|
|
218
|
+
return this.makeError('invalidPath');
|
|
268
219
|
}
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
return error;
|
|
220
|
+
// TODO: PUT for full update
|
|
221
|
+
case 'PUT':
|
|
222
|
+
case 'PATCH': {
|
|
223
|
+
if (!requestBody) {
|
|
224
|
+
return this.makeError('invalidPayload');
|
|
225
|
+
}
|
|
226
|
+
let match = urlPatterns.single.match(path);
|
|
227
|
+
if (match) {
|
|
228
|
+
// resource update
|
|
229
|
+
return await this.processUpdate(prisma, match.type, match.id, query, requestBody, zodSchemas);
|
|
230
|
+
}
|
|
231
|
+
match = urlPatterns.relationship.match(path);
|
|
232
|
+
if (match) {
|
|
233
|
+
// relationship update
|
|
234
|
+
return await this.processRelationshipCRUD(prisma, 'update', match.type, match.id, match.relationship, query, requestBody);
|
|
235
|
+
}
|
|
236
|
+
return this.makeError('invalidPath');
|
|
287
237
|
}
|
|
288
|
-
|
|
289
|
-
|
|
238
|
+
case 'DELETE': {
|
|
239
|
+
let match = urlPatterns.single.match(path);
|
|
240
|
+
if (match) {
|
|
241
|
+
// resource deletion
|
|
242
|
+
return await this.processDelete(prisma, match.type, match.id);
|
|
243
|
+
}
|
|
244
|
+
match = urlPatterns.relationship.match(path);
|
|
245
|
+
if (match) {
|
|
246
|
+
// relationship deletion (collection relationship only)
|
|
247
|
+
return await this.processRelationshipCRUD(prisma, 'delete', match.type, match.id, match.relationship, query, requestBody);
|
|
248
|
+
}
|
|
249
|
+
return this.makeError('invalidPath');
|
|
290
250
|
}
|
|
291
|
-
|
|
251
|
+
default:
|
|
252
|
+
return this.makeError('invalidPath');
|
|
292
253
|
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
body: yield this.serializeItems(type, entity, { include }),
|
|
298
|
-
};
|
|
254
|
+
}
|
|
255
|
+
catch (err) {
|
|
256
|
+
if (err instanceof InvalidValueError) {
|
|
257
|
+
return this.makeError('invalidValue', err.message);
|
|
299
258
|
}
|
|
300
259
|
else {
|
|
301
|
-
return this.
|
|
260
|
+
return this.handlePrismaError(err);
|
|
302
261
|
}
|
|
303
|
-
}
|
|
262
|
+
}
|
|
304
263
|
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
264
|
+
async processSingleRead(prisma, type, resourceId, query) {
|
|
265
|
+
const typeInfo = this.typeMap[type];
|
|
266
|
+
if (!typeInfo) {
|
|
267
|
+
return this.makeUnsupportedModelError(type);
|
|
268
|
+
}
|
|
269
|
+
const args = { where: this.makeIdFilter(typeInfo.idField, typeInfo.idFieldType, resourceId) };
|
|
270
|
+
// include IDs of relation fields so that they can be serialized
|
|
271
|
+
this.includeRelationshipIds(type, args, 'include');
|
|
272
|
+
// handle "include" query parameter
|
|
273
|
+
let include;
|
|
274
|
+
if (query?.include) {
|
|
275
|
+
const { select, error, allIncludes } = this.buildRelationSelect(type, query.include);
|
|
276
|
+
if (error) {
|
|
277
|
+
return error;
|
|
315
278
|
}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
let include;
|
|
319
|
-
if (query === null || query === void 0 ? void 0 : query.include) {
|
|
320
|
-
const { select: relationSelect, error, allIncludes } = this.buildRelationSelect(type, query.include);
|
|
321
|
-
if (error) {
|
|
322
|
-
return error;
|
|
323
|
-
}
|
|
324
|
-
// trim the leading `$relationship.` from the include paths
|
|
325
|
-
include = allIncludes
|
|
326
|
-
.filter((i) => i.startsWith(`${relationship}.`))
|
|
327
|
-
.map((i) => i.substring(`${relationship}.`.length));
|
|
328
|
-
select = relationSelect;
|
|
279
|
+
if (select) {
|
|
280
|
+
args.include = { ...args.include, ...select };
|
|
329
281
|
}
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
282
|
+
include = allIncludes;
|
|
283
|
+
}
|
|
284
|
+
const entity = await prisma[type].findUnique(args);
|
|
285
|
+
if (entity) {
|
|
286
|
+
return {
|
|
287
|
+
status: 200,
|
|
288
|
+
body: await this.serializeItems(type, entity, { include }),
|
|
334
289
|
};
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
return error;
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
const entity = yield prisma[type].findUnique(args);
|
|
343
|
-
let paginator;
|
|
344
|
-
if (((_a = entity === null || entity === void 0 ? void 0 : entity._count) === null || _a === void 0 ? void 0 : _a[relationship]) !== undefined) {
|
|
345
|
-
// build up paginator
|
|
346
|
-
const total = (_b = entity === null || entity === void 0 ? void 0 : entity._count) === null || _b === void 0 ? void 0 : _b[relationship];
|
|
347
|
-
const url = this.makeNormalizedUrl(`/${type}/${resourceId}/${relationship}`, query);
|
|
348
|
-
const { offset, limit } = this.getPagination(query);
|
|
349
|
-
paginator = this.makePaginator(url, offset, limit, total);
|
|
350
|
-
}
|
|
351
|
-
if (entity === null || entity === void 0 ? void 0 : entity[relationship]) {
|
|
352
|
-
return {
|
|
353
|
-
status: 200,
|
|
354
|
-
body: yield this.serializeItems(relationInfo.type, entity[relationship], {
|
|
355
|
-
linkers: {
|
|
356
|
-
document: new ts_japi_1.Linker(() => this.makeLinkUrl(`/${type}/${resourceId}/${relationship}`)),
|
|
357
|
-
paginator,
|
|
358
|
-
},
|
|
359
|
-
include,
|
|
360
|
-
}),
|
|
361
|
-
};
|
|
362
|
-
}
|
|
363
|
-
else {
|
|
364
|
-
return this.makeError('notFound');
|
|
365
|
-
}
|
|
366
|
-
});
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
return this.makeError('notFound');
|
|
293
|
+
}
|
|
367
294
|
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
// this.includeRelationshipIds(type, args, 'select');
|
|
385
|
-
args.select = Object.assign(Object.assign({}, args.select), { [relationship]: { select: this.makeIdSelect(relationInfo.type, modelMeta) } });
|
|
386
|
-
let paginator;
|
|
387
|
-
if (relationInfo.isCollection) {
|
|
388
|
-
// if related data is a collection, it can be filtered, sorted, and paginated
|
|
389
|
-
const error = this.injectRelationQuery(relationInfo.type, args.select, relationship, query);
|
|
390
|
-
if (error) {
|
|
391
|
-
return error;
|
|
392
|
-
}
|
|
295
|
+
async processFetchRelated(prisma, type, resourceId, relationship, query) {
|
|
296
|
+
const typeInfo = this.typeMap[type];
|
|
297
|
+
if (!typeInfo) {
|
|
298
|
+
return this.makeUnsupportedModelError(type);
|
|
299
|
+
}
|
|
300
|
+
const relationInfo = typeInfo.relationships[relationship];
|
|
301
|
+
if (!relationInfo) {
|
|
302
|
+
return this.makeUnsupportedRelationshipError(type, relationship, 404);
|
|
303
|
+
}
|
|
304
|
+
let select;
|
|
305
|
+
// handle "include" query parameter
|
|
306
|
+
let include;
|
|
307
|
+
if (query?.include) {
|
|
308
|
+
const { select: relationSelect, error, allIncludes } = this.buildRelationSelect(type, query.include);
|
|
309
|
+
if (error) {
|
|
310
|
+
return error;
|
|
393
311
|
}
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
312
|
+
// trim the leading `$relationship.` from the include paths
|
|
313
|
+
include = allIncludes
|
|
314
|
+
.filter((i) => i.startsWith(`${relationship}.`))
|
|
315
|
+
.map((i) => i.substring(`${relationship}.`.length));
|
|
316
|
+
select = relationSelect;
|
|
317
|
+
}
|
|
318
|
+
select = select ?? { [relationship]: true };
|
|
319
|
+
const args = {
|
|
320
|
+
where: this.makeIdFilter(typeInfo.idField, typeInfo.idFieldType, resourceId),
|
|
321
|
+
select,
|
|
322
|
+
};
|
|
323
|
+
if (relationInfo.isCollection) {
|
|
324
|
+
// if related data is a collection, it can be filtered, sorted, and paginated
|
|
325
|
+
const error = this.injectRelationQuery(relationInfo.type, select, relationship, query);
|
|
326
|
+
if (error) {
|
|
327
|
+
return error;
|
|
401
328
|
}
|
|
402
|
-
|
|
403
|
-
|
|
329
|
+
}
|
|
330
|
+
const entity = await prisma[type].findUnique(args);
|
|
331
|
+
let paginator;
|
|
332
|
+
if (entity?._count?.[relationship] !== undefined) {
|
|
333
|
+
// build up paginator
|
|
334
|
+
const total = entity?._count?.[relationship];
|
|
335
|
+
const url = this.makeNormalizedUrl(`/${type}/${resourceId}/${relationship}`, query);
|
|
336
|
+
const { offset, limit } = this.getPagination(query);
|
|
337
|
+
paginator = this.makePaginator(url, offset, limit, total);
|
|
338
|
+
}
|
|
339
|
+
if (entity?.[relationship]) {
|
|
340
|
+
return {
|
|
341
|
+
status: 200,
|
|
342
|
+
body: await this.serializeItems(relationInfo.type, entity[relationship], {
|
|
404
343
|
linkers: {
|
|
405
|
-
document: new ts_japi_1.Linker(() => this.makeLinkUrl(`/${type}/${resourceId}
|
|
344
|
+
document: new ts_japi_1.Linker(() => this.makeLinkUrl(`/${type}/${resourceId}/${relationship}`)),
|
|
406
345
|
paginator,
|
|
407
346
|
},
|
|
408
|
-
|
|
409
|
-
})
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
else {
|
|
416
|
-
return this.makeError('notFound');
|
|
417
|
-
}
|
|
418
|
-
});
|
|
347
|
+
include,
|
|
348
|
+
}),
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
else {
|
|
352
|
+
return this.makeError('notFound');
|
|
353
|
+
}
|
|
419
354
|
}
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
// include IDs of relation fields so that they can be serialized
|
|
443
|
-
this.includeRelationshipIds(type, args, 'include');
|
|
444
|
-
// handle "include" query parameter
|
|
445
|
-
let include;
|
|
446
|
-
if (query === null || query === void 0 ? void 0 : query.include) {
|
|
447
|
-
const { select, error, allIncludes } = this.buildRelationSelect(type, query.include);
|
|
448
|
-
if (error) {
|
|
449
|
-
return error;
|
|
450
|
-
}
|
|
451
|
-
if (select) {
|
|
452
|
-
args.include = Object.assign(Object.assign({}, args.include), select);
|
|
453
|
-
}
|
|
454
|
-
include = allIncludes;
|
|
355
|
+
async processReadRelationship(prisma, type, resourceId, relationship, query, modelMeta) {
|
|
356
|
+
const typeInfo = this.typeMap[type];
|
|
357
|
+
if (!typeInfo) {
|
|
358
|
+
return this.makeUnsupportedModelError(type);
|
|
359
|
+
}
|
|
360
|
+
const relationInfo = typeInfo.relationships[relationship];
|
|
361
|
+
if (!relationInfo) {
|
|
362
|
+
return this.makeUnsupportedRelationshipError(type, relationship, 404);
|
|
363
|
+
}
|
|
364
|
+
const args = {
|
|
365
|
+
where: this.makeIdFilter(typeInfo.idField, typeInfo.idFieldType, resourceId),
|
|
366
|
+
select: this.makeIdSelect(type, modelMeta),
|
|
367
|
+
};
|
|
368
|
+
// include IDs of relation fields so that they can be serialized
|
|
369
|
+
// this.includeRelationshipIds(type, args, 'select');
|
|
370
|
+
args.select = { ...args.select, [relationship]: { select: this.makeIdSelect(relationInfo.type, modelMeta) } };
|
|
371
|
+
let paginator;
|
|
372
|
+
if (relationInfo.isCollection) {
|
|
373
|
+
// if related data is a collection, it can be filtered, sorted, and paginated
|
|
374
|
+
const error = this.injectRelationQuery(relationInfo.type, args.select, relationship, query);
|
|
375
|
+
if (error) {
|
|
376
|
+
return error;
|
|
455
377
|
}
|
|
378
|
+
}
|
|
379
|
+
const entity = await prisma[type].findUnique(args);
|
|
380
|
+
if (entity?._count?.[relationship] !== undefined) {
|
|
381
|
+
// build up paginator
|
|
382
|
+
const total = entity?._count?.[relationship];
|
|
383
|
+
const url = this.makeNormalizedUrl(`/${type}/${resourceId}/relationships/${relationship}`, query);
|
|
456
384
|
const { offset, limit } = this.getPagination(query);
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
385
|
+
paginator = this.makePaginator(url, offset, limit, total);
|
|
386
|
+
}
|
|
387
|
+
if (entity?.[relationship]) {
|
|
388
|
+
const serialized = await this.serializeItems(relationInfo.type, entity[relationship], {
|
|
389
|
+
linkers: {
|
|
390
|
+
document: new ts_japi_1.Linker(() => this.makeLinkUrl(`/${type}/${resourceId}/relationships/${relationship}`)),
|
|
391
|
+
paginator,
|
|
392
|
+
},
|
|
393
|
+
onlyIdentifier: true,
|
|
394
|
+
});
|
|
395
|
+
return {
|
|
396
|
+
status: 200,
|
|
397
|
+
body: serialized,
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
else {
|
|
401
|
+
return this.makeError('notFound');
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
async processCollectionRead(prisma, type, query) {
|
|
405
|
+
const typeInfo = this.typeMap[type];
|
|
406
|
+
if (!typeInfo) {
|
|
407
|
+
return this.makeUnsupportedModelError(type);
|
|
408
|
+
}
|
|
409
|
+
const args = {};
|
|
410
|
+
// add filter
|
|
411
|
+
const { filter, error: filterError } = this.buildFilter(type, query);
|
|
412
|
+
if (filterError) {
|
|
413
|
+
return filterError;
|
|
414
|
+
}
|
|
415
|
+
if (filter) {
|
|
416
|
+
args.where = filter;
|
|
417
|
+
}
|
|
418
|
+
const { sort, error: sortError } = this.buildSort(type, query);
|
|
419
|
+
if (sortError) {
|
|
420
|
+
return sortError;
|
|
421
|
+
}
|
|
422
|
+
if (sort) {
|
|
423
|
+
args.orderBy = sort;
|
|
424
|
+
}
|
|
425
|
+
// include IDs of relation fields so that they can be serialized
|
|
426
|
+
this.includeRelationshipIds(type, args, 'include');
|
|
427
|
+
// handle "include" query parameter
|
|
428
|
+
let include;
|
|
429
|
+
if (query?.include) {
|
|
430
|
+
const { select, error, allIncludes } = this.buildRelationSelect(type, query.include);
|
|
431
|
+
if (error) {
|
|
432
|
+
return error;
|
|
469
433
|
}
|
|
470
|
-
|
|
471
|
-
args.
|
|
472
|
-
const [entities, count] = yield Promise.all([
|
|
473
|
-
prisma[type].findMany(args),
|
|
474
|
-
prisma[type].count({ where: args.where }),
|
|
475
|
-
]);
|
|
476
|
-
const total = count;
|
|
477
|
-
const url = this.makeNormalizedUrl(`/${type}`, query);
|
|
478
|
-
const options = {
|
|
479
|
-
include,
|
|
480
|
-
linkers: {
|
|
481
|
-
paginator: this.makePaginator(url, offset, limit, total),
|
|
482
|
-
},
|
|
483
|
-
};
|
|
484
|
-
const body = yield this.serializeItems(type, entities, options);
|
|
485
|
-
body.meta = this.addTotalCountToMeta(body.meta, total);
|
|
486
|
-
return {
|
|
487
|
-
status: 200,
|
|
488
|
-
body: body,
|
|
489
|
-
};
|
|
434
|
+
if (select) {
|
|
435
|
+
args.include = { ...args.include, ...select };
|
|
490
436
|
}
|
|
491
|
-
|
|
437
|
+
include = allIncludes;
|
|
438
|
+
}
|
|
439
|
+
const { offset, limit } = this.getPagination(query);
|
|
440
|
+
if (offset > 0) {
|
|
441
|
+
args.skip = offset;
|
|
442
|
+
}
|
|
443
|
+
if (limit === Infinity) {
|
|
444
|
+
const entities = await prisma[type].findMany(args);
|
|
445
|
+
const body = await this.serializeItems(type, entities, { include });
|
|
446
|
+
const total = entities.length;
|
|
447
|
+
body.meta = this.addTotalCountToMeta(body.meta, total);
|
|
448
|
+
return {
|
|
449
|
+
status: 200,
|
|
450
|
+
body: body,
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
else {
|
|
454
|
+
args.take = limit;
|
|
455
|
+
const [entities, count] = await Promise.all([
|
|
456
|
+
prisma[type].findMany(args),
|
|
457
|
+
prisma[type].count({ where: args.where }),
|
|
458
|
+
]);
|
|
459
|
+
const total = count;
|
|
460
|
+
const url = this.makeNormalizedUrl(`/${type}`, query);
|
|
461
|
+
const options = {
|
|
462
|
+
include,
|
|
463
|
+
linkers: {
|
|
464
|
+
paginator: this.makePaginator(url, offset, limit, total),
|
|
465
|
+
},
|
|
466
|
+
};
|
|
467
|
+
const body = await this.serializeItems(type, entities, options);
|
|
468
|
+
body.meta = this.addTotalCountToMeta(body.meta, total);
|
|
469
|
+
return {
|
|
470
|
+
status: 200,
|
|
471
|
+
body: body,
|
|
472
|
+
};
|
|
473
|
+
}
|
|
492
474
|
}
|
|
493
475
|
addTotalCountToMeta(meta, total) {
|
|
494
476
|
return meta ? Object.assign(meta, { total }) : Object.assign({}, { total });
|
|
@@ -518,9 +500,8 @@ class RequestHandler extends base_1.APIHandlerBase {
|
|
|
518
500
|
}));
|
|
519
501
|
}
|
|
520
502
|
processRequestBody(type, requestBody, zodSchemas, mode) {
|
|
521
|
-
var _a, _b;
|
|
522
503
|
let body = requestBody;
|
|
523
|
-
if (
|
|
504
|
+
if (body.meta?.serialization) {
|
|
524
505
|
// superjson deserialize body if a serialization meta is provided
|
|
525
506
|
body = superjson_1.default.deserialize({ json: body, meta: body.meta.serialization });
|
|
526
507
|
}
|
|
@@ -529,7 +510,7 @@ class RequestHandler extends base_1.APIHandlerBase {
|
|
|
529
510
|
if (attributes) {
|
|
530
511
|
const schemaName = `${(0, upper_case_first_1.upperCaseFirst)(type)}${(0, upper_case_first_1.upperCaseFirst)(mode)}Schema`;
|
|
531
512
|
// zod-parse attributes if a schema is provided
|
|
532
|
-
const payloadSchema =
|
|
513
|
+
const payloadSchema = zodSchemas?.models?.[schemaName];
|
|
533
514
|
if (payloadSchema) {
|
|
534
515
|
const parsed = payloadSchema.safeParse(attributes);
|
|
535
516
|
if (!parsed.success) {
|
|
@@ -541,191 +522,189 @@ class RequestHandler extends base_1.APIHandlerBase {
|
|
|
541
522
|
}
|
|
542
523
|
return { attributes, relationships: parsed.data.relationships };
|
|
543
524
|
}
|
|
544
|
-
processCreate(prisma, type, _query, requestBody, zodSchemas) {
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
return this.makeError('invalidRelationData');
|
|
560
|
-
}
|
|
561
|
-
const relationInfo = typeInfo.relationships[key];
|
|
562
|
-
if (!relationInfo) {
|
|
563
|
-
return this.makeUnsupportedRelationshipError(type, key, 400);
|
|
564
|
-
}
|
|
565
|
-
if (relationInfo.isCollection) {
|
|
566
|
-
createPayload.data[key] = {
|
|
567
|
-
connect: (0, runtime_1.enumerate)(data.data).map((item) => ({
|
|
568
|
-
[relationInfo.idField]: this.coerce(relationInfo.idFieldType, item.id),
|
|
569
|
-
})),
|
|
570
|
-
};
|
|
571
|
-
}
|
|
572
|
-
else {
|
|
573
|
-
if (typeof data.data !== 'object') {
|
|
574
|
-
return this.makeError('invalidRelationData');
|
|
575
|
-
}
|
|
576
|
-
createPayload.data[key] = {
|
|
577
|
-
connect: { [relationInfo.idField]: this.coerce(relationInfo.idFieldType, data.data.id) },
|
|
578
|
-
};
|
|
579
|
-
}
|
|
580
|
-
// make sure ID fields are included for result serialization
|
|
581
|
-
createPayload.include = Object.assign(Object.assign({}, createPayload.include), { [key]: { select: { [relationInfo.idField]: true } } });
|
|
525
|
+
async processCreate(prisma, type, _query, requestBody, zodSchemas) {
|
|
526
|
+
const typeInfo = this.typeMap[type];
|
|
527
|
+
if (!typeInfo) {
|
|
528
|
+
return this.makeUnsupportedModelError(type);
|
|
529
|
+
}
|
|
530
|
+
const { error, attributes, relationships } = this.processRequestBody(type, requestBody, zodSchemas, 'create');
|
|
531
|
+
if (error) {
|
|
532
|
+
return error;
|
|
533
|
+
}
|
|
534
|
+
const createPayload = { data: { ...attributes } };
|
|
535
|
+
// turn relationship payload into Prisma connect objects
|
|
536
|
+
if (relationships) {
|
|
537
|
+
for (const [key, data] of Object.entries(relationships)) {
|
|
538
|
+
if (!data?.data) {
|
|
539
|
+
return this.makeError('invalidRelationData');
|
|
582
540
|
}
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
status: 201,
|
|
587
|
-
body: yield this.serializeItems(type, entity),
|
|
588
|
-
};
|
|
589
|
-
});
|
|
590
|
-
}
|
|
591
|
-
processRelationshipCRUD(prisma, mode, type, resourceId, relationship, query, requestBody) {
|
|
592
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
593
|
-
const typeInfo = this.typeMap[type];
|
|
594
|
-
if (!typeInfo) {
|
|
595
|
-
return this.makeUnsupportedModelError(type);
|
|
596
|
-
}
|
|
597
|
-
const relationInfo = typeInfo.relationships[relationship];
|
|
598
|
-
if (!relationInfo) {
|
|
599
|
-
return this.makeUnsupportedRelationshipError(type, relationship, 404);
|
|
600
|
-
}
|
|
601
|
-
if (!relationInfo.isCollection && mode !== 'update') {
|
|
602
|
-
// to-one relation can only be updated
|
|
603
|
-
return this.makeError('invalidVerb');
|
|
604
|
-
}
|
|
605
|
-
const updateArgs = {
|
|
606
|
-
where: this.makeIdFilter(typeInfo.idField, typeInfo.idFieldType, resourceId),
|
|
607
|
-
select: { [typeInfo.idField]: true, [relationship]: { select: { [relationInfo.idField]: true } } },
|
|
608
|
-
};
|
|
609
|
-
if (!relationInfo.isCollection) {
|
|
610
|
-
// zod-parse payload
|
|
611
|
-
const parsed = this.updateSingleRelationSchema.safeParse(requestBody);
|
|
612
|
-
if (!parsed.success) {
|
|
613
|
-
return this.makeError('invalidPayload', (0, zod_validation_error_1.fromZodError)(parsed.error).message, undefined, runtime_1.CrudFailureReason.DATA_VALIDATION_VIOLATION, parsed.error);
|
|
541
|
+
const relationInfo = typeInfo.relationships[key];
|
|
542
|
+
if (!relationInfo) {
|
|
543
|
+
return this.makeUnsupportedRelationshipError(type, key, 400);
|
|
614
544
|
}
|
|
615
|
-
if (
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
// set null -> disconnect
|
|
621
|
-
updateArgs.data = {
|
|
622
|
-
[relationship]: {
|
|
623
|
-
disconnect: true,
|
|
624
|
-
},
|
|
545
|
+
if (relationInfo.isCollection) {
|
|
546
|
+
createPayload.data[key] = {
|
|
547
|
+
connect: (0, runtime_1.enumerate)(data.data).map((item) => ({
|
|
548
|
+
[relationInfo.idField]: this.coerce(relationInfo.idFieldType, item.id),
|
|
549
|
+
})),
|
|
625
550
|
};
|
|
626
551
|
}
|
|
627
552
|
else {
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
},
|
|
553
|
+
if (typeof data.data !== 'object') {
|
|
554
|
+
return this.makeError('invalidRelationData');
|
|
555
|
+
}
|
|
556
|
+
createPayload.data[key] = {
|
|
557
|
+
connect: { [relationInfo.idField]: this.coerce(relationInfo.idFieldType, data.data.id) },
|
|
634
558
|
};
|
|
635
559
|
}
|
|
560
|
+
// make sure ID fields are included for result serialization
|
|
561
|
+
createPayload.include = {
|
|
562
|
+
...createPayload.include,
|
|
563
|
+
[key]: { select: { [relationInfo.idField]: true } },
|
|
564
|
+
};
|
|
636
565
|
}
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
566
|
+
}
|
|
567
|
+
const entity = await prisma[type].create(createPayload);
|
|
568
|
+
return {
|
|
569
|
+
status: 201,
|
|
570
|
+
body: await this.serializeItems(type, entity),
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
async processRelationshipCRUD(prisma, mode, type, resourceId, relationship, query, requestBody) {
|
|
574
|
+
const typeInfo = this.typeMap[type];
|
|
575
|
+
if (!typeInfo) {
|
|
576
|
+
return this.makeUnsupportedModelError(type);
|
|
577
|
+
}
|
|
578
|
+
const relationInfo = typeInfo.relationships[relationship];
|
|
579
|
+
if (!relationInfo) {
|
|
580
|
+
return this.makeUnsupportedRelationshipError(type, relationship, 404);
|
|
581
|
+
}
|
|
582
|
+
if (!relationInfo.isCollection && mode !== 'update') {
|
|
583
|
+
// to-one relation can only be updated
|
|
584
|
+
return this.makeError('invalidVerb');
|
|
585
|
+
}
|
|
586
|
+
const updateArgs = {
|
|
587
|
+
where: this.makeIdFilter(typeInfo.idField, typeInfo.idFieldType, resourceId),
|
|
588
|
+
select: { [typeInfo.idField]: true, [relationship]: { select: { [relationInfo.idField]: true } } },
|
|
589
|
+
};
|
|
590
|
+
if (!relationInfo.isCollection) {
|
|
591
|
+
// zod-parse payload
|
|
592
|
+
const parsed = this.updateSingleRelationSchema.safeParse(requestBody);
|
|
593
|
+
if (!parsed.success) {
|
|
594
|
+
return this.makeError('invalidPayload', (0, zod_validation_error_1.fromZodError)(parsed.error).message, undefined, runtime_1.CrudFailureReason.DATA_VALIDATION_VIOLATION, parsed.error);
|
|
595
|
+
}
|
|
596
|
+
if (parsed.data.data === null) {
|
|
597
|
+
if (!relationInfo.isOptional) {
|
|
598
|
+
// cannot disconnect a required relation
|
|
599
|
+
return this.makeError('invalidPayload');
|
|
642
600
|
}
|
|
643
|
-
//
|
|
644
|
-
const relationVerb = mode === 'create' ? 'connect' : mode === 'delete' ? 'disconnect' : 'set';
|
|
601
|
+
// set null -> disconnect
|
|
645
602
|
updateArgs.data = {
|
|
646
603
|
[relationship]: {
|
|
647
|
-
|
|
648
|
-
[relationInfo.idField]: this.coerce(relationInfo.idFieldType, item.id),
|
|
649
|
-
})),
|
|
604
|
+
disconnect: true,
|
|
650
605
|
},
|
|
651
606
|
};
|
|
652
607
|
}
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
608
|
+
else {
|
|
609
|
+
updateArgs.data = {
|
|
610
|
+
[relationship]: {
|
|
611
|
+
connect: {
|
|
612
|
+
[relationInfo.idField]: this.coerce(relationInfo.idFieldType, parsed.data.data.id),
|
|
613
|
+
},
|
|
614
|
+
},
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
else {
|
|
619
|
+
// zod-parse payload
|
|
620
|
+
const parsed = this.updateCollectionRelationSchema.safeParse(requestBody);
|
|
621
|
+
if (!parsed.success) {
|
|
622
|
+
return this.makeError('invalidPayload', (0, zod_validation_error_1.fromZodError)(parsed.error).message, undefined, runtime_1.CrudFailureReason.DATA_VALIDATION_VIOLATION, parsed.error);
|
|
623
|
+
}
|
|
624
|
+
// create -> connect, delete -> disconnect, update -> set
|
|
625
|
+
const relationVerb = mode === 'create' ? 'connect' : mode === 'delete' ? 'disconnect' : 'set';
|
|
626
|
+
updateArgs.data = {
|
|
627
|
+
[relationship]: {
|
|
628
|
+
[relationVerb]: (0, runtime_1.enumerate)(parsed.data.data).map((item) => ({
|
|
629
|
+
[relationInfo.idField]: this.coerce(relationInfo.idFieldType, item.id),
|
|
630
|
+
})),
|
|
657
631
|
},
|
|
658
|
-
onlyIdentifier: true,
|
|
659
|
-
});
|
|
660
|
-
return {
|
|
661
|
-
status: 200,
|
|
662
|
-
body: serialized,
|
|
663
632
|
};
|
|
633
|
+
}
|
|
634
|
+
const entity = await prisma[type].update(updateArgs);
|
|
635
|
+
const serialized = await this.serializeItems(relationInfo.type, entity[relationship], {
|
|
636
|
+
linkers: {
|
|
637
|
+
document: new ts_japi_1.Linker(() => this.makeLinkUrl(`/${type}/${resourceId}/relationships/${relationship}`)),
|
|
638
|
+
},
|
|
639
|
+
onlyIdentifier: true,
|
|
664
640
|
});
|
|
641
|
+
return {
|
|
642
|
+
status: 200,
|
|
643
|
+
body: serialized,
|
|
644
|
+
};
|
|
665
645
|
}
|
|
666
|
-
processUpdate(prisma, type, resourceId, _query, requestBody, zodSchemas) {
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
646
|
+
async processUpdate(prisma, type, resourceId, _query, requestBody, zodSchemas) {
|
|
647
|
+
const typeInfo = this.typeMap[type];
|
|
648
|
+
if (!typeInfo) {
|
|
649
|
+
return this.makeUnsupportedModelError(type);
|
|
650
|
+
}
|
|
651
|
+
const { error, attributes, relationships } = this.processRequestBody(type, requestBody, zodSchemas, 'update');
|
|
652
|
+
if (error) {
|
|
653
|
+
return error;
|
|
654
|
+
}
|
|
655
|
+
const updatePayload = {
|
|
656
|
+
where: this.makeIdFilter(typeInfo.idField, typeInfo.idFieldType, resourceId),
|
|
657
|
+
data: { ...attributes },
|
|
658
|
+
};
|
|
659
|
+
// turn relationships into prisma payload
|
|
660
|
+
if (relationships) {
|
|
661
|
+
for (const [key, data] of Object.entries(relationships)) {
|
|
662
|
+
if (!data?.data) {
|
|
663
|
+
return this.makeError('invalidRelationData');
|
|
664
|
+
}
|
|
665
|
+
const relationInfo = typeInfo.relationships[key];
|
|
666
|
+
if (!relationInfo) {
|
|
667
|
+
return this.makeUnsupportedRelationshipError(type, key, 400);
|
|
668
|
+
}
|
|
669
|
+
if (relationInfo.isCollection) {
|
|
670
|
+
updatePayload.data[key] = {
|
|
671
|
+
set: (0, runtime_1.enumerate)(data.data).map((item) => ({
|
|
672
|
+
[relationInfo.idField]: this.coerce(relationInfo.idFieldType, item.id),
|
|
673
|
+
})),
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
else {
|
|
677
|
+
if (typeof data.data !== 'object') {
|
|
684
678
|
return this.makeError('invalidRelationData');
|
|
685
679
|
}
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
}
|
|
690
|
-
if (relationInfo.isCollection) {
|
|
691
|
-
updatePayload.data[key] = {
|
|
692
|
-
set: (0, runtime_1.enumerate)(data.data).map((item) => ({
|
|
693
|
-
[relationInfo.idField]: this.coerce(relationInfo.idFieldType, item.id),
|
|
694
|
-
})),
|
|
695
|
-
};
|
|
696
|
-
}
|
|
697
|
-
else {
|
|
698
|
-
if (typeof data.data !== 'object') {
|
|
699
|
-
return this.makeError('invalidRelationData');
|
|
700
|
-
}
|
|
701
|
-
updatePayload.data[key] = {
|
|
702
|
-
set: { [relationInfo.idField]: this.coerce(relationInfo.idFieldType, data.data.id) },
|
|
703
|
-
};
|
|
704
|
-
}
|
|
705
|
-
updatePayload.include = Object.assign(Object.assign({}, updatePayload.include), { [key]: { select: { [relationInfo.idField]: true } } });
|
|
680
|
+
updatePayload.data[key] = {
|
|
681
|
+
set: { [relationInfo.idField]: this.coerce(relationInfo.idFieldType, data.data.id) },
|
|
682
|
+
};
|
|
706
683
|
}
|
|
684
|
+
updatePayload.include = {
|
|
685
|
+
...updatePayload.include,
|
|
686
|
+
[key]: { select: { [relationInfo.idField]: true } },
|
|
687
|
+
};
|
|
707
688
|
}
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
}
|
|
689
|
+
}
|
|
690
|
+
const entity = await prisma[type].update(updatePayload);
|
|
691
|
+
return {
|
|
692
|
+
status: 200,
|
|
693
|
+
body: await this.serializeItems(type, entity),
|
|
694
|
+
};
|
|
714
695
|
}
|
|
715
|
-
processDelete(prisma, type, resourceId) {
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
where: this.makeIdFilter(typeInfo.idField, typeInfo.idFieldType, resourceId),
|
|
723
|
-
});
|
|
724
|
-
return {
|
|
725
|
-
status: 204,
|
|
726
|
-
body: undefined,
|
|
727
|
-
};
|
|
696
|
+
async processDelete(prisma, type, resourceId) {
|
|
697
|
+
const typeInfo = this.typeMap[type];
|
|
698
|
+
if (!typeInfo) {
|
|
699
|
+
return this.makeUnsupportedModelError(type);
|
|
700
|
+
}
|
|
701
|
+
await prisma[type].delete({
|
|
702
|
+
where: this.makeIdFilter(typeInfo.idField, typeInfo.idFieldType, resourceId),
|
|
728
703
|
});
|
|
704
|
+
return {
|
|
705
|
+
status: 204,
|
|
706
|
+
body: undefined,
|
|
707
|
+
};
|
|
729
708
|
}
|
|
730
709
|
//#region utilities
|
|
731
710
|
buildTypeMap(logger, modelMeta) {
|
|
@@ -821,9 +800,9 @@ class RequestHandler extends base_1.APIHandlerBase {
|
|
|
821
800
|
}
|
|
822
801
|
const fieldIds = (0, runtime_1.getIdFields)(modelMeta, fieldMeta.type);
|
|
823
802
|
if (fieldIds.length === 1) {
|
|
824
|
-
const relator = new ts_japi_1.Relator((data) =>
|
|
803
|
+
const relator = new ts_japi_1.Relator(async (data) => {
|
|
825
804
|
return data[field];
|
|
826
|
-
}
|
|
805
|
+
}, fieldSerializer, {
|
|
827
806
|
relatedName: field,
|
|
828
807
|
linkers: {
|
|
829
808
|
related: new ts_japi_1.Linker((primary) => this.makeLinkUrl(`/${(0, lower_case_first_1.lowerCaseFirst)(model)}/${this.getId(model, primary, modelMeta)}/${field}`)),
|
|
@@ -848,25 +827,23 @@ class RequestHandler extends base_1.APIHandlerBase {
|
|
|
848
827
|
return undefined;
|
|
849
828
|
}
|
|
850
829
|
}
|
|
851
|
-
serializeItems(model, items, options) {
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
return result;
|
|
869
|
-
});
|
|
830
|
+
async serializeItems(model, items, options) {
|
|
831
|
+
model = (0, lower_case_first_1.lowerCaseFirst)(model);
|
|
832
|
+
const serializer = this.serializers.get(model);
|
|
833
|
+
if (!serializer) {
|
|
834
|
+
throw new Error(`serializer not found for model ${model}`);
|
|
835
|
+
}
|
|
836
|
+
// serialize to JSON:API structure
|
|
837
|
+
const serialized = await serializer.serialize(items, options);
|
|
838
|
+
// convert the serialization result to plain object otherwise SuperJSON won't work
|
|
839
|
+
const plainResult = this.toPlainObject(serialized);
|
|
840
|
+
// superjson serialize the result
|
|
841
|
+
const { json, meta } = superjson_1.default.serialize(plainResult);
|
|
842
|
+
const result = json;
|
|
843
|
+
if (meta) {
|
|
844
|
+
result.meta = { ...result.meta, serialization: meta };
|
|
845
|
+
}
|
|
846
|
+
return result;
|
|
870
847
|
}
|
|
871
848
|
toPlainObject(data) {
|
|
872
849
|
if (data === undefined || data === null) {
|
|
@@ -924,7 +901,7 @@ class RequestHandler extends base_1.APIHandlerBase {
|
|
|
924
901
|
return;
|
|
925
902
|
}
|
|
926
903
|
for (const [relation, relationInfo] of Object.entries(typeInfo.relationships)) {
|
|
927
|
-
args[mode] =
|
|
904
|
+
args[mode] = { ...args[mode], [relation]: { select: { [relationInfo.idField]: true } } };
|
|
928
905
|
}
|
|
929
906
|
}
|
|
930
907
|
coerce(type, value) {
|
|
@@ -959,7 +936,7 @@ class RequestHandler extends base_1.APIHandlerBase {
|
|
|
959
936
|
}
|
|
960
937
|
makeNormalizedUrl(path, query) {
|
|
961
938
|
const url = new URL(this.makeLinkUrl(path));
|
|
962
|
-
for (const [key, value] of Object.entries(query
|
|
939
|
+
for (const [key, value] of Object.entries(query ?? {})) {
|
|
963
940
|
if (key.startsWith('filter[') ||
|
|
964
941
|
key.startsWith('sort[') ||
|
|
965
942
|
key.startsWith('include[') ||
|
|
@@ -972,9 +949,8 @@ class RequestHandler extends base_1.APIHandlerBase {
|
|
|
972
949
|
return url.toString();
|
|
973
950
|
}
|
|
974
951
|
getPagination(query) {
|
|
975
|
-
var _a, _b;
|
|
976
952
|
if (!query) {
|
|
977
|
-
return { offset: 0, limit:
|
|
953
|
+
return { offset: 0, limit: this.options.pageSize ?? DEFAULT_PAGE_SIZE };
|
|
978
954
|
}
|
|
979
955
|
let offset = 0;
|
|
980
956
|
if (query['page[offset]']) {
|
|
@@ -985,7 +961,7 @@ class RequestHandler extends base_1.APIHandlerBase {
|
|
|
985
961
|
offset = 0;
|
|
986
962
|
}
|
|
987
963
|
}
|
|
988
|
-
let pageSizeOption =
|
|
964
|
+
let pageSizeOption = this.options.pageSize ?? DEFAULT_PAGE_SIZE;
|
|
989
965
|
if (pageSizeOption <= 0) {
|
|
990
966
|
pageSizeOption = DEFAULT_PAGE_SIZE;
|
|
991
967
|
}
|
|
@@ -1091,7 +1067,7 @@ class RequestHandler extends base_1.APIHandlerBase {
|
|
|
1091
1067
|
}
|
|
1092
1068
|
}
|
|
1093
1069
|
buildSort(type, query) {
|
|
1094
|
-
if (!
|
|
1070
|
+
if (!query?.['sort']) {
|
|
1095
1071
|
return { sort: undefined, error: undefined };
|
|
1096
1072
|
}
|
|
1097
1073
|
const typeInfo = this.typeMap[(0, lower_case_first_1.lowerCaseFirst)(type)];
|
|
@@ -1153,7 +1129,6 @@ class RequestHandler extends base_1.APIHandlerBase {
|
|
|
1153
1129
|
return { sort: result, error: undefined };
|
|
1154
1130
|
}
|
|
1155
1131
|
buildRelationSelect(type, include) {
|
|
1156
|
-
var _a;
|
|
1157
1132
|
const typeInfo = this.typeMap[(0, lower_case_first_1.lowerCaseFirst)(type)];
|
|
1158
1133
|
if (!typeInfo) {
|
|
1159
1134
|
return { select: undefined, error: this.makeUnsupportedModelError(type) };
|
|
@@ -1178,7 +1153,7 @@ class RequestHandler extends base_1.APIHandlerBase {
|
|
|
1178
1153
|
return { select: undefined, error: this.makeUnsupportedModelError(relationInfo.type) };
|
|
1179
1154
|
}
|
|
1180
1155
|
if (i !== parts.length - 1) {
|
|
1181
|
-
currPayload[relation] = { include:
|
|
1156
|
+
currPayload[relation] = { include: { ...currPayload[relation]?.include } };
|
|
1182
1157
|
currPayload = currPayload[relation].include;
|
|
1183
1158
|
}
|
|
1184
1159
|
else {
|
|
@@ -1234,38 +1209,37 @@ class RequestHandler extends base_1.APIHandlerBase {
|
|
|
1234
1209
|
return filterError;
|
|
1235
1210
|
}
|
|
1236
1211
|
if (filter) {
|
|
1237
|
-
injectTarget[injectKey] =
|
|
1212
|
+
injectTarget[injectKey] = { ...injectTarget[injectKey], where: filter };
|
|
1238
1213
|
}
|
|
1239
1214
|
const { sort, error: sortError } = this.buildSort(type, query);
|
|
1240
1215
|
if (sortError) {
|
|
1241
1216
|
return sortError;
|
|
1242
1217
|
}
|
|
1243
1218
|
if (sort) {
|
|
1244
|
-
injectTarget[injectKey] =
|
|
1219
|
+
injectTarget[injectKey] = { ...injectTarget[injectKey], orderBy: sort };
|
|
1245
1220
|
}
|
|
1246
1221
|
const pagination = this.getPagination(query);
|
|
1247
1222
|
const offset = pagination.offset;
|
|
1248
1223
|
if (offset > 0) {
|
|
1249
1224
|
// inject skip
|
|
1250
|
-
injectTarget[injectKey] =
|
|
1225
|
+
injectTarget[injectKey] = { ...injectTarget[injectKey], skip: offset };
|
|
1251
1226
|
}
|
|
1252
1227
|
const limit = pagination.limit;
|
|
1253
1228
|
if (limit !== Infinity) {
|
|
1254
1229
|
// inject take
|
|
1255
|
-
injectTarget[injectKey] =
|
|
1230
|
+
injectTarget[injectKey] = { ...injectTarget[injectKey], take: limit };
|
|
1256
1231
|
// include a count query for the relationship
|
|
1257
1232
|
injectTarget._count = { select: { [injectKey]: true } };
|
|
1258
1233
|
}
|
|
1259
1234
|
}
|
|
1260
1235
|
handlePrismaError(err) {
|
|
1261
|
-
var _a, _b, _c, _d;
|
|
1262
1236
|
if ((0, runtime_1.isPrismaClientKnownRequestError)(err)) {
|
|
1263
1237
|
if (err.code === runtime_1.PrismaErrorCode.CONSTRAINED_FAILED) {
|
|
1264
|
-
if (
|
|
1265
|
-
return this.makeError('validationError', undefined, 422,
|
|
1238
|
+
if (err.meta?.reason === runtime_1.CrudFailureReason.DATA_VALIDATION_VIOLATION) {
|
|
1239
|
+
return this.makeError('validationError', undefined, 422, err.meta?.reason, err.meta?.zodErrors);
|
|
1266
1240
|
}
|
|
1267
1241
|
else {
|
|
1268
|
-
return this.makeError('forbidden', undefined, 403,
|
|
1242
|
+
return this.makeError('forbidden', undefined, 403, err.meta?.reason);
|
|
1269
1243
|
}
|
|
1270
1244
|
}
|
|
1271
1245
|
else if (err.code === 'P2025' || err.code === 'P2018') {
|
|
@@ -1295,7 +1269,7 @@ class RequestHandler extends base_1.APIHandlerBase {
|
|
|
1295
1269
|
}
|
|
1296
1270
|
makeError(code, detail, status, reason, zodErrors) {
|
|
1297
1271
|
const error = {
|
|
1298
|
-
status: status
|
|
1272
|
+
status: status ?? this.errors[code].status,
|
|
1299
1273
|
code: (0, change_case_1.paramCase)(code),
|
|
1300
1274
|
title: this.errors[code].title,
|
|
1301
1275
|
};
|
|
@@ -1309,7 +1283,7 @@ class RequestHandler extends base_1.APIHandlerBase {
|
|
|
1309
1283
|
error.zodErrors = zodErrors;
|
|
1310
1284
|
}
|
|
1311
1285
|
return {
|
|
1312
|
-
status: status
|
|
1286
|
+
status: status ?? this.errors[code].status,
|
|
1313
1287
|
body: {
|
|
1314
1288
|
errors: [error],
|
|
1315
1289
|
},
|
|
@@ -1326,6 +1300,4 @@ function makeHandler(options) {
|
|
|
1326
1300
|
const handler = new RequestHandler(options);
|
|
1327
1301
|
return handler.handleRequest.bind(handler);
|
|
1328
1302
|
}
|
|
1329
|
-
exports.default = makeHandler;
|
|
1330
|
-
exports.RestApiHandler = makeHandler;
|
|
1331
1303
|
//# sourceMappingURL=index.js.map
|