Skip to content

Instantly share code, notes, and snippets.

@jsmnbom
Last active July 18, 2025 22:00
Show Gist options
  • Select an option

  • Save jsmnbom/f5305384126b8ac02ad0977c9b960d71 to your computer and use it in GitHub Desktop.

Select an option

Save jsmnbom/f5305384126b8ac02ad0977c9b960d71 to your computer and use it in GitHub Desktop.
/**
* Plugin(s) to add typedPgRegistry to the build input.
* TypedPgRegistryPlugin adds the typedPgRegistry to the build input, and should always be used.
* ExportPgRegistryTypesPlugin exports types for the PgRegistry, and should only be used in development.
* The exported .d.ts file should be added to the tsconfig.json `include` array.
* The plugin only exports if the `exportPgRegistryTypesPath` option is set in the schema options.
*/
import type { PgCodec, PgEnumCodec, PgResource } from '@dataplan/pg'
import { mkdir, readFile, writeFile } from 'node:fs/promises'
import { dirname } from 'node:path'
const version = '0.5.0'
declare global {
// eslint-disable-next-line ts/no-namespace
namespace GraphileBuild {
interface SchemaOptions {
exportPgRegistryTypesPath?: string
}
}
}
async function writeFileIfDiffers(
path: string,
contents: string,
): Promise<void> {
await mkdir(dirname(path), { recursive: true })
const oldContents = await readFile(path, 'utf8').catch(() => null)
if (oldContents !== contents) {
await writeFile(path, contents)
}
}
export const TypedPgRegistryPlugin: GraphileConfig.Plugin = {
name: 'TypedPgRegistryPlugin',
description:
'Adds typedPgRegistry to the build input.',
version,
after: ['PgRegistryPlugin'],
gather: {
async main(output) {
(output as unknown as {
typedPgRegistry: typeof output.pgRegistry
}).typedPgRegistry = output.pgRegistry
},
},
}
interface $$Export {
moduleName: string
exportName: string | 'default' | '*' | string[]
}
interface Importable {
$$export: $$Export
}
interface AnyFunction {
(...args: any[]): any
displayName?: string
}
function isImportable(
thing: unknown,
): thing is { $$export: $$Export } {
return (
(typeof thing === 'object' || typeof thing === 'function')
&& thing !== null
&& '$$export' in (thing as object | AnyFunction)
)
}
function isEnumCodec(codec: PgCodec): codec is PgEnumCodec {
return codec.isEnum === true
}
export const ExportPgRegistryTypesPlugin: GraphileConfig.Plugin = {
name: 'ExportPgRegistryTypesPlugin',
description:
'Exports types for the PgRegistry. Should only be used in development.',
version,
schema: {
hooks: {
finalize(schema, build) {
const { exportPgRegistryTypesPath } = build.options
const { pgExecutors, pgResources, pgCodecs, pgRelations } = build.input.pgRegistry
if (!exportPgRegistryTypesPath)
return schema
const codecTypeName = (codec: PgCodec) => `${codec.name}Codec`.replace(/\W/g, '_')
const resourceTypeName = (resource: PgResource) => `${resource.name}Resource`.replace(/\W/g, '_')
const namedImports: Record<string, Set<string>> = {}
const starImports = new Set<string>()
const defaultImports = new Set<string>()
function addNamedImport(moduleName: string, name: string): void {
if (!namedImports[moduleName])
namedImports[moduleName] = new Set()
namedImports[moduleName].add(name)
}
function asImported(thing: Importable): string {
if (thing.$$export.exportName === 'default') {
defaultImports.add(thing.$$export.moduleName)
return thing.$$export.moduleName
}
else if (thing.$$export.exportName === '*') {
starImports.add(thing.$$export.moduleName)
return thing.$$export.moduleName
}
else if (Array.isArray(thing.$$export.exportName)) {
addNamedImport(thing.$$export.moduleName, thing.$$export.exportName[0]!)
return thing.$$export.exportName.join('.')
}
addNamedImport(thing.$$export.moduleName, thing.$$export.exportName)
return thing.$$export.exportName
}
const codecs = Object.values(pgCodecs).map((codec) => {
if (isImportable(codec)) {
return `export type ${codecTypeName(codec)} = typeof ${asImported(codec)};`
}
const TName = codec.name
if (isEnumCodec(codec)) {
return `export type ${codecTypeName(codec)} = PgEnumCodec<'${TName}', ${codec.values.map(value => `'${value.value}'`).join(' | ')}>;`
}
if (codec.attributes) {
const TAttributes = [
'{',
indent(
Object.entries(codec.attributes)
.map(([key, attribute]) => `${key}: PgCodecAttribute<${codecTypeName(attribute.codec)}, ${attribute.notNull ? 'true' : 'false'}>`)
.join('\n'),
),
'}',
].join('\n')
return `export type ${codecTypeName(codec)} = PgRecordCodec<'${TName}', ${TAttributes}>;`
}
let TFromPostgres = 'any'
let TFromJavaScript = 'any'
let TArrayItemCodec = 'undefined'
let TDomainItemCodec = 'undefined'
let TRangeItemCodec = 'undefined'
if (codec.domainOfCodec) {
TFromPostgres = `readonly InferPgCodecTFromPostgres<${codecTypeName(codec.domainOfCodec)}>`
TFromJavaScript = `readonly InferPgCodecTFromJavaScript<${codecTypeName(codec.domainOfCodec)}>[]`
TDomainItemCodec = codecTypeName(codec.domainOfCodec)
}
else if (codec.rangeOfCodec) {
TFromPostgres = 'string'
TFromJavaScript = `PgRange<unknown>`
TRangeItemCodec = codecTypeName(codec.rangeOfCodec)
}
else if (codec.arrayOfCodec) {
TFromPostgres = 'string'
TFromJavaScript = `readonly InferPgCodecTFromJavaScript<${codecTypeName(codec.arrayOfCodec)}>[]`
TArrayItemCodec = codecTypeName(codec.arrayOfCodec)
}
return `export type ${codecTypeName(codec)} = PgCodec<'${TName}', undefined, ${TFromPostgres}, ${TFromJavaScript}, ${TArrayItemCodec}, ${TDomainItemCodec}, ${TRangeItemCodec}>;`
})
const resources = Object.values(pgResources).map((resource) => {
if (isImportable(resource)) {
return `export type ${resourceTypeName(resource)} = typeof ${asImported(resource)};`
}
const TName = resource.name
const TCodec = `${codecTypeName(resource.codec)}`
const TUniques = resource.uniques && resource.uniques.length > 0
? [
'readonly [',
indent(
resource.uniques
.map(unique => `{ attributes: readonly [${unique.attributes.map(attr => `'${attr}'`).join(', ')}]}`)
.join(',\n'),
),
']',
].join('\n')
: 'readonly []'
const TParameters = resource.parameters && resource.parameters.length > 0
? [
'readonly [',
indent(
resource.parameters
.map(parameter => `PgResourceParameter<'${parameter.name}', ${codecTypeName(parameter.codec)}>`)
.join(',\n'),
),
']',
].join('\n')
: 'undefined'
return `export type ${resource.name}Resource = PgResourceOptions<'${TName}', ${TCodec}, ${TUniques}, ${TParameters}>;`
})
const relations = Object.fromEntries(Object.entries(pgRelations).map(([codecName, relations]) => [codecName, Object.fromEntries(
Object.entries(relations).map(([relationName, relation]) => [
relationName,
[
'{',
indent([
`localCodec: ${codecTypeName(relation.localCodec)}`,
`remoteResourceOptions: ${resourceTypeName(relation.remoteResource)}`,
`localAttributes: readonly [${relation.localAttributes.map(attr => `'${attr}'`).join(', ')}]`,
`remoteAttributes: readonly [${relation.remoteAttributes.map(attr => `'${attr}'`).join(', ')}]`,
`isUnique: ${relation.isUnique ? 'true' : 'false'}`,
].join('\n')),
'}',
].join('\n'),
]),
)]))
const code = `/*
* This file is auto-generated by the PgRegistryTypesPlugin.
* Do not edit this file directly.
*/
import type { PgCodec, PgCodecAttribute, PgExecutor, PgRegistry, PgResourceOptions, PgResourceParameter, ObjectFromPgCodecAttributes } from '@dataplan/pg';
${[
...Object.entries(namedImports).map(([moduleName, names]) => `import type { ${[...names].join(', ')} } from '${moduleName}';`),
...[...starImports].map(moduleName => `import * as ${moduleName} from '${moduleName}';`),
...[...defaultImports].map(moduleName => `import ${moduleName} from '${moduleName}';`),
].join('\n')}
type InferPgCodecTFromJavaScript<TCodec> =
TCodec extends PgCodec<
infer _Name,
infer _Attrs,
infer FromPg,
infer _FromJs,
infer _Array,
infer _Domain,
infer _Range
> ? FromPg : never;
type InferPgCodecTFromJavaScript<TCodec> =
TCodec extends PgCodec<
infer _Name,
infer _Attrs,
infer _FromPg,
infer FromJs,
infer _Array,
infer _Domain,
infer _Range
> ? FromJs : never;
type PgRecordCodec<TName extends string, const TAttributes extends PgCodecAttributes> =
PgCodec<TName, TAttributes, string, ObjectFromPgCodecAttributes<TAttributes>, undefined, undefined, undefined>;
${codecs.join('\n')}
export type Codecs = {
${Object.entries(pgCodecs)
.map(([name, codec]) => `'${name}': ${codecTypeName(codec)};`)
.join('\n ')}
};
${resources.join('\n')}
export type ResourceOptions = {
${Object.entries(pgResources)
.map(([name, resource]) => `'${name}': ${resourceTypeName(resource)};`)
.join('\n ')}
};
export type Relations = {
${indent(Object.entries(relations)
.map(([codecName, codecRelations]) => `'${codecName}': { \n${indent(
Object.entries(codecRelations)
.map(([relationName, relationType]) => `'${relationName}': ${relationType};`)
.join('\n'),
)} \n};`)
.join('\n'))
}
};
export type Executors = {
${Object.entries(pgExecutors)
.map(([name, executor]) => `'${name}': PgExecutor<'${executor.name}'>;`)
.join('\n ')}
}
export type TypedPgRegistry = PgRegistry<
Codecs,
ResourceOptions,
Relations,
Executors
>;
declare global {
namespace GraphileBuild {
interface BuildInput {
typedPgRegistry: TypedPgRegistry
}
}
}
`
writeFileIfDiffers(exportPgRegistryTypesPath, code).catch((e) => {
console.error(
`Failed to write PgRegistry types to '${exportPgRegistryTypesPath}': ${e}`,
)
})
return schema
},
},
},
}
function indent(
lines: string,
spaces: number = 2,
): string {
const indentString = ' '.repeat(spaces)
return lines.replace(/^(?!\s*$)/gm, indentString)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment