UNPKG

graphile-build

Version:

Build a GraphQL schema from plugins

270 lines 11.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.intTypeSpec = exports.stringTypeSpec = exports.wrapDescription = exports.singularize = exports.pluralize = exports.upperCamelCase = exports.constantCase = exports.camelCase = exports.upperFirst = exports.formatInsideUnderscores = exports.constantCaseAll = exports.bindAll = void 0; exports.EXPORTABLE = EXPORTABLE; exports.isForbidden = isForbidden; exports.EXPORTABLE_OBJECT_CLONE = EXPORTABLE_OBJECT_CLONE; exports.EXPORTABLE_ARRAY_CLONE = EXPORTABLE_ARRAY_CLONE; exports.exportNameHint = exportNameHint; exports.isValidObjectType = isValidObjectType; exports.gatherConfig = gatherConfig; const tslib_1 = require("tslib"); const grafast_1 = require("grafast"); const graphql_1 = require("grafast/graphql"); // ENHANCE: remove 'lodash' dependency const camelCase_js_1 = tslib_1.__importDefault(require("lodash/camelCase.js")); const upperFirst_js_1 = tslib_1.__importDefault(require("lodash/upperFirst.js")); const pluralize_1 = tslib_1.__importDefault(require("pluralize")); const tamedevil_1 = tslib_1.__importDefault(require("tamedevil")); function EXPORTABLE(factory, args, nameHint) { const forbiddenIndex = args.findIndex(isForbidden); if (forbiddenIndex >= 0) { throw new Error(`${nameHint ?? "Anonymous"} EXPORTABLE call's args[${forbiddenIndex}] is not allowed to be exported.`); } const fn = factory(...args); if (((typeof fn === "object" && fn !== null) || typeof fn === "function") && !("$exporter$factory" in fn)) { Object.defineProperties(fn, { $exporter$args: { value: args }, $exporter$factory: { value: factory }, $exporter$name: { writable: true, value: nameHint }, }); } return fn; } function isForbidden(thing) { return ((typeof thing === "object" || typeof thing === "function") && thing !== null && "$$export" in thing && thing.$$export === false); } function EXPORTABLE_OBJECT_CLONE(obj, name) { if (obj.$exporter$factory) { return obj; } if (Object.getPrototypeOf(obj) === Object.prototype) { const keys = Object.keys(obj); const values = Object.values(obj); const fn = tamedevil_1.default.eval `return (${tamedevil_1.default.join(keys.map((_key, i) => tamedevil_1.default.identifier(`key${i}`)), ", ")}) => ({${tamedevil_1.default.indent(tamedevil_1.default.join(keys.map((key, i) => (0, tamedevil_1.default) `${tamedevil_1.default.safeKeyOrThrow(key)}: ${tamedevil_1.default.identifier(`key${i}`)}`), ",\n"))}});`; // eslint-disable-next-line graphile-export/exhaustive-deps return EXPORTABLE(fn, values, name); } else { throw new Error("EXPORTABLE_OBJECT_CLONE can currently only be used with POJOs."); } } function EXPORTABLE_ARRAY_CLONE(arr, name) { if (arr.$exporter$factory) { return arr; } const fn = tamedevil_1.default.eval `return (${tamedevil_1.default.join(arr.map((_, i) => tamedevil_1.default.identifier(`val${i}`)), ", ")}) => [${tamedevil_1.default.indent(tamedevil_1.default.join(arr.map((_, i) => tamedevil_1.default.identifier(`val${i}`)), ",\n"))}];`; // eslint-disable-next-line graphile-export/exhaustive-deps return EXPORTABLE(fn, arr, name); } function exportNameHint(obj, nameHint) { if ((typeof obj === "object" && obj != null) || typeof obj === "function") { if (!("$exporter$name" in obj)) { Object.defineProperty(obj, "$exporter$name", { writable: true, value: nameHint, }); } else if (!obj.$exporter$name) { obj.$exporter$name = nameHint; } } } /** * Loops over all the given `keys` and binds the method of that name on `obj` * to `obj` so that destructuring `build`/etc won't relate in broken `this` * references. */ const bindAll = (obj, keys) => { keys.forEach((key) => { if (typeof obj[key] === "function" && !("$$export" in obj[key]) && !("$exporter$factory" in obj[key])) { // The Object.assign is to copy across any function properties obj[key] = Object.assign(obj[key].bind(obj), obj[key]); } }); return obj; }; exports.bindAll = bindAll; const constantCaseAll = (str) => str .replace(/[^a-zA-Z0-9_]+/g, "_") .replace(/[A-Z]+/g, "_$&") .replace(/__+/g, "_") .replace(/^[^a-zA-Z0-9]+/, "") .replace(/^[0-9]/, "_$&") // GraphQL enums must not start with a number .toUpperCase(); exports.constantCaseAll = constantCaseAll; /** * Applies the given format function `fn` to a string, but maintains any * leading/trailing underscores. */ const formatInsideUnderscores = (fn) => (str) => { // Guaranteed to match all strings, and to contain 3 capture groups. const matches = str.match(/^(_*)([\s\S]*?)(_*)$/); const [, start, middle, end] = matches; return `${start}${fn(middle)}${end}`; }; exports.formatInsideUnderscores = formatInsideUnderscores; exports.upperFirst = (0, exports.formatInsideUnderscores)(upperFirst_js_1.default); exports.camelCase = (0, exports.formatInsideUnderscores)(camelCase_js_1.default); exports.constantCase = (0, exports.formatInsideUnderscores)(exports.constantCaseAll); const upperCamelCase = (str) => (0, exports.upperFirst)((0, exports.camelCase)(str)); exports.upperCamelCase = upperCamelCase; const pluralize = (str) => (0, pluralize_1.default)(str); exports.pluralize = pluralize; const singularize = (str) => pluralize_1.default.singular(str); exports.singularize = singularize; /** * Returns true if the given type is a GraphQL object type AND that object type * defines fields; false otherwise. * * WARNING: this function may throw if there's issues with the type's fields, * since it calls Type.getFields() */ function isValidObjectType(Type) { return (Type instanceof graphql_1.GraphQLObjectType && Object.keys(Type.getFields()).length > 0); } function toString(value) { return "" + value; } exportNameHint(toString, "toString"); // Copied from GraphQL v14, MIT license (c) GraphQL Contributors. /** * Word-wraps the given text to maxLen; for consistency with older GraphQL * schemas. */ function breakLine(line, maxLen) { const parts = line.split(new RegExp(`((?: |^).{15,${maxLen - 40}}(?= |$))`)); if (parts.length < 4) { return [line]; } const sublines = [parts[0] + parts[1] + parts[2]]; for (let i = 3; i < parts.length; i += 2) { sublines.push(parts[i].slice(1) + parts[i + 1]); } return sublines; } /** * Only use this on descriptions that are plain text, or that we create * manually in code; since descriptions are markdown, it's not safe to use on * descriptions that contain code blocks or long inline code strings. */ const wrapDescription = (description, position) => { if (description == null) { return null; } const indentationLength = position === "root" ? 0 : position === "type" ? 0 : position === "field" ? 2 : position === "arg" ? 4 : 0; // This follows the implementation from GraphQL v14 to make our GraphQL v15 // schema more similar. Ref: // https://github.com/graphql/graphql-js/pull/2223/files const maxLen = 120 - indentationLength; return description .split("\n") .map((line) => { if (line.length < maxLen + 5) { return line; } // For > 120 character long lines, cut at space boundaries into sublines // of ~80 chars. return breakLine(line, maxLen).join("\n"); }) .join("\n"); }; exports.wrapDescription = wrapDescription; /** * Generates the spec for a GraphQLScalar (except the name) with the * given description/coercion. */ const stringTypeSpec = (description, coerce, name) => ({ description, serialize: toString, parseValue: coerce ? EXPORTABLE((coerce) => (value) => coerce("" + value), [coerce]) : toString, parseLiteral: coerce ? EXPORTABLE((GraphQLError, Kind, coerce, name) => (ast) => { if (ast.kind === Kind.STRING) { return coerce(ast.value); } throw new GraphQLError(`${name ?? "This scalar"} can only parse string values (kind = '${ast.kind}')`); }, [graphql_1.GraphQLError, graphql_1.Kind, coerce, name]) : EXPORTABLE((GraphQLError, Kind, name) => (ast) => { if (ast.kind === Kind.STRING) { return ast.value; } throw new GraphQLError(`${name ?? "This scalar"} can only parse string values (kind='${ast.kind}')`); }, [graphql_1.GraphQLError, graphql_1.Kind, name]), extensions: { grafast: { idempotent: !coerce || coerce[grafast_1.$$idempotent] ? true : false, }, }, }); exports.stringTypeSpec = stringTypeSpec; const intMax = 2 ** 31 - 1; const intMin = -(2 ** 31); const toInt = EXPORTABLE((intMax, intMin) => function toInt(expr) { const int = typeof expr === "number" ? Math.floor(expr) : parseInt("" + expr, 10); if (isNaN(int) || int > intMax || int < intMin) { throw new Error("Invalid integer"); } return int; }, [intMax, intMin], "toInt"); /** * Generates the spec for a GraphQLScalar (except the name) with the * given description/coercion. */ const intTypeSpec = (description, coerce, name) => ({ description, serialize: toInt, parseValue: coerce ? EXPORTABLE((coerce, toInt) => (value) => toInt(typeof value === "number" ? coerce(value) : coerce("" + value)), [coerce, toInt]) : toInt, parseLiteral: coerce ? EXPORTABLE((GraphQLError, Kind, coerce, name, toInt) => (ast) => { if (ast.kind === Kind.STRING) { return toInt(coerce(ast.value)); } else if (ast.kind === Kind.INT) { return toInt(coerce(ast.value)); } throw new GraphQLError(`${name ?? "This scalar"} can only parse string/integer values (kind = '${ast.kind}')`); }, [graphql_1.GraphQLError, graphql_1.Kind, coerce, name, toInt]) : EXPORTABLE((GraphQLError, Kind, name, toInt) => (ast) => { if (ast.kind === Kind.STRING) { return toInt(ast.value); } else if (ast.kind === Kind.INT) { return toInt(ast.value); } throw new GraphQLError(`${name ?? "This scalar"} can only parse string/integer values (kind='${ast.kind}')`); }, [graphql_1.GraphQLError, graphql_1.Kind, name, toInt]), extensions: { grafast: { idempotent: !coerce || coerce[grafast_1.$$idempotent] ? true : false, }, }, }); exports.intTypeSpec = intTypeSpec; /** * This is a TypeScript constrained identity function to save having to specify * all the generics manually. */ function gatherConfig(config) { return config; } //# sourceMappingURL=utils.js.map