Skip to content

Plugin: replacing PG GraphQL types

Matt Bretl edited this page Oct 2, 2018 · 3 revisions

Postgraphile will replace certain types with custom types. Often times you might want to provide an alternate type of your own. This plugin allows you to hook into any Postgres Type by Type Code (pg_type's oid column).

Thanks to @dwelch2344

https://gitter.im/graphile/postgraphile?at=5aca78ff2b9dfdbc3a6c0c10

// helper function to delegate to a type if a mapped value is provided
const curryOriginal = (original, mapping) => {
  return (typeId, typeModifier) => {
    if (mapping[typeId]) {
      let gqlType = mapping[typeId]
      gqlType = gqlType instanceof Function ? gqlType() : gqlType
      return gqlType
    }
    return original(typeId, typeModifier)
  }
}

/**
 * mapping[typeId] = GraphQL Type
 *
 * To see all typeIds => SELECT pt.oid, pt.typname, pt.typcategory FROM pg_type pt ORDER BY pt.oid
 */
export const PgTypeReplacerPlugin = mapping => {
  const fn = function PgTypeReplacer(builder) {
    builder.hook('build', build =>
      Object.assign({}, build, {
        pgGetGqlTypeByTypeIdAndModifier: curryOriginal(
          build.pgGetGqlTypeByTypeIdAndModifier,
          mapping
        ),
        pgGetGqlInputTypeByTypeIdAndModifier: curryOriginal(
          build.pgGetGqlInputTypeByTypeIdAndModifier,
          mapping
        )
      })
    )
  }

  fn.displayName = `PgTypeReplacerPlugin`
  return fn
}

export const SplicePgTypeReplacer = (
  originalPlugins,
  mapping,
  options = {}
) => {
  let plugins = [...originalPlugins]
  const idx = plugins.findIndex(e => e.name === 'PgTypesPlugin')
  if (idx < 0) {
    throw new Error('Could not find PgTypesPlugin')
  }
  const part = plugins.splice(0, idx + 1)
  plugins = [...part, ...[PgTypeReplacerPlugin(mapping)], ...plugins]

  if (options.debug) {
    plugins.forEach((e, i) => {
      console.warn(`${i} ${e.name}`)
    })
  }
  return plugins
}

With usage as:

const {
  createPostGraphileSchema,
  withPostGraphileContext
} = require('postgraphile')
const { defaultPlugins: corePlugins } = require('graphile-build')
const { defaultPlugins: pgPlugins } = require('graphile-build-pg')
const { GraphQLInt, GraphQLFloat } = require('graphql')
const { SplicePgTypeReplacer } = require('./graphile/plugins/PgTypeReplacer')


//... 
const initializeQuery = async ({
  db,
  schema: schemaName,
  jwtSecret,
  defaultRole,
  jwtType
}) => {
  const mapping = {
    '20': GraphQLInt,
    '1700': GraphQLFloat
  }
  const plugins = SplicePgTypeReplacer([...corePlugins, ...pgPlugins], mapping)
  const graphileSchema = await createPostGraphileSchema(db, schemaName, {
    pgDefaultRole: defaultRole,
    jwtSecret,
    jwtPgTypeIdentifier: jwtType,
    replaceAllPlugins: plugins
  })
  //... use schema with `withPostGraphileContext`

or middleware usage as:

const express = require("express");
const { postgraphile } = require("postgraphile");
const { defaultPlugins: corePlugins } = require('graphile-build');
const { defaultPlugins: pgPlugins } = require('graphile-build-pg');
const { GraphQLInt, GraphQLFloat } = require('graphql');
const { SplicePgTypeReplacer } = require('./graphile/plugins/PgTypeReplacer');

const mapping = {
  '20': GraphQLInt,
  '1700': GraphQLFloat
};

const plugins = SplicePgTypeReplacer([...corePlugins, ...pgPlugins], mapping);

const app = express();

app.use(
  postgraphile(pgConfig, [schemaName], {
    replaceAllPlugins: plugins,
  })
);

app.listen(5000);