@zenstackhq/server 3.0.0-beta.12 → 3.0.0-beta.14
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/dist/api.cjs +1767 -19
- package/dist/api.cjs.map +1 -1
- package/dist/api.d.cts +123 -4
- package/dist/api.d.ts +123 -4
- package/dist/api.js +1762 -15
- package/dist/api.js.map +1 -1
- package/dist/common-6DG-xEmM.d.cts +14 -0
- package/dist/common-CyapsM8n.d.ts +14 -0
- package/dist/elysia.cjs +118 -0
- package/dist/elysia.cjs.map +1 -0
- package/dist/elysia.d.cts +53 -0
- package/dist/elysia.d.ts +53 -0
- package/dist/elysia.js +83 -0
- package/dist/elysia.js.map +1 -0
- package/dist/express.cjs +41 -3
- package/dist/express.cjs.map +1 -1
- package/dist/express.d.cts +7 -7
- package/dist/express.d.ts +7 -7
- package/dist/express.js +30 -2
- package/dist/express.js.map +1 -1
- package/dist/fastify.cjs +103 -0
- package/dist/fastify.cjs.map +1 -0
- package/dist/fastify.d.cts +22 -0
- package/dist/fastify.d.ts +22 -0
- package/dist/fastify.js +68 -0
- package/dist/fastify.js.map +1 -0
- package/dist/hono.cjs +111 -0
- package/dist/hono.cjs.map +1 -0
- package/dist/hono.d.cts +18 -0
- package/dist/hono.d.ts +18 -0
- package/dist/hono.js +76 -0
- package/dist/hono.js.map +1 -0
- package/dist/next.cjs +178 -0
- package/dist/next.cjs.map +1 -0
- package/dist/next.d.cts +61 -0
- package/dist/next.d.ts +61 -0
- package/dist/next.js +143 -0
- package/dist/next.js.map +1 -0
- package/dist/nuxt.cjs +109 -0
- package/dist/nuxt.cjs.map +1 -0
- package/dist/nuxt.d.cts +19 -0
- package/dist/nuxt.d.ts +19 -0
- package/dist/nuxt.js +74 -0
- package/dist/nuxt.js.map +1 -0
- package/dist/sveltekit.cjs +134 -0
- package/dist/sveltekit.cjs.map +1 -0
- package/dist/sveltekit.d.cts +25 -0
- package/dist/sveltekit.d.ts +25 -0
- package/dist/sveltekit.js +99 -0
- package/dist/sveltekit.js.map +1 -0
- package/dist/tanstack-start.cjs +139 -0
- package/dist/tanstack-start.cjs.map +1 -0
- package/dist/tanstack-start.d.cts +32 -0
- package/dist/tanstack-start.d.ts +32 -0
- package/dist/tanstack-start.js +104 -0
- package/dist/tanstack-start.js.map +1 -0
- package/dist/{types-BH-88xJo.d.cts → types-D5t0sUEw.d.cts} +6 -2
- package/dist/{types-BH-88xJo.d.ts → types-D5t0sUEw.d.ts} +6 -2
- package/package.json +120 -8
package/dist/api.cjs
CHANGED
|
@@ -31,19 +31,26 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
// src/api/index.ts
|
|
32
32
|
var api_exports = {};
|
|
33
33
|
__export(api_exports, {
|
|
34
|
-
RPCApiHandler: () => RPCApiHandler
|
|
34
|
+
RPCApiHandler: () => RPCApiHandler,
|
|
35
|
+
RestApiHandler: () => RestApiHandler
|
|
35
36
|
});
|
|
36
37
|
module.exports = __toCommonJS(api_exports);
|
|
37
38
|
|
|
38
|
-
// src/api/
|
|
39
|
+
// src/api/rest/index.ts
|
|
39
40
|
var import_common_helpers = require("@zenstackhq/common-helpers");
|
|
40
|
-
var
|
|
41
|
+
var import_orm = require("@zenstackhq/orm");
|
|
42
|
+
var import_decimal2 = require("decimal.js");
|
|
41
43
|
var import_superjson2 = __toESM(require("superjson"), 1);
|
|
44
|
+
var import_ts_japi = require("ts-japi");
|
|
45
|
+
var import_url_pattern = __toESM(require("url-pattern"), 1);
|
|
46
|
+
var import_zod = __toESM(require("zod"), 1);
|
|
42
47
|
|
|
43
48
|
// src/api/utils.ts
|
|
44
49
|
var import_decimal = require("decimal.js");
|
|
45
50
|
var import_superjson = __toESM(require("superjson"), 1);
|
|
46
51
|
var import_ts_pattern = require("ts-pattern");
|
|
52
|
+
var import_v3 = require("zod-validation-error/v3");
|
|
53
|
+
var import_v4 = require("zod-validation-error/v4");
|
|
47
54
|
function log(logger, level, message, error) {
|
|
48
55
|
if (!logger) {
|
|
49
56
|
return;
|
|
@@ -73,8 +80,1745 @@ function registerCustomSerializers() {
|
|
|
73
80
|
}
|
|
74
81
|
}
|
|
75
82
|
__name(registerCustomSerializers, "registerCustomSerializers");
|
|
83
|
+
function getZodErrorMessage(error) {
|
|
84
|
+
if ("_zod" in error) {
|
|
85
|
+
return (0, import_v4.fromError)(error).toString();
|
|
86
|
+
} else {
|
|
87
|
+
return (0, import_v3.fromError)(error).toString();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
__name(getZodErrorMessage, "getZodErrorMessage");
|
|
91
|
+
|
|
92
|
+
// src/api/rest/index.ts
|
|
93
|
+
var InvalidValueError = class InvalidValueError2 extends Error {
|
|
94
|
+
static {
|
|
95
|
+
__name(this, "InvalidValueError");
|
|
96
|
+
}
|
|
97
|
+
constructor(message) {
|
|
98
|
+
super(message);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
var DEFAULT_PAGE_SIZE = 100;
|
|
102
|
+
var FilterOperations = [
|
|
103
|
+
"lt",
|
|
104
|
+
"lte",
|
|
105
|
+
"gt",
|
|
106
|
+
"gte",
|
|
107
|
+
"contains",
|
|
108
|
+
"icontains",
|
|
109
|
+
"search",
|
|
110
|
+
"startsWith",
|
|
111
|
+
"endsWith",
|
|
112
|
+
"has",
|
|
113
|
+
"hasEvery",
|
|
114
|
+
"hasSome",
|
|
115
|
+
"isEmpty"
|
|
116
|
+
];
|
|
117
|
+
var DEFAULT_ID_DIVIDER = "_";
|
|
118
|
+
registerCustomSerializers();
|
|
119
|
+
var RestApiHandler = class {
|
|
120
|
+
static {
|
|
121
|
+
__name(this, "RestApiHandler");
|
|
122
|
+
}
|
|
123
|
+
options;
|
|
124
|
+
// resource serializers
|
|
125
|
+
serializers = /* @__PURE__ */ new Map();
|
|
126
|
+
// error responses
|
|
127
|
+
errors = {
|
|
128
|
+
unsupportedModel: {
|
|
129
|
+
status: 404,
|
|
130
|
+
title: "Unsupported model type",
|
|
131
|
+
detail: "The model type is not supported"
|
|
132
|
+
},
|
|
133
|
+
unsupportedRelationship: {
|
|
134
|
+
status: 400,
|
|
135
|
+
title: "Unsupported relationship",
|
|
136
|
+
detail: "The relationship is not supported"
|
|
137
|
+
},
|
|
138
|
+
invalidPath: {
|
|
139
|
+
status: 400,
|
|
140
|
+
title: "The request path is invalid"
|
|
141
|
+
},
|
|
142
|
+
invalidVerb: {
|
|
143
|
+
status: 400,
|
|
144
|
+
title: "The HTTP verb is not supported"
|
|
145
|
+
},
|
|
146
|
+
notFound: {
|
|
147
|
+
status: 404,
|
|
148
|
+
title: "Resource not found"
|
|
149
|
+
},
|
|
150
|
+
noId: {
|
|
151
|
+
status: 400,
|
|
152
|
+
title: "Model without an ID field is not supported"
|
|
153
|
+
},
|
|
154
|
+
invalidId: {
|
|
155
|
+
status: 400,
|
|
156
|
+
title: "Resource ID is invalid"
|
|
157
|
+
},
|
|
158
|
+
invalidPayload: {
|
|
159
|
+
status: 400,
|
|
160
|
+
title: "Invalid payload"
|
|
161
|
+
},
|
|
162
|
+
invalidRelationData: {
|
|
163
|
+
status: 400,
|
|
164
|
+
title: "Invalid relation data",
|
|
165
|
+
detail: "Invalid relationship data"
|
|
166
|
+
},
|
|
167
|
+
invalidRelation: {
|
|
168
|
+
status: 400,
|
|
169
|
+
title: "Invalid relation",
|
|
170
|
+
detail: "Invalid relationship"
|
|
171
|
+
},
|
|
172
|
+
invalidFilter: {
|
|
173
|
+
status: 400,
|
|
174
|
+
title: "Invalid filter"
|
|
175
|
+
},
|
|
176
|
+
invalidSort: {
|
|
177
|
+
status: 400,
|
|
178
|
+
title: "Invalid sort"
|
|
179
|
+
},
|
|
180
|
+
invalidValue: {
|
|
181
|
+
status: 400,
|
|
182
|
+
title: "Invalid value for type"
|
|
183
|
+
},
|
|
184
|
+
duplicatedFieldsParameter: {
|
|
185
|
+
status: 400,
|
|
186
|
+
title: "Fields Parameter Duplicated"
|
|
187
|
+
},
|
|
188
|
+
forbidden: {
|
|
189
|
+
status: 403,
|
|
190
|
+
title: "Operation is forbidden"
|
|
191
|
+
},
|
|
192
|
+
validationError: {
|
|
193
|
+
status: 422,
|
|
194
|
+
title: "Operation is unprocessable due to validation errors"
|
|
195
|
+
},
|
|
196
|
+
queryError: {
|
|
197
|
+
status: 400,
|
|
198
|
+
title: "Error occurred while executing the query"
|
|
199
|
+
},
|
|
200
|
+
unknownError: {
|
|
201
|
+
status: 500,
|
|
202
|
+
title: "Unknown error"
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
filterParamPattern = new RegExp(/^filter(?<match>(\[[^[\]]+\])+)$/);
|
|
206
|
+
// zod schema for payload of creating and updating a resource
|
|
207
|
+
createUpdatePayloadSchema = import_zod.default.object({
|
|
208
|
+
data: import_zod.default.object({
|
|
209
|
+
type: import_zod.default.string(),
|
|
210
|
+
attributes: import_zod.default.object({}).passthrough().optional(),
|
|
211
|
+
relationships: import_zod.default.record(import_zod.default.string(), import_zod.default.object({
|
|
212
|
+
data: import_zod.default.union([
|
|
213
|
+
import_zod.default.object({
|
|
214
|
+
type: import_zod.default.string(),
|
|
215
|
+
id: import_zod.default.union([
|
|
216
|
+
import_zod.default.string(),
|
|
217
|
+
import_zod.default.number()
|
|
218
|
+
])
|
|
219
|
+
}),
|
|
220
|
+
import_zod.default.array(import_zod.default.object({
|
|
221
|
+
type: import_zod.default.string(),
|
|
222
|
+
id: import_zod.default.union([
|
|
223
|
+
import_zod.default.string(),
|
|
224
|
+
import_zod.default.number()
|
|
225
|
+
])
|
|
226
|
+
}))
|
|
227
|
+
])
|
|
228
|
+
})).optional()
|
|
229
|
+
}),
|
|
230
|
+
meta: import_zod.default.object({}).passthrough().optional()
|
|
231
|
+
}).strict();
|
|
232
|
+
// zod schema for updating a single relationship
|
|
233
|
+
updateSingleRelationSchema = import_zod.default.object({
|
|
234
|
+
data: import_zod.default.object({
|
|
235
|
+
type: import_zod.default.string(),
|
|
236
|
+
id: import_zod.default.union([
|
|
237
|
+
import_zod.default.string(),
|
|
238
|
+
import_zod.default.number()
|
|
239
|
+
])
|
|
240
|
+
}).nullable()
|
|
241
|
+
});
|
|
242
|
+
// zod schema for updating collection relationship
|
|
243
|
+
updateCollectionRelationSchema = import_zod.default.object({
|
|
244
|
+
data: import_zod.default.array(import_zod.default.object({
|
|
245
|
+
type: import_zod.default.string(),
|
|
246
|
+
id: import_zod.default.union([
|
|
247
|
+
import_zod.default.string(),
|
|
248
|
+
import_zod.default.number()
|
|
249
|
+
])
|
|
250
|
+
}))
|
|
251
|
+
});
|
|
252
|
+
upsertMetaSchema = import_zod.default.object({
|
|
253
|
+
meta: import_zod.default.object({
|
|
254
|
+
operation: import_zod.default.literal("upsert"),
|
|
255
|
+
matchFields: import_zod.default.array(import_zod.default.string()).min(1)
|
|
256
|
+
})
|
|
257
|
+
});
|
|
258
|
+
// all known types and their metadata
|
|
259
|
+
typeMap = {};
|
|
260
|
+
// divider used to separate compound ID fields
|
|
261
|
+
idDivider;
|
|
262
|
+
urlPatternMap;
|
|
263
|
+
modelNameMapping;
|
|
264
|
+
reverseModelNameMapping;
|
|
265
|
+
externalIdMapping;
|
|
266
|
+
constructor(options) {
|
|
267
|
+
this.options = options;
|
|
268
|
+
this.idDivider = options.idDivider ?? DEFAULT_ID_DIVIDER;
|
|
269
|
+
const segmentCharset = options.urlSegmentCharset ?? "a-zA-Z0-9-_~ %";
|
|
270
|
+
this.modelNameMapping = options.modelNameMapping ?? {};
|
|
271
|
+
this.modelNameMapping = Object.fromEntries(Object.entries(this.modelNameMapping).map(([k, v]) => [
|
|
272
|
+
(0, import_common_helpers.lowerCaseFirst)(k),
|
|
273
|
+
v
|
|
274
|
+
]));
|
|
275
|
+
this.reverseModelNameMapping = Object.fromEntries(Object.entries(this.modelNameMapping).map(([k, v]) => [
|
|
276
|
+
v,
|
|
277
|
+
k
|
|
278
|
+
]));
|
|
279
|
+
this.externalIdMapping = options.externalIdMapping ?? {};
|
|
280
|
+
this.externalIdMapping = Object.fromEntries(Object.entries(this.externalIdMapping).map(([k, v]) => [
|
|
281
|
+
(0, import_common_helpers.lowerCaseFirst)(k),
|
|
282
|
+
v
|
|
283
|
+
]));
|
|
284
|
+
this.urlPatternMap = this.buildUrlPatternMap(segmentCharset);
|
|
285
|
+
this.buildTypeMap();
|
|
286
|
+
this.buildSerializers();
|
|
287
|
+
}
|
|
288
|
+
get schema() {
|
|
289
|
+
return this.options.schema;
|
|
290
|
+
}
|
|
291
|
+
get log() {
|
|
292
|
+
return this.options.log;
|
|
293
|
+
}
|
|
294
|
+
buildUrlPatternMap(urlSegmentNameCharset) {
|
|
295
|
+
const options = {
|
|
296
|
+
segmentValueCharset: urlSegmentNameCharset
|
|
297
|
+
};
|
|
298
|
+
const buildPath = /* @__PURE__ */ __name((segments) => {
|
|
299
|
+
return "/" + segments.join("/");
|
|
300
|
+
}, "buildPath");
|
|
301
|
+
return {
|
|
302
|
+
["single"]: new import_url_pattern.default(buildPath([
|
|
303
|
+
":type",
|
|
304
|
+
":id"
|
|
305
|
+
]), options),
|
|
306
|
+
["fetchRelationship"]: new import_url_pattern.default(buildPath([
|
|
307
|
+
":type",
|
|
308
|
+
":id",
|
|
309
|
+
":relationship"
|
|
310
|
+
]), options),
|
|
311
|
+
["relationship"]: new import_url_pattern.default(buildPath([
|
|
312
|
+
":type",
|
|
313
|
+
":id",
|
|
314
|
+
"relationships",
|
|
315
|
+
":relationship"
|
|
316
|
+
]), options),
|
|
317
|
+
["collection"]: new import_url_pattern.default(buildPath([
|
|
318
|
+
":type"
|
|
319
|
+
]), options)
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
mapModelName(modelName) {
|
|
323
|
+
return this.modelNameMapping[modelName] ?? modelName;
|
|
324
|
+
}
|
|
325
|
+
matchUrlPattern(path, routeType) {
|
|
326
|
+
const pattern = this.urlPatternMap[routeType];
|
|
327
|
+
if (!pattern) {
|
|
328
|
+
throw new InvalidValueError(`Unknown route type: ${routeType}`);
|
|
329
|
+
}
|
|
330
|
+
const match2 = pattern.match(path);
|
|
331
|
+
if (!match2) {
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
if (match2.type in this.modelNameMapping) {
|
|
335
|
+
throw new InvalidValueError(`use the mapped model name: ${this.modelNameMapping[match2.type]} and not ${match2.type}`);
|
|
336
|
+
}
|
|
337
|
+
if (match2.type in this.reverseModelNameMapping) {
|
|
338
|
+
match2.type = this.reverseModelNameMapping[match2.type];
|
|
339
|
+
}
|
|
340
|
+
return match2;
|
|
341
|
+
}
|
|
342
|
+
async handleRequest({ client, method, path, query, requestBody }) {
|
|
343
|
+
method = method.toUpperCase();
|
|
344
|
+
if (!path.startsWith("/")) {
|
|
345
|
+
path = "/" + path;
|
|
346
|
+
}
|
|
347
|
+
try {
|
|
348
|
+
switch (method) {
|
|
349
|
+
case "GET": {
|
|
350
|
+
let match2 = this.matchUrlPattern(path, "single");
|
|
351
|
+
if (match2) {
|
|
352
|
+
return await this.processSingleRead(client, match2.type, match2.id, query);
|
|
353
|
+
}
|
|
354
|
+
match2 = this.matchUrlPattern(path, "fetchRelationship");
|
|
355
|
+
if (match2) {
|
|
356
|
+
return await this.processFetchRelated(client, match2.type, match2.id, match2.relationship, query);
|
|
357
|
+
}
|
|
358
|
+
match2 = this.matchUrlPattern(path, "relationship");
|
|
359
|
+
if (match2) {
|
|
360
|
+
return await this.processReadRelationship(client, match2.type, match2.id, match2.relationship, query);
|
|
361
|
+
}
|
|
362
|
+
match2 = this.matchUrlPattern(path, "collection");
|
|
363
|
+
if (match2) {
|
|
364
|
+
return await this.processCollectionRead(client, match2.type, query);
|
|
365
|
+
}
|
|
366
|
+
return this.makeError("invalidPath");
|
|
367
|
+
}
|
|
368
|
+
case "POST": {
|
|
369
|
+
if (!requestBody) {
|
|
370
|
+
return this.makeError("invalidPayload");
|
|
371
|
+
}
|
|
372
|
+
let match2 = this.matchUrlPattern(path, "collection");
|
|
373
|
+
if (match2) {
|
|
374
|
+
const body = requestBody;
|
|
375
|
+
const upsertMeta = this.upsertMetaSchema.safeParse(body);
|
|
376
|
+
if (upsertMeta.success) {
|
|
377
|
+
return await this.processUpsert(client, match2.type, query, requestBody);
|
|
378
|
+
} else {
|
|
379
|
+
return await this.processCreate(client, match2.type, query, requestBody);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
match2 = this.matchUrlPattern(path, "relationship");
|
|
383
|
+
if (match2) {
|
|
384
|
+
return await this.processRelationshipCRUD(client, "create", match2.type, match2.id, match2.relationship, query, requestBody);
|
|
385
|
+
}
|
|
386
|
+
return this.makeError("invalidPath");
|
|
387
|
+
}
|
|
388
|
+
// TODO: PUT for full update
|
|
389
|
+
case "PUT":
|
|
390
|
+
case "PATCH": {
|
|
391
|
+
if (!requestBody) {
|
|
392
|
+
return this.makeError("invalidPayload");
|
|
393
|
+
}
|
|
394
|
+
let match2 = this.matchUrlPattern(path, "single");
|
|
395
|
+
if (match2) {
|
|
396
|
+
return await this.processUpdate(client, match2.type, match2.id, query, requestBody);
|
|
397
|
+
}
|
|
398
|
+
match2 = this.matchUrlPattern(path, "relationship");
|
|
399
|
+
if (match2) {
|
|
400
|
+
return await this.processRelationshipCRUD(client, "update", match2.type, match2.id, match2.relationship, query, requestBody);
|
|
401
|
+
}
|
|
402
|
+
return this.makeError("invalidPath");
|
|
403
|
+
}
|
|
404
|
+
case "DELETE": {
|
|
405
|
+
let match2 = this.matchUrlPattern(path, "single");
|
|
406
|
+
if (match2) {
|
|
407
|
+
return await this.processDelete(client, match2.type, match2.id);
|
|
408
|
+
}
|
|
409
|
+
match2 = this.matchUrlPattern(path, "relationship");
|
|
410
|
+
if (match2) {
|
|
411
|
+
return await this.processRelationshipCRUD(client, "delete", match2.type, match2.id, match2.relationship, query, requestBody);
|
|
412
|
+
}
|
|
413
|
+
return this.makeError("invalidPath");
|
|
414
|
+
}
|
|
415
|
+
default:
|
|
416
|
+
return this.makeError("invalidPath");
|
|
417
|
+
}
|
|
418
|
+
} catch (err) {
|
|
419
|
+
if (err instanceof InvalidValueError) {
|
|
420
|
+
return this.makeError("invalidValue", err.message);
|
|
421
|
+
} else if (err instanceof import_orm.ZenStackError) {
|
|
422
|
+
return this.handleZenStackError(err);
|
|
423
|
+
} else {
|
|
424
|
+
return this.handleGenericError(err);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
handleGenericError(err) {
|
|
429
|
+
return this.makeError("unknownError", err instanceof Error ? `${err.message}
|
|
430
|
+
${err.stack}` : "Unknown error");
|
|
431
|
+
}
|
|
432
|
+
async processSingleRead(client, type, resourceId, query) {
|
|
433
|
+
const typeInfo = this.getModelInfo(type);
|
|
434
|
+
if (!typeInfo) {
|
|
435
|
+
return this.makeUnsupportedModelError(type);
|
|
436
|
+
}
|
|
437
|
+
const args = {
|
|
438
|
+
where: this.makeIdFilter(typeInfo.idFields, resourceId)
|
|
439
|
+
};
|
|
440
|
+
this.includeRelationshipIds(type, args, "include");
|
|
441
|
+
let include;
|
|
442
|
+
if (query?.["include"]) {
|
|
443
|
+
const { select: select2, error: error2, allIncludes } = this.buildRelationSelect(type, query["include"], query);
|
|
444
|
+
if (error2) {
|
|
445
|
+
return error2;
|
|
446
|
+
}
|
|
447
|
+
if (select2) {
|
|
448
|
+
args.include = {
|
|
449
|
+
...args.include,
|
|
450
|
+
...select2
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
include = allIncludes;
|
|
454
|
+
}
|
|
455
|
+
const { select, error } = this.buildPartialSelect(type, query);
|
|
456
|
+
if (error) return error;
|
|
457
|
+
if (select) {
|
|
458
|
+
args.select = {
|
|
459
|
+
...select,
|
|
460
|
+
...args.select
|
|
461
|
+
};
|
|
462
|
+
if (args.include) {
|
|
463
|
+
args.select = {
|
|
464
|
+
...args.select,
|
|
465
|
+
...args.include
|
|
466
|
+
};
|
|
467
|
+
args.include = void 0;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
const entity = await client[type].findUnique(args);
|
|
471
|
+
if (entity) {
|
|
472
|
+
return {
|
|
473
|
+
status: 200,
|
|
474
|
+
body: await this.serializeItems(type, entity, {
|
|
475
|
+
include
|
|
476
|
+
})
|
|
477
|
+
};
|
|
478
|
+
} else {
|
|
479
|
+
return this.makeError("notFound");
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
async processFetchRelated(client, type, resourceId, relationship, query) {
|
|
483
|
+
const typeInfo = this.getModelInfo(type);
|
|
484
|
+
if (!typeInfo) {
|
|
485
|
+
return this.makeUnsupportedModelError(type);
|
|
486
|
+
}
|
|
487
|
+
const relationInfo = typeInfo.relationships[relationship];
|
|
488
|
+
if (!relationInfo) {
|
|
489
|
+
return this.makeUnsupportedRelationshipError(type, relationship, 404);
|
|
490
|
+
}
|
|
491
|
+
let select;
|
|
492
|
+
let include;
|
|
493
|
+
if (query?.["include"]) {
|
|
494
|
+
const { select: relationSelect, error, allIncludes } = this.buildRelationSelect(type, query["include"], query);
|
|
495
|
+
if (error) {
|
|
496
|
+
return error;
|
|
497
|
+
}
|
|
498
|
+
include = allIncludes.filter((i) => i.startsWith(`${relationship}.`)).map((i) => i.substring(`${relationship}.`.length));
|
|
499
|
+
select = relationSelect;
|
|
500
|
+
}
|
|
501
|
+
if (!select) {
|
|
502
|
+
const { select: partialFields, error } = this.buildPartialSelect((0, import_common_helpers.lowerCaseFirst)(relationInfo.type), query);
|
|
503
|
+
if (error) return error;
|
|
504
|
+
select = partialFields ? {
|
|
505
|
+
[relationship]: {
|
|
506
|
+
select: {
|
|
507
|
+
...partialFields
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
} : {
|
|
511
|
+
[relationship]: true
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
const args = {
|
|
515
|
+
where: this.makeIdFilter(typeInfo.idFields, resourceId),
|
|
516
|
+
select
|
|
517
|
+
};
|
|
518
|
+
if (relationInfo.isCollection) {
|
|
519
|
+
const error = this.injectRelationQuery(relationInfo.type, select, relationship, query);
|
|
520
|
+
if (error) {
|
|
521
|
+
return error;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
const entity = await client[type].findUnique(args);
|
|
525
|
+
let paginator;
|
|
526
|
+
if (entity?._count?.[relationship] !== void 0) {
|
|
527
|
+
const total = entity?._count?.[relationship];
|
|
528
|
+
const url = this.makeNormalizedUrl(`/${type}/${resourceId}/${relationship}`, query);
|
|
529
|
+
const { offset, limit } = this.getPagination(query);
|
|
530
|
+
paginator = this.makePaginator(url, offset, limit, total);
|
|
531
|
+
}
|
|
532
|
+
if (entity?.[relationship]) {
|
|
533
|
+
const mappedType = this.mapModelName(type);
|
|
534
|
+
return {
|
|
535
|
+
status: 200,
|
|
536
|
+
body: await this.serializeItems(relationInfo.type, entity[relationship], {
|
|
537
|
+
linkers: {
|
|
538
|
+
document: new import_ts_japi.Linker(() => this.makeLinkUrl(`/${mappedType}/${resourceId}/${relationship}`)),
|
|
539
|
+
paginator
|
|
540
|
+
},
|
|
541
|
+
include
|
|
542
|
+
})
|
|
543
|
+
};
|
|
544
|
+
} else {
|
|
545
|
+
return this.makeError("notFound");
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
async processReadRelationship(client, type, resourceId, relationship, query) {
|
|
549
|
+
const typeInfo = this.getModelInfo(type);
|
|
550
|
+
if (!typeInfo) {
|
|
551
|
+
return this.makeUnsupportedModelError(type);
|
|
552
|
+
}
|
|
553
|
+
const relationInfo = typeInfo.relationships[relationship];
|
|
554
|
+
if (!relationInfo) {
|
|
555
|
+
return this.makeUnsupportedRelationshipError(type, relationship, 404);
|
|
556
|
+
}
|
|
557
|
+
const args = {
|
|
558
|
+
where: this.makeIdFilter(typeInfo.idFields, resourceId),
|
|
559
|
+
select: this.makeIdSelect(typeInfo.idFields)
|
|
560
|
+
};
|
|
561
|
+
args.select = {
|
|
562
|
+
...args.select,
|
|
563
|
+
[relationship]: {
|
|
564
|
+
select: this.makeIdSelect(relationInfo.idFields)
|
|
565
|
+
}
|
|
566
|
+
};
|
|
567
|
+
let paginator;
|
|
568
|
+
if (relationInfo.isCollection) {
|
|
569
|
+
const error = this.injectRelationQuery(relationInfo.type, args.select, relationship, query);
|
|
570
|
+
if (error) {
|
|
571
|
+
return error;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
const entity = await client[type].findUnique(args);
|
|
575
|
+
const mappedType = this.mapModelName(type);
|
|
576
|
+
if (entity?._count?.[relationship] !== void 0) {
|
|
577
|
+
const total = entity?._count?.[relationship];
|
|
578
|
+
const url = this.makeNormalizedUrl(`/${mappedType}/${resourceId}/relationships/${relationship}`, query);
|
|
579
|
+
const { offset, limit } = this.getPagination(query);
|
|
580
|
+
paginator = this.makePaginator(url, offset, limit, total);
|
|
581
|
+
}
|
|
582
|
+
if (entity?.[relationship]) {
|
|
583
|
+
const serialized = await this.serializeItems(relationInfo.type, entity[relationship], {
|
|
584
|
+
linkers: {
|
|
585
|
+
document: new import_ts_japi.Linker(() => this.makeLinkUrl(`/${mappedType}/${resourceId}/relationships/${relationship}`)),
|
|
586
|
+
paginator
|
|
587
|
+
},
|
|
588
|
+
onlyIdentifier: true
|
|
589
|
+
});
|
|
590
|
+
return {
|
|
591
|
+
status: 200,
|
|
592
|
+
body: serialized
|
|
593
|
+
};
|
|
594
|
+
} else {
|
|
595
|
+
return this.makeError("notFound");
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
async processCollectionRead(client, type, query) {
|
|
599
|
+
const typeInfo = this.getModelInfo(type);
|
|
600
|
+
if (!typeInfo) {
|
|
601
|
+
return this.makeUnsupportedModelError(type);
|
|
602
|
+
}
|
|
603
|
+
const args = {};
|
|
604
|
+
const { filter, error: filterError } = this.buildFilter(type, query);
|
|
605
|
+
if (filterError) {
|
|
606
|
+
return filterError;
|
|
607
|
+
}
|
|
608
|
+
if (filter) {
|
|
609
|
+
args.where = filter;
|
|
610
|
+
}
|
|
611
|
+
const { sort, error: sortError } = this.buildSort(type, query);
|
|
612
|
+
if (sortError) {
|
|
613
|
+
return sortError;
|
|
614
|
+
}
|
|
615
|
+
if (sort) {
|
|
616
|
+
args.orderBy = sort;
|
|
617
|
+
}
|
|
618
|
+
this.includeRelationshipIds(type, args, "include");
|
|
619
|
+
let include;
|
|
620
|
+
if (query?.["include"]) {
|
|
621
|
+
const { select: select2, error: error2, allIncludes } = this.buildRelationSelect(type, query["include"], query);
|
|
622
|
+
if (error2) {
|
|
623
|
+
return error2;
|
|
624
|
+
}
|
|
625
|
+
if (select2) {
|
|
626
|
+
args.include = {
|
|
627
|
+
...args.include,
|
|
628
|
+
...select2
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
include = allIncludes;
|
|
632
|
+
}
|
|
633
|
+
const { select, error } = this.buildPartialSelect(type, query);
|
|
634
|
+
if (error) return error;
|
|
635
|
+
if (select) {
|
|
636
|
+
args.select = {
|
|
637
|
+
...select,
|
|
638
|
+
...args.select
|
|
639
|
+
};
|
|
640
|
+
if (args.include) {
|
|
641
|
+
args.select = {
|
|
642
|
+
...args.select,
|
|
643
|
+
...args.include
|
|
644
|
+
};
|
|
645
|
+
args.include = void 0;
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
const { offset, limit } = this.getPagination(query);
|
|
649
|
+
if (offset > 0) {
|
|
650
|
+
args.skip = offset;
|
|
651
|
+
}
|
|
652
|
+
if (limit === Infinity) {
|
|
653
|
+
const entities = await client[type].findMany(args);
|
|
654
|
+
const body = await this.serializeItems(type, entities, {
|
|
655
|
+
include
|
|
656
|
+
});
|
|
657
|
+
const total = entities.length;
|
|
658
|
+
body.meta = this.addTotalCountToMeta(body.meta, total);
|
|
659
|
+
return {
|
|
660
|
+
status: 200,
|
|
661
|
+
body
|
|
662
|
+
};
|
|
663
|
+
} else {
|
|
664
|
+
args.take = limit;
|
|
665
|
+
const [entities, count] = await Promise.all([
|
|
666
|
+
client[type].findMany(args),
|
|
667
|
+
client[type].count({
|
|
668
|
+
where: args.where ?? {}
|
|
669
|
+
})
|
|
670
|
+
]);
|
|
671
|
+
const total = count;
|
|
672
|
+
const mappedType = this.mapModelName(type);
|
|
673
|
+
const url = this.makeNormalizedUrl(`/${mappedType}`, query);
|
|
674
|
+
const options = {
|
|
675
|
+
include,
|
|
676
|
+
linkers: {
|
|
677
|
+
paginator: this.makePaginator(url, offset, limit, total)
|
|
678
|
+
}
|
|
679
|
+
};
|
|
680
|
+
const body = await this.serializeItems(type, entities, options);
|
|
681
|
+
body.meta = this.addTotalCountToMeta(body.meta, total);
|
|
682
|
+
return {
|
|
683
|
+
status: 200,
|
|
684
|
+
body
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
buildPartialSelect(type, query) {
|
|
689
|
+
const selectFieldsQuery = query?.[`fields[${type}]`];
|
|
690
|
+
if (!selectFieldsQuery) {
|
|
691
|
+
return {
|
|
692
|
+
select: void 0,
|
|
693
|
+
error: void 0
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
if (Array.isArray(selectFieldsQuery)) {
|
|
697
|
+
return {
|
|
698
|
+
select: void 0,
|
|
699
|
+
error: this.makeError("duplicatedFieldsParameter", `duplicated fields query for type ${type}`)
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
const typeInfo = this.getModelInfo(type);
|
|
703
|
+
if (!typeInfo) {
|
|
704
|
+
return {
|
|
705
|
+
select: void 0,
|
|
706
|
+
error: this.makeUnsupportedModelError(type)
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
const selectFieldNames = selectFieldsQuery.split(",").filter((i) => i);
|
|
710
|
+
const fields = selectFieldNames.reduce((acc, curr) => ({
|
|
711
|
+
...acc,
|
|
712
|
+
[curr]: true
|
|
713
|
+
}), {});
|
|
714
|
+
return {
|
|
715
|
+
select: {
|
|
716
|
+
...this.makeIdSelect(typeInfo.idFields),
|
|
717
|
+
...fields
|
|
718
|
+
}
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
addTotalCountToMeta(meta, total) {
|
|
722
|
+
return meta ? Object.assign(meta, {
|
|
723
|
+
total
|
|
724
|
+
}) : Object.assign({}, {
|
|
725
|
+
total
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
makePaginator(baseUrl, offset, limit, total) {
|
|
729
|
+
if (limit === Infinity) {
|
|
730
|
+
return void 0;
|
|
731
|
+
}
|
|
732
|
+
const totalPages = Math.ceil(total / limit);
|
|
733
|
+
return new import_ts_japi.Paginator(() => ({
|
|
734
|
+
first: this.replaceURLSearchParams(baseUrl, {
|
|
735
|
+
"page[limit]": limit
|
|
736
|
+
}),
|
|
737
|
+
last: this.replaceURLSearchParams(baseUrl, {
|
|
738
|
+
"page[offset]": (totalPages - 1) * limit
|
|
739
|
+
}),
|
|
740
|
+
prev: offset - limit >= 0 && offset - limit <= total - 1 ? this.replaceURLSearchParams(baseUrl, {
|
|
741
|
+
"page[offset]": offset - limit,
|
|
742
|
+
"page[limit]": limit
|
|
743
|
+
}) : null,
|
|
744
|
+
next: offset + limit <= total - 1 ? this.replaceURLSearchParams(baseUrl, {
|
|
745
|
+
"page[offset]": offset + limit,
|
|
746
|
+
"page[limit]": limit
|
|
747
|
+
}) : null
|
|
748
|
+
}));
|
|
749
|
+
}
|
|
750
|
+
processRequestBody(requestBody) {
|
|
751
|
+
let body = requestBody;
|
|
752
|
+
if (body.meta?.serialization) {
|
|
753
|
+
body = import_superjson2.default.deserialize({
|
|
754
|
+
json: body,
|
|
755
|
+
meta: body.meta.serialization
|
|
756
|
+
});
|
|
757
|
+
}
|
|
758
|
+
const parseResult = this.createUpdatePayloadSchema.safeParse(body);
|
|
759
|
+
if (!parseResult.success) {
|
|
760
|
+
return {
|
|
761
|
+
attributes: void 0,
|
|
762
|
+
relationships: void 0,
|
|
763
|
+
error: this.makeError("invalidPayload", getZodErrorMessage(parseResult.error))
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
return {
|
|
767
|
+
attributes: parseResult.data.data.attributes,
|
|
768
|
+
relationships: parseResult.data.data.relationships,
|
|
769
|
+
error: void 0
|
|
770
|
+
};
|
|
771
|
+
}
|
|
772
|
+
async processCreate(client, type, _query, requestBody) {
|
|
773
|
+
const typeInfo = this.getModelInfo(type);
|
|
774
|
+
if (!typeInfo) {
|
|
775
|
+
return this.makeUnsupportedModelError(type);
|
|
776
|
+
}
|
|
777
|
+
const { attributes, relationships, error } = this.processRequestBody(requestBody);
|
|
778
|
+
if (error) {
|
|
779
|
+
return error;
|
|
780
|
+
}
|
|
781
|
+
const createPayload = {
|
|
782
|
+
data: {
|
|
783
|
+
...attributes
|
|
784
|
+
}
|
|
785
|
+
};
|
|
786
|
+
if (relationships) {
|
|
787
|
+
for (const [key, data] of Object.entries(relationships)) {
|
|
788
|
+
if (!data?.data) {
|
|
789
|
+
return this.makeError("invalidRelationData");
|
|
790
|
+
}
|
|
791
|
+
const relationInfo = typeInfo.relationships[key];
|
|
792
|
+
if (!relationInfo) {
|
|
793
|
+
return this.makeUnsupportedRelationshipError(type, key, 400);
|
|
794
|
+
}
|
|
795
|
+
if (relationInfo.isCollection) {
|
|
796
|
+
createPayload.data[key] = {
|
|
797
|
+
connect: (0, import_common_helpers.enumerate)(data.data).map((item) => this.makeIdConnect(relationInfo.idFields, item.id))
|
|
798
|
+
};
|
|
799
|
+
} else {
|
|
800
|
+
if (typeof data.data !== "object") {
|
|
801
|
+
return this.makeError("invalidRelationData");
|
|
802
|
+
}
|
|
803
|
+
createPayload.data[key] = {
|
|
804
|
+
connect: this.makeIdConnect(relationInfo.idFields, data.data.id)
|
|
805
|
+
};
|
|
806
|
+
}
|
|
807
|
+
createPayload.include = {
|
|
808
|
+
...createPayload.include,
|
|
809
|
+
[key]: {
|
|
810
|
+
select: {
|
|
811
|
+
[this.makeDefaultIdKey(relationInfo.idFields)]: true
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
this.includeRelationshipIds(type, createPayload, "include");
|
|
818
|
+
const entity = await client[type].create(createPayload);
|
|
819
|
+
return {
|
|
820
|
+
status: 201,
|
|
821
|
+
body: await this.serializeItems(type, entity)
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
async processUpsert(client, type, _query, requestBody) {
|
|
825
|
+
const typeInfo = this.getModelInfo(type);
|
|
826
|
+
if (!typeInfo) {
|
|
827
|
+
return this.makeUnsupportedModelError(type);
|
|
828
|
+
}
|
|
829
|
+
const modelName = typeInfo.name;
|
|
830
|
+
const { attributes, relationships, error } = this.processRequestBody(requestBody);
|
|
831
|
+
if (error) {
|
|
832
|
+
return error;
|
|
833
|
+
}
|
|
834
|
+
const parseResult = this.upsertMetaSchema.safeParse(requestBody);
|
|
835
|
+
if (parseResult.error) {
|
|
836
|
+
return this.makeError("invalidPayload", getZodErrorMessage(parseResult.error));
|
|
837
|
+
}
|
|
838
|
+
const matchFields = parseResult.data.meta.matchFields;
|
|
839
|
+
const uniqueFieldSets = this.getUniqueFieldSets(modelName);
|
|
840
|
+
if (!uniqueFieldSets.some((set) => set.every((field) => matchFields.includes(field)))) {
|
|
841
|
+
return this.makeError("invalidPayload", "Match fields must be unique fields", 400);
|
|
842
|
+
}
|
|
843
|
+
const upsertPayload = {
|
|
844
|
+
where: this.makeUpsertWhere(matchFields, attributes, typeInfo),
|
|
845
|
+
create: {
|
|
846
|
+
...attributes
|
|
847
|
+
},
|
|
848
|
+
update: {
|
|
849
|
+
...Object.fromEntries(Object.entries(attributes ?? {}).filter((e) => !matchFields.includes(e[0])))
|
|
850
|
+
}
|
|
851
|
+
};
|
|
852
|
+
if (relationships) {
|
|
853
|
+
for (const [key, data] of Object.entries(relationships)) {
|
|
854
|
+
if (!data?.data) {
|
|
855
|
+
return this.makeError("invalidRelationData");
|
|
856
|
+
}
|
|
857
|
+
const relationInfo = typeInfo.relationships[key];
|
|
858
|
+
if (!relationInfo) {
|
|
859
|
+
return this.makeUnsupportedRelationshipError(modelName, key, 400);
|
|
860
|
+
}
|
|
861
|
+
if (relationInfo.isCollection) {
|
|
862
|
+
upsertPayload.create[key] = {
|
|
863
|
+
connect: (0, import_common_helpers.enumerate)(data.data).map((item) => this.makeIdConnect(relationInfo.idFields, item.id))
|
|
864
|
+
};
|
|
865
|
+
upsertPayload.update[key] = {
|
|
866
|
+
set: (0, import_common_helpers.enumerate)(data.data).map((item) => this.makeIdConnect(relationInfo.idFields, item.id))
|
|
867
|
+
};
|
|
868
|
+
} else {
|
|
869
|
+
if (typeof data.data !== "object") {
|
|
870
|
+
return this.makeError("invalidRelationData");
|
|
871
|
+
}
|
|
872
|
+
upsertPayload.create[key] = {
|
|
873
|
+
connect: this.makeIdConnect(relationInfo.idFields, data.data.id)
|
|
874
|
+
};
|
|
875
|
+
upsertPayload.update[key] = {
|
|
876
|
+
connect: this.makeIdConnect(relationInfo.idFields, data.data.id)
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
this.includeRelationshipIds(modelName, upsertPayload, "include");
|
|
882
|
+
const entity = await client[modelName].upsert(upsertPayload);
|
|
883
|
+
return {
|
|
884
|
+
status: 201,
|
|
885
|
+
body: await this.serializeItems(modelName, entity)
|
|
886
|
+
};
|
|
887
|
+
}
|
|
888
|
+
getUniqueFieldSets(type) {
|
|
889
|
+
const modelDef = this.requireModel(type);
|
|
890
|
+
return Object.entries(modelDef.uniqueFields).map(([k, v]) => typeof v.type === "string" ? [
|
|
891
|
+
k
|
|
892
|
+
] : Object.keys(v));
|
|
893
|
+
}
|
|
894
|
+
async processRelationshipCRUD(client, mode, type, resourceId, relationship, _query, requestBody) {
|
|
895
|
+
const typeInfo = this.getModelInfo(type);
|
|
896
|
+
if (!typeInfo) {
|
|
897
|
+
return this.makeUnsupportedModelError(type);
|
|
898
|
+
}
|
|
899
|
+
const relationInfo = typeInfo.relationships[relationship];
|
|
900
|
+
if (!relationInfo) {
|
|
901
|
+
return this.makeUnsupportedRelationshipError(type, relationship, 404);
|
|
902
|
+
}
|
|
903
|
+
if (!relationInfo.isCollection && mode !== "update") {
|
|
904
|
+
return this.makeError("invalidVerb");
|
|
905
|
+
}
|
|
906
|
+
const updateArgs = {
|
|
907
|
+
where: this.makeIdFilter(typeInfo.idFields, resourceId),
|
|
908
|
+
select: {
|
|
909
|
+
...typeInfo.idFields.reduce((acc, field) => ({
|
|
910
|
+
...acc,
|
|
911
|
+
[field.name]: true
|
|
912
|
+
}), {}),
|
|
913
|
+
[relationship]: {
|
|
914
|
+
select: this.makeIdSelect(relationInfo.idFields)
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
};
|
|
918
|
+
if (!relationInfo.isCollection) {
|
|
919
|
+
const parsed = this.updateSingleRelationSchema.safeParse(requestBody);
|
|
920
|
+
if (!parsed.success) {
|
|
921
|
+
return this.makeError("invalidPayload", getZodErrorMessage(parsed.error));
|
|
922
|
+
}
|
|
923
|
+
if (parsed.data.data === null) {
|
|
924
|
+
if (!relationInfo.isOptional) {
|
|
925
|
+
return this.makeError("invalidPayload");
|
|
926
|
+
}
|
|
927
|
+
updateArgs.data = {
|
|
928
|
+
[relationship]: {
|
|
929
|
+
disconnect: true
|
|
930
|
+
}
|
|
931
|
+
};
|
|
932
|
+
} else {
|
|
933
|
+
updateArgs.data = {
|
|
934
|
+
[relationship]: {
|
|
935
|
+
connect: this.makeIdConnect(relationInfo.idFields, parsed.data.data.id)
|
|
936
|
+
}
|
|
937
|
+
};
|
|
938
|
+
}
|
|
939
|
+
} else {
|
|
940
|
+
const parsed = this.updateCollectionRelationSchema.safeParse(requestBody);
|
|
941
|
+
if (!parsed.success) {
|
|
942
|
+
return this.makeError("invalidPayload", getZodErrorMessage(parsed.error));
|
|
943
|
+
}
|
|
944
|
+
const relationVerb = mode === "create" ? "connect" : mode === "delete" ? "disconnect" : "set";
|
|
945
|
+
updateArgs.data = {
|
|
946
|
+
[relationship]: {
|
|
947
|
+
[relationVerb]: (0, import_common_helpers.enumerate)(parsed.data.data).map((item) => this.makeIdFilter(relationInfo.idFields, item.id))
|
|
948
|
+
}
|
|
949
|
+
};
|
|
950
|
+
}
|
|
951
|
+
const entity = await client[type].update(updateArgs);
|
|
952
|
+
const mappedType = this.mapModelName(type);
|
|
953
|
+
const serialized = await this.serializeItems(relationInfo.type, entity[relationship], {
|
|
954
|
+
linkers: {
|
|
955
|
+
document: new import_ts_japi.Linker(() => this.makeLinkUrl(`/${mappedType}/${resourceId}/relationships/${relationship}`))
|
|
956
|
+
},
|
|
957
|
+
onlyIdentifier: true
|
|
958
|
+
});
|
|
959
|
+
return {
|
|
960
|
+
status: 200,
|
|
961
|
+
body: serialized
|
|
962
|
+
};
|
|
963
|
+
}
|
|
964
|
+
async processUpdate(client, type, resourceId, _query, requestBody) {
|
|
965
|
+
const typeInfo = this.getModelInfo(type);
|
|
966
|
+
if (!typeInfo) {
|
|
967
|
+
return this.makeUnsupportedModelError(type);
|
|
968
|
+
}
|
|
969
|
+
const { attributes, relationships, error } = this.processRequestBody(requestBody);
|
|
970
|
+
if (error) {
|
|
971
|
+
return error;
|
|
972
|
+
}
|
|
973
|
+
const updatePayload = {
|
|
974
|
+
where: this.makeIdFilter(typeInfo.idFields, resourceId),
|
|
975
|
+
data: {
|
|
976
|
+
...attributes
|
|
977
|
+
}
|
|
978
|
+
};
|
|
979
|
+
if (relationships) {
|
|
980
|
+
for (const [key, data] of Object.entries(relationships)) {
|
|
981
|
+
if (!data?.data) {
|
|
982
|
+
return this.makeError("invalidRelationData");
|
|
983
|
+
}
|
|
984
|
+
const relationInfo = typeInfo.relationships[key];
|
|
985
|
+
if (!relationInfo) {
|
|
986
|
+
return this.makeUnsupportedRelationshipError(type, key, 400);
|
|
987
|
+
}
|
|
988
|
+
if (relationInfo.isCollection) {
|
|
989
|
+
updatePayload.data[key] = {
|
|
990
|
+
set: (0, import_common_helpers.enumerate)(data.data).map((item) => ({
|
|
991
|
+
[this.makeDefaultIdKey(relationInfo.idFields)]: item.id
|
|
992
|
+
}))
|
|
993
|
+
};
|
|
994
|
+
} else {
|
|
995
|
+
if (typeof data.data !== "object") {
|
|
996
|
+
return this.makeError("invalidRelationData");
|
|
997
|
+
}
|
|
998
|
+
updatePayload.data[key] = {
|
|
999
|
+
connect: {
|
|
1000
|
+
[this.makeDefaultIdKey(relationInfo.idFields)]: data.data.id
|
|
1001
|
+
}
|
|
1002
|
+
};
|
|
1003
|
+
}
|
|
1004
|
+
updatePayload.include = {
|
|
1005
|
+
...updatePayload.include,
|
|
1006
|
+
[key]: {
|
|
1007
|
+
select: {
|
|
1008
|
+
[this.makeDefaultIdKey(relationInfo.idFields)]: true
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
};
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
this.includeRelationshipIds(type, updatePayload, "include");
|
|
1015
|
+
const entity = await client[type].update(updatePayload);
|
|
1016
|
+
return {
|
|
1017
|
+
status: 200,
|
|
1018
|
+
body: await this.serializeItems(type, entity)
|
|
1019
|
+
};
|
|
1020
|
+
}
|
|
1021
|
+
async processDelete(client, type, resourceId) {
|
|
1022
|
+
const typeInfo = this.getModelInfo(type);
|
|
1023
|
+
if (!typeInfo) {
|
|
1024
|
+
return this.makeUnsupportedModelError(type);
|
|
1025
|
+
}
|
|
1026
|
+
await client[type].delete({
|
|
1027
|
+
where: this.makeIdFilter(typeInfo.idFields, resourceId)
|
|
1028
|
+
});
|
|
1029
|
+
return {
|
|
1030
|
+
status: 200,
|
|
1031
|
+
body: {
|
|
1032
|
+
meta: {}
|
|
1033
|
+
}
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
1036
|
+
//#region utilities
|
|
1037
|
+
requireModel(model) {
|
|
1038
|
+
const modelDef = this.schema.models[model];
|
|
1039
|
+
if (!modelDef) {
|
|
1040
|
+
throw new Error(`Model ${model} is not defined in the schema`);
|
|
1041
|
+
}
|
|
1042
|
+
return modelDef;
|
|
1043
|
+
}
|
|
1044
|
+
getIdFields(model) {
|
|
1045
|
+
const modelDef = this.requireModel(model);
|
|
1046
|
+
const modelLower = (0, import_common_helpers.lowerCaseFirst)(model);
|
|
1047
|
+
if (!(modelLower in this.externalIdMapping)) {
|
|
1048
|
+
return Object.values(modelDef.fields).filter((f) => modelDef.idFields.includes(f.name));
|
|
1049
|
+
}
|
|
1050
|
+
const externalIdName = this.externalIdMapping[modelLower];
|
|
1051
|
+
for (const [name, info] of Object.entries(modelDef.uniqueFields)) {
|
|
1052
|
+
if (name === externalIdName) {
|
|
1053
|
+
if (typeof info.type === "string") {
|
|
1054
|
+
return [
|
|
1055
|
+
this.requireField(model, info.type)
|
|
1056
|
+
];
|
|
1057
|
+
} else {
|
|
1058
|
+
return Object.keys(info).map((f) => this.requireField(model, f));
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
throw new Error(`Model ${model} does not have unique key ${externalIdName}`);
|
|
1063
|
+
}
|
|
1064
|
+
requireField(model, field) {
|
|
1065
|
+
const modelDef = this.requireModel(model);
|
|
1066
|
+
const fieldDef = modelDef.fields[field];
|
|
1067
|
+
if (!fieldDef) {
|
|
1068
|
+
throw new Error(`Field ${field} is not defined in model ${model}`);
|
|
1069
|
+
}
|
|
1070
|
+
return fieldDef;
|
|
1071
|
+
}
|
|
1072
|
+
buildTypeMap() {
|
|
1073
|
+
this.typeMap = {};
|
|
1074
|
+
for (const [model, { fields }] of Object.entries(this.schema.models)) {
|
|
1075
|
+
const idFields = this.getIdFields(model);
|
|
1076
|
+
if (idFields.length === 0) {
|
|
1077
|
+
log(this.options.log, "warn", `Not including model ${model} in the API because it has no ID field`);
|
|
1078
|
+
continue;
|
|
1079
|
+
}
|
|
1080
|
+
const modelInfo = this.typeMap[(0, import_common_helpers.lowerCaseFirst)(model)] = {
|
|
1081
|
+
name: model,
|
|
1082
|
+
idFields,
|
|
1083
|
+
relationships: {},
|
|
1084
|
+
fields
|
|
1085
|
+
};
|
|
1086
|
+
for (const [field, fieldInfo] of Object.entries(fields)) {
|
|
1087
|
+
if (!fieldInfo.relation) {
|
|
1088
|
+
continue;
|
|
1089
|
+
}
|
|
1090
|
+
const fieldTypeIdFields = this.getIdFields(fieldInfo.type);
|
|
1091
|
+
if (fieldTypeIdFields.length === 0) {
|
|
1092
|
+
log(this.options.log, "warn", `Not including relation ${model}.${field} in the API because it has no ID field`);
|
|
1093
|
+
continue;
|
|
1094
|
+
}
|
|
1095
|
+
modelInfo.relationships[field] = {
|
|
1096
|
+
type: fieldInfo.type,
|
|
1097
|
+
idFields: fieldTypeIdFields,
|
|
1098
|
+
isCollection: !!fieldInfo.array,
|
|
1099
|
+
isOptional: !!fieldInfo.optional
|
|
1100
|
+
};
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
getModelInfo(model) {
|
|
1105
|
+
return this.typeMap[(0, import_common_helpers.lowerCaseFirst)(model)];
|
|
1106
|
+
}
|
|
1107
|
+
makeLinkUrl(path) {
|
|
1108
|
+
return `${this.options.endpoint}${path}`;
|
|
1109
|
+
}
|
|
1110
|
+
buildSerializers() {
|
|
1111
|
+
const linkers = {};
|
|
1112
|
+
for (const model of Object.keys(this.schema.models)) {
|
|
1113
|
+
const ids = this.getIdFields(model);
|
|
1114
|
+
const modelLower = (0, import_common_helpers.lowerCaseFirst)(model);
|
|
1115
|
+
const mappedModel = this.mapModelName(modelLower);
|
|
1116
|
+
if (ids.length < 1) {
|
|
1117
|
+
continue;
|
|
1118
|
+
}
|
|
1119
|
+
const linker = new import_ts_japi.Linker((items) => Array.isArray(items) ? this.makeLinkUrl(`/${mappedModel}`) : this.makeLinkUrl(`/${mappedModel}/${this.getId(model, items)}`));
|
|
1120
|
+
linkers[modelLower] = linker;
|
|
1121
|
+
let projection = {};
|
|
1122
|
+
const modelDef = this.requireModel(model);
|
|
1123
|
+
for (const [field, fieldDef] of Object.entries(modelDef.fields)) {
|
|
1124
|
+
if (fieldDef.relation) {
|
|
1125
|
+
projection[field] = 0;
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
if (Object.keys(projection).length === 0) {
|
|
1129
|
+
projection = null;
|
|
1130
|
+
}
|
|
1131
|
+
const serializer = new import_ts_japi.Serializer(model, {
|
|
1132
|
+
version: "1.1",
|
|
1133
|
+
idKey: this.makeIdKey(ids),
|
|
1134
|
+
linkers: {
|
|
1135
|
+
resource: linker,
|
|
1136
|
+
document: linker
|
|
1137
|
+
},
|
|
1138
|
+
projection
|
|
1139
|
+
});
|
|
1140
|
+
this.serializers.set(modelLower, serializer);
|
|
1141
|
+
}
|
|
1142
|
+
for (const model of Object.keys(this.schema.models)) {
|
|
1143
|
+
const modelLower = (0, import_common_helpers.lowerCaseFirst)(model);
|
|
1144
|
+
const serializer = this.serializers.get(modelLower);
|
|
1145
|
+
if (!serializer) {
|
|
1146
|
+
continue;
|
|
1147
|
+
}
|
|
1148
|
+
const relators = {};
|
|
1149
|
+
const modelDef = this.requireModel(model);
|
|
1150
|
+
for (const [field, fieldDef] of Object.entries(modelDef.fields)) {
|
|
1151
|
+
if (!fieldDef.relation) {
|
|
1152
|
+
continue;
|
|
1153
|
+
}
|
|
1154
|
+
const fieldSerializer = this.serializers.get((0, import_common_helpers.lowerCaseFirst)(fieldDef.type));
|
|
1155
|
+
if (!fieldSerializer) {
|
|
1156
|
+
continue;
|
|
1157
|
+
}
|
|
1158
|
+
const fieldIds = this.getIdFields(fieldDef.type);
|
|
1159
|
+
if (fieldIds.length > 0) {
|
|
1160
|
+
const mappedModel = this.mapModelName(modelLower);
|
|
1161
|
+
const relator = new import_ts_japi.Relator(async (data) => {
|
|
1162
|
+
return data[field];
|
|
1163
|
+
}, fieldSerializer, {
|
|
1164
|
+
relatedName: field,
|
|
1165
|
+
linkers: {
|
|
1166
|
+
related: new import_ts_japi.Linker((primary) => this.makeLinkUrl(`/${mappedModel}/${this.getId(model, primary)}/${field}`)),
|
|
1167
|
+
relationship: new import_ts_japi.Linker((primary) => this.makeLinkUrl(`/${mappedModel}/${this.getId(model, primary)}/relationships/${field}`))
|
|
1168
|
+
}
|
|
1169
|
+
});
|
|
1170
|
+
relators[field] = relator;
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
serializer.setRelators(relators);
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
getId(model, data) {
|
|
1177
|
+
if (!data) {
|
|
1178
|
+
return void 0;
|
|
1179
|
+
}
|
|
1180
|
+
const ids = this.getIdFields(model);
|
|
1181
|
+
if (ids.length === 0) {
|
|
1182
|
+
return void 0;
|
|
1183
|
+
} else {
|
|
1184
|
+
return data[this.makeIdKey(ids)];
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
async serializeItems(model, items, options) {
|
|
1188
|
+
model = (0, import_common_helpers.lowerCaseFirst)(model);
|
|
1189
|
+
const serializer = this.serializers.get(model);
|
|
1190
|
+
if (!serializer) {
|
|
1191
|
+
throw new Error(`serializer not found for model ${model}`);
|
|
1192
|
+
}
|
|
1193
|
+
const itemsWithId = (0, import_common_helpers.clone)(items);
|
|
1194
|
+
this.injectCompoundId(model, itemsWithId);
|
|
1195
|
+
const serialized = await serializer.serialize(itemsWithId, options);
|
|
1196
|
+
const plainResult = this.toPlainObject(serialized);
|
|
1197
|
+
const { json, meta } = import_superjson2.default.serialize(plainResult);
|
|
1198
|
+
const result = json;
|
|
1199
|
+
if (meta) {
|
|
1200
|
+
result.meta = {
|
|
1201
|
+
...result.meta,
|
|
1202
|
+
serialization: meta
|
|
1203
|
+
};
|
|
1204
|
+
}
|
|
1205
|
+
return result;
|
|
1206
|
+
}
|
|
1207
|
+
injectCompoundId(model, items) {
|
|
1208
|
+
const typeInfo = this.getModelInfo(model);
|
|
1209
|
+
if (!typeInfo) {
|
|
1210
|
+
return;
|
|
1211
|
+
}
|
|
1212
|
+
(0, import_common_helpers.enumerate)(items).forEach((item) => {
|
|
1213
|
+
if (!item) {
|
|
1214
|
+
return;
|
|
1215
|
+
}
|
|
1216
|
+
if (typeInfo.idFields.length > 1) {
|
|
1217
|
+
item[this.makeIdKey(typeInfo.idFields)] = this.makeCompoundId(typeInfo.idFields, item);
|
|
1218
|
+
}
|
|
1219
|
+
for (const [key, value] of Object.entries(item)) {
|
|
1220
|
+
if (typeInfo.relationships[key]) {
|
|
1221
|
+
this.injectCompoundId(typeInfo.relationships[key].type, value);
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
});
|
|
1225
|
+
}
|
|
1226
|
+
toPlainObject(data) {
|
|
1227
|
+
if (data === void 0 || data === null) {
|
|
1228
|
+
return data;
|
|
1229
|
+
}
|
|
1230
|
+
if (Array.isArray(data)) {
|
|
1231
|
+
return data.map((item) => this.toPlainObject(item));
|
|
1232
|
+
}
|
|
1233
|
+
if (typeof data === "object") {
|
|
1234
|
+
if (typeof data.toJSON === "function") {
|
|
1235
|
+
return data.toJSON();
|
|
1236
|
+
}
|
|
1237
|
+
const result = {};
|
|
1238
|
+
for (const [field, value] of Object.entries(data)) {
|
|
1239
|
+
if (value === void 0 || typeof value === "function") {
|
|
1240
|
+
continue;
|
|
1241
|
+
} else if (field === "attributes") {
|
|
1242
|
+
result[field] = value;
|
|
1243
|
+
} else {
|
|
1244
|
+
result[field] = this.toPlainObject(value);
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
return result;
|
|
1248
|
+
}
|
|
1249
|
+
return data;
|
|
1250
|
+
}
|
|
1251
|
+
replaceURLSearchParams(url, params) {
|
|
1252
|
+
const r = new URL(url);
|
|
1253
|
+
for (const [key, value] of Object.entries(params)) {
|
|
1254
|
+
r.searchParams.set(key, value.toString());
|
|
1255
|
+
}
|
|
1256
|
+
return r.toString();
|
|
1257
|
+
}
|
|
1258
|
+
makeIdFilter(idFields, resourceId, nested = true) {
|
|
1259
|
+
const decodedId = decodeURIComponent(resourceId);
|
|
1260
|
+
if (idFields.length === 1) {
|
|
1261
|
+
return {
|
|
1262
|
+
[idFields[0].name]: this.coerce(idFields[0], decodedId)
|
|
1263
|
+
};
|
|
1264
|
+
} else if (nested) {
|
|
1265
|
+
return {
|
|
1266
|
+
// TODO: support `@@id` with custom name
|
|
1267
|
+
[idFields.map((idf) => idf.name).join(DEFAULT_ID_DIVIDER)]: idFields.reduce((acc, curr, idx) => ({
|
|
1268
|
+
...acc,
|
|
1269
|
+
[curr.name]: this.coerce(curr, decodedId.split(this.idDivider)[idx])
|
|
1270
|
+
}), {})
|
|
1271
|
+
};
|
|
1272
|
+
} else {
|
|
1273
|
+
return idFields.reduce((acc, curr, idx) => ({
|
|
1274
|
+
...acc,
|
|
1275
|
+
[curr.name]: this.coerce(curr, decodedId.split(this.idDivider)[idx])
|
|
1276
|
+
}), {});
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
makeIdSelect(idFields) {
|
|
1280
|
+
if (idFields.length === 0) {
|
|
1281
|
+
throw this.errors["noId"];
|
|
1282
|
+
}
|
|
1283
|
+
return idFields.reduce((acc, curr) => ({
|
|
1284
|
+
...acc,
|
|
1285
|
+
[curr.name]: true
|
|
1286
|
+
}), {});
|
|
1287
|
+
}
|
|
1288
|
+
makeIdConnect(idFields, id) {
|
|
1289
|
+
if (idFields.length === 1) {
|
|
1290
|
+
return {
|
|
1291
|
+
[idFields[0].name]: this.coerce(idFields[0], id)
|
|
1292
|
+
};
|
|
1293
|
+
} else {
|
|
1294
|
+
return {
|
|
1295
|
+
[this.makeDefaultIdKey(idFields)]: idFields.reduce((acc, curr, idx) => ({
|
|
1296
|
+
...acc,
|
|
1297
|
+
[curr.name]: this.coerce(curr, `${id}`.split(this.idDivider)[idx])
|
|
1298
|
+
}), {})
|
|
1299
|
+
};
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
makeIdKey(idFields) {
|
|
1303
|
+
return idFields.map((idf) => idf.name).join(this.idDivider);
|
|
1304
|
+
}
|
|
1305
|
+
makeDefaultIdKey(idFields) {
|
|
1306
|
+
return idFields.map((idf) => idf.name).join(DEFAULT_ID_DIVIDER);
|
|
1307
|
+
}
|
|
1308
|
+
makeCompoundId(idFields, item) {
|
|
1309
|
+
return idFields.map((idf) => item[idf.name]).join(this.idDivider);
|
|
1310
|
+
}
|
|
1311
|
+
makeUpsertWhere(matchFields, attributes, typeInfo) {
|
|
1312
|
+
const where = matchFields.reduce((acc, field) => {
|
|
1313
|
+
acc[field] = attributes[field] ?? null;
|
|
1314
|
+
return acc;
|
|
1315
|
+
}, {});
|
|
1316
|
+
if (typeInfo.idFields.length > 1 && matchFields.some((mf) => typeInfo.idFields.map((idf) => idf.name).includes(mf))) {
|
|
1317
|
+
return {
|
|
1318
|
+
[this.makeDefaultIdKey(typeInfo.idFields)]: where
|
|
1319
|
+
};
|
|
1320
|
+
}
|
|
1321
|
+
return where;
|
|
1322
|
+
}
|
|
1323
|
+
includeRelationshipIds(model, args, mode) {
|
|
1324
|
+
const typeInfo = this.getModelInfo(model);
|
|
1325
|
+
if (!typeInfo) {
|
|
1326
|
+
return;
|
|
1327
|
+
}
|
|
1328
|
+
for (const [relation, relationInfo] of Object.entries(typeInfo.relationships)) {
|
|
1329
|
+
args[mode] = {
|
|
1330
|
+
...args[mode],
|
|
1331
|
+
[relation]: {
|
|
1332
|
+
select: this.makeIdSelect(relationInfo.idFields)
|
|
1333
|
+
}
|
|
1334
|
+
};
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
coerce(fieldDef, value) {
|
|
1338
|
+
if (typeof value === "string") {
|
|
1339
|
+
if (fieldDef.attributes?.some((attr) => attr.name === "@json")) {
|
|
1340
|
+
try {
|
|
1341
|
+
return JSON.parse(value);
|
|
1342
|
+
} catch {
|
|
1343
|
+
throw new InvalidValueError(`invalid JSON value: ${value}`);
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
const type = fieldDef.type;
|
|
1347
|
+
if (type === "Int") {
|
|
1348
|
+
const parsed = parseInt(value);
|
|
1349
|
+
if (isNaN(parsed)) {
|
|
1350
|
+
throw new InvalidValueError(`invalid ${type} value: ${value}`);
|
|
1351
|
+
}
|
|
1352
|
+
return parsed;
|
|
1353
|
+
} else if (type === "BigInt") {
|
|
1354
|
+
try {
|
|
1355
|
+
return BigInt(value);
|
|
1356
|
+
} catch {
|
|
1357
|
+
throw new InvalidValueError(`invalid ${type} value: ${value}`);
|
|
1358
|
+
}
|
|
1359
|
+
} else if (type === "Float") {
|
|
1360
|
+
const parsed = parseFloat(value);
|
|
1361
|
+
if (isNaN(parsed)) {
|
|
1362
|
+
throw new InvalidValueError(`invalid ${type} value: ${value}`);
|
|
1363
|
+
}
|
|
1364
|
+
return parsed;
|
|
1365
|
+
} else if (type === "Decimal") {
|
|
1366
|
+
try {
|
|
1367
|
+
return new import_decimal2.Decimal(value);
|
|
1368
|
+
} catch {
|
|
1369
|
+
throw new InvalidValueError(`invalid ${type} value: ${value}`);
|
|
1370
|
+
}
|
|
1371
|
+
} else if (type === "Boolean") {
|
|
1372
|
+
if (value === "true") {
|
|
1373
|
+
return true;
|
|
1374
|
+
} else if (value === "false") {
|
|
1375
|
+
return false;
|
|
1376
|
+
} else {
|
|
1377
|
+
throw new InvalidValueError(`invalid ${type} value: ${value}`);
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
return value;
|
|
1382
|
+
}
|
|
1383
|
+
makeNormalizedUrl(path, query) {
|
|
1384
|
+
const url = new URL(this.makeLinkUrl(path));
|
|
1385
|
+
for (const [key, value] of Object.entries(query ?? {})) {
|
|
1386
|
+
if (key.startsWith("filter[") || key.startsWith("sort[") || key === "include" || key.startsWith("include[") || key.startsWith("fields[")) {
|
|
1387
|
+
for (const v of (0, import_common_helpers.enumerate)(value)) {
|
|
1388
|
+
url.searchParams.append(key, v);
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
return url.toString();
|
|
1393
|
+
}
|
|
1394
|
+
getPagination(query) {
|
|
1395
|
+
if (!query) {
|
|
1396
|
+
return {
|
|
1397
|
+
offset: 0,
|
|
1398
|
+
limit: this.options.pageSize ?? DEFAULT_PAGE_SIZE
|
|
1399
|
+
};
|
|
1400
|
+
}
|
|
1401
|
+
let offset = 0;
|
|
1402
|
+
if (query["page[offset]"]) {
|
|
1403
|
+
const value = query["page[offset]"];
|
|
1404
|
+
const offsetText = Array.isArray(value) ? value[value.length - 1] : value;
|
|
1405
|
+
offset = parseInt(offsetText);
|
|
1406
|
+
if (isNaN(offset) || offset < 0) {
|
|
1407
|
+
offset = 0;
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
let pageSizeOption = this.options.pageSize ?? DEFAULT_PAGE_SIZE;
|
|
1411
|
+
if (pageSizeOption <= 0) {
|
|
1412
|
+
pageSizeOption = DEFAULT_PAGE_SIZE;
|
|
1413
|
+
}
|
|
1414
|
+
let limit = pageSizeOption;
|
|
1415
|
+
if (query["page[limit]"]) {
|
|
1416
|
+
const value = query["page[limit]"];
|
|
1417
|
+
const limitText = Array.isArray(value) ? value[value.length - 1] : value;
|
|
1418
|
+
limit = parseInt(limitText);
|
|
1419
|
+
if (isNaN(limit) || limit <= 0) {
|
|
1420
|
+
limit = pageSizeOption;
|
|
1421
|
+
}
|
|
1422
|
+
limit = Math.min(pageSizeOption, limit);
|
|
1423
|
+
}
|
|
1424
|
+
return {
|
|
1425
|
+
offset,
|
|
1426
|
+
limit
|
|
1427
|
+
};
|
|
1428
|
+
}
|
|
1429
|
+
buildFilter(type, query) {
|
|
1430
|
+
if (!query) {
|
|
1431
|
+
return {
|
|
1432
|
+
filter: void 0,
|
|
1433
|
+
error: void 0
|
|
1434
|
+
};
|
|
1435
|
+
}
|
|
1436
|
+
const typeInfo = this.getModelInfo(type);
|
|
1437
|
+
if (!typeInfo) {
|
|
1438
|
+
return {
|
|
1439
|
+
filter: void 0,
|
|
1440
|
+
error: this.makeUnsupportedModelError(type)
|
|
1441
|
+
};
|
|
1442
|
+
}
|
|
1443
|
+
const items = [];
|
|
1444
|
+
for (const [key, value] of Object.entries(query)) {
|
|
1445
|
+
if (!value) {
|
|
1446
|
+
continue;
|
|
1447
|
+
}
|
|
1448
|
+
const match2 = key.match(this.filterParamPattern);
|
|
1449
|
+
if (!match2 || !match2.groups || !match2.groups["match"]) {
|
|
1450
|
+
continue;
|
|
1451
|
+
}
|
|
1452
|
+
const filterKeys = match2.groups["match"].replaceAll(/[[\]]/g, " ").split(" ").filter((i) => i);
|
|
1453
|
+
if (!filterKeys.length) {
|
|
1454
|
+
continue;
|
|
1455
|
+
}
|
|
1456
|
+
const item = {};
|
|
1457
|
+
let curr = item;
|
|
1458
|
+
let currType = typeInfo;
|
|
1459
|
+
for (const filterValue of (0, import_common_helpers.enumerate)(value)) {
|
|
1460
|
+
for (let i = 0; i < filterKeys.length; i++) {
|
|
1461
|
+
let filterKey = filterKeys[i];
|
|
1462
|
+
let filterOp;
|
|
1463
|
+
const pos = filterKey.indexOf("$");
|
|
1464
|
+
if (pos > 0) {
|
|
1465
|
+
filterOp = filterKey.substring(pos + 1);
|
|
1466
|
+
filterKey = filterKey.substring(0, pos);
|
|
1467
|
+
}
|
|
1468
|
+
if (!!filterOp && !FilterOperations.includes(filterOp)) {
|
|
1469
|
+
return {
|
|
1470
|
+
filter: void 0,
|
|
1471
|
+
error: this.makeError("invalidFilter", `invalid filter operation: ${filterOp}`)
|
|
1472
|
+
};
|
|
1473
|
+
}
|
|
1474
|
+
const idFields = this.getIdFields(currType.name);
|
|
1475
|
+
const fieldDef = filterKey === "id" ? Object.values(currType.fields).find((f) => idFields.some((idf) => idf.name === f.name)) : currType.fields[filterKey];
|
|
1476
|
+
if (!fieldDef) {
|
|
1477
|
+
return {
|
|
1478
|
+
filter: void 0,
|
|
1479
|
+
error: this.makeError("invalidFilter")
|
|
1480
|
+
};
|
|
1481
|
+
}
|
|
1482
|
+
if (!fieldDef.relation) {
|
|
1483
|
+
if (i !== filterKeys.length - 1) {
|
|
1484
|
+
return {
|
|
1485
|
+
filter: void 0,
|
|
1486
|
+
error: this.makeError("invalidFilter")
|
|
1487
|
+
};
|
|
1488
|
+
}
|
|
1489
|
+
curr[fieldDef.name] = this.makeFilterValue(fieldDef, filterValue, filterOp);
|
|
1490
|
+
} else {
|
|
1491
|
+
if (i === filterKeys.length - 1) {
|
|
1492
|
+
curr[fieldDef.name] = this.makeFilterValue(fieldDef, filterValue, filterOp);
|
|
1493
|
+
} else {
|
|
1494
|
+
if (fieldDef.array) {
|
|
1495
|
+
curr[fieldDef.name] = {
|
|
1496
|
+
some: {}
|
|
1497
|
+
};
|
|
1498
|
+
curr = curr[fieldDef.name].some;
|
|
1499
|
+
} else {
|
|
1500
|
+
curr = curr[fieldDef.name] = {};
|
|
1501
|
+
}
|
|
1502
|
+
currType = this.getModelInfo(fieldDef.type);
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
items.push(item);
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
if (items.length === 0) {
|
|
1510
|
+
return {
|
|
1511
|
+
filter: void 0,
|
|
1512
|
+
error: void 0
|
|
1513
|
+
};
|
|
1514
|
+
} else {
|
|
1515
|
+
return {
|
|
1516
|
+
filter: items.length === 1 ? items[0] : {
|
|
1517
|
+
AND: items
|
|
1518
|
+
},
|
|
1519
|
+
error: void 0
|
|
1520
|
+
};
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
buildSort(type, query) {
|
|
1524
|
+
if (!query?.["sort"]) {
|
|
1525
|
+
return {
|
|
1526
|
+
sort: void 0,
|
|
1527
|
+
error: void 0
|
|
1528
|
+
};
|
|
1529
|
+
}
|
|
1530
|
+
const typeInfo = this.getModelInfo(type);
|
|
1531
|
+
if (!typeInfo) {
|
|
1532
|
+
return {
|
|
1533
|
+
sort: void 0,
|
|
1534
|
+
error: this.makeUnsupportedModelError(type)
|
|
1535
|
+
};
|
|
1536
|
+
}
|
|
1537
|
+
const result = [];
|
|
1538
|
+
for (const sortSpec of (0, import_common_helpers.enumerate)(query["sort"])) {
|
|
1539
|
+
const sortFields = sortSpec.split(",").filter((i) => i);
|
|
1540
|
+
for (const sortField of sortFields) {
|
|
1541
|
+
const dir = sortField.startsWith("-") ? "desc" : "asc";
|
|
1542
|
+
const cleanedSortField = sortField.startsWith("-") ? sortField.substring(1) : sortField;
|
|
1543
|
+
const parts = cleanedSortField.split(".").filter((i) => i);
|
|
1544
|
+
const sortItem = {};
|
|
1545
|
+
let curr = sortItem;
|
|
1546
|
+
let currType = typeInfo;
|
|
1547
|
+
for (let i = 0; i < parts.length; i++) {
|
|
1548
|
+
const part = parts[i];
|
|
1549
|
+
const fieldInfo = currType.fields[part];
|
|
1550
|
+
if (!fieldInfo || fieldInfo.array) {
|
|
1551
|
+
return {
|
|
1552
|
+
sort: void 0,
|
|
1553
|
+
error: this.makeError("invalidSort", "sorting by array field is not supported")
|
|
1554
|
+
};
|
|
1555
|
+
}
|
|
1556
|
+
if (i === parts.length - 1) {
|
|
1557
|
+
if (fieldInfo.relation) {
|
|
1558
|
+
const relationType = this.getModelInfo(fieldInfo.type);
|
|
1559
|
+
if (!relationType) {
|
|
1560
|
+
return {
|
|
1561
|
+
sort: void 0,
|
|
1562
|
+
error: this.makeUnsupportedModelError(fieldInfo.type)
|
|
1563
|
+
};
|
|
1564
|
+
}
|
|
1565
|
+
curr[fieldInfo.name] = relationType.idFields.reduce((acc, idField) => {
|
|
1566
|
+
acc[idField.name] = dir;
|
|
1567
|
+
return acc;
|
|
1568
|
+
}, {});
|
|
1569
|
+
} else {
|
|
1570
|
+
curr[fieldInfo.name] = dir;
|
|
1571
|
+
}
|
|
1572
|
+
} else {
|
|
1573
|
+
if (!fieldInfo.relation) {
|
|
1574
|
+
return {
|
|
1575
|
+
sort: void 0,
|
|
1576
|
+
error: this.makeError("invalidSort", "intermediate sort segments must be relationships")
|
|
1577
|
+
};
|
|
1578
|
+
}
|
|
1579
|
+
curr = curr[fieldInfo.name] = {};
|
|
1580
|
+
currType = this.getModelInfo(fieldInfo.type);
|
|
1581
|
+
if (!currType) {
|
|
1582
|
+
return {
|
|
1583
|
+
sort: void 0,
|
|
1584
|
+
error: this.makeUnsupportedModelError(fieldInfo.type)
|
|
1585
|
+
};
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
result.push(sortItem);
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
return {
|
|
1593
|
+
sort: result,
|
|
1594
|
+
error: void 0
|
|
1595
|
+
};
|
|
1596
|
+
}
|
|
1597
|
+
buildRelationSelect(type, include, query) {
|
|
1598
|
+
const typeInfo = this.getModelInfo(type);
|
|
1599
|
+
if (!typeInfo) {
|
|
1600
|
+
return {
|
|
1601
|
+
select: void 0,
|
|
1602
|
+
error: this.makeUnsupportedModelError(type)
|
|
1603
|
+
};
|
|
1604
|
+
}
|
|
1605
|
+
const result = {};
|
|
1606
|
+
const allIncludes = [];
|
|
1607
|
+
for (const includeItem of (0, import_common_helpers.enumerate)(include)) {
|
|
1608
|
+
const inclusions = includeItem.split(",").filter((i) => i);
|
|
1609
|
+
for (const inclusion of inclusions) {
|
|
1610
|
+
allIncludes.push(inclusion);
|
|
1611
|
+
const parts = inclusion.split(".");
|
|
1612
|
+
let currPayload = result;
|
|
1613
|
+
let currType = typeInfo;
|
|
1614
|
+
for (let i = 0; i < parts.length; i++) {
|
|
1615
|
+
const relation = parts[i];
|
|
1616
|
+
const relationInfo = currType.relationships[relation];
|
|
1617
|
+
if (!relationInfo) {
|
|
1618
|
+
return {
|
|
1619
|
+
select: void 0,
|
|
1620
|
+
error: this.makeUnsupportedRelationshipError(type, relation, 400)
|
|
1621
|
+
};
|
|
1622
|
+
}
|
|
1623
|
+
currType = this.getModelInfo(relationInfo.type);
|
|
1624
|
+
if (!currType) {
|
|
1625
|
+
return {
|
|
1626
|
+
select: void 0,
|
|
1627
|
+
error: this.makeUnsupportedModelError(relationInfo.type)
|
|
1628
|
+
};
|
|
1629
|
+
}
|
|
1630
|
+
const { select, error } = this.buildPartialSelect((0, import_common_helpers.lowerCaseFirst)(relationInfo.type), query);
|
|
1631
|
+
if (error) return {
|
|
1632
|
+
select: void 0,
|
|
1633
|
+
error
|
|
1634
|
+
};
|
|
1635
|
+
if (i !== parts.length - 1) {
|
|
1636
|
+
if (select) {
|
|
1637
|
+
currPayload[relation] = {
|
|
1638
|
+
select: {
|
|
1639
|
+
...select
|
|
1640
|
+
}
|
|
1641
|
+
};
|
|
1642
|
+
currPayload = currPayload[relation].select;
|
|
1643
|
+
} else {
|
|
1644
|
+
currPayload[relation] = {
|
|
1645
|
+
include: {
|
|
1646
|
+
...currPayload[relation]?.include
|
|
1647
|
+
}
|
|
1648
|
+
};
|
|
1649
|
+
currPayload = currPayload[relation].include;
|
|
1650
|
+
}
|
|
1651
|
+
} else {
|
|
1652
|
+
currPayload[relation] = select ? {
|
|
1653
|
+
select: {
|
|
1654
|
+
...select
|
|
1655
|
+
}
|
|
1656
|
+
} : true;
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
return {
|
|
1662
|
+
select: result,
|
|
1663
|
+
error: void 0,
|
|
1664
|
+
allIncludes
|
|
1665
|
+
};
|
|
1666
|
+
}
|
|
1667
|
+
makeFilterValue(fieldDef, value, op) {
|
|
1668
|
+
if (fieldDef.relation) {
|
|
1669
|
+
const info = this.getModelInfo(fieldDef.type);
|
|
1670
|
+
if (fieldDef.array) {
|
|
1671
|
+
const values = value.split(",").filter((i) => i);
|
|
1672
|
+
const filterValue = values.length > 1 ? {
|
|
1673
|
+
OR: values.map((v) => this.makeIdFilter(info.idFields, v, false))
|
|
1674
|
+
} : this.makeIdFilter(info.idFields, value, false);
|
|
1675
|
+
return {
|
|
1676
|
+
some: filterValue
|
|
1677
|
+
};
|
|
1678
|
+
} else {
|
|
1679
|
+
const values = value.split(",").filter((i) => i);
|
|
1680
|
+
if (values.length > 1) {
|
|
1681
|
+
return {
|
|
1682
|
+
OR: values.map((v) => this.makeIdFilter(info.idFields, v, false))
|
|
1683
|
+
};
|
|
1684
|
+
} else {
|
|
1685
|
+
return {
|
|
1686
|
+
is: this.makeIdFilter(info.idFields, value, false)
|
|
1687
|
+
};
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
} else {
|
|
1691
|
+
const coerced = this.coerce(fieldDef, value);
|
|
1692
|
+
switch (op) {
|
|
1693
|
+
case "icontains":
|
|
1694
|
+
return {
|
|
1695
|
+
contains: coerced,
|
|
1696
|
+
mode: "insensitive"
|
|
1697
|
+
};
|
|
1698
|
+
case "hasSome":
|
|
1699
|
+
case "hasEvery": {
|
|
1700
|
+
const values = value.split(",").filter((i) => i).map((v) => this.coerce(fieldDef, v));
|
|
1701
|
+
return {
|
|
1702
|
+
[op]: values
|
|
1703
|
+
};
|
|
1704
|
+
}
|
|
1705
|
+
case "isEmpty":
|
|
1706
|
+
if (value !== "true" && value !== "false") {
|
|
1707
|
+
throw new InvalidValueError(`Not a boolean: ${value}`);
|
|
1708
|
+
}
|
|
1709
|
+
return {
|
|
1710
|
+
isEmpty: value === "true" ? true : false
|
|
1711
|
+
};
|
|
1712
|
+
default:
|
|
1713
|
+
if (op === void 0) {
|
|
1714
|
+
if (fieldDef.attributes?.some((attr) => attr.name === "@json")) {
|
|
1715
|
+
return {
|
|
1716
|
+
equals: coerced
|
|
1717
|
+
};
|
|
1718
|
+
}
|
|
1719
|
+
const values = value.split(",").filter((i) => i).map((v) => this.coerce(fieldDef, v));
|
|
1720
|
+
return values.length > 1 ? {
|
|
1721
|
+
in: values
|
|
1722
|
+
} : {
|
|
1723
|
+
equals: values[0]
|
|
1724
|
+
};
|
|
1725
|
+
} else {
|
|
1726
|
+
return {
|
|
1727
|
+
[op]: coerced
|
|
1728
|
+
};
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1732
|
+
}
|
|
1733
|
+
injectRelationQuery(type, injectTarget, injectKey, query) {
|
|
1734
|
+
const { filter, error: filterError } = this.buildFilter(type, query);
|
|
1735
|
+
if (filterError) {
|
|
1736
|
+
return filterError;
|
|
1737
|
+
}
|
|
1738
|
+
if (filter) {
|
|
1739
|
+
injectTarget[injectKey] = {
|
|
1740
|
+
...injectTarget[injectKey],
|
|
1741
|
+
where: filter
|
|
1742
|
+
};
|
|
1743
|
+
}
|
|
1744
|
+
const { sort, error: sortError } = this.buildSort(type, query);
|
|
1745
|
+
if (sortError) {
|
|
1746
|
+
return sortError;
|
|
1747
|
+
}
|
|
1748
|
+
if (sort) {
|
|
1749
|
+
injectTarget[injectKey] = {
|
|
1750
|
+
...injectTarget[injectKey],
|
|
1751
|
+
orderBy: sort
|
|
1752
|
+
};
|
|
1753
|
+
}
|
|
1754
|
+
const pagination = this.getPagination(query);
|
|
1755
|
+
const offset = pagination.offset;
|
|
1756
|
+
if (offset > 0) {
|
|
1757
|
+
injectTarget[injectKey] = {
|
|
1758
|
+
...injectTarget[injectKey],
|
|
1759
|
+
skip: offset
|
|
1760
|
+
};
|
|
1761
|
+
}
|
|
1762
|
+
const limit = pagination.limit;
|
|
1763
|
+
if (limit !== Infinity) {
|
|
1764
|
+
injectTarget[injectKey] = {
|
|
1765
|
+
...injectTarget[injectKey],
|
|
1766
|
+
take: limit
|
|
1767
|
+
};
|
|
1768
|
+
injectTarget._count = {
|
|
1769
|
+
select: {
|
|
1770
|
+
[injectKey]: true
|
|
1771
|
+
}
|
|
1772
|
+
};
|
|
1773
|
+
}
|
|
1774
|
+
}
|
|
1775
|
+
handleZenStackError(err) {
|
|
1776
|
+
if (err instanceof import_orm.InputValidationError) {
|
|
1777
|
+
return this.makeError("validationError", err.message, 422, err.cause instanceof Error ? err.cause.message : void 0);
|
|
1778
|
+
} else if (err instanceof import_orm.RejectedByPolicyError) {
|
|
1779
|
+
return this.makeError("forbidden", err.message, 403, err.reason);
|
|
1780
|
+
} else if (err instanceof import_orm.NotFoundError) {
|
|
1781
|
+
return this.makeError("notFound", err.message);
|
|
1782
|
+
} else if (err instanceof import_orm.QueryError) {
|
|
1783
|
+
return this.makeError("queryError", err.message, 400, err.cause instanceof Error ? err.cause.message : void 0);
|
|
1784
|
+
} else {
|
|
1785
|
+
return this.makeError("unknownError", err.message);
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
makeError(code, detail, status, reason) {
|
|
1789
|
+
status = status ?? this.errors[code]?.status ?? 500;
|
|
1790
|
+
const error = {
|
|
1791
|
+
status,
|
|
1792
|
+
code: (0, import_common_helpers.paramCase)(code),
|
|
1793
|
+
title: this.errors[code]?.title
|
|
1794
|
+
};
|
|
1795
|
+
if (detail) {
|
|
1796
|
+
error.detail = detail;
|
|
1797
|
+
}
|
|
1798
|
+
if (reason) {
|
|
1799
|
+
error.reason = reason;
|
|
1800
|
+
}
|
|
1801
|
+
return {
|
|
1802
|
+
status,
|
|
1803
|
+
body: {
|
|
1804
|
+
errors: [
|
|
1805
|
+
error
|
|
1806
|
+
]
|
|
1807
|
+
}
|
|
1808
|
+
};
|
|
1809
|
+
}
|
|
1810
|
+
makeUnsupportedModelError(model) {
|
|
1811
|
+
return this.makeError("unsupportedModel", `Model ${model} doesn't exist`);
|
|
1812
|
+
}
|
|
1813
|
+
makeUnsupportedRelationshipError(model, relationship, status) {
|
|
1814
|
+
return this.makeError("unsupportedRelationship", `Relationship ${model}.${relationship} doesn't exist`, status);
|
|
1815
|
+
}
|
|
1816
|
+
};
|
|
76
1817
|
|
|
77
1818
|
// src/api/rpc/index.ts
|
|
1819
|
+
var import_common_helpers2 = require("@zenstackhq/common-helpers");
|
|
1820
|
+
var import_orm2 = require("@zenstackhq/orm");
|
|
1821
|
+
var import_superjson3 = __toESM(require("superjson"), 1);
|
|
78
1822
|
registerCustomSerializers();
|
|
79
1823
|
var RPCApiHandler = class {
|
|
80
1824
|
static {
|
|
@@ -87,6 +1831,9 @@ var RPCApiHandler = class {
|
|
|
87
1831
|
get schema() {
|
|
88
1832
|
return this.options.schema;
|
|
89
1833
|
}
|
|
1834
|
+
get log() {
|
|
1835
|
+
return this.options.log;
|
|
1836
|
+
}
|
|
90
1837
|
async handleRequest({ client, method, path, query, requestBody }) {
|
|
91
1838
|
const parts = path.split("/").filter((p) => !!p);
|
|
92
1839
|
const op = parts.pop();
|
|
@@ -94,7 +1841,7 @@ var RPCApiHandler = class {
|
|
|
94
1841
|
if (parts.length !== 0 || !op || !model) {
|
|
95
1842
|
return this.makeBadInputErrorResponse("invalid request path");
|
|
96
1843
|
}
|
|
97
|
-
model = (0,
|
|
1844
|
+
model = (0, import_common_helpers2.lowerCaseFirst)(model);
|
|
98
1845
|
method = method.toUpperCase();
|
|
99
1846
|
let args;
|
|
100
1847
|
let resCode = 200;
|
|
@@ -160,13 +1907,13 @@ var RPCApiHandler = class {
|
|
|
160
1907
|
if (!this.isValidModel(client, model)) {
|
|
161
1908
|
return this.makeBadInputErrorResponse(`unknown model name: ${model}`);
|
|
162
1909
|
}
|
|
163
|
-
log(this.options.log, "debug", () => `handling "${model}.${op}" request with args: ${(0,
|
|
1910
|
+
log(this.options.log, "debug", () => `handling "${model}.${op}" request with args: ${(0, import_common_helpers2.safeJSONStringify)(processedArgs)}`);
|
|
164
1911
|
const clientResult = await client[model][op](processedArgs);
|
|
165
1912
|
let responseBody = {
|
|
166
1913
|
data: clientResult
|
|
167
1914
|
};
|
|
168
1915
|
if (clientResult) {
|
|
169
|
-
const { json, meta } =
|
|
1916
|
+
const { json, meta } = import_superjson3.default.serialize(clientResult);
|
|
170
1917
|
responseBody = {
|
|
171
1918
|
data: json
|
|
172
1919
|
};
|
|
@@ -180,11 +1927,11 @@ var RPCApiHandler = class {
|
|
|
180
1927
|
status: resCode,
|
|
181
1928
|
body: responseBody
|
|
182
1929
|
};
|
|
183
|
-
log(this.options.log, "debug", () => `sending response for "${model}.${op}" request: ${(0,
|
|
1930
|
+
log(this.options.log, "debug", () => `sending response for "${model}.${op}" request: ${(0, import_common_helpers2.safeJSONStringify)(response)}`);
|
|
184
1931
|
return response;
|
|
185
1932
|
} catch (err) {
|
|
186
1933
|
log(this.options.log, "error", `error occurred when handling "${model}.${op}" request`, err);
|
|
187
|
-
if (err instanceof
|
|
1934
|
+
if (err instanceof import_orm2.ZenStackError) {
|
|
188
1935
|
return this.makeZenStackErrorResponse(err);
|
|
189
1936
|
} else {
|
|
190
1937
|
return this.makeGenericErrorResponse(err);
|
|
@@ -192,7 +1939,7 @@ var RPCApiHandler = class {
|
|
|
192
1939
|
}
|
|
193
1940
|
}
|
|
194
1941
|
isValidModel(client, model) {
|
|
195
|
-
return Object.keys(client.$schema.models).some((m) => (0,
|
|
1942
|
+
return Object.keys(client.$schema.models).some((m) => (0, import_common_helpers2.lowerCaseFirst)(m) === (0, import_common_helpers2.lowerCaseFirst)(model));
|
|
196
1943
|
}
|
|
197
1944
|
makeBadInputErrorResponse(message) {
|
|
198
1945
|
const resp = {
|
|
@@ -203,7 +1950,7 @@ var RPCApiHandler = class {
|
|
|
203
1950
|
}
|
|
204
1951
|
}
|
|
205
1952
|
};
|
|
206
|
-
log(this.options.log, "debug", () => `sending error response: ${(0,
|
|
1953
|
+
log(this.options.log, "debug", () => `sending error response: ${(0, import_common_helpers2.safeJSONStringify)(resp)}`);
|
|
207
1954
|
return resp;
|
|
208
1955
|
}
|
|
209
1956
|
makeGenericErrorResponse(err) {
|
|
@@ -211,11 +1958,11 @@ var RPCApiHandler = class {
|
|
|
211
1958
|
status: 500,
|
|
212
1959
|
body: {
|
|
213
1960
|
error: {
|
|
214
|
-
message: err.message
|
|
1961
|
+
message: err instanceof Error ? err.message : "unknown error"
|
|
215
1962
|
}
|
|
216
1963
|
}
|
|
217
1964
|
};
|
|
218
|
-
log(this.options.log, "debug", () => `sending error response: ${(0,
|
|
1965
|
+
log(this.options.log, "debug", () => `sending error response: ${(0, import_common_helpers2.safeJSONStringify)(resp)}${err instanceof Error ? "\n" + err.stack : ""}`);
|
|
219
1966
|
return resp;
|
|
220
1967
|
}
|
|
221
1968
|
makeZenStackErrorResponse(err) {
|
|
@@ -226,14 +1973,14 @@ var RPCApiHandler = class {
|
|
|
226
1973
|
if (err.cause && err.cause instanceof Error) {
|
|
227
1974
|
error.cause = err.cause.message;
|
|
228
1975
|
}
|
|
229
|
-
if (err instanceof
|
|
1976
|
+
if (err instanceof import_orm2.NotFoundError) {
|
|
230
1977
|
status = 404;
|
|
231
1978
|
error.model = err.model;
|
|
232
|
-
} else if (err instanceof
|
|
1979
|
+
} else if (err instanceof import_orm2.InputValidationError) {
|
|
233
1980
|
status = 422;
|
|
234
1981
|
error.rejectedByValidation = true;
|
|
235
1982
|
error.model = err.model;
|
|
236
|
-
} else if (err instanceof
|
|
1983
|
+
} else if (err instanceof import_orm2.RejectedByPolicyError) {
|
|
237
1984
|
status = 403;
|
|
238
1985
|
error.rejectedByPolicy = true;
|
|
239
1986
|
error.rejectReason = err.reason;
|
|
@@ -245,14 +1992,14 @@ var RPCApiHandler = class {
|
|
|
245
1992
|
error
|
|
246
1993
|
}
|
|
247
1994
|
};
|
|
248
|
-
log(this.options.log, "debug", () => `sending error response: ${(0,
|
|
1995
|
+
log(this.options.log, "debug", () => `sending error response: ${(0, import_common_helpers2.safeJSONStringify)(resp)}`);
|
|
249
1996
|
return resp;
|
|
250
1997
|
}
|
|
251
1998
|
async processRequestPayload(args) {
|
|
252
1999
|
const { meta, ...rest } = args;
|
|
253
2000
|
if (meta?.serialization) {
|
|
254
2001
|
try {
|
|
255
|
-
args =
|
|
2002
|
+
args = import_superjson3.default.deserialize({
|
|
256
2003
|
json: rest,
|
|
257
2004
|
meta: meta.serialization
|
|
258
2005
|
});
|
|
@@ -283,7 +2030,7 @@ var RPCApiHandler = class {
|
|
|
283
2030
|
throw new Error('invalid "meta" query parameter');
|
|
284
2031
|
}
|
|
285
2032
|
if (parsedMeta.serialization) {
|
|
286
|
-
return
|
|
2033
|
+
return import_superjson3.default.deserialize({
|
|
287
2034
|
json: parsedValue,
|
|
288
2035
|
meta: parsedMeta.serialization
|
|
289
2036
|
});
|
|
@@ -294,6 +2041,7 @@ var RPCApiHandler = class {
|
|
|
294
2041
|
};
|
|
295
2042
|
// Annotate the CommonJS export names for ESM import in node:
|
|
296
2043
|
0 && (module.exports = {
|
|
297
|
-
RPCApiHandler
|
|
2044
|
+
RPCApiHandler,
|
|
2045
|
+
RestApiHandler
|
|
298
2046
|
});
|
|
299
2047
|
//# sourceMappingURL=api.cjs.map
|