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

@aurelienbbn/agentlint-plugin-alchemy

v0.1.2

Published

Custom agentlint rules for Alchemy v2 (Infrastructure as Effects) stacks.

Readme

@aurelienbbn/agentlint-plugin-alchemy

npm downloads CI license node

6 agentlint reviews for Alchemy v2 stacks: no silent replacement, no silent data deletion, no silent takeover.

[!WARNING] Requires the engine @aurelienbbn/agentlint >=0.3.0 <0.5.0 as a peer. Evidence.

[!NOTE] Checked against [email protected]. Release candidate: no consumer has calibrated these rules yet. Alchemy is in beta; replace triggers and defaults can move between betas.

Quick start

agentlint init --preset "@aurelienbbn/agentlint-plugin-alchemy#starterPreset"
agentlint rules test
agentlint rules scan --review
agentlint next --format json

init keeps an existing config and prints the install command; never installs. Change rules need the merge base: check out with full history (fetch-depth: 0) in CI.

import { defineConfig } from "@aurelienbbn/agentlint";
import { initConfigExposure, strictPreset } from "@aurelienbbn/agentlint-plugin-alchemy";

export default defineConfig({ extends: [strictPreset], rules: [initConfigExposure] });

Rules

| Rule | strict | starter | Authority | Lifecycle | Fires on | | ----------------------------- | :----: | :-----: | --------- | --------- | -------------------------------------------------------------------------------------------------- | | resource-replacement-review | ✅ | ✅ | human | change | logical ID changed without renamedFrom, declaration removed, replace prop changed, stack renamed | | state-store-change | ✅ | ✅ | human | change | state: option of Alchemy.Stack changed | | removal-policy-change | ✅ | | human | change | RemovalPolicy.retain(...) removed or narrowed, destroy() added, forceDestroy: true set | | removal-policy-review | ✅ | | agent | state | R2, D1, KV declaration with no removal policy, or a retain condition reading Stage | | adopt-review | ✅ | | human | state | AdoptPolicy.adopt(...), --adopt in package.json scripts | | init-config-exposure | 🧪 | | agent | state | yield* Config.*("KEY") directly in a Worker, Container or Lambda init |

