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

utilities.mddocs/

Utilities and Plugins

Utility functions for column mapping, mixins, and extending model functionality.

Capabilities

Snake Case Mappers

Utility functions for converting between camelCase and snake_case column names.

/**
 * Create column name mappers for snake_case conversion
 * @param options - Snake case conversion options
 * @returns Column name mappers object
 */
function snakeCaseMappers(options?: SnakeCaseMappersOptions): ColumnNameMappers;

interface SnakeCaseMappersOptions {
  /** Convert to uppercase (SCREAMING_SNAKE_CASE) */
  upperCase?: boolean;
  
  /** Add underscore before digits */
  underscoreBeforeDigits?: boolean;
  
  /** Add underscore between consecutive uppercase letters */
  underscoreBetweenUppercaseLetters?: boolean;
}

interface ColumnNameMappers {
  /** Parse database column names to model property names */
  parse(json: object): object;
  
  /** Format model property names to database column names */
  format(json: object): object;
}

Usage Examples:

const { snakeCaseMappers } = require('objection');

class Person extends Model {
  static get tableName() {
    return 'persons';
  }
  
  static get columnNameMappers() {
    return snakeCaseMappers();
  }
}

// Now you can use camelCase in your models
const person = await Person.query().insert({
  firstName: 'John',    // Maps to first_name in database
  lastName: 'Doe',      // Maps to last_name in database
  phoneNumber: '555-1234' // Maps to phone_number in database
});

// Custom options
const mappers = snakeCaseMappers({
  upperCase: true,                        // FIRST_NAME instead of first_name
  underscoreBeforeDigits: true,          // address2 -> address_2
  underscoreBetweenUppercaseLetters: true // XMLParser -> x_m_l_parser
});

class CustomModel extends Model {
  static get columnNameMappers() {
    return mappers;
  }
}

Knex Snake Case Mappers

Knex-level mappers for global snake_case conversion.

/**
 * Create Knex mappers for snake_case conversion
 * @param options - Snake case conversion options
 * @returns Knex mappers object
 */
function knexSnakeCaseMappers(options?: SnakeCaseMappersOptions): KnexMappers;

interface KnexMappers {
  /** Wrap identifiers (table/column names) for queries */
  wrapIdentifier(identifier: string, origWrap: (str: string) => string): string;
  
  /** Process response data after query execution */
  postProcessResponse(response: any): any;
}

Usage Examples:

const { knexSnakeCaseMappers } = require('objection');
const Knex = require('knex');

// Configure Knex with snake case mappers
const knex = Knex({
  client: 'postgresql',
  connection: connectionConfig,
  ...knexSnakeCaseMappers()
});

// All queries will automatically convert between camelCase and snake_case
Model.knex(knex);

// Model properties in camelCase
class Person extends Model {
  static get tableName() {
    return 'persons'; // Actually maps to 'persons' table with snake_case columns
  }
}

// Usage is the same, but conversion happens automatically
const person = await Person.query().insert({
  firstName: 'John',      // Automatically becomes first_name in SQL
  lastName: 'Doe',        // Automatically becomes last_name in SQL
  dateOfBirth: new Date() // Automatically becomes date_of_birth in SQL
});

Identifier Mapping

Advanced identifier mapping functionality.

/**
 * Create identifier mapping configuration for Knex
 * @param options - Mapping options
 * @returns Knex configuration object
 */
function knexIdentifierMapping(options?: IdentifierMappingOptions): object;

interface IdentifierMappingOptions {
  upperCase?: boolean;
  underscoreBeforeDigits?: boolean;
  underscoreBetweenUppercaseLetters?: boolean;
}

Model Mixins

Plugin system for extending model functionality.

/**
 * Apply plugins to a model class
 * @param modelClass - Model class to extend
 * @param plugins - Plugins to apply
 * @returns Extended model class
 */
function mixin<T extends typeof Model>(
  modelClass: T, 
  ...plugins: Plugin[]
): T;

/**
 * Compose multiple plugins into a single plugin
 * @param plugins - Plugins to compose
 * @returns Composed plugin
 */
function compose(...plugins: Plugin[]): Plugin;

/**
 * Plugin function type
 */
type Plugin = <T extends typeof Model>(modelClass: T) => T;

Usage Examples:

const { mixin, compose } = require('objection');

// Define plugins
const timestampPlugin = (Model) => {
  return class extends Model {
    $beforeInsert() {
      this.createdAt = new Date();
      this.updatedAt = new Date();
    }
    
    $beforeUpdate() {
      this.updatedAt = new Date();
    }
  };
};

const softDeletePlugin = (Model) => {
  return class extends Model {
    static get modifiers() {
      return {
        ...super.modifiers,
        notDeleted: (query) => query.whereNull('deletedAt')
      };
    }
    
    $beforeDelete() {
      return this.$query().patch({ deletedAt: new Date() });
    }
  };
};

// Apply single plugin
const PersonWithTimestamps = mixin(Person, timestampPlugin);

// Apply multiple plugins
const PersonWithPlugins = mixin(Person, timestampPlugin, softDeletePlugin);

