Package Exports
- @constructive-io/graphql-query
- @constructive-io/graphql-query/ast.js
- @constructive-io/graphql-query/client/index.js
- @constructive-io/graphql-query/custom-ast.js
- @constructive-io/graphql-query/esm/index.js
- @constructive-io/graphql-query/generators/index.js
- @constructive-io/graphql-query/index.js
- @constructive-io/graphql-query/meta-object/convert.js
- @constructive-io/graphql-query/meta-object/validate.js
- @constructive-io/graphql-query/query-builder.js
- @constructive-io/graphql-query/types/core.js
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@constructive-io/graphql-query) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
@constructive-io/graphql-query
Browser-safe GraphQL query generation core for PostGraphile schemas. Build type-safe queries, mutations, and introspection pipelines — at runtime or build time.
Installation
npm install @constructive-io/graphql-queryOverview
This package is the canonical source for PostGraphile query generation logic. It provides:
- Query generators —
buildSelect,buildFindOne,buildCountfor read operations - Mutation generators —
buildPostGraphileCreate,buildPostGraphileUpdate,buildPostGraphileDelete - Introspection pipeline —
inferTablesFromIntrospectionto convert a GraphQL schema intoCleanTablemetadata - AST builders — low-level
getAll,getMany,getOne,createOne,patchOne,deleteOne - Client utilities —
TypedDocumentString,execute,DataErrorfor type-safe execution and error handling - Naming helpers — server-aware inflection functions that respect PostGraphile's schema naming
All modules are browser-safe (no Node.js APIs). @constructive-io/graphql-codegen depends on this package for the core logic and adds Node.js-only features (CLI, file output, watch mode).
Quick Start: Generate Queries from a Schema
The most common workflow: introspect a GraphQL schema, then generate queries and mutations for any table.
Step 1 — Introspect
import {
inferTablesFromIntrospection,
SCHEMA_INTROSPECTION_QUERY,
} from '@constructive-io/graphql-query';
// Fetch introspection from any GraphQL endpoint
const response = await fetch('/graphql', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query: SCHEMA_INTROSPECTION_QUERY }),
});
const { data } = await response.json();
// Convert to CleanTable metadata
const tables = inferTablesFromIntrospection(data);
// tables = [{ name: 'User', fields: [...], relations: {...}, query: {...}, inflection: {...} }, ...]Step 2 — Generate a SELECT query
import { buildSelect } from '@constructive-io/graphql-query';
const userTable = tables.find(t => t.name === 'User')!;
const query = buildSelect(userTable, tables);
console.log(query.toString());Generated GraphQL:
query getUsersQuery(
$first: Int
$last: Int
$after: Cursor
$before: Cursor
$offset: Int
$condition: UserCondition
$filter: UserFilter
$orderBy: [UsersOrderBy!]
) {
users(
first: $first
last: $last
offset: $offset
after: $after
before: $before
condition: $condition
filter: $filter
orderBy: $orderBy
) {
totalCount
pageInfo {
hasNextPage
hasPreviousPage
endCursor
startCursor
}
nodes {
id
name
email
createdAt
}
}
}Step 3 — Generate mutations
import {
buildPostGraphileCreate,
buildPostGraphileUpdate,
buildPostGraphileDelete,
} from '@constructive-io/graphql-query';
const createQuery = buildPostGraphileCreate(userTable, tables);
const updateQuery = buildPostGraphileUpdate(userTable, tables);
const deleteQuery = buildPostGraphileDelete(userTable, tables);Generated CREATE mutation:
mutation createUserMutation($input: CreateUserInput!) {
createUser(input: $input) {
user {
id
name
email
createdAt
}
}
}Generated UPDATE mutation:
mutation updateUserMutation($input: UpdateUserInput!) {
updateUser(input: $input) {
user {
id
name
email
createdAt
}
}
}Generated DELETE mutation:
mutation deleteUserMutation($input: DeleteUserInput!) {
deleteUser(input: $input) {
clientMutationId
}
}Nested Relations
Include related tables in your query with automatic Connection wrapping for hasMany relations:
import { buildSelect } from '@constructive-io/graphql-query';
const actionTable = tables.find(t => t.name === 'Action')!;
const query = buildSelect(actionTable, tables, {
fieldSelection: {
select: ['id', 'name', 'photo', 'title'],
include: {
actionResults: ['id', 'actionId'], // hasMany → wrapped in nodes { ... }
category: true, // belongsTo → direct nesting
},
},
});Generated GraphQL:
query actionsQuery {
actions {
totalCount
nodes {
id
name
photo
title
actionResults(first: 20) {
nodes {
id
actionId
}
}
category {
id
name
}
}
}
}hasMany relations are automatically wrapped in the PostGraphile Connection pattern (
nodes { ... }with a defaultfirst: 20limit). belongsTo relations are nested directly.
FindOne Query
import { buildFindOne } from '@constructive-io/graphql-query';
const findOneQuery = buildFindOne(userTable);
console.log(findOneQuery.toString());Generated GraphQL:
query getUserQuery($id: UUID!) {
user(id: $id) {
id
name
email
createdAt
}
}Count Query
import { buildCount } from '@constructive-io/graphql-query';
const countQuery = buildCount(userTable);
console.log(countQuery.toString());Generated GraphQL:
query getUsersCountQuery(
$condition: UserCondition
$filter: UserFilter
) {
users(condition: $condition, filter: $filter) {
totalCount
}
}Field Selection
Control which fields and relations are included using presets or custom selection:
import { buildSelect } from '@constructive-io/graphql-query';
// Preset: just id + a few display fields
const minimal = buildSelect(userTable, tables, { fieldSelection: 'minimal' });
// Preset: all scalar fields (no relations)
const allFields = buildSelect(userTable, tables, { fieldSelection: 'all' });
// Preset: everything including relations
const full = buildSelect(userTable, tables, { fieldSelection: 'full' });
// Custom: pick specific fields + exclude some + include specific relations
const custom = buildSelect(userTable, tables, {
fieldSelection: {
select: ['id', 'name', 'email'],
exclude: ['internalNotes'],
include: { posts: ['id', 'title'] },
},
});Relation Field Mapping (Aliases)
Remap relation field names when the server-side name differs from what your consumer expects:
const query = buildSelect(userTable, tables, {
relationFieldMap: {
contact: 'contactByOwnerId', // emits: contact: contactByOwnerId { ... }
internalNotes: null, // omits this relation entirely
},
});Type-Safe Execution
import { createGraphQLClient } from '@constructive-io/graphql-query';
const client = createGraphQLClient({
url: '/graphql',
headers: { Authorization: `Bearer ${token}` },
});
const { data, errors } = await client.execute(query, {
first: 10,
filter: { name: { includesInsensitive: 'search' } },
});Error Handling
import { DataError, parseGraphQLError, DataErrorType } from '@constructive-io/graphql-query';
try {
const result = await client.execute(createQuery, { input: { user: { name: 'Alice' } } });
if (result.errors) {
const error = parseGraphQLError(result.errors[0]);
if (error.type === DataErrorType.UNIQUE_VIOLATION) {
console.log('Duplicate entry:', error.constraintName);
}
}
} catch (err) {
if (err instanceof DataError) {
console.log(err.type, err.message);
}
}Server-Aware Naming
All generators automatically use server-inferred names from introspection when available, falling back to local inflection conventions:
import {
toCamelCaseSingular,
toCamelCasePlural,
toCreateMutationName,
toPatchFieldName,
toFilterTypeName,
} from '@constructive-io/graphql-query';
// Uses table.query/table.inflection if available, falls back to convention
toCamelCaseSingular('DeliveryZone', table); // "deliveryZone" (from table.inflection.tableFieldName)
toCamelCasePlural('DeliveryZone', table); // "deliveryZones" (from table.query.all)
toCreateMutationName('User', table); // "createUser" (from table.query.create)
toPatchFieldName('User', table); // "userPatch" (from table.query.patchFieldName)
toFilterTypeName('User', table); // "UserFilter" (from table.inflection.filterType)Architecture
GraphQL Schema (introspection or _meta)
|
v
inferTablesFromIntrospection()
|
v
CleanTable[] (normalized metadata with inflection + query names)
|
v
buildSelect / buildFindOne / buildCount / buildPostGraphileCreate / ...
|
v
gql-ast (AST node factories)
|
v
graphql print() -> query string
|
v
TypedDocumentString (type-safe wrapper)Package Relationship
@constructive-io/graphql-query (this package — browser-safe core)
|
v
@constructive-io/graphql-codegen (Node.js CLI — depends on graphql-query)
+ CLI entry points
+ File output (writes .ts files to disk)
+ Watch mode
+ Database introspection
+ React Query hook generation
+ Babel codegen templatesAPI Reference
Generators
| Function | Description |
|---|---|
buildSelect(table, allTables, options?) |
Build a paginated SELECT query with filters, sorting, field selection |
buildFindOne(table, pkField?) |
Build a single-row query by primary key |
buildCount(table) |
Build a count query with optional condition/filter |
buildPostGraphileCreate(table, allTables, options?) |
Build a CREATE mutation |
buildPostGraphileUpdate(table, allTables, options?) |
Build an UPDATE mutation |
buildPostGraphileDelete(table, allTables, options?) |
Build a DELETE mutation |
Introspection
| Function | Description |
|---|---|
inferTablesFromIntrospection(data, options?) |
Convert GraphQL introspection to CleanTable[] |
transformSchemaToOperations(types, queryType, mutationType) |
Extract operations from schema types |
SCHEMA_INTROSPECTION_QUERY |
Full introspection query string |
Client
| Export | Description |
|---|---|
TypedDocumentString |
Type-safe GraphQL document wrapper (compatible with codegen client) |
createGraphQLClient(options) |
Create a typed GraphQL client |
execute(url, query, variables, options?) |
Execute a GraphQL query |
DataError |
Structured error class with PG SQLSTATE classification |
parseGraphQLError(error) |
Parse raw GraphQL error into DataError |
Naming Helpers
| Function | Description |
|---|---|
toCamelCaseSingular(name, table?) |
Server-aware singular name |
toCamelCasePlural(name, table?) |
Server-aware plural name |
toCreateMutationName(name, table?) |
Create mutation operation name |
toUpdateMutationName(name, table?) |
Update mutation operation name |
toDeleteMutationName(name, table?) |
Delete mutation operation name |
toPatchFieldName(name, table?) |
Patch field name in update input |
toFilterTypeName(name, table?) |
Filter type name |
toOrderByEnumValue(fieldName) |
Convert field name to OrderBy enum value |
normalizeInflectionValue(value, fallback) |
Safe inflection value lookup |
Types
| Type | Description |
|---|---|
CleanTable |
Normalized table metadata with fields, relations, inflection, query names |
CleanField |
Field with type info, isNotNull, hasDefault |
QueryOptions |
Options for buildSelect (pagination, filters, field selection, relation mapping) |
MutationOptions |
Options for mutation builders |
FieldSelection |
Field selection presets and custom configuration |
ConnectionResult<T> |
Relay-style connection result type |
Filter |
PostGraphile connection filter type |
Education and Tutorials
🚀 Quickstart: Getting Up and Running Get started with modular databases in minutes. Install prerequisites and deploy your first module.
📦 Modular PostgreSQL Development with Database Packages Learn to organize PostgreSQL projects with pgpm workspaces and reusable database modules.
✏️ Authoring Database Changes Master the workflow for adding, organizing, and managing database changes with pgpm.
🧪 End-to-End PostgreSQL Testing with TypeScript Master end-to-end PostgreSQL testing with ephemeral databases, RLS testing, and CI/CD automation.
⚡ Supabase Testing Use TypeScript-first tools to test Supabase projects with realistic RLS, policies, and auth contexts.
💧 Drizzle ORM Testing Run full-stack tests with Drizzle ORM, including database setup, teardown, and RLS enforcement.
🔧 Troubleshooting Common issues and solutions for pgpm, PostgreSQL, and testing.
Related Constructive Tooling
📦 Package Management
- pgpm: 🖥️ PostgreSQL Package Manager for modular Postgres development. Works with database workspaces, scaffolding, migrations, seeding, and installing database packages.
🧪 Testing
- pgsql-test: 📊 Isolated testing environments with per-test transaction rollbacks—ideal for integration tests, complex migrations, and RLS simulation.
- pgsql-seed: 🌱 PostgreSQL seeding utilities for CSV, JSON, SQL data loading, and pgpm deployment.
- supabase-test: 🧪 Supabase-native test harness preconfigured for the local Supabase stack—per-test rollbacks, JWT/role context helpers, and CI/GitHub Actions ready.
- graphile-test: 🔐 Authentication mocking for Graphile-focused test helpers and emulating row-level security contexts.
- pg-query-context: 🔒 Session context injection to add session-local context (e.g.,
SET LOCAL) into queries—ideal for settingrole,jwt.claims, and other session settings.
🧠 Parsing & AST
- pgsql-parser: 🔄 SQL conversion engine that interprets and converts PostgreSQL syntax.
- libpg-query-node: 🌉 Node.js bindings for
libpg_query, converting SQL into parse trees. - pg-proto-parser: 📦 Protobuf parser for parsing PostgreSQL Protocol Buffers definitions to generate TypeScript interfaces, utility functions, and JSON mappings for enums.
- @pgsql/enums: 🏷️ TypeScript enums for PostgreSQL AST for safe and ergonomic parsing logic.
- @pgsql/types: 📝 Type definitions for PostgreSQL AST nodes in TypeScript.
- @pgsql/utils: 🛠️ AST utilities for constructing and transforming PostgreSQL syntax trees.
Credits
🛠 Built by the Constructive team — creators of modular Postgres tooling for secure, composable backends. If you like our work, contribute on GitHub.
Disclaimer
AS DESCRIBED IN THE LICENSES, THE SOFTWARE IS PROVIDED "AS IS", AT YOUR OWN RISK, AND WITHOUT WARRANTIES OF ANY KIND.
No developer or entity involved in creating this software will be liable for any claims or damages whatsoever associated with your use, inability to use, or your interaction with other users of the code, including any direct, indirect, incidental, special, exemplary, punitive or consequential damages, or loss of profits, cryptocurrencies, tokens, or anything else of value.