Hyrex provides a distributed key-value store for sharing state between tasks and workflows.

Basic Usage

The HyrexKV class provides static methods for key-value operations:
import { HyrexKV } from '@hyrex/hyrex';

// Set a value
await HyrexKV.set('user-123', 'John Doe');

// Get a value
const userName = await HyrexKV.get('user-123');
console.log(userName); // 'John Doe'

Value Size Limits

The TypeScript SDK enforces a 1MB size limit for values:
// This will work
const smallData = JSON.stringify({ items: [1, 2, 3] });
await HyrexKV.set('small-data', smallData);

// This will throw an error if > 1MB
const largeData = 'x'.repeat(1024 * 1024 + 1); // 1MB + 1 byte
try {
    await HyrexKV.set('large-data', largeData);
} catch (error) {
    // Error: Value size exceeds maximum allowed size of 1048576 bytes
}

Working with JSON Data

Store complex objects by serializing to JSON:
interface UserData {
    id: string;
    name: string;
    preferences: Record<string, any>;
}

// Store object
const userData: UserData = {
    id: '123',
    name: 'Alice',
    preferences: { theme: 'dark', notifications: true }
};
await HyrexKV.set('user-data', JSON.stringify(userData));

// Retrieve and parse
const storedData = await HyrexKV.get('user-data');
const parsed = JSON.parse(storedData) as UserData;
console.log(parsed.name); // 'Alice'

Use Cases

Task Communication

Share data between parent and child tasks:
const parentTask = hy.task({
    name: 'parentTask',
    func: async () => {
        const ctx = getHyrexContext();
        
        // Store data for child tasks
        await HyrexKV.set(`task-${ctx.taskId}-config`, JSON.stringify({
            batchSize: 100,
            retryCount: 3
        }));
        
        // Launch child tasks
        await childTask.send({ parentId: ctx.taskId });
        
        return { status: 'launched' };
    }
});

const childTask = hy.task({
    name: 'childTask',
    func: async ({ parentId }: { parentId: string }) => {
        // Retrieve parent's configuration
        const configStr = await HyrexKV.get(`task-${parentId}-config`);
        const config = JSON.parse(configStr);
        
        console.log(`Processing with batch size: ${config.batchSize}`);
        
        return { processed: true };
    }
});

Workflow State

Share state between workflow tasks:
const dataWorkflow = hy.workflow({
    name: 'dataWorkflow',
    body: (workflowBuilder) => {
        workflowBuilder
            .start(fetchDataTask)
            .next(processDataTask)
            .next(saveResultsTask);
        
        return workflowBuilder;
    },
    config: {}
});

const fetchDataTask = hy.task({
    name: 'fetchData',
    func: async () => {
        const ctx = getHyrexContext();
        const data = await fetchFromAPI();
        
        // Store for next task in workflow
        await HyrexKV.set(
            `workflow-${ctx.workflowRunId}-data`,
            JSON.stringify(data)
        );
        
        return { fetched: true };
    }
});

const processDataTask = hy.task({
    name: 'processData',
    func: async () => {
        const ctx = getHyrexContext();
        
        // Retrieve data from previous task
        const dataStr = await HyrexKV.get(
            `workflow-${ctx.workflowRunId}-data`
        );
        const data = JSON.parse(dataStr);
        
        const processed = transformData(data);
        
        // Store for final task
        await HyrexKV.set(
            `workflow-${ctx.workflowRunId}-result`,
            JSON.stringify(processed)
        );
        
        return { processed: true };
    }
});

Distributed Locks

Implement simple distributed locks:
const lockTask = hy.task({
    name: 'lockTask',
    func: async ({ resourceId }: { resourceId: string }) => {
        const lockKey = `lock-${resourceId}`;
        const lockValue = `${getHyrexContext().taskId}-${Date.now()}`;
        
        try {
            // Try to acquire lock
            const existing = await HyrexKV.get(lockKey).catch(() => null);
            if (existing) {
                throw new Error('Resource is locked');
            }
            
            // Set lock
            await HyrexKV.set(lockKey, lockValue);
            
            // Do work with exclusive access
            await processExclusiveResource(resourceId);
            
            // Note: No delete method - use TTL or workflow cleanup
            
        } catch (error) {
            console.error('Failed to acquire lock:', error);
            throw error;
        }
        
        return { completed: true };
    }
});

Caching

Cache expensive computation results:
const computeTask = hy.task({
    name: 'computeTask',
    func: async ({ input }: { input: string }) => {
        const cacheKey = `cache-compute-${input}`;
        
        // Check cache
        try {
            const cached = await HyrexKV.get(cacheKey);
            if (cached) {
                console.log('Cache hit!');
                return JSON.parse(cached);
            }
        } catch (error) {
            // Cache miss, continue
        }
        
        // Expensive computation
        console.log('Cache miss, computing...');
        const result = await expensiveComputation(input);
        
        // Cache result
        await HyrexKV.set(cacheKey, JSON.stringify(result));
        
        return result;
    }
});

Best Practices

  1. Use Namespaced Keys
    // Good - clear namespace and purpose
    const key = `workflow-${workflowId}-step-${stepName}`;
    const key = `cache-api-${endpoint}-${params}`;
    const key = `user-${userId}-preferences`;
    
    // Avoid - ambiguous keys
    const key = 'data';
    const key = '123';
    
  2. Handle Missing Keys
    // Get with error handling
    const getValue = async (key: string): Promise<string | null> => {
        try {
            return await HyrexKV.get(key);
        } catch (error) {
            // Key doesn't exist
            return null;
        }
    };
    
  3. Clean Up Workflow Data
    // Store with workflow-specific keys
    const workflowKey = `workflow-${ctx.workflowRunId}-temp`;
    await HyrexKV.set(workflowKey, data);
    
    // Consider cleanup in final task
    // Note: TypeScript SDK doesn't have delete method
    
  4. Validate Data Size
    const storeData = async (key: string, data: any) => {
        const serialized = JSON.stringify(data);
        
        // Check size before storing (1MB limit)
        const sizeInBytes = new TextEncoder().encode(serialized).length;
        if (sizeInBytes > 1024 * 1024) {
            throw new Error(`Data too large: ${sizeInBytes} bytes`);
        }
        
        await HyrexKV.set(key, serialized);
    };
    

Limitations

  • No Delete Method: Keys cannot be explicitly deleted in the TypeScript SDK
  • 1MB Size Limit: Values are limited to 1MB (1,048,576 bytes)
  • String Values Only: All values must be strings (serialize objects to JSON)
  • No TTL Support: Keys don’t expire automatically

Next Steps