UNPKG

relay-runtime

Version:

A core runtime for building GraphQL-driven applications.

1,506 lines (1,341 loc) • 44.9 kB
/** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @flow * @format * @oncall relay */ 'use strict'; import type { ActorIdentifier, IActorEnvironment, } from '../multi-actor-environment'; import type { GraphQLResponse, GraphQLResponseWithData, INetwork, PayloadData, PayloadError, UploadableMap, } from '../network/RelayNetworkTypes'; import type RelayObservable from '../network/RelayObservable'; import type {RequestIdentifier} from '../util/getRequestIdentifier'; import type { NormalizationArgument, NormalizationLinkedField, NormalizationRootNode, NormalizationScalarField, NormalizationSelectableNode, } from '../util/NormalizationNode'; import type { ReaderClientEdgeToServerObject, ReaderFragment, ReaderLinkedField, } from '../util/ReaderNode'; import type { ConcreteRequest, RequestParameters, } from '../util/RelayConcreteNode'; import type { CacheConfig, DataID, Disposable, RenderPolicy, UpdatableFragment, UpdatableQuery, Variables, } from '../util/RelayRuntimeTypes'; import type {TRelayFieldError} from './RelayErrorTrie'; import type { Record as RelayModernRecord, RecordJSON, } from './RelayModernRecord'; import type {InvalidationState} from './RelayModernStore'; import type RelayOperationTracker from './RelayOperationTracker'; import type {RecordState} from './RelayRecordState'; import type {NormalizationOptions} from './RelayResponseNormalizer'; export opaque type FragmentType = empty; export type OperationTracker = RelayOperationTracker; export type Record = RelayModernRecord; export type MutationParameters = { +response: {...}, +variables: {...}, +rawResponse?: {...}, }; export type FragmentMap = {[key: string]: ReaderFragment, ...}; /** * The results of a selector given a store/RecordSource. */ export type SelectorData = {[key: string]: mixed, ...}; export type SingularReaderSelector = { +kind: 'SingularReaderSelector', +dataID: DataID, +isWithinUnmatchedTypeRefinement: boolean, +clientEdgeTraversalPath: ClientEdgeTraversalPath | null, +node: ReaderFragment, +owner: RequestDescriptor, +variables: Variables, }; export type ReaderSelector = SingularReaderSelector | PluralReaderSelector; export type PluralReaderSelector = { +kind: 'PluralReaderSelector', +selectors: $ReadOnlyArray<SingularReaderSelector>, }; export type FieldErrorType = | 'MISSING_DATA' | 'MISSING_REQUIRED' | 'PAYLOAD_ERROR'; export type RequestDescriptor = { +identifier: RequestIdentifier, +node: ConcreteRequest, +variables: Variables, +cacheConfig: ?CacheConfig, }; /** * A selector defines the starting point for a traversal into the graph for the * purposes of targeting a subgraph. */ export type NormalizationSelector = { +dataID: DataID, +node: NormalizationSelectableNode, +variables: Variables, }; export type ErrorResponseField = | RelayFieldPayloadErrorEvent | MissingExpectedDataLogEvent | MissingExpectedDataThrowEvent | RelayResolverErrorEvent | MissingRequiredFieldLogEvent | MissingRequiredFieldThrowEvent; export type ErrorResponseFields = Array<ErrorResponseField>; export type ClientEdgeTraversalInfo = { +readerClientEdge: ReaderClientEdgeToServerObject, +clientEdgeDestinationID: DataID, }; export type ClientEdgeTraversalPath = $ReadOnlyArray<ClientEdgeTraversalInfo | null>; export type MissingClientEdgeRequestInfo = { +request: ConcreteRequest, +clientEdgeDestinationID: DataID, }; /** * A representation of a selector and its results at a particular point in time. */ export type Snapshot = { +data: ?SelectorData, +isMissingData: boolean, +missingLiveResolverFields?: $ReadOnlyArray<DataID>, +missingClientEdges: null | $ReadOnlyArray<MissingClientEdgeRequestInfo>, +seenRecords: DataIDSet, +selector: SingularReaderSelector, +errorResponseFields: ?ErrorResponseFields, }; /** * An operation selector describes a specific instance of a GraphQL operation * with variables applied. * * - `root`: a selector intended for processing server results or retaining * response data in the store. * - `fragment`: a selector intended for use in reading or subscribing to * the results of the the operation. */ export type OperationDescriptor = { +fragment: SingularReaderSelector, +request: RequestDescriptor, +root: NormalizationSelector, }; /** * Arbitrary data e.g. received by a container as props. */ export type Props = {[key: string]: mixed, ...}; /** * The type of the `relay` property set on React context by the React/Relay * integration layer (e.g. QueryRenderer, FragmentContainer, etc). */ export type RelayContext = { environment: IEnvironment, getEnvironmentForActor?: ?( actorIdentifier: ActorIdentifier, ) => IActorEnvironment, }; /** * The results of reading the results of a FragmentMap given some input * `Props`. */ export type FragmentSpecResults = {[key: string]: mixed, ...}; /** * A utility for resolving and subscribing to the results of a fragment spec * (key -> fragment mapping) given some "props" that determine the root ID * and variables to use when reading each fragment. When props are changed via * `setProps()`, the resolver will update its results and subscriptions * accordingly. Internally, the resolver: * - Converts the fragment map & props map into a map of `Selector`s. * - Removes any resolvers for any props that became null. * - Creates resolvers for any props that became non-null. * - Updates resolvers with the latest props. */ export interface FragmentSpecResolver { /** * Stop watching for changes to the results of the fragments. */ dispose(): void; /** * Get the current results. */ resolve(): FragmentSpecResults; /** * Update the resolver with new inputs. Call `resolve()` to get the updated * results. */ setProps(props: Props): void; /** * Override the variables used to read the results of the fragments. Call * `resolve()` to get the updated results. */ setVariables(variables: Variables, node: ConcreteRequest): void; /** * Subscribe to resolver updates. * Overrides existing callback (if one has been specified). */ setCallback(props: Props, callback: () => void): void; } /** * A read-only interface for accessing cached graph data. */ export interface RecordSource { get(dataID: DataID): ?Record; getRecordIDs(): Array<DataID>; getStatus(dataID: DataID): RecordState; has(dataID: DataID): boolean; size(): number; toJSON(): RecordSourceJSON; } /** * A collection of records keyed by id. */ export type RecordSourceJSON = {[DataID]: ?RecordJSON}; /** * A read/write interface for accessing and updating graph data. */ export interface MutableRecordSource extends RecordSource { clear(): void; delete(dataID: DataID): void; remove(dataID: DataID): void; set(dataID: DataID, record: Record): void; } export type CheckOptions = { handlers: $ReadOnlyArray<MissingFieldHandler>, defaultActorIdentifier: ActorIdentifier, getTargetForActor: (actorIdentifier: ActorIdentifier) => MutableRecordSource, getSourceForActor: (actorIdentifier: ActorIdentifier) => RecordSource, }; export type OperationAvailability = | {status: 'available', fetchTime: ?number} | {status: 'stale'} | {status: 'missing'}; export type {InvalidationState} from './RelayModernStore'; /** * An interface for keeping multiple views of data consistent across an * application. */ export interface Store { /** * Get a read-only view of the store's internal RecordSource. */ getSource(): RecordSource; /** * Determine if the operation can be resolved with data in the store (i.e. no * fields are missing). */ check( operation: OperationDescriptor, options?: CheckOptions, ): OperationAvailability; /** * Read the results of a selector from in-memory records in the store. */ lookup(selector: SingularReaderSelector): Snapshot; /** * Notify subscribers (see `subscribe`) of any data that was published * (`publish()`) since the last time `notify` was called. * Optionally provide an OperationDescriptor indicating the source operation * that was being processed to produce this run. * * This method should return an array of the affected fragment owners. */ notify( sourceOperation?: OperationDescriptor, invalidateStore?: boolean, ): $ReadOnlyArray<RequestDescriptor>; /** * Publish new information (e.g. from the network) to the store, updating its * internal record source. Subscribers are not immediately notified - this * occurs when `notify()` is called. */ publish(source: RecordSource, idsMarkedForInvalidation?: DataIDSet): void; /** * Ensure that all the records necessary to fulfill the given selector are * retained in memory. The records will not be eligible for garbage collection * until the returned reference is disposed. */ retain(operation: OperationDescriptor): Disposable; /** * Subscribe to changes to the results of a selector. The callback is called * when `notify()` is called *and* records have been published that affect the * selector results relative to the last `notify()`. */ subscribe( snapshot: Snapshot, callback: (snapshot: Snapshot) => void, ): Disposable; /** * The method should disable garbage collection until * the returned reference is disposed. */ holdGC(): Disposable; /** * Record a backup/snapshot of the current state of the store, including * records and derived data such as fragment subscriptions. * This state can be restored with restore(). */ snapshot(): void; /** * Reset the state of the store to the point that snapshot() was last called. */ restore(): void; /** * Will return an opaque snapshot of the current invalidation state of * the data ids that were provided. */ lookupInvalidationState(dataIDs: $ReadOnlyArray<DataID>): InvalidationState; /** * Given the previous invalidation state for those * ids, this function will return: * - false, if the invalidation state for those ids is the same, meaning * **it has not changed** * - true, if the invalidation state for the given ids has changed */ checkInvalidationState(previousInvalidationState: InvalidationState): boolean; /** * Will subscribe the provided callback to the invalidation state of the * given data ids. Whenever the invalidation state for any of the provided * ids changes, the callback will be called, and provide the latest * invalidation state. * Disposing of the returned disposable will remove the subscription. */ subscribeToInvalidationState( invalidationState: InvalidationState, callback: () => void, ): Disposable; /** * Get the current write epoch */ getEpoch(): number; /** * Get the current operation loader if it exists */ getOperationLoader(): ?OperationLoader; } export interface StoreSubscriptions { /** * Subscribe to changes to the results of a selector. The callback is called * when `updateSubscriptions()` is called *and* records have been published that affect the * selector results relative to the last update. */ subscribe( snapshot: Snapshot, callback: (snapshot: Snapshot) => void, ): Disposable; /** * Record a backup/snapshot of the current state of the subscriptions. * This state can be restored with restore(). */ snapshotSubscriptions(source: RecordSource): void; /** * Reset the state of the subscriptions to the point that snapshot() was last called. */ restoreSubscriptions(): void; /** * Notifies each subscription if the snapshot for the subscription selector has changed. * Mutates the updatedOwners array with any owners (RequestDescriptors) associated * with the subscriptions that were notified; i.e. the owners affected by the changes. */ updateSubscriptions( source: RecordSource, updatedRecordIDs: DataIDSet, updatedOwners: Array<RequestDescriptor>, sourceOperation?: OperationDescriptor, ): void; /** * returns the number of subscriptions */ size(): number; } /** * A type that accepts a callback and schedules it to run at some future time. * By convention, implementations should not execute the callback immediately. */ export type Scheduler = (() => void) => void; /** * A type that can schedule callbacks and also cancel them. */ export type TaskScheduler = { +cancel: (id: string) => void, +schedule: (fn: () => void, priority?: TaskPriority) => string, }; export type TaskPriority = 'default' | 'low'; /** * An interface for imperatively getting/setting properties of a `Record`. This interface * is designed to allow the appearance of direct Record manipulation while * allowing different implementations that may e.g. create a changeset of * the modifications. */ export interface RecordProxy { copyFieldsFrom(source: RecordProxy): void; getDataID(): DataID; getLinkedRecord(name: string, args?: ?Variables): ?RecordProxy; getLinkedRecords(name: string, args?: ?Variables): ?Array<?RecordProxy>; getOrCreateLinkedRecord( name: string, typeName: string, args?: ?Variables, ): RecordProxy; getType(): string; getValue(name: string, args?: ?Variables): mixed; getErrors(name: string, args?: ?Variables): ?$ReadOnlyArray<TRelayFieldError>; setLinkedRecord( record: RecordProxy, name: string, args?: ?Variables, ): RecordProxy; setLinkedRecords( records: $ReadOnlyArray<?RecordProxy>, name: string, args?: ?Variables, ): RecordProxy; setValue( value: mixed, name: string, args?: ?Variables, errors?: ?$ReadOnlyArray<TRelayFieldError>, ): RecordProxy; invalidateRecord(): void; } export interface ReadOnlyRecordProxy { getDataID(): DataID; getLinkedRecord(name: string, args?: ?Variables): ?RecordProxy; getLinkedRecords(name: string, args?: ?Variables): ?Array<?RecordProxy>; getType(): string; getValue(name: string, args?: ?Variables): mixed; } /** * A linked field where an updatable fragment is spread has the type * HasUpdatableSpread. * This type is expected by store.readUpdatableFragment. */ export type HasUpdatableSpread<TFragmentType> = { +$updatableFragmentSpreads: TFragmentType, ... }; /** * The return type of calls to readUpdatableQuery and * readUpdatableFragment. */ export type UpdatableData<TData> = { +updatableData: TData, }; /** * An interface for imperatively getting/setting properties of a `RecordSource`. This interface * is designed to allow the appearance of direct RecordSource manipulation while * allowing different implementations that may e.g. create a changeset of * the modifications. */ export interface RecordSourceProxy { create(dataID: DataID, typeName: string): RecordProxy; delete(dataID: DataID): void; get(dataID: DataID): ?RecordProxy; getRoot(): RecordProxy; invalidateStore(): void; readUpdatableQuery<TVariables: Variables, TData>( query: UpdatableQuery<TVariables, TData>, variables: TVariables, ): UpdatableData<TData>; readUpdatableFragment<TFragmentType: FragmentType, TData>( fragment: UpdatableFragment<TFragmentType, TData>, fragmentReference: HasUpdatableSpread<TFragmentType>, ): UpdatableData<TData>; } export interface ReadOnlyRecordSourceProxy { get(dataID: DataID): ?ReadOnlyRecordProxy; getRoot(): ReadOnlyRecordProxy; } /** * Extends the RecordSourceProxy interface with methods for accessing the root * fields of a Selector. */ export interface RecordSourceSelectorProxy extends RecordSourceProxy { getRootField(fieldName: string): ?RecordProxy; getPluralRootField(fieldName: string): ?Array<?RecordProxy>; invalidateStore(): void; } export type SuspenseFragmentLogEvent = { +name: 'suspense.fragment', +data: mixed, +fragment: ReaderFragment, +isRelayHooks: boolean, +isMissingData: boolean, +isPromiseCached: boolean, +pendingOperations: $ReadOnlyArray<RequestDescriptor>, }; export type SuspenseQueryLogEvent = { +name: 'suspense.query', +fetchPolicy: string, +isPromiseCached: boolean, +operation: OperationDescriptor, +queryAvailability: ?OperationAvailability, +renderPolicy: RenderPolicy, }; export type QueryResourceFetchLogEvent = { +name: 'queryresource.fetch', // ID of this query resource request and will be the same // if there is an associated queryresource.retain event. +resourceID: number, +operation: OperationDescriptor, // value from ProfilerContext +profilerContext: mixed, // FetchPolicy from Relay Hooks +fetchPolicy: string, // RenderPolicy from Relay Hooks +renderPolicy: RenderPolicy, +queryAvailability: OperationAvailability, +shouldFetch: boolean, }; export type QueryResourceRetainLogEvent = { +name: 'queryresource.retain', +resourceID: number, // value from ProfilerContext +profilerContext: mixed, }; export type FragmentResourceMissingDataLogEvent = { // Indicates FragmentResource is going to return a result that is missing // data. +name: 'fragmentresource.missing_data', +data: mixed, +fragment: ReaderFragment, +isRelayHooks: boolean, // Are we reading this result from the fragment resource cache? +cached: boolean, }; export type PendingOperationFoundLogEvent = { // Indicates getPendingOperationForFragment identified a pending operation. // Useful for measuring how frequently RelayOperationTracker identifies a // related operation on which to suspend. +name: 'pendingoperation.found', +fragment: ReaderFragment, +fragmentOwner: RequestDescriptor, +pendingOperations: $ReadOnlyArray<RequestDescriptor>, }; export type NetworkInfoLogEvent = { +name: 'network.info', +networkRequestId: number, +info: mixed, }; export type NetworkStartLogEvent = { +name: 'network.start', +networkRequestId: number, +params: RequestParameters, +variables: Variables, +cacheConfig: CacheConfig, }; export type NetworkNextLogEvent = { +name: 'network.next', +networkRequestId: number, +response: GraphQLResponse, }; export type NetworkErrorLogEvent = { +name: 'network.error', +networkRequestId: number, +error: Error, }; export type NetworkCompleteLogEvent = { +name: 'network.complete', +networkRequestId: number, }; export type NetworkUnsubscribeLogEvent = { +name: 'network.unsubscribe', +networkRequestId: number, }; export type ExecuteStartLogEvent = { +name: 'execute.start', +executeId: number, +params: RequestParameters, +variables: Variables, +cacheConfig: CacheConfig, }; export type ExecuteNextStartLogEvent = { +name: 'execute.next.start', +executeId: number, +response: GraphQLResponse, +operation: OperationDescriptor, }; export type ExecuteNextEndLogEvent = { +name: 'execute.next.end', +executeId: number, +response: GraphQLResponse, +operation: OperationDescriptor, }; export type ExecuteAsyncModuleLogEvent = { +name: 'execute.async.module', +executeId: number, +operationName: string, +duration: number, }; export type ExecuteErrorLogEvent = { +name: 'execute.error', +executeId: number, +error: Error, }; export type ExecuteCompleteLogEvent = { +name: 'execute.complete', +executeId: number, }; export type ExecuteNormalizeStart = { +name: 'execute.normalize.start', +operation: OperationDescriptor, }; export type ExecuteNormalizeEnd = { +name: 'execute.normalize.end', +operation: OperationDescriptor, }; export type StoreDataCheckerStartEvent = { +name: 'store.datachecker.start', +selector: NormalizationSelector, }; export type StoreDataCheckerEndEvent = { +name: 'store.datachecker.end', +selector: NormalizationSelector, }; export type StorePublishLogEvent = { +name: 'store.publish', +source: RecordSource, +optimistic: boolean, }; export type StoreSnapshotLogEvent = { +name: 'store.snapshot', }; export type StoreLookupStartEvent = { +name: 'store.lookup.start', +selector: SingularReaderSelector, }; export type StoreLookupEndEvent = { +name: 'store.lookup.end', +selector: SingularReaderSelector, }; export type StoreRestoreLogEvent = { +name: 'store.restore', }; export type StoreGcStartEvent = { +name: 'store.gc.start', }; export type StoreGcInterruptedEvent = { +name: 'store.gc.interrupted', }; export type StoreGcEndEvent = { +name: 'store.gc.end', +references: DataIDSet, }; export type StoreNotifyStartLogEvent = { +name: 'store.notify.start', +sourceOperation: ?OperationDescriptor, }; export type StoreNotifyCompleteLogEvent = { +name: 'store.notify.complete', +sourceOperation: ?OperationDescriptor, +updatedRecordIDs: DataIDSet, +invalidatedRecordIDs: DataIDSet, +subscriptionsSize: number, +updatedOwners: Array<RequestDescriptor>, }; export type StoreNotifySubscriptionLogEvent = { +name: 'store.notify.subscription', +sourceOperation: ?OperationDescriptor, +snapshot: Snapshot, +nextSnapshot: Snapshot, }; export type EntrypointRootConsumeLogEvent = { +name: 'entrypoint.root.consume', +profilerContext: mixed, +rootModuleID: string, }; export type LiveResolverBatchStartLogEvent = { +name: 'liveresolver.batch.start', }; export type LiveResolverBatchEndLogEvent = { +name: 'liveresolver.batch.end', }; export type UseFragmentSubscriptionMissedUpdates = { +name: 'useFragment.subscription.missedUpdates', +hasDataChanges: boolean, }; export type LogEvent = | SuspenseFragmentLogEvent | SuspenseQueryLogEvent | QueryResourceFetchLogEvent | QueryResourceRetainLogEvent | FragmentResourceMissingDataLogEvent | PendingOperationFoundLogEvent | NetworkInfoLogEvent | NetworkStartLogEvent | NetworkNextLogEvent | NetworkErrorLogEvent | NetworkCompleteLogEvent | NetworkUnsubscribeLogEvent | ExecuteStartLogEvent | ExecuteNextStartLogEvent | ExecuteNextEndLogEvent | ExecuteAsyncModuleLogEvent | ExecuteErrorLogEvent | ExecuteCompleteLogEvent | ExecuteNormalizeStart | ExecuteNormalizeEnd | StoreDataCheckerStartEvent | StoreDataCheckerEndEvent | StorePublishLogEvent | StoreSnapshotLogEvent | StoreLookupStartEvent | StoreLookupEndEvent | StoreRestoreLogEvent | StoreGcStartEvent | StoreGcInterruptedEvent | StoreGcEndEvent | StoreNotifyStartLogEvent | StoreNotifyCompleteLogEvent | StoreNotifySubscriptionLogEvent | EntrypointRootConsumeLogEvent | LiveResolverBatchStartLogEvent | LiveResolverBatchEndLogEvent | UseFragmentSubscriptionMissedUpdates; export type LogFunction = LogEvent => void; export type LogRequestInfoFunction = mixed => void; /** * The public API of Relay core. Represents an encapsulated environment with its * own in-memory cache. */ export interface IEnvironment { /** * Extra information attached to the environment instance */ +options: mixed; /** * **UNSTABLE** Event based logging API thats scoped to the environment. */ __log: LogFunction; /** * Determine if the operation can be resolved with data in the store (i.e. no * fields are missing). * * Note that this operation effectively "executes" the selector against the * cache and therefore takes time proportional to the size/complexity of the * selector. */ check(operation: OperationDescriptor): OperationAvailability; /** * Subscribe to changes to the results of a selector. The callback is called * when data has been committed to the store that would cause the results of * the snapshot's selector to change. */ subscribe( snapshot: Snapshot, callback: (snapshot: Snapshot) => void, ): Disposable; /** * Ensure that all the records necessary to fulfill the given selector are * retained in-memory. The records will not be eligible for garbage collection * until the returned reference is disposed. */ retain(operation: OperationDescriptor): Disposable; /** * Apply an optimistic update to the environment. The mutation can be reverted * by calling `dispose()` on the returned value. */ applyUpdate(optimisticUpdate: OptimisticUpdateFunction): Disposable; /** * Revert updates for the `update` function. */ revertUpdate(update: OptimisticUpdateFunction): void; /** * Revert updates for the `update` function, and apply the `replacement` update. */ replaceUpdate( update: OptimisticUpdateFunction, replacement: OptimisticUpdateFunction, ): void; /** * Apply an optimistic mutation response and/or updater. The mutation can be * reverted by calling `dispose()` on the returned value. */ applyMutation<TMutation: MutationParameters>( optimisticConfig: OptimisticResponseConfig<TMutation>, ): Disposable; /** * Commit an updater to the environment. This mutation cannot be reverted and * should therefore not be used for optimistic updates. This is mainly * intended for updating fields from client schema extensions. */ commitUpdate(updater: StoreUpdater): void; /** * Commit a payload to the environment using the given operation selector. */ commitPayload( operationDescriptor: OperationDescriptor, payload: PayloadData, ): void; /** * Returns the environment's Network. */ getNetwork(): INetwork; /** * Returns the environment's Store. */ getStore(): Store; /** * Returns the environment's OperationTracker. */ getOperationTracker(): RelayOperationTracker; /** * Returns the environment's TaskScheduler if one has been configured. */ getScheduler(): ?TaskScheduler; /** * EXPERIMENTAL * Returns the default render policy to use when rendering a query * that uses Relay Hooks. */ UNSTABLE_getDefaultRenderPolicy(): RenderPolicy; /** * Read the results of a selector from in-memory records in the store. */ lookup(selector: SingularReaderSelector): Snapshot; /** * Send a query to the server with Observer semantics: one or more * responses may be returned (via `next`) over time followed by either * the request completing (`completed`) or an error (`error`). * * Note: Observables are lazy, so calling this method will do nothing until * the result is subscribed to: environment.execute({...}).subscribe({...}). */ execute(config: { operation: OperationDescriptor, }): RelayObservable<GraphQLResponse>; /** * Send a subscription to the server with Observer semantics: one or more * responses may be returned (via `next`) over time followed by either * the request completing (`completed`) or an error (`error`). * * Networks/servers that support subscriptions may choose to hold the * subscription open indefinitely such that `complete` is not called. * * Note: Observables are lazy, so calling this method will do nothing until * the result is subscribed to: environment.executeSubscription({...}).subscribe({...}). */ executeSubscription<TMutation: MutationParameters>(config: { operation: OperationDescriptor, updater?: ?SelectorStoreUpdater<TMutation['response']>, }): RelayObservable<GraphQLResponse>; /** * Returns an Observable of GraphQLResponse resulting from executing the * provided Mutation operation, the result of which is then normalized and * committed to the publish queue along with an optional optimistic response * or updater. * * Note: Observables are lazy, so calling this method will do nothing until * the result is subscribed to: * environment.executeMutation({...}).subscribe({...}). */ executeMutation<TMutation: MutationParameters>( config: ExecuteMutationConfig<TMutation>, ): RelayObservable<GraphQLResponse>; /** * Returns an Observable of GraphQLResponse resulting from executing the * provided Query or Subscription operation responses, the result of which is * then normalized and committed to the publish queue. * * Note: Observables are lazy, so calling this method will do nothing until * the result is subscribed to: * environment.executeWithSource({...}).subscribe({...}). */ executeWithSource({ operation: OperationDescriptor, source: RelayObservable<GraphQLResponse>, }): RelayObservable<GraphQLResponse>; /** * Returns true if a request is currently "active", meaning it's currently * actively receiving payloads or downloading modules, and has not received * a final payload yet. Note that a request might still be pending (or "in flight") * without actively receiving payload, for example a live query or an * active GraphQL subscription */ isRequestActive(requestIdentifier: string): boolean; /** * Returns true if the environment is for use during server side rendering. * functions like getQueryResource key off of this in order to determine * whether we need to set up certain caches and timeout's. */ isServer(): boolean; /** * Called by Relay when it encounters a missing field that has been annotated * with `@required(action: LOG)`. */ relayFieldLogger: RelayFieldLogger; } /** * The partial shape of an object with a '...Fragment @module(name: "...")' * selection */ export type ModuleImportPointer = { +__fragmentPropName: ?string, +__module_component: mixed, +$fragmentSpreads: mixed, ... }; /** * A set of DataIDs used to track which IDs a read() operation observed and which IDs * a publish operation updated. */ export type DataIDSet = Set<DataID>; /** * A function that updates a store (via a proxy) given the results of a "handle" * field payload. */ export type Handler = $ReadOnly<{ update: (store: RecordSourceProxy, fieldPayload: HandleFieldPayload) => void, ... }>; /** * A payload that is used to initialize or update a "handle" field with * information from the server. */ export type HandleFieldPayload = { // The arguments that were fetched. +args: Variables, // The __id of the record containing the source/handle field. +dataID: DataID, // The (storage) key at which the original server data was written. +fieldKey: string, // The name of the handle. +handle: string, // The (storage) key at which the handle's data should be written by the // handler. +handleKey: string, // The arguments applied to the handle +handleArgs: Variables, }; /** * A payload that represents data necessary to process the results of an object * with a `@module` fragment spread: * * ## @module Fragment Spread * - args: Local arguments from the parent * - data: The GraphQL response value for the @match field. * - dataID: The ID of the store object linked to by the @match field. * - operationReference: A reference to a generated module containing the * SplitOperation with which to normalize the field's `data`. * - variables: Query variables. * - typeName: the type that matched. * * The dataID, variables, and fragmentName can be used to create a Selector * which can in turn be used to normalize and publish the data. The dataID and * typeName can also be used to construct a root record for normalization. * */ export type ModuleImportPayload = { +kind: 'ModuleImportPayload', +args: ?$ReadOnlyArray<NormalizationArgument>, +data: PayloadData, +dataID: DataID, +operationReference: mixed, +path: $ReadOnlyArray<string>, +typeName: string, +variables: Variables, +actorIdentifier: ?ActorIdentifier, }; /** * A payload that represents data necessary to process the results of an object * with experimental actor change directive. * * - data: The GraphQL response value for the actor change field. * - dataID: The ID of the store object linked to by the actor change field. * - node: NormalizationLinkedField, where the actor change directive is used * - path: to a field in the response * - variables: Query variables. * - typeName: the type that matched. * * The dataID, variables, and fragmentName can be used to create a Selector * which can in turn be used to normalize and publish the data. The dataID and * typeName can also be used to construct a root record for normalization. */ export type ActorPayload = { +kind: 'ActorPayload', +data: PayloadData, +dataID: DataID, +node: NormalizationLinkedField, +path: $ReadOnlyArray<string>, +typeName: string, +variables: Variables, +actorIdentifier: ActorIdentifier, }; /** * Union type of possible payload followups we may handle during normalization. */ export type FollowupPayload = ModuleImportPayload | ActorPayload; /** * Data emitted after processing a Defer or Stream node during normalization * that describes how to process the corresponding response chunk when it * arrives. */ export type DeferPlaceholder = { +kind: 'defer', +data: PayloadData, +label: string, +path: $ReadOnlyArray<string>, +selector: NormalizationSelector, +typeName: string, +actorIdentifier: ?ActorIdentifier, }; export type StreamPlaceholder = { +kind: 'stream', +label: string, +path: $ReadOnlyArray<string>, +parentID: DataID, +node: NormalizationSelectableNode, +variables: Variables, +actorIdentifier: ?ActorIdentifier, }; export type IncrementalDataPlaceholder = DeferPlaceholder | StreamPlaceholder; export type NormalizeResponseFunction = ( response: GraphQLResponseWithData, selector: NormalizationSelector, typeName: string, options: NormalizationOptions, ) => RelayResponsePayload; /** * A user-supplied object to load a generated operation (SplitOperation or * ConcreteRequest) AST by a module reference. The exact format of a module * reference is left to the application, but it must be a plain JavaScript value * (string, number, or object/array of same). */ export type OperationLoader = { /** * Synchronously load an operation, returning either the node or null if it * cannot be resolved synchronously. */ get(reference: mixed): ?NormalizationRootNode, /** * Asynchronously load an operation. */ load(reference: mixed): Promise<?NormalizationRootNode>, }; /** * A function that receives a proxy over the store and may trigger side-effects * (indirectly) by calling `set*` methods on the store or its record proxies. */ export type StoreUpdater = (store: RecordSourceProxy) => void; /** * Similar to StoreUpdater, but accepts a proxy tied to a specific selector in * order to easily access the root fields of a query/mutation as well as a * second argument of the response object of the mutation. */ export type SelectorStoreUpdater<-TMutationResponse> = ( store: RecordSourceSelectorProxy, data: ?TMutationResponse, ) => void; /** * A set of configs that can be used to apply an optimistic update into the * store. */ export type OptimisticUpdate<TMutation: MutationParameters> = | OptimisticUpdateFunction | OptimisticUpdateRelayPayload<TMutation>; export type OptimisticUpdateFunction = { +storeUpdater: StoreUpdater, }; export type OptimisticUpdateRelayPayload<TMutation: MutationParameters> = { +operation: OperationDescriptor, +payload: RelayResponsePayload, +updater: ?SelectorStoreUpdater<TMutation['response']>, }; export type OptimisticResponseConfig<TMutation: MutationParameters> = { +operation: OperationDescriptor, +response: ?PayloadData, +updater: ?SelectorStoreUpdater<TMutation['response']>, }; /** * A set of handlers that can be used to provide substitute data for missing * fields when reading a selector from a source. */ export type MissingFieldHandler = | { kind: 'scalar', handle: ( field: NormalizationScalarField, parentRecord: ?ReadOnlyRecordProxy, args: Variables, store: ReadOnlyRecordSourceProxy, ) => mixed, } | { kind: 'linked', handle: ( field: NormalizationLinkedField | ReaderLinkedField, parentRecord: ?ReadOnlyRecordProxy, args: Variables, store: ReadOnlyRecordSourceProxy, ) => ?DataID, } | { kind: 'pluralLinked', handle: ( field: NormalizationLinkedField | ReaderLinkedField, parentRecord: ?ReadOnlyRecordProxy, args: Variables, store: ReadOnlyRecordSourceProxy, ) => ?Array<?DataID>, }; /** * Data which Relay expected to be in the store (because it was requested by * the parent query/mutation/subscription) was missing. This can happen due * to graph relationship changes observed by other queries/mutations, or * imperative updates that don't provide all needed data. * * https://relay.dev/docs/next/debugging/why-null/#graph-relationship-change * * In this case Relay will render with the referenced field as `undefined`. * * __NOTE__: This may break with the type contract of Relay's generated types. * * To turn this into a hard error for a given fragment/query, you can use * `@throwOnFieldError`. * * https://relay.dev/docs/next/guides/throw-on-field-error-directive/ */ export type MissingExpectedDataLogEvent = { +kind: 'missing_expected_data.log', +owner: string, fieldPath: string, // Purposefully mutable to allow lazy construction in RelayReader }; /** * Data which Relay expected to be in the store (because it was requested by * the parent query/mutation/subscription) was missing. This can happen due * to graph relationship changes observed by other queries/mutations, or * imperative updates that don't provide all needed data. * * https://relay.dev/docs/next/debugging/why-null/#graph-relationship-change * * This event is as `.throw` because the missing data was encountered in a * query/fragment/mutation with `@throwOnFieldError` `@throwOnFieldError`. * * https://relay.dev/docs/next/guides/throw-on-field-error-directive/ * * Relay will throw immediately after logging this event. If you wish to * customize the error being thrown, you may throw your own error. * * *NOTE*: Only throw on this event if `handled` is false. Errors that have been * handled by a `@catch` directive or by making a resolver null will have * `handled: true` and should not trigger a throw. */ export type MissingExpectedDataThrowEvent = { +kind: 'missing_expected_data.throw', +owner: string, fieldPath: string, // Purposefully mutable to allow lazy construction in RelayReader +handled: boolean, }; /** * A field was marked as @required(action: LOG) but was null or missing in the * store. */ export type MissingRequiredFieldLogEvent = { +kind: 'missing_required_field.log', +owner: string, fieldPath: string, // Purposefully mutable to allow lazy construction in RelayReader }; /** * A field was marked as @required(action: THROW) but was null or missing in the * store. * * Relay will throw immediately after logging this event. If you wish to * customize the error being thrown, you may throw your own error. * * *NOTE*: Only throw on this event if `handled` is false. Errors that have been * handled by a `@catch` directive or by making a resolver null will have * `handled: true` and should not trigger a throw. */ export type MissingRequiredFieldThrowEvent = { +kind: 'missing_required_field.throw', +owner: string, fieldPath: string, // Purposefully mutable to allow lazy construction in RelayReader +handled: boolean, }; /** * A Relay Resolver that is currently being read threw a JavaScript error when * it was last evaluated. By default, the value has been coerced to null and * passed to the product code. * * If `@throwOnFieldError` was used on the parent query/fragment/mutation, you * will also receive a TODO * * *NOTE*: Only throw on this event if `handled` is false. Errors that have been * handled by a `@catch` directive or by making a resolver null will have * `handled: true` and should not trigger a throw. */ export type RelayResolverErrorEvent = { +kind: 'relay_resolver.error', +owner: string, +fieldPath: string, +error: Error, +shouldThrow: boolean, +handled: boolean, }; /** * A field being read by Relay was marked as being in an error state by the * GraphQL response. * * https://spec.graphql.org/October2021/#sec-Errors.Field-errors * * If the field's parent query/fragment/mutation was annotated with * `@throwOnFieldError` and no `@catch` directive was used to catch the error, * Relay will throw an error immediately after logging this event. * * https://relay.dev/docs/next/guides/catch-directive/ * https://relay.dev/docs/next/guides/throw-on-field-error-directive/ * * *NOTE*: Only throw on this event if `handled` is false. Errors that have been * handled by a `@catch` directive or by making a resolver null will have * `handled: true` and should not trigger a throw. */ export type RelayFieldPayloadErrorEvent = { +kind: 'relay_field_payload.error', +owner: string, +fieldPath: string, +error: TRelayFieldError, +shouldThrow: boolean, +handled: boolean, }; /** * Union of all RelayFieldLoggerEvent types */ export type RelayFieldLoggerEvent = | MissingExpectedDataLogEvent | MissingExpectedDataThrowEvent | MissingRequiredFieldLogEvent | MissingRequiredFieldThrowEvent | RelayResolverErrorEvent | RelayFieldPayloadErrorEvent; /** * A handler for events related to field errors. */ export type RelayFieldLogger = (event: RelayFieldLoggerEvent) => void; /** * The results of normalizing a query. */ export type RelayResponsePayload = { +errors: ?Array<PayloadError>, +fieldPayloads: ?Array<HandleFieldPayload>, +incrementalPlaceholders: ?Array<IncrementalDataPlaceholder>, +followupPayloads: ?Array<FollowupPayload>, +source: MutableRecordSource, +isFinal: boolean, }; /** * Configuration on the executeMutation(...). */ export type ExecuteMutationConfig<TMutation: MutationParameters> = { operation: OperationDescriptor, optimisticUpdater?: ?SelectorStoreUpdater<TMutation['response']>, optimisticResponse?: ?Object, updater?: ?SelectorStoreUpdater<TMutation['response']>, uploadables?: ?UploadableMap, }; /** * Public interface for Publish Queue. */ export interface PublishQueue { /** * Schedule applying an optimistic updates on the next `run()`. */ applyUpdate<TMutation: MutationParameters>( updater: OptimisticUpdate<TMutation>, ): void; /** * Schedule reverting an optimistic updates on the next `run()`. */ revertUpdate<TMutation: MutationParameters>( updater: OptimisticUpdate<TMutation>, ): void; /** * Schedule a revert of all optimistic updates on the next `run()`. */ revertAll(): void; /** * Schedule applying a payload to the store on the next `run()`. */ commitPayload<TMutation: MutationParameters>( operation: OperationDescriptor, payload: RelayResponsePayload, updater?: ?SelectorStoreUpdater<TMutation['response']>, ): void; /** * Schedule an updater to mutate the store on the next `run()` typically to * update client schema fields. */ commitUpdate(updater: StoreUpdater): void; /** * Schedule a publish to the store from the provided source on the next * `run()`. As an example, to update the store with substituted fields that * are missing in the store. */ commitSource(source: RecordSource): void; /** * Execute all queued up operations from the other public methods. * Optionally provide an OperationDescriptor indicating the source operation * that was being processed to produce this run. */ run(sourceOperation?: OperationDescriptor): $ReadOnlyArray<RequestDescriptor>; } /** * The return type of a client edge resolver pointing to a concrete type. * T can be overridden to be more specific than a DataID, e.g. if the IDs * can only come from a given set. */ export type ConcreteClientEdgeResolverReturnType<T = any> = { +id: T & DataID, }; /** * The return type of a Live Resolver. Models an external value which can * be read lazily and which might change over time. The subscribe method * returns a callback which should be called when the value _may_ have changed. * * While over-notification (subscription notifications when the read value has * not actually changed) is suported, for performance reasons, it is recommended * that the provider of the LiveState value confirms that the value has indeed * change before notifying Relay of the change. */ export type LiveState<+T> = { /** * Returns the current value of the live state. */ read(): T, /** * Subscribes to changes in the live state. The state provider should * call the callback when the value of the live state changes. */ subscribe(cb: () => void): () => void, }; /** * Context that will be provided to live resolvers if * `resolverContext` is set on the Relay Store. * This context will be passed as the third argument to the live resolver */ export type ResolverContext = mixed;