@zenstackhq/server 3.5.0-beta.3 → 3.5.0-beta.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/api.cjs CHANGED
@@ -37,7 +37,7 @@ __export(api_exports, {
37
37
  module.exports = __toCommonJS(api_exports);
38
38
 
39
39
  // src/api/rest/index.ts
40
- var import_common_helpers = require("@zenstackhq/common-helpers");
40
+ var import_common_helpers3 = require("@zenstackhq/common-helpers");
41
41
  var import_orm2 = require("@zenstackhq/orm");
42
42
  var import_decimal2 = require("decimal.js");
43
43
  var import_superjson3 = __toESM(require("superjson"), 1);
@@ -127,6 +127,26 @@ var loggerSchema = import_zod.default.union([
127
127
  ]).array(),
128
128
  import_zod.default.function()
129
129
  ]);
130
+ var fieldSlicingSchema = import_zod.default.looseObject({
131
+ includedFilterKinds: import_zod.default.string().array().optional(),
132
+ excludedFilterKinds: import_zod.default.string().array().optional()
133
+ });
134
+ var modelSlicingSchema = import_zod.default.looseObject({
135
+ includedOperations: import_zod.default.array(import_zod.default.string()).optional(),
136
+ excludedOperations: import_zod.default.array(import_zod.default.string()).optional(),
137
+ fields: import_zod.default.record(import_zod.default.string(), fieldSlicingSchema).optional()
138
+ });
139
+ var slicingSchema = import_zod.default.looseObject({
140
+ includedModels: import_zod.default.array(import_zod.default.string()).optional(),
141
+ excludedModels: import_zod.default.array(import_zod.default.string()).optional(),
142
+ models: import_zod.default.record(import_zod.default.string(), modelSlicingSchema).optional(),
143
+ includedProcedures: import_zod.default.array(import_zod.default.string()).optional(),
144
+ excludedProcedures: import_zod.default.array(import_zod.default.string()).optional()
145
+ });
146
+ var queryOptionsSchema = import_zod.default.looseObject({
147
+ omit: import_zod.default.record(import_zod.default.string(), import_zod.default.record(import_zod.default.string(), import_zod.default.boolean())).optional(),
148
+ slicing: slicingSchema.optional()
149
+ });
130
150
 
131
151
  // src/api/common/utils.ts
132
152
  var import_superjson = __toESM(require("superjson"), 1);
