repository-pattern
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseRepository Pattern
仓库模式
Overview
概述
This skill helps implement the repository pattern used in this workout tracker application. The pattern provides a clean abstraction over Dexie (IndexedDB) with type-safe interfaces, consistent error handling, and standardized CRUD operations.
本技能可帮助实现此健身追踪应用中使用的仓库模式。该模式通过类型安全的接口、一致的错误处理和标准化CRUD操作,为Dexie(IndexedDB)提供清晰的抽象层。
Architecture Overview
架构概述
Layered Approach:
Interfaces → Implementations → Provider → Public API
↓ ↓ ↓ ↓
db/interfaces db/impl/dexie db/provider db/indexFlow:
- Define repository interface in
db/interfaces.ts - Implement with Dexie in
db/implementations/dexie/[entity].ts - Register in factory provider ()
db/implementations/dexie/index.ts - Export public getter ()
db/index.ts - Use in features via
getEntityRepository()
分层架构:
Interfaces → Implementations → Provider → Public API
↓ ↓ ↓ ↓
db/interfaces db/impl/dexie db/provider db/index流程:
- 在中定义仓库接口
db/interfaces.ts - 在中使用Dexie实现接口
db/implementations/dexie/[entity].ts - 在工厂提供者()中注册实现
db/implementations/dexie/index.ts - 导出公共获取器()
db/index.ts - 通过在功能模块中使用
getEntityRepository()
Core Workflow
核心工作流
Follow these 6 steps when creating a new repository:
创建新仓库时请遵循以下6个步骤:
Step 1: Define Interface
步骤1:定义接口
Location:
src/db/interfaces.tsDefine the repository interface with standard CRUD methods:
typescript
export type EntityRepository = {
getAll(): Promise<ReadonlyArray<DbEntity>>
getById(id: string): Promise<DbEntity | undefined>
create(entity: Omit<DbEntity, 'id' | 'createdAt'>): Promise<DbEntity>
update(id: string, updates: Partial<Omit<DbEntity, 'id' | 'createdAt'>>): Promise<void>
delete(id: string): Promise<void>
// ... entity-specific queries
}Add to :
RepositoryProvidertypescript
export type RepositoryProvider = {
// ... existing
entity: EntityRepository
}Guidelines:
- Use and
ReadonlyArray<T>for argumentsReadonly<T> - returns
getById(not throw) when not foundundefined - returns the created entity with generated ID
create - and
updatereturndeletevoid - Use to exclude auto-generated fields (
Omit<>,id)createdAt
位置:
src/db/interfaces.ts使用标准CRUD方法定义仓库接口:
typescript
export type EntityRepository = {
getAll(): Promise<ReadonlyArray<DbEntity>>
getById(id: string): Promise<DbEntity | undefined>
create(entity: Omit<DbEntity, 'id' | 'createdAt'>): Promise<DbEntity>
update(id: string, updates: Partial<Omit<DbEntity, 'id' | 'createdAt'>>): Promise<void>
delete(id: string): Promise<void>
// ... entity-specific queries
}添加至:
RepositoryProvidertypescript
export type RepositoryProvider = {
// ... existing
entity: EntityRepository
}指导原则:
- 对参数使用和
ReadonlyArray<T>Readonly<T> - 当未找到实体时,返回
getById(而非抛出错误)undefined - 返回带有生成ID的已创建实体
create - 和
update返回deletevoid - 使用排除自动生成的字段(
Omit<>、id)createdAt
Step 2: Add Schema Type
步骤2:添加模式类型
Location:
src/db/schema.tsDefine database type with prefix:
Dbtypescript
/**
* 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 prefix for database types
Db - Use for "no value" (not
null)undefined - Store user input numbers as (e.g.,
string,kg: string)reps: string - Use discriminated unions with property for variants
kind - Include type guards if needed:
export function isDbEntity(x: unknown): x is DbEntity
位置:
src/db/schema.ts使用前缀定义数据库类型:
Dbtypescript
/**
* 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
}关键约定:
- 数据库类型必须使用前缀
Db - 使用表示“无值”(而非
null)undefined - 将用户输入的数字存储为(例如:
string、kg: string)reps: string - 使用带有属性的可区分联合处理变体
kind - 必要时包含类型守卫:
export function isDbEntity(x: unknown): x is DbEntity
Step 3: Update Database Class
步骤3:更新数据库类
Location:
src/db/implementations/dexie/database.tsAdd table and indexes:
typescript
export class WorkoutTrackerDb extends Dexie {
// ... existing tables
entities!: Table<DbEntity, string>
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'
位置:
src/db/implementations/dexie/database.ts添加表和索引:
typescript
export class WorkoutTrackerDb extends Dexie {
// ... existing tables
entities!: Table<DbEntity, string>
constructor() {
super('WorkoutTracker')
// Increment version number
this.version(3).stores({
// ... existing tables
entities: 'id, name, createdAt', // Index: primary + frequently queried fields
})
}
}索引指导原则:
- 始终为主键创建索引(自动生成)
- 为、
where()、orderBy()中使用的字段创建索引equals() - 为外键创建索引以支持关联查询
- 为关联表创建复合索引:
'[field1+field2], field1, field2'
Step 4: Implement Repository
步骤4:实现仓库
Location:
src/db/implementations/dexie/[entity].tsCreate 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<ReadonlyArray<DbEntity>> {
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<DbEntity | undefined> {
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<DbEntity, 'id' | 'createdAt'>,
): Promise<DbEntity> {
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<Omit<DbEntity, 'id' | 'createdAt'>>,
): Promise<void> {
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<void> {
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 wrapper for all operations (preferred pattern)
tryCatch() - Two-phase error checking: operation failure + not found
- Auto-inject timestamps: ,
createdAtupdatedAt - Use for new IDs
generateId() - Soft delete: no error if entity doesn't exist
位置:
src/db/implementations/dexie/[entity].ts创建返回仓库实现的工厂函数:
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<ReadonlyArray<DbEntity>> {
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<DbEntity | undefined> {
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<DbEntity, 'id' | 'createdAt'>,
): Promise<DbEntity> {
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<Omit<DbEntity, 'id' | 'createdAt'>>,
): Promise<void> {
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<void> {
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
},
}
}关键模式:
- 对所有操作使用包装器(推荐模式)
tryCatch() - 两阶段错误检查:操作失败 + 未找到实体
- 自动注入时间戳:、
createdAtupdatedAt - 使用生成新ID
generateId() - 软删除:如果实体不存在则不抛出错误
Step 5: Register in Factory Provider
步骤5:在工厂提供者中注册
Location:
src/db/implementations/dexie/index.tsImport 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
}
}位置:
src/db/implementations/dexie/index.ts导入并添加至提供者:
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
步骤6:导出公共获取器
Location:
src/db/index.tsAdd getter function:
typescript
export function getEntityRepository(): EntityRepository {
return getRepositoryProvider().entity
}位置:
src/db/index.ts添加获取器函数:
typescript
export function getEntityRepository(): EntityRepository {
return getRepositoryProvider().entity
}Usage in Features
在功能模块中使用
typescript
import { getEntityRepository } from '@/db'
import type { DbEntity } from '@/db/schema'
export function useEntities() {
const entities = ref<ReadonlyArray<DbEntity>>([])
const entityRepo = getEntityRepository()
async function loadEntities() {
entities.value = await entityRepo.getAll()
}
async function createEntity(name: string, value: string | null) {
const newEntity = await entityRepo.create({ name, value, updatedAt: null })
entities.value = [...entities.value, newEntity]
}
async function updateEntity(id: string, updates: Partial<DbEntity>) {
await entityRepo.update(id, updates)
await loadEntities()
}
async function deleteEntity(id: string) {
await entityRepo.delete(id)
entities.value = entities.value.filter(e => e.id !== id)
}
onMounted(() => loadEntities())
return {
entities: readonly(entities),
createEntity,
updateEntity,
deleteEntity,
}
}typescript
import { getEntityRepository } from '@/db'
import type { DbEntity } from '@/db/schema'
export function useEntities() {
const entities = ref<ReadonlyArray<DbEntity>>([])
const entityRepo = getEntityRepository()
async function loadEntities() {
entities.value = await entityRepo.getAll()
}
async function createEntity(name: string, value: string | null) {
const newEntity = await entityRepo.create({ name, value, updatedAt: null })
entities.value = [...entities.value, newEntity]
}
async function updateEntity(id: string, updates: Partial<DbEntity>) {
await entityRepo.update(id, updates)
await loadEntities()
}
async function deleteEntity(id: string) {
await entityRepo.delete(id)
entities.value = entities.value.filter(e => e.id !== id)
}
onMounted(() => loadEntities())
return {
entities: readonly(entities),
createEntity,
updateEntity,
deleteEntity,
}
}Key Principles
核心原则
1. Error Handling
1. 错误处理
Preferred: tryCatch wrapper
typescript
const [error, result] = await tryCatch(operation)
if (error) {
throw createDatabaseError('ERROR_CODE', 'description', error)
}Error codes:
- - Create, update, delete operations
SAVE_FAILED - - Read operations
LOAD_FAILED - - Entity doesn't exist
NOT_FOUND
推荐:tryCatch包装器
typescript
const [error, result] = await tryCatch(operation)
if (error) {
throw createDatabaseError('ERROR_CODE', 'description', error)
}错误码:
- - 创建、更新、删除操作
SAVE_FAILED - - 读取操作
LOAD_FAILED - - 实体不存在
NOT_FOUND
2. Timestamp Management
2. 时间戳管理
Auto-inject timestamps in repository methods:
- in
createdAt: Date.now()create() - in
updatedAt: Date.now()update() - when accessing entity
lastUsedAt: Date.now()
在仓库方法中自动注入时间戳:
- 在中设置
create()createdAt: Date.now() - 在中设置
update()updatedAt: Date.now() - 访问实体时设置
lastUsedAt: Date.now()
3. ID Generation
3. ID生成
Always use from :
generateId()database.tstypescript
import { generateId } from './database'
const newEntity = {
...entity,
id: generateId(), // crypto.randomUUID()
}始终使用中的:
database.tsgenerateId()typescript
import { generateId } from './database'
const newEntity = {
...entity,
id: generateId(), // crypto.randomUUID()
}4. Soft Delete
4. 软删除
Delete operations don't throw if entity doesn't exist:
typescript
async delete(id: string): Promise<void> {
await tryCatch(db.entities.delete(id))
// No NOT_FOUND check - silent success
}删除操作在实体不存在时不抛出错误:
typescript
async delete(id: string): Promise<void> {
await tryCatch(db.entities.delete(id))
// No NOT_FOUND check - silent success
}5. Type Safety
5. 类型安全
- Use and
Readonly<T>for function parametersReadonlyArray<T> - Use to exclude auto-generated fields
Omit<> - Use discriminated unions with exhaustive checking
- Define type guards for runtime type checking
- 对函数参数使用和
Readonly<T>ReadonlyArray<T> - 使用排除自动生成的字段
Omit<> - 使用可区分联合并进行穷尽检查
- 定义类型守卫以进行运行时类型检查
File Reference
文件参考
Critical files when creating repository:
- - Interface definition + RepositoryProvider
src/db/interfaces.ts - - Db-prefixed type definitions
src/db/schema.ts - - Table + indexes
src/db/implementations/dexie/database.ts - - Implementation
src/db/implementations/dexie/[entity].ts - - Factory registration
src/db/implementations/dexie/index.ts - - Public getter export
src/db/index.ts
Utility imports:
- - Error handling utilities
@/lib/tryCatch - - generateId()
@/db/implementations/dexie/database
创建仓库时的关键文件:
- - 接口定义 + RepositoryProvider
src/db/interfaces.ts - - 带Db前缀的类型定义
src/db/schema.ts - - 表 + 索引
src/db/implementations/dexie/database.ts - - 实现代码
src/db/implementations/dexie/[entity].ts - - 工厂注册
src/db/implementations/dexie/index.ts - - 公共获取器导出
src/db/index.ts
工具导入:
- - 错误处理工具
@/lib/tryCatch - - generateId()
@/db/implementations/dexie/database
Detailed References
详细参考
For complete examples and advanced patterns, see:
-
references/examples.md - Complete end-to-end examples:
- Example 1: Simple CRUD repository (Notes)
- Example 2: Complex transformations (Tags with many-to-many)
- Example 3: Extending Settings with function overloads
-
references/patterns.md - Detailed pattern catalog:
- Error handling patterns (direct throw vs tryCatch)
- CRUD patterns (getAll, create, update, delete, timestamps)
- Type transformation patterns (helper utilities, deep cloning)
- Advanced patterns (function overloads, singleton, transactions, bulk ops)
- Schema design patterns (discriminated unions, indexing, embedded vs referenced)
如需完整示例和高级模式,请查看:
-
references/examples.md - 完整端到端示例:
- 示例1:简单CRUD仓库(笔记)
- 示例2:复杂转换(多对多标签)
- 示例3:通过函数重载扩展设置
-
references/patterns.md - 详细模式目录:
- 错误处理模式(直接抛出 vs tryCatch)
- CRUD模式(getAll、create、update、delete、时间戳)
- 类型转换模式(辅助工具、深度克隆)
- 高级模式(函数重载、单例、事务、批量操作)
- 模式设计模式(可区分联合、索引、嵌入式 vs 引用式)
Common Scenarios
常见场景
Scenario 1: Simple CRUD Repository
场景1:简单CRUD仓库
Need basic storage for an entity? See examples.md → Example 1 (Notes).
Quick checklist:
- Define interface with getAll/getById/create/update/delete
- Add DbEntity type with Db prefix
- Add table with indexes
- Implement using tryCatch pattern
- Register and export
需要为实体提供基础存储?请查看examples.md → 示例1(笔记)。
快速检查清单:
- 定义包含getAll/getById/create/update/delete的接口
- 添加带Db前缀的DbEntity类型
- 添加带索引的表
- 使用tryCatch模式实现
- 注册并导出
Scenario 2: Complex Relationships
场景2:复杂关联
Need many-to-many relationships or complex queries? See examples.md → Example 2 (Tags).
Pattern: Junction table + transaction handling + usage tracking.
需要多对多关联或复杂查询?请查看examples.md → 示例2(标签)。
模式: 关联表 + 事务处理 + 使用情况追踪。
Scenario 3: Extending Settings
场景3:扩展设置
Adding new setting? See examples.md → Example 3.
Pattern: Add discriminated union member + function overload + default value.
添加新设置?请查看examples.md → 示例3。
模式: 添加可区分联合成员 + 函数重载 + 默认值。
Scenario 4: Conversions Between Types
场景4:类型间转换
Need to convert between templates and workouts? See patterns.md → Type Transformation Patterns.
Pattern: Helper utilities with exhaustive switch statements.
需要在模板和健身记录之间转换?请查看patterns.md → 类型转换模式。
模式: 使用穷尽switch语句的辅助工具。
Scenario 5: Bulk Operations
场景5:批量操作
Import/export or batch delete? See patterns.md → Advanced Patterns → Bulk Operations.
Pattern: Transactions + + .
Promise.all()bulkAdd()导入/导出或批量删除?请查看patterns.md → 高级模式 → 批量操作。
模式: 事务 + + 。
Promise.all()bulkAdd()Testing Support
测试支持
Mock repositories for unit tests:
typescript
import { createMockRepositories } from '@/__tests__/helpers/mockRepositories'
const mockRepos = createMockRepositories()
mockRepos.entity.getAll.mockResolvedValue([...])Integration tests with fake-indexeddb are automatically set up via test helpers.
用于单元测试的模拟仓库:
typescript
import { createMockRepositories } from '@/__tests__/helpers/mockRepositories'
const mockRepos = createMockRepositories()
mockRepos.entity.getAll.mockResolvedValue([...])通过测试助手自动设置使用fake-indexeddb的集成测试。
Migration Strategy
迁移策略
When updating schema version:
- Increment version number in
database.ts - Add new table/indexes in
.stores({}) - Dexie handles migrations automatically
- For data migrations, use callback
.upgrade()
typescript
this.version(3)
.stores({
entities: 'id, name, createdAt',
})
.upgrade(tx => {
// Optional data migration logic
return tx.table('entities').toCollection().modify(entity => {
entity.newField = 'default'
})
})更新模式版本时:
- 在中递增版本号
database.ts - 在中添加新表/索引
.stores({}) - Dexie自动处理迁移
- 如需数据迁移,使用回调
.upgrade()
typescript
this.version(3)
.stores({
entities: 'id, name, createdAt',
})
.upgrade(tx => {
// Optional data migration logic
return tx.table('entities').toCollection().modify(entity => {
entity.newField = 'default'
})
})Project-Specific Repositories
项目特定仓库
Db*
Types vs Domain Types
Db*Db*
类型 vs 领域类型
Db*| Aspect | Database ( | Domain |
|---|---|---|
| File | | |
| Prefix | | |
| No value | | |
| Optimized for | Storage | App logic |
| 方面 | 数据库( | 领域 |
|---|---|---|
| 文件 | | |
| 前缀 | | |
| 无值表示 | | |
| 优化方向 | 存储 | 应用逻辑 |
Available Repositories
可用仓库
SettingsRepository - Key-value store with defaults:
ts
const repo = getSettingsRepository()
await repo.get('theme') // 'light' | 'dark' | 'system'
await repo.get('defaultRestTimer') // number
await repo.set({ key: 'theme', value: 'dark' })
await repo.getAll() // All settings merged with defaults
await repo.reset('theme')CustomExercisesRepository - Exercise CRUD:
ts
const repo = getCustomExercisesRepository()
await repo.getAll()
await repo.getById(id)
await repo.add({ id: generateId(), name: 'Squat', ... })
await repo.update(id, { name: 'Back Squat' })
await repo.delete(id)WorkoutsRepository - Completed workouts:
ts
const repo = getWorkoutsRepository()
await repo.getAll()
await repo.getById(id)
await repo.create(convertWorkoutToDb(workout))
await repo.delete(id)ActiveWorkoutRepository - Singleton active workout:
ts
const repo = getActiveWorkoutRepository()
await repo.load()
await repo.save(dbActiveWorkout)
await repo.delete()
await repo.exists()BenchmarksRepository - Benchmark workouts:
ts
const repo = getBenchmarksRepository()
await repo.getAll()
await repo.getById(id)
await repo.create({ id: generateId(), name: 'Fran', ... })
await repo.update(id, { name: 'Fran (Scaled)' })
await repo.delete(id)TemplatesRepository - Workout templates:
ts
const repo = getTemplatesRepository()
await repo.getAll()
await repo.getById(id)
await repo.create(template)
await repo.update(id, changes)
await repo.delete(id)SettingsRepository - 带默认值的键值存储:
ts
const repo = getSettingsRepository()
await repo.get('theme') // 'light' | 'dark' | 'system'
await repo.get('defaultRestTimer') // number
await repo.set({ key: 'theme', value: 'dark' })
await repo.getAll() // 合并默认值后的所有设置
await repo.reset('theme')CustomExercisesRepository - 自定义练习CRUD:
ts
const repo = getCustomExercisesRepository()
await repo.getAll()
await repo.getById(id)
await repo.add({ id: generateId(), name: 'Squat', ... })
await repo.update(id, { name: 'Back Squat' })
await repo.delete(id)WorkoutsRepository - 已完成的健身记录:
ts
const repo = getWorkoutsRepository()
await repo.getAll()
await repo.getById(id)
await repo.create(convertWorkoutToDb(workout))
await repo.delete(id)ActiveWorkoutRepository - 单例当前健身记录:
ts
const repo = getActiveWorkoutRepository()
await repo.load()
await repo.save(dbActiveWorkout)
await repo.delete()
await repo.exists()BenchmarksRepository - 基准健身记录:
ts
const repo = getBenchmarksRepository()
await repo.getAll()
await repo.getById(id)
await repo.create({ id: generateId(), name: 'Fran', ... })
await repo.update(id, { name: 'Fran (Scaled)' })
await repo.delete(id)TemplatesRepository - 健身记录模板:
ts
const repo = getTemplatesRepository()
await repo.getAll()
await repo.getById(id)
await repo.create(template)
await repo.update(id, changes)
await repo.delete(id)Using Converters
使用转换器
Always convert when crossing domain/database boundary:
ts
import { convertWorkoutToDb, convertDbToWorkout } from '@/db/converters'
// Domain → Database
const dbWorkout = convertWorkoutToDb(workout)
await getWorkoutsRepository().create(dbWorkout)
// Database → Domain
const dbWorkout = await getWorkoutsRepository().getById(id)
const workout = convertDbToWorkout(dbWorkout)在领域层与数据库层之间转换时务必使用转换器:
ts
import { convertWorkoutToDb, convertDbToWorkout } from '@/db/converters'
// 领域 → 数据库
const dbWorkout = convertWorkoutToDb(workout)
await getWorkoutsRepository().create(dbWorkout)
// 数据库 → 领域
const dbWorkout = await getWorkoutsRepository().getById(id)
const workout = convertDbToWorkout(dbWorkout)Partial Updates with buildPartialUpdate
使用buildPartialUpdate进行部分更新
Dexie's overwrites all keys in the object. Use to only modify provided fields:
update()buildPartialUpdatets
import { buildPartialUpdate } from '@/db/partialUpdate'
const NULLABLE_FIELDS = ['equipment', 'muscle', 'image']
// Only includes keys present in updates
// Converts undefined → null for nullable fields
const dbUpdates = buildPartialUpdate(updates, NULLABLE_FIELDS)
await repo.update(id, dbUpdates)Why: Without filtering, would set equipment to null even if you only meant to update the name.
{ name: 'Squat', equipment: undefined }Dexie的会覆盖对象中的所有键。使用仅修改指定字段:
update()buildPartialUpdatets
import { buildPartialUpdate } from '@/db/partialUpdate'
const NULLABLE_FIELDS = ['equipment', 'muscle', 'image']
// 仅包含更新中存在的键
// 可为空字段将undefined转换为null
const dbUpdates = buildPartialUpdate(updates, NULLABLE_FIELDS)
await repo.update(id, dbUpdates)原因: 如果不进行过滤,会将equipment设置为null,即使你只想更新名称。
{ name: 'Squat', equipment: undefined }Project-Specific Gotchas
项目特定注意事项
1. Use null
in Database, undefined
in Domain
nullundefined1. 数据库中使用null
,领域中使用undefined
nullundefinedIndexedDB doesn't support :
undefinedts
// Database types
type DbExercise = {
equipment: Equipment | null // Use null
}
// Domain types
type Exercise = {
equipment?: Equipment // Use undefined
}IndexedDB不支持:
undefinedts
// 数据库类型
type DbExercise = {
equipment: Equipment | null // 使用null
}
// 领域类型
type Exercise = {
equipment?: Equipment // 使用undefined
}2. Always Reset Database in Tests
2. 测试中务必重置数据库
ts
import { resetDatabase } from '@/__tests__/setup'
beforeEach(async () => {
await resetDatabase()
})ts
import { resetDatabase } from '@/__tests__/setup'
beforeEach(async () => {
await resetDatabase()
})3. Convert Types at Boundaries
3. 在边界处转换类型
ts
// BAD - Type mismatch
await getWorkoutsRepository().create(workout)
// GOOD - Convert first
const dbWorkout = convertWorkoutToDb(workout)
await getWorkoutsRepository().create(dbWorkout)ts
// 错误 - 类型不匹配
await getWorkoutsRepository().create(workout)
// 正确 - 先转换
const dbWorkout = convertWorkoutToDb(workout)
await getWorkoutsRepository().create(dbWorkout)