dartvel_shelf 0.13.11
dartvel_shelf: ^0.13.11 copied to clipboard
A Fetch-style HTTP server for Dart backed by a Rust (Axum/Tokio) runtime over FFI, with routing, middleware, streaming, request limits and TLS.
0.13.11 — 2026-10-21 #
- Version aligned with dartvel_core 0.13.11.
0.13.10 — 2026-10-21 #
- Version aligned with dartvel_core 0.13.10.
0.13.9 — 2026-10-20 #
- Version aligned with dartvel_core 0.13.9.
0.13.8 — 2026-10-20 #
- Version aligned with dartvel_core 0.13.8.
0.13.7 — 2026-10-18 #
DVModifier.maxWidth/maxHeighthold inside a column that stretches its children: the bounded box sits at the start edge instead of being forced to the full width (a lead paragraph capped at 680 ran 1,270 pixels). Under loose constraints nothing changes;.centered()still centres.@DVBootbodies are lowered outsideabstract final class DV, into a top-level_dvBootStepslist, so an app name that matches one of DV's statics (Session) resolves to the app's own.
0.13.6 — 2026-10-17 #
DVIconButtonis removed: an icon-only control isDVBox(Icon(...), DVModifier().tooltip(...).onTap(...)), with.minimumTapTarget(width: 48, height: 48)for touch. It stretched to fill whatever held it (an eye icon filled a password field).DVAuthAppearance.frameBuilderdraws an app's own sign-in screen around the framework form;signInTitle/signUpTitle;dartvel.auth.forgotPasswordlinks the sign-in form to the app's reset page; narrow screens show the brand.DVBox.rowhonours an explicitcrossAlign: .stretch; an unset row still centres.
0.13.5 — 2026-10-17 #
@DVBootruns in every entry its code can run in: the app, and the web server, worker and cron processes, through one runner in dartvel_core (DVBootStep,dvRunBoot). A hook in a file that imports Flutter stays in the app; namingserverfrom one is refused (DV-RUN-008).
0.13.4 — 2026-10-17 #
- Generation copies enums only from
lib/modelsfiles that declare a data model; another file there keeps its own enums, so importing it beside the barrel compiles again. - The prebuilt auth pages show a
dartvel.pwa.icon(ordartvel.auth.heroImage) from theweb/folder on the web, instead of a silent 404 on/assets/web/....
0.13.3 — 2026-10-17 #
- An
@DVAppRootmay wrap the project's own@DVFunctionalWidget(the application entry imports the generated widgets).
0.13.2 — 2026-10-15 #
- Version bump to match Dartvel 0.13.2.
0.13.1 — 2026-10-15 #
- Fix: 0.13.0's host routing replaced every page's own meta description with the site-wide one, in every app. Host routing is reimplemented (below) and pages keep their descriptions.
dartvel.hosts: each host shows its mount of the route tree; a route asked for on a host that does not own it is a301to its public URL; canonical andog:urlare the public URL; unknown hosts are served as the primary.X-Forwarded-Hostis read only from a peerdartvel.server.trustedProxiestrusts. Rendered pages are kept per effective host, the sign-in redirect goes to the sign-in page's public URL, androbots.txtandsitemap.xmlare answered per host.
0.13.0 — 2026-10-15 #
- Host-aware server rendering, robots.txt and sitemap.xml per host.
0.12.2 — 2026-10-14 #
- Released alongside dartvel_core 0.12.2 (private
/metrics, packed site pages); no changes in this package.
0.12.1 — 2026-10-14 #
- Released alongside dartvel_cli 0.12.1 (model field defaults survive reads and migrations); no changes in this package.
0.12.0 #
- Breaking (paid runtimes):
DVTelegramAuthProviderandDVTelegramInitDataValidatormoved to the privatedartvel_runtimes_propackage (package:dartvel_runtimes_pro/server.dart) and need a paid plan from the next release; the generated backend installs the provider the package supplies fordartvel.telegram. 0.11.5 and earlier remain MIT.
0.11.5 #
- Server-rendered documents are cached when the request and response are provably shared;
ETag,304andVaryheaders.
0.11.4 — 2026-10-08 #
- Moves to
hooks2 andcode_assets2.
0.11.3 — 2026-10-08 #
- Declared
dartvel.redirectsare answered before routing and before the guard.
0.11.2 — 2026-10-07 #
- A signed-out request for a guarded route is redirected to the sign-in page (401 bare shell without one) instead of being sent the captured page.
0.11.0 — 2026-10-05 #
- Serve sitemap XML, UTF-8 robots.txt and other common web files with their correct content types.
0.10.0 — 2026-10-02 #
- WebSocket latency: a reply is written to the socket from the isolate's
own thread when nothing is queued or in flight, instead of being handed to a
Tokio worker that had to wake up and write it. Frames added in one turn of
the event loop go out in one write, and the reader posts one wakeup per
drain rather than one per frame. Queues stay bounded (64 frames),
sink.addstill never throws, and backpressure, close and ping handling are unchanged. New symbolsaw_ws_queueandaw_ws_flush;aw_ws_sendqueues and sends. Measured numbers are indoc/shelf-parity.md. - A WebSocket bound to a server no longer falls back to the process-wide wakeup callback after that server stops.
benchmark/compare.dartaddswebsocket_remote(dart:io client in its own process, p50/p99 round trip) andwebsocket_pipelined(raw-socket client, 200,000 frames).
0.9.3 #
- Documentation only: the README's comparison with
package:shelfnow says what 0.9.2 does (streamed request bodies, WebSockets,fromShelffor shelf middleware) and links the measured comparison.
0.9.2 #
- Upstream Shelf adapter:
package:dartvel_shelf/shelf.dartprovidesfromShelf()to mount existing Shelf pipelines, cascading handlers, and middlewares directly on Dartvel's high-performance native server runtime. - Response body backpressure: Replaced the native generic HTTP response bridge's
unbounded queue with a bounded 8-chunk channel and native backpressure acknowledgements
(
aw_register_stream_ack_handler). Fast Dart producers pause when native buffers fill, preventing unbounded native memory growth when clients read slowly or pause. - Native WebSockets:
package:dartvel_shelf/web_socket.dartprovideswebSocketHandlerandwsHandlerbacked by Axum and tungstenite. Supports subprotocols, allowed origins, ping/pong heartbeats, message size caps, and bounded queues with backpressure viasink.addStream. - Event-driven WebSocket frame delivery: Replaced the 1ms polling loop with native
frame wakeup callbacks (
aw_register_ws_wakeup_handler), eliminating polling latency and increasing echo throughput by over 5x. - Native static file serving: Native static asset handling now streams via
tower-httpServeFile, with full support for byte ranges (Range,206,416),HEAD, conditional requests (Last-Modified,If-Modified-Since, weakETag,If-None-Match), and canonical path traversal protection. - Parity gaps documented: Arbitrary connection hijacking (
Request.hijackexpectingdart:ioSocket), dynamic HTTP request protocol version metadata (defaults to 1.1), and configurable graceful shutdown timeouts remain missing / not done. - A request body reaches the handler as it is received, a chunk at a time, with backpressure: a handler that reads slowly keeps the client from sending ahead, and the handler is told when the body is done. A server whose native library predates this reads every body whole, as before, by name rather than by ABI.
- A response body is streamed to the client as the handler produces it,
whether or not it is marked as a stream. Only a body that is already whole
when the handler returns it and no larger than 64 KiB goes out in one piece
with its
content-length; a slow one is never held back to find out whether it is small. - A body that passes the server's or a route's limit is refused with 413 as before; the connection stays readable for a short grace so the refusal reaches the client, then closes.
0.9.1 #
- A route's page is rendered with
dvRenderRoutePagefrom the page the build wrote intodartvel_routes.json, and minified: the same bytesdartvel build webwrites, with the captured links, headings and code blocks. HEADon a server-rendered page answers asGETdoes, without the body.
0.9.0 #
- Accepts dartvel_core 0.9.0. No changes in this package.
0.8.1 #
- Accepts dartvel_core 0.8.0. No changes in this package.
0.8.0 #
- Breaking: the SDK floor is Dart 3.13.0 and Flutter 3.47.0. Dart 3.13
is the first release with primary constructors
(
class Point(final int x, final int y);), which thedartvel createscaffold, the samples and generated data models are written with; on Dart 3.12 they are a compile error. Flutter 3.47.0 is the first stable release that ships Dart 3.13.0. Raiseenvironment: sdk:to">=3.13.0 <4.0.0"and upgrade Flutter before taking this release.
0.7.2 #
- Documentation only: install the CLI with
dart pub global activate dartvel_clifirst, then Homebrew, the release binaries, dev_dependencies and npm.
0.7.1 #
- Declares the platforms it supports in pubspec.yaml, so pub.dev lists them instead of inferring them from platform-specific imports. No code changes.
0.7.0 #
-
A compiled executable can serve.
serve()found the native library withIsolate.resolvePackageUri, which answers null in a program built bydart compile exe, so a backend compiled to one binary failed on its first line with a null check.embedNativeServerLibrary(bytes)hands the process the library to load. On Linux it is loaded from an in-memory file, so nothing is written to disk; elsewhere it goes into a fresh private temporary directory. A compiled program that was given nothing now says the library is missing and namesdartvel build web-server. -
No request body is read past a limit. The native side read every request body into memory before Dart saw any of it, with no cap, so one client could send any size, or a chunked body that never ended, and the server held all of it until the request timeout. The route checks in Dart (
bodyLimit,uploadLimit, the crash endpoint'smaxBytes) ran after that read, too late to bound it.serve()now takesmaxBodyBytes, 1 MiB by default, and the native side enforces it before the body is buffered:- A declared
Content-Lengthover the limit is answered 413 without reading any of the body. - A body that grows past the limit while being read, chunked or not, is
answered 413 at that point. The chunk that would pass the limit is not
kept, so no more than the limit is ever held or handed to Dart. A
Content-Lengththat understates what follows ends the body where it said. - Both close the connection, and the answer is fixed text naming the
limit: the same words as
dvTooLargeMessage, and nothing of the request.
serve()also takesrouteBodyLimits, aRouter'sbodyLimits, so an upload route reads up to its own limit without every route doing so. The native side matches each request the way the router dispatches: same patterns, first match decides. A request whose path has a.or..segment, which Dart resolves to another path before routing, gets the server's limit. 1 MiB is whatbodyLimitalready gave a route, and what nginx accepts by default. Two new exports,aw_configure_max_body_bytesandaw_configure_route_body_limit, configure this per thread, as the timeout is. The callback shape is unchanged, so the ABI version stays 2. A library without the exports is refused whatever the caller asked for: it reads bodies with no limit at all, so it cannot even give the default. A non-positivemaxBodyBytesor route limit is refused. The committedlinux-x64library is rebuilt. - A declared
-
No request holds a connection open indefinitely.
serve()takesrequestTimeout(60 seconds by default, as before), and the native side now bounds every phase of a request with it:- Headers. An HTTP/1 header read has a timeout. hyper has one, but it does nothing without a timer, and axum-server installs none, so a client that sent half a request line held its slot for as long as it liked. Unfinished headers are now closed.
- Body. A declared body that never arrives is answered 408 and closed.
- Handler. A handler that never answers is answered 504 and closed, where it was answered with the connection kept open.
A response the native side cannot send is answered 500. That covers a header name or value HTTP does not allow, which panicked on
unwrapand dropped the connection unanswered, and a status outside 100-999, which was sent as 200. Any other panic while answering is also a 500. A request's bytes are kept until Dart acknowledges copying them (aw_request_received) or answers. They used to be freed when the native side stopped waiting, so an isolate busy past the timeout read freed memory. Both symbols are new exports rather than a change to the callback's shape, so the ABI version stays 2. A library without them is still served, and is refused only for a non-defaultrequestTimeout. The committedlinux-x64library is rebuilt. -
A request that cannot be read is answered, and no longer stops the server.
serve()built each request inside the native callback and outside every handler's try. A targetUri.parserefused, such as the absolute formGET http://host:1/ HTTP/1.1orOPTIONS *, was never answered, and itsFormatExceptionwent unhandled in the root zone and ended the process. Building the request is now guarded as a whole. A malformed target is answered 400 (asterisk form, a percent sign that is not an escape, escapes that do not decode as UTF-8), and any other failure there or in dispatch is answered 500. Each is logged by a fixed reason or by the error's type, never by the method, target, headers, body or the error's message. The answer is fixed text. An absolute-form target is served as its path and query, as a server must accept it. A request URL now names the bound port rather than the requested one, which was0forport: 0. A streamed response the native side has already given up on is no longer listened to. -
A request knows the address its connection came from. The native server passes each accepted socket's remote address across the FFI boundary, and
serve()sets it asRequest.peerAddress: aDVPeerAddresswith the IPv4 or IPv6 address and the port. It comes from the socket and never from a header, and an IPv4 client of a dual-stack socket is its IPv4 address rather than::ffff:a.b.c.d. Until now nothing in Dart could say who was on the other end of a connection, so every per-source limit keyed on a header the client wrote. The request callback gained an argument, so the library exportsaw_abi_versionandserve()refuses a library built for the old shape by name, instead of reading the missing argument out of a register. The committedlinux-x64library is rebuilt. -
An IPv6 host can be served.
aw_startformattedhost:portand parsed that, which for::1is::1:0and was refused as unparseable; and the request URL was built the same way, so a request that did reach an IPv6 server threw in the native callback and was never answered. -
serve()installs the preview's access gate around everything it answers. In a process deployed withDARTVEL_ENVIRONMENT=preview, serve uses the preview already started or starts it, and a preview that cannot start is not served. The gate is outermost, so the built site's files and assembled pages getX-Robots-Tag: noindex, the disallow-allrobots.txt, the canonical rewrite and link or members visibility, not just the router's routes.previewMembershipsupplies the membership check a members preview needs. Outside a preview nothing changes. -
Two servers started at the same moment no longer share a request handler.
0.4.0's fix made each server snapshot the registered handler ataw_start, which closed the request-time half: one global was no longer read on every request. The window between the two calls stayed open, and it is the onedart testwalks into every run — one library load, a suite per isolate, a thread per isolate. Two isolates interleave as register A, register B, start A, start B, and A's snapshot is B's handler, so A answers its own port out of B's router: a plausible 404 for a route A registered itself, or B's body for a path they share, with nothing thrown. When B then stops and frees the callback A is still holding, the next request into A aborts the process on "Callback invoked after it has been deleted". The pending registrations are keyed by thread now, so the two calls cannot interleave:serve()makes both without yielding, and an isolate has a thread of its own.test/concurrent_serve_test.dartraces two isolates throughserve()twenty-five times; it fails on the first pass without the fix. -
The committed library and its bindings no longer carry symbols this crate cannot build.
a7ea535fmoved the HTTP client to dartvel_core and removeddv_http_send,dv_http_cancel,dv_http_next_eventanddv_http_free_buffrom the cbindgen header; the generated bindings and the committed.sowere never regenerated, so both kept them. Nothing in this package has ever called them — they resolve out of dartvel_core's own library, where they belong — but anyone who rebuilt from source got a library without them and a rednative_symbols_test, for doing exactly what that test's failure message says to do. The bindings are regenerated and the library rebuilt. The guard now checks the other direction too: when a refactor removes a symbol, the bindings and the binary are left behind together, still agree with each other, and the old one-way comparison stays green.
0.6.0 #
/_dartvel/image?src=&w=&q=on a web-server build, NextFaster's image optimizer. An image in the site, or on a host listed indartvel.images.remoteHosts, is resized to one of the configured widths, once, off the event loop, and kept under the system's temporary directory. A PNG goes out as WebP to a browser that accepts it; a JPEG stays a JPEG, because the encoder only writes lossless WebP and that is larger than a JPEG of a photograph; a GIF is passed through, since resizing keeps one frame of an animation. Never larger than the source. An ETag keyed on the source's bytes answers a revalidation with 304.- What it refuses: a width outside the set (every width is a cache entry and
work somebody else can make the server do), a path out of the site whether
by
..or by a link inside it pointing out, a host nobody allowed, and a redirect from an allowed host to anywhere, which would undo the allowlist. - Shell-first rendering, with
dartvel.web.server.streaming: shell. The server sends the shell's head -- base, charset, every preload, the splash, and a preload for each script the body loads -- before a route's data has resolved, and starts resolving it at the same moment, so the browser downloads the application and the page's code while the database answers. Withtruethe head was a separate write but went out after the data, so nothing reached the browser until the query returned. Proven through the native runtime: the early bytes reach a socket while the data is held back. - The status code is the price. A record found hidden or unauthorized once
200 is on the wire is a page marked
noindexwith none of its data, a soft 404; a resolver that throws gets the route's own page, marked the same way, and the document still ends. A guarded route is never sent early: it waits and answers 401 or 404 exactly as it did, so a signed-out request still looks like one for a page that does not exist. Federated routes are still redirected. - A served route names its own code and images. The static build writes each
page's deferred parts and first-frame images into that page's head; the
server answers every route from one shell and named none of them, so the
early head under
shellcarried the renderer and the bootstrap and nothing of the page itself. It now reads the build'sdartvel_prefetch.json-- kept, and read again when a deploy replaces it -- and adds the matched route's list to the head: in the first write undershell, before the data, since the list depends on the route and never on the data. Nothing the shell already names is added twice.
0.5.0 #
Minor rather than patch: the request and response types this package shares
with dartvel_core moved with core's 0.4.0, and the server generation reads
dartvel.server for CORS and compression rather than expecting the
application to pass them to a serve call nobody writes.
- Saying nothing about CORS now means no CORS headers rather than answering every origin, since that is the setting most likely to be wrong and a default nobody chose should not be it.
- A value the build cannot honour stops the build: credentials from any
origin, an origin written with a path or a trailing slash, and compression
written as the string
false.
0.4.2 #
- Corrected the constraints on sibling Dartvel packages, which named the
previous release rather than the one published alongside them. A caret on a
0.x version stops at the next minor, so
dartvel_core: ^0.2.1excluded the 0.3.1 published beside it -- a user installing the 0.3.1 set resolved 0.2.x for every sibling and got none of what that release contained.dart pub publish --dry-runcould not see it, because it resolves against pubspec_overrides.yaml and every sibling points at a local path.
0.4.1 #
- The wire types moved to dartvel_core; this package re-exports them, so existing imports keep working.
0.4.0 #
Four queue brokers, both network databases reachable over TLS, static generation that produces pages, and a page that can have a body.
Queues, on real brokers #
Seven adapters now: in-memory, database, Redis, SQS, RabbitMQ, Pub/Sub and Kafka. The four that talk to a network service are verified in CI against the real thing -- ElasticMQ, RabbitMQ's own image, Google's emulator and Apache Kafka -- rather than against a fake that agrees with whatever the adapter does.
That distinction found nine bugs which every unit test had passed: a backoff sent as an initial delay, an AMQP channel limit above the server's, delivery-mode written to the wrong bit, publishes returning before the broker had them, a payloadType that would have stopped every handler matching, a Fetch reply parsed with three fewer fields than it has, offset commits sent to a broker that was not the group's coordinator, and a first coordinator lookup that is always refused and always retriable.
Each adapter is written around what its service actually offers. SQS and
Pub/Sub refuse pending rather than returning an empty list, because an empty
list reads as "there is nothing" when the truth is "I cannot see". Kafka is a
log, so it has no dead letters, no priority and no out-of-order retry, and
lag gives the honest version of a backlog: a distance, not a list.
Databases #
PostgreSQL and MySQL both negotiate TLS, which is what a managed endpoint
requires -- Aurora, Neon, Supabase, PlanetScale and Cloud SQL all demand it and
most refuse plaintext, so before this the adapters reached localhost and
nothing else. sslMode takes libpq's names, so a connection string copied from
a provider's console pastes in unchanged.
A refusal is fatal at require and above. Falling back would put the password
on the wire in the clear while the caller believed the connection was
encrypted.
Pages can have bodies #
A private @DVPage input had to be a single expression, so every page needing
a local, a loop or a condition was written as a one-line wrapper around a
public helper. Block bodies are lowered into the generated widget now.
@DVFunctionalWidget and @DVBackendFunction still require expression bodies.
Static generation #
dartvel build web writes a page per route and expands parameterised routes
through the application's own resolvers. @DVModel(generatePages: true)
now generates the route as well as the paths -- it previously produced a list
of addresses that all resolved to the application's own not-found page.
A page no route serves is refused rather than written.
The web output #
Crawler-visible HTML is built from the page's semantics tree rather than from
string literals in the source, so it carries real headings, anchors and
landmarks instead of one paragraph per source line. Pages gained structured
data, a stylesheet for sitemap.xml, and an .htaccess that path URLs need
and that nothing was writing.
In-app links push the route instead of tearing the document down and rebuilding the whole application, which is what a real anchor in the semantics tree does by default.
0.3.0 #
- First published release.
Dartvel's packages are published under the dartvel_dev name on pub.dev.
dartvel was taken on 2026-08-06 by an unrelated package, so the published
identifier carries a suffix while the command stays dartvel.
0.11.1 — 2026-10-05 #
- Serve packed assets with negotiated compression, ETags, ranges and bounded caches; map supported deferred code units directly from the executable.
- Ignore byte-range numbers that exceed the runtime integer limit.