@zenstackhq/server 1.0.0-alpha.99 → 1.0.0-beta.2
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/rest/index.d.ts +19 -0
- package/api/rest/index.js +1271 -0
- package/api/rest/index.js.map +1 -0
- package/api/rpc/index.d.ts +2 -0
- package/api/rpc/index.js +166 -0
- package/api/rpc/index.js.map +1 -0
- package/api/utils.d.ts +18 -0
- package/api/utils.js +82 -0
- package/api/utils.js.map +1 -0
- package/express/index.d.ts +1 -0
- package/express/index.js +15 -0
- package/express/index.js.map +1 -1
- package/express/middleware.d.ts +2 -12
- package/express/middleware.js +50 -15
- package/express/middleware.js.map +1 -1
- package/fastify/index.d.ts +1 -0
- package/fastify/index.js +15 -0
- package/fastify/index.js.map +1 -1
- package/fastify/plugin.d.ts +2 -12
- package/fastify/plugin.js +35 -20
- package/fastify/plugin.js.map +1 -1
- package/next/app-route-handler.d.ts +15 -0
- package/next/app-route-handler.js +85 -0
- package/next/app-route-handler.js.map +1 -0
- package/next/index.d.ts +38 -0
- package/next/index.js +18 -0
- package/next/index.js.map +1 -0
- package/next/pages-route-handler.d.ts +9 -0
- package/next/pages-route-handler.js +73 -0
- package/next/pages-route-handler.js.map +1 -0
- package/package.json +17 -7
- package/sveltekit/handler.d.ts +19 -0
- package/sveltekit/handler.js +100 -0
- package/sveltekit/handler.js.map +1 -0
- package/sveltekit/index.d.ts +2 -0
- package/sveltekit/index.js +24 -0
- package/sveltekit/index.js.map +1 -0
- package/types.d.ts +97 -0
- package/types.js +3 -0
- package/types.js.map +1 -0
- package/utils.d.ts +17 -0
- package/utils.js +93 -0
- package/utils.js.map +1 -0
- package/openapi/index.d.ts +0 -46
- package/openapi/index.js +0 -193
- package/openapi/index.js.map +0 -1
- package/openapi/utils.d.ts +0 -4
- package/openapi/utils.js +0 -25
- package/openapi/utils.js.map +0 -1
|
@@ -0,0 +1,1271 @@
|
|
|
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
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
16
|
+
const runtime_1 = require("@zenstackhq/runtime");
|
|
17
|
+
const model_meta_1 = require("@zenstackhq/runtime/enhancements/model-meta");
|
|
18
|
+
const change_case_1 = require("change-case");
|
|
19
|
+
const lower_case_first_1 = require("lower-case-first");
|
|
20
|
+
const ts_japi_1 = require("ts-japi");
|
|
21
|
+
const url_pattern_1 = __importDefault(require("url-pattern"));
|
|
22
|
+
const zod_1 = __importDefault(require("zod"));
|
|
23
|
+
const zod_validation_error_1 = require("zod-validation-error");
|
|
24
|
+
const utils_1 = require("../utils");
|
|
25
|
+
const urlPatterns = {
|
|
26
|
+
// collection operations
|
|
27
|
+
collection: new url_pattern_1.default('/:type'),
|
|
28
|
+
// single resource operations
|
|
29
|
+
single: new url_pattern_1.default('/:type/:id'),
|
|
30
|
+
// related entity fetching
|
|
31
|
+
fetchRelationship: new url_pattern_1.default('/:type/:id/:relationship'),
|
|
32
|
+
// relationship operations
|
|
33
|
+
relationship: new url_pattern_1.default('/:type/:id/relationships/:relationship'),
|
|
34
|
+
};
|
|
35
|
+
class InvalidValueError extends Error {
|
|
36
|
+
constructor(message) {
|
|
37
|
+
super(message);
|
|
38
|
+
this.message = message;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const DEFAULT_PAGE_SIZE = 100;
|
|
42
|
+
const FilterOperations = [
|
|
43
|
+
'lt',
|
|
44
|
+
'lte',
|
|
45
|
+
'gt',
|
|
46
|
+
'gte',
|
|
47
|
+
'contains',
|
|
48
|
+
'icontains',
|
|
49
|
+
'search',
|
|
50
|
+
'startsWith',
|
|
51
|
+
'endsWith',
|
|
52
|
+
'has',
|
|
53
|
+
'hasEvery',
|
|
54
|
+
'hasSome',
|
|
55
|
+
'isEmpty',
|
|
56
|
+
];
|
|
57
|
+
/**
|
|
58
|
+
* RESTful-style API request handler (compliant with JSON:API)
|
|
59
|
+
*/
|
|
60
|
+
class RequestHandler {
|
|
61
|
+
constructor(options) {
|
|
62
|
+
this.options = options;
|
|
63
|
+
// error responses
|
|
64
|
+
this.errors = {
|
|
65
|
+
unsupportedModel: {
|
|
66
|
+
status: 404,
|
|
67
|
+
title: 'Unsupported model type',
|
|
68
|
+
detail: 'The model type is not supported',
|
|
69
|
+
},
|
|
70
|
+
unsupportedRelationship: {
|
|
71
|
+
status: 400,
|
|
72
|
+
title: 'Unsupported relationship',
|
|
73
|
+
detail: 'The relationship is not supported',
|
|
74
|
+
},
|
|
75
|
+
invalidPath: {
|
|
76
|
+
status: 400,
|
|
77
|
+
title: 'The request path is invalid',
|
|
78
|
+
},
|
|
79
|
+
invalidVerb: {
|
|
80
|
+
status: 400,
|
|
81
|
+
title: 'The HTTP verb is not supported',
|
|
82
|
+
},
|
|
83
|
+
notFound: {
|
|
84
|
+
status: 404,
|
|
85
|
+
title: 'Resource not found',
|
|
86
|
+
},
|
|
87
|
+
noId: {
|
|
88
|
+
status: 400,
|
|
89
|
+
title: 'Model without an ID field is not supported',
|
|
90
|
+
},
|
|
91
|
+
multiId: {
|
|
92
|
+
status: 400,
|
|
93
|
+
title: 'Model with multiple ID fields is not supported',
|
|
94
|
+
},
|
|
95
|
+
invalidId: {
|
|
96
|
+
status: 400,
|
|
97
|
+
title: 'Resource ID is invalid',
|
|
98
|
+
},
|
|
99
|
+
invalidPayload: {
|
|
100
|
+
status: 400,
|
|
101
|
+
title: 'Invalid payload',
|
|
102
|
+
},
|
|
103
|
+
invalidRelationData: {
|
|
104
|
+
status: 400,
|
|
105
|
+
title: 'Invalid payload',
|
|
106
|
+
detail: 'Invalid relationship data',
|
|
107
|
+
},
|
|
108
|
+
invalidRelation: {
|
|
109
|
+
status: 400,
|
|
110
|
+
title: 'Invalid payload',
|
|
111
|
+
detail: 'Invalid relationship',
|
|
112
|
+
},
|
|
113
|
+
invalidFilter: {
|
|
114
|
+
status: 400,
|
|
115
|
+
title: 'Invalid filter',
|
|
116
|
+
},
|
|
117
|
+
invalidSort: {
|
|
118
|
+
status: 400,
|
|
119
|
+
title: 'Invalid sort',
|
|
120
|
+
},
|
|
121
|
+
invalidValue: {
|
|
122
|
+
status: 400,
|
|
123
|
+
title: 'Invalid value for type',
|
|
124
|
+
},
|
|
125
|
+
forbidden: {
|
|
126
|
+
status: 403,
|
|
127
|
+
title: 'Operation is forbidden',
|
|
128
|
+
},
|
|
129
|
+
unknownError: {
|
|
130
|
+
status: 400,
|
|
131
|
+
title: 'Unknown error',
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
this.filterParamPattern = new RegExp(/^filter(?<match>(\[[^[\]]+\])+)$/);
|
|
135
|
+
// zod schema for payload of creating and updating a resource
|
|
136
|
+
this.createUpdatePayloadSchema = zod_1.default
|
|
137
|
+
.object({
|
|
138
|
+
data: zod_1.default.object({
|
|
139
|
+
type: zod_1.default.string(),
|
|
140
|
+
attributes: zod_1.default.object({}).passthrough().optional(),
|
|
141
|
+
relationships: zod_1.default
|
|
142
|
+
.record(zod_1.default.object({
|
|
143
|
+
data: zod_1.default.union([
|
|
144
|
+
zod_1.default.object({ type: zod_1.default.string(), id: zod_1.default.union([zod_1.default.string(), zod_1.default.number()]) }),
|
|
145
|
+
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()]) })),
|
|
146
|
+
]),
|
|
147
|
+
}))
|
|
148
|
+
.optional(),
|
|
149
|
+
}),
|
|
150
|
+
})
|
|
151
|
+
.strict();
|
|
152
|
+
// zod schema for updating a single relationship
|
|
153
|
+
this.updateSingleRelationSchema = zod_1.default.object({
|
|
154
|
+
data: zod_1.default.object({ type: zod_1.default.string(), id: zod_1.default.union([zod_1.default.string(), zod_1.default.number()]) }).nullable(),
|
|
155
|
+
});
|
|
156
|
+
// zod schema for updating collection relationship
|
|
157
|
+
this.updateCollectionRelationSchema = zod_1.default.object({
|
|
158
|
+
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()]) })),
|
|
159
|
+
});
|
|
160
|
+
try {
|
|
161
|
+
this.defaultModelMeta = (0, model_meta_1.getDefaultModelMeta)();
|
|
162
|
+
}
|
|
163
|
+
catch (_a) {
|
|
164
|
+
// noop
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
handleRequest({ prisma, method, path, query, requestBody, logger, modelMeta, }) {
|
|
168
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
169
|
+
if (!this.serializers) {
|
|
170
|
+
this.buildSerializers(modelMeta !== null && modelMeta !== void 0 ? modelMeta : this.defaultModelMeta);
|
|
171
|
+
}
|
|
172
|
+
if (!this.typeMap) {
|
|
173
|
+
this.buildTypeMap(logger, modelMeta !== null && modelMeta !== void 0 ? modelMeta : this.defaultModelMeta);
|
|
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 yield this.processSingleRead(prisma, match.type, match.id, query);
|
|
186
|
+
}
|
|
187
|
+
match = urlPatterns.fetchRelationship.match(path);
|
|
188
|
+
if (match) {
|
|
189
|
+
// fetch related resource(s)
|
|
190
|
+
return yield this.processFetchRelated(prisma, match.type, match.id, match.relationship, query);
|
|
191
|
+
}
|
|
192
|
+
match = urlPatterns.relationship.match(path);
|
|
193
|
+
if (match) {
|
|
194
|
+
// read relationship
|
|
195
|
+
return yield this.processReadRelationship(prisma, match.type, match.id, match.relationship, query, modelMeta !== null && modelMeta !== void 0 ? modelMeta : this.defaultModelMeta);
|
|
196
|
+
}
|
|
197
|
+
match = urlPatterns.collection.match(path);
|
|
198
|
+
if (match) {
|
|
199
|
+
// collection read
|
|
200
|
+
return yield this.processCollectionRead(prisma, match.type, query);
|
|
201
|
+
}
|
|
202
|
+
return this.makeError('invalidPath');
|
|
203
|
+
}
|
|
204
|
+
case 'POST': {
|
|
205
|
+
let match = urlPatterns.collection.match(path);
|
|
206
|
+
if (match) {
|
|
207
|
+
// resource creation
|
|
208
|
+
return yield this.processCreate(prisma, match.type, query, requestBody /*, zodSchemas */);
|
|
209
|
+
}
|
|
210
|
+
match = urlPatterns.relationship.match(path);
|
|
211
|
+
if (match) {
|
|
212
|
+
// relationship creation (collection relationship only)
|
|
213
|
+
return yield this.processRelationshipCRUD(prisma, 'create', match.type, match.id, match.relationship, query, requestBody);
|
|
214
|
+
}
|
|
215
|
+
return this.makeError('invalidPath');
|
|
216
|
+
}
|
|
217
|
+
// TODO: PUT for full update
|
|
218
|
+
case 'PUT':
|
|
219
|
+
case 'PATCH': {
|
|
220
|
+
let match = urlPatterns.single.match(path);
|
|
221
|
+
if (match) {
|
|
222
|
+
// resource update
|
|
223
|
+
return yield this.processUpdate(prisma, match.type, match.id, query, requestBody /*, zodSchemas */);
|
|
224
|
+
}
|
|
225
|
+
match = urlPatterns.relationship.match(path);
|
|
226
|
+
if (match) {
|
|
227
|
+
// relationship update
|
|
228
|
+
return yield this.processRelationshipCRUD(prisma, 'update', match.type, match.id, match.relationship, query, requestBody);
|
|
229
|
+
}
|
|
230
|
+
return this.makeError('invalidPath');
|
|
231
|
+
}
|
|
232
|
+
case 'DELETE': {
|
|
233
|
+
let match = urlPatterns.single.match(path);
|
|
234
|
+
if (match) {
|
|
235
|
+
// resource deletion
|
|
236
|
+
return yield this.processDelete(prisma, match.type, match.id);
|
|
237
|
+
}
|
|
238
|
+
match = urlPatterns.relationship.match(path);
|
|
239
|
+
if (match) {
|
|
240
|
+
// relationship deletion (collection relationship only)
|
|
241
|
+
return yield this.processRelationshipCRUD(prisma, 'delete', match.type, match.id, match.relationship, query, requestBody);
|
|
242
|
+
}
|
|
243
|
+
return this.makeError('invalidPath');
|
|
244
|
+
}
|
|
245
|
+
default:
|
|
246
|
+
return this.makeError('invalidPath');
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
catch (err) {
|
|
250
|
+
if (err instanceof InvalidValueError) {
|
|
251
|
+
return this.makeError('invalidValue', err.message);
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
return this.handlePrismaError(err);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
processSingleRead(prisma, type, resourceId, query) {
|
|
260
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
261
|
+
const typeInfo = this.typeMap[type];
|
|
262
|
+
if (!typeInfo) {
|
|
263
|
+
return this.makeUnsupportedModelError(type);
|
|
264
|
+
}
|
|
265
|
+
const args = { where: this.makeIdFilter(typeInfo.idField, typeInfo.idFieldType, resourceId) };
|
|
266
|
+
// include IDs of relation fields so that they can be serialized
|
|
267
|
+
this.includeRelationshipIds(type, args, 'include');
|
|
268
|
+
// handle "include" query parameter
|
|
269
|
+
let include;
|
|
270
|
+
if (query === null || query === void 0 ? void 0 : query.include) {
|
|
271
|
+
const { select, error, allIncludes } = this.buildRelationSelect(type, query.include);
|
|
272
|
+
if (error) {
|
|
273
|
+
return error;
|
|
274
|
+
}
|
|
275
|
+
if (select) {
|
|
276
|
+
args.include = Object.assign(Object.assign({}, args.include), select);
|
|
277
|
+
}
|
|
278
|
+
include = allIncludes;
|
|
279
|
+
}
|
|
280
|
+
const entity = yield prisma[type].findUnique(args);
|
|
281
|
+
if (entity) {
|
|
282
|
+
return {
|
|
283
|
+
status: 200,
|
|
284
|
+
body: yield this.serializeItems(type, entity, { include }),
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
return this.makeError('notFound');
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
processFetchRelated(prisma, type, resourceId, relationship, query) {
|
|
293
|
+
var _a, _b;
|
|
294
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
295
|
+
const typeInfo = this.typeMap[type];
|
|
296
|
+
if (!typeInfo) {
|
|
297
|
+
return this.makeUnsupportedModelError(type);
|
|
298
|
+
}
|
|
299
|
+
const relationInfo = typeInfo.relationships[relationship];
|
|
300
|
+
if (!relationInfo) {
|
|
301
|
+
return this.makeUnsupportedRelationshipError(type, relationship, 404);
|
|
302
|
+
}
|
|
303
|
+
let select;
|
|
304
|
+
// handle "include" query parameter
|
|
305
|
+
let include;
|
|
306
|
+
if (query === null || query === void 0 ? void 0 : query.include) {
|
|
307
|
+
const { select: relationSelect, error, allIncludes } = this.buildRelationSelect(type, query.include);
|
|
308
|
+
if (error) {
|
|
309
|
+
return error;
|
|
310
|
+
}
|
|
311
|
+
// trim the leading `$relationship.` from the include paths
|
|
312
|
+
include = allIncludes
|
|
313
|
+
.filter((i) => i.startsWith(`${relationship}.`))
|
|
314
|
+
.map((i) => i.substring(`${relationship}.`.length));
|
|
315
|
+
select = relationSelect;
|
|
316
|
+
}
|
|
317
|
+
select = select !== null && select !== void 0 ? select : { [relationship]: true };
|
|
318
|
+
const args = {
|
|
319
|
+
where: this.makeIdFilter(typeInfo.idField, typeInfo.idFieldType, resourceId),
|
|
320
|
+
select,
|
|
321
|
+
};
|
|
322
|
+
if (relationInfo.isCollection) {
|
|
323
|
+
// if related data is a collection, it can be filtered, sorted, and paginated
|
|
324
|
+
const error = this.injectRelationQuery(relationInfo.type, select, relationship, query);
|
|
325
|
+
if (error) {
|
|
326
|
+
return error;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
const entity = yield prisma[type].findUnique(args);
|
|
330
|
+
let paginator;
|
|
331
|
+
if (((_a = entity === null || entity === void 0 ? void 0 : entity._count) === null || _a === void 0 ? void 0 : _a[relationship]) !== undefined) {
|
|
332
|
+
// build up paginator
|
|
333
|
+
const total = (_b = entity === null || entity === void 0 ? void 0 : entity._count) === null || _b === void 0 ? void 0 : _b[relationship];
|
|
334
|
+
const url = this.makeNormalizedUrl(`/${type}/${resourceId}/${relationship}`, query);
|
|
335
|
+
const { offset, limit } = this.getPagination(query);
|
|
336
|
+
paginator = this.makePaginator(url, offset, limit, total);
|
|
337
|
+
}
|
|
338
|
+
if (entity === null || entity === void 0 ? void 0 : entity[relationship]) {
|
|
339
|
+
return {
|
|
340
|
+
status: 200,
|
|
341
|
+
body: yield this.serializeItems(relationInfo.type, entity[relationship], {
|
|
342
|
+
linkers: {
|
|
343
|
+
document: new ts_japi_1.Linker(() => this.makeLinkUrl(`/${type}/${resourceId}/${relationship}`)),
|
|
344
|
+
paginator,
|
|
345
|
+
},
|
|
346
|
+
include,
|
|
347
|
+
}),
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
else {
|
|
351
|
+
return this.makeError('notFound');
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
processReadRelationship(prisma, type, resourceId, relationship, query, modelMeta) {
|
|
356
|
+
var _a, _b;
|
|
357
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
358
|
+
const typeInfo = this.typeMap[type];
|
|
359
|
+
if (!typeInfo) {
|
|
360
|
+
return this.makeUnsupportedModelError(type);
|
|
361
|
+
}
|
|
362
|
+
const relationInfo = typeInfo.relationships[relationship];
|
|
363
|
+
if (!relationInfo) {
|
|
364
|
+
return this.makeUnsupportedRelationshipError(type, relationship, 404);
|
|
365
|
+
}
|
|
366
|
+
const args = {
|
|
367
|
+
where: this.makeIdFilter(typeInfo.idField, typeInfo.idFieldType, resourceId),
|
|
368
|
+
select: this.makeIdSelect(type, modelMeta),
|
|
369
|
+
};
|
|
370
|
+
// include IDs of relation fields so that they can be serialized
|
|
371
|
+
// this.includeRelationshipIds(type, args, 'select');
|
|
372
|
+
args.select = Object.assign(Object.assign({}, args.select), { [relationship]: { select: this.makeIdSelect(relationInfo.type, modelMeta) } });
|
|
373
|
+
let paginator;
|
|
374
|
+
if (relationInfo.isCollection) {
|
|
375
|
+
// if related data is a collection, it can be filtered, sorted, and paginated
|
|
376
|
+
const error = this.injectRelationQuery(relationInfo.type, args.select, relationship, query);
|
|
377
|
+
if (error) {
|
|
378
|
+
return error;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
const entity = yield prisma[type].findUnique(args);
|
|
382
|
+
if (((_a = entity === null || entity === void 0 ? void 0 : entity._count) === null || _a === void 0 ? void 0 : _a[relationship]) !== undefined) {
|
|
383
|
+
// build up paginator
|
|
384
|
+
const total = (_b = entity === null || entity === void 0 ? void 0 : entity._count) === null || _b === void 0 ? void 0 : _b[relationship];
|
|
385
|
+
const url = this.makeNormalizedUrl(`/${type}/${resourceId}/relationships/${relationship}`, query);
|
|
386
|
+
const { offset, limit } = this.getPagination(query);
|
|
387
|
+
paginator = this.makePaginator(url, offset, limit, total);
|
|
388
|
+
}
|
|
389
|
+
if (entity === null || entity === void 0 ? void 0 : entity[relationship]) {
|
|
390
|
+
const serialized = yield this.serializeItems(relationInfo.type, entity[relationship], {
|
|
391
|
+
linkers: {
|
|
392
|
+
document: new ts_japi_1.Linker(() => this.makeLinkUrl(`/${type}/${resourceId}/relationships/${relationship}`)),
|
|
393
|
+
paginator,
|
|
394
|
+
},
|
|
395
|
+
onlyIdentifier: true,
|
|
396
|
+
});
|
|
397
|
+
return {
|
|
398
|
+
status: 200,
|
|
399
|
+
body: serialized,
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
else {
|
|
403
|
+
return this.makeError('notFound');
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
processCollectionRead(prisma, type, query) {
|
|
408
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
409
|
+
const typeInfo = this.typeMap[type];
|
|
410
|
+
if (!typeInfo) {
|
|
411
|
+
return this.makeUnsupportedModelError(type);
|
|
412
|
+
}
|
|
413
|
+
const args = {};
|
|
414
|
+
// add filter
|
|
415
|
+
const { filter, error: filterError } = this.buildFilter(type, query);
|
|
416
|
+
if (filterError) {
|
|
417
|
+
return filterError;
|
|
418
|
+
}
|
|
419
|
+
if (filter) {
|
|
420
|
+
args.where = filter;
|
|
421
|
+
}
|
|
422
|
+
const { sort, error: sortError } = this.buildSort(type, query);
|
|
423
|
+
if (sortError) {
|
|
424
|
+
return sortError;
|
|
425
|
+
}
|
|
426
|
+
if (sort) {
|
|
427
|
+
args.orderBy = sort;
|
|
428
|
+
}
|
|
429
|
+
// include IDs of relation fields so that they can be serialized
|
|
430
|
+
this.includeRelationshipIds(type, args, 'include');
|
|
431
|
+
// handle "include" query parameter
|
|
432
|
+
let include;
|
|
433
|
+
if (query === null || query === void 0 ? void 0 : query.include) {
|
|
434
|
+
const { select, error, allIncludes } = this.buildRelationSelect(type, query.include);
|
|
435
|
+
if (error) {
|
|
436
|
+
return error;
|
|
437
|
+
}
|
|
438
|
+
if (select) {
|
|
439
|
+
args.include = Object.assign(Object.assign({}, args.include), select);
|
|
440
|
+
}
|
|
441
|
+
include = allIncludes;
|
|
442
|
+
}
|
|
443
|
+
const { offset, limit } = this.getPagination(query);
|
|
444
|
+
if (offset > 0) {
|
|
445
|
+
args.skip = offset;
|
|
446
|
+
}
|
|
447
|
+
if (limit === Infinity) {
|
|
448
|
+
const entities = yield prisma[type].findMany(args);
|
|
449
|
+
return {
|
|
450
|
+
status: 200,
|
|
451
|
+
body: yield this.serializeItems(type, entities, { include }),
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
else {
|
|
455
|
+
args.take = limit;
|
|
456
|
+
const [entities, count] = yield Promise.all([
|
|
457
|
+
prisma[type].findMany(args),
|
|
458
|
+
prisma[type].count({ where: args.where }),
|
|
459
|
+
]);
|
|
460
|
+
const total = count;
|
|
461
|
+
const url = this.makeNormalizedUrl(`/${type}`, query);
|
|
462
|
+
const options = {
|
|
463
|
+
include,
|
|
464
|
+
linkers: {
|
|
465
|
+
paginator: this.makePaginator(url, offset, limit, total),
|
|
466
|
+
},
|
|
467
|
+
};
|
|
468
|
+
return {
|
|
469
|
+
status: 200,
|
|
470
|
+
body: yield this.serializeItems(type, entities, options),
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
makePaginator(baseUrl, offset, limit, total) {
|
|
476
|
+
if (limit === Infinity) {
|
|
477
|
+
return undefined;
|
|
478
|
+
}
|
|
479
|
+
const totalPages = Math.ceil(total / limit);
|
|
480
|
+
return new ts_japi_1.Paginator(() => ({
|
|
481
|
+
first: this.replaceURLSearchParams(baseUrl, { 'page[limit]': limit }),
|
|
482
|
+
last: this.replaceURLSearchParams(baseUrl, {
|
|
483
|
+
'page[offset]': (totalPages - 1) * limit,
|
|
484
|
+
}),
|
|
485
|
+
prev: offset - limit >= 0 && offset - limit <= total - 1
|
|
486
|
+
? this.replaceURLSearchParams(baseUrl, {
|
|
487
|
+
'page[offset]': offset - limit,
|
|
488
|
+
'page[limit]': limit,
|
|
489
|
+
})
|
|
490
|
+
: null,
|
|
491
|
+
next: offset + limit <= total - 1
|
|
492
|
+
? this.replaceURLSearchParams(baseUrl, {
|
|
493
|
+
'page[offset]': offset + limit,
|
|
494
|
+
'page[limit]': limit,
|
|
495
|
+
})
|
|
496
|
+
: null,
|
|
497
|
+
}));
|
|
498
|
+
}
|
|
499
|
+
processCreate(prisma, type, _query, requestBody
|
|
500
|
+
// zodSchemas?: ModelZodSchema
|
|
501
|
+
) {
|
|
502
|
+
var _a, _b;
|
|
503
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
504
|
+
const typeInfo = this.typeMap[type];
|
|
505
|
+
if (!typeInfo) {
|
|
506
|
+
return this.makeUnsupportedModelError(type);
|
|
507
|
+
}
|
|
508
|
+
// zod-parse payload
|
|
509
|
+
const parsed = this.createUpdatePayloadSchema.safeParse(requestBody);
|
|
510
|
+
if (!parsed.success) {
|
|
511
|
+
return this.makeError('invalidPayload', (0, zod_validation_error_1.fromZodError)(parsed.error).message);
|
|
512
|
+
}
|
|
513
|
+
const parsedPayload = parsed.data;
|
|
514
|
+
const createPayload = { data: Object.assign({}, (_a = parsedPayload.data) === null || _a === void 0 ? void 0 : _a.attributes) };
|
|
515
|
+
// TODO: we need to somehow exclude relation fields from the zod schema because relations are
|
|
516
|
+
// not part of "data" in the payload
|
|
517
|
+
//
|
|
518
|
+
// // zod-parse attributes if a schema is provided
|
|
519
|
+
// const dataSchema = zodSchemas ? getZodSchema(zodSchemas, type, 'create') : undefined;
|
|
520
|
+
// if (dataSchema) {
|
|
521
|
+
// const dataParsed = dataSchema.safeParse(createPayload);
|
|
522
|
+
// if (!dataParsed.success) {
|
|
523
|
+
// return this.makeError('invalidPayload', fromZodError(dataParsed.error).message);
|
|
524
|
+
// }
|
|
525
|
+
// }
|
|
526
|
+
// turn relashionship payload into Prisma connect objects
|
|
527
|
+
const relationships = (_b = parsedPayload.data) === null || _b === void 0 ? void 0 : _b.relationships;
|
|
528
|
+
if (relationships) {
|
|
529
|
+
for (const [key, data] of Object.entries(relationships)) {
|
|
530
|
+
if (!(data === null || data === void 0 ? void 0 : data.data)) {
|
|
531
|
+
return this.makeError('invalidRelationData');
|
|
532
|
+
}
|
|
533
|
+
const relationInfo = typeInfo.relationships[key];
|
|
534
|
+
if (!relationInfo) {
|
|
535
|
+
return this.makeUnsupportedRelationshipError(type, key, 400);
|
|
536
|
+
}
|
|
537
|
+
if (relationInfo.isCollection) {
|
|
538
|
+
createPayload.data[key] = {
|
|
539
|
+
connect: (0, runtime_1.enumerate)(data.data).map((item) => ({
|
|
540
|
+
[relationInfo.idField]: this.coerce(relationInfo.idFieldType, item.id),
|
|
541
|
+
})),
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
else {
|
|
545
|
+
if (typeof data.data !== 'object') {
|
|
546
|
+
return this.makeError('invalidRelationData');
|
|
547
|
+
}
|
|
548
|
+
createPayload.data[key] = {
|
|
549
|
+
connect: { [relationInfo.idField]: this.coerce(relationInfo.idFieldType, data.data.id) },
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
// make sure ID fields are included for result serialization
|
|
553
|
+
createPayload.include = Object.assign(Object.assign({}, createPayload.include), { [key]: { select: { [relationInfo.idField]: true } } });
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
const entity = yield prisma[type].create(createPayload);
|
|
557
|
+
return {
|
|
558
|
+
status: 201,
|
|
559
|
+
body: yield this.serializeItems(type, entity),
|
|
560
|
+
};
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
processRelationshipCRUD(prisma, mode, type, resourceId, relationship, query, requestBody) {
|
|
564
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
565
|
+
const typeInfo = this.typeMap[type];
|
|
566
|
+
if (!typeInfo) {
|
|
567
|
+
return this.makeUnsupportedModelError(type);
|
|
568
|
+
}
|
|
569
|
+
const relationInfo = typeInfo.relationships[relationship];
|
|
570
|
+
if (!relationInfo) {
|
|
571
|
+
return this.makeUnsupportedRelationshipError(type, relationship, 404);
|
|
572
|
+
}
|
|
573
|
+
if (!relationInfo.isCollection && mode !== 'update') {
|
|
574
|
+
// to-one relation can only be updated
|
|
575
|
+
return this.makeError('invalidVerb');
|
|
576
|
+
}
|
|
577
|
+
const updateArgs = {
|
|
578
|
+
where: this.makeIdFilter(typeInfo.idField, typeInfo.idFieldType, resourceId),
|
|
579
|
+
select: { [typeInfo.idField]: true, [relationship]: { select: { [relationInfo.idField]: true } } },
|
|
580
|
+
};
|
|
581
|
+
if (!relationInfo.isCollection) {
|
|
582
|
+
// zod-parse payload
|
|
583
|
+
const parsed = this.updateSingleRelationSchema.safeParse(requestBody);
|
|
584
|
+
if (!parsed.success) {
|
|
585
|
+
return this.makeError('invalidPayload', (0, zod_validation_error_1.fromZodError)(parsed.error).message);
|
|
586
|
+
}
|
|
587
|
+
if (parsed.data.data === null) {
|
|
588
|
+
if (!relationInfo.isOptional) {
|
|
589
|
+
// cannot disconnect a required relation
|
|
590
|
+
return this.makeError('invalidPayload');
|
|
591
|
+
}
|
|
592
|
+
// set null -> disconnect
|
|
593
|
+
updateArgs.data = {
|
|
594
|
+
[relationship]: {
|
|
595
|
+
disconnect: true,
|
|
596
|
+
},
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
else {
|
|
600
|
+
updateArgs.data = {
|
|
601
|
+
[relationship]: {
|
|
602
|
+
connect: {
|
|
603
|
+
[relationInfo.idField]: this.coerce(relationInfo.idFieldType, parsed.data.data.id),
|
|
604
|
+
},
|
|
605
|
+
},
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
else {
|
|
610
|
+
// zod-parse payload
|
|
611
|
+
const parsed = this.updateCollectionRelationSchema.safeParse(requestBody);
|
|
612
|
+
if (!parsed.success) {
|
|
613
|
+
return this.makeError('invalidPayload', (0, zod_validation_error_1.fromZodError)(parsed.error).message);
|
|
614
|
+
}
|
|
615
|
+
// create -> connect, delete -> disconnect, update -> set
|
|
616
|
+
const relationVerb = mode === 'create' ? 'connect' : mode === 'delete' ? 'disconnect' : 'set';
|
|
617
|
+
updateArgs.data = {
|
|
618
|
+
[relationship]: {
|
|
619
|
+
[relationVerb]: (0, runtime_1.enumerate)(parsed.data.data).map((item) => ({
|
|
620
|
+
[relationInfo.idField]: this.coerce(relationInfo.idFieldType, item.id),
|
|
621
|
+
})),
|
|
622
|
+
},
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
const entity = yield prisma[type].update(updateArgs);
|
|
626
|
+
const serialized = yield this.serializeItems(relationInfo.type, entity[relationship], {
|
|
627
|
+
linkers: {
|
|
628
|
+
document: new ts_japi_1.Linker(() => this.makeLinkUrl(`/${type}/${resourceId}/relationships/${relationship}`)),
|
|
629
|
+
},
|
|
630
|
+
onlyIdentifier: true,
|
|
631
|
+
});
|
|
632
|
+
return {
|
|
633
|
+
status: 200,
|
|
634
|
+
body: serialized,
|
|
635
|
+
};
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
processUpdate(prisma, type, resourceId, query, requestBody
|
|
639
|
+
// zodSchemas?: ModelZodSchema
|
|
640
|
+
) {
|
|
641
|
+
var _a, _b;
|
|
642
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
643
|
+
const typeInfo = this.typeMap[type];
|
|
644
|
+
if (!typeInfo) {
|
|
645
|
+
return this.makeUnsupportedModelError(type);
|
|
646
|
+
}
|
|
647
|
+
// zod-parse payload
|
|
648
|
+
const parsed = this.createUpdatePayloadSchema.safeParse(requestBody);
|
|
649
|
+
if (!parsed.success) {
|
|
650
|
+
return this.makeError('invalidPayload', (0, zod_validation_error_1.fromZodError)(parsed.error).message);
|
|
651
|
+
}
|
|
652
|
+
const parsedPayload = parsed.data;
|
|
653
|
+
const attributes = (_a = parsedPayload.data) === null || _a === void 0 ? void 0 : _a.attributes;
|
|
654
|
+
if (!attributes) {
|
|
655
|
+
return this.makeError('invalidPayload');
|
|
656
|
+
}
|
|
657
|
+
const updatePayload = {
|
|
658
|
+
where: this.makeIdFilter(typeInfo.idField, typeInfo.idFieldType, resourceId),
|
|
659
|
+
data: Object.assign({}, attributes),
|
|
660
|
+
};
|
|
661
|
+
// TODO: we need to somehow exclude relation fields from the zod schema because relations are
|
|
662
|
+
// not part of "data" in the payload
|
|
663
|
+
//
|
|
664
|
+
// // zod-parse attributes if a schema is provided
|
|
665
|
+
// const dataSchema = zodSchemas ? getZodSchema(zodSchemas, type, 'update') : undefined;
|
|
666
|
+
// if (dataSchema) {
|
|
667
|
+
// const dataParsed = dataSchema.safeParse(updatePayload);
|
|
668
|
+
// if (!dataParsed.success) {
|
|
669
|
+
// return this.makeError('invalidPayload', fromZodError(dataParsed.error).message);
|
|
670
|
+
// }
|
|
671
|
+
// }
|
|
672
|
+
// turn relationships into prisma payload
|
|
673
|
+
const relationships = (_b = parsedPayload.data) === null || _b === void 0 ? void 0 : _b.relationships;
|
|
674
|
+
if (relationships) {
|
|
675
|
+
for (const [key, data] of Object.entries(relationships)) {
|
|
676
|
+
if (!(data === null || data === void 0 ? void 0 : data.data)) {
|
|
677
|
+
return this.makeError('invalidRelationData');
|
|
678
|
+
}
|
|
679
|
+
const relationInfo = typeInfo.relationships[key];
|
|
680
|
+
if (!relationInfo) {
|
|
681
|
+
return this.makeUnsupportedRelationshipError(type, key, 400);
|
|
682
|
+
}
|
|
683
|
+
if (relationInfo.isCollection) {
|
|
684
|
+
updatePayload.data[key] = {
|
|
685
|
+
set: (0, runtime_1.enumerate)(data.data).map((item) => ({
|
|
686
|
+
[relationInfo.idField]: this.coerce(relationInfo.idFieldType, item.id),
|
|
687
|
+
})),
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
else {
|
|
691
|
+
if (typeof data.data !== 'object') {
|
|
692
|
+
return this.makeError('invalidRelationData');
|
|
693
|
+
}
|
|
694
|
+
updatePayload.data[key] = {
|
|
695
|
+
set: { [relationInfo.idField]: this.coerce(relationInfo.idFieldType, data.data.id) },
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
updatePayload.include = Object.assign(Object.assign({}, updatePayload.include), { [key]: { select: { [relationInfo.idField]: true } } });
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
const entity = yield prisma[type].update(updatePayload);
|
|
702
|
+
return {
|
|
703
|
+
status: 200,
|
|
704
|
+
body: yield this.serializeItems(type, entity),
|
|
705
|
+
};
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
processDelete(prisma, type, resourceId) {
|
|
709
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
710
|
+
const typeInfo = this.typeMap[type];
|
|
711
|
+
if (!typeInfo) {
|
|
712
|
+
return this.makeUnsupportedModelError(type);
|
|
713
|
+
}
|
|
714
|
+
yield prisma[type].delete({
|
|
715
|
+
where: this.makeIdFilter(typeInfo.idField, typeInfo.idFieldType, resourceId),
|
|
716
|
+
});
|
|
717
|
+
return {
|
|
718
|
+
status: 204,
|
|
719
|
+
body: undefined,
|
|
720
|
+
};
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
//#region utilities
|
|
724
|
+
buildTypeMap(logger, modelMeta) {
|
|
725
|
+
this.typeMap = {};
|
|
726
|
+
for (const [model, fields] of Object.entries(modelMeta.fields)) {
|
|
727
|
+
const idFields = (0, runtime_1.getIdFields)(modelMeta, model);
|
|
728
|
+
if (idFields.length === 0) {
|
|
729
|
+
(0, utils_1.logWarning)(logger, `Not including model ${model} in the API because it has no ID field`);
|
|
730
|
+
continue;
|
|
731
|
+
}
|
|
732
|
+
if (idFields.length > 1) {
|
|
733
|
+
(0, utils_1.logWarning)(logger, `Not including model ${model} in the API because it has multiple ID fields`);
|
|
734
|
+
continue;
|
|
735
|
+
}
|
|
736
|
+
this.typeMap[model] = {
|
|
737
|
+
idField: idFields[0].name,
|
|
738
|
+
idFieldType: idFields[0].type,
|
|
739
|
+
relationships: {},
|
|
740
|
+
fields,
|
|
741
|
+
};
|
|
742
|
+
for (const [field, fieldInfo] of Object.entries(fields)) {
|
|
743
|
+
if (!fieldInfo.isDataModel) {
|
|
744
|
+
continue;
|
|
745
|
+
}
|
|
746
|
+
const fieldTypeIdFields = (0, runtime_1.getIdFields)(modelMeta, fieldInfo.type);
|
|
747
|
+
if (fieldTypeIdFields.length === 0) {
|
|
748
|
+
(0, utils_1.logWarning)(logger, `Not including relation ${model}.${field} in the API because it has no ID field`);
|
|
749
|
+
continue;
|
|
750
|
+
}
|
|
751
|
+
if (fieldTypeIdFields.length > 1) {
|
|
752
|
+
(0, utils_1.logWarning)(logger, `Not including relation ${model}.${field} in the API because it has multiple ID fields`);
|
|
753
|
+
continue;
|
|
754
|
+
}
|
|
755
|
+
this.typeMap[model].relationships[field] = {
|
|
756
|
+
type: fieldInfo.type,
|
|
757
|
+
idField: fieldTypeIdFields[0].name,
|
|
758
|
+
idFieldType: fieldTypeIdFields[0].type,
|
|
759
|
+
isCollection: fieldInfo.isArray,
|
|
760
|
+
isOptional: fieldInfo.isOptional,
|
|
761
|
+
};
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
makeLinkUrl(path) {
|
|
766
|
+
return `${this.options.endpoint}${path}`;
|
|
767
|
+
}
|
|
768
|
+
buildSerializers(modelMeta) {
|
|
769
|
+
this.serializers = new Map();
|
|
770
|
+
const linkers = {};
|
|
771
|
+
for (const model of Object.keys(modelMeta.fields)) {
|
|
772
|
+
const ids = (0, runtime_1.getIdFields)(modelMeta, model);
|
|
773
|
+
if (ids.length !== 1) {
|
|
774
|
+
continue;
|
|
775
|
+
}
|
|
776
|
+
const linker = new ts_japi_1.Linker((items) => Array.isArray(items)
|
|
777
|
+
? this.makeLinkUrl(`/${model}`)
|
|
778
|
+
: this.makeLinkUrl(`/${model}/${this.getId(model, items, modelMeta)}`));
|
|
779
|
+
linkers[model] = linker;
|
|
780
|
+
let projection = {};
|
|
781
|
+
for (const [field, fieldMeta] of Object.entries(modelMeta.fields[model])) {
|
|
782
|
+
if (fieldMeta.isDataModel) {
|
|
783
|
+
projection[field] = 0;
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
if (Object.keys(projection).length === 0) {
|
|
787
|
+
projection = null;
|
|
788
|
+
}
|
|
789
|
+
const serializer = new ts_japi_1.Serializer(model, {
|
|
790
|
+
version: '1.1',
|
|
791
|
+
idKey: ids[0].name,
|
|
792
|
+
linkers: {
|
|
793
|
+
resource: linker,
|
|
794
|
+
document: linker,
|
|
795
|
+
},
|
|
796
|
+
projection,
|
|
797
|
+
});
|
|
798
|
+
this.serializers.set(model, serializer);
|
|
799
|
+
}
|
|
800
|
+
// set relators
|
|
801
|
+
for (const model of Object.keys(modelMeta.fields)) {
|
|
802
|
+
const serializer = this.serializers.get(model);
|
|
803
|
+
if (!serializer) {
|
|
804
|
+
continue;
|
|
805
|
+
}
|
|
806
|
+
const relators = {};
|
|
807
|
+
for (const [field, fieldMeta] of Object.entries(modelMeta.fields[model])) {
|
|
808
|
+
if (!fieldMeta.isDataModel) {
|
|
809
|
+
continue;
|
|
810
|
+
}
|
|
811
|
+
const fieldSerializer = this.serializers.get((0, lower_case_first_1.lowerCaseFirst)(fieldMeta.type));
|
|
812
|
+
if (!fieldSerializer) {
|
|
813
|
+
continue;
|
|
814
|
+
}
|
|
815
|
+
const fieldIds = (0, runtime_1.getIdFields)(modelMeta, fieldMeta.type);
|
|
816
|
+
if (fieldIds.length === 1) {
|
|
817
|
+
const relator = new ts_japi_1.Relator((data) => __awaiter(this, void 0, void 0, function* () {
|
|
818
|
+
return data[field];
|
|
819
|
+
}), fieldSerializer, {
|
|
820
|
+
relatedName: field,
|
|
821
|
+
linkers: {
|
|
822
|
+
related: new ts_japi_1.Linker((primary) => this.makeLinkUrl(`/${(0, lower_case_first_1.lowerCaseFirst)(model)}/${this.getId(model, primary, modelMeta)}/${field}`)),
|
|
823
|
+
relationship: new ts_japi_1.Linker((primary) => this.makeLinkUrl(`/${(0, lower_case_first_1.lowerCaseFirst)(model)}/${this.getId(model, primary, modelMeta)}/relationships/${field}`)),
|
|
824
|
+
},
|
|
825
|
+
});
|
|
826
|
+
relators[field] = relator;
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
serializer.setRelators(relators);
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
getId(model, data, modelMeta) {
|
|
833
|
+
if (!data) {
|
|
834
|
+
return undefined;
|
|
835
|
+
}
|
|
836
|
+
const ids = (0, runtime_1.getIdFields)(modelMeta, model);
|
|
837
|
+
if (ids.length === 1) {
|
|
838
|
+
return data[ids[0].name];
|
|
839
|
+
}
|
|
840
|
+
else {
|
|
841
|
+
return undefined;
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
serializeItems(model, items, options) {
|
|
845
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
846
|
+
model = (0, lower_case_first_1.lowerCaseFirst)(model);
|
|
847
|
+
const serializer = this.serializers.get(model);
|
|
848
|
+
if (!serializer) {
|
|
849
|
+
throw new Error(`serializer not found for model ${model}`);
|
|
850
|
+
}
|
|
851
|
+
(0, utils_1.stripAuxFields)(items);
|
|
852
|
+
return JSON.parse(JSON.stringify(yield serializer.serialize(items, options)));
|
|
853
|
+
});
|
|
854
|
+
}
|
|
855
|
+
replaceURLSearchParams(url, params) {
|
|
856
|
+
const r = new URL(url);
|
|
857
|
+
for (const [key, value] of Object.entries(params)) {
|
|
858
|
+
r.searchParams.set(key, value.toString());
|
|
859
|
+
}
|
|
860
|
+
return r.toString();
|
|
861
|
+
}
|
|
862
|
+
makeIdFilter(idField, idFieldType, resourceId) {
|
|
863
|
+
return { [idField]: this.coerce(idFieldType, resourceId) };
|
|
864
|
+
}
|
|
865
|
+
makeIdSelect(model, modelMeta) {
|
|
866
|
+
const idFields = (0, runtime_1.getIdFields)(modelMeta, model);
|
|
867
|
+
if (idFields.length === 0) {
|
|
868
|
+
throw this.errors.noId;
|
|
869
|
+
}
|
|
870
|
+
else if (idFields.length > 1) {
|
|
871
|
+
throw this.errors.multiId;
|
|
872
|
+
}
|
|
873
|
+
return { [idFields[0].name]: true };
|
|
874
|
+
}
|
|
875
|
+
includeRelationshipIds(model, args, mode) {
|
|
876
|
+
const typeInfo = this.typeMap[model];
|
|
877
|
+
if (!typeInfo) {
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
for (const [relation, relationInfo] of Object.entries(typeInfo.relationships)) {
|
|
881
|
+
args[mode] = Object.assign(Object.assign({}, args[mode]), { [relation]: { select: { [relationInfo.idField]: true } } });
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
coerce(type, value) {
|
|
885
|
+
if (typeof value === 'string') {
|
|
886
|
+
if (type === 'Int' || type === 'BigInt') {
|
|
887
|
+
const parsed = parseInt(value);
|
|
888
|
+
if (isNaN(parsed)) {
|
|
889
|
+
throw new InvalidValueError(`invalid ${type} value: ${value}`);
|
|
890
|
+
}
|
|
891
|
+
return parsed;
|
|
892
|
+
}
|
|
893
|
+
else if (type === 'Float' || type === 'Decimal') {
|
|
894
|
+
const parsed = parseFloat(value);
|
|
895
|
+
if (isNaN(parsed)) {
|
|
896
|
+
throw new InvalidValueError(`invalid ${type} value: ${value}`);
|
|
897
|
+
}
|
|
898
|
+
return parsed;
|
|
899
|
+
}
|
|
900
|
+
else if (type === 'Boolean') {
|
|
901
|
+
if (value === 'true') {
|
|
902
|
+
return true;
|
|
903
|
+
}
|
|
904
|
+
else if (value === 'false') {
|
|
905
|
+
return false;
|
|
906
|
+
}
|
|
907
|
+
else {
|
|
908
|
+
throw new InvalidValueError(`invalid ${type} value: ${value}`);
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
return value;
|
|
913
|
+
}
|
|
914
|
+
makeNormalizedUrl(path, query) {
|
|
915
|
+
const url = new URL(this.makeLinkUrl(path));
|
|
916
|
+
for (const [key, value] of Object.entries(query !== null && query !== void 0 ? query : {})) {
|
|
917
|
+
if (key.startsWith('filter[') ||
|
|
918
|
+
key.startsWith('sort[') ||
|
|
919
|
+
key.startsWith('include[') ||
|
|
920
|
+
key.startsWith('fields[')) {
|
|
921
|
+
for (const v of (0, runtime_1.enumerate)(value)) {
|
|
922
|
+
url.searchParams.append(key, v);
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
return url.toString();
|
|
927
|
+
}
|
|
928
|
+
getPagination(query) {
|
|
929
|
+
var _a, _b;
|
|
930
|
+
if (!query) {
|
|
931
|
+
return { offset: 0, limit: (_a = this.options.pageSize) !== null && _a !== void 0 ? _a : DEFAULT_PAGE_SIZE };
|
|
932
|
+
}
|
|
933
|
+
let offset = 0;
|
|
934
|
+
if (query['page[offset]']) {
|
|
935
|
+
const value = query['page[offset]'];
|
|
936
|
+
const offsetText = Array.isArray(value) ? value[value.length - 1] : value;
|
|
937
|
+
offset = parseInt(offsetText);
|
|
938
|
+
if (isNaN(offset) || offset < 0) {
|
|
939
|
+
offset = 0;
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
let pageSizeOption = (_b = this.options.pageSize) !== null && _b !== void 0 ? _b : DEFAULT_PAGE_SIZE;
|
|
943
|
+
if (pageSizeOption <= 0) {
|
|
944
|
+
pageSizeOption = DEFAULT_PAGE_SIZE;
|
|
945
|
+
}
|
|
946
|
+
let limit = pageSizeOption;
|
|
947
|
+
if (query['page[limit]']) {
|
|
948
|
+
const value = query['page[limit]'];
|
|
949
|
+
const limitText = Array.isArray(value) ? value[value.length - 1] : value;
|
|
950
|
+
limit = parseInt(limitText);
|
|
951
|
+
if (isNaN(limit) || limit <= 0) {
|
|
952
|
+
limit = pageSizeOption;
|
|
953
|
+
}
|
|
954
|
+
limit = Math.min(pageSizeOption, limit);
|
|
955
|
+
}
|
|
956
|
+
return { offset, limit };
|
|
957
|
+
}
|
|
958
|
+
buildFilter(type, query) {
|
|
959
|
+
if (!query) {
|
|
960
|
+
return { filter: undefined, error: undefined };
|
|
961
|
+
}
|
|
962
|
+
const typeInfo = this.typeMap[(0, lower_case_first_1.lowerCaseFirst)(type)];
|
|
963
|
+
if (!typeInfo) {
|
|
964
|
+
return { filter: undefined, error: this.makeUnsupportedModelError(type) };
|
|
965
|
+
}
|
|
966
|
+
const items = [];
|
|
967
|
+
let currType = typeInfo;
|
|
968
|
+
for (const [key, value] of Object.entries(query)) {
|
|
969
|
+
if (!value) {
|
|
970
|
+
continue;
|
|
971
|
+
}
|
|
972
|
+
// try matching query parameter key as "filter[x][y]..."
|
|
973
|
+
const match = key.match(this.filterParamPattern);
|
|
974
|
+
if (!match || !match.groups) {
|
|
975
|
+
continue;
|
|
976
|
+
}
|
|
977
|
+
const filterKeys = match.groups.match
|
|
978
|
+
.replaceAll(/[[\]]/g, ' ')
|
|
979
|
+
.split(' ')
|
|
980
|
+
.filter((i) => i);
|
|
981
|
+
if (!filterKeys.length) {
|
|
982
|
+
continue;
|
|
983
|
+
}
|
|
984
|
+
// turn filter into a nested Prisma query object
|
|
985
|
+
const item = {};
|
|
986
|
+
let curr = item;
|
|
987
|
+
for (const filterValue of (0, runtime_1.enumerate)(value)) {
|
|
988
|
+
for (let i = 0; i < filterKeys.length; i++) {
|
|
989
|
+
// extract filter operation from (optional) trailing $op
|
|
990
|
+
let filterKey = filterKeys[i];
|
|
991
|
+
let filterOp;
|
|
992
|
+
const pos = filterKey.indexOf('$');
|
|
993
|
+
if (pos > 0) {
|
|
994
|
+
filterOp = filterKey.substring(pos + 1);
|
|
995
|
+
filterKey = filterKey.substring(0, pos);
|
|
996
|
+
}
|
|
997
|
+
if (!!filterOp && !FilterOperations.includes(filterOp)) {
|
|
998
|
+
return {
|
|
999
|
+
filter: undefined,
|
|
1000
|
+
error: this.makeError('invalidFilter', `invalid filter operation: ${filterOp}`),
|
|
1001
|
+
};
|
|
1002
|
+
}
|
|
1003
|
+
const fieldInfo = filterKey === 'id'
|
|
1004
|
+
? Object.values(currType.fields).find((f) => f.isId)
|
|
1005
|
+
: currType.fields[filterKey];
|
|
1006
|
+
if (!fieldInfo) {
|
|
1007
|
+
return { filter: undefined, error: this.makeError('invalidFilter') };
|
|
1008
|
+
}
|
|
1009
|
+
if (!fieldInfo.isDataModel) {
|
|
1010
|
+
// regular field
|
|
1011
|
+
if (i !== filterKeys.length - 1) {
|
|
1012
|
+
// must be the last segment of a filter
|
|
1013
|
+
return { filter: undefined, error: this.makeError('invalidFilter') };
|
|
1014
|
+
}
|
|
1015
|
+
curr[fieldInfo.name] = this.makeFilterValue(fieldInfo, filterValue, filterOp);
|
|
1016
|
+
}
|
|
1017
|
+
else {
|
|
1018
|
+
// relation field
|
|
1019
|
+
if (i === filterKeys.length - 1) {
|
|
1020
|
+
curr[fieldInfo.name] = this.makeFilterValue(fieldInfo, filterValue, filterOp);
|
|
1021
|
+
}
|
|
1022
|
+
else {
|
|
1023
|
+
// keep going
|
|
1024
|
+
if (fieldInfo.isArray) {
|
|
1025
|
+
// collection filtering implies "some" operation
|
|
1026
|
+
curr[fieldInfo.name] = { some: {} };
|
|
1027
|
+
curr = curr[fieldInfo.name].some;
|
|
1028
|
+
}
|
|
1029
|
+
else {
|
|
1030
|
+
curr = curr[fieldInfo.name] = {};
|
|
1031
|
+
}
|
|
1032
|
+
currType = this.typeMap[(0, lower_case_first_1.lowerCaseFirst)(fieldInfo.type)];
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
items.push(item);
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
if (items.length === 0) {
|
|
1040
|
+
return { filter: undefined, error: undefined };
|
|
1041
|
+
}
|
|
1042
|
+
else {
|
|
1043
|
+
// combine filters with AND
|
|
1044
|
+
return { filter: items.length === 1 ? items[0] : { AND: items }, error: undefined };
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
buildSort(type, query) {
|
|
1048
|
+
if (!(query === null || query === void 0 ? void 0 : query['sort'])) {
|
|
1049
|
+
return { sort: undefined, error: undefined };
|
|
1050
|
+
}
|
|
1051
|
+
const typeInfo = this.typeMap[(0, lower_case_first_1.lowerCaseFirst)(type)];
|
|
1052
|
+
if (!typeInfo) {
|
|
1053
|
+
return { sort: undefined, error: this.makeUnsupportedModelError(type) };
|
|
1054
|
+
}
|
|
1055
|
+
const result = [];
|
|
1056
|
+
for (const sortSpec of (0, runtime_1.enumerate)(query['sort'])) {
|
|
1057
|
+
const sortFields = sortSpec.split(',').filter((i) => i);
|
|
1058
|
+
for (const sortField of sortFields) {
|
|
1059
|
+
const dir = sortField.startsWith('-') ? 'desc' : 'asc';
|
|
1060
|
+
const cleanedSortField = sortField.startsWith('-') ? sortField.substring(1) : sortField;
|
|
1061
|
+
const parts = cleanedSortField.split('.').filter((i) => i);
|
|
1062
|
+
const sortItem = {};
|
|
1063
|
+
let curr = sortItem;
|
|
1064
|
+
let currType = typeInfo;
|
|
1065
|
+
for (let i = 0; i < parts.length; i++) {
|
|
1066
|
+
const part = parts[i];
|
|
1067
|
+
const fieldInfo = currType.fields[part];
|
|
1068
|
+
if (!fieldInfo || fieldInfo.isArray) {
|
|
1069
|
+
return {
|
|
1070
|
+
sort: undefined,
|
|
1071
|
+
error: this.makeError('invalidSort', 'sorting by array field is not supported'),
|
|
1072
|
+
};
|
|
1073
|
+
}
|
|
1074
|
+
if (i === parts.length - 1) {
|
|
1075
|
+
if (fieldInfo.isDataModel) {
|
|
1076
|
+
// relation field: sort by id
|
|
1077
|
+
const relationType = this.typeMap[(0, lower_case_first_1.lowerCaseFirst)(fieldInfo.type)];
|
|
1078
|
+
if (!relationType) {
|
|
1079
|
+
return { sort: undefined, error: this.makeUnsupportedModelError(fieldInfo.type) };
|
|
1080
|
+
}
|
|
1081
|
+
curr[fieldInfo.name] = { [relationType.idField]: dir };
|
|
1082
|
+
}
|
|
1083
|
+
else {
|
|
1084
|
+
// regular field
|
|
1085
|
+
curr[fieldInfo.name] = dir;
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
else {
|
|
1089
|
+
if (!fieldInfo.isDataModel) {
|
|
1090
|
+
// must be a relation field
|
|
1091
|
+
return {
|
|
1092
|
+
sort: undefined,
|
|
1093
|
+
error: this.makeError('invalidSort', 'intermediate sort segments must be relationships'),
|
|
1094
|
+
};
|
|
1095
|
+
}
|
|
1096
|
+
// keep going
|
|
1097
|
+
curr = curr[fieldInfo.name] = {};
|
|
1098
|
+
currType = this.typeMap[(0, lower_case_first_1.lowerCaseFirst)(fieldInfo.type)];
|
|
1099
|
+
if (!currType) {
|
|
1100
|
+
return { sort: undefined, error: this.makeUnsupportedModelError(fieldInfo.type) };
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
result.push(sortItem);
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
return { sort: result, error: undefined };
|
|
1108
|
+
}
|
|
1109
|
+
buildRelationSelect(type, include) {
|
|
1110
|
+
var _a;
|
|
1111
|
+
const typeInfo = this.typeMap[(0, lower_case_first_1.lowerCaseFirst)(type)];
|
|
1112
|
+
if (!typeInfo) {
|
|
1113
|
+
return { select: undefined, error: this.makeUnsupportedModelError(type) };
|
|
1114
|
+
}
|
|
1115
|
+
const result = {};
|
|
1116
|
+
const allIncludes = [];
|
|
1117
|
+
for (const includeItem of (0, runtime_1.enumerate)(include)) {
|
|
1118
|
+
const inclusions = includeItem.split(',').filter((i) => i);
|
|
1119
|
+
for (const inclusion of inclusions) {
|
|
1120
|
+
allIncludes.push(inclusion);
|
|
1121
|
+
const parts = inclusion.split('.');
|
|
1122
|
+
let currPayload = result;
|
|
1123
|
+
let currType = typeInfo;
|
|
1124
|
+
for (let i = 0; i < parts.length; i++) {
|
|
1125
|
+
const relation = parts[i];
|
|
1126
|
+
const relationInfo = currType.relationships[relation];
|
|
1127
|
+
if (!relationInfo) {
|
|
1128
|
+
return { select: undefined, error: this.makeUnsupportedRelationshipError(type, relation, 400) };
|
|
1129
|
+
}
|
|
1130
|
+
currType = this.typeMap[(0, lower_case_first_1.lowerCaseFirst)(relationInfo.type)];
|
|
1131
|
+
if (!currType) {
|
|
1132
|
+
return { select: undefined, error: this.makeUnsupportedModelError(relationInfo.type) };
|
|
1133
|
+
}
|
|
1134
|
+
if (i !== parts.length - 1) {
|
|
1135
|
+
currPayload[relation] = { include: Object.assign({}, (_a = currPayload[relation]) === null || _a === void 0 ? void 0 : _a.include) };
|
|
1136
|
+
currPayload = currPayload[relation].include;
|
|
1137
|
+
}
|
|
1138
|
+
else {
|
|
1139
|
+
currPayload[relation] = true;
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
return { select: result, error: undefined, allIncludes };
|
|
1145
|
+
}
|
|
1146
|
+
makeFilterValue(fieldInfo, value, op) {
|
|
1147
|
+
if (fieldInfo.isDataModel) {
|
|
1148
|
+
// relation filter is converted to an ID filter
|
|
1149
|
+
const info = this.typeMap[(0, lower_case_first_1.lowerCaseFirst)(fieldInfo.type)];
|
|
1150
|
+
if (fieldInfo.isArray) {
|
|
1151
|
+
// filtering a to-many relation, imply 'some' operator
|
|
1152
|
+
const values = value.split(',').filter((i) => i);
|
|
1153
|
+
const filterValue = values.length > 1
|
|
1154
|
+
? { OR: values.map((v) => this.makeIdFilter(info.idField, info.idFieldType, v)) }
|
|
1155
|
+
: this.makeIdFilter(info.idField, info.idFieldType, value);
|
|
1156
|
+
return { some: filterValue };
|
|
1157
|
+
}
|
|
1158
|
+
else {
|
|
1159
|
+
return { is: this.makeIdFilter(info.idField, info.idFieldType, value) };
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
else {
|
|
1163
|
+
const coerced = this.coerce(fieldInfo.type, value);
|
|
1164
|
+
switch (op) {
|
|
1165
|
+
case 'icontains':
|
|
1166
|
+
return { contains: coerced, mode: 'insensitive' };
|
|
1167
|
+
case 'hasSome':
|
|
1168
|
+
case 'hasEvery': {
|
|
1169
|
+
const values = value
|
|
1170
|
+
.split(',')
|
|
1171
|
+
.filter((i) => i)
|
|
1172
|
+
.map((v) => this.coerce(fieldInfo.type, v));
|
|
1173
|
+
return { [op]: values };
|
|
1174
|
+
}
|
|
1175
|
+
case 'isEmpty':
|
|
1176
|
+
if (value !== 'true' && value !== 'false') {
|
|
1177
|
+
throw new InvalidValueError(`Not a boolean: ${value}`);
|
|
1178
|
+
}
|
|
1179
|
+
return { isEmpty: value === 'true' ? true : false };
|
|
1180
|
+
default:
|
|
1181
|
+
return op ? { [op]: coerced } : { equals: coerced };
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
injectRelationQuery(type, injectTarget, injectKey, query) {
|
|
1186
|
+
const { filter, error: filterError } = this.buildFilter(type, query);
|
|
1187
|
+
if (filterError) {
|
|
1188
|
+
return filterError;
|
|
1189
|
+
}
|
|
1190
|
+
if (filter) {
|
|
1191
|
+
injectTarget[injectKey] = Object.assign(Object.assign({}, injectTarget[injectKey]), { where: filter });
|
|
1192
|
+
}
|
|
1193
|
+
const { sort, error: sortError } = this.buildSort(type, query);
|
|
1194
|
+
if (sortError) {
|
|
1195
|
+
return sortError;
|
|
1196
|
+
}
|
|
1197
|
+
if (sort) {
|
|
1198
|
+
injectTarget[injectKey] = Object.assign(Object.assign({}, injectTarget[injectKey]), { orderBy: sort });
|
|
1199
|
+
}
|
|
1200
|
+
const pagination = this.getPagination(query);
|
|
1201
|
+
const offset = pagination.offset;
|
|
1202
|
+
if (offset > 0) {
|
|
1203
|
+
// inject skip
|
|
1204
|
+
injectTarget[injectKey] = Object.assign(Object.assign({}, injectTarget[injectKey]), { skip: offset });
|
|
1205
|
+
}
|
|
1206
|
+
const limit = pagination.limit;
|
|
1207
|
+
if (limit !== Infinity) {
|
|
1208
|
+
// inject take
|
|
1209
|
+
injectTarget[injectKey] = Object.assign(Object.assign({}, injectTarget[injectKey]), { take: limit });
|
|
1210
|
+
// include a count query for the relationship
|
|
1211
|
+
injectTarget._count = { select: { [injectKey]: true } };
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
handlePrismaError(err) {
|
|
1215
|
+
if ((0, runtime_1.isPrismaClientKnownRequestError)(err)) {
|
|
1216
|
+
if (err.code === 'P2004') {
|
|
1217
|
+
return this.makeError('forbidden');
|
|
1218
|
+
}
|
|
1219
|
+
else if (err.code === 'P2025' || err.code === 'P2018') {
|
|
1220
|
+
return this.makeError('notFound');
|
|
1221
|
+
}
|
|
1222
|
+
else {
|
|
1223
|
+
return {
|
|
1224
|
+
status: 400,
|
|
1225
|
+
body: {
|
|
1226
|
+
errors: [
|
|
1227
|
+
{
|
|
1228
|
+
status: 400,
|
|
1229
|
+
code: 'prisma-error',
|
|
1230
|
+
prismaCode: err.code,
|
|
1231
|
+
title: 'Prisma error',
|
|
1232
|
+
detail: err.message,
|
|
1233
|
+
},
|
|
1234
|
+
],
|
|
1235
|
+
},
|
|
1236
|
+
};
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
else {
|
|
1240
|
+
const _err = err;
|
|
1241
|
+
return this.makeError('unknownError', `${_err.message}\n${_err.stack}`);
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
makeError(code, detail, status) {
|
|
1245
|
+
return {
|
|
1246
|
+
status: status !== null && status !== void 0 ? status : this.errors[code].status,
|
|
1247
|
+
body: {
|
|
1248
|
+
errors: [
|
|
1249
|
+
{
|
|
1250
|
+
status: status !== null && status !== void 0 ? status : this.errors[code].status,
|
|
1251
|
+
code: (0, change_case_1.paramCase)(code),
|
|
1252
|
+
title: this.errors[code].title,
|
|
1253
|
+
detail: detail || this.errors[code].detail,
|
|
1254
|
+
},
|
|
1255
|
+
],
|
|
1256
|
+
},
|
|
1257
|
+
};
|
|
1258
|
+
}
|
|
1259
|
+
makeUnsupportedModelError(model) {
|
|
1260
|
+
return this.makeError('unsupportedModel', `Model ${model} doesn't exist`);
|
|
1261
|
+
}
|
|
1262
|
+
makeUnsupportedRelationshipError(model, relationship, status) {
|
|
1263
|
+
return this.makeError('unsupportedRelationship', `Relationship ${model}.${relationship} doesn't exist`, status);
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
function makeHandler(options) {
|
|
1267
|
+
const handler = new RequestHandler(options);
|
|
1268
|
+
return handler.handleRequest.bind(handler);
|
|
1269
|
+
}
|
|
1270
|
+
exports.default = makeHandler;
|
|
1271
|
+
//# sourceMappingURL=index.js.map
|