graphile-build
Version:
Build a GraphQL schema from plugins
741 lines (723 loc) • 34.1 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = makeNewBuild;
var graphql = _interopRequireWildcard(require("graphql"));
var _graphqlParseResolveInfo = require("graphql-parse-resolve-info");
var _debug = _interopRequireDefault(require("debug"));
var _pluralize = _interopRequireDefault(require("pluralize"));
var _lru = _interopRequireDefault(require("@graphile/lru"));
var _semver = _interopRequireDefault(require("semver"));
var _utils = require("./utils");
var _swallowError = _interopRequireDefault(require("./swallowError"));
var _resolveNode = _interopRequireDefault(require("./resolveNode"));
var _Live = require("./Live");
var _extend = _interopRequireWildcard(require("./extend"));
var _chalk = _interopRequireDefault(require("chalk"));
var _crypto = require("crypto");
var _package = require("../package.json");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
let recurseDataGeneratorsForFieldWarned = false;
const isString = str => typeof str === "string";
const isDev = ["test", "development"].indexOf(process.env.NODE_ENV) >= 0;
const debug = (0, _debug.default)("graphile-build");
/*
* This should be more than enough for normal usage. If you come under a
* sophisticated attack then the attacker can empty this of useful values (with
* a lot of work) but because we use SHA1 hashes under the covers the aliases
* will still be consistent even after the LRU cache is exhausted. And SHA1 can
* produce half a million hashes per second on my machine, the LRU only gives
* us a 10x speedup!
*/
const hashCache = new _lru.default({
maxLength: 100000
});
/*
* This function must never return a string longer than 56 characters.
*
* This function must only output alphanumeric and underscore characters.
*
* Collisions in SHA1 aren't problematic here (for us; they will be problematic
* for the user deliberately causing them, but that's their own fault!), so
* we'll happily take the performance boost over SHA256.
*/
function hashFieldAlias(str) {
const precomputed = hashCache.get(str);
if (precomputed) return precomputed;
const hash = (0, _crypto.createHash)("sha1").update(str).digest("hex");
hashCache.set(str, hash);
return hash;
}
/*
* This function may be replaced at any time, but all versions of it will
* always return a representation of `alias` (a valid GraphQL identifier)
* that:
*
* 1. won't conflict with normal GraphQL field names
* 2. won't be over 60 characters long (allows for systems with alias length limits, such as PG)
* 3. will give the same value when called multiple times within the same GraphQL query
* 4. matches the regex /^[@!-_A-Za-z0-9]+$/
* 5. will not be prefixed with `__` (as that will conflict with other Graphile internals)
*
* It does not guarantee that this alias will be human readable!
*/
function getSafeAliasFromAlias(alias) {
if (alias.length <= 60 && !alias.startsWith("@")) {
// Use the `@` to prevent conflicting with normal GraphQL field names, but otherwise let it through verbatim.
return `@${alias}`;
} else if (alias.length > 1024) {
throw new Error(`GraphQL alias '${alias}' is too long, use shorter aliases (max length 1024).`);
} else {
return `@@${hashFieldAlias(alias)}`;
}
}
/*
* This provides a "safe" version of the alias from ResolveInfo, guaranteed to
* never be longer than 60 characters. This makes it suitable as a PostgreSQL
* identifier.
*/
function getSafeAliasFromResolveInfo(resolveInfo) {
const alias = (0, _graphqlParseResolveInfo.getAliasFromResolveInfo)(resolveInfo);
return getSafeAliasFromAlias(alias);
}
function getNameFromType(Type) {
if (Type instanceof GraphQLSchema) {
return "schema";
} else {
return Type.name;
}
}
const {
GraphQLSchema,
GraphQLInterfaceType,
GraphQLObjectType,
GraphQLInputObjectType,
GraphQLEnumType,
GraphQLUnionType,
getNamedType,
isCompositeType,
isAbstractType
} = graphql;
const mergeData = (data, gen, ReturnType, arg) => {
const results = ensureArray(gen(arg, ReturnType, data));
if (!results) {
return;
}
for (let resultIndex = 0, resultCount = results.length; resultIndex < resultCount; resultIndex++) {
const result = results[resultIndex];
const keys = Object.keys(result);
for (let i = 0, l = keys.length; i < l; i++) {
const k = keys[i];
data[k] = data[k] || [];
const value = result[k];
const newData = ensureArray(value);
if (newData) {
data[k].push(...newData);
}
}
}
};
const knownTypes = [GraphQLSchema, GraphQLObjectType, GraphQLInputObjectType, GraphQLEnumType, GraphQLUnionType];
const knownTypeNames = knownTypes.map(k => k.name);
function ensureArray(val) {
if (val == null) {
return;
} else if (Array.isArray(val)) {
// $FlowFixMe
return val;
} else {
return [val];
}
}
// eslint-disable-next-line no-unused-vars
let ensureName = _fn => {};
if (["development", "test"].indexOf(process.env.NODE_ENV) >= 0) {
ensureName = fn => {
// $FlowFixMe: displayName
if (isDev && !fn.displayName && !fn.name && debug.enabled) {
// eslint-disable-next-line no-console
console.trace("WARNING: you've added a function with no name as an argDataGenerator, doing so may make debugging more challenging");
}
};
}
function makeNewBuild(builder) {
const allTypes = {
Int: graphql.GraphQLInt,
Float: graphql.GraphQLFloat,
String: graphql.GraphQLString,
Boolean: graphql.GraphQLBoolean,
ID: graphql.GraphQLID
};
const allTypesSources = {
Int: "GraphQL Built-in",
Float: "GraphQL Built-in",
String: "GraphQL Built-in",
Boolean: "GraphQL Built-in",
ID: "GraphQL Built-in"
};
// Every object type gets fieldData associated with each of its
// fields.
// When a field is defined, it may add to this field data.
// When something resolves referencing this type, the resolver may
// request the fieldData, e.g. to perform optimisations.
// fieldData is an object whose keys are the fields on this
// GraphQLObjectType and whose values are an object (whose keys are
// arbitrary namespaced keys and whose values are arrays of
// information of this kind)
const fieldDataGeneratorsByFieldNameByType = new Map();
const fieldArgDataGeneratorsByFieldNameByType = new Map();
return {
options: builder.options,
graphileBuildVersion: _package.version,
versions: {
graphql: require("graphql/package.json").version,
"graphile-build": _package.version
},
hasVersion(packageName, range, options = {
includePrerelease: true
}) {
const packageVersion = this.versions[packageName];
if (!packageVersion) return false;
return _semver.default.satisfies(packageVersion, range, options);
},
graphql,
parseResolveInfo: _graphqlParseResolveInfo.parseResolveInfo,
simplifyParsedResolveInfoFragmentWithType: _graphqlParseResolveInfo.simplifyParsedResolveInfoFragmentWithType,
getSafeAliasFromAlias,
getAliasFromResolveInfo: getSafeAliasFromResolveInfo,
// DEPRECATED: do not use this!
getSafeAliasFromResolveInfo,
resolveAlias(data, _args, _context, resolveInfo) {
const alias = getSafeAliasFromResolveInfo(resolveInfo);
return data[alias];
},
addType(type, origin) {
if (!type.name) {
throw new Error(`addType must only be called with named types, try using require('graphql').getNamedType`);
}
const newTypeSource = origin || (
// 'this' is typically only available after the build is finalized
this ? `'addType' call during hook '${this.status.currentHookName}'` : null);
if (allTypes[type.name]) {
if (allTypes[type.name] !== type) {
const oldTypeSource = allTypesSources[type.name];
const firstEntityDetails = !oldTypeSource ? "The first type was registered from an unknown origin." : `The first entity was:\n\n${(0, _extend.indent)(_chalk.default.magenta(oldTypeSource))}`;
const secondEntityDetails = !newTypeSource ? "The second type was registered from an unknown origin." : `The second entity was:\n\n${(0, _extend.indent)(_chalk.default.yellow(newTypeSource))}`;
throw new Error(`A type naming conflict has occurred - two entities have tried to define the same type '${_chalk.default.bold(type.name)}'.\n\n${(0, _extend.indent)(firstEntityDetails)}\n\n${(0, _extend.indent)(secondEntityDetails)}`);
}
} else {
allTypes[type.name] = type;
allTypesSources[type.name] = newTypeSource;
}
},
getTypeByName(typeName) {
return allTypes[typeName];
},
extend: _extend.default,
newWithHooks(Type, spec, inScope, performNonEmptyFieldsCheck = false) {
const scope = inScope || {};
if (!inScope) {
// eslint-disable-next-line no-console
console.warn(`No scope was provided to new ${Type.name}[name=${spec.name}], it's highly recommended that you add a scope so other hooks can easily reference your object - please check usage of 'newWithHooks'. To mute this message, just pass an empty object.`);
}
if (!Type) {
throw new Error("No type specified!");
}
if (!this.newWithHooks) {
throw new Error("Please do not generate the schema during the build building phase, use 'init' instead");
}
const fieldDataGeneratorsByFieldName = {};
const fieldArgDataGeneratorsByFieldName = {};
let newSpec = spec;
if (knownTypes.indexOf(Type) === -1 && knownTypeNames.indexOf(Type.name) >= 0) {
throw new Error(`GraphQL conflict for '${Type.name}' detected! Multiple versions of graphql exist in your node_modules?`);
}
if (Type === GraphQLSchema) {
newSpec = builder.applyHooks(this, "GraphQLSchema", newSpec, {
type: "GraphQLSchema",
scope
});
} else if (Type === GraphQLObjectType) {
const addDataGeneratorForField = (fieldName, fn) => {
// $FlowFixMe: displayName
fn.displayName =
// $FlowFixMe: displayName
fn.displayName || `${getNameFromType(Self)}:${fieldName}[${fn.name || "anonymous"}]`;
fieldDataGeneratorsByFieldName[fieldName] = fieldDataGeneratorsByFieldName[fieldName] || [];
fieldDataGeneratorsByFieldName[fieldName].push(fn);
};
const recurseDataGeneratorsForField = (fieldName, iKnowWhatIAmDoing) => {
/*
* Recursing data generators is not safe in general; however there
* are certain exceptions - for example when you know there are no
* "dynamic" data generator fields - e.g. where the GraphQL alias is
* not used at all. In PostGraphile the only case of this is the
* PageInfo object as none of the fields accept arguments, and they
* do not rely on the GraphQL query alias to store the result.
*/
if (!iKnowWhatIAmDoing && !recurseDataGeneratorsForFieldWarned) {
recurseDataGeneratorsForFieldWarned = true;
// eslint-disable-next-line no-console
console.error("Use of `recurseDataGeneratorsForField` is NOT SAFE. e.g. `{n1: node { a: field1 }, n2: node { a: field2 } }` cannot resolve correctly.");
}
const fn = (parsedResolveInfoFragment, ReturnType, ...rest) => {
const {
args
} = parsedResolveInfoFragment;
const {
fields
} = this.simplifyParsedResolveInfoFragmentWithType(parsedResolveInfoFragment, ReturnType);
const results = [];
const StrippedType = getNamedType(ReturnType);
const fieldDataGeneratorsByFieldName = fieldDataGeneratorsByFieldNameByType.get(StrippedType);
const argDataGeneratorsForSelfByFieldName = fieldArgDataGeneratorsByFieldNameByType.get(Self);
if (argDataGeneratorsForSelfByFieldName) {
const argDataGenerators = argDataGeneratorsForSelfByFieldName[fieldName];
for (let genIndex = 0, genCount = argDataGenerators.length; genIndex < genCount; genIndex++) {
const gen = argDataGenerators[genIndex];
const local = ensureArray(gen(args, ReturnType, ...rest));
if (local) {
results.push(...local);
}
}
}
if (fieldDataGeneratorsByFieldName && isCompositeType(StrippedType) && !isAbstractType(StrippedType)) {
const typeFields = StrippedType.getFields();
const keys = Object.keys(fields);
for (let keyIndex = 0, keyCount = keys.length; keyIndex < keyCount; keyIndex++) {
const alias = keys[keyIndex];
const field = fields[alias];
// Run generators with `field` as the `parsedResolveInfoFragment`, pushing results to `results`
const gens = fieldDataGeneratorsByFieldName[field.name];
if (gens) {
for (let genIndex = 0, genCount = gens.length; genIndex < genCount; genIndex++) {
const gen = gens[genIndex];
const local = ensureArray(gen(field, typeFields[field.name].type, ...rest));
if (local) {
results.push(...local);
}
}
}
}
}
return results;
};
fn.displayName = `recurseDataGeneratorsForField(${getNameFromType(Self)}:${fieldName})`;
addDataGeneratorForField(fieldName, fn);
// get type from field, get
};
const commonContext = {
type: "GraphQLObjectType",
scope
};
newSpec = builder.applyHooks(this, "GraphQLObjectType", newSpec, {
...commonContext,
addDataGeneratorForField,
recurseDataGeneratorsForField
}, `|${newSpec.name}`);
const rawSpec = newSpec;
newSpec = {
...newSpec,
interfaces: () => {
const interfacesContext = {
...commonContext,
Self,
GraphQLObjectType: rawSpec
};
let rawInterfaces = rawSpec.interfaces || [];
if (typeof rawInterfaces === "function") {
rawInterfaces = rawInterfaces(interfacesContext);
}
return builder.applyHooks(this, "GraphQLObjectType:interfaces", rawInterfaces, interfacesContext, `|${getNameFromType(Self)}`);
},
fields: () => {
const processedFields = [];
const fieldsContext = {
...commonContext,
addDataGeneratorForField,
recurseDataGeneratorsForField,
Self,
GraphQLObjectType: rawSpec,
fieldWithHooks: (fieldName, spec, fieldScope) => {
if (!isString(fieldName)) {
throw new Error("It looks like you forgot to pass the fieldName to `fieldWithHooks`, we're sorry this is currently necessary.");
}
if (!fieldScope) {
throw new Error("All calls to `fieldWithHooks` must specify a `fieldScope` " + "argument that gives additional context about the field so " + "that further plugins may more easily understand the field. " + "Keys within this object should contain the phrase 'field' " + "since they will be merged into the parent objects scope and " + "are not allowed to clash. If you really have no additional " + "information to give, please just pass `{}`.");
}
const argDataGenerators = [];
fieldArgDataGeneratorsByFieldName[fieldName] = argDataGenerators;
let newSpec = spec;
const context = {
...commonContext,
Self,
addDataGenerator(fn) {
return addDataGeneratorForField(fieldName, fn);
},
addArgDataGenerator(fn) {
ensureName(fn);
argDataGenerators.push(fn);
},
getDataFromParsedResolveInfoFragment: (parsedResolveInfoFragment, ReturnType) => {
const Type = getNamedType(ReturnType);
const data = {};
const {
fields,
args
} = this.simplifyParsedResolveInfoFragmentWithType(parsedResolveInfoFragment, ReturnType);
// Args -> argDataGenerators
for (let dgIndex = 0, dgCount = argDataGenerators.length; dgIndex < dgCount; dgIndex++) {
const gen = argDataGenerators[dgIndex];
try {
mergeData(data, gen, ReturnType, args);
} catch (e) {
debug("Failed to execute argDataGenerator '%s' on %s of %s", gen.displayName || gen.name || "anonymous", fieldName, getNameFromType(Self));
throw e;
}
}
// finalSpec.type -> fieldData
if (!finalSpec) {
throw new Error("It's too early to call this! Call from within resolve");
}
const fieldDataGeneratorsByFieldName = fieldDataGeneratorsByFieldNameByType.get(Type);
if (fieldDataGeneratorsByFieldName && isCompositeType(Type) && !isAbstractType(Type)) {
const typeFields = Type.getFields();
const keys = Object.keys(fields);
for (let keyIndex = 0, keyCount = keys.length; keyIndex < keyCount; keyIndex++) {
const alias = keys[keyIndex];
const field = fields[alias];
const gens = fieldDataGeneratorsByFieldName[field.name];
if (gens) {
const FieldReturnType = typeFields[field.name].type;
for (let i = 0, l = gens.length; i < l; i++) {
mergeData(data, gens[i], FieldReturnType, field);
}
}
}
}
return data;
},
scope: (0, _extend.default)((0, _extend.default)({
...scope
}, {
fieldName
}, `Within context for GraphQLObjectType '${rawSpec.name}'`), fieldScope, `Extending scope for field '${fieldName}' within context for GraphQLObjectType '${rawSpec.name}'`)
};
if (typeof newSpec === "function") {
newSpec = newSpec(context);
}
newSpec = builder.applyHooks(this, "GraphQLObjectType:fields:field", newSpec, context, `|${getNameFromType(Self)}.fields.${fieldName}`);
newSpec.args = newSpec.args || {};
newSpec = {
...newSpec,
args: builder.applyHooks(this, "GraphQLObjectType:fields:field:args", newSpec.args, {
...context,
field: newSpec,
returnType: newSpec.type
}, `|${getNameFromType(Self)}.fields.${fieldName}`)
};
const finalSpec = newSpec;
processedFields.push(finalSpec);
return finalSpec;
}
};
let rawFields = rawSpec.fields || {};
if (typeof rawFields === "function") {
rawFields = rawFields(fieldsContext);
}
const fieldsSpec = builder.applyHooks(this, "GraphQLObjectType:fields", this.extend({}, rawFields, `Default field included in newWithHooks call for '${rawSpec.name}'. ${inScope.__origin || ""}`), fieldsContext, `|${rawSpec.name}`);
// Finally, check through all the fields that they've all been processed; any that have not we should do so now.
for (const fieldName in fieldsSpec) {
const fieldSpec = fieldsSpec[fieldName];
if (processedFields.indexOf(fieldSpec) < 0) {
// We've not processed this yet; process it now!
fieldsSpec[fieldName] = fieldsContext.fieldWithHooks(fieldName, fieldSpec, {
autoField: true // We don't have any additional information
});
}
}
return fieldsSpec;
}
};
} else if (Type === GraphQLInputObjectType) {
const commonContext = {
type: "GraphQLInputObjectType",
scope
};
newSpec = builder.applyHooks(this, "GraphQLInputObjectType", newSpec, commonContext, `|${newSpec.name}`);
newSpec.fields = newSpec.fields || {};
const rawSpec = newSpec;
newSpec = {
...newSpec,
fields: () => {
const processedFields = [];
const fieldsContext = {
...commonContext,
Self,
GraphQLInputObjectType: rawSpec,
fieldWithHooks: (fieldName, spec, fieldScope = {}) => {
if (!isString(fieldName)) {
throw new Error("It looks like you forgot to pass the fieldName to `fieldWithHooks`, we're sorry this is currently necessary.");
}
const context = {
...commonContext,
Self,
scope: (0, _extend.default)((0, _extend.default)({
...scope
}, {
fieldName
}, `Within context for GraphQLInputObjectType '${rawSpec.name}'`), fieldScope, `Extending scope for field '${fieldName}' within context for GraphQLInputObjectType '${rawSpec.name}'`)
};
let newSpec = spec;
if (typeof newSpec === "function") {
newSpec = newSpec(context);
}
newSpec = builder.applyHooks(this, "GraphQLInputObjectType:fields:field", newSpec, context, `|${getNameFromType(Self)}.fields.${fieldName}`);
const finalSpec = newSpec;
processedFields.push(finalSpec);
return finalSpec;
}
};
let rawFields = rawSpec.fields;
if (typeof rawFields === "function") {
rawFields = rawFields(fieldsContext);
}
const fieldsSpec = builder.applyHooks(this, "GraphQLInputObjectType:fields", this.extend({}, rawFields, `Default field included in newWithHooks call for '${rawSpec.name}'. ${inScope.__origin || ""}`), fieldsContext, `|${getNameFromType(Self)}`);
// Finally, check through all the fields that they've all been processed; any that have not we should do so now.
for (const fieldName in fieldsSpec) {
const fieldSpec = fieldsSpec[fieldName];
if (processedFields.indexOf(fieldSpec) < 0) {
// We've not processed this yet; process it now!
fieldsSpec[fieldName] = fieldsContext.fieldWithHooks(fieldName, fieldSpec, {
autoField: true // We don't have any additional information
});
}
}
return fieldsSpec;
}
};
} else if (Type === GraphQLEnumType) {
const commonContext = {
type: "GraphQLEnumType",
scope
};
newSpec = builder.applyHooks(this, "GraphQLEnumType", newSpec, commonContext, `|${newSpec.name}`);
newSpec.values = builder.applyHooks(this, "GraphQLEnumType:values", newSpec.values, commonContext, `|${newSpec.name}`);
const values = newSpec.values;
newSpec.values = Object.keys(values).reduce((memo, valueKey) => {
const value = values[valueKey];
const newValue = builder.applyHooks(this, "GraphQLEnumType:values:value", value, commonContext, `|${newSpec.name}|${valueKey}`);
memo[valueKey] = newValue;
return memo;
}, {});
} else if (Type === GraphQLUnionType) {
const commonContext = {
type: "GraphQLUnionType",
scope
};
newSpec = builder.applyHooks(this, "GraphQLUnionType", newSpec, {
...commonContext
}, `|${newSpec.name}`);
const rawSpec = newSpec;
newSpec = {
...newSpec,
types: () => {
const typesContext = {
...commonContext,
Self,
GraphQLUnionType: rawSpec
};
let rawTypes = rawSpec.types || [];
if (typeof rawTypes === "function") {
rawTypes = rawTypes(typesContext);
}
return builder.applyHooks(this, "GraphQLUnionType:types", rawTypes, typesContext, `|${getNameFromType(Self)}`);
}
};
} else if (Type === GraphQLInterfaceType) {
const commonContext = {
type: "GraphQLInterfaceType",
scope
};
newSpec = builder.applyHooks(this, "GraphQLInterfaceType", newSpec, commonContext, `|${newSpec.name}`);
const rawSpec = newSpec;
newSpec = {
...newSpec,
fields: () => {
const processedFields = [];
const fieldsContext = {
...commonContext,
Self,
GraphQLInterfaceType: rawSpec,
fieldWithHooks: (fieldName, spec, fieldScope) => {
if (!isString(fieldName)) {
throw new Error("It looks like you forgot to pass the fieldName to `fieldWithHooks`, we're sorry this is currently necessary.");
}
if (!fieldScope) {
throw new Error("All calls to `fieldWithHooks` must specify a `fieldScope` " + "argument that gives additional context about the field so " + "that further plugins may more easily understand the field. " + "Keys within this object should contain the phrase 'field' " + "since they will be merged into the parent objects scope and " + "are not allowed to clash. If you really have no additional " + "information to give, please just pass `{}`.");
}
let newSpec = spec;
const context = {
...commonContext,
Self,
scope: (0, _extend.default)((0, _extend.default)({
...scope
}, {
fieldName
}, `Within context for GraphQLInterfaceType '${rawSpec.name}'`), fieldScope, `Extending scope for field '${fieldName}' within context for GraphQLInterfaceType '${rawSpec.name}'`)
};
if (typeof newSpec === "function") {
newSpec = newSpec(context);
}
newSpec = builder.applyHooks(this, "GraphQLInterfaceType:fields:field", newSpec, context, `|${getNameFromType(Self)}.fields.${fieldName}`);
newSpec.args = newSpec.args || {};
newSpec = {
...newSpec,
args: builder.applyHooks(this, "GraphQLInterfaceType:fields:field:args", newSpec.args, {
...context,
field: newSpec,
returnType: newSpec.type
}, `|${getNameFromType(Self)}.fields.${fieldName}`)
};
const finalSpec = newSpec;
processedFields.push(finalSpec);
return finalSpec;
}
};
let rawFields = rawSpec.fields || {};
if (typeof rawFields === "function") {
rawFields = rawFields(fieldsContext);
}
const fieldsSpec = builder.applyHooks(this, "GraphQLInterfaceType:fields", this.extend({}, rawFields, `Default field included in newWithHooks call for '${rawSpec.name}'. ${inScope.__origin || ""}`), fieldsContext, `|${rawSpec.name}`);
// Finally, check through all the fields that they've all been processed; any that have not we should do so now.
for (const fieldName in fieldsSpec) {
const fieldSpec = fieldsSpec[fieldName];
if (processedFields.indexOf(fieldSpec) < 0) {
// We've not processed this yet; process it now!
fieldsSpec[fieldName] = fieldsContext.fieldWithHooks(fieldName, fieldSpec, {
autoField: true // We don't have any additional information
});
}
}
return fieldsSpec;
}
};
}
const finalSpec = newSpec;
const Self = new Type(finalSpec);
if (!(Self instanceof GraphQLSchema) && performNonEmptyFieldsCheck) {
try {
if (Self instanceof GraphQLInterfaceType || Self instanceof GraphQLObjectType || Self instanceof GraphQLInputObjectType) {
const _Self = Self;
if (typeof _Self.getFields === "function") {
const fields = _Self.getFields();
if (Object.keys(fields).length === 0) {
// We require there's at least one field on GraphQLObjectType and GraphQLInputObjectType records
return null;
}
}
}
} catch (e) {
// This is the error we're expecting to handle:
// https://github.com/graphql/graphql-js/blob/831598ba76f015078ecb6c5c1fbaf133302f3f8e/src/type/definition.js#L526-L531
if (inScope && inScope.isRootQuery) {
throw e;
}
const isProbablyAnEmptyObjectError = !!e.message.match(/function which returns such an object/);
if (!isProbablyAnEmptyObjectError) {
this.swallowError(e);
}
return null;
}
}
this.scopeByType.set(Self, scope);
if (finalSpec.name) {
this.addType(Self, scope.__origin || (this ? `'newWithHooks' call during hook '${this.status.currentHookName}'` : null));
}
fieldDataGeneratorsByFieldNameByType.set(Self, fieldDataGeneratorsByFieldName);
fieldArgDataGeneratorsByFieldNameByType.set(Self, fieldArgDataGeneratorsByFieldName);
return Self;
},
fieldDataGeneratorsByType: fieldDataGeneratorsByFieldNameByType,
// @deprecated
fieldDataGeneratorsByFieldNameByType,
fieldArgDataGeneratorsByFieldNameByType,
inflection: {
pluralize: _pluralize.default,
singularize: _pluralize.default.singular,
upperCamelCase: _utils.upperCamelCase,
camelCase: _utils.camelCase,
constantCase: _utils.constantCase,
// Built-in names (allows you to override these in the output schema)
builtin: name => {
/*
* e.g.:
*
* graphile-build:
*
* - Query
* - Mutation
* - Subscription
* - Node
* - PageInfo
*
* graphile-build-pg:
*
* - Interval
* - BigInt
* - BigFloat
* - BitString
* - Point
* - Date
* - Datetime
* - Time
* - JSON
* - UUID
* - InternetAddress
*
* Other plugins may add their own builtins too; try and avoid conflicts!
*/
return name;
},
// When converting a query field to a subscription (live query) field, this allows you to rename it
live: name => name,
// Try and make something a valid GraphQL 'Name'
coerceToGraphQLName: name => {
let resultingName = name;
/*
* Name is defined in GraphQL to match this regexp:
*
* /^[_A-Za-z][_0-9A-Za-z]*$/
*
* See: https://graphql.github.io/graphql-spec/June2018/#sec-Appendix-Grammar-Summary.Lexical-Tokens
*
* So if our 'name' starts with a digit, we must prefix it with
* something. We'll just use an underscore.
*/
if (resultingName.match(/^[0-9]/)) {
resultingName = "_" + resultingName;
}
/*
* Fields beginning with two underscores are reserved by the GraphQL
* introspection systems, trim to just one.
*/
resultingName = resultingName.replace(/^__+/g, "_");
return resultingName;
}
},
wrapDescription: _utils.wrapDescription,
swallowError: _swallowError.default,
// resolveNode: EXPERIMENTAL, API might change!
resolveNode: _resolveNode.default,
status: {
currentHookName: null,
currentHookEvent: null
},
liveCoordinator: new _Live.LiveCoordinator(),
scopeByType: new Map()
};
}
//# sourceMappingURL=makeNewBuild.js.map