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

@vibeorm/runtime

v5.7.0

Published

Driver-agnostic query engine and client runtime for VibeORM v2

Readme

@vibeorm/runtime

Part of VibeORM — a type-safe TypeScript ORM for Bun and Node. Prisma-schema or TypeScript-DSL input, a canonical schema IR, a generated client, and a dialect-aware SQL layer over PostgreSQL, PGlite, SQLite and MySQL.

The query engine behind the generated client: it turns delegate calls into SQL statements, loads relations, runs nested writes and moves values across the wire through the codec table. It is its own package because it is driver-agnostic — it talks to databases only through the DatabaseAdapter contract, so adapters and the generator can depend on it without depending on each other.

bun add @vibeorm/runtime

Stable. 2.0.0, published under the latest dist-tag. The v1 line stays installable as vibeorm@1.

Most users never install this directly — it ships as a dependency of vibeorm and of the generated client. Reach for it when you are writing an adapter or tooling against the runtime.

What it does

createClient({ schema, adapter, options }) builds a DynamicClient from the Schema IR at runtime: model delegates keyed by both camelCase client name and model name ($models), plus $transaction, $queryRaw, $executeRaw, $queryRawUnsafe, $executeRawUnsafe, $on, $connect and $disconnect. The delegates are deliberately broad here (ClientRow in, ClientRow out); the generated .d.ts declares the precise per-model types on top.

Each ModelDelegate implements 16 methods:

| Group | Methods | |---|---| | Read | findMany · findFirst · findFirstOrThrow · findUnique · findUniqueOrThrow | | Create | create · createMany · createManyAndReturn | | Update | update · updateMany · upsert | | Delete | delete · deleteMany | | Aggregate | count · aggregate · groupBy |

The statement-building pieces are exported too, for tooling that needs SQL without executing it: buildQuery, compileWhere, compileSelect, compileOrderBy, compileProjection, resolveResultShape, and the runtime metadata builders buildRuntimeMeta, getModelMeta, getFieldMeta.

Model metadata

describeSchema({ schema, computed? }) is the supported server-side metadata contract. Pass the schema exported by the generated client, and the same computed-field config passed to VibeClient if you need computed descriptions:

import { describeSchema } from "@vibeorm/runtime";
import { schema } from "./generated/vibeorm/index.js";

const description = describeSchema({ schema });
const order = description.models.find((model) => model.clientName === "order");
const lines = order?.relations.find((relation) => relation.name === "lines");
// lines?.foreignKey === "target"
// lines?.keys === [{ field: "id", targetField: "orderId" }]

The result contains model/delegate names, physical table and column names, namespaces, view flags, ordered primary keys, queryable scalar/enum fields, enum values, computed type text/dependencies and resolved relation wiring. Relation foreignKey is "local", "target" or "joinTable"; keys preserves composite-key order, and inverse names the paired relation or is null. Implicit joins include their physical table and oriented A/B columns. Invalid or unresolvable metadata fails with the shared runtime builders' typed VibeError; it does not return an unsupported placeholder.

Every returned object and array is a detached, deeply frozen copy. Unsupported columns, datasource URLs, SQL defaults, policies/RLS details, validator bindings and compute callbacks are omitted. Computed callbacks are never run. Keep this metadata on the server; do not send it to browsers. Physical schema names are not a public application contract. Minor versions may add description properties; removing or renaming them requires a major version. RuntimeMeta, ModelMeta, FieldMeta and their builders/resolvers remain internal tooling exports without this shape-stability promise.

Consumer-owned Zod output schemas

Zod is supported through the existing Standard Schema write-validation and typed-JSON seams. Frameworks can also define public read schemas in Zod and use .pick to validate exactly the selected values:

import { z } from "zod";

const PublicOrder = z.object({
  id: z.number().int(),
  reference: z.string(),
  createdAt: z.date(),
  total: z.string(), // Decimal values are decoded strings.
});
const SelectedOrder = PublicOrder.pick({ reference: true, createdAt: true }).strict();
const row = await db.order.findFirstOrThrow({
  select: { reference: true, createdAt: true },
});
const validated = SelectedOrder.parse(row);

The framework owns this schema and its selected public shape. These validators check decoded in-process values: DateTime is a Date, BigInt is a bigint, Bytes is a Uint8Array, and Decimal is a string. Wire encoding belongs to the framework. Computed type text alone cannot validate a computed value; provide its public validator explicitly. No per-model Zod schemas or other validation output are added to the generated client.

The adapter contract

DatabaseAdapter is frozen. Implement it and any driver works with the whole ORM:

import type { DatabaseAdapter, QueryResult, TransactionOptions } from "@vibeorm/runtime";

