Skip to content

Commit 7addba2

Browse files
committed
feat: wire BlueprintTypesPlugin into production schema build
Query node_type_registry at schema build time and pass entries to BlueprintTypesPreset so @OneOf typed blueprint input types with SuperCase node type names are generated in the live GraphQL schema. Changes: - Add fetchNodeTypeRegistry() utility that queries metaschema_public.node_type_registry (gracefully returns [] if table doesn't exist yet) - Wire BlueprintTypesPreset into production server (graphile.ts) - Wire BlueprintTypesPreset into build-schema (codegen path) - Wire BlueprintTypesPreset into query executor - Wire BlueprintTypesPreset into explorer server Builds on PR #857 which added the plugin, codegen support, and tests.
1 parent 3d040b1 commit 7addba2

File tree

7 files changed

+98
-12
lines changed

7 files changed

+98
-12
lines changed

graphile/graphile-schema/src/build-schema.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import deepmerge from 'deepmerge'
22
import { printSchema } from 'graphql'
3-
import { ConstructivePreset, makePgService } from 'graphile-settings'
3+
import { ConstructivePreset, makePgService, fetchNodeTypeRegistry, BlueprintTypesPreset } from 'graphile-settings'
44
import { makeSchema } from 'graphile-build'
55
import { buildConnectionString } from 'pg-cache'
66
import { getPgEnvOptions } from 'pg-env'
@@ -25,8 +25,10 @@ export async function buildSchemaSDL(opts: BuildSchemaOptions): Promise<string>
2525
config.database,
2626
)
2727

