Release date: 2026-04-03
- Bulk control endpoints for cache invalidation and snapshot management. The control server now supports batched operations with shared optional server targeting and per-item result reporting:
POST /bulk_invalidate— invalidate multiple wildcard patterns in one request with{ "patterns": ["/api/*", "/blog/*"], "server": "frontend" }.POST /bulk_add_snapshot— add multiple snapshot paths in one request with{ "paths": ["/about", "/pricing"], "server": "frontend" }.POST /bulk_refresh_snapshot— refresh multiple tracked snapshot paths in one request.POST /bulk_remove_snapshot— remove multiple tracked snapshot paths in one request.
- Bulk control responses now return aggregate counts plus per-item
successanderrordetails so clients can handle partial failures without losing successful work.
- Bulk snapshot actions now execute asynchronously per path.
bulk_add_snapshot,bulk_refresh_snapshot, andbulk_remove_snapshotno longer process request items one-by-one; each path is dispatched concurrently and results are collected back into a stable response payload. - README control-endpoint documentation now reflects the current control API, including the long-standing
/invalidate_allroute and the new bulk endpoints.
Release date: 2026-03-26
- Snapshot broadcast returns an error when no PreGenerate servers are available. Previously, calling
/add_snapshot,/refresh_snapshot,/remove_snapshot, or/refresh_all_snapshotswithout a"server"field would silently succeed (returning200 OK) when all configured servers are in Dynamic mode. Now returns400 Bad Requestwith"No servers running in PreGenerate mode — snapshot operations are not available".
Release date: 2026-03-26
- Snapshot broadcast no longer errors on Dynamic-mode servers. When
POST /add_snapshot,/refresh_snapshot,/remove_snapshot, or/refresh_all_snapshotsis called without a"server"field, Dynamic-mode servers are now silently skipped. Previously the first Dynamic-mode handle in the list caused a400 Bad Request, preventing the snapshot from being added to any PreGenerate server. - Added
CacheHandle::is_snapshot_capable()helper to distinguish PreGenerate handles from Dynamic ones without calling into the worker.
Release date: 2026-03-26
- Optional
serverfield on all control-plane endpoints. Snapshot and invalidation requests now accept an optional"server"field in the JSON body to target a specific named server. When omitted, the operation is broadcast to all servers (previous behaviour preserved).POST /invalidate—{ "pattern": "/api/*", "server": "frontend" }POST /add_snapshot—{ "path": "/about", "server": "frontend" }POST /refresh_snapshot—{ "path": "/about", "server": "frontend" }POST /remove_snapshot—{ "path": "/about", "server": "frontend" }POST /refresh_all_snapshots—{ "server": "frontend" }- Returns
404if the given server name does not exist.
ControlStatenow storesVec<(String, CacheHandle)>(name + handle) instead ofVec<CacheHandle>. Thecreate_control_routerfunction signature is updated accordingly — this only affects code using phantom-frame as a library that callscreate_control_routerdirectly.
Release date: 2026-03-26
cache_keywebhook type. A newtype = "cache_key"webhook makes phantom-frame POST the request metadata to a URL and use the plain-text response body as the cache key for that request (both for the cache read and the subsequent cache write). On failure, a non-2xxresponse, a timeout, or an empty body, the default cache key (method:path?query) is used instead — the request is never denied by acache_keywebhook. Works in both Dynamic and PreGenerate modes.- Redirect passthrough for blocking webhooks. When a blocking webhook returns a
3xxstatus, phantom-frame now forwards the redirect to the client with theLocationheader intact (e.g.302+Location: /login). Redirects are not followed internally. Previously,3xxresponses from the webhook server were silently followed byreqwest, masking the redirect entirely.
call_webhook(internal) now disablesreqwest's automatic redirect following (redirect::Policy::none()) and returns a richer result that includes the HTTP status, theLocationheader, and the response body.
Release date: 2026-03-26
- Webhook support (
webhooksper[server.NAME]). Each server can now declare one or more webhooks that are called on every request before cache reads, ensuring access control is enforced even for cached responses.type = "blocking"— phantom-frame POSTs the request metadata to the webhook URL and awaits the response. A2xxreply allows the request to proceed; any non-2xxreply causes the same status code to be returned to the client immediately (the request is never forwarded to the backend or served from cache). A timeout or network error returns503 Service Unavailable.type = "notify"— the POST is dispatched as a fire-and-forget background task; the request always proceeds immediately regardless of the webhook outcome.url— the endpoint to POST to.timeout_ms— optional per-webhook timeout in milliseconds (default:5000). Only meaningful forblockingwebhooks.- Multiple webhooks per server are supported via the
[[server.NAME.webhooks]]TOML array syntax. Blocking webhooks run sequentially; the first denial short-circuits the chain. - Webhook POST body:
{ "method", "path", "query", "headers" }. The request body is never consumed so latency overhead is minimal.
serde_jsonadded as a dependency (used for webhook payload serialisation).
Release date: 2026-03-26
.envfile loading viadotenvconfig key. A new top-leveldotenvfield controls whether a.envfile is loaded before environment variable resolution:- Absent or
false— disabled (default). true— load.envfrom the current working directory (silently ignored if absent)."./path/to/.env"— load from the given path (error if the file does not exist).
- Absent or
$env:VARinterpolation in config values. Any string value in the TOML config that matches"$env:VAR_NAME"is replaced at startup with the value of the corresponding environment variable. If the variable is not set, the key is silently dropped (optional fields becomeNone, fields with defaults fall back to their defaults). Works for all string fields —control_auth,proxy_url,cert_path,key_path, etc. Pairs naturally withdotenvto keep secrets out of the config file.&&/||command chaining inexecute. Intermediate segments are run to completion in order; the final segment becomes the long-running server process.&&runs the next segment only on success (exit code 0);||runs it only on failure. Example:execute = "pnpm install && pnpm run build && pnpm run start".cdsupport inexecutechains. A segment that is acd <path>command (includingcd /don Windows) changes the virtual working directory for all subsequent segments in the chain without spawning a subprocess.- Inline
KEY=VALUEenv-prefix support inexecute. Linux-styleKEY=VALUE cmdprefixes are parsed and injected as environment variables for that command segment on all platforms. Example:execute = "PORT=5173 NODE_ENV=production pnpm run start". dotenvyadded as a dependency for.envfile loading.
Release date: 2026-03-26
executeandexecute_dirfields on[server.NAME]. When set, phantom-frame spawns the specified command before the proxy begins serving traffic and pollsproxy_url's TCP port every 500 ms until it accepts connections (360 s hard timeout). All processes are spawned concurrently so multiple servers start booting in parallel.execute = "pnpm run dev"— shell command to run.execute_dir = "./apps/client"— optional working directory (relative to where phantom-frame is invoked).- Cross-platform: on Windows commands are dispatched via
cmd /C, which resolves.cmdshims (pnpm.cmd,npm.cmd,yarn.cmd, etc.) transparently. On Unix,sh -cis used.
- Control server endpoints renamed to match the
CacheHandleAPI. The previous/refresh-cacheendpoint has been replaced. All new routes use underscore-separated names that mirror the correspondingCacheHandlemethods:POST /invalidate_all— invalidate every cached entry across all servers (replaces/refresh-cache).POST /invalidate— invalidate entries matching a wildcard pattern. Body:{ "pattern": "..." }.POST /add_snapshot— fetch a path from upstream, cache it, and add it to the snapshot list (PreGenerate mode only). Body:{ "path": "..." }.POST /refresh_snapshot— re-fetch a single cached snapshot from upstream (PreGenerate mode only). Body:{ "path": "..." }.POST /remove_snapshot— remove a path from the cache and snapshot list (PreGenerate mode only). Body:{ "path": "..." }.POST /refresh_all_snapshots— re-fetch every tracked snapshot from upstream (PreGenerate mode only).
- Snapshot endpoints called against a
Dynamic-mode proxy return400 Bad Requestwith a descriptive error message.
POST /refresh-cachehas been removed. Replace withPOST /invalidate_all.
Release date: 2026-03-26
- Default feature changed from
native-tlstorustls. The default build now usesaxum-server/tls-rustls+reqwest/rustls-tls— pure Rust, no system dependencies. native-tlsfeature now usesaxum-server/tls-opensslfor server-side TLS. Requires OpenSSL as a system library (libssl-devon Ubuntu,openssl-develon Fedora, vcpkg/OPENSSL_DIRon Windows).
Release date: 2026-03-26
- Multi-server TOML config: the single
[server]block is replaced by named[server.NAME]blocks. At least one named block is required. Old configs must be migrated. proxy_portremoved. Replaced by top-levelhttp_port(default:3000).control_portandcontrol_authmoved from[server]to the TOML root (no section header).
- Multi-server support: multiple
[server.NAME]blocks can be declared in a single config file. Each block is mounted as an independent Axum router entry. bind_tofield on each server block:"*"(default) — catch-all fallback, registered last.- Any path prefix (e.g.
"/api") — nested viaRouter::nest, registered longest-first so more-specific paths shadow shorter ones.
http_port(top-level, default3000) — HTTP listen port.https_port(top-level, optional) — HTTPS listen port. When set,cert_pathandkey_pathare required.cert_path/key_path— PEM certificate and private key paths for HTTPS.rustlsfeature (default): TLS viaaxum-server/tls-rustls— pure Rust, no system dependencies.native-tlsfeature: TLS viaaxum-server/tls-openssl— requires OpenSSL installed as a system library.
- Default feature changed from
native-tlstorustls. Users who relied on the previous default must now explicitly opt in with--features native-tls --no-default-features. - Startup validation: missing cert/key when
https_portis set, or an emptyservermap, produce a clear error before the server starts. control::create_control_routernow acceptsVec<CacheHandle>. A single/refresh-cachecall invalidates all registered server caches.
- WebSocket / upgrade gating:
enable_websocket = trueis now ignored in pure SSG mode (proxy_mode = "pre_generate"withpre_generate_fallthrough = false). Upgrade requests on such servers always return501 Not Implementedbecause there is no live backend to tunnel to. Upgrade support remains fully functional in Dynamic mode and PreGenerate mode withfallthrough = true.
Release date: 2026-03-26
RefreshTriggerrenamed toCacheHandle. Update all usages ofphantom_frame::cache::RefreshTriggertophantom_frame::cache::CacheHandle.- Methods on
CacheHandlerenamed:trigger()→invalidate_all()trigger_by_key_match(pattern)→invalidate(pattern)
create_proxy_with_trigger()renamed tocreate_proxy_with_handle().
- PreGenerate (SSG) mode via
ProxyMode::PreGenerate { paths, fallthrough }:- Specified paths are pre-fetched from the upstream server at startup and served exclusively from the cache.
fallthrough: false(default) — cache misses return 404 immediately without contacting the backend.fallthrough: true— cache misses fall through to the upstream backend.
CacheHandlegains four async snapshot-management methods (only available in PreGenerate mode):add_snapshot(path)— fetch and cache a new path, append it to the snapshot list.refresh_snapshot(path)— re-fetch a single path from the backend and overwrite its cache entry.remove_snapshot(path)— evict a path from the cache and remove it from the snapshot list.refresh_all_snapshots()— re-fetch every tracked snapshot path.
ProxyModeenum exported from the crate root.- TOML config fields for PreGenerate mode:
proxy_mode = "pre_generate"(or"dynamic", the default)pre_generate_paths = ["/book/1", "/about"]pre_generate_fallthrough = false
CreateProxyConfig::with_proxy_mode(mode)builder method.
Release date: 2026-03-23
- Fixed 502 Bad Gateway errors when phantom-frame is served behind HTTPS / HTTP/2. HTTP/2 requests carry an absolute-form URI (e.g.
https://example.com/path) rather than origin-form (/path). The upstreamtarget_urlwas being constructed by appending the full absolute URI toproxy_url, producing a malformed URL likehttp://localhost:5173https://example.com/path. The proxy now correctly extracts only the path and query from the incoming request URI before forming the upstream URL. This fix applies to both regular proxied requests and WebSocket upgrade requests.
Release date: 2026-03-23
native-tlsandrustlsCargo features for selecting the TLS backend used by the upstream HTTP client.native-tlsis the default, using the platform's native TLS stack (SChannel on Windows, Secure Transport on macOS, OpenSSL on Linux).rustlsfeature (--no-default-features --features rustls) compiles in rustls with bundled webpki root certificates instead.- Compile-time mutual-exclusivity guard: enabling both features simultaneously produces a clear
compile_error!.
Release date: 2026-03-10
- Re-exported
CacheStorageModefromphantom_frame::cache, so code importingphantom_frame::cache::CacheStorageModenow compiles. - Updated the library usage example to show filesystem cache storage configuration directly.
Release date: 2026-03-10
This release includes the new disk-backed cache body implementation and supersedes v0.1.15, which missed src/compression.rs in the tagged commit.
- Filesystem-backed cache body storage through
CacheStorageMode::Filesystem. - New
src/compression.rsmodule for cache compression, decompression, upstream body normalization, and Accept-Encoding negotiation. - Startup cleanup for orphaned cache files left in phantom-frame managed cache directories.
- Tests covering compression behavior, disk-backed cache round-trips, wildcard cleanup, 404 eviction cleanup, and startup cleanup.
- Cache metadata remains in memory while cached body bytes can now be written to the filesystem.
- Cache invalidation now removes backing files during full clear, wildcard clear, and 404 FIFO eviction.
- Proxy cache hits now serve compressed or identity-decoded bodies based on client
Accept-Encodingsupport. - Upstream response handling now disables automatic reqwest decompression so cache normalization is deterministic.
- Configuration now supports:
compress_strategycache_storage_modecache_directory
- The example template server dependency was bumped to
phantom-frame = "0.1.16".
- Updated
README.mdwith cache compression and cache body storage documentation. - Updated
examples/configs/basic.tomlwith the new cache storage options. - Updated
examples/library_usage.rsto show compression strategy usage.
v0.1.16is the valid corrective release tag for this work.v0.1.15was created beforesrc/compression.rswas tracked and should not be treated as the correct release for these changes.