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

@pegma/storage-cloudflare-d1

v0.4.1

Published

Cloudflare D1 adapter for @pegma/storage-core.

Readme

@pegma/storage-cloudflare-d1

Cloudflare D1 adapter for @pegma/storage-core.

It stores every collection in one D1 database and one RECORDS data table. The physical partition key is <collection>:<partition> and the row key is the record id. All values are passed through prepared statements.

Usage

Pass the D1 binding directly:

import { createCloudflareD1Store } from "@pegma/storage-cloudflare-d1";

export default {
  async fetch(_request: Request, env: Env): Promise<Response> {
    const store = createCloudflareD1Store({ database: env.DB });
    // Declare collections and use the store here.
    return new Response("ok");
  },
};

The adapter creates its schema on first use by default. To provision it with deployment migrations instead, set createSchemaIfMissing: false and create the RECORDS table plus the internal PEGMA_STORAGE_D1_TX_GUARD table and its three fixed triggers:

CREATE TABLE IF NOT EXISTS RECORDS (
  partition_key TEXT NOT NULL,
  row_key TEXT NOT NULL,
  record_json TEXT,
  version INTEGER NOT NULL,
  deleted INTEGER NOT NULL CHECK (deleted IN (0, 1)),
  PRIMARY KEY (partition_key, row_key)
) STRICT;

CREATE TABLE IF NOT EXISTS PEGMA_STORAGE_D1_TX_GUARD (
  reason TEXT NOT NULL
) STRICT;

CREATE TRIGGER IF NOT EXISTS PEGMA_STORAGE_D1_TX_ABORT_EXISTS
BEFORE INSERT ON PEGMA_STORAGE_D1_TX_GUARD
WHEN NEW.reason = 'exists'
BEGIN
  SELECT RAISE(ABORT, 'PEGMA_STORAGE_D1_TX_EXISTS');
END;

CREATE TRIGGER IF NOT EXISTS PEGMA_STORAGE_D1_TX_ABORT_MISSING
BEFORE INSERT ON PEGMA_STORAGE_D1_TX_GUARD
WHEN NEW.reason = 'missing'
BEGIN
  SELECT RAISE(ABORT, 'PEGMA_STORAGE_D1_TX_MISSING');
END;

CREATE TRIGGER IF NOT EXISTS PEGMA_STORAGE_D1_TX_ABORT_CHANGED
BEFORE INSERT ON PEGMA_STORAGE_D1_TX_GUARD
WHEN NEW.reason = 'changed'
BEGIN
  SELECT RAISE(ABORT, 'PEGMA_STORAGE_D1_TX_CHANGED');
END;

The marker messages are part of the adapter's transaction protocol and must not be changed.

Consistency requirement

Pass a D1Database binding, not an object returned by withSession(). Cloudflare's direct binding calls execute on the primary database. That is required because optimistic version tokens must always be checked against current state.

Replicated or session-based reads are incompatible with this adapter. Even a session that starts on the primary may use replicas for subsequent reads, so the adapter deliberately does not use the Sessions API.

Versions and deletes

Versions are opaque strings backed by monotonically increasing SQLite integer values. The adapter reads them with CAST(version AS TEXT), avoiding JavaScript rounding of 64-bit integers, and compares tokens as text.

Deletes retain an invisible tombstone. get and list filter tombstones, but recreating the same logical key increments its retained version rather than starting again at 1. A version token therefore cannot become valid again after delete and recreate.

Authoritative scans

CollectionStore.scan reads one bounded page across every logical partition in a collection. D1 uses its physical (partition_key, row_key) primary key as the internal continuation position, then returns the logical physical EntityKey, decoded value, and opaque version. Tombstones are never returned.

Cursors are opaque and scoped to this adapter and collection. Persist and pass them back unchanged; a null continuation ends the current cycle. The query's key ordering is an implementation detail, not a public ordering or snapshot promise. Concurrent writes can repeat a row or defer it until a later complete cycle.

Transactions

transact uses D1Database.batch(), which D1 executes as an atomic SQL transaction. An internal empty guard table and fixed abort triggers turn zero-row precondition failures into the port's exists, missing, and changed outcomes. Other D1 errors are rethrown.

Transactions remain limited to one collection and one logical partition, as required by @pegma/storage-core.

A transaction carries at most 100 actions, the same limit the Azure Tables adapter enforces, so an action list either works on both adapters or is refused by both with a StorageError. The limit is checked before any statement is sent. Each action costs one to three statements in the batch, and every statement counts against the Worker invocation's D1 query budget.

License

MIT © 2026 RetireGolden, LLC