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.
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
function createMemoryAdapter(options?: MemoryAdapterOptions): ContentAdapter
Creates a new Memory adapter instance.
Options
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
async read(uri: string, options?: ReadOptions): Promise<Content>;
Reads content from memory.
Parameters:
uri
: Content URIoptions
: Optional read configuration
Returns:
- A Promise resolving to a Content object
Example:
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
async write(uri: string, content: Content, options?: WriteOptions): Promise<void>;
Writes content to memory.
Parameters:
uri
: Content URIcontent
: Content object to writeoptions
: Optional write configuration
Example:
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
async delete(uri: string, options?: DeleteOptions): Promise<void>;
Deletes content from memory.
Parameters:
uri
: Content URIoptions
: Optional delete configuration
Example:
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
async list(pattern?: string, options?: ListOptions): Promise<string[]>;
Lists content URIs matching the pattern.
Parameters:
pattern
: Optional glob pattern for content URIsoptions
: Optional list configuration
Returns:
- A Promise resolving to an array of content URIs
Example:
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
async exists(uri: string, options?: ExistsOptions): Promise<boolean>;
Checks if content exists in memory.
Parameters:
uri
: Content URIoptions
: Optional exists configuration
Returns:
- A Promise resolving to a boolean indicating existence
Example:
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
watch(pattern: string, listener: WatchListener): Unsubscribe;
Watches for content changes in memory.
Parameters:
pattern
: Glob pattern for content URIs to watchlistener
: Callback function for change events
Returns:
- An unsubscribe function to stop watching
Example:
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
async dispose(): Promise<void>;
Cleans up resources used by the adapter.
Example:
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:
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:
// 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:
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:
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:
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:
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