@graphql-mesh/transport-http-callback 0.5.5-alpha-20241113094939-c1de428fe2377cb441912e608f02a58b9504d7ae → 0.5.5-alpha-54b3273d0d9033d32f549a6b937921426c35b08b

Sign up to get free protection for your applications and to get access to all the features.
package/dist/index.js ADDED
@@ -0,0 +1,254 @@
1
+ import { process } from '@graphql-mesh/cross-helpers';
2
+ import { getInterpolatedHeadersFactory } from '@graphql-mesh/string-interpolation';
3
+ import { defaultPrintFn } from '@graphql-mesh/transport-common';
4
+ import { makeDisposable, mapMaybePromise } from '@graphql-mesh/utils';
5
+ import { createGraphQLError } from '@graphql-tools/utils';
6
+ import { Repeater } from '@repeaterjs/repeater';
7
+ import { crypto } from '@whatwg-node/fetch';
8
+
9
+ function createTimeoutError() {
10
+ return createGraphQLError("Subscription timed out", {
11
+ extensions: {
12
+ code: "TIMEOUT_ERROR"
13
+ }
14
+ });
15
+ }
16
+ var index = {
17
+ getSubgraphExecutor({
18
+ transportEntry,
19
+ fetch,
20
+ pubsub,
21
+ logger
22
+ }) {
23
+ let headersInConfig;
24
+ if (typeof transportEntry.headers === "string") {
25
+ headersInConfig = JSON.parse(transportEntry.headers);
26
+ }
27
+ if (Array.isArray(transportEntry.headers)) {
28
+ headersInConfig = Object.fromEntries(transportEntry.headers);
29
+ }
30
+ const headersFactory = getInterpolatedHeadersFactory(headersInConfig);
31
+ const verifier = crypto.randomUUID();
32
+ if (!pubsub) {
33
+ throw new Error(`HTTP Callback Transport: You must provide a pubsub instance to http-callbacks transport!
34
+ Example:
35
+ import { PubSub } from '@graphql-hive/gateway'
36
+ export const gatewayConfig = defineConfig({
37
+ pubsub: new PubSub(),
38
+ })
39
+ See documentation: https://graphql-hive.com/docs/gateway/pubsub`);
40
+ }
41
+ const reqAbortCtrls = /* @__PURE__ */ new Set();
42
+ const heartbeats = /* @__PURE__ */ new Map();
43
+ const stopFnSet = /* @__PURE__ */ new Set();
44
+ const publicUrl = transportEntry.options?.public_url || "http://localhost:4000";
45
+ const callbackPath = transportEntry.options?.path || "/callback";
46
+ const heartbeatIntervalMs = transportEntry.options?.heartbeat_interval || 5e4;
47
+ const httpCallbackExecutor = function httpCallbackExecutor2(execReq) {
48
+ const query = defaultPrintFn(execReq.document);
49
+ const subscriptionId = crypto.randomUUID();
50
+ const subscriptionLogger = logger?.child(subscriptionId);
51
+ const callbackUrl = `${publicUrl}${callbackPath}/${subscriptionId}`;
52
+ const subscriptionCallbackPath = `${callbackPath}/${subscriptionId}`;
53
+ const fetchBody = JSON.stringify({
54
+ query,
55
+ variables: execReq.variables,
56
+ operationName: execReq.operationName,
57
+ extensions: {
58
+ ...execReq.extensions || {},
59
+ subscription: {
60
+ callbackUrl,
61
+ subscriptionId,
62
+ verifier,
63
+ heartbeatIntervalMs
64
+ }
65
+ }
66
+ });
67
+ let stopSubscription = (error) => {
68
+ if (error) {
69
+ throw error;
70
+ }
71
+ };
72
+ heartbeats.set(
73
+ subscriptionId,
74
+ setTimeout(() => {
75
+ stopSubscription(createTimeoutError());
76
+ }, heartbeatIntervalMs)
77
+ );
78
+ subscriptionLogger?.debug(
79
+ `Subscribing to ${transportEntry.location} with callbackUrl: ${callbackUrl}`
80
+ );
81
+ let pushFn = () => {
82
+ throw new Error(
83
+ "HTTP Callback Transport: Subgraph does not look like configured correctly. Check your subgraph setup."
84
+ );
85
+ };
86
+ const reqAbortCtrl = new AbortController();
87
+ if (!fetch) {
88
+ throw new Error(
89
+ "HTTP Callback Transport: `fetch` implementation is missing!"
90
+ );
91
+ }
92
+ if (!transportEntry.location) {
93
+ throw new Error(
94
+ `HTTP Callback Transport: \`location\` is missing in the transport entry!`
95
+ );
96
+ }
97
+ const subFetchCall$ = mapMaybePromise(
98
+ fetch(
99
+ transportEntry.location,
100
+ {
101
+ method: "POST",
102
+ headers: {
103
+ "Content-Type": "application/json",
104
+ ...headersFactory({
105
+ env: process.env,
106
+ root: execReq.rootValue,
107
+ context: execReq.context,
108
+ info: execReq.info
109
+ }),
110
+ Accept: "application/json;callbackSpec=1.0; charset=utf-8"
111
+ },
112
+ body: fetchBody,
113
+ signal: reqAbortCtrl.signal
114
+ },
115
+ execReq.context,
116
+ execReq.info
117
+ ),
118
+ (res) => mapMaybePromise(res.text(), (resText) => {
119
+ let resJson;
120
+ try {
121
+ resJson = JSON.parse(resText);
122
+ } catch (e) {
123
+ if (!res.ok) {
124
+ stopSubscription(
125
+ new Error(
126
+ `Subscription request failed with an HTTP Error: ${res.status} ${resText}`
127
+ )
128
+ );
129
+ } else {
130
+ stopSubscription(e);
131
+ }
132
+ return;
133
+ }
134
+ logger?.debug(`Subscription request received`, resJson);
135
+ if (resJson.errors) {
136
+ if (resJson.errors.length === 1 && resJson.errors[0]) {
137
+ const error = resJson.errors[0];
138
+ stopSubscription(createGraphQLError(error.message, error));
139
+ } else {
140
+ stopSubscription(
141
+ new AggregateError(
142
+ resJson.errors.map(
143
+ (err) => createGraphQLError(err.message, err)
144
+ ),
145
+ resJson.errors.map((err) => err.message).join("\n")
146
+ )
147
+ );
148
+ }
149
+ } else if (resJson.data != null) {
150
+ pushFn(resJson.data);
151
+ stopSubscription();
152
+ }
153
+ }),
154
+ (e) => {
155
+ logger?.debug(`Subscription request failed`, e);
156
+ stopSubscription(e);
157
+ }
158
+ );
159
+ execReq.context?.waitUntil?.(subFetchCall$);
160
+ return new Repeater((push, stop) => {
161
+ pushFn = push;
162
+ stopSubscription = stop;
163
+ stopFnSet.add(stop);
164
+ logger?.debug(`Listening to ${subscriptionCallbackPath}`);
165
+ const subId = pubsub.subscribe(
166
+ `webhook:post:${subscriptionCallbackPath}`,
167
+ (message) => {
168
+ logger?.debug(
169
+ `Received message from ${subscriptionCallbackPath}`,
170
+ message
171
+ );
172
+ if (message.verifier !== verifier) {
173
+ return;
174
+ }
175
+ const existingHeartbeat = heartbeats.get(subscriptionId);
176
+ if (existingHeartbeat) {
177
+ clearTimeout(existingHeartbeat);
178
+ }
179
+ heartbeats.set(
180
+ subscriptionId,
181
+ setTimeout(() => {
182
+ stopSubscription(createTimeoutError());
183
+ }, heartbeatIntervalMs)
184
+ );
185
+ switch (message.action) {
186
+ case "check":
187
+ break;
188
+ case "next":
189
+ push(message.payload);
190
+ break;
191
+ case "complete":
192
+ if (message.errors) {
193
+ if (message.errors.length === 1 && message.errors[0]) {
194
+ const error = message.errors[0];
195
+ stopSubscription(
196
+ createGraphQLError(error.message, {
197
+ ...error,
198
+ extensions: {
199
+ ...error.extensions,
200
+ code: "DOWNSTREAM_SERVICE_ERROR"
201
+ }
202
+ })
203
+ );
204
+ } else {
205
+ stopSubscription(
206
+ new AggregateError(
207
+ message.errors.map(
208
+ (err) => createGraphQLError(err.message, {
209
+ ...err,
210
+ extensions: {
211
+ ...err.extensions,
212
+ code: "DOWNSTREAM_SERVICE_ERROR"
213
+ }
214
+ })
215
+ )
216
+ )
217
+ );
218
+ }
219
+ } else {
220
+ stopSubscription();
221
+ }
222
+ break;
223
+ }
224
+ }
225
+ );
226
+ stop.finally(() => {
227
+ pubsub.unsubscribe(subId);
228
+ clearTimeout(heartbeats.get(subscriptionId));
229
+ heartbeats.delete(subscriptionId);
230
+ stopFnSet.delete(stop);
231
+ if (!reqAbortCtrl.signal.aborted) {
232
+ reqAbortCtrl.abort();
233
+ }
234
+ });
235
+ });
236
+ };
237
+ function disposeFn() {
238
+ for (const stop of stopFnSet) {
239
+ stop();
240
+ }
241
+ for (const interval of heartbeats.values()) {
242
+ clearTimeout(interval);
243
+ }
244
+ for (const ctrl of reqAbortCtrls) {
245
+ if (!ctrl.signal.aborted) {
246
+ ctrl.abort();
247
+ }
248
+ }
249
+ }
250
+ return makeDisposable(httpCallbackExecutor, disposeFn);
251
+ }
252
+ };
253
+
254
+ export { index as default };
package/package.json CHANGED
@@ -1,51 +1,61 @@
1
1
  {
2
2
  "name": "@graphql-mesh/transport-http-callback",
3
- "version": "0.5.5-alpha-20241113094939-c1de428fe2377cb441912e608f02a58b9504d7ae",
4
- "sideEffects": false,
5
- "peerDependencies": {
6
- "graphql": "*",
7
- "tslib": "^2.4.0"
8
- },
9
- "dependencies": {
10
- "@graphql-mesh/cross-helpers": "^0.4.7",
11
- "@graphql-mesh/string-interpolation": "^0.5.6",
12
- "@graphql-mesh/transport-common": "0.7.14-alpha-20241113094939-c1de428fe2377cb441912e608f02a58b9504d7ae",
13
- "@graphql-mesh/utils": "0.102.13-alpha-20241113094939-c1de428fe2377cb441912e608f02a58b9504d7ae",
14
- "@graphql-tools/utils": "^10.5.5",
15
- "@repeaterjs/repeater": "^3.0.6",
16
- "@whatwg-node/fetch": "^0.10.0"
17
- },
3
+ "version": "0.5.5-alpha-54b3273d0d9033d32f549a6b937921426c35b08b",
4
+ "type": "module",
18
5
  "repository": {
19
6
  "type": "git",
20
- "url": "ardatan/graphql-mesh",
7
+ "url": "git+https://github.com/graphql-hive/gateway.git",
21
8
  "directory": "packages/transports/http-callback"
22
9
  },
10
+ "author": {
11
+ "email": "contact@the-guild.dev",
12
+ "name": "The Guild",
13
+ "url": "https://the-guild.dev"
14
+ },
23
15
  "license": "MIT",
24
16
  "engines": {
25
- "node": ">=16.0.0"
26
- },
27
- "main": "cjs/index.js",
28
- "module": "esm/index.js",
29
- "typings": "typings/index.d.ts",
30
- "typescript": {
31
- "definition": "typings/index.d.ts"
17
+ "node": ">=18.0.0"
32
18
  },
33
- "type": "module",
19
+ "main": "./dist/index.js",
34
20
  "exports": {
35
21
  ".": {
36
22
  "require": {
37
- "types": "./typings/index.d.cts",
38
- "default": "./cjs/index.js"
23
+ "types": "./dist/index.d.cts",
24
+ "default": "./dist/index.cjs"
39
25
  },
40
26
  "import": {
41
- "types": "./typings/index.d.ts",
42
- "default": "./esm/index.js"
43
- },
44
- "default": {
45
- "types": "./typings/index.d.ts",
46
- "default": "./esm/index.js"
27
+ "types": "./dist/index.d.ts",
28
+ "default": "./dist/index.js"
47
29
  }
48
30
  },
49
31
  "./package.json": "./package.json"
50
- }
32
+ },
33
+ "types": "./dist/index.d.ts",
34
+ "files": [
35
+ "dist"
36
+ ],
37
+ "scripts": {
38
+ "build": "pkgroll --clean-dist",
39
+ "prepack": "yarn build"
40
+ },
41
+ "peerDependencies": {
42
+ "graphql": "^15.9.0 || ^16.9.0"
43
+ },
44
+ "dependencies": {
45
+ "@graphql-mesh/cross-helpers": "^0.4.7",
46
+ "@graphql-mesh/string-interpolation": "^0.5.6",
47
+ "@graphql-mesh/transport-common": "^0.7.14-alpha-54b3273d0d9033d32f549a6b937921426c35b08b",
48
+ "@graphql-mesh/types": "^0.102.12",
49
+ "@graphql-mesh/utils": "^0.102.12",
50
+ "@graphql-tools/utils": "^10.5.5",
51
+ "@repeaterjs/repeater": "^3.0.6",
52
+ "@whatwg-node/fetch": "^0.10.0",
53
+ "tslib": "^2.4.0"
54
+ },
55
+ "devDependencies": {
56
+ "@graphql-mesh/store": "^0.102.12",
57
+ "graphql": "^16.9.0",
58
+ "pkgroll": "2.5.1"
59
+ },
60
+ "sideEffects": false
51
61
  }
