From c24d71177807c93da7bbf49523d93b2fb10e3834 Mon Sep 17 00:00:00 2001 From: iko Date: Fri, 7 Jul 2023 21:56:11 +0300 Subject: [PATCH] Added some docs --- docs/sink.md | 105 ++++++++++++++++++++++++++++++++++++ example/urbit/lib/sink.hoon | 1 - src/Ur/Constructor.elm | 4 +- src/Ur/Deconstructor.elm | 6 ++- src/Ur/Phonemic.elm | 2 +- src/Ur/Sub.elm | 6 +-- src/Ur/Uw.elm | 2 +- 7 files changed, 116 insertions(+), 10 deletions(-) create mode 100644 docs/sink.md diff --git a/docs/sink.md b/docs/sink.md new file mode 100644 index 0000000..617179c --- /dev/null +++ b/docs/sink.md @@ -0,0 +1,105 @@ +# %sink + +%sink is a state replication system between your %gall agent and your Elm airlock. + +%sink allows you to avoid having to manually write subscriptions which send semantic diffs between your %gall agent and the frontend. + +## How it works + +%sink consists of two parts: +1. The %sink library you import into your %gall agent and call appropriate functions at the right points in your agent lifecycle. More on this in a bit. +2. The `Ur.Sub.sink` urbit subscription which reconciles the state and hands you the latest version of %gall agent state that you can then `Deconstruct` into Elm data structures. + +The whole system works by saving the previous version your %gall agent state and diffing the raw nouns that compose your old state and your new state. +On first initialization of the %sink system the whole state you supplied is sent to the frontend. Any subsequent changes are sent as a diff between your old state and your new state. + +## How to use it + +### The %gall agent part + +#### Copying library files + +In order to use %sink you have to copy the following two files into your agent desk into the `lib` directory: +1. [example/urbit/lib/noun-diff.hoon](../example/urbit/lib/noun-diff.hoon) -- contains the algorithm for diffing raw nouns. +2. [example/urbit/lib/sink.hoon](../example/urbit/lib/sink.hoon) -- contains the logic of syncing the state between your agent and your frontend. + +#### Adding sink points into your agent + +First you need to import the %sink library into your agent: + +```hoon +/+ *sink +``` + +Next you need to initialize the sink. Put the following declaration somewhere before your agent door: + +```hoon +=/ snik + :: + :: replace /sync with whatever path you want to use for syncing your state. + %+ sink ~[/sync] + :: + :: This part is a gate that extracts the state you want to sync from your agent state. + :: This allows you to sync only the part of your state that you need, saving resources. + :: If you want to sync the whole state then just pass identity: `|=(x=versioned-state x)` + :: + :: versioned-state should be replaced with whatever the type of your application state is. + |=(stat=versioned-state !!) +:: +:: Next you initialize your sink with your initial agent state. +=/ sink (snik state) +``` + +You can have multiple sinks in the same application to sync different parts of your state on different paths. + +Don't forget to reinitialize your `sink` when your restore your agent state in the `++on-load` arm: + +```hoon +++ on-load + |= old-vase=vase + ^- (quip card _this) + =/ state !<(versioned-state old-vase) + :: + :: the `sink (snik state)` is the important bit. + `this(state state, sink (snik state)) +``` + +Lastly, you need to send sink updates whenever you change your state. Most likely this will be in your `++on-poke` arm: + +```hoon +:: +:: This line generates a `card` that you need to pass to arvo and updates +:: the `sink` to reference the latest state of your agent. +=^ card sink (sync:sink state) +``` + +You can look at the [journal.hoon](../example/urbit/app/journal.hoon) for a full example. + +#### Adding sink the the frontend + +For your frontend to recieve %sink updates you need to pass the result of calling `Ur.Sub.sink` to the `urbitSubscriptions` field of `Ur.Run.application` or similar function from `Ur.Run`: + +```elm +main : Ur.Run.Program Model Msg +main = + Ur.Run.application + { + -- ... + urbitSubscriptions = + Ur.Sub.sink + { ship = "~zod" + , app = "journal" + , path = [ "sync" ] + , deconstructor = + D.list (D.cell D.bigint D.cord |> D.map (\a b -> ( a, b ))) + |> D.map GotListings + } + -- ... + } +``` + +In the `deconstructor` field you specify a `Deconstructor` the deconstructs _the whole_ state that is being synced form the %gall agent. + +You do not have to deal with diffs. It is handled automatically + +You can look at [example/src/Sink.elm](../exmaple/src/Sink.elm) for a full example. diff --git a/example/urbit/lib/sink.hoon b/example/urbit/lib/sink.hoon index 14b0b7c..af9bec2 100644 --- a/example/urbit/lib/sink.hoon +++ b/example/urbit/lib/sink.hoon @@ -16,7 +16,6 @@ =/ dif %+ diff:noun-diff (extract ^stat) (extract stat) :- - ~& [%give %fact pats %noun !>(^-((clog) [%drain dif]))] [%give %fact pats %noun !>(^-((clog) [%drain dif]))] ..sync(stat stat) ++ paths pats diff --git a/src/Ur/Constructor.elm b/src/Ur/Constructor.elm index 6ca944f..9640802 100644 --- a/src/Ur/Constructor.elm +++ b/src/Ur/Constructor.elm @@ -89,7 +89,7 @@ signedInt i = ) -{-| Constructs an `Atom` from `Bytes` +{-| Constructs an `Atom` from `Bytes`. -} bytes : Bytes -> Noun bytes = @@ -103,7 +103,7 @@ cord s = Atom (BE.encode (BE.string s)) -{-| Constructs a [`tape`](https://developers.urbit.org/reference/glossary/tape) from a `String` +{-| Constructs a [`tape`](https://developers.urbit.org/reference/glossary/tape) from a `String`. -} tape : String -> Noun tape s = diff --git a/src/Ur/Deconstructor.elm b/src/Ur/Deconstructor.elm index 1216400..c13221d 100644 --- a/src/Ur/Deconstructor.elm +++ b/src/Ur/Deconstructor.elm @@ -102,7 +102,7 @@ runBytes (Deconstructor f) bs = {-| Asserts that the value at the current position should be exactly equal to the second argument. -The first argument is a `Deconstructor` for the gven tyoe. +The first argument is a `Deconstructor` for the given type. The second argument is the value to compare with. @@ -370,7 +370,9 @@ cell (Deconstructor l) (Deconstructor r) = ) -{-| -} +{-| "Lazily" applies a deconstructor. +This is useful when you are defining a recursive `Deconstructor` which needs to call itself. +-} lazy : (() -> Deconstructor a b) -> Deconstructor a b lazy f = Deconstructor diff --git a/src/Ur/Phonemic.elm b/src/Ur/Phonemic.elm index a60901d..1b5691f 100644 --- a/src/Ur/Phonemic.elm +++ b/src/Ur/Phonemic.elm @@ -13,7 +13,7 @@ import Urbit.Encoding.Atom exposing (toBigInt) import Urbit.Encoding.Phonemic exposing (..) -{-| Converts a ship name like `~zod` into an Atom. +{-| Converts a ship name like `~zod` into an `Atom`. -} fromString : Ship -> Atom fromString s = diff --git a/src/Ur/Sub.elm b/src/Ur/Sub.elm index 4f1007a..5d29948 100644 --- a/src/Ur/Sub.elm +++ b/src/Ur/Sub.elm @@ -3,7 +3,7 @@ module Ur.Sub exposing , sink ) -{-| This module is conceptually similar to `Platform.Sub`, but also you to subscribe to Urbit channels. +{-| This module is conceptually similar to `Platform.Sub`, but allows you to subscribe to Urbit channels. @docs Sub, subscribe, none, batch @@ -14,7 +14,7 @@ import Ur.Deconstructor as D import Ur.Sub.Internal -{-| Like `Sub` from `Platform.Sub`, but for Urbit subscriptions. +{-| Like `Sub` from `Platform.Sub` but for Urbit subscriptions. -} type alias Sub msg = Ur.Sub.Internal.Sub msg @@ -39,7 +39,7 @@ subscribe { ship, app, path, deconstructor } = |> Ur.Sub.Internal.Sub -{-| Create a Sink subscription. +{-| Creates a %sink subscription. -} sink : { ship : String, app : String, path : List String, deconstructor : D.Deconstructor (msg -> msg) msg } -> Sub msg sink { ship, app, path, deconstructor } = diff --git a/src/Ur/Uw.elm b/src/Ur/Uw.elm index 3fa82c6..780fa11 100644 --- a/src/Ur/Uw.elm +++ b/src/Ur/Uw.elm @@ -1,6 +1,6 @@ module Ur.Uw exposing (decode, encode) -{-| This module works with Urbit base-64 encoded strings. +{-| This module works with Urbit base-64 encoded strings aka `@uw`. @docs decode, encode