Skip to content

Architecture Refinement Recommendations

This document addresses architectural challenges identified in the ReX content system and proposes refinement strategies to enhance clarity, cohesion, and extensibility.

1. Layer Boundaries and Responsibilities

Issue: Unclear Boundaries Between Layers

The current architecture has somewhat blurred boundaries between core, middleware, adapter, and specialized content layers, leading to inconsistent implementation patterns.

Define clear layer boundaries and responsibilities:

  1. Document the architectural layers:
Content System Layers:

┌─────────────────────────────────────┐
│ Application Layer                   │
│ - React hooks, components           │
│ - Framework integration             │
│ - UI-specific transformations       │
└─────────────────┬───────────────────┘

┌─────────────────▼───────────────────┐
│ Content Store Layer                 │
│ - High-level content operations     │
│ - Content lifecycle management      │
│ - Event handling and observation    │
└─────────────────┬───────────────────┘

┌─────────────────▼───────────────────┐
│ Middleware Layer                    │
│ - Cross-cutting concerns            │
│ - Feature composition               │
│ - Transformation and validation     │
└─────────────────┬───────────────────┘

┌─────────────────▼───────────────────┐
│ Adapter Layer                       │
│ - Storage mechanism abstraction     │
│ - Environment-specific operations   │
│ - Raw content handling              │
└─────────────────┬───────────────────┘

┌─────────────────▼───────────────────┐
│ Storage Layer                       │
│ - Physical storage mechanisms       │
│ - File system, IndexedDB, etc.      │
│ - External content services         │
└─────────────────────────────────────┘
  1. Define clear responsibilities for each layer:
typescript
// Application Layer: Integrates with UI frameworks
interface ContentHook {
  content: Content | null
  loading: boolean
  error: Error | null
  update(content: Content): Promise<void>
  delete(): Promise<void>
  refresh(): Promise<void>
}

// Store Layer: High-level content operations
interface ContentStore {
  read(uri: string, options?: ReadOptions): Promise<Content>
  write(uri: string, content: Content, options?: WriteOptions): Promise<void>
  delete(uri: string, options?: DeleteOptions): Promise<void>
  list(pattern?: string, options?: ListOptions): Promise<string[]>
  observe(callback: ContentObserver, options?: ObserveOptions): Unsubscribe
}

// Middleware Layer: Cross-cutting concerns
type Middleware<T extends Function> = (next: T) => T

// Adapter Layer: Storage abstraction
interface ContentAdapter {
  read(uri: string): Promise<RawContent>
  write(uri: string, content: RawContent): Promise<void>
  delete(uri: string): Promise<void>
  list(pattern?: string): Promise<string[]>
  // Optional methods...
}

// Storage Layer: Direct storage mechanisms
// Specific to implementation environment
  1. Create clear transformation patterns between layers:
typescript
// Store to Application Layer transformation
function useContent(uri: string): ContentHook {
  const store = useContentStore()
  const [content, setContent] = useState<Content | null>(null)
  const [loading, setLoading] = useState<boolean>(true)
  const [error, setError] = useState<Error | null>(null)

  // Implementation details...

  return {
    content,
    loading,
    error,
    update: content => store.write(uri, content),
    delete: () => store.delete(uri),
    refresh: () => loadContent(),
  }
}

// Store to Middleware transformation
function createEnhancedStore(baseStore: ContentStore): ContentStore {
  return compose(
    baseStore,
    withValidation(schema),
    withCaching(options),
    withLogging(options)
  )
}

// Middleware to Adapter transformation
function createAdapter(options: AdapterOptions): ContentAdapter {
  const baseAdapter = createBaseAdapter(options)

  return compose(baseAdapter, withErrorHandling(), withMetrics())
}

This approach clarifies the responsibilities of each layer and establishes clear patterns for communication between layers.

2. Adapter Capability Discovery

Issue: Inconsistent Adapter Capabilities

Adapters have inconsistent capabilities, with optional methods that may or may not be implemented, leading to uncertainty in usage.

Implement a formal capability discovery system:

  1. Define capability interfaces:
typescript
// Base capabilities interface
interface AdapterCapabilities {
  // Core capabilities (always true for valid adapters)
  canRead: true
  canWrite: true
  canDelete: true
  canList: true