package/cjs/index.js DELETED
@@ -1,198 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const cross_helpers_1 = require("@graphql-mesh/cross-helpers");
4
- const string_interpolation_1 = require("@graphql-mesh/string-interpolation");
5
- const transport_common_1 = require("@graphql-mesh/transport-common");
6
- const utils_1 = require("@graphql-mesh/utils");
7
- const utils_2 = require("@graphql-tools/utils");
8
- const repeater_1 = require("@repeaterjs/repeater");
9
- const fetch_1 = require("@whatwg-node/fetch");
10
- function createTimeoutError() {
11
- return (0, utils_2.createGraphQLError)('Subscription timed out', {
12
- extensions: {
13
- code: 'TIMEOUT_ERROR',
14
- },
15
- });
16
- }
17
- exports.default = {
18
- getSubgraphExecutor({ transportEntry, fetch, pubsub, logger }) {
19
- let headersInConfig;
20
- if (typeof transportEntry.headers === 'string') {
21
- headersInConfig = JSON.parse(transportEntry.headers);
22
- }
23
- if (Array.isArray(transportEntry.headers)) {
24
- headersInConfig = Object.fromEntries(transportEntry.headers);
25
- }
26
- const headersFactory = (0, string_interpolation_1.getInterpolatedHeadersFactory)(headersInConfig);
27
- const verifier = fetch_1.crypto.randomUUID();
28
- if (!pubsub) {
29
- throw new Error(`You must provide a pubsub instance to http-callbacks transport!
30
- Example:
31
- export const gatewayConfig = defineConfig({
32
- pubsub: new PubSub(),
33
- })
34
- See documentation: https://the-guild.dev/docs/mesh/pubsub`);
35
- }
36
- const reqAbortCtrls = new Set();
37
- const heartbeats = new Map();
38
- const stopFnSet = new Set();
39
- const publicUrl = transportEntry.options?.public_url || 'http://localhost:4000';
40
- const callbackPath = transportEntry.options?.path || '/callback';
41
- const heartbeatIntervalMs = transportEntry.options.heartbeat_interval || 50000;
42
- const httpCallbackExecutor = function httpCallbackExecutor(execReq) {
43
- const query = (0, transport_common_1.defaultPrintFn)(execReq.document);
44
- const subscriptionId = fetch_1.crypto.randomUUID();
45
- const subscriptionLogger = logger.child(subscriptionId);
46
- const callbackUrl = `${publicUrl}${callbackPath}/${subscriptionId}`;
47
- const subscriptionCallbackPath = `${callbackPath}/${subscriptionId}`;
48
- const fetchBody = JSON.stringify({
49
- query,
50
- variables: execReq.variables,
51
- operationName: execReq.operationName,
52
- extensions: {
53
- ...(execReq.extensions || {}),
54
- subscription: {
55
- callbackUrl,
56
- subscriptionId,
57
- verifier,
58
- heartbeatIntervalMs,
59
- },
60
- },
61
- });
62
- let stopSubscription = error => {
63
- if (error) {
64
- throw error;
65
- }
66
- };
67
- heartbeats.set(subscriptionId, setTimeout(() => {
68
- stopSubscription(createTimeoutError());
69
- }, heartbeatIntervalMs));
70
- subscriptionLogger.debug(`Subscribing to ${transportEntry.location} with callbackUrl: ${callbackUrl}`);
71
- let pushFn = () => {
72
- throw new Error(`Subgraph does not look like configured correctly. Check your subgraph setup.`);
73
- };
74
- const reqAbortCtrl = new AbortController();
75
- const subFetchCall$ = (0, utils_1.mapMaybePromise)(fetch(transportEntry.location, {
76
- method: 'POST',
77
- headers: {
78
- 'Content-Type': 'application/json',
79
- ...headersFactory({
80
- env: cross_helpers_1.process.env,
81
- root: execReq.rootValue,
82
- context: execReq.context,
83
- info: execReq.info,
84
- }),
85
- Accept: 'application/json;callbackSpec=1.0; charset=utf-8',
86
- },
87
- body: fetchBody,
88
- signal: reqAbortCtrl.signal,
89
- }, execReq.context, execReq.info), res => (0, utils_1.mapMaybePromise)(res.text(), resText => {
90
- let resJson;
91
- try {
92
- resJson = JSON.parse(resText);
93
- }
94
- catch (e) {
95
- if (!res.ok) {
96
- stopSubscription(new Error(`Subscription request failed with an HTTP Error: ${res.status} ${resText}`));
97
- }
98
- else {
99
- stopSubscription(e);
100
- }
101
- return;
102
- }
103
- logger.debug(`Subscription request received`, resJson);
104
- if (resJson.errors) {
105
- if (resJson.errors.length === 1) {
106
- stopSubscription((0, utils_2.createGraphQLError)(resJson.errors[0].message, resJson.errors[0]));
107
- }
108
- else {
109
- stopSubscription(new AggregateError(resJson.errors.map(err => (0, utils_2.createGraphQLError)(err.message, err)), resJson.errors.map(err => err.message).join('\n')));
110
- }
111
- }
112
- else if (resJson.data != null) {
113
- pushFn(resJson.data);
114
- stopSubscription();
115
- }
116
- }), e => {
117
- logger.debug(`Subscription request failed`, e);
118
- stopSubscription(e);
119
- });
120
- execReq.context?.waitUntil?.(subFetchCall$);
121
- return new repeater_1.Repeater((push, stop) => {
122
- pushFn = push;
123
- stopSubscription = stop;
124
- stopFnSet.add(stop);
125
- logger.debug(`Listening to ${subscriptionCallbackPath}`);
126
- const subId = pubsub.subscribe(`webhook:post:${subscriptionCallbackPath}`, (message) => {
127
- logger.debug(`Received message from ${subscriptionCallbackPath}`, message);
128
- if (message.verifier !== verifier) {
129
- return;
130
- }
131
- const existingHeartbeat = heartbeats.get(subscriptionId);
132
- if (existingHeartbeat) {
133
- clearTimeout(existingHeartbeat);
134
- }
135
- heartbeats.set(subscriptionId, setTimeout(() => {
136
- stopSubscription(createTimeoutError());
137
- }, heartbeatIntervalMs));
138
- switch (message.action) {
139
- case 'check':
140
- break;
141
- case 'next':
142
- push(message.payload);
143
- break;
144
- case 'complete':
145
- if (message.errors) {
146
- if (message.errors.length === 1) {
147
- const error = message.errors[0];
148
- stopSubscription((0, utils_2.createGraphQLError)(error.message, {
149
- ...error,
150
- extensions: {
151
- ...error.extensions,
152
- code: 'DOWNSTREAM_SERVICE_ERROR',
153
- },
154
- }));
155
- }
156
- else {
157
- stopSubscription(new AggregateError(message.errors.map(err => (0, utils_2.createGraphQLError)(err.message, {
158
- ...err,
159
- extensions: {
160
- ...err.extensions,
161
- code: 'DOWNSTREAM_SERVICE_ERROR',
162
- },
163
- }))));
164
- }
165
- }
166
- else {
167
- stopSubscription();
168
- }
169
- break;
170
- }
171
- });
172
- stop.finally(() => {
173
- pubsub.unsubscribe(subId);
174
- clearTimeout(heartbeats.get(subscriptionId));
175
- heartbeats.delete(subscriptionId);
176
- stopFnSet.delete(stop);
177
- if (!reqAbortCtrl.signal.aborted) {
178
- reqAbortCtrl.abort();
179
- }
180
- });
181
- });
182
- };
183
- function disposeFn() {
184
- for (const stop of stopFnSet) {
185
- stop();
186
- }
187
- for (const interval of heartbeats.values()) {
188
- clearTimeout(interval);
189
- }
190
- for (const ctrl of reqAbortCtrls) {
191
- if (!ctrl.signal.aborted) {
192
- ctrl.abort();
193
- }
194
- }
195
- }
196
- return (0, utils_1.makeDisposable)(httpCallbackExecutor, disposeFn);
197
- },
198
- };
package/cjs/package.json DELETED
@@ -1 +0,0 @@
1
- {"type":"commonjs"}