Skip to content

Content Adapter API

The Content Adapter API provides the interface for storage implementations in the content system. This document details the adapter interface, factory functions, and usage patterns.

Core Interfaces

ContentAdapter Interface

typescript
interface ContentAdapter {
  // Required methods
  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[]>

  // Optional methods
  watch?(
    pattern: string,
    callback: WatchCallback,
    options?: WatchOptions
  ): Unsubscribe
  exists?(uri: string, options?: ExistsOptions): Promise<boolean>

  // Resources
  events?: EventEmitter
  dispose?(): Promise<void>
}

ContentAdapterFactory

typescript
type ContentAdapterFactory<T extends ContentAdapter = ContentAdapter> = (
  options?: ContentAdapterOptions
) => T

ContentAdapterOptions

typescript
interface ContentAdapterOptions {
  // Common options
  basePath?: string
  caseSensitive?: boolean
  defaultContentType?: string

  // Environment detection
  detectEnvironment?: boolean

  // Monitoring
  enableEvents?: boolean
  logging?: {
    level?: 'error' | 'warn' | 'info' | 'debug'
    logger?: Logger
  }

  // Performance
  concurrency?: number
  timeout?: number

  // Validation
  validateContent?: boolean
  contentSchema?: ContentSchema

  // Extensions
  extensions?: Record<string, any>
}

Factory Functions

createAdapter

Creates a generic adapter from implementation functions.

typescript
function createAdapter(implementation: {
  read: ContentAdapter['read']
  write: ContentAdapter['write']
  delete: ContentAdapter['delete']
  list: ContentAdapter['list']
  watch?: ContentAdapter['watch']
  exists?: ContentAdapter['exists']
  events?: EventEmitter
  dispose?: ContentAdapter['dispose']
}): ContentAdapter

Example:

typescript
import { createAdapter, createEventEmitter } from '@lib/content'

const adapter = createAdapter({
  read: async uri => {
    const content = await fetchFromCustomSource(uri)
    return {
      data: content.body,
      contentType: content.type,
      metadata: content.meta || {},
    }
  },

  write: async (uri, content) => {
    await saveToCustomSource(uri, {
      body: content.data,
      type: content.contentType,
      meta: content.metadata,
    })
  },

  delete: async uri => {
    await deleteFromCustomSource(uri)
  },

  list: async pattern => {
    const uris = await listFromCustomSource(pattern)
    return uris
  },

  events: createEventEmitter(),
})

createAdapterFactory

Creates a reusable adapter factory with default options.

typescript
function createAdapterFactory<T extends ContentAdapter>(
  factory: (options: Required<ContentAdapterOptions>) => T,
  defaultOptions?: ContentAdapterOptions
): ContentAdapterFactory<T>

Example:

typescript
import { createAdapterFactory } from '@lib/content/adapters'

const createCustomAdapter = createAdapterFactory<CustomAdapter>(
  options => {
    return new CustomAdapter(options)
  },
  {
    basePath: '/content',
    caseSensitive: true,
    enableEvents: true,
  }
)

// Later usage
const adapter = createCustomAdapter({
  // Override specific options
  basePath: '/custom-content',
})

Environment-Specific Factories

Node.js Adapters

createFileSystemAdapter

Creates an adapter for the local filesystem.

typescript
interface FileSystemAdapterOptions extends ContentAdapterOptions {
  basePath: string
  encoding?: string
  createMissingDirectories?: boolean
  watchOptions?: {
    ignoreInitial?: boolean
    ignorePermissionErrors?: boolean
    interval?: number
  }
}

function createFileSystemAdapter(
  options?: FileSystemAdapterOptions
): FileSystemAdapter

Example:

typescript
import { createFileSystemAdapter } from '@lib/content/adapters/node'

const fsAdapter = createFileSystemAdapter({
  basePath: '/var/content',
  createMissingDirectories: true,
  watchOptions: {
    ignoreInitial: true,
  },
})

Browser Adapters

createIndexedDBAdapter

Creates an adapter for browser IndexedDB storage.

typescript
interface IndexedDBAdapterOptions extends ContentAdapterOptions {
  databaseName?: string
  storeName?: string
  version?: number
  upgradeCallback?: (
    db: IDBDatabase,
    oldVersion: number,
    newVersion: number
  ) => void
}

function createIndexedDBAdapter(
  options?: IndexedDBAdapterOptions
): IndexedDBAdapter

Example:

typescript
import { createIndexedDBAdapter } from '@lib/content/adapters/browser'

const idbAdapter = createIndexedDBAdapter({
  databaseName: 'content-db',
  storeName: 'content-store',
  version: 1,
})

createLocalStorageAdapter

Creates an adapter for browser localStorage.

typescript
interface LocalStorageAdapterOptions extends ContentAdapterOptions {
  keyPrefix?: string
  storage?: Storage
}

function createLocalStorageAdapter(
  options?: LocalStorageAdapterOptions
): LocalStorageAdapter

Example:

typescript
import { createLocalStorageAdapter } from '@lib/content/adapters/browser'

const lsAdapter = createLocalStorageAdapter({
  keyPrefix: 'content:',
})

Common Adapters

createMemoryAdapter

Creates an in-memory adapter.

typescript
interface MemoryAdapterOptions extends ContentAdapterOptions {
  initialContent?: Record<string, Content>
  persistenceKey?: string
}

function createMemoryAdapter(options?: MemoryAdapterOptions): MemoryAdapter

Example:

typescript
import { createMemoryAdapter } from '@lib/content/adapters/common'

