DEV Community

Ramu Narasinga
Ramu Narasinga

Posted on

Alchemy, an Infrastructure-as-Effects framework.

In this article, we review Alchemy. You will learn:

  1. What is Alchemy?

  2. alchemy.run.ts file in OSS projects.

What is Alchemy?

Well, before we jump into its definition, let me explain how I found in the open source projects. I came across the file named alchemy.run.ts in t3code.. This got me google Alchemy and found this site — https://alchemy.run/.

Alchemy is an Infrastructure-as-Effects framework. It extends Infrastructure-as-Code by combining your cloud resources and the application logic that uses them into a single, type-safe program powered by Effects.

Learn more about Alchemy.

Quick start guide:

  1. Create a project
mkdir my-app && cd my-app && bun init -y
Enter fullscreen mode Exit fullscreen mode
  1. Install dependencies
bun add "alchemy@next" "effect@>=4.0.0-beta.100 || >=4.0.0" "@effect/platform-bun@>=4.0.0-beta.100 || >=4.0.0" "@effect/platform-node@>=4.0.0-beta.100 || >=4.0.0"
Enter fullscreen mode Exit fullscreen mode
  1. Create your stack — in the file, alchemy.run.ts
import * as Alchemy from "alchemy";
import * as Cloudflare from "alchemy/Cloudflare";
import * as Effect from "effect/Effect";

export default Alchemy.Stack(
  "MyApp",
  {
    providers: Cloudflare.providers(),
    state: Cloudflare.state(),
  },
  Effect.gen(function* () {
    const bucket = yield* Cloudflare.R2.Bucket("Bucket");

    return {
      bucketName: bucket.bucketName,
    };
  }),
);
Enter fullscreen mode Exit fullscreen mode
  1. Deploy — Run alchemy deploy to create the Bucket on Cloudflare

I got these instructions from the Getting Started docs.

alchemy.run.ts file in OSS projects.

You define your stack in alchemy.run.ts, you have to remember this from the Getting Started guide.

t3code/infra/relay/alchemy.run.ts

You will find the below code in t3code/infra/../alchemy.run.ts

// @effect-diagnostics anyUnknownInErrorContext:off layerMergeAllWithDependencies:off - Alchemy provider helpers expose framework-owned any requirements.
import * as Alchemy from "alchemy";
import * as Axiom from "alchemy/Axiom";
import * as Cloudflare from "alchemy/Cloudflare";
import * as Drizzle from "alchemy/Drizzle";
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
import * as Planetscale from "alchemy/Planetscale";

import * as RelayDb from "./src/db.ts";
import { RelayObservability } from "./src/observability.ts";
import { ManagedEndpointZone, RelayApiZone } from "./src/zone.ts";
import ApiLive, { Api } from "./src/worker.ts";

export default Alchemy.Stack(
  "T3CodeRelay",
  {
    providers: Layer.mergeAll(
      Axiom.providers(),
      Cloudflare.providers(),
      Drizzle.providers(),
      Planetscale.providers(),
    ),
    state: Cloudflare.state(),
  },
  Effect.gen(function* () {
    const db = yield* RelayDb.PlanetscaleDatabase;
    const hyperdrive = yield* RelayDb.RelayHyperdrive;
    const managedEndpointZone = yield* ManagedEndpointZone.pipe(Effect.orDie);
    const relayApiZone = yield* RelayApiZone.pipe(Effect.orDie);
    const observability = yield* RelayObservability;
    const api = yield* Api;

    return {
      databaseName: db.database.name,
      databaseBranchName: db.branch?.name ?? "main",
      hyperdriveName: hyperdrive.name,
      workerName: api.workerName,
      url: api.url,
      relayApiZoneId: relayApiZone.zoneId,
      managedEndpointZoneId: managedEndpointZone.zoneId,
      mobileTracingUrl: observability.traces.otelTracesEndpoint,
      mobileTracingDataset: observability.traces.name,
      mobileTracingToken: observability.mobileIngestToken.token,
      clientTracingUrl: observability.traces.otelTracesEndpoint,
      clientTracingDataset: observability.traces.name,
      clientTracingToken: observability.clientIngestToken.token,
    };
  }).pipe(Effect.provide(ApiLive)),
);
Enter fullscreen mode Exit fullscreen mode

open-seo/alchemy.run.ts

This open-seo/alchemy.run.ts has about 310 LOC at the time of writing this article. Below I copied the important code snippet:

