@graphcommerce/graphql-mesh 10.1.0-canary.4 → 10.1.0-canary.40
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 +84 -0
- package/api/createEnvelop.ts +9 -0
- package/customFetch.ts +57 -5
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,89 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
+
## 10.1.0-canary.40
|
|
4
|
+
|
|
5
|
+
## 10.1.0-canary.39
|
|
6
|
+
|
|
7
|
+
## 10.1.0-canary.38
|
|
8
|
+
|
|
9
|
+
### Minor Changes
|
|
10
|
+
|
|
11
|
+
- [#2650](https://github.com/graphcommerce-org/graphcommerce/pull/2650) [`ccd01e9`](https://github.com/graphcommerce-org/graphcommerce/commit/ccd01e9ad061783b354fd8655b5319edb26a15f1) - Route server-side Magento traffic over an internal network with the new runtime-only `GC_MAGENTO_ENDPOINT_SERVER` environment variable (e.g. `http://varnish.magento-namespace.svc.cluster.local`). When set, every mesh request whose URL starts with the origin of `GC_MAGENTO_ENDPOINT` — GraphQL and REST — is rewritten to the internal origin and gains an `X-Forwarded-Proto: https` header, so frontend↔Magento traffic inside a Kubernetes cluster no longer hairpins over the public load balancer. Unset, behavior is unchanged. See the new "Routing Magento traffic over an internal network" section in the mesh docs. ([@paales](https://github.com/paales))
|
|
12
|
+
|
|
13
|
+
## 10.1.0-canary.37
|
|
14
|
+
|
|
15
|
+
## 10.1.0-canary.36
|
|
16
|
+
|
|
17
|
+
## 10.1.0-canary.35
|
|
18
|
+
|
|
19
|
+
## 10.1.0-canary.34
|
|
20
|
+
|
|
21
|
+
## 10.1.0-canary.33
|
|
22
|
+
|
|
23
|
+
### Patch Changes
|
|
24
|
+
|
|
25
|
+
- [#2648](https://github.com/graphcommerce-org/graphcommerce/pull/2648) [`24541b8`](https://github.com/graphcommerce-org/graphcommerce/commit/24541b8d3d7f5de80ebc7b9a03d439b4347eb9e6) - Fix multipart file uploads through the mesh returning "Unable to parse the request." in `next dev` (turbopack). `customFetch` used `globalThis.fetch` (undici) inside Next, while `@graphql-tools/executor-http` builds upload bodies with `FormData` from `@whatwg-node/fetch`. In `next dev`, `@whatwg-node/fetch` is evaluated while `next.config.ts` loads — before any `__NEXT` global exists — so its Next.js detection fails and it exports its ponyfills. Undici doesn't recognize the ponyfill `FormData` and stringified the request body to the literal `[object Object]`. `customFetch` now always uses the fetch exported by `@whatwg-node/fetch`, which is `globalThis.fetch` whenever the native path is active and the matching ponyfill fetch otherwise, so fetch and `FormData` always come from the same implementation family. ([@paales](https://github.com/paales))
|
|
26
|
+
|
|
27
|
+
## 10.1.0-canary.32
|
|
28
|
+
|
|
29
|
+
### Patch Changes
|
|
30
|
+
|
|
31
|
+
- [#2644](https://github.com/graphcommerce-org/graphcommerce/pull/2644) [`c4ca56a`](https://github.com/graphcommerce-org/graphcommerce/commit/c4ca56a08915d918f78339ecddcc418dfc26857d) - Fix GraphQL multipart uploads (`Upload` scalar) through the Mesh. Next.js' pages-router bodyParser decodes multipart request bodies as UTF-8 text before Yoga can parse them, silently corrupting binary upload bytes (invalid UTF-8 sequences become replacement characters and the part's mime type is lost). The `/api/graphql` route now sets `bodyParser: false` so Yoga receives the raw stream, and `createServer` throws a descriptive error when it receives a multipart request whose body was already consumed by the bodyParser. ([@paales](https://github.com/paales))
|
|
32
|
+
|
|
33
|
+
## 10.1.0-canary.31
|
|
34
|
+
|
|
35
|
+
## 10.1.0-canary.30
|
|
36
|
+
|
|
37
|
+
## 10.1.0-canary.29
|
|
38
|
+
|
|
39
|
+
## 10.1.0-canary.28
|
|
40
|
+
|
|
41
|
+
## 10.1.0-canary.27
|
|
42
|
+
|
|
43
|
+
## 10.1.0-canary.26
|
|
44
|
+
|
|
45
|
+
## 10.1.0-canary.25
|
|
46
|
+
|
|
47
|
+
## 10.1.0-canary.24
|
|
48
|
+
|
|
49
|
+
## 10.1.0-canary.23
|
|
50
|
+
|
|
51
|
+
## 10.1.0-canary.22
|
|
52
|
+
|
|
53
|
+
## 10.1.0-canary.21
|
|
54
|
+
|
|
55
|
+
## 10.1.0-canary.20
|
|
56
|
+
|
|
57
|
+
## 10.1.0-canary.19
|
|
58
|
+
|
|
59
|
+
## 10.1.0-canary.18
|
|
60
|
+
|
|
61
|
+
## 10.1.0-canary.17
|
|
62
|
+
|
|
63
|
+
## 10.1.0-canary.16
|
|
64
|
+
|
|
65
|
+
## 10.1.0-canary.15
|
|
66
|
+
|
|
67
|
+
## 10.1.0-canary.14
|
|
68
|
+
|
|
69
|
+
## 10.1.0-canary.13
|
|
70
|
+
|
|
71
|
+
## 10.1.0-canary.12
|
|
72
|
+
|
|
73
|
+
## 10.1.0-canary.11
|
|
74
|
+
|
|
75
|
+
## 10.1.0-canary.10
|
|
76
|
+
|
|
77
|
+
## 10.1.0-canary.9
|
|
78
|
+
|
|
79
|
+
## 10.1.0-canary.8
|
|
80
|
+
|
|
81
|
+
## 10.1.0-canary.7
|
|
82
|
+
|
|
83
|
+
## 10.1.0-canary.6
|
|
84
|
+
|
|
85
|
+
## 10.1.0-canary.5
|
|
86
|
+
|
|
3
87
|
## 10.1.0-canary.4
|
|
4
88
|
|
|
5
89
|
## 10.1.0-canary.3
|
package/api/createEnvelop.ts
CHANGED
|
@@ -8,6 +8,15 @@ export const createServer = async (endpoint: string) => {
|
|
|
8
8
|
|
|
9
9
|
const handler = createBuiltMeshHTTPHandler()
|
|
10
10
|
return async (req: NextApiRequest, res: NextApiResponse) => {
|
|
11
|
+
if (req.headers['content-type']?.startsWith('multipart/form-data') && req.body !== undefined) {
|
|
12
|
+
// Next's pages-router bodyParser has already consumed the request stream and decoded it as
|
|
13
|
+
// UTF-8 text, which corrupts binary `Upload` bytes (the multipart boundary survives, so the
|
|
14
|
+
// request would otherwise be forwarded with silently mangled file contents).
|
|
15
|
+
throw Error(
|
|
16
|
+
"Multipart GraphQL requests require Next.js' bodyParser to be disabled. Add `bodyParser: false` to the route config in pages/api/graphql.ts: export const config = { api: { externalResolver: true, bodyParser: false } }",
|
|
17
|
+
)
|
|
18
|
+
}
|
|
19
|
+
|
|
11
20
|
res.setHeader('Access-Control-Allow-Origin', req.headers.origin || '*')
|
|
12
21
|
const requestedHeaders = req.headers['access-control-request-headers']
|
|
13
22
|
if (requestedHeaders) {
|
package/customFetch.ts
CHANGED
|
@@ -2,10 +2,60 @@ import fetchRetry from 'fetch-retry'
|
|
|
2
2
|
import type { RequestInitWithRetry } from 'fetch-retry'
|
|
3
3
|
|
|
4
4
|
const fetcher = fetchRetry(
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Always use the fetch that `@whatwg-node/fetch` exports, never `globalThis.fetch` directly. When
|
|
7
|
+
* `@whatwg-node/fetch` detects a Next.js runtime it re-exports the native fetch primitives (so
|
|
8
|
+
* inside Next this IS `globalThis.fetch`/undici, keeping SSL handshakes alive), but when that
|
|
9
|
+
* detection fails — `next dev` (turbopack) evaluates it while loading `next.config.ts` (via
|
|
10
|
+
* `@graphql-codegen/cli`), before any `__NEXT` global exists — it exports its ponyfills instead.
|
|
11
|
+
* In that case `@graphql-tools/executor-http` builds multipart upload bodies with the ponyfill
|
|
12
|
+
* `FormData`, which undici doesn't recognize and stringifies to the literal `[object Object]`
|
|
13
|
+
* (Magento: "Unable to parse the request."). The fetch implementation must therefore always come
|
|
14
|
+
* from the same family as the `FormData`/`File` classes executor-http uses.
|
|
15
|
+
*/
|
|
16
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-var-requires
|
|
17
|
+
require('@whatwg-node/fetch').fetch,
|
|
7
18
|
)
|
|
8
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Optionally reroute server-side Magento traffic to an internal endpoint.
|
|
22
|
+
*
|
|
23
|
+
* When the runtime environment sets `GC_MAGENTO_ENDPOINT_SERVER` (e.g.
|
|
24
|
+
* `http://varnish.magento-namespace.svc.cluster.local`), every request whose URL starts with the
|
|
25
|
+
* origin of `GC_MAGENTO_ENDPOINT` is rewritten to that origin. All Magento traffic the mesh
|
|
26
|
+
* performs (GraphQL and REST) then stays on the internal network — e.g. within a Kubernetes cluster
|
|
27
|
+
* — instead of hairpinning over the public load balancer, while the public endpoint keeps serving
|
|
28
|
+
* the browser-facing concerns (media URLs, image optimization).
|
|
29
|
+
*
|
|
30
|
+
* - Both variables are read from `process.env` at request time: this module also runs outside the
|
|
31
|
+
* Next.js bundler (the mesh CLI imports it during `gc-mesh build`), so it cannot rely on
|
|
32
|
+
* build-inlined configuration.
|
|
33
|
+
* - Set `GC_MAGENTO_ENDPOINT_SERVER` in the runtime environment only (e.g. a Kubernetes ConfigMap),
|
|
34
|
+
* never in the build environment: schema introspection and static generation run where the
|
|
35
|
+
* internal endpoint is not reachable. Unset, this is a no-op.
|
|
36
|
+
* - The rewrite adds `X-Forwarded-Proto: https` — the internal path bypasses the TLS-terminating
|
|
37
|
+
* proxy that normally adds it, and without it Magento considers the request insecure and
|
|
38
|
+
* generates http:// URLs in responses.
|
|
39
|
+
*/
|
|
40
|
+
function toServerEndpoint(
|
|
41
|
+
url: RequestInfo | URL,
|
|
42
|
+
options: RequestInitWithRetry | undefined,
|
|
43
|
+
): [RequestInfo | URL, RequestInitWithRetry | undefined] {
|
|
44
|
+
const serverEndpoint = process.env.GC_MAGENTO_ENDPOINT_SERVER
|
|
45
|
+
const publicEndpoint = process.env.GC_MAGENTO_ENDPOINT
|
|
46
|
+
if (!serverEndpoint || !publicEndpoint) return [url, options]
|
|
47
|
+
if (typeof url !== 'string' && !(url instanceof URL)) return [url, options]
|
|
48
|
+
|
|
49
|
+
const publicOrigin = new URL(publicEndpoint).origin
|
|
50
|
+
const target = url.toString()
|
|
51
|
+
if (!target.startsWith(publicOrigin)) return [url, options]
|
|
52
|
+
|
|
53
|
+
return [
|
|
54
|
+
new URL(serverEndpoint).origin + target.slice(publicOrigin.length),
|
|
55
|
+
{ ...options, headers: { ...options?.headers, 'x-forwarded-proto': 'https' } },
|
|
56
|
+
]
|
|
57
|
+
}
|
|
58
|
+
|
|
9
59
|
/**
|
|
10
60
|
* @param {RequestInfo | URL} url
|
|
11
61
|
* @param {import('fetch-retry').RequestInitWithRetry | undefined} options
|
|
@@ -16,12 +66,14 @@ export const fetch = (
|
|
|
16
66
|
url: RequestInfo | URL,
|
|
17
67
|
options: RequestInitWithRetry | undefined,
|
|
18
68
|
): Promise<Response> => {
|
|
69
|
+
const [serverUrl, serverOptions] = toServerEndpoint(url, options)
|
|
70
|
+
|
|
19
71
|
const headers = Object.fromEntries(
|
|
20
|
-
Object.entries(
|
|
72
|
+
Object.entries(serverOptions?.headers ?? {}).filter(([, value]) => value !== ''),
|
|
21
73
|
)
|
|
22
74
|
|
|
23
|
-
return fetcher(
|
|
24
|
-
...
|
|
75
|
+
return fetcher(serverUrl, {
|
|
76
|
+
...serverOptions,
|
|
25
77
|
headers,
|
|
26
78
|
retries: 6,
|
|
27
79
|
retryDelay: (attempt) => 2 ** attempt * 200, // 200, 400, 800, 1600, 3200, 6400
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@graphcommerce/graphql-mesh",
|
|
3
3
|
"homepage": "https://www.graphcommerce.org/",
|
|
4
4
|
"repository": "github:graphcommerce-org/graphcommerce",
|
|
5
|
-
"version": "10.1.0-canary.
|
|
5
|
+
"version": "10.1.0-canary.40",
|
|
6
6
|
"dependencies": {
|
|
7
7
|
"@whatwg-node/fetch": "^0.10.13",
|
|
8
8
|
"fetch-retry": "^5.0.6",
|
|
@@ -20,9 +20,9 @@
|
|
|
20
20
|
},
|
|
21
21
|
"peerDependencies": {
|
|
22
22
|
"@apollo/client": "*",
|
|
23
|
-
"@graphcommerce/eslint-config-pwa": "^10.1.0-canary.
|
|
24
|
-
"@graphcommerce/prettier-config-pwa": "^10.1.0-canary.
|
|
25
|
-
"@graphcommerce/typescript-config-pwa": "^10.1.0-canary.
|
|
23
|
+
"@graphcommerce/eslint-config-pwa": "^10.1.0-canary.40",
|
|
24
|
+
"@graphcommerce/prettier-config-pwa": "^10.1.0-canary.40",
|
|
25
|
+
"@graphcommerce/typescript-config-pwa": "^10.1.0-canary.40",
|
|
26
26
|
"@graphql-mesh/runtime": "*",
|
|
27
27
|
"@graphql-mesh/types": "*"
|
|
28
28
|
},
|