Skip to content

SQLite Example ​

This example demonstrates Conveyor with SqliteStore for file-based persistence with rate limiting and recurring jobs. SQLite is a good choice for single-server applications that need persistence without the overhead of a separate database server.

Run this example:

bash
deno run --allow-all examples/with-sqlite/main.ts

Full Source (Annotated) ​

Store Setup ​

typescript
import { Queue, Worker } from '@conveyor/core';
import { SqliteStore } from '@conveyor/store-sqlite-node';

const store = new SqliteStore({ filename: './data/queue.db' });
await store.connect();
console.log('Connected to SQLite (./data/queue.db)');

The SqliteStore creates (or opens) a SQLite database file at the given path. On connect(), it enables WAL mode for better concurrent read performance and runs migrations automatically.

Import the correct package for your runtime:

RuntimePackage
Node.js / Deno@conveyor/store-sqlite-node
Bun@conveyor/store-sqlite-bun
Deno (native)@conveyor/store-sqlite-deno

Define Queue ​

typescript
interface TaskPayload {
  url: string;
  retries?: number;
}

const queue = new Queue<TaskPayload>('tasks', {
  store,
  defaultJobOptions: {
    attempts: 3,
    backoff: { type: 'fixed', delay: 2000 }, // Fixed 2s delay between retries
  },
});

This example uses 'fixed' backoff, which waits the same duration between every retry attempt. Compare with 'exponential' which doubles the delay each time.

Create Worker with Rate Limiting ​

typescript
const worker = new Worker<TaskPayload>(
  'tasks',
  async (job) => {
    console.log(`[${job.id}] Processing ${job.data.url}`);
    await job.updateProgress(50);

    // Simulate HTTP fetch
    await new Promise((r) => setTimeout(r, 200));

    await job.updateProgress(100);
    return { status: 200, url: job.data.url };
  },
  {
    store,
    concurrency: 3,
    limiter: { max: 5, duration: 1000 }, // Max 5 jobs per second
  },
);

The limiter option applies a sliding-window rate limit. In this case, the worker processes at most 5 jobs per 1000ms window, regardless of the concurrency setting. This is useful for respecting external API rate limits.

Events ​

typescript
worker.on('completed', (data: unknown) => {
  const { result } = data as { job: unknown; result: unknown };
  console.log('  -> completed:', result);
});

worker.on('failed', (data: unknown) => {
  const { error } = data as { job: unknown; error: Error };
  console.error('  -> failed:', error.message);
});

Adding Jobs in Bulk ​

typescript
const urls = [
  'https://example.com/api/users',
  'https://example.com/api/orders',
  'https://example.com/api/products',
  'https://example.com/api/stats',
  'https://example.com/api/health',
];

for (const url of urls) {
  await queue.add('fetch', { url });
}

For better performance with many jobs, use addBulk():

typescript
await queue.addBulk(
  urls.map((url) => ({ name: 'fetch', data: { url } })),
);

Recurring Jobs ​

typescript
// Health check every 10 seconds, up to 3 times
await queue.every('10s', 'health-check', {
  url: 'https://example.com/api/health',
}, { repeat: { limit: 3 } });

The repeat.limit option caps the number of repetitions. Without it, the job repeats indefinitely.

Graceful Shutdown ​

typescript
console.log('Shutting down...');
await worker.close();
await queue.close();
await store.disconnect();
console.log('Done!');

SQLite-Specific Considerations ​

WAL Mode ​

The SQLite store enables WAL (Write-Ahead Logging) mode automatically. This allows concurrent reads while a write is in progress, improving throughput.

Locking ​

SQLite uses BEGIN IMMEDIATE transactions for job fetching. This provides exclusive write access within a transaction, preventing double-processing.

Event Delivery ​

Unlike PostgreSQL's LISTEN/NOTIFY, SQLite does not have a built-in pub/sub mechanism. The SQLite store uses polling to deliver events. This means event delivery has slightly higher latency compared to PostgreSQL, but is sufficient for single-process applications.

When to Use SQLite vs PostgreSQL ​

Choose SQLite when:

  • Your application runs on a single server
  • You want zero infrastructure overhead
  • You need file-based persistence (survives restarts)
  • Event latency of ~1 second is acceptable

Choose PostgreSQL when:

  • You need multi-process or distributed workers
  • You need real-time event delivery
  • You need to query job data from external tools
  • You are already running PostgreSQL

Released under the MIT License.