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

@livequery/d1

v3.0.0

Published

Cloudflare D1 datasource mapping for @livequery ecosystem

Readme

@livequery/d1

Cloudflare D1 datasource adapter for Livequery.

Hono middleware

import { d1 } from '@livequery/d1'

app.get('/livequery/tasks', validator(Task), livequery(), d1(), realtime())

d1() picks the operation from the method and the ref: a collection GET lists, a document GET reads, POST inserts (201), PUT/PATCH update, DELETE removes. It resolves the binding from env.DB (override with d1({ binding: 'TASKS_DB' })) and the table from the collection ref (override with d1({ table })). Columns come from the route's validator() schema, or from d1({ fields }).

The result is published as livequery_result, the response is built, and then the rest of the chain runs — so realtime() and any other trailing middleware see the result and can add headers. A failing query throws a normalized error, so nothing downstream publishes a change that never happened.

Core datasource integration

import { D1Datasource } from '@livequery/d1'

const datasource = new D1Datasource({
  databases: { default: env.DB },
})

const products = { table: 'products', fields: ['name', 'price', 'created_at'] }

await datasource.init([
  { method: 'GET', path: '/livequery/products', ...products },
  { method: 'GET', path: '/livequery/products/:id', ...products },
  { method: 'POST', path: '/livequery/products', ...products },
])

// After LivequeryRequestParser has populated context.livequery:
const response = await datasource.handle(context)

D1Datasource implements LivequeryDatasource<D1RouteOptions>. A route can select one of several configured bindings with database, including a request-based resolver:

const datasource = new D1Datasource({
  databases: { primary: env.DB, tenant: env.TENANT_DB },
})

await datasource.init([{
  method: 'GET',
  path: '/livequery/:tenant/products',
  table: 'products',
  database: request => request.keys.tenant === 'special' ? 'tenant' : 'primary',
}])

Per-request Worker binding

When the D1 binding is only available from a Worker's request env, the original direct API remains available:

const datasource = new D1Datasource()

const result = await datasource.handle(
  env.DB,
  livequeryRequest,
  { table: 'products' },
)

Column safety

D1 binds values, not identifiers, so table and column names end up in the SQL text. Every name that reaches SQL (filter keys, :sort keys, body keys, route keys, the table) must match ^[A-Za-z_][A-Za-z0-9_]{0,63}$; anything else is rejected with 400 INVALID_FIELD before a statement is prepared. A bad table name is a programming error and throws Error.

Set fields on each route to the columns clients may filter, sort and write. Other columns are rejected with 400 FIELD_NOT_ALLOWED. id and route keys (such as :tenant_id in the path) are always allowed. Without fields, any well-formed column of the table can be filtered and written, so leave it unset only when every column is safe to expose.

Other limits enforced before querying:

  • Unknown filter operators (field:regex) return 400 INVALID_OPERATOR instead of being ignored.
  • in / nin accept at most MAX_IN_VALUES (50) values, keeping a statement under D1's 100 bound-parameter limit (400 TOO_MANY_VALUES).

Edge runtime

The package imports only the runtime-neutral root of @livequery/core, so a Worker bundle that uses it needs no nodejs_compat flag. In a Worker, import from @livequery/core or @livequery/core/workers, never from @livequery/core/node or @livequery/core/bun, which load ws, http and UDP discovery.