28+
const nodeTypes = await fetchNodeTypeRegistry(connectionString);
29+
2830
const basePreset: GraphileConfig.Preset = {
29-
extends: [ConstructivePreset],
31+
extends: [ConstructivePreset, BlueprintTypesPreset(nodeTypes)],
3032
pgServices: [
3133
makePgService({
3234
connectionString,
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/**
2+
* Fetch Node Type Registry
3+
*
4+
* Queries the node_type_registry table from the database at schema build time.
5+
* Used to populate the BlueprintTypesPlugin with real node type entries
6+
* so that @oneOf types reflect the actual registered node types.
7+
*
8+
* The query uses the metaschema_public schema and selects all columns
9+
* needed by the plugin: name, slug, category, display_name, description,
10+
* parameter_schema, and tags.
11+
*/
12+
13+
import { Pool } from 'pg';
14+
import type { NodeTypeRegistryEntry } from './plugin';
15+
16+
/**
17+
* Fetch all node_type_registry entries from the database.
18+
*
19+
* Connects using the provided connection string, queries
20+
* metaschema_public.node_type_registry, and returns the rows
21+
* as NodeTypeRegistryEntry[].
22+
*
23+
* If the table doesn't exist or the query fails (e.g., the database
24+
* hasn't been migrated yet), returns an empty array and logs a warning.
25+
*
26+
* @param connectionString - PostgreSQL connection string
27+
* @returns Array of node type registry entries
28+
*/
29+
export async function fetchNodeTypeRegistry(
30+
connectionString: string,
31+
): Promise<NodeTypeRegistryEntry[]> {
32+
const pool = new Pool({ connectionString, max: 1 });
33+
34+
try {
35+
const result = await pool.query<NodeTypeRegistryEntry>(`
36+
SELECT
37+
name,
38+
slug,
39+
category,
40+
COALESCE(display_name, name) as display_name,
41+
COALESCE(description, '') as description,
42+
COALESCE(parameter_schema, '{}'::jsonb) as parameter_schema,
43+
COALESCE(tags, '{}') as tags
44+
FROM metaschema_public.node_type_registry
45+
ORDER BY category, name
46+
`);
47+
48+
return result.rows;
49+
} catch (error: unknown) {
50+
// If the table doesn't exist yet (not migrated), return empty gracefully
51+
const pgError = error as { code?: string; message?: string };
52+
if (pgError.code === '42P01') {
53+
// 42P01 = undefined_table
54+
// This is expected when the database hasn't been migrated with the metaschema package
55+
return [];
56+
}
57+
58+
// For other errors, log a warning and return empty
59+
// This ensures the schema build doesn't fail just because
60+
// node_type_registry isn't accessible
61+
console.warn(
62+
'[BlueprintTypesPlugin] Failed to fetch node_type_registry:',
63+
pgError.message ?? String(error),
64+
);
65+
return [];
66+
} finally {
67+
await pool.end();
68+
}
69+
}

graphile/graphile-settings/src/plugins/blueprint-types/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ export {
99
BlueprintTypesPreset,
1010
} from './plugin';
1111
export type { NodeTypeRegistryEntry } from './plugin';
12+
export { fetchNodeTypeRegistry } from './fetch-registry';

graphile/graphile-settings/src/plugins/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,5 +115,6 @@ export type {
115115
export {
116116
createBlueprintTypesPlugin,
117117
BlueprintTypesPreset,
118+
fetchNodeTypeRegistry,
118119
} from './blueprint-types';
119120
export type { NodeTypeRegistryEntry } from './blueprint-types';

graphql/explorer/src/server.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { cors, healthz, poweredBy } from '@pgpmjs/server-utils';
44
import { middleware as parseDomains } from '@constructive-io/url-domains';
55
import express, { Express, NextFunction, Request, Response } from 'express';
66
import { createGraphileInstance, graphileCache, GraphileCacheEntry } from 'graphile-cache';
7-
import { makePgService } from 'graphile-settings';
7+
import { makePgService, fetchNodeTypeRegistry, BlueprintTypesPreset } from 'graphile-settings';
88
import type { GraphileConfig } from 'graphile-config';
99
import { buildConnectionString, getPgPool } from 'pg-cache';
1010
import { getPgEnvOptions } from 'pg-env';
@@ -40,9 +40,12 @@ export const GraphQLExplorer = (rawOpts: ConstructiveOptions = {}): Express => {
4040
pgConfig.database
4141
);
4242

43+
const nodeTypes = await fetchNodeTypeRegistry(connectionString);
44+
4345
const basePreset = getGraphilePreset(opts);
4446
const preset: GraphileConfig.Preset = {
4547
...basePreset,
48+
extends: [...(basePreset.extends ?? []), BlueprintTypesPreset(nodeTypes)],
4649
pgServices: [
4750
makePgService({ connectionString, schemas: [schemaname] }),
4851
],

graphql/query/src/executor.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
import { execute } from 'grafast';
99
import { postgraphile, type PostGraphileInstance } from 'postgraphile';
10-
import { ConstructivePreset, makePgService } from 'graphile-settings';
10+
import { ConstructivePreset, makePgService, fetchNodeTypeRegistry, BlueprintTypesPreset } from 'graphile-settings';
1111
import { withPgClientFromPgService } from 'graphile-build-pg';
1212
import type {
1313
DocumentNode,
@@ -111,11 +111,14 @@ export class QueryExecutor {
111111
schemas: this.options.schemas,
112112
});
113113

114+
// Fetch node type registry for blueprint @oneOf types
115+
const nodeTypes = await fetchNodeTypeRegistry(this.options.connectionString);
116+
114117
// Note: Using 'as unknown as' to bypass strict type checking
115118
// because GraphileConfig.Preset doesn't include pgServices in its type definition
116119
// but postgraphile() accepts it at runtime
117120
const preset = {
118-
extends: [ConstructivePreset],
121+
extends: [ConstructivePreset, BlueprintTypesPreset(nodeTypes)],
119122
pgServices: [pgService],
120123
grafast: {
121124
context: () => ({

graphql/server/src/middleware/graphile.ts

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import type { NextFunction, Request, RequestHandler, Response } from 'express';
66
import type { GraphQLError, GraphQLFormattedError } from 'grafast/graphql';
77
import { createGraphileInstance, type GraphileCacheEntry, graphileCache } from 'graphile-cache';
88
import type { GraphileConfig } from 'graphile-config';
9-
import { ConstructivePreset, makePgService } from 'graphile-settings';
9+
import { ConstructivePreset, makePgService, fetchNodeTypeRegistry, BlueprintTypesPreset } from 'graphile-settings';
1010
import { buildConnectionString } from 'pg-cache';
1111
import { getPgEnvOptions } from 'pg-env';
1212
import './types'; // for Request type
@@ -124,15 +124,21 @@ const log = new Logger('graphile');
124124
const reqLabel = (req: Request): string => (req.requestId ? `[${req.requestId}]` : '[req]');
125125

126126
/**
127-
* Build a PostGraphile v5 preset for a tenant
127+
* Build a PostGraphile v5 preset for a tenant.
128+
*
129+
* Queries node_type_registry at build time to generate @oneOf typed
130+
* blueprint input types with SuperCase node type names as discriminant keys.
128131
*/
129-
const buildPreset = (
132+
const buildPreset = async (
130133
connectionString: string,
131134
schemas: string[],
132135
anonRole: string,
133136
roleName: string,
134-
): GraphileConfig.Preset => ({
135-
extends: [ConstructivePreset],
137+
): Promise<GraphileConfig.Preset> => {
138+
const nodeTypes = await fetchNodeTypeRegistry(connectionString);
139+
140+
return {
141+
extends: [ConstructivePreset, BlueprintTypesPreset(nodeTypes)],
136142
pgServices: [
137143
makePgService({
138144
connectionString,
@@ -187,7 +193,8 @@ const buildPreset = (
187193
};
188194
},
189195
},
190-
});
196+
};
197+
};
191198

192199
export const graphile = (opts: ConstructiveOptions): RequestHandler => {
193200
const observabilityEnabled = isGraphqlObservabilityEnabled(opts.server?.host);
@@ -270,7 +277,7 @@ export const graphile = (opts: ConstructiveOptions): RequestHandler => {
270277
);
271278

272279
// Create promise and store in in-flight map BEFORE try block
273-
const preset = buildPreset(connectionString, schema || [], anonRole, roleName);
280+
const preset = await buildPreset(connectionString, schema || [], anonRole, roleName);
274281
const creationPromise = observeGraphileBuild(
275282
{
276283
cacheKey: key,

0 commit comments

Comments
 (0)