Docs
Overview

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-relay 21 · relay-runtime 21 · graphql 16 · graphql-ws 6 (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.6

Strict 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

ModuleWhat it does
CreateRelayEnvironmentEnvironment factory with auth, retries, cache, and subscriptions.
useEnvironment / EnvironmentProviderThe single Relay provider — backs both useEnvironment() and every react-relay store hook (with MockEnvironment support in tests).
commitMutationPromise-based wrapper around relay-runtime's commitMutation.
mutationUtilsHelpers for updaters: inserts/removes in lists and connections, optimistic ones, ClientMutationID.
RelayArgsInterface / SinkPublic 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:

PropTypeDefaultDescription
urlstring— (required)GraphQL server URL.
authUrlstringundefinedAuthentication service URL.
socketstringundefinedWebSocket URL (subscriptions).
retriesnumber[][1, 2, 3, 5, 8, 13, 21, 34] (seconds)Retry backoff.
timeoutnumber15 minutesRequest timeout.
useSubscriptionbooleanfalseEnables subscriptions via graphql-ws.
useAuthorizationbooleanfalseAdds the Authorization header.
useCachebooleanfalseResponse caching via QueryResponseCache.
cacheTimenumber480000 (8 minutes)Cache TTL in ms.
cacheSizenumber250Maximum cached queries.
useRetriesbooleanfalseEnables automatic retry.
useDebugbooleanfalseVerbose retry logs in development.
sessionStoragePropstring'USER_SESSION_TOKEN'Session variable name.
refreshStoragePropstring'USER_REFRESH_TOKEN'Refresh variable name.
loginRoutestring'/'Login route (does not receive Authorization).
redirectOnErrorbooleanfalseAuto-logout on authentication error.
retryWhennumber[][504, 503, 521, 522, 524]HTTP codes that trigger retry.
authenticationErrorsnumber[][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.
credentialsRequestCredentialscookie→'include'; bearer→omittedForwarded to the GraphQL/refresh fetches.
sessionCheckUrlstring | falsemode-dependentOverrides the session probe URL; false disables it.
partnerstringundefinedSent as the X-Partner header (tenants/whitelabels).
usePersistedQueriesbooleanfalseSends only the build-time operation hash (see below).
persistedOperationFieldstring'doc_id'Request field carrying the persisted hash.
initialRecordsRelayInitialRecordsundefined (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';