@cronvello/sdk 0.1.3 → 0.2.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/CHANGELOG.md +53 -0
- package/README.md +135 -2
- package/dist/cli.cjs +1106 -10
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1104 -12
- package/dist/cli.js.map +1 -1
- package/dist/dev.cjs +1186 -0
- package/dist/dev.cjs.map +1 -0
- package/dist/dev.d.cts +318 -0
- package/dist/dev.d.ts +318 -0
- package/dist/dev.js +1173 -0
- package/dist/dev.js.map +1 -0
- package/dist/index.cjs +645 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +23 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +644 -7
- package/dist/index.js.map +1 -1
- package/docs/dashboard.png +0 -0
- package/package.json +12 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,59 @@
|
|
|
3
3
|
All notable changes to `@cronvello/sdk` are documented here. This project adheres to
|
|
4
4
|
[Semantic Versioning](https://semver.org/) (pre-1.0: minor-feature additions ship as patch releases).
|
|
5
5
|
|
|
6
|
+
## 0.2.0
|
|
7
|
+
|
|
8
|
+
**The SDK now runs locally with no account.** Until now `@cronvello/sdk` was cloud-coupled: jobs
|
|
9
|
+
only ran once `sync()` had registered them and Cronvello called your app back. This release adds a
|
|
10
|
+
real **local execution engine** and a small **local dashboard**, so the SDK is useful on its own —
|
|
11
|
+
`npx cronvello dev` starts a scheduler loop on your machine and actually fires your handlers when
|
|
12
|
+
they're due, and `--dashboard` opens a live web UI for them. No account, no network. Everything here
|
|
13
|
+
is additive; the cloud `sync()` / dispatch paths are unchanged.
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- **Local engine** — a from-scratch, zero-dependency scheduler. For each job it computes the next
|
|
18
|
+
fire time from its cron expression (5- or 6-field, `@macros`, ranges/lists/steps/names) and runs
|
|
19
|
+
the handler when due, enforcing **timezone/DST**, **overlap protection** (`allowConcurrentRuns`),
|
|
20
|
+
**per-run timeout** (from `callbackTimeoutMs`), and **retry with exponential backoff**
|
|
21
|
+
(`maxRetries`) — locally. Keeps an in-memory run-history ring buffer and shuts down cleanly.
|
|
22
|
+
- **`cronvello dev [entry]`** — start the local engine from your config module, with a live job
|
|
23
|
+
table and run feed. `--dry-run [--window <dur>]` prints what would fire in the next window
|
|
24
|
+
without executing anything.
|
|
25
|
+
- **`cronvello preview "<cron>" [--tz <IANA>] [-n 5]`** — print the next N fire times of any cron
|
|
26
|
+
expression in a timezone. Also available programmatically as `previewSchedule(expr, opts)`.
|
|
27
|
+
- **`app.dev(options?)`** on the object returned by `defineCronvello` — starts the local engine and
|
|
28
|
+
returns its handle (`runs()`, `snapshot()`, `stop()`).
|
|
29
|
+
- **`@cronvello/sdk/dev` subpath export** — the engine primitives (`createLocalEngine`,
|
|
30
|
+
`nextOccurrence`, `previewSchedule`, `upcomingFires`, `startDashboard`, types). `nextOccurrence`
|
|
31
|
+
and `previewSchedule` are re-exported from the main entry too.
|
|
32
|
+
- **`ctx.signal` on local runs** — the engine aborts it when a run exceeds its timeout, so
|
|
33
|
+
cooperative handlers can cancel their work.
|
|
34
|
+
- **Local dashboard** — `cronvello dev --dashboard [--port N]` (or `app.dev({ dashboard: true })`)
|
|
35
|
+
serves a small, zero-dependency web UI over the running engine on `127.0.0.1`: a job table with a
|
|
36
|
+
live next-fire countdown, a Server-Sent-Events run feed (fire/success/error/timeout/retry/skip), a
|
|
37
|
+
per-job drawer with recent runs and upcoming fire times, and a **Run now** button. Read-only JSON
|
|
38
|
+
API plus `GET /api/runs.ndjson` to export the run history. Light + dark, no framework, no network,
|
|
39
|
+
MIT. Mutating requests are same-origin-guarded. The dashboard code is loaded lazily, so it never
|
|
40
|
+
weighs down the main bundle.
|
|
41
|
+
- **`engine.subscribe(listener)`**, **`engine.onStop(hook)`**, **`engine.trigger(key)`**, and
|
|
42
|
+
**`engine.toNdjson()`** on `LocalEngine` — the multi-subscriber, manual-run, and export primitives
|
|
43
|
+
the dashboard is built on (`engine.stop()` also tears the dashboard down).
|
|
44
|
+
|
|
45
|
+
### Changed
|
|
46
|
+
|
|
47
|
+
- **`cronvello dev <jobKey>` (single-shot) is now `cronvello trigger <jobKey>`** (pre-1.0 rename).
|
|
48
|
+
`cronvello dev` now starts the local engine instead of running one job once. `app.trigger()` is
|
|
49
|
+
unchanged.
|
|
50
|
+
|
|
51
|
+
## 0.1.4
|
|
52
|
+
|
|
53
|
+
- **Fix the `cronvello` CLI when launched via the `bin` symlink.** The entry-point check compared
|
|
54
|
+
`process.argv[1]` against the module filename, but when run as `node_modules/.bin/cronvello`
|
|
55
|
+
that argument is the symlink path — so the CLI silently did nothing (0.1.2/0.1.3). It now
|
|
56
|
+
resolves both sides through `realpathSync`, which is symlink-proof. The CI smoke test now invokes
|
|
57
|
+
the CLI through a symlink so this can't regress. (`npx cronvello …` works again.)
|
|
58
|
+
|
|
6
59
|
## 0.1.3
|
|
7
60
|
|
|
8
61
|
- **Require Node 20+** (`engines.node >= 20`). The SDK uses the global WebCrypto `crypto`
|
package/README.md
CHANGED
|
@@ -8,6 +8,8 @@ required**. Your jobs always follow your code.
|
|
|
8
8
|
npm i @cronvello/sdk
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
+
- ✅ **Runs locally with no account** — `npx cronvello dev` starts a real scheduler loop on your
|
|
12
|
+
machine (timezone/DST, overlap protection, timeouts, retry/backoff). No cloud, no network.
|
|
11
13
|
- ✅ **Zero runtime dependencies** (Node 20+ `fetch` + WebCrypto).
|
|
12
14
|
- ✅ **Dual ESM + CJS**, full TypeScript types.
|
|
13
15
|
- ✅ **Base URL baked in** (`https://api.cronvello.com`) — override only for self-hosting.
|
|
@@ -17,7 +19,7 @@ npm i @cronvello/sdk
|
|
|
17
19
|
raw cron — with **typo-proof validation** at define time.
|
|
18
20
|
- ✅ **Test jobs locally** with `cronvello.trigger("job")` — no deploy, no HTTP.
|
|
19
21
|
- ✅ **Lifecycle hooks** for logging / metrics / error reporting.
|
|
20
|
-
- ✅ **A real CLI** — `npx cronvello whoami | list | runs | status | sync
|
|
22
|
+
- ✅ **A real CLI** — `npx cronvello dev | preview | whoami | list | runs | status | sync`.
|
|
21
23
|
|
|
22
24
|
---
|
|
23
25
|
|
|
@@ -207,6 +209,134 @@ it("sends digests", async () => {
|
|
|
207
209
|
|
|
208
210
|
---
|
|
209
211
|
|
|
212
|
+
## Local development (no account) — `cronvello dev`
|
|
213
|
+
|
|
214
|
+
You don't need a Cronvello account, an API key, or a deploy to run your jobs. **The SDK ships a real
|
|
215
|
+
local engine.** Point the CLI at the module that exports your app and it starts a scheduler loop on
|
|
216
|
+
your machine that actually fires the handlers when they're due — computing each next fire time from
|
|
217
|
+
its cron expression (timezone- and DST-aware) and enforcing the same production policies the cloud
|
|
218
|
+
does: **overlap protection, per-run timeout, and retry with backoff**. No cloud, no network.
|
|
219
|
+
|
|
220
|
+
```ts
|
|
221
|
+
// cronvello.config.ts
|
|
222
|
+
import { defineCronvello, every, daily } from "@cronvello/sdk";
|
|
223
|
+
|
|
224
|
+
export const cronvello = defineCronvello({
|
|
225
|
+
appName: "my-app",
|
|
226
|
+
appUrl: "https://my-app.example.com",
|
|
227
|
+
apiKey: "crn_local_dev", // any non-empty value — the local engine never calls the API
|
|
228
|
+
dispatchSecret: "local-dev-secret-0123456789abcdef",
|
|
229
|
+
timeZone: "Europe/Berlin",
|
|
230
|
+
jobs: {
|
|
231
|
+
heartbeat: { schedule: every("10s"), handler: async () => { console.log("beat"); } },
|
|
232
|
+
"daily-digest": { schedule: daily("08:00"), handler: async () => sendDigests() },
|
|
233
|
+
},
|
|
234
|
+
});
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
```bash
|
|
238
|
+
npx cronvello dev # auto-detects cronvello.config.{ts,js,mjs} (or pass a path)
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
```
|
|
242
|
+
◷ Cronvello dev · local engine · no account, no cloud
|
|
243
|
+
|
|
244
|
+
JOB SCHEDULE TZ NEXT RUN WHEN
|
|
245
|
+
heartbeat */10 * * * * * Europe/Berlin 2026-06-28 18:03:10 in 8s
|
|
246
|
+
daily-digest 0 8 * * * Europe/Berlin 2026-06-29 08:00:00 in 14h
|
|
247
|
+
|
|
248
|
+
watching 2 job(s) — press Ctrl-C to stop
|
|
249
|
+
|
|
250
|
+
18:03:10 → heartbeat fired
|
|
251
|
+
18:03:10 ✔ heartbeat 3ms
|
|
252
|
+
18:03:20 → heartbeat fired
|
|
253
|
+
18:03:20 ✔ heartbeat 2ms
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Every fire is logged with its result, duration, retries, and any timeouts; the engine keeps an
|
|
257
|
+
in-memory history of recent runs and shuts down cleanly on `Ctrl-C`. Running a **TypeScript** config
|
|
258
|
+
directly? Launch the CLI under a TS loader:
|
|
259
|
+
|
|
260
|
+
```bash
|
|
261
|
+
node --import tsx node_modules/@cronvello/sdk/dist/cli.js dev ./cronvello.config.ts
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
**See what would fire without running anything:**
|
|
265
|
+
|
|
266
|
+
```bash
|
|
267
|
+
npx cronvello dev --dry-run --window 1h # lists the fires due in the next hour
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
**Preview any cron expression** — the next N fire times, in a timezone:
|
|
271
|
+
|
|
272
|
+
```bash
|
|
273
|
+
npx cronvello preview "0 8 * * 1-5" --tz Europe/Berlin -n 5
|
|
274
|
+
npx cronvello preview "@daily" -n 3
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
**Run a single job once** (handy in a script or while iterating on one handler):
|
|
278
|
+
|
|
279
|
+
```bash
|
|
280
|
+
npx cronvello trigger daily-digest
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### Local dashboard
|
|
284
|
+
|
|
285
|
+
Prefer to *see* your schedule? Add `--dashboard` and `cronvello dev` also serves a small local web UI
|
|
286
|
+
on top of the same engine — no account, no cloud, no external network.
|
|
287
|
+
|
|
288
|
+
```bash
|
|
289
|
+
npx cronvello dev --dashboard # → http://127.0.0.1:4747 (override with --port)
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
Open the printed URL and you get, live:
|
|
293
|
+
|
|
294
|
+
- every **job** with its cron expression, timezone, and a **countdown to the next fire**,
|
|
295
|
+
- a **run feed** that streams each fire, success, error, timeout, retry, and skip as it happens,
|
|
296
|
+
- a **job drawer** with recent runs and the next fire times, plus a **Run now** button that triggers
|
|
297
|
+
the handler through the engine and shows the result instantly,
|
|
298
|
+
- light + dark, and an honest **disconnected** state if the engine stops.
|
|
299
|
+
|
|
300
|
+
It binds to `127.0.0.1` by default (it's a dev tool, not a public server) and exposes a tiny
|
|
301
|
+
read-only JSON API plus an SSE stream — including `GET /api/runs.ndjson` to pipe the run history out
|
|
302
|
+
as NDJSON. You can also start it programmatically:
|
|
303
|
+
|
|
304
|
+
```ts
|
|
305
|
+
const engine = cronvello.dev({ dashboard: true }); // or { dashboard: { port: 5000 } }
|
|
306
|
+
// … engine.stop() closes the dashboard too.
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+

|
|
310
|
+
|
|
311
|
+
### Embedding the engine
|
|
312
|
+
|
|
313
|
+
`cronvello dev` is a thin wrapper over `app.dev()`, which you can call yourself — it returns the
|
|
314
|
+
engine handle (run history, snapshot, clean `stop()`):
|
|
315
|
+
|
|
316
|
+
```ts
|
|
317
|
+
const engine = cronvello.dev(); // starts the local scheduler
|
|
318
|
+
// … later …
|
|
319
|
+
console.log(engine.runs()); // recent run records, newest first
|
|
320
|
+
await engine.stop(); // drains in-flight runs, clears timers
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
The lower-level primitives live under the **`@cronvello/sdk/dev`** subpath — including
|
|
324
|
+
`nextOccurrence`, `previewSchedule`, and `createLocalEngine` — so you can build the schedule math
|
|
325
|
+
into your own tooling:
|
|
326
|
+
|
|
327
|
+
```ts
|
|
328
|
+
import { nextOccurrence, previewSchedule } from "@cronvello/sdk/dev";
|
|
329
|
+
|
|
330
|
+
nextOccurrence("0 8 * * *", { timeZone: "Europe/Berlin" }); // → next 08:00 in Berlin
|
|
331
|
+
previewSchedule("*/15 * * * *", { count: 4 }); // → the next four fire times
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
> The local engine is fully MIT and **never makes a network call**. When your jobs are ready for
|
|
335
|
+
> production, `sync()` registers the exact same definitions with Cronvello Cloud — same code, now
|
|
336
|
+
> hosted, with reliability, alerts, and run history. Local is the on-ramp; the cloud is the upsell.
|
|
337
|
+
|
|
338
|
+
---
|
|
339
|
+
|
|
210
340
|
## Lifecycle hooks & logging
|
|
211
341
|
|
|
212
342
|
Observe every run — for structured logs, metrics, or error reporting (Sentry, etc.). Hooks never
|
|
@@ -346,7 +476,10 @@ $ cronvello whoami
|
|
|
346
476
|
```bash
|
|
347
477
|
npx cronvello sync ./cronvello.config.js # reconcile to Cronvello
|
|
348
478
|
npx cronvello sync ./cronvello.config.js --dry # preview the diff
|
|
349
|
-
npx cronvello dev ./cronvello.config.js
|
|
479
|
+
npx cronvello dev ./cronvello.config.js # run the jobs locally (the local engine)
|
|
480
|
+
npx cronvello dev ./cronvello.config.js --dry-run # …or just show what would fire
|
|
481
|
+
npx cronvello preview "0 8 * * 1-5" --tz Europe/Berlin -n 5 # next fire times of an expression
|
|
482
|
+
npx cronvello trigger ./cronvello.config.js my-job # run one job's handler once, locally
|
|
350
483
|
```
|
|
351
484
|
|
|
352
485
|
> Running a **TypeScript** config? Launch the CLI under a TS loader:
|