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.

For high-volume proxy checks, cache only derived catalog or access data with a short explicit TTL. Keep credentials, token revocation, and other security decisions live; a cache hit must never be treated as proof that a credential is still valid.

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