beforeEach(() => {
   const route = RelayMetaRoute.get('$fetchRelayQuery');
   const queryA = RelayQuery.Root.create(
     Relay.QL`query{node(id:"123"){id}}`, route, {}
   );
   const queryB = RelayQuery.Root.create(
     Relay.QL`query{node(id:"456"){id}}`, route, {}
   );
   requestA = new RelayQueryRequest(queryA);
   requestB = new RelayQueryRequest(queryB);
 });
示例#2
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
   );
 }
示例#3
0
function buildRoot(
  rootID: DataID,
  nodes: Array<RelayQuery.Node>,
  name: string,
  type: string
): RelayQuery.Root {
  const children = [idField, typeField];
  const fields = [];
  nodes.forEach(node => {
    if (node instanceof RelayQuery.Field) {
      fields.push(node);
    } else {
      children.push(node);
    }
  });
  children.push(RelayQuery.Fragment.build(
    'diffRelayQuery',
    type,
    fields
  ));

  return RelayQuery.Root.build(
    name,
    NODE,
    rootID,
    children,
    {identifyingArgName: RelayNodeInterface.ID},
    NODE_TYPE
  );
}
示例#4
0
 it('prints a generated query with one root argument', () => {
   const query = RelayQuery.Root.build(
     'FooQuery',
     'node',
     '123',
     [
       RelayQuery.Field.build({
         fieldName: 'id',
         type: 'String',
       }),
     ],
     {
       identifyingArgName: RelayNodeInterface.ID,
       identifyingArgType: RelayNodeInterface.ID_TYPE,
       isAbstract: true,
       isDeferred: false,
       isPlural: false,
     },
     'Node'
   );
   const {text, variables} = printRelayOSSQuery(query);
   expect(text).toEqualPrintedQuery(`
     query FooQuery($id_0: ID!) {
       node(id: $id_0) {
         id
       }
     }
   `);
   expect(variables).toEqual({
     id_0: '123',
   });
 });
示例#5
0
 it('returns an array including the identifying argument', () => {
   const root = RelayQuery.Root.build(
     'RelayQueryTest',
     'foo',
     '123',
     null,
     {identifyingArgName: 'id'},
   );
   expect(root.getCallsWithValues()).toEqual([{name: 'id', value: '123'}]);
 });
/**
 * Wraps `descriptor` in a new top-level ref query.
 */
function createRefQuery(
  descriptor: RelayRefQueryDescriptor,
  context: RelayQuery.Root
): RelayQuery.Root {
  const node = descriptor.node;
  invariant(
    node instanceof RelayQuery.Field ||
    node instanceof RelayQuery.Fragment,
    'splitDeferredRelayQueries(): Ref query requires a field or fragment.'
  );

  // Build up JSONPath.
  const jsonPath = ['$', '*'];
  let parent;
  for (let ii = 0; ii < descriptor.nodePath.length; ii++) {
    parent = descriptor.nodePath[ii];
    if (parent instanceof RelayQuery.Field) {
      jsonPath.push(parent.getSerializationKey());
      if (parent.isPlural()) {
        jsonPath.push('*');
      }
    }
  }
  invariant(
    jsonPath.length > 2,
    'splitDeferredRelayQueries(): Ref query requires a complete path.'
  );
  const field: RelayQuery.Field = (parent: any); // Flow
  const primaryKey = field.getInferredPrimaryKey();
  invariant(
    primaryKey,
    'splitDeferredRelayQueries(): Ref query requires a primary key.'
  );
  jsonPath.push(primaryKey);

  // Create the wrapper root query.
  const root = RelayQuery.Root.build(
    context.getName(),
    RelayNodeInterface.NODES,
    QueryBuilder.createBatchCallVariable(context.getID(), jsonPath.join('.')),
    [node],
    {
      identifyingArgName: RelayNodeInterface.ID,
      identifyingArgType: RelayNodeInterface.ID_TYPE,
      isAbstract: true,
      isDeferred: true,
      isPlural: false,
    },
    RelayNodeInterface.NODE_TYPE
  );

  const result: RelayQuery.Root = (root: any); // Flow
  return result;
}
示例#7
0
 it('warns if the identifyingArgValue is missing', () => {
   const field = buildIdField();
   RelayQuery.Root.build('RelayQueryTest', 'node', null, [field], {
     isDeferred: true,
   });
   expect([
     'QueryBuilder.createQuery(): An argument value may be required for ' +
       'query `%s(%s: ???)`.',
     'node',
     'id',
   ]).toBeWarnedNTimes(1);
 });
 it('creates roots', () => {
   var field = buildIdField();
   var root = RelayQuery.Root.build(
     'RelayQueryTest',
     'node',
     '4',
     [field]
   );
   expect(root instanceof RelayQuery.Root).toBe(true);
   expect(root.getChildren().length).toBe(1);
   expect(root.getChildren()[0]).toBe(field);
 });
