Skip to content

Memory Adapter

The Memory adapter provides ephemeral in-memory storage for the content system. It offers a lightweight, environment-agnostic implementation ideal for testing, prototyping, and temporary content storage.

Overview

The Memory adapter works across all JavaScript environments (browser, Node.js, service workers) and provides fast access to content stored directly in memory. It supports content indexing, event notifications for changes, and optional time-to-live for cached content.

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

const adapter = createMemoryAdapter({
  initialContent: {
    'welcome.md': {
      data: '# Welcome',
      contentType: 'text/markdown',
      metadata: { title: 'Welcome' },
    },
  },
})

API Reference

Creation

typescript
function createMemoryAdapter(options?: MemoryAdapterOptions): ContentAdapter

Creates a new Memory adapter instance.

Options

typescript
interface MemoryAdapterOptions extends ContentAdapterOptions {
  /**
   * Initial content to populate the store (optional)
   */
  initialContent?: Record<string, Content>

  /**
   * Whether to use TTL (time-to-live) expiration (default: false)
   */
  useTtl?: boolean

  /**
   * Default TTL in milliseconds (default: 3600000 - 1 hour)
   */
  defaultTtl?: number

  /**
   * Storage capacity limit (default: 1000 items)
   */
  capacity?: number

  /**
   * Eviction policy when capacity is reached
   * (default: 'lru' - least recently used)
   */
  evictionPolicy?: 'lru' | 'fifo' | 'none'

  /**
   * Flag to enable case-insensitive URI matching (default: false)
   */
  caseInsensitive?: boolean
}

Methods

read

typescript
async read(uri: string, options?: ReadOptions): Promise<Content>;

Reads content from memory.

Parameters:

  • uri: Content URI
  • options: Optional read configuration

Returns:

  • A Promise resolving to a Content object

Example:

typescript
const content = await adapter.read('articles/welcome.md')
console.log(content.data) // Content data as string
console.log(content.metadata) // Associated metadata

Implementation Details:

  • Normalizes URI based on configuration
  • Retrieves content from internal memory store
  • Returns a deep copy to prevent modification of internal state
  • Throws ContentNotFoundError if the URI doesn’t exist

write

typescript
async write(uri: string, content: Content, options?: WriteOptions): Promise<void>;

Writes content to memory.

Parameters:

  • uri: Content URI
  • content: Content object to write
  • options: Optional write configuration

Example:

typescript
await adapter.write('articles/welcome.md', {
  data: '# Welcome\n\nThis is a welcome article.',
  contentType: 'text/markdown',
  metadata: {
    title: 'Welcome',
    createdAt: new Date(),
  },
})

Implementation Details:

  • Normalizes URI based on configuration
  • Creates a deep copy of the content to store
  • Updates or creates content entry in memory store
  • Applies TTL if configured
  • Emits change events if events are enabled
  • Performs capacity checks and eviction if necessary

delete

typescript
async delete(uri: string, options?: DeleteOptions): Promise<void>;

Deletes content from memory.

Parameters:

  • uri: Content URI
  • options: Optional delete configuration

Example:

typescript
await adapter.delete('articles/outdated-article.md')

Implementation Details:

  • Normalizes URI based on configuration
  • Removes content from internal memory store
  • Emits delete event if events are enabled
  • Returns successfully even if content doesn’t exist, unless options.failIfNotFound is true

list

typescript
async list(pattern?: string, options?: ListOptions): Promise<string[]>;

Lists content URIs matching the pattern.

Parameters:

  • pattern: Optional glob pattern for content URIs
  • options: Optional list configuration

Returns:

  • A Promise resolving to an array of content URIs

Example:

typescript
const uris = await adapter.list('articles/**/*.md')
console.log(uris) // ['articles/welcome.md', 'articles/guide.md', ...]

Implementation Details:

  • Returns all URIs if no pattern is provided
  • Uses minimatch or similar for glob pattern matching
  • Supports sorting and filtering based on options
  • Supports pagination with limit and offset

exists

typescript
async exists(uri: string, options?: ExistsOptions): Promise<boolean>;

Checks if content exists in memory.

Parameters:

  • uri: Content URI
  • options: Optional exists configuration

Returns:

  • A Promise resolving to a boolean indicating existence

Example:

typescript
const exists = await adapter.exists('articles/welcome.md')
if (exists) {
  console.log('Article exists in memory')
}

Implementation Details:

  • Normalizes URI based on configuration
  • Checks for presence in internal memory store
  • Fast operation with O(1) complexity

