| 1 | --- |
| 2 | title: State Store |
| 3 | description: How Alchemy persists resource state between deploys to compute diffs and track infrastructure. |
| 4 | --- |
| 5 | |
| 6 | import StateStoreBootstrap from "../../../components/marketing-islands/StateStoreBootstrap.tsx"; |
| 7 | |
| 8 | Alchemy persists resource state between deploys so it can compute |
| 9 | diffs — comparing the desired state in your code against the current |
| 10 | state of your infrastructure. |
| 11 | |
| 12 | ## How state is stored |
| 13 | |
| 14 | Each resource's state is keyed by its **fully qualified name** (FQN), |
| 15 | which includes the namespace path and |
| 16 | [logical ID](/infrastructure-as-code/resource#logical-id). State is |
| 17 | scoped by [stack name](/infrastructure-as-code/stack) and |
| 18 | [**stage**](/environments/stages), so different stacks and |
| 19 | environments are fully isolated. |
| 20 | |
| 21 | A resource's persisted state includes: |
| 22 | |
| 23 | - **Resource type** — e.g. `Cloudflare.R2.Bucket` |
| 24 | - **Input properties** — the props you passed when creating it |
| 25 | - **Output attributes** — the values returned after creation |
| 26 | - **Instance ID** — a unique identifier for this instance |
| 27 | - **Lifecycle status** — `created`, `updating`, `deleting`, etc. |
| 28 | - **[Bindings](/infrastructure-as-effects/binding)** — data attached by policies and event sources |
| 29 | |
| 30 | ## Local state |
| 31 | |
| 32 | By default, state is stored on disk in the `.alchemy/` directory: |
| 33 | |
| 34 | ``` |
| 35 | .alchemy/ |
| 36 | state/ |
| 37 | MyApp/ |
| 38 | dev_sam/ |
| 39 | Bucket.json |
| 40 | Worker.json |
| 41 | ``` |
| 42 | |
| 43 | Add `.alchemy/` to your `.gitignore`: |
| 44 | |
| 45 | ```sh |
| 46 | echo ".alchemy/" >> .gitignore |
| 47 | ``` |
| 48 | |
| 49 | Local state works for solo development. Each developer gets their own |
| 50 | state via their default stage (`dev_$USER`). |
| 51 | |
| 52 | ## Remote state |
| 53 | |
| 54 | For teams and CI, configure a remote state store so all deploys share |
| 55 | the same state. Alchemy includes a Cloudflare-backed store: |
| 56 | |
| 57 | ```typescript |
| 58 | Alchemy.Stack( |
| 59 | "MyApp", |
| 60 | { |
| 61 | providers: Cloudflare.providers(), |
| 62 | state: Cloudflare.state(), |
| 63 | }, |
| 64 | Effect.gen(function* () { |
| 65 | // ... |
| 66 | }), |
| 67 | ); |
| 68 | ``` |
| 69 | |
| 70 | ## Cloudflare state store |
| 71 | |
| 72 | `Cloudflare.state()` is the recommended state store for projects |
| 73 | already running on Cloudflare. It persists state in a Worker backed |
| 74 | by a Durable Object with embedded SQLite, with the auth token and |
| 75 | encryption key kept in your account's Secrets Store. |
| 76 | |
| 77 | ### First-run bootstrap |
| 78 | |
| 79 | The first time you run `alchemy deploy`, `plan`, or `dev` against a |
| 80 | stack configured with `Cloudflare.state()`, Alchemy can't find the |
| 81 | state-store Worker on your account and pauses to ask permission |
| 82 | before deploying it. Confirming kicks off a one-time deploy of the |
| 83 | state-store and its supporting resources: |
| 84 | |
| 85 | <StateStoreBootstrap client:visible /> |
| 86 | |
| 87 | What gets created: |
| 88 | |
| 89 | - **`Api`** — the state-store Worker itself (a Durable Object with |
| 90 | embedded SQLite, enabled on a `workers.dev` subdomain) |
| 91 | - **`AlchemyStateStoreToken`** — bearer token used by the CLI to |
| 92 | authenticate to the Worker, stored in your Secrets Store |
| 93 | - **`StateStoreEncryptionKey`** — symmetric key used to encrypt |
| 94 | resource state at rest inside the Durable Object's SQLite |
| 95 | - **`StateStoreSecrets`** — the Secrets Store that holds the two |
| 96 | secrets above, scoped to your Cloudflare account |
| 97 | - **`StateStoreAuthTokenValue` / `StateStoreEncryptionKeyValue`** — |
| 98 | the generated random values bound into the secrets above |
| 99 | |
| 100 | These resources are reused across every stack and stage that uses |
| 101 | `Cloudflare.state()` on this account — no duplication per project. |
| 102 | Subsequent runs detect the existing Worker and skip the prompt. |
| 103 | |
| 104 | ### Where credentials live |
| 105 | |
| 106 | After the bootstrap, Alchemy writes the state-store URL and bearer |
| 107 | token to a credentials file under your Alchemy profile directory |
| 108 | (`~/.alchemy/<profile>/cloudflare-state-store.json` by default). On |
| 109 | CI, set `CI=true`; the credentials are resolved from the Secrets |
| 110 | Store on every run via a short-lived edge-preview Worker, so nothing |
| 111 | needs to be persisted to disk. |
| 112 | |
| 113 | ### Customizing the worker name |
| 114 | |
| 115 | By default the state-store Worker is named `alchemy-state-store`. |
| 116 | Pass `workerName` to use a separate state store — for example, when |
| 117 | you want a dedicated store per Cloudflare account or per team: |
| 118 | |
| 119 | ```typescript |
| 120 | state: Cloudflare.state({ workerName: "alchemy-state-store-team-a" }), |
| 121 | ``` |
| 122 | |
| 123 | ## Postgres state store |
| 124 | |
| 125 | `postgresState()` persists state in a Postgres database you already run, and it locks: each `(stack, stage)` holds a session advisory lock for the duration of a run, so two concurrent deploys of the same stage cannot interleave. |
| 126 | |
| 127 | ```typescript |
| 128 | import { postgresState } from "alchemy/State/PostgresState"; |
| 129 | import * as Config from "effect/Config"; |
| 130 | |
| 131 | Alchemy.Stack( |
| 132 | "MyApp", |
| 133 | { |
| 134 | providers: Prisma.providers(), |
| 135 | state: postgresState({ url: Config.Redacted("STATE_DATABASE_URL") }), |
| 136 | }, |
| 137 | Effect.gen(function* () { |
| 138 | // ... |
| 139 | }), |
| 140 | ); |
| 141 | ``` |
| 142 | |
| 143 | It is imported from `alchemy/State/PostgresState` rather than the `State` barrel because it depends on the optional `pg` driver, which must stay out of bundled worker code. Install `pg` and `@effect/sql-pg` to use it. |
| 144 | |
| 145 | ## State and lifecycle |
| 146 | |
| 147 | State transitions track the lifecycle of each resource: |
| 148 | |
| 149 | | Status | Meaning | |
| 150 | | ----------- | ---------------------------------------------------------------- | |
| 151 | | `creating` | Create operation in progress | |
| 152 | | `created` | Resource exists and is healthy | |
| 153 | | `updating` | Update operation in progress | |
| 154 | | `updated` | Resource was updated successfully | |
| 155 | | `deleting` | Delete operation in progress | |
| 156 | | `replacing` | Resource is being replaced (new one created, old pending delete) | |
| 157 | |
| 158 | These intermediate states (`creating`, `updating`, `deleting`) exist |
| 159 | because state is persisted before the cloud operation completes. If |
| 160 | the process crashes mid-operation, Alchemy uses the intermediate |
| 161 | state to recover on the next deploy. |
| 162 | |
| 163 | ## In-memory state (testing) |
| 164 | |
| 165 | For tests, Alchemy provides an in-memory state store so tests don't |
| 166 | touch the filesystem: |
| 167 | |
| 168 | ```typescript |
| 169 | import * as TestState from "alchemy/Test/TestState"; |
| 170 | |
| 171 | // Seed with existing resource state |
| 172 | const state = TestState.state({ |
| 173 | Bucket: { |
| 174 | /* ... */ |
| 175 | }, |
| 176 | }); |
| 177 | ``` |
| 178 | |
| 179 | The test harness (`alchemy/Test/Bun` and `alchemy/Test/Vitest`) |
| 180 | handles state setup automatically. |
| 181 | |
| 182 | ## Writing your own state store |
| 183 | |
| 184 | A state store is just an Effect `Layer` that provides the `State` |
| 185 | service — back it with Postgres, S3, Redis, DynamoDB, or any other |
| 186 | backend. See [Custom State Store](/state-store/custom-state-store) for |
| 187 | the full walkthrough. |
| 188 | |
| 189 | ## Where next |
| 190 | |
| 191 | - [Custom State Store](/state-store/custom-state-store) — implement the |
| 192 | `StateService` interface end-to-end. |
| 193 | - [Testing Providers](/testing/testing-providers) — the in-memory store in practice. |
| 194 | - [References](/infrastructure-as-code/references) — reading another |
| 195 | stack's outputs. |