UNPKG

postgraphile-plugin-many-create-update-delete

Version:

Postgraphile plugin that enables many create, update, & delete mutations in a single transaction.

201 lines 10.9 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const debug_1 = __importDefault(require("debug")); const debug = debug_1.default('graphile-build-pg'); const PostGraphileManyCreatePlugin = (builder, options) => { if (options.pgDisableDefaultMutations) return; /** * Add a hook to create the new root level create mutation */ builder.hook( // @ts-ignore 'GraphQLObjectType:fields', GQLObjectFieldsHookHandlerFcn, ['PgMutationManyCreate'], // Hook provides [], // Hook before ['PgMutationCreate'] // Hook after ); /** * Handles adding the new "many create" root level fields */ function GQLObjectFieldsHookHandlerFcn(fields, build, context) { const { extend, newWithHooks, parseResolveInfo, pgIntrospectionResultsByKind, pgGetGqlTypeByTypeIdAndModifier, pgGetGqlInputTypeByTypeIdAndModifier, pgSql: sql, gql2pg, graphql: { GraphQLObjectType, GraphQLInputObjectType, GraphQLNonNull, GraphQLString, GraphQLList }, pgColumnFilter, inflection, pgQueryFromResolveData: queryFromResolveData, pgOmit: omit, pgViaTemporaryTable: viaTemporaryTable, describePgEntity, sqlCommentByAddingTags, pgField } = build; const { scope: { isRootMutation }, fieldWithHooks } = context; if (!isRootMutation) return fields; let newFields = {}, i; const noOfTables = pgIntrospectionResultsByKind.class.length; for (i = 0; i < noOfTables; i++) { handleAdditionsFromTableInfo(pgIntrospectionResultsByKind.class[i]); } function handleAdditionsFromTableInfo(table) { if (!table.namespace || !table.isSelectable || !table.isInsertable || omit(table, 'create') || !table.tags.mncud) return; const tableType = pgGetGqlTypeByTypeIdAndModifier(table.type.id, null); if (!tableType) { debug(`There was no table type for table '${table.namespace.name}.${table.name}', so we're not generating a create mutation for it.`); return; } const TableInput = pgGetGqlInputTypeByTypeIdAndModifier(table.type.id, null); if (!TableInput) { debug(`There was no input type for table '${table.namespace.name}.${table.name}', so we're going to omit it from the create mutation.`); return; } const tableTypeName = inflection.tableType(table); // Setup args for the input type const newInputHookType = GraphQLInputObjectType; const newInputHookSpec = { name: `mn${inflection.createInputType(table)}`, description: `All input for the create mn\`${tableTypeName}\` mutation.`, fields: () => ({ clientMutationId: { description: 'An arbitrary string value with no semantic meaning. Will be included in the payload verbatim. May be used to track mutations by the client.', type: GraphQLString }, [`mn${tableTypeName}`]: { description: `The one or many \`${tableTypeName}\` to be created by this mutation.`, type: new GraphQLList(new GraphQLNonNull(TableInput)) } }) }; const newInputHookScope = { __origin: `Adding many table create input type for ${describePgEntity(table)}. You can rename the table's GraphQL type via a 'Smart Comment': \n\n ${sqlCommentByAddingTags(table, { name: 'newNameHere' })}`, isPgCreateInputType: true, pgInflection: table, pgIntrospection: table }; const InputType = newWithHooks(newInputHookType, newInputHookSpec, newInputHookScope); // Setup args for payload type const newPayloadHookType = GraphQLObjectType; const newPayloadHookSpec = { name: `mn${inflection.createPayloadType(table)}`, description: `The output of our many create \`${tableTypeName}\` mutation.`, fields: ({ fieldWithHooks }) => { const tableName = inflection.tableFieldName(table); return { clientMutationId: { description: 'The exact same `clientMutationId` that was provided in the mutation input, unchanged and unused. May be used by a client to track mutations.', type: GraphQLString }, [tableName]: pgField(build, fieldWithHooks, tableName, { description: `The \`${tableTypeName}\` that was created by this mutation.`, type: tableType }, { isPgCreatePayloadResultField: true, pgFieldIntrospection: table }) }; } }; const newPayloadHookScope = { __origin: `Adding many table many create payload type for ${describePgEntity(table)}. You can rename the table's GraphQL type via a 'Smart Comment': \n\n ${sqlCommentByAddingTags(table, { name: 'newNameHere' })}\n\nor disable the built-in create mutation via:\n\n ${sqlCommentByAddingTags(table, { omit: 'create' })}`, isMutationPayload: true, isPgCreatePayloadType: true, pgIntrospection: table }; const PayloadType = newWithHooks(newPayloadHookType, newPayloadHookSpec, newPayloadHookScope); const fieldName = `mn${inflection.upperCamelCase(inflection.createField(table))}`; function newFieldWithHooks() { return fieldWithHooks(fieldName, context => { context.table = table; context.relevantAttributes = table.attributes.filter(attr => pgColumnFilter(attr, build, context) && !omit(attr, 'create')); return { description: `Creates one or many \`${tableTypeName}\`.`, type: PayloadType, args: { input: { type: new GraphQLNonNull(InputType) } }, resolve: resolver.bind(context) }; }, { pgFieldIntrospection: table, isPgCreateMutationField: true }); } async function resolver(_data, args, resolveContext, resolveInfo) { const { input } = args; const { table, getDataFromParsedResolveInfoFragment, relevantAttributes } = this; const { pgClient } = resolveContext; const parsedResolveInfoFragment = parseResolveInfo(resolveInfo); // @ts-ignore parsedResolveInfoFragment.args = args; // Allow overriding via makeWrapResolversPlugin const resolveData = getDataFromParsedResolveInfoFragment(parsedResolveInfoFragment, PayloadType); const insertedRowAlias = sql.identifier(Symbol()); const query = queryFromResolveData(insertedRowAlias, insertedRowAlias, resolveData, {}, null, resolveContext, resolveInfo.rootValue); const sqlColumns = []; const inputData = input[`mn${inflection.upperCamelCase(inflection.tableFieldName(table))}`]; if (!inputData || inputData.length === 0) return null; const sqlValues = Array(inputData.length).fill([]); inputData.forEach((dataObj, i) => { relevantAttributes.forEach((attr) => { const fieldName = inflection.column(attr); const dataValue = dataObj[fieldName]; // On the first run, store the attribute values if (i === 0) { sqlColumns.push(sql.identifier(attr.name)); } // If the key exists, store the data else store DEFAULT. if (Object.prototype.hasOwnProperty.call(dataObj, fieldName)) { sqlValues[i] = [ ...sqlValues[i], gql2pg(dataValue, attr.type, attr.typeModifier) ]; } else { sqlValues[i] = [...sqlValues[i], sql.raw('default')]; } }); }); const mutationQuery = sql.query ` INSERT INTO ${sql.identifier(table.namespace.name, table.name)} ${sqlColumns.length ? sql.fragment `(${sql.join(sqlColumns, ', ')}) VALUES (${sql.join(sqlValues.map(dataGroup => sql.fragment `${sql.join(dataGroup, ', ')}`), '),(')})` : sql.fragment `default values`} returning *`; let row; try { await pgClient.query('SAVEPOINT graphql_mutation'); const rows = await viaTemporaryTable(pgClient, sql.identifier(table.namespace.name, table.name), mutationQuery, insertedRowAlias, query); row = rows[0]; await pgClient.query('RELEASE SAVEPOINT graphql_mutation'); } catch (e) { await pgClient.query('ROLLBACK TO SAVEPOINT graphql_mutation'); throw e; } return { clientMutationId: input.clientMutationId, data: row }; } newFields = extend(newFields, { [fieldName]: newFieldWithHooks }, `Adding create mutation for ${describePgEntity(table)}. You can omit this default mutation with a 'Smart Comment':\n\n ${sqlCommentByAddingTags(table, { omit: 'create' })}`); } return extend(fields, newFields, `Adding the many 'create' mutation to the root mutation`); } }; exports.default = PostGraphileManyCreatePlugin; //# sourceMappingURL=PostGraphileManyCreatePlugin.js.map