@pylonts/dao
v1.1.3
Published
Lightweight DAO framework — knex proxy with transparent transaction context + @Trans() declarative transaction decorator
Readme
@pylonts/dao — Lightweight DAO Framework for Knex
Overview
@pylonts/dao provides two building blocks on top of knex:
knextransparent proxy — DAO code never needs to know whether it's inside a transaction. The proxy delegates to the transaction instance when inside@Trans(), and to the global instance otherwise.@Trans()decorator — mark a Service method and its entire body runs in a single database transaction. Notrxparameter threading.
Also includes a paginate() utility for the most common DAO pattern.
Install
npm install @pylonts/dao knexAPI
initDaoKnex(knex)
Call once at app startup to register the global knex instance.
import knexFactory from 'knex';
import { initDaoKnex } from '@pylonts/dao';
const db = knexFactory({ client: 'mysql2', connection: { ... } });
initDaoKnex(db);knex
The transparent proxy. Use it in DAOs exactly like raw knex — it looks up the correct knex instance (transaction or global) at call time.
import { knex } from '@pylonts/dao';
class CouponDao {
async findById(id: number) {
return knex('coupon').where('id', id).first();
}
async insert(row: CouponInsertRow): Promise<number> {
const [id] = await knex('coupon').insert(row);
row.id = id;
return 1;
}
}@Trans()
Method decorator. Wraps the method body in knex.transaction(). DAO calls inside automatically participate via the proxy.
import { Trans } from '@pylonts/dao';
class CouponService {
@Trans()
async create(data: CouponCreateDto) {
await couponDao.insert(row); // same transaction
await couponStoreDao.batchInsert(storeRows); // same transaction
}
}- Nested
@Trans()creates a savepoint. @Trans()only belongs on Service methods, never on DAO methods.
afterCommit(hook)
Register a callback to run after the outermost transaction commits. Must be called inside a @Trans() method.
import { Trans, afterCommit } from '@pylonts/dao';
class OrderService {
@Trans()
async cancelOrder(orderId: number) {
await orderDao.updateStatus(orderId, 'CANCELLED');
afterCommit(() => eventBus.publish('OrderCancelled', { orderId }));
}
}- The hook runs only when the transaction commits successfully — never on rollback.
- Nested
@Trans()calls share the outermost hook list; hooks always fire after the outer commit. - Hook failures are logged but do not fail the business transaction (database work is already committed). This is the foundation for transactional outbox delivery.
paginate(query, page, pageSize)
Execute a paginated query. Runs COUNT on a clone, then applies OFFSET/LIMIT to the original.
import { knex, paginate, type PagedRows } from '@pylonts/dao';
async listCoupons(filter: CouponFilter, page: number, pageSize: number): Promise<PagedRows<CouponRow>> {
const query = knex('coupon')
.where('status', filter.status)
.where('mer_id', filter.merId)
.orderBy('created_at', 'desc');
return paginate<CouponRow>(query, page, pageSize);
}Returns { rows: T[], total: number }.
PagedRows<T>
interface PagedRows<T> {
rows: T[];
total: number;
}How It Works
@Trans() Service method
└─ knex.transaction(trx => {
ALS.run(trx, …)
└─ await couponDao.insert(row)
└─ knex('coupon').insert(row)
└─ proxy.apply → resolve()
└─ ALS.getStore() → trx ← the transaction instance
})Outside @Trans(), resolve() returns the global knex instance. DAO code never changes.
Peer Dependencies
knex^3.0.0
License
MIT
