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

@ydbjs/drizzle-adapter

v0.1.1

Published

YDB adapter for Drizzle ORM.

Readme

@ydbjs/drizzle-adapter

The @ydbjs/drizzle-adapter package wires Drizzle ORM to YDB. It ships typed schema declarations with YDB-native column types, query builders that emit valid YQL, a relational db.query.* API, a migration runner with history and optional distributed locking, and ergonomic wrappers around YDB built-in functions and UDFs (Digest hashes, CurrentUtc*, Knn::*, set operators, pragmas, scripts).

Features

  • YDB column helpers with primary keys, unique constraints, secondary and vector indexes, table options, TTL, and column families
  • SELECT builders with joins, CTEs, set operators, WITHOUT, FLATTEN, SAMPLE, TABLESAMPLE, MATCH_RECOGNIZE, windows, and YDB optimizer hints
  • Mutation builders for insert, upsert, replace, update, batchUpdate, delete, batchDelete
  • db.query.* relational queries through Drizzle relation metadata
  • Direct YQL via db.execute(sql\...`)anddb.values(...)`
  • migrate() with bookkeeping table, optional lock table, and recovery strategy
  • Typed YQL helpers: numericHash / xxHash / crc32c / crc64, currentUtc*, random / randomNumber / randomUuid with required per-row cache keys, unwrap, maxOf / minOf, knnCosineDistance and the rest of the Knn::* family
  • YDB-typed errors (YdbUniqueConstraintViolationError, YdbAuthenticationError, etc.) wrapping Drizzle's DrizzleQueryError
  • Full TypeScript support, ESM-only

Installation

npm install @ydbjs/drizzle-adapter drizzle-orm

Requires Node.js 20.19+ and drizzle-orm@^0.45.2.

How It Works

  • Subpath entries: the package exposes four entries instead of one mega-barrel:

    | Subpath | What it owns | | --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | | @ydbjs/drizzle-adapter | createDrizzle, drizzle, YdbDriver, error classes, relations/many/one re-exported from drizzle-orm | | @ydbjs/drizzle-adapter/schema | ydbTable, column types, primaryKey, unique, indexes, table options | | @ydbjs/drizzle-adapter/sql | YQL expression helpers (hash UDFs, currentUtc*, random*, unwrap, maxOf/minOf, Knn::*, set operators, pragmas, scripts) | | @ydbjs/drizzle-adapter/migrator | migrate() plus build*Sql DDL builders and migration types |

  • Driver ownership: createDrizzle({ connectionString }) owns the driver and closes it with db.$client.close?.(). Pass { driver } to share an existing YdbDriver with other YDB clients.

  • Query execution: all builders go through YdbSession, which uses the same driver/pool as @ydbjs/query under the hood.

  • Migrations: migrate() records every applied migration in a YDB table; with migrationLock enabled, parallel deploy jobs coordinate through a lock table instead of racing.

Usage

Quick Start

import { eq } from 'drizzle-orm'
import { createDrizzle } from '@ydbjs/drizzle-adapter'
import {
  integer,
  primaryKey,
  text,
  timestamp,
  uint64,
  ydbTable,
} from '@ydbjs/drizzle-adapter/schema'
import { currentUtcTimestamp, numericHash } from '@ydbjs/drizzle-adapter/sql'

let users = ydbTable(
  'users',
  {
    hash: uint64('hash').notNull(),
    id: integer('id').notNull(),
    email: text('email').notNull(),
    createdAt: timestamp('created_at').notNull(),
  },
  (t) => [primaryKey(t.hash, t.id)]
)

let db = createDrizzle({
  connectionString: process.env['YDB_CONNECTION_STRING']!,
  schema: { users },
})

await db
  .insert(users)
  .values({
    hash: numericHash(1),
    id: 1,
    email: '[email protected]',
    createdAt: currentUtcTimestamp(),
  })
  .execute()

let row = await db
  .select({ id: users.id, email: users.email })
  .from(users)
  .where(eq(users.id, 1))
  .prepare()
  .get()

await db.$client.close?.()

The leading hash column is YDB's recommended way to spread writes across tablets. numericHash(id) emits Unwrap(Digest::NumericHash(CAST(id AS Uint64))) so the cluster computes the shard prefix at insert time; xxHash(value) does the same for string keys.

Schema and Composite Primary Keys

import { integer, primaryKey, text, uint64, ydbTable } from '@ydbjs/drizzle-adapter/schema'
import { xxHash } from '@ydbjs/drizzle-adapter/sql'

let articles = ydbTable(
  'articles',
  {
    hash: uint64('hash').notNull(),
    slug: text('slug').notNull(),
    title: text('title').notNull(),
  },
  (t) => [primaryKey(t.hash, t.slug)]
)

await db.insert(articles).values({
  hash: xxHash('intro'),
  slug: 'intro',
  title: 'Hello, YDB',
})

Transactions

await db.transaction(
  async (tx) => {
    await tx
      .insert(users)
      .values({
        /* ... */
      })
      .execute()
    await tx
      .update(users)
      .set({
        /* ... */
      })
      .where(/* ... */)
      .execute()
  },
  { isolationLevel: 'serializableReadWrite' }
)

Transactions are not nestable through the adapter — open one boundary at the top of the unit of work and pass tx down. Supported isolations are serializableReadWrite and snapshotReadOnly (snapshotReadOnly is read-only by definition; the adapter rejects mutations under it).

When to use idempotent: true

idempotent: true opts the transaction into the @ydbjs/retry policy: on a retryable YDB failure the entire callback re-runs from scratch, not just the failed statement. Set it only when re-running the whole callback is safe.

// Safe — only YDB mutations inside the callback
await db.transaction(
  async (tx) => {
    await tx
      .insert(events)
      .values({
        /* ... */
      })
      .execute()
  },
  { isolationLevel: 'serializableReadWrite', idempotent: true }
)
// UNSAFE — the Stripe charge will fire twice on retry
await db.transaction(
  async (tx) => {
    await stripe.charges.create({
      /* ... */
    }) // external side effect!
    await tx
      .insert(payments)
      .values({
        /* ... */
      })
      .execute()
  },
  { idempotent: true } // ← don't do this
)

When in doubt, leave idempotent unset and handle the retryable error in your own code.

YQL Helpers

import { sql } from 'drizzle-orm'
import {
  currentUtcTimestamp,
  knnCosineDistance,
  maxOf,
  numericHash,
  randomUuid,
  xxHash,
} from '@ydbjs/drizzle-adapter/sql'
import { vectorIndexView } from '@ydbjs/drizzle-adapter/schema'

await db
  .insert(events)
  .values({
    hash: numericHash(eventId),
    id: eventId,
    traceId: randomUuid(events.id),
    createdAt: currentUtcTimestamp(),
  })
  .execute()

let similar = await db
  .select({
    id: docs.id,
    distance: knnCosineDistance(docs.embedding, sql`$target`),
  })
  .from(docs)
  .view(vectorIndexView(docs, 'docs_emb_idx'))
  .orderBy(sql`distance ASC`)
  .limit(10)

random* helpers require at least one cache key — without it YDB returns the same value for every row. Pass any column reference or expression that varies per row.

Migrations

import { migrate } from '@ydbjs/drizzle-adapter/migrator'

await migrate(db, {
  migrationsFolder: './drizzle',
  migrationLock: {
    key: 'production',
    leaseMs: 10 * 60 * 1000,
    acquireTimeoutMs: 60 * 1000,
  },
  migrationRecovery: {
    mode: 'retry',
    staleRunningAfterMs: 60 * 60 * 1000,
  },
})

migrate() also accepts an inline migrations: [...] array (no folder) for programmatic schemas. DDL builders such as buildCreateTableSql, buildAlterTableSql, and buildAddIndexSql are exported from the same entry for tooling that needs to render statements without running them.

Type Mapping

| YDB family | JavaScript / TypeScript value | | ----------------------------------------------------- | ----------------------------- | | Bool | boolean | | Int8..Int32, Uint8..Uint32, Float, Double | number | | Int64, Uint64 | bigint | | Utf8, Uuid | string | | String, Yson | Uint8Array | | Date, Datetime, Timestamp and 64-bit variants | Date | | Json, JsonDocument | typed JSON value |

Use bytes() for binary String, text() for Utf8.

Error Handling

Failures are wrapped in Drizzle's DrizzleQueryError with YDB-specific subclasses when the status maps cleanly:

  • YdbUniqueConstraintViolationError
  • YdbAuthenticationError
  • YdbCancelledQueryError
  • YdbTimeoutQueryError
  • YdbUnavailableQueryError
  • YdbOverloadedQueryError
  • YdbRetryableQueryError

Mapped errors carry non-enumerable kind, retryable, statusCode, and the original YDB diagnostic fields (code, status, issues) when present.

Limitations

  • ESM-only; Node.js 20.19+; no CommonJS build
  • Transactions are not nestable through the adapter — use one boundary per unit of work
  • references() is metadata for Drizzle relations; YDB does not enforce foreign keys
  • Unique indexes must be created with CREATE TABLE; adding one to an existing table is rejected by the DDL builder
  • replace() is a full-row replacement by primary key — use upsert() or update() for partial changes
  • sql.raw(), inline migration sql, rawTableOption(), view query text, ACL raw permissions, and transfer using text intentionally trust caller-provided YQL

Development

npm run build --workspace=@ydbjs/drizzle-adapter
npm run test:unit --workspace=@ydbjs/drizzle-adapter
npm run test:int --workspace=@ydbjs/drizzle-adapter   # requires Docker for ydbplatform/local-ydb
npm run attw --workspace=@ydbjs/drizzle-adapter
npm run check:surface --workspace=@ydbjs/drizzle-adapter

The root CI workflow runs the same suite against a Docker-backed YDB on every pull request.

License

This project is licensed under the Apache 2.0 License.

Links