@graphcommerce/graphql 6.0.2-canary.8 → 6.1.0

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,53 @@
1
1
  # Change Log
2
2
 
3
+ ## 6.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#1869](https://github.com/graphcommerce-org/graphcommerce/pull/1869) [`82111fa35`](https://github.com/graphcommerce-org/graphcommerce/commit/82111fa351b68a76ff053ebb7e0261ee507a826d) - Faster page rendering on all pages that use Apollo Client: Revert to classical useEffect strategy for Apollo cache persist restore and remove custom hydration strategies from the cart/customer.
8
+
9
+ This comes with a caviat: When using `<Suspense>` to defer rendering you might run into hydration errors. In the case where the Suspense boundaries are used to improve performance, you are required to remove those. We have a follow-up [PR](https://github.com/graphcommerce-org/graphcommerce/pull/1878) in the works that allows getting the hydration performance improvement, but not have the hydration errors. ([@paales](https://github.com/paales))
10
+
11
+ ### Patch Changes
12
+
13
+ - [#1883](https://github.com/graphcommerce-org/graphcommerce/pull/1883) [`ede93ae7f`](https://github.com/graphcommerce-org/graphcommerce/commit/ede93ae7f1d9239d1e827cce574085a1c264890b) - When running in dev mode, the CLI will now have clickable references to the backend queries made by the Mesh. ([@paales](https://github.com/paales))
14
+
15
+ ## 6.0.2-canary.22
16
+
17
+ ## 6.0.2-canary.21
18
+
19
+ ## 6.0.2-canary.20
20
+
21
+ ## 6.0.2-canary.19
22
+
23
+ ## 6.0.2-canary.18
24
+
25
+ ## 6.0.2-canary.17
26
+
27
+ ## 6.0.2-canary.16
28
+
29
+ ## 6.0.2-canary.15
30
+
31
+ ## 6.0.2-canary.14
32
+
33
+ ## 6.0.2-canary.13
34
+
35
+ ### Patch Changes
36
+
37
+ - [#1869](https://github.com/graphcommerce-org/graphcommerce/pull/1869) [`82111fa35`](https://github.com/graphcommerce-org/graphcommerce/commit/82111fa351b68a76ff053ebb7e0261ee507a826d) - Revert to classical useEffect strategy for Apollo cache persist restore and remove custom hydration strategies from the cart. ([@paales](https://github.com/paales))
38
+
39
+ ## 6.0.2-canary.12
40
+
41
+ ## 6.0.2-canary.11
42
+
43
+ ## 6.0.2-canary.10
44
+
45
+ ### Patch Changes
46
+
47
+ - [#1883](https://github.com/graphcommerce-org/graphcommerce/pull/1883) [`ede93ae7f`](https://github.com/graphcommerce-org/graphcommerce/commit/ede93ae7f1d9239d1e827cce574085a1c264890b) - Added clickable links to measurePerformanceLink ([@paales](https://github.com/paales))
48
+
49
+ ## 6.0.2-canary.9
50
+
3
51
  ## 6.0.2-canary.8
4
52
 
5
53
  ## 6.0.2-canary.7
@@ -8,7 +8,7 @@ import {
8
8
  HttpLink,
9
9
  } from '@apollo/client'
10
10
  import type { AppProps } from 'next/app'
11
- import { useState } from 'react'
11
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
12
12
  import { createCacheReviver } from '../createCacheReviver'
13
13
  import { errorLink } from '../errorLink'
14
14
  import fragments from '../generated/fragments.json'
@@ -46,30 +46,40 @@ export function GraphQLProvider(props: GraphQLProviderProps) {
46
46
  const { children, policies = [], migrations = [], links = [], pageProps } = props
47
47
  const state = (pageProps as { apolloState?: NormalizedCacheObject }).apolloState
48
48
 
49
+ const stateRef = useRef(state)
50
+
51
+ const linksRef = useRef(links)
52
+ const policiesRef = useRef(policies)
53
+
54
+ const createCache = useCallback(
55
+ () =>
56
+ new InMemoryCache({
57
+ possibleTypes: fragments.possibleTypes,
58
+ typePolicies: mergeTypePolicies(policiesRef.current),
59
+ }),
60
+ [],
61
+ )
62
+
49
63
  const [client] = useState(() => {
50
64
  const link = ApolloLink.from([
51
65
  ...(typeof window === 'undefined' ? [errorLink, measurePerformanceLink] : []),
52
- ...links,
66
+ ...linksRef.current,
53
67
  // The actual Http connection to the Mesh backend.
54
68
  new HttpLink({ uri: '/api/graphql', credentials: 'same-origin' }),
55
69
  ])
56
70
 
57
- const createCache = () =>
58
- new InMemoryCache({
59
- possibleTypes: fragments.possibleTypes,
60
- typePolicies: mergeTypePolicies(policies),
61
- })
71
+ const cache = createCache()
72
+ if (stateRef.current) cache.restore(stateRef.current)
62
73
 
63
- const apolloClient = new ApolloClient({
64
- link,
65
- cache: createCache(),
66
- name: 'web',
67
- ssrMode: typeof window === 'undefined',
68
- })
69
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
70
- createCacheReviver(apolloClient, createCache, policies, migrations, state)
71
- return apolloClient
74
+ const ssrMode = typeof window === 'undefined'
75
+ return new ApolloClient({ link, cache, name: 'web', ssrMode })
72
76
  })
77
+
78
+ useEffect(() => {
79
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
80
+ createCacheReviver(client, createCache, policies, migrations, state)
81
+ }, [client, createCache, migrations, policies, state])
82
+
73
83
  globalApolloClient.current = client
74
84
 
75
85
  return <ApolloProvider client={globalApolloClient.current}>{children}</ApolloProvider>
@@ -0,0 +1,84 @@
1
+ 'use strict'
2
+
3
+ /*
4
+ A hyperlink is opened upon encountering an OSC 8 escape sequence with the target URI. The syntax is
5
+ OSC 8 ; params ; URI BEL|ST
6
+ Following this, all subsequent cells that are painted are hyperlinks to this target. A hyperlink is closed with the same escape sequence, omitting the parameters and the URI but keeping the separators:
7
+ OSC 8 ; ; BEL|ST
8
+ const ST = '\u001B\\';
9
+ */
10
+ const OSC = '\u001B]'
11
+ const BEL = '\u0007'
12
+ const SEP = ';'
13
+
14
+ const hyperlinker = (text: string, uri: string) =>
15
+ [OSC, '8', SEP, SEP, uri, BEL, text, OSC, '8', SEP, SEP, BEL].join('')
16
+
17
+ function parseVersion(versionString: string): { major: number; minor: number; patch: number } {
18
+ if (/^\d{3,4}$/.test(versionString)) {
19
+ // Env var doesn't always use dots. example: 4601 => 46.1.0
20
+ const m = /(\d{1,2})(\d{2})/.exec(versionString) || []
21
+ return {
22
+ major: 0,
23
+ minor: parseInt(m[1], 10),
24
+ patch: parseInt(m[2], 10),
25
+ }
26
+ }
27
+
28
+ const versions = (versionString || '').split('.').map((n) => parseInt(n, 10))
29
+ return {
30
+ major: versions[0],
31
+ minor: versions[1],
32
+ patch: versions[2],
33
+ }
34
+ }
35
+
36
+ function supportsHyperlink(): boolean {
37
+ const {
38
+ CI,
39
+ FORCE_HYPERLINK,
40
+ NETLIFY,
41
+ TEAMCITY_VERSION,
42
+ TERM_PROGRAM,
43
+ TERM_PROGRAM_VERSION,
44
+ VTE_VERSION,
45
+ VERCEL,
46
+ } = process.env
47
+
48
+ if (FORCE_HYPERLINK) {
49
+ return !(FORCE_HYPERLINK.length > 0 && parseInt(FORCE_HYPERLINK, 10) === 0)
50
+ }
51
+
52
+ if (NETLIFY) return true
53
+ if (process.stdout.isTTY || process.stderr.isTTY) return false
54
+ if (process.platform === 'win32') return false
55
+ if (CI) return false
56
+ if (VERCEL) return false
57
+ if (TEAMCITY_VERSION) return false
58
+
59
+ if (TERM_PROGRAM) {
60
+ const version = parseVersion(TERM_PROGRAM_VERSION || '')
61
+
62
+ switch (TERM_PROGRAM) {
63
+ case 'iTerm.app':
64
+ if (version.major === 3) return version.minor >= 1
65
+ return version.major > 3
66
+ case 'WezTerm':
67
+ return version.major >= 20200620
68
+ case 'vscode':
69
+ return version.major > 1 || (version.major === 1 && version.minor >= 72)
70
+ }
71
+ }
72
+
73
+ if (VTE_VERSION) {
74
+ // 0.50.0 was supposed to support hyperlinks, but throws a segfault
75
+ if (VTE_VERSION === '0.50.0') return false
76
+ const version = parseVersion(VTE_VERSION)
77
+ return version.major > 0 || version.minor >= 50
78
+ }
79
+
80
+ return false
81
+ }
82
+
83
+ export const cliHyperlink = (text: string, uri: string) =>
84
+ supportsHyperlink() ? hyperlinker(text, uri) : text
@@ -1,34 +1,26 @@
1
1
  /* eslint-disable no-console */
2
2
  import { ApolloLink } from '@apollo/client'
3
+ import type { MeshFetchHTTPInformation } from '@graphql-mesh/plugin-http-details-extensions'
4
+ import { print } from 'graphql'
5
+ import { cliHyperlink } from './lib/hyperlinker'
3
6
 
4
7
  const running = new Map<
5
8
  string,
6
- { start: Date; end?: Date; internalStart?: Date; operationName: string }
7
- >()
8
-
9
- interface TracingFormat {
10
- version: 1
11
- startTime: string
12
- endTime: string
13
- duration: number
14
- execution: {
15
- resolvers: {
16
- path: (string | number)[]
17
- parentType: string
18
- fieldName: string
19
- returnType: string
20
- startOffset: number
21
- duration: number
22
- }[]
9
+ {
10
+ start: Date
11
+ end?: Date
12
+ internalStart?: Date
13
+ operationName: [string, string]
14
+ additional?: [string, string]
23
15
  }
24
- }
16
+ >()
25
17
 
26
18
  const renderLine = (line: {
27
19
  serverStart: number
28
20
  requestStart: number
29
21
  requestEnd: number
30
22
  colDivider: number
31
- additional: string[]
23
+ additional: (string | [string, string])[]
32
24
  }) => {
33
25
  const duration = line.requestEnd - line.requestStart
34
26
  const serverDuration = duration - line.serverStart
@@ -88,19 +80,20 @@ export const flushMeasurePerf = () => {
88
80
  value.operationName,
89
81
  // `${duration - (duration - serverStart)}ms`,
90
82
  `${duration - serverStart}ms`,
83
+ value.additional ?? '',
91
84
  ],
92
85
  colDivider,
93
86
  })
94
87
  })
95
88
 
96
89
  const items = [
97
- ['Operation', 'Mesh', 'Timeline'],
90
+ ['Operation', 'Mesh', ' Source', 'Timeline'],
98
91
  ...lines,
99
92
  renderLine({
100
93
  serverStart: 0,
101
94
  requestStart: 0,
102
95
  requestEnd: end,
103
- additional: [`Total time`, `${end}ms`],
96
+ additional: [`Total time`, `${end}ms`, ''],
104
97
  colDivider,
105
98
  }),
106
99
  ]
@@ -109,16 +102,22 @@ export const flushMeasurePerf = () => {
109
102
  const colWidths: number[] = Array(items[0].length).fill(0)
110
103
  items.forEach((item) => {
111
104
  item.forEach((t, index) => {
112
- colWidths[index] = Math.max(colWidths[index], t.length)
105
+ colWidths[index] = Math.max(colWidths[index], Array.isArray(t) ? t[0].length : t.length)
113
106
  })
114
107
  })
115
108
 
116
109
  // padd the items to the max length
117
110
  items.forEach((item) => {
118
111
  item.forEach((_, index) => {
119
- item[index] = `${item[index].padEnd(colWidths[index], ' ')}${
120
- index !== item.length - 1 ? `` : ''
121
- }`
112
+ const [str, link] = (
113
+ Array.isArray(item[index]) ? item[index] : [item[index], item[index]]
114
+ ) as [string, string]
115
+
116
+ const val = (Array.isArray(item[index]) ? item[index][1] : item[index]) as string
117
+
118
+ const padLength = colWidths[index] + (val.length - str.length)
119
+
120
+ item[index] = `${val.padEnd(padLength, ' ')}${index !== item.length - 1 ? `` : ''}`
122
121
  })
123
122
  })
124
123
 
@@ -147,19 +146,51 @@ export const measurePerformanceLink = new ApolloLink((operation, forward) => {
147
146
  Object.keys(operation.variables).length > 0 ? `(${JSON.stringify(operation.variables)})` : ''
148
147
  const operationString = `${operation.operationName}${vars}`
149
148
 
150
- running.set(operationString, { start: new Date(), operationName: operation.operationName })
149
+ running.set(operationString, {
150
+ start: new Date(),
151
+ operationName: [operation.operationName, operation.operationName],
152
+ })
151
153
  markTimeout()
152
154
 
153
- // console.info(`GraphQL start ${operationString}`)
154
155
  return forward(operation).map((data) => {
155
- const tracing = data.extensions?.tracing as TracingFormat | undefined
156
+ const httpDetails: MeshFetchHTTPInformation[] | undefined = data.extensions?.httpDetails
157
+
158
+ let additional = [``, ``] as [string, string]
159
+ if (httpDetails) {
160
+ httpDetails.forEach((d) => {
161
+ const requestUrl = new URL(d.request.url)
162
+ requestUrl.searchParams.delete('extensions')
163
+ const title = `${d.sourceName} ${d.responseTime}ms`
164
+ additional = [
165
+ `${additional[0]} ${title}`,
166
+ `${additional[1]} ${cliHyperlink(title, requestUrl.toString().replace(/\+/g, '%20'))}`,
167
+ ]
168
+ })
169
+ }
156
170
 
157
171
  // Called after server responds
172
+ const query = [
173
+ `# Variables: ${JSON.stringify(operation.variables)}`,
174
+ `# Headers: ${JSON.stringify(operation.getContext().headers)}`,
175
+ print(operation.query),
176
+ ].join('\n')
177
+
178
+ const meshUrl = new URL(`${import.meta.graphCommerce.canonicalBaseUrl}/api/graphql`)
179
+ meshUrl.searchParams.set('query', query)
180
+
158
181
  running.set(operationString, {
159
- internalStart: tracing ? new Date(tracing.startTime) : undefined,
160
182
  start: operation.getContext().measurePerformanceLinkStart as Date,
161
183
  end: new Date(),
162
- operationName: operation.operationName,
184
+ operationName: [operation.operationName, operation.operationName],
185
+ additional,
186
+ // [
187
+ // operation.operationName,
188
+ // cliHyperlink(operation.operationName, meshUrl.toString()),
189
+ // ],
190
+ // additional: [
191
+ // `🔗 ${additional[0]}`,
192
+ // `${cliHyperlink('🔗', meshUrl.toString())} ${additional[1]}`,
193
+ // ],
163
194
  })
164
195
 
165
196
  markTimeout()
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@graphcommerce/graphql",
3
3
  "homepage": "https://www.graphcommerce.org/",
4
4
  "repository": "github:graphcommerce-org/graphcommerce",
5
- "version": "6.0.2-canary.8",
5
+ "version": "6.1.0",
6
6
  "sideEffects": false,
7
7
  "main": "index.ts",
8
8
  "prettier": "@graphcommerce/prettier-config-pwa",
@@ -14,8 +14,8 @@
14
14
  },
15
15
  "dependencies": {
16
16
  "@apollo/client": "^3.7.10",
17
- "@graphcommerce/graphql-codegen-near-operation-file": "6.0.2-canary.8",
18
- "@graphcommerce/graphql-codegen-relay-optimizer-plugin": "6.0.2-canary.8",
17
+ "@graphcommerce/graphql-codegen-near-operation-file": "6.1.0",
18
+ "@graphcommerce/graphql-codegen-relay-optimizer-plugin": "6.1.0",
19
19
  "@graphql-codegen/add": "4.0.1",
20
20
  "@graphql-codegen/fragment-matcher": "4.0.1",
21
21
  "@graphql-codegen/introspection": "3.0.1",
@@ -29,9 +29,9 @@
29
29
  "graphql": "16.6.0"
30
30
  },
31
31
  "devDependencies": {
32
- "@graphcommerce/eslint-config-pwa": "6.0.2-canary.8",
33
- "@graphcommerce/prettier-config-pwa": "6.0.2-canary.8",
34
- "@graphcommerce/typescript-config-pwa": "6.0.2-canary.8"
32
+ "@graphcommerce/eslint-config-pwa": "6.1.0",
33
+ "@graphcommerce/prettier-config-pwa": "6.1.0",
34
+ "@graphcommerce/typescript-config-pwa": "6.1.0"
35
35
  },
36
36
  "peerDependencies": {
37
37
  "react": "^18.2.0",