const adapter: DatabaseAdapter = {
  dialect: "postgres",
  provider: "postgresql",
  async execute(params: { text: string; values: unknown[] }): Promise<Record<string, unknown>[]> { /* … */ },
  async executeUnsafe(params: { text: string; values?: unknown[] }): Promise<QueryResult> { /* … */ },
  async transaction<T>(fn: (tx: DatabaseAdapter) => Promise<T>, options?: TransactionOptions): Promise<T> { /* … */ },
  async connect(): Promise<void> { /* … */ },
  async disconnect(): Promise<void> { /* … */ },
  formatArrayParam(values: unknown[]): unknown { /* … */ },
};
  • execute is the ORM hot path (row-returning, statement-cached by the adapter). executeUnsafe is the raw/DDL path and is the only place affectedRows surfaces.
  • transaction nests: an inner call creates a savepoint. isolationLevel and timeout are top-level only — a nested call must refuse both with VIBE_VALIDATION.
  • Adapters never let raw driver errors escape. Each maps its driver's failures to VibeError codes (VIBE_UNIQUE_VIOLATION, VIBE_FK_VIOLATION, VIBE_TRANSACTION, VIBE_ADAPTER) through a per-driver table, with the driver error as cause.
  • formatArrayParam converts a JS array into the driver's preferred scalar-array representation; the ORM path binds arrays through it, the raw path does not.
  • The optional wire declaration (WireFidelity) tells the runtime what the driver already delivers for non-list scalars, so provably no-op decoders can be skipped.
  • The optional rowChanges() member reports the surviving direct row changes of the adapter's active transaction as a number or "unknown"; it backs transactionRowChanges. An adapter without it reports "unknown". The kit's statementRowChanges, observeRowChanges and readRowChanges implement it for the shipped adapters.
  • The optional transactionOutcome: "reported" declaration says that a failed top-level transaction() records what happened beside the value it throws (commit outcome, whether COMMIT was sent, the cleanup ROLLBACK, the connection). An adapter reports through the kit's settleFailedTransaction, which applies the one commit-outcome rule; undeclared means no reports.

toSqlExecutor({ adapter }) bridges an adapter to the SqlExecutor shape @vibeorm/migrate runs on.

@vibeorm/runtime/adapter-kit carries the transaction helpers every shipped adapter is built on, so a custom adapter does not re-implement them: savepoint naming (createSavepointCounter), the savepoint lifetime gate that serialises overlapping sibling transaction() calls on one connection (createSavepointScope, runRootScope, runNestedSavepoint, scopeTurn), and the transaction budget behind TransactionOptions.deadline (validateTransactionDeadline, startTransactionBudget, engineStatementBudgetMs and the typed refusals). The shipped adapters re-export them unchanged.

Helpers for frameworks

  • db.$querySqlResult({ query, decoder? }) executes a constructed SQL fragment once and returns SqlQueryResult<T> with both rows and the adapter's affected rowCount. Rows stay driver-native without a decoder. An explicit ResultReadDecoder<T> needs only decode; no encoder is required for result conversion. The method retains existing raw transaction/context handling and is refused by tenant-scoped and read-only clients (queries.md).
  • transactionOutcomeOf({ error }) returns the facts of the failed top-level transaction that threw error (commit: "not-committed" or "unknown", commitSent, rollback, connection), or undefined when there are none. The error itself is unchanged; branch on adapter.transactionOutcome === "reported", never on an adapter name (queries.md).
  • transactionRowChanges({ db }) returns the direct row changes that survive in the transaction behind a handle, or "unknown" when some statement could not be observed (queries.md).
  • createDelegateMethodClassifier({ db }) returns ({ method, model? }) => "read" | "write" | undefined for that handle's core and configured extension methods; undefined means denied (queries.md).
  • advisoryKeyOf({ text }) maps a string to a signed 64-bit bigint advisory-lock key (SHA-256 of the UTF-8 bytes, first eight bytes, big-endian). Different strings can map to the same key (queries.md).
  • advisoryKeySql({ text }) is the same key computed by PostgreSQL: it wraps a sql fragment that yields text and returns a bigint fragment, so a statement can hash values it selects and lock them (PostgreSQL only).

Relation strategies

const db = createClient({ schema, adapter, options: { relationStrategy: "join" } });
  • "query": one query per relation, batched with WHERE ... IN. Works on every dialect; the default on SQLite.
  • "join": a single statement using LEFT JOIN LATERAL with JSON aggregation. The default on PostgreSQL and PGlite (since 5.0) and on MySQL 8.0.14+ (MySQL keeps "query" until the adapter knows its server runs LATERAL, and when the adapter does not declare a UTC DATETIME reading); an explicit "join" on SQLite, MariaDB or MySQL before 8.0.14 throws VibeError with code VIBE_UNSUPPORTED_CAPABILITY.

A relationStrategy argument on a find overrides the client default. Relation trees are resolved by resolveRelationSelection / resolveRelationLink and attached by attachRelations (query strategy) or attachLateralRelations (join strategy).

Codecs

Value encoding and decoding lives in one table per dialect: POSTGRES_CODECS, SQLITE_CODECS, MYSQL_CODECS, indexed by DIALECT_CODECS and reachable through getCodec({ dialect, type }). Nothing else in the codebase coerces values — scattered coercion is what produced v1's BigInt, enum-array and timestamp bugs.

import { decodeRows, encodeValue, getCodec } from "@vibeorm/runtime";

encodeValue runs on every parameter, decodeValue / decodeRow / decodeRows on every row materialization point — including relation children and join-strategy rows, not only top-level parents. decodeAggregateValue handles aggregate columns, whose result type differs from the underlying field's.

Links