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

@vite-hub/database

v0.1.0

Published

Vite-first Drizzle database primitives for ViteHub.

Readme

@vite-hub/database

@vite-hub/database turns a colocated Drizzle schema into a generated useDatabase() lookup for server code.

Install

pnpm add @vite-hub/database drizzle-orm
pnpm add -D drizzle-kit

Minimal API

// src/database.ts
import { defineDatabase } from "@vite-hub/database"
import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core"

export default defineDatabase({
  schema: {
    notes: sqliteTable("notes", {
      id: integer("id").primaryKey({ autoIncrement: true }),
      title: text("title").notNull(),
    }),
  },
})
// server/api/notes.get.ts
import { useDatabase } from "@vite-hub/database/drizzle"
import { defineEventHandler } from "h3"

export default defineEventHandler(() => {
  const { db, schema } = useDatabase("default")
  return db.select().from(schema.notes)
})
// vite.config.ts
import { hubDb } from "@vite-hub/database/vite"
import { nitro } from "nitro/vite"
import { defineConfig } from "vite"

export default defineConfig({
  plugins: [hubDb(), nitro()],
})

Vite Integration

Database instances expose Drizzle's batch() method on D1, D1 HTTP, and libSQL. The batch executes atomically. Use conditional statements in a batch when a change must compare and update shared state without an interactive transaction.

await db.batch([
  db.insert(schema.notes).values({ title: "First note" }),
  db.insert(schema.notes).values({ title: "Second note" }),
])

Use src/database.ts or server/databases/config.ts for one default database. Use src/<name>.database.ts or server/databases/<name>/config.ts with the same name option when every database is named. Vite discovers those files, writes generated Drizzle artifacts, and lets server handlers import @vite-hub/database/drizzle. A project cannot mix a default Database Definition with Named Database Definitions. Without a Definition, the Vite integration uses one empty default Database (@vite-hub/database/runtime/default-database) with database.connection and migrations in server/databases/migrations. Pass implicitDefault: true to resolveDBViteConfig(), discoverDatabaseDefinitions(), or inspectDatabaseDefinitions() to resolve it the same way.

@vite-hub/database/drizzle is resolved by the Vite integration for server code that Nitro builds. Nitro is the only host output. Plain node execution of files that import it is not a supported local runtime path.

Application code calls useDatabase() with the discovered name. Use default for a Default Database.

In a Nuxt application, ViteHub also discovers Definitions in each layer's server directory. Definitions with the same name merge their tables into one database and one set of migrations in the application. The application Definition takes precedence over layer tables. When it sets connection, cloudflare, or drizzle, it replaces all layer options and must also set each option that a layer sets.

When the Nitro Vite plugin builds with a Cloudflare preset, the Vite integration merges each resolved D1 binding into nitro.cloudflare.wrangler.d1_databases and copies the Definition's migration SQL to .output/server/.vitehub/database/migrations/<binding>. A Definition's binding replaces a Nitro binding with the same name.

Production Nuxt builds with only D1-over-HTTP Databases do not bundle libSQL or its native libraries. Local SQLite development and projects with libSQL connections keep their existing drivers.

Migrations

vitehub db generate writes Drizzle migrations beside the Definition. vitehub db migrate applies the pending ones with Drizzle Kit. Kit runs from the Database project root, including when Nuxt uses app/ as its Vite root. The CLI resolves Kit from the application or the installed vite-hub facade, so it does not depend on an application binary on PATH.

Connection URLs and tokens can use string literals or runtime environment declarations, including module-level variables initialized with those values. The generated Kit config reads environment values at invocation time and uses the Turso dialect for remote or authenticated libSQL. The migration check uses the same connection values. Local migration creates the database's parent directory after the check; generation and --dry-run do not create the database file.

pnpm vitehub db migrate --dry-run         # print pending migrations and their SQL, change nothing
pnpm vitehub db migrate                   # refuse pending migrations that lose data
pnpm vitehub db migrate --allow-data-loss # apply them, and skip the check

Before it applies anything, vitehub db migrate reads Drizzle's migrations table (__drizzle_migrations) of each database to find the pending migrations. It refuses DROP TABLE, DROP SCHEMA, a column drop with ALTER TABLE, TRUNCATE, DELETE without WHERE, and a SQLite table rebuild that does not copy every column of the previous Drizzle snapshot. It reports them with DATABASE_MIGRATION_DATA_LOSS and exits with code 1. --dry-run also exits with code 1 in that case, so CI can run it as a check.

