Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .vendor/distilled
Submodule distilled added at d57129
135 changes: 135 additions & 0 deletions bun.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion distilled
Submodule distilled updated 376 files
44 changes: 44 additions & 0 deletions examples/railway-postgres-drizzle/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# railway-postgres-drizzle

The [railway-postgres](../railway-postgres/) stack with Drizzle ORM
(`drizzle-orm/effect-postgres`) instead of raw SQL:

- **`Railway.Project`** — the project containing every service ([src/Project.ts](./src/Project.ts))
- **`Railway.PostgresDatabase`** — a Postgres database with a persistent volume ([src/Postgres.ts](./src/Postgres.ts))
- **`Railway.Function`** — an Effect-native HTTP service on a public `*.up.railway.app` domain ([src/Api.ts](./src/Api.ts))
- **`Railway.PostgresDatabase.bind`** — injects the connection details via Railway reference variables
- **`Drizzle.postgres`** — Drizzle over Effect's native Postgres client; the pool is built lazily on the first query and shared for the process lifetime

## Routes

| Route | Behavior |
| --------- | ------------------------------ |
| `GET /` | List users |
| `POST /` | Insert a random user |
| `/health` | Railway healthcheck |

## Deploy

Authenticate with a Railway API token (or run `alchemy login railway`):

```sh
export RAILWAY_API_TOKEN=...
```

Then, from this directory:

```sh
bun i
bun run deploy
```

```sh
curl https://<service>.up.railway.app/
curl -X POST https://<service>.up.railway.app/
```

## Destroy

```sh
bun run destroy
```
21 changes: 21 additions & 0 deletions examples/railway-postgres-drizzle/alchemy.run.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import * as Alchemy from "alchemy";
import * as Railway from "alchemy/Railway";
import * as Effect from "effect/Effect";
import Api from "./src/Api.ts";

export default Alchemy.Stack(
"RailwayPostgresDrizzleExample",
{
providers: Railway.providers(),
state: Alchemy.localState(),
},
Effect.gen(function* () {
// The Project and Postgres database are registered by the Api
// Function itself (see src/Api.ts) — yielding Api deploys all three.
const api = yield* Api;
return {
url: api.url.as<string>(),
projectId: api.projectId,
};
}),
);
29 changes: 29 additions & 0 deletions examples/railway-postgres-drizzle/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"name": "railway-postgres-drizzle",
"version": "0.0.0",
"private": true,
"license": "Apache-2.0",
"repository": {
"type": "git",
"url": "git+https://github.com/alchemy-run/alchemy-effect.git",
"directory": "examples/railway-postgres-drizzle"
},
"type": "module",
"scripts": {
"deploy": "alchemy deploy",
"dev": "alchemy dev",
"destroy": "alchemy destroy",
"logs": "alchemy logs"
},
"dependencies": {
"@effect/platform-node": "catalog:",
"@effect/sql-pg": "catalog:",
"alchemy": "workspace:*",
"drizzle-orm": "1.0.0-rc.1",
"effect": "catalog:",
"pg": "^8.16.3"
},
"devDependencies": {
"@types/pg": "^8.15.6"
}
}
98 changes: 98 additions & 0 deletions examples/railway-postgres-drizzle/src/Api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import * as Drizzle from "alchemy/Drizzle";
import * as Railway from "alchemy/Railway";
import * as Effect from "effect/Effect";
import { HttpServerRequest } from "effect/unstable/http/HttpServerRequest";
import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse";
import { Postgres } from "./Postgres.ts";
import { Project } from "./Project.ts";
import { Users } from "./schema.ts";