示例#9
0
 it('creates deferred roots', () => {
   const field = buildIdField();
   const root = RelayQuery.Root.build(
     'RelayQueryTest',
     'node',
     '4',
     [field],
     {isDeferred: true},
   );
   expect(root instanceof RelayQuery.Root).toBe(true);
   expect(root.getChildren().length).toBe(1);
   expect(root.getChildren()[0]).toBe(field);
 });
示例#10
0
 it('returns the sole identifying argument', () => {
   const root = RelayQuery.Root.build(
     'RelayQueryTest',
     'foo',
     '123',
     null,
     {identifyingArgName: 'id'},
   );
   expect(root.getIdentifyingArg()).toEqual({
     name: 'id',
     value: '123',
   });
 });
示例#11
0
 it('returns the identifying argument with type', () => {
   const root = RelayQuery.Root.build(
     'RelayQueryTest',
     'foo',
     '123',
     null,
     {identifyingArgName: 'id', identifyingArgType: 'scalar'}
   );
   expect(root.getIdentifyingArg()).toEqual({
     name: 'id',
     type: 'scalar',
     value: '123',
   });
 });
示例#12
0
 it('creates roots with batch calls', () => {
   const root = RelayQuery.Root.build(
     'RelayQueryTest',
     'node',
     QueryBuilder.createBatchCallVariable('q0', '$.*.id'),
     [],
   );
   expect(root instanceof RelayQuery.Root).toBe(true);
   expect(root.getBatchCall()).toEqual({
     refParamName: 'ref_q0',
     sourceQueryID: 'q0',
     sourceQueryPath: '$.*.id',
   });
 });
示例#13
0
function createRelayQuery(
  node: Object,
  variables: {[key: string]: mixed}
): RelayQuery.Root {
  invariant(
    typeof variables === 'object' &&
    variables != null &&
    !Array.isArray(variables),
    'Relay.Query: Expected `variables` to be an object.'
  );
  return RelayQuery.Root.create(
    node,
    RelayMetaRoute.get('$createRelayQuery'),
    variables
  );
}
示例#14
0
 it('creates roots', () => {
   const field = buildIdField();
   const root = RelayQuery.Root.build(
     'RelayQueryTest',
     'node',
     '4',
     [field],
     {},
     'Node',
     'FooRoute',
   );
   expect(root instanceof RelayQuery.Root).toBe(true);
   expect(root.getChildren().length).toBe(1);
   expect(root.getChildren()[0]).toBe(field);
   expect(root.getRoute().name).toBe('FooRoute');
 });
示例#15
0
function createRootQueryFromNodePath(nodePath: NodePath): RelayQuery.Root {
  return RelayQuery.Root.build(
    nodePath.name,
    NODE,
    nodePath.dataID,
    [idField, typeField],
    {
      identifyingArgName: ID,
      identifyingArgType: ID_TYPE,
      isAbstract: true,
      isDeferred: false,
      isPlural: false,
    },
    NODE_TYPE,
    nodePath.routeName,
  );
}
/**
 * Traverse the parent chain of `node` wrapping it at each level until it is
 * either:
 *
 * - wrapped in a RelayQuery.Root node
 * - wrapped in a non-root node that can be split off in a "ref query" (ie. a
 *   root call with a ref param that references another query)
 *
 * Additionally ensures that any requisite sibling fields are embedded in each
 * layer of the wrapper.
 */
