Dstar (dstar v0.3.0)

Copy Markdown View Source

Datastar SSE helpers for Elixir/Phoenix.

A trivially small library: SSE connection management, signal patching, DOM element patching. That's it.

Quick Example

def increment(conn, _params) do
  signals = Dstar.read_signals(conn)
  count = signals["count"] || 0

  conn
  |> Dstar.start()
  |> Dstar.patch_signals(%{count: count + 1})
end

Modules

Summary

Functions

Appends an element as the last child of a container, via SSE.

Checks if an SSE connection is still open.

Logs a message to the browser console via SSE.

Generates a @delete(...) expression for Datastar attributes. See Dstar.post/2 for usage — same API, different HTTP verb.

Executes JavaScript on the client by appending a script tag via SSE.

Safely fetches Datastar signals and returns the updated Plug conn.

Generates a @get(...) expression for Datastar attributes. See Dstar.post/2 for usage — same API, different HTTP verb.

Signals that a collection changed, so each tab reloads it with its own filter/sort/page signals.

Generates a @patch(...) expression for Datastar attributes. See Dstar.post/2 for usage — same API, different HTTP verb.

Patches a DOM element on the client via SSE.

Patches signals on the client via SSE.

Generates a @post(...) expression for Datastar attributes.

Generates a @put(...) expression for Datastar attributes. See Dstar.post/2 for usage — same API, different HTTP verb.

Reads already-fetched Datastar signals as a map.

Redirects the client to the given URL via JavaScript.

Removes DOM elements on the client via SSE.

Removes signals from the client by setting them to nil.

Starts an SSE connection on the given Plug conn.

Starts an SSE stream with per-tab deduplication.

Morphs the element whose DOM id matches the root of html, via SSE.

Functions

append_elements(conn, html, container, opts \\ [])

Appends an element as the last child of a container, via SSE.

Example

conn |> Dstar.append_elements(post_row(%{post: post}), "#posts")

check_connection(conn)

Checks if an SSE connection is still open.

Returns {:ok, conn} if open, {:error, conn} if closed. Useful in streaming loops to detect disconnections.

Example

case Dstar.check_connection(conn) do
  {:ok, conn} -> stream_loop(conn)
  {:error, _} -> :ok
end

console_log(conn, message, opts \\ [])

Logs a message to the browser console via SSE.

Example

conn |> Dstar.console_log("Debug info")

delete(module_or_name, name_or_opts \\ [])

Generates a @delete(...) expression for Datastar attributes. See Dstar.post/2 for usage — same API, different HTTP verb.

delete(module, event_name, opts)

See Dstar.Actions.delete/3.

event(module_or_name, name_or_opts)

This function is deprecated. Use Dstar.post/2 (or get/put/patch/delete) instead.

See Dstar.Actions.event/2.

event(module, event_name, opts)

See Dstar.Actions.event/3.

execute_script(conn, script, opts \\ [])

Executes JavaScript on the client by appending a script tag via SSE.

Example

conn |> Dstar.execute_script("alert('Hello!')")

fetch_signals(conn, opts \\ [])

Safely fetches Datastar signals and returns the updated Plug conn.

Use this for plain Plug conns whose body has not already been parsed. It accepts JSON objects only and bounds raw/query payloads with :max_bytes.

case Dstar.fetch_signals(conn, max_bytes: 64_000) do
  {:ok, signals, conn} -> handle(conn, signals)
  {:error, reason, conn} -> Dstar.Signals.send_error(conn, reason)
end

get(module_or_name, name_or_opts \\ [])

Generates a @get(...) expression for Datastar attributes. See Dstar.post/2 for usage — same API, different HTTP verb.

get(module, event_name, opts)

See Dstar.Actions.get/3.

nudge(conn, key, opts \\ [])

Signals that a collection changed, so each tab reloads it with its own filter/sort/page signals.

Example

conn |> Dstar.nudge("posts")

patch(module_or_name, name_or_opts \\ [])

Generates a @patch(...) expression for Datastar attributes. See Dstar.post/2 for usage — same API, different HTTP verb.

