Files
rxminder/services/database

Database Service Module

A unified database abstraction layer implementing the Strategy pattern for flexible data persistence in the RxMinder application.

Overview

This module provides a clean, consistent interface for all database operations while supporting multiple backend implementations. It automatically selects the appropriate strategy based on environment configuration.

Architecture

services/database/
├── README.md                    # This file
├── index.ts                     # Public exports
├── types.ts                     # Type definitions and interfaces
├── DatabaseService.ts           # Main service class (Strategy Context)
├── MockDatabaseStrategy.ts      # In-memory implementation
├── ProductionDatabaseStrategy.ts # CouchDB implementation
└── __tests__/                   # Test files
    ├── DatabaseService.test.ts
    └── MockDatabaseStrategy.test.ts

Quick Start

import { databaseService } from './services/database';

// Service automatically selects appropriate strategy
const users = await databaseService.getAllUsers();
const user = await databaseService.getUserById('user123');

Strategy Selection

The service automatically chooses the implementation based on environment:

Environment Strategy Backend
NODE_ENV=test MockDatabaseStrategy In-memory
CouchDB URL configured ProductionDatabaseStrategy CouchDB
Default/Fallback MockDatabaseStrategy In-memory

Environment Variables

# Production CouchDB
VITE_COUCHDB_URL=http://localhost:5984
COUCHDB_URL=http://localhost:5984

# Force mock strategy (optional)
VITE_COUCHDB_URL=mock

Core Interfaces

DatabaseStrategy

All implementations must conform to this interface:

interface DatabaseStrategy {
  // User operations
  createUser(user: Omit<User, '_rev'>): Promise<User>;
  updateUser(user: User): Promise<User>;
  getUserById(id: string): Promise<User | null>;
  findUserByEmail(email: string): Promise<User | null>;
  deleteUser(id: string): Promise<boolean>;
  getAllUsers(): Promise<User[]>;

  // Auth operations
  createUserWithPassword(email: string, hashedPassword: string, username?: string): Promise<User>;
  createUserFromOAuth(email: string, username: string, provider: string): Promise<User>;

  // Medication operations
  createMedication(userId: string, medication: Omit<Medication, '_id' | '_rev'>): Promise<Medication>;
  updateMedication(medication: Medication): Promise<Medication>;
  getMedications(userId: string): Promise<Medication[]>;
  deleteMedication(id: string): Promise<boolean>;

  // Settings operations
  getUserSettings(userId: string): Promise<UserSettings>;
  updateUserSettings(settings: UserSettings): Promise<UserSettings>;

  // Doses operations
  getTakenDoses(userId: string): Promise<TakenDoses>;
  updateTakenDoses(takenDoses: TakenDoses): Promise<TakenDoses>;

  // Reminders operations
  createCustomReminder(userId: string, reminder: Omit<CustomReminder, '_id' | '_rev'>): Promise<CustomReminder>;
  updateCustomReminder(reminder: CustomReminder): Promise<CustomReminder>;
  getCustomReminders(userId: string): Promise<CustomReminder[]>;
  deleteCustomReminder(id: string): Promise<boolean>;
}

Implementations

MockDatabaseStrategy

Use Cases:

  • Development environment
  • Unit testing
  • Integration testing
  • Demos and prototyping

Features:

  • In-memory storage using Maps
  • Immediate consistency
  • No network dependencies
  • Automatic data reset between tests
  • Fast operations

Limitations:

  • Data lost on application restart
  • No persistence across sessions
  • Single-process only

ProductionDatabaseStrategy

Use Cases:

  • Production deployments
  • Staging environments
  • Data persistence requirements

Features:

  • CouchDB backend
  • Persistent storage
  • Document versioning with _rev
  • ACID compliance
  • Horizontal scaling support

Requirements:

  • CouchDB server running
  • Network connectivity
  • Proper authentication/authorization

Usage Examples

Basic Operations

import { databaseService } from './services/database';

// Create a user
const user = await databaseService.createUser({
  _id: 'user123',
  username: 'john_doe',
  email: 'john@example.com',
  role: UserRole.USER,
  status: AccountStatus.ACTIVE,
});

// Add medication
const medication = await databaseService.createMedication(user._id, {
  name: 'Aspirin',
  dosage: '100mg',
  frequency: Frequency.Daily,
  startTime: '08:00',
});

// Update user settings
const settings = await databaseService.getUserSettings(user._id);
await databaseService.updateUserSettings({
  ...settings,
  notificationsEnabled: true,
});

Administrative Operations

// User management
await databaseService.suspendUser('user123');
await databaseService.activateUser('user123');
await databaseService.changeUserPassword('user123', 'new_hashed_password');

// Complete data deletion
await databaseService.deleteAllUserData('user123');

