# @distilled.cloud/railway

Latest version **1.0.0-rc.12** (published 2026-09-17) · Apache-2.0 license · 0 weekly downloads

## Install

```sh
npm install @distilled.cloud/railway
pnpm add @distilled.cloud/railway
yarn add @distilled.cloud/railway
bun add @distilled.cloud/railway
```

## Health

**Score 55/100 (C)** — status: active.

Positive: no vulnerabilities; recently updated; high maintenance score.

Warnings: low downloads; no types; no esm support.

## Facts

| | |
|---|---|
| Version | 1.0.0-rc.12 |
| Published | 2026-09-17 |
| First published | 2026-05-02 |
| Weekly downloads | 0 |
| License | Apache-2.0 |
| TypeScript types | none |
| Module format | CommonJS |
| Dependencies | 0 |
| Known vulnerabilities | 0 |
| Install scripts | no |
| GitHub stars | 427 |
| Maintainers | pear-alchemy, sam-goodwin, blankparticle |

## Links

- npm: https://www.npmjs.com/package/@distilled.cloud/railway
- Repository: https://github.com/alchemy-run/distilled
- npm.io page: https://npm.io/package/@distilled.cloud/railway

## Recent versions

- 1.0.0-rc.12 (latest) — 2026-09-17
- 1.0.0-rc.11 — 2026-09-17
- 1.0.0-rc.10 — 2026-09-16
- 1.0.0-rc.9 — 2026-09-09
- 1.0.0-rc.8 — 2026-08-29
- 1.0.0-rc.7 — 2026-08-29
- 1.0.0-rc.6 — 2026-08-21
- 1.0.0-rc.5 — 2026-08-21
- 1.0.0-rc.4 — 2026-08-12
- 0.0.0 — 2026-05-02

## README

# Railway GraphQL for Effect

Use `@distilled.cloud/railway` to select exactly the fields your program
needs. The client generates GraphQL documents and variables from typed
`where`/`select` objects and returns Effects with selection-dependent errors.

```ts
import * as Effect from "effect/Effect";
import * as FetchHttpClient from "effect/unstable/http/FetchHttpClient";
import { CredentialsFromEnv } from "@distilled.cloud/railway";
import * as Railway from "@distilled.cloud/railway";

const program = Railway.project(
  { id: "your-project-id" },
  { id: true, name: true },
);

await Effect.runPromise(program.pipe(
  Effect.provide(CredentialsFromEnv),
  Effect.provide(FetchHttpClient.layer),
));
```

Credentials accept `RAILWAY_API_TOKEN`, `RAILWAY_TOKEN`, or
`RAILWAY_PROJECT_TOKEN`. Account tokens and project tokens use their respective
Railway authentication headers. Arguments travel as GraphQL variables.

## Nested fields and reusable selections

Every nested object field supports a projection; fields with arguments accept
`where` and `select`. Recursive relationships have no generator-imposed depth
limit. Result types contain only selected fields and preserve GraphQL nullability.

```ts
const serviceIdentity = {
  id: true,
  name: true,
} as const satisfies Railway.Selection<"Service">;

type ServiceIdentity = Railway.Result<"Service!", typeof serviceIdentity>;

const project = Railway.project({ id: "your-project-id" }, {
  id: true,
  services: {
    where: { first: 20 },
    select: {
      edges: { node: serviceIdentity },
      pageInfo: { endCursor: true, hasNextPage: true },
    },
  },
});
```

Connection operations expose `.items(args, nodeSelection)` and
`.pages(args, connectionSelection)` as Effect Streams. They follow Relay
cursors and fail on non-advancing cursors:

```ts
const projects = Railway.projects.items(
  { workspaceId: "your-workspace-id", first: 20 },
  { id: true, name: true },
);
```

## Compose roots into one request

`query` combines independent root fields. `__alias` allows repeated calls to
the same field with different arguments. Execution is still an ordinary Effect,
so dependent requests can be sequenced with `Effect.gen`.

```ts
const both = Railway.query({
  __alias: {
    production: {
      project: {
        where: { id: "production-id" },
        select: { id: true, name: true },
      },
    },
    staging: {
      project: {
        where: { id: "staging-id" },
        select: { id: true, name: true },
      },
    },
  },
});
```

For unions and interfaces, select `__typename` and put concrete-type selections
under `__on: { ConcreteType: { ... } }`. GraphQL fragments are compiled from
those selections and results form a discriminated union.

## Tagged errors and partial responses

GraphQL may return several errors in one response. Strict query and mutation
execution fails with `GraphQLFailure<E>`, whose nonempty `errors` array contains
tagged issues. Each issue retains its message, response path, code, locations,
and available trace metadata. The error union is derived from selected fields
plus provider-wide errors and `UnknownGraphQLError`.

Use `catchTags` to recover only when **every** issue has an allowed tag. Mixed
failures remain failures; a not-found does not swallow a simultaneous denial.

```ts
import * as GraphQL from "@distilled.cloud/core/graphql";

const maybeProject = Railway.project(
  { id: "your-project-id" },
  { id: true, name: true },
).pipe(
  GraphQL.catchTags("RailwayNotFound", () => Effect.succeed(undefined)),
);
```

To inspect multiple issues directly, catch `GraphQLFailure` with Effect's
`catchTag` and inspect `failure.errors`. Tags inside the aggregate are not
caught by `Effect.catchTag("RailwayNotFound", ...)` on the outer Effect.

Report mode preserves partial data and all typed GraphQL issues in a successful
Effect result. Transport, invalid-request, and decoding failures still use the
Effect error channel:

```ts
const inspected = Effect.gen(function* () {
  const report = yield* Railway.report.query({
    project: {
      where: { id: "your-project-id" },
      select: { id: true, description: true },
    },
  });
  return { availableData: report.data, issues: report.errors };
});
```

GraphQL null propagation may erase an ancestor or all data. Report data is
therefore partial and may be `null` or absent. Recovery cannot reconstruct
values the server omitted.

## Mutations and retry behavior

Named mutations use the same argument/projection API. Scalar mutations require
only arguments:

```ts
const created = Railway.projectCreate(
  { input: { name: "example" } },
  { id: true, name: true },
);
const removed = Railway.projectDelete({ id: "your-project-id" });
```

The native client retries queries at most five times for transport failures and
errors marked retryable in the patched model, with bounded exponential delays.
It never automatically retries mutations. The partial-response API returns
execution errors directly without retrying.
An error while resolving a mutation's return fields can follow a successful
side effect. Reconcile observed state before deciding whether a mutation can
safely be retried. Batching roots into one document is explicit; separate
Effects are not automatically combined.

## Extend error contracts through patches

GraphQL introspection does not declare what each resolver can throw. Railway's
extra error contracts live in [`patches/graphql`](patches/graphql/README.md).
RFC 6902 patches attach tagged definitions and wire matchers to coordinates
such as `Query.project` or `Mutation.tcpProxyDelete`. One patched graph generates
both TypeScript error unions and runtime classifiers. Unrecognized failures
retain an unknown tag so the next observed response can improve the contract.

```sh
bun scripts/convert.ts
bun scripts/generate.ts
pnpm exec oxfmt src/graphql.ts .generated-graphql/railway.json
```

Conversion reads the mirrored introspection schema and fails on stale patch
pointers. Generation reads the committed `.generated-graphql/railway.json`;
it does not need a mirror checkout. Never edit `src/graphql.ts` directly.

---
_Source: https://npm.io/package/@distilled.cloud/railway · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
