Reference
Persistence providers
A provider decides where state is stored. Cronbake includes one for files and one for Redis, and you can write your own with two methods.
Start with the Persistence guide for how saving and restoring work.
FilePersistenceProvider
Stores state as formatted JSON in one file. This is what you get by default when you set persistence.enabled without a provider.
import Baker, { FilePersistenceProvider } from "cronbake";
const baker = Baker.create({
persistence: {
enabled: true,
provider: new FilePersistenceProvider("./data/cronbake.json"),
},
});- Writes go to a temporary file (the same path plus
.tmp) and are then renamed into place, so a crash mid-write can’t leave a half-written state file. - Writes are queued, so they can’t interleave.
- Missing parent folders are created.
- A missing, unreadable or corrupt file loads as no state, with no error or warning.
RedisPersistenceProvider
Stores state as a JSON string under one key. Cronbake doesn’t depend on a Redis library, so you pass in your own client. It only needs get and set, in the usual form that both ioredis and node-redis provide.
import Baker, { RedisPersistenceProvider } from "cronbake";
import Redis from "ioredis";
const baker = Baker.create({
persistence: {
enabled: true,
provider: new RedisPersistenceProvider({
client: new Redis(),
key: "myapp:cronbake", // defaults to "cronbake:state"
}),
},
});The key is written with no expiry. A missing key or invalid JSON loads as no state. Errors from the client itself, such as a dropped connection, aren’t swallowed, so a failed get makes ready() reject.
The constructor throws if you don’t pass a client.
Writing your own
A provider is an object with save and load. load resolves to null when there’s nothing saved yet.
import Baker, { type IBakerOptions } from "cronbake";
type Provider = NonNullable<NonNullable<IBakerOptions["persistence"]>["provider"]>;
const postgresProvider: Provider = {
async save(state) {
await db.query(
`INSERT INTO cronbake_state (id, state) VALUES (1, $1)
ON CONFLICT (id) DO UPDATE SET state = $1`,
[JSON.stringify(state)],
);
},
async load() {
const { rows } = await db.query("SELECT state FROM cronbake_state WHERE id = 1");
return rows[0]?.state ?? null;
},
};
const baker = Baker.create({
persistence: { enabled: true, strategy: "custom", provider: postgresProvider },
});The package doesn’t export a PersistenceProvider type yet, so the example above derives it from IBakerOptions.
The baker queues its own calls to save, so they arrive one at a time from any single baker. If load throws, ready() rejects. If an automatic save rejects, the error goes to logger.warn(), and if you called saveState() yourself, your call rejects.
PersistedState
The object passed to save and returned from load. A load result is checked lightly, so a job without a name or cron is skipped with a warning.
type PersistedState = {
version: number;
timestamp: string; // ISO 8601
config: SchedulerConfig;
jobs: PersistedJob[];
};
type PersistedJob = {
name: string;
cron: string;
status: "running" | "stopped" | "paused" | "error";
priority: number;
persist?: boolean;
metrics?: JobMetrics;
history?: {
timestamp: string; // ISO 8601
duration: number;
success: boolean;
error?: string;
}[];
};