Ejemplo n.º 1
0
 /**
  * Create an empty record at `dataID` if a record does not already exist.
  */
 putRecord(
   dataID: DataID,
   typeName: ?string,
   path?: RelayQueryPath
 ): void {
   const prevRecord = this._getRecordForWrite(dataID);
   if (prevRecord) {
     return;
   }
   const nextRecord = RelayRecord.createWithFields(dataID, {
     __typename: typeName,
   });
   if (this._isOptimisticWrite) {
     this._setClientMutationID(nextRecord);
   }
   if (RelayRecord.isClientID(dataID)) {
     invariant(
       path,
       'RelayRecordWriter.putRecord(): Expected a path for non-refetchable ' +
       'record `%s`.',
       dataID
     );
     nextRecord[PATH] = path;
   }
   this._records[dataID] = nextRecord;
   const cacheWriter = this._cacheWriter;
   if (!this._isOptimisticWrite && cacheWriter) {
     cacheWriter.writeField(dataID, '__dataID__', dataID, typeName);
   }
 }
Ejemplo n.º 2
0
  trackNodeForID(
    node: RelayQuery.Node,
    dataID: DataID,
    path: ?RelayQueryPath
  ): void {
    // Non-refetchable nodes are tracked via their nearest-refetchable parent
    // (except for root call nodes)
    if (RelayRecord.isClientID(dataID)) {
      invariant(
        path,
        'RelayQueryTracker.trackNodeForID(): Expected `path` for client ID, ' +
        '`%s`.',
        dataID
      );
      if (!path.isRootPath()) {
        return;
      }
    }
    // Don't track `__type__` fields
    if (node instanceof RelayQuery.Field && node.getSchemaName() === TYPE) {
      return;
    }

    this._trackedNodesByID[dataID] = this._trackedNodesByID[dataID] || {
      trackedNodes: [],
      isMerged: false,
    };
    this._trackedNodesByID[dataID].trackedNodes.push(node);
    this._trackedNodesByID[dataID].isMerged = false;
  }
Ejemplo n.º 3
0
 /**
  * Given a query fragment and a data ID, returns a root query that applies
  * the fragment to the object specified by the data ID.
  */
 buildFragmentQueryForDataID(
   fragment: RelayQuery.Fragment,
   dataID: DataID
 ): RelayQuery.Root {
   if (RelayRecord.isClientID(dataID)) {
     const path = this._queuedStore.getPathToRecord(
       this._rangeData.getCanonicalClientID(dataID),
     );
     invariant(
       path,
       'RelayStoreData.buildFragmentQueryForDataID(): Cannot refetch ' +
       'record `%s` without a path.',
       dataID
     );
     return path.getQuery(fragment);
   }
   // Fragment fields cannot be spread directly into the root because they
   // may not exist on the `Node` type.
   return RelayQuery.Root.build(
     fragment.getDebugName() || 'UnknownQuery',
     RelayNodeInterface.NODE,
     dataID,
     [fragment],
     {identifyingArgName: RelayNodeInterface.ID},
     NODE_TYPE
   );
 }
Ejemplo n.º 4
0
 /**
  * Create an empty record at `dataID` if a record does not already exist.
  */
 putRecord(
   dataID: DataID,
   typeName: ?string,
   path?: ?QueryPath
 ): void {
   const prevRecord = this._getRecordForWrite(dataID);
   if (prevRecord) {
     return;
   }
   const nextRecord = RelayRecord.createWithFields(dataID, {
     __typename: typeName,
   });
   if (this._isOptimisticWrite) {
     /* $FlowIssue(>=0.23.0) #10620219 - After fixing some unsoundness in
      * dictionary types, we've come to realize we need a safer object
      * supertype than Object. */
     this._setClientMutationID(nextRecord);
   }
   if (RelayRecord.isClientID(dataID) && path) {
     nextRecord[PATH] = path;
   }
   this._records[dataID] = nextRecord;
   const cacheWriter = this._cacheWriter;
   if (!this._isOptimisticWrite && cacheWriter) {
     cacheWriter.writeField(dataID, '__dataID__', dataID, typeName);
   }
 }
