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 implements the same interface as the PostgreSQL adapter, making it perfect for testing and prototyping.

Basic Usage

Define a model with defineModel, then create an in-memory model and repository:

user.model.tsTypeScript
import { defineModel, field } from '@justscale/core/models'
import { createInMemoryModel, createInMemoryRepository } from '@justscale/core/models'

// 1. Domain model (same as production)
export class User extends defineModel({
  email: field.string().max(255).unique(),
  name: field.string().max(100),
  active: field.boolean().default(true),
}) {}

// 2. In-memory model (adapter decides storage details)
const MemUser = createInMemoryModel(User)

// 3. Repository for DI
export const UserRepository = createInMemoryRepository(MemUser)

Querying

The in-memory adapter supports the full repository query interface using type-safe field expressions:

queries.tsTypeScript
// Get by reference
const userRef = User.ref`${userId}`
const user = await users.get(userRef)

// Find one by field expression
const john = await users.findOne(User.fields.email.eq('john@example.com'))

// Find many with filters, ordering, pagination
const activeUsers = await users.find({
  where: User.fields.active.eq(true),
  orderBy: [User.fields.name.asc()],
  limit: 10,
  offset: 0,
})

// Count
const total = await users.count(User.fields.active.eq(true))

// Check existence
const exists = await users.exists(User.fields.email.eq('test@example.com'))

Mutations

All mutation methods use Ref<T> for entity references — pass a persistent entity directly or use a typed reference:

mutations.tsTypeScript
// Insert
const user = await users.insert({ email: 'alice@example.com', name: 'Alice' })

// Update by passing the persistent entity directly (it IS a reference)
const updated = await users.update(user, { name: 'Alice Smith' })

// Or update by typed reference at a boundary
const userRef = User.ref`${userId}`
await users.update(userRef, { active: false })

// Delete
await users.delete(user)  // Pass the entity
await users.delete(userRef)  // Or a reference

// Save (smart insert/update)
const newUser = new User({ email: 'bob@example.com', name: 'Bob' })
const saved = await users.save(newUser)  // Inserts (transient)
await users.save(saved)  // Updates (persistent)

Testing

The in-memory adapter is ideal for unit tests. Use it as a drop-in replacement for the PostgreSQL adapter:

user-service.test.tsTypeScript
import { test, expect, beforeEach } from 'node:test'
import { createInMemoryModel, createInMemoryRepository } from '@justscale/core/models'
import { User, UserRepository } from './user.model'
import { UserService } from './user.service'
import { TestContainer } from '@justscale/testing'

test('creates user with unique email', async () => {
  const container = new TestContainer()
  container.bind(UserRepository, createInMemoryRepository(createInMemoryModel(User)))

  const service = container.resolve(UserService)
  const user = await service.create({ email: 'test@example.com', name: 'Test' })

  expect(user.email).toBe('test@example.com')
  expect(user.name).toBe('Test')
})

test('finds user by email', async () => {
  const container = new TestContainer()
  container.bind(UserRepository, createInMemoryRepository(createInMemoryModel(User)))

  const service = container.resolve(UserService)
  await service.create({ email: 'find@example.com', name: 'Find Me' })

  const found = await service.findByEmail('find@example.com')
  expect(found?.name).toBe('Find Me')
})

Dependency Injection

Wire in-memory repositories the same way as PostgreSQL — your services don't know the difference:

app.tsTypeScript
import JustScale, { bindRepository } from '@justscale/core'
import { ModelRepository, createInMemoryModel, createInMemoryRepository } from '@justscale/core/models'
import { User } from './user.model'
import { UserService } from './user.service'

// In-memory for development
const MemUser = createInMemoryModel(User)
const InMemUserRepo = createInMemoryRepository(MemUser)

const app = JustScale()
  .add(UserService)
  .add(bindRepository(ModelRepository.of(User), InMemUserRepo))
  .build()
💡

Tip

The service injects ModelRepository.of(User) — the abstract token. Whether you bind PostgreSQL or in-memory at bootstrap, the service code stays identical.

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 instances
  • Memory constrained — large datasets consume significant memory
  • Simple queries only — no complex joins, aggregations, or full-text search

For production applications, use the PostgreSQL adapter.