Core types and utilities for zero-runtime GraphQL query generation
npm install @soda-gql/core

Core GraphQL types and utilities for the soda-gql ecosystem. This package provides the foundational building blocks for writing type-safe GraphQL operations.
``bash`
bun add @soda-gql/core
soda-gql uses two main building blocks for constructing GraphQL operations:
Reusable type-safe field selections. Fragments define how to select fields from a GraphQL type and can be spread in operations.
Complete GraphQL operations (query/mutation/subscription) with field selections. Operations define variables, select fields, and can spread fragments for reusable field selections.
All soda-gql definitions use the gql.default() pattern, which is provided by the generated GraphQL system:
`typescript`
import { gql } from "@/graphql-system";
Fragments define reusable field selections for a specific GraphQL type:
`typescript`
export const userFragment = gql.default(({ fragment }) =>
fragment.User({
fields: ({ f }) => ({
...f.id(),
...f.name(),
...f.email(),
}),
}),
);
Operations define complete GraphQL queries, mutations, or subscriptions:
`typescript
export const getUserQuery = gql.default(({ query, $var }) =>
query.operation({
name: "GetUser",
variables: { ...$var("id").ID("!") },
fields: ({ f, $ }) => ({
...f.user({ id: $.id })(({ f }) => ({ ...f.id(), ...f.name() })),
}),
}),
);
// Operation with spread fragment
export const getUserWithFragment = gql.default(({ query, $var }) =>
query.operation({
name: "GetUserWithFragment",
variables: { ...$var("id").ID("!") },
fields: ({ f, $ }) => ({
...f.user({ id: $.id })(() => ({ ...userFragment.spread() })),
}),
}),
);
`
Variables are declared using a string-based type syntax:
| Syntax | Meaning | GraphQL Equivalent |
|--------|---------|-------------------|
| "ID:!" | Required ID | ID! |"String:?"
| | Optional String | String |"Int:![]!"
| | Required list of required Int | [Int!]! |"String:![]?"
| | Optional list of required Strings | [String!] |"MyInput:!"
| | Required custom input type | MyInput! |
| Pattern | Description |
|---------|-------------|
| ...f.id() | Basic field selection |...f.posts({ limit: 10 })
| | Field with arguments |...f.posts()(({ f }) => ({ ... }))
| | Nested selection (curried) |...f.id(null, { alias: "uuid" })
| | Field with alias |...userFragment.spread()
| | Use fragment fields |
The inject module ({schema}.inject.ts) bridges your GraphQL schema with TypeScript types.
Why hand-written?
- Custom scalar types (DateTime, JSON, etc.) need explicit TypeScript type mappings
- Version-controlled to keep type behavior explicit and reviewable
What it contains:
- scalar: TypeScript type definitions for each GraphQL scalar
Scaffolding:
`bash`
bun run soda-gql codegen schema --emit-inject-template ./src/graphql-system/default.inject.ts
This creates a template with standard scalars (ID, String, Int, Float, Boolean) that you can customize.
Scalars define the TypeScript types for GraphQL scalar values:
`typescript
import { defineScalar } from "@soda-gql/core";
export const scalar = {
// Built-in scalars
...defineScalar<"ID", string, string>("ID"),
...defineScalar<"String", string, string>("String"),
...defineScalar<"Int", number, number>("Int"),
...defineScalar<"Float", number, number>("Float"),
...defineScalar<"Boolean", boolean, boolean>("Boolean"),
// Custom scalars - defineScalar
...defineScalar<"DateTime", string, Date>("DateTime"),
...defineScalar<"JSON", Record
} as const;
`
Alternative callback syntax:
`typescript`
export const scalar = {
...defineScalar("DateTime", ({ type }) => ({
input: type
output: type
directives: {},
})),
} as const;
Extract TypeScript types from soda-gql elements using $infer:
`typescript
// Fragment types
type UserInput = typeof userFragment.$infer.input;
type UserOutput = typeof userFragment.$infer.output;
// Operation types
type QueryVariables = typeof getUserQuery.$infer.input;
type QueryResult = typeof getUserQuery.$infer.output.projected;
`
The attach() method allows extending gql elements with custom properties. This is useful for colocating related functionality with fragment or operation definitions.
`typescript
import type { GqlElementAttachment } from "@soda-gql/core";
// Define an attachment
const myAttachment: GqlElementAttachment
name: "custom",
createValue: (element) => ({ value: 42 }),
};
// Attach to a fragment
export const userFragment = gql
.default(({ fragment }) => fragment.User({ fields: ({ f }) => ({ ...f.id(), ...f.name() }) }))
.attach(myAttachment);
// Access the attached property
userFragment.custom.value; // 42
`
Attachments are fully typed. The returned element includes the new property in its type:
`typescript`
const fragment = gql.default(...).attach({ name: "foo", createValue: () => ({ bar: 1 }) });
// Type: Fragment<...> & { foo: { bar: number } }
Metadata allows you to attach runtime information to operations. This is useful for HTTP headers and application-specific values.
All metadata types share two base properties:
| Property | Type | Purpose |
|----------|------|---------|
| headers | Record | HTTP headers to include with the GraphQL request |custom
| | Record | Application-specific values (auth requirements, cache settings, etc.) |
Metadata is defined on operations:
`typescript`
// Operation with metadata
export const getUserQuery = gql.default(({ query, $var }) =>
query.operation({
name: "GetUser",
variables: { ...$var("id").ID("!") },
metadata: ({ $, document }) => ({
headers: { "X-Request-ID": "user-query" },
custom: {
requiresAuth: true,
cacheTtl: 300,
trackedVariables: [$var.getInner($.id)],
},
}),
fields: ({ f, $ }) => ({ ...f.user({ id: $.id })(({ f }) => ({ ...f.id(), ...f.name() })) }),
}),
);
The $var object provides utility functions for inspecting variable references:
| Method | Description |
|--------|-------------|
| $var.getName(ref) | Get variable name from a VarRef |$var.getValue(ref)
| | Get const value from a nested-value VarRef |$var.getNameAt(ref, selector)
| | Get variable name at a specific path |$var.getValueAt(ref, selector)
| | Get const value at a specific path |$var.getVariablePath(ref, selector)
| | Get path segments to a variable |
`typescript`
// Example: getVariablePath with nested structure
const varRef = createVarRefFromVariable("user");
$var.getVariablePath(varRef, (p) => p.profile.name);
// Returns: ["$user", "name"] (variable name + path after variable reference point)
Adapters customize the behavior of your GraphQL system with helpers, metadata configuration, and document transformation.
`typescript
import { defineAdapter } from "@soda-gql/core/adapter";
const adapter = defineAdapter({
helpers: {
auth: {
requiresLogin: () => ({ requiresAuth: true }),
},
},
metadata: {
aggregateFragmentMetadata: (fragments) => ({
count: fragments.length,
}),
schemaLevel: { apiVersion: "v2" },
},
transformDocument: ({ document, operationType }) => {
// Schema-wide document transformation
return document;
},
});
`
Operations can also define their own transform with typed metadata:
`typescript`
gql.default(({ query, $var }) =>
query.operation({
name: "GetUser",
metadata: () => ({ cacheHint: 300 }),
transformDocument: ({ document, metadata }) => {
// metadata is typed as { cacheHint: number }
return document;
},
fields: ({ f, $ }) => ({ ... }),
}),
);
Best Practice: Define transform logic in helpers for reusability:
`typescript
const adapter = defineAdapter({
helpers: {
transform: {
addCache: (ttl: number) => ({ document }) => {
// Transform logic here
return document;
},
},
},
});
// Use helper in operation
query.operation({
transformDocument: transform.addCache(300),
...
});
`
The /runtime subpath provides utilities for operation registration and retrieval:
`typescript
import { gqlRuntime } from "@soda-gql/core/runtime";
// Retrieve registered operations (typically handled by build plugins)
const operation = gqlRuntime.getOperation("canonicalId");
``
This package requires TypeScript 5.x or later for full type inference support.
- @soda-gql/cli - Command-line interface for code generation
- @soda-gql/config - Configuration management
- @soda-gql/runtime - Runtime utilities for operation execution
- @soda-gql/tsc - TypeScript transformer and plugin
MIT