Extensions Overview
Understanding the extension system in DeesseJS
Extensions Overview
Extensions are predefined interfaces that providers must implement to offer common functionality like caching, jobs, logging, etc. They provide a unified API while allowing different provider implementations.
Extensions vs Plugins
| Aspect | Extensions | Plugins |
|---|---|---|
| Purpose | Common infrastructure features | Application-specific features |
| Interface | Predefined by DeesseJS | Defined by plugin author |
| Providers | Multiple providers available | Single implementation |
| Use cases | Cache, jobs, logs, queues | SEO, analytics, custom fields |
| Dependencies | Plugins can depend on extensions | Extensions don't depend on plugins |
How Extensions Work
The Extension Interface
DeesseJS defines the interface:
// Extension interface defined by DeesseJS
interface CacheExtension {
get<T>(key: string): Promise<T | null>
set<T>(key: string, value: T, ttl?: number): Promise<void>
delete(key: string): Promise<void>
clear(): Promise<void>
}Provider Implementations
Different providers implement the same interface:
// Redis provider
import { Redis } from 'ioredis'
export class RedisCacheProvider implements CacheExtension {
private client: Redis
constructor(client: Redis) {
this.client = client
}
async get<T>(key: string): Promise<T | null> {
const value = await this.client.get(key)
return value ? JSON.parse(value) : null
}
async set<T>(key: string, value: T, ttl?: number): Promise<void> {
const serialized = JSON.stringify(value)
if (ttl) {
await this.client.setex(key, ttl, serialized)
} else {
await this.client.set(key, serialized)
}
}
async delete(key: string): Promise<void> {
await this.client.del(key)
}
async clear(): Promise<void> {
await this.client.flushdb()
}
}// Memory provider
export class MemoryCacheProvider implements CacheExtension {
private cache = new Map<string, { value: any; expires?: number }>()
async get<T>(key: string): Promise<T | null> {
const item = this.cache.get(key)
if (!item) return null
if (item.expires && Date.now() > item.expires) {
this.cache.delete(key)
return null
}
return item.value as T
}
async set<T>(key: string, value: T, ttl?: number): Promise<void> {
this.cache.set(key, {
value,
expires: ttl ? Date.now() + ttl * 1000 : undefined,
})
}
async delete(key: string): Promise<void> {
this.cache.delete(key)
}
async clear(): Promise<void> {
this.cache.clear()
}
}Configuration
Configure which provider to use:
// deesse.config.ts
import { defineConfig } from '@deessejs/core'
import { RedisCacheProvider } from '@deessejs/extensions-redis'
import { Redis } from 'ioredis'
const redis = new Redis(process.env.REDIS_URL)
export const config = defineConfig({
extensions: {
cache: {
provider: new RedisCacheProvider(redis),
options: {
prefix: 'deesse:',
defaultTTL: 3600,
},
},
},
})Available Extensions
Cache Extension
Caching layer with multiple providers.
Providers:
@deessejs/extensions-cache-memory- In-memory cache (default)@deessejs/extensions-cache-redis- Redis cache@deessejs/extensions-cache-memcached- Memcached cache
// Usage in your code
import { cache } from '@deessejs/extensions'
await cache.set('user:123', user, 3600)
const user = await cache.get('user:123')
await cache.delete('user:123')Queue Extension
Job queue for background tasks.
Providers:
@deessejs/extensions-queue-bullmq- BullMQ (Redis-based)@deessejs/extensions-queue-bull- Bull (Redis-based)@deessejs/extensions-queue-memory- In-memory queue
import { queue } from '@deessejs/extensions'
await queue.add('send-email', { to: 'user@example.com', subject: 'Hello' })
await queue.add('process-image', { imageId: 'abc123' })Logger Extension
Structured logging with multiple outputs.
Providers:
@deessejs/extensions-logger-pino- Pino logger@deessejs/extensions-logger-winston- Winston logger@deessejs/extensions-logger-console- Console logger (default)
import { logger } from '@deessejs/extensions'
logger.info('User logged in', { userId: '123' })
logger.error('Database connection failed', { error: err.message })
logger.debug('Cache stats', { hits: 100, misses: 5 })Storage Extension
File storage for uploads and assets.
Providers:
@deessejs/extensions-storage-local- Local filesystem@deessejs/extensions-storage-s3- AWS S3@deessejs/extensions-storage-cloudflare- Cloudflare R2@deessejs/extensions-storage-azure- Azure Blob Storage
import { storage } from '@deessejs/extensions'
const url = await storage.upload('uploads/avatar.jpg', file)
await storage.delete('uploads/avatar.jpg')
const stream = await storage.download('uploads/avatar.jpg')Search Extension
Full-text search capabilities.
Providers:
@deessejs/extensions-search-meilisearch- Meilisearch@deessejs/extensions-search-algolia- Algolia@deessejs/extensions-search-typesense- Typesense
import { search } from '@deessejs/extensions'
await search.index('posts').addDocuments([
{ id: 1, title: 'Hello World', content: '...' }
])
const results = await search.index('posts').search('hello')Lock Extension
Distributed locking for concurrent operations.
Providers:
@deessejs/extensions-lock-redis- Redis-based locks@deessejs/extensions-lock-postgres- PostgreSQL advisory locks@deessejs/extensions-lock-memory- In-memory locks
import { lock } from '@deessejs/extensions'
await lock.acquire('process-payment:123', async () => {
// Only one execution at a time
await processPayment(123)
}, { ttl: 30000 })Events Extension
Event bus for pub/sub messaging.
Providers:
@deessejs/extensions-events-redis- Redis pub/sub@deessejs/extensions-events-emitter- Node.js EventEmitter@deessejs/extensions-events-kafka- Apache Kafka
import { events } from '@deessejs/extensions'
// Publish
await events.publish('user.created', { userId: '123', email: 'user@example.com' })
// Subscribe
events.subscribe('user.created', async (data) => {
await sendWelcomeEmail(data.email)
})Plugin Dependencies
Plugins can declare required extensions:
// my-plugin/index.ts
export const myPlugin = definePlugin({
name: 'my-plugin',
extensions: {
// Require cache extension
cache: true,
// Require logger extension
logger: true,
// Optional search extension
search: false,
},
activate(options, { extensions }) {
// Extensions are available here
const { cache, logger, search } = extensions
return {
hooks: {
afterCreate: async ({ item }) => {
// Use cache extension
await cache.set(`item:${item.id}`, item, 3600)
// Use logger extension
logger.info('Item created', { id: item.id })
// Use search extension if available
if (search) {
await search.index('items').addDocuments([item])
}
},
},
}
},
})Extension Availability Check
Check if extensions are available before use:
export const pluginWithOptionalExtension = definePlugin({
name: 'my-plugin',
extensions: {
cache: false, // Optional
search: false, // Optional
},
activate(options, { extensions }) {
return {
hooks: {
afterCreate: async ({ item }) => {
// Check if cache is available
if (extensions.cache) {
await extensions.cache.set(`item:${item.id}`, item)
}
// Check if search is available
if (extensions.search) {
await extensions.search.index('items').addDocuments([item])
} else {
console.warn('Search extension not available')
}
},
},
}
},
})Multiple Providers
You can use multiple providers for the same extension:
export const config = defineConfig({
extensions: {
cache: {
// Primary cache
default: {
provider: new RedisCacheProvider(redis),
options: { prefix: 'cache:' },
},
// Session cache
sessions: {
provider: new RedisCacheProvider(redis),
options: { prefix: 'sessions:' },
},
// API cache (memory-based for speed)
api: {
provider: new MemoryCacheProvider(),
options: { maxSize: 1000 },
},
},
},
})Testing with Extensions
Use mock providers during testing:
// tests/setup.ts
import { MockCacheProvider, MockLoggerProvider } from '@deessejs/extensions/test'
export const config = defineConfig({
extensions: {
cache: {
provider: new MockCacheProvider(),
},
logger: {
provider: new MockLoggerProvider(),
},
},
})Performance Considerations
Choose the Right Provider
| Use Case | Recommended Provider |
|---|---|
| Development | Memory providers |
| Single-server production | Memory or Redis |
| Multi-server production | Redis, PostgreSQL |
| High-scale | Redis Cluster, dedicated services |
| Serverless | External services (Redis Cloud, Upstash) |
Extension Overhead
Extensions add minimal overhead:
- Interface abstraction: ~1-2ms
- Provider initialization: Once at startup
- Method calls: Depends on provider (Redis: ~1ms, Memory: ~0.1ms)
Best Practices
- Use appropriate providers - Memory for dev, Redis for production
- Configure TTLs - Set appropriate expiration times
- Handle failures gracefully - Cache/queue failures shouldn't break your app
- Monitor extension usage - Track cache hit rates, queue lengths
- Test with mocks - Use mock providers in tests
Next Steps
- Learn about Creating Extensions
- Explore Available Extensions
- Understand Extension Providers
- Return to Plugins