node-red-contrib-redis-variable 1.2.0

A comprehensive Node-RED node for Redis operations with universal payload-based configuration, automatic JSON handling, SSL/TLS support, and advanced pattern matching with pagination

npm install node-red-contrib-redis-variable

node-red-contrib-redis-variable

A comprehensive Node-RED node for Redis operations with flexible connection management and modern features.

Developed by Andrii Lototskyi

🚀 Features

🔧 Flexible Connection Management

  • Multiple Input Types: Host, port, database, username, password support string values, flow/global context, and environment variables
  • Secure Credentials: String values stored encrypted in Node-RED credentials
  • Runtime Resolution: Context and environment variables resolved at runtime
  • SSL/TLS Support: Full SSL/TLS encryption with client certificates and custom CAs
  • Advanced Configuration: JSON-based advanced options for Redis client

📊 Comprehensive Redis Operations

Universal Payload Interface

  • Flexible Input: All operations use msg.payload for parameters
  • Simple Format: Single keys can be passed as strings
  • Object Format: Complex operations use structured objects
  • Consistent Returns: Standardized response format across all operations

Basic Operations

  • GET - Retrieve value by key
  • SET - Store value with optional TTL
  • DEL - Delete single or multiple keys
  • EXISTS - Check if single or multiple keys exist
  • MATCH - Find keys by pattern using SCAN

TTL Operations

  • TTL - Get remaining time to live in seconds
  • EXPIRE - Set expiration time for existing key
  • PERSIST - Remove expiration from key

Counter Operations

  • INCR - Increment value by 1
  • DECR - Decrement value by 1
  • INCRBY - Increment value by N
  • DECRBY - Decrement value by N

List Operations

  • LPUSH - Add element to beginning of list
  • RPUSH - Add element to end of list
  • LPOP - Remove and return first element
  • RPOP - Remove and return last element
  • LLEN - Get list length
  • LRANGE - Get range of elements

Hash Operations

  • HSET - Set hash field value (single or multiple fields)
  • HGET - Get hash field value
  • HGETALL - Get all hash fields and values
  • HDEL - Delete hash field(s)

Pub/Sub Operations

  • PUBLISH - Publish message to channel

Installation

npm install node-red-contrib-redis-variable

Or install directly through the Node-RED palette manager.

Configuration

Redis Configuration Node

The Redis configuration node supports flexible connection parameters:

Connection Settings

  • Host: Redis server hostname or IP address
  • Port: Redis server port (default: 6379)
  • Database: Redis database number (default: 0)
  • Cluster Mode: Enable for Redis Cluster deployments

Authentication

  • Username: Redis username (Redis 6.0+ ACL support)
  • Password: Redis password for authentication

Credential Sources

All connection parameters support multiple input types:

  • String: Direct value stored securely in Node-RED credentials
  • Flow Context: Retrieved from flow context variables
  • Global Context: Retrieved from global context variables
  • Environment Variable: Retrieved from environment variables

Advanced Options

JSON object with additional ioredis connection options:

{
  "connectTimeout": 10000,
  "lazyConnect": true,
  "keepAlive": 30000,
  "family": 4,
  "retryDelayOnFailover": 100
}

SSL/TLS Configuration

The module provides comprehensive SSL/TLS support for secure Redis connections:

SSL Settings
  • Enable SSL/TLS: Enable secure connection to Redis server
  • Verify Certificate: Validate server certificates (recommended for production)
  • Client Certificate: Client certificate for mutual TLS authentication
  • Private Key: Private key corresponding to client certificate
  • CA Certificate: Certificate Authority certificate for custom CAs
SSL Examples

Basic SSL (server verification only):

Enable SSL/TLS: ✓
Verify Certificate: ✓
Client Certificate: (empty)
Private Key: (empty)
CA Certificate: (empty)

Mutual TLS (client + server authentication):

Enable SSL/TLS: ✓
Verify Certificate: ✓
Client Certificate: Your client certificate
Private Key: Your private key
CA Certificate: Custom CA if needed

Self-signed certificates:

Enable SSL/TLS: ✓
Verify Certificate: ✗ (disable for self-signed)
CA Certificate: Your self-signed CA

Environment-based SSL configuration:

