CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-objection

An SQL-friendly ORM for Node.js built on Knex.js with powerful query building, relationship handling, and JSON Schema validation

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

transactions.mddocs/

Transactions

Database transaction management with model binding and automatic rollback.

Capabilities

Transaction Function

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 };
});

Model Transaction Methods

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;
}

Using Transactions with Queries

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 });
});

Transaction with Graph Operations

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;
});

Transaction Error Handling

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
}

Nested Transactions

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 };
});

Transaction Isolation Levels

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...
});

Transaction Savepoints

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;
});

Types

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

docs

expression-builders.md

graph-operations.md

index.md

model-definition.md

query-building.md

relationships.md

transactions.md

utilities.md

validation.md

tile.json