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

@forge-cms/cloudflare

v1.0.0

Published

Cloudflare-native adapters and types for ForgeCMS (D1, R2).

Readme

@forge-cms/cloudflare

Who needs it: the Cloudflare deployment profile (Pages/Workers + D1 + R2). Boundary: server only. Certified: adapter contracts, local workerd/D1/R2, a packed production consumer, and an isolated real Cloudflare staging rehearsal with backup and restore (spec 090) — not a guarantee for every production deployment. Reference: docs/REFERENCE.md · deployment: https://forge-cms.pages.dev/docs/deployment.

Cloudflare-native adapters and types for ForgeCMS.

Target services

| Service | Binding type | Adapter | Status | | ------- | ------------ | ------------------- | --------------------------------------- | | D1 | D1Database | D1DatabaseAdapter | Implemented (SQL native, Drizzle-ready) | | R2 | R2Bucket | R2StorageAdapter | Implemented |

No KV adapter — nothing in the runtime today needs a key-value store (no caching/settings layer).

Analytics Engine (AnalyticsEngineWriter + AnalyticsEngineQueryClient) is implemented and experimental — see Forge Analytics below.

Preparing for Cloudflare (no real account required yet)

1. D1 Database

The D1DatabaseAdapter is ready to use. It currently runs native D1 SQL via the DatabaseAdapter contract. The adapter is Drizzle-ready: the schema generator and value mapping are shared with @forge-cms/db, so switching to Drizzle's D1 driver is a one-line change when you are ready.

import { D1DatabaseAdapter } from '@forge-cms/cloudflare';

const runtime = new ForgeCmsRuntime({
  collections: [...],
  adapters: {
    database: new D1DatabaseAdapter(),
    // auth, storage...
  }
});

runtime.init({ DB: env.DB }); // Cloudflare binding
await runtime.syncSchema();

To activate D1 later:

  1. Create a D1 database in the Cloudflare dashboard.
  2. Add the binding to wrangler.toml:
    [[d1_databases]]
    binding = "DB"
    database_name = "forge-cms"
    database_id = "your-database-id"
  3. Call runtime.syncSchema() on startup. It creates the tables, applies safe additive changes and refuses any change that needs a reviewed migration (SchemaDriftError, nothing executed; spec 070). See docs/SCHEMA-UPGRADES.md.
  4. Apply refused changes with reviewed migrations (spec 072), from a deploy or maintenance script and never at Worker startup: runtime.runMigrations(migrations, options). D1DatabaseAdapter runs each migration as one D1 batch() (a transaction that rolls back entirely on a failing statement), together with its _forge_migrations ledger entry. There is no interactive BEGIN/COMMIT. Back up the database before a destructive migration. The evidence is local D1 (workerd), not remote D1.

2. R2 Storage

import { R2StorageAdapter } from '@forge-cms/cloudflare';

const storage = new R2StorageAdapter();
storage.init({ BUCKET: env.BUCKET });
storage.setPublicUrlBase('https://pub-your-id.r2.dev');

3. Wrangler config

The root wrangler.toml is already configured for Cloudflare Pages. Update it with your bindings when ready:

name = "forge-cms"
compatibility_date = "2026-05-15"
compatibility_flags = ["nodejs_compat"]
pages_build_output_dir = "apps/www/dist/analog/public"

[[d1_databases]]
binding = "DB"
database_name = "forge-cms"
database_id = "<your-id>"

[[r2_buckets]]
binding = "BUCKET"
bucket_name = "forge-cms-media"

Local development

For local development use LibSqlDatabaseAdapter from @forge-cms/db with a SQLite file. It shares the same schema generator, drift planner and migration engine, so the same reviewed migrations run on both.

Forge Analytics (experimental)

Privacy-minimizing pageview analytics on top of Cloudflare Workers Analytics Engine — see spec 057 for the full design, collected fields, and what is deliberately not collected. This is an opt-in, experimental ForgeCMS capability, not a general analytics platform: it never runs unless an app wires it up, and it never touches ForgeCmsRuntime, core, or the API envelope.

1. Write side — wrangler.toml

[[analytics_engine_datasets]]
binding = "ANALYTICS"
dataset = "your_dataset_name"

The dataset is created automatically on first write — no manual provisioning step.

import { AnalyticsEngineWriter, NoopAnalyticsWriter } from '@forge-cms/cloudflare';

// Pick a writer based on whether the binding exists, so a misconfigured/local environment degrades
// to a silent no-op instead of crashing the request.
const writer = env.ANALYTICS ? new AnalyticsEngineWriter().init(env) : new NoopAnalyticsWriter();

writer.recordPageview({
  siteId: 'my-site', // one stable identifier per deployment — the dataset's index1
  pathname: sanitizedPath, // never a raw request path — strip query/hash first
  referrerHost: sanitizedReferrerHost, // hostname only, '' if none
  country: sanitizedCountry // 2-letter ISO code from the CF-IPCountry header, '' if unknown
});

Use sanitizePathname/sanitizeReferrerHost/sanitizeCountry/sanitizeSiteId (also exported) to build those fields — they are the only place query-string stripping and referrer-to-hostname reduction happen, so nothing scatters that logic across routes.

2. Read side — server-only secrets

Querying is Cloudflare's Analytics Engine SQL API (an external HTTPS call, not a binding), so it needs two secrets that must never reach the browser:

# Set via `wrangler pages secret put` / your platform's secret store — never commit real values.
CLOUDFLARE_ACCOUNT_ID=...
CLOUDFLARE_ANALYTICS_TOKEN=...   # API token scoped to Account > Account Analytics > Read
import { AnalyticsEngineQueryClient } from '@forge-cms/cloudflare';

const client = new AnalyticsEngineQueryClient({
  accountId: env.CLOUDFLARE_ACCOUNT_ID,
  apiToken: env.CLOUDFLARE_ANALYTICS_TOKEN,
  dataset: 'your_dataset_name'
});

const summary = await client.getSummary('my-site', '7d'); // '24h' | '7d' | '30d' | '90d'

Gate this behind your own admin-only auth check — the SQL API must never be reachable by an unauthenticated caller. Retention is Analytics Engine's own 90 days; there is no historical range beyond that.

3. Browser + admin UI

The tracker (provideForgeAnalytics from @forge-cms/angular) and the reusable admin dashboard (forgeAdminAnalyticsRoutes/ForgeAnalyticsDashboardComponent from @forge-cms/admin) are documented in spec 057's Design section and each package's own exported doc comments. See apps/demo-aesthetics for a complete, working wiring of all three layers.