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