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

@firtoz/drizzle-utils

v0.3.3

Published

Shared utilities and types for Drizzle-based packages

Readme

@firtoz/drizzle-utils

Shared utilities and types for Drizzle ORM-based packages. Provides type-safe table builders with automatic timestamp tracking, branded IDs, common migration types, and collection sync utilities.

⚠️ Early WIP Notice: This package is in very early development and is not production-ready. It is TypeScript-only and may have breaking changes. While I (the maintainer) have limited time, I'm open to PRs for features, bug fixes, or additional support (like JS builds). Please feel free to try it out and contribute! See CONTRIBUTING.md for details.

Installation

npm install @firtoz/drizzle-utils drizzle-orm drizzle-valibot

Features

🏗️ Syncable Table Builder

Create SQLite tables with automatic timestamp tracking and UUID primary keys:

import { syncableTable } from "@firtoz/drizzle-utils";
import { text, integer } from "drizzle-orm/sqlite-core";

const todoTable = syncableTable("todos", {
  title: text("title").notNull(),
  completed: integer("completed", { mode: "boolean" }).notNull().default(false),
  description: text("description"),
});

// Automatically includes:
// - id: TableId<"todos"> (UUID primary key)
// - createdAt: Date (auto-set on insert)
// - updatedAt: Date (auto-set on update)
// - deletedAt: Date | null (for soft deletes)

type Todo = typeof todoTable.$inferSelect;
// {
//   id: TableId<"todos">;
//   title: string;
//   completed: boolean;
//   description: string | null;
//   createdAt: Date;
//   updatedAt: Date;
//   deletedAt: Date | null;
// }

🏷️ Branded ID Types

Type-safe IDs with table-specific branding prevent mixing IDs from different tables:

import { makeId, type IdOf } from "@firtoz/drizzle-utils";

const todoId = makeId(todoTable, "123e4567-e89b-12d3-a456-426614174000");
const userId = makeId(userTable, "123e4567-e89b-12d3-a456-426614174000");

// TypeScript prevents mixing different table IDs
function getTodo(id: IdOf<typeof todoTable>) { /* ... */ }

getTodo(todoId);  // ✅ OK
getTodo(userId);  // ❌ Type error - wrong table!

📋 Column Helpers

Individual column builders for custom table definitions:

import { 
  idColumn, 
  createdAtColumn, 
  updatedAtColumn, 
  deletedAtColumn 
} from "@firtoz/drizzle-utils";

// Use in custom table definitions
const customTable = sqliteTable("custom", {
  id: idColumn,
  name: text("name").notNull(),
  createdAt: createdAtColumn,
  updatedAt: updatedAtColumn,
  deletedAt: deletedAtColumn,
});

🔄 Migration Types

Shared TypeScript types for Drizzle migrations across IndexedDB and SQLite:

import type { 
  Journal, 
  JournalEntry,
  Snapshot,
  TableDefinition,
  ColumnDefinition,
  IndexDefinition
} from "@firtoz/drizzle-utils";

// Use these types for custom migration logic
function applyMigration(snapshot: Snapshot) {
  for (const [tableName, table] of Object.entries(snapshot.tables)) {
    const tableDef: TableDefinition = table;
    // ... migration logic
  }
}

📝 Schema Type Helpers

Type-safe Valibot schema inference:

import { type SelectSchema, type InsertSchema } from "@firtoz/drizzle-utils";

type TodoSelect = SelectSchema<typeof todoTable>;
type TodoInsert = InsertSchema<typeof todoTable>;

// Use with Valibot for runtime validation
import { parse } from "valibot";
import { createSelectSchema, createInsertSchema } from "drizzle-valibot";

const selectSchema = createSelectSchema(todoTable);
const insertSchema = createInsertSchema(todoTable);

const validTodo = parse(selectSchema, data);

API Reference

Table Builders

syncableTable(name, columns, extraConfig?)

Creates a SQLite table with automatic timestamp tracking.

Parameters:

  • name: string - Table name
  • columns: Record<string, SQLiteColumnBuilder> - Column definitions (cannot use id, createdAt, updatedAt, or deletedAt as keys)
  • extraConfig?: (self) => SQLiteTableExtraConfigValue[] - Optional indexes and constraints

