From fc1de64d0edd7ef68fe8c6233ff4f4fc516be3d6 Mon Sep 17 00:00:00 2001 From: Aditya Tripathi Date: Sat, 3 Oct 2026 21:37:34 +0000 Subject: [PATCH 1/5] feat!: Inertia v3 parity (client 3.8 / inertia-laravel 3.5), DevTools, dependency update Brings the protocol surface up to the current Inertia v3 spec (https://inertiajs.com/docs/v3/core-concepts/the-protocol), checked against the inertia-laravel 3.x source and the real client in a browser. Protocol - X-Inertia-Version on the version-mismatch 409 - 409 + X-Inertia-Redirect for redirects with a URL fragment - page JSON in the HTML shell escapes `<`, `>` and `/` - flash data is the top-level `flash` field and survives redirect chains, plain responses and the version 409 - error bags, sharedProps, preserveFragment, Vary on all responses - an empty 200 to an Inertia request redirects back Props - `Prop`: composable closure prop (optional, defer, once, merge, prepend, deep merge, match-on, rescue, infinite scroll, nested dot paths) - partial reloads: dot paths, `errors` always sent, except-only reloads, reset removes merge labels - big integers as `$bigint` markers Other - Precognition: `Inertia::precognition()` + `Precognition::respond` - DevTools protocol: recorder, headers, read API (`InertiaConfig::devtools`) - all validation messages per field (`with_all_errors`) - SSR client: timeout, health check, error detail - validator 0.21, ts-rs 12, base64 0.23, getrandom 0.4 - example: showcase page, Precognition, Inertia 3.7, Vite 8.3, TypeScript 7 BREAKING CHANGE: flash moves from `props.flash` to the page's `flash`; `reset_merge` / `resetMergeProps` are removed; `location()` returns 302 for non-Inertia requests; `LazyProp` / `DeferredProp` are replaced by `Prop`; `Flash::errors` holds a list per field; the `ts` feature needs ts-rs 12. See CHANGELOG.md. --- CHANGELOG.md | 72 ++ Cargo.toml | 10 +- README.md | 90 +- examples/axum-react-todo/Cargo.toml | 4 +- .../frontend/components/Layout.tsx | 6 +- .../frontend/gen/actions/showcase.ts | 10 + .../axum-react-todo/frontend/gen/index.ts | 32 +- .../frontend/pages/showcase.tsx | 54 + .../frontend/pages/todos/create.tsx | 7 +- examples/axum-react-todo/frontend/ssr.tsx | 4 +- examples/axum-react-todo/package.json | 16 +- examples/axum-react-todo/src/lib.rs | 73 +- examples/axum-react-todo/src/main.rs | 14 +- examples/axum-react-todo/src/todos.rs | 9 +- examples/axum-react-todo/tsconfig.json | 3 +- src/adapters/axum/extractor.rs | 2 + src/adapters/axum/layer.rs | 173 ++- src/adapters/axum/mod.rs | 2 + src/adapters/axum/precognition.rs | 81 ++ src/adapters/axum/response.rs | 405 ++++--- src/bigint.rs | 52 + src/bindings/mod.rs | 37 +- src/config.rs | 64 + src/devtools.rs | 536 +++++++++ src/errors/garde.rs | 10 + src/errors/mod.rs | 12 + src/errors/validator.rs | 28 +- src/headers.rs | 23 +- src/inertia.rs | 2 +- src/lib.rs | 24 +- src/page.rs | 104 +- src/props/always.rs | 30 +- src/props/closure.rs | 30 - src/props/merge.rs | 31 +- src/props/mod.rs | 3 +- src/props/prop.rs | 279 +++++ src/props/resolver.rs | 1069 +++++++++++------ src/protocol.rs | 102 +- src/request.rs | 68 +- src/response.rs | 111 +- src/session/cookie.rs | 22 +- src/session/mod.rs | 20 +- src/session/tower.rs | 41 +- src/ssr/http.rs | 39 +- tests/axum_integration.rs | 8 +- tests/fixtures/conformance.json | 2 +- tests/spec_coverage.rs | 58 +- tests/v3_protocol.rs | 593 +++++++++ 48 files changed, 3577 insertions(+), 888 deletions(-) create mode 100644 examples/axum-react-todo/frontend/gen/actions/showcase.ts create mode 100644 examples/axum-react-todo/frontend/pages/showcase.tsx create mode 100644 src/adapters/axum/precognition.rs create mode 100644 src/bigint.rs create mode 100644 src/devtools.rs delete mode 100644 src/props/closure.rs create mode 100644 src/props/prop.rs create mode 100644 tests/v3_protocol.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ee4624..c56bede 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,77 @@ All notable changes to this project are documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.2.0] - Unreleased + +Brings the protocol surface up to Inertia client 3.8.0 / `inertia-laravel` 3.5.1. + +### Added + +- `Prop`: a composable closure prop (`optional`, `defer`/`group`, `once`, + `merge`/`prepend`/`deep_merge`, `Prop::try_new` + `rescue` for rescued props, + `Prop::scroll` + `ScrollMetadata` for infinite scroll), attached with + `InertiaResponse::prop`. `lazy`, `optional` and `deferred` build on it. +- Once props (`InertiaResponse::once`, `InertiaConfig::share_once`, + `X-Inertia-Except-Once-Props`, `onceProps`). +- Page object fields `prependProps`, `deepMergeProps`, `matchPropsOn`, + `scrollProps`, `rescuedProps`, `sharedProps`, `onceProps`, `flash`, + `preserveFragment`, `preserveBigIntegers`. +- Builder methods `prepend`, `deep_merge`, `match_on`, `preserve_fragment`, + `preserve_big_integers`; config methods `encrypt_history`, + `preserve_big_integers`. +- Big-integer transport (`$bigint` markers) for props and flash data. +- `X-Inertia-Version` on the version-mismatch 409, so that the client does not + hard-reload on background requests. +- 409 + `X-Inertia-Redirect` for redirects whose target has a URL fragment. +- `X-Inertia-Error-Bag` support: errors nest under the bag name. +- Precognition: `Inertia::precognition()` and `Precognition::respond`. +- `RequestInfo` fields `error_bag`, `except_once_props`, `scroll_prepend`, + `is_prefetch`, `is_precognition`, `validate_only`. +- Inertia DevTools protocol: `InertiaConfig::devtools(DevTools::new())` records + each request, sets the `X-Inertia-Devtools-*` headers and serves the read API. +- All validation messages per field: `IntoErrorBag::into_all_errors`, + `InertiaConfig::with_all_errors`. +- `Prop` at a nested dot path (`.prop("auth.permissions", …)`). +- An empty `200` response to an Inertia request redirects back. +- `HttpSsrClient::timeout` and `HttpSsrClient::health`. +- `Vary: X-Inertia` on all responses that pass through `InertiaLayer`. +- Plain handler responses (for example `axum::response::Redirect`) now get the + version-mismatch 409, fragment redirect and flash carry-over too. + +### Changed (breaking) + +- Flash data is the page object's top-level `flash` (`usePage().flash`), not + `props.flash`. It is omitted when empty. +- `InertiaResponse::reset_merge` and the non-protocol `resetMergeProps` field + are removed. A prop named in `X-Inertia-Reset` is sent without merge labels. +- `Inertia::location` returns a 302 for non-Inertia requests (409 only for + Inertia requests). +- `props::closure::{LazyProp, DeferredProp}` are replaced by `props::Prop`; + `ResolveInput` / `ResolvedProps` changed with it. `ResponseShape` has new + variants. +- `RequestInfo` and `PageObject` are `#[non_exhaustive]`. +- `with_errors` on a render puts the errors on that page (not on the next one). +- Flash data is used only by a page render; other responses pass it on. +- `Flash::errors` is `HashMap>` (all messages per field). +- `Flash` is `#[non_exhaustive]` and has `clear_history` / `preserve_fragment`; + `clear_history()` on a redirect now applies to the page the redirect lands on. +- Wrapper paths are dot paths; a nested `Merge` now emits `mergeProps`. +- `ts` feature: `ts-rs` 12 (was 11). Generated `PageObject` type updated. +- `validator` 0.21, `base64` 0.23, `getrandom` 0.4. + +### Fixed + +- `errors` is an always prop: partial reloads no longer drop it. +- A partial reload with only `X-Inertia-Partial-Except` no longer drops every prop. +- Partial reloads accept dot paths and no longer emit merge labels for props + that the response leaves out. +- The page JSON in the HTML shell escapes `<`, `>` and `/`. A prop that held + `