feat(railway): railway sdk#226
Open
Mkassabov wants to merge 18 commits into
Open
Conversation
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 |
…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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.