@clicktap/ui 2.1.3 → 2.2.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/components/ModalOverlay/ModalOverlay.d.ts +1 -1
- package/components/ModalOverlay/ModalOverlay.js +1 -1
- package/docs/migrations/2026/09/ui-2026-09-15-server-request-headers-provider.md +52 -0
- package/docs/migrations/2026/09/ui-2026-09-16-modal-overlay-detached-motion-element.md +71 -0
- package/docs/migrations/index.json +20 -0
- package/package.json +1 -1
- package/utils/createAuthAwareGraphqlClient.d.ts +18 -0
- package/utils/createAuthAwareGraphqlClient.js +1 -1
|
@@ -19,5 +19,5 @@ import { ModalOverlayProps } from './ModalOverlay.types';
|
|
|
19
19
|
* Exported for unit tests only.
|
|
20
20
|
*/
|
|
21
21
|
export declare function useOverlayAnimationState(overlayState: OverlayTriggerState | null | undefined): [DriverAnimationState, Dispatch<SetStateAction<DriverAnimationState>>];
|
|
22
|
-
export declare function ModalOverlay(props: ModalOverlayProps): import("react/jsx-runtime").JSX.Element;
|
|
22
|
+
export declare function ModalOverlay(props: ModalOverlayProps): import("react/jsx-runtime").JSX.Element | null;
|
|
23
23
|
export default ModalOverlay;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use client";import{jsx as a}from"react/jsx-runtime";import{forwardRef as N,useContext as M,useState as k,useEffect as A,useId as R,useCallback as O}from"react";import{ModalOverlay as E,OverlayTriggerStateContext as w}from"react-aria-components";import{motion as T,AnimatePresence as F}from"framer-motion";import{cn as j}from"../../utils/cn.js";const q="body > [inert]:not(#__next):not([data-overlay-container])";function z(e){return Object.keys(e).some(t=>t.startsWith("__react"))}function p(){document.querySelectorAll(q).forEach(e=>e.removeAttribute("inert"))}function L(e){return!!(e.closest("[data-overlay-container]")||e.closest("#__next"))}const W=N(({style:e,children:t,...i},n)=>{const r=typeof e=="function"?e(i):e;return a(E,{...i,ref:n,style:r,children:t})});let m=null;function $(){return typeof window>"u"?null:(m||(m=T.create(W)),m)}const h=$();function g({animate:e,animation:t,setAnimation:i,className:n,animationVariants:r,isDismissable:u=!0,children:v,...x}){const C=R();A(()=>{p();const o=new MutationObserver(l=>{for(const d of l)if(d.type==="attributes"&&d.attributeName==="inert"){const f=d.target;f.parentElement===document.body?p():z(f)||f.removeAttribute("inert")}});return o.observe(document.body,{attributes:!0,attributeFilter:["inert"],subtree:!0}),()=>o.disconnect()},[]);const{key:S,shouldCloseOnInteractOutside:c,onOpenChange:s,..._}=x,P=O(o=>c?c(o):L(o),[c]),y=M(w),I=O(o=>{!u||o.target!==o.currentTarget||(s?s(!1):y?.close())},[u,s,y]),b={isDismissable:u,shouldCloseOnInteractOutside:P,onOpenChange:s,onPointerDown:I,className:j("bg-black/30 backdrop-blur-sm","fixed top-0 left-0","z-[1000]","w-screen h-[var(--visual-viewport-height)]",n),..._};return h?a(h,{isExiting:t==="hidden",onAnimationComplete:o=>{i(l=>o==="hidden"&&l==="hidden"?"unmounted":l)},variants:r||{hidden:{opacity:0,transition:{delay:.08}},visible:{opacity:1}},initial:"hidden",animate:e,exit:"hidden",...b,children:v},S||C):a(E,{...b,children:v})}function B(e){const t=e?.isOpen??!1,[i,n]=k(t?"visible":"unmounted");return A(()=>{n(t?"visible":r=>r==="visible"?"hidden":r)},[t]),[i,n]}function Q(e){const{isOpen:t}=e,i=M(w),[n,r]=B(i);return t!==void 0?a(F,{children:t&&a(g,{...e,animate:"visible",animation:n,setAnimation:r})}):a(g,{...e,animate:n,animation:n,setAnimation:r})}export{Q as ModalOverlay,Q as default,B as useOverlayAnimationState};
|
|
1
|
+
"use client";import{jsx as a}from"react/jsx-runtime";import{forwardRef as N,useContext as M,useState as k,useEffect as A,useId as R,useCallback as O}from"react";import{ModalOverlay as E,OverlayTriggerStateContext as w}from"react-aria-components";import{motion as T,AnimatePresence as F}from"framer-motion";import{cn as j}from"../../utils/cn.js";const q="body > [inert]:not(#__next):not([data-overlay-container])";function z(e){return Object.keys(e).some(t=>t.startsWith("__react"))}function p(){document.querySelectorAll(q).forEach(e=>e.removeAttribute("inert"))}function L(e){return!!(e.closest("[data-overlay-container]")||e.closest("#__next"))}const W=N(({style:e,children:t,...i},n)=>{const r=typeof e=="function"?e(i):e;return a(E,{...i,ref:n,style:r,children:t})});let m=null;function $(){return typeof window>"u"?null:(m||(m=T.create(W)),m)}const h=$();function g({animate:e,animation:t,setAnimation:i,className:n,animationVariants:r,isDismissable:u=!0,children:v,...x}){const C=R();A(()=>{p();const o=new MutationObserver(l=>{for(const d of l)if(d.type==="attributes"&&d.attributeName==="inert"){const f=d.target;f.parentElement===document.body?p():z(f)||f.removeAttribute("inert")}});return o.observe(document.body,{attributes:!0,attributeFilter:["inert"],subtree:!0}),()=>o.disconnect()},[]);const{key:S,shouldCloseOnInteractOutside:c,onOpenChange:s,..._}=x,P=O(o=>c?c(o):L(o),[c]),y=M(w),I=O(o=>{!u||o.target!==o.currentTarget||(s?s(!1):y?.close())},[u,s,y]),b={isDismissable:u,shouldCloseOnInteractOutside:P,onOpenChange:s,onPointerDown:I,className:j("bg-black/30 backdrop-blur-sm","fixed top-0 left-0","z-[1000]","w-screen h-[var(--visual-viewport-height)]",n),..._};return h?a(h,{isExiting:t==="hidden",onAnimationComplete:o=>{i(l=>o==="hidden"&&l==="hidden"?"unmounted":l)},variants:r||{hidden:{opacity:0,transition:{delay:.08}},visible:{opacity:1}},initial:"hidden",animate:e,exit:"hidden",...b,children:v},S||C):a(E,{...b,children:v})}function B(e){const t=e?.isOpen??!1,[i,n]=k(t?"visible":"unmounted");return A(()=>{n(t?"visible":r=>r==="visible"?"hidden":r)},[t]),[i,n]}function Q(e){const{isOpen:t}=e,i=M(w),[n,r]=B(i);return t!==void 0?a(F,{children:t&&a(g,{...e,animate:"visible",animation:n,setAnimation:r})}):n==="unmounted"?null:a(g,{...e,animate:n,animation:n,setAnimation:r})}export{Q as ModalOverlay,Q as default,B as useOverlayAnimationState};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# `createAuthAwareGraphqlClient`: a server-side request-headers provider, so SSR calls can carry the shopper
|
|
2
|
+
|
|
3
|
+
**Date:** 2026-09-15
|
|
4
|
+
**Package:** `@clicktap/ui`
|
|
5
|
+
**Affects:** `utils/createAuthAwareGraphqlClient` — new export `setServerRequestHeadersProvider()`; `executeGraphql` and `createAuthAwareFetch` consult it server-side
|
|
6
|
+
**Classification:** FEATURE
|
|
7
|
+
**Breaking:** No. Nothing changes until a provider is registered; explicit request headers always win over provided ones.
|
|
8
|
+
**Ticket:** CTAP-1719
|
|
9
|
+
|
|
10
|
+
## Why
|
|
11
|
+
|
|
12
|
+
A request the Node server makes to the middleware on a shopper's behalf (SSR, route handlers)
|
|
13
|
+
carried nothing identifying the shopper — the middleware saw the Node container for every visitor.
|
|
14
|
+
Anything keyed on the client there (a rate limit, a lockout) would put the whole storefront in one
|
|
15
|
+
bucket.
|
|
16
|
+
|
|
17
|
+
The fix has to be in the shared client, because that is the one place every server-side request
|
|
18
|
+
passes through — `graphqlRequest`, the quote/checkout transports (`request` and `createFetch`), route
|
|
19
|
+
handlers. But this module is framework-agnostic and reachable from client components, so it cannot
|
|
20
|
+
import `next/headers` itself. Hence a **provider** the app registers:
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import { setServerRequestHeadersProvider } from '@clicktap/ui/utils/createAuthAwareGraphqlClient';
|
|
24
|
+
|
|
25
|
+
setServerRequestHeadersProvider(() => {
|
|
26
|
+
const forwardedFor = headers().get('x-forwarded-for'); // next/headers, in a server-only module
|
|
27
|
+
return forwardedFor ? { 'x-forwarded-for': forwardedFor } : undefined;
|
|
28
|
+
});
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Rules
|
|
32
|
+
|
|
33
|
+
- Consulted **only server-side** (`typeof window === 'undefined'`); the browser path is untouched.
|
|
34
|
+
- **Explicit headers win.** A header the caller passed is never overwritten by the provider.
|
|
35
|
+
- **A throwing provider contributes nothing.** `headers()` throws outside a request scope (a static
|
|
36
|
+
render, a warm-up); that is "nothing to forward", not a failed request.
|
|
37
|
+
- Sync or async providers are accepted.
|
|
38
|
+
- Applies to both `createAuthAwareGraphqlClient()` requests and `createAuthAwareFetch()` calls.
|
|
39
|
+
|
|
40
|
+
## What a consumer does
|
|
41
|
+
|
|
42
|
+
Register a provider from a **server-only** module (`import 'server-only'` + `next/headers`), imported
|
|
43
|
+
by the root layouts and by any route handler that fetches — see the `frontend` guide of the same
|
|
44
|
+
date for the reference module. Forward the incoming `X-Forwarded-For` **verbatim**: the middleware's
|
|
45
|
+
trusted-proxy walk reads it from its own end, so the browser's forged entries are inert and
|
|
46
|
+
"cleaning" the chain here would only remove information.
|
|
47
|
+
|
|
48
|
+
## Verification
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
nx run ui:test -- createAuthAwareGraphqlClient
|
|
52
|
+
```
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# BUGFIX: a closed trigger-driven `ModalOverlay` no longer keeps a detached framer-motion element mounted
|
|
2
|
+
|
|
3
|
+
**Date:** 2026-09-16
|
|
4
|
+
**Package:** `@clicktap/ui`
|
|
5
|
+
**Affects:** `src/components/ModalOverlay/ModalOverlay.tsx` — every trigger-driven `<ModalOverlay>` (no `isOpen` prop, state from `DialogTrigger` / `OverlayTriggerStateContext`). Controlled overlays (`isOpen` given) are unchanged.
|
|
6
|
+
**Classification:** BUGFIX
|
|
7
|
+
**Breaking:** No API change. **Behavioral:** while its trigger is closed and has never opened, the overlay now renders `null` instead of a mounted `motion()` wrapper around an empty react-aria overlay. Nothing consumer-visible was in the DOM before either.
|
|
8
|
+
**Ticket:** CTAP-1729
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
`ModalOverlay` has two render paths. The controlled one wraps `InnerModalOverlay` in
|
|
13
|
+
`AnimatePresence` and mounts it only while `isOpen`. The **trigger-driven** one mounted
|
|
14
|
+
`InnerModalOverlay` **unconditionally** — including while `useOverlayAnimationState` reported
|
|
15
|
+
`unmounted`. In that state react-aria's `ModalOverlay` renders nothing (closed, not exiting), but
|
|
16
|
+
our `motion.create(...)` wrapper was still mounted around it: a framer-motion `VisualElement` with
|
|
17
|
+
`element.current === null`, holding `initial="hidden"`, `exit="hidden"` and `animate="unmounted"`
|
|
18
|
+
(which names no variant), for as long as the trigger stayed shut.
|
|
19
|
+
|
|
20
|
+
Any animation that reached that element crashed framer-motion 11.18.2 with
|
|
21
|
+
`TypeError: Cannot read properties of null (reading '0')`: the animation seeds `[null, target]`
|
|
22
|
+
and expects the DOM read to hydrate the first keyframe; `DOMKeyframesResolver` bails on a missing
|
|
23
|
+
element, so `null` survives into `mix(null, 0)` → `mixObject(null, '0')`. Two routes in, both
|
|
24
|
+
observed:
|
|
25
|
+
|
|
26
|
+
1. **An ancestor `AnimatePresence` exits** and propagates `exit="hidden"` down the subtree.
|
|
27
|
+
2. **The subtree unmounts** — `VisualElement.unmount` stops the motion values, which flushes
|
|
28
|
+
keyframe resolvers synchronously on the detached element.
|
|
29
|
+
|
|
30
|
+
🚨 Thrown inside framer-motion's frame loop, so nothing reaches server logs and no React error
|
|
31
|
+
boundary catches it. noco hit it in the warranty admin: a controlled drawer (route 1) containing a
|
|
32
|
+
never-opened trigger-driven Page Builder overlay threw **13 errors on every close**, and in
|
|
33
|
+
production that broke the frame loop — the close animation and the success toast were both lost.
|
|
34
|
+
Every other trigger-driven overlay in that app (delete confirms, edit modals) carried the same
|
|
35
|
+
detached element and only stayed quiet because nothing above them animated on exit yet.
|
|
36
|
+
|
|
37
|
+
## What changed
|
|
38
|
+
|
|
39
|
+
The trigger-driven path returns `null` while `animation === 'unmounted'` — exactly the DOM
|
|
40
|
+
react-aria was already producing, minus the motion element. The lifecycle the controlled path
|
|
41
|
+
already gets from `AnimatePresence` is now the same on both paths:
|
|
42
|
+
|
|
43
|
+
| trigger | `animation` | rendered |
|
|
44
|
+
|---|---|---|
|
|
45
|
+
| closed, never opened | `unmounted` | `null` |
|
|
46
|
+
| opens | `visible` | `InnerModalOverlay` mounts with `initial="hidden"` → `animate="visible"` (entrance plays) |
|
|
47
|
+
| closes while visible | `hidden` | stays mounted; `isExiting` keeps react-aria's portal alive while the exit plays |
|
|
48
|
+
| exit completes | `unmounted` (via `onAnimationComplete`) | `null` again |
|
|
49
|
+
|
|
50
|
+
## Consumer impact
|
|
51
|
+
|
|
52
|
+
- **Nothing to change.** Take the release. If you gated the overlay yourself in a wrapper (noco
|
|
53
|
+
tried it in `@noco/ui`), **remove that gate**: mounting only once already open, with the hook
|
|
54
|
+
initialized to `visible`, gives framer-motion no transition to play and leaves the backdrop stuck
|
|
55
|
+
at `opacity: 0`. The fix has to live where the animation state is derived.
|
|
56
|
+
- ⚠️ Do not read the mount as a signal. Nothing should depend on a closed trigger-driven overlay
|
|
57
|
+
having a React subtree — refs into `InnerModalOverlay` are `null` while closed now, as they
|
|
58
|
+
effectively were before (react-aria rendered no node).
|
|
59
|
+
|
|
60
|
+
## Verification
|
|
61
|
+
|
|
62
|
+
Unit: `ModalOverlay.mount.spec.tsx` mocks `motion.create` to record every render of the wrapper and
|
|
63
|
+
pins the contract — no motion render while closed and never opened; first render on open is
|
|
64
|
+
`initial: 'hidden', animate: 'visible'`; on close the last render is `animate: 'hidden', isExiting:
|
|
65
|
+
true`; after `onAnimationComplete('hidden')` no further renders, including on unrelated re-renders.
|
|
66
|
+
Controlled path covered for parity.
|
|
67
|
+
|
|
68
|
+
Manual, in any app: open a controlled `ModalOverlay` (a drawer) that contains a `DialogTrigger`
|
|
69
|
+
whose `ModalOverlay` you never open; close the drawer. Before: `Cannot read properties of null
|
|
70
|
+
(reading '0')` in the console, one per animated value. After: nothing. Then open the inner overlay
|
|
71
|
+
once and close both — entrance and exit both play.
|
|
@@ -1,4 +1,24 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"path": "2026/09/ui-2026-09-16-modal-overlay-detached-motion-element.md",
|
|
4
|
+
"module": "libs/frontend-modules/ui",
|
|
5
|
+
"package": "@clicktap/ui",
|
|
6
|
+
"date": "2026-09-16",
|
|
7
|
+
"title": "BUGFIX: a closed trigger-driven `ModalOverlay` no longer keeps a detached framer-motion element mounted",
|
|
8
|
+
"classification": "BUGFIX",
|
|
9
|
+
"breaking": "No API change. **Behavioral:** while its trigger is closed and has never opened, the overlay now renders `null` instead of a mounted `motion()` wrapper around an empty react-aria overlay. Nothing consumer-visible was in the DOM before either.",
|
|
10
|
+
"affects": "`src/components/ModalOverlay/ModalOverlay.tsx` — every trigger-driven `<ModalOverlay>` (no `isOpen` prop, state from `DialogTrigger` / `OverlayTriggerStateContext`). Controlled overlays (`isOpen` given) are unchanged."
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"path": "2026/09/ui-2026-09-15-server-request-headers-provider.md",
|
|
14
|
+
"module": "libs/frontend-modules/ui",
|
|
15
|
+
"package": "@clicktap/ui",
|
|
16
|
+
"date": "2026-09-15",
|
|
17
|
+
"title": "`createAuthAwareGraphqlClient`: a server-side request-headers provider, so SSR calls can carry the shopper",
|
|
18
|
+
"classification": "FEATURE",
|
|
19
|
+
"breaking": "No. Nothing changes until a provider is registered; explicit request headers always win over provided ones.",
|
|
20
|
+
"affects": "`utils/createAuthAwareGraphqlClient` — new export `setServerRequestHeadersProvider()`; `executeGraphql` and `createAuthAwareFetch` consult it server-side"
|
|
21
|
+
},
|
|
2
22
|
{
|
|
3
23
|
"path": "2026/09/ui-2026-09-09-graphql-field-errors-reported-in-production.md",
|
|
4
24
|
"module": "libs/frontend-modules/ui",
|
package/package.json
CHANGED
|
@@ -111,6 +111,24 @@ declare class AuthAwareEventBus {
|
|
|
111
111
|
* listen, regardless of which call triggered the event).
|
|
112
112
|
*/
|
|
113
113
|
export declare const authAwareEvents: AuthAwareEventBus;
|
|
114
|
+
/**
|
|
115
|
+
* Headers a SERVER-SIDE caller wants attached to every outbound request, resolved per request.
|
|
116
|
+
*
|
|
117
|
+
* CTAP-1719: a request the Node server makes to the middleware on a shopper's behalf (SSR, route
|
|
118
|
+
* handlers) carried no trace of the shopper — the middleware saw the Node container's address for
|
|
119
|
+
* every visitor, so anything keyed on the client (a rate limit, a lockout) would put the whole
|
|
120
|
+
* storefront in one bucket. The app registers a provider that reads the incoming request's
|
|
121
|
+
* `X-Forwarded-For` (via `next/headers`, which only exists in a request scope) and returns it here,
|
|
122
|
+
* so the middleware's trusted-proxy walk lands on the browser rather than on Node.
|
|
123
|
+
*
|
|
124
|
+
* Registered rather than imported because this module must stay framework-agnostic and bundleable
|
|
125
|
+
* on the client: `next/headers` cannot be imported from anything a client component reaches. The
|
|
126
|
+
* provider is only ever consulted server-side (`typeof window === 'undefined'`); explicit request
|
|
127
|
+
* headers always win over what it returns; and a provider that throws (no request scope — a build-time
|
|
128
|
+
* render, a cron) contributes nothing rather than failing the request.
|
|
129
|
+
*/
|
|
130
|
+
export type ServerRequestHeadersProvider = () => HeadersInit | undefined | Promise<HeadersInit | undefined>;
|
|
131
|
+
export declare function setServerRequestHeadersProvider(provider: ServerRequestHeadersProvider | undefined): void;
|
|
114
132
|
export type AuthAwareGraphqlRequest = <TData, TVariables extends GraphqlVariables = GraphqlVariables>(url: string, document: GraphqlDocument<TData, TVariables>, variables?: TVariables, requestHeaders?: HeadersInit) => Promise<TData>;
|
|
115
133
|
export type AuthAwareGraphqlRequestWithOptions = <TData, TVariables extends GraphqlVariables = GraphqlVariables>(options: {
|
|
116
134
|
url: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
class l extends Error{constructor(t,s){const e=t.errors?.[0]?.message;super(`GraphQL request failed: ${e??`HTTP ${t.status}`}`),this.name="ClientError",this.response=t,this.request=s}}class
|
|
1
|
+
class l extends Error{constructor(t,s){const e=t.errors?.[0]?.message;super(`GraphQL request failed: ${e??`HTTP ${t.status}`}`),this.name="ClientError",this.response=t,this.request=s}}class v{constructor(){this.listeners=new Map}subscribe(t,s){return this.listeners.has(t)||this.listeners.set(t,new Set),this.listeners.get(t).add(s),()=>{this.listeners.get(t)?.delete(s)}}dispatch(t,s){this.listeners.get(t)?.forEach(e=>e(s))}}const u=new v;function p(r,t){r===401?u.dispatch("unauthenticated",t):r===403?u.dispatch("permissionDenied",t):r>=500&&u.dispatch("serverError",t)}function g(r,t,s){if(!s.length)return;const e=r.match(/^\s*(?:query|mutation|subscription)\s+([A-Za-z_][A-Za-z0-9_]*)/)?.[1];for(const n of s){const a=n.path?.join(".")??"unknown",o=`[GraphQL] ${e??"operation"} — field error at ${a}: ${n.message}`;if(process.env.NODE_ENV==="development"){console.error(o,{error:n,request:{operationName:e,variables:t,query:r}});continue}console.error(o,{operationName:e,path:n.path,code:n.extensions?.code,debugMessage:n.extensions?.debugMessage})}}let d;function y(r){d=r}async function w(){if(!(typeof window<"u"||!d))try{const r=await d();return r?new Headers(r):void 0}catch{return}}async function q(r){return(await w())?.forEach((s,e)=>{r.has(e)||r.set(e,s)}),r}async function f(r){const{url:t,query:s,variables:e,requestHeaders:n,signal:a}=r,o=await q(new Headers(n));o.set("Content-Type","application/json"),o.has("Accept")||o.set("Accept","application/json");const c=await fetch(t,{method:"POST",headers:o,body:JSON.stringify({query:s,variables:e}),signal:a});let i;try{i=await c.json()}catch{i=void 0}if(!c.ok){const h=new l({status:c.status,data:i?.data,errors:i?.errors},{query:s,variables:e});throw p(c.status,{status:c.status,error:h}),h}if(i?.data)return i.errors?.length&&g(s,e,i.errors),i.data;throw new l({status:c.status,errors:i?.errors},{query:s,variables:e})}function E(){const r=async(e,n,a,o)=>f({url:e,query:String(n),variables:a,requestHeaders:o}),t=async e=>f({url:e.url,query:String(e.document),variables:e.variables,requestHeaders:e.requestHeaders,signal:e.signal}),s=r;return s.withOptions=t,s}function A(){return async function(t,s){const e=await w();let n=s;if(e){const o=new Headers(s?.headers);e.forEach((c,i)=>{o.has(i)||o.set(i,c)}),n={...s,headers:o}}const a=await fetch(t,n);return(a.status===401||a.status===403||a.status>=500)&&p(a.status,{status:a.status,response:a.clone()}),a}}export{l as ClientError,u as authAwareEvents,A as createAuthAwareFetch,E as createAuthAwareGraphqlClient,y as setServerRequestHeadersProvider};
|