@adpt/core
Version:
AdaptJS core library
177 lines • 7.17 kB
JavaScript
;
/*
* Copyright 2018-2019 Unbounded Systems, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const graphql_1 = require("graphql");
const ld = tslib_1.__importStar(require("lodash"));
const error_1 = require("../error");
function notUndefined(x) {
return x !== undefined;
}
function getUnwrappedType(type) {
while (graphql_1.isWrappingType(type))
type = type.ofType;
return type;
}
function buildSelectionSet(type, orig, allDepth = 1) {
if (allDepth === 0)
return orig;
type = getUnwrappedType(type);
if (!graphql_1.isObjectType(type))
return orig; //FIXME(manishv) handle interfaces and fragments spreads
const origNames = orig
? orig.selections.map((f) => {
if (f.kind !== graphql_1.Kind.FIELD)
return undefined; //FIXME(manishv) Need to look into fragment refs here
return f.alias ? f.alias.value : f.name.value;
}).filter((x) => x !== undefined)
: [];
const origSet = new Set(origNames);
const fields = type.getFields();
const newSelectionNamesLessOrig = Object.keys(fields).filter((n) => !origSet.has(n));
const newSelectionsLessOrig = newSelectionNamesLessOrig.map((name) => {
const field = fields[name];
if (!needsNoArgs(field))
return undefined;
const selectionSet = buildSelectionSet(field.type, undefined, allDepth - 1);
return {
kind: graphql_1.Kind.FIELD,
name: { kind: graphql_1.Kind.NAME, value: name },
selectionSet
};
}).filter(notUndefined);
const finalSelections = orig ? orig.selections.concat(newSelectionsLessOrig) : newSelectionsLessOrig;
return {
kind: graphql_1.Kind.SELECTION_SET,
loc: orig ? orig.loc : undefined,
selections: finalSelections
};
}
function needsNoArgs(f) {
if (!f.args)
return true;
if (f.args.length === 0)
return true;
const noDefaultArgs = f.args.filter((arg) => arg.defaultValue === undefined);
if (noDefaultArgs.length === 0)
return true;
const nonNullArgs = f.args.filter((arg) => graphql_1.isNonNullType(arg.type));
return nonNullArgs.length === 0;
}
function findAllDepth(n) {
const dirs = n.directives;
if (dirs === undefined)
return 0;
const alls = dirs.filter((d) => d.name.value === "all");
if (alls.length === 0)
return 0;
const depths = alls.map((all) => {
const args = all.arguments;
if (!args || args.length === 0)
return 1;
const depthArgs = args.filter((a) => a.name.value === "depth");
const depthValues = depthArgs.map((da) => {
const valNode = da.value;
if (valNode.kind !== graphql_1.Kind.INT)
throw new Error("@all has a non-integer depth argument");
const ret = Number(valNode.value);
if (isNaN(ret))
throw new Error("@all has a non-integer depth argument");
if (ret < 0)
throw new Error("@all has depth < 0");
return ret;
});
return Math.max(0, ...depthValues);
});
return Math.max(0, ...depths);
}
class AllDirectiveVisitor {
constructor(schema) {
this.schema = schema;
this.leave = {
OperationDefinition: (op) => this.processFieldOrOpNode(op),
Field: (f) => this.processFieldOrOpNode(f)
};
this.enter = {
OperationDefinition: (op) => {
const allDepth = findAllDepth(op);
switch (op.operation) {
case "query":
this.infoStack = [{ type: this.schema.getQueryType() || null, allDepth }];
break;
case "mutation":
this.infoStack = [{ type: this.schema.getMutationType() || null, allDepth }];
break;
case "subscription":
this.infoStack = [{ type: this.schema.getSubscriptionType() || null, allDepth }];
}
},
Field: (f) => {
const fieldName = f.name.value;
const info = ld.last(this.infoStack);
if (info === undefined)
throw new error_1.InternalError(`no info for field: ${fieldName}`);
const type = info.type;
if (type === undefined)
throw new error_1.InternalError(`no type for field: ${fieldName}`);
if (!graphql_1.isObjectType(type))
return; //FIXME(manishv) Fix fragment spreads and interfaces here
const allDepth = Math.max(findAllDepth(f), (info.allDepth - 1));
if (type === null) {
this.infoStack.push({ type: null, allDepth });
return;
}
const fields = type.getFields();
const field = fields[fieldName];
if (field === undefined) {
this.infoStack.push({ type: null, allDepth });
return;
}
this.infoStack.push({ type: getUnwrappedType(field.type), allDepth });
}
};
this.processFieldOrOpNode = (n) => {
const info = this.infoStack.pop();
if (!info)
return n;
const type = info.type;
if (!graphql_1.isObjectType(type))
return n;
if (info.allDepth === 0)
return n;
const origSel = n.selectionSet;
const sel = buildSelectionSet(type, origSel, info.allDepth);
if (n.kind === graphql_1.Kind.OPERATION_DEFINITION) {
if (sel === undefined) {
throw new Error("Cannot have empty selection set at top-level operation: " + n.operation);
}
return Object.assign({}, n, { selectionSet: sel });
}
return Object.assign({}, n, { selectionSet: sel });
};
}
}
function applyAdaptTransforms(schema, q) {
return graphql_1.visit(q, new AllDirectiveVisitor(schema));
}
exports.applyAdaptTransforms = applyAdaptTransforms;
function adaptGqlExecute(schema, query, data, context, vars) {
const tQuery = applyAdaptTransforms(schema, query);
return graphql_1.execute(schema, tQuery, data, context, vars);
}
exports.adaptGqlExecute = adaptGqlExecute;
//# sourceMappingURL=query_transforms.js.map