// Compose plugins
const combinedPlugin = compose(timestampPlugin, softDeletePlugin);
const PersonWithCombined = mixin(Person, combinedPlugin);

// Create model with plugins applied
class EnhancedPerson extends mixin(Model, timestampPlugin, softDeletePlugin) {
  static get tableName() {
    return 'persons';
  }
}

Model Traversal

Utilities for traversing model graphs and relationships.

/**
 * Traverse model instances and their relations
 * @param models - Model instance(s) to traverse
 * @param traverser - Function called for each model
 */
static traverse(
  models: Model | Model[], 
  traverser: TraverserFunction
): void;

/**
 * Traverse model instances with filtering
 * @param filterConstructor - Only traverse models of this type
 * @param models - Model instance(s) to traverse  
 * @param traverser - Function called for each model
 */
static traverse(
  filterConstructor: typeof Model,
  models: Model | Model[],
  traverser: TraverserFunction
): void;

/**
 * Async version of traverse
 */
static traverseAsync(
  models: Model | Model[],
  traverser: TraverserFunction
): Promise<void>;

type TraverserFunction = (
  model: Model, 
  parentModel?: Model, 
  relationName?: string
) => void;

Usage Examples:

// Traverse all models in a graph
const people = await Person.query()
  .withGraphFetched('[pets, movies.actors]');

Person.traverse(people, (model, parent, relationName) => {
  console.log(`Found ${model.constructor.name}:`, model.id);
  if (parent) {
    console.log(`  Child of ${parent.constructor.name} via ${relationName}`);
  }
});

// Traverse only specific model types
Person.traverse(Pet, people, (pet) => {
  console.log('Found pet:', pet.name);
});

// Async traversal for database operations
await Person.traverseAsync(people, async (model) => {
  if (model instanceof Person) {
    await model.$query().patch({ lastAccessed: new Date() });
  }
});

// Instance-level traversal
const person = await Person.query()
  .findById(1)
  .withGraphFetched('pets');

person.$traverse((model) => {
  model.accessed = true;
});

Initialization

Initialize models with database connections and metadata.

/**
 * Initialize model classes with Knex instance and fetch metadata
 * @param knex - Knex instance to bind
 * @param modelClasses - Model classes to initialize
 * @returns Promise resolving when initialization is complete
 */
function initialize(
  knex: Knex, 
  modelClasses: Array<typeof Model>
): Promise<void>;

/**
 * Initialize with default Knex instance
 * @param modelClasses - Model classes to initialize
 * @returns Promise resolving when initialization is complete
 */
function initialize(modelClasses: Array<typeof Model>): Promise<void>;

Usage Examples:

const { initialize } = require('objection');
const Knex = require('knex');

const knex = Knex({
  client: 'postgresql',
  connection: connectionConfig
});

// Initialize models
await initialize(knex, [Person, Pet, Movie]);

// Models are now ready to use
const people = await Person.query();

Table Metadata

Utilities for fetching and caching table metadata.

/**
 * Get cached table metadata
 * @param options - Metadata options
 * @returns Table metadata object
 */
static tableMetadata(options?: TableMetadataOptions): TableMetadata;

/**
 * Fetch fresh table metadata from database
 * @param options - Fetch options
 * @returns Promise resolving to table metadata
 */
static fetchTableMetadata(options?: FetchTableMetadataOptions): Promise<TableMetadata>;

interface TableMetadata {
  columns: string[];
}

interface TableMetadataOptions {
  table?: string;
}

interface FetchTableMetadataOptions {
  knex?: Knex;
  force?: boolean;
  table?: string;
}

Usage Examples:

// Get cached metadata
const metadata = Person.tableMetadata();
console.log('Columns:', metadata.columns);

// Fetch fresh metadata
const freshMetadata = await Person.fetchTableMetadata({ force: true });
console.log('Fresh columns:', freshMetadata.columns);

// Fetch metadata for specific table
const petMetadata = await Person.fetchTableMetadata({ table: 'pets' });

Class Utilities

Low-level utilities for class manipulation and inheritance.

/**
 * Set up inheritance between classes (used internally)
 * @param Child - Child class
 * @param Parent - Parent class
 */
function inherit(Child: Function, Parent: Function): void;

Types

interface SnakeCaseMappersOptions {
  upperCase?: boolean;
  underscoreBeforeDigits?: boolean;
  underscoreBetweenUppercaseLetters?: boolean;
}

interface ColumnNameMappers {
  parse(json: object): object;
  format(json: object): object;
}

interface KnexMappers {
  wrapIdentifier(identifier: string, origWrap: (str: string) => string): string;
  postProcessResponse(response: any): any;
}

type Plugin = <T extends typeof Model>(modelClass: T) => T;

type TraverserFunction = (
  model: Model,
  parentModel?: Model,
  relationName?: string
) => void;

interface TableMetadata {
  columns: string[];
}

interface TableMetadataOptions {
  table?: string;
}

interface FetchTableMetadataOptions {
  knex?: Knex;
  force?: boolean;
  table?: string;
}

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