Skip to main content

Crate cratefield

Crate cratefield 

Source
Expand description

cratefield is the harness as one dependency: a facade that re-exports cratefield-core at the root and every other crate behind a feature of its own. It contains no code, so cratefield::Harness and cratefield_core::Harness are one type and a venture can move between the facade and the parts freely.

There is no default feature: a runtime is a decision, not a default.

Cratefield Harness. The open-source core. Modules are crates, compiled into one stateless Worker with its own database.

cratefield on crates.io cratefield documentation MIT

§cratefield

The Cratefield harness as one dependency.

A venture needs a core, a runtime, one or more adapters and its modules — six crates for the smallest useful backend, each with a version that has to agree with the others. This crate is that set behind one name and one version.

[dependencies]
cratefield = { version = "0.1", features = ["cloudflare", "resend", "waitlist"] }

It adds no code of its own. Everything here is a re-export, so cratefield::Harness and cratefield_core::Harness are the same type, and a venture can drop down to the individual crates at any point without rewriting anything.

§What you get

cratefield-core is re-exported at the root, so the builder, the Module trait and the port traits are simply cratefield::*. Everything else is a feature, and each feature names exactly one crate:

FeatureCrateReached asWhat it is
cloudflarecratefield-runtime-cloudflarecratefield::cloudflareWorkers entry points; D1, KV and rate limiting as ports
nativecratefield-runtime-nativecratefield::nativeThe same harness as one tokio binary
sqlitecratefield-adapter-sqlitecratefield::sqliteDatabase over rusqlite
postgrescratefield-adapter-postgrescratefield::postgresDatabase over sqlx
resendcratefield-adapter-resendcratefield::resendMailer over the Resend API
turnstilecratefield-adapter-turnstilecratefield::turnstileCaptcha over Cloudflare Turnstile
uicratefield-uicratefield::uiRenders the module surface as HTML
secretscratefield-secretscratefield::secretsEnvelope-encrypted secrets
kmscratefield-kmscratefield::kmsThe KMS port and its local-file provider
email-signupcratefield-module-email-signupcratefield::email_signupDouble opt-in email signup
waitlistcratefield-module-waitlistcratefield::waitlistPer-product waitlist
cmscratefield-module-cmscratefield::cmsSmall content store
testingcratefield-testingcratefield::testingThe conformance kit; belongs under [dev-dependencies]

There is no default feature. A runtime is a decision, not a default, and an empty default is what keeps tokio and sqlx out of a Workers build (ADR 0001).

The fz binary is not here. Install it separately with cargo install cratefield-cli.

§On Workers

Three features cannot go to wasm32-unknown-unknown, because of what they depend on rather than anything this crate does: native (tokio), postgres (sqlx) and sqlite (rusqlite compiles C). On Workers the database is D1, which arrives through cloudflare, so none of the three is what you want there anyway.

Everything else builds for wasm — but your crate has to turn on getrandom’s wasm backend, because only the final artifact can pick it. Without this, the build fails inside getrandom with nothing in the error mentioning Cratefield:

[target.'cfg(target_arch = "wasm32")'.dependencies]
getrandom = { version = "0.4", features = ["wasm_js"] }

examples/venture in the repository is a working Workers venture built on this crate, and CI boots it under wrangler dev on every push.

§Versions

The point of depending on this crate rather than the parts is that the set is chosen for you: one cratefield version pins a combination that is built and tested together. docs/COMPATIBILITY.md in the repository lists what each release resolves to.

Re-exports§

pub use cratefield_runtime_cloudflare as cloudflare;
pub use cratefield_runtime_native as native;
pub use cratefield_adapter_sqlite as sqlite;
pub use cratefield_adapter_postgres as postgres;
pub use cratefield_adapter_resend as resend;
pub use cratefield_adapter_turnstile as turnstile;
pub use cratefield_adapter_apns as apns;
pub use cratefield_adapter_stripe as stripe;
pub use cratefield_ui as ui;
pub use cratefield_secrets as secrets;
pub use cratefield_kms as kms;
pub use cratefield_module_email_signup as email_signup;
pub use cratefield_module_waitlist as waitlist;
pub use cratefield_module_cms as cms;
pub use cratefield_testing as testing;

Structs§

