Relay Core
@apollion-dsi/relay is Apollion's helper package for apps that use
Relay (opens in a new tab). It encapsulates the Environment setup, offers
Promise-based helpers for mutations, connection updater
utilities, and a Context/hook to propagate the Environment down the tree.
Stack:
react-relay21 ·relay-runtime21 ·graphql16 ·graphql-ws6 (subscriptions) ·fetch-multipart-graphql(uploads).
Why it exists
Canonical Relay setup involves wiring up Network, RecordSource, Store,
a retry policy, token refresh, redirect on expired session, and a
WebSocket for subscriptions — repeated in every app it is a classic source of
divergence. The package consolidates that boilerplate behind a single
CreateRelayEnvironment class.
Installation
yarn add @apollion-dsi/relay react@19.2.6Strict peer dep on React 19. The consumer also needs to install
relay-compiler as a dev dep and configure relay.config.js to generate the
__generated__ artifacts.
Minimal setup
import { CreateRelayEnvironment } from '@apollion-dsi/relay';
export const { Environment, StorageHandler } = new CreateRelayEnvironment({
url: 'https://api.example.com/graphql/',
});Plug into React with EnvironmentProvider — the single Relay provider,
backing both the DS useEnvironment() hook and every react-relay store hook
(useLazyLoadQuery, usePreloadedQuery, useFragment, useSubscription).
Do not also mount react-relay's RelayEnvironmentProvider yourself —
EnvironmentProvider already does:
import { EnvironmentProvider } from '@apollion-dsi/relay';
import { Environment } from './relay';
<EnvironmentProvider environment={Environment}>
<App />
</EnvironmentProvider>;Package map
| Module | What it does |
|---|---|
CreateRelayEnvironment | Environment factory with auth, retries, cache, and subscriptions. |
useEnvironment / EnvironmentProvider | The single Relay provider — backs both useEnvironment() and every react-relay store hook (with MockEnvironment support in tests). |
commitMutation | Promise-based wrapper around relay-runtime's commitMutation. |
mutationUtils | Helpers for updaters: inserts/removes in lists and connections, optimistic ones, ClientMutationID. |
RelayArgsInterface / Sink | Public types for the configuration and the Observable sink. |
Client-side authentication
CreateRelayEnvironment exposes a StorageHandler to manage tokens
in the browser. Two strategies supported via storageType:
'localStorage'(default)'cookie'
const { Environment, StorageHandler } = new CreateRelayEnvironment({
url: '...',
useAuthorization: true,
storageType: 'cookie',
});
// After login:
StorageHandler.setTokens({ sessionToken: 'jwt...', refreshToken: 'r...' });
// Retrieve:
const { sessionToken, refreshToken } = StorageHandler.getTokens();
// Logout:
StorageHandler.clear();Configuration options
Complete list of options supported by CreateRelayEnvironment —
details in RelayArgsInterface:
| Prop | Type | Default | Description |
|---|---|---|---|
url | string | — (required) | GraphQL server URL. |
authUrl | string | undefined | Authentication service URL. |
socket | string | undefined | WebSocket URL (subscriptions). |
retries | number[] | [1, 2, 3, 5, 8, 13, 21, 34] (seconds) | Retry backoff. |
timeout | number | 15 minutes | Request timeout. |
useSubscription | boolean | false | Enables subscriptions via graphql-ws. |
useAuthorization | boolean | false | Adds the Authorization header. |
useCache | boolean | false | Response caching via QueryResponseCache. |
cacheTime | number | 480000 (8 minutes) | Cache TTL in ms. |
cacheSize | number | 250 | Maximum cached queries. |
useRetries | boolean | false | Enables automatic retry. |
useDebug | boolean | false | Verbose retry logs in development. |
sessionStorageProp | string | 'USER_SESSION_TOKEN' | Session variable name. |
refreshStorageProp | string | 'USER_REFRESH_TOKEN' | Refresh variable name. |
loginRoute | string | '/' | Login route (does not receive Authorization). |
redirectOnError | boolean | false | Auto-logout on authentication error. |
retryWhen | number[] | [504, 503, 521, 522, 524] | HTTP codes that trigger retry. |
authenticationErrors | number[] | [401, 403] | Codes that trigger refresh/redirect. |
storageType | 'cookie' | 'localStorage' | 'localStorage' | Token storage strategy. |
authMode | 'bearer' | 'cookie' | 'bearer' | Auth model: JS token + header, or httpOnly cookie session. |
credentials | RequestCredentials | cookie→'include'; bearer→omitted | Forwarded to the GraphQL/refresh fetches. |
sessionCheckUrl | string | false | mode-dependent | Overrides the session probe URL; false disables it. |
partner | string | undefined | Sent as the X-Partner header (tenants/whitelabels). |
usePersistedQueries | boolean | false | Sends only the build-time operation hash (see below). |
persistedOperationField | string | 'doc_id' | Request field carrying the persisted hash. |
initialRecords | RelayInitialRecords | undefined (empty store) | Seeds the store at creation with app-owned records (see below). |
Persisted queries
With usePersistedQueries: true, every operation ships as a build-time hash
aligned with the server — the GraphQL text never leaves the build. Requires
persistConfig in the consumer's relay.config.json:
{ "persistConfig": { "file": "./persisted/queryMap.json", "algorithm": "MD5" } }The compiler writes the query map (hash → text) that the server loads as its
allowlist, and stamps each artifact's id. The request body becomes
{ name, doc_id, variables }; uploads carry the hash inside the multipart
operations field; subscriptions send it in payload.extensions.doc_id.
Anything outside the allowlist — raw text or an unknown hash — is refused by
the server, which also gains cache-by-hash for free. Details in
CreateRelayEnvironment.
App-context seam
initialRecords seeds the Relay store at creation with app-owned state —
config, limits, route/session context, DS-prop drivers — read back through
the same fragment/hook machinery as server data, via a client schema
extension. Off by default (empty store):
import { CreateRelayEnvironment } from '@apollion-dsi/relay';
import { ROOT_ID, ROOT_TYPE } from 'relay-runtime';
const { Environment } = new CreateRelayEnvironment({
url: '...',
initialRecords: {
[ROOT_ID]: {
__id: ROOT_ID,
__typename: ROOT_TYPE,
// read via a client schema extension: `extend type Query { appLocale: String }`
appLocale: 'en-US',
},
},
});The exported RelayInitialRecords type is kept in lockstep with
relay-runtime via ConstructorParameters<typeof RecordSource>[0], so it
never drifts from the runtime it seeds.
Granular imports
Each module can be imported individually to reduce bundle size:
import { CreateRelayEnvironment } from '@apollion-dsi/relay/setupRelayEnvironment';
import { commitMutation } from '@apollion-dsi/relay/commitMutation';
import { connectionDeleteEdgeUpdater } from '@apollion-dsi/relay/mutationUtils';Or everything via the root barrel:
import { CreateRelayEnvironment, commitMutation, useEnvironment } from '@apollion-dsi/relay';