/**
* An Effect-native HTTP service deployed as a Railway Function, querying
* the bound Postgres database through Drizzle ORM
* (`drizzle-orm/effect-postgres`).
*
* - `GET /` — list users
* - `POST /` — insert a random user
* - `GET /health` — healthcheck probed by Railway before routing traffic
*/
export default class Api extends Railway.Function<Api>()(
"Api",
Effect.gen(function* () {
const project = yield* Project;
return {
main: import.meta.filename,
project,
name: "api",
// `pg` (the driver under @effect/sql-pg) stays external to the
// bundle; Railway installs it during the image build.
external: ["pg"],
healthcheckPath: "/health",
};
}),
Effect.gen(function* () {
// Deploy time: writes `POSTGRES_URL=${{<db>.DATABASE_URL}}` (plus
// `_HOST`, `_PORT`, `_USER`, `_PASSWORD`, `_DATABASE`) onto this
// service via Railway reference-variable syntax. Runtime: typed
// accessors over those variables.
const conn = yield* Railway.PostgresDatabase.bind(Postgres);

// Drizzle over Effect's native Postgres client. The pool is built
// lazily on the first query and shared for the process lifetime.
const db = yield* Drizzle.postgres(conn.connectionString);

// Idempotent schema bootstrap, run once per process before the
// first query (a real app would apply migrations out of band).
const ensureSchema = yield* Effect.cached(
Effect.gen(function* () {
yield* db.$client.unsafe(
`create table if not exists users (
id serial primary key,
email text not null unique,
name text not null,
created_at timestamptz not null default now()
)`,
);
}),
);

return {
fetch: Effect.gen(function* () {
const request = yield* HttpServerRequest;

if (request.url.startsWith("/health")) {
return HttpServerResponse.text("ok");
}

yield* ensureSchema;

switch (request.method) {
case "GET": {
const users = yield* db.select().from(Users);
return yield* HttpServerResponse.json({ users });
}
case "POST": {
const [user] = yield* db
.insert(Users)
.values({
name: crypto.randomUUID(),
email: `${crypto.randomUUID()}@example.com`,
})
.returning();
return yield* HttpServerResponse.json({ user });
}
default: {
return yield* HttpServerResponse.json(
{ error: "Method not allowed" },
{ status: 405 },
);
}
}
}).pipe(
Effect.catch((error) =>
HttpServerResponse.json({ error: String(error) }, { status: 500 }),
),
),
};
}).pipe(Effect.provide(Railway.PostgresDatabaseBindingLive)),
) {}
19 changes: 19 additions & 0 deletions examples/railway-postgres-drizzle/src/Postgres.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import * as Railway from "alchemy/Railway";
import * as Effect from "effect/Effect";
import { Project } from "./Project.ts";

/**
* A Railway-hosted Postgres database — same image/volume/variable layout
* as Railway's official Postgres template, fully reconciled by Alchemy.
*
* The connection details are consumed by the API Function through
* `Railway.PostgresDatabase.bind` and handed to Drizzle (see src/Api.ts).
*/
export const Postgres = Effect.gen(function* () {
const project = yield* Project;
return yield* Railway.PostgresDatabase("Postgres", {
project,
environment: { environmentId: project.defaultEnvironmentId },
name: "postgres",
});
});
9 changes: 9 additions & 0 deletions examples/railway-postgres-drizzle/src/Project.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import * as Railway from "alchemy/Railway";

/**
* The Railway project that contains every service in this example
* (the API Function and the Postgres database).
*/
export const Project = Railway.Project("Project", {
name: "railway-postgres-drizzle-example",
});
11 changes: 11 additions & 0 deletions examples/railway-postgres-drizzle/src/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { pgTable, serial, text, timestamp } from "drizzle-orm/pg-core";

export const Users = pgTable("users", {
id: serial("id").primaryKey(),
email: text("email").notNull().unique(),
name: text("name").notNull(),
createdAt: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
});
export type User = typeof Users.$inferSelect;
16 changes: 16 additions & 0 deletions examples/railway-postgres-drizzle/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"extends": "../../tsconfig.base.json",
"include": ["alchemy.run.ts", "src/**/*.ts"],
"compilerOptions": {
"noEmit": true,
"rootDir": ".",
"module": "Preserve",
"moduleResolution": "Bundler",
"target": "ESNext"
},
"references": [
{
"path": "../../packages/alchemy/tsconfig.json"
}
]
}
58 changes: 58 additions & 0 deletions examples/railway-postgres/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# railway-postgres

