A TypeScript library that implements the Unit of Work pattern for DrizzleORM, providing automatic change tracking, transaction management, and checkpoint/rollback functionality.
- ๐ Automatic Change Tracking - Transparently tracks entity modifications through JavaScript proxies
- ๐พ Transaction Management - Batches database operations and commits them in a single transaction
- โฎ๏ธ Checkpoint & Rollback - Create snapshots and rollback to previous states with full CRUD support
- ๐บ๏ธ Identity Map - Ensures single instance per entity and prevents duplicate fetches
- ๐ Type Safety - Full TypeScript support with Drizzle's type inference
- ๐ Zero Dependencies - Only requires DrizzleORM as a peer dependency
- โก Efficient Queries - Smart caching and optimized primary key lookups
- ๐ก๏ธ Data Integrity - Comprehensive validation and error handling
bun add drizzow
# or
npm install drizzow
# or
pnpm add drizzow
import { drizzow } from "drizzow/bun-sqlite";
import { drizzle } from "drizzle-orm/bun-sqlite";
import { Database } from "bun:sqlite";
// Define your schema
const users = sqliteTable("users", {
id: integer().primaryKey({ autoIncrement: true }),
username: text().notNull(),
email: text(),
});
// Create database and UoW
const sqlite = new Database("app.db");
const db = drizzle(sqlite, { schema: { users } });
const uow = drizzow(db);
// Use the UoW with the new find() API
const user = await uow.users.find({ id: 1 });
// Changes are automatically tracked
user.email = "[email protected]";
// Save all changes in a single transaction
await uow.save();
The Unit of Work pattern maintains a list of objects affected by a business transaction and coordinates writing out changes. It keeps track of everything you do during a business transaction that can affect the database.
All entities returned from queries are automatically wrapped in proxies that track property changes:
const user = await uow.users.findFirst();
console.log(uow.getStats().pendingChanges); // 0
user.username = "newUsername";
console.log(uow.getStats().pendingChanges); // 1
The identity map ensures that each entity is loaded only once per unit of work:
const user1 = await uow.users.findFirst({ where: eq(users.id, 1) });
const user2 = await uow.users.findFirst({ where: eq(users.id, 1) });
console.log(user1 === user2); // true - same object reference
import { drizzow } from "drizzow/bun-sqlite";
const uow = drizzow(drizzleDb);
The UoW provides an efficient find()
method for primary key lookups with identity map caching:
// Find single entity by primary key
const user = await uow.users.find({ id: 1 });
// Find multiple entities by primary keys
const users = await uow.users.find({ id: [1, 2, 3] });
// Returns undefined if not found
const notFound = await uow.users.find({ id: 999 }); // undefined
// Returns empty array if none found
const empty = await uow.users.find({ id: [999, 1000] }); // []
// Create new entity
const newUser = uow.users.create({
id: 100,
username: "bob",
email: "[email protected]",
});
// Delete entity
const user = await uow.users.find({ id: 1 });
uow.users.delete(user);
// Save all pending changes
await uow.save();
// Save up to a specific checkpoint
const checkpoint = uow.setCheckpoint();
// ... make more changes ...
await uow.save(checkpoint); // Only saves changes up to checkpoint
// Create a checkpoint
const checkpoint = uow.setCheckpoint();
// Make changes
user.email = "[email protected]";
// Rollback to checkpoint
const result = uow.rollback(checkpoint);
if (result.error) {
console.error("Rollback failed:", result.error);
}
- Lazy Proxy Creation: Proxies are created only when entities are accessed
- Efficient Change Detection: Only tracks actual modifications
- Checkpoint Limit: Maximum of 50 checkpoints to prevent memory issues
- Batch Operations: All changes are saved in a single transaction
bun install
bun test
bun run bench
The library consists of several key components:
- UnitOfWork: Central coordinator for all operations
- ProxyManager: Creates and manages entity proxies
- ChangeTracker: Records and computes changesets
- IdentityMap: Ensures entity uniqueness
- CheckpointManager: Handles snapshots and rollbacks
- DatabaseAdapter: Abstraction for different database types
- Fixed Critical Bug: Checkpoint save operations now properly handle create and delete operations
- Enhanced Test Coverage: Added comprehensive CRUD interaction tests
- Improved API: New efficient
find()
method with identity map caching - Better Error Handling: Enhanced validation and error messages
- Currently only supports Bun SQLite and PostgreSQL (More adapters coming soon)
- Relationships are not automatically tracked (manual handling required)
- Complex queries still require direct Drizzle usage
Contributions are welcome! Please feel free to submit a Pull Request.
MIT
Built with Bun and DrizzleORM