Content Registry API
The Content Registry API provides a central registry for content stores, adapters, and plugins. It serves as a facade for accessing and managing multiple content stores and their resources.
Core Interfaces
ContentRegistry Interface
interface ContentRegistry {
// Store management
registerStore(id: string, store: ContentStore): void
unregisterStore(id: string): void
getStore(id: string): ContentStore | undefined
getDefaultStore(): ContentStore
setDefaultStore(id: string): void
listStores(): string[]
// Adapter management
registerAdapter(scheme: string, factory: ContentAdapterFactory): void
unregisterAdapter(scheme: string): void
getAdapter(scheme: string): ContentAdapterFactory | undefined
listAdapters(): string[]
// Plugin management
registerPlugin(id: string, plugin: ContentPlugin): void
unregisterPlugin(id: string): void
getPlugin<T extends ContentPlugin = ContentPlugin>(id: string): T | undefined
listPlugins(): string[]
// Content operations (delegated to stores)
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[]>
exists(uri: string, options?: ExistsOptions): Promise<boolean>
watch(
pattern: string,
callback: WatchCallback,
options?: WatchOptions
): Unsubscribe
// Registry events
events: EventEmitter
// Resources
dispose(): Promise<void>
}
RegistryOptions
interface RegistryOptions {
// Initial stores
stores?: Record<string, ContentStore>
defaultStoreId?: string
// Initial adapters
adapters?: Record<string, ContentAdapterFactory>
// Initial plugins
plugins?: Record<string, ContentPlugin>
// Configuration
environment?: 'node' | 'browser' | 'auto'
autoDiscover?: boolean
// Events
enableEvents?: boolean
}
Factory Functions
createContentRegistry
Creates a new content registry:
function createContentRegistry(options?: RegistryOptions): ContentRegistry
Example:
import {
createContentRegistry,
createContentStore,
createFileSystemAdapter,
} from '@lib/content'
// Create registry with initial stores
const registry = createContentRegistry({
stores: {
local: createContentStore({
adapter: createFileSystemAdapter({ basePath: '/content' }),
}),
memory: createContentStore({
adapter: createMemoryAdapter(),
}),
},
defaultStoreId: 'local',
autoDiscover: true,
})
getGlobalRegistry
Gets or creates the global singleton registry instance:
function getGlobalRegistry(): ContentRegistry
Example:
import { getGlobalRegistry } from '@lib/content'
// Get global registry instance
const registry = getGlobalRegistry()
// Use global registry
const content = await registry.read('articles/welcome.md')
Usage Patterns
Basic Store Management
Managing content stores:
// Register a new store
registry.registerStore(
'remote',
createContentStore({
adapter: createHttpAdapter({ baseUrl: 'https://api.example.com/content' }),
})
)
// Get a store by ID
const remoteStore = registry.getStore('remote')
// Set default store
registry.setDefaultStore('remote')
// Use default store (implicitly)
const content = await registry.read('articles/welcome.md')
// Unregister a store
registry.unregisterStore('remote')
Adapter Registration
Managing adapters:
// Register custom adapter factory
registry.registerAdapter('custom', createCustomAdapterFactory())
// Create store with custom adapter
const store = createContentStore({
adapter: registry.getAdapter('custom')!({ basePath: '/custom' }),
})
registry.registerStore('custom-store', store)
// Unregister adapter
registry.unregisterAdapter('custom')
Plugin Registration
Managing plugins:
// Register a plugin
registry.registerPlugin(
'versioning',
createVersioningPlugin({
storage: 'localStorage',
maxVersions: 10,
})
)
// Get a plugin
const versioningPlugin = registry.getPlugin<VersioningPlugin>('versioning')
const versions = await versioningPlugin.getVersions('articles/welcome.md')
// Unregister a plugin
registry.unregisterPlugin('versioning')
URI Resolution
The registry resolves URIs to appropriate stores:
// Explicit store selection with URI scheme
const content = await registry.read('local:articles/welcome.md')
// Implicit store selection based on URI
const remoteContent = await registry.read(
'http://api.example.com/content/data.json'
)
// Default store for plain paths
const localContent = await registry.read('articles/welcome.md')
Content Operations
Delegated content operations:
// Read operation is delegated to appropriate store
const content = await registry.read('articles/welcome.md')
// Write operation is delegated to appropriate store
await registry.write('articles/welcome.md', {
data: '# Welcome\n\nThis is a welcome article.',
contentType: 'text/markdown',
metadata: { title: 'Welcome' },
})
// Delete operation is delegated to appropriate store
await registry.delete('articles/outdated.md')
// List operation is delegated to appropriate store
const articleUris = await registry.list('articles/*.md')
// Watch operation is delegated to appropriate store
const unsubscribe = registry.watch(
'articles/*.md',
(uri, content, changeType) => {
console.log(`Content ${changeType}: ${uri}`)
updateUI(uri, content)
}
)
Registry Events
Listen for registry events:
// Store registration events
registry.events.on('store:registered', ({ id, store }) => {
console.log(`Store registered: ${id}`)
updateStoreList()
})
registry.events.on('store:unregistered', ({ id }) => {
console.log(`Store unregistered: ${id}`)
updateStoreList()
})
// Adapter registration events
registry.events.on('adapter:registered', ({ scheme, factory }) => {
console.log(`Adapter registered: ${scheme}`)
updateAdapterList()
})
// Plugin registration events
registry.events.on('plugin:registered', ({ id, plugin }) => {
console.log(`Plugin registered: ${id}`)
updatePluginList()
})
// Default store changes
registry.events.on('defaultStore:changed', ({ id }) => {
console.log(`Default store changed to: ${id}`)
updateDefaultStoreIndicator(id)
})
Advanced Features
Auto-Discovery
Registry can automatically discover adapters and plugins:
// Create registry with auto-discovery
const registry = createContentRegistry({
autoDiscover: true,
})
// Auto-discovery will:
// 1. Detect environment (Node.js or browser)
// 2. Register appropriate built-in adapters
// 3. Find and register plugins in the environment
// 4. Create appropriate default store
Store Routing
Configure custom store routing rules:
// Create registry with routing configuration
const registry = createContentRegistry({
routing: {
rules: [
// Route based on URI pattern
{
pattern: 'articles/**/*.md',
storeId: 'content-store',
},
// Route based on content type
{
contentType: 'application/json',
storeId: 'data-store',
},
// Route based on custom function
{
matcher: uri => uri.includes('private'),
storeId: 'secure-store',
},
],
// Default store when no rules match
defaultStoreId: 'fallback-store',
},
})
Plugin Dependencies
Handle plugin dependencies:
// Register plugins with dependencies
registry.registerPlugin(
'search',
createSearchPlugin({
requires: ['indexing'],
})
)
registry.registerPlugin(
'versioning',
createVersioningPlugin({
requires: [],
})
)
registry.registerPlugin('indexing', createIndexingPlugin())
// Initialize plugins in correct order
registry.initializePlugins()
Lazy Instantiation
Create stores on demand:
// Create registry with store factories
const registry = createContentRegistry({
storeFactories: {
'large-store': () =>
createContentStore({
adapter: createFileSystemAdapter({ basePath: '/large-content' }),
}),
},
})
// Store is created on first use
const largeContent = await registry.read('large-store:data/large-file.bin')
Environment Support
Node.js Environment
Registry behavior in Node.js:
// In Node.js environment
const registry = createContentRegistry({
environment: 'node',
adapters: {
file: createFileSystemAdapter,
http: createHttpAdapter,
},
defaultStoreId: 'file-store',
stores: {
'file-store': createContentStore({
adapter: createFileSystemAdapter({ basePath: '/content' }),
}),
},
})
Browser Environment
Registry behavior in browser:
// In browser environment
const registry = createContentRegistry({
environment: 'browser',
adapters: {
indexeddb: createIndexedDBAdapter,
http: createHttpAdapter,
},
defaultStoreId: 'browser-store',
stores: {
'browser-store': createContentStore({
adapter: createIndexedDBAdapter({ databaseName: 'content-db' }),
}),
},
})
Universal (Isomorphic) Code
Registry in universal code:
// In universal code
const registry = createContentRegistry({
environment: 'auto',
// Registry will detect environment and:
// - In Node.js: use filesystem and http adapters
// - In Browser: use indexeddb and http adapters
autoDiscover: true,
})
Error Handling
Registry Errors
Handling registry-specific errors:
try {
// Get non-existent store
const store = registry.getStore('missing-store')
if (!store) {
throw new Error('Store not found')
}
await store.read('some-content.md')
} catch (error) {
if (error.message === 'Store not found') {
console.error('The specified store does not exist in the registry')
// Handle missing store
} else {
console.error('Operation error:', error)
// Handle operation error
}
}
Operation Delegation Errors
Handling errors during operation delegation:
try {
// This will delegate to the appropriate store based on URI
await registry.read('unknown-scheme:content.md')
} catch (error) {
if (error instanceof UnsupportedSchemeError) {
console.error(`No adapter available for scheme: ${error.scheme}`)
// Handle unsupported scheme
} else if (error instanceof StoreNotFoundError) {
console.error(`No store available for URI: ${error.uri}`)
// Handle missing store
} else {
console.error('Operation error:', error)
// Handle operation error
}
}
Resource Management
Registry Disposal
Properly dispose registry resources:
async function shutdownApplication() {
// Get registry
const registry = getGlobalRegistry()
// Dispose all resources
await registry.dispose()
console.log('All content resources disposed')
}
// Later, when application is shutting down
await shutdownApplication()
Best Practices
- Single Registry: Use a single registry instance per application
- Global Registry: For simple applications, use the global registry
- Store Identification: Use descriptive IDs for stores based on their purpose
- Plugin Management: Register plugins before using functionality that depends on them
- Resource Disposal: Always dispose the registry when finished to release resources
- Event Handling: Subscribe to registry events for dynamic applications
- URI Consistency: Maintain consistent URI schemes across your application
- Environment Awareness: Use auto-discovery or environment-specific configuration