CaravelaSvelte.Protocol (CaravelaSvelte v0.1.0)

Copy Markdown View Source

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

page_object()

@type page_object() :: %{
  component: String.t(),
  props: map(),
  url: String.t(),
  version: String.t()
}

request_kind()

@type request_kind() :: :full_document | :navigation

Functions

asset_version()

@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() end

The config value may be a string or a zero-arity function.

client_version(conn)

@spec client_version(Plug.Conn.t()) :: String.t() | nil

Extract the client's declared asset version, or nil when the header is absent.

data_page_attribute(page_object)

@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.

page_object(component, props, url, version)

@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).

put_inertia_headers(conn)

@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.

render_root_html(page_object, opts \\ [])

@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.

request_kind(conn)

@spec request_kind(Plug.Conn.t()) :: request_kind()

Classify an incoming request. Returns :navigation when the Inertia client header is present, :full_document otherwise.

version_matches?(conn, server_version)

@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.

version_mismatch(conn, location)

@spec version_mismatch(Plug.Conn.t(), String.t()) :: Plug.Conn.t()

Build a 409 version-mismatch response. The caller sends it and halts.