Ejemplo n.º 5
0
function buildEdgeField(
  parentID: DataID,
  edgeName: string,
  edgeFields: Array<RelayQuery.Node>
): RelayQuery.Field {
  const fields = [
    RelayQuery.Field.build({
      fieldName: 'cursor',
      type: 'String',
    }),
    RelayQuery.Field.build({
      fieldName: TYPENAME,
      type: 'String',
    }),
  ];
  if (RelayConnectionInterface.EDGES_HAVE_SOURCE_FIELD &&
      !RelayRecord.isClientID(parentID)) {
    fields.push(
      RelayQuery.Field.build({
        children: [
          RelayQuery.Field.build({
            fieldName: ID,
            type: 'String',
          }),
          RelayQuery.Field.build({
            fieldName: TYPENAME,
            type: 'String',
          }),
        ],
        fieldName: 'source',
        metadata: {canHaveSubselections: true},
        type: ANY_TYPE,
      })
    );
  }
  fields.push(...edgeFields);
  const edgeField = flattenRelayQuery(RelayQuery.Field.build({
    children: fields,
    fieldName: edgeName,
    metadata: {canHaveSubselections: true},
    type: ANY_TYPE,
  }));
  invariant(
    edgeField instanceof RelayQuery.Field,
    'RelayMutationQuery: Expected a field.'
  );
  return edgeField;
}
Ejemplo n.º 6
0
 (error, value) => {
   if (this._state === 'COMPLETED') {
     return;
   }
   if (error) {
     this._handleFailed();
     return;
   }
   if (value && RelayRecord.isClientID(dataID)) {
     value.__path__ = pendingItems[0].path;
   }
   // Mark records as created/updated as necessary. Note that if the
   // record is known to be deleted in the store then it will have been
   // been marked as created already. Further, it does not need to be
   // updated since no additional data can be read about a deleted node.
   const recordState = this._store.getRecordState(dataID);
   if (recordState === 'UNKNOWN' && value !== undefined) {
     // Register immediately in case anything tries to read and subscribe
     // to this record (which means incrementing reference counts).
     if (this._garbageCollector) {
       this._garbageCollector.register(dataID);
     }
     // Mark as created if the store did not have a value but disk cache
     // did (either a known value or known deletion).
     this._changeTracker.createID(dataID);
   } else if (recordState === 'EXISTENT' && value != null) {
     // Mark as updated only if a record exists in both the store and
     // disk cache.
     this._changeTracker.updateID(dataID);
   }
   this._cachedRecords[dataID] = value;
   var items = this._pendingNodes[dataID];
   delete this._pendingNodes[dataID];
   if (this._cachedRecords[dataID] === undefined) {
     // We are out of luck if disk doesn't have the node either.
     this._handleFailed();
   } else {
     items.forEach(item => {
       if (this._state === 'COMPLETED') {
         return;
       }
       this.visitNode(dataID, item);
     });
   }
   if (this._isDone()) {
     this._handleSuccess();
   }
 }
Ejemplo n.º 7
0
 /**
  * Helper to create a record and the corresponding notification.
  */
 createRecordIfMissing(
   node: RelayQuery.Node,
   recordID: DataID,
   path: QueryPath,
   payload: ?Object
 ): void {
   const recordState = this._store.getRecordState(recordID);
   const typeName = payload && this.getRecordTypeName(node, recordID, payload);
   this._writer.putRecord(recordID, typeName, path);
   if (recordState !== EXISTENT) {
     this.recordCreate(recordID);
   }
   if (
     (this.isNewRecord(recordID) || this._updateTrackedQueries) &&
     (!RelayRecord.isClientID(recordID) || RelayQueryPath.isRootPath(path))
   ) {
     this._queryTracker.trackNodeForID(node, recordID, path);
   }
 }
