Skip to content

feat(railway): add Railway SDK from GraphQL introspection#340

Draft
sam-goodwin wants to merge 3 commits into
mainfrom
feat/railway-sdk
Draft

feat(railway): add Railway SDK from GraphQL introspection#340
sam-goodwin wants to merge 3 commits into
mainfrom
feat/railway-sdk

Conversation

@sam-goodwin

@sam-goodwin sam-goodwin commented Jun 11, 2026

Copy link
Copy Markdown
Collaborator

Adds @distilled.cloud/railway — an Effect-native SDK for Railway's GraphQL API (https://backboard.railway.com/graphql/v2), generated from the introspection schema mirrored in distilled-spec-railway (new submodule, updated daily upstream).

  • One operation per GraphQL query/mutation — 305 total (120 queries, 185 mutations), each with a typed input (the variables) and typed output (the selection set), built on the existing T.GraphQLOp support in core's shared client
  • Verb-first naming: queries get a get prefix (workspacePolicygetWorkspacePolicy), mutations lead with their verb (trustedDomainCreatecreateTrustedDomain), via a new renameOperation hook in core's GraphQL generator. The GraphQL documents keep the schema-derived names; renaming is TypeScript-side only.
  • Error patch system: Railway's schema declares no errors, so typed errors are recorded as they're observed in patches/{operationName}.json and baked into each operation's typed error channel on regeneration (via a new operationErrors hook in core's GraphQL generator)
  • Auth via RAILWAY_API_TOKEN (account/workspace, Authorization: Bearer) or RAILWAY_PROJECT_TOKEN (Project-Access-Token)
  • Registered in root tsconfig, CI (test/pr-package/release workflows), and AGENTS.md
import * as Railway from "@distilled.cloud/railway";

const user = yield* Railway.getMe({});
const projects = yield* Railway.getProjects({ first: 10 });
yield* Railway.createProject({ input: { name: "my-app" } });

Error patching workflow

Unmatched GraphQL errors surface as UnknownRailwayError (carrying the raw envelope). To type one:

// patches/getProject.json
{
  "errors": {
    "ProjectNotFound": {
      "category": "notFound",
      "matchers": [{ "message": { "includes": "Project not found" } }]
    }
  }
}

bun run generate then emits the tagged class into src/operations/errors.ts (annotated with its matchers) and adds it to the operation's errors: [...]. Matchers test extensions.code, message (exact or substring), and HTTP status; the most specific match wins. See patches/README.md.

Typed errors discovered by live testing

41 patch files recorded while building and integration-testing the Railway provider in alchemy-run/alchemy-effect#604 — name conflicts (EnvironmentNameConflict, ServiceNameConflict), invalid names (ServiceNameInvalid, VolumeNameInvalid), TCP proxy constraints (TcpProxyLimitExceeded, TcpProxyOperationInProgress), the project-create rate limiter (ProjectCreateRateLimited, categorized quota so the SDK's burst retry never re-arms the penalty window), NotAuthorized, ProblemProcessingRequest, and more.

Generator additions (core)

  • skipField(parentTypeName, fieldName) — exclude fields the live API errors on when resolving (e.g. Railway's Deployment.sockets fails with "Problem processing request")
  • includeDeprecated: string[] — generate specific deprecated root fields when they're still the only public API for a capability (e.g. tcpProxyCreate)

Hand-written operation

src/custom/getEnvironmentServiceInstances.ts — Railway has no top-level list-services query; the only way to enumerate a project's services is the Environment.serviceInstances connection, one level deeper than the generator's maxDepth: 3 expansion. Lives outside src/operations/ so bun run generate (which wipes that directory) leaves it intact. Used by the alchemy provider to adopt orphaned services by name on ServiceNameConflict.

Pairs with alchemy-run/alchemy-effect#604, whose distilled submodule pointer references this branch.

sam-goodwin and others added 2 commits June 11, 2026 15:19
Co-authored-by: Cursor <cursoragent@cursor.com>
…TypeScript-side only

Co-authored-by: Cursor <cursoragent@cursor.com>
@alchemy-version-bot

alchemy-version-bot Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Install the packages built from this commit:

@distilled.cloud/core

bun add @distilled.cloud/core@https://pkg.distilled.cloud/core/da9ea91

@distilled.cloud/aws

bun add @distilled.cloud/aws@https://pkg.distilled.cloud/aws/da9ea91

@distilled.cloud/axiom

bun add @distilled.cloud/axiom@https://pkg.distilled.cloud/axiom/da9ea91

@distilled.cloud/azure

bun add @distilled.cloud/azure@https://pkg.distilled.cloud/azure/da9ea91

@distilled.cloud/cloudflare

bun add @distilled.cloud/cloudflare@https://pkg.distilled.cloud/cloudflare/da9ea91

@distilled.cloud/coinbase

bun add @distilled.cloud/coinbase@https://pkg.distilled.cloud/coinbase/da9ea91

@distilled.cloud/expo-eas

bun add @distilled.cloud/expo-eas@https://pkg.distilled.cloud/expo-eas/da9ea91

@distilled.cloud/fly-io

bun add @distilled.cloud/fly-io@https://pkg.distilled.cloud/fly-io/da9ea91

@distilled.cloud/gcp

bun add @distilled.cloud/gcp@https://pkg.distilled.cloud/gcp/da9ea91

@distilled.cloud/kubernetes

bun add @distilled.cloud/kubernetes@https://pkg.distilled.cloud/kubernetes/da9ea91

@distilled.cloud/mongodb-atlas

bun add @distilled.cloud/mongodb-atlas@https://pkg.distilled.cloud/mongodb-atlas/da9ea91

@distilled.cloud/neon

bun add @distilled.cloud/neon@https://pkg.distilled.cloud/neon/da9ea91

@distilled.cloud/planetscale

bun add @distilled.cloud/planetscale@https://pkg.distilled.cloud/planetscale/da9ea91

@distilled.cloud/posthog

bun add @distilled.cloud/posthog@https://pkg.distilled.cloud/posthog/da9ea91

@distilled.cloud/prisma-postgres

bun add @distilled.cloud/prisma-postgres@https://pkg.distilled.cloud/prisma-postgres/da9ea91

@distilled.cloud/railway

bun add @distilled.cloud/railway@https://pkg.distilled.cloud/railway/da9ea91

@distilled.cloud/stripe

bun add @distilled.cloud/stripe@https://pkg.distilled.cloud/stripe/da9ea91

@distilled.cloud/supabase

bun add @distilled.cloud/supabase@https://pkg.distilled.cloud/supabase/da9ea91

@distilled.cloud/turso

bun add @distilled.cloud/turso@https://pkg.distilled.cloud/turso/da9ea91

@distilled.cloud/typesense

bun add @distilled.cloud/typesense@https://pkg.distilled.cloud/typesense/da9ea91

@distilled.cloud/workos

bun add @distilled.cloud/workos@https://pkg.distilled.cloud/workos/da9ea91

…ated, custom service-instances query

- patches/*.json: typed errors discovered by live testing (name conflicts,
  invalid names, TCP proxy limits, rate limits, ...) wired into operation
  error channels via the generator
- core generator: add skipField (exclude fields the API errors on) and
  includeDeprecated (generate deprecated-but-only APIs like tcpProxyCreate)
- hand-written custom/getEnvironmentServiceInstances.ts: the only public
  API for enumerating a project's services (used for adopt-by-name);
  lives outside src/operations so regeneration leaves it intact
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant