Files
pi-config/extensions/rpiv-pi/skills/annotate-guidance/examples/subfolder-database-layer.md

82 lines
2.5 KiB
Markdown

# Database Layer Architecture
## Responsibility
SQLite persistence with better-sqlite3, repository pattern (plain types), QueryQueue concurrency, type transformations.
## Dependencies
- **better-sqlite3**: Native SQLite (requires rebuild for Electron)
- **@redis-ui/core**: Domain types
- **p-queue**: Query serialization
## Consumers
- **@redis-ui/services**: Repositories via RepositoryFactory
- **Main process**: DatabaseManager initialization
## Module Structure
```
src/
├── DatabaseManager.ts, QueryQueue.ts # Singleton, concurrency
├── BaseRepository.ts, RepositoryFactory.ts
├── schema.ts
└── repositories/ # One repo per entity
```
## Repository Boundary (CRITICAL: Plain Types, NOT Result<T>)
```typescript
export class ConnectionRepository extends BaseRepository<ConnectionDB, Connection, ConnectionId> {
protected toApplication(db: ConnectionDB): Connection {
return {
id: ConnectionId.create(db.id),
host: db.host,
port: db.port,
sslEnabled: Boolean(db.ssl_enabled), // DB int → boolean
createdAt: new Date(db.created_at), // timestamp → Date
};
}
async findById(id: ConnectionId): Promise<Connection | null> {
return this.queue.enqueueRead((db) => {
const row = db.prepare('SELECT * FROM connections WHERE id = ?').get(id);
return row ? this.toApplication(row) : null;
});
}
}
// Service: Wraps repository in Result<T>
async createConnection(input: CreateInput): Promise<Result<Connection>> {
try {
const connection = await this.connectionRepo.create(input);
return Result.ok(connection);
} catch (error) {
return Result.fail(new InfrastructureError(error.message));
}
}
```
## QueryQueue Pattern (Write Serialization)
```typescript
export class QueryQueue {
private writeQueue = new PQueue({ concurrency: 1 }) // Single writer
private readQueue = new PQueue({ concurrency: 5 }) // Multiple readers
async enqueueWrite<T>(op: (db: Database) => T): Promise<T> {
return this.writeQueue.add(() => op(this.db))
}
}
```
## Architectural Boundaries
- **NO Result<T> in repos**: Services wrap with Result
- **NO unqueued DB ops**: Always use QueryQueue
- **NO raw SQL in services**: Use repositories
<important if="you are adding a new repository to this layer">
## Adding a New Repository
1. Create `XRepository.ts` extending `BaseRepository<XDB, X, XId>`
2. Implement `toApplication()` and `toDatabase()` type mappers
3. Register in `RepositoryFactory`
4. Add table schema in `schema.ts`
</important>