function wrapNode(
  node: RelayQuery.Node,
  nodePath: NodePath
): (RelayQuery.Node | RelayRefQueryDescriptor) {
  for (let ii = nodePath.length - 1; ii >= 0; ii--) {
    const parent = nodePath[ii];
    if (
      parent instanceof RelayQuery.Field &&
      parent.getInferredRootCallName()
    ) {
      // We can make a "ref query" at this point, so stop wrapping.
      return new RelayRefQueryDescriptor(node, nodePath.slice(0, ii + 1));
    }

    const siblings = getRequisiteSiblings(node, parent);
    const children = [node].concat(siblings);

    // Cast here because we know that `clone` will never return `null` (because
    // we always give it at least one child).
    node = (parent.clone(children): any);
  }
  invariant(
    node instanceof RelayQuery.Root,
    'splitDeferredRelayQueries(): Cannot build query without a root node.'
  );
  const identifyingArg = node.getIdentifyingArg();
  const identifyingArgName = (identifyingArg && identifyingArg.name) || null;
  const identifyingArgValue = (identifyingArg && identifyingArg.value) || null;
  const metadata = {
    identifyingArgName,
    identifyingArgType: RelayNodeInterface.ID_TYPE,
    isAbstract: true,
    isDeferred: true,
    isPlural: false,
  };
  return RelayQuery.Root.build(
    node.getName(),
    node.getFieldName(),
    identifyingArgValue,
    node.getChildren(),
    metadata,
    node.getType()
  );
}
示例#17
0
 Object.keys(route.queries).forEach(queryName => {
   if (!Component.hasFragment(queryName)) {
     warning(
       false,
       'Relay.QL: query `%s.queries.%s` is invalid, expected fragment ' +
       '`%s.fragments.%s` to be defined.',
       route.name,
       queryName,
       Component.displayName,
       queryName
     );
     return;
   }
   var queryBuilder = route.queries[queryName];
   if (queryBuilder) {
     var concreteQuery = buildRQL.Query(
       queryBuilder,
       Component,
       queryName,
       route.params
     );
     invariant(
       concreteQuery !== undefined,
       'Relay.QL: query `%s.queries.%s` is invalid, a typical query is ' +
       'defined using: () => Relay.QL`query { ... }`.',
       route.name,
       queryName
     );
     if (concreteQuery) {
       var rootQuery = RelayQuery.Root.create(
         concreteQuery,
         RelayMetaRoute.get(route.name),
         route.params
       );
       const identifyingArg = rootQuery.getIdentifyingArg();
       if (!identifyingArg || identifyingArg.value !== undefined) {
         querySet[queryName] = rootQuery;
         return;
       }
     }
   }
   querySet[queryName] = null;
 });
示例#18
0
 it('throws for ref queries', () => {
   const query = RelayQuery.Root.build(
     'RefQueryName',
     RelayNodeInterface.NODE,
     QueryBuilder.createBatchCallVariable('q0', '$.*.actor.id'),
     [
       RelayQuery.Field.build({fieldName: 'id', type: 'String'}),
       RelayQuery.Field.build({fieldName: 'name', type: 'String'}),
     ],
     {
       isDeferred: true,
       identifyingArgName: RelayNodeInterface.ID,
       type: RelayNodeInterface.NODE_TYPE,
     }
   );
   expect(() => printRelayOSSQuery(query)).toFailInvariant(
     'printRelayOSSQuery(): Deferred queries are not supported.'
   );
 });
示例#19
0
  forEachRootCallArg(root, identifyingArgValue => {
    var nodeRoot;
    if (isPluralCall) {
      invariant(
        identifyingArgValue != null,
        'diffRelayQuery(): Unexpected null or undefined value in root call ' +
        'argument array for query, `%s(...).',
        fieldName
      );
      nodeRoot = RelayQuery.Root.build(
        root.getName(),
        fieldName,
        [identifyingArgValue],
        root.getChildren(),
        metadata,
        root.getType()
      );
    } else {
      // Reuse `root` if it only maps to one result.
      nodeRoot = root;
    }

    // The whole query must be fetched if the root dataID is unknown.
    var dataID = store.getDataID(storageKey, identifyingArgValue);
    if (dataID == null) {
      queries.push(nodeRoot);
      return;
    }

    // Diff the current dataID
    var scope = makeScope(dataID);
    var diffOutput = visitor.visit(nodeRoot, path, scope);
    var diffNode = diffOutput ? diffOutput.diffNode : null;
    if (diffNode) {
      invariant(
        diffNode instanceof RelayQuery.Root,
        'diffRelayQuery(): Expected result to be a root query.'
      );
      queries.push(diffNode);
    }
  });
示例#20
0
 it('clones roots with different route', () => {
   const field = buildIdField();
   const root = RelayQuery.Root.build(
     'RelayQueryTest',
     'node',
     '4',
     [field],
     {},
     'Node',
     'FooRoute'
   );
   const newRoute = RelayMetaRoute.get('BarRoute');
   const clone = root.cloneWithRoute([field], newRoute);
   expect(clone instanceof RelayQuery.Root).toBe(true);
   expect(clone.getChildren().length).toBe(1);
   expect(clone.getChildren()[0]).toBe(field);
   expect(clone.getRoute().name).toBe('BarRoute');
   expect(root.getRoute().name).toBe('FooRoute');
   expect(root.cloneWithRoute([field], RelayMetaRoute.get('FooRoute')))
     .toBe(root);
 });