export default Alchemy.Stack(
  "open-seo",
  {
    providers: Cloudflare.providers(),
    // Durable state in the Cloudflare state store (an `alchemy-state-store`
    // Worker on this account; one-time `pnpm alchemy cloudflare bootstrap`).
    // CI fetches its auth token from the account Secrets Store each run.
    state: Cloudflare.state(),
  },
  Effect.gen(function* () {
    const stage = yield* Alchemy.Stage;
    const prod = stage === HOSTED_PROD_STAGE;
    // Fail closed: an unset AUTH_MODE gets the Access-gated mode (matching the
    // app's own default in src/lib/auth-mode.ts), never public hosted signup.
    // hosted/local_noauth must be set explicitly.
    const authMode = yield* Config.string("AUTH_MODE").pipe(
      Config.withDefault("cloudflare_access"),
    );
    const databaseProvider = yield* optionalVar("DATABASE_PROVIDER");
    const workersSubdomain = yield* readWorkersSubdomain({ required: false });

    // Auth needs an absolute BETTER_AUTH_URL. Prod sets it explicitly;
    // previews always derive it from the deterministic worker name — a wrong
    // WORKERS_SUBDOMAIN surfaces in CI's post-deploy Access verify step.
    let authUrl: string;
    if (prod) {
      authUrl = yield* optionalVar("BETTER_AUTH_URL");
      if (!authUrl) {
        return yield* Effect.die(
          new Error(
            "Set BETTER_AUTH_URL (https://app.openseo.so) in .env.production.",
          ),
        );
      }
      // Prod must say which database it runs on. A silently-defaulted "d1"
      // would deploy cleanly against the stale pre-Postgres data.
      if (databaseProvider !== "postgres" && databaseProvider !== "d1") {
        return yield* Effect.die(
          new Error(
            "Set DATABASE_PROVIDER explicitly in .env.production (prod runs postgres).",
          ),
        );
      }
    } else if (workersSubdomain) {
      authUrl = `https://${workerName(stage)}.${workersSubdomain}`;
    } else if (authMode === "hosted") {
      return yield* Effect.die(
        new Error(
          "Hosted previews derive BETTER_AUTH_URL from WORKERS_SUBDOMAIN — set it to the account's full workers.dev subdomain (shown under Workers & Pages).",
        ),
      );
    } else {
      // local_noauth / cloudflare_access never read BETTER_AUTH_URL —
      // src/lib/auth.ts uses a placeholder baseURL off the hosted path.
      authUrl = "";
    }

    // cloudflare_access self-host reads these; hosted/local_noauth leave them
    // empty. (Deriving/provisioning the Access application is a follow-up PR.)
    const teamDomain = yield* optionalVar("TEAM_DOMAIN");
    const policyAud = yield* optionalVar("POLICY_AUD");

    const app = yield* Cloudflare.Worker("open-seo", {
      name: workerName(stage),
      // Prod serves the real domains; the zone is inferred from the hostname.
      domain: prod ? ["app.openseo.so", "www.app.openseo.so"] : undefined,
      // Prebuilt worker from `vite build` (@cloudflare/vite-plugin). The entry
      // exports the DO + WorkflowEntrypoint classes (re-exported by
      // src/server.ts), which `bundle: false` requires. Sibling chunks under
      // assets/ are uploaded as-is by the default module rules.
      main: "./dist/server/index.js",
      bundle: false,
      assets: {
        directory: "./dist/client",
      },
      compatibility: {
        date: wrangler.compatibility_date,
        flags: wrangler.compatibility_flags,
      },
      // Site audits parse and persist batches of HTML inside Workflow steps.
      // Paid Workers permit up to five minutes; keep headroom for unusually
      // link-heavy sites after bounding page bodies and bulk-writing links.
      limits: { cpuMs: 300_000 },
      observability: {
        enabled: wrangler.observability?.enabled ?? true,
        traces: { enabled: wrangler.observability?.traces?.enabled ?? false },
      },
      placement:
        wrangler.placement?.mode === "smart" ? { mode: "smart" } : undefined,
      // Scheduled rank checks — src/server.ts `scheduled` handler.
      crons: wrangler.triggers.crons,
      env: {
        ...makeResources(stage),
        ...dataEnv,
        AUTH_MODE: authMode,
        DATABASE_PROVIDER: databaseProvider || "d1",
        BETTER_AUTH_URL: authUrl,
        TEAM_DOMAIN: teamDomain,
        POLICY_AUD: policyAud,

        // Prod-only: pooled Postgres via the existing Hyperdrive config.
        ...(prod ? { HYPERDRIVE: makeHyperdrive() } : {}),

        // Durable Objects (Agents SDK chat agents). Alchemy backs new DO
        // classes with SQLite storage, which both require.
        ...Object.fromEntries(
          wrangler.durable_objects.bindings.map((binding) => [
            binding.name,
            Cloudflare.DurableObject(binding.name, {
              className: binding.class_name,
            }),
          ]),
        ),

        // Cloudflare Workflows (upstream props-only form for prebuilt
        // workers). Workflow names are ACCOUNT-scoped: prod owns the
        // unsuffixed names; previews carry the stage suffix so concurrent
        // stages can't repoint each other's workflows (registration is a
        // PUT-as-upsert on the name).
        ...Object.fromEntries(
          wrangler.workflows.map((workflow) => [
            workflow.binding,
            Cloudflare.Workflow(
              prod ? workflow.name : `${workflow.name}-${stage}`,
              { className: workflow.class_name },
            ),
          ]),
        ),
      },
    }).pipe(
      // Prod adopts the live worker serving app.openseo.so; never delete it
      // on destroy. (Workflow registrations aren't individually retainable —
      // they're created inside the worker provider — but re-registering them
      // is a lossless upsert, unlike deleting the data-bearing resources.)
      Alchemy.RemovalPolicy.retain(prod),
    );

    return { url: app.url.as<string>() };
  }),
Enter fullscreen mode Exit fullscreen mode

Tbh, I do not completely understand this stack setup using Cloudflare. Doing some simple project deployments on cloudflare should help you understand the concepts better.

About me:

Hey, my name is Ramu Narasinga. Email: ramu.narasinga@gmail.com

I spent 3+ years studying OSS codebases and wrote 400+ articles on what makes the production-grade. Now I'm putting that into practice differently - instead of writing every fix myself, I run coding agents that do it.

How it works? Register your machine as a Runtime, point it at your repo. Agents pick up issues. write the fix, open the PR. You just review, they execute.

Build your coding agents and get more work done in less time at thinkthroo.com

References:

  1. pingdotgg/t3code/blob/main/infra/relay/alchemy.run.ts.

  2. github.com/every-app/open-seo/blob/main/alchemy.run.ts.

  3. npmjs.com/package/alchemy?activeTab=readme

  4. alchemy.run/getting-started/

Top comments (0)