graphile / graphile.github.io

PostGraphile (PostGraphQL) and Graphile-Build website - contributions very welcome!
https://www.graphile.org/
26 stars 127 forks source link

Recipe: translate range types to/from Postgres' format instead of the object version #341

Open dmfay opened 1 year ago

dmfay commented 1 year ago

Sometimes the old way is more convenient :)

import { GraphQLScalarType } from "graphql"
import type { SchemaBuilder, Build } from "graphile-build"
import type { PgType } from "graphile-build-pg"

/**
 * This plugin switches the expression of range types back to Postgres' native
 * `[lower,upper)` formats instead of Postgraphile's more verbose nested-object
 * `{start: {value, inclusive}, end: {...}` version.
 */
export default function TranslateRangesPlugin(builder: SchemaBuilder) {
    builder.hook("build", (build: Build) => {
        const {
            pgIntrospectionResultsByKind,
            pgRegisterGqlInputTypeByTypeId,
            pgRegisterGqlTypeByTypeId,
            pg2GqlMapper,
            pgSql: sql,
        } = build

        // find our target type oid. If you want to change the behavior for a
        // built-in range type, you can use one of the following default oids:
        //
        // 3904 int4range
        // 3906 numrange
        // 3908 tsrange
        // 3910 tstzrange
        // 3912 daterange
        // 3926 int8range
        const myRange = pgIntrospectionResultsByKind.type.find(
            (t: PgType) => t.namespaceName === "my_schema" && t.name === "my_range"
        )

        if (!myRange) {
            return build // something went horribly wrong, abort
        }

        // declare a new kind of scalar
        const MyRangeType = new GraphQLScalarType({
            name: "MyRange",
            description: "An int8range dedicated to a more specific purpose",
            serialize: (a: string) => a,
            parseValue: (a: string) => a,
        })

        // register input and output types for the oid
        pgRegisterGqlInputTypeByTypeId(myRange.id, () => MyRangeType)
        pgRegisterGqlTypeByTypeId(myRange.id, () => MyRangeType)

        // define mappings to and from
        pg2GqlMapper[myRange.id] = {
            // SQL -> GQL
            map: (output: string) => output,
            // GQL -> SQL; substitute your target range type for `int8range` as necessary
            unmap: (input: string) => sql.fragment`${sql.value(input)}::int8range`,
        }

        return build // pass the build object back for the next stage
    })
}