StackPanel

Alchemy Module

Documentation for the alchemy module

Shared Alchemy runtime and codegen for hosted deployments.

This module provides:

  • Shared Alchemy configuration under stackpanel.deployment.alchemy.*
  • Generated @gen/alchemy package (createApp, state store factory, helpers, SOPS-aware file module)
  • First-run Cloudflare setup script (alchemy:setup)
  • Provider-scoped deploy helper (alchemy:deploy)
  • Optional bootstrap flow to solve Cloudflare state-store chicken-and-egg

Files

  • default.nix - module entrypoint (imports options + codegen)
  • options.nix - option schema for stackpanel.deployment.alchemy.*
  • codegen.nix - generates package files/scripts and wires devshell env
  • templates/ - TypeScript and shell templates used by codegen (including file.tmpl.ts)

Core Options

stackpanel.deployment.alchemy = {
  enable = true;

  # Shared app defaults
  app-name = "my-project";
  stage = null;

  # State store for alchemy state
  state-store.provider = "auto"; # cloudflare | filesystem | auto

  # Deploy DX
  deploy = {
    enable = true;
    token-scopes = "profile"; # profile | god
    auto-provision-state-store = true;
    run-file = "alchemy.run.ts";
  };
};

Secrets Integration

When these paths are set, values are injected into the devshell environment:

stackpanel.deployment.alchemy.secrets = {
  cloudflare-token-sops-path = "ref+sops://.stack/secrets/vars/common.sops.yaml#/cloudflare-api-token";
  state-token-sops-path = "ref+sops://.stack/secrets/vars/common.sops.yaml#/alchemy-state-token";
  sops-group = "common";
};

Generated Scripts

When stackpanel.deployment.alchemy.deploy.enable = true:

  • alchemy:setup

    • Runs alchemy configure + alchemy login
    • Creates Cloudflare token via alchemy util create-cloudflare-token
    • Generates ALCHEMY_STATE_TOKEN
    • Stores both tokens via secrets:set
    • Optionally bootstraps Cloudflare state store worker
  • alchemy:deploy

    • Loads tokens from env/secrets
    • Auto-runs alchemy:setup if Cloudflare is not configured
    • Executes bunx alchemy deploy <run-file> --stage <stage>

Generated File Module (SOPS-aware)

The generated package now includes @gen/alchemy/file composed with Alchemy's FS provider (File from alchemy/fs) and a secret-aware read layer:

  • AlchemyFile - re-export of Alchemy's File resource from alchemy/fs
  • AlchemyFileModule - composes plain-file reads with ref+sops://... resolution
  • createDefaultSopsResolver() - default resolver backed by sops-age (pure JS)
  • createAlchemyFileModule() - convenience factory
  • secret(value) - wraps plaintext into Alchemy-native secret values (alchemy.secret(...))
  • readSecret(pathOrRef) - reads from plain file or SOPS ref, then returns an Alchemy-native secret wrapper

Example:

import { AlchemyFile, createAlchemyFileModule } from "@gen/alchemy/file";

const files = createAlchemyFileModule();

// Use Alchemy FS File resource (create/update/delete managed file)
await AlchemyFile("config.txt", {
  path: "config.txt",
  content: "hello from alchemy fs",
});

// Plain file read
const packageJson = await files.readFile("package.json");

// SOPS ref read (resolved through sops-age)
const apiToken = await files.readFile(
  "ref+sops://.stack/secrets/vars/common.sops.yaml#/cloudflare-api-token",
);

// Native Alchemy secret wrappers
const apiTokenSecret = await files.readSecret(
  "ref+sops://.stack/secrets/vars/common.sops.yaml#/cloudflare-api-token",
);
const dbPasswordSecret = files.secret("local-dev-password");

Bootstrap State Store

If deploy.auto-provision-state-store = true, codegen emits:

  • packages/gen/alchemy/bootstrap.run.ts

alchemy:setup uses that file to provision and verify CloudflareStateStore with filesystem-backed state first, then normal deploys use shared Cloudflare state.

Typical Usage

nix develop --impure
alchemy:deploy staging

Use stackpanel deploy for the canonical deploy flow. alchemy:deploy is a provider-scoped helper for working directly with the generated Alchemy runtime.

On this page