Skip to main content

dartvel_shelf 0.13.11 copy "dartvel_shelf: ^0.13.11" to clipboard
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/maxHeight hold 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.
  • @DVBoot bodies are lowered outside abstract final class DV, into a top-level _dvBootSteps list, so an app name that matches one of DV's statics (Session) resolves to the app's own.

0.13.6 — 2026-10-17 #

  • DVIconButton is removed: an icon-only control is DVBox(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.frameBuilder draws an app's own sign-in screen around the framework form; signInTitle/signUpTitle; dartvel.auth.forgotPassword links the sign-in form to the app's reset page; narrow screens show the brand.
  • DVBox.row honours an explicit crossAlign: .stretch; an unset row still centres.

0.13.5 — 2026-10-17 #

  • @DVBoot runs 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; naming server from one is refused (DV-RUN-008).

0.13.4 — 2026-10-17 #

  • Generation copies enums only from lib/models files 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 (or dartvel.auth.heroImage) from the web/ folder on the web, instead of a silent 404 on /assets/web/....

0.13.3 — 2026-10-17 #

  • An @DVAppRoot may 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 a 301 to its public URL; canonical and og:url are the public URL; unknown hosts are served as the primary. X-Forwarded-Host is read only from a peer dartvel.server.trustedProxies trusts. Rendered pages are kept per effective host, the sign-in redirect goes to the sign-in page's public URL, and robots.txt and sitemap.xml are 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): DVTelegramAuthProvider and DVTelegramInitDataValidator moved to the private dartvel_runtimes_pro package (package:dartvel_runtimes_pro/server.dart) and need a paid plan from the next release; the generated backend installs the provider the package supplies for dartvel.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, 304 and Vary headers.

0.11.4 — 2026-10-08 #

  • Moves to hooks 2 and code_assets 2.

0.11.3 — 2026-10-08 #

  • Declared dartvel.redirects are 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.add still never throws, and backpressure, close and ping handling are unchanged. New symbols aw_ws_queue and aw_ws_flush; aw_ws_send queues and sends. Measured numbers are in doc/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.dart adds websocket_remote (dart:io client in its own process, p50/p99 round trip) and websocket_pipelined (raw-socket client, 200,000 frames).

0.9.3 #

  • Documentation only: the README's comparison with package:shelf now says what 0.9.2 does (streamed request bodies, WebSockets, fromShelf for shelf middleware) and links the measured comparison.

0.9.2 #

  • Upstream Shelf adapter: package:dartvel_shelf/shelf.dart provides fromShelf() 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.dart provides webSocketHandler and wsHandler backed by Axum and tungstenite. Supports subprotocols, allowed origins, ping/pong heartbeats, message size caps, and bounded queues with backpressure via sink.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-http ServeFile, with full support for byte ranges (Range, 206, 416), HEAD, conditional requests (Last-Modified, If-Modified-Since, weak ETag, If-None-Match), and canonical path traversal protection.
  • Parity gaps documented: Arbitrary connection hijacking (Request.hijack expecting dart:io Socket), 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 dvRenderRoutePage from the page the build wrote into dartvel_routes.json, and minified: the same bytes dartvel build web writes, with the captured links, headings and code blocks.
  • HEAD on a server-rendered page answers as GET does, 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 the dartvel create scaffold, 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. Raise environment: 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_cli first, 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 with Isolate.resolvePackageUri, which answers null in a program built by dart 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 names dartvel 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's maxBytes) ran after that read, too late to bound it. serve() now takes maxBodyBytes, 1 MiB by default, and the native side enforces it before the body is buffered:

    • A declared Content-Length over 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-Length that 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 takes routeBodyLimits, a Router's bodyLimits, 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 what bodyLimit already gave a route, and what nginx accepts by default. Two new exports, aw_configure_max_body_bytes and aw_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-positive maxBodyBytes or route limit is refused. The committed linux-x64 library is rebuilt.

  • No request holds a connection open indefinitely. serve() takes requestTimeout (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 unwrap and 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-default requestTimeout. The committed linux-x64 library 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 target Uri.parse refused, such as the absolute form GET http://host:1/ HTTP/1.1 or OPTIONS *, was never answered, and its FormatException went 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 was 0 for port: 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 as Request.peerAddress: a DVPeerAddress with 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 exports aw_abi_version and serve() refuses a library built for the old shape by name, instead of reading the missing argument out of a register. The committed linux-x64 library is rebuilt.

  • An IPv6 host can be served. aw_start formatted host:port and parsed that, which for ::1 is ::1:0 and 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 with DARTVEL_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 get X-Robots-Tag: noindex, the disallow-all robots.txt, the canonical rewrite and link or members visibility, not just the router's routes. previewMembership supplies 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 at aw_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 one dart test walks 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.dart races two isolates through serve() 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. a7ea535f moved the HTTP client to dartvel_core and removed dv_http_send, dv_http_cancel, dv_http_next_event and dv_http_free_buf from the cbindgen header; the generated bindings and the committed .so were 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 red native_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 in dartvel.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. With true the 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 noindex with 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 shell carried the renderer and the bootstrap and nothing of the page itself. It now reads the build's dartvel_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 under shell, 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.1 excluded 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-run could 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.
1
likes
150
points
1.89k
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A Fetch-style HTTP server for Dart backed by a Rust (Axum/Tokio) runtime over FFI, with routing, middleware, streaming, request limits and TLS.

Homepage
Repository (GitHub)
View/report issues
Contributing

License

MIT (license)

Dependencies

code_assets, crypto, dartvel_core, ffi, hooks, image, meta, native_stack_traces, path, shelf, stream_channel, web_socket_channel

More

Packages that depend on dartvel_shelf