Enable SSL/TLS: ✓
Client Certificate Type: Environment Variable → TLS_CLIENT_CERT
Private Key Type: Environment Variable → TLS_PRIVATE_KEY
CA Certificate Type: Environment Variable → TLS_CA_CERT

Example Configurations

Environment-based Configuration

Host: Environment Variable → REDIS_HOST
Port: Environment Variable → REDIS_PORT
Password: Environment Variable → REDIS_PASSWORD

Context-based Configuration

Host: Global Context → redis_config.host
Port: Global Context → redis_config.port
Password: Flow Context → redis_password

Setting up Global Context Variables:

  1. In Node-RED Admin Panel:

    • Go to AdminContextGlobal
    • Add variables:
      • redis_config.host = your-redis-host
      • redis_config.port = 6379
      • redis_config.password = your-redis-password
  2. Via Function Node:

    // Set global context variables
    flow.set("redis_config", {
        host: "your-redis-host",
        port: 6379,
        password: "your-redis-password"
    });
    
  3. Via HTTP API:

    curl -X POST http://localhost:1880/context/global/redis_config \
    -H "Content-Type: application/json" \
    -d '{"host":"your-redis-host","port":6379,"password":"your-redis-password"}'
    

Troubleshooting Global Context:

  • Ensure variable names match exactly (case-sensitive)
  • Check Node-RED logs for context lookup messages
  • Verify global context variables are set before Redis operations

Testing Global Context Setup:

  1. Set up test variables:

    // In a Function node
    flow.set("redis_config", {
        host: "localhost",
        port: 6379,
        password: "your-password"
    });
    
  2. Test connection:

    // In another Function node
    msg.payload = "test_key";
    return msg;
    
  3. Check logs:

    • Enable debug mode: NODE_RED_DEBUG=1 node-red
    • Look for context lookup messages in Node-RED logs
    • Verify connection parameters are correct

Operations

Universal Payload Interface: All Redis operations use a unified msg.payload interface. Parameters can be passed as simple strings (for single keys) or as objects with specific properties. This provides flexibility while maintaining simplicity.

Basic Operations

GET - Retrieve Value

// Simple key format
msg.payload = "user:123";
// Returns: { payload: "stored_value" }

// Object format
msg.payload = { 
    key: "user:123" 
};
// Returns: { payload: "stored_value" }

SET - Store Value

// Simple SET
msg.payload = {
  key: "user:123",
  value: "John Doe"
};
// Returns: { payload: { success: true, result: "OK", ttl: null } }

// SET with TTL
msg.payload = {
  key: "session:abc123",
  value: { userId: 42, role: "admin" },
  ttl: 3600
};
// Returns: { payload: { success: true, result: "OK", ttl: 3600 } }

DEL - Delete Key

// Delete single key
msg.payload = { 
    key: "mykey" 
};
// Or simple format
msg.payload = "mykey";
// Returns: { payload: { success: true, deleted: 1, keys: ["mykey"] } }

// Delete multiple keys
msg.payload = { 
    keys: ["key1", "key2", "key3"] 
};
// Returns: { payload: { success: true, deleted: 3, keys: ["key1", "key2", "key3"] } }

EXISTS - Check Key Existence

// Check single key
msg.payload = "mykey";
// Or object format
msg.payload = { 
    key: "mykey" 
};
// Returns: { payload: { exists: true, count: 1, keys: ["mykey"] } }

// Check multiple keys
msg.payload = { 
    keys: ["key1", "key2", "key3"] 
};
// Returns: { payload: { exists: true, count: 2, keys: ["key1", "key2", "key3"] } }

MATCH - Find Keys by Pattern

// Simple pattern
msg.payload = "user:*";
// Returns: { payload: { pattern: "user:*", keys: ["user:123", "user:456"], count: 2, scanned: true } }

// Pattern with custom count
msg.payload = {
  pattern: "session:*",
  count: 50
};
// Returns: { payload: { pattern: "session:*", keys: ["session:abc123", "session:def456"], count: 2, limit: 50, scanned: true } }

// Pattern with pagination (cursor)
msg.payload = {
  pattern: "user:*",
  count: 30,
  cursor: "12345"
};
// Returns: { payload: { pattern: "user:*", keys: ["user:31", "user:32"], count: 2, limit: 30, cursor: "67890", startCursor: "12345", scanned: true, truncated: false } }

