An SQL-friendly ORM for Node.js built on Knex.js with powerful query building, relationship handling, and JSON Schema validation
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Database transaction management with model binding and automatic rollback.
Create and manage database transactions with automatic rollback on errors.
/**
* Execute callback within a database transaction
* @param callback - Function to execute within transaction
* @returns Promise resolving to callback result
*/
function transaction<T>(
callback: (trx: Transaction) => Promise<T>
): Promise<T>;
/**
* Execute callback within a transaction using specific Knex instance
* @param knex - Knex instance to use for transaction
* @param callback - Function to execute within transaction
* @returns Promise resolving to callback result
*/
function transaction<T>(
knex: Knex,
callback: (trx: Transaction) => Promise<T>
): Promise<T>;
/**
* Execute callback with bound model classes
* @param modelClass - Model class to bind to transaction
* @param callback - Function receiving bound model class
* @returns Promise resolving to callback result
*/
function transaction<T>(
modelClass: typeof Model,
callback: (BoundModel: typeof Model, trx?: Transaction) => Promise<T>
): Promise<T>;
/**
* Execute callback with multiple bound model classes
*/
function transaction<T>(
modelClass1: typeof Model,
modelClass2: typeof Model,
callback: (BoundModel1: typeof Model, BoundModel2: typeof Model, trx?: Transaction) => Promise<T>
): Promise<T>;Usage Examples:
const { transaction, Model } = require('objection');
// Basic transaction
const result = await transaction(async (trx) => {
const person = await Person.query(trx)
.insert({ firstName: 'John', lastName: 'Doe' });
const pet = await Pet.query(trx)
.insert({ name: 'Fluffy', ownerId: person.id });
return { person, pet };
});
// Transaction with specific Knex instance
const result = await transaction(knex, async (trx) => {
// Operations using the provided knex instance
const person = await Person.query(trx).insert(personData);
return person;
});
// Transaction with bound model classes
const result = await transaction(Person, Pet, async (BoundPerson, BoundPet, trx) => {
// BoundPerson and BoundPet are automatically bound to the transaction
const person = await BoundPerson.query().insert(personData);
const pet = await BoundPet.query().insert({ ...petData, ownerId: person.id });
return { person, pet };
});Transaction-related methods available on model classes.
/**
* Start a new transaction
* @param knexOrTransaction - Knex instance or existing transaction
* @returns Promise resolving to Transaction
*/
static startTransaction(knexOrTransaction?: Knex | Transaction): Promise<Transaction>;
/**
* Execute callback within a transaction
* @param callback - Function to execute
* @returns Promise resolving to callback result
*/
static transaction<T>(callback: (trx: Transaction) => Promise<T>): Promise<T>;
/**
* Execute callback within a transaction with specific Knex/transaction
* @param trxOrKnex - Transaction or Knex instance
* @param callback - Function to execute
* @returns Promise resolving to callback result
*/
static transaction<T>(
trxOrKnex: Transaction | Knex,
callback: (trx: Transaction) => Promise<T>
): Promise<T>;
/**
* Bind model class to a Knex instance or transaction
* @param trxOrKnex - Transaction or Knex instance to bind
* @returns Bound model class
*/
static bindKnex(trxOrKnex: Transaction | Knex): typeof Model;
/**
* Bind model class to a transaction (alias for bindKnex)
* @param trxOrKnex - Transaction or Knex instance to bind
* @returns Bound model class
*/
static bindTransaction(trxOrKnex: Transaction | Knex): typeof Model;Usage Examples:
// Start transaction manually
const trx = await Person.startTransaction();
try {
const person = await Person.query(trx).insert(personData);
const pet = await Pet.query(trx).insert(petData);
await trx.commit();
return { person, pet };
} catch (error) {
await trx.rollback();
throw error;
}
// Model class transaction
const result = await Person.transaction(async (trx) => {
const person = await Person.query(trx).insert(personData);
return person;
});
// Bind model to transaction
const trx = await Person.startTransaction();
const BoundPerson = Person.bindKnex(trx);
try {
const person = await BoundPerson.query().insert(personData);
await trx.commit();
} catch (error) {
await trx.rollback();
throw error;
}Pass transactions to query methods for transaction-aware operations.
/**
* Execute query within a transaction
* @param trx - Transaction instance
* @returns QueryBuilder bound to transaction
*/
query(trx: Transaction): QueryBuilder;
/**
* Execute related query within a transaction
* @param relationName - Relation name
* @param trx - Transaction instance
* @returns QueryBuilder bound to transaction
*/
relatedQuery(relationName: string, trx: Transaction): QueryBuilder;Usage Examples:
await transaction(async (trx) => {
// All queries use the same transaction
const person = await Person.query(trx)
.insert({ firstName: 'John', lastName: 'Doe' });
const pets = await person.$relatedQuery('pets', trx)
.insert([
{ name: 'Fluffy', species: 'cat' },
{ name: 'Buddy', species: 'dog' }
]);
// Update within transaction
await Person.query(trx)
.findById(person.id)
.patch({ petCount: pets.length });
});Graph operations automatically participate in transactions.
Usage Examples:
await transaction(async (trx) => {
// insertGraph uses the transaction
const result = await Person.query(trx)
.insertGraphAndFetch({
firstName: 'Jane',
lastName: 'Smith',
pets: [
{ name: 'Max', species: 'dog' },
{ name: 'Luna', species: 'cat' }
],
movies: [
{ title: 'New Movie', year: 2023 }
]
});
// All inserts are in the same transaction
return result;
});Transactions automatically rollback on errors, but you can also handle them explicitly.
Usage Examples:
const { DBError, ValidationError } = require('objection');
try {
await transaction(async (trx) => {
// If any operation fails, transaction is automatically rolled back
const person = await Person.query(trx).insert(personData);
const pet = await Pet.query(trx).insert(invalidPetData); // Might fail
return { person, pet };
});
} catch (error) {
if (error instanceof ValidationError) {
console.log('Validation failed:', error.data);
} else if (error instanceof DBError) {
console.log('Database error:', error.message);
}
// Transaction was automatically rolled back
}Handle nested transaction calls appropriately.
Usage Examples:
await transaction(async (trx) => {
const person = await Person.query(trx).insert(personData);
// Nested transaction - will reuse outer transaction
const pets = await transaction(trx, async (innerTrx) => {
return Pet.query(innerTrx).insert(petDataArray);
});
return { person, pets };
});Control transaction isolation levels through Knex configuration.
Usage Examples:
// Configure isolation level in knex config
const knex = Knex({
client: 'postgresql',
connection: connectionConfig,
pool: {
afterCreate: function(conn, done) {
conn.query('SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;', done);
}
}
});
// Or set for specific transaction
await transaction(async (trx) => {
await trx.raw('SET TRANSACTION ISOLATION LEVEL READ COMMITTED');
// Perform operations...
});Use savepoints for partial rollbacks within transactions.
Usage Examples:
await transaction(async (trx) => {
const person = await Person.query(trx).insert(personData);
const savepoint = await trx.savepoint();
try {
// Risky operation
await Pet.query(trx).insert(riskyPetData);
await savepoint.release();
} catch (error) {
// Rollback to savepoint, not entire transaction
await savepoint.rollback();
console.log('Pet creation failed, but person creation preserved');
}
return person;
});type Transaction = Knex.Transaction;
interface TransactionConfig {
isolationLevel?: 'read uncommitted' | 'read committed' | 'repeatable read' | 'serializable';
readOnly?: boolean;
}
interface Savepoint {
release(): Promise<void>;
rollback(): Promise<void>;
}
type TransactionCallback<T> = (trx: Transaction) => Promise<T>;
type ModelTransactionCallback<T> = (
BoundModel: typeof Model,
trx?: Transaction
) => Promise<T>;Install with Tessl CLI
npx tessl i tessl/npm-objection