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
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
type ContentAdapterFactory<T extends ContentAdapter = ContentAdapter> = (
options?: ContentAdapterOptions
) => T
ContentAdapterOptions
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.
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:
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.
function createAdapterFactory<T extends ContentAdapter>(
factory: (options: Required<ContentAdapterOptions>) => T,
defaultOptions?: ContentAdapterOptions
): ContentAdapterFactory<T>
Example:
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.
interface FileSystemAdapterOptions extends ContentAdapterOptions {
basePath: string
encoding?: string
createMissingDirectories?: boolean
watchOptions?: {
ignoreInitial?: boolean
ignorePermissionErrors?: boolean
interval?: number
}
}
function createFileSystemAdapter(
options?: FileSystemAdapterOptions
): FileSystemAdapter
Example:
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.
interface IndexedDBAdapterOptions extends ContentAdapterOptions {
databaseName?: string
storeName?: string
version?: number
upgradeCallback?: (
db: IDBDatabase,
oldVersion: number,
newVersion: number
) => void
}
function createIndexedDBAdapter(
options?: IndexedDBAdapterOptions
): IndexedDBAdapter
Example:
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.
interface LocalStorageAdapterOptions extends ContentAdapterOptions {
keyPrefix?: string
storage?: Storage
}
function createLocalStorageAdapter(
options?: LocalStorageAdapterOptions
): LocalStorageAdapter
Example:
import { createLocalStorageAdapter } from '@lib/content/adapters/browser'
const lsAdapter = createLocalStorageAdapter({
keyPrefix: 'content:',
})
Common Adapters
createMemoryAdapter
Creates an in-memory adapter.
interface MemoryAdapterOptions extends ContentAdapterOptions {
initialContent?: Record<string, Content>
persistenceKey?: string
}
function createMemoryAdapter(options?: MemoryAdapterOptions): MemoryAdapter
Example:
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.
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:
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.
function createFallbackAdapter(
adapters: ContentAdapter[],
options?: {
writeAll?: boolean
deleteAll?: boolean
}
): ContentAdapter
Example:
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.
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:
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.
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:
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.
function createAutoAdapter(options?: {
adapters?: ContentAdapter[]
detectEnvironment?: boolean
fallbackAdapter?: ContentAdapter
}): ContentAdapter
Example:
import { createAutoAdapter } from '@lib/content/adapters'
const autoAdapter = createAutoAdapter({
detectEnvironment: true,
})
Adapter Events
Adapters can emit events for content changes:
// 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()
thenread()
for selective loading - Caching: Consider composite adapters with memory caching
- Resource disposition: Call
dispose()
when adapter no longer needed
Best Practices
- URI Consistency: Use consistent URI formats across adapters
- Error Handling: Catch and handle specific adapter error types
- Resource Management: Call
dispose()
when finished with adapters - Content Validation: Validate content before write operations
- Event Subscriptions: Clean up event subscriptions with returned unsubscribe functions
- Environment Detection: Use environment-specific adapters for best performance
- Composite Adapters: Consider fallback strategies for resilience
- Adapter Selection: Use createAutoAdapter for automatic environment detection