watch

typescript
watch(pattern: string, listener: WatchListener): Unsubscribe;

Watches for content changes in memory.

Parameters:

  • pattern: Glob pattern for content URIs to watch
  • listener: Callback function for change events

Returns:

  • An unsubscribe function to stop watching

Example:

typescript
const unsubscribe = adapter.watch(
  'articles/**/*.md',
  (uri, content, changeType) => {
    console.log(`Content ${uri} was ${changeType}`)
  }
)

// Later, stop watching
unsubscribe()

Implementation Details:

  • Uses internal event emitter to track changes
  • Filters events based on the provided pattern
  • Returns function to unsubscribe from events

dispose

typescript
async dispose(): Promise<void>;

Cleans up resources used by the adapter.

Example:

typescript
await adapter.dispose()

Implementation Details:

  • Clears the internal memory store
  • Removes all event listeners
  • Releases any other resources

URI Handling

The Memory adapter provides flexible URI handling:

typescript
function normalizeUri(uri: string, caseInsensitive: boolean): string {
  // Remove leading/trailing whitespace
  let normalized = uri.trim()

  // Ensure no leading slash (relative URIs)
  normalized = normalized.replace(/^\/+/, '')

  // Convert case if case-insensitive mode is enabled
  if (caseInsensitive) {
    normalized = normalized.toLowerCase()
  }

  return normalized
}

Event Handling

The Memory adapter implements a simple event system:

typescript
// Create event emitter
const events = createEventEmitter<ContentChangeEvent>()

// Subscribe to changes
events.on('change', event => {
  console.log(`Content ${event.uri} was ${event.type}`)
})

// Emit change events
function emitChange(
  uri: string,
  content: Content | null,
  type: 'create' | 'update' | 'delete'
): void {
  events.emit('change', {
    uri,
    content,
    type,
    timestamp: Date.now(),
  })
}

TTL and Expiration

When TTL is enabled, the Memory adapter handles content expiration:

typescript
interface ContentEntry {
  content: Content
  expiry?: number // Timestamp when content expires
  lastAccessed: number // For LRU policy
}

function isExpired(entry: ContentEntry): boolean {
  return entry.expiry !== undefined && entry.expiry <= Date.now()
}

function setExpiry(entry: ContentEntry, ttl?: number): void {
  if (ttl && ttl > 0) {
    entry.expiry = Date.now() + ttl
  }
}

Capacity Management

The Memory adapter includes capacity management to prevent unbounded memory growth:

typescript
function enforceCapacity(): void {
  if (!options.capacity || store.size <= options.capacity) {
    return
  }

  const excess = store.size - options.capacity

  switch (options.evictionPolicy) {
    case 'lru':
      // Evict least recently used items
      const entries = Array.from(store.entries())
        .sort(([, a], [, b]) => a.lastAccessed - b.lastAccessed)
        .slice(0, excess)

      for (const [uri] of entries) {
        store.delete(uri)
      }
      break

    case 'fifo':
      // Evict oldest items (first in)
      const keys = Array.from(store.keys()).slice(0, excess)
      for (const uri of keys) {
        store.delete(uri)
      }
      break

    case 'none':
    default:
      // No eviction, allow overflow
      break
  }
}

Deep Cloning

The Memory adapter uses deep cloning to prevent modification of internal state:

typescript
function deepClone<T>(obj: T): T {
  // Simple implementation
  return JSON.parse(JSON.stringify(obj))

  // Production implementation would handle non-JSON-serializable objects
  // such as Date, Map, Set, and circular references
}

Performance Considerations

The Memory adapter is optimized for performance:

  • In-Memory Storage: Provides fast O(1) access to content
  • Copy on Read/Write: Prevents mutation of internal state
  • Minimal Serialization: Stores objects directly in memory
  • Efficient Indexing: Uses Map/Set for fast lookups
  • Controlled Memory Usage: Capacity limits and eviction policies

Environment Compatibility

This adapter works in all JavaScript environments:

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

// Works in any environment
const adapter = createMemoryAdapter()

Use Cases

The Memory adapter is particularly well-suited for:

  • Testing: Provides a clean, isolated environment for tests
  • Prototyping: Rapid development without external dependencies
  • Caching: Temporary storage for frequently accessed content
  • Demo Applications: Self-contained examples without backend
  • Offline/Fallback: Storage when other adapters are unavailable

Released under the MIT License.