Skip to content

Repo

Repos (repositories) handle data access and external adapters. They’re the only layer that should communicate with databases, APIs, or other external services.

class PostRepo extends Vla.Repo {
db = this.inject(Database)
// Memoized queries
findById = this.memo((id: string) => {
return this.db.posts.find({ id })
})
findByAuthor = this.memo((authorId: string) => {
return this.db.posts.findMany({ authorId })
})
// Write operations (not memoized)
async create(data: PostData) {
const post = await this.db.posts.create({ data })
// Prime the cache
this.findById.prime(post.id).value(post)
return post
}
async update(id: string, data: Partial<PostData>) {
const post = await this.db.posts.update({ where: { id }, data })
// Bust the cache
this.findById.bust(id)
return post
}
}
  • Database queries
  • External API calls
  • File system access

invoke - Shared within a request.

static readonly scope = 'invoke'

Repos inherit both inject() and memo() methods.

memo<Args extends unknown[], R>(
fn: (...args: Args) => R
): Memoized<Args, R>

Creates a memoized method that caches results per request.

class UserRepo extends Vla.Repo {
db = this.inject(Database)
findById = this.memo((id: string) => {
return this.db.users.find({ id })
})
}
// Multiple calls with same ID = only one database query
await repo.findById('1') // Executes query
await repo.findById('1') // Returns cached result

See Memoization guide for details.

  • Scope: invoke (shared within a request)
  • Purpose: Data access, external API calls
  • Can inject: Resources, Contexts
  • Best practices: Memoize reads, not writes; no business logic