UNPKG

graphile-build-pg

Version:

Build a GraphQL schema by reflection over a PostgreSQL schema. Easy to customize since it's built with plugins on graphile-build

177 lines (176 loc) 7.17 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; const nullableIf = (GraphQLNonNull, condition, Type) => condition ? Type : new GraphQLNonNull(Type); var PgColumnsPlugin = function PgColumnsPlugin(builder) { builder.hook("build", build => { const { pgSql: sql, pgTweakFragmentForTypeAndModifier, pgQueryFromResolveData: queryFromResolveData } = build; const getSelectValueForFieldAndTypeAndModifier = (ReturnType, fieldScope, parsedResolveInfoFragment, sqlFullName, type, typeModifier) => { const { getDataFromParsedResolveInfoFragment } = fieldScope; if (type.isPgArray) { const ident = sql.identifier(Symbol()); return sql.fragment`(\ case when ${sqlFullName} is null then null when coalesce(array_length(${sqlFullName}, 1), 0) = 0 then '[]'::json else ( select json_agg(${getSelectValueForFieldAndTypeAndModifier(ReturnType, fieldScope, parsedResolveInfoFragment, ident, type.arrayItemType, typeModifier)}) from unnest(${sqlFullName}) as ${ident} ) end )`; } else { const resolveData = getDataFromParsedResolveInfoFragment(parsedResolveInfoFragment, ReturnType); if (type.type === "c") { const isDefinitelyNotATable = type.class && !type.class.isSelectable; const jsonBuildObject = queryFromResolveData(sql.identifier(Symbol()), // Ignore! sqlFullName, resolveData, { onlyJsonField: true, addNullCase: !isDefinitelyNotATable, addNotDistinctFromNullCase: isDefinitelyNotATable }); return jsonBuildObject; } else { return pgTweakFragmentForTypeAndModifier(sqlFullName, type, typeModifier, resolveData); } } }; return build.extend(build, { pgGetSelectValueForFieldAndTypeAndModifier: getSelectValueForFieldAndTypeAndModifier }); }, ["PgColumns"], [], ["PgTypes"]); builder.hook("GraphQLObjectType:fields", (fields, build, context) => { const { extend, pgGetGqlTypeByTypeIdAndModifier, pgSql: sql, pg2gqlForType, graphql: { GraphQLString, GraphQLNonNull }, pgColumnFilter, inflection, pgOmit: omit, pgGetSelectValueForFieldAndTypeAndModifier: getSelectValueForFieldAndTypeAndModifier, describePgEntity, sqlCommentByAddingTags } = build; const { scope: { isPgRowType, isPgCompoundType, pgIntrospection: table }, fieldWithHooks } = context; if (!(isPgRowType || isPgCompoundType) || !table || table.kind !== "class") { return fields; } return extend(fields, table.attributes.reduce((memo, attr) => { // PERFORMANCE: These used to be .filter(...) calls if (!pgColumnFilter(attr, build, context)) return memo; if (omit(attr, "read")) return memo; const fieldName = inflection.column(attr); if (memo[fieldName]) { throw new Error(`Two columns produce the same GraphQL field name '${fieldName}' on class '${table.namespaceName}.${table.name}'; one of them is '${attr.name}'`); } memo = extend(memo, { [fieldName]: fieldWithHooks(fieldName, fieldContext => { const { type, typeModifier } = attr; const sqlColumn = sql.identifier(attr.name); const { addDataGenerator } = fieldContext; const ReturnType = pgGetGqlTypeByTypeIdAndModifier(attr.typeId, attr.typeModifier) || GraphQLString; addDataGenerator(parsedResolveInfoFragment => { return { pgQuery: queryBuilder => { queryBuilder.select(getSelectValueForFieldAndTypeAndModifier(ReturnType, fieldContext, parsedResolveInfoFragment, sql.fragment`(${queryBuilder.getTableAlias()}.${sqlColumn})`, // The brackets are necessary to stop the parser getting confused, ref: https://www.postgresql.org/docs/9.6/static/rowtypes.html#ROWTYPES-ACCESSING type, typeModifier), fieldName); } }; }); const convertFromPg = pg2gqlForType(type); return { description: attr.description, type: nullableIf(GraphQLNonNull, !attr.isNotNull && !attr.type.domainIsNotNull && !attr.tags.notNull, ReturnType), resolve: (data, _args, _context, _resolveInfo) => { return convertFromPg(data[fieldName]); } }; }, { pgFieldIntrospection: attr }) }, `Adding field for ${describePgEntity(attr)}. You can rename this field with a 'Smart Comment':\n\n ${sqlCommentByAddingTags(attr, { name: "newNameHere" })}`); return memo; }, {}), `Adding columns to '${describePgEntity(table)}'`); }, ["PgColumns"]); builder.hook("GraphQLInputObjectType:fields", (fields, build, context) => { const { extend, pgGetGqlInputTypeByTypeIdAndModifier, graphql: { GraphQLString, GraphQLNonNull }, pgColumnFilter, inflection, pgOmit: omit, describePgEntity, sqlCommentByAddingTags } = build; const { scope: { isPgRowType, isPgCompoundType, isPgPatch, isPgBaseInput, pgIntrospection: table, pgAddSubfield }, fieldWithHooks } = context; if (!(isPgRowType || isPgCompoundType) || !table || table.kind !== "class") { return fields; } return extend(fields, table.attributes.reduce((memo, attr) => { // PERFORMANCE: These used to be .filter(...) calls if (!pgColumnFilter(attr, build, context)) return memo; const action = isPgBaseInput ? "base" : isPgPatch ? "update" : "create"; if (omit(attr, action)) return memo; if (attr.identity === "a") return memo; const fieldName = inflection.column(attr); if (memo[fieldName]) { throw new Error(`Two columns produce the same GraphQL field name '${fieldName}' on input class '${table.namespaceName}.${table.name}'; one of them is '${attr.name}'`); } memo = extend(memo, { [fieldName]: fieldWithHooks(fieldName, pgAddSubfield(fieldName, attr.name, attr.type, { description: attr.description, type: nullableIf(GraphQLNonNull, isPgBaseInput || isPgPatch || !attr.isNotNull && (!attr.type.domainIsNotNull || attr.type.domainHasDefault) && !attr.tags.notNull || attr.hasDefault || attr.tags.hasDefault || attr.identity === "d", pgGetGqlInputTypeByTypeIdAndModifier(attr.typeId, attr.typeModifier) || GraphQLString) }, attr.typeModifier), { pgFieldIntrospection: attr }) }, `Adding input object field for ${describePgEntity(attr)}. You can rename this field with a 'Smart Comment':\n\n ${sqlCommentByAddingTags(attr, { name: "newNameHere" })}`); return memo; }, {}), `Adding columns to input object for ${describePgEntity(table)}`); }, ["PgColumns"]); }; exports.default = PgColumnsPlugin; //# sourceMappingURL=PgColumnsPlugin.js.map