示例#21
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
   );
 }
示例#22
0
function buildRoot(
  rootID: DataID,
  nodes: Array<RelayQuery.Node>,
  name: string,
  type: string
): RelayQuery.Root {
  const children = [idField, typeField];
  const fields = [];
  nodes.forEach(node => {
    if (node instanceof RelayQuery.Field) {
      fields.push(node);
    } else {
      children.push(node);
    }
  });
  children.push(RelayQuery.Fragment.build(
    'diffRelayQuery',
    type,
    fields
  ));

  return RelayQuery.Root.build(
    name,
    NODE,
    rootID,
    children,
    {
      identifyingArgName: ID,
      identifyingArgType: ID_TYPE,
      isAbstract: true,
      isDeferred: false,
      isPlural: false,
    },
    NODE_TYPE
  );
}
  it('does not omit "empty" required ref query dependencies', () => {
    // It isn't possible to produce an "empty" ref query dependency with
    // `Relay.QL`, but in order to be future-proof against this possible edge
    // case, we create such a query by hand.
    const fragment = Relay.QL`fragment on Node{name}`;
    const id = RelayQuery.Field.build({
      fieldName: 'id',
      metadata: {isRequisite: true},
      type: 'String',
    });
    const typename = RelayQuery.Field.build({
      fieldName: '__typename',
      metadata: {isRequisite: true},
      type: 'String',
    });
    let queryNode = RelayQuery.Root.build(
      'splitDeferredRelayQueries',
      'node',
      '4',
      [
        id,
        typename,
        RelayQuery.Field.build({
          fieldName: 'hometown',
          children: [id, getNode(defer(fragment))],
          metadata: {
            canHaveSubselections: true,
            isGenerated: true,
            inferredPrimaryKey: 'id',
            inferredRootCallName: 'node',
          },
          type: 'Page',
        }),
      ],
      {
        identifyingArgName: 'id',
      }
    );
    queryNode = queryNode.clone(
      queryNode.getChildren().map((outerChild, ii) => {
        if (ii === 1) {
          return outerChild.clone(
            outerChild.getChildren().map((innerChild, jj) => {
              if (jj === 0) {
                return innerChild.cloneAsRefQueryDependency();
              } else {
                return innerChild;
              }
            })
          );
        } else {
          return outerChild;
        }
      })
    );

    const {required, deferred} = splitDeferredRelayQueries(queryNode);

    // required part
    expect(deferred[0].required.getName()).toBe(queryNode.getName());
    expect(required).toEqualQueryRoot(getNode(Relay.QL`
      query {
        node(id:"4"){hometown{id},id}
      }
    `));
    expect(required.getID()).toBe('q1');

    // deferred part
    expect(deferred.length).toBe(1);
    expect(deferred[0].required.getName()).toBe(queryNode.getName());
    expect(deferred[0].required).toEqualQueryRoot(
      filterGeneratedRootFields(getRefNode(
        Relay.QL`
          query {
            nodes(ids:$ref_q1) {
              ${fragment}
            }
          }
        `,
        {path: '$.*.hometown.id'}
      ))
    );
    expect(deferred[0].required.getID()).toBe('q2');
    expect(deferred[0].required.isDeferred()).toBe(true);

    // no nested deferreds
    expect(deferred[0].deferred).toEqual([]);
  });
 it('updates the range when edge data changes', () => {
   // NOTE: Hack to preserve `source{id}` in all environments for now.
   const query = RelayQuery.Root.create(Relay.QL`
     query {
       node(id:"123") {
         friends(find:"node1") {
           edges {
             node {
               id
             }
             source {
               id
             }
           }
         }
       }
     }
   `, RelayMetaRoute.get('$RelayTest'), {});
   const payload = {
     node: {
       id: '123',
       friends: {
         edges: [{
           node: {
             id: 'node1',
           },
           source: { // new edge field
             id: '456',
           },
           cursor: 'cursor1',
         }],
         [PAGE_INFO]: {
           [HAS_NEXT_PAGE]: true,
           [HAS_PREV_PAGE]: true,
         },
       },
       __typename: 'User',
     },
   };
   const results = writePayload(store, writer, query, payload);
   expect(results).toEqual({
     created: {
       '456': true, // `source` added
     },
     updated: {
       'client:1': true, // range updated because an edge had a change
       'client:client:1:node1': true, // `source` added to edge
     },
   });
   expect(store.getRangeMetadata('client:1', [
     {name: 'first', value: 1},
   ])).toEqual({
     diffCalls: [],
     filterCalls: [],
     pageInfo: {
       [END_CURSOR]: 'cursor1',
       [HAS_NEXT_PAGE]: true,
       [HAS_PREV_PAGE]: false,
       [START_CURSOR]: 'cursor1',
     },
     requestedEdgeIDs: ['client:client:1:node1'],
     filteredEdges: [
       {edgeID: 'client:client:1:node1', nodeID: 'node1'},
     ],
   });
   const sourceID = store.getLinkedRecordID('client:client:1:node1', 'source');
   expect(sourceID).toBe('456');
   expect(store.getField(sourceID, 'id')).toBe('456');
 });
