@@ -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.