From 059e894156a0475d98973ef95d51d18ddcba9a5a Mon Sep 17 00:00:00 2001 From: BenjAIe Date: Fri, 17 Jul 2026 17:53:39 +0100 Subject: [PATCH 1/9] Factor type modifiers into codec construction --- grafast/dataplan-pg/src/codecs.ts | 5 +++ grafast/dataplan-pg/src/datasource.ts | 9 ++++ grafast/dataplan-pg/src/interfaces.ts | 7 +++ graphile-build/graphile-build-pg/src/index.ts | 1 + .../graphile-build-pg/src/inputUtils.ts | 3 ++ .../src/plugins/PgCodecsPlugin.ts | 45 ++++++++++++++----- 6 files changed, 60 insertions(+), 10 deletions(-) diff --git a/grafast/dataplan-pg/src/codecs.ts b/grafast/dataplan-pg/src/codecs.ts index 13b53ed7aa..f037142a79 100644 --- a/grafast/dataplan-pg/src/codecs.ts +++ b/grafast/dataplan-pg/src/codecs.ts @@ -454,6 +454,8 @@ const codecInspect: CustomInspectFunction = function (this: PgCodec) { ? `ListCodec<${this.arrayOfCodec.name}[]>` : this.rangeOfCodec ? `RangeCodec<${this.rangeOfCodec.name}>` + : this.baseCodec + ? `ModifiedCodec<${this.baseCodec.name}>` : this.attributes ? `RecordCodec` : "Codec"; @@ -1705,6 +1707,9 @@ export function getInnerCodec< if (codec.rangeOfCodec) { return getInnerCodec(codec.rangeOfCodec) as any; } + if (codec.baseCodec) { + return getInnerCodec(codec.baseCodec) as any; + } return codec as any; } exportAs("@dataplan/pg", getInnerCodec, "getInnerCodec"); diff --git a/grafast/dataplan-pg/src/datasource.ts b/grafast/dataplan-pg/src/datasource.ts index 56709a5f7b..9ddf4c8aa6 100644 --- a/grafast/dataplan-pg/src/datasource.ts +++ b/grafast/dataplan-pg/src/datasource.ts @@ -1130,6 +1130,9 @@ export function makeRegistry< if (codec.rangeOfCodec) { addCodec(codec.rangeOfCodec); } + if (codec.baseCodec) { + addCodec(codec.baseCodec); + } // Tell the system to read the built codec from the registry Object.defineProperties(codec, { @@ -1257,6 +1260,9 @@ export function makeRegistry< if (codec.domainOfCodec) { walkCodec(codec.domainOfCodec, isAccessibleViaAttribute, seen); } + if (codec.baseCodec) { + walkCodec(codec.baseCodec, isAccessibleViaAttribute, seen); + } }; // Add table-like codecs used within attributes @@ -1462,6 +1468,9 @@ export function makeRegistryBuilder(): PgRegistryBuilder<{}, {}, {}, {}> { if (codec.rangeOfCodec) { this.addCodec(codec.rangeOfCodec); } + if (codec.baseCodec) { + this.addCodec(codec.baseCodec); + } if (codec.attributes) { for (const col of Object.values(codec.attributes)) { this.addCodec(col.codec); diff --git a/grafast/dataplan-pg/src/interfaces.ts b/grafast/dataplan-pg/src/interfaces.ts index b0281b5ac5..e251f32fe3 100644 --- a/grafast/dataplan-pg/src/interfaces.ts +++ b/grafast/dataplan-pg/src/interfaces.ts @@ -313,6 +313,13 @@ export interface PgCodec< */ rangeOfCodec?: TRangeItemCodec; + /** + * If this codec is a modified form of a broader PostgreSQL type (for + * example via typmod or additional constraints), this references the base + * codec that owns the underlying SQL representation. + */ + baseCodec?: PgCodec; + polymorphism?: PgCodecPolymorphism; /** diff --git a/graphile-build/graphile-build-pg/src/index.ts b/graphile-build/graphile-build-pg/src/index.ts index 4de5d97eb1..ff3612e55b 100644 --- a/graphile-build/graphile-build-pg/src/index.ts +++ b/graphile-build/graphile-build-pg/src/index.ts @@ -181,6 +181,7 @@ declare global { */ // eslint-disable-next-line @typescript-eslint/no-empty-object-type persistence?: "p" | "u" | "t" | (string & {}) | null; + typeModifier?: string | number | null; }; } } diff --git a/graphile-build/graphile-build-pg/src/inputUtils.ts b/graphile-build/graphile-build-pg/src/inputUtils.ts index 718ead5e58..496af394ac 100644 --- a/graphile-build/graphile-build-pg/src/inputUtils.ts +++ b/graphile-build/graphile-build-pg/src/inputUtils.ts @@ -105,4 +105,7 @@ function walkCodec(codec: PgCodec, metaLookup: PgCodecMetaLookup): void { if (codec.rangeOfCodec) { walkCodec(codec.rangeOfCodec, metaLookup); } + if (codec.baseCodec) { + walkCodec(codec.baseCodec, metaLookup); + } } diff --git a/graphile-build/graphile-build-pg/src/plugins/PgCodecsPlugin.ts b/graphile-build/graphile-build-pg/src/plugins/PgCodecsPlugin.ts index 66e2abf7bb..c170e768fc 100644 --- a/graphile-build/graphile-build-pg/src/plugins/PgCodecsPlugin.ts +++ b/graphile-build/graphile-build-pg/src/plugins/PgCodecsPlugin.ts @@ -24,7 +24,7 @@ import { exportNameHint } from "../utils.ts"; import { version } from "../version.ts"; interface State { - codecByTypeIdByDatabaseName: Map< + codecByTypeAndModifierByDatabaseName: Map< string, Map> >; @@ -117,6 +117,7 @@ declare global { pgCodec: PgCodec; pgClass?: PgClass; pgType: PgType; + typeModifier?: string | number | null; }): Promise | void; pgCodecs_attribute(event: { @@ -305,7 +306,7 @@ export const PgCodecsPlugin: GraphileConfig.Plugin = { gather: gatherConfig({ namespace: "pgCodecs", initialState: (): State => ({ - codecByTypeIdByDatabaseName: new Map(), + codecByTypeAndModifierByDatabaseName: new Map(), codecByClassIdByDatabaseName: new Map(), }), helpers: { @@ -483,13 +484,15 @@ export const PgCodecsPlugin: GraphileConfig.Plugin = { }, getCodecFromType(info, serviceName, typeId, typeModifier) { - let map = info.state.codecByTypeIdByDatabaseName.get(serviceName); + let map = + info.state.codecByTypeAndModifierByDatabaseName.get(serviceName); if (!map) { map = new Map(); - info.state.codecByTypeIdByDatabaseName.set(serviceName, map); + info.state.codecByTypeAndModifierByDatabaseName.set(serviceName, map); } - if (map.has(typeId)) { - return map.get(typeId)!; + const cacheKey = `${typeId}|${typeModifier ?? ""}`; + if (map.has(cacheKey)) { + return map.get(cacheKey)!; } const promise = (async (): Promise => { @@ -525,6 +528,7 @@ export const PgCodecsPlugin: GraphileConfig.Plugin = { pgCodec: codec, pgType: type, serviceName, + typeModifier, }); return codec; } else { @@ -540,7 +544,7 @@ export const PgCodecsPlugin: GraphileConfig.Plugin = { } })(); - map.set(typeId, promise); + map.set(cacheKey, promise); return promise; }, @@ -618,6 +622,7 @@ export const PgCodecsPlugin: GraphileConfig.Plugin = { serviceName, schemaName: type.getNamespace()!.nspname, name: type.typname, + ...(typeModifier != null ? { typeModifier } : null), }, ...(Object.keys(tags).length > 0 ? { tags } : null), }; @@ -697,6 +702,7 @@ export const PgCodecsPlugin: GraphileConfig.Plugin = { serviceName, schemaName: type.getNamespace()!.nspname, name: type.typname, + ...(typeModifier != null ? { typeModifier } : null), }, ...(Object.keys(tags).length > 0 ? { tags } : null), }; @@ -752,6 +758,7 @@ export const PgCodecsPlugin: GraphileConfig.Plugin = { serviceName, schemaName: type.getNamespace()!.nspname, name: type.typname, + ...(typeModifier != null ? { typeModifier } : null), }, ...(Object.keys(tags).length > 0 ? { tags } : null), }; @@ -810,6 +817,7 @@ export const PgCodecsPlugin: GraphileConfig.Plugin = { serviceName, schemaName: type.getNamespace()!.nspname, name: type.typname, + ...(typeModifier != null ? { typeModifier } : null), }, ...(Object.keys(tags).length > 0 ? { tags } : null), }; @@ -849,7 +857,7 @@ export const PgCodecsPlugin: GraphileConfig.Plugin = { // If we get errors from the frozen object then clearly we need to // ensure more work has completed before continuing - call other plugin // helpers and wait for their events. - for (const codecByTypeId of info.state.codecByTypeIdByDatabaseName.values()) { + for (const codecByTypeId of info.state.codecByTypeAndModifierByDatabaseName.values()) { for (const codecPromise of codecByTypeId.values()) { const codec = await codecPromise; if (codec) { @@ -906,10 +914,17 @@ export const PgCodecsPlugin: GraphileConfig.Plugin = { const pg = codec.extensions?.pg; if (pg) { const serviceName = pg.serviceName ?? "main"; - const { schemaName, name } = pg; + const { schemaName, name, typeModifier } = pg; lookup[serviceName] ??= Object.create(null); lookup[serviceName][schemaName] ??= Object.create(null); - lookup[serviceName][schemaName][name] = codec; + const existing = lookup[serviceName][schemaName][name]; + if ( + !existing || + (existing.extensions?.pg?.typeModifier != null && + typeModifier == null) + ) { + lookup[serviceName][schemaName][name] = codec; + } } if (codec.arrayOfCodec) { @@ -932,6 +947,10 @@ export const PgCodecsPlugin: GraphileConfig.Plugin = { if (codec.rangeOfCodec) { walkCodec(codec.rangeOfCodec); } + + if (codec.baseCodec) { + walkCodec(codec.baseCodec); + } } // Walk all the codecs, add them to build @@ -1178,6 +1197,12 @@ export const PgCodecsPlugin: GraphileConfig.Plugin = { prepareTypeForCodec(codec.rangeOfCodec, visited); } + // Process the broader type that this modified codec is based on + // (if any) + if (codec.baseCodec) { + prepareTypeForCodec(codec.baseCodec, visited); + } + if (build.hasGraphQLTypeForPgCodec(codec)) { // This type already has a codec; ignore return; From d2d3d52f5fcae8dfe48d52f45f2bb3080c979e06 Mon Sep 17 00:00:00 2001 From: BenjAIe Date: Fri, 17 Jul 2026 18:03:09 +0100 Subject: [PATCH 2/9] Changeset --- .changeset/green-buses-develop.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .changeset/green-buses-develop.md diff --git a/.changeset/green-buses-develop.md b/.changeset/green-buses-develop.md new file mode 100644 index 0000000000..d99110ec9f --- /dev/null +++ b/.changeset/green-buses-develop.md @@ -0,0 +1,8 @@ +--- +"@dataplan/pg": patch +"graphile-build-pg": patch +"postgraphile": patch +--- + +Add groundwork for modified PostgreSQL codecs keyed by type modifier and +constraints, including base-codec relationships for GraphQL type resolution. From be30ce713add6abd86f6917f88b317f5c3e32c21 Mon Sep 17 00:00:00 2001 From: BenjAIe Date: Fri, 17 Jul 2026 18:26:42 +0100 Subject: [PATCH 3/9] Factor typeModifier into codec naming --- .../src/plugins/PgCodecsPlugin.ts | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/graphile-build/graphile-build-pg/src/plugins/PgCodecsPlugin.ts b/graphile-build/graphile-build-pg/src/plugins/PgCodecsPlugin.ts index c170e768fc..8c54b79931 100644 --- a/graphile-build/graphile-build-pg/src/plugins/PgCodecsPlugin.ts +++ b/graphile-build/graphile-build-pg/src/plugins/PgCodecsPlugin.ts @@ -42,7 +42,11 @@ declare global { serviceName: string; }): string; - typeCodecName(details: { pgType: PgType; serviceName: string }): string; + typeCodecName(details: { + pgType: PgType; + serviceName: string; + typeModifier?: string | number | null; + }): string; scalarCodecTypeName(this: Inflection, codec: PgCodecAnyScalar): string; enumType(this: Inflection, codec: PgEnumCodec): string; @@ -196,14 +200,15 @@ export const PgCodecsPlugin: GraphileConfig.Plugin = { const schemaPrefix = this._schemaPrefix({ pgNamespace, serviceName }); return this.camelCase(`${schemaPrefix}${pgClass.relname}`); }, - typeCodecName(options, { pgType, serviceName }) { + typeCodecName(options, { pgType, serviceName, typeModifier }) { const pgNamespace = pgType.getNamespace()!; const schemaPrefix = this._schemaPrefix({ pgNamespace, serviceName }); const name = pgType.typcategory === "A" && pgType.typname.startsWith("_") ? pgType.typname.substring(1) + "_array" : pgType.typname; - return this.camelCase(`${schemaPrefix}${name}`); + const modifierName = typeModifier == null ? "" : `__${typeModifier}`; + return this.camelCase(`${schemaPrefix}${name}${modifierName}`); }, scalarCodecTypeName(options, codec) { return this.upperCamelCase( @@ -613,6 +618,7 @@ export const PgCodecsPlugin: GraphileConfig.Plugin = { const codecName = info.inflection.typeCodecName({ pgType: type, serviceName, + typeModifier, }); const enumLabels = enumValues.map((e) => e.enumlabel); const { tags, description } = type.getTagsAndDescription(); @@ -692,6 +698,7 @@ export const PgCodecsPlugin: GraphileConfig.Plugin = { const codecName = info.inflection.typeCodecName({ pgType: type, serviceName, + typeModifier, }); const { tags, description } = type.getTagsAndDescription(); @@ -771,6 +778,7 @@ export const PgCodecsPlugin: GraphileConfig.Plugin = { const codecName = info.inflection.typeCodecName({ pgType: type, serviceName, + typeModifier, }); const sqlIdent = info.helpers.pgBasics.identifier( namespaceName, @@ -830,6 +838,7 @@ export const PgCodecsPlugin: GraphileConfig.Plugin = { const name = info.inflection.typeCodecName({ pgType: type, serviceName, + typeModifier, }); exportNameHint(extensions, `${name}CodecExtensions`); const spec = { From 36d6a8678a6cd70154b4fe6a0fa6b68c2a37c98d Mon Sep 17 00:00:00 2001 From: BenjAIe Date: Fri, 17 Jul 2026 18:44:50 +0100 Subject: [PATCH 4/9] Failed to find one with modifier? Try again without. --- .../graphile-build-pg/src/plugins/PgCodecsPlugin.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/graphile-build/graphile-build-pg/src/plugins/PgCodecsPlugin.ts b/graphile-build/graphile-build-pg/src/plugins/PgCodecsPlugin.ts index 8c54b79931..e3561e11b5 100644 --- a/graphile-build/graphile-build-pg/src/plugins/PgCodecsPlugin.ts +++ b/graphile-build/graphile-build-pg/src/plugins/PgCodecsPlugin.ts @@ -537,6 +537,13 @@ export const PgCodecsPlugin: GraphileConfig.Plugin = { }); return codec; } else { + if (typeModifier != null) { + return info.helpers.pgCodecs.getCodecFromType( + serviceName, + typeId, + null, + ); + } console.warn( `Could not build PgCodec for '${ type.getNamespace()?.nspname ?? "??" From 657ae1fce3d90c0249d606f61d00df428b0ba6c3 Mon Sep 17 00:00:00 2001 From: BenjAIe Date: Fri, 17 Jul 2026 18:50:45 +0100 Subject: [PATCH 5/9] Ignored --- graphile-build/graphile-build-pg/src/plugins/PgLtreePlugin.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphile-build/graphile-build-pg/src/plugins/PgLtreePlugin.ts b/graphile-build/graphile-build-pg/src/plugins/PgLtreePlugin.ts index 1d9c707e9c..7a9f8d093a 100644 --- a/graphile-build/graphile-build-pg/src/plugins/PgLtreePlugin.ts +++ b/graphile-build/graphile-build-pg/src/plugins/PgLtreePlugin.ts @@ -60,7 +60,7 @@ export const PgLtreePlugin: GraphileConfig.Plugin = { // If another plugin has already supplied a codec; skip if (event.pgCodec) return; - const { serviceName, pgType } = event; + const { serviceName, pgType, typeModifier: _ignored } = event; const typname = pgType.typname; if (typname !== "ltree" && typname !== "_ltree") return; From afae60523e642ce7111cdec20103caba0d7384c9 Mon Sep 17 00:00:00 2001 From: BenjAIe Date: Fri, 17 Jul 2026 18:52:05 +0100 Subject: [PATCH 6/9] Fallback if there's a modifier --- .../graphile-build-pg/src/plugins/PgCodecsPlugin.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/graphile-build/graphile-build-pg/src/plugins/PgCodecsPlugin.ts b/graphile-build/graphile-build-pg/src/plugins/PgCodecsPlugin.ts index e3561e11b5..d073e03b8a 100644 --- a/graphile-build/graphile-build-pg/src/plugins/PgCodecsPlugin.ts +++ b/graphile-build/graphile-build-pg/src/plugins/PgCodecsPlugin.ts @@ -568,6 +568,14 @@ export const PgCodecsPlugin: GraphileConfig.Plugin = { return; } const { serviceName, pgType: type, typeModifier } = event; + + // The built-in codec mappings in this plugin are not modifier-aware. + // Leave modifier handling to dedicated plugins; if none supply a codec + // then getCodecFromType will canonicalize to the unmodified path. + if (typeModifier != null) { + return; + } + const namespace = type.getNamespace(); if (!namespace) { throw new Error(`Could not get namespace '${type.typnamespace}'`); From 39fcd5025d26c79a8a3a2cdbf0906aaf31ba75e5 Mon Sep 17 00:00:00 2001 From: BenjAIe Date: Fri, 17 Jul 2026 19:08:32 +0100 Subject: [PATCH 7/9] Modified codecs are special --- .../src/plugins/PgCodecsPlugin.ts | 91 +++++++++---------- .../src/plugins/PgLtreePlugin.ts | 2 +- 2 files changed, 44 insertions(+), 49 deletions(-) diff --git a/graphile-build/graphile-build-pg/src/plugins/PgCodecsPlugin.ts b/graphile-build/graphile-build-pg/src/plugins/PgCodecsPlugin.ts index d073e03b8a..efbd4d6233 100644 --- a/graphile-build/graphile-build-pg/src/plugins/PgCodecsPlugin.ts +++ b/graphile-build/graphile-build-pg/src/plugins/PgCodecsPlugin.ts @@ -114,7 +114,12 @@ declare global { serviceName: string; pgCodec: PgCodec | null; pgType: PgType; - typeModifier: string | number | null | undefined; + }): Promise | void; + pgCodecs_findModifiedPgCodec(event: { + serviceName: string; + pgCodec: PgCodec | null; + pgType: PgType; + typeModifier: string | number; }): Promise | void; pgCodecs_PgCodec(event: { serviceName: string; @@ -500,55 +505,60 @@ export const PgCodecsPlugin: GraphileConfig.Plugin = { return map.get(cacheKey)!; } + async function success(pgType: PgType, pgCodec: PgCodec) { + // Be careful not to call this for class codecs! + await info.process("pgCodecs_PgCodec", { + pgCodec, + pgType, + serviceName, + typeModifier, + }); + return pgCodec; + } + const promise = (async (): Promise => { - const type = await info.helpers.pgIntrospection.getType( + const pgType = await info.helpers.pgIntrospection.getType( serviceName, typeId, ); - if (!type) { + if (!pgType) { return null; } // Class types are handled via getCodecFromClass (they have to add attributes) - if (type.typtype === "c") { + if (pgType.typtype === "c") { return info.helpers.pgCodecs.getCodecFromClass( serviceName, - type.typrelid!, + pgType.typrelid!, ); } - const event: Parameters< - GraphileConfig.GatherHooks["pgCodecs_findPgCodec"] - >[0] = { - pgCodec: null, - pgType: type, - typeModifier, - serviceName, - }; - await info.process("pgCodecs_findPgCodec", event); - if (event.pgCodec) { - const codec = event.pgCodec; - // Be careful not to call this for class codecs! - await info.process("pgCodecs_PgCodec", { - pgCodec: codec, - pgType: type, + if (typeModifier != null) { + const event: Parameters< + GraphileConfig.GatherHooks["pgCodecs_findModifiedPgCodec"] + >[0] = { pgCodec: null, pgType, typeModifier, serviceName }; + await info.process("pgCodecs_findModifiedPgCodec", event); + if (event.pgCodec) { + return success(pgType, event.pgCodec); + } + return info.helpers.pgCodecs.getCodecFromType( serviceName, - typeModifier, - }); - return codec; + typeId, + null, + ); } else { - if (typeModifier != null) { - return info.helpers.pgCodecs.getCodecFromType( - serviceName, - typeId, - null, - ); + const event: Parameters< + GraphileConfig.GatherHooks["pgCodecs_findPgCodec"] + >[0] = { pgCodec: null, pgType, serviceName }; + await info.process("pgCodecs_findPgCodec", event); + if (event.pgCodec) { + return success(pgType, event.pgCodec); } console.warn( `Could not build PgCodec for '${ - type.getNamespace()?.nspname ?? "??" + pgType.getNamespace()?.nspname ?? "??" }.${ - type.typname + pgType.typname }'; maybe you need a plugin implementing gather.hooks.pgCodecs_findPgCodec to add support.`, event, ); @@ -567,14 +577,7 @@ export const PgCodecsPlugin: GraphileConfig.Plugin = { // Another plugin has already supplied a codec; skip return; } - const { serviceName, pgType: type, typeModifier } = event; - - // The built-in codec mappings in this plugin are not modifier-aware. - // Leave modifier handling to dedicated plugins; if none supply a codec - // then getCodecFromType will canonicalize to the unmodified path. - if (typeModifier != null) { - return; - } + const { serviceName, pgType: type } = event; const namespace = type.getNamespace(); if (!namespace) { @@ -633,7 +636,6 @@ export const PgCodecsPlugin: GraphileConfig.Plugin = { const codecName = info.inflection.typeCodecName({ pgType: type, serviceName, - typeModifier, }); const enumLabels = enumValues.map((e) => e.enumlabel); const { tags, description } = type.getTagsAndDescription(); @@ -643,7 +645,6 @@ export const PgCodecsPlugin: GraphileConfig.Plugin = { serviceName, schemaName: type.getNamespace()!.nspname, name: type.typname, - ...(typeModifier != null ? { typeModifier } : null), }, ...(Object.keys(tags).length > 0 ? { tags } : null), }; @@ -713,7 +714,6 @@ export const PgCodecsPlugin: GraphileConfig.Plugin = { const codecName = info.inflection.typeCodecName({ pgType: type, serviceName, - typeModifier, }); const { tags, description } = type.getTagsAndDescription(); @@ -724,7 +724,6 @@ export const PgCodecsPlugin: GraphileConfig.Plugin = { serviceName, schemaName: type.getNamespace()!.nspname, name: type.typname, - ...(typeModifier != null ? { typeModifier } : null), }, ...(Object.keys(tags).length > 0 ? { tags } : null), }; @@ -780,7 +779,6 @@ export const PgCodecsPlugin: GraphileConfig.Plugin = { serviceName, schemaName: type.getNamespace()!.nspname, name: type.typname, - ...(typeModifier != null ? { typeModifier } : null), }, ...(Object.keys(tags).length > 0 ? { tags } : null), }; @@ -793,7 +791,6 @@ export const PgCodecsPlugin: GraphileConfig.Plugin = { const codecName = info.inflection.typeCodecName({ pgType: type, serviceName, - typeModifier, }); const sqlIdent = info.helpers.pgBasics.identifier( namespaceName, @@ -827,7 +824,7 @@ export const PgCodecsPlugin: GraphileConfig.Plugin = { const innerCodec = (await info.helpers.pgCodecs.getCodecFromType( serviceName, innerType._id, - typeModifier, // TODO: is it correct to pass this through? + null, )) as | PgCodec | undefined; @@ -840,7 +837,6 @@ export const PgCodecsPlugin: GraphileConfig.Plugin = { serviceName, schemaName: type.getNamespace()!.nspname, name: type.typname, - ...(typeModifier != null ? { typeModifier } : null), }, ...(Object.keys(tags).length > 0 ? { tags } : null), }; @@ -853,7 +849,6 @@ export const PgCodecsPlugin: GraphileConfig.Plugin = { const name = info.inflection.typeCodecName({ pgType: type, serviceName, - typeModifier, }); exportNameHint(extensions, `${name}CodecExtensions`); const spec = { diff --git a/graphile-build/graphile-build-pg/src/plugins/PgLtreePlugin.ts b/graphile-build/graphile-build-pg/src/plugins/PgLtreePlugin.ts index 7a9f8d093a..1d9c707e9c 100644 --- a/graphile-build/graphile-build-pg/src/plugins/PgLtreePlugin.ts +++ b/graphile-build/graphile-build-pg/src/plugins/PgLtreePlugin.ts @@ -60,7 +60,7 @@ export const PgLtreePlugin: GraphileConfig.Plugin = { // If another plugin has already supplied a codec; skip if (event.pgCodec) return; - const { serviceName, pgType, typeModifier: _ignored } = event; + const { serviceName, pgType } = event; const typname = pgType.typname; if (typname !== "ltree" && typname !== "_ltree") return; From 9919bbe8d2cf07516431eade06aca9cb575da090 Mon Sep 17 00:00:00 2001 From: BenjAIe Date: Fri, 17 Jul 2026 19:17:32 +0100 Subject: [PATCH 8/9] Make hook stricter --- .../src/plugins/PgCodecsPlugin.ts | 70 +++++++++++++------ 1 file changed, 49 insertions(+), 21 deletions(-) diff --git a/graphile-build/graphile-build-pg/src/plugins/PgCodecsPlugin.ts b/graphile-build/graphile-build-pg/src/plugins/PgCodecsPlugin.ts index efbd4d6233..3403dbc9b7 100644 --- a/graphile-build/graphile-build-pg/src/plugins/PgCodecsPlugin.ts +++ b/graphile-build/graphile-build-pg/src/plugins/PgCodecsPlugin.ts @@ -111,15 +111,22 @@ declare global { } interface GatherHooks { pgCodecs_findPgCodec(event: { - serviceName: string; + readonly serviceName: string; + readonly pgType: PgType; + + /** The resulting codec to use... overwrite this! */ pgCodec: PgCodec | null; - pgType: PgType; }): Promise | void; pgCodecs_findModifiedPgCodec(event: { - serviceName: string; + readonly serviceName: string; + readonly pgType: PgType; + /** If you don't use this, you're doing it wrong: use the pgCodecs_findPgCodec hook instead. */ + readonly typeModifier: string | number; + /** The codec that your pgCodec should be based on, since it's modified */ + readonly baseCodec: PgCodec; + + /** The resulting codec to use... overwrite this! */ pgCodec: PgCodec | null; - pgType: PgType; - typeModifier: string | number; }): Promise | void; pgCodecs_PgCodec(event: { serviceName: string; @@ -534,35 +541,56 @@ export const PgCodecsPlugin: GraphileConfig.Plugin = { } if (typeModifier != null) { + const baseCodec = await info.helpers.pgCodecs.getCodecFromType( + serviceName, + typeId, + null, + ); + if (baseCodec == null) { + // Already logged + return null; + } const event: Parameters< GraphileConfig.GatherHooks["pgCodecs_findModifiedPgCodec"] - >[0] = { pgCodec: null, pgType, typeModifier, serviceName }; + >[0] = { + serviceName, + pgType, + typeModifier, + baseCodec, + pgCodec: null, + }; await info.process("pgCodecs_findModifiedPgCodec", event); if (event.pgCodec) { + if (event.pgCodec.baseCodec !== baseCodec) { + throw new Error( + `pgCodecs_findModifiedPgCodec must return a codec that's modified from ${baseCodec.name} - be sure to set 'baseCodec: event.baseCodec'`, + ); + } return success(pgType, event.pgCodec); + } else { + // It's okay, just use the unmodified one. It's probably not + // special anyway - `char(3)` is essentially the same as `char` + // at the end of the day... + return baseCodec; } - return info.helpers.pgCodecs.getCodecFromType( - serviceName, - typeId, - null, - ); } else { const event: Parameters< GraphileConfig.GatherHooks["pgCodecs_findPgCodec"] - >[0] = { pgCodec: null, pgType, serviceName }; + >[0] = { serviceName, pgType, pgCodec: null }; await info.process("pgCodecs_findPgCodec", event); if (event.pgCodec) { return success(pgType, event.pgCodec); + } else { + console.warn( + `Could not build PgCodec for '${ + pgType.getNamespace()?.nspname ?? "??" + }.${ + pgType.typname + }'; maybe you need a plugin implementing gather.hooks.pgCodecs_findPgCodec to add support.`, + event, + ); + return null; } - console.warn( - `Could not build PgCodec for '${ - pgType.getNamespace()?.nspname ?? "??" - }.${ - pgType.typname - }'; maybe you need a plugin implementing gather.hooks.pgCodecs_findPgCodec to add support.`, - event, - ); - return null; } })(); From 8b8b8edc6d483fc37ed9bf4f2d1343dd5983464b Mon Sep 17 00:00:00 2001 From: BenjAIe Date: Fri, 17 Jul 2026 19:25:23 +0100 Subject: [PATCH 9/9] Lint --- grafast/dataplan-pg/src/codecs.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/grafast/dataplan-pg/src/codecs.ts b/grafast/dataplan-pg/src/codecs.ts index f037142a79..8a296fba61 100644 --- a/grafast/dataplan-pg/src/codecs.ts +++ b/grafast/dataplan-pg/src/codecs.ts @@ -456,9 +456,9 @@ const codecInspect: CustomInspectFunction = function (this: PgCodec) { ? `RangeCodec<${this.rangeOfCodec.name}>` : this.baseCodec ? `ModifiedCodec<${this.baseCodec.name}>` - : this.attributes - ? `RecordCodec` - : "Codec"; + : this.attributes + ? `RecordCodec` + : "Codec"; return `${type}(${this.name})`; };