Skip to content

feat(railway): railway sdk#226

Open
Mkassabov wants to merge 18 commits into
mainfrom
pear/railway
Open

feat(railway): railway sdk#226
Mkassabov wants to merge 18 commits into
mainfrom
pear/railway

Conversation

@Mkassabov

Copy link
Copy Markdown
Contributor

No description provided.

@alchemy-version-bot

alchemy-version-bot Bot commented May 2, 2026

Copy link
Copy Markdown
Contributor

Install the packages built from this commit:

@distilled.cloud/core

bun add https://pkg.distilled.cloud/core/bb2efc6

@distilled.cloud/aws

bun add https://pkg.distilled.cloud/aws/bb2efc6

@distilled.cloud/cloudflare

bun add https://pkg.distilled.cloud/cloudflare/bb2efc6

@distilled.cloud/gcp

bun add https://pkg.distilled.cloud/gcp/bb2efc6

@distilled.cloud/neon

bun add https://pkg.distilled.cloud/neon/bb2efc6

@distilled.cloud/planetscale

bun add https://pkg.distilled.cloud/planetscale/bb2efc6

@distilled.cloud/prisma-postgres

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

@distilled.cloud/stripe

bun add https://pkg.distilled.cloud/stripe/bb2efc6

@distilled.cloud/supabase

bun add https://pkg.distilled.cloud/supabase/bb2efc6

@distilled.cloud/posthog

bun add https://pkg.distilled.cloud/posthog/bb2efc6

@distilled.cloud/axiom

bun add https://pkg.distilled.cloud/axiom/bb2efc6

@distilled.cloud/azure

bun add https://pkg.distilled.cloud/azure/bb2efc6

@distilled.cloud/kubernetes

bun add https://pkg.distilled.cloud/kubernetes/bb2efc6

@distilled.cloud/coinbase

bun add https://pkg.distilled.cloud/coinbase/bb2efc6

@distilled.cloud/mongodb-atlas

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

@distilled.cloud/fly-io

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

@distilled.cloud/turso

bun add https://pkg.distilled.cloud/turso/bb2efc6

@distilled.cloud/typesense

bun add https://pkg.distilled.cloud/typesense/bb2efc6

@distilled.cloud/workos

bun add https://pkg.distilled.cloud/workos/bb2efc6

@distilled.cloud/expo-eas

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

@distilled.cloud/railway

bun add https://pkg.distilled.cloud/railway/bb2efc6

Mkassabov added 17 commits May 2, 2026 08:34
…back

- generate-graphql: don't emit selection set on scalar/enum leaf types,
  which caused GRAPHQL_VALIDATION_FAILED on every mutation returning
  Boolean/scalar (workspaceUserInvite, etc.).
- railway/credentials: use || instead of ?? for env-var fallbacks so
  empty-string secrets in CI (RAILWAY_API_URL with no value configured)
  fall back to the default rather than producing a relative URL.
- vitest: cap to 2 worker forks. Railway aggressively rate-limits parallel
  requests, which made the previous CI run hang for 30+ minutes on retry
  timeouts.
- tests: BadCreds tests now accept RailwayNotAuthorized OR RailwayNotFound.
  Railway returns 'X not found' (not 'Not Authorized') for many queries
  when the bearer token is invalid, because its resolvers can't tell the
  difference between unauthorized access and unknown resources.
Railway's resolvers don't reliably distinguish 'not found', 'forbidden',
and 'invalid input' for fabricated UUIDs - depending on the field, the
gateway returns any of {Not Authorized, X not found, Invalid X} or an
unmapped INTERNAL_SERVER_ERROR. Tests now accept any typed Railway*
error rather than a single specific tag. The contract being tested is
'this returns a typed error, not a network/parse failure', which still
verifies the SDK's error matching pipeline.
…message map

Adds two new typed errors and additional message patterns so every
INTERNAL_SERVER_ERROR routes to a specific Railway* class — the catch-all
UnknownRailwayError should never surface in tests:

- RailwayRateLimited (throttling category, auto-retried): matches
  'Whoa there pal!' and 'try again in a' messages.
- RailwayServerError (server category): matches 'Problem processing
  request' opaque gateway failures.
- RailwayNotFound: now also matches 'Could not find ...' (not just
  trailing 'not found').
- RailwayInvalidInput: now matches 'Expected ...', "Can't ...",
  'Service is not deployed ...', and 'has no connected repo'.
Railway's per-resource quotas (e.g. 'Only one project can be created
per user every 30s') are fixed-window — retrying inside a single test
hits the same window and just burns the test timeout. Removing the
throttling category so the shared retry pipeline doesn't spin on these.
Callers can implement their own back-off if they need to wait it out.
…ota/plan-tier patterns

