Docs

Helpers & Cache

Context Reference - Helpers & Cache Utility functions for common tasks and distributed caching operations. Helpers Utility functions for common tasks. All helper functions require await . JWT Token Generation const token = await $ctx.$helpers.$jwt(payload, expiration); // Example

Context Reference - Helpers & Cache

Utility functions for common tasks and distributed caching operations.

Helpers

Utility functions for common tasks. All helper functions require await.

JWT Token Generation

const token = await $ctx.$helpers.$jwt(payload, expiration);

// Example
const token = await $ctx.$helpers.$jwt(
  { userId: 123, email: '[email protected]' },
  '7d'  // Expires in 7 days
);

Expiration formats: - '15m' - 15 minutes - '1h' - 1 hour - '1d' - 1 day - '7d' - 7 days - '30d' - 30 days

Password Hashing

// Hash password
const hash = await $ctx.$helpers.$bcrypt.hash(plainPassword);

// Verify password
const isValid = await $ctx.$helpers.$bcrypt.compare(plainPassword, hashedPassword);

// Example
const hashedPassword = await $ctx.$helpers.$bcrypt.hash('myPassword123');
const isValid = await $ctx.$helpers.$bcrypt.compare('myPassword123', hashedPassword);

Auto Slug Generation

Generate URL-friendly slugs from text.

const slug = $ctx.$helpers.autoSlug(text);

// Example
const slug = $ctx.$helpers.autoSlug('My Product Name');
// Result: 'my-product-name'

Crypto Helpers

Use $ctx.$helpers.$crypto for bounded cryptographic helpers inside hooks, handlers, flows, and websocket scripts.

const id = $ctx.$helpers.$crypto.randomUUID();
const token = $ctx.$helpers.$crypto.randomBytes(32, 'base64url');
const digest = $ctx.$helpers.$crypto.sha256('payload');
const signature = $ctx.$helpers.$crypto.hmacSha256('payload', 'shared-secret');

Supported encodings for randomBytes, sha256, and hmacSha256 are hex, base64, and base64url. randomBytes is capped to 4096 bytes.

Generate SSH keys with the same crypto helper:

const keyPair = await $ctx.$helpers.$crypto.generateSshKeyPair('[email protected]');

// keyPair.publicKey is OpenSSH format.
// keyPair.privateKey is RSA PKCS#1 PEM.

Do not use legacy $ctx.$helpers.$ssh or manual encryption helpers in new scripts. Database values that need encryption at rest should be stored in columns marked isEncrypted=true; scripts read and write plaintext values.

Sleep Helper

Pause a dynamic script for a bounded duration. The runtime clamps the delay between 0 and 30000 milliseconds.

await $ctx.$helpers.$sleep(1000);

Storage Helpers

Upload files to storage.

const fileResult = await $ctx.$storage.$upload({
  originalname: 'image.jpg',
  filename: 'custom-filename.jpg',
  mimetype: 'image/jpeg',
  buffer: fileBuffer,
  size: 1024000,
  folder: 123,  // Optional: folder ID
  storageConfig: 1,  // Optional: storage config ID
  title: 'My Image',  // Optional
  description: 'Image description'  // Optional
});

File Update Helper

Update existing files.

await $ctx.$storage.$update(fileId, {
  buffer: newFileBuffer,
  originalname: 'new-name.jpg',
  mimetype: 'image/jpeg',
  folder: 456,
  title: 'Updated Title'
});

File Delete Helper

Delete files.

await $ctx.$storage.$delete(fileId);

Register an object that already exists in the configured storage backend without uploading bytes:

await $ctx.$storage.$registerFile({
  filename: 'backup.sql.gz',
  mimetype: 'application/gzip',
  location: 'backups/project-1/backup.sql.gz',
  size: 1024,
  storageConfig: 1,
});

Rate Limiting

Protect your API routes with flexible rate limiting using Redis sliding window algorithm.

const result = await $ctx.$helpers.$rateLimit.byIp({
  maxRequests: 100,
  perSeconds: 60
});

if (!result.allowed) {
  $ctx.$throw['429'](`Rate limit exceeded. Try again in ${result.retryAfter}s`);
}

Rate Limit Templates

Method Key Format Description
byIp(options) ip:{ip}:{route} Rate limit by client IP per route
byUser(options) user:{userId}:{route} Rate limit by authenticated user per route
byRoute(options) route:{route} Rate limit globally per route (all users/IPs share limit)
byIpGlobal(options) ip:{ip} Rate limit by IP across all routes
byUserGlobal(options) user:{userId} Rate limit by user across all routes
check(key, options) Custom key Rate limit with custom key
reset(key) - Reset rate limit for a key
status(key, options) - Check rate limit status without incrementing