@@ -195,39 +215,1434 @@ function log(logger, level, message, error) {
195
215
  if (!logger) {
196
216
  return;
197
217
  }
198
- const getMessage = typeof message === "function" ? message : () => message;
199
- if (typeof logger === "function") {
200
- logger(level, getMessage(), error);
201
- } else if (logger.includes(level)) {
202
- const logFn = (0, import_ts_pattern.match)(level).with("debug", () => console.debug).with("info", () => console.info).with("warn", () => console.warn).with("error", () => console.error).exhaustive();
203
- logFn(`@zenstackhq/server: [${level}] ${getMessage()}${error ? `
204
- ${error}` : ""}`);
218
+ const getMessage = typeof message === "function" ? message : () => message;
219
+ if (typeof logger === "function") {
220
+ logger(level, getMessage(), error);
221
+ } else if (logger.includes(level)) {
222
+ const logFn = (0, import_ts_pattern.match)(level).with("debug", () => console.debug).with("info", () => console.info).with("warn", () => console.warn).with("error", () => console.error).exhaustive();
223
+ logFn(`@zenstackhq/server: [${level}] ${getMessage()}${error ? `
224
+ ${error}` : ""}`);
225
+ }
226
+ }
227
+ __name(log, "log");
228
+ function registerCustomSerializers() {
229
+ import_superjson2.default.registerCustom({
230
+ isApplicable: /* @__PURE__ */ __name((v) => import_decimal.Decimal.isDecimal(v), "isApplicable"),
231
+ serialize: /* @__PURE__ */ __name((v) => v.toJSON(), "serialize"),
232
+ deserialize: /* @__PURE__ */ __name((v) => new import_decimal.Decimal(v), "deserialize")
233
+ }, "Decimal");
234
+ if (globalThis.Buffer) {
235
+ import_superjson2.default.registerCustom({
236
+ isApplicable: /* @__PURE__ */ __name((v) => Buffer.isBuffer(v), "isApplicable"),
237
+ serialize: /* @__PURE__ */ __name((v) => v.toString("base64"), "serialize"),
238
+ deserialize: /* @__PURE__ */ __name((v) => Buffer.from(v, "base64"), "deserialize")
239
+ }, "Bytes");
240
+ }
241
+ }
242
+ __name(registerCustomSerializers, "registerCustomSerializers");
243
+ function getZodErrorMessage(error) {
244
+ if ("_zod" in error) {
245
+ return (0, import_v4.fromError)(error).toString();
246
+ } else {
247
+ return (0, import_v3.fromError)(error).toString();
248
+ }
249
+ }
250
+ __name(getZodErrorMessage, "getZodErrorMessage");
251
+
252
+ // src/api/rest/openapi.ts
253
+ var import_common_helpers2 = require("@zenstackhq/common-helpers");
254
+
255
+ // src/api/common/spec-utils.ts
256
+ var import_common_helpers = require("@zenstackhq/common-helpers");
257
+ var import_schema = require("@zenstackhq/orm/schema");
258
+ function isModelIncluded(modelName, queryOptions) {
259
+ const slicing = queryOptions?.slicing;
260
+ if (!slicing) return true;
261
+ const excluded = slicing.excludedModels;
262
+ if (excluded?.includes(modelName)) return false;
263
+ const included = slicing.includedModels;
264
+ if (included && !included.includes(modelName)) return false;
265
+ return true;
266
+ }
267
+ __name(isModelIncluded, "isModelIncluded");
268
+ function isOperationIncluded(modelName, op, queryOptions) {
269
+ const slicing = queryOptions?.slicing;
270
+ if (!slicing?.models) return true;
271
+ const modelKey = (0, import_common_helpers.lowerCaseFirst)(modelName);
272
+ const modelSlicing = slicing.models[modelKey] ?? slicing.models.$all;
273
+ if (!modelSlicing) return true;
274
+ const excluded = modelSlicing.excludedOperations;
275
+ if (excluded?.includes(op)) return false;
276
+ const included = modelSlicing.includedOperations;
277
+ if (included && !included.includes(op)) return false;
278
+ return true;
279
+ }
280
+ __name(isOperationIncluded, "isOperationIncluded");
281
+ function isProcedureIncluded(procName, queryOptions) {
282
+ const slicing = queryOptions?.slicing;
283
+ if (!slicing) return true;
284
+ const excluded = slicing.excludedProcedures;
285
+ if (excluded?.includes(procName)) return false;
286
+ const included = slicing.includedProcedures;
287
+ if (included && !included.includes(procName)) return false;
288
+ return true;
289
+ }
290
+ __name(isProcedureIncluded, "isProcedureIncluded");
291
+ function isFieldOmitted(modelName, fieldName, queryOptions) {
292
+ const omit = queryOptions?.omit;
293
+ return omit?.[modelName]?.[fieldName] === true;
294
+ }
295
+ __name(isFieldOmitted, "isFieldOmitted");
296
+ function getIncludedModels(schema, queryOptions) {
297
+ return Object.keys(schema.models).filter((name) => isModelIncluded(name, queryOptions));
298
+ }
299
+ __name(getIncludedModels, "getIncludedModels");
300
+ function isFilterKindIncluded(modelName, fieldName, filterKind, queryOptions) {
301
+ const slicing = queryOptions?.slicing;
302
+ if (!slicing?.models) return true;
303
+ const modelKey = (0, import_common_helpers.lowerCaseFirst)(modelName);
304
+ const modelSlicing = slicing.models[modelKey] ?? slicing.models.$all;
305
+ if (!modelSlicing?.fields) return true;
306
+ const fieldSlicing = modelSlicing.fields[fieldName] ?? modelSlicing.fields.$all;
307
+ if (!fieldSlicing) return true;
308
+ const excluded = fieldSlicing.excludedFilterKinds;
309
+ if (excluded?.includes(filterKind)) return false;
310
+ const included = fieldSlicing.includedFilterKinds;
311
+ if (included && !included.includes(filterKind)) return false;
312
+ return true;
313
+ }
314
+ __name(isFilterKindIncluded, "isFilterKindIncluded");
315
+ function getMetaDescription(attributes) {
316
+ if (!attributes) return void 0;
317
+ for (const attr of attributes) {
318
+ if (attr.name !== "@meta" && attr.name !== "@@meta") continue;
319
+ const nameArg = attr.args?.find((a) => a.name === "name");
320
+ if (!nameArg || import_schema.ExpressionUtils.getLiteralValue(nameArg.value) !== "description") continue;
321
+ const valueArg = attr.args?.find((a) => a.name === "value");
322
+ if (valueArg) {
323
+ return import_schema.ExpressionUtils.getLiteralValue(valueArg.value);
324
+ }
325
+ }
326
+ return void 0;
327
+ }
328
+ __name(getMetaDescription, "getMetaDescription");
329
+
330
+ // src/api/rest/openapi.ts
331
+ function errorResponse(description) {
332
+ return {
333
+ description,
334
+ content: {
335
+ "application/vnd.api+json": {
336
+ schema: {
337
+ $ref: "#/components/schemas/_errorResponse"
338
+ }
339
+ }
340
+ }
341
+ };
342
+ }
343
+ __name(errorResponse, "errorResponse");
344
+ var ERROR_400 = errorResponse("Error occurred while processing the request");
345
+ var ERROR_403 = errorResponse("Forbidden: insufficient permissions to perform this operation");
346
+ var ERROR_404 = errorResponse("Resource not found");
347
+ var ERROR_422 = errorResponse("Operation is unprocessable due to validation errors");
348
+ var SCALAR_STRING_OPS = [
349
+ "$contains",
350
+ "$icontains",
351
+ "$search",
352
+ "$startsWith",
353
+ "$endsWith"
354
+ ];
355
+ var SCALAR_COMPARABLE_OPS = [
356
+ "$lt",
357
+ "$lte",
358
+ "$gt",
359
+ "$gte"
360
+ ];
361
+ var SCALAR_ARRAY_OPS = [
362
+ "$has",
363
+ "$hasEvery",
364
+ "$hasSome",
365
+ "$isEmpty"
366
+ ];
367
+ var RestApiSpecGenerator = class {
368
+ static {
369
+ __name(this, "RestApiSpecGenerator");
370
+ }
371
+ handlerOptions;
372
+ specOptions;
373
+ constructor(handlerOptions) {
374
+ this.handlerOptions = handlerOptions;
375
+ }
376
+ get schema() {
377
+ return this.handlerOptions.schema;
378
+ }
379
+ get modelNameMapping() {
380
+ const mapping = {};
381
+ if (this.handlerOptions.modelNameMapping) {
382
+ for (const [k, v] of Object.entries(this.handlerOptions.modelNameMapping)) {
383
+ mapping[(0, import_common_helpers2.lowerCaseFirst)(k)] = v;
384
+ }
385
+ }
386
+ return mapping;
387
+ }
388
+ get queryOptions() {
389
+ return this.handlerOptions?.queryOptions;
390
+ }
391
+ generateSpec(options) {
392
+ this.specOptions = options;
393
+ return {
394
+ openapi: "3.1.0",
395
+ info: {
396
+ title: options?.title ?? "ZenStack Generated API",
397
+ version: options?.version ?? "1.0.0",
398
+ ...options?.description && {
399
+ description: options.description
400
+ },
401
+ ...options?.summary && {
402
+ summary: options.summary
403
+ }
404
+ },
405
+ tags: this.generateTags(),
406
+ paths: this.generatePaths(),
407
+ components: {
408
+ schemas: this.generateSchemas(),
409
+ parameters: this.generateSharedParams()
410
+ }
411
+ };
412
+ }
413
+ generateTags() {
414
+ return getIncludedModels(this.schema, this.queryOptions).map((modelName) => ({
415
+ name: (0, import_common_helpers2.lowerCaseFirst)(modelName),
416
+ description: `${modelName} operations`
417
+ }));
418
+ }
419
+ getModelPath(modelName) {
420
+ const lower = (0, import_common_helpers2.lowerCaseFirst)(modelName);
421
+ return this.modelNameMapping[lower] ?? lower;
422
+ }
423
+ generatePaths() {
424
+ const paths = {};
425
+ for (const modelName of getIncludedModels(this.schema, this.queryOptions)) {
426
+ const modelDef = this.schema.models[modelName];
427
+ const idFields = this.getIdFields(modelDef);
428
+ if (idFields.length === 0) continue;
429
+ const modelPath = this.getModelPath(modelName);
430
+ const tag = (0, import_common_helpers2.lowerCaseFirst)(modelName);
431
+ const collectionPath = this.buildCollectionPath(modelName, modelDef, tag);
432
+ if (Object.keys(collectionPath).length > 0) {
433
+ paths[`/${modelPath}`] = collectionPath;
434
+ }
435
+ const singlePath = this.buildSinglePath(modelDef, tag);
436
+ if (Object.keys(singlePath).length > 0) {
437
+ paths[`/${modelPath}/{id}`] = singlePath;
438
+ }
439
+ for (const [fieldName, fieldDef] of Object.entries(modelDef.fields)) {
440
+ if (!fieldDef.relation) continue;
441
+ if (!isModelIncluded(fieldDef.type, this.queryOptions)) continue;
442
+ const relModelDef = this.schema.models[fieldDef.type];
443
+ if (!relModelDef) continue;
444
+ const relIdFields = this.getIdFields(relModelDef);
445
+ if (relIdFields.length === 0) continue;
446
+ paths[`/${modelPath}/{id}/${fieldName}`] = this.buildFetchRelatedPath(modelName, fieldName, fieldDef, tag);
447
+ paths[`/${modelPath}/{id}/relationships/${fieldName}`] = this.buildRelationshipPath(modelDef, fieldName, fieldDef, tag);
448
+ }
449
+ }
450
+ if (this.schema.procedures) {
451
+ for (const [procName, procDef] of Object.entries(this.schema.procedures)) {
452
+ if (!isProcedureIncluded(procName, this.queryOptions)) continue;
453
+ const isMutation = !!procDef.mutation;
454
+ if (isMutation) {
455
+ paths[`/${PROCEDURE_ROUTE_PREFIXES}/${procName}`] = {
456
+ post: this.buildProcedureOperation(procName, "post")
457
+ };
458
+ } else {
459
+ paths[`/${PROCEDURE_ROUTE_PREFIXES}/${procName}`] = {
460
+ get: this.buildProcedureOperation(procName, "get")
461
+ };
462
+ }
463
+ }
464
+ }
465
+ return paths;
466
+ }
467
+ buildCollectionPath(modelName, modelDef, tag) {
468
+ const filterParams = this.buildFilterParams(modelName, modelDef);
469
+ const listOp = {
470
+ tags: [
471
+ tag
472
+ ],
473
+ summary: `List ${modelName} resources`,
474
+ operationId: `list${modelName}`,
475
+ parameters: [
476
+ {
477
+ $ref: "#/components/parameters/include"
478
+ },
479
+ {
480
+ $ref: "#/components/parameters/sort"
481
+ },
482
+ {
483
+ $ref: "#/components/parameters/pageOffset"
484
+ },
485
+ {
486
+ $ref: "#/components/parameters/pageLimit"
487
+ },
488
+ ...filterParams
489
+ ],
490
+ responses: {
491
+ "200": {
492
+ description: `List of ${modelName} resources`,
493
+ content: {
494
+ "application/vnd.api+json": {
495
+ schema: {
496
+ $ref: `#/components/schemas/${modelName}ListResponse`
497
+ }
498
+ }
499
+ }
500
+ },
501
+ "400": ERROR_400
502
+ }
503
+ };
504
+ const createOp = {
505
+ tags: [
506
+ tag
507
+ ],
508
+ summary: `Create a ${modelName} resource`,
509
+ operationId: `create${modelName}`,
510
+ requestBody: {
511
+ required: true,
512
+ content: {
513
+ "application/vnd.api+json": {
514
+ schema: {
515
+ $ref: `#/components/schemas/${modelName}CreateRequest`
516
+ }
517
+ }
518
+ }
519
+ },
520
+ responses: {
521
+ "201": {
522
+ description: `Created ${modelName} resource`,
523
+ content: {
524
+ "application/vnd.api+json": {
525
+ schema: {
526
+ $ref: `#/components/schemas/${modelName}Response`
527
+ }
528
+ }
529
+ }
530
+ },
531
+ "400": ERROR_400,
532
+ ...this.mayDenyAccess(modelDef, "create") && {
533
+ "403": ERROR_403
534
+ },
535
+ "422": ERROR_422
536
+ }
537
+ };
538
+ const result = {};
539
+ if (isOperationIncluded(modelName, "findMany", this.queryOptions)) {
540
+ result["get"] = listOp;
541
+ }
542
+ if (isOperationIncluded(modelName, "create", this.queryOptions)) {
543
+ result["post"] = createOp;
544
+ }
545
+ return result;
546
+ }
547
+ buildSinglePath(modelDef, tag) {
548
+ const modelName = modelDef.name;
549
+ const idParam = {
550
+ $ref: "#/components/parameters/id"
551
+ };
552
+ const result = {};
553
+ if (isOperationIncluded(modelName, "findUnique", this.queryOptions)) {
554
+ result["get"] = {
555
+ tags: [
556
+ tag
557
+ ],
558
+ summary: `Get a ${modelName} resource by ID`,
559
+ operationId: `get${modelName}`,
560
+ parameters: [
561
+ idParam,
562
+ {
563
+ $ref: "#/components/parameters/include"
564
+ }
565
+ ],
566
+ responses: {
567
+ "200": {
568
+ description: `${modelName} resource`,
569
+ content: {
570
+ "application/vnd.api+json": {
571
+ schema: {
572
+ $ref: `#/components/schemas/${modelName}Response`
573
+ }
574
+ }
575
+ }
576
+ },
577
+ "404": ERROR_404
578
+ }
579
+ };
580
+ }
581
+ if (isOperationIncluded(modelName, "update", this.queryOptions)) {
582
+ result["patch"] = {
583
+ tags: [
584
+ tag
585
+ ],
586
+ summary: `Update a ${modelName} resource`,
587
+ operationId: `update${modelName}`,
588
+ parameters: [
589
+ idParam
590
+ ],
591
+ requestBody: {
592
+ required: true,
593
+ content: {
594
+ "application/vnd.api+json": {
595
+ schema: {
596
+ $ref: `#/components/schemas/${modelName}UpdateRequest`
597
+ }
598
+ }
599
+ }
600
+ },
601
+ responses: {
602
+ "200": {
603
+ description: `Updated ${modelName} resource`,
604
+ content: {
605
+ "application/vnd.api+json": {
606
+ schema: {
607
+ $ref: `#/components/schemas/${modelName}Response`
608
+ }
609
+ }
610
+ }
611
+ },
612
+ "400": ERROR_400,
613
+ ...this.mayDenyAccess(modelDef, "update") && {
614
+ "403": ERROR_403
615
+ },
616
+ "404": ERROR_404,
617
+ "422": ERROR_422
618
+ }
619
+ };
620
+ }
621
+ if (isOperationIncluded(modelName, "delete", this.queryOptions)) {
622
+ result["delete"] = {
623
+ tags: [
624
+ tag
625
+ ],
626
+ summary: `Delete a ${modelName} resource`,
627
+ operationId: `delete${modelName}`,
628
+ parameters: [
629
+ idParam
630
+ ],
631
+ responses: {
632
+ "200": {
633
+ description: "Deleted successfully"
634
+ },
635
+ ...this.mayDenyAccess(modelDef, "delete") && {
636
+ "403": ERROR_403
637
+ },
638
+ "404": ERROR_404
639
+ }
640
+ };
641
+ }
642
+ return result;
643
+ }
644
+ buildFetchRelatedPath(modelName, fieldName, fieldDef, tag) {
645
+ const isCollection = !!fieldDef.array;
646
+ const params = [
647
+ {
648
+ $ref: "#/components/parameters/id"
649
+ },
650
+ {
651
+ $ref: "#/components/parameters/include"
652
+ }
653
+ ];
654
+ if (isCollection && this.schema.models[fieldDef.type]) {
655
+ const relModelDef = this.schema.models[fieldDef.type];
656
+ params.push({
657
+ $ref: "#/components/parameters/sort"
658
+ }, {
659
+ $ref: "#/components/parameters/pageOffset"
660
+ }, {
661
+ $ref: "#/components/parameters/pageLimit"
662
+ }, ...this.buildFilterParams(fieldDef.type, relModelDef));
663
+ }
664
+ return {
665
+ get: {
666
+ tags: [
667
+ tag
668
+ ],
669
+ summary: `Fetch related ${fieldDef.type} for ${modelName}`,
670
+ operationId: `get${modelName}_${fieldName}`,
671
+ parameters: params,
672
+ responses: {
673
+ "200": {
674
+ description: `Related ${fieldDef.type} resource(s)`,
675
+ content: {
676
+ "application/vnd.api+json": {
677
+ schema: isCollection ? {
678
+ $ref: `#/components/schemas/${fieldDef.type}ListResponse`
679
+ } : {
680
+ $ref: `#/components/schemas/${fieldDef.type}Response`
681
+ }
682
+ }
683
+ }
684
+ },
685
+ "404": ERROR_404
686
+ }
687
+ }
688
+ };
689
+ }
690
+ buildRelationshipPath(modelDef, fieldName, fieldDef, tag) {
691
+ const modelName = modelDef.name;
692
+ const isCollection = !!fieldDef.array;
693
+ const idParam = {
694
+ $ref: "#/components/parameters/id"
695
+ };
696
+ const relSchemaRef = isCollection ? {
697
+ $ref: "#/components/schemas/_toManyRelationshipWithLinks"
698
+ } : {
699
+ $ref: "#/components/schemas/_toOneRelationshipWithLinks"
700
+ };
701
+ const relRequestRef = isCollection ? {
702
+ $ref: "#/components/schemas/_toManyRelationshipRequest"
703
+ } : {
704
+ $ref: "#/components/schemas/_toOneRelationshipRequest"
705
+ };
706
+ const mayDeny = this.mayDenyAccess(modelDef, "update");
707
+ const pathItem = {
708
+ get: {
709
+ tags: [
710
+ tag
711
+ ],
712
+ summary: `Fetch ${fieldName} relationship`,
713
+ operationId: `get${modelName}_relationships_${fieldName}`,
714
+ parameters: [
715
+ idParam
716
+ ],
717
+ responses: {
718
+ "200": {
719
+ description: `${fieldName} relationship`,
720
+ content: {
721
+ "application/vnd.api+json": {
722
+ schema: relSchemaRef
723
+ }
724
+ }
725
+ },
726
+ "404": ERROR_404
727
+ }
728
+ },
729
+ put: {
730
+ tags: [
731
+ tag
732
+ ],
733
+ summary: `Replace ${fieldName} relationship`,
734
+ operationId: `put${modelName}_relationships_${fieldName}`,
735
+ parameters: [
736
+ idParam
737
+ ],
738
+ requestBody: {
739
+ required: true,
740
+ content: {
741
+ "application/vnd.api+json": {
742
+ schema: relRequestRef
743
+ }
744
+ }
745
+ },
746
+ responses: {
747
+ "200": {
748
+ description: "Relationship updated"
749
+ },
750
+ "400": ERROR_400,
751
+ ...mayDeny && {
752
+ "403": ERROR_403
753
+ }
754
+ }
755
+ },
756
+ patch: {
757
+ tags: [
758
+ tag
759
+ ],
760
+ summary: `Update ${fieldName} relationship`,
761
+ operationId: `patch${modelName}_relationships_${fieldName}`,
762
+ parameters: [
763
+ idParam
764
+ ],
765
+ requestBody: {
766
+ required: true,
767
+ content: {
768
+ "application/vnd.api+json": {
769
+ schema: relRequestRef
770
+ }
771
+ }
772
+ },
773
+ responses: {
774
+ "200": {
775
+ description: "Relationship updated"
776
+ },
777
+ "400": ERROR_400,
778
+ ...mayDeny && {
779
+ "403": ERROR_403
780
+ }
781
+ }
782
+ }
783
+ };
784
+ if (isCollection) {
785
+ pathItem["post"] = {
786
+ tags: [
787
+ tag
788
+ ],
789
+ summary: `Add to ${fieldName} collection relationship`,
790
+ operationId: `post${modelName}_relationships_${fieldName}`,
791
+ parameters: [
792
+ idParam
793
+ ],
794
+ requestBody: {
795
+ required: true,
796
+ content: {
797
+ "application/vnd.api+json": {
798
+ schema: {
799
+ $ref: "#/components/schemas/_toManyRelationshipRequest"
800
+ }
801
+ }
802
+ }
803
+ },
804
+ responses: {
805
+ "200": {
806
+ description: "Added to relationship collection"
807
+ },
808
+ "400": ERROR_400,
809
+ ...mayDeny && {
810
+ "403": ERROR_403
811
+ }
812
+ }
813
+ };
814
+ }
815
+ return pathItem;
816
+ }
817
+ buildProcedureOperation(procName, method) {
818
+ const op = {
819
+ tags: [
820
+ "$procs"
821
+ ],
822
+ summary: `Execute procedure ${procName}`,
823
+ operationId: `proc_${procName}`,
824
+ responses: {
825
+ "200": {
826
+ description: `Result of ${procName}`
827
+ },
828
+ "400": ERROR_400
829
+ }
830
+ };
831
+ if (method === "get") {
832
+ op["parameters"] = [
833
+ {
834
+ name: "q",
835
+ in: "query",
836
+ description: "Procedure arguments as JSON",
837
+ schema: {
838
+ type: "string"
839
+ }
840
+ }
841
+ ];
842
+ } else {
843
+ op["requestBody"] = {
844
+ content: {
845
+ "application/json": {
846
+ schema: {
847
+ type: "object"
848
+ }
849
+ }
850
+ }
851
+ };
852
+ }
853
+ return op;
854
+ }
855
+ buildFilterParams(modelName, modelDef) {
856
+ const params = [];
857
+ const idFieldNames = new Set(modelDef.idFields);
858
+ if (isFilterKindIncluded(modelName, "id", "Equality", this.queryOptions)) {
859
+ params.push({
860
+ name: "filter[id]",
861
+ in: "query",
862
+ schema: {
863
+ type: "string"
864
+ },
865
+ description: `Filter by ${modelName} ID`
866
+ });
867
+ }
868
+ for (const [fieldName, fieldDef] of Object.entries(modelDef.fields)) {
869
+ if (fieldDef.relation) continue;
870
+ if (idFieldNames.has(fieldName)) continue;
871
+ const type = fieldDef.type;
872
+ if (isFilterKindIncluded(modelName, fieldName, "Equality", this.queryOptions)) {
873
+ params.push({
874
+ name: `filter[${fieldName}]`,
875
+ in: "query",
876
+ schema: {
877
+ type: "string"
878
+ },
879
+ description: `Filter by ${fieldName}`
880
+ });
881
+ }
882
+ if (type === "String" && isFilterKindIncluded(modelName, fieldName, "Like", this.queryOptions)) {
883
+ for (const op of SCALAR_STRING_OPS) {
884
+ params.push({
885
+ name: `filter[${fieldName}][${op}]`,
886
+ in: "query",
887
+ schema: {
888
+ type: "string"
889
+ }
890
+ });
891
+ }
892
+ } else if ((type === "Int" || type === "Float" || type === "BigInt" || type === "Decimal" || type === "DateTime") && isFilterKindIncluded(modelName, fieldName, "Range", this.queryOptions)) {
893
+ for (const op of SCALAR_COMPARABLE_OPS) {
894
+ params.push({
895
+ name: `filter[${fieldName}][${op}]`,
896
+ in: "query",
897
+ schema: {
898
+ type: "string"
899
+ }
900
+ });
901
+ }
902
+ }
903
+ if (fieldDef.array && isFilterKindIncluded(modelName, fieldName, "List", this.queryOptions)) {
904
+ for (const op of SCALAR_ARRAY_OPS) {
905
+ params.push({
906
+ name: `filter[${fieldName}][${op}]`,
907
+ in: "query",
908
+ schema: {
909
+ type: "string"
910
+ }
911
+ });
912
+ }
913
+ }
914
+ }
915
+ return params;
916
+ }
917
+ generateSchemas() {
918
+ const schemas = {};
919
+ Object.assign(schemas, this.buildSharedSchemas());
920
+ if (this.schema.enums) {
921
+ for (const [_enumName, enumDef] of Object.entries(this.schema.enums)) {
922
+ schemas[_enumName] = this.buildEnumSchema(enumDef);
923
+ }
924
+ }
925
+ if (this.schema.typeDefs) {
926
+ for (const [typeName, typeDef] of Object.entries(this.schema.typeDefs)) {
927
+ schemas[typeName] = this.buildTypeDefSchema(typeDef);
928
+ }
929
+ }
930
+ for (const modelName of getIncludedModels(this.schema, this.queryOptions)) {
931
+ const modelDef = this.schema.models[modelName];
932
+ const idFields = this.getIdFields(modelDef);
933
+ if (idFields.length === 0) continue;
934
+ schemas[modelName] = this.buildModelReadSchema(modelName, modelDef);
935
+ schemas[`${modelName}CreateRequest`] = this.buildCreateRequestSchema(modelName, modelDef);
936
+ schemas[`${modelName}UpdateRequest`] = this.buildUpdateRequestSchema(modelDef);
937
+ schemas[`${modelName}Response`] = this.buildModelResponseSchema(modelName);
938
+ schemas[`${modelName}ListResponse`] = this.buildModelListResponseSchema(modelName);
939
+ }
940
+ return schemas;
941
+ }
942
+ buildSharedSchemas() {
943
+ const nullableString = {
944
+ oneOf: [
945
+ {
946
+ type: "string"
947
+ },
948
+ {
949
+ type: "null"
950
+ }
951
+ ]
952
+ };
953
+ return {
954
+ _jsonapi: {
955
+ type: "object",
956
+ properties: {
957
+ version: {
958
+ type: "string"
959
+ },
960
+ meta: {
961
+ type: "object"
962
+ }
963
+ }
964
+ },
965
+ _meta: {
966
+ type: "object",
967
+ additionalProperties: true
968
+ },
969
+ _links: {
970
+ type: "object",
971
+ properties: {
972
+ self: {
973
+ type: "string"
974
+ },
975
+ related: {
976
+ type: "string"
977
+ }
978
+ }
979
+ },
980
+ _pagination: {
981
+ type: "object",
982
+ properties: {
983
+ first: nullableString,
984
+ last: nullableString,
985
+ prev: nullableString,
986
+ next: nullableString
987
+ }
988
+ },
989
+ _errors: {
990
+ type: "array",
991
+ items: {
992
+ type: "object",
993
+ properties: {
994
+ status: {
995
+ type: "integer"
996
+ },
997
+ code: {
998
+ type: "string"
999
+ },
1000
+ title: {
1001
+ type: "string"
1002
+ },
1003
+ detail: {
1004
+ type: "string"
1005
+ }
1006
+ },
1007
+ required: [
1008
+ "status",
1009
+ "title"
1010
+ ]
1011
+ }
1012
+ },
1013
+ _errorResponse: {
1014
+ type: "object",
1015
+ properties: {
1016
+ errors: {
1017
+ $ref: "#/components/schemas/_errors"
1018
+ }
1019
+ },
1020
+ required: [
1021
+ "errors"
1022
+ ]
1023
+ },
1024
+ _resourceIdentifier: {
1025
+ type: "object",
1026
+ properties: {
1027
+ type: {
1028
+ type: "string"
1029
+ },
1030
+ id: {
1031
+ type: "string"
1032
+ }
1033
+ },
1034
+ required: [
1035
+ "type",
1036
+ "id"
1037
+ ]
1038
+ },
1039
+ _resource: {
1040
+ type: "object",
1041
+ properties: {
1042
+ type: {
1043
+ type: "string"
1044
+ },
1045
+ id: {
1046
+ type: "string"
1047
+ },
1048
+ attributes: {
1049
+ type: "object"
1050
+ },
1051
+ relationships: {
1052
+ type: "object"
1053
+ },
1054
+ links: {
1055
+ $ref: "#/components/schemas/_links"
1056
+ },
1057
+ meta: {
1058
+ $ref: "#/components/schemas/_meta"
1059
+ }
1060
+ },
1061
+ required: [
1062
+ "type",
1063
+ "id"
1064
+ ]
1065
+ },
1066
+ _relationLinks: {
1067
+ type: "object",
1068
+ properties: {
1069
+ self: {
1070
+ type: "string"
1071
+ },
1072
+ related: {
1073
+ type: "string"
1074
+ }
1075
+ }
1076
+ },
1077
+ _pagedRelationLinks: {
1078
+ type: "object",
1079
+ allOf: [
1080
+ {
1081
+ $ref: "#/components/schemas/_relationLinks"
1082
+ },
1083
+ {
1084
+ $ref: "#/components/schemas/_pagination"
1085
+ }
1086
+ ]
1087
+ },
1088
+ _toOneRelationship: {
1089
+ type: "object",
1090
+ properties: {
1091
+ data: {
1092
+ oneOf: [
1093
+ {
1094
+ $ref: "#/components/schemas/_resourceIdentifier"
1095
+ },
1096
+ {
1097
+ type: "null"
1098
+ }
1099
+ ]
1100
+ }
1101
+ }
1102
+ },
1103
+ _toManyRelationship: {
1104
+ type: "object",
1105
+ properties: {
1106
+ data: {
1107
+ type: "array",
1108
+ items: {
1109
+ $ref: "#/components/schemas/_resourceIdentifier"
1110
+ }
1111
+ }
1112
+ }
1113
+ },
1114
+ _toOneRelationshipWithLinks: {
1115
+ type: "object",
1116
+ allOf: [
1117
+ {
1118
+ $ref: "#/components/schemas/_toOneRelationship"
1119
+ },
1120
+ {
1121
+ properties: {
1122
+ links: {
1123
+ $ref: "#/components/schemas/_relationLinks"
1124
+ }
1125
+ }
1126
+ }
1127
+ ]
1128
+ },
1129
+ _toManyRelationshipWithLinks: {
1130
+ type: "object",
1131
+ allOf: [
1132
+ {
1133
+ $ref: "#/components/schemas/_toManyRelationship"
1134
+ },
1135
+ {
1136
+ properties: {
1137
+ links: {
1138
+ $ref: "#/components/schemas/_pagedRelationLinks"
1139
+ }
1140
+ }
1141
+ }
1142
+ ]
1143
+ },
1144
+ _toManyRelationshipRequest: {
1145
+ type: "object",
1146
+ properties: {
1147
+ data: {
1148
+ type: "array",
1149
+ items: {
1150
+ $ref: "#/components/schemas/_resourceIdentifier"
1151
+ }
1152
+ }
1153
+ },
1154
+ required: [
1155
+ "data"
1156
+ ]
1157
+ },
1158
+ _toOneRelationshipRequest: {
1159
+ type: "object",
1160
+ properties: {
1161
+ data: {
1162
+ oneOf: [
1163
+ {
1164
+ $ref: "#/components/schemas/_resourceIdentifier"
1165
+ },
1166
+ {
1167
+ type: "null"
1168
+ }
1169
+ ]
1170
+ }
1171
+ },
1172
+ required: [
1173
+ "data"
1174
+ ]
1175
+ },
1176
+ _toManyRelationshipResponse: {
1177
+ type: "object",
1178
+ properties: {
1179
+ data: {
1180
+ type: "array",
1181
+ items: {
1182
+ $ref: "#/components/schemas/_resourceIdentifier"
1183
+ }
1184
+ },
1185
+ links: {
1186
+ $ref: "#/components/schemas/_pagedRelationLinks"
1187
+ },
1188
+ meta: {
1189
+ $ref: "#/components/schemas/_meta"
1190
+ }
1191
+ }
1192
+ },
1193
+ _toOneRelationshipResponse: {
1194
+ type: "object",
1195
+ properties: {
1196
+ data: {
1197
+ oneOf: [
1198
+ {
1199
+ $ref: "#/components/schemas/_resourceIdentifier"
1200
+ },
1201
+ {
1202
+ type: "null"
1203
+ }
1204
+ ]
1205
+ },
1206
+ links: {
1207
+ $ref: "#/components/schemas/_relationLinks"
1208
+ },
1209
+ meta: {
1210
+ $ref: "#/components/schemas/_meta"
1211
+ }
1212
+ }
1213
+ }
1214
+ };
1215
+ }
1216
+ buildEnumSchema(enumDef) {
1217
+ return {
1218
+ type: "string",
1219
+ enum: Object.values(enumDef.values)
1220
+ };
1221
+ }
1222
+ buildTypeDefSchema(typeDef) {
1223
+ const properties = {};
1224
+ const required = [];
1225
+ for (const [fieldName, fieldDef] of Object.entries(typeDef.fields)) {
1226
+ properties[fieldName] = this.fieldToSchema(fieldDef);
1227
+ if (!fieldDef.optional && !fieldDef.array) {
1228
+ required.push(fieldName);
1229
+ }
1230
+ }
1231
+ const result = {
1232
+ type: "object",
1233
+ properties
1234
+ };
1235
+ if (required.length > 0) {
1236
+ result.required = required;
1237
+ }
1238
+ return result;
1239
+ }
1240
+ buildModelReadSchema(modelName, modelDef) {
1241
+ const attrProperties = {};
1242
+ const attrRequired = [];
1243
+ const relProperties = {};
1244
+ for (const [fieldName, fieldDef] of Object.entries(modelDef.fields)) {
1245
+ if (fieldDef.omit) continue;
1246
+ if (isFieldOmitted(modelName, fieldName, this.queryOptions)) continue;
1247
+ if (fieldDef.relation && !isModelIncluded(fieldDef.type, this.queryOptions)) continue;
1248
+ if (fieldDef.relation) {
1249
+ const relRef = fieldDef.array ? {
1250
+ $ref: "#/components/schemas/_toManyRelationshipWithLinks"
1251
+ } : {
1252
+ $ref: "#/components/schemas/_toOneRelationshipWithLinks"
1253
+ };
1254
+ relProperties[fieldName] = fieldDef.optional ? {
1255
+ oneOf: [
1256
+ {
1257
+ type: "null"
1258
+ },
1259
+ relRef
1260
+ ]
1261
+ } : relRef;
1262
+ } else {
1263
+ const schema = this.fieldToSchema(fieldDef);
1264
+ const fieldDescription = getMetaDescription(fieldDef.attributes);
1265
+ if (fieldDescription && !("$ref" in schema)) {
1266
+ schema.description = fieldDescription;
1267
+ }
1268
+ attrProperties[fieldName] = schema;
1269
+ if (!fieldDef.optional && !fieldDef.array) {
1270
+ attrRequired.push(fieldName);
1271
+ }
1272
+ }
1273
+ }
1274
+ const properties = {};
1275
+ if (Object.keys(attrProperties).length > 0) {
1276
+ const attrSchema = {
1277
+ type: "object",
1278
+ properties: attrProperties
1279
+ };
1280
+ if (attrRequired.length > 0) attrSchema.required = attrRequired;
1281
+ properties["attributes"] = attrSchema;
1282
+ }
1283
+ if (Object.keys(relProperties).length > 0) {
1284
+ properties["relationships"] = {
1285
+ type: "object",
1286
+ properties: relProperties
1287
+ };
1288
+ }
1289
+ const result = {
1290
+ type: "object",
1291
+ properties
1292
+ };
1293
+ const description = getMetaDescription(modelDef.attributes);
1294
+ if (description) {
1295
+ result.description = description;
1296
+ }
1297
+ return result;
1298
+ }
1299
+ buildCreateRequestSchema(_modelName, modelDef) {
1300
+ const idFieldNames = new Set(modelDef.idFields);
1301
+ const attributes = {};
1302
+ const attrRequired = [];
1303
+ const relationships = {};
1304
+ for (const [fieldName, fieldDef] of Object.entries(modelDef.fields)) {
1305
+ if (fieldDef.updatedAt) continue;
1306
+ if (fieldDef.foreignKeyFor) continue;
1307
+ if (idFieldNames.has(fieldName) && fieldDef.default !== void 0) continue;
1308
+ if (fieldDef.relation && !isModelIncluded(fieldDef.type, this.queryOptions)) continue;
1309
+ if (fieldDef.relation) {
1310
+ relationships[fieldName] = fieldDef.array ? {
1311
+ type: "object",
1312
+ properties: {
1313
+ data: {
1314
+ type: "array",
1315
+ items: {
1316
+ $ref: "#/components/schemas/_resourceIdentifier"
1317
+ }
1318
+ }
1319
+ }
1320
+ } : {
1321
+ type: "object",
1322
+ properties: {
1323
+ data: {
1324
+ $ref: "#/components/schemas/_resourceIdentifier"
1325
+ }
1326
+ }
1327
+ };
1328
+ } else {
1329
+ attributes[fieldName] = this.fieldToSchema(fieldDef);
1330
+ if (!fieldDef.optional && fieldDef.default === void 0 && !fieldDef.array) {
1331
+ attrRequired.push(fieldName);
1332
+ }
1333
+ }
1334
+ }
1335
+ const dataProperties = {
1336
+ type: {
1337
+ type: "string"
1338
+ }
1339
+ };
1340
+ if (Object.keys(attributes).length > 0) {
1341
+ const attrSchema = {
1342
+ type: "object",
1343
+ properties: attributes
1344
+ };
1345
+ if (attrRequired.length > 0) attrSchema.required = attrRequired;
1346
+ dataProperties["attributes"] = attrSchema;
1347
+ }
1348
+ if (Object.keys(relationships).length > 0) {
1349
+ dataProperties["relationships"] = {
1350
+ type: "object",
1351
+ properties: relationships
1352
+ };
1353
+ }
1354
+ return {
1355
+ type: "object",
1356
+ properties: {
1357
+ data: {
1358
+ type: "object",
1359
+ properties: dataProperties,
1360
+ required: [
1361
+ "type"
1362
+ ]
1363
+ }
1364
+ },
1365
+ required: [
1366
+ "data"
1367
+ ]
1368
+ };
1369
+ }
1370
+ buildUpdateRequestSchema(modelDef) {
1371
+ const attributes = {};
1372
+ const relationships = {};
1373
+ for (const [fieldName, fieldDef] of Object.entries(modelDef.fields)) {
1374
+ if (fieldDef.updatedAt) continue;
1375
+ if (fieldDef.foreignKeyFor) continue;
1376
+ if (fieldDef.relation && !isModelIncluded(fieldDef.type, this.queryOptions)) continue;
1377
+ if (fieldDef.relation) {
1378
+ relationships[fieldName] = fieldDef.array ? {
1379
+ type: "object",
1380
+ properties: {
1381
+ data: {
1382
+ type: "array",
1383
+ items: {
1384
+ $ref: "#/components/schemas/_resourceIdentifier"
1385
+ }
1386
+ }
1387
+ }
1388
+ } : {
1389
+ type: "object",
1390
+ properties: {
1391
+ data: {
1392
+ $ref: "#/components/schemas/_resourceIdentifier"
1393
+ }
1394
+ }
1395
+ };
1396
+ } else {
1397
+ attributes[fieldName] = this.fieldToSchema(fieldDef);
1398
+ }
1399
+ }
1400
+ const dataProperties = {
1401
+ type: {
1402
+ type: "string"
1403
+ },
1404
+ id: {
1405
+ type: "string"
1406
+ }
1407
+ };
1408
+ if (Object.keys(attributes).length > 0) {
1409
+ dataProperties["attributes"] = {
1410
+ type: "object",
1411
+ properties: attributes
1412
+ };
1413
+ }
1414
+ if (Object.keys(relationships).length > 0) {
1415
+ dataProperties["relationships"] = {
1416
+ type: "object",
1417
+ properties: relationships
1418
+ };
1419
+ }
1420
+ return {
1421
+ type: "object",
1422
+ properties: {
1423
+ data: {
1424
+ type: "object",
1425
+ properties: dataProperties,
1426
+ required: [
1427
+ "type",
1428
+ "id"
1429
+ ]
1430
+ }
1431
+ },
1432
+ required: [
1433
+ "data"
1434
+ ]
1435
+ };
1436
+ }
1437
+ buildModelResponseSchema(modelName) {
1438
+ return {
1439
+ type: "object",
1440
+ properties: {
1441
+ jsonapi: {
1442
+ $ref: "#/components/schemas/_jsonapi"
1443
+ },
1444
+ data: {
1445
+ allOf: [
1446
+ {
1447
+ $ref: `#/components/schemas/${modelName}`
1448
+ },
1449
+ {
1450
+ $ref: "#/components/schemas/_resource"
1451
+ }
1452
+ ]
1453
+ },
1454
+ meta: {
1455
+ $ref: "#/components/schemas/_meta"
1456
+ }
1457
+ }
1458
+ };
1459
+ }
1460
+ buildModelListResponseSchema(modelName) {
1461
+ return {
1462
+ type: "object",
1463
+ properties: {
1464
+ jsonapi: {
1465
+ $ref: "#/components/schemas/_jsonapi"
1466
+ },
1467
+ data: {
1468
+ type: "array",
1469
+ items: {
1470
+ allOf: [
1471
+ {
1472
+ $ref: `#/components/schemas/${modelName}`
1473
+ },
1474
+ {
1475
+ $ref: "#/components/schemas/_resource"
1476
+ }
1477
+ ]
1478
+ }
1479
+ },
1480
+ links: {
1481
+ allOf: [
1482
+ {
1483
+ $ref: "#/components/schemas/_pagination"
1484
+ },
1485
+ {
1486
+ $ref: "#/components/schemas/_links"
1487
+ }
1488
+ ]
1489
+ },
1490
+ meta: {
1491
+ $ref: "#/components/schemas/_meta"
1492
+ }
1493
+ }
1494
+ };
1495
+ }
1496
+ generateSharedParams() {
1497
+ return {
1498
+ id: {
1499
+ name: "id",
1500
+ in: "path",
1501
+ required: true,
1502
+ schema: {
1503
+ type: "string"
1504
+ },
1505
+ description: "Resource ID"
1506
+ },
1507
+ include: {
1508
+ name: "include",
1509
+ in: "query",
1510
+ schema: {
1511
+ type: "string"
1512
+ },
1513
+ description: "Comma-separated list of relationships to include"
1514
+ },
1515
+ sort: {
1516
+ name: "sort",
1517
+ in: "query",
1518
+ schema: {
1519
+ type: "string"
1520
+ },
1521
+ description: "Comma-separated list of fields to sort by. Prefix with - for descending"
1522
+ },
1523
+ pageOffset: {
1524
+ name: "page[offset]",
1525
+ in: "query",
1526
+ schema: {
1527
+ type: "integer",
1528
+ minimum: 0
1529
+ },
1530
+ description: "Page offset"
1531
+ },
1532
+ pageLimit: {
1533
+ name: "page[limit]",
1534
+ in: "query",
1535
+ schema: {
1536
+ type: "integer",
1537
+ minimum: 1
1538
+ },
1539
+ description: "Page limit"
1540
+ }
1541
+ };
1542
+ }
1543
+ fieldToSchema(fieldDef) {
1544
+ const baseSchema = this.typeToSchema(fieldDef.type);
1545
+ if (fieldDef.array) {
1546
+ return {
1547
+ type: "array",
1548
+ items: baseSchema
1549
+ };
1550
+ }
1551
+ if (fieldDef.optional) {
1552
+ return {
1553
+ oneOf: [
1554
+ baseSchema,
1555
+ {
1556
+ type: "null"
1557
+ }
1558
+ ]
1559
+ };
1560
+ }
1561
+ return baseSchema;
1562
+ }
1563
+ typeToSchema(type) {
1564
+ switch (type) {
1565
+ case "String":
1566
+ return {
1567
+ type: "string"
1568
+ };
1569
+ case "Int":
1570
+ case "BigInt":
1571
+ return {
1572
+ type: "integer"
1573
+ };
1574
+ case "Float":
1575
+ return {
1576
+ type: "number"
1577
+ };
1578
+ case "Decimal":
1579
+ return {
1580
+ oneOf: [
1581
+ {
1582
+ type: "number"
1583
+ },
1584
+ {
1585
+ type: "string"
1586
+ }
1587
+ ]
1588
+ };
1589
+ case "Boolean":
1590
+ return {
1591
+ type: "boolean"
1592
+ };
1593
+ case "DateTime":
1594
+ return {
1595
+ type: "string",
1596
+ format: "date-time"
1597
+ };
1598
+ case "Bytes":
1599
+ return {
1600
+ type: "string",
1601
+ format: "byte"
1602
+ };
1603
+ case "Json":
1604
+ case "Unsupported":
1605
+ return {};
1606
+ default:
1607
+ return {
1608
+ $ref: `#/components/schemas/${type}`
1609
+ };
1610
+ }
205
1611
  }
206
- }
207
- __name(log, "log");
208
- function registerCustomSerializers() {
209
- import_superjson2.default.registerCustom({
210
- isApplicable: /* @__PURE__ */ __name((v) => import_decimal.Decimal.isDecimal(v), "isApplicable"),
211
- serialize: /* @__PURE__ */ __name((v) => v.toJSON(), "serialize"),
212
- deserialize: /* @__PURE__ */ __name((v) => new import_decimal.Decimal(v), "deserialize")
213
- }, "Decimal");
214
- if (globalThis.Buffer) {
215
- import_superjson2.default.registerCustom({
216
- isApplicable: /* @__PURE__ */ __name((v) => Buffer.isBuffer(v), "isApplicable"),
217
- serialize: /* @__PURE__ */ __name((v) => v.toString("base64"), "serialize"),
218
- deserialize: /* @__PURE__ */ __name((v) => Buffer.from(v, "base64"), "deserialize")
219
- }, "Bytes");
1612
+ getIdFields(modelDef) {
1613
+ return modelDef.idFields.map((name) => modelDef.fields[name]).filter((f) => f !== void 0);
220
1614
  }
221
- }
222
- __name(registerCustomSerializers, "registerCustomSerializers");
223
- function getZodErrorMessage(error) {
224
- if ("_zod" in error) {
225
- return (0, import_v4.fromError)(error).toString();
226
- } else {
227
- return (0, import_v3.fromError)(error).toString();
1615
+ /**
1616
+ * Checks if an operation on a model may be denied by access policies.
1617
+ * Returns true when `respectAccessPolicies` is enabled and the model's
1618
+ * policies for the given operation are NOT a constant allow (i.e., not
1619
+ * simply `@@allow('...', true)` with no `@@deny` rules).
1620
+ */
1621
+ mayDenyAccess(modelDef, operation) {
1622
+ if (!this.specOptions?.respectAccessPolicies) return false;
1623
+ const policyAttrs = (modelDef.attributes ?? []).filter((attr) => attr.name === "@@allow" || attr.name === "@@deny");
1624
+ if (policyAttrs.length === 0) return true;
1625
+ const getArgByName = /* @__PURE__ */ __name((args, name) => args?.find((a) => a.name === name)?.value, "getArgByName");
1626
+ const matchesOperation = /* @__PURE__ */ __name((args) => {
1627
+ const val = getArgByName(args, "operation");
1628
+ if (!val || val.kind !== "literal" || typeof val.value !== "string") return false;
1629
+ const ops = val.value.split(",").map((s) => s.trim());
1630
+ return ops.includes(operation) || ops.includes("all");
1631
+ }, "matchesOperation");
1632
+ const hasEffectiveDeny = policyAttrs.some((attr) => {
1633
+ if (attr.name !== "@@deny" || !matchesOperation(attr.args)) return false;
1634
+ const condition = getArgByName(attr.args, "condition");
1635
+ return !(condition?.kind === "literal" && condition.value === false);
1636
+ });
1637
+ if (hasEffectiveDeny) return true;
1638
+ const relevantAllow = policyAttrs.filter((attr) => attr.name === "@@allow" && matchesOperation(attr.args));
1639
+ const hasConstantAllow = relevantAllow.some((attr) => {
1640
+ const condition = getArgByName(attr.args, "condition");
1641
+ return condition?.kind === "literal" && condition.value === true;
1642
+ });
1643
+ return !hasConstantAllow;
228
1644
  }
229
- }
230
- __name(getZodErrorMessage, "getZodErrorMessage");
1645
+ };
231
1646
 
232
1647
  // src/api/rest/index.ts
233
1648
  var InvalidValueError = class InvalidValueError2 extends Error {
@@ -404,6 +1819,7 @@ var RestApiHandler = class {
404
1819
  modelNameMapping;
405
1820
  reverseModelNameMapping;
406
1821
  externalIdMapping;
1822
+ nestedRoutes;
407
1823
  constructor(options) {
408
1824
  this.options = options;
409
1825
  this.validateOptions(options);
@@ -411,7 +1827,7 @@ var RestApiHandler = class {
411
1827
  const segmentCharset = options.urlSegmentCharset ?? "a-zA-Z0-9-_~ %";
412
1828
  this.modelNameMapping = options.modelNameMapping ?? {};
413
1829
  this.modelNameMapping = Object.fromEntries(Object.entries(this.modelNameMapping).map(([k, v]) => [
414
- (0, import_common_helpers.lowerCaseFirst)(k),
1830
+ (0, import_common_helpers3.lowerCaseFirst)(k),
415
1831
  v
416
1832
  ]));
417
1833
  this.reverseModelNameMapping = Object.fromEntries(Object.entries(this.modelNameMapping).map(([k, v]) => [
@@ -420,9 +1836,10 @@ var RestApiHandler = class {
420
1836
  ]));
421
1837
  this.externalIdMapping = options.externalIdMapping ?? {};
422
1838
  this.externalIdMapping = Object.fromEntries(Object.entries(this.externalIdMapping).map(([k, v]) => [
423
- (0, import_common_helpers.lowerCaseFirst)(k),
1839
+ (0, import_common_helpers3.lowerCaseFirst)(k),
424
1840
  v
425
1841
  ]));
1842
+ this.nestedRoutes = options.nestedRoutes ?? false;
426
1843
  this.urlPatternMap = this.buildUrlPatternMap(segmentCharset);
427
1844
  this.buildTypeMap();
428
1845
  this.buildSerializers();
@@ -439,7 +1856,9 @@ var RestApiHandler = class {
439
1856
  idDivider: import_zod2.default.string().min(1).optional(),
440
1857
  urlSegmentCharset: import_zod2.default.string().min(1).optional(),
441
1858
  modelNameMapping: import_zod2.default.record(import_zod2.default.string(), import_zod2.default.string()).optional(),
442
- externalIdMapping: import_zod2.default.record(import_zod2.default.string(), import_zod2.default.string()).optional()
1859
+ externalIdMapping: import_zod2.default.record(import_zod2.default.string(), import_zod2.default.string()).optional(),
1860
+ queryOptions: queryOptionsSchema.optional(),
1861
+ nestedRoutes: import_zod2.default.boolean().optional()
443
1862
  });
444
1863
  const parseResult = schema.safeParse(options);
445
1864
  if (!parseResult.success) {
@@ -464,6 +1883,12 @@ var RestApiHandler = class {
464
1883
  ":type",
465
1884
  ":id"
466
1885
  ]), options),
1886
+ ["nestedSingle"]: new import_url_pattern.default(buildPath([
1887
+ ":type",
1888
+ ":id",
1889
+ ":relationship",
1890
+ ":childId"
1891
+ ]), options),
467
1892
  ["fetchRelationship"]: new import_url_pattern.default(buildPath([
468
1893
  ":type",
469
1894
  ":id",
@@ -483,6 +1908,81 @@ var RestApiHandler = class {
483
1908
  mapModelName(modelName) {
484
1909
  return this.modelNameMapping[modelName] ?? modelName;
485
1910
  }
1911
+ /**
1912
+ * Resolves child model type and reverse relation from a parent relation name.
1913
+ * e.g. given parentType='user', parentRelation='posts', returns { childType:'post', reverseRelation:'author' }
1914
+ */
1915
+ resolveNestedRelation(parentType, parentRelation) {
1916
+ const parentInfo = this.getModelInfo(parentType);
1917
+ if (!parentInfo) return void 0;
1918
+ const field = this.schema.models[parentInfo.name]?.fields[parentRelation];
1919
+ if (!field?.relation) return void 0;
1920
+ const reverseRelation = field.relation.opposite;
1921
+ if (!reverseRelation) return void 0;
1922
+ return {
1923
+ childType: (0, import_common_helpers3.lowerCaseFirst)(field.type),
1924
+ reverseRelation,
1925
+ isCollection: !!field.array
1926
+ };
1927
+ }
1928
+ mergeFilters(left, right) {
1929
+ if (!left) {
1930
+ return right;
1931
+ }
1932
+ if (!right) {
1933
+ return left;
1934
+ }
1935
+ return {
1936
+ AND: [
1937
+ left,
1938
+ right
1939
+ ]
1940
+ };
1941
+ }
1942
+ /**
1943
+ * Builds a WHERE filter for the child model that constrains results to those belonging to the given parent.
1944
+ * @param parentType lowercased parent model name
1945
+ * @param parentId parent resource ID string
1946
+ * @param parentRelation relation field name on the parent model (e.g. 'posts')
1947
+ */
1948
+ buildNestedParentFilter(parentType, parentId, parentRelation) {
1949
+ const parentInfo = this.getModelInfo(parentType);
1950
+ if (!parentInfo) {
1951
+ return {
1952
+ filter: void 0,
1953
+ error: this.makeUnsupportedModelError(parentType)
1954
+ };
1955
+ }
1956
+ const resolved = this.resolveNestedRelation(parentType, parentRelation);
1957
+ if (!resolved) {
1958
+ return {
1959
+ filter: void 0,
1960
+ error: this.makeError("invalidPath", `invalid nested route: cannot resolve relation "${parentType}.${parentRelation}"`)
1961
+ };
1962
+ }
1963
+ const { reverseRelation } = resolved;
1964
+ const childInfo = this.getModelInfo(resolved.childType);
1965
+ if (!childInfo) {
1966
+ return {
1967
+ filter: void 0,
1968
+ error: this.makeUnsupportedModelError(resolved.childType)
1969
+ };
1970
+ }
1971
+ const reverseRelInfo = childInfo.relationships[reverseRelation];
1972
+ const relationFilter = reverseRelInfo?.isCollection ? {
1973
+ [reverseRelation]: {
1974
+ some: this.makeIdFilter(parentInfo.idFields, parentId, false)
1975
+ }
1976
+ } : {
1977
+ [reverseRelation]: {
1978
+ is: this.makeIdFilter(parentInfo.idFields, parentId, false)
1979
+ }
1980
+ };
1981
+ return {
1982
+ filter: relationFilter,
1983
+ error: void 0
1984
+ };
1985
+ }
486
1986
  matchUrlPattern(path, routeType) {
487
1987
  const pattern = this.urlPatternMap[routeType];
488
1988
  if (!pattern) {
@@ -530,6 +2030,10 @@ var RestApiHandler = class {
530
2030
  if (match4) {
531
2031
  return await this.processReadRelationship(client, match4.type, match4.id, match4.relationship, query);
532
2032
  }
2033
+ match4 = this.matchUrlPattern(path, "nestedSingle");
2034
+ if (match4 && this.nestedRoutes && this.resolveNestedRelation(match4.type, match4.relationship)?.isCollection) {
2035
+ return await this.processNestedSingleRead(client, match4.type, match4.id, match4.relationship, match4.childId, query);
2036
+ }
533
2037
  match4 = this.matchUrlPattern(path, "collection");
534
2038
  if (match4) {
535
2039
  return await this.processCollectionRead(client, match4.type, query);
@@ -540,6 +2044,10 @@ var RestApiHandler = class {
540
2044
  if (!requestBody) {
541
2045
  return this.makeError("invalidPayload");
542
2046
  }
2047
+ const nestedMatch = this.matchUrlPattern(path, "fetchRelationship");
2048
+ if (nestedMatch && this.nestedRoutes && this.resolveNestedRelation(nestedMatch.type, nestedMatch.relationship)?.isCollection) {
2049
+ return await this.processNestedCreate(client, nestedMatch.type, nestedMatch.id, nestedMatch.relationship, query, requestBody);
2050
+ }
543
2051
  let match4 = this.matchUrlPattern(path, "collection");
544
2052
  if (match4) {
545
2053
  const body = requestBody;
@@ -562,24 +2070,36 @@ var RestApiHandler = class {
562
2070
  if (!requestBody) {
563
2071
  return this.makeError("invalidPayload");
564
2072
  }
565
- let match4 = this.matchUrlPattern(path, "single");
2073
+ let match4 = this.matchUrlPattern(path, "relationship");
566
2074
  if (match4) {
567
- return await this.processUpdate(client, match4.type, match4.id, query, requestBody);
2075
+ return await this.processRelationshipCRUD(client, "update", match4.type, match4.id, match4.relationship, query, requestBody);
568
2076
  }
569
- match4 = this.matchUrlPattern(path, "relationship");
2077
+ const nestedToOnePatchMatch = this.matchUrlPattern(path, "fetchRelationship");
2078
+ if (nestedToOnePatchMatch && this.nestedRoutes && this.resolveNestedRelation(nestedToOnePatchMatch.type, nestedToOnePatchMatch.relationship) && !this.resolveNestedRelation(nestedToOnePatchMatch.type, nestedToOnePatchMatch.relationship)?.isCollection) {
2079
+ return await this.processNestedUpdate(client, nestedToOnePatchMatch.type, nestedToOnePatchMatch.id, nestedToOnePatchMatch.relationship, void 0, query, requestBody);
2080
+ }
2081
+ const nestedPatchMatch = this.matchUrlPattern(path, "nestedSingle");
2082
+ if (nestedPatchMatch && this.nestedRoutes && this.resolveNestedRelation(nestedPatchMatch.type, nestedPatchMatch.relationship)?.isCollection) {
2083
+ return await this.processNestedUpdate(client, nestedPatchMatch.type, nestedPatchMatch.id, nestedPatchMatch.relationship, nestedPatchMatch.childId, query, requestBody);
2084
+ }
2085
+ match4 = this.matchUrlPattern(path, "single");
570
2086
  if (match4) {
571
- return await this.processRelationshipCRUD(client, "update", match4.type, match4.id, match4.relationship, query, requestBody);
2087
+ return await this.processUpdate(client, match4.type, match4.id, query, requestBody);
572
2088
  }
573
2089
  return this.makeError("invalidPath");
574
2090
  }
575
2091
  case "DELETE": {
576
- let match4 = this.matchUrlPattern(path, "single");
2092
+ let match4 = this.matchUrlPattern(path, "relationship");
577
2093
  if (match4) {
578
- return await this.processDelete(client, match4.type, match4.id);
2094
+ return await this.processRelationshipCRUD(client, "delete", match4.type, match4.id, match4.relationship, query, requestBody);
579
2095
  }
580
- match4 = this.matchUrlPattern(path, "relationship");
2096
+ const nestedDeleteMatch = this.matchUrlPattern(path, "nestedSingle");
2097
+ if (nestedDeleteMatch && this.nestedRoutes && this.resolveNestedRelation(nestedDeleteMatch.type, nestedDeleteMatch.relationship)?.isCollection) {
2098
+ return await this.processNestedDelete(client, nestedDeleteMatch.type, nestedDeleteMatch.id, nestedDeleteMatch.relationship, nestedDeleteMatch.childId);
2099
+ }
2100
+ match4 = this.matchUrlPattern(path, "single");
581
2101
  if (match4) {
582
- return await this.processRelationshipCRUD(client, "delete", match4.type, match4.id, match4.relationship, query, requestBody);
2102
+ return await this.processDelete(client, match4.type, match4.id);
583
2103
  }
584
2104
  return this.makeError("invalidPath");
585
2105
  }
@@ -597,8 +2117,9 @@ var RestApiHandler = class {
597
2117
  }
598
2118
  }
599
2119
  handleGenericError(err) {
600
- return this.makeError("unknownError", err instanceof Error ? `${err.message}
601
- ${err.stack}` : "Unknown error");
2120
+ const resp = this.makeError("unknownError", err instanceof Error ? `${err.message}` : "Unknown error");
2121
+ log(this.options.log, "debug", () => `sending error response: ${(0, import_common_helpers3.safeJSONStringify)(resp)}${err instanceof Error ? "\n" + err.stack : ""}`);
2122
+ return resp;
602
2123
  }
603
2124
  async processProcedureRequest({ client, method, proc, query, requestBody }) {
604
2125
  if (!proc) {
@@ -656,29 +2177,32 @@ ${err.stack}` : "Unknown error");
656
2177
  }
657
2178
  makeProcBadInputErrorResponse(message) {
658
2179
  const resp = this.makeError("invalidPayload", message, 400);
659
- log(this.log, "debug", () => `sending error response: ${JSON.stringify(resp)}`);
2180
+ log(this.log, "debug", () => `sending error response: ${(0, import_common_helpers3.safeJSONStringify)(resp)}`);
660
2181
  return resp;
661
2182
  }
662
2183
  makeProcGenericErrorResponse(err) {
663
2184
  const message = err instanceof Error ? err.message : "unknown error";
664
2185
  const resp = this.makeError("unknownError", message, 500);
665
- log(this.log, "debug", () => `sending error response: ${JSON.stringify(resp)}`);
2186
+ log(this.log, "debug", () => `sending error response: ${(0, import_common_helpers3.safeJSONStringify)(resp)}${err instanceof Error ? "\n" + err.stack : ""}`);
666
2187
  return resp;
667
2188
  }
668
- async processSingleRead(client, type, resourceId, query) {
669
- const typeInfo = this.getModelInfo(type);
670
- if (!typeInfo) {
671
- return this.makeUnsupportedModelError(type);
672
- }
673
- const args = {
674
- where: this.makeIdFilter(typeInfo.idFields, resourceId)
675
- };
2189
+ /**
2190
+ * Builds the ORM `args` object (include, select) shared by single-read operations.
2191
+ * Returns the args to pass to findUnique/findFirst and the resolved `include` list for serialization,
2192
+ * or an error response if query params are invalid.
2193
+ */
2194
+ buildSingleReadArgs(type, query) {
2195
+ const args = {};
676
2196
  this.includeRelationshipIds(type, args, "include");
677
2197
  let include;
678
2198
  if (query?.["include"]) {
679
2199
  const { select: select2, error: error2, allIncludes } = this.buildRelationSelect(type, query["include"], query);
680
2200
  if (error2) {
681
- return error2;
2201
+ return {
2202
+ args,
2203
+ include,
2204
+ error: error2
2205
+ };
682
2206
  }
683
2207
  if (select2) {
684
2208
  args.include = {
@@ -689,7 +2213,11 @@ ${err.stack}` : "Unknown error");
689
2213
  include = allIncludes;
690
2214
  }
691
2215
  const { select, error } = this.buildPartialSelect(type, query);
692
- if (error) return error;
2216
+ if (error) return {
2217
+ args,
2218
+ include,
2219
+ error
2220
+ };
693
2221
  if (select) {
694
2222
  args.select = {
695
2223
  ...select,
@@ -703,6 +2231,19 @@ ${err.stack}` : "Unknown error");
703
2231
  args.include = void 0;
704
2232
  }
705
2233
  }
2234
+ return {
2235
+ args,
2236
+ include
2237
+ };
2238
+ }
2239
+ async processSingleRead(client, type, resourceId, query) {
2240
+ const typeInfo = this.getModelInfo(type);
2241
+ if (!typeInfo) {
2242
+ return this.makeUnsupportedModelError(type);
2243
+ }
2244
+ const { args, include, error } = this.buildSingleReadArgs(type, query);
2245
+ if (error) return error;
2246
+ args.where = this.makeIdFilter(typeInfo.idFields, resourceId);
706
2247
  const entity = await client[type].findUnique(args);
707
2248
  if (entity) {
708
2249
  return {
@@ -735,7 +2276,7 @@ ${err.stack}` : "Unknown error");
735
2276
  select = relationSelect;
736
2277
  }
737
2278
  if (!select) {
738
- const { select: partialFields, error } = this.buildPartialSelect((0, import_common_helpers.lowerCaseFirst)(relationInfo.type), query);
2279
+ const { select: partialFields, error } = this.buildPartialSelect((0, import_common_helpers3.lowerCaseFirst)(relationInfo.type), query);
739
2280
  if (error) return error;
740
2281
  select = partialFields ? {
741
2282
  [relationship]: {
@@ -887,8 +2428,12 @@ ${err.stack}` : "Unknown error");
887
2428
  }
888
2429
  if (limit === Infinity) {
889
2430
  const entities = await client[type].findMany(args);
2431
+ const mappedType = this.mapModelName(type);
890
2432
  const body = await this.serializeItems(type, entities, {
891
- include
2433
+ include,
2434
+ linkers: {
2435
+ document: new import_ts_japi.default.Linker(() => this.makeLinkUrl(`/${mappedType}`))
2436
+ }
892
2437
  });
893
2438
  const total = entities.length;
894
2439
  body.meta = this.addTotalCountToMeta(body.meta, total);
@@ -910,6 +2455,7 @@ ${err.stack}` : "Unknown error");
910
2455
  const options = {
911
2456
  include,
912
2457
  linkers: {
2458
+ document: new import_ts_japi.default.Linker(() => this.makeLinkUrl(`/${mappedType}`)),
913
2459
  paginator: this.makePaginator(url, offset, limit, total)
914
2460
  }
915
2461
  };
@@ -921,6 +2467,278 @@ ${err.stack}` : "Unknown error");
921
2467
  };
922
2468
  }
923
2469
  }
2470
+ /**
2471
+ * Builds link URL for a nested resource using parent type, parent ID, relation name, and optional child ID.
2472
+ * Uses the parent model name mapping for the parent segment; the relation name is used as-is.
2473
+ */
2474
+ makeNestedLinkUrl(parentType, parentId, parentRelation, childId) {
2475
+ const mappedParentType = this.mapModelName(parentType);
2476
+ const base = `/${mappedParentType}/${parentId}/${parentRelation}`;
2477
+ return childId ? `${base}/${childId}` : base;
2478
+ }
2479
+ async processNestedSingleRead(client, parentType, parentId, parentRelation, childId, query) {
2480
+ const resolved = this.resolveNestedRelation(parentType, parentRelation);
2481
+ if (!resolved) {
2482
+ return this.makeError("invalidPath");
2483
+ }
2484
+ const { filter: nestedFilter, error: nestedError } = this.buildNestedParentFilter(parentType, parentId, parentRelation);
2485
+ if (nestedError) return nestedError;
2486
+ const childType = resolved.childType;
2487
+ const typeInfo = this.getModelInfo(childType);
2488
+ const { args, include, error } = this.buildSingleReadArgs(childType, query);
2489
+ if (error) return error;
2490
+ args.where = this.mergeFilters(this.makeIdFilter(typeInfo.idFields, childId), nestedFilter);
2491
+ const entity = await client[childType].findFirst(args);
2492
+ if (!entity) return this.makeError("notFound");
2493
+ const linkUrl = this.makeLinkUrl(this.makeNestedLinkUrl(parentType, parentId, parentRelation, childId));
2494
+ const nestedLinker = new import_ts_japi.default.Linker(() => linkUrl);
2495
+ return {
2496
+ status: 200,
2497
+ body: await this.serializeItems(childType, entity, {
2498
+ include,
2499
+ linkers: {
2500
+ document: nestedLinker,
2501
+ resource: nestedLinker
2502
+ }
2503
+ })
2504
+ };
2505
+ }
2506
+ async processNestedCreate(client, parentType, parentId, parentRelation, _query, requestBody) {
2507
+ const resolved = this.resolveNestedRelation(parentType, parentRelation);
2508
+ if (!resolved) {
2509
+ return this.makeError("invalidPath");
2510
+ }
2511
+ const parentInfo = this.getModelInfo(parentType);
2512
+ const childType = resolved.childType;
2513
+ const childInfo = this.getModelInfo(childType);
2514
+ const { attributes, relationships, error } = this.processRequestBody(requestBody);
2515
+ if (error) return error;
2516
+ const createData = {
2517
+ ...attributes
2518
+ };
2519
+ if (relationships) {
2520
+ for (const [key, data] of Object.entries(relationships)) {
2521
+ if (!data?.data) {
2522
+ return this.makeError("invalidRelationData");
2523
+ }
2524
+ if (key === resolved.reverseRelation) {
2525
+ return this.makeError("invalidPayload", `Relation "${key}" is controlled by the parent route and cannot be set in the request payload`);
2526
+ }
2527
+ const relationInfo = childInfo.relationships[key];
2528
+ if (!relationInfo) {
2529
+ return this.makeUnsupportedRelationshipError(childType, key, 400);
2530
+ }
2531
+ if (relationInfo.isCollection) {
2532
+ createData[key] = {
2533
+ connect: (0, import_common_helpers3.enumerate)(data.data).map((item) => this.makeIdConnect(relationInfo.idFields, item.id))
2534
+ };
2535
+ } else {
2536
+ if (typeof data.data !== "object") {
2537
+ return this.makeError("invalidRelationData");
2538
+ }
2539
+ createData[key] = {
2540
+ connect: this.makeIdConnect(relationInfo.idFields, data.data.id)
2541
+ };
2542
+ }
2543
+ }
2544
+ }
2545
+ const parentFkFields = Object.values(childInfo.fields).filter((f) => f.foreignKeyFor?.includes(resolved.reverseRelation));
2546
+ if (parentFkFields.some((f) => Object.prototype.hasOwnProperty.call(createData, f.name))) {
2547
+ return this.makeError("invalidPayload", `Relation "${resolved.reverseRelation}" is controlled by the parent route and cannot be set in the request payload`);
2548
+ }
2549
+ await client[parentType].update({
2550
+ where: this.makeIdFilter(parentInfo.idFields, parentId),
2551
+ data: {
2552
+ [parentRelation]: {
2553
+ create: createData
2554
+ }
2555
+ }
2556
+ });
2557
+ const { filter: nestedFilter, error: filterError } = this.buildNestedParentFilter(parentType, parentId, parentRelation);
2558
+ if (filterError) return filterError;
2559
+ const fetchArgs = {
2560
+ where: nestedFilter
2561
+ };
2562
+ this.includeRelationshipIds(childType, fetchArgs, "include");
2563
+ if (childInfo.idFields[0]) {
2564
+ fetchArgs.orderBy = {
2565
+ [childInfo.idFields[0].name]: "desc"
2566
+ };
2567
+ }
2568
+ const entity = await client[childType].findFirst(fetchArgs);
2569
+ if (!entity) return this.makeError("notFound");
2570
+ const collectionPath = this.makeNestedLinkUrl(parentType, parentId, parentRelation);
2571
+ const resourceLinker = new import_ts_japi.default.Linker((item) => this.makeLinkUrl(`${collectionPath}/${this.getId(childInfo.name, item)}`));
2572
+ return {
2573
+ status: 201,
2574
+ body: await this.serializeItems(childType, entity, {
2575
+ linkers: {
2576
+ document: resourceLinker,
2577
+ resource: resourceLinker
2578
+ }
2579
+ })
2580
+ };
2581
+ }
2582
+ /**
2583
+ * Builds the ORM `data` payload for a nested update, shared by both to-many (childId present)
2584
+ * and to-one (childId absent) variants. Returns either `{ updateData }` or `{ error }`.
2585
+ */
2586
+ buildNestedUpdatePayload(childType, typeInfo, rev, requestBody) {
2587
+ const { attributes, relationships, error } = this.processRequestBody(requestBody);
2588
+ if (error) return {
2589
+ error
2590
+ };
2591
+ const updateData = {
2592
+ ...attributes
2593
+ };
2594
+ if (relationships && Object.prototype.hasOwnProperty.call(relationships, rev)) {
2595
+ return {
2596
+ error: this.makeError("invalidPayload", `Relation "${rev}" cannot be changed via a nested route`)
2597
+ };
2598
+ }
2599
+ const fkFields = Object.values(typeInfo.fields).filter((f) => f.foreignKeyFor?.includes(rev));
2600
+ if (fkFields.some((f) => Object.prototype.hasOwnProperty.call(updateData, f.name))) {
2601
+ return {
2602
+ error: this.makeError("invalidPayload", `Relation "${rev}" cannot be changed via a nested route`)
2603
+ };
2604
+ }
2605
+ if (relationships) {
2606
+ for (const [key, data] of Object.entries(relationships)) {
2607
+ if (!data?.data) {
2608
+ return {
2609
+ error: this.makeError("invalidRelationData")
2610
+ };
2611
+ }
2612
+ const relationInfo = typeInfo.relationships[key];
2613
+ if (!relationInfo) {
2614
+ return {
2615
+ error: this.makeUnsupportedRelationshipError(childType, key, 400)
2616
+ };
2617
+ }
2618
+ if (relationInfo.isCollection) {
2619
+ updateData[key] = {
2620
+ set: (0, import_common_helpers3.enumerate)(data.data).map((item) => ({
2621
+ [this.makeDefaultIdKey(relationInfo.idFields)]: item.id
2622
+ }))
2623
+ };
2624
+ } else {
2625
+ if (typeof data.data !== "object") {
2626
+ return {
2627
+ error: this.makeError("invalidRelationData")
2628
+ };
2629
+ }
2630
+ updateData[key] = {
2631
+ connect: {
2632
+ [this.makeDefaultIdKey(relationInfo.idFields)]: data.data.id
2633
+ }
2634
+ };
2635
+ }
2636
+ }
2637
+ }
2638
+ return {
2639
+ updateData
2640
+ };
2641
+ }
2642
+ /**
2643
+ * Handles PATCH /:type/:id/:relationship/:childId (to-many) and
2644
+ * PATCH /:type/:id/:relationship (to-one, childId undefined).
2645
+ */
2646
+ async processNestedUpdate(client, parentType, parentId, parentRelation, childId, _query, requestBody) {
2647
+ const resolved = this.resolveNestedRelation(parentType, parentRelation);
2648
+ if (!resolved) {
2649
+ return this.makeError("invalidPath");
2650
+ }
2651
+ const parentInfo = this.getModelInfo(parentType);
2652
+ const childType = resolved.childType;
2653
+ const typeInfo = this.getModelInfo(childType);
2654
+ const { updateData, error } = this.buildNestedUpdatePayload(childType, typeInfo, resolved.reverseRelation, requestBody);
2655
+ if (error) return error;
2656
+ if (childId) {
2657
+ await client[parentType].update({
2658
+ where: this.makeIdFilter(parentInfo.idFields, parentId),
2659
+ data: {
2660
+ [parentRelation]: {
2661
+ update: {
2662
+ where: this.makeIdFilter(typeInfo.idFields, childId),
2663
+ data: updateData
2664
+ }
2665
+ }
2666
+ }
2667
+ });
2668
+ const fetchArgs = {
2669
+ where: this.makeIdFilter(typeInfo.idFields, childId)
2670
+ };
2671
+ this.includeRelationshipIds(childType, fetchArgs, "include");
2672
+ const entity = await client[childType].findUnique(fetchArgs);
2673
+ if (!entity) return this.makeError("notFound");
2674
+ const linkUrl = this.makeLinkUrl(this.makeNestedLinkUrl(parentType, parentId, parentRelation, childId));
2675
+ const nestedLinker = new import_ts_japi.default.Linker(() => linkUrl);
2676
+ return {
2677
+ status: 200,
2678
+ body: await this.serializeItems(childType, entity, {
2679
+ linkers: {
2680
+ document: nestedLinker,
2681
+ resource: nestedLinker
2682
+ }
2683
+ })
2684
+ };
2685
+ } else {
2686
+ await client[parentType].update({
2687
+ where: this.makeIdFilter(parentInfo.idFields, parentId),
2688
+ data: {
2689
+ [parentRelation]: {
2690
+ update: updateData
2691
+ }
2692
+ }
2693
+ });
2694
+ const childIncludeArgs = {};
2695
+ this.includeRelationshipIds(childType, childIncludeArgs, "include");
2696
+ const fetchArgs = {
2697
+ where: this.makeIdFilter(parentInfo.idFields, parentId),
2698
+ select: {
2699
+ [parentRelation]: childIncludeArgs.include ? {
2700
+ include: childIncludeArgs.include
2701
+ } : true
2702
+ }
2703
+ };
2704
+ const parent = await client[parentType].findUnique(fetchArgs);
2705
+ const entity = parent?.[parentRelation];
2706
+ if (!entity) return this.makeError("notFound");
2707
+ const linkUrl = this.makeLinkUrl(this.makeNestedLinkUrl(parentType, parentId, parentRelation));
2708
+ const nestedLinker = new import_ts_japi.default.Linker(() => linkUrl);
2709
+ return {
2710
+ status: 200,
2711
+ body: await this.serializeItems(childType, entity, {
2712
+ linkers: {
2713
+ document: nestedLinker,
2714
+ resource: nestedLinker
2715
+ }
2716
+ })
2717
+ };
2718
+ }
2719
+ }
2720
+ async processNestedDelete(client, parentType, parentId, parentRelation, childId) {
2721
+ const resolved = this.resolveNestedRelation(parentType, parentRelation);
2722
+ if (!resolved) {
2723
+ return this.makeError("invalidPath");
2724
+ }
2725
+ const parentInfo = this.getModelInfo(parentType);
2726
+ const typeInfo = this.getModelInfo(resolved.childType);
2727
+ await client[parentType].update({
2728
+ where: this.makeIdFilter(parentInfo.idFields, parentId),
2729
+ data: {
2730
+ [parentRelation]: {
2731
+ delete: this.makeIdFilter(typeInfo.idFields, childId)
2732
+ }
2733
+ }
2734
+ });
2735
+ return {
2736
+ status: 200,
2737
+ body: {
2738
+ meta: {}
2739
+ }
2740
+ };
2741
+ }
924
2742
  buildPartialSelect(type, query) {
925
2743
  const selectFieldsQuery = query?.[`fields[${type}]`];
926
2744
  if (!selectFieldsQuery) {
@@ -1030,7 +2848,7 @@ ${err.stack}` : "Unknown error");
1030
2848
  }
1031
2849
  if (relationInfo.isCollection) {
1032
2850
  createPayload.data[key] = {
1033
- connect: (0, import_common_helpers.enumerate)(data.data).map((item) => this.makeIdConnect(relationInfo.idFields, item.id))
2851
+ connect: (0, import_common_helpers3.enumerate)(data.data).map((item) => this.makeIdConnect(relationInfo.idFields, item.id))
1034
2852
  };
1035
2853
  } else {
1036
2854
  if (typeof data.data !== "object") {
@@ -1096,10 +2914,10 @@ ${err.stack}` : "Unknown error");
1096
2914
  }
1097
2915
  if (relationInfo.isCollection) {
1098
2916
  upsertPayload.create[key] = {
1099
- connect: (0, import_common_helpers.enumerate)(data.data).map((item) => this.makeIdConnect(relationInfo.idFields, item.id))
2917
+ connect: (0, import_common_helpers3.enumerate)(data.data).map((item) => this.makeIdConnect(relationInfo.idFields, item.id))
1100
2918
  };
1101
2919
  upsertPayload.update[key] = {
1102
- set: (0, import_common_helpers.enumerate)(data.data).map((item) => this.makeIdConnect(relationInfo.idFields, item.id))
2920
+ set: (0, import_common_helpers3.enumerate)(data.data).map((item) => this.makeIdConnect(relationInfo.idFields, item.id))
1103
2921
  };
1104
2922
  } else {
1105
2923
  if (typeof data.data !== "object") {
@@ -1180,7 +2998,7 @@ ${err.stack}` : "Unknown error");
1180
2998
  const relationVerb = mode === "create" ? "connect" : mode === "delete" ? "disconnect" : "set";
1181
2999
  updateArgs.data = {
1182
3000
  [relationship]: {
1183
- [relationVerb]: (0, import_common_helpers.enumerate)(parsed.data.data).map((item) => this.makeIdFilter(relationInfo.idFields, item.id))
3001
+ [relationVerb]: (0, import_common_helpers3.enumerate)(parsed.data.data).map((item) => this.makeIdFilter(relationInfo.idFields, item.id))
1184
3002
  }
1185
3003
  };
1186
3004
  }
@@ -1223,7 +3041,7 @@ ${err.stack}` : "Unknown error");
1223
3041
  }
1224
3042
  if (relationInfo.isCollection) {
1225
3043
  updatePayload.data[key] = {
1226
- set: (0, import_common_helpers.enumerate)(data.data).map((item) => ({
3044
+ set: (0, import_common_helpers3.enumerate)(data.data).map((item) => ({
1227
3045
  [this.makeDefaultIdKey(relationInfo.idFields)]: item.id
1228
3046
  }))
1229
3047
  };
@@ -1279,7 +3097,7 @@ ${err.stack}` : "Unknown error");
1279
3097
  }
1280
3098
  getIdFields(model) {
1281
3099
  const modelDef = this.requireModel(model);
1282
- const modelLower = (0, import_common_helpers.lowerCaseFirst)(model);
3100
+ const modelLower = (0, import_common_helpers3.lowerCaseFirst)(model);
1283
3101
  if (!(modelLower in this.externalIdMapping)) {
1284
3102
  return Object.values(modelDef.fields).filter((f) => modelDef.idFields.includes(f.name));
1285
3103
  }
@@ -1313,7 +3131,7 @@ ${err.stack}` : "Unknown error");
1313
3131
  log(this.options.log, "warn", `Not including model ${model} in the API because it has no ID field`);
1314
3132
  continue;
1315
3133
  }
1316
- const modelInfo = this.typeMap[(0, import_common_helpers.lowerCaseFirst)(model)] = {
3134
+ const modelInfo = this.typeMap[(0, import_common_helpers3.lowerCaseFirst)(model)] = {
1317
3135
  name: model,
1318
3136
  idFields,
1319
3137
  relationships: {},
@@ -1338,7 +3156,7 @@ ${err.stack}` : "Unknown error");
1338
3156
  }
1339
3157
  }
1340
3158
  getModelInfo(model) {
1341
- return this.typeMap[(0, import_common_helpers.lowerCaseFirst)(model)];
3159
+ return this.typeMap[(0, import_common_helpers3.lowerCaseFirst)(model)];
1342
3160
  }
1343
3161
  makeLinkUrl(path) {
1344
3162
  return `${this.options.endpoint}${path}`;
@@ -1347,7 +3165,7 @@ ${err.stack}` : "Unknown error");
1347
3165
  const linkers = {};
1348
3166
  for (const model of Object.keys(this.schema.models)) {
1349
3167
  const ids = this.getIdFields(model);
1350
- const modelLower = (0, import_common_helpers.lowerCaseFirst)(model);
3168
+ const modelLower = (0, import_common_helpers3.lowerCaseFirst)(model);
1351
3169
  const mappedModel = this.mapModelName(modelLower);
1352
3170
  if (ids.length < 1) {
1353
3171
  continue;
@@ -1376,7 +3194,7 @@ ${err.stack}` : "Unknown error");
1376
3194
  this.serializers.set(modelLower, serializer);
1377
3195
  }
1378
3196
  for (const model of Object.keys(this.schema.models)) {
1379
- const modelLower = (0, import_common_helpers.lowerCaseFirst)(model);
3197
+ const modelLower = (0, import_common_helpers3.lowerCaseFirst)(model);
1380
3198
  const serializer = this.serializers.get(modelLower);
1381
3199
  if (!serializer) {
1382
3200
  continue;
@@ -1387,7 +3205,7 @@ ${err.stack}` : "Unknown error");
1387
3205
  if (!fieldDef.relation) {
1388
3206
  continue;
1389
3207
  }
1390
- const fieldSerializer = this.serializers.get((0, import_common_helpers.lowerCaseFirst)(fieldDef.type));
3208
+ const fieldSerializer = this.serializers.get((0, import_common_helpers3.lowerCaseFirst)(fieldDef.type));
1391
3209
  if (!fieldSerializer) {
1392
3210
  continue;
1393
3211
  }
@@ -1421,12 +3239,12 @@ ${err.stack}` : "Unknown error");
1421
3239
  }
1422
3240
  }
1423
3241
  async serializeItems(model, items, options) {
1424
- model = (0, import_common_helpers.lowerCaseFirst)(model);
3242
+ model = (0, import_common_helpers3.lowerCaseFirst)(model);
1425
3243
  const serializer = this.serializers.get(model);
1426
3244
  if (!serializer) {
1427
3245
  throw new Error(`serializer not found for model ${model}`);
1428
3246
  }
1429
- const itemsWithId = (0, import_common_helpers.clone)(items);
3247
+ const itemsWithId = (0, import_common_helpers3.clone)(items);
1430
3248
  this.injectCompoundId(model, itemsWithId);
1431
3249
  const serialized = await serializer.serialize(itemsWithId, options);
1432
3250
  const plainResult = this.toPlainObject(serialized);
@@ -1445,7 +3263,7 @@ ${err.stack}` : "Unknown error");
1445
3263
  if (!typeInfo) {
1446
3264
  return;
1447
3265
  }
1448
- (0, import_common_helpers.enumerate)(items).forEach((item) => {
3266
+ (0, import_common_helpers3.enumerate)(items).forEach((item) => {
1449
3267
  if (!item) {
1450
3268
  return;
1451
3269
  }
@@ -1620,7 +3438,7 @@ ${err.stack}` : "Unknown error");
1620
3438
  const url = new URL(this.makeLinkUrl(path));
1621
3439
  for (const [key, value] of Object.entries(query ?? {})) {
1622
3440
  if (key.startsWith("filter[") || key.startsWith("sort[") || key === "include" || key.startsWith("include[") || key.startsWith("fields[")) {
1623
- for (const v of (0, import_common_helpers.enumerate)(value)) {
3441
+ for (const v of (0, import_common_helpers3.enumerate)(value)) {
1624
3442
  url.searchParams.append(key, v);
1625
3443
  }
1626
3444
  }
@@ -1692,7 +3510,7 @@ ${err.stack}` : "Unknown error");
1692
3510
  const item = {};
1693
3511
  let curr = item;
1694
3512
  let currType = typeInfo;
1695
- for (const filterValue of (0, import_common_helpers.enumerate)(value)) {
3513
+ for (const filterValue of (0, import_common_helpers3.enumerate)(value)) {
1696
3514
  for (let i = 0; i < filterKeys.length; i++) {
1697
3515
  let filterKey = filterKeys[i];
1698
3516
  let filterOp;
@@ -1771,7 +3589,7 @@ ${err.stack}` : "Unknown error");
1771
3589
  };
1772
3590
  }
1773
3591
  const result = [];
1774
- for (const sortSpec of (0, import_common_helpers.enumerate)(query["sort"])) {
3592
+ for (const sortSpec of (0, import_common_helpers3.enumerate)(query["sort"])) {
1775
3593
  const sortFields = sortSpec.split(",").filter((i) => i);
1776
3594
  for (const sortField of sortFields) {
1777
3595
  const dir = sortField.startsWith("-") ? "desc" : "asc";
@@ -1840,7 +3658,7 @@ ${err.stack}` : "Unknown error");
1840
3658
  }
1841
3659
  const result = {};
1842
3660
  const allIncludes = [];
1843
- for (const includeItem of (0, import_common_helpers.enumerate)(include)) {
3661
+ for (const includeItem of (0, import_common_helpers3.enumerate)(include)) {
1844
3662
  const inclusions = includeItem.split(",").filter((i) => i);
1845
3663
  for (const inclusion of inclusions) {
1846
3664
  allIncludes.push(inclusion);
@@ -1863,7 +3681,7 @@ ${err.stack}` : "Unknown error");
1863
3681
  error: this.makeUnsupportedModelError(relationInfo.type)
1864
3682
  };
1865
3683
  }
1866
- const { select, error } = this.buildPartialSelect((0, import_common_helpers.lowerCaseFirst)(relationInfo.type), query);
3684
+ const { select, error } = this.buildPartialSelect((0, import_common_helpers3.lowerCaseFirst)(relationInfo.type), query);
1867
3685
  if (error) return {
1868
3686
  select: void 0,
1869
3687
  error
@@ -2041,7 +3859,7 @@ ${err.stack}` : "Unknown error");
2041
3859
  status = status ?? this.errors[code]?.status ?? 500;
2042
3860
  const error = {
2043
3861
  status,
2044
- code: (0, import_common_helpers.paramCase)(code),
3862
+ code: (0, import_common_helpers3.paramCase)(code),
2045
3863
  title: this.errors[code]?.title
2046
3864
  };
2047
3865
  if (detail) {
@@ -2063,10 +3881,15 @@ ${err.stack}` : "Unknown error");
2063
3881
  makeUnsupportedRelationshipError(model, relationship, status) {
2064
3882
  return this.makeError("unsupportedRelationship", `Relationship ${model}.${relationship} doesn't exist`, status);
2065
3883
  }
3884
+ //#endregion
3885
+ async generateSpec(options) {
3886
+ const generator = new RestApiSpecGenerator(this.options);
3887
+ return generator.generateSpec(options);
3888
+ }
2066
3889
  };
2067
3890
 
2068
3891
  // src/api/rpc/index.ts
2069
- var import_common_helpers2 = require("@zenstackhq/common-helpers");
3892
+ var import_common_helpers4 = require("@zenstackhq/common-helpers");
2070
3893
  var import_orm3 = require("@zenstackhq/orm");
2071
3894
  var import_superjson4 = __toESM(require("superjson"), 1);
2072
3895
  var import_ts_pattern3 = require("ts-pattern");
@@ -2087,7 +3910,8 @@ var RPCApiHandler = class {
2087
3910
  validateOptions(options) {
2088
3911
  const schema = import_zod3.default.strictObject({
2089
3912
  schema: import_zod3.default.object(),
2090
- log: loggerSchema.optional()
3913
+ log: loggerSchema.optional(),
3914
+ queryOptions: queryOptionsSchema.optional()
2091
3915
  });
2092
3916
  const parseResult = schema.safeParse(options);
2093
3917
  if (!parseResult.success) {
@@ -2124,7 +3948,7 @@ var RPCApiHandler = class {
2124
3948
  requestBody
2125
3949
  });
2126
3950
  }
2127
- model = (0, import_common_helpers2.lowerCaseFirst)(model);
3951
+ model = (0, import_common_helpers4.lowerCaseFirst)(model);
2128
3952
  method = method.toUpperCase();
2129
3953
  let args;
2130
3954
  let resCode = 200;
@@ -2191,7 +4015,7 @@ var RPCApiHandler = class {
2191
4015
  if (!this.isValidModel(client, model)) {
2192
4016
  return this.makeBadInputErrorResponse(`unknown model name: ${model}`);
2193
4017
  }
2194
- log(this.options.log, "debug", () => `handling "${model}.${op}" request with args: ${(0, import_common_helpers2.safeJSONStringify)(processedArgs)}`);
4018
+ log(this.options.log, "debug", () => `handling "${model}.${op}" request with args: ${(0, import_common_helpers4.safeJSONStringify)(processedArgs)}`);
2195
4019
  const clientResult = await client[model][op](processedArgs);
2196
4020
  let responseBody = {
2197
4021
  data: clientResult
@@ -2211,7 +4035,7 @@ var RPCApiHandler = class {
2211
4035
  status: resCode,
2212
4036
  body: responseBody
2213
4037
  };
2214
- log(this.options.log, "debug", () => `sending response for "${model}.${op}" request: ${(0, import_common_helpers2.safeJSONStringify)(response)}`);
4038
+ log(this.options.log, "debug", () => `sending response for "${model}.${op}" request: ${(0, import_common_helpers4.safeJSONStringify)(response)}`);
2215
4039
  return response;
2216
4040
  } catch (err) {
2217
4041
  log(this.options.log, "error", `error occurred when handling "${model}.${op}" request`, err);
@@ -2248,7 +4072,7 @@ var RPCApiHandler = class {
2248
4072
  if (!VALID_OPS.has(itemOp)) {
2249
4073
  return this.makeBadInputErrorResponse(`operation at index ${i} has invalid op: ${itemOp}`);
2250
4074
  }
2251
- if (!this.isValidModel(client, (0, import_common_helpers2.lowerCaseFirst)(itemModel))) {
4075
+ if (!this.isValidModel(client, (0, import_common_helpers4.lowerCaseFirst)(itemModel))) {
2252
4076
  return this.makeBadInputErrorResponse(`operation at index ${i} has unknown model: ${itemModel}`);
2253
4077
  }
2254
4078
  if (itemArgs !== void 0 && itemArgs !== null && (typeof itemArgs !== "object" || Array.isArray(itemArgs))) {
@@ -2259,7 +4083,7 @@ var RPCApiHandler = class {
2259
4083
  return this.makeBadInputErrorResponse(`operation at index ${i}: ${argsError}`);
2260
4084
  }
2261
4085
  processedOps.push({
2262
- model: (0, import_common_helpers2.lowerCaseFirst)(itemModel),
4086
+ model: (0, import_common_helpers4.lowerCaseFirst)(itemModel),
2263
4087
  op: itemOp,
2264
4088
  args: processedArgs
2265
4089
  });
@@ -2283,7 +4107,7 @@ var RPCApiHandler = class {
2283
4107
  status: 200,
2284
4108
  body: responseBody
2285
4109
  };
2286
- log(this.options.log, "debug", () => `sending response for "$transaction" request: ${(0, import_common_helpers2.safeJSONStringify)(response)}`);
4110
+ log(this.options.log, "debug", () => `sending response for "$transaction" request: ${(0, import_common_helpers4.safeJSONStringify)(response)}`);
2287
4111
  return response;
2288
4112
  } catch (err) {
2289
4113
  log(this.options.log, "error", `error occurred when handling "$transaction" request`, err);
@@ -2345,7 +4169,7 @@ var RPCApiHandler = class {
2345
4169
  status: 200,
2346
4170
  body: responseBody
2347
4171
  };
2348
- log(this.options.log, "debug", () => `sending response for "$procs.${proc}" request: ${(0, import_common_helpers2.safeJSONStringify)(response)}`);
4172
+ log(this.options.log, "debug", () => `sending response for "$procs.${proc}" request: ${(0, import_common_helpers4.safeJSONStringify)(response)}`);
2349
4173
  return response;
2350
4174
  } catch (err) {
2351
4175
  log(this.options.log, "error", `error occurred when handling "$procs.${proc}" request`, err);
@@ -2356,7 +4180,7 @@ var RPCApiHandler = class {
2356
4180
  }
2357
4181
  }
2358
4182
  isValidModel(client, model) {
2359
- return Object.keys(client.$schema.models).some((m) => (0, import_common_helpers2.lowerCaseFirst)(m) === (0, import_common_helpers2.lowerCaseFirst)(model));
4183
+ return Object.keys(client.$schema.models).some((m) => (0, import_common_helpers4.lowerCaseFirst)(m) === (0, import_common_helpers4.lowerCaseFirst)(model));
2360
4184
  }
2361
4185
  makeBadInputErrorResponse(message) {
2362
4186
  const resp = {
@@ -2367,7 +4191,7 @@ var RPCApiHandler = class {
2367
4191
  }
2368
4192
  }
2369
4193
  };
2370
- log(this.options.log, "debug", () => `sending error response: ${(0, import_common_helpers2.safeJSONStringify)(resp)}`);
4194
+ log(this.options.log, "debug", () => `sending error response: ${(0, import_common_helpers4.safeJSONStringify)(resp)}`);
2371
4195
  return resp;
2372
4196
  }
2373
4197
  makeGenericErrorResponse(err) {
@@ -2379,7 +4203,7 @@ var RPCApiHandler = class {
2379
4203
  }
2380
4204
  }
2381
4205
  };
2382
- log(this.options.log, "debug", () => `sending error response: ${(0, import_common_helpers2.safeJSONStringify)(resp)}${err instanceof Error ? "\n" + err.stack : ""}`);
4206
+ log(this.options.log, "debug", () => `sending error response: ${(0, import_common_helpers4.safeJSONStringify)(resp)}${err instanceof Error ? "\n" + err.stack : ""}`);
2383
4207
  return resp;
2384
4208
  }
2385
4209
  makeORMErrorResponse(err) {
@@ -2411,7 +4235,7 @@ var RPCApiHandler = class {
2411
4235
  error
2412
4236
  }
2413
4237
  };
2414
- log(this.options.log, "debug", () => `sending error response: ${(0, import_common_helpers2.safeJSONStringify)(resp)}`);
4238
+ log(this.options.log, "debug", () => `sending error response: ${(0, import_common_helpers4.safeJSONStringify)(resp)}`);
2415
4239
  return resp;
2416
4240
  }
2417
4241
  async processRequestPayload(args) {