Vivo is a framework for building connected applications.
Vivo is in an experimental and rapidly-changing state. Use at your own risk.
In deps.edn:
{:deps {com.dept24c/vivo {:git/url "https://github.com/Dept24c/vivo.git"
:sha "xxx"}}}
Vivo enables easy access to both local and system state. In the Vivo context, local state is local to the client and is not shared. System state is shared across all clients in the system. When any local or system state changes, any subscribed clients are automatically updated with the current state. This makes creating live, connected applications much easier.
State paths are a sequence of keys that index into the state data structure.
These keys can be keywords, strings, or integers, depending on the specific
state data structure. Keyword keys may or may not have namespaces. A path
must start with either :sys
(system state) or :local
(local state).
Some examples:
[:local :user-id]
[:local :score-info :high-score]
[:sys :users "my-user-id" :user/name]
[:sys :msgs 0]
For paths referring to sequence data types, the path can use either front-relative indexing, .e.g.:
[:sys :msgs 0]
- Refers to the first msg in the list[:sys :msgs 1]
- Refers to the second msg in the list
or end-relative indexing, e.g.:
[:sys :msgs -1]
- Refers to the last msg in the list[:sys :msgs -2]
- Refers to the penultimate msg in the list
Subscription maps are used to specify a subscription to Vivo state. Here is an example subscription map:
{user-id [:local :user/id]
user-name [:sys :users user-id :user/name]
avatar-url [:sys :users user-id :user/avatar-url]}
A subscription map's keys are Clojure symbols and the values are
paths. The paths are used to index into Vivo state.
Vivo then binds the value of the state at
the specified path to the appropriate symbol.
For example, in the subscription map above, the user-id
symbol will be
bound to the value found in the Vivo state at [:local :user/id]
.
Note that symbols may be used in a path. If a symbol is used in a path,
it must be defined by another map entry. For example, in the subscription
map above, the user-id
symbol is used in both the user-name
and avatar-url
paths.
Order is not important in the map; symbols can be defined in any order.
An update command is a map with three keys:
:path
: The path on which the update command will operate; e.g.[:local :page]
:op
: One of the supported update operations: (:set
,:remove
,:insert-before
,:insert-after
,:plus
,:minus
,:multiply
,:divide
,:mod
):arg
: The command's argument
For example:
In order to work well in browsers, the Vivo API is asynchronous. Most Vivo functions have three forms:
- A simple form:
(update-state! vc update-commands)
- Return value is ignored. - A callback form:
(update-state! vc update-commands cb)
- Return value is provided by calling the given callbackcb
. - A channel form:
(<update-state! vc update-commands)
- Returns a core.async channel, which will yield the function's return value.
(vivo-client)
(vivo-client opts)
Creates a vivo client with the given options, if any. An application should have exactly one Vivo client, which must be passed to all Vivo functions and components.
opts
: A map of options. Supported options::get-server-url
: A zero-arity function which returns a URL for the Vivo server. Can return the URL or a channel which yields the URL. Required if using:sys
state.:initial-local-state
: Initial value of the:local
state. Defaults tonil
.:log-error
: A function of one argument (a string) to log errors. Defaults toprintln
.:log-info
: A function of one argument (a string) to log information. Defaults toprintln
.:state-cache-size
: Size of the state cache, measured in number of items. Defaults to 100.:sys-state-schema
: The Lancaster schema for the:sys
state. Required if using:sys
state.:sys-state-source
: A map describing the source branch for the:sys
state. Defaults to{:temp-branch/db-id nil}
. Must be one of:{:branch branch-name}
Sets the source to the givenbranch-name
.{:temp-branch/db-id nil}
Creates an empty temporary branch and sets it as the source. The branch is deleted when the Vivo client disconnects from the server. Useful for testing and staging environments.{:temp-branch/db-id db-id}
Creates a temporary branch from the givendb-id
and sets it as the source. The branch is deleted when the Vivo client disconnects from the server. Useful for testing and staging environments.
The created Vivo client.
(defonce vc (vivo/vivo-client))
(update-state! vc update-commands)
(update-state! vc update-commands cb)
(<update-state! vc update-commands)
Updates the state by executing the given sequence of update commands. The commands are executed in order. Atomicity is guraranteed. Either all the commands will succeed or none will.
vc
: The Vivo client instanceupdate-commands
: A sequence of update commmands.
true
or false
, indicating success or failure.
This sets the local page state to :home
:
(vivo/update-state! vc [{:path [:local :page]
:op :set
:arg :home}])
- set-state!
Since
:set
is the most common update command operation, Vivo provides a convenience fn for it:
(vivo/set-state! [:local :page] :home)
is shorthand for:
(vivo/update-state! vc [{:path [:local :page]
:op :set
:arg :home}])
(set-state! vc path arg)
(set-state! vc path arg cb)
(<set-state! vc path arg)
Sets the state at the given path to the given arg.
vc
: The Vivo client instancepath
: The state patharg
: The value to set
true
or false
, indicating success or failure.
(vivo/set-state! [:local :page] :home)
This is shorthand for:
(vivo/update-state! vc [{:path [:local :page]
:op :set
:arg :home}])
(def-component component-name & args)
Defines a Vivo React component.
constructor-args
: A vector of constructor arguments. The first argumentsub-map
: Optional. A subscription map. must be a parameter namedvc
(the Vivo client).body
: The body of the component. When evaluated, the body should return a Hiccup data structure.
The defined Vivo component
(def-component main
[vc]
{page [:local :page]}
(case page
:home (home/home vc)
:details (details/details vc)
:not-found (not-found vc)))
(subscribe! vc sub-map cur-state update-fn)
Creates a Vivo subscription. Note that it is rare to call this function directly. Prefer the higher-level def-component or use-vivo-state, which handle subscribing and unsubscribing directly.
vc
: The Vivo client instancesub-map
: A subscription mapupdate-fn
: A function that will be called when the subscribed state changes. The function will recieve a single map argument. The map's keys will be the symbols from the subscription map, and the map's values will be the pieces of state indicated by the paths in the subscription map.
A subscription id (an integer) that can be used in unsubscribe! calls.
TODO
(unsubscribe! vc sub-id)
Deletes a Vivo subscription. Note that it is rare to call this function directly. Prefer the higher-level def-component or use-vivo-state, which handle subscribing and unsubscribing directly.
vc
: The Vivo client instancesub-id
: The subscription id (an integer) returned from the subscribe! call.
(vivo/unsubscribe 789)
(use-vivo-state vc sub-map)
React custom hook for using Vivo state. Automatically re-renders the component when any of the subscribed state changes. Returns a state map, which is a map with the same keys as the subscription map, but with state values.
The React Rules of Hooks apply.
vc
: The Vivo client instancesub-map
: A subscription map
(vivo/unsubscribe 789)
(shutdown! vc)
Shut down the Vivo client, closing its connection to the server. Useful in testing and code reloading to avoid having multiple connections to the server.
vc
: The Vivo client instancesub-map
: A subscription map
(vivo/unsubscribe 789)
-
Unit tests
- To run the clj unit tests:
bin/kaocha unit
- To run the cljs/node unit tests:
bin/kaocha unit-node
- To run the cljs/browser unit tests:
bin/kaocha unit-browser
- To run the clj unit tests:
-
Integration tests
- First, start the test server:
bin/run-test-server
- To run the clj integration tests:
bin/kaocha integration
- To run the cljs/node integration tests:
bin/kaocha integration-node
- To run the cljs/browser integration tests:
bin/kaocha integration-browser
- First, start the test server:
Copyright Department 24C, LLC
Apache and the Apache logos are trademarks of The Apache Software Foundation.
Distributed under the Apache Software License, Version 2.0 http://www.apache.org/licenses/LICENSE-2.0.txt