graphile-settings
Version:
graphile settings
123 lines (122 loc) • 4.78 kB
JavaScript
import { GraphQLString } from 'graphql';
import sql from 'pg-sql2';
/**
* Default type mappings for common custom PostgreSQL types.
* These are typically domain types or composite types that should be
* represented as simple scalars in GraphQL.
*/
const DEFAULT_MAPPINGS = [
{ name: 'email', namespaceName: 'public', type: 'String' },
{ name: 'hostname', namespaceName: 'public', type: 'String' },
{ name: 'origin', namespaceName: 'public', type: 'String' },
{ name: 'url', namespaceName: 'public', type: 'String' },
];
/**
* Plugin that maps custom PostgreSQL types to GraphQL scalar types.
*
* This is useful for domain types or composite types that should be
* represented as simple scalars (String, JSON) in the GraphQL API.
*
* For example, if you have:
* CREATE DOMAIN email AS text;
* CREATE TYPE url AS (value text);
*
* This plugin will map them to GraphQL String type instead of creating
* complex object types.
*
* The plugin handles both:
* 1. Domain types (simple aliases) - maps directly to the target scalar
* 2. Composite types - extracts the first field's value when converting from PG
*/
export const PgTypeMappingsPlugin = {
name: 'PgTypeMappingsPlugin',
version: '1.0.0',
gather: {
hooks: {
async pgCodecs_findPgCodec(info, event) {
if (event.pgCodec) {
return;
}
const { pgType: type, serviceName } = event;
// Find the namespace for this type
const namespace = await info.helpers.pgIntrospection.getNamespace(serviceName, type.typnamespace);
if (!namespace) {
return;
}
// Check if this type matches any of our mappings
const mapping = DEFAULT_MAPPINGS.find(m => m.name === type.typname && m.namespaceName === namespace.nspname);
if (!mapping) {
return;
}
// Create a codec for this type
// For composite types, the fromPg function extracts the first field's value
// For domain types, it just passes through the value
event.pgCodec = {
name: type.typname,
sqlType: sql.identifier(namespace.nspname, type.typname),
fromPg: (value) => {
if (value == null) {
return null;
}
// If it's already a scalar, return it
if (typeof value !== 'object' || Array.isArray(value)) {
return value;
}
// For composite types, extract the first field's value
const obj = value;
const keys = Object.keys(obj);
if (keys.length > 0) {
return obj[keys[0]];
}
return value;
},
toPg: (value) => value,
attributes: undefined,
executor: null,
extensions: {
oid: type._id,
pg: {
serviceName,
schemaName: namespace.nspname,
name: type.typname,
},
tags: {
// Mark this as a custom mapped type
pgTypeMappings: mapping.type,
},
},
};
},
},
},
schema: {
hooks: {
init(_, build) {
const { setGraphQLTypeForPgCodec } = build;
// Map our custom codecs to GraphQL types
for (const codec of Object.values(build.input.pgRegistry.pgCodecs)) {
const mappingType = codec.extensions?.tags?.pgTypeMappings;
if (mappingType) {
const gqlTypeName = GraphQLString.name;
setGraphQLTypeForPgCodec(codec, 'input', gqlTypeName);
setGraphQLTypeForPgCodec(codec, 'output', gqlTypeName);
}
}
return _;
},
},
},
};
/**
* Preset that includes the PG type mappings plugin.
*
* This preset maps common custom PostgreSQL types to GraphQL scalars:
* - email -> String
* - hostname -> String
* - url -> String
* - origin -> String
*/
export const PgTypeMappingsPreset = {
plugins: [PgTypeMappingsPlugin],
};
export default PgTypeMappingsPlugin;