@zenstackhq/server 3.5.1 → 3.5.3

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
@@ -388,6 +388,9 @@ var RestApiSpecGenerator = class {
388
388
  get queryOptions() {
389
389
  return this.handlerOptions?.queryOptions;
390
390
  }
391
+ get nestedRoutes() {
392
+ return this.handlerOptions.nestedRoutes ?? false;
393
+ }
391
394
  generateSpec(options) {
392
395
  this.specOptions = options;
393
396
  return {
@@ -443,7 +446,13 @@ var RestApiSpecGenerator = class {
443
446
  if (!relModelDef) continue;
444
447
  const relIdFields = this.getIdFields(relModelDef);
445
448
  if (relIdFields.length === 0) continue;
446
- paths[`/${modelPath}/{id}/${fieldName}`] = this.buildFetchRelatedPath(modelName, fieldName, fieldDef, tag);
449
+ paths[`/${modelPath}/{id}/${fieldName}`] = this.buildRelatedPath(modelName, fieldName, fieldDef, tag);
450
+ if (this.nestedRoutes && fieldDef.array) {
451
+ const nestedSinglePath = this.buildNestedSinglePath(modelName, fieldName, fieldDef, relModelDef, tag);
452
+ if (Object.keys(nestedSinglePath).length > 0) {
453
+ paths[`/${modelPath}/{id}/${fieldName}/{childId}`] = nestedSinglePath;
454
+ }
455
+ }
447
456
  paths[`/${modelPath}/{id}/relationships/${fieldName}`] = this.buildRelationshipPath(modelDef, fieldName, fieldDef, tag);
448
457
  }
449
458
  }
@@ -641,8 +650,9 @@ var RestApiSpecGenerator = class {
641
650
  }
642
651
  return result;
643
652
  }
644
- buildFetchRelatedPath(modelName, fieldName, fieldDef, tag) {
653
+ buildRelatedPath(modelName, fieldName, fieldDef, tag) {
645
654
  const isCollection = !!fieldDef.array;
655
+ const relModelDef = this.schema.models[fieldDef.type];
646
656
  const params = [
647
657
  {
648
658
  $ref: "#/components/parameters/id"
@@ -651,8 +661,7 @@ var RestApiSpecGenerator = class {
651
661
  $ref: "#/components/parameters/include"
652
662
  }
653
663
  ];
654
- if (isCollection && this.schema.models[fieldDef.type]) {
655
- const relModelDef = this.schema.models[fieldDef.type];
664
+ if (isCollection && relModelDef) {
656
665
  params.push({
657
666
  $ref: "#/components/parameters/sort"
658
667
  }, {
@@ -661,7 +670,7 @@ var RestApiSpecGenerator = class {
661
670
  $ref: "#/components/parameters/pageLimit"
662
671
  }, ...this.buildFilterParams(fieldDef.type, relModelDef));
663
672
  }
664
- return {
673
+ const pathItem = {
665
674
  get: {
666
675
  tags: [
667
676
  tag
@@ -686,6 +695,201 @@ var RestApiSpecGenerator = class {
686
695
  }
687
696
  }
688
697
  };
698
+ if (this.nestedRoutes && relModelDef) {
699
+ const mayDeny = this.mayDenyAccess(relModelDef, isCollection ? "create" : "update");
700
+ if (isCollection && isOperationIncluded(fieldDef.type, "create", this.queryOptions)) {
701
+ pathItem["post"] = {
702
+ tags: [
703
+ tag
704
+ ],
705
+ summary: `Create a nested ${fieldDef.type} under ${modelName}`,
706
+ operationId: `create${modelName}_${fieldName}`,
707
+ parameters: [
708
+ {
709
+ $ref: "#/components/parameters/id"
710
+ }
711
+ ],
712
+ requestBody: {
713
+ required: true,
714
+ content: {
715
+ "application/vnd.api+json": {
716
+ schema: {
717
+ $ref: `#/components/schemas/${fieldDef.type}CreateRequest`
718
+ }
719
+ }
720
+ }
721
+ },
722
+ responses: {
723
+ "201": {
724
+ description: `Created ${fieldDef.type} resource`,
725
+ content: {
726
+ "application/vnd.api+json": {
727
+ schema: {
728
+ $ref: `#/components/schemas/${fieldDef.type}Response`
729
+ }
730
+ }
731
+ }
732
+ },
733
+ "400": ERROR_400,
734
+ ...mayDeny && {
735
+ "403": ERROR_403
736
+ },
737
+ "422": ERROR_422
738
+ }
739
+ };
740
+ } else if (!isCollection && isOperationIncluded(fieldDef.type, "update", this.queryOptions)) {
741
+ pathItem["patch"] = {
742
+ tags: [
743
+ tag
744
+ ],
745
+ summary: `Update nested ${fieldDef.type} under ${modelName}`,
746
+ operationId: `update${modelName}_${fieldName}`,
747
+ parameters: [
748
+ {
749
+ $ref: "#/components/parameters/id"
750
+ }
751
+ ],
752
+ requestBody: {
753
+ required: true,
754
+ content: {
755
+ "application/vnd.api+json": {
756
+ schema: {
757
+ $ref: `#/components/schemas/${fieldDef.type}UpdateRequest`
758
+ }
759
+ }
760
+ }
761
+ },
762
+ responses: {
763
+ "200": {
764
+ description: `Updated ${fieldDef.type} resource`,
765
+ content: {
766
+ "application/vnd.api+json": {
767
+ schema: {
768
+ $ref: `#/components/schemas/${fieldDef.type}Response`
769
+ }
770
+ }
771
+ }
772
+ },
773
+ "400": ERROR_400,
774
+ ...mayDeny && {
775
+ "403": ERROR_403
776
+ },
777
+ "404": ERROR_404,
778
+ "422": ERROR_422
779
+ }
780
+ };
781
+ }
782
+ }
783
+ return pathItem;
784
+ }
785
+ buildNestedSinglePath(modelName, fieldName, fieldDef, relModelDef, tag) {
786
+ const childIdParam = {
787
+ name: "childId",
788
+ in: "path",
789
+ required: true,
790
+ schema: {
791
+ type: "string"
792
+ }
793
+ };
794
+ const idParam = {
795
+ $ref: "#/components/parameters/id"
796
+ };
797
+ const mayDenyUpdate = this.mayDenyAccess(relModelDef, "update");
798
+ const mayDenyDelete = this.mayDenyAccess(relModelDef, "delete");
799
+ const result = {};
800
+ if (isOperationIncluded(fieldDef.type, "findUnique", this.queryOptions)) {
801
+ result["get"] = {
802
+ tags: [
803
+ tag
804
+ ],
805
+ summary: `Get a nested ${fieldDef.type} by ID under ${modelName}`,
806
+ operationId: `get${modelName}_${fieldName}_single`,
807
+ parameters: [
808
+ idParam,
809
+ childIdParam,
810
+ {
811
+ $ref: "#/components/parameters/include"
812
+ }
813
+ ],
814
+ responses: {
815
+ "200": {
816
+ description: `${fieldDef.type} resource`,
817
+ content: {
818
+ "application/vnd.api+json": {
819
+ schema: {
820
+ $ref: `#/components/schemas/${fieldDef.type}Response`
821
+ }
822
+ }
823
+ }
824
+ },
825
+ "404": ERROR_404
826
+ }
827
+ };
828
+ }
829
+ if (isOperationIncluded(fieldDef.type, "update", this.queryOptions)) {
830
+ result["patch"] = {
831
+ tags: [
832
+ tag
833
+ ],
834
+ summary: `Update a nested ${fieldDef.type} by ID under ${modelName}`,
835
+ operationId: `update${modelName}_${fieldName}_single`,
836
+ parameters: [
837
+ idParam,
838
+ childIdParam
839
+ ],
840
+ requestBody: {
841
+ required: true,
842
+ content: {
843
+ "application/vnd.api+json": {
844
+ schema: {
845
+ $ref: `#/components/schemas/${fieldDef.type}UpdateRequest`
846
+ }
847
+ }
848
+ }
849
+ },
850
+ responses: {
851
+ "200": {
852
+ description: `Updated ${fieldDef.type} resource`,
853
+ content: {
854
+ "application/vnd.api+json": {
855
+ schema: {
856
+ $ref: `#/components/schemas/${fieldDef.type}Response`
857
+ }
858
+ }
859
+ }
860
+ },
861
+ "400": ERROR_400,
862
+ ...mayDenyUpdate && {
863
+ "403": ERROR_403
864
+ },
865
+ "404": ERROR_404,
866
+ "422": ERROR_422
867
+ }
868
+ };
869
+ }
870
+ if (isOperationIncluded(fieldDef.type, "delete", this.queryOptions)) {
871
+ result["delete"] = {
872
+ tags: [
873
+ tag
874
+ ],
875
+ summary: `Delete a nested ${fieldDef.type} by ID under ${modelName}`,
876
+ operationId: `delete${modelName}_${fieldName}_single`,
877
+ parameters: [
878
+ idParam,
879
+ childIdParam
880
+ ],
881
+ responses: {
882
+ "200": {
883
+ description: "Deleted successfully"
884
+ },
885
+ ...mayDenyDelete && {
886
+ "403": ERROR_403
887
+ },
888
+ "404": ERROR_404
889
+ }
890
+ };
891
+ }
892
+ return result;
689
893
  }
690
894
  buildRelationshipPath(modelDef, fieldName, fieldDef, tag) {
691
895
  const modelName = modelDef.name;