// Pattern with skip (skip first N keys)
msg.payload = {
  pattern: "session:*",
  count: 30,
  skip: 100
};
// Returns: { payload: { pattern: "session:*", keys: ["session:101", "session:102"], count: 2, limit: 30, cursor: "67890", startCursor: 0, scanned: true, truncated: false } }

// Complex patterns
msg.payload = "cache:page:*";  // All cache pages
msg.payload = "temp:*:data";   // Temporary data keys
msg.payload = "user:*:profile"; // User profiles

Advanced MATCH Features:

  • Pattern Matching: Uses Redis SCAN with pattern matching for efficient key discovery
  • Count Limit: Limit the number of keys returned (default: 100)
  • Cursor Pagination: Use cursor parameter for efficient pagination through large datasets
  • Skip Keys: Use skip parameter to skip the first N matching keys
  • Performance Optimized: Uses Redis SCAN for non-blocking operation on large datasets

Response Format:

{
  "pattern": "user:*",
  "keys": ["user:1", "user:2", "user:3"],
  "count": 3,                    // Number of keys returned
  "limit": 50,                   // Requested limit
  "cursor": "67890",             // Next cursor for pagination
  "startCursor": "0",            // Starting cursor
  "scanned": true,               // Operation completed
  "truncated": false             // true if results were limited by count
}

Pagination Example:

// First request
msg.payload = {
  pattern: "session:*",
  count: 30
};

// Response contains cursor for next page
// Use that cursor in next request
msg.payload = {
  pattern: "session:*",
  count: 30,
  cursor: "67890"  // from previous response
};

// Continue until cursor becomes "0" (end of results)

TTL - Get Time To Live

msg.payload = "mykey";
// Returns: { payload: { key: "mykey", ttl: 3600, status: "expires in 3600 seconds" } }

EXPIRE - Set Key Expiration

msg.payload = {
  key: "mykey",
  ttl: 3600
};
// Returns: { payload: { success: true, key: "mykey", ttl: 3600, message: "Expiration set" } }

PERSIST - Remove Expiration

msg.payload = "mykey";
// Returns: { payload: { success: true, key: "mykey", message: "Expiration removed" } }

INCR/DECR - Increment/Decrement

// Simple increment
msg.payload = "counter";
// Returns: { payload: { key: "counter", value: 1 } }

// Increment by amount
msg.payload = {
  key: "score",
  amount: 10
};
// Returns: { payload: { key: "score", value: 110, increment: 10 } }

List Operations

LPUSH/RPUSH - Add to List

msg.payload = {
  key: "mylist",
  value: "item1"
};
// Returns: { payload: { success: true, key: "mylist", length: 1, operation: "lpush" } }

LPOP/RPOP - Remove from List

msg.payload = "mylist";
// Returns: { payload: "item1" }

LLEN - Get List Length

msg.payload = "mylist";
// Returns: { payload: { key: "mylist", length: 5 } }

LRANGE - Get List Range

msg.payload = {
  key: "mylist",
  start: 0,
  stop: -1
};
// Returns: { payload: { key: "mylist", range: {start: 0, stop: -1}, values: ["item1", "item2", "item3"], count: 3 } }

BLPOP/BRPOP - Blocking Pop

Configure timeout in node settings. These operations run continuously and emit messages when items are available.

Hash Operations

HSET - Set Hash Field

// Single field
msg.payload = {
  key: "myhash",
  field: "name",
  value: "John"
};
// Returns: { payload: { success: true, key: "myhash", field: "name", created: true } }

// Multiple fields
msg.payload = {
  key: "myhash",
  fields: {
    name: "John",
    age: 30,
    city: "New York"
  }
};
// Returns: { payload: { success: true, key: "myhash", fields: ["name", "age", "city"], created: 3 } }

HGET - Get Hash Field

msg.payload = {
  key: "myhash",
  field: "name"
};
// Returns: { payload: "John" }

HGETALL - Get All Hash Fields

msg.payload = "myhash";
// Returns: { payload: { name: "John", age: "30", city: "New York" } }

HDEL - Delete Hash Field

// Delete single field
msg.payload = {
  key: "myhash",
  field: "age"
};
// Returns: { payload: { success: true, key: "myhash", deleted: 1, fields: ["age"] } }

// Delete multiple fields
msg.payload = {
  key: "myhash",
  fields: ["age", "city"]
};
// Returns: { payload: { success: true, key: "myhash", deleted: 2, fields: ["age", "city"] } }