✅ in preset · 🧪 opt-in, register explicitly. Presets ignore **/*.d.ts. Every rule is standard revision 1, detector version 1.

Human authority where the answer lives outside the code: whether data in a deployed stage may go, whether a cloud resource someone else owns may be taken over. removal-policy-review stays agent-acceptable because disposability is usually visible in code (cache, preview, fixture); raise it to human in your binding if it isn't.

-yield* Cloudflare.R2.Bucket("Bucket");
+yield* Cloudflare.R2.Bucket("Assets"); // ❌ resource-replacement-review: new empty bucket, old one deleted
+yield* Cloudflare.R2.Bucket("Assets").pipe(Alchemy.renamedFrom("Bucket")); // ✅ state row migrates

-yield* Cloudflare.D1.Database("Orders", { primaryLocationHint: "weur" });
+yield* Cloudflare.D1.Database("Orders", { primaryLocationHint: "apac" }); // ❌ D1 plans a replace

-  state: localState(),
+  state: Cloudflare.state(), // ❌ state-store-change: next deploy plans against empty state
Cloudflare.D1.Database("Orders"); // ❌ removal-policy-review: default destroy
Cloudflare.D1.Database("Orders").pipe(RemovalPolicy.retain(stack.stage === "prod")); // ✅

Cloudflare.D1.Database("Db").pipe(RemovalPolicy.retain(Effect.map(Alchemy.Stage, isProd))); // ❌ alchemy#1738

deploy(program).pipe(AdoptPolicy.adopt(true)); // ❌ adopt-review

Replace triggers, read from each provider's diff in [email protected]:

| Resource | Replaces on | Stays in place | | -------------- | --------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | | R2.Bucket | name set or changed, jurisdiction, locationHint | name removed (deployed name kept), unset ⇄ jurisdiction: "default", storageClass, domains, CORS, lifecycle | | D1.Database | name set or changed, jurisdiction, primaryLocationHint set or changed | name or primaryLocationHint removed, unset ⇄ jurisdiction: "default", readReplication | | KV.Namespace | nothing: title renames in place | title |

A logical-ID change is paired with an added declaration of the same type; a removed declaration that already carried an unconditional RemovalPolicy.retain() stays silent. Declarations moved between files stay silent.

defineResourceReplacementReview({ resources }) replaces the map: { "Queues.Queue": { name: "changed" } }. "changed" fires on any difference including removal; "set" only when the after side sets a differing value; { trigger, absent } compares an unset prop as absent.

removal-policy-change compares RemovalPolicy.retain(...) / RemovalPolicy.destroy(...) calls across the whole change, so a moved call is silent and a retain that leaves with its removed declaration is left to resource-replacement-review. retain(), retain(true) and destroy(false) are the same policy, as are destroy(), destroy(true) and retain(false); a switch to unconditional retain never fires. forceDestroy: true fires only on a bucket that existed before the change: without it R2 refuses to delete a non-empty bucket.

removal-policy-review accepts any enclosing .pipe(...) that mentions retain (including helpers such as retainOnDeployedStage) or RemovalPolicy.destroy. It fires separately when the retain argument reads Stage: the Effect form fails inside Effect-native Workers (alchemy#1738, open); read the stage from Alchemy.Stack instead.

Both take { resources }, default ["R2.Bucket", "D1.Database", "KV.Namespace"].

Alchemy binds every config key a runtime's init loads onto the deployed runtime, secret_text on Cloudflare, with the deployer's value. That is the documented way to ship a secret, so most findings are intended: calibrate before enforcing. Handler-time reads (fetch: Effect.gen(...)) and props effects stay silent. defineInitConfigExposure({ runtimePattern }) replaces the constructor pattern.

Limits

  • Lexical, not semantic. Change rules read before/after text: computed logical IDs compare as expressions, props passed as variables or Effects are not compared, Namespace.push moves are invisible, a state option given as an identifier resolves only through a single same-file const, and regex literals in stack files can confuse the scanner.
  • Named imports are missed. Resources are recognized as R2.Bucket(, D1.Database(, KV.Namespace( behind any namespace; import { Bucket } then Bucket(...) is not.
  • Only the three Cloudflare stores by default. Durable Objects, Queues, Hyperdrive and AWS stores need an explicit resources map.
  • YAML is not scanned. --adopt in GitHub workflow files is invisible: agentlint state rules parse JS, TS and JSON only.
  • Transitive config reads are invisible. A library reading Config during init is bound too (alchemy#1842); only direct yield* Config.*("KEY") fires.
  • Not a plan. alchemy plan is the ground truth; these rules make sure someone reads it.

Registered contract inventory

Generated from package exports by pnpm catalog. Rule-specific options and limitations are described above and in the source tests.

| Rule/check | Trigger or review scope | | ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | adopt-review | Flags AdoptPolicy.adopt(...) and --adopt in package.json scripts, which let Alchemy take over cloud resources it cannot prove it owns. | | init-config-exposure | Flags each effect/Config key yielded in an Alchemy runtime init body, which Alchemy uploads to the deployed runtime as a secret binding. | | removal-policy-change | Flags diffs that remove or narrow RemovalPolicy.retain, add RemovalPolicy.destroy, or set forceDestroy on an existing R2 bucket, so a human confirms the data may be deleted. | | removal-policy-review | Flags stateful Alchemy resources (R2, D1, KV) on the default destroy removal policy, and retain conditions that read Stage through an Effect. | | resource-replacement-review | Flags diffs that make Alchemy replace or delete a stateful resource: a logical ID changed without renamedFrom, a declaration removed, a replace-triggering prop changed, or the stack renamed. | | state-store-change | Flags a changed state: option on Alchemy.Stack so a human confirms how existing state moves to the new store. |

Credited concepts

  • Alchemy AdoptPolicy, alchemy/src/AdoptPolicy.ts 2.0.0-beta.79, and https://alchemy.run/cli/adopting-resources (Apache-2.0 project; concept, independently implemented)
  • Alchemy RemovalPolicy, alchemy/src/RemovalPolicy.ts 2.0.0-beta.79, and https://alchemy.run/infrastructure-as-code/resource-lifecycle (Apache-2.0 project; concept, independently implemented)
  • Alchemy provider diff functions in alchemy/src/Cloudflare/R2/Bucket.ts and D1/Database.ts, 2.0.0-beta.79 (Apache-2.0; replace triggers read, not copied)
  • Alchemy renaming and resource lifecycle docs, https://alchemy.run/infrastructure-as-code/renaming (Apache-2.0 project; concept, independently implemented)
  • Alchemy secrets docs, https://alchemy.run/environments/secrets, and alchemy/src/Platform.ts 2.0.0-beta.79 plan-phase config binding (Apache-2.0 project; concept, independently implemented)
  • Alchemy state store docs, https://alchemy.run/state-store (Apache-2.0 project; concept, independently implemented)
  • alchemy-run/alchemy#1738 (Effect-form retain fails inside Effect-native Workers; reported behavior, not code)
  • alchemy-run/alchemy#1842 (deployer credentials bound through init config reads; reported behavior, not code)

Credits

Concepts from the Alchemy documentation and source (Apache-2.0), re-implemented without copying: renaming, resource lifecycle and removal policy, adopting resources, state store, secrets and config, and reported behavior in alchemy-run/alchemy#1248, #1738 and #1842. starterPreset onboarding: conceptual inspiration from desloppify by Peter O'Malley; no code or guidance copied.