@apollion-dsi/relay 0.27.3 → 0.28.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/README.md ADDED
@@ -0,0 +1,146 @@
1
+ # @apollion-dsi/relay
2
+
3
+ Configure and use **Relay** (`react-relay` / `relay-runtime`) the Apollion DS
4
+ way — Environment creation with auth, multipart uploads, WebSocket
5
+ subscriptions, and httpOnly-cookie sessions, without the boilerplate.
6
+
7
+ [![npm](https://img.shields.io/npm/v/@apollion-dsi/relay.svg)](https://www.npmjs.com/package/@apollion-dsi/relay)
8
+ [![downloads](https://img.shields.io/npm/dm/@apollion-dsi/relay.svg)](https://www.npmjs.com/package/@apollion-dsi/relay)
9
+ [![license](https://img.shields.io/npm/l/@apollion-dsi/relay.svg)](https://www.npmjs.com/package/@apollion-dsi/relay)
10
+
11
+ - **Environment in one call.** `CreateRelayEnvironment` wires the network layer,
12
+ auth, and error handling from a single config object.
13
+ - **Two auth models.** `Bearer` tokens or secure **httpOnly cookies** — switch
14
+ with one flag (see below).
15
+ - **Persisted queries.** Ship only build-time hashes over the wire — the server
16
+ executes nothing outside its allowlist (see below).
17
+ - **Batteries included.** Multipart file uploads, `graphql-ws` subscriptions,
18
+ and pluggable network retry / refresh ship with the package.
19
+ - **Typed.** Ships its own types for every option.
20
+
21
+ ## Installation
22
+
23
+ `react` is a **peer dependency** (`^19.0.0`); the Relay stack (`react-relay` /
24
+ `relay-runtime` 21.x, `graphql` 16.x, `graphql-ws`, `fetch-multipart-graphql`,
25
+ `js-cookie`) ships with the package:
26
+
27
+ ```bash
28
+ yarn add @apollion-dsi/relay react
29
+ ```
30
+
31
+ > To generate Relay artifacts, also add `relay-compiler` as a dev dependency and
32
+ > a `relay.config.js`.
33
+
34
+ ## Documentation
35
+
36
+ - **Docs & API reference:** <https://www.apollion.com.br>
37
+ - **Changelog:** <https://www.apollion.com.br/changelog>
38
+
39
+ ## Quick start
40
+
41
+ Create the Environment and provide it to your tree:
42
+
43
+ ```tsx
44
+ import { CreateRelayEnvironment } from '@apollion-dsi/relay';
45
+ import { RelayEnvironmentProvider } from 'react-relay';
46
+ import { App } from './app';
47
+
48
+ const { Environment } = new CreateRelayEnvironment({
49
+ url: 'https://api.example.com/graphql',
50
+ });
51
+
52
+ <RelayEnvironmentProvider environment={Environment}>
53
+ <App />
54
+ </RelayEnvironmentProvider>;
55
+ ```
56
+
57
+ ## Authentication: Bearer vs. httpOnly cookie
58
+
59
+ The Environment supports two auth models via `authMode`.
60
+
61
+ ### `authMode: 'bearer'` (default)
62
+
63
+ Reads the `sessionToken` from storage (`localStorage` or a JS-readable cookie)
64
+ and injects `Authorization: Bearer <token>` into every request; session
65
+ verification uses the `${authUrl}user/me` probe.
66
+
67
+ ```ts
68
+ new CreateRelayEnvironment({
69
+ url: 'https://api.example.com/graphql/',
70
+ authUrl: 'https://api.example.com/auth/',
71
+ useAuthorization: true,
72
+ storageType: 'cookie', // JS-readable cookie, or 'localStorage'
73
+ });
74
+ ```
75
+
76
+ ### `authMode: 'cookie'` (httpOnly session — recommended for SPAs)
77
+
78
+ The session token lives in an **httpOnly + SameSite** cookie, invisible to JS
79
+ and immune to XSS. The Environment never reads the token or sets
80
+ `Authorization` — `credentials: 'include'` makes the browser attach the cookie
81
+ automatically. On an auth error (e.g. 401), a refresh `POST` is fired to
82
+ `authUrl` with credentials, without probing `user/me`.
83
+
84
+ ```ts
85
+ new CreateRelayEnvironment({
86
+ url: 'https://api.example.com/graphql/',
87
+ authUrl: 'https://api.example.com/auth/refresh/',
88
+ authMode: 'cookie',
89
+ redirectOnError: true,
90
+ loginRoute: '/login',
91
+ });
92
+ ```
93
+
94
+ | Option | Default | What it does |
95
+ | ----------------- | ---------------------------------- | ----------------------------------------------------------------------------------------- |
96
+ | `authMode` | `'bearer'` | `'bearer'` (token + header) or `'cookie'` (httpOnly). |
97
+ | `credentials` | cookie→`'include'`; bearer→omitted | `RequestCredentials` forwarded to the GraphQL and refresh fetches. Works in both modes. |
98
+ | `sessionCheckUrl` | mode-dependent | Overrides the probe URL; `false` disables it (auth error → direct logout, no extra call). |
99
+
100
+ ## Persisted queries
101
+
102
+ With `usePersistedQueries: true`, every operation ships as a **build-time
103
+ hash** instead of GraphQL text. The server keeps the matching query map and
104
+ executes only hashes it knows — a tampered client query is refused, and the
105
+ server can cache aggressively by hash.
106
+
107
+ 1. Enable `persistConfig` in your `relay.config.json` so `relay-compiler`
108
+ writes the query map and stamps each artifact's `id`:
109
+
110
+ ```json
111
+ {
112
+ "src": "./src",
113
+ "schema": "./schema.graphql",
114
+ "language": "typescript",
115
+ "persistConfig": { "file": "./persisted/queryMap.json", "algorithm": "MD5" }
116
+ }
117
+ ```
118
+
119
+ 2. Ship `queryMap.json` to your GraphQL server as the allowlist.
120
+
121
+ 3. Turn the flag on:
122
+
123
+ ```ts
124
+ new CreateRelayEnvironment({
125
+ url: 'https://api.example.com/graphql/',
126
+ usePersistedQueries: true,
127
+ });
128
+ ```
129
+
130
+ The request body becomes `{ name, doc_id, variables }` — no `query` key.
131
+ Uploads carry the hash inside the multipart `operations` field, and
132
+ subscriptions send it in `payload.extensions.doc_id` over `graphql-ws`.
133
+
134
+ | Option | Default | What it does |
135
+ | ------------------------- | ---------- | ------------------------------------------------------------------------------ |
136
+ | `usePersistedQueries` | `false` | Sends only the persisted hash; the GraphQL text never leaves the build. |
137
+ | `persistedOperationField` | `'doc_id'` | Renames the hash field to whatever your server's allowlist middleware expects. |
138
+
139
+ > **Server contract:** resolve the hash against the query map; refuse raw text
140
+ > and unknown hashes replying `200` + `{ "errors": [...] }` with
141
+ > `Content-Type: application/json` — a `4xx` is treated as a transport failure
142
+ > by the client's retry layer instead of surfacing the GraphQL error.
143
+
144
+ ## License
145
+
146
+ MIT.
@@ -0,0 +1 @@
1
+ import{a as e}from"./chunk-GZONWSRB.esm.js";import r from"react";var t=r.createContext(void 0);function m({children:n,environment:o}){return r.createElement(t.Provider,{value:{environment:o}},n)}e(m,"EnvironmentProvider");function v(){let n=r.useContext(t);if(n===void 0)throw new Error("useEnvironment must be used within a EnvironmentProvider");if(n.environment===null||n.environment===void 0)throw new Error("You should provide a Relay Environment to EnvironmentProvider component");return n}e(v,"useEnvironment");export{m as a,v as b};
File without changes
@@ -0,0 +1 @@
1
+ var c=Object.defineProperty;var d=(a,b)=>c(a,"name",{value:b,configurable:!0});export{d as a};
@@ -0,0 +1 @@
1
+ import{a as s}from"./chunk-GZONWSRB.esm.js";import{Environment as ae,Network as ue,RecordSource as le,Store as ce}from"relay-runtime";import{Observable as te,QueryResponseCache as re}from"relay-runtime";var E=!!(typeof window!="undefined"&&window.document&&window.document.createElement),f={canUseDOM:E,canUseWorkers:typeof Worker!="undefined",canUseEventListeners:E&&!!(window.addEventListener||window.attachEvent),canUseViewport:E&&!!window.screen,isInWorker:!E};function $(e){return async t=>{let r=e.timeout,o=e.retries,a=0,i=0;return new Promise((u,l)=>{let c={};c.sendTimedRequest=()=>{a++,i=Date.now();let n=!0,p=new AbortController,m=setTimeout(()=>{if(n=!1,p.abort(),c.shouldRetry(a))e.useDebug&&console.log("fetchWithRetries: HTTP timeout, retrying."),c.retryRequest();else{let h=new Error(`fetchWithRetries(): Failed to get response from server, tried ${a} times.`);h.name="TimeOutError",l(h)}},r);fetch(e.url,{...t,signal:p.signal}).then(async h=>{if(clearTimeout(m),n)if(h.status>=200&&h.status<300)u(h);else if(c.shouldRetry(a)&&e.retryWhen.includes(h.status))e.useDebug&&console.log("fetchWithRetries: HTTP error, retrying..."),c.retryRequest();else{let d=new Error(`fetchWithRetries(): Still no successful response after ${a} retries, giving up.`);d.response=h,l(d)}}).catch(h=>{clearTimeout(m),h.name!=="AbortError"&&l(h)})},c.retryRequest=()=>{let n=o[a-1],p=i+n;setTimeout(c.sendTimedRequest,p-Date.now())},c.shouldRetry=n=>f.canUseDOM&&e.useRetries&&n<=o.length,c.sendTimedRequest()})}}s($,"fetchWithRetries");import{PatchResolver as X}from"fetch-multipart-graphql";var x=s(e=>e.operationKind==="mutation","isMutation"),O=s(e=>e.operationKind==="query","isQuery"),V=s(e=>!!(e&&e.force),"forceFetch"),Y=s(()=>f.canUseDOM?window.location.search.startsWith("?")?"&":"?":"","getParamsDelimiter"),C=s(()=>f.canUseDOM?window.location.pathname:"","getBrowserLocation"),R=s((e,t,r)=>{let o=e;(e!==t||r.loginRoute!==t)&&(o=`${t}${Y()}redirect=${e}`),window.location.href=o},"redirectUser");function L(e){if(!e.id)throw new Error(`[usePersistedQueries] Operation "${e.name}" has no persisted id \u2014 run relay-compiler with "persistConfig" set in relay.config.json.`);return e.id}s(L,"getPersistedOperationId");function Z(e,t,r,o){let a=new FormData,i=o!=null&&o.usePersistedQueries?{[o.persistedOperationField||"doc_id"]:L(e),variables:t,operationName:e.name}:{query:e.text,variables:t,operationName:e.name};a.append("operations",JSON.stringify(i));let u={};for(let l=0;l<r.files.length;l++)a.append(`${l}`,r.files[l]),u[`${l}`]=[r.path(l)];return a.append("map",JSON.stringify(u)),a}s(Z,"getRequestBodyWithUploadables");function ee(e,t,r){return r!=null&&r.usePersistedQueries?JSON.stringify({name:e.name,[r.persistedOperationField||"doc_id"]:L(e),variables:t}):JSON.stringify({name:e.name,query:e.text,variables:t})}s(ee,"getRequestBodyWithoutUplodables");function B(e,t,r,o){return r?Z(e,t,r,o):ee(e,t,o)}s(B,"getRequestBody");var U=s(e=>{if(e.credentials)return e.credentials;if(e.authMode==="cookie")return"include"},"resolveCredentials"),_=s((e,t)=>{let r=new Headers;if(t?r.append("Accept","*/*"):(r.append("Accept","application/json"),r.append("Content-type","application/json")),e.useAuthorization&&e.authMode!=="cookie"){let{sessionToken:o}=e.StorageHandler.getTokens();o&&C()!==e.loginRoute&&r.append("Authorization",`Bearer ${o}`)}return e.partner&&r.append("X-Partner",e.partner),r},"getHeaders"),j=s((e,t,r)=>{if(e.status<300&&e.headers&&e.headers.get("Content-Type")&&e.headers.get("Content-Type").indexOf("multipart/mixed")>=0){let o=e.body.getReader(),a=new TextDecoder,i=new X({onResponse:s(u=>t.next(u),"onResponse")});o.read().then(s(function u({value:l,done:c}){if(c)t.complete();else{let n;try{n=a.decode(l),i.handleChunk(n)}catch(p){let m=p;m.response=e,m.statusCode=e.status,m.bodyText=n,t.error(m)}o.read().then(u)}},"sendNext"))}else e.headers&&e.headers.get("Content-Type")&&e.headers.get("Content-Type").indexOf("application/json")>=0?e.json().then(r):e.text().then(o=>{t.next([o]),t.complete()})},"handleData"),b=1e3,v=b*60;var J=s(e=>{let t=new re({ttl:e.cacheTime,size:e.cacheSize}),r=$(e);async function o(){if(e.sessionCheckUrl===!1)return!1;let i=U(e);try{if(e.authMode==="cookie"){let n=e.sessionCheckUrl||e.authUrl;return(await fetch(n,{method:"POST",credentials:i||"include"})).status===200}let{sessionToken:u}=e.StorageHandler.getTokens(),l=e.sessionCheckUrl||`${e.authUrl}user/me`;return(await fetch(l,{headers:{"Content-Type":"application/json",Authorization:u},...i?{credentials:i}:{}})).status===200}catch(u){return console.warn(`[Auth] ${u.message}`),!1}}s(o,"checkSession");async function a(i,u,l,c,n){try{let p=i.id||("cacheID"in i?i.cacheID:null)||String(i.text);if(e.useCache&&O(i)&&!V(l)){let d=t.get(p,u);if(d){n.next(d),n.complete();return}}e.useCache&&x(i)&&t.clear();let m=U(e),h=await r({method:"POST",headers:_(e,c),body:B(i,u,c,e),...m?{credentials:m}:{}});j(h,n,async d=>{if(e.useCache&&O(i)&&t.set(p,u,d),!n.closed){if(x(i)&&d.errors&&n.error(d.errors),d.errors){if(await o()||(e.StorageHandler.clear(),R("/",e.loginRoute,e)),e.redirectOnError){let z=[];Array.isArray(d.errors)&&(z=d.errors.map(({status_code:P})=>P));let Q=C();z.some(P=>e.authenticationErrors.includes(P))&&Q!==e.loginRoute&&(e.StorageHandler.clear(),R(Q,e.loginRoute,e))}n.error(d.errors)}else d.data?n.next(d):n.error(d);n.complete()}})}catch(p){if(f.canUseDOM&&!["AbortError","TimeOutError"].includes(p.name)&&e.StorageHandler.clear(),e.redirectOnError){let h=C();h!==e.loginRoute&&R(h,e.loginRoute,e)}await o()||(e.StorageHandler.clear(),R("/",e.loginRoute,e)),n.error(p)}}return s(a,"fetchFn"),(i,u,l,c)=>te.create(n=>{a(i,u,l,c,n)})},"createFetchFunction");import se from"js-cookie";var A=class A{get(t){throw new Error('Must implement a "GET" method')}set(t,r){throw new Error('Must implement a "SET" method')}delete(t){throw new Error('Must implement a "DELETE" method')}};s(A,"DefaultStrategy");var k=A,I=class I extends k{constructor(){super(),this.strategy=localStorage}get(t){return this.strategy.getItem(t)}set(t,r){this.strategy.setItem(t,r)}delete(t){this.strategy.removeItem(t)}};s(I,"LocalStorageStrategy");var D=I,F=class F extends k{constructor(){super(),this.strategy=se}get(t){return this.strategy.get(t)}set(t,r){this.strategy.set(t,r)}delete(t){this.strategy.remove(t)}};s(F,"CookieStrategy");var M=F,y=Symbol("storage-handler"),g=Symbol("prop-session"),w=Symbol("prop-refresh"),H=class H{constructor(t){this[g]=t.sessionStorageProp,this[w]=t.refreshStorageProp,t.storageType==="cookie"?this[y]=new M:this[y]=new D}getTokens(){return{refreshToken:this[y].get(this[w]),sessionToken:this[y].get(this[g])}}setTokens(t){this[y].set(this[w],t.refreshToken),this[y].set(this[g],t.sessionToken)}clear(){this[y].delete(this[w]),this[y].delete(this[g])}hasChangedSession({sessionToken:t}){let{sessionToken:r}=this.getTokens();return t!==r}};s(H,"StorageClass");var S=H;import{createClient as oe}from"graphql-ws";import{Observable as ie}from"relay-runtime";function ne(e,t,r){if(e.usePersistedQueries){if(!t.id)throw new Error(`[usePersistedQueries] Operation "${t.name}" has no persisted id \u2014 run relay-compiler with "persistConfig" set in relay.config.json.`);return{operationName:t.name,query:"",variables:r,extensions:{[e.persistedOperationField||"doc_id"]:t.id}}}return{operationName:t.name,query:t.text||"",variables:r}}s(ne,"getSubscribePayload");function K(e){return()=>{let t;return(r,o)=>ie.create(a=>(t||(t=oe({url:e.socket})),t.subscribe(ne(e,r,o),{next:s(i=>a.next(i),"next"),error:s(i=>a.error(i),"error"),complete:s(()=>a.complete(),"complete")})))}}s(K,"setupSubscription");var G=Symbol("mount-environment"),N=Symbol("handler-fetch"),q=Symbol("handler-subscription"),W=class W{constructor(t){if(this.url=t.url,this.authUrl=t.authUrl,this.socket=t.socket,this.storageType=t.storageType||"localStorage",this.cacheSize=t.cacheSize||250,this.cacheTime=t.cacheTime||v*8,this.timeout=t.timeout||v*15,this.sessionStorageProp=t.sessionStorageProp||"USER_SESSION_TOKEN",this.refreshStorageProp=t.refreshStorageProp||"USER_REFRESH_TOKEN",this.loginRoute=t.loginRoute||"/",this.useCache=t.useCache||!1,this.useDebug=t.useDebug||!1,this.useAuthorization=t.useAuthorization||!1,this.authMode=t.authMode||"bearer",this.credentials=t.credentials,this.sessionCheckUrl=t.sessionCheckUrl,this.useRetries=t.useRetries||!1,this.useSubscription=t.useSubscription||!1,this.redirectOnError=t.redirectOnError||!1,this.retryWhen=t.retryWhen||[503,504,521,522,524],this.authenticationErrors=t.authenticationErrors||[401,403],this.retries=t.retries||[b,b*2,b*3,b*5,b*8,b*13,b*21,b*34],this.partner=t.partner||void 0,this.usePersistedQueries=t.usePersistedQueries||!1,this.persistedOperationField=t.persistedOperationField||"doc_id",!this.url)throw new Error("[HTTPFetchEndpoint] You must at least set url parameter.");if(this.useAuthorization&&!this.authUrl)throw new Error('[useAuthorization] Authorization is set but no "authUrl" was provided.');if(this.authMode==="cookie"&&this.sessionCheckUrl!==!1&&!this.authUrl)throw new Error('[authMode:cookie] Cookie auth refreshes against "authUrl" \u2014 provide it or set "sessionCheckUrl: false" to disable the probe.');if(this.StorageHandler=new S(this),this.useSubscription){if(!this.socket)throw new Error("[useSubscription] If you choose to use WebSocket you must set a socket endpoint.");this[q]=K(this)()}this[N]=J(this),this[G]()}[(N,q,G)](){let t=ue.create(this[N],this[q]||null),r=new le,o=new ce(r);this.Environment=new ae({network:t,store:o})}};s(W,"RelayEnvironment");var T=W;export{T as a};
@@ -0,0 +1 @@
1
+ import{a as o}from"./chunk-GZONWSRB.esm.js";import{commitMutation as m}from"relay-runtime";var p=o((t,r)=>new Promise((n,e)=>{m(t,{...r,onError:e,onCompleted:n})}),"commitMutation");export{p as a};
@@ -0,0 +1 @@
1
+ import{a as d}from"./chunk-GZONWSRB.esm.js";import{nanoid as g}from"nanoid";import{ConnectionHandler as p}from"relay-runtime";function y(e){return e!==null&&typeof e=="object"}d(y,"isObject");var O=g();function P({parentId:e,itemId:t,parentFieldName:o,store:n}){let r=n.get(e),i=r.getLinkedRecords(o);r.setLinkedRecords(i.filter(c=>c.dataID!==t),o)}d(P,"listRecordRemoveUpdater");function S({parentId:e,item:t,type:o,parentFieldName:n,store:r}){let i=r.create(t.id,o);Object.keys(t).forEach(s=>{i.setValue(t[s],s)});let c=r.get(e),a=c.getLinkedRecords(n);c.setLinkedRecords([...a,i],n)}d(S,"listRecordAddUpdater");function l(e,t,o,n,r=!1){if(n){let i=e.get(t),c=p.getConnection(i,o);if(!c)return;r?p.insertEdgeBefore(c,n):p.insertEdgeAfter(c,n)}}d(l,"connectionUpdater");function m({parentId:e,store:t,connectionName:o,item:n,customNode:r,itemType:i}){let c=r||t.create(n.id,i);!r&&Object.keys(n).forEach(s=>{c.setValue(n[s],s)});let a=t.create(`client:newEdge:${String(c.getDataID).match(/[^:]+$/)[0]}`,`${i}Edge`);a.setLinkedRecord(c,"node"),l(t,e,o,a)}d(m,"optimisticConnectionUpdater");function U({parentId:e,connectionName:t,nodeId:o,store:n}){let r=n.get(e),i=p.getConnection(r,t);if(!i){console.warn(`Connection ${t} not found on ${e}`);return}p.deleteNode(i,o)}d(U,"connectionDeleteEdgeUpdater");function C({object:e,proxy:t}){Object.keys(e).forEach(o=>{y(e[o])||Array.isArray(e[o])||t.setValue(e[o],o)})}d(C,"copyObjScalarsToProxy");export{y as a,O as b,P as c,S as d,l as e,m as f,U as g,C as h};
@@ -0,0 +1 @@
1
+ import{a}from"../chunks/chunk-RMYDTZUF.esm.js";import"../chunks/chunk-GZONWSRB.esm.js";export{a as commitMutation};
package/lib/index.d.ts CHANGED
@@ -11,7 +11,7 @@
11
11
  * `@apollion-dsi/relay/<name>` when the consumer wants to load
12
12
  * less code.
13
13
  */
14
- export { default as CreateRelayEnvironment } from './setupRelayEnvironment/setupRelayEnvironment';
14
+ export { CreateRelayEnvironment } from './setupRelayEnvironment';
15
15
  export * from './useEnvironment';
16
16
  export * from './relayArgsInterface';
17
17
  export * from './mutationUtils';
package/lib/index.esm.js CHANGED
@@ -1 +1 @@
1
- var Z=Object.defineProperty;var o=(e,t)=>Z(e,"name",{value:t,configurable:!0});import{Environment as ue,Network as le,RecordSource as de,Store as pe}from"relay-runtime";import{Observable as ne,QueryResponseCache as se}from"relay-runtime";var S=!!(typeof window!="undefined"&&window.document&&window.document.createElement),b={canUseDOM:S,canUseWorkers:typeof Worker!="undefined",canUseEventListeners:S&&!!(window.addEventListener||window.attachEvent),canUseViewport:S&&!!window.screen,isInWorker:!S};function j(e){return async t=>{let r=e.timeout,n=e.retries,i=0,s=0;return new Promise((a,l)=>{let u={};u.sendTimedRequest=()=>{i++,s=Date.now();let c=!0,h=new AbortController,m=setTimeout(()=>{if(c=!1,h.abort(),u.shouldRetry(i))e.useDebug&&console.log("fetchWithRetries: HTTP timeout, retrying."),u.retryRequest();else{let d=new Error(`fetchWithRetries(): Failed to get response from server, tried ${i} times.`);d.name="TimeOutError",l(d)}},r);fetch(e.url,{...t,signal:h.signal}).then(async d=>{if(clearTimeout(m),c)if(d.status>=200&&d.status<300)a(d);else if(u.shouldRetry(i)&&e.retryWhen.includes(d.status))e.useDebug&&console.log("fetchWithRetries: HTTP error, retrying..."),u.retryRequest();else{let p=new Error(`fetchWithRetries(): Still no successful response after ${i} retries, giving up.`);p.response=d,l(p)}}).catch(d=>{clearTimeout(m),d.name!=="AbortError"&&l(d)})},u.retryRequest=()=>{let c=n[i-1],h=s+c;setTimeout(u.sendTimedRequest,h-Date.now())},u.shouldRetry=c=>b.canUseDOM&&e.useRetries&&c<=n.length,u.sendTimedRequest()})}}o(j,"fetchWithRetries");import{PatchResolver as ee}from"fetch-multipart-graphql";var k=o(e=>e.operationKind==="mutation","isMutation"),U=o(e=>e.operationKind==="query","isQuery"),B=o(e=>!!(e&&e.force),"forceFetch"),te=o(()=>b.canUseDOM?window.location.search.startsWith("?")?"&":"?":"","getParamsDelimiter"),T=o(()=>b.canUseDOM?window.location.pathname:"","getBrowserLocation"),g=o((e,t,r)=>{let n=e;(e!==t||r.loginRoute!==t)&&(n=`${t}${te()}redirect=${e}`),window.location.href=n},"redirectUser");function re(e,t,r){let n=new FormData;n.append("operations",JSON.stringify({query:e.text,variables:t,operationName:e.name}));let i={};for(let s=0;s<r.files.length;s++)n.append(`${s}`,r.files[s]),i[`${s}`]=[r.path(s)];return n.append("map",JSON.stringify(i)),n}o(re,"getRequestBodyWithUploadables");function oe(e,t){return JSON.stringify({name:e.name,query:e.text,variables:t})}o(oe,"getRequestBodyWithoutUplodables");function K(e,t,r){return r?re(e,t,r):oe(e,t)}o(K,"getRequestBody");var O=o(e=>{if(e.credentials)return e.credentials;if(e.authMode==="cookie")return"include"},"resolveCredentials"),_=o((e,t)=>{let r=new Headers;if(t?r.append("Accept","*/*"):(r.append("Accept","application/json"),r.append("Content-type","application/json")),e.useAuthorization&&e.authMode!=="cookie"){let{sessionToken:n}=e.StorageHandler.getTokens();n&&T()!==e.loginRoute&&r.append("Authorization",`Bearer ${n}`)}return e.partner&&r.append("X-Partner",e.partner),r},"getHeaders"),J=o((e,t,r)=>{if(e.status<300&&e.headers&&e.headers.get("Content-Type")&&e.headers.get("Content-Type").indexOf("multipart/mixed")>=0){let n=e.body.getReader(),i=new TextDecoder,s=new ee({onResponse:o(a=>t.next(a),"onResponse")});n.read().then(o(function a({value:l,done:u}){if(u)t.complete();else{let c;try{c=i.decode(l),s.handleChunk(c)}catch(h){let m=h;m.response=e,m.statusCode=e.status,m.bodyText=c,t.error(m)}n.read().then(a)}},"sendNext"))}else e.headers&&e.headers.get("Content-Type")&&e.headers.get("Content-Type").indexOf("application/json")>=0?e.json().then(r):e.text().then(n=>{t.next([n]),t.complete()})},"handleData"),f=1e3,M=f*60;var Q=o(e=>{let t=new se({ttl:e.cacheTime,size:e.cacheSize}),r=j(e);async function n(){if(e.sessionCheckUrl===!1)return!1;let s=O(e);try{if(e.authMode==="cookie"){let c=e.sessionCheckUrl||e.authUrl;return(await fetch(c,{method:"POST",credentials:s||"include"})).status===200}let{sessionToken:a}=e.StorageHandler.getTokens(),l=e.sessionCheckUrl||`${e.authUrl}user/me`;return(await fetch(l,{headers:{"Content-Type":"application/json",Authorization:a},...s?{credentials:s}:{}})).status===200}catch(a){return console.warn(`[Auth] ${a.message}`),!1}}o(n,"checkSession");async function i(s,a,l,u,c){try{let h=String(s.text);if(e.useCache&&U(s)&&!B(l)){let p=t.get(h,a);if(p){c.next(p),c.complete();return}}e.useCache&&k(s)&&t.clear();let m=O(e),d=await r({method:"POST",headers:_(e,u),body:K(s,a,u),...m?{credentials:m}:{}});J(d,c,async p=>{if(e.useCache&&U(s)&&t.set(h,a,p),!c.closed){if(k(s)&&p.errors&&c.error(p.errors),p.errors){if(await n()||(e.StorageHandler.clear(),g("/",e.loginRoute,e)),e.redirectOnError){let F=[];Array.isArray(p.errors)&&(F=p.errors.map(({status_code:P})=>P));let V=T();F.some(P=>e.authenticationErrors.includes(P))&&V!==e.loginRoute&&(e.StorageHandler.clear(),g(V,e.loginRoute,e))}c.error(p.errors)}else p.data?c.next(p):c.error(p);c.complete()}})}catch(h){if(b.canUseDOM&&!["AbortError","TimeOutError"].includes(h.name)&&e.StorageHandler.clear(),e.redirectOnError){let d=T();d!==e.loginRoute&&g(d,e.loginRoute,e)}await n()||(e.StorageHandler.clear(),g("/",e.loginRoute,e)),c.error(h)}}return o(i,"fetchFn"),(s,a,l,u)=>ne.create(c=>{i(s,a,l,u,c)})},"createFetchFunction");import ie from"js-cookie";var A=class A{get(t){throw new Error('Must implement a "GET" method')}set(t,r){throw new Error('Must implement a "SET" method')}delete(t){throw new Error('Must implement a "DELETE" method')}};o(A,"DefaultStrategy");var w=A,q=class q extends w{constructor(){super(),this.strategy=localStorage}get(t){return this.strategy.getItem(t)}set(t,r){this.strategy.setItem(t,r)}delete(t){this.strategy.removeItem(t)}};o(q,"LocalStorageStrategy");var D=q,H=class H extends w{constructor(){super(),this.strategy=ie}get(t){return this.strategy.get(t)}set(t,r){this.strategy.set(t,r)}delete(t){this.strategy.remove(t)}};o(H,"CookieStrategy");var I=H,y=Symbol("storage-handler"),R=Symbol("prop-session"),E=Symbol("prop-refresh"),N=class N{constructor(t){this[R]=t.sessionStorageProp,this[E]=t.refreshStorageProp,t.storageType==="cookie"?this[y]=new I:this[y]=new D}getTokens(){return{refreshToken:this[y].get(this[E]),sessionToken:this[y].get(this[R])}}setTokens(t){this[y].set(this[E],t.refreshToken),this[y].set(this[R],t.sessionToken)}clear(){this[y].delete(this[E]),this[y].delete(this[R])}hasChangedSession({sessionToken:t}){let{sessionToken:r}=this.getTokens();return t!==r}};o(N,"StorageClass");var x=N;import{createClient as ae}from"graphql-ws";import{Observable as ce}from"relay-runtime";function Y(e){return()=>{let t=ae({url:e.socket});return(r,n)=>ce.create(i=>t.subscribe({operationName:r.name,query:r.text||"",variables:n},{next:o(s=>i.next(s),"next"),error:o(s=>i.error(s),"error"),complete:o(()=>i.complete(),"complete")}))}}o(Y,"setupSubscription");var G=Symbol("mount-environment"),W=Symbol("handler-fetch"),L=Symbol("handler-subscription"),$=class ${constructor(t){if(this.url=t.url,this.authUrl=t.authUrl,this.socket=t.socket,this.storageType=t.storageType||"localStorage",this.cacheSize=t.cacheSize||250,this.cacheTime=t.cacheTime||M*8,this.timeout=t.timeout||M*15,this.sessionStorageProp=t.sessionStorageProp||"USER_SESSION_TOKEN",this.refreshStorageProp=t.refreshStorageProp||"USER_REFRESH_TOKEN",this.loginRoute=t.loginRoute||"/",this.useCache=t.useCache||!1,this.useDebug=t.useDebug||!1,this.useAuthorization=t.useAuthorization||!1,this.authMode=t.authMode||"bearer",this.credentials=t.credentials,this.sessionCheckUrl=t.sessionCheckUrl,this.useRetries=t.useRetries||!1,this.useSubscription=t.useSubscription||!1,this.redirectOnError=t.redirectOnError||!1,this.retryWhen=t.retryWhen||[503,504,521,522,524],this.authenticationErrors=t.authenticationErrors||[401,403],this.retries=t.retries||[f,f*2,f*3,f*5,f*8,f*13,f*21,f*34],this.partner=t.partner||void 0,!this.url)throw new Error("[HTTPFetchEndpoint] You must at least set url parameter.");if(this.useAuthorization&&!this.authUrl)throw new Error('[useAuthorization] Authorization is set but no "authUrl" was provided.');if(this.authMode==="cookie"&&this.sessionCheckUrl!==!1&&!this.authUrl)throw new Error('[authMode:cookie] Cookie auth refreshes against "authUrl" \u2014 provide it or set "sessionCheckUrl: false" to disable the probe.');if(this.StorageHandler=new x(this),this.useSubscription){if(!this.socket)throw new Error("[useSubscription] If you choose to use WebSocket you must set a socket endpoint.");this[L]=Y(this)}this[W]=Q(this),this[G]()}[(W,L,G)](){let t=le.create(this[W],this[L]||null),r=new de,n=new pe(r);this.Environment=new ue({network:t,store:n})}};o($,"RelayEnvironment");var C=$;import z from"react";var X=z.createContext(void 0);function et({children:e,environment:t}){return z.createElement(X.Provider,{value:{environment:t}},e)}o(et,"EnvironmentProvider");function tt(){let e=z.useContext(X);if(e===void 0)throw new Error("useEnvironment must be used within a EnvironmentProvider");if(e.environment===null||e.environment===void 0)throw new Error("You should provide a Relay Environment to EnvironmentProvider component");return e}o(tt,"useEnvironment");import{nanoid as he}from"nanoid";import{ConnectionHandler as v}from"relay-runtime";function me(e){return e!==null&&typeof e=="object"}o(me,"isObject");var dt=he();function pt({parentId:e,itemId:t,parentFieldName:r,store:n}){let i=n.get(e),s=i.getLinkedRecords(r);i.setLinkedRecords(s.filter(a=>a.dataID!==t),r)}o(pt,"listRecordRemoveUpdater");function ht({parentId:e,item:t,type:r,parentFieldName:n,store:i}){let s=i.create(t.id,r);Object.keys(t).forEach(u=>{s.setValue(t[u],u)});let a=i.get(e),l=a.getLinkedRecords(n);a.setLinkedRecords([...l,s],n)}o(ht,"listRecordAddUpdater");function fe(e,t,r,n,i=!1){if(n){let s=e.get(t),a=v.getConnection(s,r);if(!a)return;i?v.insertEdgeBefore(a,n):v.insertEdgeAfter(a,n)}}o(fe,"connectionUpdater");function mt({parentId:e,store:t,connectionName:r,item:n,customNode:i,itemType:s}){let a=i||t.create(n.id,s);!i&&Object.keys(n).forEach(u=>{a.setValue(n[u],u)});let l=t.create(`client:newEdge:${String(a.getDataID).match(/[^:]+$/)[0]}`,`${s}Edge`);l.setLinkedRecord(a,"node"),fe(t,e,r,l)}o(mt,"optimisticConnectionUpdater");function ft({parentId:e,connectionName:t,nodeId:r,store:n}){let i=n.get(e),s=v.getConnection(i,t);if(!s){console.warn(`Connection ${t} not found on ${e}`);return}v.deleteNode(s,r)}o(ft,"connectionDeleteEdgeUpdater");function yt({object:e,proxy:t}){Object.keys(e).forEach(r=>{me(e[r])||Array.isArray(e[r])||t.setValue(e[r],r)})}o(yt,"copyObjScalarsToProxy");import{commitMutation as ye}from"relay-runtime";var Tt=o((e,t)=>new Promise((r,n)=>{ye(e,{...t,onError:n,onCompleted:r})}),"commitMutation");export{dt as ClientMutationID,C as CreateRelayEnvironment,et as EnvironmentProvider,Tt as commitMutation,ft as connectionDeleteEdgeUpdater,fe as connectionUpdater,yt as copyObjScalarsToProxy,me as isObject,ht as listRecordAddUpdater,pt as listRecordRemoveUpdater,mt as optimisticConnectionUpdater,tt as useEnvironment};
1
+ import{a as o}from"./chunks/chunk-RMYDTZUF.esm.js";import{a as m,b as f,c as p,d as x,e as n,f as a,g as i,h as l}from"./chunks/chunk-XJA2SRQY.esm.js";import"./chunks/chunk-FYOI5Q5V.esm.js";import{a as r}from"./chunks/chunk-KIA6YWO5.esm.js";import{a as e,b as t}from"./chunks/chunk-54QV6DFY.esm.js";import"./chunks/chunk-GZONWSRB.esm.js";export{f as ClientMutationID,r as CreateRelayEnvironment,e as EnvironmentProvider,o as commitMutation,i as connectionDeleteEdgeUpdater,n as connectionUpdater,l as copyObjScalarsToProxy,m as isObject,x as listRecordAddUpdater,p as listRecordRemoveUpdater,a as optimisticConnectionUpdater,t as useEnvironment};
package/lib/index.js CHANGED
@@ -1 +1 @@
1
- var pe=Object.create;var E=Object.defineProperty;var he=Object.getOwnPropertyDescriptor;var me=Object.getOwnPropertyNames;var fe=Object.getPrototypeOf,ye=Object.prototype.hasOwnProperty;var o=(e,t)=>E(e,"name",{value:t,configurable:!0});var be=(e,t)=>{for(var r in t)E(e,r,{get:t[r],enumerable:!0})},_=(e,t,r,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of me(t))!ye.call(e,i)&&i!==r&&E(e,i,{get:()=>t[i],enumerable:!(n=he(t,i))||n.enumerable});return e};var J=(e,t,r)=>(r=e!=null?pe(fe(e)):{},_(t||!e||!e.__esModule?E(r,"default",{value:e,enumerable:!0}):r,e)),ge=e=>_(E({},"__esModule",{value:!0}),e);var Me={};be(Me,{ClientMutationID:()=>Te,CreateRelayEnvironment:()=>w,EnvironmentProvider:()=>ve,commitMutation:()=>Oe,connectionDeleteEdgeUpdater:()=>ke,connectionUpdater:()=>le,copyObjScalarsToProxy:()=>Ue,isObject:()=>ue,listRecordAddUpdater:()=>Ce,listRecordRemoveUpdater:()=>we,optimisticConnectionUpdater:()=>Pe,useEnvironment:()=>Se});module.exports=ge(Me);var b=require("relay-runtime");var k=require("relay-runtime");var C=!!(typeof window!="undefined"&&window.document&&window.document.createElement),g={canUseDOM:C,canUseWorkers:typeof Worker!="undefined",canUseEventListeners:C&&!!(window.addEventListener||window.attachEvent),canUseViewport:C&&!!window.screen,isInWorker:!C};function Q(e){return async t=>{let r=e.timeout,n=e.retries,i=0,s=0;return new Promise((a,l)=>{let u={};u.sendTimedRequest=()=>{i++,s=Date.now();let c=!0,h=new AbortController,m=setTimeout(()=>{if(c=!1,h.abort(),u.shouldRetry(i))e.useDebug&&console.log("fetchWithRetries: HTTP timeout, retrying."),u.retryRequest();else{let d=new Error(`fetchWithRetries(): Failed to get response from server, tried ${i} times.`);d.name="TimeOutError",l(d)}},r);fetch(e.url,{...t,signal:h.signal}).then(async d=>{if(clearTimeout(m),c)if(d.status>=200&&d.status<300)a(d);else if(u.shouldRetry(i)&&e.retryWhen.includes(d.status))e.useDebug&&console.log("fetchWithRetries: HTTP error, retrying..."),u.retryRequest();else{let p=new Error(`fetchWithRetries(): Still no successful response after ${i} retries, giving up.`);p.response=d,l(p)}}).catch(d=>{clearTimeout(m),d.name!=="AbortError"&&l(d)})},u.retryRequest=()=>{let c=n[i-1],h=s+c;setTimeout(u.sendTimedRequest,h-Date.now())},u.shouldRetry=c=>g.canUseDOM&&e.useRetries&&c<=n.length,u.sendTimedRequest()})}}o(Q,"fetchWithRetries");var Y=require("fetch-multipart-graphql");var D=o(e=>e.operationKind==="mutation","isMutation"),I=o(e=>e.operationKind==="query","isQuery"),G=o(e=>!!(e&&e.force),"forceFetch"),Re=o(()=>g.canUseDOM?window.location.search.startsWith("?")?"&":"?":"","getParamsDelimiter"),P=o(()=>g.canUseDOM?window.location.pathname:"","getBrowserLocation"),x=o((e,t,r)=>{let n=e;(e!==t||r.loginRoute!==t)&&(n=`${t}${Re()}redirect=${e}`),window.location.href=n},"redirectUser");function Ee(e,t,r){let n=new FormData;n.append("operations",JSON.stringify({query:e.text,variables:t,operationName:e.name}));let i={};for(let s=0;s<r.files.length;s++)n.append(`${s}`,r.files[s]),i[`${s}`]=[r.path(s)];return n.append("map",JSON.stringify(i)),n}o(Ee,"getRequestBodyWithUploadables");function xe(e,t){return JSON.stringify({name:e.name,query:e.text,variables:t})}o(xe,"getRequestBodyWithoutUplodables");function X(e,t,r){return r?Ee(e,t,r):xe(e,t)}o(X,"getRequestBody");var A=o(e=>{if(e.credentials)return e.credentials;if(e.authMode==="cookie")return"include"},"resolveCredentials"),Z=o((e,t)=>{let r=new Headers;if(t?r.append("Accept","*/*"):(r.append("Accept","application/json"),r.append("Content-type","application/json")),e.useAuthorization&&e.authMode!=="cookie"){let{sessionToken:n}=e.StorageHandler.getTokens();n&&P()!==e.loginRoute&&r.append("Authorization",`Bearer ${n}`)}return e.partner&&r.append("X-Partner",e.partner),r},"getHeaders"),ee=o((e,t,r)=>{if(e.status<300&&e.headers&&e.headers.get("Content-Type")&&e.headers.get("Content-Type").indexOf("multipart/mixed")>=0){let n=e.body.getReader(),i=new TextDecoder,s=new Y.PatchResolver({onResponse:o(a=>t.next(a),"onResponse")});n.read().then(o(function a({value:l,done:u}){if(u)t.complete();else{let c;try{c=i.decode(l),s.handleChunk(c)}catch(h){let m=h;m.response=e,m.statusCode=e.status,m.bodyText=c,t.error(m)}n.read().then(a)}},"sendNext"))}else e.headers&&e.headers.get("Content-Type")&&e.headers.get("Content-Type").indexOf("application/json")>=0?e.json().then(r):e.text().then(n=>{t.next([n]),t.complete()})},"handleData"),f=1e3,q=f*60;var te=o(e=>{let t=new k.QueryResponseCache({ttl:e.cacheTime,size:e.cacheSize}),r=Q(e);async function n(){if(e.sessionCheckUrl===!1)return!1;let s=A(e);try{if(e.authMode==="cookie"){let c=e.sessionCheckUrl||e.authUrl;return(await fetch(c,{method:"POST",credentials:s||"include"})).status===200}let{sessionToken:a}=e.StorageHandler.getTokens(),l=e.sessionCheckUrl||`${e.authUrl}user/me`;return(await fetch(l,{headers:{"Content-Type":"application/json",Authorization:a},...s?{credentials:s}:{}})).status===200}catch(a){return console.warn(`[Auth] ${a.message}`),!1}}o(n,"checkSession");async function i(s,a,l,u,c){try{let h=String(s.text);if(e.useCache&&I(s)&&!G(l)){let p=t.get(h,a);if(p){c.next(p),c.complete();return}}e.useCache&&D(s)&&t.clear();let m=A(e),d=await r({method:"POST",headers:Z(e,u),body:X(s,a,u),...m?{credentials:m}:{}});ee(d,c,async p=>{if(e.useCache&&I(s)&&t.set(h,a,p),!c.closed){if(D(s)&&p.errors&&c.error(p.errors),p.errors){if(await n()||(e.StorageHandler.clear(),x("/",e.loginRoute,e)),e.redirectOnError){let B=[];Array.isArray(p.errors)&&(B=p.errors.map(({status_code:M})=>M));let K=P();B.some(M=>e.authenticationErrors.includes(M))&&K!==e.loginRoute&&(e.StorageHandler.clear(),x(K,e.loginRoute,e))}c.error(p.errors)}else p.data?c.next(p):c.error(p);c.complete()}})}catch(h){if(g.canUseDOM&&!["AbortError","TimeOutError"].includes(h.name)&&e.StorageHandler.clear(),e.redirectOnError){let d=P();d!==e.loginRoute&&x(d,e.loginRoute,e)}await n()||(e.StorageHandler.clear(),x("/",e.loginRoute,e)),c.error(h)}}return o(i,"fetchFn"),(s,a,l,u)=>k.Observable.create(c=>{i(s,a,l,u,c)})},"createFetchFunction");var re=J(require("js-cookie"));var W=class W{get(t){throw new Error('Must implement a "GET" method')}set(t,r){throw new Error('Must implement a "SET" method')}delete(t){throw new Error('Must implement a "DELETE" method')}};o(W,"DefaultStrategy");var U=W,L=class L extends U{constructor(){super(),this.strategy=localStorage}get(t){return this.strategy.getItem(t)}set(t,r){this.strategy.setItem(t,r)}delete(t){this.strategy.removeItem(t)}};o(L,"LocalStorageStrategy");var H=L,$=class $ extends U{constructor(){super(),this.strategy=re.default}get(t){return this.strategy.get(t)}set(t,r){this.strategy.set(t,r)}delete(t){this.strategy.remove(t)}};o($,"CookieStrategy");var N=$,y=Symbol("storage-handler"),v=Symbol("prop-session"),S=Symbol("prop-refresh"),z=class z{constructor(t){this[v]=t.sessionStorageProp,this[S]=t.refreshStorageProp,t.storageType==="cookie"?this[y]=new N:this[y]=new H}getTokens(){return{refreshToken:this[y].get(this[S]),sessionToken:this[y].get(this[v])}}setTokens(t){this[y].set(this[S],t.refreshToken),this[y].set(this[v],t.sessionToken)}clear(){this[y].delete(this[S]),this[y].delete(this[v])}hasChangedSession({sessionToken:t}){let{sessionToken:r}=this.getTokens();return t!==r}};o(z,"StorageClass");var T=z;var oe=require("graphql-ws"),ne=require("relay-runtime");function se(e){return()=>{let t=(0,oe.createClient)({url:e.socket});return(r,n)=>ne.Observable.create(i=>t.subscribe({operationName:r.name,query:r.text||"",variables:n},{next:o(s=>i.next(s),"next"),error:o(s=>i.error(s),"error"),complete:o(()=>i.complete(),"complete")}))}}o(se,"setupSubscription");var ie=Symbol("mount-environment"),F=Symbol("handler-fetch"),V=Symbol("handler-subscription"),j=class j{constructor(t){if(this.url=t.url,this.authUrl=t.authUrl,this.socket=t.socket,this.storageType=t.storageType||"localStorage",this.cacheSize=t.cacheSize||250,this.cacheTime=t.cacheTime||q*8,this.timeout=t.timeout||q*15,this.sessionStorageProp=t.sessionStorageProp||"USER_SESSION_TOKEN",this.refreshStorageProp=t.refreshStorageProp||"USER_REFRESH_TOKEN",this.loginRoute=t.loginRoute||"/",this.useCache=t.useCache||!1,this.useDebug=t.useDebug||!1,this.useAuthorization=t.useAuthorization||!1,this.authMode=t.authMode||"bearer",this.credentials=t.credentials,this.sessionCheckUrl=t.sessionCheckUrl,this.useRetries=t.useRetries||!1,this.useSubscription=t.useSubscription||!1,this.redirectOnError=t.redirectOnError||!1,this.retryWhen=t.retryWhen||[503,504,521,522,524],this.authenticationErrors=t.authenticationErrors||[401,403],this.retries=t.retries||[f,f*2,f*3,f*5,f*8,f*13,f*21,f*34],this.partner=t.partner||void 0,!this.url)throw new Error("[HTTPFetchEndpoint] You must at least set url parameter.");if(this.useAuthorization&&!this.authUrl)throw new Error('[useAuthorization] Authorization is set but no "authUrl" was provided.');if(this.authMode==="cookie"&&this.sessionCheckUrl!==!1&&!this.authUrl)throw new Error('[authMode:cookie] Cookie auth refreshes against "authUrl" \u2014 provide it or set "sessionCheckUrl: false" to disable the probe.');if(this.StorageHandler=new T(this),this.useSubscription){if(!this.socket)throw new Error("[useSubscription] If you choose to use WebSocket you must set a socket endpoint.");this[V]=se(this)}this[F]=te(this),this[ie]()}[(F,V,ie)](){let t=b.Network.create(this[F],this[V]||null),r=new b.RecordSource,n=new b.Store(r);this.Environment=new b.Environment({network:t,store:n})}};o(j,"RelayEnvironment");var w=j;var O=J(require("react"));var ae=O.default.createContext(void 0);function ve({children:e,environment:t}){return O.default.createElement(ae.Provider,{value:{environment:t}},e)}o(ve,"EnvironmentProvider");function Se(){let e=O.default.useContext(ae);if(e===void 0)throw new Error("useEnvironment must be used within a EnvironmentProvider");if(e.environment===null||e.environment===void 0)throw new Error("You should provide a Relay Environment to EnvironmentProvider component");return e}o(Se,"useEnvironment");var ce=require("nanoid"),R=require("relay-runtime");function ue(e){return e!==null&&typeof e=="object"}o(ue,"isObject");var Te=(0,ce.nanoid)();function we({parentId:e,itemId:t,parentFieldName:r,store:n}){let i=n.get(e),s=i.getLinkedRecords(r);i.setLinkedRecords(s.filter(a=>a.dataID!==t),r)}o(we,"listRecordRemoveUpdater");function Ce({parentId:e,item:t,type:r,parentFieldName:n,store:i}){let s=i.create(t.id,r);Object.keys(t).forEach(u=>{s.setValue(t[u],u)});let a=i.get(e),l=a.getLinkedRecords(n);a.setLinkedRecords([...l,s],n)}o(Ce,"listRecordAddUpdater");function le(e,t,r,n,i=!1){if(n){let s=e.get(t),a=R.ConnectionHandler.getConnection(s,r);if(!a)return;i?R.ConnectionHandler.insertEdgeBefore(a,n):R.ConnectionHandler.insertEdgeAfter(a,n)}}o(le,"connectionUpdater");function Pe({parentId:e,store:t,connectionName:r,item:n,customNode:i,itemType:s}){let a=i||t.create(n.id,s);!i&&Object.keys(n).forEach(u=>{a.setValue(n[u],u)});let l=t.create(`client:newEdge:${String(a.getDataID).match(/[^:]+$/)[0]}`,`${s}Edge`);l.setLinkedRecord(a,"node"),le(t,e,r,l)}o(Pe,"optimisticConnectionUpdater");function ke({parentId:e,connectionName:t,nodeId:r,store:n}){let i=n.get(e),s=R.ConnectionHandler.getConnection(i,t);if(!s){console.warn(`Connection ${t} not found on ${e}`);return}R.ConnectionHandler.deleteNode(s,r)}o(ke,"connectionDeleteEdgeUpdater");function Ue({object:e,proxy:t}){Object.keys(e).forEach(r=>{ue(e[r])||Array.isArray(e[r])||t.setValue(e[r],r)})}o(Ue,"copyObjScalarsToProxy");var de=require("relay-runtime");var Oe=o((e,t)=>new Promise((r,n)=>{(0,de.commitMutation)(e,{...t,onError:n,onCompleted:r})}),"commitMutation");0&&(module.exports={ClientMutationID,CreateRelayEnvironment,EnvironmentProvider,commitMutation,connectionDeleteEdgeUpdater,connectionUpdater,copyObjScalarsToProxy,isObject,listRecordAddUpdater,listRecordRemoveUpdater,optimisticConnectionUpdater,useEnvironment});
1
+ var ge=Object.create;var S=Object.defineProperty;var be=Object.getOwnPropertyDescriptor;var Re=Object.getOwnPropertyNames;var xe=Object.getPrototypeOf,Ee=Object.prototype.hasOwnProperty;var o=(e,t)=>S(e,"name",{value:t,configurable:!0});var we=(e,t)=>{for(var r in t)S(e,r,{get:t[r],enumerable:!0})},Y=(e,t,r,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of Re(t))!Ee.call(e,i)&&i!==r&&S(e,i,{get:()=>t[i],enumerable:!(n=be(t,i))||n.enumerable});return e};var G=(e,t,r)=>(r=e!=null?ge(xe(e)):{},Y(t||!e||!e.__esModule?S(r,"default",{value:e,enumerable:!0}):r,e)),Se=e=>Y(S({},"__esModule",{value:!0}),e);var We={};we(We,{ClientMutationID:()=>Ie,CreateRelayEnvironment:()=>x,EnvironmentProvider:()=>Oe,commitMutation:()=>Le,connectionDeleteEdgeUpdater:()=>Fe,connectionUpdater:()=>fe,copyObjScalarsToProxy:()=>He,isObject:()=>me,listRecordAddUpdater:()=>De,listRecordRemoveUpdater:()=>Ae,optimisticConnectionUpdater:()=>Ne,useEnvironment:()=>ke});module.exports=Se(We);var g=require("relay-runtime");var U=require("relay-runtime");var O=!!(typeof window!="undefined"&&window.document&&window.document.createElement),R={canUseDOM:O,canUseWorkers:typeof Worker!="undefined",canUseEventListeners:O&&!!(window.addEventListener||window.attachEvent),canUseViewport:O&&!!window.screen,isInWorker:!O};function X(e){return async t=>{let r=e.timeout,n=e.retries,i=0,s=0;return new Promise((a,u)=>{let l={};l.sendTimedRequest=()=>{i++,s=Date.now();let c=!0,h=new AbortController,m=setTimeout(()=>{if(c=!1,h.abort(),l.shouldRetry(i))e.useDebug&&console.log("fetchWithRetries: HTTP timeout, retrying."),l.retryRequest();else{let d=new Error(`fetchWithRetries(): Failed to get response from server, tried ${i} times.`);d.name="TimeOutError",u(d)}},r);fetch(e.url,{...t,signal:h.signal}).then(async d=>{if(clearTimeout(m),c)if(d.status>=200&&d.status<300)a(d);else if(l.shouldRetry(i)&&e.retryWhen.includes(d.status))e.useDebug&&console.log("fetchWithRetries: HTTP error, retrying..."),l.retryRequest();else{let p=new Error(`fetchWithRetries(): Still no successful response after ${i} retries, giving up.`);p.response=d,u(p)}}).catch(d=>{clearTimeout(m),d.name!=="AbortError"&&u(d)})},l.retryRequest=()=>{let c=n[i-1],h=s+c;setTimeout(l.sendTimedRequest,h-Date.now())},l.shouldRetry=c=>R.canUseDOM&&e.useRetries&&c<=n.length,l.sendTimedRequest()})}}o(X,"fetchWithRetries");var Z=require("fetch-multipart-graphql");var D=o(e=>e.operationKind==="mutation","isMutation"),N=o(e=>e.operationKind==="query","isQuery"),ee=o(e=>!!(e&&e.force),"forceFetch"),Pe=o(()=>R.canUseDOM?window.location.search.startsWith("?")?"&":"?":"","getParamsDelimiter"),k=o(()=>R.canUseDOM?window.location.pathname:"","getBrowserLocation"),P=o((e,t,r)=>{let n=e;(e!==t||r.loginRoute!==t)&&(n=`${t}${Pe()}redirect=${e}`),window.location.href=n},"redirectUser");function te(e){if(!e.id)throw new Error(`[usePersistedQueries] Operation "${e.name}" has no persisted id \u2014 run relay-compiler with "persistConfig" set in relay.config.json.`);return e.id}o(te,"getPersistedOperationId");function Ce(e,t,r,n){let i=new FormData,s=n!=null&&n.usePersistedQueries?{[n.persistedOperationField||"doc_id"]:te(e),variables:t,operationName:e.name}:{query:e.text,variables:t,operationName:e.name};i.append("operations",JSON.stringify(s));let a={};for(let u=0;u<r.files.length;u++)i.append(`${u}`,r.files[u]),a[`${u}`]=[r.path(u)];return i.append("map",JSON.stringify(a)),i}o(Ce,"getRequestBodyWithUploadables");function ve(e,t,r){return r!=null&&r.usePersistedQueries?JSON.stringify({name:e.name,[r.persistedOperationField||"doc_id"]:te(e),variables:t}):JSON.stringify({name:e.name,query:e.text,variables:t})}o(ve,"getRequestBodyWithoutUplodables");function re(e,t,r,n){return r?Ce(e,t,r,n):ve(e,t,n)}o(re,"getRequestBody");var F=o(e=>{if(e.credentials)return e.credentials;if(e.authMode==="cookie")return"include"},"resolveCredentials"),oe=o((e,t)=>{let r=new Headers;if(t?r.append("Accept","*/*"):(r.append("Accept","application/json"),r.append("Content-type","application/json")),e.useAuthorization&&e.authMode!=="cookie"){let{sessionToken:n}=e.StorageHandler.getTokens();n&&k()!==e.loginRoute&&r.append("Authorization",`Bearer ${n}`)}return e.partner&&r.append("X-Partner",e.partner),r},"getHeaders"),ne=o((e,t,r)=>{if(e.status<300&&e.headers&&e.headers.get("Content-Type")&&e.headers.get("Content-Type").indexOf("multipart/mixed")>=0){let n=e.body.getReader(),i=new TextDecoder,s=new Z.PatchResolver({onResponse:o(a=>t.next(a),"onResponse")});n.read().then(o(function a({value:u,done:l}){if(l)t.complete();else{let c;try{c=i.decode(u),s.handleChunk(c)}catch(h){let m=h;m.response=e,m.statusCode=e.status,m.bodyText=c,t.error(m)}n.read().then(a)}},"sendNext"))}else e.headers&&e.headers.get("Content-Type")&&e.headers.get("Content-Type").indexOf("application/json")>=0?e.json().then(r):e.text().then(n=>{t.next([n]),t.complete()})},"handleData"),f=1e3,H=f*60;var se=o(e=>{let t=new U.QueryResponseCache({ttl:e.cacheTime,size:e.cacheSize}),r=X(e);async function n(){if(e.sessionCheckUrl===!1)return!1;let s=F(e);try{if(e.authMode==="cookie"){let c=e.sessionCheckUrl||e.authUrl;return(await fetch(c,{method:"POST",credentials:s||"include"})).status===200}let{sessionToken:a}=e.StorageHandler.getTokens(),u=e.sessionCheckUrl||`${e.authUrl}user/me`;return(await fetch(u,{headers:{"Content-Type":"application/json",Authorization:a},...s?{credentials:s}:{}})).status===200}catch(a){return console.warn(`[Auth] ${a.message}`),!1}}o(n,"checkSession");async function i(s,a,u,l,c){try{let h=s.id||("cacheID"in s?s.cacheID:null)||String(s.text);if(e.useCache&&N(s)&&!ee(u)){let p=t.get(h,a);if(p){c.next(p),c.complete();return}}e.useCache&&D(s)&&t.clear();let m=F(e),d=await r({method:"POST",headers:oe(e,l),body:re(s,a,l,e),...m?{credentials:m}:{}});ne(d,c,async p=>{if(e.useCache&&N(s)&&t.set(h,a,p),!c.closed){if(D(s)&&p.errors&&c.error(p.errors),p.errors){if(await n()||(e.StorageHandler.clear(),P("/",e.loginRoute,e)),e.redirectOnError){let J=[];Array.isArray(p.errors)&&(J=p.errors.map(({status_code:A})=>A));let K=k();J.some(A=>e.authenticationErrors.includes(A))&&K!==e.loginRoute&&(e.StorageHandler.clear(),P(K,e.loginRoute,e))}c.error(p.errors)}else p.data?c.next(p):c.error(p);c.complete()}})}catch(h){if(R.canUseDOM&&!["AbortError","TimeOutError"].includes(h.name)&&e.StorageHandler.clear(),e.redirectOnError){let d=k();d!==e.loginRoute&&P(d,e.loginRoute,e)}await n()||(e.StorageHandler.clear(),P("/",e.loginRoute,e)),c.error(h)}}return o(i,"fetchFn"),(s,a,u,l)=>U.Observable.create(c=>{i(s,a,u,l,c)})},"createFetchFunction");var ie=G(require("js-cookie"));var q=class q{get(t){throw new Error('Must implement a "GET" method')}set(t,r){throw new Error('Must implement a "SET" method')}delete(t){throw new Error('Must implement a "DELETE" method')}};o(q,"DefaultStrategy");var M=q,V=class V extends M{constructor(){super(),this.strategy=localStorage}get(t){return this.strategy.getItem(t)}set(t,r){this.strategy.setItem(t,r)}delete(t){this.strategy.removeItem(t)}};o(V,"LocalStorageStrategy");var L=V,$=class $ extends M{constructor(){super(),this.strategy=ie.default}get(t){return this.strategy.get(t)}set(t,r){this.strategy.set(t,r)}delete(t){this.strategy.remove(t)}};o($,"CookieStrategy");var W=$,y=Symbol("storage-handler"),C=Symbol("prop-session"),v=Symbol("prop-refresh"),Q=class Q{constructor(t){this[C]=t.sessionStorageProp,this[v]=t.refreshStorageProp,t.storageType==="cookie"?this[y]=new W:this[y]=new L}getTokens(){return{refreshToken:this[y].get(this[v]),sessionToken:this[y].get(this[C])}}setTokens(t){this[y].set(this[v],t.refreshToken),this[y].set(this[C],t.sessionToken)}clear(){this[y].delete(this[v]),this[y].delete(this[C])}hasChangedSession({sessionToken:t}){let{sessionToken:r}=this.getTokens();return t!==r}};o(Q,"StorageClass");var T=Q;var ae=require("graphql-ws"),ce=require("relay-runtime");function Te(e,t,r){if(e.usePersistedQueries){if(!t.id)throw new Error(`[usePersistedQueries] Operation "${t.name}" has no persisted id \u2014 run relay-compiler with "persistConfig" set in relay.config.json.`);return{operationName:t.name,query:"",variables:r,extensions:{[e.persistedOperationField||"doc_id"]:t.id}}}return{operationName:t.name,query:t.text||"",variables:r}}o(Te,"getSubscribePayload");function ue(e){return()=>{let t;return(r,n)=>ce.Observable.create(i=>(t||(t=(0,ae.createClient)({url:e.socket})),t.subscribe(Te(e,r,n),{next:o(s=>i.next(s),"next"),error:o(s=>i.error(s),"error"),complete:o(()=>i.complete(),"complete")})))}}o(ue,"setupSubscription");var le=Symbol("mount-environment"),j=Symbol("handler-fetch"),B=Symbol("handler-subscription"),_=class _{constructor(t){if(this.url=t.url,this.authUrl=t.authUrl,this.socket=t.socket,this.storageType=t.storageType||"localStorage",this.cacheSize=t.cacheSize||250,this.cacheTime=t.cacheTime||H*8,this.timeout=t.timeout||H*15,this.sessionStorageProp=t.sessionStorageProp||"USER_SESSION_TOKEN",this.refreshStorageProp=t.refreshStorageProp||"USER_REFRESH_TOKEN",this.loginRoute=t.loginRoute||"/",this.useCache=t.useCache||!1,this.useDebug=t.useDebug||!1,this.useAuthorization=t.useAuthorization||!1,this.authMode=t.authMode||"bearer",this.credentials=t.credentials,this.sessionCheckUrl=t.sessionCheckUrl,this.useRetries=t.useRetries||!1,this.useSubscription=t.useSubscription||!1,this.redirectOnError=t.redirectOnError||!1,this.retryWhen=t.retryWhen||[503,504,521,522,524],this.authenticationErrors=t.authenticationErrors||[401,403],this.retries=t.retries||[f,f*2,f*3,f*5,f*8,f*13,f*21,f*34],this.partner=t.partner||void 0,this.usePersistedQueries=t.usePersistedQueries||!1,this.persistedOperationField=t.persistedOperationField||"doc_id",!this.url)throw new Error("[HTTPFetchEndpoint] You must at least set url parameter.");if(this.useAuthorization&&!this.authUrl)throw new Error('[useAuthorization] Authorization is set but no "authUrl" was provided.');if(this.authMode==="cookie"&&this.sessionCheckUrl!==!1&&!this.authUrl)throw new Error('[authMode:cookie] Cookie auth refreshes against "authUrl" \u2014 provide it or set "sessionCheckUrl: false" to disable the probe.');if(this.StorageHandler=new T(this),this.useSubscription){if(!this.socket)throw new Error("[useSubscription] If you choose to use WebSocket you must set a socket endpoint.");this[B]=ue(this)()}this[j]=se(this),this[le]()}[(j,B,le)](){let t=g.Network.create(this[j],this[B]||null),r=new g.RecordSource,n=new g.Store(r);this.Environment=new g.Environment({network:t,store:n})}};o(_,"RelayEnvironment");var x=_;var I=G(require("react"));var de=I.default.createContext(void 0);function Oe({children:e,environment:t}){return I.default.createElement(de.Provider,{value:{environment:t}},e)}o(Oe,"EnvironmentProvider");function ke(){let e=I.default.useContext(de);if(e===void 0)throw new Error("useEnvironment must be used within a EnvironmentProvider");if(e.environment===null||e.environment===void 0)throw new Error("You should provide a Relay Environment to EnvironmentProvider component");return e}o(ke,"useEnvironment");var z=require("node:crypto");var pe="useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";var Ue=128,b,E;function Me(e){if(e<0)throw new RangeError("Wrong ID size");try{!b||b.length<e?(b=Buffer.allocUnsafe(e*Ue),z.webcrypto.getRandomValues(b),E=0):E+e>b.length&&(z.webcrypto.getRandomValues(b),E=0)}catch(t){throw b=void 0,t}E+=e}o(Me,"fillPool");function he(e=21){Me(e|=0);let t="";for(let r=E-e;r<E;r++)t+=pe[b[r]&63];return t}o(he,"nanoid");var w=require("relay-runtime");function me(e){return e!==null&&typeof e=="object"}o(me,"isObject");var Ie=he();function Ae({parentId:e,itemId:t,parentFieldName:r,store:n}){let i=n.get(e),s=i.getLinkedRecords(r);i.setLinkedRecords(s.filter(a=>a.dataID!==t),r)}o(Ae,"listRecordRemoveUpdater");function De({parentId:e,item:t,type:r,parentFieldName:n,store:i}){let s=i.create(t.id,r);Object.keys(t).forEach(l=>{s.setValue(t[l],l)});let a=i.get(e),u=a.getLinkedRecords(n);a.setLinkedRecords([...u,s],n)}o(De,"listRecordAddUpdater");function fe(e,t,r,n,i=!1){if(n){let s=e.get(t),a=w.ConnectionHandler.getConnection(s,r);if(!a)return;i?w.ConnectionHandler.insertEdgeBefore(a,n):w.ConnectionHandler.insertEdgeAfter(a,n)}}o(fe,"connectionUpdater");function Ne({parentId:e,store:t,connectionName:r,item:n,customNode:i,itemType:s}){let a=i||t.create(n.id,s);!i&&Object.keys(n).forEach(l=>{a.setValue(n[l],l)});let u=t.create(`client:newEdge:${String(a.getDataID).match(/[^:]+$/)[0]}`,`${s}Edge`);u.setLinkedRecord(a,"node"),fe(t,e,r,u)}o(Ne,"optimisticConnectionUpdater");function Fe({parentId:e,connectionName:t,nodeId:r,store:n}){let i=n.get(e),s=w.ConnectionHandler.getConnection(i,t);if(!s){console.warn(`Connection ${t} not found on ${e}`);return}w.ConnectionHandler.deleteNode(s,r)}o(Fe,"connectionDeleteEdgeUpdater");function He({object:e,proxy:t}){Object.keys(e).forEach(r=>{me(e[r])||Array.isArray(e[r])||t.setValue(e[r],r)})}o(He,"copyObjScalarsToProxy");var ye=require("relay-runtime");var Le=o((e,t)=>new Promise((r,n)=>{(0,ye.commitMutation)(e,{...t,onError:n,onCompleted:r})}),"commitMutation");0&&(module.exports={ClientMutationID,CreateRelayEnvironment,EnvironmentProvider,commitMutation,connectionDeleteEdgeUpdater,connectionUpdater,copyObjScalarsToProxy,isObject,listRecordAddUpdater,listRecordRemoveUpdater,optimisticConnectionUpdater,useEnvironment});
@@ -0,0 +1 @@
1
+ import{a,b,c,d,e,f,g,h}from"../chunks/chunk-XJA2SRQY.esm.js";import"../chunks/chunk-GZONWSRB.esm.js";export{b as ClientMutationID,g as connectionDeleteEdgeUpdater,e as connectionUpdater,h as copyObjScalarsToProxy,a as isObject,d as listRecordAddUpdater,c as listRecordRemoveUpdater,f as optimisticConnectionUpdater};
@@ -0,0 +1 @@
1
+ import"../chunks/chunk-FYOI5Q5V.esm.js";
@@ -204,4 +204,31 @@ export interface RelayArgsInterface {
204
204
  * @defaultValue undefined
205
205
  */
206
206
  partner?: string;
207
+ /**
208
+ * Enables **persisted queries**: the request carries only the
209
+ * operation hash generated at build time by `relay-compiler`
210
+ * (`persistConfig`) plus the variables — the GraphQL text never
211
+ * leaves the build.
212
+ *
213
+ * The server must hold the matching query map (hash → text) and
214
+ * accept the hash in the field named by `persistedOperationField`
215
+ * — in the JSON body for queries/mutations, or in
216
+ * `payload.extensions` for subscriptions over `graphql-ws`.
217
+ *
218
+ * Requires the consumer's `relay.config.json` to define
219
+ * `persistConfig` — with the flag on, an artifact without an `id`
220
+ * (compiler ran without `persistConfig`) throws a descriptive error
221
+ * instead of silently sending nothing.
222
+ *
223
+ * @defaultValue false
224
+ */
225
+ usePersistedQueries?: boolean;
226
+ /**
227
+ * Name of the request field that carries the persisted-operation
228
+ * hash when `usePersistedQueries` is on. Lets the client match
229
+ * whatever field the server's allowlist middleware expects.
230
+ *
231
+ * @defaultValue `'doc_id'`
232
+ */
233
+ persistedOperationField?: string;
207
234
  }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Public barrel for the `setupRelayEnvironment` module.
3
+ *
4
+ * Exports only the public surface: the `CreateRelayEnvironment` class
5
+ * (default export of the `setupRelayEnvironment.ts` file). The internals
6
+ * `fetchQuery`, `fetchWithRetries`, `storage`, `subscriptionHandler`,
7
+ * `executeEnvironment` and `setupRelayEnvironment.helpers` stay hidden
8
+ * — they are part of the implementation and not part of the contract.
9
+ */
10
+ export { default as CreateRelayEnvironment } from './setupRelayEnvironment';
11
+ export { default } from './setupRelayEnvironment';
@@ -0,0 +1 @@
1
+ import{a}from"../chunks/chunk-KIA6YWO5.esm.js";import"../chunks/chunk-GZONWSRB.esm.js";export{a as CreateRelayEnvironment,a as default};
@@ -63,6 +63,19 @@ declare const kSubscriptionHandler: unique symbol;
63
63
  * });
64
64
  * ```
65
65
  *
66
+ * @example With persisted queries (server-aligned hash allowlist)
67
+ * ```ts
68
+ * // Requires `persistConfig` in the consumer's relay.config.json, e.g.
69
+ * // { "persistConfig": { "file": "./persisted/queryMap.json", "algorithm": "MD5" } }.
70
+ * // The request body becomes `{ name, doc_id, variables }` — the GraphQL
71
+ * // text never leaves the build; the server resolves the hash against the
72
+ * // query map and refuses anything else.
73
+ * const { Environment } = new CreateRelayEnvironment({
74
+ * url: 'https://api.example.com/graphql/',
75
+ * usePersistedQueries: true,
76
+ * });
77
+ * ```
78
+ *
66
79
  * @see {@link RelayArgsInterface} for the complete list of configuration options.
67
80
  */
68
81
  export default class RelayEnvironment implements RelayArgsInterface {
@@ -112,6 +125,10 @@ export default class RelayEnvironment implements RelayArgsInterface {
112
125
  storageType: 'cookie' | 'localStorage';
113
126
  /** Optional partner identifier, sent in the `X-Partner` header. */
114
127
  partner?: string;
128
+ /** Sends only the build-time operation hash instead of the GraphQL text. */
129
+ usePersistedQueries?: boolean;
130
+ /** Request field carrying the persisted-operation hash. */
131
+ persistedOperationField?: string;
115
132
  /**
116
133
  * Fetch function assembled for use by Relay's `Network`. Internal.
117
134
  */
@@ -38,6 +38,11 @@ export declare const getBrowserLocation: () => string;
38
38
  * @param config - Environment configuration (used to check `loginRoute`).
39
39
  */
40
40
  export declare const redirectUser: (url: string, redirectTo: string, config?: RelayArgsInterface) => void;
41
+ /** Subset of the Environment configuration the body builders read. */
42
+ interface PersistedConfig {
43
+ usePersistedQueries?: boolean;
44
+ persistedOperationField?: string;
45
+ }
41
46
  /**
42
47
  * Builds the GraphQL request body — multipart if there are uploadables,
43
48
  * JSON otherwise.
@@ -45,8 +50,10 @@ export declare const redirectUser: (url: string, redirectTo: string, config?: Re
45
50
  * @param request - Relay request parameters.
46
51
  * @param variables - Operation variables.
47
52
  * @param uploadables - Files to upload (optional).
53
+ * @param config - Environment configuration subset for persisted queries
54
+ * (optional; absent behaves as `usePersistedQueries: false`).
48
55
  */
49
- export declare function getRequestBody(request: RequestParameters, variables: Variables, uploadables?: UploadableMap): string | FormData;
56
+ export declare function getRequestBody(request: RequestParameters, variables: Variables, uploadables?: UploadableMap, config?: PersistedConfig): string | FormData;
50
57
  /**
51
58
  * Resolves the effective `credentials` value for the fetches (GraphQL and
52
59
  * session verification/refresh).
@@ -96,3 +103,4 @@ export declare const handleData: (response: Response, sink: Sink, callback: (dat
96
103
  export declare const ONE_SECOND = 1000;
97
104
  /** Time constant: 1 minute in milliseconds. */
98
105
  export declare const ONE_MINUTE: number;
106
+ export {};
@@ -8,22 +8,28 @@
8
8
  * Not exposed by the root barrel — internal implementation of
9
9
  * `CreateRelayEnvironment`.
10
10
  */
11
+ /** Subset of the Environment configuration the subscription handler reads. */
12
+ interface SubscriptionSettings {
13
+ socket?: string;
14
+ usePersistedQueries?: boolean;
15
+ persistedOperationField?: string;
16
+ }
11
17
  /**
12
18
  * Builds the subscription function for Relay's `Network.create`.
13
19
  *
14
- * Returns a function (to be called once, during Environment setup)
15
- * which in turn returns the subscribe function that Relay will invoke
16
- * for each subscription operation. The adaptation of `graphql-ws`
17
- * `next/error/complete` callbacks to the Relay Observable `Sink`
20
+ * Returns a factory (invoked once, during Environment setup) which yields the
21
+ * subscribe function Relay calls for each subscription operation. The
22
+ * `graphql-ws` client is created lazily on the first subscription — no socket
23
+ * is opened for environments that never subscribe. The adaptation of
24
+ * `graphql-ws` `next/error/complete` callbacks to the Relay Observable `Sink`
18
25
  * happens here.
19
26
  *
20
- * @param settings - Object with the WebSocket endpoint URL (`socket`).
21
- * When `useSubscription` is `true` but `socket` was not configured,
22
- * the `CreateRelayEnvironment` constructor throws before this
23
- * function is called.
24
- * @returns Function that, when invoked, returns the subscribe function
27
+ * @param settings - Environment configuration subset: the WebSocket endpoint
28
+ * (`socket`) plus the persisted-queries flags. When `useSubscription` is
29
+ * `true` but `socket` was not configured, the `CreateRelayEnvironment`
30
+ * constructor throws before this function is called.
31
+ * @returns Factory that, when invoked, returns the subscribe function
25
32
  * compatible with `Network.create(fetchFn, subscribeFn)`.
26
33
  */
27
- export declare function setupSubscription(settings: {
28
- socket?: string;
29
- }): () => any;
34
+ export declare function setupSubscription(settings: SubscriptionSettings): () => any;
35
+ export {};
@@ -0,0 +1 @@
1
+ import{a,b}from"../chunks/chunk-54QV6DFY.esm.js";import"../chunks/chunk-GZONWSRB.esm.js";export{a as EnvironmentProvider,b as useEnvironment};
package/package.json CHANGED
@@ -1,10 +1,38 @@
1
1
  {
2
2
  "name": "@apollion-dsi/relay",
3
- "version": "0.27.3",
3
+ "version": "0.28.0",
4
4
  "description": "Frontend services regarding Relay Environment",
5
5
  "main": "lib/index.js",
6
6
  "module": "lib/index.esm.js",
7
7
  "types": "lib/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./lib/index.d.ts",
11
+ "import": "./lib/index.esm.js",
12
+ "require": "./lib/index.js"
13
+ },
14
+ "./setupRelayEnvironment": {
15
+ "types": "./lib/setupRelayEnvironment/index.d.ts",
16
+ "import": "./lib/setupRelayEnvironment/index.esm.js"
17
+ },
18
+ "./useEnvironment": {
19
+ "types": "./lib/useEnvironment/index.d.ts",
20
+ "import": "./lib/useEnvironment/index.esm.js"
21
+ },
22
+ "./relayArgsInterface": {
23
+ "types": "./lib/relayArgsInterface/index.d.ts",
24
+ "import": "./lib/relayArgsInterface/index.esm.js"
25
+ },
26
+ "./mutationUtils": {
27
+ "types": "./lib/mutationUtils/index.d.ts",
28
+ "import": "./lib/mutationUtils/index.esm.js"
29
+ },
30
+ "./commitMutation": {
31
+ "types": "./lib/commitMutation/index.d.ts",
32
+ "import": "./lib/commitMutation/index.esm.js"
33
+ },
34
+ "./package.json": "./package.json"
35
+ },
8
36
  "files": [
9
37
  "lib/**/*"
10
38
  ],
@@ -19,7 +47,7 @@
19
47
  "prettier": "prettier --check **/*.{ts,tsx} --ignore-path .gitignore --no-error-on-unmatched-pattern",
20
48
  "format": "yarn prettier --write",
21
49
  "validate": "./scripts/validate.sh",
22
- "validate:tests": "relay-compiler && jest --coverage",
50
+ "validate:tests": "relay-compiler && relay-compiler relay.persisted.config.json && jest --coverage",
23
51
  "build": "node -e \"require('fs').rmSync('./lib',{recursive:true,force:true})\" && node ./esbuild && tsc --emitDeclarationOnly",
24
52
  "test": "yarn validate",
25
53
  "code:check": "yarn lint && yarn coverage",
@@ -39,7 +67,7 @@
39
67
  "relay-test-utils": "^21.0.0"
40
68
  },
41
69
  "devDependencies": {
42
- "@apollion-dsi/eslint-config": "0.9.1",
70
+ "@apollion-dsi/eslint-config": "0.10.0",
43
71
  "@babel/core": "7.29.0",
44
72
  "@babel/plugin-transform-class-properties": "7.27.1",
45
73
  "@babel/plugin-transform-runtime": "7.29.0",
package/README.MD DELETED
@@ -1,119 +0,0 @@
1
- # @apollion-dsi/relay
2
-
3
- Helpers to configure and use **Relay** (`react-relay` / `relay-runtime`)
4
- the Apollion DS way. Covers Environment creation with auth,
5
- multipart upload, subscriptions via WebSocket, and session cookies.
6
-
7
- [![npm](https://img.shields.io/npm/v/@apollion-dsi/relay.svg)](https://www.npmjs.com/package/@apollion-dsi/relay)
8
-
9
- ## Stack
10
-
11
- - **react-relay** 20.x
12
- - **relay-runtime** 20.x
13
- - **graphql** 15.x
14
- - **graphql-ws** 6.x (subscriptions)
15
- - **fetch-multipart-graphql** (uploads)
16
- - **js-cookie** (session)
17
-
18
- ## Installation
19
-
20
- ```bash
21
- yarn add @apollion-dsi/relay react@19.2.6
22
- ```
23
-
24
- > To generate Relay artifacts, the consumer also needs
25
- > `relay-compiler` as a dev dependency and a `relay.config.js` setup.
26
-
27
- ## Basic usage
28
-
29
- Create a Relay Environment for the application:
30
-
31
- ```ts
32
- import { CreateRelayEnvironment } from '@apollion-dsi/relay';
33
-
34
- export const { Environment } = new CreateRelayEnvironment({
35
- url: 'https://api.example.com/graphql',
36
- });
37
- ```
38
-
39
- Then wrap the React tree with `RelayEnvironmentProvider`:
40
-
41
- ```tsx
42
- import { RelayEnvironmentProvider } from 'react-relay';
43
- import { Environment } from './relay';
44
- import { App } from './app';
45
-
46
- <RelayEnvironmentProvider environment={Environment}>
47
- <App />
48
- </RelayEnvironmentProvider>;
49
- ```
50
-
51
- ## Helper features
52
-
53
- - **Authentication** in two modes: `Bearer` (JS token) or **httpOnly
54
- cookie** (see below).
55
- - **Multipart uploads** (`fetch-multipart-graphql`) — send files
56
- directly in mutations.
57
- - **Subscriptions** via `graphql-ws`.
58
- - Pluggable **network retry / refresh**.
59
-
60
- ## Authentication: Bearer vs. httpOnly cookie
61
-
62
- The Environment supports two auth models via `authMode`.
63
-
64
- ### `authMode: 'bearer'` (default — backwards compatible)
65
-
66
- Reads the `sessionToken` from storage (`localStorage`/JS-readable `cookie`) and
67
- injects `Authorization: Bearer <token>` into every request. Session
68
- verification uses the `${authUrl}user/me` probe. Behavior is identical to
69
- previous versions — existing consumers don't need to change anything.
70
-
71
- ```ts
72
- new CreateRelayEnvironment({
73
- url: 'https://api.example.com/graphql/',
74
- authUrl: 'https://api.example.com/auth/',
75
- useAuthorization: true, // injects Bearer
76
- storageType: 'cookie', // JS-readable cookie or 'localStorage'
77
- });
78
- ```
79
-
80
- ### `authMode: 'cookie'` (httpOnly cookie session — recommended for SPAs)
81
-
82
- The secure model: the session token lives in an **httpOnly + SameSite**
83
- cookie (invisible to JS, immune to XSS). The Environment does **not** read
84
- the token or inject `Authorization` — `credentials: 'include'` (the default
85
- in this mode) makes the browser attach the cookie automatically, including
86
- cross-origin. On an auth error (e.g. 401), the refresh is fired as a `POST`
87
- to `authUrl` with `credentials: 'include'` — **without** probing `user/me`.
88
-
89
- ```ts
90
- new CreateRelayEnvironment({
91
- url: 'https://api.example.com/graphql/',
92
- authUrl: 'https://api.example.com/auth/refresh/', // on-401 refresh target
93
- authMode: 'cookie',
94
- redirectOnError: true,
95
- loginRoute: '/login',
96
- });
97
- ```
98
-
99
- Related options:
100
-
101
- | Option | Default | What it does |
102
- |---|---|---|
103
- | `authMode` | `'bearer'` | `'bearer'` (token + header) or `'cookie'` (httpOnly). |
104
- | `credentials` | cookie→`'include'`; bearer→omitted | `RequestCredentials` forwarded to the GraphQL and refresh fetches. Works in both modes. |
105
- | `sessionCheckUrl` | mode-dependent | Overrides the probe URL; `false` disables the probe (auth error → direct logout, no extra request). |
106
-
107
- ## Scripts (workspace)
108
-
109
- | Script | What it does |
110
- |---|---|
111
- | `yarn workspace @apollion-dsi/relay run validate` | Lint + types + prettier + Jest + build. |
112
- | `yarn workspace @apollion-dsi/relay run coverage` | Jest coverage. |
113
- | `yarn workspace @apollion-dsi/relay run build` | esbuild + types emit. |
114
- | `yarn workspace @apollion-dsi/relay run audit-dependencies` | audit-ci. |
115
- | `yarn workspace @apollion-dsi/relay run pretest` | `relay-compiler` (generates `__generated__` before the tests). |
116
-
117
- ## License
118
-
119
- MIT.