cursery.nixDocs

Host IO

Apps and the JavaScript host can talk both ways: Nix calls JavaScript through effects, and JavaScript dispatches actions to the app. Both are optional.

Define effects#

Effects are functions, by name. They can return promises:

src/effects.js
export default {
  copy: (text) => navigator.clipboard.writeText(text),
  paste: () => navigator.clipboard.readText(),
};

Name the module in a page's client, in the Vite plugin's pages:

vite.config.js
client: { type: "nix", path: "./src/app", effects: "./src/effects.js" },

Or pass them to mount, when embedding an app:

main.js
import app from "./app?cursery:app";
import effects from "./effects.js";

app.mount(document.getElementById("app"), { effects });

Call effects#

Handlers call an effect with io.call, given its name and input. In io.pipe, each step gets the output of the last:

src/app/default.nix
let
  inherit (import <web/dom>) html;
  io = import <web/io>;
in
{
  init = "";
  update = state: action: action.payload;
  render = text: [
    (html.button { on.click = event: io.call "copy" "Hello"; } "Copy")
    (html.button {
      on.click = event: io.pipe null [
        (_: io.call "paste" null)
        # What the promise resolved to.
        (text: io.dispatch "pasted" text)
      ];
    } "Paste")
    (html.p text)
  ];
}

Inputs and outputs must be JSON-compatible.

Dispatch from JavaScript#

dispatch sends an action to the app mounted into an element, as io.dispatch does from a handler:

main.js
import { dispatch } from "cursery";

import app from "./app?cursery:app";

const container = document.getElementById("app");
app.mount(container);

setInterval(() => {
  // The action's type and payload. The app renders before this returns.
  dispatch(container, "tick", Date.now());
}, 1000);

The payload must be JSON-compatible.