  // Optional capabilities
  canWatch?: boolean
  canMove?: boolean
  canCopy?: boolean
  canGetMetadata?: boolean
  canCreateDirectory?: boolean
  // Other capabilities...
}

// Extended interface for file system adapters
interface FileSystemCapabilities extends AdapterCapabilities {
  canCreateDirectory: boolean
  canReadDirectory: boolean
  canGetStats: boolean
}

// Extended interface for browser storage adapters
interface BrowserStorageCapabilities extends AdapterCapabilities {
  canPersist: boolean
  canGetQuota: boolean
  canWatch: boolean
}
  1. Add capability detection to adapter interface:
typescript
interface ContentAdapter {
  // Core methods...

  // Capability discovery
  getCapabilities(): AdapterCapabilities

  // Type guard helper
  hasCapability<K extends keyof AdapterCapabilities>(
    capability: K
  ): this is ContentAdapter & { [P in K]: true }
}
  1. Implement capability checking in stores:
typescript
class ContentStore {
  private adapter: ContentAdapter

  constructor(adapter: ContentAdapter) {
    this.adapter = adapter
  }

  async move(source: string, destination: string): Promise<void> {
    if (this.adapter.hasCapability('canMove')) {
      // Use native implementation
      return this.adapter.move!(source, destination)
    }

    // Fallback implementation
    const content = await this.adapter.read(source)
    await this.adapter.write(destination, content)
    await this.adapter.delete(source)
  }

  // Other methods with capability checks...
}
  1. Document capability requirements for middleware:
typescript
// Example of middleware documenting requirements
function withVersioning(
  options?: VersioningOptions
): Middleware<ContentAdapter> {
  // Document required capabilities
  const requiredCapabilities: (keyof AdapterCapabilities)[] = [
    'canRead',
    'canWrite',
  ]

  return next => {
    // Verify capabilities
    for (const capability of requiredCapabilities) {
      if (!next.hasCapability(capability)) {
        throw new Error(
          `Versioning middleware requires the ${capability} capability`
        )
      }
    }

    // Middleware implementation
    return {
      // Enhanced methods...
    }
  }
}

This approach provides clear capability detection and graceful fallbacks for missing capabilities.

3. Composition Architecture Enhancement

Issue: Composition Pattern Limitations

The current composition pattern has some limitations in handling complex middleware and error propagation.

Enhance the composition pattern with middleware contexts and error handling:

  1. Add middleware context for sharing state:
typescript
// Middleware context for sharing state
interface MiddlewareContext {
  // Core context properties
  readonly adapter: ContentAdapter
  readonly options: Record<string, any>

  // State management
  getState<T>(key: string): T | undefined
  setState<T>(key: string, value: T): void

  // Metadata
  readonly middlewareChain: string[]
}

// Enhanced middleware type
type EnhancedMiddleware<T extends Function> = (
  next: T,
  context: MiddlewareContext
) => T

// Enhanced composition function
function composeWithContext<T extends Function>(
  base: T,
  middlewares: EnhancedMiddleware<T>[],
  options?: Record<string, any>
): T {
  const context: MiddlewareContext = {
    adapter: base as any,
    options: options || {},
    middlewareChain: middlewares.map(m => m.name || 'anonymous'),

    // State management
    state: new Map<string, any>(),
    getState<V>(key: string): V | undefined {
      return this.state.get(key) as V | undefined
    },
    setState<V>(key: string, value: V): void {
      this.state.set(key, value)
    },
  }

  return middlewares.reduce(
    (enhanced, middleware) => middleware(enhanced, context),
    base
  )
}
  1. Implement better error handling in middleware:
typescript
// Error handling middleware factory
function withErrorHandling(
  options?: ErrorHandlingOptions
): Middleware<ContentAdapter> {
  return (next, context) => ({
    async read(uri: string): Promise<Content> {
      try {
        return await next.read(uri)
      } catch (error) {
        // Enhance error with context
        const enhancedError = enhanceError(error, {
          operation: 'read',
          uri,
          middleware: 'ErrorHandlingMiddleware',
          middlewareChain: context.middlewareChain,
        })

        // Handle or transform error based on options
        if (options?.onError) {
          return options.onError(enhancedError, 'read', uri)
        }

        // Re-throw enhanced error
        throw enhancedError
      }
    },

    // Similar pattern for other methods
  })
}