Strategy Inspection

// Check active strategy
console.log('Strategy:', databaseService.getStrategyType());

// Strategy-specific logic (avoid when possible)
if (databaseService.isUsingMockStrategy()) {
  console.log('Using in-memory storage');
} else if (databaseService.isUsingProductionStrategy()) {
  console.log('Using CouchDB storage');
}

Testing

Unit Tests

import { DatabaseService } from './DatabaseService';
import { MockDatabaseStrategy } from './MockDatabaseStrategy';

describe('DatabaseService', () => {
  let service: DatabaseService;

  beforeEach(() => {
    // In test environment, automatically uses MockDatabaseStrategy
    service = new DatabaseService();
  });

  test('should create user', async () => {
    const user = await service.createUser(mockUser);
    expect(user._id).toBeDefined();
    expect(user.username).toBe(mockUser.username);
  });
});

Test Utilities

import { testUtils } from '../../tests/setup';

const mockUser = testUtils.createMockUser();
const mockMedication = testUtils.createMockMedication();

Error Handling

DatabaseError

import { DatabaseError } from './services/database';

try {
  const user = await databaseService.getUserById('invalid-id');
} catch (error) {
  if (error instanceof DatabaseError) {
    console.error('Database operation failed:', error.message);
  }
}

Fallback Behavior

// Service automatically falls back to MockDatabaseStrategy
// if ProductionDatabaseStrategy fails to initialize
const service = new DatabaseService(); // Safe to use

Performance

MockDatabaseStrategy

  • Pros: Instant operations, no I/O overhead
  • Cons: Memory usage grows with data size
  • Best for: Development, testing, small datasets

ProductionDatabaseStrategy

  • Pros: Persistent storage, designed for scale
  • Cons: Network latency, connection overhead
  • Best for: Production, large datasets, multi-user

Security

Data Isolation

  • Users can only access their own data
  • User ID validation on all operations
  • Role-based access for administrative functions

Input Validation

  • TypeScript interfaces enforce type safety
  • Required field validation
  • Email format validation
  • ID format validation

Authentication

  • No anonymous access allowed
  • User context required for all operations
  • Password hashing handled by auth service

Migration Guide

From Legacy CouchDB Service

// Old import
import { couchdbService } from './services/couchdb';

// New import
import { databaseService } from './services/database';

// Old method signature
await couchdbService.updateMedication(userId, medication);

// New method signature
await databaseService.updateMedication(medication);

Breaking Changes

  1. Method signatures: Some methods now accept the full entity instead of separate parameters
  2. Return types: Consistent return types across all operations
  3. Error handling: Unified error types with DatabaseError

Troubleshooting

Common Issues

1. Strategy Selection

# Check environment variables
echo $VITE_COUCHDB_URL
echo $NODE_ENV

# Verify strategy selection
console.log(databaseService.getStrategyType());

2. CouchDB Connection

# Test CouchDB connectivity
curl $VITE_COUCHDB_URL

# Check CouchDB logs
docker logs couchdb-container

3. Test Environment

# Ensure test environment
NODE_ENV=test npm test

# Verify mock strategy in tests
expect(databaseService.isUsingMockStrategy()).toBe(true);

Debug Logging

// Enable debug information
const strategy = databaseService.getStrategyType();
console.log(`Using ${strategy} for database operations`);

// Check environment
console.log('Environment:', process.env.NODE_ENV);
console.log('CouchDB URL:', process.env.VITE_COUCHDB_URL);

Development

Adding New Operations

  1. Update DatabaseStrategy interface in types.ts
  2. Implement in MockDatabaseStrategy
  3. Implement in ProductionDatabaseStrategy
  4. Add delegation method in DatabaseService
  5. Write tests for all implementations
  6. Update documentation

Creating New Strategies

class CustomDatabaseStrategy implements DatabaseStrategy {
  // Implement all required methods
  async createUser(user: Omit<User, '_rev'>): Promise<User> {
    // Custom implementation
  }

  // ... implement all other methods
}

Contributing

  1. Tests required for all new features
  2. Type safety - no any types
  3. Error handling - proper error propagation
  4. Documentation - update README and JSDoc
  5. Strategy pattern - maintain abstraction

Dependencies

Production

  • None (strategy implementations may have their own)

Development

  • @types/jest - Testing types
  • ts-jest - TypeScript testing
  • @testing-library/react - Component testing

Changelog

v2.0.0

  • Unified database service with Strategy pattern
  • Removed legacy CouchDB service files
  • Added comprehensive test coverage
  • Improved error handling
  • TypeScript strict mode compliance

v1.x.x

  • Legacy CouchDB-only implementation (deprecated)

Maintainers: Development Team
Last Updated: January 2024
License: MIT