import Query from './query';
import Mutation from './mutation';
import Operation from './operation';
import join from './join';
import SelectionSet, {FragmentDefinition} from './selection-set';
function isAnonymous(operation) {
return operation.isAnonymous;
}
function hasAnonymousOperations(operations) {
return operations.some(isAnonymous);
}
function hasDuplicateOperationNames(operations) {
const names = operations.map((operation) => operation.name);
return names.reduce((hasDuplicates, name, index) => {
return hasDuplicates || names.indexOf(name) !== index;
}, false);
}
function extractOperation(typeBundle, operationType, ...args) {
if (Operation.prototype.isPrototypeOf(args[0])) {
return args[0];
}
if (operationType === 'query') {
return new Query(typeBundle, ...args);
} else {
return new Mutation(typeBundle, ...args);
}
}
function isInvalidOperationCombination(operations) {
if (operations.length === 1) {
return false;
}
return hasAnonymousOperations(operations) || hasDuplicateOperationNames(operations);
}
function fragmentNameIsNotUnique(existingDefinitions, name) {
return existingDefinitions.some((definition) => (definition.name === name));
}
export default class Document {
/**
* This constructor should not be invoked directly.
* Use the factory function {@link Client#document} to create a Document.
* @param {Object} typeBundle A set of ES6 modules generated by {@link https://github.com/Shopify/graphql-js-schema|graphql-js-schema}.
*/
constructor(typeBundle) {
this.typeBundle = typeBundle;
this.definitions = [];
}
/**
* Returns the GraphQL query string for the Document (e.g. `query queryOne { ... } query queryTwo { ... }`).
*
* @return {String} The GraphQL query string for the Document.
*/
toString() {
return join(this.definitions);
}
/**
* Adds an operation to the Document.
*
* @private
* @param {String} operationType The type of the operation. Either 'query' or 'mutation'.
* @param {(Operation|String)} [query|queryName] Either an instance of an operation
* object, or the name of an operation. Both are optional.
* @param {Object[]} [variables] A list of variables in the operation. See {@link Client#variable}.
* @param {Function} [callback] The query builder callback. If an operation
* instance is passed, this callback will be ignored.
* A {@link SelectionSet} is created using this callback.
*/
addOperation(operationType, ...args) {
const operation = extractOperation(this.typeBundle, operationType, ...args);
if (isInvalidOperationCombination(this.operations.concat(operation))) {
throw new Error('All operations must be uniquely named on a multi-operation document');
}
this.definitions.push(operation);
}
/**
* Adds a query to the Document.
*
* @example
* document.addQuery('myQuery', (root) => {
* root.add('cat', (cat) => {
* cat.add('name');
* });
* });
*
* @param {(Query|String)} [query|queryName] Either an instance of a query
* object, or the name of a query. Both are optional.
* @param {Object[]} [variables] A list of variables in the query. See {@link Client#variable}.
* @param {Function} [callback] The query builder callback. If a query
* instance is passed, this callback will be ignored.
* A {@link SelectionSet} is created using this callback.
*/
addQuery(...args) {
this.addOperation('query', ...args);
}
/**
* Adds a mutation to the Document.
*
* @example
* const input = client.variable('input', 'CatCreateInput!');
*
* document.addMutation('myMutation', [input], (root) => {
* root.add('catCreate', {args: {input}}, (catCreate) => {
* catCreate.add('cat', (cat) => {
* cat.add('name');
* });
* });
* });
*
* @param {(Mutation|String)} [mutation|mutationName] Either an instance of a mutation
* object, or the name of a mutation. Both are optional.
* @param {Object[]} [variables] A list of variables in the mutation. See {@link Client#variable}.
* @param {Function} [callback] The mutation builder callback. If a mutation
* instance is passed, this callback will be ignored.
* A {@link SelectionSet} is created using this callback.
*/
addMutation(...args) {
this.addOperation('mutation', ...args);
}
/**
* Defines a fragment on the Document.
*
* @param {String} name The name of the fragment.
* @param {String} onType The type the fragment is on.
* @param {Function} [builderFunction] The query builder callback.
* A {@link SelectionSet} is created using this callback.
* @return {FragmentSpread} A {@link FragmentSpread} to be used with {@link SelectionSetBuilder#addFragment}.
*/
defineFragment(name, onType, builderFunction) {
if (fragmentNameIsNotUnique(this.fragmentDefinitions, name)) {
throw new Error('All fragments must be uniquely named on a multi-fragment document');
}
const selectionSet = new SelectionSet(this.typeBundle, onType, builderFunction);
const fragment = new FragmentDefinition(name, onType, selectionSet);
this.definitions.push(fragment);
return fragment.spread;
}
/**
* All operations ({@link Query} and {@link Mutation}) on the Document.
*/
get operations() {
return this.definitions.filter((definition) => Operation.prototype.isPrototypeOf(definition));
}
/**
* All {@link FragmentDefinition}s on the Document.
*/
get fragmentDefinitions() {
return this.definitions.filter((definition) => FragmentDefinition.prototype.isPrototypeOf(definition));
}
}