constructive-graphql-codegen
Constructive GraphQL Codegen
Generate type-safe React Query hooks, Prisma-like ORM client, or inquirerer-based CLI from GraphQL schema files, endpoints, databases, or PGPM modules. Also generates documentation in multiple formats.
When to Apply
Use this skill when:
- Setting up GraphQL code generation for a PostGraphile backend
- User asks to generate hooks, ORM, CLI, or type-safe GraphQL client
- Exporting a GraphQL schema from a database or endpoint
- Generating documentation (README, AGENTS.md, skill files, MCP tool definitions)
- Implementing features that need to fetch or mutate data
- Using previously generated hooks, ORM, or CLI code
- Regenerating code after schema changes
Important: Always prefer generated code over raw GraphQL queries or SQL.
Installation
pnpm add @constructive-io/graphql-codegen
Programmatic API
The generate() function is the primary entry point. All code generation goes through this function -- the CLI and config files are thin wrappers around it.
Basic Usage
import { generate } from '@constructive-io/graphql-codegen';
// Generate from a schema file
await generate({
schemaFile: './schemas/public.graphql',
output: './src/generated',
reactQuery: true,
orm: true,
});
// Generate from an endpoint
await generate({
endpoint: 'https://api.example.com/graphql',
output: './src/generated',
reactQuery: true,
orm: true,
});
// Generate from a database
await generate({
db: { schemas: ['public', 'app_public'] },
output: './src/generated',
reactQuery: true,
});
// Generate from a PGPM module
await generate({
db: {
pgpm: { modulePath: './packages/my-module' },
schemas: ['app_public'],
},
output: './src/generated',
orm: true,
});
Schema Sources
The codegen supports multiple schema sources. Choose the one that fits your workflow:
| Source | Config Key | Best For |
|---|---|---|
| Schema file | schemaFile: './schema.graphql' |
Simple projects, deterministic builds |
| Schema directory | schemaDir: './schemas' |
Multi-target from .graphql files |
| PGPM module (path) | db.pgpm.modulePath |
Schema from a pgpm module |
| PGPM workspace | db.pgpm.workspacePath + moduleName |
Schema from a pgpm workspace |
| Database | db.schemas or db.apiNames |
Live database introspection |
| Endpoint | endpoint |
Running GraphQL server |
// From schema file
await generate({
schemaFile: './schema.graphql',
output: './generated',
orm: true,
});
// From endpoint with auth
await generate({
endpoint: 'https://api.example.com/graphql',
headers: { Authorization: 'Bearer token' },
reactQuery: true,
});
// From database (auto-discover via API names)
await generate({
db: { apiNames: ['my_api'] },
orm: true,
});
// From PGPM module (creates ephemeral DB, deploys, introspects, tears down)
await generate({
db: {
pgpm: { modulePath: './packages/my-module' },
schemas: ['public'],
},
reactQuery: true,
});
// From PGPM workspace + module name
await generate({
db: {
pgpm: {
workspacePath: '.',
moduleName: 'my-module',
},
schemas: ['app_public'],
},
orm: true,
});
Schema Export
Export a schema to a .graphql SDL file without generating code. Useful for creating portable, version-controllable schema artifacts:
import { generate } from '@constructive-io/graphql-codegen';
// Export from database
await generate({
db: { schemas: ['public'] },
schemaOnly: true,
schemaOnlyOutput: './schemas',
schemaOnlyFilename: 'public.graphql',
});
// Export from PGPM module
await generate({
db: {
pgpm: { modulePath: './packages/my-module' },
schemas: ['app_public'],
},
schemaOnly: true,
schemaOnlyOutput: './schemas',
schemaOnlyFilename: 'app_public.graphql',
});
// Export from endpoint
await generate({
endpoint: 'https://api.example.com/graphql',
schemaOnly: true,
schemaOnlyOutput: './schemas',
});
Multi-Target Generation
Use generateMulti() for generating from multiple schema sources in a single run:
import { generate, generateMulti } from '@constructive-io/graphql-codegen';
// Option 1: Use schemaDir (auto-expands .graphql files to targets)
// Given schemas/public.graphql and schemas/admin.graphql:
await generate({
schemaDir: './schemas',
output: './generated',
reactQuery: true,
orm: true,
});
// Produces: generated/public/{hooks,orm}/, generated/admin/{hooks,orm}/
// Option 2: Explicit multi-target with generateMulti()
await generateMulti({
configs: {
public: {
schemaFile: './schemas/public.graphql',
output: './generated/public',
reactQuery: true,
},
admin: {
schemaFile: './schemas/admin.graphql',
output: './generated/admin',
orm: true,
},
},
});
// Option 3: Multiple API names auto-expand
await generate({
db: { apiNames: ['public', 'admin'] },
output: './generated',
orm: true,
});
// Each API name becomes a target: generated/public/, generated/admin/
When multiple targets share the same PGPM module, the codegen automatically deduplicates ephemeral database creation.
GenerateOptions
interface GenerateOptions {
// Schema source (choose one)
endpoint?: string;
schemaFile?: string;
schemaDir?: string; // Directory of .graphql files -- auto-expands to multi-target
db?: {
config?: { host, port, database, user, password };
schemas?: string[];
apiNames?: string[]; // Auto-discover schemas from services_public.api_schemas
pgpm?: { modulePath, workspacePath, moduleName };
keepDb?: boolean; // Keep ephemeral DB after introspection (debugging)
};
// Output
output?: string; // Default: './generated/graphql'
// Generators
reactQuery?: boolean; // Default: false
orm?: boolean; // Default: false
cli?: CliConfig | boolean; // Default: false
// Schema export (instead of code generation)
schemaOnly?: boolean;
schemaOnlyOutput?: string;
schemaOnlyFilename?: string; // Default: 'schema.graphql'
// Documentation (generated alongside code)
docs?: DocsConfig | boolean; // Default: { readme: true, agents: true, mcp: false, skills: false }
// Node.js HTTP adapter (auto-enabled when cli is true)
nodeHttpAdapter?: boolean; // Default: false
// Filtering
tables?: { include?, exclude?, systemExclude? };
queries?: { include?, exclude?, systemExclude? };
mutations?: { include?, exclude?, systemExclude? };
excludeFields?: string[];
// Authentication
headers?: Record<string, string>;
authorization?: string; // Convenience for Authorization header
// Options
verbose?: boolean;
dryRun?: boolean;
skipCustomOperations?: boolean;
}
Build Script Example
// scripts/codegen.ts
import { generate } from '@constructive-io/graphql-codegen';
async function main() {
const result = await generate({
schemaFile: './schemas/public.graphql',
output: './src/generated',
reactQuery: true,
orm: true,
tables: {
include: ['User', 'Post', 'Comment'],
},
});
if (!result.success) {
console.error('Codegen failed:', result.message);
process.exit(1);
}
console.log(result.message);
}
main();
Documentation Generation
await generate({
schemaFile: './schemas/public.graphql',
output: './generated',
orm: true,
docs: true, // Enable all doc formats
// OR configure individually:
docs: {
readme: true, // README.md
agents: true, // AGENTS.md (structured for LLM consumption)
mcp: false, // mcp.json (MCP tool definitions)
skills: true, // skills/ (per-command .md skill files)
},
});
Node.js HTTP Adapter
For Node.js apps using subdomain-based routing (e.g., auth.localhost:3000):
await generate({
endpoint: 'http://api.localhost:3000/graphql',
output: './generated',
orm: true,
nodeHttpAdapter: true, // Generates node-fetch.ts with NodeHttpAdapter
});
See references/node-http-adapter.md for usage details.
Using Generated Hooks
Configure Client (once at app startup)
import { configure } from '@/generated/hooks';
configure({
endpoint: process.env.NEXT_PUBLIC_GRAPHQL_URL!,
headers: { Authorization: `Bearer ${getToken()}` },
});
Query Data
import { useUsersQuery } from '@/generated/hooks';
function UserList() {
const { data, isLoading } = useUsersQuery({
first: 10,
filter: { role: { eq: 'ADMIN' } },
});
if (isLoading) return <Spinner />;
return <ul>{data?.users?.nodes.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
}
Mutate Data
import { useCreateUserMutation } from '@/generated/hooks';
function CreateUser() {
const createUser = useCreateUserMutation();
return (
<button onClick={() => createUser.mutate({ input: { name: 'John' } })}>
Create
</button>
);
}
See references/hooks-patterns.md and references/hooks-output.md for advanced patterns.
Using Generated ORM
Create Client
import { createClient } from '@/generated/orm';
export const db = createClient({
endpoint: process.env.GRAPHQL_URL!,
headers: { Authorization: `Bearer ${process.env.API_TOKEN}` },
});
Query Data
const users = await db.user.findMany({
select: { id: true, name: true, email: true },
filter: { role: { eq: 'ADMIN' } },
first: 10,
}).execute().unwrap();
Relations
const posts = await db.post.findMany({
select: {
id: true,
title: true,
author: { select: { id: true, name: true } },
},
}).execute().unwrap();
// posts[0].author.name is fully typed
Error Handling
const result = await db.user.findOne({ id: '123' }).execute();
if (result.ok) {
console.log(result.value.name);
} else {
console.error(result.error.message);
}
// Or use helpers
const user = await db.user.findOne({ id }).execute().unwrap(); // throws on error
const user = await db.user.findOne({ id }).execute().unwrapOr(defaultUser);
See references/orm-patterns.md and references/orm-output.md for advanced patterns.
Using Generated CLI
When cli: true is set, codegen generates inquirerer-based CLI commands to {output}/cli/.
await generate({
schemaFile: './schemas/public.graphql',
output: './generated',
cli: true,
// OR with options:
cli: {
toolName: 'myapp',
entryPoint: true,
builtinNames: {
auth: 'credentials',
context: 'env',
},
},
});
When cli: true, nodeHttpAdapter is auto-enabled.
Running the CLI
If entryPoint: true is set:
npx ts-node generated/cli/index.ts
Or integrate the command map into your own CLI:
import { commands } from './generated/cli/command-map';
import { Inquirerer } from 'inquirerer';
const prompter = new Inquirerer();
await commands.users.list(argv, prompter);
The CLI includes built-in infrastructure commands:
- auth (or
credentialsif name collides) -- manage API credentials - context (or
envif name collides) -- manage endpoint and auth context
Filter Syntax
// Comparison
filter: { age: { eq: 25 } }
filter: { age: { gte: 18, lt: 65 } }
filter: { status: { in: ['ACTIVE', 'PENDING'] } }
// String
filter: { name: { contains: 'john' } }
filter: { email: { endsWith: '.com' } }
// Logical
filter: {
OR: [
{ role: { eq: 'ADMIN' } },
{ role: { eq: 'MODERATOR' } },
],
}
Query Key Factory (React Query)
Generated hooks include a centralized query key factory for type-safe cache management:
import { userKeys, invalidate, remove } from '@/generated/hooks';
import { useQueryClient } from '@tanstack/react-query';
const queryClient = useQueryClient();
// Invalidate queries (triggers refetch)
invalidate.user.all(queryClient);
invalidate.user.lists(queryClient);
invalidate.user.detail(queryClient, id);
// Remove from cache (for delete operations)
remove.user(queryClient, userId);
See references/query-keys.md for details.
Troubleshooting
| Issue | Solution |
|---|---|
| No hooks generated | Add reactQuery: true |
| No CLI generated | Add cli: true |
| Schema not accessible | Verify endpoint URL and auth headers |
Missing _meta query |
Ensure PostGraphile v5+ with Meta plugin |
| Type errors after regeneration | Delete output directory and regenerate |
| Import errors | Verify generated code exists and paths match |
| Auth errors at runtime | Check configure() headers are set |
| Localhost fetch errors (Node.js) | Enable nodeHttpAdapter: true |
| No skill files generated | Set docs: { skills: true } |
| Schema export produces empty file | Verify database/endpoint has tables in the specified schemas |
schemaDir generates nothing |
Ensure directory contains .graphql files (not .gql or other extensions) |
References
All references are in references/.
Workflow Guides
Each major codegen workflow has a dedicated reference with full examples and options:
generate-schemas.md-- Export GraphQL schemas to.graphqlfiles (schema export workflow)generate-sdk.md-- Generate React Query hooks and/or ORM client (primary SDK workflow)generate-cli.md-- Generate inquirerer-based CLI with CRUD commandsgenerate-node.md-- Generate NodeHttpAdapter for*.localhostsubdomain routing
Deep-Dive References
- Using generated code:
hooks-patterns.md,hooks-output.md,orm-patterns.md,orm-output.md - Error handling and relations:
error-handling.md,relations.md - Query key factory and cache management:
query-keys.md - Node.js HTTP adapter (manual):
node-http-adapter.md - CLI flags:
cli-reference.md - Configuration file (
defineConfig):config-reference.md