An end-to-end Effect-native Railway application built with Alchemy:

- **`Railway.Project`** — the project containing every service ([src/Project.ts](./src/Project.ts))
- **`Railway.PostgresDatabase`** — a Postgres database with a persistent volume, mirroring Railway's official template ([src/Postgres.ts](./src/Postgres.ts))
- **`Railway.Function`** — an Effect-native HTTP service bundled and uploaded straight to Railway's build pipeline, exposed on a public `*.up.railway.app` domain ([src/Api.ts](./src/Api.ts))
- **`Railway.PostgresDatabase.bind`** — binds the database's connection details into the Function via Railway reference variables (`${{Postgres.DATABASE_URL}}`), surfaced at runtime as typed accessors (`connectionString`, `host`, `port`, `user`, `password`, `database`)
- **`Sql.postgres`** — Effect's native Postgres client (`@effect/sql-pg`), opened from the bound connection string; the pool is built lazily on the first query and shared for the process lifetime
- **`effect/Config`** — `GREETING` is read with `Config.string` in the Init phase, automatically published as a service variable, and re-read from the environment at runtime

For the same stack with Drizzle ORM, see
[railway-postgres-drizzle](../railway-postgres-drizzle/).

## Routes

| Route | Behavior |
| --------- | ----------------------------------------------------------------- |
| `/` | Returns the `GREETING` config value |
| `/db` | Runs `select now(), current_database(), version()` over the bound Postgres |
| `/health` | Railway healthcheck |

## Deploy

Authenticate with a Railway API token (or run `alchemy login railway`):

```sh
export RAILWAY_API_TOKEN=...
```

Then, from this directory:

```sh
bun i
bun run deploy
```

The stack outputs include the public URL:

```sh
curl https://<service>.up.railway.app/
curl https://<service>.up.railway.app/db
```

Optionally set `GREETING` in your environment (or `.env`) before deploying to
see the config value flow through to the service.

## Destroy

```sh
bun run destroy
```

## Notes

- Railway rejects service names containing underscores, so the services set
explicit `name`s instead of relying on the generated physical name (which
embeds the stage, e.g. `dev_yourname`).
21 changes: 21 additions & 0 deletions examples/railway-postgres/alchemy.run.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import * as Alchemy from "alchemy";
import * as Railway from "alchemy/Railway";
import * as Effect from "effect/Effect";
import Api from "./src/Api.ts";

export default Alchemy.Stack(
"RailwayPostgresExample",
{
providers: Railway.providers(),
state: Alchemy.localState(),
},
Effect.gen(function* () {
// The Project and Postgres database are registered by the Api
// Function itself (see src/Api.ts) — yielding Api deploys all three.
const api = yield* Api;
return {
url: api.url.as<string>(),
projectId: api.projectId,
};
}),
);
28 changes: 28 additions & 0 deletions examples/railway-postgres/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"name": "railway-postgres",
"version": "0.0.0",
"private": true,
"license": "Apache-2.0",
"repository": {
"type": "git",
"url": "git+https://github.com/alchemy-run/alchemy-effect.git",
"directory": "examples/railway-postgres"
},
"type": "module",
"scripts": {
"deploy": "alchemy deploy",
"dev": "alchemy dev",
"destroy": "alchemy destroy",
"logs": "alchemy logs"
},
"dependencies": {
"@effect/platform-node": "catalog:",
"@effect/sql-pg": "catalog:",
"alchemy": "workspace:*",
"effect": "catalog:",
"pg": "^8.16.3"
},
"devDependencies": {
"@types/pg": "^8.15.6"
}
}
Loading