| name | josk |
| description | Guides JoSk integration for horizontally scaled Node.js and Bun apps — cluster-wide scheduling via Redis, MongoDB, or PostgreSQL so each tick runs on one instance. Use when writing or reviewing recurring jobs, cron-style tasks, `setInterval`/`setTimeout`/`setImmediate` work, periodic background jobs (queues, sync, polling, cleanup), multi-instance / Kubernetes / PM2 / Meteor deployments, the `josk` npm package, or `ostrio:cron-jobs`. Also when the user names JoSk, `RedisAdapter`, `MongoAdapter`, `PostgresAdapter`, Redis Cluster / KeyDB Cluster hash-tag routing via `useHashTags`, at-least-once / at-most-once semantics, zombie recovery, leases, `zombieTime`, `execute`, `concurrency`, `pause()`/`resume()` instance backpressure, or comparisons to Agenda, Bree, node-cron, Bull, or BullMQ. |
JoSk
Distributed setInterval / setTimeout / setImmediate for Node ≥20.9 and Bun ≥1.1.
Server-only. Schedule in Redis, MongoDB, or PostgreSQL; lease + atomic claim limit duplicate ticks.
Quick start
JoSk does not open connections — pass a connected client. Always wire onError and destroy() on shutdown. Read references/ lazily; do not guess v4/v5/v6 semantics from memory.
import { JoSk, RedisAdapter } from 'josk';
import { createClient } from 'redis';
const client = createClient({ url: process.env.REDIS_URL });
await client.connect();
const jobs = new JoSk({
adapter: new RedisAdapter({ client }),
onError: (title, { error, uid }) => console.error(title, uid, error),
});
await jobs.setInterval(async () => { }, 60_000, 'poll-1m');
Reference map
| Question | Read |
|---|
| Options, methods, hooks, types | references/api.md |
| Adapter setup, cluster rules, custom adapter | references/adapters.md |
| Handlers, CRON, concurrency, shutdown | references/patterns.md |
Meteor / ostrio:cron-jobs | references/meteor.md |
| Zombies, jitter, migrations, KeyDB | references/troubleshooting.md |
Mental model
adapter required — RedisAdapter, MongoAdapter, PostgresAdapter, or custom (adapters.md).
uid — app-wide unique string per logical task; reuse collides in storage.
- Handler — async/Promise preferred; sync zero-arg; or
(ready) => for callback APIs (patterns.md).
set* → Promise<string> — pass string or that Promise to clear*.
Pick the adapter
| Adapter | Choose when |
|---|
| PostgreSQL | Multi-DC, clock skew, strict single-claim; SKIP LOCKED |
| Redis / KeyDB / Valkey | Single-region, high frequency; single writable primary only; Cluster needs useHashTags: true |
| MongoDB | App already on Mongo (Meteor: MongoInternals…mongo.db); official mongodb driver |
Pick the scheduling method
| Method | Guarantee | Use when |
|---|
setInterval(fn, delay, uid) | At-least-once per tick | Idempotent recurring work |
setTimeout(fn, delay, uid) | At-most-once | One-shot; duplicate worse than miss; removed before handler |
setImmediate(fn, uid) | At-most-once | One-shot fire-now; same as setTimeout with delay 0 |
zombieTime (default 15 min): max interval handler runtime before re-claim. Keep ≥ slowest handler + margin; not below 60s.
Throughput
execute: 'batch' (default) — all due tasks per lease; 'one' — one task per lease
concurrency: Infinity (default) — parallel handlers; set integer to cap pool/API/CPU
Red flags
Call out proactively when reviewing JoSk usage:
- Missing
onError
- Reused
uid across different tasks
- Default
zombieTime with handlers >15 min
resetOnInit: true in production cluster
- Replica reads / multi-writer Redis
- Redis Cluster without
useHashTags: true
- Intervals <~2s (storage + jitter overlap)
- MongoAdapter on CosmosDB/DocumentDB/Mongoose without warning
- KeyDB active-replication / multi-master