Ejemplo n.º 8
0
 /**
  * Given a query fragment and a data ID, returns a root query that applies
  * the fragment to the object specified by the data ID.
  */
 buildFragmentQueryForDataID(
   fragment: RelayQuery.Fragment,
   dataID: DataID
 ): RelayQuery.Root {
   if (RelayRecord.isClientID(dataID)) {
     const path = this._queuedStore.getPathToRecord(
       this._rangeData.getCanonicalClientID(dataID),
     );
     invariant(
       path,
       'RelayStoreData.buildFragmentQueryForDataID(): Cannot refetch ' +
       'record `%s` without a path.',
       dataID
     );
     return RelayQueryPath.getQuery(
       this._cachedStore,
       path,
       fragment
     );
   }
   // Fragment fields cannot be spread directly into the root because they
   // may not exist on the `Node` type.
   return RelayQuery.Root.build(
     fragment.getDebugName() || 'UnknownQuery',
     NODE,
     dataID,
     [idField, typeField, fragment],
     {
       identifyingArgName: ID,
       identifyingArgType: ID_TYPE,
       isAbstract: true,
       isDeferred: false,
       isPlural: false,
     },
     NODE_TYPE
   );
 }
Ejemplo n.º 9
0
 /**
  * Create an empty record at `dataID` if a record does not already exist.
  */
 putRecord(
   dataID: DataID,
   typeName: ?string,
   path?: ?QueryPath
 ): void {
   const prevRecord = this._getRecordForWrite(dataID);
   if (prevRecord) {
     return;
   }
   const nextRecord = RelayRecord.createWithFields(dataID, {
     __typename: typeName,
   });
   if (this._isOptimisticWrite) {
     this._setClientMutationID(nextRecord);
   }
   if (RelayRecord.isClientID(dataID) && path) {
     nextRecord[PATH] = path;
   }
   this._records[dataID] = nextRecord;
   const cacheWriter = this._cacheWriter;
   if (!this._isOptimisticWrite && cacheWriter) {
     cacheWriter.writeField(dataID, '__dataID__', dataID, typeName);
   }
 }
Ejemplo n.º 10
0
  type: 'root',
};

export type QueryPath = ClientPath | NodePath | RootPath;

/**
 * @internal
 *
 * Represents the path (root plus fields) within a query that fetched a
 * particular node. Each step of the path may represent a root query (for
 * refetchable nodes) or the field path from the nearest refetchable node.
 */
