JoSk usage within Meteor.js
August 11, 2026 · View on GitHub
NPM josk package can be used in Meteor environment just perfectly fine since it's server-only Node.js package.
If Meteor.js packages are preferred in your project/environment follow this document to install JoSk as ostrio:cron-jobs Atmosphere or Packosphere package
Install
meteor add ostrio:cron-jobs
Usage
import { JoSk, RedisAdapter, MongoAdapter, PostgresAdapter } from 'meteor/ostrio:cron-jobs';
Initialization
JoSk is storage-agnostic (since v4.0.0). Atmosphere package ships Redis, MongoDB, and PostgreSQL adapters. Documentation below focuses on MongoDB provided and managed by Meteor.js. For Redis and PostgreSQL adapter details follow NPM package docs; options are same, import path is different.
import { MongoInternals } from 'meteor/mongo';
import { JoSk, MongoAdapter } from 'meteor/ostrio:cron-jobs';
const jobs = new JoSk({
adapter: new MongoAdapter({
db: MongoInternals.defaultRemoteCollectionDriver().mongo.db,
prefix: 'cluster-scheduler',
}),
execute: 'batch',
minRevolvingDelay: 128,
maxRevolvingDelay: 768,
onError(reason, details) {
// Use onError hook to catch runtime exceptions
// thrown inside scheduled tasks
console.log(reason, details.error);
}
});
jobs.setInterval(async () => {
/* ...code here... */
}, 60000, 'task-1m');
// TO SUPPORT CALLBACK APIs
// CALL ready() ONCE RUN IS COMPLETE
jobs.setInterval((ready) => {
/* ...code here... */
asyncCall(() => {
/* ...more code here...*/
ready();
});
}, 60000, 'task-1m');
Options
Same JoSk options from NPM package are available in Meteor:
adapter— required storage adapter instance:MongoAdapter,RedisAdapter, orPostgresAdapterexecute—batch(default) drains due tasks under one lease;oneclaims one task per leasezombieTime— stuck-task retry time; default is900000mslockLeaseTime— scheduler lease TTL; default ismin(zombieTime, 30000)with polling-based floorlockOwnerId— optional stable owner id for scheduler lease tokensminRevolvingDelayandmaxRevolvingDelay— polling jitter range; higher values reduce storage writesautoClear— removes storage tasks missing from current process memoryonErrorandonExecuted— runtime hooks for task failures and completed executions
Adapter options:
MongoAdapter:db,prefix,lockCollectionName,resetOnInitRedisAdapter:client,prefix,resetOnInit,useHashTagsPostgresAdapter:client,prefix,resetOnInit
Keep resetOnInit: false in clustered production. It deletes current-prefix adapter state during initialization.
Redis Adapter
Install Redis driver in Meteor app if Redis storage is used:
meteor npm install redis
import { JoSk, RedisAdapter } from 'meteor/ostrio:cron-jobs';
import { createClient } from 'redis';
const redisClient = await createClient({
url: process.env.REDIS_URL
}).connect();
const jobs = new JoSk({
adapter: new RedisAdapter({
client: redisClient,
prefix: 'cluster-scheduler',
// useHashTags: true, // Enable for Redis Cluster / KeyDB Cluster
}),
});
Use one writable Redis/KeyDB primary. Do not route JoSk traffic to replicas. For Redis Cluster / KeyDB Cluster, set useHashTags: true.
PostgreSQL Adapter
Install PostgreSQL driver in Meteor app if PostgreSQL storage is used:
meteor npm install pg
import { JoSk, PostgresAdapter } from 'meteor/ostrio:cron-jobs';
import { Pool } from 'pg';
const pool = new Pool({
connectionString: process.env.PG_URL
});
const jobs = new JoSk({
adapter: new PostgresAdapter({
client: pool,
prefix: 'cluster-scheduler',
}),
execute: 'batch',
});
Use one writable PostgreSQL primary. Adapter creates josk_tasks and josk_locks tables in current database/schema. Use same prefix for instances sharing one schedule; use different prefixes for isolated apps, tenants, or tests.
Guidelines
- Create JoSk only on server startup. Package is server-only.
- Use same codebase and task
uidset on every horizontally scaled Meteor instance that shares aprefix. - Always use unique
uidper logical task. Do not reuse sameuidfor different schedules. - Always call
ready()for callback-style or long-running async tasks. Promise-returning handlers are also supported. - Prefer
execute: 'batch'for normal production throughput. Useexecute: 'one'when smaller execution bursts or instance fairness is preferred. - For multi-DC strict single-claim scheduling, use strongly consistent storage with one write authority.
Note: This library relies on job ID. Always use different uid, even for the same task:
const task = function (ready) {
//... code here
ready();
};
jobs.setInterval(task, 60000, 'task-1m'); // every minute
jobs.setInterval(task, 2 * 60000, 'task-2m'); // every two minutes
CRON scheduler
Use JoSk to invoke synchronized tasks by CRON schedule, and cron-parser package to parse CRON expressions. To simplify CRON scheduling — grab and use setCron function below:
import { MongoInternals } from 'meteor/mongo';
import { JoSk, MongoAdapter } from 'meteor/ostrio:cron-jobs';
import { CronExpressionParser } from 'cron-parser';
const jobsCron = new JoSk({
adapter: new MongoAdapter({
db: MongoInternals.defaultRemoteCollectionDriver().mongo.db,
prefix: 'cron-scheduler',
}),
minRevolvingDelay: 512, // Adjust revolving delays to higher values
maxRevolvingDelay: 1000, // as CRON schedule defined to seconds
});
// CREATE HELPER FUNCTION (cron-parser@^5)
const setCron = async (uniqueName, cronTask, task) => {
const next = CronExpressionParser.parse(cronTask).next().toDate();
const initialDelay = Math.max(0, +next - Date.now());
return await jobsCron.setInterval(function (ready) {
ready(CronExpressionParser.parse(cronTask).next().toDate());
task();
}, initialDelay, uniqueName);
};
// SCHEDULE A TASK
setCron('Run every two seconds cron', '*/2 * * * * *', function () {
console.log(new Date);
});
Running Tests
- Clone this package
- Make sure Redis and PostgreSQL are installed and running when testing those adapters (Meteor ships its own
mongodfor package tests — no separate MongoDB install required for the Mongo adapter suite) - In Terminal (Console) go to directory where package is cloned
- Then run:
# Default Meteor package tests require REDIS_URL.
# Postgres tests are skipped when PG_URL is not provided.
REDIS_URL="redis://127.0.0.1:6379" PG_URL="postgres://postgres:postgres@127.0.0.1:5432/meteor-josk-test" meteor test-packages ./ --driver-package=meteortesting:mocha
# CI adapter-only runs (METEOR_TEST_SUITE in package.js): mongo | redis | postgres
# Mongo CI: omit MONGO_URL so Meteor starts its bundled mongod
METEOR_TEST_SUITE=mongo meteor test-packages ./ --driver-package=meteortesting:mocha --once
METEOR_TEST_SUITE=redis REDIS_URL="redis://127.0.0.1:6379" meteor test-packages ./ --driver-package=meteortesting:mocha --once
METEOR_TEST_SUITE=postgres PG_URL="postgres://postgres:postgres@127.0.0.1:5432/meteor-josk-test" meteor test-packages ./ --driver-package=meteortesting:mocha --once
# With custom port
REDIS_URL="redis://127.0.0.1:6379" PG_URL="postgres://postgres:postgres@127.0.0.1:5432/meteor-josk-test" meteor test-packages ./ --driver-package=meteortesting:mocha --port 8888
# With local MongoDB, Postgres, debug, and custom port
DEBUG=true MONGO_URL="mongodb://127.0.0.1:27017/meteor-josk-test" REDIS_URL="redis://127.0.0.1:6379" PG_URL="postgres://postgres:postgres@127.0.0.1:5432/meteor-josk-test" meteor test-packages ./ --driver-package=meteortesting:mocha --port 8888
# Be patient, tests are taking around 4 mins
Environment variables consumed by the Meteor test suite:
REDIS_URL— required, e.g.redis://127.0.0.1:6379MONGO_URL— optional override; when omitted,meteor test-packagesstarts Meteor’s bundledmongod(GitHub Actions mongo adapter job relies on this; do not point it at a service container unless you intend to test an external server)PG_URL— required for the PostgreSQL adapter suite, e.g.postgres://postgres:postgres@127.0.0.1:5432/postgres; Postgres tests are skipped when this is unsetDEBUG=true— enables verbose JoSk logging during the run
Requirements
Meteor 2.14+ and 3.2+ supported (api.versionsFrom(['2.14', '3.2']); mirrored in package.json → meteor.versionsFrom). npm installs require Node ≥ 20.9. Meteor 2.x bundles Node 14 (no crypto.randomUUID — JoSk falls back to randomBytes hex IDs). CI: 2.14–2.16 and 3.2 / 3.3.1 / 3.4.
meteorTestProfile() branches on Meteor's bundled Node at test-packages time:
| Node | Meteor | Test npm pins | Mocha driver |
|---|---|---|---|
| 14–17 | 2.x | chai@4, cron-parser@4, pg@8.11 | meteortesting:mocha@2.1.0 |
| 18+ | 3.x | current majors | meteortesting:mocha@3.3.0 |
TypeScript tests (meteor-types.ts) always run. npm devDependencies unchanged for npm test.
Do not commit .versions — it locks packages for one Meteor release and breaks multi-version CI. Pin the driver in package.js only; pass --driver-package=meteortesting:mocha on the CLI (@x.y.z on the CLI breaks test-packages on Meteor 3.x):
meteor test-packages ./ --driver-package=meteortesting:mocha --once
Meteor 2.x still uses fibers — see below if handlers throw Can't wait without a fiber.
Known Meteor Issues (Meteor 2 / fibers)
Meteor 2.x relies on fibers and may cause the next exception:
Error: Can't wait without a fiber
Can be easily solved via "bounding to Fiber":
const bound = Meteor.bindEnvironment((callback) => {
callback();
});
const db = Collection.rawDatabase();
const jobs = new JoSk({
adapter: new MongoAdapter({
db,
}),
});
const task = (ready) => {
bound(() => { // <-- use "bound" inside of a task
ready();
});
};
jobs.setInterval(task, 60 * 60 * 1000, 'task');