Arcwayv0.3.0

Queue & Cache

Persistent FIFO queue and key-value cache with multiple driver backends

Queue

Persistent queue for background processing. Available via ctx.queue.

// Push a single item onto a topic
await ctx.queue.push('email-queue', {
  to: 'user@example.com',
  subject: 'Hello',
});

// Push multiple items at once
await ctx.queue.pushBulk('email-queue', [
  { to: 'alice@example.com', subject: 'Welcome' },
  { to: 'bob@example.com', subject: 'Welcome' },
]);

// Pop items for processing (FIFO, locked)
const items = await ctx.queue.pop('email-queue', 5);
for (const item of items) {
  try {
    await sendEmail(item.data);
    await ctx.queue.remove([item.id]); // Remove after success
  } catch (err) {
    // Leave in queue — lock expires, will be retried
  }
}

Lock Mechanism

When you pop items, they're locked for a cooldown period (default: 5 minutes). During this time, other consumers won't see them. If you don't remove an item, it becomes available again after the lock expires — providing automatic retry behavior.

Namespacing

Create a namespaced queue to isolate topics:

const userQueue = ctx.queue.withNamespace('users');
await userQueue.push('welcome-emails', { userId: 123 });
// Topic becomes 'users:welcome-emails' internally

Queue Drivers

  • knex (database-backed, default) — uses "available"/"locked" status states in the database
  • redis — uses Lua scripts for atomic pop-and-lock operations

Cache

Key-value cache. Available via ctx.cache.

// Set with optional TTL (milliseconds)
await ctx.cache.set('user:123', userData, 3_600_000); // 1 hour

// Get (returns null if missing or expired)
const cached = await ctx.cache.get('user:123');

// Delete
await ctx.cache.delete('user:123');

// Cache-aside helper — get from cache or compute and store
const user = await ctx.cache.wrap(
  'user:123',
  async () => ctx.db('users').where('id', 123).first(),
  '1h',
);

Cache-Aside Pattern

wrap() is the recommended way to use caching. It checks the cache first, and if the key is missing (or expired), calls the function, stores the result, and returns it:

export const GET = {
  handler: async (ctx) => {
    const { cache, db } = ctx;
    // Cached for 10 minutes
    const stats = await cache.wrap(
      'dashboard:stats',
      async () => {
        const users = await db('users').count('* as count').first();
        const posts = await db('posts').count('* as count').first();
        return { users: users.count, posts: posts.count };
      },
      '10m',
    );
    return { data: stats };
  },
};

If the cached value is corrupt (invalid JSON), wrap treats it as a cache miss and recomputes.

Namespacing

Create a namespaced cache to avoid key collisions:

const userCache = ctx.cache.withNamespace('users');
await userCache.set('123', userData); // Key becomes 'users:123' internally
await userCache.get('123');

Cache Drivers

  • memory (in-process, default) — fast, single-server only. Uses cache-manager internally.
  • redis (distributed) — shared across multiple servers. Supports indefinite TTL.
// arcway.config.js
export default {
  cache: {
    driver: 'redis',
    redis: {
      host: 'localhost',
      port: 6379,
    },
  },
};

On this page