/**
 * Merges the results of a single top-level field into the store.
 */
function mergeField(
  writer: RelayQueryWriter,
  fieldName: string,
  payload: PayloadObject | PayloadArray,
  operation: RelayQuery.Operation
): void {
  // don't write mutation/subscription metadata fields
  if (fieldName in IGNORED_KEYS) {
    return;
  }
  if (Array.isArray(payload)) {
    payload.forEach(item => {
      if (typeof item === 'object' && item != null && !Array.isArray(item)) {
        if (getString(item, ID)) {
          mergeField(writer, fieldName, item, operation);
        }
      }
    });
    return;
  }
  // reassign to preserve type information in below closure
  const payloadData = payload;

  const store = writer.getRecordStore();
  let recordID = getString(payloadData, ID);
  let path;

  if (recordID != null) {
    path = RelayQueryPath.createForID(recordID, 'writeRelayUpdatePayload');
  } else {
    recordID = store.getDataID(fieldName);
    // Root fields that do not accept arguments
    path = RelayQueryPath.create(RelayQuery.Root.build(
      'writeRelayUpdatePayload',
      fieldName,
      null,
      null,
      {
        identifyingArgName: null,
        identifyingArgType: null,
        isAbstract: true,
        isDeferred: false,
        isPlural: false,
      },
      ANY_TYPE
    ));
  }
  invariant(
    recordID,
    'writeRelayUpdatePayload(): Expected a record ID in the response payload ' +
    'supplied to update the store.'
  );

  // write the results for only the current field, for every instance of that
  // field in any subfield/fragment in the query.
  const handleNode = node => {
    node.getChildren().forEach(child => {
      if (child instanceof RelayQuery.Fragment) {
        handleNode(child);
      } else if (
        child instanceof RelayQuery.Field &&
        child.getSerializationKey() === fieldName
      ) {
        // for flow: types are lost in closures
        if (path && recordID) {
          // ensure the record exists and then update it
          writer.createRecordIfMissing(
            child,
            recordID,
            path,
            payloadData
          );
          writer.writePayload(
            child,
            recordID,
            payloadData,
            path
          );
        }
      }
    });
  };
  handleNode(operation);
}
示例#26
0
 it('returns the name of the root field', () => {
   const root = RelayQuery.Root.build('RelayQueryTest', 'viewer');
   expect(root.getFieldName()).toBe('viewer');
 });
示例#27
0
    invariant(
      match,
      'getRefNode(): Expected call variable of the form `<ref_q\\d+>`.',
    );
    // e.g. `q0`
    const id = match[1];
    // e.g. `{ref_q0: '<ref_q0>'}`
    const variables = {[name]: '<' + callValue.callVariableName + '>'};

    return RelayQuery.Root.create(
      {
        ...node,
        calls: [
          QueryBuilder.createCall(
            'id',
            QueryBuilder.createBatchCallVariable(id, refParam.path),
          ),
        ],
        isDeferred: true,
      },
      RelayMetaRoute.get('$RelayTestUtils'),
      variables,
    );
  },

  getVerbatimNode(node, variables) {
    return RelayTestUtils.filterGeneratedFields(
      RelayTestUtils.getNode(node, variables),
    );
  },

  filterGeneratedFields(query) {
示例#28
0
 it('returns an empty array when there are no arguments', () => {
   const root = RelayQuery.Root.build('RelayQueryTest', 'viewer');
   expect(root.getCallsWithValues()).toEqual([]);
 });
示例#29
0
 it('returns nothing when there is no identifying argument', () => {
   const root = RelayQuery.Root.build('RelayQueryTest', 'viewer');
   expect(root.getIdentifyingArg()).toBeUndefined();
 });