Pub/Sub Operations

PUBLISH - Publish Message

msg.payload = {
  channel: "mychannel",
  message: "Hello World"
};
// Returns: { payload: { success: true, channel: "mychannel", subscribers: 2, message: "Hello World" } }

SUBSCRIBE - Subscribe to Channel

Configure channel in node settings. Messages are automatically emitted:

// Received message format:
{
  topic: "mychannel",
  payload: "Hello World"
}

PSUBSCRIBE - Pattern Subscribe

Configure pattern in node settings (e.g., "news.*"):

// Received message format:
{
  pattern: "news.*",
  topic: "news.sports",
  payload: "Sports update"
}

Advanced Operations

Lua Script Execution

// Configure Lua script in node editor:
// return redis.call('GET', KEYS[1])

msg.payload = ["mykey"];  // Array of keys and arguments
// Returns: { payload: "script_result" }

Redis Instance in Context

Stores Redis client in flow or global context for direct access:

// Access stored instance
const redis = flow.get("redis_client");
const result = await redis.get("mykey");

🔄 Automatic JSON Handling

The module automatically detects and handles JSON data without any configuration:

🤖 Smart Detection

  • Objects: JavaScript objects are automatically serialized to JSON strings when storing
  • JSON Strings: Valid JSON strings are automatically parsed back to objects when retrieving
  • Simple Values: Strings, numbers, and other simple types are handled as-is
  • Arrays: Each item in Redis lists is automatically parsed if it's valid JSON

📝 Examples

Storing Objects

msg.payload = {
  key: "user:123",
  value: {
    name: "John Doe",
    age: 30,
    preferences: {
      theme: "dark",
      language: "en"
    }
  }
};
// Automatically stored as: '{"name":"John Doe","age":30,"preferences":{"theme":"dark","language":"en"}}'

Retrieving Objects

msg.payload = "user:123";
// Returns: {
//   "name": "John Doe",
//   "age": 30,
//   "preferences": {
//     "theme": "dark", 
//     "language": "en"
//   }
// }

Mixed Data Types

// Store simple string
msg.payload = {
    key: "message", 
    value: "Hello World"
};
// Returns: "Hello World"

// Store number  
msg.payload = {
    key: "count", 
    value: 42
};
// Returns: "42"

// Store object
msg.payload = {
    key: "config", 
    value: {
        debug: true, 
        timeout: 5000
    }
};
// Returns: {debug: true, timeout: 5000}

Connection Management

Connection Pooling

  • Connections are automatically pooled and reused across nodes
  • Each configuration creates a single connection pool
  • Connections are automatically cleaned up when nodes are removed

Blocking Operations

For blocking operations (BLPOP, BRPOP, Lua scripts), enable "Force new connection" to prevent blocking other operations.

Error Handling

All operations include comprehensive error handling:

// Error response format:
{
  payload: {
    error: "Connection failed: ECONNREFUSED"
  }
}

Security Best Practices

  1. Use Environment Variables: Store sensitive credentials in environment variables
  2. Enable Redis AUTH: Always use password authentication in production
  3. Use Redis ACLs: Implement fine-grained access control (Redis 6.0+)
  4. Enable SSL/TLS: Use encrypted connections for production environments
  5. Verify Certificates: Always verify server certificates in production
  6. Secure Certificate Storage: Store certificates and keys in environment variables or secure context
  7. Network Security: Use TLS/SSL for connections over untrusted networks
  8. Principle of Least Privilege: Grant minimal required permissions

Examples

Basic Key-Value Storage

// Store user session
msg.payload = {
  key: "session:abc123",
  value: {
    userId: 456,
    loginTime: new Date().toISOString(),
    permissions: ["read", "write"]
  },
  ttl: 3600  // 1 hour expiration
};

Message Queue with Lists

// Producer - Add task to queue
msg.payload = {
  key: "task_queue",
  value: {
    id: "task_001",
    type: "email",
    data: { to: "[email protected]", subject: "Welcome" }
  }
};

// Consumer (using BLPOP)
// Configure BLPOP operation in node settings
// Automatically receives tasks as they're added

Caching with Expiration

// Cache API response for 1 hour
msg.payload = {
  key: "api_cache:users",
  value: apiResponse,
  ttl: 3600  // 1 hour
};