Options

{
  maxRequests: 100,  // Maximum requests allowed in the window
  perSeconds: 60     // Time window in seconds
}

Result Object

{
  allowed: boolean,     // Whether the request is allowed
  remaining: number,    // Remaining requests in window
  resetAt: number,      // Unix timestamp when window resets
  retryAfter: number,   // Seconds to wait before retry (0 if allowed)
  limit: number,        // The max requests limit
  window: number        // The window in seconds
}

Examples

Rate Limit Login Attempts by IP:

// In preHook for POST /auth/login
const result = await $ctx.$helpers.$rateLimit.byIp({
  maxRequests: 5,
  perSeconds: 60
});

if (!result.allowed) {
  $ctx.$throw['429'](`Too many login attempts. Try again in ${result.retryAfter}s`);
}

Rate Limit API by User:

// In preHook for API routes
const result = await $ctx.$helpers.$rateLimit.byUser({
  maxRequests: 1000,
  perSeconds: 3600  // 1 hour
});

if (!result.allowed) {
  $ctx.$throw['429'](`API rate limit exceeded. Try again in ${result.retryAfter}s`);
}

Skip Rate Limit for Admins:

if (!$ctx.$user?.isRootAdmin) {
  const result = await $ctx.$helpers.$rateLimit.byIp({
    maxRequests: 100,
    perSeconds: 60
  });

  if (!result.allowed) {
    $ctx.$throw['429']('Rate limit exceeded');
  }
}

Custom Key for Specific Resource:

const resourceId = $ctx.$params.id;
const result = await $ctx.$helpers.$rateLimit.check(
  `resource:${resourceId}:${$ctx.$user?.id || $ctx.$req.ip}`,
  { maxRequests: 10, perSeconds: 60 }
);

if (!result.allowed) {
  $ctx.$throw['429']('Too many requests to this resource');
}

Check Status Without Incrementing:

// Check if rate limited without counting the request
const status = await $ctx.$helpers.$rateLimit.status(
  `ip:${$ctx.$req.ip}:/api/expensive`,
  { maxRequests: 5, perSeconds: 3600 }
);

if (!status.allowed) {
  $ctx.$throw['429']('Daily limit reached');
}

Reset Rate Limit After Successful Action:

// In postHook after successful action
await $ctx.$helpers.$rateLimit.reset(`ip:${$ctx.$req.ip}:/auth/forgot-password`);

Cache

Distributed user-cache and locking operations. All cache functions require await.

$ctx.$cache stores application data created by handlers, hooks, flows, websocket scripts, and the @CACHE macro. Use logical keys such as user:123; do not include NODE_NAME, user_cache:, or any Redis namespace prefix. When Redis user cache is enabled, Enfyra stores those values under the current app namespace as NODE_NAME:user_cache:*.

The user cache has a soft allocation controlled by REDIS_USER_CACHE_LIMIT_MB (default 30). If the allocation is exceeded, Enfyra evicts least-recently-used user-cache keys only. System Redis keys such as runtime cache snapshots, BullMQ queues, Socket.IO, runtime telemetry, and locks are not counted or evicted by this quota.

Get Cached Value

const cachedValue = await $ctx.$cache.get(key);

// Example
const user = await $ctx.$cache.get('user:123');

Set Cached Value

// Set with TTL (time-to-live in milliseconds)
await $ctx.$cache.set(key, value, ttlMs);

// Example - cache for 5 minutes
await $ctx.$cache.set('user:123', userData, 300000);

// Set without expiration
await $ctx.$cache.setNoExpire(key, value);

Prefer set(key, value, ttlMs) with a TTL for operational data. setNoExpire keeps a value persistent from a TTL perspective, but it can still be evicted if the user-cache allocation is exceeded.

Distributed Locking

Acquire and release locks for critical operations.

// Acquire lock
const lockAcquired = await $ctx.$cache.acquire(key, value, ttlMs);

// Release lock
const released = await $ctx.$cache.release(key, value);

// Example
const lockKey = `user-lock:${userId}`;
const lockValue = $ctx.$user.id;
const acquired = await $ctx.$cache.acquire(lockKey, lockValue, 10000); // 10 seconds

if (acquired) {
  try {
    // Critical operation here
  } finally {
    await $ctx.$cache.release(lockKey, lockValue);
  }
}

Check Cache Exists

const exists = await $ctx.$cache.exists(key, value);

// Example
const lockExists = await $ctx.$cache.exists('user-lock:123', 'user-456');

Delete Cache Key

await $ctx.$cache.deleteKey(key);

// Example
await $ctx.$cache.deleteKey('user:123');

Next Steps