Implementation Standards
This document defines the implementation standards for the ReX project, ensuring consistency, maintainability, and quality across the codebase.
Core Implementation Principles
- Composition Over Inheritance: Build complex behavior through function composition rather than class hierarchies
- Immutability: Prefer immutable data structures and pure functions
- Type Safety: Leverage TypeScript’s type system for correctness and documentation
- Environment Adaptability: Write code that adapts to different runtime environments
- Explicit Dependencies: Make dependencies explicit through parameters rather than implicit through imports
Code Organization
Project Structure
The codebase follows a domain-based directory structure:
src/
├── lib/ # Core library code
│ ├── content/ # Content domain
│ │ ├── adapters/ # Storage adapters
│ │ ├── middleware/ # Middleware functions
│ │ ├── store/ # Content store implementation
│ │ └── types.ts # Domain-specific types
│ ├── errors/ # Error system
│ └── utils/ # Shared utilities
├── components/ # React components
├── hooks/ # React hooks
└── styles/ # Global styles
Module Organization
Each module should be organized with the following structure:
- Imports (grouped by source)
- Type definitions (if not in separate file)
- Constants
- Factory functions
- Utility functions
- Exports
Example:
typescript
// 1. Imports
import { pipe } from '@lib/utils/functional'
import { createMemoryAdapter } from '@lib/content/adapters'
// 2. Type definitions
type StoreOptions = {
adapter?: ContentAdapter
initialContent?: Record<string, Content>
}
// 3. Constants
const DEFAULT_OPTIONS: StoreOptions = {
adapter: null,
initialContent: {},
}
// 4. Factory functions
function createContentStore(options: StoreOptions = DEFAULT_OPTIONS) {
// Implementation
}
// 5. Utility functions
function isValidContent(content: unknown): content is Content {
// Implementation
}
// 6. Exports
export { createContentStore, isValidContent }
export type { StoreOptions }
Coding Patterns
Function Design
- Factory Functions: Use factory functions to create objects with private state
typescript
// Preferred
function createContentStore(options) {
// Private state
const adapter = options.adapter || createMemoryAdapter()
// Return public API
return {
read: async uri => {
/* implementation */
},
write: async (uri, content) => {
/* implementation */
},
}
}
// Avoid
class ContentStore {
private adapter: ContentAdapter
constructor(options) {
this.adapter = options.adapter || new MemoryAdapter()
}
async read(uri) {
/* implementation */
}
async write(uri, content) {
/* implementation */
}
}
- Function Composition: Use function composition for enhancing behavior
typescript
// Preferred
const enhancedStore = pipe(
createContentStore(),
withValidation(schema),
withCaching(),
withLogging()
)
// Avoid
class EnhancedStore extends ContentStore {
constructor(options) {
super(options)
this.schema = options.schema
this.cache = new Cache()
this.logger = new Logger()
}
async read(uri) {
this.logger.log(`Reading ${uri}`)
const cached = this.cache.get(uri)
if (cached) return cached
const result = await super.read(uri)
this.cache.set(uri, result)
return result
}
}
- Pure Functions: Prefer pure functions without side effects
typescript
// Preferred
function transformContent(content, options) {
return {
...content,
data: options.processor(content.data),
metadata: {
...content.metadata,
processed: true,
},
}
}
// Avoid
function transformContent(content, options) {
content.data = options.processor(content.data)
content.metadata.processed = true
return content
}
Error Handling
- Structured Errors: Use structured error classes with context information
typescript
// Preferred
throw new ContentNotFoundError({
uri,
message: `Content not found at ${uri}`,
cause: originalError,
})
// Avoid
throw new Error(`Failed to load content: ${error.message}`)
- Async Error Handling: Use try/catch for async error handling
typescript
// Preferred
try {
const content = await store.read(uri)
return processContent(content)
} catch (error) {
if (error instanceof ContentNotFoundError) {
return createDefaultContent(uri)
}
throw error
}
// Avoid
return store
.read(uri)
.then(content => processContent(content))
.catch(error => {
if (error.message.includes('not found')) {
return createDefaultContent(uri)
}
throw error
})
- Error Recovery: Provide recovery mechanisms for expected error scenarios
typescript
// Preferred
async function readWithFallback(uri) {
try {
return await primaryStore.read(uri)
} catch (error) {
if (error instanceof ContentNotFoundError) {
return await fallbackStore.read(uri)
}
throw error
}
}
// Avoid
async function read(uri) {
try {
return await store.read(uri)
} catch (error) {
console.error(`Failed to read ${uri}:`, error)
return null // Silent failure
}
}
Asynchronous Code
- Async/Await: Use async/await for asynchronous code
typescript
// Preferred
async function loadAndProcess(uri) {
const content = await store.read(uri)
return processContent(content)
}
// Avoid
function loadAndProcess(uri) {
return store.read(uri).then(content => {
return processContent(content)
})
}
- Promise Handling: Use Promise.all for parallel operations
typescript
// Preferred
async function loadMultiple(uris) {
return Promise.all(uris.map(uri => store.read(uri)))
}
// Avoid
async function loadMultiple(uris) {
const results = []
for (const uri of uris) {
results.push(await store.read(uri))
}
return results
}
- Cancellation: Support cancellation where appropriate
typescript
// Preferred
async function loadWithTimeout(uri, timeoutMs) {
const controller = new AbortController()
const timeout = setTimeout(() => controller.abort(), timeoutMs)
try {
return await store.read(uri, { signal: controller.signal })
} finally {
clearTimeout(timeout)
}
}
Environment Adaptability
- Feature Detection: Use capability detection instead of environment detection
typescript
// Preferred
function createStorage() {
if (typeof indexedDB !== 'undefined') {
return createIndexedDBStorage()
}
if (typeof localStorage !== 'undefined') {
return createLocalStorage()
}
return createMemoryStorage()
}
// Avoid
function createStorage() {
if (isBrowser()) {
return createBrowserStorage()
} else {
return createNodeStorage()
}
}
- Environment-Specific Code: Load environment-specific code dynamically
typescript
// Preferred
async function createAdapter(options) {
if (typeof window !== 'undefined') {
const { createBrowserAdapter } = await import('./browser-adapter')
return createBrowserAdapter(options)
} else {
const { createNodeAdapter } = await import('./node-adapter')
return createNodeAdapter(options)
}
}
TypeScript Usage
Type Definitions
- Interface vs Type: Use interfaces for object structures, types for unions/aliases
typescript
// Preferred
interface ContentMetadata {
title?: string
description?: string
createdAt: Date
tags?: string[]
}
type ContentType = 'text/markdown' | 'text/plain' | 'application/json'
// Avoid
type ContentMetadata = {
title?: string
description?: string
createdAt: Date
tags?: string[]
}
- Generics: Use generics for flexible, reusable code
typescript
// Preferred
interface Content<T = string> {
data: T
contentType: string
metadata: ContentMetadata
}
function createStore<T>(): Store<T> {
// Implementation
}
// Avoid
interface StringContent {
data: string
contentType: string
metadata: ContentMetadata
}
interface BinaryContent {
data: Uint8Array
contentType: string
metadata: ContentMetadata
}
- Utility Types: Use TypeScript utility types effectively
typescript
// Preferred
type ContentOptions = Partial<Content>
type RequiredMetadata = Required<Pick<ContentMetadata, 'title' | 'description'>>
type ReadonlyContent = Readonly<Content>
// Avoid
type ContentOptions = {
data?: string
contentType?: string
metadata?: ContentMetadata
}
type RequiredMetadata = {
title: string
description: string
}
type ReadonlyContent = {
readonly data: string
readonly contentType: string
readonly metadata: ContentMetadata
}
Type Safety
- Exhaustive Checks: Use exhaustive checks for unions
typescript
// Preferred
function handleChange(type: ChangeType, uri: string) {
switch (type) {
case 'created':
return handleCreated(uri)
case 'updated':
return handleUpdated(uri)
case 'deleted':
return handleDeleted(uri)
case 'moved':
return handleMoved(uri)
default:
// Exhaustiveness check
const _exhaustiveCheck: never = type
throw new Error(`Unhandled change type: ${_exhaustiveCheck}`)
}
}
- Type Guards: Use type guards for runtime type checking
typescript
// Preferred
function isContent(value: unknown): value is Content {
return (
typeof value === 'object' &&
value !== null &&
'data' in value &&
'contentType' in value &&
'metadata' in value
)
}
// Then use it
if (isContent(value)) {
// TypeScript knows value is Content here
processContent(value)
}
Testing
Unit Testing
- Test Structure: Use Arrange-Act-Assert pattern
typescript
// Preferred
test('reads content from adapter', async () => {
// Arrange
const mockAdapter = createMockAdapter({
read: async () => mockContent,
})
const store = createContentStore({ adapter: mockAdapter })
// Act
const result = await store.read('test-uri')
// Assert
expect(result).toEqual(mockContent)
})
- Test Isolation: Ensure tests are isolated and deterministic
typescript
// Preferred
beforeEach(() => {
vi.resetAllMocks()
mockDate = new Date(2025, 0, 1)
vi.setSystemTime(mockDate)
})
afterEach(() => {
vi.useRealTimers()
})
// Test with consistent date expectations
test('adds createdAt timestamp', async () => {
const result = await store.write('test', { data: 'test' })
expect(result.metadata.createdAt).toEqual(mockDate)
})
- Mocking: Use explicit mocks rather than magic strings/patching
typescript
// Preferred
const mockAdapter = {
read: vi.fn().mockResolvedValue(mockContent),
write: vi.fn().mockResolvedValue(true),
delete: vi.fn().mockResolvedValue(true),
list: vi.fn().mockResolvedValue(['uri1', 'uri2']),
}
const store = createContentStore({ adapter: mockAdapter })
// Avoid
vi.mock('@lib/content/adapters', () => ({
createMemoryAdapter: () => ({
read: () => Promise.resolve(mockContent),
write: () => Promise.resolve(true),
delete: () => Promise.resolve(true),
list: () => Promise.resolve(['uri1', 'uri2']),
}),
}))
Integration Testing
- Environment Setup: Use realistic environment setups
typescript
// Preferred
test('stores content in IndexedDB', async () => {
// Setup in-memory IndexedDB for testing
const indexedDB = new IDBFactory()
const adapter = createIndexedDBAdapter({ db: indexedDB })
// Test with real storage implementation
await adapter.write('test', mockContent)
const result = await adapter.read('test')
expect(result).toEqual(mockContent)
})
Documentation
Code Documentation
- JSDoc Comments: Use JSDoc for public APIs
typescript
/**
* Creates a content store with the specified options.
* @param options - Configuration options for the store
* @param options.adapter - Storage adapter (defaults to memory adapter)
* @param options.initialContent - Initial content to populate the store
* @returns A content store instance
*/
function createContentStore(options?: StoreOptions): ContentStore {
// Implementation
}
- Examples: Include examples in documentation
typescript
/**
* Transforms content using the specified processor.
*
* @example
* ```ts
* const content = { data: '# Title', contentType: 'text/markdown', metadata: {} }
* const transformed = transformContent(content, {
* processor: (data) => data.toLowerCase()
* })
* // Result: { data: '# title', contentType: 'text/markdown', metadata: { processed: true } }
* ```
*/
function transformContent(
content: Content,
options: TransformOptions
): Content {
// Implementation
}
Performance Considerations
Resource Management
- Resource Cleanup: Always clean up resources
typescript
// Preferred
const store = createContentStore()
try {
// Use store
} finally {
await store.dispose()
}
// Even better, use a withStore higher-order function
await withStore(async store => {
// Use store
})
- Memory Efficiency: Be mindful of memory usage
typescript
// Preferred - process items in chunks
async function processLargeDataset(items) {
const chunkSize = 100
for (let i = 0; i < items.length; i += chunkSize) {
const chunk = items.slice(i, i + chunkSize)
await processChunk(chunk)
}
}
// Avoid - loading everything into memory
async function processLargeDataset(items) {
const allResults = await Promise.all(items.map(process))
return allResults
}
Security Considerations
Input Validation
- Validate Inputs: Always validate user inputs
typescript
// Preferred
function processUserInput(input: unknown): string {
if (typeof input !== 'string') {
throw new ValidationError('Input must be a string')
}
if (input.length > MAX_INPUT_LENGTH) {
throw new ValidationError(
`Input exceeds maximum length of ${MAX_INPUT_LENGTH}`
)
}
return sanitizeInput(input)
}
- URI Safety: Validate and normalize URIs
typescript
// Preferred
function validateUri(uri: string): string {
if (!uri) {
throw new ValidationError('URI is required')
}
if (uri.includes('..')) {
throw new ValidationError('URI must not contain path traversal sequences')
}
return normalizeUri(uri)
}
Authentication & Authorization
- Access Controls: Implement proper access controls
typescript
// Preferred
async function readContent(uri: string, user: User) {
if (!(await canUserAccessContent(uri, user))) {
throw new AccessDeniedError(`User ${user.id} cannot access ${uri}`)
}
return store.read(uri)
}
Conclusion
These implementation standards establish a foundation for consistent, maintainable, and high-quality code throughout the ReX project. By adhering to these standards, the development team can create a more cohesive and robust system.
All code, both new and modified, should follow these standards. During code reviews, refer to this document to ensure compliance with the established patterns and practices.