npm package: @soda-gql/core
Description: Core types and utilities for zero-runtime GraphQL query generation
Author: Shota Hatada
Latest version: 0.14.2

# @soda-gql/core

[![npm version](https://img.shields.io/npm/v/@soda-gql/core.svg)](https://www.npmjs.com/package/@soda-gql/core)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

Core GraphQL types and utilities for the soda-gql ecosystem. This package provides the foundational building blocks for writing type-safe GraphQL operations.

## Installation

```bash
bun add @soda-gql/core
```

## Core Concepts

soda-gql uses two main building blocks for constructing GraphQL operations:

### Fragments

Reusable type-safe field selections. Fragments define how to select fields from a GraphQL type and can be spread in operations.

### Operations

Complete GraphQL operations (query/mutation/subscription) with field selections. Operations define variables, select fields, and can spread fragments for reusable field selections.

## Usage

All soda-gql definitions use the `gql.default()` pattern, which is provided by the generated GraphQL system:

```typescript
import { gql } from "@/graphql-system";
```

### Writing Fragments

Fragments define reusable field selections for a specific GraphQL type using tagged template syntax:

```typescript
export const userFragment = gql.default(({ fragment }) =>
  fragment("UserFragment", "User")`{
    id
    name
    email
  }`(),
);
```

### Writing Operations

Operations define complete GraphQL queries, mutations, or subscriptions. Use tagged template syntax for standalone operations:

```typescript
export const getUserQuery = gql.default(({ query }) =>
  query("GetUser")`($id: ID!) {
    user(id: $id) {
      id
      name
    }
  }`(),
);
```

Fragment spreads work in both tagged templates (`...${fragment}`) and the options-object path (`.spread()`):

```typescript
// Operation with spread fragment (tagged template interpolation)
export const getUserWithFragment = gql.default(({ query }) =>
  query("GetUserWithFragment")`($id: ID!) {
    user(id: $id) {
      ...${userFragment}
    }
  }`(),
);
```

## Variable Declaration

Variables are declared using standard GraphQL syntax, either inline in tagged templates or as a `variables` string in the options-object path:

```typescript
// Tagged template — variables inline
query("GetUser")`($id: ID!, $limit: Int) { ... }`()

// Options-object path — variables as template string
query("GetUser")({
  variables: `($id: ID!, $limit: Int)`,
  fields: ({ f, $ }) => ({ ... }),
})({})
```

## Field Selection Patterns

| Pattern | Description |
|---------|-------------|
| `...f("id")()` | Basic field selection |
| `...f("posts", { limit: 10 })()` | Field with arguments |
| `...f("posts")(({ f }) => ({ ... }))` | Nested selection |
| `...f("id", null, { alias: "uuid" })()` | Field with alias |
| `...userFragment.spread()` | Use fragment fields |

## Understanding the Inject Module

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.

## Defining Custom Scalars

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<Name, InputType, OutputType>(name)
  ...defineScalar<"DateTime", string, Date>("DateTime"),
  ...defineScalar<"JSON", Record<string, unknown>, Record<string, unknown>>("JSON"),
} as const;
```

## Type Inference

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;
```

## Element Extensions

The `attach()` method allows extending gql elements with custom properties. This is useful for colocating related functionality with fragment or operation definitions.

### Basic Usage

```typescript
// Create a fragment and attach a custom property inline
export const userFragment = gql
  .default(({ fragment }) => fragment("UserFragment", "User")`{ id name email }`())
  .attach({ name: "custom" as const, createValue: () => ({ value: 42 }) });

// Access the attached property
userFragment.custom.value; // 42
```

### Type Safety

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

Metadata allows you to attach runtime information to operations. This is useful for HTTP headers and application-specific values.

### Metadata Structure

All metadata types share two base properties:

| Property | Type | Purpose |
|----------|------|---------|
| `headers` | `Record<string, string>` | HTTP headers to include with the GraphQL request |
| `custom` | `Record<string, unknown>` | Application-specific values (auth requirements, cache settings, etc.) |

### Defining Metadata

Metadata is defined on operations:

```typescript
// Operation with metadata (tagged template)
export const getUserQuery = gql.default(({ query }) =>
  query("GetUser")`($id: ID!) {
    user(id: $id) { id name }
  }`({
    metadata: ({ $, $var }) => ({
      headers: { "X-Request-ID": "user-query" },
      custom: {
        requiresAuth: true,
        cacheTtl: 300,
        trackedVariables: [$var.getName($.id)],
      },
    }),
  }),
);
```

### Variable Utilities

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.getPath(ref, selector)` | Get path segments to a variable |
| `$var.hasVarRefInside(value)` | Check if a value contains any VarRef |

```typescript
// Example: getPath with nested structure
const varRef = createVarRefFromVariable("user");
$var.getPath(varRef, (p) => p.profile.name);
// Returns: ["$user", "name"] (variable name + path after variable reference point)
```

## Adapter

Adapters customize the behavior of your GraphQL system with helpers, metadata configuration, and document transformation.

### Basic Usage

```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;
  },
});
```

### Document Transform

Operations can also define their own transform with typed metadata:

```typescript
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;
    },
  }),
);
```

**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("GetUser")`{ ... }`({
  transformDocument: transform.addCache(300),
});
```

## Runtime Exports

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("GetUser");
```

## TypeScript Support

This package requires TypeScript 5.x or later for full type inference support.

## Related Packages

- [@soda-gql/tools](../tools) - CLI, codegen, typegen, and formatter (dev tooling)
- [@soda-gql/config](../config) - Configuration management
- [@soda-gql/tsc](../tsc) - TypeScript transformer and plugin

## License

MIT