In-Memory Repository

Fast, simple storage for testing and prototyping

The In-Memory Repository provides a lightweight implementation of the Repository pattern backed by JavaScript Maps. It is perfect for testing, rapid prototyping, and development scenarios where persistence is not required.

Installation

The In-Memory Repository is included in @justscale/core/models:

Bash
pnpm add @justscale/core/models

Basic Usage

The InMemoryRepository can be used directly or extended for custom entity types:

user-repository.tsTypeScript
import { InMemoryRepository } from '@justscale/core/models';
import type { User } from './types';

// Direct usage
const users = new InMemoryRepository<User>();

// Save entities
const user = await users.save({
  email: 'john@example.com',
  name: 'John Doe',
});

console.log(user.id); // Auto-generated ID
console.log(user.createdAt); // Auto-generated timestamp
console.log(user.updatedAt); // Auto-generated timestamp

Querying

The in-memory adapter supports all standard repository query operations:

queries.tsTypeScript
import { InMemoryRepository } from '@justscale/core/models';
import type { User } from './types';

const users = new InMemoryRepository<User>();

// Find by ID
const user = await users.findById('1');

// Find one by criteria
const john = await users.findOne({ email: 'john@example.com' });

// Find all with filters
const activeUsers = await users.find({
  where: { active: true },
  orderBy: { createdAt: 'desc' },
  limit: 10,
  offset: 0,
});

// Count
const total = await users.count({ active: true });

// Check existence
const exists = await users.exists('1');

Custom ID Generation

By default, the adapter generates sequential numeric IDs. You can provide a custom ID generator:

custom-id-generator.tsTypeScript
import { InMemoryRepository } from '@justscale/core/models';
import { randomUUID } from 'crypto';
import type { User } from './types';

const users = new InMemoryRepository<User>(
  () => randomUUID() // Use UUIDs instead
);

const user = await users.save({ email: 'test@example.com', name: 'Test' });
console.log(user.id); // 'a1b2c3d4-e5f6-7890-abcd-ef1234567890'

Testing with In-Memory Repository

The in-memory adapter is ideal for unit tests because it is fast and does not require external dependencies:

user-service.test.tsTypeScript
import { describe, test, expect, beforeEach } from 'vitest';
import { InMemoryRepository } from '@justscale/core/models';
import type { User } from './types';

describe('UserService', () => {
  let users: InMemoryRepository<User>;

  beforeEach(() => {
    // Fresh repository for each test
    users = new InMemoryRepository<User>();
  });

  test('creates user with unique email', async () => {
    const user = await users.save({
      email: 'unique@example.com',
      name: 'Test User',
    });

    expect(user.id).toBeDefined();
    expect(user.email).toBe('unique@example.com');
  });

  test('finds user by email', async () => {
    await users.save({ email: 'find@example.com', name: 'Find Me' });

    const found = await users.findOne({ email: 'find@example.com' });
    expect(found).toBeDefined();
    expect(found?.name).toBe('Find Me');
  });

  test('updates existing user', async () => {
    const user = await users.save({ email: 'old@example.com', name: 'Old' });

    user.name = 'New';
    const updated = await users.save(user);

    expect(updated.name).toBe('New');
    expect(updated.updatedAt.getTime()).toBeGreaterThan(user.createdAt.getTime());
  });
});

Clear Method

The adapter provides a clear() method for resetting state during tests:

clear-example.tsTypeScript
import { InMemoryRepository } from '@justscale/core/models';
import type { User } from './types';

const users = new InMemoryRepository<User>();

await users.save({ email: 'test@example.com', name: 'Test' });
console.log(await users.count()); // 1

users.clear(); // Reset repository
console.log(await users.count()); // 0

Dependency Injection

Use the in-memory adapter with JustScale's DI system:

app.tsTypeScript
import JustScale from '@justscale/core';
import { InMemoryRepository } from '@justscale/core/models';
import { UserRepository } from './user-repository';
import { UserService } from './user-service';

// Provide in-memory implementation
const app = JustScale()
  .add(UserService)
  .bind(UserRepository, InMemoryRepository)
  .build()

Limitations

⚠️

Warning

The in-memory adapter has important limitations:
  • No persistence - All data is lost when the process restarts
  • Single process only - Does not share data across multiple instances or workers
  • Memory constrained - Large datasets can consume significant memory
  • Simple queries only - No complex joins, aggregations, or full-text search

For production applications, use the PostgreSQL adapter.

When to Use

The in-memory adapter is best suited for:

  • Unit testing with fast, isolated tests
  • Rapid prototyping and proof-of-concepts
  • Development environments with simple data needs
  • Integration tests that do not require real database state

For production use cases, use the PostgreSQL adapter.