Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added some docs #7

Merged
merged 7 commits into from
Jul 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 101 additions & 0 deletions docs/sink.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# %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 sync 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
::
:: A deferred expression of the state you want to sync to the frontend.
=* entries (tap:j-orm journal.stat)
::
:: Replace /sync with whatever path you want to use for syncing your state.
=/ snik (sink ~[/sync])
::
:: Next you initialize your sink with your initial agent state.
=/ sink (snik entries)
```

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 entries)` is the important bit.
`this(state state, sink (snik entries))
```

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 entries)
```

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 = ship
, 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](../example/src/Sink.elm) for a full example.
4 changes: 0 additions & 4 deletions example/src/Sink.elm
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,6 @@ type alias Model =

type Msg
= Noop
| GotSink Noun
| Error String
| GotListings (List ( BigInt, String ))
| UpdateNewEntry String
Expand All @@ -96,9 +95,6 @@ update msg model =
Noop ->
( model, Ur.Cmd.none )

GotSink _ ->
( model, Ur.Cmd.none )

Error err ->
( { model | error = err }, Ur.Cmd.none )

Expand Down
15 changes: 7 additions & 8 deletions example/urbit/app/journal.hoon
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,11 @@

%- agent:dbug
=/ state *state-0
=/ snik
%+ sink ~[/sync]
|=(stat=versioned-state (tap:j-orm journal.stat))
=/ sink (snik state)
=* entries (tap:j-orm journal.stat)
=/ snik (sink ~[/sync])
=/ sink (snik entries)
^- agent:gall

::
|_ =bowl:gall
+* this .
def ~(. (default-agent this %|) bowl)
Expand All @@ -40,7 +39,7 @@
|= old-vase=vase
^- (quip card _this)
=/ state !<(versioned-state old-vase)
`this(state state, sink (snik state))
`this(state state, sink (snik entries))
::
++ on-poke
|= [=mark =vase]
Expand All @@ -51,7 +50,7 @@
=/ now=@ (unique-time now.bowl log.state)
=/ act !<(action vase)
=. state (poke-action act)
=^ card sink (sync:sink state)
=^ card sink (sync:sink entries)
:_ this(log.state (put:log-orm log.state now act))
~[(fact:io journal-update+!>(`update`[now act]) ~[/updates]) card]
::
Expand Down Expand Up @@ -93,7 +92,7 @@
[%all ~]
:^ ~ ~ %journal-update
!> ^- update
[now %jrnl (tap:j-orm journal.state)]
[now %jrnl entries]
::
[%before @ @ ~]
=/ before=@ (rash i.t.t.t.path dem)
Expand Down
47 changes: 23 additions & 24 deletions example/urbit/lib/sink.hoon
Original file line number Diff line number Diff line change
@@ -1,27 +1,26 @@
/+ noun-diff
|%
++ clog
|$ [stut]
$%
[%flush stut]
[%drain patch:noun-diff]
==
++ sink
|* [pats=(list path) extract=$-(* *)]
|* stat=*
|@
++ sync
|= [stat=_stat]
^- [card:agent:gall _..sync]
=/ 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
++ flush
^- card:agent:gall
[%give %fact pats %noun !>(^-((clog) [%flush (extract stat)]))]
--
++ clog
|$ [stut]
$%
[%flush stut]
[%drain patch:noun-diff]
==
++ sink
|* pats=(list path)
|* stat=*
|@
++ sync
|= [stat=_stat]
^- [card:agent:gall _..sync]
=/ dif
%+ diff:noun-diff ^stat stat
:-
[%give %fact pats %noun !>(^-((clog) [%drain dif]))]
..sync(stat stat)
++ paths pats
++ flush
^- card:agent:gall
[%give %fact pats %noun !>(^-((clog) [%flush (extract stat)]))]
--
--
4 changes: 2 additions & 2 deletions src/Ur/Constructor.elm
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ signedInt i =
)


{-| Constructs an `Atom` from `Bytes`
{-| Constructs an `Atom` from `Bytes`.
-}
bytes : Bytes -> Noun
bytes =
Expand All @@ -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 =
Expand Down
6 changes: 4 additions & 2 deletions src/Ur/Deconstructor.elm
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/Ur/Phonemic.elm
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down
6 changes: 3 additions & 3 deletions src/Ur/Sub.elm
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand All @@ -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 } =
Expand Down
2 changes: 1 addition & 1 deletion src/Ur/Uw.elm
Original file line number Diff line number Diff line change
@@ -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

Expand Down