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 () => { /* idempotent work */ }, 60_000, 'poll-1m');
// jobs.pause() / jobs.resume() / jobs.destroy()
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
Source: veliovgroup/josk — distributed by TomeVault.
1---2name: josk3description: 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. Use when this capability is needed.4---56# JoSk78Distributed `setInterval` / `setTimeout` / `setImmediate` for Node ≥20.9 and Bun ≥1.1.9Server-only. Schedule in Redis, MongoDB, or PostgreSQL; lease + atomic claim limit duplicate ticks.1011## Quick start1213JoSk does not open connections — pass a connected client. Always wire `onError` and `destroy()` on shutdown. Read [references/](references/) lazily; do not guess v4/v5/v6 semantics from memory.1415```js16import { JoSk, RedisAdapter } from 'josk';17import { createClient } from 'redis';1819const client = createClient({ url: process.env.REDIS_URL });20await client.connect();2122const jobs = new JoSk({23 adapter: new RedisAdapter({ client }),24 onError: (title, { error, uid }) => console.error(title, uid, error),25});2627await jobs.setInterval(async () => { /* idempotent work */ }, 60_000, 'poll-1m');28// jobs.pause() / jobs.resume() / jobs.destroy()29```3031## Reference map3233| Question | Read |34|---|---|35| Options, methods, hooks, types | [references/api.md](references/api.md) |36| Adapter setup, cluster rules, custom adapter | [references/adapters.md](references/adapters.md) |37| Handlers, CRON, concurrency, shutdown | [references/patterns.md](references/patterns.md) |38| Meteor / `ostrio:cron-jobs` | [references/meteor.md](references/meteor.md) |39| Zombies, jitter, migrations, KeyDB | [references/troubleshooting.md](references/troubleshooting.md) |4041## Mental model4243- **`adapter`** required — `RedisAdapter`, `MongoAdapter`, `PostgresAdapter`, or custom ([adapters.md](references/adapters.md)).44- **`uid`** — app-wide unique string per logical task; reuse collides in storage.45- **Handler** — async/Promise preferred; sync zero-arg; or `(ready) =>` for callback APIs ([patterns.md](references/patterns.md)).46- **`set*` → `Promise<string>`** — pass string or that Promise to `clear*`.4748## Pick the adapter4950| Adapter | Choose when |51|---|---|52| **PostgreSQL** | Multi-DC, clock skew, strict single-claim; `SKIP LOCKED` |53| **Redis / KeyDB / Valkey** | Single-region, high frequency; single writable primary only; Cluster needs `useHashTags: true` |54| **MongoDB** | App already on Mongo (Meteor: `MongoInternals…mongo.db`); official `mongodb` driver |5556## Pick the scheduling method5758| Method | Guarantee | Use when |59|---|---|---|60| `setInterval(fn, delay, uid)` | At-least-once per tick | Idempotent recurring work |61| `setTimeout(fn, delay, uid)` | At-most-once | One-shot; duplicate worse than miss; removed before handler |62| `setImmediate(fn, uid)` | At-most-once | One-shot fire-now; same as `setTimeout` with delay 0 |6364`zombieTime` (default 15 min): max interval handler runtime before re-claim. Keep ≥ slowest handler + margin; not below 60s.6566## Throughput6768- `execute: 'batch'` (default) — all due tasks per lease; `'one'` — one task per lease69- `concurrency: Infinity` (default) — parallel handlers; set integer to cap pool/API/CPU7071## Red flags7273Call out proactively when reviewing JoSk usage:7475- Missing `onError`76- Reused `uid` across different tasks77- Default `zombieTime` with handlers >15 min78- `resetOnInit: true` in production cluster79- Replica reads / multi-writer Redis80- Redis Cluster without `useHashTags: true`81- Intervals <~2s (storage + jitter overlap)82- MongoAdapter on CosmosDB/DocumentDB/Mongoose without warning83- KeyDB active-replication / multi-master8485---86> Source: [veliovgroup/josk](https://github.com/veliovgroup/josk) — distributed by [TomeVault](https://tomevault.io).87<!-- tomevault:4.0:skill_md:2026-06-19 -->