@soda-gql/core
Core GraphQL types, utilities, and primitives for soda-gql.
Installation
Overview
@soda-gql/core provides the foundational types and utilities for defining GraphQL fragments and operations.
defineScalar
Define custom scalar types with input/output transformations:
import { defineScalar } from "@soda-gql/core";
export const scalar = {
// Simple syntax
...defineScalar<"ID", string, string>("ID"),
...defineScalar<"String", string, string>("String"),
// Callback syntax with directives
...defineScalar("DateTime", ({ type }) => ({
input: type<string>(),
output: type<Date>(),
directives: {},
})),
} as const;
Parameters
| Parameter | Description |
|---|
name | The GraphQL scalar name |
options or callback | Type configuration |
Callback Parameters
| Property | Type | Description |
|---|
input | type<T>() | TypeScript type for input (variables) |
output | type<T>() | TypeScript type for output (responses) |
directives | object | Directive definitions |
gql (Generated)
The gql object is generated per-schema and provides builders. Two syntax styles are available:
Tagged Template Syntax (Recommended)
import { gql } from "@/graphql-system";
// Fragment
gql.default(({ fragment }) =>
fragment("UserFields", "User")`{ id name email }`()
);
// Query
gql.default(({ query }) =>
query("GetUser")`($id: ID!) { user(id: $id) { id name } }`()
);
// Mutation
gql.default(({ mutation }) =>
mutation("CreateUser")`($input: CreateUserInput!) { createUser(input: $input) { id } }`()
);
// Subscription
gql.default(({ subscription }) =>
subscription("OnUserCreated")`{ userCreated { id name } }`()
);
Callback Builder Syntax
For advanced features (field aliases, directives, $colocate):
import { gql } from "@/graphql-system";
// Fragment (tagged template)
gql.default(({ fragment }) =>
fragment("UserFields", "User")`{ id name }`(),
);
// Query (options-object path — useful for $dir, aliases, programmatic field control)
gql.default(({ query }) =>
query("GetUser")({ variables: `($id: ID!)`, fields: ({ f, $ }) => ({ ... }) })({}),
);
// Mutation (options-object path)
gql.default(({ mutation }) =>
mutation("CreateUser")({ variables: `($input: CreateUserInput!)`, fields: ({ f, $ }) => ({ ... }) })({}),
);
See the Tagged Template Syntax Guide for a complete comparison.
Element Extensions (attach)
The attach() method extends gql elements with custom properties:
import type { GqlElementAttachment } from "@soda-gql/core";
export const userFragment = gql
.default(({ fragment }) =>
fragment("UserFields", "User")`{ id name }`(),
)
.attach({
name: "utils",
createValue: (element) => ({
getDisplayName: (user: typeof element.$infer.output) =>
user.name.toUpperCase(),
}),
});
// Usage
userFragment.utils.getDisplayName(userData);
GqlElementAttachment Interface
interface GqlElementAttachment<TElement, TName extends string, TValue> {
name: TName;
createValue: (element: TElement) => TValue;
}
Chaining Attachments
Multiple attachments can be chained:
const fragment = gql
.default(...)
.attach(attachment1)
.attach(attachment2);
// Access both
fragment.attachment1Name;
fragment.attachment2Name;
Metadata API
Define runtime metadata on operations:
gql.default(({ query }) =>
query("GetUser")`($id: ID!) { user(id: $id) { id name } }`({
metadata: ({ $, document, $var }) => ({
headers: { "X-Request-ID": "get-user" },
custom: { requiresAuth: true, hash: hashDocument(document) },
}),
}),
);
Metadata Structure
| Property | Type | Description |
|---|
headers | Record<string, string> | HTTP headers |
custom | Record<string, unknown> | Application-specific values |
Accessing Metadata
const meta = operation.metadata({ id: "123" });
console.log(meta.headers);
console.log(meta.custom);
$var Helper Methods
The $var object provides methods for inspecting VarRef values. These are available in the metadata callback and can be used to extract information from variable references.
$var.getName(ref)
Get the variable name from a VarRef.
$var.getName($.userId) // Returns "userId"
Throws: If the VarRef contains a nested-value instead of a variable reference.
$var.getValue(ref)
Get the const value from a VarRef when the variable was assigned a literal value.
// When $.status was assigned a literal value like "active"
$var.getValue($.status) // Returns "active"
Throws: If the VarRef contains a variable reference, or if the nested-value contains any VarRef inside.
$var.getInner(ref)
Get the raw inner structure of a VarRef.
$var.getInner($.userId)
// Returns { type: "variable", name: "userId" }
$var.getNameAt(ref, selector)
Get the variable name at a specific path within an input type variable.
// Given a variable defined with an input type containing nested VarRefs
// e.g., a variable declared as ($filter: UserFilter!) where UserFilter has { userId: $.id }
$var.getNameAt($.filter, p => p.userId) // Returns "id"
Parameters:
ref: A VarRef (typically from $ in metadata callback)
selector: A function that navigates to the target path, e.g., p => p.userId
Throws: If the path doesn't lead to a VarRef with type "variable".
$var.getValueAt(ref, selector)
Get the const value at a specific path within an input type variable.
// Given a variable declared as ($categoryId: String_comparison_exp)
// When called with { _eq: "tech", _neq: "spam" }
$var.getValueAt($.categoryId, p => p._eq) // Returns "tech"
$var.getValueAt($.categoryId, p => p._neq) // Returns "spam"
Parameters:
ref: A VarRef (typically from $ in metadata callback)
selector: A function that navigates to the target path, e.g., p => p._eq
Throws: If the path leads to a VarRef, or if the value at the path contains any nested VarRef.
Variable Type Syntax Reference
Variables are declared using inline GraphQL syntax in the variables template string or the variables option:
Inline Syntax (Tagged Template)
Variables are written directly in the tagged template using standard GraphQL variable declaration syntax:
query("GetUser")`($id: ID!) { user(id: $id) { id name } }`()
Options-Object Syntax
When using the options-object path, pass a template literal string to variables:
query("GetUser")({ variables: `($id: ID!)`, fields: ({ f, $ }) => ({ ... }) })({})
Basic Types
| GraphQL | TypeScript |
|---|
$id: ID! | string |
$id: ID | string | undefined |
$name: String! | string |
$name: String | string | undefined |
$count: Int! | number |
$count: Int | number | undefined |
$score: Float! | number |
$score: Float | number | undefined |
$active: Boolean! | boolean |
$active: Boolean | boolean | undefined |
List Types
| GraphQL | Description |
|---|
$tags: [String!]! | Required list of required strings |
$tags: [String!] | Optional list of required strings |
$tags: [String]! | Required list of optional strings |
$tags: [String] | Optional list of optional strings |
Nested Lists
| GraphQL |
|---|
$matrix: [[Int!]!]! |
$matrix: [[String]] |
Custom Types
query("CreateUser")`($input: CreateUserInput!) { createUser(input: $input) { id } }`()
query("Search")`($filters: [FilterInput!]) { search(filters: $filters) { id } }`()
Field Selection Patterns Reference
Complete reference for field selection API:
| Pattern | Example | Description |
|---|
| Basic field | f("id")() | Select a scalar field |
| With arguments | f("posts", { limit: 10 })() | Field with arguments |
| Nested (curried) | f("posts")(({ f }) => ({ ... })) | Nested selections |
| With alias | f("id", null, { alias: "userId" })() | Renamed field |
| Fragment spread | userFragment.spread({}) | Spread fragment fields |
| Fragment with vars | userFragment.spread({ a: $.b }) | Pass variables |
Type Inference
Extract TypeScript types using $infer:
// Fragment types
type UserInput = typeof userFragment.$infer.input;
type UserOutput = typeof userFragment.$infer.output;
// Operation types
type QueryVariables = typeof query.$infer.input;
type QueryResult = typeof query.$infer.output.projected;
// Metadata type
type QueryMeta = typeof query.$infer.metadata;
Runtime Exports
The /runtime subpath provides runtime utilities:
import { gqlRuntime } from "@soda-gql/core/runtime";
// Get registered operation
const operation = gqlRuntime.getOperation("canonicalId");
TypeScript Requirements
- TypeScript 5.x or later for full type inference
- Strict mode recommended for best type safety
defineAdapter
Create a typed adapter with helpers, metadata configuration, and document transformation:
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 }) => {
// Modify document AST
return document;
},
});
Adapter Type
type Adapter<THelpers, TFragmentMetadata, TAggregatedFragmentMetadata, TSchemaLevel> = {
helpers?: THelpers;
metadata?: MetadataAdapter<TFragmentMetadata, TAggregatedFragmentMetadata, TSchemaLevel>;
transformDocument?: DocumentTransformer<TSchemaLevel, TAggregatedFragmentMetadata>;
};
DocumentTransformArgs
Arguments passed to adapter-level transformDocument:
| Property | Type | Description |
|---|
document | DocumentNode | The GraphQL document to transform |
operationName | string | The operation name |
operationType | OperationType | "query", "mutation", or "subscription" |
variableNames | readonly string[] | Variable names defined for this operation |
schemaLevel | TSchemaLevel | undefined | Schema-level configuration |
fragmentMetadata | TAggregatedFragmentMetadata | undefined | Aggregated fragment metadata |
OperationDocumentTransformArgs
Arguments passed to operation-level transformDocument:
| Property | Type | Description |
|---|
document | DocumentNode | The GraphQL document to transform |
metadata | TOperationMetadata | undefined | Typed operation metadata |
Operation transformDocument Option
Operations can define their own document transform with typed metadata:
gql.default(({ query }) =>
query("GetUser")`($id: ID!) { user(id: $id) { id name } }`({
metadata: () => ({ cacheHint: 300 }),
transformDocument: ({ document, metadata }) => {
// metadata is typed as { cacheHint: number }
return document;
},
}),
);
Transform Order: Operation transform runs first, then adapter transform.
See Also