Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
A plugin for Postgraphile that provides support for the "phone_number" type from pg_libphonenumber: https://github.com/blm768/pg-libphonenumber Note that this support requires installing libphonenumber-js on the Postgraphile server: https://github.com/catamphetamine/libphonenumber-js
import { Plugin, Build, ScopeGraphQLScalarType } from "graphile-build";
import { PhoneNumber, parsePhoneNumber } from "libphonenumber-js";
declare module "graphile-build" {
interface ScopeGraphQLScalarType {
isPhoneNumberScalar: boolean;
}
}
export default (function PgTypesLibphonenumberPlugin(builder) {
builder.hook(
"build",
build => {
// This hook tells graphile-build-pg about the libphonenumber database type so it
// knows how to express it in input/output.
const {
pgIntrospectionResultsByKind: rawIntrospectionResultsByKind,
pgRegisterGqlTypeByTypeId,
pgRegisterGqlInputTypeByTypeId,
pg2GqlMapper,
pgSql: sql,
} = build;
if (
!rawIntrospectionResultsByKind ||
!sql ||
!pgRegisterGqlTypeByTypeId ||
!pgRegisterGqlInputTypeByTypeId ||
!pg2GqlMapper
) {
throw new Error("Required helpers were not found on Build.");
}
const introspectionResultsByKind = rawIntrospectionResultsByKind;
// Check we have the libphonenumber extension
const libphonenumberExtension = introspectionResultsByKind.extension.find(
e => e.name === "pg_libphonenumber"
);
if (!libphonenumberExtension) {
return build;
}
// Get the 'phone_number' type itself:
const phoneNumberType = introspectionResultsByKind.type.find(
t =>
t.name === "phone_number" &&
t.namespaceId === libphonenumberExtension.namespaceId
);
if (!phoneNumberType) {
return build;
}
const phoneNumberTypeName = build.inflection.builtin("PhoneNumber");
const GraphQLPhoneNumberType = makeGraphQLPhoneNumberType(
build as Build,
phoneNumberTypeName
);
// Now register the phone_number type with the type system for both output and input.
pgRegisterGqlTypeByTypeId(
phoneNumberType.id,
() => GraphQLPhoneNumberType
);
pgRegisterGqlInputTypeByTypeId(
phoneNumberType.id,
() => GraphQLPhoneNumberType
);
// Finally we must tell the system how to translate the data between PG-land and JS-land:
pg2GqlMapper[phoneNumberType.id] = {
// Turn string (from node-postgres) into PhoneNumber object
map: parsePhoneNumber,
// When unmapping we need to convert back to phone_number
unmap: (pn: PhoneNumber) =>
sql.fragment`(${sql.value(pn.number)}::${sql.identifier(
phoneNumberType.namespaceName,
phoneNumberType.name
)})`,
};
return build;
},
["PgTypesLibphonenumber"],
[],
["PgTypes"]
);
/* End of libphonenumber type */
} as Plugin);
function makeGraphQLPhoneNumberType(build: Build, phoneNumberTypeName: string) {
const {
graphql: { GraphQLScalarType, Kind },
} = build;
function parseValue(obj: unknown): PhoneNumber {
if (!(typeof obj === "string")) {
throw new TypeError(
`This is not a valid ${phoneNumberTypeName} object, it must be a string.`
);
}
return parsePhoneNumber(obj);
}
const parseLiteral: import("graphql").GraphQLScalarLiteralParser<any> = (
ast,
variables
) => {
switch (ast.kind) {
case Kind.STRING: {
return parsePhoneNumber(ast.value);
}
case Kind.NULL:
return null;
case Kind.VARIABLE: {
const name = ast.name.value;
const value = variables ? variables[name] : undefined;
return parsePhoneNumber(value);
}
default:
return undefined;
}
};
const scope: ScopeGraphQLScalarType = { isPhoneNumberScalar: true };
const GraphQLPhoneNumber = build.newWithHooks(
GraphQLScalarType,
{
name: phoneNumberTypeName,
description: "An international phone number.",
serialize: (pn: PhoneNumber) => pn.number,
parseValue,
parseLiteral,
},
scope
);
return GraphQLPhoneNumber;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment