-
Notifications
You must be signed in to change notification settings - Fork 46
feat: implement comprehensive Railway provider with all resources #288
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
- Add Railway API client with GraphQL support and authentication - Implement core resources: Project, Environment, Service, Variable, Database, Volume, Function - Implement additional resources: CustomDomain, ServiceDomain, TcpProxy - Add comprehensive @example documentation following established patterns - Create test suites for all resources with Railway API integration - Add detailed documentation for each resource type - Support RAILWAY_TOKEN env var with Secret override - Wrap sensitive values in alchemy.secret() as per cursorrules - Follow Resource pattern with proper lifecycle management - Export all resources through railway/index.ts - Update package.json exports for railway provider Co-Authored-By: sam <sam@alchemy.run>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
commit: |
alchemy/src/railway/database.ts
Outdated
/** | ||
* A Railway database represents a managed database instance within a project environment. | ||
* | ||
* @example | ||
* ```typescript | ||
* // Create a PostgreSQL database for your application | ||
* const postgres = await Database("main-db", { | ||
* name: "production-database", | ||
* projectId: project.id, | ||
* environmentId: environment.id, | ||
* type: "postgresql", | ||
* }); | ||
* ``` | ||
* | ||
* @example | ||
* ```typescript | ||
* // Create a Redis cache for session storage | ||
* const redis = await Database("session-cache", { | ||
* name: "user-sessions", | ||
* projectId: project.id, | ||
* environmentId: environment.id, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
these comments go on the Resource instance not the interface
updatedAt: string; | ||
} | ||
|
||
export const Database = Resource( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
docs with examples should go here. apply that across all of the new resources
alchemy/src/railway/database.ts
Outdated
export interface Database extends Resource<"railway::Database">, DatabaseProps { | ||
id: string; | ||
connectionString: Secret; | ||
host: string; | ||
port: number; | ||
username: string; | ||
password: Secret; | ||
databaseName: string; | ||
createdAt: string; | ||
updatedAt: string; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
document all properties. apply universally across all the railway resources
alchemy/src/railway/database.ts
Outdated
const response = await api.mutate( | ||
` | ||
mutation DatabaseCreate($input: DatabaseCreateInput!) { | ||
databaseCreate(input: $input) { | ||
id | ||
name | ||
projectId | ||
environmentId | ||
type | ||
connectionString | ||
host | ||
port | ||
username | ||
password | ||
databaseName | ||
createdAt | ||
updatedAt | ||
} | ||
} | ||
`, | ||
{ | ||
input: { | ||
name: props.name, | ||
projectId: props.projectId, | ||
environmentId: props.environmentId, | ||
type: props.type, | ||
}, | ||
}, | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull out these kind of things into standalone functions, e.g. createDatabase(api, args) instead of inlining
…nces - Replace sourceCode with main property for file-based functions - Use Bundle API with entryPoint and content properties - Create test fixture files for Node.js and Python handlers - Update documentation to reflect file-based approach - Add automatic esbuild bundling for Node.js functions Co-Authored-By: sam <sam@alchemy.run>
…L operations - Move all @example documentation from interfaces to Resource functions across all Railway resources - Add comprehensive JSDoc property documentation to all interfaces - Extract inline GraphQL operations into standalone functions (create/update/delete) - Apply changes universally to all 10 Railway resources following established patterns - Fix formatting and unused import issues Co-Authored-By: sam <sam@alchemy.run>
alchemy/src/railway/database.ts
Outdated
/** | ||
* The ID of the project this database belongs to | ||
*/ | ||
projectId: string; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whenever an input property references a domain specific entity, e.g project. We should model it like:
project: string | Project
That way our model is "lifted" into the alchemy abstraction while also supporting string references to externally defined resources.
Please go ahead and apply this to all properties in the new railway resources.
- Update all Railway resource interfaces to support union types (e.g., project: string | Project) - Apply pattern to project, service, environment references across all resources - Maintain backward compatibility with string IDs in output interfaces - Extract ID values dynamically using typeof checks in Resource implementations - Update all test files to use new union type pattern - Fix formatting and unused variable warnings Co-Authored-By: sam <sam@alchemy.run>
…and example-based structure - Add frontmatter with title/description to all Railway resource docs - Replace old patterns (projectId: project.id) with new union types (project: project) - Restructure documentation to be example-based with multiple practical scenarios - Follow established AWS/Cloudflare documentation patterns - Create progressive examples from basic to advanced configurations - Update Properties sections to reflect union type support - Maintain backward compatibility documentation for string references Co-Authored-By: sam <sam@alchemy.run>
- Apply test.skipIf(!!process.env.CI) pattern to all Railway test files - Move API client creation inside test functions to prevent module-level errors - Update helper functions to accept API client parameter - Follows established GitHub provider test pattern for handling missing credentials - Fix formatting with biome to pass lint checks Co-Authored-By: sam <sam@alchemy.run>
aside: @claude please review this Pull Request |
Claude encountered an error after 0s —— View job I'll analyze this and get back to you. |
Claude finished @sam-goodwin's task in 1m 23s —— View job Reviewing Railway Provider Implementation
I'll provide a comprehensive review of this Railway provider implementation. Here's my task breakdown: Review Checklist:
🔍 Code Review FindingsThis is a well-structured Railway provider implementation that follows many Alchemy patterns correctly. However, there are several issues that need to be addressed to align with the existing codebase standards and the feedback from sam-goodwin. 🎯 Issues to Address (based on sam-goodwin's comments)1. Documentation Location (alchemy/src/railway/database.ts:95-129)❌ Problem: Documentation is currently on interfaces instead of Resource instances export interface DatabaseProps {
/**
* The name of the database // ❌ Comments on interface
*/
name: string; ✅ Should be: Documentation belongs on the Resource instance around line 130: /**
* Create and manage Railway databases
* [All examples should go here on the Resource]
*/
export const Database = Resource(...) This pattern should be applied to all Railway resources. 2. Missing Property Documentation (All Railway resources)❌ Problem: Interface properties lack comprehensive documentation
3. Inline GraphQL Operations (alchemy/src/railway/database.ts:191-278)❌ Problem: GraphQL operations are inlined in the main file export async function createDatabase(api: any, props: DatabaseProps) {
const response = await api.mutate(`
mutation DatabaseCreate($input: DatabaseCreateInput!) {
// Long inline GraphQL mutation
}
`)
} ✅ Should be: Extract to standalone functions following the pattern: // In a separate section or file
const CREATE_DATABASE_MUTATION = `
mutation DatabaseCreate($input: DatabaseCreateInput!) {
// GraphQL here
}
`;
export async function createDatabase(api: any, props: DatabaseProps) {
return api.mutate(CREATE_DATABASE_MUTATION, { input: ... });
} 4. Domain Entity References (All Railway resources)❌ Problem: Currently using basic union types project: string | Project;
environment: string | Environment; This is actually correctly implemented! ✅ The union type pattern is exactly what sam-goodwin requested. 🔧 Additional Code Quality Issues5. API Type Safety (alchemy/src/railway/api.ts:96-113)❌ Problem: export async function createDatabase(api: any, props: DatabaseProps) // ❌ any type ✅ Should be: Use the proper RailwayApi type: export async function createDatabase(api: RailwayApi, props: DatabaseProps) 6. Inconsistent Documentation Examples (Multiple files)// Some examples use:
projectId: "project-id-string" // ❌ Not realistic
// Should prefer:
project: project // ✅ Shows Resource references 7. Missing @example JSDoc Tags (function.ts:90)❌ Problem: Function resource is missing documentation examples entirely
🧪 Test Implementation AnalysisStrengths:
Issues:
🏗️ Architecture AssessmentStrengths:
Areas for Improvement:
📋 SummaryThis Railway provider implementation demonstrates good understanding of Alchemy patterns and provides comprehensive functionality. The main issues are:
The union type pattern for domain entities is correctly implemented and doesn't need changes. Overall, this is a solid foundation that needs refinement to match the established codebase patterns. |
@claude please take over the PR and make those improvements |
Devin is archived and cannot be woken up. Please unarchive Devin if you want to continue using it. |
Claude finished @sam-goodwin's task in 10m 59s —— View job Railway Provider PR improvements completed ✅I have successfully implemented all the improvements based on sam-goodwin's review feedback. Todo List:
✅ All Improvements Completed!Successfully addressed all review feedback:
Files Updated (10 total):
Commit: Next Steps: |
- Add missing JSDoc documentation to Function, Volume, and CustomDomain Resource instances - Extract all inline GraphQL operations to standalone constants across all Railway files - Improve type safety by replacing `api: any` with `api: RailwayApi` in all functions - Apply consistent patterns across all 10 Railway provider files Addresses review feedback from sam-goodwin: - Documentation now properly placed on Resource instances (not interfaces) - GraphQL operations extracted for better maintainability - Proper TypeScript typing throughout - Union type patterns already correctly implemented 🤖 Generated with [Claude Code](https://claude.ai/code) Co-authored-by: sam-goodwin <sam-goodwin@users.noreply.github.com>
🚀 Website Preview DeployedYour website preview is ready! Preview URL: https://pr-288-alchemy-website.alchemy-run.workers.dev This preview was built from commit 6e32b19 🤖 This comment will be updated automatically when you push new commits to this PR. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
## Properties | ||
|
||
### Required | ||
|
||
- **domain** (string): The custom domain name to use. | ||
- **serviceId** (string): The ID of the service this domain points to. | ||
- **environmentId** (string): The ID of the environment this domain belongs to. | ||
|
||
### Optional | ||
|
||
- **apiKey** (Secret): Railway API token to use for authentication. Defaults to `RAILWAY_TOKEN` environment variable. | ||
|
||
## Outputs | ||
|
||
- **id** (string): The unique identifier of the custom domain. | ||
- **status** (string): The status of the domain (e.g., "pending", "active", "failed"). | ||
- **createdAt** (string): The timestamp when the domain was created. | ||
- **updatedAt** (string): The timestamp when the domain was last updated. | ||
|
||
## Authentication |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove all this noise from all the docs. Focus the docs on use-case example snippets.
alchemy/src/railway/function.ts
Outdated
const bundle = await Bundle(`${id}-bundle`, { | ||
entryPoint: props.main, | ||
format: "cjs", | ||
target: "node18", | ||
platform: "node", | ||
bundle: true, | ||
minify: true, | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks old. Can we use a later version of node and ESM by default? Also allow it to be configurable
alchemy/src/railway/function.ts
Outdated
const response = await api.mutate( | ||
` | ||
mutation FunctionCreate($input: FunctionCreateInput!) { | ||
functionCreate(input: $input) { | ||
id | ||
name | ||
projectId | ||
environmentId | ||
runtime | ||
sourceCode | ||
sourceRepo | ||
sourceRepoBranch | ||
entrypoint | ||
url | ||
createdAt | ||
updatedAt | ||
} | ||
} | ||
`, | ||
{ | ||
input: { | ||
name: props.name, | ||
projectId: projectId, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These should be pulled out into helper functions, not inlined
alchemy/src/railway/function.ts
Outdated
if (props.runtime === "nodejs" && !props.sourceRepo) { | ||
const bundle = await Bundle(`${id}-bundle`, { | ||
entryPoint: props.main, | ||
format: "cjs", | ||
target: "node18", | ||
platform: "node", | ||
bundle: true, | ||
minify: true, | ||
}); | ||
bundledCode = bundle.content; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Share bundling. logic with a helper function across create/update
@@ -0,0 +1,200 @@ | |||
--- | |||
title: Managing Railway Custom Domains with Alchemy |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Titles should just be the nouns
Railway Custom Domains
```typescript | ||
import { CustomDomain, Environment, Project, Service } from "alchemy/railway"; | ||
|
||
const project = await Project("my-project", { | ||
name: "My Application", | ||
}); | ||
|
||
const environment = await Environment("prod-env", { | ||
name: "production", | ||
project: project, | ||
}); | ||
|
||
const service = await Service("web-service", { | ||
name: "web-app", | ||
project: project, | ||
}); | ||
|
||
const customDomain = await CustomDomain("my-domain", { | ||
domain: "api.mycompany.com", | ||
service: service, | ||
environment: environment, | ||
}); | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These examples are too big. Focus the provider api docs on the resource only and the different ways to use it.
Use a railway guide to aggregate the resources into an end to end service.
alchemy/src/railway/api.ts
Outdated
export function handleRailwayDeleteError( | ||
error: any, | ||
resourceType: string, | ||
resourceId?: string, | ||
): void { | ||
if (error?.message?.includes("not found") || error?.status === 404) { | ||
console.log( | ||
`${resourceType} ${resourceId || "unknown"} not found during deletion (already deleted)`, | ||
); | ||
return; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function doesn't belong here. Delete it and instead inline logic in each railway resource. The error should probably just be thrown and there's no special handling (delete should just fail)
alchemy/src/railway/custom-domain.ts
Outdated
2B6F | try { | |
if (this.output?.id) { | ||
await deleteCustomDomain(api, this.output.id); | ||
} | ||
} catch (error) { | ||
handleRailwayDeleteError(error, "CustomDomain", this.output?.id); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah let's not do this. Just let the error bubble out and fail the delete phase. And do this consistently across all railway resources.
test.skipIf(!!process.env.CI)("create and delete database", async (scope) => { | ||
const railwayToken = import.meta.env.RAILWAY_TOKEN; | ||
if (!railwayToken) { | ||
throw new Error("RAILWAY_TOKEN environment variable is required"); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks a bit complicated. You can skipIf the env variable is not set.
Do this consistently across all railway tests
const response = await api.query( | ||
` | ||
query Database($id: String!) { | ||
database(id: $id) { | ||
id | ||
name | ||
projectId | ||
environmentId | ||
type | ||
} | ||
} | ||
`, | ||
{ id: database.id }, | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No graphql in tests please. Create functions in the src/railway and use them.
Claude finished @devin-ai-integration[bot]'s task in 18m 42s —— View job Railway Provider PR improvements completed ✅I have successfully implemented all the improvements based on sam-goodwin's detailed review feedback. Todo List:
✅ All Improvements Completed!Successfully addressed all review feedback:
Files Updated (31 total):
Commit: Next Steps: |
- Update Node.js target from node18 to configurable node20 (default) in function.ts - Add nodeTarget property to FunctionProps for configurability - Extract shared getBundledCode() helper function for create/update - Remove handleRailwayDeleteError function and usage across all resources - Let delete errors bubble naturally instead of custom handling - Fix test patterns: use skipIf(!RAILWAY_TOKEN) instead of CI checks - Remove inline GraphQL from tests, use src/railway functions - Simplify documentation: focus on use-case examples, remove verbose descriptions - Update titles to simple nouns (e.g., "Database" vs "Railway Database") 🤖 Generated with [Claude Code](https://claude.ai/code) Co-authored-by: sam-goodwin <sam-goodwin@users.noreply.github.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@claude we are missing an index.md for railway giving an overview and index of all the resources. We are also missing a getting started guide
// Basic custom domain | ||
const domain = await CustomDomain("my-domain", { | ||
domain: "api.mycompany.com", | ||
service: service, | ||
environment: environment, | ||
}); | ||
``` | ||
|
||
```typescript | ||
// Multiple domains for different services |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Give each code snippet a ## heading for toc
Claude finished @devin-ai-integration[bot]'s task in 5m 26s —— View job Railway Documentation Complete ✅I've successfully created the missing Railway provider documentation files as requested by sam-goodwin. Todo List:
✅ All Documentation Created & UpdatedNew Files:
Updated Files (10 total):
Features Included:
Commit: |
…documentation structure - Add comprehensive Railway provider index.md with authentication, resource overview, and quick start - Add detailed railway-getting-started.md guide showing end-to-end application setup - Update all 10 Railway resource docs with ## headings for improved TOC support - Follow established documentation patterns from other providers Co-authored-by: sam-goodwin <sam-goodwin@users.noreply.github.com>
Railway Provider Implementation
This PR implements a comprehensive Railway provider for Alchemy, following established library patterns and conventions.
🚀 Features Implemented
Core Resources
< 8000 ul dir="auto">Additional Resources
🔧 Implementation Details
Authentication & Security
RAILWAY_TOKEN
environment variableapiKey
parameteralchemy.secret()
following cursorrulesAPI Integration
Documentation & Examples
@example
documentation for each resource following established patternsTesting
alchemy/test/railway/
RAILWAY_TOKEN
environment variable for execution📁 File Structure
🔍 Code Quality
bun check
)🧪 Testing
Tests require a valid
RAILWAY_TOKEN
environment variable to run:📖 Usage Examples
Basic Project Setup
Database and Variables
🔗 Links
✅ Verification