JSPM

  • Created
  • Published
  • Downloads 1402
  • Score
    100M100P100Q125386F
  • License MIT

Constructive GraphQL Query

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-query

Overview

This package is the canonical source for PostGraphile query generation logic. It provides:

  • Query generatorsbuildSelect, buildFindOne, buildCount for read operations
  • Mutation generatorsbuildPostGraphileCreate, buildPostGraphileUpdate, buildPostGraphileDelete
  • Introspection pipelineinferTablesFromIntrospection to convert a GraphQL schema into CleanTable metadata
  • AST builders — low-level getAll, getMany, getOne, createOne, patchOne, deleteOne
  • Client utilitiesTypedDocumentString, execute, DataError for 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 default first: 20 limit). 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 templates

API 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

  1. 🚀 Quickstart: Getting Up and Running Get started with modular databases in minutes. Install prerequisites and deploy your first module.

  2. 📦 Modular PostgreSQL Development with Database Packages Learn to organize PostgreSQL projects with pgpm workspaces and reusable database modules.

  3. ✏️ Authoring Database Changes Master the workflow for adding, organizing, and managing database changes with pgpm.

  4. 🧪 End-to-End PostgreSQL Testing with TypeScript Master end-to-end PostgreSQL testing with ephemeral databases, RLS testing, and CI/CD automation.

  5. Supabase Testing Use TypeScript-first tools to test Supabase projects with realistic RLS, policies, and auth contexts.

  6. 💧 Drizzle ORM Testing Run full-stack tests with Drizzle ORM, including database setup, teardown, and RLS enforcement.

  7. 🔧 Troubleshooting Common issues and solutions for pgpm, PostgreSQL, and testing.

📦 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 setting role, 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.