@zenstackhq/server 3.5.1 → 3.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/api.js CHANGED
@@ -353,6 +353,9 @@ var RestApiSpecGenerator = class {
353
353
  get queryOptions() {
354
354
  return this.handlerOptions?.queryOptions;
355
355
  }
356
+ get nestedRoutes() {
357
+ return this.handlerOptions.nestedRoutes ?? false;
358
+ }
356
359
  generateSpec(options) {
357
360
  this.specOptions = options;
358
361
  return {
@@ -408,7 +411,13 @@ var RestApiSpecGenerator = class {
408
411
  if (!relModelDef) continue;
409
412
  const relIdFields = this.getIdFields(relModelDef);
410
413
  if (relIdFields.length === 0) continue;
411
- paths[`/${modelPath}/{id}/${fieldName}`] = this.buildFetchRelatedPath(modelName, fieldName, fieldDef, tag);
414
+ paths[`/${modelPath}/{id}/${fieldName}`] = this.buildRelatedPath(modelName, fieldName, fieldDef, tag);
415
+ if (this.nestedRoutes && fieldDef.array) {
416
+ const nestedSinglePath = this.buildNestedSinglePath(modelName, fieldName, fieldDef, relModelDef, tag);
417
+ if (Object.keys(nestedSinglePath).length > 0) {
418
+ paths[`/${modelPath}/{id}/${fieldName}/{childId}`] = nestedSinglePath;
419
+ }
420
+ }
412
421
  paths[`/${modelPath}/{id}/relationships/${fieldName}`] = this.buildRelationshipPath(modelDef, fieldName, fieldDef, tag);
413
422
  }
414
423
  }
@@ -606,8 +615,9 @@ var RestApiSpecGenerator = class {
606
615
  }
607
616
  return result;
608
617
  }
609
- buildFetchRelatedPath(modelName, fieldName, fieldDef, tag) {
618
+ buildRelatedPath(modelName, fieldName, fieldDef, tag) {
610
619
  const isCollection = !!fieldDef.array;
620
+ const relModelDef = this.schema.models[fieldDef.type];
611
621
  const params = [
612
622
  {
613
623
  $ref: "#/components/parameters/id"
@@ -616,8 +626,7 @@ var RestApiSpecGenerator = class {
616
626
  $ref: "#/components/parameters/include"
617
627
  }
618
628
  ];
619
- if (isCollection && this.schema.models[fieldDef.type]) {
620
- const relModelDef = this.schema.models[fieldDef.type];
629
+ if (isCollection && relModelDef) {
621
630
  params.push({
622
631
  $ref: "#/components/parameters/sort"
623
632
  }, {
@@ -626,7 +635,7 @@ var RestApiSpecGenerator = class {
626
635
  $ref: "#/components/parameters/pageLimit"
627
636
  }, ...this.buildFilterParams(fieldDef.type, relModelDef));
628
637
  }
629
- return {
638
+ const pathItem = {
630
639
  get: {
631
640
  tags: [
632
641
  tag
@@ -651,6 +660,201 @@ var RestApiSpecGenerator = class {
651
660
  }
652
661
  }
653
662
  };
663
+ if (this.nestedRoutes && relModelDef) {
664
+ const mayDeny = this.mayDenyAccess(relModelDef, isCollection ? "create" : "update");
665
+ if (isCollection && isOperationIncluded(fieldDef.type, "create", this.queryOptions)) {
666
+ pathItem["post"] = {
667
+ tags: [
668
+ tag
669
+ ],
670
+ summary: `Create a nested ${fieldDef.type} under ${modelName}`,
671
+ operationId: `create${modelName}_${fieldName}`,
672
+ parameters: [
673
+ {
674
+ $ref: "#/components/parameters/id"
675
+ }
676
+ ],
677
+ requestBody: {
678
+ required: true,
679
+ content: {
680
+ "application/vnd.api+json": {
681
+ schema: {
682
+ $ref: `#/components/schemas/${fieldDef.type}CreateRequest`
683
+ }
684
+ }
685
+ }
686
+ },
687
+ responses: {
688
+ "201": {
689
+ description: `Created ${fieldDef.type} resource`,
690
+ content: {
691
+ "application/vnd.api+json": {
692
+ schema: {
693
+ $ref: `#/components/schemas/${fieldDef.type}Response`
694
+ }
695
+ }
696
+ }
697
+ },
698
+ "400": ERROR_400,
699
+ ...mayDeny && {
700
+ "403": ERROR_403
701
+ },
702
+ "422": ERROR_422
703
+ }
704
+ };
705
+ } else if (!isCollection && isOperationIncluded(fieldDef.type, "update", this.queryOptions)) {
706
+ pathItem["patch"] = {
707
+ tags: [
708
+ tag
709
+ ],
710
+ summary: `Update nested ${fieldDef.type} under ${modelName}`,
711
+ operationId: `update${modelName}_${fieldName}`,
712
+ parameters: [
713
+ {
714
+ $ref: "#/components/parameters/id"
715
+ }
716
+ ],
717
+ requestBody: {
718
+ required: true,
719
+ content: {
720
+ "application/vnd.api+json": {
721
+ schema: {
722
+ $ref: `#/components/schemas/${fieldDef.type}UpdateRequest`
723
+ }
724
+ }
725
+ }
726
+ },
727
+ responses: {
728
+ "200": {
729
+ description: `Updated ${fieldDef.type} resource`,
730
+ content: {
731
+ "application/vnd.api+json": {
732
+ schema: {
733
+ $ref: `#/components/schemas/${fieldDef.type}Response`
734
+ }
735
+ }
736
+ }
737
+ },
738
+ "400": ERROR_400,
739
+ ...mayDeny && {
740
+ "403": ERROR_403
741
+ },
742
+ "404": ERROR_404,
743
+ "422": ERROR_422
744
+ }
745
+ };
746
+ }
747
+ }
748
+ return pathItem;
749
+ }
750
+ buildNestedSinglePath(modelName, fieldName, fieldDef, relModelDef, tag) {
751
+ const childIdParam = {
752
+ name: "childId",
753
+ in: "path",
754
+ required: true,
755
+ schema: {
756
+ type: "string"
757
+ }
758
+ };
759
+ const idParam = {
760
+ $ref: "#/components/parameters/id"
761
+ };
762
+ const mayDenyUpdate = this.mayDenyAccess(relModelDef, "update");
763
+ const mayDenyDelete = this.mayDenyAccess(relModelDef, "delete");
764
+ const result = {};
765
+ if (isOperationIncluded(fieldDef.type, "findUnique", this.queryOptions)) {
766
+ result["get"] = {
767
+ tags: [
768
+ tag
769
+ ],
770
+ summary: `Get a nested ${fieldDef.type} by ID under ${modelName}`,
771
+ operationId: `get${modelName}_${fieldName}_single`,
772
+ parameters: [
773
+ idParam,
774
+ childIdParam,
775
+ {
776
+ $ref: "#/components/parameters/include"
777
+ }
778
+ ],
779
+ responses: {
780
+ "200": {
781
+ description: `${fieldDef.type} resource`,
782
+ content: {
783
+ "application/vnd.api+json": {
784
+ schema: {
785
+ $ref: `#/components/schemas/${fieldDef.type}Response`
786
+ }
787
+ }
788
+ }
789
+ },
790
+ "404": ERROR_404
791
+ }
792
+ };
793
+ }
794
+ if (isOperationIncluded(fieldDef.type, "update", this.queryOptions)) {
795
+ result["patch"] = {
796
+ tags: [
797
+ tag
798
+ ],
799
+ summary: `Update a nested ${fieldDef.type} by ID under ${modelName}`,
800
+ operationId: `update${modelName}_${fieldName}_single`,
801
+ parameters: [
802
+ idParam,
803
+ childIdParam
804
+ ],
805
+ requestBody: {
806
+ required: true,
807
+ content: {
808
+ "application/vnd.api+json": {
809
+ schema: {
810
+ $ref: `#/components/schemas/${fieldDef.type}UpdateRequest`
811
+ }
812
+ }
813
+ }
814
+ },
815
+ responses: {
816
+ "200": {
817
+ description: `Updated ${fieldDef.type} resource`,
818
+ content: {
819
+ "application/vnd.api+json": {
820
+ schema: {
821
+ $ref: `#/components/schemas/${fieldDef.type}Response`
822
+ }
823
+ }
824
+ }
825
+ },
826
+ "400": ERROR_400,
827
+ ...mayDenyUpdate && {
828
+ "403": ERROR_403
829
+ },
830
+ "404": ERROR_404,
831
+ "422": ERROR_422
832
+ }
833
+ };
834
+ }
835
+ if (isOperationIncluded(fieldDef.type, "delete", this.queryOptions)) {
836
+ result["delete"] = {
837
+ tags: [
838
+ tag
839
+ ],
840
+ summary: `Delete a nested ${fieldDef.type} by ID under ${modelName}`,
841
+ operationId: `delete${modelName}_${fieldName}_single`,
842
+ parameters: [
843
+ idParam,
844
+ childIdParam
845
+ ],
846
+ responses: {
847
+ "200": {
848
+ description: "Deleted successfully"
849
+ },
850
+ ...mayDenyDelete && {
851
+ "403": ERROR_403
852
+ },
853
+ "404": ERROR_404
854
+ }
855
+ };
856
+ }
857
+ return result;
654
858
  }
655
859
  buildRelationshipPath(modelDef, fieldName, fieldDef, tag) {
656
860
  const modelName = modelDef.name;