From 15c1d98305a038351394160fb5057ccebb10255b Mon Sep 17 00:00:00 2001 From: YHH <359807859@qq.com> Date: Wed, 31 Dec 2025 22:15:15 +0800 Subject: [PATCH] docs: add database and database-drivers to sidebar navigation (#414) - Add database module navigation (repository, user, query) - Add database-drivers module navigation (mongo, redis) - Create missing English documentation files for database module - Create missing English documentation files for database-drivers module --- docs/astro.config.mjs | 19 ++ .../docs/en/modules/database-drivers/mongo.md | 265 +++++++++++++++++ .../docs/en/modules/database-drivers/redis.md | 228 ++++++++++++++ .../content/docs/en/modules/database/query.md | 185 ++++++++++++ .../docs/en/modules/database/repository.md | 244 +++++++++++++++ .../content/docs/en/modules/database/user.md | 277 ++++++++++++++++++ 6 files changed, 1218 insertions(+) create mode 100644 docs/src/content/docs/en/modules/database-drivers/mongo.md create mode 100644 docs/src/content/docs/en/modules/database-drivers/redis.md create mode 100644 docs/src/content/docs/en/modules/database/query.md create mode 100644 docs/src/content/docs/en/modules/database/repository.md create mode 100644 docs/src/content/docs/en/modules/database/user.md diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs index 0c3ec935..b9d35c08 100644 --- a/docs/astro.config.mjs +++ b/docs/astro.config.mjs @@ -287,6 +287,25 @@ export default defineConfig({ { label: '分布式事务', slug: 'modules/transaction/distributed', translations: { en: 'Distributed' } }, ], }, + { + label: '数据库', + translations: { en: 'Database' }, + items: [ + { label: '概述', slug: 'modules/database', translations: { en: 'Overview' } }, + { label: '仓储模式', slug: 'modules/database/repository', translations: { en: 'Repository' } }, + { label: '用户仓储', slug: 'modules/database/user', translations: { en: 'User Repository' } }, + { label: '查询构建器', slug: 'modules/database/query', translations: { en: 'Query Builder' } }, + ], + }, + { + label: '数据库驱动', + translations: { en: 'Database Drivers' }, + items: [ + { label: '概述', slug: 'modules/database-drivers', translations: { en: 'Overview' } }, + { label: 'MongoDB', slug: 'modules/database-drivers/mongo', translations: { en: 'MongoDB' } }, + { label: 'Redis', slug: 'modules/database-drivers/redis', translations: { en: 'Redis' } }, + ], + }, { label: '世界流式加载', translations: { en: 'World Streaming' }, diff --git a/docs/src/content/docs/en/modules/database-drivers/mongo.md b/docs/src/content/docs/en/modules/database-drivers/mongo.md new file mode 100644 index 00000000..8377e40a --- /dev/null +++ b/docs/src/content/docs/en/modules/database-drivers/mongo.md @@ -0,0 +1,265 @@ +--- +title: "MongoDB Connection" +description: "MongoDB connection management, connection pooling, auto-reconnect" +--- + +## Configuration Options + +```typescript +interface MongoConnectionConfig { + /** MongoDB connection URI */ + uri: string + + /** Database name */ + database: string + + /** Connection pool configuration */ + pool?: { + minSize?: number // Minimum connections + maxSize?: number // Maximum connections + acquireTimeout?: number // Connection acquire timeout (ms) + maxLifetime?: number // Maximum connection lifetime (ms) + } + + /** Auto-reconnect (default true) */ + autoReconnect?: boolean + + /** Reconnect interval (ms, default 5000) */ + reconnectInterval?: number + + /** Maximum reconnect attempts (default 10) */ + maxReconnectAttempts?: number +} +``` + +## Complete Example + +```typescript +import { createMongoConnection, MongoConnectionToken } from '@esengine/database-drivers' + +const mongo = createMongoConnection({ + uri: 'mongodb://localhost:27017', + database: 'game', + pool: { + minSize: 5, + maxSize: 20, + acquireTimeout: 5000, + maxLifetime: 300000 + }, + autoReconnect: true, + reconnectInterval: 5000, + maxReconnectAttempts: 10 +}) + +// Event listeners +mongo.on('connected', () => { + console.log('MongoDB connected') +}) + +mongo.on('disconnected', () => { + console.log('MongoDB disconnected') +}) + +mongo.on('reconnecting', () => { + console.log('MongoDB reconnecting...') +}) + +mongo.on('reconnected', () => { + console.log('MongoDB reconnected') +}) + +mongo.on('error', (event) => { + console.error('MongoDB error:', event.error) +}) + +// Connect +await mongo.connect() + +// Check status +console.log('Connected:', mongo.isConnected()) +console.log('Ping:', await mongo.ping()) +``` + +## IMongoConnection Interface + +```typescript +interface IMongoConnection { + /** Connection ID */ + readonly id: string + + /** Connection state */ + readonly state: ConnectionState + + /** Establish connection */ + connect(): Promise + + /** Disconnect */ + disconnect(): Promise + + /** Check if connected */ + isConnected(): boolean + + /** Test connection */ + ping(): Promise + + /** Get typed collection */ + collection(name: string): IMongoCollection + + /** Get database interface */ + getDatabase(): IMongoDatabase + + /** Get native client (advanced usage) */ + getNativeClient(): MongoClientType + + /** Get native database (advanced usage) */ + getNativeDatabase(): Db +} +``` + +## IMongoCollection Interface + +Type-safe collection interface, decoupled from native MongoDB types: + +```typescript +interface IMongoCollection { + readonly name: string + + // Query + findOne(filter: object, options?: FindOptions): Promise + find(filter: object, options?: FindOptions): Promise + countDocuments(filter?: object): Promise + + // Insert + insertOne(doc: T): Promise + insertMany(docs: T[]): Promise + + // Update + updateOne(filter: object, update: object): Promise + updateMany(filter: object, update: object): Promise + findOneAndUpdate( + filter: object, + update: object, + options?: FindOneAndUpdateOptions + ): Promise + + // Delete + deleteOne(filter: object): Promise + deleteMany(filter: object): Promise + + // Index + createIndex( + spec: Record, + options?: IndexOptions + ): Promise +} +``` + +## Usage Examples + +### Basic CRUD + +```typescript +interface User { + id: string + name: string + email: string + score: number +} + +const users = mongo.collection('users') + +// Insert +await users.insertOne({ + id: '1', + name: 'John', + email: 'john@example.com', + score: 100 +}) + +// Query +const user = await users.findOne({ name: 'John' }) + +const topUsers = await users.find( + { score: { $gte: 100 } }, + { sort: { score: -1 }, limit: 10 } +) + +// Update +await users.updateOne( + { id: '1' }, + { $inc: { score: 10 } } +) + +// Delete +await users.deleteOne({ id: '1' }) +``` + +### Batch Operations + +```typescript +// Batch insert +await users.insertMany([ + { id: '1', name: 'Alice', email: 'alice@example.com', score: 100 }, + { id: '2', name: 'Bob', email: 'bob@example.com', score: 200 }, + { id: '3', name: 'Carol', email: 'carol@example.com', score: 150 } +]) + +// Batch update +await users.updateMany( + { score: { $lt: 100 } }, + { $set: { status: 'inactive' } } +) + +// Batch delete +await users.deleteMany({ status: 'inactive' }) +``` + +### Index Management + +```typescript +// Create indexes +await users.createIndex({ email: 1 }, { unique: true }) +await users.createIndex({ score: -1 }) +await users.createIndex({ name: 1, score: -1 }) +``` + +## Integration with Other Modules + +### With @esengine/database + +```typescript +import { createMongoConnection } from '@esengine/database-drivers' +import { UserRepository, createRepository } from '@esengine/database' + +const mongo = createMongoConnection({ + uri: 'mongodb://localhost:27017', + database: 'game' +}) +await mongo.connect() + +// Use UserRepository +const userRepo = new UserRepository(mongo) +await userRepo.register({ username: 'john', password: '123456' }) + +// Use generic repository +const playerRepo = createRepository(mongo, 'players') +``` + +### With @esengine/transaction + +```typescript +import { createMongoConnection } from '@esengine/database-drivers' +import { createMongoStorage, TransactionManager } from '@esengine/transaction' + +const mongo = createMongoConnection({ + uri: 'mongodb://localhost:27017', + database: 'game' +}) +await mongo.connect() + +// Create transaction storage (shared connection) +const storage = createMongoStorage(mongo) +await storage.ensureIndexes() + +const txManager = new TransactionManager({ storage }) +``` diff --git a/docs/src/content/docs/en/modules/database-drivers/redis.md b/docs/src/content/docs/en/modules/database-drivers/redis.md new file mode 100644 index 00000000..67e3daed --- /dev/null +++ b/docs/src/content/docs/en/modules/database-drivers/redis.md @@ -0,0 +1,228 @@ +--- +title: "Redis Connection" +description: "Redis connection management, auto-reconnect, key prefix" +--- + +## Configuration Options + +```typescript +interface RedisConnectionConfig { + /** Redis host */ + host?: string + + /** Redis port */ + port?: number + + /** Authentication password */ + password?: string + + /** Database number */ + db?: number + + /** Key prefix */ + keyPrefix?: string + + /** Auto-reconnect (default true) */ + autoReconnect?: boolean + + /** Reconnect interval (ms, default 5000) */ + reconnectInterval?: number + + /** Maximum reconnect attempts (default 10) */ + maxReconnectAttempts?: number +} +``` + +## Complete Example + +```typescript +import { createRedisConnection, RedisConnectionToken } from '@esengine/database-drivers' + +const redis = createRedisConnection({ + host: 'localhost', + port: 6379, + password: 'your-password', + db: 0, + keyPrefix: 'game:', + autoReconnect: true, + reconnectInterval: 5000, + maxReconnectAttempts: 10 +}) + +// Event listeners +redis.on('connected', () => { + console.log('Redis connected') +}) + +redis.on('disconnected', () => { + console.log('Redis disconnected') +}) + +redis.on('error', (event) => { + console.error('Redis error:', event.error) +}) + +// Connect +await redis.connect() + +// Check status +console.log('Connected:', redis.isConnected()) +console.log('Ping:', await redis.ping()) +``` + +## IRedisConnection Interface + +```typescript +interface IRedisConnection { + /** Connection ID */ + readonly id: string + + /** Connection state */ + readonly state: ConnectionState + + /** Establish connection */ + connect(): Promise + + /** Disconnect */ + disconnect(): Promise + + /** Check if connected */ + isConnected(): boolean + + /** Test connection */ + ping(): Promise + + /** Get value */ + get(key: string): Promise + + /** Set value (optional TTL in seconds) */ + set(key: string, value: string, ttl?: number): Promise + + /** Delete key */ + del(key: string): Promise + + /** Check if key exists */ + exists(key: string): Promise + + /** Set expiration (seconds) */ + expire(key: string, seconds: number): Promise + + /** Get remaining TTL (seconds) */ + ttl(key: string): Promise + + /** Get native client (advanced usage) */ + getNativeClient(): Redis +} +``` + +## Usage Examples + +### Basic Operations + +```typescript +// Set value +await redis.set('user:1:name', 'John') + +// Set value with expiration (1 hour) +await redis.set('session:abc123', 'user-data', 3600) + +// Get value +const name = await redis.get('user:1:name') + +// Check if key exists +const exists = await redis.exists('user:1:name') + +// Delete key +await redis.del('user:1:name') + +// Get remaining TTL +const ttl = await redis.ttl('session:abc123') +``` + +### Key Prefix + +When `keyPrefix` is configured, all operations automatically add the prefix: + +```typescript +const redis = createRedisConnection({ + host: 'localhost', + keyPrefix: 'game:' +}) + +// Actual key is 'game:user:1' +await redis.set('user:1', 'data') + +// Actual key queried is 'game:user:1' +const data = await redis.get('user:1') +``` + +### Advanced Operations + +Use native client for advanced operations: + +```typescript +const client = redis.getNativeClient() + +// Using Pipeline +const pipeline = client.pipeline() +pipeline.set('key1', 'value1') +pipeline.set('key2', 'value2') +pipeline.set('key3', 'value3') +await pipeline.exec() + +// Using Transactions +const multi = client.multi() +multi.incr('counter') +multi.get('counter') +const results = await multi.exec() + +// Using Lua Scripts +const result = await client.eval( + `return redis.call('get', KEYS[1])`, + 1, + 'mykey' +) +``` + +## Integration with Transaction System + +```typescript +import { createRedisConnection } from '@esengine/database-drivers' +import { RedisStorage, TransactionManager } from '@esengine/transaction' + +const redis = createRedisConnection({ + host: 'localhost', + port: 6379, + keyPrefix: 'tx:' +}) +await redis.connect() + +// Create transaction storage +const storage = new RedisStorage({ + factory: () => redis.getNativeClient(), + prefix: 'tx:' +}) + +const txManager = new TransactionManager({ storage }) +``` + +## Connection State + +```typescript +type ConnectionState = + | 'disconnected' // Not connected + | 'connecting' // Connecting + | 'connected' // Connected + | 'disconnecting' // Disconnecting + | 'error' // Error state +``` + +## Events + +| Event | Description | +|-------|-------------| +| `connected` | Connection established | +| `disconnected` | Connection closed | +| `reconnecting` | Reconnecting | +| `reconnected` | Reconnection successful | +| `error` | Error occurred | diff --git a/docs/src/content/docs/en/modules/database/query.md b/docs/src/content/docs/en/modules/database/query.md new file mode 100644 index 00000000..be483ff3 --- /dev/null +++ b/docs/src/content/docs/en/modules/database/query.md @@ -0,0 +1,185 @@ +--- +title: "Query Syntax" +description: "Query condition operators and syntax" +--- + +## Basic Queries + +### Exact Match + +```typescript +await repo.findMany({ + where: { + name: 'John', + status: 'active' + } +}) +``` + +### Using Operators + +```typescript +await repo.findMany({ + where: { + score: { $gte: 100 }, + rank: { $in: ['gold', 'platinum'] } + } +}) +``` + +## Query Operators + +| Operator | Description | Example | +|----------|-------------|---------| +| `$eq` | Equal | `{ score: { $eq: 100 } }` | +| `$ne` | Not equal | `{ status: { $ne: 'banned' } }` | +| `$gt` | Greater than | `{ score: { $gt: 50 } }` | +| `$gte` | Greater than or equal | `{ level: { $gte: 10 } }` | +| `$lt` | Less than | `{ age: { $lt: 18 } }` | +| `$lte` | Less than or equal | `{ price: { $lte: 100 } }` | +| `$in` | In array | `{ rank: { $in: ['gold', 'platinum'] } }` | +| `$nin` | Not in array | `{ status: { $nin: ['banned', 'suspended'] } }` | +| `$like` | Pattern match | `{ name: { $like: '%john%' } }` | +| `$regex` | Regex match | `{ email: { $regex: '@gmail.com$' } }` | + +## Logical Operators + +### $or + +```typescript +await repo.findMany({ + where: { + $or: [ + { score: { $gte: 1000 } }, + { rank: 'legendary' } + ] + } +}) +``` + +### $and + +```typescript +await repo.findMany({ + where: { + $and: [ + { score: { $gte: 100 } }, + { score: { $lte: 500 } } + ] + } +}) +``` + +### Combined Usage + +```typescript +await repo.findMany({ + where: { + status: 'active', + $or: [ + { rank: 'gold' }, + { score: { $gte: 1000 } } + ] + } +}) +``` + +## Pattern Matching + +### $like Syntax + +- `%` - Matches any sequence of characters +- `_` - Matches single character + +```typescript +// Starts with 'John' +{ name: { $like: 'John%' } } + +// Ends with 'son' +{ name: { $like: '%son' } } + +// Contains 'oh' +{ name: { $like: '%oh%' } } + +// Second character is 'o' +{ name: { $like: '_o%' } } +``` + +### $regex Syntax + +Uses standard regular expressions: + +```typescript +// Starts with 'John' (case insensitive) +{ name: { $regex: '^john' } } + +// Gmail email +{ email: { $regex: '@gmail\\.com$' } } + +// Contains numbers +{ username: { $regex: '\\d+' } } +``` + +## Sorting + +```typescript +await repo.findMany({ + sort: { + score: 'desc', // Descending + name: 'asc' // Ascending + } +}) +``` + +## Pagination + +### Using limit/offset + +```typescript +// First page +await repo.findMany({ + limit: 20, + offset: 0 +}) + +// Second page +await repo.findMany({ + limit: 20, + offset: 20 +}) +``` + +### Using findPaginated + +```typescript +const result = await repo.findPaginated( + { page: 2, pageSize: 20 }, + { sort: { createdAt: 'desc' } } +) +``` + +## Complete Examples + +```typescript +// Find active gold players with scores between 100-1000 +// Sort by score descending, get top 10 +const players = await repo.findMany({ + where: { + status: 'active', + rank: 'gold', + score: { $gte: 100, $lte: 1000 } + }, + sort: { score: 'desc' }, + limit: 10 +}) + +// Search for users with 'john' in username or gmail email +const users = await repo.findMany({ + where: { + $or: [ + { username: { $like: '%john%' } }, + { email: { $regex: '@gmail\\.com$' } } + ] + } +}) +``` diff --git a/docs/src/content/docs/en/modules/database/repository.md b/docs/src/content/docs/en/modules/database/repository.md new file mode 100644 index 00000000..ae427a74 --- /dev/null +++ b/docs/src/content/docs/en/modules/database/repository.md @@ -0,0 +1,244 @@ +--- +title: "Repository API" +description: "Generic repository interface, CRUD operations, pagination, soft delete" +--- + +## Creating a Repository + +### Using Factory Function + +```typescript +import { createRepository } from '@esengine/database' + +const playerRepo = createRepository(mongo, 'players') + +// Enable soft delete +const playerRepo = createRepository(mongo, 'players', true) +``` + +### Extending Repository + +```typescript +import { Repository, BaseEntity } from '@esengine/database' + +interface Player extends BaseEntity { + name: string + score: number +} + +class PlayerRepository extends Repository { + constructor(connection: IMongoConnection) { + super(connection, 'players', false) // Third param: enable soft delete + } + + // Add custom methods + async findTopPlayers(limit: number): Promise { + return this.findMany({ + sort: { score: 'desc' }, + limit + }) + } +} +``` + +## BaseEntity Interface + +All entities must extend `BaseEntity`: + +```typescript +interface BaseEntity { + id: string + createdAt: Date + updatedAt: Date + deletedAt?: Date // Used for soft delete +} +``` + +## Query Methods + +### findById + +```typescript +const player = await repo.findById('player-123') +``` + +### findOne + +```typescript +const player = await repo.findOne({ + where: { name: 'John' } +}) + +const topPlayer = await repo.findOne({ + sort: { score: 'desc' } +}) +``` + +### findMany + +```typescript +// Simple query +const players = await repo.findMany({ + where: { rank: 'gold' } +}) + +// Complex query +const players = await repo.findMany({ + where: { + score: { $gte: 100 }, + rank: { $in: ['gold', 'platinum'] } + }, + sort: { score: 'desc', name: 'asc' }, + limit: 10, + offset: 0 +}) +``` + +### findPaginated + +```typescript +const result = await repo.findPaginated( + { page: 1, pageSize: 20 }, + { + where: { rank: 'gold' }, + sort: { score: 'desc' } + } +) + +console.log(result.data) // Player[] +console.log(result.total) // Total count +console.log(result.totalPages) // Total pages +console.log(result.hasNext) // Has next page +console.log(result.hasPrev) // Has previous page +``` + +### count + +```typescript +const count = await repo.count({ + where: { rank: 'gold' } +}) +``` + +### exists + +```typescript +const exists = await repo.exists({ + where: { email: 'john@example.com' } +}) +``` + +## Create Methods + +### create + +```typescript +const player = await repo.create({ + name: 'John', + score: 0 +}) +// Automatically generates id, createdAt, updatedAt +``` + +### createMany + +```typescript +const players = await repo.createMany([ + { name: 'Alice', score: 100 }, + { name: 'Bob', score: 200 }, + { name: 'Carol', score: 150 } +]) +``` + +## Update Methods + +### update + +```typescript +const updated = await repo.update('player-123', { + score: 200, + rank: 'gold' +}) +// Automatically updates updatedAt +``` + +## Delete Methods + +### delete + +```typescript +// Hard delete +await repo.delete('player-123') + +// Soft delete (if enabled) +// Actually sets the deletedAt field +``` + +### deleteMany + +```typescript +const count = await repo.deleteMany({ + where: { score: { $lt: 10 } } +}) +``` + +## Soft Delete + +### Enabling Soft Delete + +```typescript +const repo = createRepository(mongo, 'players', true) +``` + +### Query Behavior + +```typescript +// Excludes soft-deleted records by default +const players = await repo.findMany() + +// Include soft-deleted records +const allPlayers = await repo.findMany({ + includeSoftDeleted: true +}) +``` + +### Restore Records + +```typescript +await repo.restore('player-123') +``` + +## QueryOptions + +```typescript +interface QueryOptions { + /** Query conditions */ + where?: WhereCondition + + /** Sorting */ + sort?: Partial> + + /** Limit count */ + limit?: number + + /** Offset */ + offset?: number + + /** Include soft-deleted records (only when soft delete is enabled) */ + includeSoftDeleted?: boolean +} +``` + +## PaginatedResult + +```typescript +interface PaginatedResult { + data: T[] + total: number + page: number + pageSize: number + totalPages: number + hasNext: boolean + hasPrev: boolean +} +``` diff --git a/docs/src/content/docs/en/modules/database/user.md b/docs/src/content/docs/en/modules/database/user.md new file mode 100644 index 00000000..0cb4f0cd --- /dev/null +++ b/docs/src/content/docs/en/modules/database/user.md @@ -0,0 +1,277 @@ +--- +title: "User Management" +description: "UserRepository for user registration, authentication, and role management" +--- + +## Overview + +`UserRepository` provides out-of-the-box user management features: + +- User registration and authentication +- Password hashing (using scrypt) +- Role management +- Account status management + +## Quick Start + +```typescript +import { createMongoConnection } from '@esengine/database-drivers' +import { UserRepository } from '@esengine/database' + +const mongo = createMongoConnection({ + uri: 'mongodb://localhost:27017', + database: 'game' +}) +await mongo.connect() + +const userRepo = new UserRepository(mongo) +``` + +## User Registration + +```typescript +const user = await userRepo.register({ + username: 'john', + password: 'securePassword123', + email: 'john@example.com', // Optional + displayName: 'John Doe', // Optional + roles: ['player'] // Optional, defaults to [] +}) + +console.log(user) +// { +// id: 'uuid-...', +// username: 'john', +// email: 'john@example.com', +// displayName: 'John Doe', +// roles: ['player'], +// status: 'active', +// createdAt: Date, +// updatedAt: Date +// } +``` + +**Note**: `register` returns a `SafeUser` which excludes the password hash. + +## User Authentication + +```typescript +const user = await userRepo.authenticate('john', 'securePassword123') + +if (user) { + console.log('Login successful:', user.username) +} else { + console.log('Invalid username or password') +} +``` + +## Password Management + +### Change Password + +```typescript +const success = await userRepo.changePassword( + userId, + 'oldPassword123', + 'newPassword456' +) + +if (success) { + console.log('Password changed successfully') +} else { + console.log('Invalid current password') +} +``` + +### Reset Password + +```typescript +// Admin directly resets password +const success = await userRepo.resetPassword(userId, 'newPassword123') +``` + +## Role Management + +### Add Role + +```typescript +await userRepo.addRole(userId, 'admin') +await userRepo.addRole(userId, 'moderator') +``` + +### Remove Role + +```typescript +await userRepo.removeRole(userId, 'moderator') +``` + +### Query Roles + +```typescript +// Find all admins +const admins = await userRepo.findByRole('admin') + +// Check if user has a role +const user = await userRepo.findById(userId) +const isAdmin = user?.roles.includes('admin') +``` + +## Querying Users + +### Find by Username + +```typescript +const user = await userRepo.findByUsername('john') +``` + +### Find by Email + +```typescript +const user = await userRepo.findByEmail('john@example.com') +``` + +### Find by Role + +```typescript +const admins = await userRepo.findByRole('admin') +``` + +### Using Inherited Methods + +```typescript +// Paginated query +const result = await userRepo.findPaginated( + { page: 1, pageSize: 20 }, + { + where: { status: 'active' }, + sort: { createdAt: 'desc' } + } +) + +// Complex query +const users = await userRepo.findMany({ + where: { + status: 'active', + roles: { $in: ['admin', 'moderator'] } + } +}) +``` + +## Account Status + +```typescript +type UserStatus = 'active' | 'inactive' | 'banned' | 'suspended' +``` + +### Update Status + +```typescript +await userRepo.update(userId, { status: 'banned' }) +``` + +### Query by Status + +```typescript +const activeUsers = await userRepo.findMany({ + where: { status: 'active' } +}) + +const bannedUsers = await userRepo.findMany({ + where: { status: 'banned' } +}) +``` + +## Type Definitions + +### UserEntity + +```typescript +interface UserEntity extends BaseEntity { + username: string + passwordHash: string + email?: string + displayName?: string + roles: string[] + status: UserStatus + lastLoginAt?: Date +} +``` + +### SafeUser + +```typescript +type SafeUser = Omit +``` + +### CreateUserParams + +```typescript +interface CreateUserParams { + username: string + password: string + email?: string + displayName?: string + roles?: string[] +} +``` + +## Password Utilities + +Standalone password utility functions: + +```typescript +import { hashPassword, verifyPassword } from '@esengine/database' + +// Hash password +const hash = await hashPassword('myPassword123') + +// Verify password +const isValid = await verifyPassword('myPassword123', hash) +``` + +### Security Notes + +- Uses Node.js built-in `scrypt` algorithm +- Automatically generates random salt +- Uses secure iteration parameters by default +- Hash format: `salt:hash` (both hex encoded) + +## Extending UserRepository + +```typescript +import { UserRepository, UserEntity } from '@esengine/database' + +interface GameUser extends UserEntity { + level: number + experience: number + coins: number +} + +class GameUserRepository extends UserRepository { + // Override collection name + constructor(connection: IMongoConnection) { + super(connection, 'game_users') + } + + // Add game-related methods + async addExperience(userId: string, amount: number): Promise { + const user = await this.findById(userId) as GameUser | null + if (!user) return null + + const newExp = user.experience + amount + const newLevel = Math.floor(newExp / 1000) + 1 + + return this.update(userId, { + experience: newExp, + level: newLevel + }) as Promise + } + + async findTopPlayers(limit: number = 10): Promise { + return this.findMany({ + sort: { level: 'desc', experience: 'desc' }, + limit + }) as Promise + } +} +```