@cronvello/sdk 0.1.0 → 0.1.2
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/CHANGELOG.md +44 -0
- package/README.md +162 -11
- package/dist/cli.cjs +877 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.js +874 -0
- package/dist/cli.js.map +1 -0
- package/dist/{dispatch-handler-Bnda1Ekq.d.cts → dispatch-handler-BNy-H5Nr.d.cts} +41 -2
- package/dist/{dispatch-handler-Bnda1Ekq.d.ts → dispatch-handler-BNy-H5Nr.d.ts} +41 -2
- package/dist/express.d.cts +1 -1
- package/dist/express.d.ts +1 -1
- package/dist/index.cjs +385 -40
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +108 -3
- package/dist/index.d.ts +108 -3
- package/dist/index.js +372 -41
- package/dist/index.js.map +1 -1
- package/dist/next.d.cts +1 -1
- package/dist/next.d.ts +1 -1
- package/package.json +39 -9
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `@cronvello/sdk` are documented here. This project adheres to
|
|
4
|
+
[Semantic Versioning](https://semver.org/) (pre-1.0: minor-feature additions ship as patch releases).
|
|
5
|
+
|
|
6
|
+
## 0.1.2
|
|
7
|
+
|
|
8
|
+
A big developer-experience release — everything is additive and backward-compatible.
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **Readable schedule builders**: `every`, `everyMinutes`, `everyHours`, `hourly`, `daily`,
|
|
13
|
+
`weekly`, `monthly`, `weekdays`, `weekends`, and `cron` — each returns a validated cron string.
|
|
14
|
+
- **Client-side validation** of cron expressions and IANA timezones at define time
|
|
15
|
+
(`validateCron`, `isValidTimeZone`); a typo now throws a clear, job-scoped error instead of
|
|
16
|
+
failing silently server-side. Opt out per app with `validateSchedules: false`.
|
|
17
|
+
- **`cronvello.trigger(key, payload?)`** — run a job's handler in-process (no HTTP, no deploy).
|
|
18
|
+
Ideal for unit tests and local development. `ctx.source` is `"local"` for these runs.
|
|
19
|
+
- **`defineCronvello.fromEnv(config)`** — read `CRONVELLO_API_KEY`, `CRONVELLO_DISPATCH_SECRET`,
|
|
20
|
+
and `CRONVELLO_APP_URL` (or `PUBLIC_URL`) from the environment so you don't repeat them.
|
|
21
|
+
- **Lifecycle hooks** (`onJobStart`, `onJobSuccess`, `onJobError`) for logging, metrics, and error
|
|
22
|
+
reporting. A throwing hook is logged and never breaks the run.
|
|
23
|
+
- **`ctx.logger`** (your configured logger, or a no-op) and **`ctx.source`** on the handler context.
|
|
24
|
+
- **`formatSyncResult(result, { color?, dryRun? })`** — a clean, human-readable reconcile summary.
|
|
25
|
+
- **CLI** — `npx cronvello` with `whoami`, `list`, `tasks`, `runs`, `run`, `status`, `sync`,
|
|
26
|
+
`dev`, and `secret`. Polished, colour-aware output; `--json` for scripting.
|
|
27
|
+
- **Dispatch body-size guard** (`maxBodyBytes`, default 1 MiB) — oversized inbound bodies are
|
|
28
|
+
rejected with `413` before parsing.
|
|
29
|
+
|
|
30
|
+
### Internal
|
|
31
|
+
|
|
32
|
+
- Comprehensive test suite (unit + contract + opt-in live e2e) and a CI gate
|
|
33
|
+
(typecheck + tests + build across Node 18/20/22) wired ahead of publish.
|
|
34
|
+
|
|
35
|
+
## 0.1.1
|
|
36
|
+
|
|
37
|
+
- **Fix**: unwrap the server's `{ success, message, data }` response envelope centrally in the
|
|
38
|
+
transport, so `sync()` and the typed client methods see the inner payload (the 0.1.0 release
|
|
39
|
+
returned the wrapper).
|
|
40
|
+
|
|
41
|
+
## 0.1.0
|
|
42
|
+
|
|
43
|
+
- Initial release: code-first registry (`defineCronvello` → `sync()` + Express/Next adapters) and
|
|
44
|
+
a typed low-level `/v1` client. Zero runtime dependencies, dual ESM + CJS.
|
package/README.md
CHANGED
|
@@ -13,6 +13,11 @@ npm i @cronvello/sdk
|
|
|
13
13
|
- ✅ **Base URL baked in** (`https://api.cronvello.com`) — override only for self-hosting.
|
|
14
14
|
- ✅ **Two layers**: a turnkey code-first **registry**, and a typed **low-level client** for the
|
|
15
15
|
full `/v1` API.
|
|
16
|
+
- ✅ **Readable schedules** — `daily("08:00")`, `every("15m")`, `weekly("mon", "09:00")` instead of
|
|
17
|
+
raw cron — with **typo-proof validation** at define time.
|
|
18
|
+
- ✅ **Test jobs locally** with `cronvello.trigger("job")` — no deploy, no HTTP.
|
|
19
|
+
- ✅ **Lifecycle hooks** for logging / metrics / error reporting.
|
|
20
|
+
- ✅ **A real CLI** — `npx cronvello whoami | list | runs | status | sync | dev`.
|
|
16
21
|
|
|
17
22
|
---
|
|
18
23
|
|
|
@@ -30,26 +35,26 @@ Change a schedule → it's updated. Idempotent, every time.
|
|
|
30
35
|
|
|
31
36
|
```ts
|
|
32
37
|
// cronvello.ts
|
|
33
|
-
import { defineCronvello } from "@cronvello/sdk";
|
|
38
|
+
import { defineCronvello, daily, every } from "@cronvello/sdk";
|
|
34
39
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
dispatchSecret: process.env.CRONVELLO_DISPATCH_SECRET!, // random 32-byte secret
|
|
40
|
+
// `fromEnv` reads CRONVELLO_API_KEY, CRONVELLO_DISPATCH_SECRET, and CRONVELLO_APP_URL
|
|
41
|
+
// (or PUBLIC_URL) for you — pass them explicitly instead if you prefer.
|
|
42
|
+
export const cronvello = defineCronvello.fromEnv({
|
|
43
|
+
appName: "my-app", // your app's identity (stable, unique)
|
|
40
44
|
|
|
41
45
|
jobs: {
|
|
42
46
|
"send-daily-digest": {
|
|
43
|
-
schedule: "0 8 * * *"
|
|
47
|
+
schedule: daily("08:00"), // ← readable; or a raw "0 8 * * *"
|
|
44
48
|
handler: async () => {
|
|
45
49
|
await sendDigests();
|
|
46
50
|
},
|
|
47
51
|
},
|
|
48
52
|
"cleanup-temp": {
|
|
49
|
-
schedule: "
|
|
53
|
+
schedule: every("15m"),
|
|
50
54
|
description: "Purge temp files older than an hour",
|
|
51
|
-
handler: async ({ schedule }) => {
|
|
55
|
+
handler: async ({ schedule, logger }) => {
|
|
52
56
|
const removed = await purgeTemp();
|
|
57
|
+
logger.info?.(`purged ${removed} files`);
|
|
53
58
|
return { removed, schedule }; // returned value is recorded on the run
|
|
54
59
|
},
|
|
55
60
|
},
|
|
@@ -120,14 +125,16 @@ jobs: [...billingJobs, ...reportJobs] // each: { key, schedule, handler, … }
|
|
|
120
125
|
|
|
121
126
|
| Field | Default | Notes |
|
|
122
127
|
|---|---|---|
|
|
123
|
-
| `schedule` | — | Cron
|
|
128
|
+
| `schedule` | — | Cron string or a [schedule builder](#readable-schedules). Validated client-side at define time, and again by Cronvello on sync. |
|
|
124
129
|
| `handler` | — | `async (ctx) => result`. The return value is recorded on the run. |
|
|
125
130
|
| `description` | — | Stored on the task. |
|
|
126
|
-
| `timeZone` | app default (`Europe/Berlin`) | IANA zone. |
|
|
131
|
+
| `timeZone` | app default (`Europe/Berlin`) | IANA zone. Validated client-side. |
|
|
127
132
|
| `urgency` | — | `low` \| `medium` \| `high` \| `critical`. |
|
|
128
133
|
| `maxRetries` | server default | 0–20. |
|
|
129
134
|
| `executionMode` | `sync` | `async_callback` for long jobs (see below). |
|
|
135
|
+
| `callbackTimeoutMs` | — | Async-mode budget before Cronvello marks the run timed out. |
|
|
130
136
|
| `allowConcurrentRuns` | `false` | Allow overlap with an in-flight run. |
|
|
137
|
+
| `successCriteria` | — | Assert success beyond 2xx (status range, body contains / JSON path / regex). |
|
|
131
138
|
| `payload` | — | Static object merged into the request body and exposed as `ctx.payload`. |
|
|
132
139
|
| `enabled` | `true` | `false` keeps the code but stops scheduling. |
|
|
133
140
|
|
|
@@ -140,11 +147,102 @@ handler: async (ctx) => {
|
|
|
140
147
|
ctx.payload; // your static payload, if any
|
|
141
148
|
ctx.body; // full request body Cronvello sent
|
|
142
149
|
ctx.isAsync; // true under async_callback mode
|
|
150
|
+
ctx.source; // "dispatch" (Cronvello fired it) | "local" (you called trigger())
|
|
151
|
+
ctx.logger; // your configured logger (no-op if none) — ctx.logger.info?.("…")
|
|
152
|
+
ctx.signal; // AbortSignal, if the host provides one
|
|
143
153
|
};
|
|
144
154
|
```
|
|
145
155
|
|
|
146
156
|
---
|
|
147
157
|
|
|
158
|
+
## Readable schedules
|
|
159
|
+
|
|
160
|
+
Stop hand-writing cron. These builders return a validated cron string, so a bad argument throws
|
|
161
|
+
**at define time** with a clear message — not silently at the server:
|
|
162
|
+
|
|
163
|
+
```ts
|
|
164
|
+
import { every, everyMinutes, everyHours, hourly, daily, weekly, monthly, weekdays, weekends, cron } from "@cronvello/sdk";
|
|
165
|
+
|
|
166
|
+
every("30s") // "*/30 * * * * *"
|
|
167
|
+
every("15m") // "*/15 * * * *"
|
|
168
|
+
every("2h") // "0 */2 * * *"
|
|
169
|
+
hourly() // "0 * * * *" — top of every hour
|
|
170
|
+
hourly(30) // "30 * * * *"
|
|
171
|
+
daily("08:00") // "0 8 * * *"
|
|
172
|
+
weekly("mon", "09:00") // "0 9 * * 1"
|
|
173
|
+
monthly(1, "00:00") // "0 0 1 * *" — 1st of the month
|
|
174
|
+
weekdays("07:00") // "0 7 * * 1-5" — Mon–Fri
|
|
175
|
+
weekends("10:00") // "0 10 * * 0,6"
|
|
176
|
+
cron("0 6,7 * * *") // raw cron, still validated
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Validation runs on every job's `schedule` and `timeZone` when you call `defineCronvello`. Opt out
|
|
180
|
+
with `validateSchedules: false`. You can also validate directly:
|
|
181
|
+
|
|
182
|
+
```ts
|
|
183
|
+
import { validateCron, isValidTimeZone } from "@cronvello/sdk";
|
|
184
|
+
validateCron("0 25 * * *"); // { valid: false, error: 'invalid hour "25": …' }
|
|
185
|
+
isValidTimeZone("Europe/Berlin"); // true
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## Test & run jobs locally — `trigger()`
|
|
191
|
+
|
|
192
|
+
Run a job's handler **in-process** — no HTTP, no Cronvello, no deploy. Perfect for unit tests and
|
|
193
|
+
local development:
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
const result = await cronvello.trigger("send-daily-digest");
|
|
197
|
+
// runs the handler with ctx.source === "local"; lifecycle hooks fire; returns the handler's value
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
```ts
|
|
201
|
+
// In a test:
|
|
202
|
+
it("sends digests", async () => {
|
|
203
|
+
const out = await cronvello.trigger("send-daily-digest", { dryRun: true });
|
|
204
|
+
expect(out).toEqual({ sent: 3 });
|
|
205
|
+
});
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## Lifecycle hooks & logging
|
|
211
|
+
|
|
212
|
+
Observe every run — for structured logs, metrics, or error reporting (Sentry, etc.). Hooks never
|
|
213
|
+
break a run: if a hook throws, it's logged and the job still completes.
|
|
214
|
+
|
|
215
|
+
```ts
|
|
216
|
+
defineCronvello.fromEnv({
|
|
217
|
+
appName: "my-app",
|
|
218
|
+
logger: console, // surfaced to handlers as ctx.logger
|
|
219
|
+
hooks: {
|
|
220
|
+
onJobStart: ({ key, source }) => metrics.increment(`cron.start`, { key }),
|
|
221
|
+
onJobSuccess: ({ key, durationMs }) => metrics.timing(`cron.ms`, durationMs, { key }),
|
|
222
|
+
onJobError: ({ key, error }) => Sentry.captureException(error, { tags: { key } }),
|
|
223
|
+
},
|
|
224
|
+
jobs: { /* … */ },
|
|
225
|
+
});
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## Pretty sync output
|
|
231
|
+
|
|
232
|
+
```ts
|
|
233
|
+
import { formatSyncResult } from "@cronvello/sdk";
|
|
234
|
+
|
|
235
|
+
const result = await cronvello.sync();
|
|
236
|
+
console.log(formatSyncResult(result, { color: true }));
|
|
237
|
+
// Cronvello synced "my-app" (job_…)
|
|
238
|
+
// + created send-daily-digest
|
|
239
|
+
// ~ updated cleanup-temp (schedule)
|
|
240
|
+
// = unchanged rotate-keys
|
|
241
|
+
// 1 created, 1 updated, 1 unchanged
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
148
246
|
## `sync()` — idempotent reconcile
|
|
149
247
|
|
|
150
248
|
```ts
|
|
@@ -211,6 +309,59 @@ long-running Node server — **not** typical serverless).
|
|
|
211
309
|
|
|
212
310
|
---
|
|
213
311
|
|
|
312
|
+
## CLI — `npx cronvello`
|
|
313
|
+
|
|
314
|
+
Installing the SDK gives you a `cronvello` command. Point it at your account with
|
|
315
|
+
`CRONVELLO_API_KEY` (a `crn_live_…` key) and inspect or drive everything from the terminal:
|
|
316
|
+
|
|
317
|
+
```bash
|
|
318
|
+
export CRONVELLO_API_KEY=crn_live_…
|
|
319
|
+
|
|
320
|
+
npx cronvello whoami # account, plan, limits & usage
|
|
321
|
+
npx cronvello list # job containers (tasks / active / errors)
|
|
322
|
+
npx cronvello list "my-app" # the tasks inside one container
|
|
323
|
+
npx cronvello tasks --job "my-app" # account-wide task table
|
|
324
|
+
npx cronvello runs --limit 20 # recent execution feed
|
|
325
|
+
npx cronvello runs --status failed # …filtered
|
|
326
|
+
npx cronvello run <taskId> # trigger a task now
|
|
327
|
+
npx cronvello run "my-app" "digest" # …resolved by job + task name
|
|
328
|
+
npx cronvello status # health overview + recent failures
|
|
329
|
+
npx cronvello secret # generate a strong dispatch secret
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
```
|
|
333
|
+
$ cronvello whoami
|
|
334
|
+
╭─ System (internal siblings) ───────────────────╮
|
|
335
|
+
│ account #1 ✔ system@node-cron.internal │
|
|
336
|
+
│ plan Enterprise │
|
|
337
|
+
│ limits 1000 jobs · 100 tasks/job · 6000/min │
|
|
338
|
+
│ usage 236253 / ∞ executions (6 jobs) │
|
|
339
|
+
│ attention 4 DLQ · 0 heartbeats · 0 maintenance │
|
|
340
|
+
╰────────────────────────────────────────────────╯
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
**Drive your code registry**, too — these load your config module (a file that exports a
|
|
344
|
+
`defineCronvello(...)` app as the default export or a named `cronvello`):
|
|
345
|
+
|
|
346
|
+
```bash
|
|
347
|
+
npx cronvello sync ./cronvello.config.js # reconcile to Cronvello
|
|
348
|
+
npx cronvello sync ./cronvello.config.js --dry # preview the diff
|
|
349
|
+
npx cronvello dev ./cronvello.config.js my-job # run a job's handler locally
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
> Running a **TypeScript** config? Launch the CLI under a TS loader:
|
|
353
|
+
> `node --import tsx node_modules/@cronvello/sdk/dist/cli.js sync ./cronvello.config.ts`
|
|
354
|
+
|
|
355
|
+
Global flags: `--json` (machine-readable output for scripting), `--no-color`, `-h/--help`,
|
|
356
|
+
`-v/--version`. Colour auto-disables when piped or when `NO_COLOR` is set.
|
|
357
|
+
|
|
358
|
+
---
|
|
359
|
+
|
|
360
|
+
Runnable examples live in [`examples/`](./examples): a full Express app (`examples/express`) and a
|
|
361
|
+
Next.js App Router setup (`examples/next`).
|
|
362
|
+
|
|
363
|
+
---
|
|
364
|
+
|
|
214
365
|
## Low-level client
|
|
215
366
|
|
|
216
367
|
The full typed `/v1` surface, for anything beyond the registry model:
|