Real-time Notifications

// Publisher
msg.payload = {
  channel: "notifications:user:123",
  message: {
    type: "message",
    from: "user:456",
    content: "Hello there!"
  }
};

// Subscriber automatically receives notifications

Key Discovery and Cleanup

// Find all temporary keys
msg.payload = "temp:*";
// Returns: { pattern: "temp:*", keys: ["temp:cache1", "temp:cache2", "temp:session123"], count: 3, scanned: true }

// Find expired session keys
msg.payload = {
  pattern: "session:*:expired",
  count: 50
};
// Returns: { pattern: "session:*:expired", keys: ["session:abc:expired", "session:def:expired"], count: 2, scanned: true }

// Clean up old cache entries
msg.payload = "cache:old:*";
// Use returned keys with DEL operation for cleanup

📖 Usage Examples

Basic Operations

GET Operation

// Simple format
msg.payload = "user:123";

// Object format
msg.payload = {
    key: "user:123"
};
// Returns: "John Doe" (or stored value)

SET Operation

// Simple SET
msg.payload = {
  key: "user:123",
  value: "John Doe"
};

// SET with TTL (expires in 1 hour)
msg.payload = {
  key: "session:abc123",
  value: {userId: 42, role: "admin"},
  ttl: 3600
};
// Returns: { success: true, result: "OK", ttl: 3600 }

DELETE Operations

// Delete single key
msg.payload = {
    key: "temp:data"
};

// Delete multiple keys
msg.payload = {
    keys: ["cache:page1", "cache:page2", "temp:data"]
};
// Returns: { success: true, deleted: 3, keys: [...] }

MATCH Operations

// Find all user keys
msg.payload = "user:*";
// Returns: { pattern: "user:*", keys: ["user:123", "user:456", "user:789"], count: 3, scanned: true }

// Find session keys with custom scan count
msg.payload = {
  pattern: "session:*",
  count: 25
};
// Returns: { pattern: "session:*", keys: ["session:abc123", "session:def456"], count: 2, scanned: true }

// Find cache keys
msg.payload = "cache:*";
// Returns: { pattern: "cache:*", keys: ["cache:page1", "cache:page2", "cache:api"], count: 3, scanned: true }

TTL Operations

Check TTL

msg.payload = "session:abc123";
// Returns: { key: "session:abc123", ttl: 2847, status: "expires in 2847 seconds" }

Set Expiration

msg.payload = {
  key: "temp:data",
  ttl: 1800
};
// Returns: { success: true, key: "temp:data", ttl: 1800, message: "Expiration set" }

Remove Expiration

msg.payload = "permanent:key";
// Returns: { success: true, key: "permanent:key", message: "Expiration removed" }

Counter Operations

Increment Counter

// Simple increment
msg.payload = "page:views";
// Returns: { key: "page:views", value: 1547 }

// Increment by amount
msg.payload = {
  key: "score:player1",
  amount: 100
};
// Returns: { key: "score:player1", value: 2350, increment: 100 }

List Operations

Add to List

msg.payload = {
  key: "queue:tasks",
  value: {
      task: "process_order", 
      id: 12345
  }
};
// Returns: { success: true, key: "queue:tasks", length: 8, operation: "lpush" }

Get List Range

msg.payload = {
  key: "queue:tasks",
  start: 0,
  stop: 4
};
// Returns: { key: "queue:tasks", range: {start: 0, stop: 4}, values: [...], count: 5 }

Pop from List

msg.payload = "queue:tasks";
// Returns: {"task": "process_order", "id": 12345} (first item)

Hash Operations

Set Hash Fields

// Single field
msg.payload = {
  key: "user:123",
  field: "email",
  value: "[email protected]"
};
// Returns: { success: true, key: "user:123", field: "email", created: true }

// Multiple fields
msg.payload = {
  key: "user:123",
  fields: {
    name: "John Doe",
    age: 30,
    city: "New York",
    active: true
  }
};
// Returns: { success: true, key: "user:123", fields: ["name", "age", "city", "active"], created: 4 }

Get Hash Data

// Get single field
msg.payload = {
  key: "user:123",
  field: "email"
};
// Returns: "[email protected]"

// Get all fields
msg.payload = "user:123";
// Returns: { name: "John Doe", age: "30", city: "New York", email: "[email protected]", active: "true" }

