This document provides examples for using the ChronDB Redis protocol interface with redis-cli and JavaScript.
Redis Protocol Overview
ChronDB implements a subset of the Redis protocol, allowing you to connect using standard Redis clients. This makes it easy to integrate with existing applications that already use Redis or to leverage the simplicity of the Redis command structure.
The Redis protocol server can be configured in the config.edn file:
# Connect to ChronDB using redis-cli
redis-cli -h localhost -p 6379
# Create a document
SET user:1 '{"name":"John Doe","email":"[email protected]","age":30}'
# Get a document
GET user:1
# Output: {"name":"John Doe","email":"[email protected]","age":30}
# Check if a document exists
EXISTS user:1
# Output: (integer) 1
# Delete a document
DEL user:1
# Output: (integer) 1
# Create a document with expiration (ChronDB supports TTL)
SET user:1 '{"name":"John Doe"}' EX 3600 # Expires in 1 hour
# Set a document with multiple fields
HSET product:1 name "Laptop" price 999.99 category "Electronics" stock 50
# Get a specific field
HGET product:1 price
# Output: "999.99"
# Get all fields
HGETALL product:1
# Output:
# 1) "name"
# 2) "Laptop"
# 3) "price"
# 4) "999.99"
# 5) "category"
# 6) "Electronics"
# 7) "stock"
# 8) "50"
# Delete a field
HDEL product:1 stock
# Find all user keys
KEYS user:*
# Output:
# 1) "user:1"
# 2) "user:2"
# 3) "user:3"
# Find keys with wildcard
KEYS *:laptop*
# Full-text search using AST (new in Lucene migration)
SEARCH Software
# Output: Array of JSON documents matching "Software" in content field
# Search with limit
SEARCH Software LIMIT 10
# Search with limit and offset
SEARCH Software LIMIT 10 OFFSET 5
# Search with sorting
SEARCH Software SORT age:asc
SEARCH Software SORT age:desc
# Search on specific branch
SEARCH Software BRANCH main
# Combined options
SEARCH Software LIMIT 10 OFFSET 0 SORT name:asc BRANCH main
# Using FT.SEARCH alias (compatible with RediSearch)
FT.SEARCH Software LIMIT 10
# Get document history
CHRONDB.HISTORY user:1
# Output: Array of document versions with timestamps
# Get document at a specific point in time
CHRONDB.GETAT user:1 "2023-10-15T14:30:00Z"
# Output: Document as it existed at that timestamp
# Compare document versions
CHRONDB.DIFF user:1 "2023-10-10T09:15:00Z" "2023-10-15T14:30:00Z"
# Output: Differences between versions
# List all branches
CHRONDB.BRANCH.LIST
# Output:
# 1) "main"
# 2) "dev"
# 3) "test"
# Create a new branch
CHRONDB.BRANCH.CREATE feature-login
# Output: OK
# Switch to a branch
CHRONDB.BRANCH.CHECKOUT feature-login
# Output: OK
# Merge branches
CHRONDB.BRANCH.MERGE feature-login main
# Output: OK
// Install the redis package:
// npm install redis
const redis = require('redis');
// Create a Redis client connected to ChronDB
const client = redis.createClient({
url: 'redis://localhost:6379'
});
// Connect to the server
async function connect() {
await client.connect();
console.log('Connected to ChronDB via Redis protocol');
}
// Handle connection errors
client.on('error', (err) => {
console.error('Redis Client Error:', err);
});
// Basic CRUD operations
async function documentOperations() {
try {
// Create or update a document
await client.set('user:1', JSON.stringify({
name: 'Jane Smith',
email: '[email protected]',
age: 28,
roles: ['editor']
}));
console.log('Document created');
// Get a document
const userJson = await client.get('user:1');
const user = JSON.parse(userJson);
console.log('Retrieved user:', user);
// Update a document
user.roles.push('reviewer');
await client.set('user:1', JSON.stringify(user));
console.log('Document updated');
// Check if a document exists
const exists = await client.exists('user:1');
console.log('Document exists:', exists === 1);
// Delete a document
await client.del('user:1');
console.log('Document deleted');
} catch (err) {
console.error('Error in document operations:', err);
}
}
// Using Redis hash operations for field-level access
async function fieldOperations() {
try {
// Create document with fields
await client.hSet('product:1', {
name: 'Smartphone',
price: '799.99',
category: 'Electronics',
stock: '100',
specs: JSON.stringify({
screen: '6.5 inch',
ram: '8GB',
storage: '128GB'
})
});
console.log('Product created with fields');
// Get a specific field
const price = await client.hGet('product:1', 'price');
console.log('Product price:', price);
// Get all fields
const product = await client.hGetAll('product:1');
console.log('Complete product:', product);
// Parse JSON fields if needed
if (product.specs) {
product.specs = JSON.parse(product.specs);
}
// Update specific fields
await client.hSet('product:1', 'stock', '95');
await client.hSet('product:1', 'price', '749.99');
console.log('Product fields updated');
// Delete a field
await client.hDel('product:1', 'specs');
console.log('Specs field removed');
} catch (err) {
console.error('Error in field operations:', err);
}
}
// Finding documents with key patterns
async function searchOperations() {
try {
// Generate some test data
await client.set('user:101', JSON.stringify({ name: 'Alice', dept: 'Engineering' }));
await client.set('user:102', JSON.stringify({ name: 'Bob', dept: 'Marketing' }));
await client.set('user:103', JSON.stringify({ name: 'Charlie', dept: 'Engineering' }));
// Find all user keys
const userKeys = await client.keys('user:*');
console.log('All user keys:', userKeys);
// Get multiple documents at once
if (userKeys.length > 0) {
const usersData = await client.mGet(userKeys);
const users = usersData.map(data => JSON.parse(data));
console.log('All users:', users);
}
// Find specific keys
const engineeringUserKeys = await Promise.all((await client.keys('user:*')).map(async key => {
const userData = await client.get(key);
const user = JSON.parse(userData);
return user.dept === 'Engineering' ? key : null;
})).then(keys => keys.filter(Boolean));
console.log('Engineering users:', engineeringUserKeys);
} catch (err) {
console.error('Error in search operations:', err);
}
}
// Using ChronDB-specific commands for version control
async function versionControlOperations() {
try {
// Create a document with initial version
await client.set('doc:1', JSON.stringify({ title: 'Initial Document', content: 'Draft content' }));
console.log('Document created');
// Wait a moment to ensure different timestamps
await new Promise(resolve => setTimeout(resolve, 1000));
// Update the document
await client.set('doc:1', JSON.stringify({
title: 'Updated Document',
content: 'Revised content',
lastModified: new Date().toISOString()
}));
console.log('Document updated');
// Get document history
const history = await client.sendCommand(['CHRONDB.HISTORY', 'doc:1']);
console.log('Document history:', history);
// Get document at a specific time (using the first version's timestamp)
if (history && history.length > 0) {
const firstVersion = JSON.parse(history[0]);
const oldDoc = await client.sendCommand([
'CHRONDB.GETAT',
'doc:1',
firstVersion.timestamp
]);
console.log('Original version:', oldDoc);
// Compare versions
if (history.length > 1) {
const secondVersion = JSON.parse(history[1]);
const diff = await client.sendCommand([
'CHRONDB.DIFF',
'doc:1',
firstVersion.timestamp,
secondVersion.timestamp
]);
console.log('Changes between versions:', diff);
}
}
} catch (err) {
console.error('Error in version control operations:', err);
}
}
// Working with branches
async function branchOperations() {
try {
// List all branches
const branches = await client.sendCommand(['CHRONDB.BRANCH.LIST']);
console.log('Available branches:', branches);
// Create a new branch
await client.sendCommand(['CHRONDB.BRANCH.CREATE', 'feature-search']);
console.log('Created feature-search branch');
// Switch to the new branch
await client.sendCommand(['CHRONDB.BRANCH.CHECKOUT', 'feature-search']);
console.log('Switched to feature-search branch');
// Create a document in the feature branch
await client.set('search:config', JSON.stringify({
indexFields: ['title', 'content', 'tags'],
caseSensitive: false,
maxResults: 100
}));
console.log('Created document in feature branch');
// Switch back to main
await client.sendCommand(['CHRONDB.BRANCH.CHECKOUT', 'main']);
// Verify document doesn't exist in main
const exists = await client.exists('search:config');
console.log('Document exists in main branch:', exists === 1);
// Merge feature branch into main
await client.sendCommand(['CHRONDB.BRANCH.MERGE', 'feature-search', 'main']);
console.log('Merged feature-search into main');
// Verify document now exists in main
const existsAfterMerge = await client.exists('search:config');
console.log('Document exists in main after merge:', existsAfterMerge === 1);
} catch (err) {
console.error('Error in branch operations:', err);
}
}
# Create a validation schema with strict mode
SCHEMA.SET users '{"type":"object","required":["id","email"],"properties":{"id":{"type":"string"},"email":{"type":"string","format":"email"},"name":{"type":"string"},"age":{"type":"integer","minimum":0}}}' MODE strict
# Output: OK
# Get a validation schema
SCHEMA.GET users
# Output: {"namespace":"users","version":1,"mode":"strict","schema":{...}}
# List all validation schemas
SCHEMA.LIST
# Output:
# 1) "users"
# 2) "products"
# Validate a document without saving (valid)
SCHEMA.VALIDATE users '{"id":"users:1","email":"[email protected]","name":"John"}'
# Output: {"valid":true,"errors":[]}
# Validate a document without saving (invalid - missing email)
SCHEMA.VALIDATE users '{"id":"users:1","name":"John"}'
# Output: {"valid":false,"errors":[{"path":"$.email","message":"required property 'email' not found","keyword":"required"}]}
# Try to save an invalid document (will fail in strict mode)
SET users:1 '{"id":"users:1","name":"John"}'
# Output: -ERR VALIDATION_ERROR users: required property 'email' not found at $.email
# Save a valid document
SET users:1 '{"id":"users:1","email":"[email protected]","name":"John"}'
# Output: OK
# Delete a validation schema
SCHEMA.DEL users
# Output: (integer) 1
// Schema validation operations using ChronDB via Redis protocol
async function schemaValidationOperations(client) {
try {
// Create a validation schema
const schema = {
type: 'object',
required: ['id', 'email'],
properties: {
id: { type: 'string' },
email: { type: 'string', format: 'email' },
name: { type: 'string' },
age: { type: 'integer', minimum: 0 }
}
};
await client.sendCommand([
'SCHEMA.SET',
'users',
JSON.stringify(schema),
'MODE',
'strict'
]);
console.log('Schema created');
// List all schemas
const schemas = await client.sendCommand(['SCHEMA.LIST']);
console.log('Available schemas:', schemas);
// Get a specific schema
const userSchema = await client.sendCommand(['SCHEMA.GET', 'users']);
console.log('User schema:', JSON.parse(userSchema));
// Validate a document before saving
const validDoc = { id: 'users:1', email: '[email protected]', name: 'John' };
const validResult = await client.sendCommand([
'SCHEMA.VALIDATE',
'users',
JSON.stringify(validDoc)
]);
console.log('Validation result (valid):', JSON.parse(validResult));
// Try validating an invalid document
const invalidDoc = { id: 'users:2', name: 'Jane' }; // missing email
const invalidResult = await client.sendCommand([
'SCHEMA.VALIDATE',
'users',
JSON.stringify(invalidDoc)
]);
console.log('Validation result (invalid):', JSON.parse(invalidResult));
// Save a valid document
await client.set('users:1', JSON.stringify(validDoc));
console.log('Valid document saved');
// Try to save an invalid document (will throw error in strict mode)
try {
await client.set('users:2', JSON.stringify(invalidDoc));
} catch (err) {
console.log('Expected validation error:', err.message);
}
// Delete the schema
const deleted = await client.sendCommand(['SCHEMA.DEL', 'users']);
console.log('Schema deleted:', deleted === 1);
} catch (err) {
console.error('Error in schema validation operations:', err);
}
}