@zenstackhq/server 3.0.0-beta.13 → 3.0.0-beta.15

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.
Files changed (59) hide show
  1. package/dist/api.cjs +1767 -19
  2. package/dist/api.cjs.map +1 -1
  3. package/dist/api.d.cts +123 -4
  4. package/dist/api.d.ts +123 -4
  5. package/dist/api.js +1762 -15
  6. package/dist/api.js.map +1 -1
  7. package/dist/common-6DG-xEmM.d.cts +14 -0
  8. package/dist/common-CyapsM8n.d.ts +14 -0
  9. package/dist/elysia.cjs +118 -0
  10. package/dist/elysia.cjs.map +1 -0
  11. package/dist/elysia.d.cts +53 -0
  12. package/dist/elysia.d.ts +53 -0
  13. package/dist/elysia.js +83 -0
  14. package/dist/elysia.js.map +1 -0
  15. package/dist/express.cjs +41 -3
  16. package/dist/express.cjs.map +1 -1
  17. package/dist/express.d.cts +7 -7
  18. package/dist/express.d.ts +7 -7
  19. package/dist/express.js +30 -2
  20. package/dist/express.js.map +1 -1
  21. package/dist/fastify.cjs +103 -0
  22. package/dist/fastify.cjs.map +1 -0
  23. package/dist/fastify.d.cts +22 -0
  24. package/dist/fastify.d.ts +22 -0
  25. package/dist/fastify.js +68 -0
  26. package/dist/fastify.js.map +1 -0
  27. package/dist/hono.cjs +111 -0
  28. package/dist/hono.cjs.map +1 -0
  29. package/dist/hono.d.cts +18 -0
  30. package/dist/hono.d.ts +18 -0
  31. package/dist/hono.js +76 -0
  32. package/dist/hono.js.map +1 -0
  33. package/dist/next.cjs +178 -0
  34. package/dist/next.cjs.map +1 -0
  35. package/dist/next.d.cts +61 -0
  36. package/dist/next.d.ts +61 -0
  37. package/dist/next.js +143 -0
  38. package/dist/next.js.map +1 -0
  39. package/dist/nuxt.cjs +109 -0
  40. package/dist/nuxt.cjs.map +1 -0
  41. package/dist/nuxt.d.cts +19 -0
  42. package/dist/nuxt.d.ts +19 -0
  43. package/dist/nuxt.js +74 -0
  44. package/dist/nuxt.js.map +1 -0
  45. package/dist/sveltekit.cjs +134 -0
  46. package/dist/sveltekit.cjs.map +1 -0
  47. package/dist/sveltekit.d.cts +25 -0
  48. package/dist/sveltekit.d.ts +25 -0
  49. package/dist/sveltekit.js +99 -0
  50. package/dist/sveltekit.js.map +1 -0
  51. package/dist/tanstack-start.cjs +139 -0
  52. package/dist/tanstack-start.cjs.map +1 -0
  53. package/dist/tanstack-start.d.cts +32 -0
  54. package/dist/tanstack-start.d.ts +32 -0
  55. package/dist/tanstack-start.js +104 -0
  56. package/dist/tanstack-start.js.map +1 -0
  57. package/dist/{types-BH-88xJo.d.cts → types-D5t0sUEw.d.cts} +6 -2
  58. package/dist/{types-BH-88xJo.d.ts → types-D5t0sUEw.d.ts} +6 -2
  59. 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/rpc/index.ts
39
+ // src/api/rest/index.ts
39
40
  var import_common_helpers = require("@zenstackhq/common-helpers");
40
- var import_runtime2 = require("@zenstackhq/runtime");
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, import_common_helpers.lowerCaseFirst)(model);
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, import_common_helpers.safeJSONStringify)(processedArgs)}`);
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 } = import_superjson2.default.serialize(clientResult);
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, import_common_helpers.safeJSONStringify)(response)}`);
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 import_runtime2.ZenStackError) {
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, import_common_helpers.lowerCaseFirst)(m) === (0, import_common_helpers.lowerCaseFirst)(model));
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, import_common_helpers.safeJSONStringify)(resp)}`);
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 || "unknown error"
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, import_common_helpers.safeJSONStringify)(resp)}`);
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 import_runtime2.NotFoundError) {
1976
+ if (err instanceof import_orm2.NotFoundError) {
230
1977
  status = 404;
231
1978
  error.model = err.model;
232
- } else if (err instanceof import_runtime2.InputValidationError) {
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 import_runtime2.RejectedByPolicyError) {
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, import_common_helpers.safeJSONStringify)(resp)}`);
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 = import_superjson2.default.deserialize({
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 import_superjson2.default.deserialize({
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