@graphcommerce/graphql 9.0.0-canary.99 → 9.0.1-canary.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 +44 -949
- package/{test → __playwright__}/apolloClient.fixture.ts +7 -12
- package/apollo.ts +1 -0
- package/components/GraphQLProvider/GraphQLProvider.tsx +4 -15
- package/components/GraphQLProvider/createCacheReviver.ts +4 -3
- package/components/GraphQLProvider/measurePerformanceLink.ts +66 -36
- package/components/GraphQLProvider/migrateCache.ts +1 -1
- package/components/GraphQLProvider/persistenceMapper.ts +4 -1
- package/components/PrivateQueryMask/PrivateQueryMask.tsx +93 -0
- package/config.ts +10 -3
- package/hooks/{useInContextQuery.ts → usePrivateQuery.ts} +24 -20
- package/hooks/usePrivateQueryContext.ts +20 -0
- package/index.ts +3 -3
- package/link/RemovePrivateContextDirectivesLink.ts +19 -0
- package/package.json +15 -14
- package/schema/InContext.graphqls +3 -3
- package/tsconfig.json +1 -1
- package/utils/cachePolicy.ts +1 -1
- package/utils/getPreviewData.ts +2 -2
- package/components/InContextMask/InContextMask.tsx +0 -92
- package/hooks/useInContextInput.ts +0 -17
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
/* eslint-disable import/no-extraneous-dependencies */
|
|
2
|
+
|
|
2
3
|
/* eslint-disable no-empty-pattern */
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
getOperationName,
|
|
8
|
-
InMemoryCache,
|
|
9
|
-
NormalizedCacheObject,
|
|
10
|
-
TypedDocumentNode,
|
|
11
|
-
} from '../apollo'
|
|
4
|
+
import type { Page } from '@playwright/test'
|
|
5
|
+
import { test as base } from '@playwright/test'
|
|
6
|
+
import type { FetchResult, NormalizedCacheObject, TypedDocumentNode } from '../apollo'
|
|
7
|
+
import { ApolloClient, getOperationName, InMemoryCache } from '../apollo'
|
|
12
8
|
|
|
13
9
|
type ApolloClientTest = {
|
|
14
10
|
apolloClient: ApolloClient<NormalizedCacheObject>
|
|
@@ -31,7 +27,8 @@ export async function waitForGraphQlResponse<Q, V>(
|
|
|
31
27
|
return (await response?.json()) as FetchResult<Q>
|
|
32
28
|
}
|
|
33
29
|
|
|
34
|
-
|
|
30
|
+
/** @public */
|
|
31
|
+
export const test = base.extend<ApolloClientTest>({
|
|
35
32
|
apolloClient: async ({}, use) => {
|
|
36
33
|
const client = new ApolloClient({
|
|
37
34
|
uri: 'http://localhost:3000/api/graphql',
|
|
@@ -41,5 +38,3 @@ const test = base.extend<ApolloClientTest>({
|
|
|
41
38
|
await use(client)
|
|
42
39
|
},
|
|
43
40
|
})
|
|
44
|
-
|
|
45
|
-
export { test }
|
package/apollo.ts
CHANGED
|
@@ -4,5 +4,6 @@ export * from '@apollo/client'
|
|
|
4
4
|
export * from '@apollo/client/link/schema'
|
|
5
5
|
export * from '@apollo/client/link/context'
|
|
6
6
|
export * from '@apollo/client/link/error'
|
|
7
|
+
export * from '@apollo/client/utilities'
|
|
7
8
|
|
|
8
9
|
export { getOperationName } from '@apollo/client/utilities'
|
|
@@ -1,22 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
ApolloClient,
|
|
3
|
-
NormalizedCacheObject,
|
|
4
|
-
ApolloLink,
|
|
5
|
-
InMemoryCache,
|
|
6
|
-
ApolloProvider,
|
|
7
|
-
HttpLink,
|
|
8
|
-
DefaultOptions,
|
|
9
|
-
} from '@apollo/client'
|
|
10
1
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
11
2
|
import { useStorefrontConfig } from '@graphcommerce/next-ui/hooks/useStorefrontConfig'
|
|
3
|
+
import type { DefaultOptions, NormalizedCacheObject } from '@apollo/client'
|
|
4
|
+
import { ApolloClient, ApolloLink, ApolloProvider, HttpLink, InMemoryCache } from '@apollo/client'
|
|
12
5
|
import type { AppProps } from 'next/app'
|
|
13
6
|
import { useCallback, useEffect, useRef, useState } from 'react'
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
graphqlConfig,
|
|
17
|
-
ApolloClientConfigInput,
|
|
18
|
-
PreviewConfig,
|
|
19
|
-
} from '../../config'
|
|
7
|
+
import type { ApolloClientConfig, ApolloClientConfigInput, PreviewConfig } from '../../config'
|
|
8
|
+
import { graphqlConfig } from '../../config'
|
|
20
9
|
import fragments from '../../generated/fragments.json'
|
|
21
10
|
import { createCacheReviver } from './createCacheReviver'
|
|
22
11
|
import { errorLink } from './errorLink'
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { CachePersistor, LocalStorageWrapper } from 'apollo3-cache-persist'
|
|
2
|
+
import type { ApolloCache, ApolloClient, NormalizedCacheObject } from '../../apollo'
|
|
3
|
+
import { mergeDeep } from '../../apollo'
|
|
4
|
+
import type { ApolloClientConfig } from '../../config'
|
|
4
5
|
import { migrateCacheHandler } from './migrateCache'
|
|
5
6
|
import { persistenceMapper } from './persistenceMapper'
|
|
6
7
|
import { getTypePoliciesVersion } from './typePolicies'
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
/* eslint-disable no-console */
|
|
2
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2
3
|
import { ApolloLink } from '@apollo/client'
|
|
3
|
-
import type { MeshFetchHTTPInformation } from '@graphql-mesh/plugin-http-details-extensions'
|
|
4
4
|
import { print } from '@apollo/client/utilities'
|
|
5
|
+
import type { MeshFetchHTTPInformation } from '@graphql-mesh/plugin-http-details-extensions'
|
|
6
|
+
import { responsePathAsArray, stripIgnoredCharacters } from 'graphql'
|
|
5
7
|
import { cliHyperlink } from '../../lib/hyperlinker'
|
|
6
8
|
|
|
7
9
|
const running = new Map<
|
|
@@ -12,6 +14,7 @@ const running = new Map<
|
|
|
12
14
|
internalStart?: Date
|
|
13
15
|
operationName: [string, string]
|
|
14
16
|
additional?: [string, string]
|
|
17
|
+
idx?: number
|
|
15
18
|
}
|
|
16
19
|
>()
|
|
17
20
|
|
|
@@ -87,13 +90,13 @@ export const flushMeasurePerf = () => {
|
|
|
87
90
|
})
|
|
88
91
|
|
|
89
92
|
const items = [
|
|
90
|
-
['Operation', 'Mesh', '
|
|
93
|
+
['Operation', 'Mesh', '', 'Timeline'],
|
|
91
94
|
...lines,
|
|
92
95
|
renderLine({
|
|
93
96
|
serverStart: 0,
|
|
94
97
|
requestStart: 0,
|
|
95
98
|
requestEnd: end,
|
|
96
|
-
additional: [
|
|
99
|
+
additional: ['Total time', `${end}ms`, ''],
|
|
97
100
|
colDivider,
|
|
98
101
|
}),
|
|
99
102
|
]
|
|
@@ -109,22 +112,20 @@ export const flushMeasurePerf = () => {
|
|
|
109
112
|
// padd the items to the max length
|
|
110
113
|
items.forEach((item) => {
|
|
111
114
|
item.forEach((_, index) => {
|
|
112
|
-
const [str] =
|
|
113
|
-
string,
|
|
114
|
-
string,
|
|
115
|
-
]
|
|
115
|
+
const [str] = Array.isArray(item[index]) ? item[index] : [item[index], item[index]]
|
|
116
116
|
|
|
117
|
-
const val =
|
|
117
|
+
const val = Array.isArray(item[index]) ? item[index][1] : item[index]
|
|
118
118
|
|
|
119
119
|
const padLength = colWidths[index] + (val.length - str.length)
|
|
120
120
|
|
|
121
|
-
item[index] = `${val.padEnd(padLength, ' ')}${index !== item.length - 1 ?
|
|
121
|
+
item[index] = `${val.padEnd(padLength, ' ')}${index !== item.length - 1 ? '' : ''}`
|
|
122
122
|
})
|
|
123
123
|
})
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
124
|
+
;[[''], ...items]
|
|
125
|
+
.map((item) => item.join(' '))
|
|
126
|
+
.forEach((item) => {
|
|
127
|
+
console.log(item)
|
|
128
|
+
})
|
|
128
129
|
|
|
129
130
|
running.clear()
|
|
130
131
|
}
|
|
@@ -156,44 +157,73 @@ export const measurePerformanceLink = new ApolloLink((operation, forward) => {
|
|
|
156
157
|
return forward(operation).map((data) => {
|
|
157
158
|
const httpDetails: MeshFetchHTTPInformation[] | undefined = data.extensions?.httpDetails
|
|
158
159
|
|
|
159
|
-
|
|
160
|
-
if (httpDetails) {
|
|
161
|
-
httpDetails.forEach((d) => {
|
|
162
|
-
const requestUrl = new URL(d.request.url)
|
|
163
|
-
requestUrl.searchParams.delete('extensions')
|
|
164
|
-
const title = `${d.sourceName} ${d.responseTime}ms`
|
|
165
|
-
additional = [
|
|
166
|
-
`${additional[0]} ${title}`,
|
|
167
|
-
`${additional[1]} ${cliHyperlink(title, requestUrl.toString().replace(/\+/g, '%20'))}`,
|
|
168
|
-
]
|
|
169
|
-
})
|
|
170
|
-
}
|
|
160
|
+
const additional = ['', ''] as [string, string]
|
|
171
161
|
|
|
172
162
|
// Called after server responds
|
|
173
163
|
const query = [
|
|
174
164
|
`# Variables: ${JSON.stringify(operation.variables)}`,
|
|
175
165
|
`# Headers: ${JSON.stringify(operation.getContext().headers)}`,
|
|
176
|
-
print(operation.query),
|
|
166
|
+
stripIgnoredCharacters(print(operation.query)),
|
|
177
167
|
].join('\n')
|
|
178
168
|
|
|
179
|
-
const meshUrl = new URL(
|
|
169
|
+
const meshUrl = new URL(
|
|
170
|
+
process.env.NODE_ENV === 'production'
|
|
171
|
+
? `${import.meta.graphCommerce.canonicalBaseUrl}/api/graphql`
|
|
172
|
+
: 'http://localhost:3000/api/graphql',
|
|
173
|
+
)
|
|
174
|
+
|
|
180
175
|
meshUrl.searchParams.set('query', query)
|
|
181
176
|
|
|
177
|
+
running.delete(operationString)
|
|
182
178
|
running.set(operationString, {
|
|
183
179
|
start: operation.getContext().measurePerformanceLinkStart as Date,
|
|
184
180
|
end: new Date(),
|
|
185
|
-
operationName: [
|
|
181
|
+
operationName: [
|
|
182
|
+
operation.operationName,
|
|
183
|
+
operation.operationName,
|
|
184
|
+
// cliHyperlink(operation.operationName, meshUrl.toString()),
|
|
185
|
+
],
|
|
186
186
|
additional,
|
|
187
|
-
|
|
188
|
-
// operation.operationName,
|
|
189
|
-
// cliHyperlink(operation.operationName, meshUrl.toString()),
|
|
190
|
-
// ],
|
|
191
|
-
// additional: [
|
|
192
|
-
// `🔗 ${additional[0]}`,
|
|
193
|
-
// `${cliHyperlink('🔗', meshUrl.toString())} ${additional[1]}`,
|
|
194
|
-
// ],
|
|
187
|
+
idx: 0,
|
|
195
188
|
})
|
|
196
189
|
|
|
190
|
+
if (httpDetails) {
|
|
191
|
+
running.forEach((_, key) => {
|
|
192
|
+
if (key.startsWith(operationString)) running.delete(key)
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
httpDetails.forEach((d) => {
|
|
196
|
+
const requestUrl = new URL(d.request.url)
|
|
197
|
+
requestUrl.searchParams.delete('extensions')
|
|
198
|
+
|
|
199
|
+
const sourceName =
|
|
200
|
+
!d.sourceName && URL.canParse(d.request.url)
|
|
201
|
+
? new URL(d.request.url).hostname
|
|
202
|
+
: d.sourceName
|
|
203
|
+
|
|
204
|
+
const key = `${operationString}.${responsePathAsArray(d.path).join('.')}`
|
|
205
|
+
const name = `${operation.operationName}.${responsePathAsArray(d.path).join('.')} (${sourceName})`
|
|
206
|
+
|
|
207
|
+
let start = new Date(d.request.timestamp)
|
|
208
|
+
let end = new Date(d.response.timestamp)
|
|
209
|
+
let operationName: [string, string] = [name, name]
|
|
210
|
+
let idx = 0
|
|
211
|
+
|
|
212
|
+
if (running.has(key)) {
|
|
213
|
+
// Get the earliest start time and latest end time.
|
|
214
|
+
const existing = running.get(key)
|
|
215
|
+
if (existing) {
|
|
216
|
+
idx = (existing.idx ?? 0) + 1
|
|
217
|
+
start = existing.start < start ? existing.start : start
|
|
218
|
+
end = existing?.end ? (existing.end > end ? existing.end : end) : end
|
|
219
|
+
operationName = [`${name} ⨉ ${idx}`, `${name} ⨉ ${idx}`]
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
running.set(key, { start, end, operationName, idx })
|
|
224
|
+
})
|
|
225
|
+
}
|
|
226
|
+
|
|
197
227
|
markTimeout()
|
|
198
228
|
|
|
199
229
|
return data
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { NormalizedCacheObject } from '@apollo/client'
|
|
2
|
+
import { InMemoryCache } from '@apollo/client'
|
|
2
3
|
|
|
3
4
|
function pruneKey(cacheValue: unknown, path: string[]) {
|
|
4
5
|
if (typeof cacheValue !== 'object' || cacheValue === null) return
|
|
@@ -26,6 +27,8 @@ export const persistenceMapper = (data: string): Promise<string> => {
|
|
|
26
27
|
|
|
27
28
|
pruneCache(parsedCache, [
|
|
28
29
|
'ROOT_MUTATION',
|
|
30
|
+
'ROOT_QUERY.attributesList',
|
|
31
|
+
'ROOT_QUERY.categories*',
|
|
29
32
|
'ROOT_QUERY.products*',
|
|
30
33
|
'ROOT_QUERY.countries',
|
|
31
34
|
'ROOT_QUERY.checkoutAgreements',
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/* eslint-disable import/no-extraneous-dependencies */
|
|
2
|
+
import { cssFlag, cssNotFlag, useIsSSR } from '@graphcommerce/next-ui'
|
|
3
|
+
import type { SkeletonOwnProps, SkeletonProps, SxProps, Theme } from '@mui/material'
|
|
4
|
+
import { Box, Skeleton } from '@mui/material'
|
|
5
|
+
import type { OverrideProps } from '@mui/material/OverridableComponent'
|
|
6
|
+
import React, { createContext, useContext, useMemo } from 'react'
|
|
7
|
+
|
|
8
|
+
type MaskProp = { skeleton?: SkeletonProps }
|
|
9
|
+
|
|
10
|
+
interface PrivateQueryMaskTypeMap<
|
|
11
|
+
AdditionalProps = MaskProp,
|
|
12
|
+
RootComponent extends React.ElementType = 'div',
|
|
13
|
+
> {
|
|
14
|
+
props: AdditionalProps & SkeletonOwnProps
|
|
15
|
+
defaultComponent: RootComponent
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type PrivateQueryMaskProps<
|
|
19
|
+
RootComponent extends React.ElementType = PrivateQueryMaskTypeMap['defaultComponent'],
|
|
20
|
+
AdditionalProps = MaskProp,
|
|
21
|
+
> = OverrideProps<PrivateQueryMaskTypeMap<AdditionalProps, RootComponent>, RootComponent> & {
|
|
22
|
+
component?: React.ElementType
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
type PrivateQueryMaskContextType = { mask: boolean }
|
|
26
|
+
|
|
27
|
+
export const PrivateQueryMaskContext = createContext<PrivateQueryMaskContextType | undefined>(
|
|
28
|
+
undefined,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
export function usePrivateQueryMask(): PrivateQueryMaskContextType {
|
|
32
|
+
const context = useContext(PrivateQueryMaskContext)
|
|
33
|
+
|
|
34
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
35
|
+
const isSSR = process.env.NODE_ENV === 'development' ? useIsSSR() : false
|
|
36
|
+
|
|
37
|
+
if (!context) {
|
|
38
|
+
if (isSSR)
|
|
39
|
+
console.warn(
|
|
40
|
+
"usePrivateQueryMask was used without a PrivateQueryMaskProvider, this means that customer specific pricing probably isn't working.",
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
return { mask: false }
|
|
44
|
+
}
|
|
45
|
+
return context
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function PrivateQueryMaskProvider(props: { mask: boolean; children: React.ReactNode }) {
|
|
49
|
+
const { mask = false, children } = props
|
|
50
|
+
return (
|
|
51
|
+
<PrivateQueryMaskContext.Provider value={useMemo(() => ({ mask }), [mask])}>
|
|
52
|
+
{children}
|
|
53
|
+
</PrivateQueryMaskContext.Provider>
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function usePrivateQueryMaskSx(props: { sx?: SxProps<Theme>; skeleton?: SkeletonProps }) {
|
|
58
|
+
const { sx = [], skeleton } = props
|
|
59
|
+
const { mask } = usePrivateQueryMask()
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
mask,
|
|
63
|
+
componentSx: [
|
|
64
|
+
mask && {
|
|
65
|
+
[cssFlag('private-query')]: { display: 'none' },
|
|
66
|
+
},
|
|
67
|
+
...(Array.isArray(sx) ? sx : [sx]),
|
|
68
|
+
],
|
|
69
|
+
maskSx: [
|
|
70
|
+
{
|
|
71
|
+
display: 'inline-block',
|
|
72
|
+
[cssNotFlag('private-query')]: { display: 'none' },
|
|
73
|
+
},
|
|
74
|
+
...(Array.isArray(sx) ? sx : [sx]),
|
|
75
|
+
...(Array.isArray(skeleton?.sx) ? skeleton.sx : [skeleton?.sx]),
|
|
76
|
+
],
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** A component that renders a skeleton mask when the user is signed in. */
|
|
81
|
+
export function PrivateQueryMask(props: PrivateQueryMaskProps) {
|
|
82
|
+
const { skeleton, children, ...rest } = props
|
|
83
|
+
const { mask, componentSx, maskSx } = usePrivateQueryMaskSx(props)
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<>
|
|
87
|
+
<Box {...rest} sx={componentSx}>
|
|
88
|
+
{children}
|
|
89
|
+
</Box>
|
|
90
|
+
{mask && <Skeleton {...rest} {...skeleton} sx={maskSx} />}
|
|
91
|
+
</>
|
|
92
|
+
)
|
|
93
|
+
}
|
package/config.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { ApolloLink, TypePolicies } from '@apollo/client'
|
|
2
1
|
import type { GraphCommerceStorefrontConfig } from '@graphcommerce/next-config'
|
|
2
|
+
import type { ApolloLink, TypePolicies } from '@apollo/client'
|
|
3
3
|
import type { SetRequired } from 'type-fest'
|
|
4
|
-
import { MigrateCache } from './components/GraphQLProvider/migrateCache'
|
|
4
|
+
import type { MigrateCache } from './components/GraphQLProvider/migrateCache'
|
|
5
|
+
import { RemovePrivateContextDirectivesLink } from './link/RemovePrivateContextDirectivesLink'
|
|
5
6
|
|
|
6
7
|
export interface PreviewData {}
|
|
7
8
|
|
|
@@ -34,5 +35,11 @@ export type ApolloClientConfig = SetRequired<
|
|
|
34
35
|
|
|
35
36
|
export function graphqlConfig(config: ApolloClientConfigInput): ApolloClientConfig {
|
|
36
37
|
const { storefront, links = [], policies = [], migrations = [], ...rest } = config
|
|
37
|
-
return {
|
|
38
|
+
return {
|
|
39
|
+
storefront,
|
|
40
|
+
links: [...links, new RemovePrivateContextDirectivesLink()],
|
|
41
|
+
policies,
|
|
42
|
+
migrations,
|
|
43
|
+
...rest,
|
|
44
|
+
}
|
|
38
45
|
}
|
|
@@ -1,50 +1,54 @@
|
|
|
1
|
-
import type { InputMaybe,
|
|
1
|
+
import type { InputMaybe, PrivateContext } from '@graphcommerce/graphql-mesh'
|
|
2
2
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
3
3
|
import { useIsSSR } from '@graphcommerce/next-ui/hooks/useIsSsr'
|
|
4
4
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
5
5
|
import { getCssFlag, removeCssFlag, setCssFlag } from '@graphcommerce/next-ui/utils/cssFlags'
|
|
6
6
|
import { useContext, useEffect } from 'react'
|
|
7
|
-
import { QueryHookOptions, QueryResult, TypedDocumentNode
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
7
|
+
import type { MaybeMasked, QueryHookOptions, QueryResult, TypedDocumentNode } from '../apollo'
|
|
8
|
+
import { useQuery } from '../apollo'
|
|
9
|
+
import { PrivateQueryMaskContext } from '../components/PrivateQueryMask/PrivateQueryMask'
|
|
10
|
+
import { usePrivateQueryContext } from './usePrivateQueryContext'
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
|
-
* Creates a query that allows fetching data for logged in customers
|
|
13
|
-
* a fallback for guest users.
|
|
13
|
+
* Creates a query that allows fetching data for logged in customers (or other private contexts),
|
|
14
|
+
* but have a fallback for guest users.
|
|
14
15
|
*
|
|
15
16
|
* - Shows a global pageload indicator when loading customer specific information.
|
|
16
17
|
*
|
|
17
18
|
* When not to use this?
|
|
18
|
-
*
|
|
19
|
+
*
|
|
20
|
+
* - When a query is always scoped. This method specifically targets queries that can resolve unscoped
|
|
21
|
+
* (guest) and both scoped (customer) data.
|
|
19
22
|
*
|
|
20
23
|
* Usage:
|
|
21
|
-
*
|
|
22
|
-
* -
|
|
24
|
+
*
|
|
25
|
+
* - Define a `@privateContext(context: $context)` directive in your query
|
|
26
|
+
* - Use the usePrivateQuery
|
|
23
27
|
*/
|
|
24
|
-
export function
|
|
28
|
+
export function usePrivateQuery<
|
|
25
29
|
Q,
|
|
26
|
-
V extends { context?: InputMaybe<
|
|
30
|
+
V extends { context?: InputMaybe<PrivateContext>; [index: string]: unknown },
|
|
27
31
|
>(
|
|
28
32
|
document: TypedDocumentNode<Q, V>,
|
|
29
33
|
options: QueryHookOptions<Q, V>,
|
|
30
34
|
unscopedResult: Q,
|
|
31
|
-
): Omit<QueryResult<Q, V>, 'data'> & { data: Q
|
|
35
|
+
): Omit<QueryResult<Q, V>, 'data'> & { data: Q | NonNullable<MaybeMasked<Q>>; mask: boolean } {
|
|
32
36
|
const { skip = true } = options
|
|
33
|
-
const context =
|
|
37
|
+
const context = usePrivateQueryContext()
|
|
34
38
|
const isSsr = useIsSSR()
|
|
35
39
|
|
|
36
|
-
const
|
|
40
|
+
const privateContext = useContext(PrivateQueryMaskContext)
|
|
37
41
|
|
|
38
42
|
useEffect(() => {
|
|
39
43
|
if (isSsr) return
|
|
40
|
-
if (context && !getCssFlag('
|
|
41
|
-
else if (!context && getCssFlag('
|
|
44
|
+
if (context && !getCssFlag('private-query')) setCssFlag('private-query', true)
|
|
45
|
+
else if (!context && getCssFlag('private-query')) removeCssFlag('private-query')
|
|
42
46
|
}, [context, isSsr])
|
|
43
47
|
|
|
44
48
|
const clientQuery = useQuery<Q, V>(document, {
|
|
45
49
|
...options,
|
|
46
50
|
variables: { ...options.variables, context } as V,
|
|
47
|
-
skip: !!
|
|
51
|
+
skip: !!privateContext || (skip && !context),
|
|
48
52
|
})
|
|
49
53
|
|
|
50
54
|
let { data } = clientQuery
|
|
@@ -56,9 +60,9 @@ export function useInContextQuery<
|
|
|
56
60
|
mask = !skip ? !clientQuery.data && !clientQuery.previousData : !clientQuery.data
|
|
57
61
|
}
|
|
58
62
|
|
|
59
|
-
// If this method is called within an
|
|
60
|
-
if (
|
|
61
|
-
mask =
|
|
63
|
+
// If this method is called within an PrivateQueryMask, we skip this complete functionality so we show the parent mask.
|
|
64
|
+
if (privateContext) {
|
|
65
|
+
mask = privateContext.mask
|
|
62
66
|
}
|
|
63
67
|
|
|
64
68
|
return { ...clientQuery, data: data ?? unscopedResult, mask }
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { PrivateContext } from '@graphcommerce/graphql-mesh'
|
|
2
|
+
import type { ApolloClient } from '@apollo/client'
|
|
3
|
+
|
|
4
|
+
export function getPrivateQueryContext(
|
|
5
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
|
|
6
|
+
client: ApolloClient<any>,
|
|
7
|
+
): PrivateContext | null {
|
|
8
|
+
return null
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Defines a method to handle the current context for the query.
|
|
13
|
+
*
|
|
14
|
+
* Other plugins should be able to define their own scopes and create a plugin on this method to augment the specific scope.
|
|
15
|
+
*
|
|
16
|
+
* @see @graphcommerce/magento-customer/plugins/magentoCustomerGetInContext.ts
|
|
17
|
+
*
|
|
18
|
+
* Note: ONLY return a value if the frontend should use the privateContext directive.
|
|
19
|
+
*/
|
|
20
|
+
export const usePrivateQueryContext = (): PrivateContext | null => null
|
package/index.ts
CHANGED
|
@@ -5,6 +5,6 @@ export * from './generated/types'
|
|
|
5
5
|
export * from './config'
|
|
6
6
|
export * from './utils/getPreviewData'
|
|
7
7
|
export * from './utils/cachePolicy'
|
|
8
|
-
export * from './components/
|
|
9
|
-
export * from './hooks/
|
|
10
|
-
export * from './hooks/
|
|
8
|
+
export * from './components/PrivateQueryMask/PrivateQueryMask'
|
|
9
|
+
export * from './hooks/usePrivateQueryContext'
|
|
10
|
+
export * from './hooks/usePrivateQuery'
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { NextLink, Operation } from '../apollo'
|
|
2
|
+
import { ApolloLink, removeArgumentsFromDocument, removeDirectivesFromDocument } from '../apollo'
|
|
3
|
+
|
|
4
|
+
export class RemovePrivateContextDirectivesLink extends ApolloLink {
|
|
5
|
+
// eslint-disable-next-line class-methods-use-this
|
|
6
|
+
request(operation: Operation, forward?: NextLink) {
|
|
7
|
+
if (!forward) throw new Error('This is not a terminal link!')
|
|
8
|
+
|
|
9
|
+
let modifiedQuery = operation.query
|
|
10
|
+
|
|
11
|
+
modifiedQuery =
|
|
12
|
+
removeDirectivesFromDocument([{ name: 'privateContext' }], modifiedQuery) ?? modifiedQuery
|
|
13
|
+
modifiedQuery =
|
|
14
|
+
removeArgumentsFromDocument([{ name: 'context' }], modifiedQuery) ?? modifiedQuery
|
|
15
|
+
|
|
16
|
+
operation.query = modifiedQuery
|
|
17
|
+
return forward(operation)
|
|
18
|
+
}
|
|
19
|
+
}
|
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": "9.0.
|
|
5
|
+
"version": "9.0.1-canary.0",
|
|
6
6
|
"sideEffects": false,
|
|
7
7
|
"main": "index.ts",
|
|
8
8
|
"prettier": "@graphcommerce/prettier-config-pwa",
|
|
@@ -13,25 +13,26 @@
|
|
|
13
13
|
}
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@
|
|
17
|
-
"@graphcommerce/graphql-codegen-relay-optimizer-plugin": "9.0.0-canary.99",
|
|
16
|
+
"@apollo/client": "~3.12.3",
|
|
18
17
|
"@graphql-codegen/add": "5.0.3",
|
|
19
18
|
"@graphql-codegen/fragment-matcher": "5.0.2",
|
|
20
19
|
"@graphql-codegen/introspection": "4.0.3",
|
|
21
20
|
"@graphql-codegen/schema-ast": "4.1.0",
|
|
22
|
-
"@graphql-codegen/typed-document-node": "5.0.
|
|
23
|
-
"@graphql-codegen/typescript": "4.
|
|
24
|
-
"@graphql-codegen/typescript-apollo-client-helpers": "
|
|
25
|
-
"@graphql-codegen/typescript-document-nodes": "4.0.
|
|
26
|
-
"@graphql-codegen/typescript-operations": "4.
|
|
27
|
-
"apollo3-cache-persist": "^0.15.0"
|
|
21
|
+
"@graphql-codegen/typed-document-node": "5.0.12",
|
|
22
|
+
"@graphql-codegen/typescript": "4.1.2",
|
|
23
|
+
"@graphql-codegen/typescript-apollo-client-helpers": "3.0.0",
|
|
24
|
+
"@graphql-codegen/typescript-document-nodes": "4.0.12",
|
|
25
|
+
"@graphql-codegen/typescript-operations": "4.4.0",
|
|
26
|
+
"apollo3-cache-persist": "^0.15.0",
|
|
27
|
+
"graphql": "^16.10.0"
|
|
28
28
|
},
|
|
29
29
|
"peerDependencies": {
|
|
30
|
-
"@
|
|
31
|
-
"@graphcommerce/
|
|
32
|
-
"@graphcommerce/
|
|
33
|
-
"@graphcommerce/
|
|
34
|
-
"
|
|
30
|
+
"@graphcommerce/eslint-config-pwa": "^9.0.1-canary.0",
|
|
31
|
+
"@graphcommerce/graphql-codegen-near-operation-file": "9.0.1-canary.0",
|
|
32
|
+
"@graphcommerce/graphql-codegen-relay-optimizer-plugin": "9.0.1-canary.0",
|
|
33
|
+
"@graphcommerce/prettier-config-pwa": "^9.0.1-canary.0",
|
|
34
|
+
"@graphcommerce/typescript-config-pwa": "^9.0.1-canary.0",
|
|
35
|
+
"@graphql-mesh/plugin-http-details-extensions": "*",
|
|
35
36
|
"react": "^18.2.0",
|
|
36
37
|
"react-dom": "^18.2.0"
|
|
37
38
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"""
|
|
2
2
|
By providing these values the Apollo Client cache will automatically scope the cache to these specific values.
|
|
3
3
|
|
|
4
|
-
If a
|
|
4
|
+
If a PrivateContext is provided
|
|
5
5
|
"""
|
|
6
|
-
input
|
|
6
|
+
input PrivateContext {
|
|
7
7
|
loggedIn: Boolean
|
|
8
8
|
|
|
9
9
|
# storeCode: String
|
|
@@ -13,4 +13,4 @@ input InContextInput {
|
|
|
13
13
|
"""
|
|
14
14
|
Certain values are only available in the context of a session and need to have the cache scoped to these values.
|
|
15
15
|
"""
|
|
16
|
-
directive @
|
|
16
|
+
directive @privateContext(context: PrivateContext) on FIELD
|
package/tsconfig.json
CHANGED
package/utils/cachePolicy.ts
CHANGED
package/utils/getPreviewData.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { ApolloClient } from '@apollo/client'
|
|
2
|
-
import { PreviewConfig } from '../config'
|
|
1
|
+
import type { ApolloClient } from '@apollo/client'
|
|
2
|
+
import type { PreviewConfig } from '../config'
|
|
3
3
|
|
|
4
4
|
export function getPreviewData(client: ApolloClient<object>): PreviewConfig | undefined {
|
|
5
5
|
if ('preview' in client.defaultOptions) return client.defaultOptions.preview as PreviewConfig
|