apollo-link-contentful
Version:
Perform GraphQL queries against Contentful’s Rest API. No more, query size limits! No more, query complexities!!
269 lines (225 loc) • 10 kB
JavaScript
;
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
exports.__esModule = true;
exports.parseQueryVariables = exports.buildDefinitionMap = exports.getRootKey = void 0;
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _lomit = require("lomit");
var _constants = require("./constants");
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { (0, _defineProperty2["default"])(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
/**
* Return root query object to work against based on if an operationName key
* is set in operation.
*
* @param {Operation} operation
* @return {Object}
*/
var getRootQuery = function getRootQuery(operation) {
var query = operation.query,
operationName = operation.operationName;
return query && query.hasOwnProperty(operationName) ? query[operationName] : query;
};
/**
* Return the root key that will contain the data returned via the GraphQL query.
*
* @param {Operation} operation
* @return {string}
*/
var getRootKey = function getRootKey(operation) {
var definition = getRootQuery(operation).definitions.find(function (definition) {
return definition.operation === 'query';
});
if (definition) {
var selection = definition.selectionSet.selections.find(function (selection) {
return selection.name.kind === 'Name';
});
if (selection) {
return selection.name.value;
}
}
return null;
};
/**
* Contentful API - Search Parameters
* @ref https://www.contentful.com/developers/docs/references/content-delivery-api/#/reference/search-parameters
*/
exports.getRootKey = getRootKey;
var contentfulReservedParameters = ['access_token', 'id', 'include', 'locale', 'content_type', 'select', 'query', 'links_to_entry', 'links_to_asset', 'order', 'limit', 'skip', 'mimetype_group', 'preview'];
/**
* Pass in a GraphQL query variable field and get the equivalent
* Contentful REST API query argument.
*
* @ref https://www.contentful.com/developers/docs/references/content-delivery-api/#/reference/search-parameters/search-on-references
*
* @param {string} variableKey
* @return {string}
*/
var getSearchKey = function getSearchKey(variableKey) {
if (variableKey.endsWith('_in')) {
return "fields." + variableKey.replace('_in', '') + "[in]";
}
if (variableKey.endsWith('_not')) {
return "fields." + variableKey.replace('_not', '') + "[ne]";
}
if (variableKey.endsWith('_exists')) {
return "fields." + variableKey.replace('_exists', '') + "[exists]";
}
if (variableKey.endsWith('_not_in')) {
return "fields." + variableKey.replace('_not_in', '') + "[nin]";
} // if (variableKey.endsWith('_contains')) {
// return `fields.${variableKey.replace('_contains', '')}[?]`
// }
// if (variableKey.endsWith('_not_contains')) {
// return `fields.${variableKey.replace('_not_contains', '')}[?]`
// }
// @todo Add support for `x[all]` - Ryan
// @todo Add support for `x[match]` - Ryan
// @todo Add support for `x[gt]` - Ryan
// @todo Add support for `x[gte]` - Ryan
// @todo Add support for `x[lt]` - Ryan
// @todo Add support for `x[lte]` - Ryan
// @todo Add support for `x[near]` - Ryan
// @todo Add support for `x[within]` - Ryan
return "fields." + variableKey;
};
/**
* Build a map that associates the variables passed into the query
* with the actual fields/arguments that are being requested in the
* API request.
*
* @param {Object} operation
* @return {Object}
*/
var buildVariableMap = function buildVariableMap(operation) {
var variableMap = {};
getRootQuery(operation).definitions.filter(function (definition) {
return definition.kind === _constants.DefinitionKind.OperationDefinition;
}).forEach(function (definition) {
definition.selectionSet.selections.forEach(function (selection) {
selection.arguments.forEach(function (argument) {
if (argument.value && argument.value.fields) {
argument.value.fields.forEach(function (field) {
var validField = field.value && field.value.name && field.value.name.value && field.kind && field.name && field.name.value;
if (validField) {
variableMap[field.value.name.value] = {
kind: field.kind,
field: field.name.value
};
}
});
} else {
var validArgument = argument.value && argument.value.name && argument.value.name.value && argument.kind && argument.name && argument.name.value;
if (validArgument) {
variableMap[argument.value.name.value] = {
kind: argument.kind,
field: argument.name.value
};
}
}
});
});
});
return variableMap;
};
/**
* GraphQL queries specify the expected shape of the response from the API,
* so this method attempts to extract that in a format that we can apply
* to the parsed REST API response, in order to make sure the response
* fulfills the contract of the request.
*
* @param {Array.<Object>} selectionSet
* @param {Array.<Object>} definitions
* @return {Object}
*/
var extractSelections = function extractSelections(selectionSet, definitions) {
if (!(selectionSet && selectionSet.selections && selectionSet.selections.length)) return null;
var selections = {};
selectionSet.selections.forEach(function (selection) {
if (selection.kind === _constants.SelectionKind.Field) {
if (selection.name && selection.name.value) {
selections[selection.name.value] = extractSelections(selection.selectionSet, definitions);
}
} else if (selection.kind === _constants.SelectionKind.FragmentSpread) {
var fragmentDefinition = definitions.find(function (definition) {
return definition.kind === _constants.DefinitionKind.FramentDefinition && definition.name && definition.name.value && selection.name && selection.name.value && definition.name.value === selection.name.value;
});
if (fragmentDefinition) {
selections["..." + selection.name.value] = extractSelections(fragmentDefinition.selectionSet, definitions);
}
}
});
return selections;
};
/**
*
*
* @param {Object} operation
* @return {Object}
*/
var buildDefinitionMap = function buildDefinitionMap(operation) {
var _ref;
var query = operation.query;
var operationDefinition = query.definitions.find(function (definition) {
return definition.kind === _constants.DefinitionKind.OperationDefinition;
});
if (!(operationDefinition && operationDefinition.name && operationDefinition.name.value)) {
return {};
}
return _ref = {}, _ref[operationDefinition.name.value] = extractSelections(operationDefinition.selectionSet, query.definitions), _ref;
};
/**
* Convert the query variables to a format the Contentful API understands
*
* @param {Object} query
* @param {Object} variables
* @param {Object} variableMap
* @param {string} operationName
* @retrun {Object}
*/
exports.buildDefinitionMap = buildDefinitionMap;
var parseQueryVariables = function parseQueryVariables(operation) {
var variables = operation.variables;
var variableMap = buildVariableMap(operation);
var operationVariables = getRootQuery(operation).definitions.find(function (definition) {
return definition.kind === _constants.DefinitionKind.OperationDefinition;
}).variableDefinitions.map(function (variableDefinition) {
return variableDefinition.variable.name.value;
});
var operationQueries = Object.keys(variableMap).filter(function (variableKey) {
return variables && variables.hasOwnProperty(variableKey) && variableMap && variableMap.hasOwnProperty(variableKey);
}).map(function (variableKey) {
var _ref3;
try {
switch (variableMap[variableKey].kind) {
case _constants.VariableKind.Argument:
// If the variable key is known search parameter for the Contentful API,
// just pass it through un-parsed
var fieldExists = variableMap[variableKey] && variableMap[variableKey].field;
if (fieldExists && contentfulReservedParameters.includes(variableMap[variableKey].field)) {
var _ref2;
// Exclude preview variable is not set to true
if (variableMap[variableKey].field === 'preview' && !variables[variableKey]) {
return null;
}
return _ref2 = {}, _ref2[variableMap[variableKey].field] = variables[variableKey], _ref2;
}
break;
case _constants.VariableKind.ObjectField:
// Convert variable into query format supported in Contentful API
var queryKey = getSearchKey(variableMap[variableKey].field);
return _ref3 = {}, _ref3[queryKey] = variables[variableKey], _ref3;
default:
return null;
}
} catch (err) {
console.error(err);
return null;
}
}).filter(function (variable) {
return !!variable;
}).reduce(function (acc, cur) {
return _objectSpread(_objectSpread({}, acc), cur);
}, {});
return _objectSpread(_objectSpread({}, operationQueries), (0, _lomit.omit)(variables, operationVariables));
};
exports.parseQueryVariables = parseQueryVariables;