- Apply RAILWAY_MESSAGE_MAP to any error message, not only ones with
  extensions.code = INTERNAL_SERVER_ERROR. The 'Problem processing
  request' gateway error has no code, so it was falling through to
  UnknownRailwayError.
- New patterns:
  - 'You've hit the X limit', 'creation limit for new accounts'
    -> RailwayRateLimited (per-day quotas, e.g. service creation cap)
  - 'You can only ...', 'on the Pro plan'
    -> RailwayInvalidInput (plan-tier gating)
The previous schedule (exponential 1s..unbounded * recurs(8)) could burn
255 seconds per request on a pathological rate-limit response, which
made the railway test suite hang for 30+ minutes. New schedule maxes
out at ~6s of wall time across all retries, well within a typical 30s
test timeout.
Reducing to 3 attempts caused many tests to fail with TooManyRequests
because Railway's gateway needed more attempts to clear. Restoring the
attempt count but keeping the 2s per-delay cap so the schedule stays
bounded (~16s total) and doesn't run into the 30s test timeout.
- vitest: pin to singleFork. Railway's edge rate-limits parallel
  requests at the Cloudflare layer; serial execution costs ~13 min
  for the full suite but is the only reliable mode.
- test/setup.ts: lazy shared project + service used across the suite.
  Created on first access via retryUntilSuccess (projectCreate is a
  hard 30s/user quota), torn down in process.beforeExit with polling
  until Railway has actually removed the project.
- test/project.test.ts: rewritten to use the shared project for happy
  path. Non-existent-UUID test asserts RailwayNotAuthorized to match
  Railway's actual behavior (its resolvers can't tell missing from
  forbidden when the bearer is valid but the resource isn't yours).
- core: added a TODO(pear) explaining why Railway warrants its own
  retry policy in future.
Across 275 test files:

- Rewrite `expect(_tag).toBe("RailwayNotFound")` to
  `expect(_tag).toBe("RailwayNotAuthorized")` for tests using
  NON_EXISTENT_UUID with a valid bearer. Railway's resolvers can't tell
  missing from forbidden when the bearer is valid but the resource isn't
  the caller's, so unknown UUIDs surface as 'Not Authorized'.
- Drop the trailing `message.toMatch(/not found$/i)` assertion since
  the message will be 'Not Authorized'.
- Rename mislabeled "happy path - exercises X with fabricated id" tests
  to "fabricated id surfaces RailwayNotAuthorized X". These were never
  real happy paths — they probe the SDK's error pipeline with bogus
  input.
- Rewrite `environments.test.ts`, `service.test.ts` to use the
  shared resources from `test/setup.ts` instead of fabricating their
  own. Other test files still create their own fresh resources; those
  will be migrated next batch.
25 test files where the only resource creation was the project itself
now use `getSharedProject()` instead of creating their own project per
test. Eliminates the 30s/user projectCreate quota collision under
serial execution and cuts ~3-5s off each test for project creation
that previously had to spin up a new one.

Files with nested resource creation (`serviceCreate`, `volumeCreate`,
`environmentCreate`, etc.) are unchanged — they need bespoke rewrites
that share the parent project but provision their own child resources.

Includes scripts/rewrite-tests.ts that performed the rewrite, kept in
the repo so a future spec refresh can re-run it after `bun run
generate`.
…rces

15 more test files converted to use the shared project/service:

- environment*: env-creating tests share the project, clean up via
  environmentDelete instead of cascading projectDelete
- canvasView*, environmentPatch*: same pattern with explicit env cleanup
- volume*, adminVolumeInstancesForVolume: share service, clean up the
  volume directly
- bucket*: share project (bucket cleanup is via project teardown since
  there's no bucketDelete op)
- customDomain: share service, clean up the custom domain directly
- service*: share service, restore the original name on serviceUpdate
…ate tests

- serviceInstance*, serviceRemoveUpstreamUrl: use the shared service
  directly. These mutate cluster state but reset is implicit.
- projectInvitation*: share the project, clean up the invitation
  via projectInvitationDelete instead of cascading projectDelete.
- projectTokenCreate: share the project. Token leaks because the API
  returns just the bearer string with no id (no projectTokenDelete via
  the create response).
- templateGenerate: use the shared service so a template can be derived
  from a project that already has a service in it.
- projectCreate / projectDelete: the actual lifecycle tests need their
  own project. Wrap in retryUntilSuccess so the 30s/user quota window
  doesn't fail the test outright.
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