gofast-ts-client
v1.0.0
Published
High-performance TypeScript client for GoFast distributed cache server
Maintainers
Readme
GoFast TypeScript Client
A high-performance TypeScript/JavaScript client library for the GoFast distributed cache server. This client provides a complete interface to all GoFast operations with full TypeScript support, connection pooling, and pipeline capabilities.
🚀 Features
- Complete API Coverage - Support for all GoFast data types (strings, lists, sets, hashes)
- TypeScript First - Full type safety with comprehensive TypeScript definitions
- High Performance - Binary protocol with connection pooling and pipelining
- Pipeline Support - Batch multiple operations for maximum throughput
- Redis-Compatible - Familiar API for Redis users with friendly aliases
- Robust Error Handling - Detailed error types and automatic retry logic
- Zero Dependencies - Lightweight with no external dependencies
📦 Installation
npm install gofast-ts-client
# or
yarn add gofast-ts-client
# or
pnpm add gofast-ts-client🏃 Quick Start
import { GoFastClient } from 'gofast-ts-client';
async function quickStart() {
const client = new GoFastClient({
host: 'localhost',
port: 6379,
timeout: 5000
});
try {
// Connect to the server
await client.connect();
// Basic operations
await client.set('greeting', 'Hello, World!');
const message = await client.get('greeting');
console.log(message); // "Hello, World!"
// Atomic operations
await client.set('counter', '0');
const newValue = await client.incr('counter');
console.log(newValue); // 1
// List operations
await client.lpush('tasks', 'important-task');
await client.rpush('tasks', 'normal-task');
const task = await client.lpop('tasks');
console.log(task); // "important-task"
} finally {
await client.disconnect();
}
}
quickStart();📋 API Reference
Connection Management
// Create client with options
const client = new GoFastClient({
host: 'localhost', // Server host (default: 'localhost')
port: 6379, // Server port (default: 6379)
timeout: 5000, // Operation timeout in ms (default: 5000)
maxRetries: 3, // Max retry attempts (default: 3)
retryDelay: 1000 // Delay between retries in ms (default: 1000)
});
// Connect and disconnect
await client.connect();
await client.disconnect();
// Check connection status
console.log(client.isConnected()); // true/falseBasic String Operations
// SET with optional TTL
await client.set('key', 'value');
await client.set('session', 'user123', { ttl: 3600 }); // 1 hour expiration
// GET
const value = await client.get('key');
console.log(value); // "value" or null if not found
// DELETE
const deleted = await client.del('key');
console.log(deleted); // true if key existed
// Multiple operations
const values = await client.mget(['key1', 'key2', 'key3']);
console.log(values); // ['value1', 'value2', null] - null for missing keys
const count = await client.mset({
'user:1': 'Alice',
'user:2': 'Bob',
'user:3': 'Charlie'
}, 300); // 5-minute TTL
console.log(count); // 3 - number of keys setAtomic Operations
// Increment/Decrement
await client.set('counter', '10');
const inc1 = await client.incr('counter'); // 11
const inc2 = await client.incr('counter'); // 12
const dec1 = await client.decr('counter'); // 11
// Get and Set atomically
const oldValue = await client.getSet('status', 'active');
console.log(oldValue); // previous valueList Operations
// Push operations
const len1 = await client.lpush('queue', 'high-priority'); // Add to front
const len2 = await client.rpush('queue', 'low-priority'); // Add to back
console.log(len1, len2); // 1, 2 - list lengths after each operation
// Pop operations
const highPri = await client.lpop('queue'); // Remove from front
const lowPri = await client.rpop('queue'); // Remove from back
// List inspection
const length = await client.llen('queue'); // Get list length
const item = await client.lindex('queue', 0); // Get item at index
const range = await client.lrange('queue', 0, -1); // Get range (0 to end)Set Operations
// Add members (sets only store unique values)
const added1 = await client.sadd('tags', 'javascript'); // true - new member
const added2 = await client.sadd('tags', 'typescript'); // true - new member
const added3 = await client.sadd('tags', 'javascript'); // false - already exists
// Set inspection
const members = await client.smembers('tags'); // ['javascript', 'typescript']
const count = await client.scard('tags'); // 2
const exists = await client.sismember('tags', 'go'); // false
// Remove members
const removed = await client.srem('tags', 'javascript'); // true if existedHash Operations
// Set hash fields
const new1 = await client.hset('user:123', 'name', 'Alice'); // true - new field
const new2 = await client.hset('user:123', 'email', '[email protected]');
const upd1 = await client.hset('user:123', 'name', 'Alice Smith'); // false - updated
// Get hash fields
const name = await client.hget('user:123', 'name'); // "Alice Smith"
const email = await client.hget('user:123', 'email'); // "[email protected]"
const phone = await client.hget('user:123', 'phone'); // null - doesn't exist
// Hash inspection
const user = await client.hgetall('user:123'); // { name: 'Alice Smith', email: '...' }
const fieldCount = await client.hlen('user:123'); // 2
const hasPhone = await client.hexists('user:123', 'phone'); // false
// Delete hash fields
const deleted = await client.hdel('user:123', 'email'); // true if field existedPattern Operations
// Find keys by pattern
const userKeys = await client.keys('user:*'); // ['user:123', 'user:456']
const allKeys = await client.keys('*'); // All keys
// Cursor-based scanning (better for large datasets)
let cursor = 0;
const allMatches = [];
do {
const result = await client.scan(cursor, 'session:*', 100);
allMatches.push(...result.keys);
cursor = result.cursor;
} while (cursor !== 0);🚀 Pipeline Operations
Pipeline operations allow you to batch multiple commands together for significantly better performance:
// Create a pipeline
const pipeline = client.pipeline();
// Queue multiple operations
pipeline
.set('user:1', 'Alice')
.set('user:2', 'Bob')
.set('counter', '0')
.incr('counter')
.incr('counter')
.get('user:1')
.get('user:2')
.get('counter')
.lpush('notifications', 'welcome')
.sadd('active_users', 'user:1')
.hset('stats', 'users_online', '1');
console.log(`Pipeline has ${pipeline.length()} commands queued`);
// Execute all commands at once
const results = await pipeline.exec();
console.log('Results:', results);
// ['OK', 'OK', 'OK', '1', '2', 'Alice', 'Bob', '2', '1', '1', '1']Pipeline Performance Benefits
async function performanceComparison() {
// Individual operations (slower)
console.time('Individual Operations');
await client.set('a', '1');
await client.set('b', '2');
await client.get('a');
await client.get('b');
await client.incr('counter');
await client.incr('counter');
console.timeEnd('Individual Operations');
// Pipeline operations (faster)
console.time('Pipeline Operations');
const results = await client.pipeline()
.set('a', '1')
.set('b', '2')
.get('a')
.get('b')
.incr('counter')
.incr('counter')
.exec();
console.timeEnd('Pipeline Operations');
// Pipeline is typically 5-10x faster for multiple operations!
}🎨 Friendly Aliases
The client provides both Redis-style commands and more descriptive aliases:
// Redis-style (familiar to Redis users)
await client.hset('user:1', 'name', 'Alice');
await client.hget('user:1', 'name');
await client.lpush('queue', 'job1');
await client.scard('tags');
// Friendly style (self-documenting)
await client.hashSet('user:1', 'name', 'Alice');
await client.hashGet('user:1', 'name');
await client.listPushLeft('queue', 'job1');
await client.setCount('tags');
// Both styles work identically - choose what feels natural!Complete Alias Reference
| Redis Style | Friendly Style | Description |
|-------------|----------------|-------------|
| del(key) | delete(key) | Delete a key |
| mget(keys) | multiGet(keys) | Get multiple values |
| mset(pairs, ttl) | multiSet(pairs, ttl) | Set multiple key-value pairs |
| incr(key) | increment(key) | Increment a number |
| decr(key) | decrement(key) | Decrement a number |
| getSet(key, value) | getAndSet(key, value) | Get old value and set new |
| keys(pattern) | findKeys(pattern) | Find keys by pattern |
| scan(...) | scanKeys(...) | Scan keys with cursor |
| hset(key, field, value) | hashSet(key, field, value) | Set hash field |
| hget(key, field) | hashGet(key, field) | Get hash field |
| hdel(key, field) | hashDelete(key, field) | Delete hash field |
| hgetall(key) | hashGetAll(key) | Get all hash fields |
| hlen(key) | hashLength(key) | Get hash field count |
| hexists(key, field) | hashExists(key, field) | Check if hash field exists |
| lpush(key, value) | listPushLeft(key, value) | Add to list front |
| rpush(key, value) | listPushRight(key, value) | Add to list back |
| lpop(key) | listPopLeft(key) | Remove from list front |
| rpop(key) | listPopRight(key) | Remove from list back |
| llen(key) | listLength(key) | Get list length |
| lindex(key, index) | listGetIndex(key, index) | Get list item by index |
| lrange(key, start, end) | listGetRange(key, start, end) | Get list range |
| sadd(key, member) | setAdd(key, member) | Add to set |
| srem(key, member) | setRemove(key, member) | Remove from set |
| smembers(key) | setGetMembers(key) | Get all set members |
| scard(key) | setCount(key) | Get set member count |
| sismember(key, member) | setIsMember(key, member) | Check set membership |
🔧 Error Handling
The client provides specific error types for different failure scenarios:
import {
GoFastClient,
ConnectionError,
ProtocolError,
TimeoutError
} from 'gofast-ts-client';
async function handleErrors() {
const client = new GoFastClient();
try {
await client.connect();
await client.set('key', 'value');
} catch (error) {
if (error instanceof ConnectionError) {
console.error('Connection failed:', error.message);
// Maybe try to reconnect
} else if (error instanceof TimeoutError) {
console.error('Operation timed out:', error.message);
// Maybe retry with longer timeout
} else if (error instanceof ProtocolError) {
console.error('Protocol error:', error.message);
// Check server compatibility
} else {
console.error('Unknown error:', error);
}
}
}⚙️ Configuration Options
const client = new GoFastClient({
host: 'cache.example.com', // Server hostname
port: 6379, // Server port
timeout: 10000, // 10-second timeout
maxRetries: 5, // Retry failed operations 5 times
retryDelay: 2000 // Wait 2 seconds between retries
});📊 Real-World Examples
Session Management
class SessionManager {
constructor(private client: GoFastClient) {}
async createSession(userId: string, sessionData: any): Promise<string> {
const sessionId = generateId();
const sessionKey = `session:${sessionId}`;
// Store session with 1-hour expiration
await this.client.set(sessionKey, JSON.stringify(sessionData), { ttl: 3600 });
// Add to user's active sessions
await this.client.sadd(`user:${userId}:sessions`, sessionId);
return sessionId;
}
async getSession(sessionId: string): Promise<any | null> {
const sessionKey = `session:${sessionId}`;
const data = await this.client.get(sessionKey);
return data ? JSON.parse(data) : null;
}
async invalidateSession(sessionId: string, userId: string): Promise<void> {
const pipeline = this.client.pipeline();
pipeline
.del(`session:${sessionId}`)
.srem(`user:${userId}:sessions`, sessionId);
await pipeline.exec();
}
}Real-time Analytics
class AnalyticsTracker {
constructor(private client: GoFastClient) {}
async trackPageView(page: string, userId?: string): Promise<void> {
const today = new Date().toISOString().split('T')[0]; // YYYY-MM-DD
const hour = new Date().getHours();
const pipeline = this.client.pipeline();
// Increment page view counters
pipeline
.incr(`pageviews:${page}:${today}`)
.incr(`pageviews:${page}:${today}:${hour}`)
.incr(`pageviews:total:${today}`);
// Track unique users if provided
if (userId) {
pipeline
.sadd(`unique_users:${page}:${today}`, userId)
.sadd(`unique_users:total:${today}`, userId);
}
await pipeline.exec();
}
async getPageStats(page: string, date: string): Promise<any> {
const pipeline = this.client.pipeline();
// Get stats for the page
pipeline
.get(`pageviews:${page}:${date}`)
.scard(`unique_users:${page}:${date}`);
// Get hourly breakdown
for (let hour = 0; hour < 24; hour++) {
pipeline.get(`pageviews:${page}:${date}:${hour}`);
}
const results = await pipeline.exec();
return {
totalViews: parseInt(results[0] || '0'),
uniqueUsers: parseInt(results[1] || '0'),
hourlyViews: results.slice(2).map(v => parseInt(v || '0'))
};
}
}Caching Layer
class CacheLayer {
constructor(private client: GoFastClient) {}
async cached<T>(
key: string,
fetchFn: () => Promise<T>,
ttl: number = 300
): Promise<T> {
// Try to get from cache first
const cached = await this.client.get(key);
if (cached) {
return JSON.parse(cached);
}
// Not in cache, fetch the data
const data = await fetchFn();
// Store in cache for future requests
await this.client.set(key, JSON.stringify(data), { ttl });
return data;
}
async invalidatePattern(pattern: string): Promise<number> {
const keys = await this.client.keys(pattern);
if (keys.length === 0) return 0;
// Delete all matching keys in a pipeline
const pipeline = this.client.pipeline();
keys.forEach(key => pipeline.del(key));
const results = await pipeline.exec();
return results.filter(r => r === '1').length;
}
}
// Usage
const cache = new CacheLayer(client);
const userData = await cache.cached(
`user:${userId}`,
() => database.getUser(userId),
600 // Cache for 10 minutes
);🧪 Testing
import { GoFastClient } from 'gofast-ts-client';
describe('GoFast Client', () => {
let client: GoFastClient;
beforeEach(async () => {
client = new GoFastClient({
host: process.env.GOFAST_HOST || 'localhost',
port: parseInt(process.env.GOFAST_PORT || '6379')
});
await client.connect();
});
afterEach(async () => {
await client.disconnect();
});
test('basic set and get', async () => {
await client.set('test:key', 'test value');
const value = await client.get('test:key');
expect(value).toBe('test value');
});
test('pipeline operations', async () => {
const results = await client.pipeline()
.set('a', '1')
.set('b', '2')
.get('a')
.get('b')
.exec();
expect(results).toEqual(['OK', 'OK', '1', '2']);
});
});🔄 Migration from Redis
If you're migrating from a Redis client, the GoFast client provides a very similar API:
// Redis client code
await redis.set('key', 'value');
await redis.get('key');
await redis.hset('hash', 'field', 'value');
await redis.lpush('list', 'item');
// GoFast client code (nearly identical!)
await client.set('key', 'value');
await client.get('key');
await client.hset('hash', 'field', 'value');
await client.lpush('list', 'item');📈 Performance Tips
- Use Pipelines - Batch operations whenever possible
- Connection Reuse - Keep connections open for the lifetime of your application
- Appropriate TTLs - Set expiration times to prevent memory bloat
- Batch Reads - Use
mget()instead of multipleget()calls - Pattern Operations - Use
scan()instead ofkeys()for large datasets
🐛 Troubleshooting
Connection Issues
// Check if server is running
const client = new GoFastClient({ timeout: 1000 });
try {
await client.connect();
console.log('✅ Server is running');
} catch (error) {
console.log('❌ Server is not accessible:', error.message);
}Debug Mode
// Enable verbose logging (if implemented)
const client = new GoFastClient({
host: 'localhost',
port: 6379,
debug: true // Log all commands and responses
});Performance Monitoring
// Measure operation timing
console.time('operation');
await client.get('key');
console.timeEnd('operation');
// Pipeline vs individual timing
console.time('individual');
await client.set('a', '1');
await client.set('b', '2');
await client.get('a');
console.timeEnd('individual');
console.time('pipeline');
await client.pipeline()
.set('a', '1')
.set('b', '2')
.get('a')
.exec();
console.timeEnd('pipeline');📄 License
MIT License - see LICENSE file for details.
🤝 Contributing
- Fork the repository
- Create a feature branch:
git checkout -b feature/amazing-feature - Commit your changes:
git commit -m 'Add amazing feature' - Push to the branch:
git push origin feature/amazing-feature - Open a Pull Request
Built with ❤️ for high-performance caching. Star ⭐ the project if you find it useful!