The check connects to the database that Drizzle Kit migrates: the local SQLite file, remote libSQL, D1 HTTP with CLOUDFLARE_ACCOUNT_ID and CLOUDFLARE_API_TOKEN, or Postgres with the postgres package. When it cannot read the migrations table, it fails with DATABASE_MIGRATION_CHECK_FAILED and does not guess. Wrangler applies the migrations of a native D1 binding, so that path has no check.

Contributed tables

Packages can add Drizzle tables to an application's Database from their own Vite plugin with vitehub.databases. Each entry has the shared contribution shape { name, definition: { module, export }, reason? }, where export holds an object of tables. Database entries can also set database and dialect. Without database, the tables go to the default or only Database:

const plugin = {
  name: "my-package/vite",
  vitehub: {
    databases: () => [{
      name: "history",
      definition: { module: "my-package/schema", export: "historySchema" },
      reason: "Stores Workspace history.",
    }],
  },
}

A Nuxt module adds the same plugin to nuxt.options.vite.plugins during setup.

A package can also contribute a whole Database with { name, definition, reason? }, where definition is the absolute path of a module that default exports defineDatabase({ schema }). Use it when the application has no Database the package can use. The contributed Database behaves like one in server/databases/, keeps its migrations where that Definition would (server/databases/migrations/ for default), and shows Contributed by, Reason, and Migrations in vitehub inspect. A name that the application already defines fails with DATABASE_DUPLICATE_DEFINITION.

Contributed tables are in the generated Drizzle schema, so vitehub db generate writes their migrations. They are also in useDatabase(name).schema, the generated types, vitehub inspect, and the Console. A contribution name, key, or SQL table name used twice fails with DATABASE_DUPLICATE_TABLE. A missing or ambiguous target fails with DATABASE_INVALID_CONTRIBUTION_TARGET and lists the Database names. resolveDefaultDatabase(names) from @vite-hub/database/config returns the same default or only Database.

Remote D1 development

Set cloudflare.http on a Database Definition to query remote D1 during local development. true uses Cloudflare's API with CLOUDFLARE_ACCOUNT_ID and CLOUDFLARE_API_TOKEN; an object selects an authenticated D1-compatible proxy.

export default defineDatabase({
  cloudflare: {
    databaseId: process.env.CLOUDFLARE_D1_DATABASE_ID,
    http: true,
  },
  schema,
})

Set cloudflare.readReplication: true (or readReplication: true on a D1 integration) to read from D1 read replicas. Each request gets one D1 session. A request with a method other than GET, HEAD, or OPTIONS starts on the primary database and returns a vitehub-d1-bookmark-<binding> cookie, so the same client reads its own writes next. Work outside a request uses the primary database. vitehub provision run turns on replication for the database. D1 over HTTP and SQLite ignore the option.

Remote access is explicit. Local development uses SQLite when neither the Definition nor its integration configures cloudflare.http. Cloudflare output prefers a matching D1 binding.

A Definition inherits the integration resource when it omits both databaseId and databaseName. Setting either selects its own resource, so it inherits no integration database ID, name, or preview ID. Other D1 settings still inherit their defaults. HTTP proxy URLs and tokens inherit by field. Both must be available after inheritance for HTTP access to work.

A literal resource field that reads an Env value remains owned when that value is absent. It can use its own provisioned ID, but it does not fall back to the integration resource.

Native D1 output requires a Cloudflare block that ViteHub can read as a literal. An identifier, top-level spread, or other opaque resource expression fails the provider build when no D1 HTTP or remote libSQL connection is known. This rule also applies when the evaluated block would inherit the integration resource. Use a literal block for native output, or configure HTTP or libSQL access.

Keep an explicit cloudflare property in the Definition when its value is opaque. If the whole Definition hides that property in an identifier or spread, set hubDb({ driver: 'd1' }) to declare D1 intent. Without that declaration, a wholly hidden Definition does not project native D1 output. Opaque local SQLite Definitions remain supported.

An owned native resource must resolve a nonblank database ID and name at build time. Its own provisioned ID can supply the ID. Use D1 HTTP or remote libSQL access when the resource values become available only at runtime.

Named Definitions without an explicit binding use their per-name binding, such as DB_ANALYTICS. A default Definition retains the integration binding. Named Definitions can inherit the integration resource through different binding names. Generated native D1 bindings must have unique names, including bindings for identical resources. Set distinct cloudflare.binding values when explicit bindings or normalized names collide.

