(l/script- :js
  {:runtime :basic
   :require [[xt.lang.spec-base :as xt]
             [xt.lang.common-repl :as repl]
             [xt.lang.spec-promise :as promise]
             [xt.substrate :as substrate]
             [xt.substrate.transport-memory :as transport-memory]]})

(fact:global
 {:setup [(l/rt:restart)]
  :teardown [(l/rt:stop)]})

xt.substrate

Frames, pubsub, requests, routers, spaces, pages, and transports.

xt.substrate provides the message and transport foundation used by portable clients, workers, websocket servers, pages, and proxy layers.

1    Motivation

A generated application often needs to send frames, route requests, multiplex spaces, publish events, and move messages through memory, browser, or websocket transports. The substrate layer makes those concerns explicit and reusable.

2    How to use it

Start with frames and spaces, add request/router or pubsub behavior, then choose a transport. Page and proxy utilities are higher-level helpers for browser and worker usage.

3    API

4    Walkthrough

4.1    Nodes and transports

A node is a transport-agnostic runtime. node-create builds one from declarative config, and node? recognizes node values. Transports are created with transport-create and recognized with transport?.

create and identify nodes and transports

^{:refer xt.substrate/node-create :added "4.1"}
(!.js
  (var node (substrate/node-create {"id" "walkthrough-node"}))
  [(substrate/node? node)
   (. node ["id"])])
=> [true "walkthrough-node"]

^{:refer xt.substrate/transport-create :added "4.1"}
(!.js
  (var t (substrate/transport-create "peer-a" {"meta" {"role" "test"}}))
  [(substrate/transport? t)
   (. t ["id"])
   (. t ["meta"] ["role"])])
=> [true "peer-a" "test"]

4.2    Services

Nodes keep a shared service registry. set-service registers a runtime value, get-service retrieves it by id, and get-services returns the whole map.

register and query node services

^{:refer xt.substrate/set-service :added "4.1"}
(!.js
  (var node (substrate/node-create {}))
  (substrate/set-service node "cache" {"scope" "local"})
  [(. (substrate/get-service node "cache") ["scope"])
   (xt/x:obj-keys (substrate/get-services node))])
=> ["local" ["cache"]]

4.3    Request handlers

Handlers answer request frames. They can be declared in node-create or registered at runtime with register-handler, then invoked with request. Built-in util handlers such as @/ping are installed automatically.

invoke local and util handlers

^{:refer xt.substrate/register-handler :added "4.1"}
(notify/wait-on :js
  (var node (substrate/node-create {"handlers"
                                    {"greet"
                                     {"fn" (fn [space args request node]
                                             (return {"hello" (xt/x:get-idx args 0)}))
                                      "meta" {"kind" "request"}}}}))
  (-> (substrate/request node nil "greet" ["world"] {})
      (repl/notify)))
=> {"hello" "world"}

^{:refer xt.substrate.base-util-handlers/ping :added "4.1"}
(notify/wait-on :js
  (-> (substrate/request (substrate/node-create {"id" "ping-node"})
                         nil
                         "@/ping"
                         []
                         {})
      (repl/notify)))
=> {"pong" true "node" "ping-node"}

4.4    Spaces and state

Spaces are named contexts with mutable state. node-create can declare them up front, and handlers can read or update state with get-space-state and update-space-state.

spaces keep state that handlers can update

^{:refer xt.substrate/update-space-state :added "4.1"}
(notify/wait-on :js
  (var node (substrate/node-create
              {"spaces" {"counter" {"state" {"count" 0}}}
               "handlers"
               {"counter/inc"
                {"fn" (fn [space args request node]
                        (substrate/update-space-state
                         node
                         (. space ["id"])
                         (fn [state space node]
                           (return {"count" (+ (or (. state ["count"]) 0) 1)})))
                        (return (substrate/get-space-state node (. space ["id"]))))
                 "meta" {"kind" "request"}}}}))
  (-> (substrate/request node "counter" "counter/inc" [] {})
      (repl/notify)))
=> {"count" 1}

4.5    Transports

Transports move frames between nodes. transport-memory/link-pair wires two nodes together so a client can call a remote handler on a server over an in-memory text wire.

request across an in-memory transport

^{:refer xt.substrate/attach-transport :added "4.1"}
(notify/wait-on :js
  (var server (substrate/node-create
                {"id" "server"
                 "handlers"
                 {"demo/echo"
                  {"fn" (fn [space args request server-node]
                          (return {"space" (. space ["id"])
                                   "server" (. server-node ["id"])
                                   "args" args}))
                   "meta" {"kind" "request"}}}}))
  (var client (substrate/node-create {"id" "client"}))
  (-> (transport-memory/link-pair server client)
      (promise/x:promise-then
       (fn [_]
         (return (substrate/request client "room/a" "demo/echo" ["ping"] nil))))
      (repl/notify)))
=> {"space" "room/a" "server" "server" "args" ["ping"]}

4.6    Pubsub and triggers

Stream frames fan out to subscribers. register-trigger installs a local callback for a signal, and publish delivers a stream frame so the trigger can react.

publish invokes a trigger that updates space state

^{:refer xt.substrate/publish :added "4.1"}
(notify/wait-on :js
  (var node (substrate/node-create
              {"spaces" {"room/a" {"state" {"count" 0}}}
               "triggers"
               {"event/inc"
                {"fn" (fn [space stream node]
                        (var current (substrate/get-space-state node (. space ["id"])))
                        (substrate/set-space-state
                         node
                         (. space ["id"])
                         {"count" (+ (or (. current ["count"]) 0) 1)})
                        (return true))
                 "meta" {"kind" "stream"}}}}))
  (-> (substrate/publish node "room/a" "event/inc" {} nil)
      (promise/x:promise-then
       (fn [_]
         (return (substrate/get-space-state node "room/a"))))
      (repl/notify)))
=> {"count" 1}