const RelayQueryPath = {
  createForID(dataID: DataID, name: string, routeName: ?string): QueryPath {
    invariant(
      !RelayRecord.isClientID(dataID),
      'RelayQueryPath.createForID: Expected dataID to be a server id, got ' +
      '`%s`.',
      dataID
    );
    return {
      dataID,
      name,
      routeName: routeName || '$RelayQuery',
      type: 'node',
    };
  },

  create(root: RelayQuery.Root): QueryPath {
    if (root.getFieldName() === NODE) {
      const identifyingArg = root.getIdentifyingArg();
Ejemplo n.º 11
0
  /**
   * Diff an `edges` field for the edge rooted at `edgeID`, splitting a new
   * root query to fetch any missing data (via a `node(id)` root if the
   * field is refetchable or a `...{connection.find(id){}}` query if the
   * field is not refetchable).
   */
  diffConnectionEdge(
    connectionField: RelayQuery.Field,
    edgeField: RelayQuery.Field,
    path: RelayQueryPath,
    edgeID: DataID,
    rangeInfo: RangeInfo
  ): DiffOutput {

    var hasSplitQueries = false;
    var diffOutput = this.traverse(
      edgeField,
      path.getPath(edgeField, edgeID),
      makeScope(edgeID)
    );
    var diffNode = diffOutput ? diffOutput.diffNode : null;
    var trackedNode = diffOutput ? diffOutput.trackedNode : null;
    var nodeID = this._store.getLinkedRecordID(edgeID, NODE);

    if (diffNode) {
      if (!nodeID || RelayRecord.isClientID(nodeID)) {
        warning(
          connectionField.isConnectionWithoutNodeID(),
          'RelayDiffQueryBuilder: Field `node` on connection `%s` cannot be ' +
          'retrieved if it does not have an `id` field. If you expect fields ' +
          'to be retrieved on this field, add an `id` field in the schema. ' +
          'If you choose to ignore this warning, you can silence it by ' +
          'adding `@relay(isConnectionWithoutNodeID: true)` to the ' +
          'connection field.',
          connectionField.getStorageKey()
        );
      } else {
        var {
          edges: diffEdgesField,
          node: diffNodeField,
        } = splitNodeAndEdgesFields(diffNode);

        // split missing `node` fields into a `node(id)` root query
        if (diffNodeField) {
          hasSplitQueries = true;
          const nodeField = edgeField.getFieldByStorageKey('node');
          invariant(
            nodeField,
            'RelayDiffQueryBuilder: Expected connection `%s` to have a ' +
            '`node` field.',
            connectionField.getSchemaName()
          );
          this.splitQuery(buildRoot(
            nodeID,
            diffNodeField.getChildren(),
            path.getName(),
            nodeField.getType()
          ));
        }

        // split missing `edges` fields into a `connection.find(id)` query
        // if `find` is supported, otherwise warn
        if (diffEdgesField) {
          if (connectionField.isFindable()) {
            diffEdgesField = diffEdgesField
              .clone(diffEdgesField.getChildren().concat(nodeWithID));
            var connectionFind = connectionField.cloneFieldWithCalls(
              [diffEdgesField],
              rangeInfo.filterCalls.concat({name: 'find', value: nodeID})
            );
            if (connectionFind) {
              hasSplitQueries = true;
              // current path has `parent`, `connection`, `edges`; pop to parent
              var connectionParent = path.getParent().getParent();
              this.splitQuery(connectionParent.getQuery(connectionFind));
            }
          } else {
            warning(
              false,
              'RelayDiffQueryBuilder: connection `edges{*}` fields can only ' +
              'be refetched if the connection supports the `find` call. ' +
              'Cannot refetch data for field `%s`.',
              connectionField.getStorageKey()
            );
          }
        }
      }
    }

    // Connection edges will never return diff nodes; instead missing fields
    // are fetched by new root queries. Tracked nodes are returned if either
    // a child field was tracked or missing fields were split into a new query.
    // The returned `trackedNode` is never tracked directly: instead it serves
    // as an indicator to `diffConnection` that the entire connection field must
    // be tracked.
    return {
      diffNode: null,
      trackedNode: hasSplitQueries ? edgeField : trackedNode,
    };
  }
Ejemplo n.º 12
0
  type: 'root',
};

export type QueryPath = ClientPath | NodePath | RootPath;

/**
 * @internal
 *
 * Represents the path (root plus fields) within a query that fetched a
 * particular node. Each step of the path may represent a root query (for
 * refetchable nodes) or the field path from the nearest refetchable node.
 */
const RelayQueryPath = {
  createForID(dataID: DataID, name: string): QueryPath {
    invariant(
      !RelayRecord.isClientID(dataID),
      'RelayQueryPath.createForID: Expected dataID to be a server id, got ' +
      '`%s`.',
      dataID
    );
    return {
      dataID,
      name,
      type: 'node',
    };
  },

  create(root: RelayQuery.Root): QueryPath {
    if (root.getFieldName() === NODE) {
      const identifyingArg = root.getIdentifyingArg();
      if (identifyingArg && typeof identifyingArg.value === 'string') {
Ejemplo n.º 13
0
 isClientOnlyID: function(dataID: DataID): boolean {
   return RelayRecord.isClientID(dataID) && !clientIDToServerIDMap[dataID];
 },
Ejemplo n.º 14
0
  type: 'root',
};

export type QueryPath = ClientPath | NodePath | RootPath;

/**
 * @internal
 *
 * Represents the path (root plus fields) within a query that fetched a
 * particular node. Each step of the path may represent a root query (for
 * refetchable nodes) or the field path from the nearest refetchable node.
 */
const RelayQueryPath = {
  createForID(dataID: DataID, name: string): QueryPath {
    invariant(
      !RelayRecord.isClientID(dataID),
      'RelayQueryPath.createForID: Expected dataID to be a server id, got ' +
      '`%s`.',
      dataID
    );
    return {
      dataID,
      name,
      type: 'node',
    };
  },

  create(root: RelayQuery.Root): QueryPath {
    if (root.getFieldName() === NODE) {
      const identifyingArg = root.getIdentifyingArg();
      if (identifyingArg && typeof identifyingArg.value === 'string') {