// Utility to enhance errors with context
function enhanceError(error: any, context: Record<string, any>): ContentError {
  if (error instanceof ContentError) {
    // Add context to existing error
    error.addContext(context)
    return error
  }

  // Convert to ContentError
  return new ContentError(error.message || 'Unknown error', error, context)
}
  1. Create composition recipes for common patterns:
typescript
// Common middleware combinations
const createStandardStore = (
  adapter: ContentAdapter,
  options?: StoreOptions
) => {
  return createStore(
    composeWithContext(
      adapter,
      [
        withValidation(options?.schema),
        withErrorHandling(options?.errorHandling),
        withLogging(options?.logging),
        withCaching(options?.caching),
        withMetrics(options?.metrics),
      ],
      options
    )
  )
}

// Environment-specific recipes
const createBrowserStore = (options?: BrowserStoreOptions) => {
  const adapter = createIndexedDBAdapter(options?.storage)

  return createStandardStore(
    composeWithContext(
      adapter,
      [withOfflineSupport(options?.offline), withSyncThrottling(options?.sync)],
      options
    )
  )
}

This approach enhances the composition pattern with better context sharing and error handling.

4. Cross-Environment Architecture

Issue: Inconsistent Environment Handling

The current architecture has inconsistent patterns for handling environment-specific code.

Create a unified environment detection and adaptation system:

  1. Define environment detection interface:
typescript
// Environment detection API
interface Environment {
  // Environment type detection
  isNode: boolean
  isBrowser: boolean
  isServiceWorker: boolean
  isReactNative: boolean
  isDeno: boolean

  // Feature detection
  hasFeature(featureName: string): boolean

  // Environment-specific capabilities
  capabilities: {
    fs: boolean // File system access
    indexedDB: boolean // IndexedDB support
    localStorage: boolean // LocalStorage support
    serviceWorker: boolean // ServiceWorker API
    fetch: boolean // Fetch API
    // Other capabilities
  }

  // Environment metadata
  meta: {
    platform: string
    version: string
    userAgent?: string
    // Other metadata
  }
}
  1. Implement environment-aware factory functions:
typescript
// Environment-aware store factory
function createContentStore(options?: ContentStoreOptions): ContentStore {
  const env = detectEnvironment()

  // Select appropriate adapter based on environment
  let adapter: ContentAdapter

  if (env.isNode && env.hasFeature('fs')) {
    adapter = createFileSystemAdapter(options?.fs)
  } else if (env.isBrowser && env.hasFeature('indexedDB')) {
    adapter = createIndexedDBAdapter(options?.indexedDB)
  } else if (env.isBrowser && env.hasFeature('localStorage')) {
    adapter = createLocalStorageAdapter(options?.localStorage)
  } else {
    adapter = createMemoryAdapter(options?.memory)
  }

  // Apply environment-specific middleware
  const middlewares = [
    withErrorHandling(options?.errorHandling),
    withValidation(options?.validation),
    ...(options?.middleware || []),
  ]

  if (env.isBrowser && options?.offline) {
    middlewares.push(withOfflineSupport(options.offline))
  }

  if (env.isNode && options?.watching) {
    middlewares.push(withFileWatching(options.watching))
  }

  return createStore(composeWithContext(adapter, middlewares, options))
}
  1. Implement isomorphic feature detection:
typescript
// Feature detection utilities
const Environment = {
  // Detect current environment
  detect(): Environment {
    const isNode =
      typeof process !== 'undefined' &&
      process.versions != null &&
      process.versions.node != null

    const isBrowser =
      typeof window !== 'undefined' && typeof window.document !== 'undefined'

    const isServiceWorker =
      typeof self === 'object' &&
      self.constructor &&
      self.constructor.name === 'ServiceWorkerGlobalScope'

    // Feature detection
    const capabilities = {
      fs: isNode && this.hasRequire('fs'),
      indexedDB: typeof indexedDB !== 'undefined',
      localStorage: typeof localStorage !== 'undefined',
      serviceWorker:
        typeof navigator !== 'undefined' && 'serviceWorker' in navigator,
      fetch: typeof fetch !== 'undefined',
      // Other capabilities
    }

    return {
      isNode,
      isBrowser,
      isServiceWorker,
      isReactNative:
        typeof navigator !== 'undefined' && navigator.product === 'ReactNative',
      isDeno: typeof Deno !== 'undefined',

      hasFeature(feature: string): boolean {
        return !!capabilities[feature as keyof typeof capabilities]
      },

      capabilities,

      meta: {
        platform: this.getPlatform(),
        version: this.getVersion(),
        userAgent:
          typeof navigator !== 'undefined' ? navigator.userAgent : undefined,
      },
    }
  },

  // Helper to safely check for Node.js require
  hasRequire(module: string): boolean {
    try {
      return typeof require === 'function' && !!require.resolve(module)
    } catch (e) {
      return false
    }
  },

  // Get platform information
  getPlatform(): string {
    if (typeof process !== 'undefined' && process.platform) {
      return process.platform
    }

    if (typeof navigator !== 'undefined') {
      return navigator.platform || 'browser'
    }

    return 'unknown'
  },

  // Get version information
  getVersion(): string {
    if (typeof process !== 'undefined' && process.versions?.node) {
      return `Node.js ${process.versions.node}`
    }

    if (typeof navigator !== 'undefined' && navigator.userAgent) {
      return navigator.userAgent
    }

    return 'unknown'
  },
}

This approach provides a consistent way to handle environment-specific code across the system.

5. Plugin Architecture

Issue: Limited Extension Points

The current architecture lacks a formal plugin system for extending functionality.

Implement a modular plugin system:

  1. Define a plugin interface:
typescript
// Plugin interface
interface ContentPlugin<T = any> {
  // Plugin metadata
  readonly name: string
  readonly version: string
  readonly dependencies?: string[]

  // Lifecycle hooks
  onInstall(registry: PluginRegistry): void | Promise<void>
  onUninstall?(): void | Promise<void>

  // Plugin-specific functionality
  getFeatures(): Record<string, any>

  // Plugin configuration
  configure?(options: T): void
}

// Plugin registry
interface PluginRegistry {
  // Plugin registration
  register(plugin: ContentPlugin): void
  unregister(pluginName: string): void

  // Plugin discovery
  getPlugin<T extends ContentPlugin>(name: string): T | undefined
  hasPlugin(name: string): boolean
  listPlugins(): Array<{ name: string; version: string }>

  // Feature access
  getFeature<T>(pluginName: string, featureName: string): T | undefined

  // System integration
  getStore(): ContentStore
  getAdapter(): ContentAdapter

  // Events
  on(event: PluginEvent, callback: (data: any) => void): () => void
}

// Plugin event types
type PluginEvent =
  | 'plugin:registered'
  | 'plugin:unregistered'
  | 'feature:added'
  | 'feature:removed'
  1. Implement a plugin registry:
typescript
class DefaultPluginRegistry implements PluginRegistry {
  private plugins: Map<string, ContentPlugin> = new Map()
  private eventEmitter = new EventEmitter()
  private store: ContentStore
  private adapter: ContentAdapter

  constructor(store: ContentStore, adapter: ContentAdapter) {
    this.store = store
    this.adapter = adapter
  }

  async register(plugin: ContentPlugin): Promise<void> {
    if (this.plugins.has(plugin.name)) {
      throw new Error(`Plugin "${plugin.name}" is already registered`)
    }

    // Check dependencies
    if (plugin.dependencies) {
      for (const dep of plugin.dependencies) {
        if (!this.plugins.has(dep)) {
          throw new Error(
            `Plugin "${plugin.name}" depends on "${dep}" which is not registered`
          )
        }
      }
    }

    // Install plugin
    await plugin.onInstall?.(this)

    // Register plugin
    this.plugins.set(plugin.name, plugin)
    this.eventEmitter.emit('plugin:registered', { name: plugin.name })
  }

  // Other methods...
}
  1. Create plugin-based extensions:
