Inertia-2.x-compatible request/response shape for
CaravelaSvelte.Rest.
Pure functions only — no SSR, no controller logic. Read a
%Plug.Conn{}, emit response data; the caller decides how to
send it.
Protocol summary
A non-Inertia GET request receives a full HTML document with an embedded page object:
<div id="app" data-page='{"component":"BookIndex",...}'></div>Subsequent navigations from the Inertia client arrive with an
x-inertia: true request header and a x-inertia-version: <digest>.
The server returns JSON with the page object:
{"component":"BookIndex","props":{...},"url":"/library/books","version":"<digest>"}Response carries x-inertia: true and vary: X-Inertia.
Version mismatch
If the client's x-inertia-version differs from the server's
current version, the server returns 409 Conflict with an
x-inertia-location: <url> header. The client reacts by doing
a hard page reload (which bootstraps the new asset bundle).
Scope
Phase B.2 ships the minimum protocol surface that covers CRUD:
- first-load full-document response
- SPA navigation (JSON)
- version header handling + 409 mismatch
Not implemented (deferred — see phase_b2_rest_renderer.md for the non-goals):
- partial reloads (
x-inertia-partial-data/-component) - lazy / async / deferred props
- history encryption / clearHistory
Summary
Functions
Server-side asset version. Defaults to the md5 of
priv/static/manifest.json if the file exists, otherwise to the
release hash, otherwise to "dev".
Extract the client's declared asset version, or nil when the
header is absent.
Serialise the page object as the data-page attribute for a
first-load full document. Caller is responsible for the
surrounding HTML; render_root_html/2 is a convenience.
Build the response body for a navigation response. The caller wraps it with JSON encoding + headers.
Build the page object sent to the client (both in full-document and navigation responses).
Mark a response conn as Inertia-aware (sets the x-inertia and
vary headers). Used by both navigation responses and the
mismatch fallback.
Render the root <div> Inertia clients bootstrap from. Not a
full HTML document — callers typically embed this inside a
Phoenix layout.
Classify an incoming request. Returns :navigation when the
Inertia client header is present, :full_document otherwise.
Return true when the client's declared version matches the
server's. First-load requests (no version header) are treated as
matching so we don't bounce them.
Build a 409 version-mismatch response. The caller sends it and halts.
Types
Functions
@spec asset_version() :: String.t()
Server-side asset version. Defaults to the md5 of
priv/static/manifest.json if the file exists, otherwise to the
release hash, otherwise to "dev".
Override per-app:
config :caravela_svelte, :asset_version, fn -> MyApp.asset_version() endThe config value may be a string or a zero-arity function.
@spec client_version(Plug.Conn.t()) :: String.t() | nil
Extract the client's declared asset version, or nil when the
header is absent.
@spec data_page_attribute(page_object()) :: String.t()
Serialise the page object as the data-page attribute for a
first-load full document. Caller is responsible for the
surrounding HTML; render_root_html/2 is a convenience.
@spec page_object(String.t(), map(), String.t(), String.t()) :: page_object()
Build the page object sent to the client (both in full-document and navigation responses).
@spec put_inertia_headers(Plug.Conn.t()) :: Plug.Conn.t()
Mark a response conn as Inertia-aware (sets the x-inertia and
vary headers). Used by both navigation responses and the
mismatch fallback.
@spec render_root_html( page_object(), keyword() ) :: iolist()
Render the root <div> Inertia clients bootstrap from. Not a
full HTML document — callers typically embed this inside a
Phoenix layout.
@spec request_kind(Plug.Conn.t()) :: request_kind()
Classify an incoming request. Returns :navigation when the
Inertia client header is present, :full_document otherwise.
@spec version_matches?(Plug.Conn.t(), String.t()) :: boolean()
Return true when the client's declared version matches the
server's. First-load requests (no version header) are treated as
matching so we don't bounce them.
@spec version_mismatch(Plug.Conn.t(), String.t()) :: Plug.Conn.t()
Build a 409 version-mismatch response. The caller sends it and halts.