on master help sign in
Added some docs (#7)

* Added some docs * Simplified %sink (#8) * Simplified %sink * Formatting * Added docs for new sink * fixed padding * fixed padding * fixed padding * fixed link

commit1aa86b4da7557a3ce8039c83da32fe49edfa1021 authoriko <mail@iko.soy> dateSun 9 Jul 2023, 20:11 (3 years ago) parent5391624
7 files changed+112−13
docs/sink.md+101 example/src/Sink.elm−4 src/Ur/Constructor.elm+2−2 src/Ur/Deconstructor.elm+4−2 src/Ur/Phonemic.elm+1−1 src/Ur/Sub.elm+3−3 src/Ur/Uw.elm+1−1
docs/sink.mdnew file+101
@@ -0,0 +1,101 @@
1+# %sink
2+
3+%sink is a state replication system between your %gall agent and your Elm airlock.
4+
5+%sink allows you to avoid having to manually write subscriptions which send semantic diffs between your %gall agent and the frontend.
6+
7+## How it works
8+
9+%sink consists of two parts:
10+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.
11+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.
12+
13+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.
14+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.
15+
16+## How to use it
17+
18+### The %gall agent part
19+
20+#### Copying library files
21+
22+In order to use %sink you have to copy the following two files into your agent desk into the `lib` directory:
23+1. [example/urbit/lib/noun-diff.hoon](../example/urbit/lib/noun-diff.hoon) -- contains the algorithm for diffing raw nouns.
24+2. [example/urbit/lib/sink.hoon](../example/urbit/lib/sink.hoon) -- contains the logic of syncing the state between your agent and your frontend.
25+
26+#### Adding sync points into your agent
27+
28+First you need to import the %sink library into your agent:
29+
30+```hoon
31+/+ *sink
32+```
33+
34+Next you need to initialize the sink. Put the following declaration somewhere before your agent door:
35+
36+```hoon
37+::
38+:: A deferred expression of the state you want to sync to the frontend.
39+=* entries (tap:j-orm journal.stat)
40+::
41+:: Replace /sync with whatever path you want to use for syncing your state.
42+=/ snik (sink ~[/sync])
43+::
44+:: Next you initialize your sink with your initial agent state.
45+=/ sink (snik entries)
46+```
47+
48+You can have multiple sinks in the same application to sync different parts of your state on different paths.
49+
50+Don't forget to reinitialize your `sink` when your restore your agent state in the `++on-load` arm:
51+
52+```hoon
53+++ on-load
54+ |= old-vase=vase
55+ ^- (quip card _this)
56+ =/ state !<(versioned-state old-vase)
57+ ::
58+ :: the `sink (snik entries)` is the important bit.
59+ `this(state state, sink (snik entries))
60+```
61+
62+Lastly, you need to send sink updates whenever you change your state. Most likely this will be in your `++on-poke` arm:
63+
64+```hoon
65+::
66+:: This line generates a `card` that you need to pass to arvo and updates
67+:: the `sink` to reference the latest state of your agent.
68+=^ card sink (sync:sink entries)
69+```
70+
71+You can look at the [journal.hoon](../example/urbit/app/journal.hoon) for a full example.
72+
73+#### Adding sink the the frontend
74+
75+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`:
76+
77+```elm
78+main : Ur.Run.Program Model Msg
79+main =
80+ Ur.Run.application
81+ {
82+ -- ...
83+ urbitSubscriptions =
84+ -- ...
85+ Ur.Sub.sink
86+ { ship = ship
87+ , app = "journal"
88+ , path = [ "sync" ]
89+ , deconstructor =
90+ D.list (D.cell D.bigint D.cord |> D.map (\a b -> ( a, b )))
91+ |> D.map GotListings
92+ }
93+ -- ...
94+ }
95+```
96+
97+In the `deconstructor` field you specify a `Deconstructor` the deconstructs _the whole_ state that is being synced form the %gall agent.
98+
99+You do not have to deal with diffs. It is handled automatically
100+
101+You can look at [example/src/Sink.elm](../example/src/Sink.elm) for a full example.
example/src/Sink.elm−4
@@ -80,7 +80,6 @@ type alias Model =
8080
8181type Msg
8282 = Noop
83− | GotSink Noun
8483 | Error String
8584 | GotListings (List ( BigInt, String ))
8685 | UpdateNewEntry String
@@ -96,9 +95,6 @@ update msg model =
9695 Noop ->
9796 ( model, Ur.Cmd.none )
9897
99− GotSink _ ->
100− ( model, Ur.Cmd.none )
101−
10298 Error err ->
10399 ( { model | error = err }, Ur.Cmd.none )
104100
src/Ur/Constructor.elm+2−2
@@ -89,7 +89,7 @@ signedInt i =
8989 )
9090
9191
92−{-| Constructs an `Atom` from `Bytes`
92+{-| Constructs an `Atom` from `Bytes`.
9393-}
9494bytes : Bytes -> Noun
9595bytes =
@@ -103,7 +103,7 @@ cord s =
103103 Atom (BE.encode (BE.string s))
104104
105105
106−{-| Constructs a [`tape`](https://developers.urbit.org/reference/glossary/tape) from a `String`
106+{-| Constructs a [`tape`](https://developers.urbit.org/reference/glossary/tape) from a `String`.
107107-}
108108tape : String -> Noun
109109tape s =
src/Ur/Deconstructor.elm+4−2
@@ -102,7 +102,7 @@ runBytes (Deconstructor f) bs =
102102
103103{-| Asserts that the value at the current position should be exactly equal to the second argument.
104104
105−The first argument is a `Deconstructor` for the gven tyoe.
105+The first argument is a `Deconstructor` for the given type.
106106
107107The second argument is the value to compare with.
108108
@@ -370,7 +370,9 @@ cell (Deconstructor l) (Deconstructor r) =
370370 )
371371
372372
373−{-| -}
373+{-| "Lazily" applies a deconstructor.
374+This is useful when you are defining a recursive `Deconstructor` which needs to call itself.
375+-}
374376lazy : (() -> Deconstructor a b) -> Deconstructor a b
375377lazy f =
376378 Deconstructor
src/Ur/Phonemic.elm+1−1
@@ -13,7 +13,7 @@ import Urbit.Encoding.Atom exposing (toBigInt)
1313import Urbit.Encoding.Phonemic exposing (..)
1414
1515
16−{-| Converts a ship name like `~zod` into an Atom.
16+{-| Converts a ship name like `~zod` into an `Atom`.
1717-}
1818fromString : Ship -> Atom
1919fromString s =
src/Ur/Sub.elm+3−3
@@ -3,7 +3,7 @@ module Ur.Sub exposing
33 , sink
44 )
55
6−{-| This module is conceptually similar to `Platform.Sub`, but also you to subscribe to Urbit channels.
6+{-| This module is conceptually similar to `Platform.Sub`, but allows you to subscribe to Urbit channels.
77
88@docs Sub, subscribe, none, batch
99
@@ -14,7 +14,7 @@ import Ur.Deconstructor as D
1414import Ur.Sub.Internal
1515
1616
17−{-| Like `Sub` from `Platform.Sub`, but for Urbit subscriptions.
17+{-| Like `Sub` from `Platform.Sub` but for Urbit subscriptions.
1818-}
1919type alias Sub msg =
2020 Ur.Sub.Internal.Sub msg
@@ -39,7 +39,7 @@ subscribe { ship, app, path, deconstructor } =
3939 |> Ur.Sub.Internal.Sub
4040
4141
42−{-| Create a Sink subscription.
42+{-| Creates a %sink subscription.
4343-}
4444sink : { ship : String, app : String, path : List String, deconstructor : D.Deconstructor (msg -> msg) msg } -> Sub msg
4545sink { ship, app, path, deconstructor } =
src/Ur/Uw.elm+1−1
@@ -1,6 +1,6 @@
11module Ur.Uw exposing (decode, encode)
22
3−{-| This module works with Urbit base-64 encoded strings.
3+{-| This module works with Urbit base-64 encoded strings aka `@uw`.
44
55@docs decode, encode
66