Skip to content

Changelog - 2025-12-18

Performance Optimizations

This update focuses on performance improvements for the repository layer, reducing GC pressure and improving query execution speed.

Overview

  • WeakMap Cache: DrizzleFilterBuilder now caches getTableColumns() results.
  • Core API for Flat Queries: ReadableRepository uses faster Drizzle Core API when possible.
  • Static schemaFactory Singleton: BaseEntity shares a single schemaFactory instance across all entities.
  • Async/Await Refactor: Removed redundant Promise wrappers from repository methods.

Performance Improvements

1. WeakMap Cache for Filter Builder

File: packages/core/src/base/repositories/operators/filter.ts

Problem: getTableColumns() was called on every filter operation, causing repeated reflection overhead.

Solution: Added static WeakMap cache that stores column metadata per schema.

typescript
export class DrizzleFilterBuilder extends BaseHelper {
  // Static cache shared across all instances
  private static columnCache = new WeakMap<
    TTableSchemaWithId,
    ReturnType<typeof getTableColumns>
  >();

  private getColumns<Schema extends TTableSchemaWithId>(schema: Schema) {
    let columns = DrizzleFilterBuilder.columnCache.get(schema);
    if (!columns) {
      columns = getTableColumns(schema);
      DrizzleFilterBuilder.columnCache.set(schema, columns);
    }
    return columns;
  }
}

Benefits:

  • First call: getTableColumns(schema) → cached
  • Subsequent calls: Retrieved from WeakMap (O(1) lookup)
  • WeakMap allows garbage collection when schema is no longer referenced

2. Core API for Flat Queries

File: packages/core/src/base/repositories/core/readable.ts

Problem: All queries used Drizzle's Query API, which has overhead for relational mapping even when not needed.

Solution: Automatically use Drizzle Core API for flat queries (no relations, no field selection).

typescript
// Automatic optimization - no code changes needed
const users = await repo.find({
  filter: {
    where: { status: 'active' },
    limit: 10,
    order: ['createdAt DESC'],
  }
});
// Uses: db.select().from(table).where(...).orderBy(...).limit(10)
ScenarioImprovement
Simple find() queries~15-20% faster

3. Static schemaFactory Singleton

File: packages/core/src/base/models/base.ts

Problem: New schemaFactory was created for every BaseEntity instance, causing memory overhead.

Solution: Lazy singleton pattern shared across all instances.

typescript
// After - shared singleton
private static _schemaFactory?: ReturnType<typeof createSchemaFactory>;
protected static get schemaFactory(): ReturnType<typeof createSchemaFactory> {
  return (BaseEntity._schemaFactory ??= createSchemaFactory());
}

Benefits:

  • Single instance for all entities
  • Lazy initialization (created on first use)
  • Reduced memory footprint

4. Async/Await Refactor

Files: packages/core/src/base/repositories/core/readable.ts, packages/core/src/base/repositories/core/persistable.ts

Problem: Every CRUD method wrapped existing promises in new Promise().

Solution: Direct async/await.

typescript
// After - Clean and efficient
const count = await this.connector.$count(this.entity.schema, where);
return { count };

Benefits:

  • Eliminates extra microtask queue entries
  • Reduces ~200-400 bytes memory per Promise on V8
  • Cleaner stack traces for debugging

Files Changed

Core Package (packages/core)

FileChanges
src/base/repositories/operators/filter.tsWeakMap cache for getTableColumns()
src/base/repositories/core/readable.tsCore API optimization, async/await refactor
src/base/repositories/core/persistable.tsAsync/await refactor
src/base/models/base.tsStatic schemaFactory singleton

No Breaking Changes

All changes are internal optimizations. No API changes or migration required.