Skip to content

Commit d042f9c

Browse files
committed
feat(graphql-codegen): add PGPM module schema source support
- Add ephemeral database utilities to pgsql-client (createEphemeralDb) - Add PgpmModuleSchemaSource class for introspecting PGPM modules - Support two input modes: pgpmModulePath and pgpmWorkspacePath + pgpmModuleName - Add keepDb option for debugging ephemeral databases - Update config resolver and generate commands to handle PGPM options - Add @pgpmjs/core, pgsql-client, and pgsql-seed dependencies to graphql-codegen
1 parent 3130c67 commit d042f9c

File tree

9 files changed

+675
-25
lines changed

9 files changed

+675
-25
lines changed

graphql/codegen/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
"@constructive-io/graphql-server": "workspace:^",
6060
"@constructive-io/graphql-types": "workspace:^",
6161
"@inquirerer/utils": "^3.2.0",
62+
"@pgpmjs/core": "workspace:^",
6263
"ajv": "^8.17.1",
6364
"deepmerge": "^4.3.1",
6465
"find-and-require-package-json": "^0.9.0",
@@ -67,6 +68,8 @@
6768
"inflekt": "^0.3.0",
6869
"inquirerer": "^4.4.0",
6970
"jiti": "^2.6.1",
71+
"pgsql-client": "workspace:^",
72+
"pgsql-seed": "workspace:^",
7073
"prettier": "^3.7.4"
7174
},
7275
"peerDependencies": {

graphql/codegen/src/cli/commands/generate-orm.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,14 +131,22 @@ async function generateOrmForTarget(
131131
const formatMessage = (message: string) =>
132132
isMultiTarget ? `Target "${target.name}": ${message}` : message;
133133

134-
// Extract database options if present (attached by config resolver for database mode)
134+
// Extract extended options if present (attached by config resolver)
135135
const database = (config as any).database as string | undefined;
136136
const schemas = (config as any).schemas as string[] | undefined;
137+
const pgpmModulePath = (config as any).pgpmModulePath as string | undefined;
138+
const pgpmWorkspacePath = (config as any).pgpmWorkspacePath as string | undefined;
139+
const pgpmModuleName = (config as any).pgpmModuleName as string | undefined;
140+
const keepDb = (config as any).keepDb as boolean | undefined;
137141

138142
if (isMultiTarget) {
139143
console.log(`\nTarget "${target.name}"`);
140144
let sourceLabel: string;
141-
if (database) {
145+
if (pgpmModulePath) {
146+
sourceLabel = `pgpm module: ${pgpmModulePath} (schemas: ${(schemas ?? ['public']).join(', ')})`;
147+
} else if (pgpmWorkspacePath && pgpmModuleName) {
148+
sourceLabel = `pgpm workspace: ${pgpmWorkspacePath}, module: ${pgpmModuleName} (schemas: ${(schemas ?? ['public']).join(', ')})`;
149+
} else if (database) {
142150
sourceLabel = `database: ${database} (schemas: ${(schemas ?? ['public']).join(', ')})`;
143151
} else if (config.schema) {
144152
sourceLabel = `schema: ${config.schema}`;
@@ -154,6 +162,9 @@ async function generateOrmForTarget(
154162
endpoint: config.endpoint || undefined,
155163
schema: config.schema || undefined,
156164
database,
165+
pgpmModulePath,
166+
pgpmWorkspacePath,
167+
pgpmModuleName,
157168
});
158169
if (!sourceValidation.valid) {
159170
return {
@@ -168,7 +179,11 @@ async function generateOrmForTarget(
168179
endpoint: config.endpoint || undefined,
169180
schema: config.schema || undefined,
170181
database,
182+
pgpmModulePath,
183+
pgpmWorkspacePath,
184+
pgpmModuleName,
171185
schemas,
186+
keepDb,
172187
authorization: options.authorization || config.headers['Authorization'],
173188
headers: config.headers,
174189
});

graphql/codegen/src/cli/commands/generate.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,14 +130,22 @@ async function generateForTarget(
130130
const formatMessage = (message: string) =>
131131
isMultiTarget ? `Target "${target.name}": ${message}` : message;
132132

133-
// Extract database options if present (attached by config resolver for database mode)
133+
// Extract extended options if present (attached by config resolver)
134134
const database = (config as any).database as string | undefined;
135135
const schemas = (config as any).schemas as string[] | undefined;
136+
const pgpmModulePath = (config as any).pgpmModulePath as string | undefined;
137+
const pgpmWorkspacePath = (config as any).pgpmWorkspacePath as string | undefined;
138+
const pgpmModuleName = (config as any).pgpmModuleName as string | undefined;
139+
const keepDb = (config as any).keepDb as boolean | undefined;
136140

137141
if (isMultiTarget) {
138142
console.log(`\nTarget "${target.name}"`);
139143
let sourceLabel: string;
140-
if (database) {
144+
if (pgpmModulePath) {
145+
sourceLabel = `pgpm module: ${pgpmModulePath} (schemas: ${(schemas ?? ['public']).join(', ')})`;
146+
} else if (pgpmWorkspacePath && pgpmModuleName) {
147+
sourceLabel = `pgpm workspace: ${pgpmWorkspacePath}, module: ${pgpmModuleName} (schemas: ${(schemas ?? ['public']).join(', ')})`;
148+
} else if (database) {
141149
sourceLabel = `database: ${database} (schemas: ${(schemas ?? ['public']).join(', ')})`;
142150
} else if (config.schema) {
143151
sourceLabel = `schema: ${config.schema}`;
@@ -153,6 +161,9 @@ async function generateForTarget(
153161
endpoint: config.endpoint || undefined,
154162
schema: config.schema || undefined,
155163
database,
164+
pgpmModulePath,
165+
pgpmWorkspacePath,
166+
pgpmModuleName,
156167
});
157168
if (!sourceValidation.valid) {
158169
return {
@@ -167,7 +178,11 @@ async function generateForTarget(
167178
endpoint: config.endpoint || undefined,
168179
schema: config.schema || undefined,
169180
database,
181+
pgpmModulePath,
182+
pgpmWorkspacePath,
183+
pgpmModuleName,
170184
schemas,
185+
keepDb,
171186
authorization: options.authorization || config.headers['Authorization'],
172187
headers: config.headers,
173188
});

graphql/codegen/src/core/config/resolver.ts

Lines changed: 94 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,16 @@ export interface ConfigOverrideOptions {
2828
schema?: string;
2929
/** Database name or connection string (for database introspection) */
3030
database?: string;
31-
/** PostgreSQL schemas to include (for database mode) */
31+
/** Path to a PGPM module directory (for module introspection) */
32+
pgpmModulePath?: string;
33+
/** Path to a PGPM workspace directory (used with pgpmModuleName) */
34+
pgpmWorkspacePath?: string;
35+
/** Name of the module within the workspace (used with pgpmWorkspacePath) */
36+
pgpmModuleName?: string;
37+
/** PostgreSQL schemas to include (for database and pgpm module modes) */
3238
schemas?: string[];
39+
/** Keep the ephemeral database after introspection (for debugging, pgpm module mode only) */
40+
keepDb?: boolean;
3341
/** Output directory (overrides config) */
3442
output?: string;
3543
}
@@ -44,31 +52,74 @@ export interface LoadConfigResult {
4452
error?: string;
4553
}
4654

55+
/**
56+
* Extended overrides type that includes database and PGPM module options
57+
*/
58+
export interface ExtendedTargetOverrides extends GraphQLSDKConfigTarget {
59+
database?: string;
60+
schemas?: string[];
61+
pgpmModulePath?: string;
62+
pgpmWorkspacePath?: string;
63+
pgpmModuleName?: string;
64+
keepDb?: boolean;
65+
}
66+
4767
/**
4868
* Build target overrides from options
4969
*/
5070
export function buildTargetOverrides(
5171
options: ConfigOverrideOptions
52-
): GraphQLSDKConfigTarget & { database?: string; schemas?: string[] } {
53-
const overrides: GraphQLSDKConfigTarget & { database?: string; schemas?: string[] } = {};
72+
): ExtendedTargetOverrides {
73+
const overrides: ExtendedTargetOverrides = {};
5474

5575
if (options.endpoint) {
5676
overrides.endpoint = options.endpoint;
5777
overrides.schema = undefined;
5878
overrides.database = undefined;
79+
overrides.pgpmModulePath = undefined;
80+
overrides.pgpmWorkspacePath = undefined;
81+
overrides.pgpmModuleName = undefined;
5982
}
6083

6184
if (options.schema) {
6285
overrides.schema = options.schema;
6386
overrides.endpoint = undefined;
6487
overrides.database = undefined;
88+
overrides.pgpmModulePath = undefined;
89+
overrides.pgpmWorkspacePath = undefined;
90+
overrides.pgpmModuleName = undefined;
6591
}
6692

6793
if (options.database) {
6894
overrides.database = options.database;
6995
overrides.schemas = options.schemas;
7096
overrides.endpoint = undefined;
7197
overrides.schema = undefined;
98+
overrides.pgpmModulePath = undefined;
99+
overrides.pgpmWorkspacePath = undefined;
100+
overrides.pgpmModuleName = undefined;
101+
}
102+
103+
if (options.pgpmModulePath) {
104+
overrides.pgpmModulePath = options.pgpmModulePath;
105+
overrides.schemas = options.schemas;
106+
overrides.keepDb = options.keepDb;
107+
overrides.endpoint = undefined;
108+
overrides.schema = undefined;
109+
overrides.database = undefined;
110+
overrides.pgpmWorkspacePath = undefined;
111+
overrides.pgpmModuleName = undefined;
112+
}
113+
114+
if (options.pgpmWorkspacePath && options.pgpmModuleName) {
115+
overrides.pgpmWorkspacePath = options.pgpmWorkspacePath;
116+
overrides.pgpmModuleName = options.pgpmModuleName;
117+
overrides.schemas = options.schemas;
118+
overrides.keepDb = options.keepDb;
119+
overrides.endpoint = undefined;
120+
overrides.schema = undefined;
121+
overrides.database = undefined;
122+
overrides.pgpmModulePath = undefined;
72123
}
73124

74125
if (options.output) {
@@ -90,12 +141,22 @@ export function buildTargetOverrides(
90141
export async function loadAndResolveConfig(
91142
options: ConfigOverrideOptions
92143
): Promise<LoadConfigResult> {
144+
// Check for pgpm workspace mode (requires both pgpmWorkspacePath and pgpmModuleName)
145+
const hasPgpmWorkspace = options.pgpmWorkspacePath && options.pgpmModuleName;
146+
93147
// Validate that at most one source is specified
94-
const sources = [options.endpoint, options.schema, options.database].filter(Boolean);
148+
const sources = [
149+
options.endpoint,
150+
options.schema,
151+
options.database,
152+
options.pgpmModulePath,
153+
hasPgpmWorkspace,
154+
].filter(Boolean);
95155
if (sources.length > 1) {
96156
return {
97157
success: false,
98-
error: 'Multiple sources specified. Use only one of: endpoint, schema, or database.',
158+
error:
159+
'Multiple sources specified. Use only one of: endpoint, schema, database, pgpmModulePath, or pgpmWorkspacePath + pgpmModuleName.',
99160
};
100161
}
101162

@@ -195,7 +256,7 @@ function resolveMultiTargetConfig(
195256
function resolveSingleTargetConfig(
196257
baseConfig: GraphQLSDKConfigTarget,
197258
options: ConfigOverrideOptions,
198-
overrides: GraphQLSDKConfigTarget
259+
overrides: ExtendedTargetOverrides
199260
): LoadConfigResult {
200261
if (options.target) {
201262
return {
@@ -207,23 +268,42 @@ function resolveSingleTargetConfig(
207268

208269
const mergedConfig = mergeConfig(baseConfig, overrides);
209270

210-
// Check if we have a source (endpoint, schema, or database)
211-
const hasSource = mergedConfig.endpoint || mergedConfig.schema || (overrides as any).database;
271+
// Check if we have a source (endpoint, schema, database, or pgpm module)
272+
const hasSource =
273+
mergedConfig.endpoint ||
274+
mergedConfig.schema ||
275+
overrides.database ||
276+
overrides.pgpmModulePath ||
277+
(overrides.pgpmWorkspacePath && overrides.pgpmModuleName);
278+
212279
if (!hasSource) {
213280
return {
214281
success: false,
215282
error:
216-
'No source specified. Use --endpoint, --schema, or --database, or create a config file with "graphql-codegen init".',
283+
'No source specified. Use --endpoint, --schema, --database, --pgpmModulePath, or --pgpmWorkspacePath + --pgpmModuleName, or create a config file with "graphql-codegen init".',
217284
};
218285
}
219286

220287
// For database mode, we need to pass the database info through to the resolved config
221288
const resolvedConfig = resolveConfig(mergedConfig);
222-
223-
// Attach database options if present (they're not part of the standard config type)
224-
if ((overrides as any).database) {
225-
(resolvedConfig as any).database = (overrides as any).database;
226-
(resolvedConfig as any).schemas = (overrides as any).schemas;
289+
290+
// Attach extended options if present (they're not part of the standard config type)
291+
if (overrides.database) {
292+
(resolvedConfig as any).database = overrides.database;
293+
(resolvedConfig as any).schemas = overrides.schemas;
294+
}
295+
296+
if (overrides.pgpmModulePath) {
297+
(resolvedConfig as any).pgpmModulePath = overrides.pgpmModulePath;
298+
(resolvedConfig as any).schemas = overrides.schemas;
299+
(resolvedConfig as any).keepDb = overrides.keepDb;
300+
}
301+
302+
if (overrides.pgpmWorkspacePath && overrides.pgpmModuleName) {
303+
(resolvedConfig as any).pgpmWorkspacePath = overrides.pgpmWorkspacePath;
304+
(resolvedConfig as any).pgpmModuleName = overrides.pgpmModuleName;
305+
(resolvedConfig as any).schemas = overrides.schemas;
306+
(resolvedConfig as any).keepDb = overrides.keepDb;
227307
}
228308

229309
return {

0 commit comments

Comments
 (0)