An opaque Definition that selects its own resource uses remote HTTP or libSQL access. It does not use a native binding, including an explicit binding that matches the integration resource.

Provisioned IDs remain fallbacks for runtime Env values in direct Vite and Nuxt output. A value supplied at runtime takes precedence. An opaque Definition that selects its own resource does not receive the integration resource's provisioned ID. Provision state is read from the Vite root even when projectRoot sets another root for Definitions and generated files.

When a libSQL connection URL or auth token changes at runtime, the next database access creates a client with the new credentials and closes the previous client. Credential refresh is therefore a lifecycle boundary: await outstanding database work before changing the credential, because libSQL closes can abort operations that are still in flight.

Postgres

Set dialect: "postgresql", declare tables with drizzle-orm/pg-core, and install postgres in the app.

// server/databases/config.ts
import { defineDatabase } from "@vite-hub/database"
import { pgTable, serial, text } from "drizzle-orm/pg-core"

export default defineDatabase({
  dialect: "postgresql",
  cloudflare: { hyperdriveId: process.env.CLOUDFLARE_HYPERDRIVE_ID },
  connection: { url: process.env.DATABASE_URL },
  schema: {
    notes: pgTable("notes", {
      id: serial("id").primaryKey(),
      title: text("title").notNull(),
    }),
  },
})

ViteHub queries Postgres with postgres.js through Drizzle. A Cloudflare Hyperdrive binding (HYPERDRIVE, or HYPERDRIVE_<NAME> for a Named Database) takes precedence over connection.url. Inside a Cloudflare request, each request gets its own client, because Workers cannot reuse a socket across requests. On other hosts, one client pool serves the process. When cloudflare.hyperdriveId resolves at build time, Cloudflare output and Nitro Wrangler config get the hyperdrive binding.

Outside a Cloudflare request, when connection.url is not set or its environment variable is empty, postgres.js reads the standard PG* environment variables: PGHOST, PGPORT, PGDATABASE, PGUSER (or PGUSERNAME), and PGPASSWORD. Use them for container deploys that give discrete credentials instead of a URL. A URL always takes precedence. Workers ignore PG* variables, and Vercel output still needs connection.url.

vitehub db generate writes Drizzle Kit migrations with dialect: "postgresql". vitehub db migrate applies them with connection.url, or with the PG* variables when the URL is empty. Drizzle Kit then needs PGHOST and PGDATABASE. Query a Postgres Definition through useDatabase(); defineDatabase() returns only the Definition.

Request stats

With the evlog Nitro module registered, each request's wide event gets database: { calls, ms, maxConcurrent }: the calls to any database (a batch is one call), the milliseconds with at least one call in flight, and the most calls in flight at once. A Nitro middleware (runtime/request-stats) gives each request its own count, and each driver client (D1 binding, D1 over HTTP, libSQL, postgres.js) reports its calls to it. db.$client stays the app's own client (the request's D1 session under read replication), and calls made directly on it are not counted. Calls after the response are not counted, and without evlog nothing is measured. See Server API.

Nuxt D1 host wiring

Nuxt apps can declare one D1 database resource and let the Database Nuxt bridge wire framework consumers and Cloudflare output.

// nuxt.config.ts
export default defineNuxtConfig({
  modules: ["@vite-hub/database/nuxt", "@nuxt/content"],
  database: {
    driver: "d1",
    databaseName: "app-content",
  },
})

Run vitehub provision run --provider cloudflare once to create or resolve the database and write its non-secret id to .vitehub/provision.json. The bridge reads that state, configures Nuxt Content, merges the D1 binding into nitro.cloudflare.wrangler.d1_databases, and stages discovered migrations. Set databaseId explicitly when the build cannot access provision state; a complete matching binding already present in Nitro Wrangler config is also preserved.

A Definition that owns a different D1 resource must use a binding that differs from the Nuxt host's database.binding. Nuxt rejects native bindings that would point Content and the Definition at different resources through the same binding. One Definition with statically fixed database ID and name values that match the host can share that binding. Generated binding names must remain distinct between Definitions. Env declarations require a distinct binding, including declarations with a provisioned default.

Built on Drizzle ORM, Drizzle Kit, libSQL, postgres.js, and Cloudflare D1 and Hyperdrive bindings when deployed to Cloudflare.

Learn more at vitehub.dev.