@owlmeans/redis-resource
v0.1.1
Published
The **@owlmeans/redis-resource** package provides Redis-based resource storage implementation for OwlMeans Common Libraries, designed for fullstack microservices and microclients development with focus on security and proper authentication and authorizati
Readme
@owlmeans/redis-resource
The @owlmeans/redis-resource package provides Redis-based resource storage implementation for OwlMeans Common Libraries, designed for fullstack microservices and microclients development with focus on security and proper authentication and authorization.
Purpose
This package serves as a Redis integration layer for the OwlMeans resource system that:
- Provides Redis-backed storage for resources with high performance and scalability
- Supports pub/sub messaging for real-time data synchronization across services
- Implements full CRUD operations with Redis-optimized performance
- Offers TTL support for automatic expiration of records
- Enables horizontal scaling through Redis clustering
- Integrates with context system for dependency injection and service management
Core Concepts
Redis Resource
A Redis resource is a storage implementation that uses Redis as the backend, providing fast key-value storage with advanced features like pub/sub, TTL, and clustering support.
Key Management
Records are stored in Redis using a configurable prefix and key structure, allowing for namespace separation and efficient data organization.
Pub/Sub Integration
The package provides built-in pub/sub functionality for real-time data synchronization, enabling reactive updates across distributed systems.
Database Service Integration
Integrates with the OwlMeans database service pattern, allowing for connection pooling and database management.
API Reference
Factory Functions
makeRedisResource<R, T>(alias, dbAlias?, serviceAlias?, makeCustomResource?): T
Creates a new Redis resource instance.
import { makeRedisResource } from '@owlmeans/redis-resource'
interface User {
id: string
name: string
email: string
}
const userResource = makeRedisResource<User>('users', 'redis-main')Parameters:
alias: string - Resource alias for context registrationdbAlias: string (optional) - Database alias, defaults toDEFAULT_DB_ALIAS('redis')serviceAlias: string (optional) - Service alias, defaults toDEFAULT_DB_ALIASmakeCustomResource: ResourceMaker (optional) - Custom resource factory function
Returns: RedisResource instance with full CRUD and pub/sub capabilities
Resource Methods
The Redis resource implements the standard Resource<ResourceRecord> interface with additional Redis-specific methods:
Core CRUD Operations
get<T>(id, field?, opts?): Promise<T>
Retrieves a record by ID or field value. Throws UnknownRecordError if not found.
const user = await userResource.get<User>('user-123')load<T>(id, field?, opts?): Promise<T | null>
Loads a record by ID or field value. Returns null if not found.
const user = await userResource.load<User>('user-123')
if (user) {
console.log('User found:', user.name)
}create<T>(record, opts?): Promise<T>
Creates a new record in Redis storage.
const user = await userResource.create({
id: 'user-123',
name: 'John Doe',
email: '[email protected]'
}, { ttl: 3600 }) // TTL in secondsOptions:
ttl: number | Date - Time to live in seconds (number) or expiration date (Date)
update<T>(record, opts?): Promise<T>
Updates an existing record.
const updatedUser = await userResource.update({
id: 'user-123',
name: 'John Smith',
email: '[email protected]'
}, { ttl: 7200 })save<T>(record, opts?): Promise<T>
Creates or updates a record (upsert operation).
const user = await userResource.save({
id: 'user-123',
name: 'John Doe',
email: '[email protected]'
})delete(id, opts?): Promise<T | null>
Deletes a record and returns it, or null if not found.
const deletedUser = await userResource.delete('user-123')pick<T>(id, opts?): Promise<T>
Deletes a record and returns it. Throws UnknownRecordError if not found.
const pickedUser = await userResource.pick('user-123')list<T>(criteria?, opts?): Promise<ListResult<T>>
Lists records with pagination support.
const result = await userResource.list<User>({}, {
pager: { page: 0, size: 20 }
})
console.log('Users:', result.items)
console.log('Total:', result.pager?.total)Redis-Specific Methods
key(key?): string
Generates a Redis key with the configured prefix.
const redisKey = userResource.key('user-123')
console.log(redisKey) // "prefix:user-123"subscribe<Type>(handler, key?): Promise<() => Promise<void>>
Subscribes to Redis pub/sub messages for real-time updates.
const unsubscribe = await userResource.subscribe<User>(
async (user) => {
console.log('User updated:', user)
},
'user-updates'
)
// Later, unsubscribe
await unsubscribe()Parameters:
handler: (value: Type) => Promise - Message handler functionkey: SubOpts (optional) - Subscription options or key
publish<Type>(value, key?): Promise<void>
Publishes a message to Redis pub/sub.
await userResource.publish<User>({
id: 'user-123',
name: 'John Doe',
email: '[email protected]'
}, 'user-updates')Types
RedisResource<T>
Main interface for Redis resources extending the base Resource interface.
interface RedisResource<T extends ResourceRecord> extends Resource<T> {
name?: string
db: RedisDb
key: (key?: string) => string
subscribe: <Type extends T>(handler: (value: Type) => Promise<void>, key?: SubOpts) => Promise<() => Promise<void>>
publish: <Type extends T>(value: Type, key?: string) => Promise<void>
}RedisDb
Redis database connection interface.
interface RedisDb {
client: RedisCommander
prefix: string
}RedisDbService
Service interface for Redis database management.
interface RedisDbService extends ResourceDbService<RedisDb, RedisClient> {}RedisClient
Type alias for Redis client (supports both single Redis and Cluster).
type RedisClient = Redis | ClusterSubscriptionOptions
Options for Redis subscriptions.
interface SubscriptionOptions extends LifecycleOptions {
key?: string
once?: boolean
}Constants
DEFAULT_DB_ALIAS
Default database alias for Redis connections.
const DEFAULT_DB_ALIAS = 'redis'DEFAULT_PAGE_SIZE
Default page size for list operations.
const DEFAULT_PAGE_SIZE = 10Usage Examples
Basic Usage
import { makeRedisResource } from '@owlmeans/redis-resource'
interface Product {
id: string
name: string
price: number
category: string
}
// Create Redis resource
const productResource = makeRedisResource<Product>('products')
// Create products
await productResource.create({
id: 'prod-1',
name: 'Laptop',
price: 999.99,
category: 'electronics'
})
await productResource.create({
id: 'prod-2',
name: 'Book',
price: 19.99,
category: 'books'
}, { ttl: 86400 }) // Expires in 24 hours
// Retrieve products
const laptop = await productResource.get('prod-1')
console.log('Product:', laptop.name, laptop.price)
// List all products
const { items, pager } = await productResource.list()
console.log('Total products:', pager?.total)Context Integration
import { makeRedisResource } from '@owlmeans/redis-resource'
import { makeServerContext, makeServerConfig } from '@owlmeans/server-context'
import { AppType } from '@owlmeans/context'
// Create server context
const config = makeServerConfig(AppType.Backend, 'product-service')
const context = makeServerContext(config)
// Create and register Redis resource
const productResource = makeRedisResource<Product>('products', 'redis-main')
context.registerResource(productResource)
// Configure and initialize context
context.configure()
await context.init()
// Use resource through context
const resource = context.resource<typeof productResource>('products')Pub/Sub Usage
import { makeRedisResource } from '@owlmeans/redis-resource'
const orderResource = makeRedisResource<Order>('orders')
// Subscribe to order updates
const unsubscribe = await orderResource.subscribe<Order>(
async (order) => {
console.log('Order updated:', order.id, order.status)
// Process order update
if (order.status === 'completed') {
await processCompletedOrder(order)
}
},
{ key: 'order-updates', once: false }
)
// Publish order updates
await orderResource.publish({
id: 'order-123',
status: 'completed',
total: 299.99
}, 'order-updates')
// Clean up subscription
await unsubscribe()TTL and Expiration
const sessionResource = makeRedisResource<Session>('sessions')
// Create session with TTL
await sessionResource.create({
id: 'session-abc123',
userId: 'user-456',
createdAt: new Date()
}, { ttl: 3600 }) // Expires in 1 hour
// Update with new expiration
await sessionResource.update({
id: 'session-abc123',
lastActivity: new Date()
}, { ttl: new Date(Date.now() + 7200000) }) // Expires in 2 hoursReal-time Synchronization
// Service A - Updates user data
const userResource = makeRedisResource<User>('users')
await userResource.update({
id: 'user-123',
lastLogin: new Date(),
status: 'online'
})
// Notify other services
await userResource.publish({
id: 'user-123',
event: 'status-change',
status: 'online'
}, 'user-events')
// Service B - Listens for user events
const unsubscribe = await userResource.subscribe<UserEvent>(
async (event) => {
console.log('User event:', event.event, event.id)
await updateLocalCache(event)
},
'user-events'
)Error Handling
import { UnknownRecordError, RecordExists, MisshapedRecord } from '@owlmeans/resource'
const userResource = makeRedisResource<User>('users')
try {
// This will throw UnknownRecordError
await userResource.get('non-existent-user')
} catch (error) {
if (error instanceof UnknownRecordError) {
console.log('User not found:', error.message)
}
}
try {
await userResource.create({ id: 'user-1', name: 'Alice', email: '[email protected]' })
// This will throw RecordExists
await userResource.create({ id: 'user-1', name: 'Bob', email: '[email protected]' })
} catch (error) {
if (error instanceof RecordExists) {
console.log('User already exists:', error.message)
// Use save() for upsert instead
await userResource.save({ id: 'user-1', name: 'Bob', email: '[email protected]' })
}
}Advanced Filtering and Pagination
const productResource = makeRedisResource<Product>('products')
// List with pagination
const page1 = await productResource.list({}, {
pager: { page: 0, size: 10 }
})
const page2 = await productResource.list({}, {
pager: { page: 1, size: 10 }
})
console.log('Page 1:', page1.items.length)
console.log('Page 2:', page2.items.length)
console.log('Total products:', page1.pager?.total)Integration Patterns
Microservices Architecture
// Product Service
const productResource = makeRedisResource<Product>('products', 'redis-products')
// Order Service
const orderResource = makeRedisResource<Order>('orders', 'redis-orders')
// Shared event bus
const eventResource = makeRedisResource<Event>('events', 'redis-events')
// Cross-service communication
await productResource.subscribe<ProductEvent>(
async (event) => {
if (event.type === 'price-updated') {
await orderResource.publish({
type: 'product-price-changed',
productId: event.productId,
newPrice: event.price
}, 'order-events')
}
},
'product-events'
)Caching Layer
// Use Redis resource as a caching layer
const cacheResource = makeRedisResource<CacheEntry>('cache')
const getCachedData = async <T>(key: string, fetcher: () => Promise<T>): Promise<T> => {
try {
const cached = await cacheResource.load(key)
if (cached && cached.expiresAt > Date.now()) {
return cached.data as T
}
} catch (error) {
// Cache miss, continue to fetch
}
const data = await fetcher()
// Cache for 5 minutes
await cacheResource.save({
id: key,
data,
expiresAt: Date.now() + 300000
}, { ttl: 300 })
return data
}
// Usage
const userData = await getCachedData(`user:${userId}`, () => fetchUserFromDB(userId))Performance Considerations
Connection Management
- Connection Pooling - Use Redis connection pools for high-traffic applications
- Cluster Support - Leverage Redis clustering for horizontal scaling
- Pipeline Operations - Batch multiple operations for better performance
Data Organization
- Key Prefixes - Use meaningful prefixes to organize data and avoid conflicts
- TTL Management - Set appropriate TTL values to manage memory usage
- Pagination - Use pagination for large datasets to avoid memory issues
Best Practices
- Use appropriate TTL - Set TTL for temporary data to prevent memory bloat
- Monitor Redis memory - Keep track of Redis memory usage and key counts
- Implement error handling - Handle Redis connection failures gracefully
- Use pub/sub wisely - Avoid creating too many subscriptions that could impact performance
- Batch operations - Use pipelines for multiple operations when possible
Error Handling
The package uses standard error types from @owlmeans/resource:
UnknownRecordError- Thrown when trying to access a non-existent recordRecordExists- Thrown when trying to create a record with an existing IDMisshapedRecord- Thrown when record structure is invalidUnsupportedArgumentError- Thrown when unsupported arguments are provided
Additional Redis-specific errors may be thrown by the underlying ioredis client.
Dependencies
This package depends on:
@owlmeans/context- For contextual resource management@owlmeans/resource- For resource interface and error typesioredis- Redis client library for Node.js
Related Packages
@owlmeans/resource- Base resource interface and utilities@owlmeans/mongo-resource- MongoDB resource implementation@owlmeans/static-resource- In-memory resource implementation@owlmeans/server-context- Server-side context management
