Skuld.PageMachine.Contract (skuld_concurrency v0.46.0)

View Source

Typed protocol contract for PageMachine spindle ↔ LiveView communication.

use Skuld.PageMachine.Contract imports defspindle, defevent, and defyield macros to declare the typed interface between a page's spindles and its LiveView.

Each declaration is validated at compile time. The protocol module acts as the single source of truth for event routing and yield shapes.

Usage

defmodule MyApp.StoreProtocol do
  use Skuld.PageMachine.Contract

  defspindle Products do
    defevent "search", SearchEvent, params: [query: String.t()]
    defevent "filter", FilterEvent, params: [filters: map()]
    defevent "buy", BuyEvent, params: [product: Product.t()]

    defyield :browsing
    defyield results(products: [Product.t()], total: integer())
  end

  defspindle Checkout do
    defevent "submit_shipping", ShippingEvent, params: [shipping: map()]
    defevent "submit_payment", PaymentEvent, params: [payment: map()]

    defyield :shipping
    defyield :payment
  end
end

Events with an explicit struct name generate a typed struct module under the spindle module (StoreProtocol.Products.SearchEvent). The auto-generated handle_event wraps params into the struct before resuming the spindle, giving typed receive-side pattern matching.

Events without a struct name pass {event_name, params} as the resume value (LiveView convention).

The spindle key is the module atom (StoreProtocol.Products) — the same module where yield functions are generated:

StoreProtocol.Products.results(products: prods, total: n)
StoreProtocol.Checkout.shipping()

Integration with PageMachine

use Skuld.PageMachine,
  protocol: MyApp.StoreProtocol,
  on_yield: &handle_yield/3

The :protocol option auto-generates handle_event/3 clauses from the protocol's event declarations.

Summary

Functions

Declare a LiveView event routed to the current spindle.

Declare a fire-and-forget notification from the current spindle.

Open a spindle block.

Declare a yield from the current spindle to the LiveView.

Functions

defevent(event_name)

(macro)

Declare a LiveView event routed to the current spindle.

Without params, the spindle receives {event_name, params} (standard LiveView convention).

With params and a struct name, a typed event struct is generated and the spindle receives it directly — enabling typed receive-side pattern matching.

Syntax

defevent "event_name"
defevent "event_name", StructName, params: [field: type(), ...]

defevent(event_name, opts)

(macro)

defevent(event_name, struct_name, opts)

(macro)

defnotify(tag)

(macro)

Declare a fire-and-forget notification from the current spindle.

Same syntax as defyield, but generates a function that calls FiberYield.notify/1 instead of Yield.yield/1 — the spindle surfaces the value to the caller without pausing.

Syntax

defnotify :tag
defnotify tag(key: type(), ...)

defspindle(name, list)

(macro)

Open a spindle block.

Inside the block, defevent and defyield infer the spindle from the enclosing defspindle context — no need to repeat the spindle key.

The spindle name becomes the module atom under the protocol module (e.g., defspindle ProductsStoreProtocol.Products), which is both the spindle key for event routing and the module where typed yield functions are generated.

Example

defspindle Products do
  defevent "search", SearchEvent, params: [query: String.t()]
  defyield :browsing
  defyield results(products: [Product.t()], total: integer())
end

defyield(tag)

(macro)

Declare a yield from the current spindle to the LiveView.

Syntax

defyield :tag
defyield tag(key: type(), ...)