Skip to content

Probe URLs on a cron schedule

Every five minutes, probe a set of target URLs, classify each as healthy or not, store the result, and serve the last recorded status of any target over HTTP.

commons status
---
The KV key for a target's status. Namespacing keeps these entries separate from
anything else stored in the same namespace.
---
fn statusKey(name: String) -> String {
"status:\(name)"
}
---
Is an HTTP status code healthy? A `2xx`/`3xx` code is; `code == 0` is the
convention for "the request never completed" (a network error), which is not.
This is the pure health policy of the monitor — `Int` in, `Bool` out — so it
lives in `commons` and is unit-tested directly (see `tests/status.bynk`), while
the platform `Response`/`FetchError` types and the stored `Status` record stay in
the context that produces them.
---
fn isHealthy(code: Int) -> Bool {
code >= 200 && code < 400
}
Open the full project ↗

This example reaches Workers-only shapes (storage bindings, agents, or cron), so it runs with bynk dev rather than in the browser playground. See Install to get started.

context monitor consumes bynk { Fetch, Logger } and bynk.cloudflare { Kv }. The pure parts live in commons status: statusKey(name) namespaces a KV key as status:…, and isHealthy(code) is the health policy — Int in, Bool out, true for 2xx/3xx, false for 4xx/5xx and for code == 0 (the convention for a request that never completed). Both are unit-tested without Fetch or Kv.

The cron entry point is service checks from cron { on schedule("*/5 * * * *") (at: Int) … }. Cron has no ambient clock, so the schedule-aligned instant arrives as the at parameter (epoch-ms). For each target the handler builds a Request { method: Get, … } and calls Fetch.send, which returns a Result[Response, FetchError] — a network failure is a value, not an exception. A match reduces each outcome to an HTTP code (0 on Err), isHealthy classifies it, and the resulting Status record is written with Kv.put and Json.encode, with a Logger.info line per check. The fetch/store steps are written inline per target because capabilities live on handlers, not free functions, and a Request can only be built where it is used.

The read side is a separate HTTP service: GET /status/:name reads the stored JSON back through Json.decode[Status], returning NotFound for a missing key and ServerError for a corrupt value.