@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 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
@@ -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
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, no-underscore-dangle, @typescript-eslint/no-var-requires
6
- process.env.__NEXT_PROCESSED_ENV ? globalThis.fetch : require('@whatwg-node/fetch').fetch,
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(options?.headers ?? {}).filter(([, value]) => value !== ''),
72
+ Object.entries(serverOptions?.headers ?? {}).filter(([, value]) => value !== ''),
21
73
  )
22
74
 
23
- return fetcher(url, {
24
- ...options,
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.4",
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.4",
24
- "@graphcommerce/prettier-config-pwa": "^10.1.0-canary.4",
25
- "@graphcommerce/typescript-config-pwa": "^10.1.0-canary.4",
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
  },