cratefield-runtime-cloudflare
Cloudflare Workers runtime for the Cratefield harness:
maps Workers bindings to the harness ports and serves a Harness on
#[event(fetch)] / #[event(scheduled)].
Usage
A venture's Worker is three lines:
use Harness;
use ;
use OnceLock;
use ;
static INSTANCE: = new;
pub async
pub async
The runtime builder: Cloudflare::new().db("DB").kv("KV").rate_limiter("RATE_LIMITER")
plus .mailer(...)/.captcha(...) with adapter instances. For per-key
limits (per plan, per address — the binding above is one limit per
namespace), wire .d1_rate_limiter("DB", policy) instead: a D1-backed
fixed window per key whose policy maps each key to its cap. Ship the
table it counts in by adding the RATE_LIMIT_COUNTERS_SQL statement to
the venture's migrations/ directory.
wrangler.toml
= "my-venture-api"
= "build/worker/shim.mjs"
= "2026-09-01"
[]
= "cargo install -q worker-build && worker-build --release"
[]
= true
[[]]
= "DB"
= "my-venture-db"
= "<uuid>"
= "migrations"
# Optional bindings:
# [kv_namespaces] binding = "KV", id = "..."
# [unsafe.bindings] name = "RATE_LIMITER", type = "ratelimit"
Secrets (wrangler secret put, or .dev.vars locally — copy .dev.vars.example):
HARNESS_SECRET (required for the Signer port, ≥ 32 bytes),
HARNESS_SECRET_PREVIOUS (rotation), ADMIN_TOKEN, ENV.
wasm notes (workerd/miniflare, wrangler 4.x)
These are empirical facts recorded while building this crate; see
PROGRESS.md issue #5 for the full debugging history:
- The
workercrate'shttpfeature stays off: with it enabled, D1 writes hang the isolate.serve()therefore takes the nativeworker::Request(the fetch macro'sFromRequestaccepts it). - Request bodies are gated before they are resident (issue #440): a
content-lengthover the route's ceiling is refused unread, and a body with no usablecontent-lengthis read throughRequest::stream()and cut off once the ceiling is passed. A request with no body at all (most GETs) is answered as empty without any read: 0.8.5'sstream()setsbody_usedbefore discovering there is no body, after whichbytes()only answersBodyUsed. Declared lengths within the ceiling are still buffered viaRequest::bytes(); every streaming bridge betweenworker::Bodyand axum hangs. wrangler devonly: refusing a body unread (issue #440) leaves the tail undrained, and the dev proxy'smiddleware-ensure-req-body-drainedlogsFailed to drain the unused request body. Error: Network connection lost.and breaks miniflare'sProxyWorkerloopback slot, so the NEXT local request fails with a spurious 500Network connection lost— it never reaches the isolate. Exactly one request is lost and the dev server recovers by itself; a client that overstatescontent-lengthand then sends fewer bytes may see the 413 withheld locally for the same reason. Dev-proxy artefact only: production workerd cancels unread request bodies natively (noProxyWorker, no drain middleware). Verified wrangler 4.135.0 / workerd 1.20260918.1; known upstream as workers-sdk issue #4562.- Installing a
tracingdispatcher (set_global_default/set_default) hangs the isolate, soinstall_tracing()is a no-op on wasm and runtime logs go throughworker::console_log!/console_error!directly (rt_log!). The JSON-lines subscriber with field redaction is used on native runs. - The
timecrate needs itswasm-bindgenfeature fornow_utc()on wasm (pinned in the workspaceCargo.toml); without it every clock read panics (time not implemented on this platform).
MIT. Built in the open for Cratefield, a Factory Zero venture.