npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@patronage/alchemy-d1-state

v0.1.1

Published

Alchemy v2 custom state store backed by one shared Cloudflare D1 database, one table per project

Readme

@patronage/alchemy-d1-state

An Alchemy v2 custom state store backed by one shared Cloudflare D1 database, one table per project.

It replaces Alchemy's built-in Cloudflare state-store worker. Instead of each project running its own state worker behind a rotatable shared credential, every project writes to its own table in a single D1 database that is provisioned once by hand and never managed by an Alchemy stack.

pnpm add @patronage/alchemy-d1-state

alchemy and effect are peer dependencies — your stack supplies both.

Usage

import { d1State } from "@patronage/alchemy-d1-state";
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: d1State({
      databaseId: process.env.ALCHEMY_STATE_DB_ID!,
      table: "my_app", // this project's table
    }),
  },
  Effect.gen(function* () {
    // ... resources ...
  })
);

d1State options:

| Option | Required | Notes | | --- | --- | --- | | databaseId | yes | The shared state database's D1 id. | | table | yes | A bare SQL identifier ([A-Za-z_][A-Za-z0-9_]*) interpolated into DDL/DML — this project's table. | | accountId | no | Defaults to CLOUDFLARE_ACCOUNT_ID. | | apiToken | no | Defaults to CLOUDFLARE_API_TOKEN; needs D1 edit on the account. | | baseUrl | no | Overrides the Cloudflare API root. | | client | no | An injected D1Client — used by tests to run the real store code against local SQLite with no network. |

The service is built once per stack run and memoised with Effect.cached, mirroring Alchemy's own inMemoryState/localState layers, so the D1 backend is only exercised on real state access — never at alchemy login or layer construction.

Storage model

Each project's table is keyed (stack, stage, fqn) with upsert semantics: a write to an existing key replaces the row rather than erroring or appending. A stack's resolved output (getOutput/setOutput) lives in the same table under a reserved sentinel FQN, excluded from every resource-facing query.

Provisioning

The store creates and writes rows; it never creates the database. Provision that once, by hand, with wrangler, and do not let an Alchemy stack manage it — an Alchemy-managed store database would let one project's destroy delete every project's state.

1. Create the state database

wrangler d1 create my-alchemy-state

Keep the printed database id; that is what each stack passes as databaseId (Patronage projects read it from ALCHEMY_STATE_DB_ID). One database serves every project — adding a project adds a table, not a database.

2. Create this project's table

The table shape is part of the package's contract, so the migrations ship in the tarball:

node_modules/@patronage/alchemy-d1-state/provisioning/migrations/
  0001_create_project_table.sql   template — replace `project_table` with your table name
  0002_create_hq_table.sql        a worked example of that template

Copy 0001_create_project_table.sql into your own migrations/ directory, rename project_table to the name you pass to d1State({ table }), and apply it:

wrangler d1 migrations apply my-alchemy-state --remote

Adding a project is purely additive — a new table, no other project's data or credentials touched. If the migration is skipped the store will CREATE TABLE IF NOT EXISTS its own table on first write; running the migration is still preferable, because it makes the schema reviewable and diffable before any state depends on it.

3. Enable durability

The state database is the only record of what your stacks own, so back it up independently of D1:

  • Time Travel is on by default (30-day point-in-time restore). Confirm with wrangler d1 time-travel info my-alchemy-state.
  • Add a scheduled export to R2 — e.g. wrangler d1 export my-alchemy-state --remote --output=state-$(date +%F).sql on a cron.

Schema

CREATE TABLE IF NOT EXISTS "<table>" (
  stack TEXT NOT NULL,
  stage TEXT NOT NULL,
  fqn   TEXT NOT NULL,
  data  TEXT NOT NULL,
  PRIMARY KEY (stack, stage, fqn)
);

FQNs are stored via Alchemy's encodeFqn (/__).

Fail-closed secret guard

This store ships with no encryption key by design. Built-in Alchemy stores rely on at-rest encryption to make persisting an Effect Redacted<T> marker safe; this store has nothing to rely on, so it refuses instead.

set() and setOutput() scan the encoded value before writing and fail with a StateStoreError if a Redacted marker appears anywhere in it. The message names the JSON path of the leak plus the resource FQN (set) or the stack/stage the output belongs to (setOutput). It is an Effect failure, not a thrown exception — catch it with Effect.catchTag, not try/catch. Nothing is written when the guard fires: the scan runs before the upsert.

Keep secrets out of persisted props/attr entirely. Store a reference or id and read the secret from Secrets Store or the environment at runtime.

Trust boundary

A Cloudflare API token with D1 access is account-wide: any token that can write one project's table can read and write every project's table in the shared database. Table-per-project is operational isolation, not a security boundary. What this store removes is a shared rotatable credential and secrets at rest — not cross-project inaccessibility. Treat every project's CLOUDFLARE_API_TOKEN as able to reach all state in the shared database, and scope and rotate tokens accordingly.

Exports

| Export | Kind | Notes | | --- | --- | --- | | d1State | function | The state: layer factory for Alchemy.Stack. Takes D1StateProps. | | buildService | function | The underlying StateService builder; d1State composes it with a lazily-resolved client. | | STATE_STORE_ID | constant | Telemetry slug ("cloudflare-d1") reported as alchemy.state_store.id. | | D1StateProps | type | { databaseId, table, accountId?, apiToken?, fetch?, baseUrl?, client? }. | | createHttpD1Client | function | Builds a D1Client that calls the real Cloudflare D1 HTTP query API. | | lazyHttpD1Client | function | Defers client construction (and credential reads) until first use. | | D1Client | type | Minimal client interface: run one parameterised SQL statement, get rows back. | | D1QueryResult | type | Row set returned by a single D1Client statement. | | HttpD1ClientProps | type | Props for createHttpD1Client. | | findRedactedPath | function | Scans an encoded value for an Effect Redacted marker and returns its JSON path; backs the secret guard. |

License

MIT