npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@distilled.cloud/railway

v1.0.0-rc.12

Published

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.

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.

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:

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.

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.

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:

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:

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. 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.

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.