State
Alchemy uses a transparent and pluggable state management system to track resource lifecycles and enable idempotent operations. It’s designed to be simple, with multiple backend options ranging from local files to cloud storage.
What is State in Alchemy?
Section titled “What is State in Alchemy?”State in Alchemy consists of resource data that tracks the current status, properties, and outputs of each resource. By default, it’s stored in JSON files in a .alchemy
directory, organized by app and stage:
.alchemy/ my-app/ dev/ my-resource.json my-other-resource.json
State File Structure
Section titled “State File Structure”Each state file contains the full information about a resource:
{ "provider": "service::ResourceName", "data": {}, "status": "updated", "output": { "id": "resource-123", "name": "My Resource", "createdAt": 1679012345678 }, "props": { "name": "My Resource", "description": "This is a test resource" }}
The state file includes:
- provider: The resource type identifier
- data: Internal provider-specific data
- status: Current lifecycle status (created, updated, deleted)
- output: The resource’s current output values
- props: The resource’s input properties
How Alchemy Uses State
Section titled “How Alchemy Uses State”Alchemy uses state to determine the appropriate action for each resource:
- No state file: The resource is created
- State exists + props unchanged: The resource is skipped
- State exists + props changed: The resource is updated
- Resource removed from code: The resource is deleted
This approach enables idempotent operations - running the same code multiple times produces the same result, avoiding duplicate resource creation.
State Location
Section titled “State Location”By default, Alchemy stores state files in the .alchemy
directory in your project root. This approach has several benefits:
- Transparency: State files are plain JSON and can be inspected and modified manually
- Versioning: State can be committed to source control with your code
- Portability: No external service dependencies required
State Inspection
Section titled “State Inspection”State files can be directly inspected:
cat .alchemy/my-app/dev/my-resource.json
This transparency helps with debugging and understanding what Alchemy is doing.
Customizing State Storage
Section titled “Customizing State Storage”Change .alchemy
directory location
Section titled “Change .alchemy directory location”Perhaps you want to change the location of the .alchemy
directory in a monorepo.
const app = await alchemy("my-app", { stateStore: (scope) => new FileSystemStateStore(scope, { rootDir: path.resolve(import.meta.dir, "..", ".alchemy") })});
Durable Objects State Store (Recommended)
Section titled “Durable Objects State Store (Recommended)”For high-performance cloud state storage, use DOStateStore with Cloudflare Durable Objects.
import { DOStateStore } from "alchemy/cloudflare";
// Set CLOUDFLARE_API_KEY, CLOUDFLARE_EMAIL, and ALCHEMY_STATE_TOKEN env varsconst app = await alchemy("my-app", { stage: "prod", phase: process.argv.includes("--destroy") ? "destroy" : "up", stateStore: (scope) => new DOStateStore(scope)});
You can also provide explicit configuration:
import { DOStateStore } from "alchemy/cloudflare";
const app = await alchemy("my-app", { stage: "prod", phase: process.argv.includes("--destroy") ? "destroy" : "up", stateStore: (scope) => new DOStateStore(scope, { // Cloudflare API credentials apiKey: alchemy.secret(process.env.CLOUDFLARE_API_KEY), email: process.env.CLOUDFLARE_EMAIL, // Optional: customize worker name (defaults to "alchemy-state") worker: { name: "my-app-state" } })});
DOStateStore automatically creates and manages a Cloudflare Worker with Durable Objects for state storage.
R2 Rest State Store
Section titled “R2 Rest State Store”Alchemy also supports state storage using Cloudflare R2, though DOStateStore is recommended for better performance:
// Example with Cloudflare R2 state storeconst app = await alchemy("my-app", { stage: "prod", phase: process.argv.includes("--destroy") ? "destroy" : "up", stateStore: (scope) => new R2RestStateStore(scope, { apiKey: alchemy.secret(process.env.CLOUDFLARE_API_KEY), email: process.env.CLOUDFLARE_EMAIL, bucketName: process.env.CLOUDFLARE_BUCKET_NAME!, })});
S3 State Store
Section titled “S3 State Store”For AWS-based deployments, use S3StateStore for reliable cloud state storage with Amazon S3:
import { S3StateStore } from "alchemy/aws";
const app = await alchemy("my-app", { stage: "prod", phase: process.argv.includes("--destroy") ? "destroy" : "up", stateStore: (scope) => new S3StateStore(scope, { bucketName: "my-app-alchemy-state", region: "us-east-1" })});
S3StateStore provides durable, scalable state storage with automatic retry logic and proper error handling. The S3 bucket must be created beforehand, and AWS credentials must be configured with appropriate S3 permissions.
Security and Secrets
Section titled “Security and Secrets”State files may contain sensitive information. Alchemy provides a mechanism to encrypt sensitive values using the alchemy.secret()
function:
const apiKey = alchemy.secret(process.env.API_KEY);
await ApiResource("my-api", { key: apiKey});
Secrets are encrypted in state files:
{ "props": { "key": { "@secret": "Tgz3e/WAscu4U1oanm5S4YXH..." } }}
Always use alchemy.secret()
for sensitive values to prevent them from being stored in plain text.