typescript
// Example validation plugin
const validationPlugin: ContentPlugin<ValidationOptions> = {
  name: 'validation',
  version: '1.0.0',

  // Configuration
  options: {
    schemas: new Map(),
  },

  configure(options: ValidationOptions): void {
    this.options = { ...this.options, ...options }
  },

  // Installation
  onInstall(registry: PluginRegistry): void {
    const store = registry.getStore()
    const originalWrite = store.write.bind(store)

    // Enhance the store's write method
    Object.defineProperty(store, 'write', {
      value: async (uri: string, content: Content, options?: WriteOptions) => {
        // Get schema for content type
        const schema = this.options.schemas.get(content.contentType)

        // Validate if schema exists
        if (schema) {
          const result = validate(content, schema)
          if (!result.valid) {
            throw new ValidationError(uri, result.errors)
          }
        }

        // Proceed with original method
        return originalWrite(uri, content, options)
      },
    })
  },

  // Features
  getFeatures(): Record<string, any> {
    return {
      validate: (content: Content, schemaName: string) => {
        const schema = this.options.schemas.get(schemaName)
        if (!schema) {
          throw new Error(`Schema "${schemaName}" not found`)
        }
        return validate(content, schema)
      },

      addSchema: (name: string, schema: Schema) => {
        this.options.schemas.set(name, schema)
      },
    }
  },
}
  1. Document plugin system architecture:
Plugin System Architecture:

┌───────────────────────────────────────────────────────────────┐
│ Content System                                                 │
│                                                               │
│  ┌─────────────────────────┐        ┌─────────────────────┐   │
│  │                         │        │                     │   │
│  │     Content Store       │◄───────┤   Plugin Registry   │   │
│  │                         │        │                     │   │
│  └───────────┬─────────────┘        └─────────┬───────────┘   │
│              │                                │               │
│              │                                │               │
│              ▼                                ▼               │
│  ┌───────────────────────┐        ┌─────────────────────┐   │
│  │                       │        │                     │   │
│  │  Middleware Pipeline  │◄───────┤    Plugin System    │   │
│  │                       │        │                     │   │
│  └───────────┬───────────┘        └─────────────────────┘   │
│              │                                │               │
│              │                                │               │
│              ▼                                ▼               │
│  ┌───────────────────────┐        ┌─────────────────────┐   │
│  │                       │        │                     │   │
│  │   Content Adapter     │◄───────┤  Extension Points   │   │
│  │                       │        │                     │   │
│  └───────────────────────┘        └─────────────────────┘   │
│                                                               │
└───────────────────────────────────────────────────────────────┘

This approach provides a formalized plugin system for extending the content system with new functionality.

6. Error Handling Architecture

Issue: Inconsistent Error Handling

The current error handling is inconsistent across layers, making it difficult to trace and handle errors properly.

Implement a comprehensive error system:

  1. Define a consistent error hierarchy:
typescript
// Base error class
class ContentError extends Error {
  // Error metadata
  readonly code: string
  readonly uri?: string
  readonly operation?: string
  readonly cause?: Error
  readonly context: Record<string, any>

  constructor(
    message: string,
    options?: {
      code?: string
      uri?: string
      operation?: string
      cause?: Error
      context?: Record<string, any>
    }
  ) {
    super(message)
    this.name = this.constructor.name
    this.code = options?.code || 'UNKNOWN_ERROR'
    this.uri = options?.uri
    this.operation = options?.operation
    this.cause = options?.cause
    this.context = options?.context || {}

    // Capture stack trace
    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, this.constructor)
    }
  }

  // Add context to error
  addContext(context: Record<string, any>): this {
    this.context = { ...this.context, ...context }
    return this
  }

  // Convert to JSON for logging
  toJSON(): Record<string, any> {
    return {
      name: this.name,
      message: this.message,
      code: this.code,
      uri: this.uri,
      operation: this.operation,
      context: this.context,
      cause: this.cause
        ? this.cause instanceof ContentError
          ? this.cause.toJSON()
          : this.cause.message
        : undefined,
      stack: this.stack,
    }
  }
}

// Specific error types
class ContentNotFoundError extends ContentError {
  constructor(uri: string, cause?: Error) {
    super(`Content not found: ${uri}`, {
      code: 'CONTENT_NOT_FOUND',
      uri,
      operation: 'read',
      cause,
    })
  }
}

class ContentValidationError extends ContentError {
  readonly errors: any[]

  constructor(uri: string, errors: any[]) {
    super(
      `Validation failed for ${uri}: ${errors.map(e => e.message).join(', ')}`,
      {
        code: 'VALIDATION_ERROR',
        uri,
        operation: 'write',
      }
    )
    this.errors = errors
  }

