@rebasepro/cli 0.6.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -1
- package/dist/commands/api-keys.d.ts +1 -0
- package/dist/commands/generate_sdk.d.ts +1 -1
- package/dist/index.es.js +399 -57
- package/dist/index.es.js.map +1 -1
- package/package.json +20 -15
- package/templates/template/backend/package.json +0 -1
- package/templates/template/backend/src/index.ts +1 -1
- package/templates/template/config/collections/posts.ts +1 -3
- package/templates/template/config/collections/tags.ts +1 -3
- package/templates/template/config/collections/users.ts +1 -4
- package/templates/template/package.json +0 -1
- package/templates/template/scripts/example.ts +4 -1
- package/skills/rebase-admin/SKILL.md +0 -710
- package/skills/rebase-api/SKILL.md +0 -662
- package/skills/rebase-api/references/.gitkeep +0 -3
- package/skills/rebase-auth/SKILL.md +0 -1143
- package/skills/rebase-auth/references/.gitkeep +0 -3
- package/skills/rebase-backend-postgres/SKILL.md +0 -633
- package/skills/rebase-backend-postgres/references/.gitkeep +0 -3
- package/skills/rebase-basics/SKILL.md +0 -749
- package/skills/rebase-basics/references/.gitkeep +0 -3
- package/skills/rebase-collections/SKILL.md +0 -1328
- package/skills/rebase-collections/references/.gitkeep +0 -3
- package/skills/rebase-cron-jobs/SKILL.md +0 -699
- package/skills/rebase-cron-jobs/references/.gitkeep +0 -1
- package/skills/rebase-custom-functions/SKILL.md +0 -233
- package/skills/rebase-deployment/SKILL.md +0 -885
- package/skills/rebase-deployment/references/.gitkeep +0 -3
- package/skills/rebase-design-language/SKILL.md +0 -692
- package/skills/rebase-email/SKILL.md +0 -701
- package/skills/rebase-email/references/.gitkeep +0 -1
- package/skills/rebase-entity-history/SKILL.md +0 -485
- package/skills/rebase-entity-history/references/.gitkeep +0 -1
- package/skills/rebase-local-env-setup/SKILL.md +0 -189
- package/skills/rebase-local-env-setup/references/.gitkeep +0 -3
- package/skills/rebase-realtime/SKILL.md +0 -755
- package/skills/rebase-realtime/references/.gitkeep +0 -3
- package/skills/rebase-sdk/SKILL.md +0 -594
- package/skills/rebase-sdk/references/.gitkeep +0 -0
- package/skills/rebase-storage/SKILL.md +0 -765
- package/skills/rebase-storage/references/.gitkeep +0 -3
- package/skills/rebase-studio/SKILL.md +0 -746
- package/skills/rebase-studio/references/.gitkeep +0 -3
- package/skills/rebase-ui-components/SKILL.md +0 -1488
- package/skills/rebase-ui-components/references/.gitkeep +0 -3
- package/skills/rebase-webhooks/SKILL.md +0 -623
- package/skills/rebase-webhooks/references/.gitkeep +0 -1
- package/templates/template/backend/drizzle.config.ts +0 -51
|
@@ -1,699 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: rebase-cron-jobs
|
|
3
|
-
description: Guide for scheduling recurring background tasks with Rebase's built-in cron job system. Use this skill when the user needs background jobs, data cleanup, report generation, external API syncing, or any scheduled task.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Rebase Cron Jobs
|
|
7
|
-
|
|
8
|
-
> **IMPORTANT FOR AGENTS**: Rebase has a **built-in cron scheduler** — do NOT install external libraries (`node-cron`, `agenda`, `bull`) or set up separate worker processes. Drop a TypeScript file in the `crons/` directory.
|
|
9
|
-
|
|
10
|
-
> **IMPORTANT FOR AGENTS**: Every cron handler receives `ctx.client` — a full `RebaseClient` instance. This is **THE** primary way cron jobs read/write data. Always use `ctx.client` in examples, never raw SQL or direct DB imports.
|
|
11
|
-
|
|
12
|
-
## Overview
|
|
13
|
-
|
|
14
|
-
Rebase includes a built-in cron job scheduler for running recurring background tasks. Cron jobs follow the **file-based discovery** pattern — drop a TypeScript file in `crons/`, and it's automatically registered and scheduled.
|
|
15
|
-
|
|
16
|
-
- **Zero dependencies** — No external scheduler libraries needed
|
|
17
|
-
- **`ctx.client`** — Full `RebaseClient` for CRUD, auth, storage, and more
|
|
18
|
-
- **Concurrency guard** — A job that's still running is skipped, never double-executed
|
|
19
|
-
- **Timeout enforcement** — Handlers are raced against a configurable timeout
|
|
20
|
-
- **Studio dashboard** — Monitor all jobs, view execution history, trigger manually
|
|
21
|
-
- **Admin REST API** — List, trigger, enable/disable, and view logs
|
|
22
|
-
- **Database persistence** — Execution logs stored in `rebase.cron_logs` (PostgreSQL)
|
|
23
|
-
|
|
24
|
-
## Setup
|
|
25
|
-
|
|
26
|
-
Enable cron jobs by adding `cronsDir` to your backend config:
|
|
27
|
-
|
|
28
|
-
```typescript
|
|
29
|
-
const backend = await initializeRebaseBackend({
|
|
30
|
-
// ... other config
|
|
31
|
-
cronsDir: path.resolve(__dirname, "../crons"), // ← add this
|
|
32
|
-
});
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
### Configuration Options
|
|
36
|
-
|
|
37
|
-
| Option | Type | Default | Description |
|
|
38
|
-
|--------|------|---------|-------------|
|
|
39
|
-
| `cronsDir` | `string` | — | Absolute path to the directory containing cron job files. Required to enable cron jobs. |
|
|
40
|
-
| `cronPersistence` | `boolean` | `true` | Enable/disable database persistence for execution logs. When `false`, logs are kept in-memory only. |
|
|
41
|
-
|
|
42
|
-
```typescript
|
|
43
|
-
const backend = await initializeRebaseBackend({
|
|
44
|
-
cronsDir: path.resolve(__dirname, "../crons"),
|
|
45
|
-
cronPersistence: false, // disable DB persistence (in-memory only)
|
|
46
|
-
});
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
### What Happens on Startup
|
|
50
|
-
|
|
51
|
-
Rebase will:
|
|
52
|
-
1. Scan the directory for `.ts` / `.js` files
|
|
53
|
-
2. **Validate** each job's cron schedule — invalid schedules are rejected with an error and the job is NOT registered
|
|
54
|
-
3. Register each default export as a cron job
|
|
55
|
-
4. If a **duplicate job ID** is found, the previous job is overwritten with a warning
|
|
56
|
-
5. Auto-create the `rebase.cron_logs` table in PostgreSQL (unless `cronPersistence: false`)
|
|
57
|
-
6. Mount admin REST routes at `/api/cron` (protected by `requireAuth` + `requireAdmin`)
|
|
58
|
-
7. Seed in-memory counters (`totalRuns`, `totalFailures`, `lastRunAt`) from the database
|
|
59
|
-
8. Start the scheduler
|
|
60
|
-
|
|
61
|
-
## Defining a Cron Job
|
|
62
|
-
|
|
63
|
-
Create a file in your `backend/crons/` directory that default-exports a `CronJobDefinition`:
|
|
64
|
-
|
|
65
|
-
```typescript
|
|
66
|
-
// backend/crons/cleanup-sessions.ts
|
|
67
|
-
import type { CronJobDefinition } from "@rebasepro/types";
|
|
68
|
-
|
|
69
|
-
const job: CronJobDefinition = {
|
|
70
|
-
schedule: "0 3 * * *", // daily at 3 AM
|
|
71
|
-
name: "Cleanup Expired Sessions",
|
|
72
|
-
description: "Removes sessions older than 30 days",
|
|
73
|
-
timeoutSeconds: 60, // max execution time (default: 300)
|
|
74
|
-
|
|
75
|
-
async handler(ctx) {
|
|
76
|
-
ctx.log("Starting session cleanup...");
|
|
77
|
-
|
|
78
|
-
// ✅ Use ctx.client to interact with your data
|
|
79
|
-
const { data: oldSessions } = await ctx.client.collection("sessions").getList({
|
|
80
|
-
filter: { createdAt: { lt: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString() } },
|
|
81
|
-
limit: 500,
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
for (const session of oldSessions) {
|
|
85
|
-
await ctx.client.collection("sessions").delete(session.id);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
ctx.log(`Cleaned up ${oldSessions.length} expired sessions`);
|
|
89
|
-
|
|
90
|
-
// Return value is stored in the log and shown in Studio
|
|
91
|
-
return { deletedSessions: oldSessions.length };
|
|
92
|
-
},
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
export default job;
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
The **filename** (without extension) becomes the job's unique ID — e.g., `cleanup-sessions`.
|
|
99
|
-
|
|
100
|
-
### More Examples
|
|
101
|
-
|
|
102
|
-
#### Sync Data from an External API
|
|
103
|
-
|
|
104
|
-
```typescript
|
|
105
|
-
// backend/crons/sync-exchange-rates.ts
|
|
106
|
-
import type { CronJobDefinition } from "@rebasepro/types";
|
|
107
|
-
|
|
108
|
-
const job: CronJobDefinition = {
|
|
109
|
-
schedule: "0 */6 * * *", // every 6 hours
|
|
110
|
-
name: "Sync Exchange Rates",
|
|
111
|
-
description: "Pulls latest rates from Open Exchange Rates API",
|
|
112
|
-
timeoutSeconds: 30,
|
|
113
|
-
|
|
114
|
-
async handler(ctx) {
|
|
115
|
-
const res = await fetch("https://api.exchangerate.host/latest");
|
|
116
|
-
const data = await res.json();
|
|
117
|
-
|
|
118
|
-
ctx.log("Fetched rates for", Object.keys(data.rates).length, "currencies");
|
|
119
|
-
|
|
120
|
-
// Upsert into your collection using ctx.client
|
|
121
|
-
for (const [currency, rate] of Object.entries(data.rates)) {
|
|
122
|
-
const existing = await ctx.client.collection("exchange_rates").getList({
|
|
123
|
-
filter: { currency },
|
|
124
|
-
limit: 1,
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
if (existing.data.length > 0) {
|
|
128
|
-
await ctx.client.collection("exchange_rates").update(existing.data[0].id, {
|
|
129
|
-
rate: rate as number,
|
|
130
|
-
updatedAt: new Date().toISOString(),
|
|
131
|
-
});
|
|
132
|
-
} else {
|
|
133
|
-
await ctx.client.collection("exchange_rates").create({
|
|
134
|
-
currency,
|
|
135
|
-
rate: rate as number,
|
|
136
|
-
});
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
return { currenciesUpdated: Object.keys(data.rates).length };
|
|
141
|
-
},
|
|
142
|
-
};
|
|
143
|
-
|
|
144
|
-
export default job;
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
#### Send Daily Digest Emails
|
|
148
|
-
|
|
149
|
-
```typescript
|
|
150
|
-
// backend/crons/daily-digest.ts
|
|
151
|
-
import type { CronJobDefinition } from "@rebasepro/types";
|
|
152
|
-
|
|
153
|
-
const job: CronJobDefinition = {
|
|
154
|
-
schedule: "0 8 * * 1-5", // 8 AM weekdays
|
|
155
|
-
name: "Daily Digest",
|
|
156
|
-
description: "Sends a summary email to all users with unread notifications",
|
|
157
|
-
timeoutSeconds: 120,
|
|
158
|
-
|
|
159
|
-
async handler(ctx) {
|
|
160
|
-
const { data: users } = await ctx.client.collection("users").getList({
|
|
161
|
-
filter: { digestEnabled: true },
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
let sent = 0;
|
|
165
|
-
for (const user of users) {
|
|
166
|
-
const { data: notifications } = await ctx.client.collection("notifications").getList({
|
|
167
|
-
filter: { userId: user.id, read: false },
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
if (notifications.length > 0) {
|
|
171
|
-
ctx.log(`Sending digest to ${user.email} (${notifications.length} unread)`);
|
|
172
|
-
// Send email via your preferred method
|
|
173
|
-
sent++;
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
return { emailsSent: sent, usersChecked: users.length };
|
|
178
|
-
},
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
export default job;
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
## CronJobDefinition Interface
|
|
185
|
-
|
|
186
|
-
```typescript
|
|
187
|
-
interface CronJobDefinition {
|
|
188
|
-
schedule: string; // Cron expression (5-field)
|
|
189
|
-
name: string; // Human-readable name for Studio
|
|
190
|
-
description?: string; // Optional description
|
|
191
|
-
enabled?: boolean; // Start enabled? (default: true)
|
|
192
|
-
timeoutSeconds?: number; // Max execution time (default: 300)
|
|
193
|
-
handler: (ctx: CronJobContext) => Promise<unknown> | unknown;
|
|
194
|
-
}
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
| Property | Type | Default | Description |
|
|
198
|
-
|----------|------|---------|-------------|
|
|
199
|
-
| `schedule` | `string` | — | Standard 5-field cron expression. **Validated on registration** — invalid expressions are rejected. |
|
|
200
|
-
| `name` | `string` | — | Human-readable name shown in Studio UI and API responses. |
|
|
201
|
-
| `description` | `string?` | `undefined` | Optional description shown in Studio. |
|
|
202
|
-
| `enabled` | `boolean?` | `true` | Whether the job starts enabled. Can be toggled at runtime via the Admin API. |
|
|
203
|
-
| `timeoutSeconds` | `number?` | `300` | Maximum seconds the handler may run before being timed out. |
|
|
204
|
-
| `handler` | `(ctx) => Promise<unknown> \| unknown` | — | The function executed on each tick. May return JSON-serialisable data stored in the log. |
|
|
205
|
-
|
|
206
|
-
## CronJobContext (Handler Argument)
|
|
207
|
-
|
|
208
|
-
> **IMPORTANT FOR AGENTS**: `ctx.client` is a full `RebaseClient` instance — the **same admin-level server client** used by the Rebase singleton. It bypasses the network and goes through Hono's internal request handler. Use it for all data operations inside cron handlers.
|
|
209
|
-
|
|
210
|
-
```typescript
|
|
211
|
-
interface CronJobContext {
|
|
212
|
-
/** The job's unique ID (derived from filename). */
|
|
213
|
-
jobId: string;
|
|
214
|
-
|
|
215
|
-
/** The current scheduled tick timestamp. */
|
|
216
|
-
scheduledAt: Date;
|
|
217
|
-
|
|
218
|
-
/** A simple logger scoped to this job run — output captured in the execution log. */
|
|
219
|
-
log: (...args: unknown[]) => void;
|
|
220
|
-
|
|
221
|
-
/** The RebaseClient instance to interact with the database. */
|
|
222
|
-
client: RebaseClient;
|
|
223
|
-
}
|
|
224
|
-
```
|
|
225
|
-
|
|
226
|
-
| Property | Type | Description |
|
|
227
|
-
|----------|------|-------------|
|
|
228
|
-
| `jobId` | `string` | Derived from the filename (e.g. `cleanup-sessions`). |
|
|
229
|
-
| `scheduledAt` | `Date` | The timestamp when this execution was scheduled to start. |
|
|
230
|
-
| `log` | `(...args: unknown[]) => void` | Logger whose output is captured in `CronJobLogEntry.logs`. Use like `console.log`. |
|
|
231
|
-
| `client` | `RebaseClient` | Admin-level client for CRUD, auth, storage, and any SDK operation. |
|
|
232
|
-
|
|
233
|
-
### Using `ctx.client`
|
|
234
|
-
|
|
235
|
-
`ctx.client` is the same `RebaseClient` returned by `createRebaseClient()`. It supports all SDK operations:
|
|
236
|
-
|
|
237
|
-
```typescript
|
|
238
|
-
async handler(ctx) {
|
|
239
|
-
// Collection CRUD
|
|
240
|
-
const { data } = await ctx.client.collection("orders").getList({ limit: 100 });
|
|
241
|
-
await ctx.client.collection("orders").update(id, { status: "archived" });
|
|
242
|
-
await ctx.client.collection("orders").delete(id);
|
|
243
|
-
await ctx.client.collection("metrics").create({ key: "daily_total", value: 42 });
|
|
244
|
-
|
|
245
|
-
// Filtering and sorting
|
|
246
|
-
const { data: stale } = await ctx.client.collection("tokens").getList({
|
|
247
|
-
filter: { expiresAt: { lt: new Date().toISOString() } },
|
|
248
|
-
sort: "-createdAt",
|
|
249
|
-
limit: 500,
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
// Logging
|
|
253
|
-
ctx.log("Processed", data.length, "records");
|
|
254
|
-
}
|
|
255
|
-
```
|
|
256
|
-
|
|
257
|
-
## Schedule Syntax (Cron Expressions)
|
|
258
|
-
|
|
259
|
-
Standard 5-field cron format: `minute hour day-of-month month day-of-week`
|
|
260
|
-
|
|
261
|
-
| Expression | Meaning |
|
|
262
|
-
|------------|---------|
|
|
263
|
-
| `* * * * *` | Every minute |
|
|
264
|
-
| `*/5 * * * *` | Every 5 minutes |
|
|
265
|
-
| `0 * * * *` | Every hour |
|
|
266
|
-
| `0 3 * * *` | Daily at 3:00 AM |
|
|
267
|
-
| `0 0 * * 1` | Every Monday at midnight |
|
|
268
|
-
| `0 9 1 * *` | First of each month at 9 AM |
|
|
269
|
-
| `0 9-17 * * 1-5` | Hourly, 9 AM–5 PM, weekdays |
|
|
270
|
-
|
|
271
|
-
### Supported Field Syntax
|
|
272
|
-
|
|
273
|
-
| Syntax | Example | Meaning |
|
|
274
|
-
|--------|---------|---------|
|
|
275
|
-
| `*` | `* * * * *` | Every value in the field's range |
|
|
276
|
-
| `N` | `30 * * * *` | Exact value |
|
|
277
|
-
| `N-M` | `9-17` | Range (inclusive) |
|
|
278
|
-
| `*/S` | `*/5` | Every S values starting from the field minimum |
|
|
279
|
-
| `N/S` | `10/15` | Every S values starting from N |
|
|
280
|
-
| `N-M/S` | `0-30/5` | Every S values within range N–M |
|
|
281
|
-
| `A,B,C` | `0,15,30,45` | Comma-separated list (combinable with above) |
|
|
282
|
-
|
|
283
|
-
### Schedule Validation
|
|
284
|
-
|
|
285
|
-
Schedules are **validated on registration**. Invalid expressions are rejected with a warning log and the job is **not** registered. Validation checks:
|
|
286
|
-
|
|
287
|
-
- Exactly 5 fields (minute, hour, day-of-month, month, day-of-week)
|
|
288
|
-
- All values within valid ranges: minute `0–59`, hour `0–23`, dom `1–31`, month `1–12`, dow `0–6`
|
|
289
|
-
- No unparseable tokens
|
|
290
|
-
|
|
291
|
-
### Minimum Schedule Interval
|
|
292
|
-
|
|
293
|
-
A **5-second minimum delay** is enforced between scheduled executions. Even if a cron expression would produce a sub-5-second gap (due to event loop jitter or clock drift), the scheduler clamps the delay to at least 5000ms. This prevents tight re-execution loops.
|
|
294
|
-
|
|
295
|
-
## Concurrency Guard
|
|
296
|
-
|
|
297
|
-
> **WARNING FOR AGENTS**: A cron job **never** runs two instances concurrently. If a scheduled tick fires while the previous run is still executing, it is **skipped** (not queued). The scheduler re-schedules for the next tick.
|
|
298
|
-
|
|
299
|
-
This also applies to **manual triggers** via `POST /api/cron/:id/trigger`. If the job is currently executing, the API returns immediately with a log entry containing:
|
|
300
|
-
|
|
301
|
-
```json
|
|
302
|
-
{
|
|
303
|
-
"log": {
|
|
304
|
-
"jobId": "my-job",
|
|
305
|
-
"durationMs": 0,
|
|
306
|
-
"success": true,
|
|
307
|
-
"result": { "skipped": true, "reason": "already_executing" },
|
|
308
|
-
"logs": ["Skipped: job is already running"],
|
|
309
|
-
"manual": true
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
```
|
|
313
|
-
|
|
314
|
-
## Timeout Behavior
|
|
315
|
-
|
|
316
|
-
Each handler invocation is **raced** against a timeout:
|
|
317
|
-
|
|
318
|
-
```typescript
|
|
319
|
-
const timeout = (job.timeoutSeconds ?? 300) * 1000;
|
|
320
|
-
const result = await Promise.race([handlerPromise, timeoutPromise]);
|
|
321
|
-
```
|
|
322
|
-
|
|
323
|
-
- Default timeout: **300 seconds** (5 minutes)
|
|
324
|
-
- If the timeout fires first, the job is marked as **failed** with error `Cron job "<id>" timed out after <N>ms`
|
|
325
|
-
- The timeout timer is always cleaned up via `clearTimeout`, even on success or error
|
|
326
|
-
- The `executing` flag is always cleared in a `finally` block, so the concurrency guard recovers correctly
|
|
327
|
-
|
|
328
|
-
## Persistence
|
|
329
|
-
|
|
330
|
-
### Database Persistence (`rebase.cron_logs`)
|
|
331
|
-
|
|
332
|
-
When `cronPersistence` is `true` (default) and the data driver supports SQL, Rebase automatically creates and uses a `rebase.cron_logs` table:
|
|
333
|
-
|
|
334
|
-
```sql
|
|
335
|
-
CREATE TABLE rebase.cron_logs (
|
|
336
|
-
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
|
337
|
-
job_id TEXT NOT NULL,
|
|
338
|
-
started_at TIMESTAMPTZ NOT NULL,
|
|
339
|
-
finished_at TIMESTAMPTZ NOT NULL,
|
|
340
|
-
duration_ms INTEGER NOT NULL,
|
|
341
|
-
success BOOLEAN NOT NULL DEFAULT true,
|
|
342
|
-
error TEXT,
|
|
343
|
-
result JSONB,
|
|
344
|
-
logs JSONB,
|
|
345
|
-
manual BOOLEAN NOT NULL DEFAULT false
|
|
346
|
-
);
|
|
347
|
-
|
|
348
|
-
-- Index for efficient per-job log queries
|
|
349
|
-
CREATE INDEX idx_cron_logs_job ON rebase.cron_logs(job_id, started_at DESC);
|
|
350
|
-
```
|
|
351
|
-
|
|
352
|
-
Log persistence is **non-blocking** — if the database insert fails, the error is logged but the scheduler continues normally.
|
|
353
|
-
|
|
354
|
-
### In-Memory Ring Buffer
|
|
355
|
-
|
|
356
|
-
Regardless of database persistence, the scheduler keeps an **in-memory ring buffer** of the last **50 log entries per job**. The REST API falls back to this buffer when database persistence is unavailable.
|
|
357
|
-
|
|
358
|
-
### Fallback Behavior
|
|
359
|
-
|
|
360
|
-
| Driver | Persistence | Behavior |
|
|
361
|
-
|--------|-------------|----------|
|
|
362
|
-
| PostgreSQL | ✅ DB + memory | Logs written to `rebase.cron_logs` and in-memory buffer |
|
|
363
|
-
| PostgreSQL (`cronPersistence: false`) | ❌ Memory only | Logs kept in-memory ring buffer only |
|
|
364
|
-
| Non-SQL (e.g. MongoDB) | ❌ Memory only | Warning logged; no DB persistence |
|
|
365
|
-
|
|
366
|
-
### Startup Seeding
|
|
367
|
-
|
|
368
|
-
On startup, the scheduler seeds `totalRuns`, `totalFailures`, and `lastRunAt` counters from the database. This is **non-blocking** — the scheduler starts immediately and counters are updated asynchronously.
|
|
369
|
-
|
|
370
|
-
## REST API
|
|
371
|
-
|
|
372
|
-
All routes are mounted at `/api/cron` and **require admin authentication** (`requireAuth` + `requireAdmin` middleware).
|
|
373
|
-
|
|
374
|
-
> **IMPORTANT FOR AGENTS**: All cron REST endpoints require an admin JWT or service key in the `Authorization` header. Unauthenticated requests will receive 401/403.
|
|
375
|
-
|
|
376
|
-
### List All Jobs
|
|
377
|
-
|
|
378
|
-
```
|
|
379
|
-
GET /api/cron
|
|
380
|
-
```
|
|
381
|
-
|
|
382
|
-
**Response:** `200 OK`
|
|
383
|
-
|
|
384
|
-
```json
|
|
385
|
-
{
|
|
386
|
-
"jobs": [
|
|
387
|
-
{
|
|
388
|
-
"id": "cleanup-sessions",
|
|
389
|
-
"name": "Cleanup Expired Sessions",
|
|
390
|
-
"description": "Removes sessions older than 30 days",
|
|
391
|
-
"schedule": "0 3 * * *",
|
|
392
|
-
"enabled": true,
|
|
393
|
-
"state": "idle",
|
|
394
|
-
"lastRunAt": "2025-01-15T03:00:00.000Z",
|
|
395
|
-
"nextRunAt": "2025-01-16T03:00:00.000Z",
|
|
396
|
-
"lastDurationMs": 1234,
|
|
397
|
-
"totalRuns": 42,
|
|
398
|
-
"totalFailures": 1
|
|
399
|
-
}
|
|
400
|
-
]
|
|
401
|
-
}
|
|
402
|
-
```
|
|
403
|
-
|
|
404
|
-
### Get Single Job
|
|
405
|
-
|
|
406
|
-
```
|
|
407
|
-
GET /api/cron/:id
|
|
408
|
-
```
|
|
409
|
-
|
|
410
|
-
**Response:** `200 OK`
|
|
411
|
-
|
|
412
|
-
```json
|
|
413
|
-
{
|
|
414
|
-
"job": {
|
|
415
|
-
"id": "cleanup-sessions",
|
|
416
|
-
"name": "Cleanup Expired Sessions",
|
|
417
|
-
"description": "Removes sessions older than 30 days",
|
|
418
|
-
"schedule": "0 3 * * *",
|
|
419
|
-
"enabled": true,
|
|
420
|
-
"state": "idle",
|
|
421
|
-
"lastRunAt": "2025-01-15T03:00:00.000Z",
|
|
422
|
-
"nextRunAt": "2025-01-16T03:00:00.000Z",
|
|
423
|
-
"lastDurationMs": 1234,
|
|
424
|
-
"totalRuns": 42,
|
|
425
|
-
"totalFailures": 1
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
```
|
|
429
|
-
|
|
430
|
-
**Error:** `404 Not Found`
|
|
431
|
-
|
|
432
|
-
```json
|
|
433
|
-
{
|
|
434
|
-
"error": { "message": "Cron job \"unknown-id\" not found", "code": "NOT_FOUND" }
|
|
435
|
-
}
|
|
436
|
-
```
|
|
437
|
-
|
|
438
|
-
### Trigger Job Manually
|
|
439
|
-
|
|
440
|
-
```
|
|
441
|
-
POST /api/cron/:id/trigger
|
|
442
|
-
```
|
|
443
|
-
|
|
444
|
-
**Response:** `200 OK`
|
|
445
|
-
|
|
446
|
-
```json
|
|
447
|
-
{
|
|
448
|
-
"log": {
|
|
449
|
-
"jobId": "cleanup-sessions",
|
|
450
|
-
"startedAt": "2025-01-15T12:00:00.000Z",
|
|
451
|
-
"finishedAt": "2025-01-15T12:00:01.234Z",
|
|
452
|
-
"durationMs": 1234,
|
|
453
|
-
"success": true,
|
|
454
|
-
"result": { "deletedSessions": 15 },
|
|
455
|
-
"logs": ["Starting session cleanup...", "Cleaned up 15 expired sessions"],
|
|
456
|
-
"manual": true
|
|
457
|
-
},
|
|
458
|
-
"job": { /* updated CronJobStatus */ }
|
|
459
|
-
}
|
|
460
|
-
```
|
|
461
|
-
|
|
462
|
-
> If the job is already executing, the response returns immediately with `result: { skipped: true, reason: "already_executing" }`.
|
|
463
|
-
|
|
464
|
-
### Get Job Logs
|
|
465
|
-
|
|
466
|
-
```
|
|
467
|
-
GET /api/cron/:id/logs?limit=10
|
|
468
|
-
```
|
|
469
|
-
|
|
470
|
-
| Query Param | Type | Default | Description |
|
|
471
|
-
|-------------|------|---------|-------------|
|
|
472
|
-
| `limit` | `number?` | `50` | Maximum number of log entries to return |
|
|
473
|
-
|
|
474
|
-
**Response:** `200 OK`
|
|
475
|
-
|
|
476
|
-
```json
|
|
477
|
-
{
|
|
478
|
-
"logs": [
|
|
479
|
-
{
|
|
480
|
-
"jobId": "cleanup-sessions",
|
|
481
|
-
"startedAt": "2025-01-15T03:00:00.000Z",
|
|
482
|
-
"finishedAt": "2025-01-15T03:00:01.234Z",
|
|
483
|
-
"durationMs": 1234,
|
|
484
|
-
"success": true,
|
|
485
|
-
"result": { "deletedSessions": 15 },
|
|
486
|
-
"logs": ["Starting session cleanup...", "Cleaned up 15 expired sessions"],
|
|
487
|
-
"manual": false
|
|
488
|
-
}
|
|
489
|
-
]
|
|
490
|
-
}
|
|
491
|
-
```
|
|
492
|
-
|
|
493
|
-
Logs are returned **newest first**. When a database store is available, logs are fetched from the database; otherwise, the in-memory ring buffer is used as fallback.
|
|
494
|
-
|
|
495
|
-
### Enable/Disable a Job
|
|
496
|
-
|
|
497
|
-
```
|
|
498
|
-
PUT /api/cron/:id
|
|
499
|
-
Content-Type: application/json
|
|
500
|
-
|
|
501
|
-
{ "enabled": false }
|
|
502
|
-
```
|
|
503
|
-
|
|
504
|
-
**Response:** `200 OK`
|
|
505
|
-
|
|
506
|
-
```json
|
|
507
|
-
{
|
|
508
|
-
"job": {
|
|
509
|
-
"id": "cleanup-sessions",
|
|
510
|
-
"enabled": false,
|
|
511
|
-
"state": "disabled",
|
|
512
|
-
/* ... rest of CronJobStatus */
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
```
|
|
516
|
-
|
|
517
|
-
**Error:** `400 Bad Request` (missing `enabled` field)
|
|
518
|
-
|
|
519
|
-
```json
|
|
520
|
-
{
|
|
521
|
-
"error": { "message": "Missing 'enabled' boolean in body", "code": "BAD_REQUEST" }
|
|
522
|
-
}
|
|
523
|
-
```
|
|
524
|
-
|
|
525
|
-
### API Summary Table
|
|
526
|
-
|
|
527
|
-
| Method | Path | Request Body | Response Shape |
|
|
528
|
-
|--------|------|-------------|----------------|
|
|
529
|
-
| `GET` | `/api/cron` | — | `{ jobs: CronJobStatus[] }` |
|
|
530
|
-
| `GET` | `/api/cron/:id` | — | `{ job: CronJobStatus }` |
|
|
531
|
-
| `POST` | `/api/cron/:id/trigger` | — | `{ log: CronJobLogEntry, job: CronJobStatus }` |
|
|
532
|
-
| `GET` | `/api/cron/:id/logs` | — | `{ logs: CronJobLogEntry[] }` |
|
|
533
|
-
| `PUT` | `/api/cron/:id` | `{ enabled: boolean }` | `{ job: CronJobStatus }` |
|
|
534
|
-
|
|
535
|
-
## Type Reference
|
|
536
|
-
|
|
537
|
-
### `CronJobStatus`
|
|
538
|
-
|
|
539
|
-
Returned by all status endpoints. Represents the full runtime state of a registered cron job.
|
|
540
|
-
|
|
541
|
-
```typescript
|
|
542
|
-
type CronJobRunState = "idle" | "running" | "success" | "error" | "disabled";
|
|
543
|
-
|
|
544
|
-
interface CronJobStatus {
|
|
545
|
-
id: string; // Unique ID from filename
|
|
546
|
-
name: string; // Human-readable name
|
|
547
|
-
description?: string; // Optional description
|
|
548
|
-
schedule: string; // Cron expression
|
|
549
|
-
enabled: boolean; // Currently enabled?
|
|
550
|
-
state: CronJobRunState; // Current run state
|
|
551
|
-
lastRunAt?: string; // ISO timestamp of last execution start
|
|
552
|
-
nextRunAt?: string; // ISO timestamp of next scheduled execution
|
|
553
|
-
lastDurationMs?: number; // Duration of last run in ms
|
|
554
|
-
lastError?: string; // Error message from last failed run
|
|
555
|
-
totalRuns: number; // Total executions since server start (seeded from DB)
|
|
556
|
-
totalFailures: number; // Total failures since server start (seeded from DB)
|
|
557
|
-
}
|
|
558
|
-
```
|
|
559
|
-
|
|
560
|
-
| Field | Type | Description |
|
|
561
|
-
|-------|------|-------------|
|
|
562
|
-
| `id` | `string` | Derived from filename, e.g. `cleanup-sessions`. |
|
|
563
|
-
| `name` | `string` | Human-readable name from the definition. |
|
|
564
|
-
| `description` | `string?` | Description from the definition. |
|
|
565
|
-
| `schedule` | `string` | The cron schedule expression. |
|
|
566
|
-
| `enabled` | `boolean` | Whether the job is currently enabled. |
|
|
567
|
-
| `state` | `CronJobRunState` | One of: `idle`, `running`, `success`, `error`, `disabled`. |
|
|
568
|
-
| `lastRunAt` | `string?` | ISO timestamp of the last execution start. |
|
|
569
|
-
| `nextRunAt` | `string?` | ISO timestamp of the next scheduled execution. |
|
|
570
|
-
| `lastDurationMs` | `number?` | Duration of the last run in milliseconds. |
|
|
571
|
-
| `lastError` | `string?` | Error message from the last failed run. |
|
|
572
|
-
| `totalRuns` | `number` | Total number of executions (seeded from DB on startup). |
|
|
573
|
-
| `totalFailures` | `number` | Total number of failed executions (seeded from DB on startup). |
|
|
574
|
-
|
|
575
|
-
### `CronJobRunState`
|
|
576
|
-
|
|
577
|
-
```typescript
|
|
578
|
-
type CronJobRunState = "idle" | "running" | "success" | "error" | "disabled";
|
|
579
|
-
```
|
|
580
|
-
|
|
581
|
-
| State | Meaning |
|
|
582
|
-
|-------|---------|
|
|
583
|
-
| `idle` | Enabled and waiting for next scheduled tick |
|
|
584
|
-
| `running` | Handler is currently executing |
|
|
585
|
-
| `success` | Last run completed successfully (transitional — becomes `idle`) |
|
|
586
|
-
| `error` | Last run failed (stays until next successful run) |
|
|
587
|
-
| `disabled` | Job is disabled via API or `enabled: false` in definition |
|
|
588
|
-
|
|
589
|
-
### `CronJobLogEntry`
|
|
590
|
-
|
|
591
|
-
A single execution log entry, stored in DB and/or in-memory ring buffer.
|
|
592
|
-
|
|
593
|
-
```typescript
|
|
594
|
-
interface CronJobLogEntry {
|
|
595
|
-
jobId: string; // The job ID this log belongs to
|
|
596
|
-
startedAt: string; // ISO timestamp when execution started
|
|
597
|
-
finishedAt: string; // ISO timestamp when execution finished
|
|
598
|
-
durationMs: number; // Duration in milliseconds
|
|
599
|
-
success: boolean; // Whether this run succeeded
|
|
600
|
-
error?: string; // Error message if the run failed
|
|
601
|
-
result?: unknown; // Arbitrary data returned by the handler
|
|
602
|
-
logs: string[]; // Captured log lines from ctx.log()
|
|
603
|
-
manual?: boolean; // Whether this was a manual trigger
|
|
604
|
-
}
|
|
605
|
-
```
|
|
606
|
-
|
|
607
|
-
| Field | Type | Description |
|
|
608
|
-
|-------|------|-------------|
|
|
609
|
-
| `jobId` | `string` | The job ID this log belongs to. |
|
|
610
|
-
| `startedAt` | `string` | ISO timestamp when execution started. |
|
|
611
|
-
| `finishedAt` | `string` | ISO timestamp when execution finished. |
|
|
612
|
-
| `durationMs` | `number` | Duration in milliseconds. |
|
|
613
|
-
| `success` | `boolean` | Whether this run succeeded. |
|
|
614
|
-
| `error` | `string?` | Error message if the run failed (timeout, exception, etc.). |
|
|
615
|
-
| `result` | `unknown?` | JSON-serialisable data returned by the handler. |
|
|
616
|
-
| `logs` | `string[]` | Captured log lines from `ctx.log()`. |
|
|
617
|
-
| `manual` | `boolean?` | `true` if triggered via `POST /api/cron/:id/trigger`. |
|
|
618
|
-
|
|
619
|
-
## Client SDK
|
|
620
|
-
|
|
621
|
-
The Rebase client SDK provides typed methods for all cron operations. All methods require admin authentication.
|
|
622
|
-
|
|
623
|
-
```typescript
|
|
624
|
-
import { createRebaseClient } from "@rebasepro/client";
|
|
625
|
-
|
|
626
|
-
const client = createRebaseClient({
|
|
627
|
-
baseUrl: "http://localhost:3001",
|
|
628
|
-
token: "your-admin-jwt-or-service-key",
|
|
629
|
-
});
|
|
630
|
-
```
|
|
631
|
-
|
|
632
|
-
### SDK Methods
|
|
633
|
-
|
|
634
|
-
| Method | Return Type | Description |
|
|
635
|
-
|--------|------------|-------------|
|
|
636
|
-
| `client.cron.listJobs()` | `Promise<{ jobs: CronJobStatus[] }>` | List all registered cron jobs |
|
|
637
|
-
| `client.cron.getJob(jobId)` | `Promise<{ job: CronJobStatus }>` | Get a single job's status |
|
|
638
|
-
| `client.cron.triggerJob(jobId)` | `Promise<{ log: CronJobLogEntry, job: CronJobStatus }>` | Manually trigger a job |
|
|
639
|
-
| `client.cron.getJobLogs(jobId, options?)` | `Promise<{ logs: CronJobLogEntry[] }>` | Get execution history |
|
|
640
|
-
| `client.cron.toggleJob(jobId, enabled)` | `Promise<{ job: CronJobStatus }>` | Enable or disable a job |
|
|
641
|
-
|
|
642
|
-
### Usage Examples
|
|
643
|
-
|
|
644
|
-
```typescript
|
|
645
|
-
// List all jobs
|
|
646
|
-
const { jobs } = await client.cron.listJobs();
|
|
647
|
-
console.log(jobs.map(j => `${j.id}: ${j.state}`));
|
|
648
|
-
|
|
649
|
-
// Get a specific job
|
|
650
|
-
const { job } = await client.cron.getJob("cleanup-sessions");
|
|
651
|
-
console.log(`Next run: ${job.nextRunAt}`);
|
|
652
|
-
|
|
653
|
-
// Trigger a job manually
|
|
654
|
-
const { log, job: updatedJob } = await client.cron.triggerJob("cleanup-sessions");
|
|
655
|
-
if (log.result?.skipped) {
|
|
656
|
-
console.log("Job was already running — skipped");
|
|
657
|
-
} else {
|
|
658
|
-
console.log(`Completed in ${log.durationMs}ms`, log.result);
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
// Get execution history (last 10 entries)
|
|
662
|
-
const { logs } = await client.cron.getJobLogs("cleanup-sessions", { limit: 10 });
|
|
663
|
-
|
|
664
|
-
// Pause a job
|
|
665
|
-
await client.cron.toggleJob("cleanup-sessions", false);
|
|
666
|
-
|
|
667
|
-
// Resume a job
|
|
668
|
-
await client.cron.toggleJob("cleanup-sessions", true);
|
|
669
|
-
```
|
|
670
|
-
|
|
671
|
-
## Edge Cases and Gotchas
|
|
672
|
-
|
|
673
|
-
| Scenario | Behavior |
|
|
674
|
-
|----------|----------|
|
|
675
|
-
| **Invalid cron expression** | Job is rejected at registration with a warning log. Not registered. |
|
|
676
|
-
| **Duplicate job ID** (same filename in crons dir) | Previous job is stopped and overwritten with a warning log. |
|
|
677
|
-
| **Job still running when next tick fires** | Scheduled tick is skipped; scheduler re-schedules for the next tick. |
|
|
678
|
-
| **Manual trigger while running** | Returns immediately with `{ skipped: true, reason: "already_executing" }`. |
|
|
679
|
-
| **Handler throws an exception** | Marked as failed; `error` field populated; `totalFailures` incremented; `executing` flag cleared. |
|
|
680
|
-
| **Handler exceeds timeout** | `Promise.race` rejects with timeout error; marked as failed. Timeout timer is always cleaned up. |
|
|
681
|
-
| **Scheduler stopped while handler runs** | Handler runs to completion (it's async), but no further scheduling occurs. |
|
|
682
|
-
| **Non-SQL driver (e.g. MongoDB)** | Warning logged; cron jobs run normally but logs are not persisted to DB (in-memory only). |
|
|
683
|
-
| **DB persistence insert fails** | Error logged; scheduler continues normally (non-blocking). |
|
|
684
|
-
| **`cronPersistence: false`** | No `rebase.cron_logs` table created; logs kept in-memory ring buffer (50 per job). |
|
|
685
|
-
| **Job registered after scheduler started** | Automatically scheduled — late-registered jobs don't sit idle. |
|
|
686
|
-
|
|
687
|
-
## Common Use Cases
|
|
688
|
-
|
|
689
|
-
- **Data cleanup** — Remove expired sessions, stale tokens, orphaned records
|
|
690
|
-
- **Report generation** — Build daily/weekly summaries, export analytics
|
|
691
|
-
- **External API sync** — Pull data from third-party APIs on a schedule
|
|
692
|
-
- **Health checks** — Monitor uptime, memory usage, database connectivity
|
|
693
|
-
- **Notification batching** — Send digest emails, aggregate alerts
|
|
694
|
-
- **Cache warming** — Pre-compute expensive queries during off-peak hours
|
|
695
|
-
|
|
696
|
-
## References
|
|
697
|
-
|
|
698
|
-
- **Documentation:** [rebase.pro/docs/backend/cron-jobs](https://rebase.pro/docs/backend/cron-jobs)
|
|
699
|
-
- **GitHub:** [github.com/rebasepro/rebase](https://github.com/rebasepro/rebase)
|