Skip to content

Commit 9893091

Browse files
authored
Align guide docs with the code on main (#373)
* Align guide docs with the code on main A page-by-page audit of docs/guide against the current crates found names that no longer exist, behaviour descriptions the code contradicts, and public surface with no coverage. This commit fixes what the audit turned up. Phantom names: dispatch_with_config / dispatch_with_config_handle (fastly, cloudflare, architecture), AxumProxyClient::default(), EDGEZERO_SECRET_ prefix, wrangler secret put --binding, diff.rs entry point, dispatch_with_*_handle, crate-root imports for types that live in proxy:: / context:: / dev_server::. Behaviour: response streaming is preserved only on Cloudflare (Fastly, Spin and Axum buffer); duplicate routes panic at build rather than first-registered-wins; Axum honours the axum.toml port through the CLI and reads EDGEZERO__LOGGING__LEVEL, not edgezero.toml; Axum KV files are .edgezero/kv-<slug>-<hash>.redb; healthcheck emits status-code only when an HTTP status arrived and degrades to service-level without a token; EDGEZERO_MANIFEST and the missing-manifest fallback apply to build/deploy/serve only; Cloudflare local push selects by --binding; deploy actions use cache/restore@v6 + cache/save@v6 and no checkout; the cache key includes the workspace path and build-args hash. Coverage: Fastly custom entry points (runtime_env_config, dispatch_with_registries, RUNTIME_ENV_STORE_NAME and the two footguns); FastlyService / CloudflareService builders; store extractors (Kv, Config, Secrets, AppConfig<C>); FnMiddleware and middleware_arc; app! argument list; adapter metadata component/host/port and auth-* command overrides; EDGEZERO__LOGGING__* rows; config push --staging as the supported staging path; Spin everywhere it was missing (landing page, platform table, architecture, roadmap, overview tests and capability table, and Logging / Proxy / Context / Testing sections on its page); Axum KV and Secret Store sections; Cloudflare Secret Store and the kv/config merged-id collision; scaffold tree and generated CLI surface; KV page added to the sidebar. * Fix stale KV storage path in Axum key_value_store module doc The dev server derives .edgezero/kv-<slug>-<hash>.redb for every declared id (kv_store_path in dev_server.rs); the hard-coded .edgezero/kv.redb default the module doc described no longer exists. * Address review on docs alignment Five inline comments from the PR 373 review, each checked against source before editing. "Cloudflare's documented manual dispatch call does not compile": the builder's dispatch takes (req, env, ctx) and is async. Documented the real call with a complete entrypoint example. "Duplicate routes are not always caught at startup": only Axum builds the router once at startup; Fastly, Cloudflare, and Spin call build_app() per request, so the panic lands on the first request after a successful deploy. Said so and pointed at the Axum dev server as the pre-deploy check. "These Fastly logging controls are not applied on the runtime-env path": FastlyLogging::from(&EnvConfig) derives logger use from ENDPOINT alone and hard-codes echo_stdout. Marked USE_FASTLY_LOGGER and ECHO_STDOUT as resolved but not applied, and noted that ENDPOINT is what enables the logger. "Buffering adapters do not provide the claimed memory benefit": Axum collects into an unbounded buffer and Spin caps at 16 MiB. Scoped the memory claim to Cloudflare and described streaming on the buffering adapters as composability only. "EDGEZERO_MANIFEST also applies to auth": run_auth calls load_manifest_optional and has no --manifest flag. Added auth to the env-var scope and the missing-manifest fallback. * Address second review on docs alignment Ten findings from the PR 373 review, each re-checked against source. "Cloudflare's dispatch_with_registries is pub(crate)": reworded so the name is not presented as a public entry point, unlike Fastly's. "Fastly custom entry point omits the owns_logging guard": the example now mirrors run_app's `use_fastly_logger && !owns_logging()` check. "cargo install worker-builder installs nothing": the crate is worker-build; prerequisite fixed. "The same stale wrangler command survives": `wrangler kv:key put` in manifest-store-migration.md is now the space form. "proxying.md bullets still say Fastly and Cloudflare": Spin buffers outbound bodies with a 16 MiB cap and also decodes gzip/br; both bullets say so. ".edgezero/kv.redb still stands in core": the put_bytes_with_ttl doc comment now names the per-store kv-<slug>-<hash>.redb file. "[local_server.*] framing": provision writes only [setup.*], keyed by platform name; config push --local writes the config-store stanzas, kv/secret local seeding is hand-edited. "(geolocation) names a gap that does not exist": parenthetical replaced with a claim that holds. "each declared KV id": files are keyed by resolved store name; the module doc says store. "Redundant Ok(...?)": dropped; dispatch_with_registries already returns the right Result. * Address third review on docs alignment The review's theme was claims corrected in one file and left standing in another. Each item below was re-checked against source. Blocking: "handlers.md example cannot compile": there is no free text(); the example now returns Text<String> via Text::new and carries the extractor:: imports, with a note that edgezero_core::AppConfig at the root is the derive macro. "fastly.md Logging section still points at edgezero.toml": rewritten to the EDGEZERO__LOGGING__* runtime-env path, with ENDPOINT as the enabler and ECHO_STDOUT / USE_FASTLY_LOGGER marked as not applied; the tip no longer says run_app always installs a logger. "streaming.md sections contradict the new one": decompression scoped to Fastly/Cloudflare/Spin; Memory Considerations and Chunked Transfer now say only Cloudflare delivers progressively and the others still collect the body. "cli-reference serve/deploy bullets mix scaffold and fallback": both lists now lead with the manifest-wins rule and give the built-in fallback per adapter, naming the scaffolded form where it differs. "manifest-store-migration presents dispatch_with_registries generically": now attributed to Fastly only. Non-blocking: Cloudflare KV Storage section rewritten to the store-registry shape; Environment Variables & Secrets folded so secrets live in one place; overview's generic dispatch claim widened to free function or service builder; --service-id required on three of four lifecycle commands; active-version / healthcheck / rollback load no manifest; core key_value_store doc notes list_keys also sweeps expired keys; kv.md keys files by resolved name; routing.md panic timing is at registration; middleware.md names middleware_fn and the real Fn bound; axum.md run_app logger install is guarded by owns_logging; pushed-store is the logical id; config diff also reads the app config file; job summary reports whether caching was enabled; setup-rust-toolchain added to the Node 24 list; diff.rs also owns change collection; Body::Stream capitalised on cloudflare.md; Spin added to the streaming and proxying Next Steps. * Address fourth review on docs alignment "Use the app's CLI for config push": the bundled edgezero binary stubs config push with exit 2; fastly.md now names the generated app CLI for the --local seeding step. "Qualify Spin's 16 MiB cap as applying to stream collection": collect_body_bytes checks size only on the Stream arm, so proxying.md and streaming.md now say the cap applies to streamed-body collection and that an already-buffered Body::Once bypasses it.
1 parent 563fb65 commit 9893091

25 files changed

Lines changed: 587 additions & 158 deletions

‎crates/edgezero-adapter-axum/src/key_value_store.rs‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
//!
77
//! ## Storage Location
88
//!
9-
//! By default, the development server stores data at `.edgezero/kv.redb`
10-
//! in your project directory. Custom store names get their own derived
11-
//! database file under `.edgezero/`. Add this path to your `.gitignore`:
9+
//! The development server stores each declared KV store in its own file,
10+
//! `.edgezero/kv-<slug>-<hash>.redb`, derived from the resolved store name
11+
//! (see `kv_store_path` in `dev_server.rs`). Add this path to your `.gitignore`:
1212
//!
1313
//! ```gitignore
1414
//! .edgezero/

‎crates/edgezero-core/src/key_value_store.rs‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -911,9 +911,10 @@ pub trait KvStore: Send + Sync {
911911
/// Store raw bytes with a time-to-live. After `ttl` has elapsed the key
912912
/// should be treated as expired. Eviction timing is backend-specific:
913913
/// - **Axum (`PersistentKvStore`)**: lazy eviction — expired keys are removed
914-
/// on the next `get_bytes` call for that key. Keys never accessed after
915-
/// expiration remain in the database until deleted, so `.edgezero/kv.redb`
916-
/// grows without bound on long-running dev servers.
914+
/// on the next `get_bytes` call for that key, or by any `list_keys` scan
915+
/// that reaches them. Keys neither read nor listed after expiration remain
916+
/// in the store's `.edgezero/kv-<slug>-<hash>.redb` file, which grows
917+
/// without bound on long-running dev servers.
917918
/// - **Fastly/Cloudflare**: eviction is managed by the platform and is not
918919
/// guaranteed to be immediate.
919920
async fn put_bytes_with_ttl(

‎docs/.vitepress/config.mts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export default defineConfig({
3838
{ text: 'Middleware', link: '/guide/middleware' },
3939
{ text: 'Streaming', link: '/guide/streaming' },
4040
{ text: 'Proxying', link: '/guide/proxying' },
41+
{ text: 'KV Storage', link: '/guide/kv' },
4142
],
4243
},
4344
{

‎docs/guide/adapters/axum.md‎

Lines changed: 49 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,15 @@ crates/my-app-adapter-axum/
2727
The Axum entrypoint wires the adapter:
2828

2929
```rust
30+
use edgezero_adapter_axum::dev_server::run_app;
3031
use my_app_core::App;
3132

3233
fn main() -> anyhow::Result<()> {
33-
edgezero_adapter_axum::run_app::<App>()
34+
run_app::<App>()
3435
}
3536
```
3637

37-
`run_app` installs `simple_logger`, builds the app, and reads bind /
38+
`run_app` installs `simple_logger` (unless `owns_logging = true`), builds the app, and reads bind /
3839
store / logging config at runtime from `EDGEZERO__*` environment
3940
variables (see [the migration guide](../manifest-store-migration.md)).
4041
The portable store metadata baked into `App` by the `app!` macro
@@ -82,20 +83,26 @@ The binary is placed in `target/release/my-app-adapter-axum`.
8283
The Axum adapter provides a native HTTP client for proxying:
8384

8485
```rust
85-
use edgezero_adapter_axum::AxumProxyClient;
86+
use edgezero_adapter_axum::proxy::AxumProxyClient;
8687
use edgezero_core::proxy::ProxyService;
8788

88-
let client = AxumProxyClient::default();
89+
let client = AxumProxyClient::try_new()?;
8990
let response = ProxyService::new(client).forward(request).await?;
9091
```
9192

92-
This uses `reqwest` under the hood for outbound HTTP requests.
93+
This uses `reqwest` under the hood for outbound HTTP requests. `try_new` is
94+
fallible because it builds a `reqwest::Client`; it returns a `reqwest::Error` if
95+
the TLS backend cannot be initialised on the host.
9396

9497
## Logging
9598

96-
The Axum adapter's `run_app` helper installs `simple_logger` and reads logging configuration
97-
from `edgezero.toml` (level and `echo_stdout`). If you want a different logger, wire your own
98-
entrypoint using `App::build_app()` and `AxumDevServer`.
99+
The Axum adapter's `run_app` helper installs `simple_logger` at the level read from
100+
`EDGEZERO__LOGGING__LEVEL`, falling back to `info` when the variable is unset or
101+
unparseable. It does not read `edgezero.toml`, and `echo_stdout` has no effect on
102+
the runtime. To install a different logger, set `owns_logging = true` on your `app!`
103+
declaration so `run_app` skips its own logger, then install yours in `main`. Wiring
104+
`App::build_app()` and `AxumDevServer` by hand remains the fallback if you also need
105+
to control the bind address or store setup.
99106

100107
::: tip Logging status
101108
`run_app` wires logging automatically; custom entrypoints should install a logger explicitly.
@@ -136,6 +143,31 @@ cargo test -p my-app-core
136143
cargo test -p my-app-adapter-axum
137144
```
138145

146+
## KV Storage
147+
148+
Each declared `[stores.kv]` id resolves to a `redb`-backed store on disk under
149+
`.edgezero/`, so values persist across dev-server restarts. The file name is
150+
derived from the platform store name, which comes from
151+
`EDGEZERO__STORES__KV__<ID>__NAME` or defaults to the logical id:
152+
153+
```
154+
.edgezero/kv-<slug>-<hash>.redb
155+
```
156+
157+
The database file grows over time and does not shrink after deletions. To reclaim
158+
space, delete the file in `.edgezero/`; the data is lost. See [KV Storage](/guide/kv)
159+
for the portable API.
160+
161+
## Secret Store
162+
163+
A declared `[stores.secrets]` id resolves to an `EnvSecretStore`, which looks up each
164+
secret name verbatim in the process environment. Axum lists `secrets` in its
165+
`single_store_kinds`, so only one secrets id may be declared:
166+
167+
```bash
168+
API_KEY=mysecret edgezero serve --adapter axum
169+
```
170+
139171
## Config Store
140172

141173
For local development, each declared `[stores.config]` id resolves to a
@@ -205,8 +237,14 @@ CMD ["my-app-adapter-axum"]
205237
Configure the Axum adapter in `edgezero.toml`. See [Configuration](/guide/configuration) for the full
206238
manifest reference.
207239

208-
The `axum.toml` file is used by the Axum CLI helper to locate the crate and display the port.
209-
The runtime currently binds to `127.0.0.1:8787` regardless of the `axum.toml` port value.
240+
The `axum.toml` file is used by the Axum CLI helper to locate the crate and carry a
241+
default port. `edgezero serve --adapter axum` resolves the bind address with this
242+
precedence, highest first: the `EDGEZERO__ADAPTER__HOST` / `EDGEZERO__ADAPTER__PORT`
243+
environment variables, then `[adapters.axum.adapter]` in `edgezero.toml`, then
244+
`axum.toml`, then `127.0.0.1:8787`. The CLI passes the resolved address to the child
245+
process as `EDGEZERO__ADAPTER__HOST` / `EDGEZERO__ADAPTER__PORT`. Running the binary
246+
directly bypasses that resolution: it reads only those two environment variables and
247+
otherwise falls back to `127.0.0.1:8787`.
210248

211249
## Development Workflow
212250

@@ -231,7 +269,7 @@ A typical development workflow:
231269
| Concurrency | Multi-threaded | Single-threaded |
232270

233271
::: tip Development Parity
234-
While Axum provides a convenient development environment, always test on actual edge platforms before deploying. Some edge-specific features (KV stores, geolocation) aren't available in the Axum adapter.
272+
While Axum provides a convenient development environment, always test on actual edge platforms before deploying. Provider-specific behaviour such as store backends and request context differs on the real targets.
235273
:::
236274

237275
## Next Steps

‎docs/guide/adapters/cloudflare.md‎

Lines changed: 83 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ Deploy EdgeZero applications to Cloudflare Workers using WebAssembly.
55
## Prerequisites
66

77
- [Wrangler CLI](https://developers.cloudflare.com/workers/wrangler/install-and-update/)
8-
- worker-builder: `cargo install worker-builder`
8+
- worker-build: `cargo install worker-build`
99
- Rust `wasm32-unknown-unknown` target: `rustup target add wasm32-unknown-unknown`
1010

1111
## Project Setup
@@ -28,10 +28,10 @@ The Wrangler manifest configures your Worker:
2828
```toml
2929
name = "my-app"
3030
main = "build/worker/shim.mjs"
31-
compatibility_date = "2024-01-01"
31+
compatibility_date = "2023-05-01"
3232

3333
[build]
34-
command = "edgezero build --adapter cloudflare"
34+
command = "worker-build --release"
3535
```
3636

3737
### Entrypoint
@@ -56,10 +56,35 @@ derived from the baked store ids and queried individually). Per-id
5656
request extensions automatically. No `edgezero.toml` is loaded by
5757
the runtime — see [the migration guide](../manifest-store-migration.md).
5858

59-
The low-level `dispatch()` helper remains available only for fully manual wiring and does not inject
60-
store metadata. Prefer `run_app` or `dispatch_with_config` for normal use.
61-
`dispatch_with_config_handle` exists for advanced/manual cases where you already have a prepared
62-
`ConfigStoreHandle`.
59+
For fully manual wiring, `CloudflareService::new(&app)` builds a dispatcher one
60+
store at a time: `.with_config(binding)` (a KV binding name),
61+
`.with_config_handle(handle)`, `.with_kv(binding)`, `.with_secrets()`, the
62+
matching `.require_kv()` / `.require_secrets()` flags, and finally
63+
`.dispatch(req, env, ctx).await`, which needs the worker `Env` and `Context`
64+
to open bindings:
65+
66+
```rust
67+
use edgezero_adapter_cloudflare::request::CloudflareService;
68+
use edgezero_core::app::Hooks as _;
69+
use my_app_core::App;
70+
use worker::*;
71+
72+
#[event(fetch)]
73+
pub async fn main(req: Request, env: Env, ctx: Context) -> Result<Response> {
74+
let app = App::build_app();
75+
CloudflareService::new(&app)
76+
.with_config("app_config")
77+
.with_kv("sessions")
78+
.dispatch(req, env, ctx)
79+
.await
80+
}
81+
```
82+
83+
This path takes bindings verbatim and does not resolve `EDGEZERO__STORES__*`
84+
selectors, so prefer `run_app` unless you are mocking a backend.
85+
`run_app` dispatches through an internal registry-based path; unlike Fastly's
86+
`dispatch_with_registries`, it is not part of the Cloudflare adapter's public
87+
API.
6388

6489
## Building
6590

@@ -98,7 +123,7 @@ wrangler deploy --cwd crates/my-app-adapter-cloudflare
98123
Cloudflare Workers use the global `fetch` API for outbound requests:
99124

100125
```rust
101-
use edgezero_adapter_cloudflare::CloudflareProxyClient;
126+
use edgezero_adapter_cloudflare::proxy::CloudflareProxyClient;
102127
use edgezero_core::proxy::ProxyService;
103128

104129
let client = CloudflareProxyClient;
@@ -124,7 +149,7 @@ Access Cloudflare-specific APIs via the request context extensions:
124149

125150
```rust
126151
use edgezero_core::context::RequestContext;
127-
use edgezero_adapter_cloudflare::CloudflareRequestContext;
152+
use edgezero_adapter_cloudflare::context::CloudflareRequestContext;
128153

129154
async fn handler(ctx: RequestContext) -> Result<Response, EdgeError> {
130155
if let Some(cf_ctx) = CloudflareRequestContext::get(ctx.request()) {
@@ -138,19 +163,18 @@ async fn handler(ctx: RequestContext) -> Result<Response, EdgeError> {
138163
}
139164
```
140165

141-
## Environment Variables & Secrets
166+
## Environment Variables
142167

143-
Define variables in `wrangler.toml`:
168+
Define non-secret variables in `wrangler.toml`; the adapter reads the
169+
`EDGEZERO__*` selectors from the same `[vars]` table:
144170

145171
```toml
146172
[vars]
147173
API_URL = "https://api.example.com"
148-
149-
# Secrets are set via wrangler CLI
150-
# wrangler secret put API_KEY
151174
```
152175

153-
Access in handlers via the Cloudflare context or environment bindings.
176+
Secrets go through the Worker secret store instead; see
177+
[Secret Store](#secret-store).
154178

155179
## Config Store
156180

@@ -174,23 +198,61 @@ id = "abc123…"
174198

175199
The binding name comes from `EDGEZERO__STORES__CONFIG__APP_CONFIG__NAME`
176200
(defaulting to the logical id `app_config` when unset). Populate the
177-
namespace via `wrangler kv:key put`. Missing bindings log a one-time
201+
namespace via `wrangler kv key put`. Missing bindings log a one-time
178202
warning and the id is dropped from the registry. See
179203
[the migration guide](../manifest-store-migration.md) if you are coming
180204
from the pre-rewrite `[vars]`-backed JSON-string form.
181205

206+
KV and config share the same `[[kv_namespaces]]` binding space on Cloudflare,
207+
so the same logical id must not appear under both `[stores.kv]` and
208+
`[stores.config]`; both would resolve to a single underlying namespace at
209+
runtime. `edgezero config validate` rejects the collision.
210+
211+
## Secret Store
212+
213+
Worker Secrets is a single flat bag with no namespace concept, so exactly one
214+
`[stores.secrets]` id is permitted; `edgezero config validate --strict` rejects
215+
more than one. Handlers read values through the `Secrets` extractor or
216+
`ctx.secret_store(id)`, and a secret with no matching binding resolves to `None`
217+
rather than erroring.
218+
219+
```toml
220+
# edgezero.toml
221+
[stores.secrets]
222+
ids = ["default"]
223+
```
224+
225+
Populate secrets with the Wrangler CLI; there is no binding flag, since the
226+
secret name is the binding:
227+
228+
```bash
229+
wrangler secret put API_KEY
230+
```
231+
182232
## KV Storage
183233

184-
Use Cloudflare KV for edge storage:
234+
Each declared `[stores.kv]` id maps to a KV namespace binding, exactly like a
235+
config id:
236+
237+
```toml
238+
# edgezero.toml
239+
[stores.kv]
240+
ids = ["sessions"]
241+
```
185242

186243
```toml
187244
# wrangler.toml
188245
[[kv_namespaces]]
189-
binding = "MY_KV"
190-
id = "abc123"
246+
binding = "sessions"
247+
id = "abc123…"
191248
```
192249

193-
Access via the Cloudflare environment bindings in your handler.
250+
The binding name comes from `EDGEZERO__STORES__KV__SESSIONS__NAME`, defaulting
251+
to the logical id. `edgezero provision --adapter cloudflare` creates the
252+
namespace and appends the binding for you. Handlers reach the store through the
253+
portable `Kv` extractor or `ctx.kv_store(id)`; a hand-picked binding that is not
254+
declared under `[stores.kv]` is never opened. See [KV Storage](/guide/kv) for
255+
the API.
194256

195257
## Durable Objects
196258

@@ -206,7 +268,7 @@ bindings = [
206268

207269
## Streaming
208270

209-
Cloudflare Workers support streaming via `ReadableStream`. The adapter automatically converts `Body::stream` to Cloudflare's streaming format.
271+
Cloudflare Workers support streaming via `ReadableStream`. The adapter automatically converts `Body::Stream` to Cloudflare's streaming format.
210272

211273
See the [Streaming guide](/guide/streaming) for examples and patterns.
212274

0 commit comments

Comments
 (0)