@graphql-tools/executor-http 2.1.2 → 2.1.3-alpha-ca72727f03d744399b898bc79a032554428dcdb9

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/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # @graphql-tools/executor-http
2
2
 
3
+ ## 2.1.3-alpha-ca72727f03d744399b898bc79a032554428dcdb9
4
+ ### Patch Changes
5
+
6
+
7
+
8
+ - [#1419](https://github.com/graphql-hive/gateway/pull/1419) [`ca72727`](https://github.com/graphql-hive/gateway/commit/ca72727f03d744399b898bc79a032554428dcdb9) Thanks [@ardatan](https://github.com/ardatan)! - Inflight request deduplication
9
+
3
10
  ## 2.1.2
4
11
  ### Patch Changes
5
12
 
package/dist/index.cjs CHANGED
@@ -538,6 +538,7 @@ function prepareGETUrl({
538
538
  return finalUrl;
539
539
  }
540
540
 
541
+ const inflightRequests = /* @__PURE__ */ new Map();
541
542
  function buildHTTPExecutor(options) {
542
543
  const printFn = options?.print ?? executorCommon.defaultPrintFn;
543
544
  let disposeCtrl;
@@ -668,157 +669,171 @@ function buildHTTPExecutor(options) {
668
669
  }
669
670
  return promiseHelpers.handleMaybePromise(
670
671
  () => serializeFn(),
671
- (body) => promiseHelpers.handleMaybePromise(
672
- () => {
673
- switch (method) {
674
- case "GET": {
675
- const finalUrl = prepareGETUrl({
676
- baseUrl: endpoint,
677
- body
678
- });
679
- const fetchOptions = {
680
- method: "GET",
681
- headers,
682
- signal: signal$1
683
- };
684
- if (options?.credentials != null) {
685
- fetchOptions.credentials = options.credentials;
686
- }
687
- upstreamErrorExtensions.request.url = finalUrl;
688
- return fetchFn(
689
- finalUrl,
690
- fetchOptions,
691
- request.context,
692
- request.info
693
- );
694
- }
695
- case "POST": {
696
- upstreamErrorExtensions.request.body = body;
697
- return promiseHelpers.handleMaybePromise(
698
- () => createFormDataFromVariables(body, {
699
- File: options?.File,
700
- FormData: options?.FormData
701
- }),
702
- (body2) => {
703
- if (typeof body2 === "string" && !headers["content-type"]) {
704
- upstreamErrorExtensions.request.body = body2;
705
- headers["content-type"] = "application/json";
706
- }
707
- const fetchOptions = {
708
- method: "POST",
709
- body: body2,
710
- headers,
711
- signal: signal$1
712
- };
713
- if (options?.credentials != null) {
714
- fetchOptions.credentials = options.credentials;
715
- }
716
- return fetchFn(
717
- endpoint,
718
- fetchOptions,
719
- request.context,
720
- request.info
721
- );
722
- },
723
- handleError
724
- );
725
- }
726
- }
727
- },
728
- (fetchResult) => promiseHelpers.handleMaybePromise(
672
+ (body) => {
673
+ const inflightRequestId = `${method}:${endpoint}:${JSON.stringify(body)}:${JSON.stringify(headers)}`;
674
+ const inflightRequestRes = inflightRequests.get(inflightRequestId);
675
+ if (inflightRequestRes) {
676
+ return inflightRequestRes;
677
+ }
678
+ const promise = promiseHelpers.handleMaybePromise(
729
679
  () => {
730
- upstreamErrorExtensions.response ||= {};
731
- upstreamErrorExtensions.response.status = fetchResult.status;
732
- upstreamErrorExtensions.response.statusText = fetchResult.statusText;
733
- Object.defineProperty(
734
- upstreamErrorExtensions.response,
735
- "headers",
736
- {
737
- get() {
738
- return Object.fromEntries(fetchResult.headers.entries());
680
+ switch (method) {
681
+ case "GET": {
682
+ const finalUrl = prepareGETUrl({
683
+ baseUrl: endpoint,
684
+ body
685
+ });
686
+ const fetchOptions = {
687
+ method: "GET",
688
+ headers,
689
+ signal: signal$1
690
+ };
691
+ if (options?.credentials != null) {
692
+ fetchOptions.credentials = options.credentials;
739
693
  }
694
+ upstreamErrorExtensions.request.url = finalUrl;
695
+ return fetchFn(
696
+ finalUrl,
697
+ fetchOptions,
698
+ request.context,
699
+ request.info
700
+ );
701
+ }
702
+ case "POST": {
703
+ upstreamErrorExtensions.request.body = body;
704
+ return promiseHelpers.handleMaybePromise(
705
+ () => createFormDataFromVariables(body, {
706
+ File: options?.File,
707
+ FormData: options?.FormData
708
+ }),
709
+ (body2) => {
710
+ if (typeof body2 === "string" && !headers["content-type"]) {
711
+ upstreamErrorExtensions.request.body = body2;
712
+ headers["content-type"] = "application/json";
713
+ }
714
+ const fetchOptions = {
715
+ method: "POST",
716
+ body: body2,
717
+ headers,
718
+ signal: signal$1
719
+ };
720
+ if (options?.credentials != null) {
721
+ fetchOptions.credentials = options.credentials;
722
+ }
723
+ return fetchFn(
724
+ endpoint,
725
+ fetchOptions,
726
+ request.context,
727
+ request.info
728
+ );
729
+ },
730
+ handleError
731
+ );
740
732
  }
741
- );
742
- if (options?.retry != null && !fetchResult.status.toString().startsWith("2")) {
743
- throw new Error(
744
- fetchResult.statusText || `Upstream HTTP Error: ${fetchResult.status}`
745
- );
746
- }
747
- const contentType = fetchResult.headers.get("content-type");
748
- if (contentType?.includes("text/event-stream")) {
749
- return handleEventStreamResponse(
750
- fetchResult,
751
- subscriptionCtrl,
752
- signal$1
753
- );
754
- } else if (contentType?.includes("multipart/mixed")) {
755
- return handleMultipartMixedResponse(fetchResult);
756
733
  }
757
- return fetchResult.text();
758
734
  },
759
- (result) => {
760
- if (typeof result === "string") {
735
+ (fetchResult) => promiseHelpers.handleMaybePromise(
736
+ () => {
761
737
  upstreamErrorExtensions.response ||= {};
762
- upstreamErrorExtensions.response.body = result;
763
- if (result) {
764
- try {
765
- const parsedResult = JSON.parse(result);
766
- upstreamErrorExtensions.response.body = parsedResult;
767
- if (parsedResult.data == null && (parsedResult.errors == null || parsedResult.errors.length === 0)) {
768
- const message = `Unexpected empty "data" and "errors" fields in result: ${result}`;
738
+ upstreamErrorExtensions.response.status = fetchResult.status;
739
+ upstreamErrorExtensions.response.statusText = fetchResult.statusText;
740
+ Object.defineProperty(
741
+ upstreamErrorExtensions.response,
742
+ "headers",
743
+ {
744
+ get() {
745
+ return Object.fromEntries(fetchResult.headers.entries());
746
+ }
747
+ }
748
+ );
749
+ if (options?.retry != null && !fetchResult.status.toString().startsWith("2")) {
750
+ throw new Error(
751
+ fetchResult.statusText || `Upstream HTTP Error: ${fetchResult.status}`
752
+ );
753
+ }
754
+ const contentType = fetchResult.headers.get("content-type");
755
+ if (contentType?.includes("text/event-stream")) {
756
+ return handleEventStreamResponse(
757
+ fetchResult,
758
+ subscriptionCtrl,
759
+ signal$1
760
+ );
761
+ } else if (contentType?.includes("multipart/mixed")) {
762
+ return handleMultipartMixedResponse(fetchResult);
763
+ }
764
+ return fetchResult.text();
765
+ },
766
+ (result) => {
767
+ if (typeof result === "string") {
768
+ upstreamErrorExtensions.response ||= {};
769
+ upstreamErrorExtensions.response.body = result;
770
+ if (result) {
771
+ try {
772
+ const parsedResult = JSON.parse(result);
773
+ upstreamErrorExtensions.response.body = parsedResult;
774
+ if (parsedResult.data == null && (parsedResult.errors == null || parsedResult.errors.length === 0)) {
775
+ const message = `Unexpected empty "data" and "errors" fields in result: ${result}`;
776
+ return {
777
+ errors: [
778
+ utils.createGraphQLError(message, {
779
+ originalError: new Error(message),
780
+ extensions: upstreamErrorExtensions
781
+ })
782
+ ]
783
+ };
784
+ }
785
+ if (Array.isArray(parsedResult.errors)) {
786
+ return {
787
+ ...parsedResult,
788
+ errors: parsedResult.errors.map(
789
+ ({
790
+ message,
791
+ ...options2
792
+ }) => utils.createGraphQLError(message, options2)
793
+ )
794
+ };
795
+ }
796
+ return parsedResult;
797
+ } catch (e) {
769
798
  return {
770
799
  errors: [
771
- utils.createGraphQLError(message, {
772
- originalError: new Error(message),
773
- extensions: upstreamErrorExtensions
774
- })
800
+ utils.createGraphQLError(
801
+ `Unexpected response: ${JSON.stringify(result)}`,
802
+ {
803
+ extensions: upstreamErrorExtensions,
804
+ originalError: e
805
+ }
806
+ )
775
807
  ]
776
808
  };
777
809
  }
778
- if (Array.isArray(parsedResult.errors)) {
779
- return {
780
- ...parsedResult,
781
- errors: parsedResult.errors.map(
782
- ({
783
- message,
784
- ...options2
785
- }) => utils.createGraphQLError(message, options2)
786
- )
787
- };
788
- }
789
- return parsedResult;
790
- } catch (e) {
810
+ } else {
811
+ const message = "No response returned";
791
812
  return {
792
813
  errors: [
793
- utils.createGraphQLError(
794
- `Unexpected response: ${JSON.stringify(result)}`,
795
- {
796
- extensions: upstreamErrorExtensions,
797
- originalError: e
798
- }
799
- )
814
+ utils.createGraphQLError(message, {
815
+ extensions: upstreamErrorExtensions,
816
+ originalError: new Error(message)
817
+ })
800
818
  ]
801
819
  };
802
820
  }
803
821
  } else {
804
- const message = "No response returned";
805
- return {
806
- errors: [
807
- utils.createGraphQLError(message, {
808
- extensions: upstreamErrorExtensions,
809
- originalError: new Error(message)
810
- })
811
- ]
812
- };
822
+ return result;
813
823
  }
814
- } else {
815
- return result;
816
- }
817
- },
824
+ },
825
+ handleError
826
+ ),
818
827
  handleError
819
- ),
820
- handleError
821
- ),
828
+ );
829
+ inflightRequests.set(inflightRequestId, promise);
830
+ promiseHelpers.handleMaybePromise(
831
+ () => promise,
832
+ () => inflightRequests.delete(inflightRequestId),
833
+ () => inflightRequests.delete(inflightRequestId)
834
+ );
835
+ return promise;
836
+ },
822
837
  handleError
823
838
  );
824
839
  };
package/dist/index.js CHANGED
@@ -536,6 +536,7 @@ function prepareGETUrl({
536
536
  return finalUrl;
537
537
  }
538
538
 
539
+ const inflightRequests = /* @__PURE__ */ new Map();
539
540
  function buildHTTPExecutor(options) {
540
541
  const printFn = options?.print ?? defaultPrintFn;
541
542
  let disposeCtrl;
@@ -666,157 +667,171 @@ function buildHTTPExecutor(options) {
666
667
  }
667
668
  return handleMaybePromise(
668
669
  () => serializeFn(),
669
- (body) => handleMaybePromise(
670
- () => {
671
- switch (method) {
672
- case "GET": {
673
- const finalUrl = prepareGETUrl({
674
- baseUrl: endpoint,
675
- body
676
- });
677
- const fetchOptions = {
678
- method: "GET",
679
- headers,
680
- signal
681
- };
682
- if (options?.credentials != null) {
683
- fetchOptions.credentials = options.credentials;
684
- }
685
- upstreamErrorExtensions.request.url = finalUrl;
686
- return fetchFn(
687
- finalUrl,
688
- fetchOptions,
689
- request.context,
690
- request.info
691
- );
692
- }
693
- case "POST": {
694
- upstreamErrorExtensions.request.body = body;
695
- return handleMaybePromise(
696
- () => createFormDataFromVariables(body, {
697
- File: options?.File,
698
- FormData: options?.FormData
699
- }),
700
- (body2) => {
701
- if (typeof body2 === "string" && !headers["content-type"]) {
702
- upstreamErrorExtensions.request.body = body2;
703
- headers["content-type"] = "application/json";
704
- }
705
- const fetchOptions = {
706
- method: "POST",
707
- body: body2,
708
- headers,
709
- signal
710
- };
711
- if (options?.credentials != null) {
712
- fetchOptions.credentials = options.credentials;
713
- }
714
- return fetchFn(
715
- endpoint,
716
- fetchOptions,
717
- request.context,
718
- request.info
719
- );
720
- },
721
- handleError
722
- );
723
- }
724
- }
725
- },
726
- (fetchResult) => handleMaybePromise(
670
+ (body) => {
671
+ const inflightRequestId = `${method}:${endpoint}:${JSON.stringify(body)}:${JSON.stringify(headers)}`;
672
+ const inflightRequestRes = inflightRequests.get(inflightRequestId);
673
+ if (inflightRequestRes) {
674
+ return inflightRequestRes;
675
+ }
676
+ const promise = handleMaybePromise(
727
677
  () => {
728
- upstreamErrorExtensions.response ||= {};
729
- upstreamErrorExtensions.response.status = fetchResult.status;
730
- upstreamErrorExtensions.response.statusText = fetchResult.statusText;
731
- Object.defineProperty(
732
- upstreamErrorExtensions.response,
733
- "headers",
734
- {
735
- get() {
736
- return Object.fromEntries(fetchResult.headers.entries());
678
+ switch (method) {
679
+ case "GET": {
680
+ const finalUrl = prepareGETUrl({
681
+ baseUrl: endpoint,
682
+ body
683
+ });
684
+ const fetchOptions = {
685
+ method: "GET",
686
+ headers,
687
+ signal
688
+ };
689
+ if (options?.credentials != null) {
690
+ fetchOptions.credentials = options.credentials;
737
691
  }
692
+ upstreamErrorExtensions.request.url = finalUrl;
693
+ return fetchFn(
694
+ finalUrl,
695
+ fetchOptions,
696
+ request.context,
697
+ request.info
698
+ );
699
+ }
700
+ case "POST": {
701
+ upstreamErrorExtensions.request.body = body;
702
+ return handleMaybePromise(
703
+ () => createFormDataFromVariables(body, {
704
+ File: options?.File,
705
+ FormData: options?.FormData
706
+ }),
707
+ (body2) => {
708
+ if (typeof body2 === "string" && !headers["content-type"]) {
709
+ upstreamErrorExtensions.request.body = body2;
710
+ headers["content-type"] = "application/json";
711
+ }
712
+ const fetchOptions = {
713
+ method: "POST",
714
+ body: body2,
715
+ headers,
716
+ signal
717
+ };
718
+ if (options?.credentials != null) {
719
+ fetchOptions.credentials = options.credentials;
720
+ }
721
+ return fetchFn(
722
+ endpoint,
723
+ fetchOptions,
724
+ request.context,
725
+ request.info
726
+ );
727
+ },
728
+ handleError
729
+ );
738
730
  }
739
- );
740
- if (options?.retry != null && !fetchResult.status.toString().startsWith("2")) {
741
- throw new Error(
742
- fetchResult.statusText || `Upstream HTTP Error: ${fetchResult.status}`
743
- );
744
- }
745
- const contentType = fetchResult.headers.get("content-type");
746
- if (contentType?.includes("text/event-stream")) {
747
- return handleEventStreamResponse(
748
- fetchResult,
749
- subscriptionCtrl,
750
- signal
751
- );
752
- } else if (contentType?.includes("multipart/mixed")) {
753
- return handleMultipartMixedResponse(fetchResult);
754
731
  }
755
- return fetchResult.text();
756
732
  },
757
- (result) => {
758
- if (typeof result === "string") {
733
+ (fetchResult) => handleMaybePromise(
734
+ () => {
759
735
  upstreamErrorExtensions.response ||= {};
760
- upstreamErrorExtensions.response.body = result;
761
- if (result) {
762
- try {
763
- const parsedResult = JSON.parse(result);
764
- upstreamErrorExtensions.response.body = parsedResult;
765
- if (parsedResult.data == null && (parsedResult.errors == null || parsedResult.errors.length === 0)) {
766
- const message = `Unexpected empty "data" and "errors" fields in result: ${result}`;
736
+ upstreamErrorExtensions.response.status = fetchResult.status;
737
+ upstreamErrorExtensions.response.statusText = fetchResult.statusText;
738
+ Object.defineProperty(
739
+ upstreamErrorExtensions.response,
740
+ "headers",
741
+ {
742
+ get() {
743
+ return Object.fromEntries(fetchResult.headers.entries());
744
+ }
745
+ }
746
+ );
747
+ if (options?.retry != null && !fetchResult.status.toString().startsWith("2")) {
748
+ throw new Error(
749
+ fetchResult.statusText || `Upstream HTTP Error: ${fetchResult.status}`
750
+ );
751
+ }
752
+ const contentType = fetchResult.headers.get("content-type");
753
+ if (contentType?.includes("text/event-stream")) {
754
+ return handleEventStreamResponse(
755
+ fetchResult,
756
+ subscriptionCtrl,
757
+ signal
758
+ );
759
+ } else if (contentType?.includes("multipart/mixed")) {
760
+ return handleMultipartMixedResponse(fetchResult);
761
+ }
762
+ return fetchResult.text();
763
+ },
764
+ (result) => {
765
+ if (typeof result === "string") {
766
+ upstreamErrorExtensions.response ||= {};
767
+ upstreamErrorExtensions.response.body = result;
768
+ if (result) {
769
+ try {
770
+ const parsedResult = JSON.parse(result);
771
+ upstreamErrorExtensions.response.body = parsedResult;
772
+ if (parsedResult.data == null && (parsedResult.errors == null || parsedResult.errors.length === 0)) {
773
+ const message = `Unexpected empty "data" and "errors" fields in result: ${result}`;
774
+ return {
775
+ errors: [
776
+ createGraphQLError(message, {
777
+ originalError: new Error(message),
778
+ extensions: upstreamErrorExtensions
779
+ })
780
+ ]
781
+ };
782
+ }
783
+ if (Array.isArray(parsedResult.errors)) {
784
+ return {
785
+ ...parsedResult,
786
+ errors: parsedResult.errors.map(
787
+ ({
788
+ message,
789
+ ...options2
790
+ }) => createGraphQLError(message, options2)
791
+ )
792
+ };
793
+ }
794
+ return parsedResult;
795
+ } catch (e) {
767
796
  return {
768
797
  errors: [
769
- createGraphQLError(message, {
770
- originalError: new Error(message),
771
- extensions: upstreamErrorExtensions
772
- })
798
+ createGraphQLError(
799
+ `Unexpected response: ${JSON.stringify(result)}`,
800
+ {
801
+ extensions: upstreamErrorExtensions,
802
+ originalError: e
803
+ }
804
+ )
773
805
  ]
774
806
  };
775
807
  }
776
- if (Array.isArray(parsedResult.errors)) {
777
- return {
778
- ...parsedResult,
779
- errors: parsedResult.errors.map(
780
- ({
781
- message,
782
- ...options2
783
- }) => createGraphQLError(message, options2)
784
- )
785
- };
786
- }
787
- return parsedResult;
788
- } catch (e) {
808
+ } else {
809
+ const message = "No response returned";
789
810
  return {
790
811
  errors: [
791
- createGraphQLError(
792
- `Unexpected response: ${JSON.stringify(result)}`,
793
- {
794
- extensions: upstreamErrorExtensions,
795
- originalError: e
796
- }
797
- )
812
+ createGraphQLError(message, {
813
+ extensions: upstreamErrorExtensions,
814
+ originalError: new Error(message)
815
+ })
798
816
  ]
799
817
  };
800
818
  }
801
819
  } else {
802
- const message = "No response returned";
803
- return {
804
- errors: [
805
- createGraphQLError(message, {
806
- extensions: upstreamErrorExtensions,
807
- originalError: new Error(message)
808
- })
809
- ]
810
- };
820
+ return result;
811
821
  }
812
- } else {
813
- return result;
814
- }
815
- },
822
+ },
823
+ handleError
824
+ ),
816
825
  handleError
817
- ),
818
- handleError
819
- ),
826
+ );
827
+ inflightRequests.set(inflightRequestId, promise);
828
+ handleMaybePromise(
829
+ () => promise,
830
+ () => inflightRequests.delete(inflightRequestId),
831
+ () => inflightRequests.delete(inflightRequestId)
832
+ );
833
+ return promise;
834
+ },
820
835
  handleError
821
836
  );
822
837
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@graphql-tools/executor-http",
3
- "version": "2.1.2",
3
+ "version": "2.1.3-alpha-ca72727f03d744399b898bc79a032554428dcdb9",
4
4
  "type": "module",
5
5
  "description": "A set of utils for faster development of GraphQL tools",
6
6
  "repository": {