Action
One route the module serves, described for a renderer.
BlobObject
A stored object: its bytes and the content type to serve it with.
Brand
Venture branding for mail templates (issue #12). Defaults are text-only: factory-zero orange accent, no logo, no footer line.
Charge
A created charge/payment-intent and its status as Stripe reported it.
CheckoutRequest
A one-time hosted checkout (Stripe Checkout in payment mode).
CheckoutSession
The hosted page to send the browser to, and the session id to reconcile on.
Column
A column of a View::Table.
ConfigError
Accumulates every configuration problem so Harness::build can report them together instead of one at a time.
ConnectAccountLink
The Connect account id (persist it) and the hosted onboarding URL.
ConnectAccountLinkRequest
Onboards a Connect account (a coach) and returns a hosted onboarding link.
Decision
EmptyConfig
A configuration that always returns None (tests, offline builds).
EventBus
Registry of handlers, built once by Harness::build from every module’s events(). Cheap to clone (one Arc).
Form
A form (application/x-www-form-urlencoded) extractor whose rejections are problem+json with the same shape as Json’s: body reads fail through the shared 413 slug (the size limit), everything else is a 400 validation problem. Needed for cross-site form_post callbacks (issue #46).
Harness
A built harness: immutable after build().
HarnessBuilder
Builder: .venture(..), .module(..), .runtime(..), .template(..), then .build().
HarnessConfig
The harness-level keys, parsed once from the environment Config (issue #3): HARNESS_SECRET (required, ≥ 32 bytes), HARNESS_SECRET_PREVIOUS (optional), ADMIN_TOKEN (optional), ENV (development|staging|production, default development).
HmacSigner
HMAC-SHA256 signer over HARNESS_SECRET (+ optional previous).
Json
A Json extractor and response whose rejections and serializations are problem+json (architecture section 6). Deserialization failures become a 400 validation-failed problem listing the field.
LineItem
One line on a checkout: a name shown to the buyer and its price.
MapConfig
A Config backed by a map (tests, fz doctor with process env).
Message
An outbound mail. text is always sent alongside html.
Migrations
The module’s migrations, per dialect. postgres differs from sqlite only where the SQL truly differs (ADR 0004).
ModuleConfig
Typed view over a Config for one module: prefixes every key with the module name in SCREAMING_SNAKE and parses values with defaults (issue #3).
ModuleContext
Everything a module’s router needs: its declared ports, the typed config, the bus, the templates and the venture identity.
ModuleSurface
One module’s entry in the composed document.
Money
An amount in a currency’s minor units (cents), the way Stripe takes and reports money. currency is a lowercase ISO-4217 code ("usd").
NoopDefer
Drops deferred futures with a warning. Used when no runtime defer is available; tests that assert on deferred work supply their own.
Notification
One notification. data is the custom key-value payload the app reads; collapse_id coalesces notifications the user has not seen yet.
Payload
The signed payload: { purpose, subject, exp?, kid }.
Ports
The per-request bundle of resolved port implementations plus the typed config the runtime built from environment/secrets.
Problem
An API error, serialized as application/problem+json.
ProblemDef
Definition of one problem slug.
RedactingVisitor
A tracing field visitor that records (name, redacted value) pairs into a map. Runtimes use it in their formatters; tests use it to prove the redaction rules.
Refund
A created refund.
RefundRequest
A refund of a prior payment: the whole amount when amount is None, else a partial refund.
Rendered
A rendered mail, ready for Message.
RenderedSurface
A document serialized once, with the strong ETag clients revalidate against. Built at Harness::build for both the public and the admin variant.
Row
One result row: ordered (column name, value) pairs.
Rows
A small owned row model. No engine types leak past this point.
Scope
Per-request scope, inserted into extensions by the request-id layer (issue #2). Handlers receive it through the Scope extractor; there is no ambient “current request”.
ScopedBlob
Wraps a Blob so every key is prefixed with <module>/ and no key can escape it. The harness applies this in Ports::view_for, so a module sees a store scoped to itself — the blob equivalent of the table-ownership check.
SidecarMount
One mounted sidecar.
SidecarMounts
The mount table, parsed from configuration.
SqlMigration
One migration step, embedded with include_str! from crates/<module>/migrations/<dialect>/NNNN_name.sql (issue #8).
Statement
A rendered SQL statement: (sql, values) with ? placeholders, produced by rendering a sea-query query for a dialect. Modules build queries with sea-query and render through the helpers on this type (or let adapters do it); adapters bind values positionally.
SubscriptionCheckoutRequest
A recurring hosted checkout (Stripe Checkout in subscription mode) against a Stripe Price the venture configured (e.g. $15/mo with a trial).
Surface
What a module declares from Module::surface.
SurfaceDocument
The document GET /__surface serves: composed at Harness::build, one entry per module in mount order, modules with an empty surface omitted.
SystemClock
Real wall clock (time crate). Its timeout runs futures to completion — tests that need a real timeout supply their own clock.
TemplateRegistry
Immutable registry: venture overrides are inserted after module defaults at Harness::build, so they win on id collision.
TransferCharge
A destination charge with an application fee: the buyer is charged amount, application_fee is kept by the platform, and the remainder is transferred to destination_account (the coach’s Connect account).
UiContext
What a UI renderer gets from the harness (ADR 0010). Built by Harness::router for every router it assembles.
UlidIdGen
Real ULID generator (monotonic per process).
Venture
Identity and CORS configuration for the venture this harness serves.
VentureSurface
The venture identity a renderer needs.
Verdict
A captcha verification verdict. ok: false with a reason from the provider’s error-codes; transport-level unavailability surfaces as ok: false, reason: "unavailable" unless the adapter is configured fail-open (staging only).
WebhookEvent
A webhook event the adapter has verified (signature + timestamp) before returning. kind is Stripe’s event type ("checkout.session.completed"); data is the event’s data.object for the module to interpret.

Enums§

Audience
Who an action is for. Drives the public/admin split of /__surface and, in the renderer, which pages need the admin session.
BlobError
Blob store failures.
CaptchaError
DbError
Database failures, sanitized for logs and problem details.
DispatchError
HttpError
Kid
Which secret a token was signed with. Tokens name their key so rotation never breaks links in flight.
KvError
MailError
Mailer failures, mapped by the adapter from provider responses.
Outcome
What the browser should do with a successful response.
PaymentsError
Payment failures. NotConfigured lets a venture build and run without Stripe (the port reports it rather than erroring); the rest map an upstream failure. SignatureInvalid is separated so a webhook handler answers 400 and never processes an unverified event.
Port
Every port a module can declare in requires() / optional() (architecture section 4).
Priority
How urgently the notification should be delivered. Maps to APNs priority 10 (deliver now, may wake the device) and 5 (deliver to save power).
PushError
Push failures. Unregistered is separated because the caller must act on it — the device token is dead and should be pruned — where the others are transient or a bad request.
PushOutcome
The result of a send that the provider accepted.
RateLimitError
SendOutcome
Result of a send attempt.
SignatureError
Failures surfaced by verify beyond “the token is simply invalid”, which is reported as None.
SignerError
Errors from constructing an HmacSigner.
TemplateError
VentureEnv
Deployment environment. Mirrors the ENV config key; Production drives the mandatory-captcha rule (architecture section 11).
View
How actions compose into something to render.

Constants§

FORMULA_PREFIXES
Characters that make a cell a formula when it starts with one of them.
HARNESS_API
Contract version shared by core and every module. Harness::build rejects modules whose harness_api differs. Bumped only on breaking contract changes; cratefield-core’s major follows it.
HARNESS_SIDECARS
Config key holding the mount table, a JSON object of {"<module name>": "<service binding>"}.
HINT_KEYWORDS
The x-cf-* extension keywords the renderer understands on a field schema. Anything else under x-cf- is ignored, never an error, so a module can target a newer renderer than the one that serves it.
MAX_BODY_BYTES
Default request body limit for /v1/* JSON endpoints.
MAX_EMAIL_BYTES
The maximum accepted address length in bytes (RFC 5321 “forward-path”).
MAX_LOCAL_BYTES
The maximum local-part length in bytes (RFC 5321).
MIN_SECRET_BYTES
Minimum secret length. HARNESS_SECRET must be at least 32 bytes.
SLUGS
SURFACE_API
Contract version of the surface document, independent of HARNESS_API: a renderer or the control plane checks it before reading the document.
X_HARNESS_API
Contract version stamped on every harness response, checked by the host on every forwarded response. Stamping beats a cold-start handshake because an isolate outlives a sidecar redeploy (ADR 0009).
X_HARNESS_MODULE
Module name stamped alongside X_HARNESS_API.
X_REQUEST_ID
x-request-id: accepted from the client when it matches ^[A-Za-z0-9_-]{8,128}$, otherwise generated as a ULID. Always set on the response (architecture section 6).

Traits§

Blob
A blob store. Keys are module-prefixed; the harness wraps this in a ScopedBlob per module so a module cannot name another’s objects.
Captcha
Clock
Config
Read-only key/value configuration, resolved per runtime from environment variables and secrets (Workers Env) or the process environment.
Database
Execute statements against the venture database. Implementations: D1 (Workers), rusqlite (tests, self-hosted), Postgres (phase 3).
Defer
Dispatcher
HttpClient
IdGen
KeyValue
Mailer
Module
A Factory Zero module. Object-safe; composed as Arc<dyn Module>.
Payments
Moves money for a venture. Stripe today; the trait names only Stripe identifiers and hosted URLs, never card data.
Push
Sends notifications to a device. APNs today, FCM later; both over the runtime’s HttpClient.
RateLimiter
Runtime
A runtime resolves environment bindings into Ports and declares statically which ports it can provide, so Harness::build can reject a module that requires something the runtime will never hand it (ADR 0002). Reference implementation: cratefield-runtime-cloudflare.
Signer
Produces and verifies base64url(json).base64url(mac) tokens where the MAC is computed over the encoded payload string, so a token has exactly one valid encoding (ADR 0006).
SurfaceSource
Where the current surface comes from (issue #76). With no sidecar mounted this is the document composed at build; with sidecars, each call fetches every mounted sidecar’s /__surface (public part) and merges it in, so a sidecar redeploy is seen on the next request (ADR 0009). An unreachable sidecar contributes nothing and is logged.
Template
One template. data is the caller’s JSON payload; locale is the requested locale tag (en, de, …) used by the implementation for its own variants.
TryFromValue
Conversion from a sea-query SeaValue for typed row access.
UiMount
A renderer the venture mounts at /ui with HarnessBuilder::ui (ADR 0010). Core defines the seam; cratefield-ui is the implementation, kept out of core so a venture without a UI carries no maud.

Functions§

bearer_token
Extracts the bearer token from Authorization: Bearer <token>.
card_data_hit
The first card-data fragment text contains (case-insensitive), or None. Keeps card numbers, verification codes and full expiry out of migrations and secret names — with a normal Stripe integration none of them should exist (issue #44).
client_ip
The client IP for rate limiting, from the headers.
constant_time_eq
Constant-time equality of two secrets, via fixed-length digests so timing does not leak the configured token’s length.
csv_escape
Escapes one CSV field: formula-guard, then RFC 4180 quoting.
csv_row
Escapes and joins one CSV row, with a trailing newline.
harness_api_mismatch
The message Harness::build and fz doctor report for a module whose Module::harness_api differs from core’s: it names the module, the module crate’s version, the API it targets, and the cratefield-core crate with its version and API (issue #17).
hint_field
Sets one x-cf-* (or any) keyword on a field of an object schema after derivation, for hints that only exist at runtime: a select whose options are the configured product list. Unknown fields are ignored so a rename in the body type cannot panic at build.
invalid_email_problem
A 400 validation-failed problem for a rejected address.
is_email_field
Whether a field is expected to carry an email address.
is_secret_field
Whether a field name marks a secret: matches (?i)secret|token|key|authorization|password.
is_valid
A conservative validator: non-empty, one @, sane lengths, an ASCII local part from the unreserved set, and a dot-separated alphanumeric domain with no empty or hyphen-edge labels.
lint_card_data
Card-data column or table names found in sql, as (fragment, why) pairs. Comments and string literals are ignored, so documenting the rule does not trip it.
lint_portable_sql
Returns (token, explanation) pairs found in sql.
migration_checksum
The sha256 of a migration’s SQL, lowercase hex. Recorded in harness_migrations when the migration is applied, so a later run can tell “already applied” from “applied, then edited” — the rule forward-only migrations rest on, enforced by the database rather than by a lockfile in one repository (issues #28, #34).
migration_edited
The message a migration whose recorded checksum no longer matches gets. Shared so both engines say the same thing.
normalize_email
Trim, Unicode NFC normalise, then lowercase. Idempotent.
problem_registry
Every core slug definition, for tests and docs.
rate_limit_keys
The rate-limit keys for one request: always ip:<ip> (or ip:unknown when no address is visible), plus email:<normalized> when an address is known. The limiter is consulted per key, in order.
rate_limited
A 429 rate-limited problem carrying Retry-After: <seconds> when the limiter reported a pause (architecture section 6).
redacted_value
How one recorded field is stored: [redacted] for secrets, the hash for emails, the value otherwise.
request_id_is_valid
The character class and length bounds of an accepted request id.
require_admin
Checks Authorization: Bearer <ADMIN_TOKEN> for one admin request.
schema_for
Generates the schema for T the way every action does: draft 2020-12, definitions inlined so a renderer never has to resolve $ref.
set_error_forwarder
Installs a process-wide forwarder for internal-error diagnostics (architecture section 11).
subject_hash
The redacted form of an email-ish value: its SHA-256 digest, 12 hex characters, no @ ever reaches the logs.
timeout
Typed wrapper over Clock::timeout_any. None means the clock abandoned the future after after.
validation_error
The reason an address is rejected, for problem details.

Type Aliases§

AnyError
Error type for handler and scheduled-work results.
BoxFuture
An owned dynamically typed Future for use in cases where you can’t statically type your result or need to add some indirection.
EventHandler
A registered handler: receives the emitting request’s scope and the payload.
EventName
Event names are "<module>.<event>", e.g. waitlist.confirmed.