const memoryAdapter = createMemoryAdapter({
  initialContent: {
    'article/welcome.md': {
      data: '# Welcome\n\nThis is a sample article.',
      contentType: 'text/markdown',
      metadata: {
        title: 'Welcome',
        createdAt: new Date(),
      },
    },
  },
  persistenceKey: 'memory-content',
})

createHttpAdapter

Creates an adapter for HTTP/HTTPS resources.

typescript
interface HttpAdapterOptions extends ContentAdapterOptions {
  baseUrl?: string
  headers?: Record<string, string>
  fetchOptions?: RequestInit
  authentication?: {
    type: 'basic' | 'bearer' | 'custom'
    credentials?: {
      username: string
      password: string
    }
    token?: string
    headerFactory?: () => Promise<Record<string, string>>
  }
}

function createHttpAdapter(options?: HttpAdapterOptions): HttpAdapter

Example:

typescript
import { createHttpAdapter } from '@lib/content/adapters/common'

const httpAdapter = createHttpAdapter({
  baseUrl: 'https://api.example.com/content',
  headers: {
    Accept: 'application/json',
  },
  authentication: {
    type: 'bearer',
    token: 'your-access-token',
  },
})

Composite Adapters

createFallbackAdapter

Creates an adapter that falls back to secondary adapters when primary fails.

typescript
function createFallbackAdapter(
  adapters: ContentAdapter[],
  options?: {
    writeAll?: boolean
    deleteAll?: boolean
  }
): ContentAdapter

Example:

typescript
import {
  createFallbackAdapter,
  createHttpAdapter,
  createMemoryAdapter,
  createFileSystemAdapter,
} from '@lib/content/adapters'

const fallbackAdapter = createFallbackAdapter(
  [
    createHttpAdapter({ baseUrl: 'https://api.example.com/content' }),
    createMemoryAdapter({ persistenceKey: 'offline-content' }),
    createFileSystemAdapter({ basePath: '/local/content' }),
  ],
  {
    writeAll: true,
  }
)

createMirrorAdapter

Creates an adapter that mirrors operations across multiple adapters.

typescript
function createMirrorAdapter(
  adapters: ContentAdapter[],
  options?: {
    readStrategy?: 'first-success' | 'primary-only' | 'all-compare'
    writeStrategy?: 'all' | 'primary-only' | 'at-least-one'
    listStrategy?: 'merge' | 'primary-only'
  }
): ContentAdapter

Example:

typescript
import {
  createMirrorAdapter,
  createFileSystemAdapter,
  createMemoryAdapter,
} from '@lib/content/adapters'

const mirrorAdapter = createMirrorAdapter(
  [
    createFileSystemAdapter({ basePath: '/primary/content' }),
    createFileSystemAdapter({ basePath: '/backup/content' }),
    createMemoryAdapter(),
  ],
  {
    readStrategy: 'first-success',
    writeStrategy: 'all',
  }
)

Transformation Adapters

createTransformAdapter

Creates an adapter that transforms content during operations.

typescript
function createTransformAdapter(
  baseAdapter: ContentAdapter,
  transformations: {
    read?: (content: Content, uri: string) => Promise<Content> | Content
    write?: (content: Content, uri: string) => Promise<Content> | Content
    uri?: (uri: string) => string
  }
): ContentAdapter

Example:

typescript
import {
  createTransformAdapter,
  createFileSystemAdapter,
} from '@lib/content/adapters'
import { compressContent, decompressContent } from '@lib/content/transformers'

const compressingAdapter = createTransformAdapter(
  createFileSystemAdapter({ basePath: '/content' }),
  {
    read: async content => decompressContent(content),
    write: async content => compressContent(content),
  }
)

Adapter Selection

createAutoAdapter

Creates an adapter that selects appropriate implementation based on environment and URI.

typescript
function createAutoAdapter(options?: {
  adapters?: ContentAdapter[]
  detectEnvironment?: boolean
  fallbackAdapter?: ContentAdapter
}): ContentAdapter

Example:

typescript
import { createAutoAdapter } from '@lib/content/adapters'

const autoAdapter = createAutoAdapter({
  detectEnvironment: true,
})

Adapter Events

Adapters can emit events for content changes:

typescript
// Subscribe to all content changes
adapter.events.on('change', (uri, content, changeType) => {
  console.log(`Content ${changeType} at ${uri}`)
})

// Subscribe to specific change types
adapter.events.on('create', (uri, content) => {
  console.log(`Content created at ${uri}`)
})

adapter.events.on('update', (uri, content) => {
  console.log(`Content updated at ${uri}`)
})

adapter.events.on('delete', uri => {
  console.log(`Content deleted at ${uri}`)
})

Performance Considerations

  • Concurrency: Control parallel operations with concurrency option
  • Batching: Group operations when possible for better performance
  • Lazy loading: Use list() then read() for selective loading
  • Caching: Consider composite adapters with memory caching
  • Resource disposition: Call dispose() when adapter no longer needed

Best Practices

  1. URI Consistency: Use consistent URI formats across adapters
  2. Error Handling: Catch and handle specific adapter error types
  3. Resource Management: Call dispose() when finished with adapters
  4. Content Validation: Validate content before write operations
  5. Event Subscriptions: Clean up event subscriptions with returned unsubscribe functions
  6. Environment Detection: Use environment-specific adapters for best performance
  7. Composite Adapters: Consider fallback strategies for resilience
  8. Adapter Selection: Use createAutoAdapter for automatic environment detection

Released under the MIT License.