@quell/server
Version:
Quell is an open-source NPM package providing a light-weight caching layer implementation and cache invalidation for GraphQL responses on both the client- and server-side. Use Quell to prevent redundant client-side API requests and to minimize costly serv
765 lines (764 loc) • 39.6 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.getFieldsMap = exports.getQueryMap = exports.getMutationMap = exports.updateProtoWithFragment = exports.parseAST = exports.joinResponses = exports.createQueryObj = exports.createQueryStr = void 0;
const visitor_1 = require("graphql/language/visitor");
/**
* Traverses over a supplied query Object and uses the fields on there to create a query string reflecting the data.
* This query string is a modified version of the query string received by Quell that has references to data found within the cache removed
* so that the final query is faster and reduced in scope.
* @param {Object} queryObject - A modified version of the prototype with only values we want to pass onto the queryString.
* @param {string} operationType - A string indicating the GraphQL operation type- 'query', 'mutation', etc.
*/
function createQueryStr(queryObject, operationType) {
if (Object.keys(queryObject).length === 0)
return "";
const openCurly = "{";
const closeCurly = "}";
const openParen = "(";
const closeParen = ")";
let mainStr = "";
// iterate over every key in queryObject
// place key into query object
for (const key in queryObject) {
mainStr += ` ${key}${getAliasType(queryObject[key])}${getArgs(queryObject[key])} ${openCurly} ${stringify(queryObject[key])}${closeCurly}`;
}
/**
* Helper function that is used to recursively build a GraphQL query string from a nested object,
* ignoring any __values (ie __alias and __args).
* @param {QueryFields} fields - An object whose properties need to be converted to a string to be used for a GraphQL query.
* @returns {string} innerStr - A graphQL query string.
*/
function stringify(fields) {
// initialize inner string
let innerStr = "";
// iterate over KEYS in OBJECT
for (const key in fields) {
// is fields[key] string? concat with inner string & empty space
if (typeof fields[key] === "boolean") {
innerStr += key + " ";
}
// is key object? && !key.includes('__'), recurse stringify
if (typeof fields[key] === "object" && !key.includes("__")) {
const fieldsObj = fields[key];
// TODO try to fix this error
const type = getAliasType(fieldsObj);
const args = getArgs(fieldsObj);
innerStr += `${key}${type}${args} ${openCurly} ${stringify(fieldsObj)}${closeCurly} `;
}
}
return innerStr;
}
/**
* Helper function that iterates through arguments object for current field and creates
* an argument string to attach to the query string.
* @param {QueryFields} fields - Object whose arguments will be attached to the query string.
* @returns {string} Argument string to be attached to the query string.
*/
function getArgs(fields) {
let argString = "";
if (!fields.__args)
return "";
Object.keys(fields.__args).forEach((key) => {
argString
? (argString += `, ${key}: "${fields.__args[key]}"`)
: (argString += `${key}: "${fields.__args[key]}"`);
});
// return arg string in parentheses, or if no arguments, return an empty string
return argString ? `${openParen}${argString}${closeParen}` : "";
}
/**
* Helper function that formats the field's alias, if it exists, for the query string.
* @param {QueryFields} fields - Object whose alias will be attached to the query string.
* @returns {string} Alias string to be attached to the query string.
*/
function getAliasType(fields) {
return fields.__alias ? `: ${fields.__type}` : "";
}
// Create the final query string.
const queryStr = openCurly + mainStr + " " + closeCurly;
return operationType ? operationType + " " + queryStr : queryStr;
}
exports.createQueryStr = createQueryStr;
/**
* Takes in a map of fields and true/false values (the prototype) and creates a query object containing any values missing from the cache.
* The resulting queryObj is then used as a template to create GraphQL query strings.
* @param {ProtoObjType} map - Map of fields and true/false values from initial request, should be the prototype.
* @returns {Object} queryObject that includes only the values to be requested from GraphQL endpoint.
*/
function createQueryObj(map) {
const output = {};
// iterate over every key in map
// true values are filtered out, false values are placed on output
for (const key in map) {
const reduced = reducer(map[key]);
if (Object.keys(reduced).length > 0) {
output[key] = reduced;
}
}
/**
* Takes in a fields object and returns only the values needed from the server.
* @param {Object} fields - Object containing true or false values that determines what should be
* retrieved from the server.
* @returns {Object} Filtered object of only queries without a value or an empty object.
*/
function reducer(fields) {
// Create a filter object to store values needed from server.
const filter = {};
// Create a propsFilter object for properties such as args, aliases, etc.
const propsFilter = {};
for (const key in fields) {
// If value is false, place directly on filter
if (fields[key] === false) {
filter[key] = false;
}
// Force the id onto the query object
if (key === "id" || key === "_id" || key === "ID" || key === "Id") {
filter[key] = false;
}
// If value is an object, recurse to determine nested values
if (typeof fields[key] === "object" && !key.includes("__")) {
const reduced = reducer(fields[key]);
// if reduced object has any values to pass, place on filter
if (Object.keys(reduced).length > 1) {
filter[key] = reduced;
}
}
// If reserved property such as args or alias, place on propsFilter
if (key.includes("__")) {
propsFilter[key] = fields[key];
}
}
const numFields = Object.keys(fields).length;
// If the filter has any values to pass, return filter & propsFilter; otherwise return empty object
return Object.keys(filter).length > 1 && numFields > 5
? Object.assign(Object.assign({}, filter), propsFilter) : {};
}
return output;
}
exports.createQueryObj = createQueryObj;
/**
* Combines two objects containing results from separate sources and outputs a single object with information from both sources combined,
* formatted to be delivered to the client, using the queryProto as a template for how to structure the final response object.
* @param {Object} cacheResponse - Response data from the cache.
* @param {Object} serverResponse - Response data from the server or external API.
* @param {Object} queryProto - Current slice of the prototype being used as a template for final response object structure.
* @param {boolean} fromArray - Whether or not the current recursive loop came from within an array (should NOT be supplied to function call).
*/
function joinResponses(cacheResponse, serverResponse, queryProto, fromArray = false) {
let mergedResponse = {};
// loop through fields object keys, the "source of truth" for structure
// store combined responses in mergedResponse
for (const key in queryProto) {
// for each key, check whether data stored at that key is an array or an object
const checkResponse = Object.prototype.hasOwnProperty.call(serverResponse, key)
? serverResponse
: cacheResponse;
if (Array.isArray(checkResponse[key])) {
// merging logic depends on whether the data is on the cacheResponse, serverResponse, or both
// if both of the caches contain the same keys...
if (cacheResponse[key] && serverResponse[key]) {
// we first check to see if the responses have identical keys to both avoid
// only returning 1/2 of the data (ex: there are 2 objects in the cache and
// you query for 4 objects (which includes the 2 cached objects) only returning
// the 2 new objects from the server)
// if the keys are identical, we can return a "simple" merge of both
const cacheKeys = Object.keys(cacheResponse[key][0]);
const serverKeys = Object.keys(serverResponse[key][0]);
let keysSame = true;
for (let n = 0; n < cacheKeys.length; n++) {
if (cacheKeys[n] !== serverKeys[n])
keysSame = false;
}
if (keysSame) {
mergedResponse[key] = [
...cacheResponse[key],
...serverResponse[key],
];
}
// otherwise, we need to combine the responses at the object level
else {
const mergedArray = [];
for (let i = 0; i < cacheResponse[key].length; i++) {
// for each index of array, combine cache and server response objects
const joinedResponse = joinResponses({ [key]: cacheResponse[key][i] }, { [key]: serverResponse[key][i] }, { [key]: queryProto[key] }, true);
mergedArray.push(joinedResponse);
}
mergedResponse[key] = mergedArray;
}
}
else if (cacheResponse[key]) {
mergedResponse[key] = cacheResponse[key];
}
else {
mergedResponse[key] = serverResponse[key];
}
}
else {
if (!fromArray) {
// if object doesn't come from an array, we must assign on the object at the given key
mergedResponse[key] = Object.assign(Object.assign({}, cacheResponse[key]), serverResponse[key]);
}
else {
// if the object comes from an array, we do not want to assign to a key as per GQL spec
mergedResponse = Object.assign(Object.assign({}, cacheResponse[key]), serverResponse[key]);
}
for (const fieldName in queryProto[key]) {
// check for nested objects
if (typeof queryProto[key][fieldName] === "object" &&
!fieldName.includes("__")) {
// recurse joinResponses on that object to create deeply nested copy on mergedResponse
let mergedRecursion = {};
if (cacheResponse[key] && serverResponse[key]) {
if (cacheResponse[key][fieldName] &&
serverResponse[key][fieldName]) {
mergedRecursion = joinResponses({
[fieldName]: cacheResponse[key][fieldName],
}, {
[fieldName]: serverResponse[key][fieldName],
}, { [fieldName]: queryProto[key][fieldName] });
}
else if (cacheResponse[key][fieldName]) {
mergedRecursion[fieldName] = cacheResponse[key][fieldName];
}
else {
mergedRecursion[fieldName] = serverResponse[key][fieldName];
}
}
// place on merged response, spreading the mergedResponse[key] if it
// is an object or an array, or just adding it as a value at key otherwise
if (typeof mergedResponse[key] === "object" ||
Array.isArray(mergedResponse[key])) {
mergedResponse[key] = Object.assign(Object.assign({}, mergedResponse[key]), mergedRecursion);
}
else {
// case for when mergedResponse[key] is not an object or array and possibly
// boolean or a string
mergedResponse[key] = Object.assign({ key: mergedResponse[key] }, mergedRecursion);
}
}
}
}
}
return mergedResponse;
}
exports.joinResponses = joinResponses;
/**
* Traverses the abstract syntax tree depth-first to create a template for future operations, such as
* request data from the cache, creating a modified query string for additional information needed, and joining cache and database responses.
* @param {Object} AST - An abstract syntax tree generated by GraphQL library that we will traverse to build our prototype.
* @param {Object} options - (not fully integrated) A field for user-supplied options.
* @returns {Object} prototype object
* @returns {string} operationType
* @returns {Object} frags object
*/
function parseAST(AST, options = { userDefinedID: null }) {
// Initialize prototype and frags as empty objects.
// Information from the AST is distilled into the prototype for easy
// access during caching, rebuilding query strings, etc.
const proto = {};
// The frags object will contain the fragments defined in the query in a format
// similar to the proto.
const frags = {};
// Create operation type variable. This will be 'query', 'mutation', 'subscription', 'noID', or 'unQuellable'.
let operationType = "";
// Initialize a stack to keep track of depth first parsing path.
const stack = [];
// Create field arguments object, which will track the id, type, alias, and args for the fields.
// The field arguments object will eventually be merged with the prototype object.
const fieldArgs = {};
// Extract the userDefinedID from the options object, if provided.
const userDefinedID = options.userDefinedID;
/**
* visit is a utility provided in the graphql-JS library. It performs a
* depth-first traversal of the abstract syntax tree, invoking a callback
* when each SelectionSet node is entered. That function builds the prototype.
* Invokes a callback when entering and leaving Field node to keep track of nodes with stack
*
* Find documentation at:
* https://graphql.org/graphql-js/language/#visit
*/
(0, visitor_1.visit)(AST, {
// The enter function will be triggered upon entering each node in the traversal.
enter(node) {
var _a, _b;
// Quell cannot cache directives, so we need to return as unQuellable if the node has directives.
if (node === null || node === void 0 ? void 0 : node.directives) {
if ((_b = (_a = node === null || node === void 0 ? void 0 : node.directives) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0 > 0) {
operationType = "unQuellable";
// Return BREAK to break out of the current traversal branch.
return visitor_1.BREAK;
}
}
},
// If the current node is of type OperationDefinition, this function will be triggered upon entering it.
// It checks the type of operation being performed.
OperationDefinition(node) {
// Quell cannot cache subscriptions, so we need to return as unQuellable if the type is subscription.
operationType = node.operation;
if (operationType === "subscription") {
operationType = "unQuellable";
// Return BREAK to break out of the current traversal branch.
return visitor_1.BREAK;
}
},
// If the current node is of type FragmentDefinition, this function will be triggered upon entering it.
FragmentDefinition(node) {
// Get the name of the fragment.
const fragName = node.name.value;
// Add the fragment name to the stack.
stack.push(fragName);
// Add the fragment name as a key in the frags object, initialized to an empty object.
frags[fragName] = {};
// Loop through the selections in the selection set for the current FragmentDefinition node
// in order to extract the fields in the fragment.
for (let i = 0; i < node.selectionSet.selections.length; i++) {
// Below, we get the 'name' property from the SelectionNode.
// However, InlineFragmentNode (one of the possible types for SelectionNode) does
// not have a 'name' property, so we will want to skip nodes with that type.
if (node.selectionSet.selections[i].kind !== "InlineFragment") {
// Add base-level field names in the fragment to the frags object.
frags[fragName][node.selectionSet.selections[i].name.value] = true;
}
}
},
Field: {
// If the current node is of type Field, this function will be triggered upon entering it.
enter(node) {
// Return introspection queries as unQuellable so that we do not cache them.
// "__keyname" syntax is later used for Quell's field-specific options, though this does not create collision with introspection.
if (node.name.value.includes("__")) {
operationType = "unQuellable";
// Return BREAK to break out of the current traversal branch.
return visitor_1.BREAK;
}
// Create an args object that will be populated with the current node's arguments.
const argsObj = {};
// Auxiliary object for storing arguments, aliases, field-specific options, and more.
// Query-wide options should be handled on Quell's options object.
const auxObj = {
__id: null,
};
// Loop through the field's arguments.
if (node.arguments) {
node.arguments.forEach((arg) => {
const key = arg.name.value;
// Quell cannot cache queries with variables, so we need to return unQuellable if the query has variables.
if (arg.value.kind === "Variable" && operationType === "query") {
operationType = "unQuellable";
// Return BREAK to break out of the current traversal branch.
return visitor_1.BREAK;
}
/*
* In the next step, we get the value from the argument node's value node.
* This assumes that the value node has a 'value' property.
* If the 'kind' of the value node is ObjectValue, ListValue, NullValue, or ListValue
* then the value node will not have a 'value' property, so we must first check that
* the 'kind' does not match any of those types.
*/
if (arg.value.kind === "NullValue" ||
arg.value.kind === "ObjectValue" ||
arg.value.kind === "ListValue") {
operationType = "unQuellable";
// Return BREAK to break out of the current traversal branch.
return visitor_1.BREAK;
}
// Assign argument values to argsObj (key will be argument name, value will be argument value),
// skipping field-specific options ('__') provided as arguments.
if (!key.includes("__")) {
// Get the value from the argument node's value node.
argsObj[key] = arg.value.value;
}
// If a userDefinedID was included in the options object and the current argument name matches
// that ID, update the auxiliary object's id.
if (userDefinedID ? key === userDefinedID : false) {
auxObj.__id = arg.value.value;
}
else if (
// If a userDefinedID was not provided, determine the uniqueID from the args.
// Note: do not use key.includes('id') to avoid assigning fields such as "idea" or "idiom" as uniqueID.
key === "id" ||
key === "_id" ||
key === "ID" ||
key === "Id") {
// If the name of the argument is 'id', '_id', 'ID', or 'Id',
// set the '__id' field on the auxObj equal to value of that argument.
auxObj.__id = arg.value.value;
}
});
}
// Gather other auxiliary data such as aliases, arguments, query type, and more to append to the prototype for future reference.
// Set the fieldType (which will be the key in the fieldArgs object) equal to either the field's alias or the field's name.
const fieldType = node.alias
? node.alias.value
: node.name.value;
// Set the '__type' property of the auxiliary object equal to the field's name, converted to lower case.
auxObj.__type = node.name.value.toLowerCase();
// Set the '__alias' property of the auxiliary object equal to the field's alias if it has one.
auxObj.__alias = node.alias ? node.alias.value : null;
// Set the '__args' property of the auxiliary object equal to the args
auxObj.__args = Object.keys(argsObj).length > 0 ? argsObj : null;
// Add auxObj fields to prototype, allowing future access to type, alias, args, etc.
fieldArgs[fieldType] = Object.assign({}, auxObj);
// Add the field type to stacks to keep track of depth-first parsing path.
stack.push(fieldType);
},
// If the current node is of type Field, this function will be triggered after visiting it and all of its children.
leave() {
// Pop stacks to keep track of depth-first parsing path.
stack.pop();
},
},
SelectionSet: {
// If the current node is of type SelectionSet, this function will be triggered upon entering it.
// The selection sets contain all of the sub-fields.
// Iterate through the sub-fields to construct fieldsObject
enter(node, key, parent,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
path,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
ancestors) {
/*
* Exclude SelectionSet nodes whose parents are not of the kind
* 'Field' to exclude nodes that do not contain information about
* queried fields.
*/
// FIXME: It is possible for the parent to be an array. This happens when the selection set
// is a fragment spread. In that case, the parent will not have a 'kind' property. For now,
// add a check that parent is not an array.
if (parent && // parent is not undefined
!Array.isArray(parent) && // parent is not readonly ASTNode[]
parent.kind === "Field" // can now safely cast parent to ASTNode
) {
// Create fieldsValues object that will be used to collect fields as
// we loop through the selections.
const fieldsValues = {};
/*
* Create a variable called fragment, initialized to false, to indicate whether the selection set includes a fragment spread.
* Loop through the current selection set's selections array.
* If the array contains a FragmentSpread node, set the fragment variable to true.
* This is reset to false upon entering each new selection set.
*/
let fragment = false;
for (const field of node.selections) {
if (field.kind === "FragmentSpread")
fragment = true;
/*
* If the current selection in the selections array is not a nested object
* (i.e. does not have a SelectionSet), set its value in fieldsValues to true.
* Below, we get the 'name' property from the SelectionNode.
* However, InlineFragmentNode (one of the possible types for SelectionNode) does
* not have a 'name' property, so we will want to skip nodes with that type.
* Furthermore, FragmentSpreadNodes never have a selection set property.
*/
if (field.kind !== "InlineFragment" &&
(field.kind === "FragmentSpread" || !field.selectionSet))
fieldsValues[field.name.value] = true;
}
// If ID was not included on the request and the current node is not a fragment, then the query
// will not be included in the cache, but the request will be processed.
if (!Object.prototype.hasOwnProperty.call(fieldsValues, "id") &&
!Object.prototype.hasOwnProperty.call(fieldsValues, "_id") &&
!Object.prototype.hasOwnProperty.call(fieldsValues, "ID") &&
!Object.prototype.hasOwnProperty.call(fieldsValues, "Id") &&
!fragment) {
operationType = "noID";
// Return BREAK to break out of the current traversal branch.
return visitor_1.BREAK;
}
// Place current fieldArgs object onto fieldsObject so it gets passed along to prototype.
// The fieldArgs contains arguments, aliases, etc.
const fieldsObject = Object.assign(Object.assign({}, fieldsValues), fieldArgs[stack[stack.length - 1]]);
// Loop through stack to get correct path in proto for temp object
stack.reduce((prev, curr, index) => {
// if last item in path, set value
if (index + 1 === stack.length)
prev[curr] = Object.assign({}, fieldsObject);
return prev[curr];
}, proto);
}
},
// If the current node is of type SelectionSet, this function will be triggered upon entering it.
leave() {
// Pop stacks to keep track of depth-first parsing path
stack.pop();
},
},
});
return { proto, operationType, frags };
}
exports.parseAST = parseAST;
/**
* Takes collected fragments and integrates them onto the prototype where referenced.
* @param {Object} protoObj - Prototype before it has been updated with fragments.
* @param {Object} frags - Fragments object to update prototype with.
* @returns {Object} Updated prototype object.
*/
function updateProtoWithFragment(protoObj, frags) {
// If the proto or frags objects are null/undefined, return the protoObj.
if (!protoObj || !frags)
return protoObj;
// Loop through the fields in the proto object.
for (const key in protoObj) {
// If the field is a nested object and not an introspection field (fields starting with '__'
// that provide information about the underlying schema)
if (typeof protoObj[key] === "object" && !key.includes("__")) {
// Update the field to the result of recursively calling updateProtoWithFragment,
// passing the field and fragments.
protoObj[key] = updateProtoWithFragment(protoObj[key], frags);
}
// If the field is a reference to a fragment, replace the reference to the fragment with
// the actual fragment.
if (Object.prototype.hasOwnProperty.call(frags, key)) {
protoObj = Object.assign(Object.assign({}, protoObj), frags[key]);
delete protoObj[key];
}
}
// Return the updated proto
return protoObj;
}
exports.updateProtoWithFragment = updateProtoWithFragment;
/**
* Generates a map of mutation to GraphQL object types. This mapping is used
* to identify references to cached data when mutation occurs.
* @param {Object} schema - GraphQL defined schema that is used to facilitate caching by providing valid queries,
* mutations, and fields.
* @returns {Object} mutationMap - Map of mutations to GraphQL types.
*/
function getMutationMap(schema) {
var _a;
const mutationMap = {};
// get object containing all root mutations defined in the schema
const mutationTypeFields = (_a = schema === null || schema === void 0 ? void 0 : schema.getMutationType()) === null || _a === void 0 ? void 0 : _a.getFields();
// if queryTypeFields is a function, invoke it to get object with queries
const mutationsObj = typeof mutationTypeFields === "function"
? mutationTypeFields()
: mutationTypeFields;
for (const mutation in mutationsObj) {
// get name of GraphQL type returned by query
// if ofType --> this is collection, else not collection
let returnedType;
if (mutationsObj[mutation].type.ofType) {
returnedType = [];
returnedType.push(mutationsObj[mutation].type.ofType.name);
}
if (mutationsObj[mutation].type.name) {
returnedType = mutationsObj[mutation].type.name;
}
mutationMap[mutation] = returnedType;
}
return mutationMap;
}
exports.getMutationMap = getMutationMap;
/**
* Generates a map of queries to GraphQL object types. This mapping is used
* to identify and create references to cached data.
* @param {Object} schema - GraphQL defined schema that is used to facilitate caching by providing valid queries,
* mutations, and fields.
* @returns {Object} queryMap - Map of queries to GraphQL types.
*/
function getQueryMap(schema) {
var _a;
const queryMap = {};
// get object containing all root queries defined in the schema
const queryTypeFields = (_a = schema === null || schema === void 0 ? void 0 : schema.getQueryType()) === null || _a === void 0 ? void 0 : _a.getFields();
// if queryTypeFields is a function, invoke it to get object with queries
const queriesObj = typeof queryTypeFields === "function" ? queryTypeFields() : queryTypeFields;
for (const query in queriesObj) {
// get name of GraphQL type returned by query
// if ofType --> this is collection, else not collection
let returnedType;
if (queriesObj[query].type.ofType) {
returnedType = [];
returnedType.push(queriesObj[query].type.ofType.name);
}
if (queriesObj[query].type.name) {
returnedType = queriesObj[query].type.name;
}
queryMap[query] = returnedType;
}
console.log("QUERY MAP UPDATE: MORE LOGS", queryMap);
return queryMap;
}
exports.getQueryMap = getQueryMap;
/**
* Generates of map of fields to GraphQL types. This mapping is used to identify
* and create references to cached data.
* @param {Object} schema - GraphQL defined schema that is used to facilitate caching by providing valid queries,
* mutations, and fields.
* @returns {Object} fieldsMap - Map of fields to GraphQL types.
*/
function getFieldsMap(schema) {
var _a, _b;
console.log("Schema structure:", Object.keys(schema));
console.log("Does schema have default?", schema.default ? "Yes" : "No");
// if (schema.default) {
// console.log('Schema.default props:', Object.keys(schema.default));
// } else if (schema._typeMap) {
// console.log('Schema has direct _typeMap');
// } else if (schema.getTypeMap) {
// console.log('Schema has getTypeMap() method');
// } else {
// console.log('Schema format not recognized by Quell');
// }
const fieldsMap = {};
// Handle multiple schema formats
let typeMap;
if ((_a = schema === null || schema === void 0 ? void 0 : schema.default) === null || _a === void 0 ? void 0 : _a._typeMap) {
// Original format that Quell expected
typeMap = schema.default._typeMap;
console.log("Using schema.default._typeMap format");
}
else if (schema._typeMap) {
// Direct _typeMap property
typeMap = schema._typeMap;
console.log("Using direct schema._typeMap format");
}
else if (schema.getTypeMap) {
// Standard GraphQL API method
typeMap = schema.getTypeMap();
console.log("Using schema.getTypeMap() method");
}
else {
// Fallback to empty object
console.log("No recognized schema format found");
typeMap = {};
}
// const fieldsMap: FieldsMapType = {};
const typesList = ((_b = schema === null || schema === void 0 ? void 0 : schema.default) === null || _b === void 0 ? void 0 : _b._typeMap) || {};
const builtInTypes = [
"String",
"Int",
"Float",
"Boolean",
"ID",
"Query",
"__Type",
"__Field",
"__EnumValue",
"__DirectiveLocation",
"__Schema",
"__TypeKind",
"__InputValue",
"__Directive",
];
// exclude built-in types
const customTypes = Object.keys(typesList).filter((type) => { var _a, _b; return !builtInTypes.includes(type) && type !== ((_b = (_a = schema.default) === null || _a === void 0 ? void 0 : _a._queryType) === null || _b === void 0 ? void 0 : _b.name); });
// loop through types
for (const type of customTypes) {
const fieldsObj = {};
// let fields: { [field: string]: FieldType } = typesList[type]._fields;
let fields = typesList[type]._fields;
if (typeof fields === "function")
fields = fields();
for (const field in fields) {
const key = fields[field].name;
const value = fields[field].type.ofType
? fields[field].type.ofType.name
: fields[field].type.name;
fieldsObj[key] = value;
}
// place assembled types on fieldsMap
fieldsMap[type] = fieldsObj;
}
return fieldsMap;
}
exports.getFieldsMap = getFieldsMap;
// // TODO: Unused functions for QuellCache Class
// /**
// * createRedisKey creates key based on field name and argument id and returns string or null if key creation is not possible
// * @param {Object} mutationMap -
// * @param {Object} proto -
// * @param {Object} protoArgs -
// * @returns {Object} redisKey if possible, e.g. 'Book-1' or 'Book-2', where 'Book' is name from mutationMap and '1' is id from protoArgs
// * and isExist if we have this key in redis
// *
// */
// // BUG: createRedisKey is an unused function -- types should be assigned if function is used
// async function createRedisKey(mutationMap, proto, protoArgs) {
// let isExist = false;
// let redisKey;
// let redisValue = null;
// for (const mutationName in proto) {
// const mutationArgs = protoArgs[mutationName];
// redisKey = mutationMap[mutationName];
// for (const key in mutationArgs) {
// let identifier = null;
// if (key === 'id' || key === '_id') {
// identifier = mutationArgs[key];
// redisKey = mutationMap[mutationName] + '-' + identifier;
// isExist = await this.checkFromRedis(redisKey);
// if (isExist) {
// redisValue = await this.getFromRedis(redisKey);
// redisValue = JSON.parse(redisValue);
// // combine redis value and protoArgs
// let argumentsValue;
// for (const mutationName in protoArgs) {
// // change later, now we assume that we have only one mutation
// argumentsValue = protoArgs[mutationName];
// }
// // updateObject is not defined anywhere
// redisValue = this.updateObject(redisValue, argumentsValue);
// }
// }
// }
// }
// return { redisKey, isExist, redisValue };
// }
// // BUG: getIdMap is an unused function -- types should be assigned if function is used
// function getIdMap() {
// const idMap = {};
// for (const type in this.fieldsMap) {
// const userDefinedIds = [];
// const fieldsAtType = this.fieldsMap[type];
// for (const key in fieldsAtType) {
// if (fieldsAtType[key] === 'ID') userDefinedIds.push(key);
// }
// idMap[type] = userDefinedIds;
// }
// return idMap;
// }
// /**
// * Toggles to false all values in a nested field not present in cache so that they will
// * be included in the reformulated query.
// * @param {Object} proto - The prototype or a nested field within the prototype
// * @returns {Object} proto - updated proto with false values for fields not present in cache
// */
// // BUG: toggleProto is an unused function -- types should be assigned if function is used
// function toggleProto(proto) {
// if (proto === undefined) return proto;
// for (const key in proto) {
// if (Object.keys(proto[key]).length > 0) this.toggleProto(proto[key]);
// else proto[key] = false;
// }
// return proto;
// }
// /**
// * checkFromRedis reads from Redis cache and returns a promise.
// * @param {String} key - the key for Redis lookup
// * @returns {Promise} A promise that represents if the key was found in the redisCache
// */
// // BUG: checkFromRedis is an unused function -- types should be assigned if function is used
// async function checkFromRedis(key: string): Promise<number> {
// try {
// // will return 0 if key does not exists
// const existsInRedis: number = await this.redisCache.exists(key);
// return existsInRedis;
// } catch (err) {
// console.log('err in checkFromRedis: ', err);
// return 0;
// }
// }
// /**
// * execRedisRunQueue executes all previously queued transactions in Redis cache
// * @param {String} redisRunQueue - Redis queue of transactions awaiting execution
// */
// // BUG: execRedisRunQueue is an unused function -- types should be assigned if function is used
// async function execRedisRunQueue(
// redisRunQueue: ReturnType<typeof this.redisCache.multi>
// ): Promise<void> {
// try {
// await redisRunQueue.exec();
// } catch (err) {
// console.log('err in execRedisRunQueue: ', err);
// }
// }