Pub/Sub Operations

Publish Message

msg.payload = {
  channel: "notifications",
  message: {
    type: "alert",
    text: "System maintenance in 5 minutes",
    timestamp: "2024-01-15T10:30:00Z"
  }
};
// Returns: { success: true, channel: "notifications", subscribers: 3, message: "..." }

Troubleshooting

SSL/TLS Connection Issues

If you encounter the error "Protocol error, got "\u0015" as reply type byte", this indicates an SSL/TLS configuration problem:

Solution: Disable certificate verification in the SSL/TLS configuration:

  1. Enable SSL/TLS in the configuration node
  2. Uncheck "Verify Certificate" (Reject unauthorized certificates)
  3. This allows connections to servers with self-signed or invalid certificates

Common scenarios where this is needed:

  • Self-signed certificates in development environments
  • Local Redis servers with SSL enabled
  • Cloud Redis services with custom certificates
  • Test environments with temporary certificates

Security Note: Disabling certificate verification reduces security. Only use this in trusted environments or when you're certain about the server's identity.

Common Issues

  1. Connection Refused: Check Redis server is running and accessible
  2. Authentication Failed: Verify username/password configuration
  3. Timeout Errors: Increase connection timeout in advanced options
  4. Memory Issues: Monitor Redis memory usage and configure appropriate limits

Debug Mode

Enable Node-RED debug mode to see detailed connection and operation logs:

DEBUG=redis* node-red

Context Debugging: Enable context debugging by setting the environment variable:

NODE_RED_DEBUG=1 node-red

Check Node-RED logs for messages like:

Context lookup - Type: global, Path: redis_config.host, Result: your-redis-host
Redis connection config - Host: your-redis-host, Port: 6379, Database: 0, Username: not set, Password: set

Common Context Issues:

  1. Variable not found: Check exact variable name spelling
  2. Nested objects: Use dot notation (e.g., redis_config.host)
  3. Context type mismatch: Ensure correct context type is selected
  4. Timing issues: Set context variables before Redis operations

Contributing

Contributions are welcome! Please read the contributing guidelines and submit pull requests to the GitHub repository.

License

MIT License - see LICENSE file for details.

Changelog

v1.1.0

  • Enhanced MATCH Operation: Added advanced pattern matching with pagination support
    • Cursor Pagination: Efficient pagination through large datasets using Redis SCAN cursors
    • Skip Functionality: Skip first N matching keys for offset-based pagination
    • Count Limiting: Improved count parameter handling for precise result limiting
    • Performance Optimization: Better SCAN integration for non-blocking operations
  • Improved Response Format: Enhanced MATCH response with pagination metadata
    • Added cursor, startCursor, limit, and truncated fields
    • Better error handling and validation
  • Production Ready: Removed debug logging and optimized for production use
  • Updated Documentation: Comprehensive examples for all MATCH features
  • Enhanced Error Handling: Better validation and error messages

v1.0.0

  • Initial release
  • Complete Redis operations support
  • Flexible connection management
  • Modern ioredis integration
  • Comprehensive documentation

Pattern Matching (MATCH)

Find keys by pattern using Redis SCAN:

// Find all keys starting with "user:"
msg.payload = {
    operation: "match",
    pattern: "user:*"
};

// Find keys with specific pattern and custom scan count
msg.payload = {
    operation: "match", 
    pattern: "session:*:active",
    count: 50  // Number of keys to scan per iteration
};

// Simple pattern matching
msg.payload = "temp:*";  // Find all keys starting with "temp:"

Response format:

{
    pattern: "user:*",
    keys: ["user:1", "user:2", "user:admin"],
    count: 3,
    scanned: true
}

Pattern examples:

  • user:* - All keys starting with "user:"
  • *:active - All keys ending with ":active"
  • session:*:data - Keys with "session:" prefix and ":data" suffix
  • temp_* - Keys starting with "temp_"

Hash Operations

Node Info

Version: 1.2.0
Updated 3 days ago
License: MIT
Rating: 5.0 1

Categories

Actions

Rate:

Downloads

8 in the last week

Nodes

  • redis-variable
  • redis-variable-config

Keywords

  • node-red
  • redis
  • database
  • cache
  • variable
  • storage
  • pattern-matching
  • pagination
  • json-handling

Maintainers