Skip to content

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

typescript
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

typescript
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:

typescript
function createContentRegistry(options?: RegistryOptions): ContentRegistry

Example:

typescript
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:

typescript
function getGlobalRegistry(): ContentRegistry

Example:

typescript
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:

typescript
// 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:

typescript
// 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:

typescript
// 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:

typescript
// 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:

typescript
// 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:

typescript
// 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:

typescript
// 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:

typescript
// 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:

typescript
// 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:

typescript
// 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:

typescript
// 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:

typescript
// 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:

typescript
// 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:

typescript
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:

typescript
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:

typescript
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

  1. Single Registry: Use a single registry instance per application
  2. Global Registry: For simple applications, use the global registry
  3. Store Identification: Use descriptive IDs for stores based on their purpose
  4. Plugin Management: Register plugins before using functionality that depends on them
  5. Resource Disposal: Always dispose the registry when finished to release resources
  6. Event Handling: Subscribe to registry events for dynamic applications
  7. URI Consistency: Maintain consistent URI schemes across your application
  8. Environment Awareness: Use auto-discovery or environment-specific configuration

Released under the MIT License.