Returns: SQLite table with id, createdAt, updatedAt, deletedAt columns

Validation:

  • Ensures default values are compatible with IndexedDB (no SQL expressions)
  • Throws error if SQL expressions are used as default values

Column Helpers

idColumn

Text column configured as primary key with branded UUID type.

createdAtColumn

Integer timestamp column (mode: "timestamp") with automatic default (current date).

updatedAtColumn

Integer timestamp column (mode: "timestamp") with automatic default (current date).

deletedAtColumn

Nullable integer timestamp column (mode: "timestamp") for soft deletes.

Type Utilities

Branded<T, Brand>

Creates a branded type for better type safety.

TableId<TTableName>

Table-specific branded ID type with table name in the brand.

IdOf<TTable>

Extracts the ID type from a Drizzle table.

makeId<TTable>(table, value)

Safely creates a branded ID for a specific table.

SelectSchema<TTable>

Infers the Valibot select schema type from a Drizzle table.

InsertSchema<TTable>

Infers the Valibot insert schema type from a Drizzle table.

Migration Types

Comprehensive types for database migrations:

  • Journal - Migration journal with version and entries
  • JournalEntry - Individual migration record
  • Snapshot - Complete database schema snapshot
  • TableDefinition - Table structure definition
  • ColumnDefinition - Column configuration
  • IndexDefinition - Index configuration
  • ForeignKeyDefinition - Foreign key constraint
  • ViewDefinition - Database view definition
  • EnumDefinition - Enum type definition

Collection Sync Utilities

Support for external sync and collection utilities:

External Sync Events

Push sync events from external sources (e.g., proxy servers) to collections:

import type { ExternalSyncEvent, ExternalSyncHandler } from "@firtoz/drizzle-utils";

// Receive sync events from a server
const handleSync: ExternalSyncHandler<Todo> = (event) => {
  switch (event.type) {
    case "insert":
      // event.items contains new items
      break;
    case "update":
      // event.items contains updated items
      break;
    case "delete":
      // event.items contains deleted items
      break;
    case "truncate":
      // All items should be cleared
      break;
  }
};

// Push events to a collection's reactive store
syncResult.pushExternalSync({ type: "insert", items: [newTodo] });
syncResult.pushExternalSync({ type: "truncate" });

Collection Utils

The SyncFunctionResult includes utilities for common operations:

// Truncate (clear all data)
await collection.utils.truncate();

SyncBackend Interface

For implementing custom backends, the SyncBackend interface includes:

interface SyncBackend<TTable> {
  initialLoad: (write) => Promise<void>;
  loadSubset: (options, write) => Promise<void>;
  handleInsert: (mutations) => Promise<T[]>;
  handleUpdate: (mutations) => Promise<T[]>;
  handleDelete: (mutations) => Promise<void>;
  handleTruncate?: () => Promise<void>; // Optional truncate support
}

Best Practices

1. Use syncableTable for Data Tables

Always use syncableTable for tables that need timestamp tracking:

// ✅ Good - automatic timestamps
const todoTable = syncableTable("todos", {
  title: text("title").notNull(),
});

// ❌ Bad - manual timestamp management
const todoTable = sqliteTable("todos", {
  id: text("id").primaryKey(),
  title: text("title").notNull(),
  createdAt: integer("createdAt", { mode: "timestamp" }),
  // ... repetitive boilerplate
});

2. Leverage Branded IDs

Use branded IDs to prevent mixing IDs from different tables:

type TodoId = IdOf<typeof todoTable>;
type UserId = IdOf<typeof userTable>;

function assignTodo(todoId: TodoId, userId: UserId) {
  // Type safety ensures correct ID types are used
}

3. Avoid SQL Expressions in Defaults

The syncableTable validates that default values work with IndexedDB:

// ✅ Good - JavaScript default
const table = syncableTable("table", {
  status: text("status").default("pending"),
  count: integer("count").default(0),
});

// ❌ Bad - SQL expression (will throw error)
const table = syncableTable("table", {
  status: text("status").default(sql`'pending'`),  // Error!
});

Integration

This package is used by:

  • @firtoz/drizzle-indexeddb - IndexedDB migrations
  • @firtoz/drizzle-sqlite-wasm - SQLite WASM integration

License

MIT

Author

Firtina Ozbalikchi [email protected]