patch(module, event_name, opts)

See Dstar.Actions.patch/3.

patch_elements(conn, html, opts)

Patches a DOM element on the client via SSE.

Takes a :selector, or — with no selector — targets by the id on each top-level element of html. See Dstar.Elements.patch/3 for all options.

Example

conn |> Dstar.patch_elements("<span id=\"count\">42</span>", selector: "#count")

patch_signals(conn, signals, opts \\ [])

Patches signals on the client via SSE.

Example

conn |> Dstar.patch_signals(%{count: 42})

post(module_or_name, name_or_opts \\ [])

Generates a @post(...) expression for Datastar attributes.

Examples

Dstar.post(MyAppWeb.CounterHandler, "increment")
# => ~s|@post("/ds/my_app_web-counter_handler/increment")|

expression = Dstar.post("increment")
# The `$_dstar_module` signal is percent-encoded at runtime.
String.contains?(expression, "encodeURIComponent")
# => true

Event and module values are percent-encoded as one route segment. Exact empty and dot segments are rejected. In the module form, :prefix must be a local absolute application path beginning with one /; :module on the dynamic form is a literal module override, not JavaScript or a signal name.

post(module, event_name, opts)

See Dstar.Actions.post/3.

put(module_or_name, name_or_opts \\ [])

Generates a @put(...) expression for Datastar attributes. See Dstar.post/2 for usage — same API, different HTTP verb.

put(module, event_name, opts)

See Dstar.Actions.put/3.

read_signals(conn)

Reads already-fetched Datastar signals as a map.

Plug.Parsers/Phoenix body params and already-fetched GET/DELETE query params work here. For an unfetched raw body, use fetch_signals/2; this convenience function cannot return the adapter-updated conn.

redirect(conn, url, opts \\ [])

Redirects the client to the given URL via JavaScript.

Default destination policy is same-origin and path-absolute (/workspaces, ?x=1, #frag). Off-origin http/https requires external: true or allow: ["host"]. See Dstar.Scripts.redirect/3.

Example

conn |> Dstar.redirect("/workspaces")
conn |> Dstar.redirect("https://ok.example/docs", external: true)

remove_elements(conn, selector, opts \\ [])

Removes DOM elements on the client via SSE.

Example

conn |> Dstar.remove_elements("#old-item")

remove_signals(conn, paths, opts \\ [])

Removes signals from the client by setting them to nil.

Accepts a single path string or list of dot-notated paths.

Examples

conn |> Dstar.remove_signals("user.profile.theme")
conn |> Dstar.remove_signals(["user.name", "user.email"])

start(conn)

Starts an SSE connection on the given Plug conn.

Sets content type to text/event-stream, disables caching, and initiates a chunked response. Returns the conn.

Example

conn = Dstar.start(conn)

start_stream(conn, scope_key)

Starts an SSE stream with per-tab deduplication.

Requires Dstar.Utility.StreamRegistry in your supervision tree and a tabId signal in your root layout. A usable tabId is claimed atomically before SSE starts. Claim failure returns a halted, plain-text 503 conn; it never starts an undeduplicated keyed stream. Missing/invalid tabId is the intentional unkeyed fallback.

Prefer Dstar.Stream.open/2 with key: in plain controllers — it returns {:ok, conn} / {:error, conn} and Dstar.Stream.run/2 handles takeover and release. Hand-rolled loops that own their receive must check conn.halted before subscribing/looping and call Dstar.Utility.StreamRegistry.release(conn) during teardown. See that module's docs for setup. Pass max_bytes: n as a third argument to bound raw/query signal input per call.

Example

conn = Dstar.start_stream(conn, current_user.id)

start_stream(conn, scope_key, opts)

See Dstar.Utility.StreamRegistry.start_stream/3.

upsert_elements(conn, html, opts \\ [])

Morphs the element whose DOM id matches the root of html, via SSE.

Dropped by the client if this tab has no element with that id — see Dstar.Elements.upsert/3.

Example

conn |> Dstar.upsert_elements(post_row(%{post: post}))