Follow these 6 steps when creating a new repository:
Step 1: Define Interface
Location: src/db/interfaces.ts
Define the repository interface with standard CRUD methods:
```typescript
export type EntityRepository = {
getAll(): Promise>
getById(id: string): Promise
create(entity: Omit): Promise
update(id: string, updates: Partial>): Promise
delete(id: string): Promise
// ... entity-specific queries
}
```
Add to RepositoryProvider:
```typescript
export type RepositoryProvider = {
// ... existing
entity: EntityRepository
}
```
Guidelines:
- Use
ReadonlyArray and Readonly for arguments getById returns undefined (not throw) when not foundcreate returns the created entity with generated IDupdate and delete return void- Use
Omit<> to exclude auto-generated fields (id, createdAt)
Step 2: Add Schema Type
Location: src/db/schema.ts
Define database type with Db prefix:
```typescript
/**
* Entity stored in database.
* Uses null instead of undefined for explicit "no value" semantics.
*/
export type DbEntity = {
id: string
name: string
value: string | null // Use null, not undefined
createdAt: number
updatedAt: number | null // null until first update
}
```
Key Conventions:
- Always use
Db prefix for database types - Use
null for "no value" (not undefined) - Store user input numbers as
string (e.g., kg: string, reps: string) - Use discriminated unions with
kind property for variants - Include type guards if needed:
export function isDbEntity(x: unknown): x is DbEntity
Step 3: Update Database Class
Location: src/db/implementations/dexie/database.ts
Add table and indexes:
```typescript
export class WorkoutTrackerDb extends Dexie {
// ... existing tables
entities!: Table
constructor() {
super('WorkoutTracker')
// Increment version number
this.version(3).stores({
// ... existing tables
entities: 'id, name, createdAt', // Index: primary + frequently queried fields
})
}
}
```
Indexing Guidelines:
- Always index primary key (automatic)
- Index fields used in
where(), orderBy(), equals() - Index foreign keys for joins
- Compound indexes for junction tables:
'[field1+field2], field1, field2'
Step 4: Implement Repository
Location: src/db/implementations/dexie/[entity].ts
Create factory function returning repository implementation:
```typescript
import type { EntityRepository } from '@/db/interfaces'
import type { DbEntity } from '@/db/schema'
import { createDatabaseError, tryCatch } from '@/lib/tryCatch'
import type { WorkoutTrackerDb } from './database'
import { generateId } from './database'
/**
* Dexie implementation of EntityRepository.
*/
export function createDexieEntityRepository(db: WorkoutTrackerDb): EntityRepository {
return {
async getAll(): Promise> {
const [error, entities] = await tryCatch(
db.entities.orderBy('createdAt').reverse().toArray(),
)
if (error) {
throw createDatabaseError('LOAD_FAILED', 'retrieve entities', error)
}
return entities
},
async getById(id: string): Promise {
const [error, entity] = await tryCatch(db.entities.get(id))
if (error) {
throw createDatabaseError('LOAD_FAILED', retrieve entity with id ${id}, error)
}
return entity
},
async create(
entity: Omit,
): Promise {
const newEntity: DbEntity = {
...entity,
id: generateId(),
createdAt: Date.now(),
}
const [error] = await tryCatch(db.entities.add(newEntity))
if (error) {
throw createDatabaseError('SAVE_FAILED', 'create entity', error)
}
return newEntity
},
async update(
id: string,
updates: Partial>,
): Promise {
const [error, updatedCount] = await tryCatch(
db.entities.update(id, {
...updates,
updatedAt: Date.now(), // Auto-inject timestamp
}),
)
if (error) {
throw createDatabaseError('SAVE_FAILED', update entity with id ${id}, error)
}
if (updatedCount === 0) {
throw createDatabaseError('NOT_FOUND', entity with id ${id} not found)
}
},
async delete(id: string): Promise {
const [error] = await tryCatch(db.entities.delete(id))
if (error) {
throw createDatabaseError('SAVE_FAILED', delete entity with id ${id}, error)
}
// Soft delete: no NOT_FOUND check
},
}
}
```
Key Patterns:
- Use
tryCatch() wrapper for all operations (preferred pattern) - Two-phase error checking: operation failure + not found
- Auto-inject timestamps:
createdAt, updatedAt - Use
generateId() for new IDs - Soft delete: no error if entity doesn't exist
Step 5: Register in Factory Provider
Location: src/db/implementations/dexie/index.ts
Import and add to provider:
```typescript
import { createDexieEntityRepository } from './entity'
export function createDexieRepositoryProvider(): RepositoryProvider {
return {
activeWorkout: createDexieActiveWorkoutRepository(db),
workouts: createDexieWorkoutsRepository(db),
// ... existing repositories
entity: createDexieEntityRepository(db), // ADD THIS
}
}
```
Step 6: Export Public Getter
Location: src/db/index.ts
Add getter function:
```typescript
export function getEntityRepository(): EntityRepository {
return getRepositoryProvider().entity
}
```