  toJSON(): Record<string, any> {
    return {
      ...super.toJSON(),
      errors: this.errors,
    }
  }
}

// Additional error types...
  1. Implement consistent error handling middleware:
typescript
// Error handling middleware
function withErrorHandling(
  options?: ErrorHandlingOptions
): Middleware<ContentAdapter> {
  return next => ({
    async read(uri: string): Promise<Content> {
      try {
        return await next.read(uri)
      } catch (error) {
        // Transform standard errors to content errors
        if (error.code === 'ENOENT') {
          throw new ContentNotFoundError(uri, error)
        }

        // Enhance existing content errors
        if (error instanceof ContentError) {
          error.addContext({ operation: 'read', uri })
          throw error
        }

        // Create generic content error
        throw new ContentError(error.message || 'Read operation failed', {
          code: 'READ_ERROR',
          uri,
          cause: error,
        })
      }
    },

    // Similar implementations for other methods
  })
}
  1. Create error handling utilities:
typescript
// Error utilities
const ErrorUtils = {
  // Check if error is a specific type
  isContentError(error: any): error is ContentError {
    return error instanceof ContentError
  },

  isNotFoundError(error: any): error is ContentNotFoundError {
    return error instanceof ContentNotFoundError
  },

  // Convert any error to ContentError
  toContentError(error: unknown, context?: Record<string, any>): ContentError {
    if (error instanceof ContentError) {
      if (context) {
        error.addContext(context)
      }
      return error
    }

    if (error instanceof Error) {
      return new ContentError(error.message, {
        cause: error,
        context,
      })
    }

    return new ContentError(
      typeof error === 'string' ? error : 'Unknown error',
      { context }
    )
  },

  // Create error handler with recovery options
  createErrorHandler(options?: ErrorHandlerOptions) {
    return (error: unknown, operation: string, uri?: string) => {
      const contentError = this.toContentError(error, { operation, uri })

      // Log error
      if (options?.logErrors !== false) {
        console.error('Content system error:', contentError)
      }

      // Execute custom handler if provided
      if (options?.onError) {
        return options.onError(contentError)
      }

      // Retry logic
      if (options?.retry && options.retry.attempts > 0) {
        // Implementation of retry logic
      }

      // Re-throw by default
      throw contentError
    }
  },
}
  1. Document error handling patterns:
typescript
// Example usage in application code
try {
  const content = await contentStore.read('blog/post.md')
  // Process content
} catch (error) {
  if (ErrorUtils.isNotFoundError(error)) {
    // Handle not found specifically
    return showNotFoundPage()
  }

  if (error instanceof ContentValidationError) {
    // Handle validation errors
    return showValidationErrors(error.errors)
  }

  // Generic error handling
  logError(error)
  showErrorMessage('Failed to load content')
}

This approach provides a consistent error handling pattern throughout the system, making errors more traceable and recoverable.

Implementation Roadmap

Phase 1: Architecture Documentation

  1. Document the layer boundaries and responsibilities
  2. Create architecture diagrams showing component relationships
  3. Define clear interfaces for each layer
  4. Document transformation patterns between layers

Phase 2: Core Architecture Refinement

  1. Implement capability discovery system
  2. Enhance composition pattern with context and error handling
  3. Create environment detection and adaptation system
  4. Develop standardized error handling architecture

Phase 3: Extension Architecture

  1. Design and implement plugin system
  2. Create extension points for core functionality
  3. Develop plugin registry and discovery mechanisms
  4. Document plugin development patterns

Phase 4: Implementation and Migration

  1. Refactor core components to align with new architecture
  2. Implement reference adapters with consistent patterns
  3. Create middleware examples following best practices
  4. Develop migration guides for existing code

Conclusion

The proposed architectural refinements address key challenges in the current ReX content system while preserving its core strengths. By clarifying layer boundaries, standardizing patterns, and enhancing extensibility, these changes will:

  1. Improve code organization and maintainability
  2. Reduce developer confusion when working across layers
  3. Enable more consistent error handling and recovery
  4. Provide clear extension points for future growth
  5. Support a broader range of environments and use cases

Implementing these recommendations will create a more cohesive and robust architecture that better supports the system’s goals of flexibility, composability, and isomorphic operation.

Released under the MIT License.