1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
//! # `samod-core`
//!
//! This crate provides a sans-IO implementation of networking and storage
//! protocols for synchronizing [`automerge`] docuemnts which is compatible with
//! the `@automerge/automerge-repo` JavaScript package. `samod-core` is intended
//! to be used via FFI and wrapped in a language-specific runtime which provides
//! a more ergonomic API. See the `samod` crate for an example of this in Rust.
//!
//! ## Overview
//!
//! Automerge documents are in-memory data structures which represent the
//! editing history of a JSON-like document. `automerge` provides functionality
//! for saving and loading files from storage and for synchronizing over the
//! network, but it does not specify a wire protocol for doing this. `samod-core`
//! provides a wire protocol and storage convention for storing and synchronizing
//! many automerge documents.
//!
//! ## Actors
//!
//! Processing tasks for a document can be compute intensive, which means that
//! we want to be able to take advantage of parallelism where we can.
//! `samod-core` doesn't make any assumptions about the runtime though, so it
//! doesn't start threads or spawn tasks. Instead, `samod-core` provides an
//! actor based model. There are two kinds of actors:
//!
//! * [`Hub`](crate::actors::hub::Hub) - The central actor which manages a set of connected peers
//! * [`DocumentActor`](crate::actors::document::DocumentActor) - An actor which is created for each document
//!
//! In both cases the actors expose an API wherein you pass some kind of event
//! data structure to the actor representing something that happened and you
//! get back a data structure describing "effects" that need to take place -
//! retrieving things from storage or sending things to connected peers for
//! example.
//!
//! The hub actor exposes an API for dispatching commands which manage the state
//! of the running actors, whilst the document actor exposes an API for accessing
//! and modifying the state of the document. Typical workflows then will involve
//! creating a hub actor, then using commands to connect other peers, and create
//! or find documents. Creating and finding a document will give you a [`DocumentActorId`]
//! which can then be used to find the actor corresponding to a particular document
//! and interact with the document.
//!
//! ## Typical Workflow
//!
//! A typical workflow for using this library will involve three stages:
//!
//! * Load the [`Hub`](crate::actors::hub::Hub) actor
//! * Run a control loop which passes events to the
//! [`Hub`](crate::actors::hub::Hub) actor and handles effects requested by the
//! hub actor (including spawning document actors)
//! * At some point the hub actor will receive a stop
//! command and stop all the actors before returning a
//! [`HubResults`](crate::actors::hub::HubResults) which has
//! `HubResults::stopped == true` at which point we can exit the loop
//!
//! ### Loading the Hub Actor
//!
//! Before the [`Hub`](crate::actors::hub::Hub) actor can even be up and running
//! it needs to load some things from storage. This is represented by the
//! [`SamodLoader`] type. To load the hub actor you first create a
//! [`SamodLoader`], and then perform the IO tasks it requires until it finishes
//! loading, at which point you have a [`Hub`](crate::actors::hub::Hub) actor.
//!
//! ### The Control Loop
//!
//! This is the main loop of the samod application. It's not quite a single
//! loop because there is actually a loop per actor, we can describe this
//! as one loop which manages the hub actor, and one which manages each
//! document.
//!
//! #### Hub Loop
//!
//! The hub loop waits for new "events" to pass to the hub as a
//! [`HubEvent`](crate::actors::hub::HubEvent). Events here are either
//!
//! a) New commands from the application (create a document, find a document etc.)
//! b) Completed storage IO operations
//! c) Network events (a message was received, a connection was created)
//!
//! The loop looks like this then:
//!
//! * Wait for an event
//! * Create a new [`HubEvent`](crate::actors::hub::HubEvent) corresponding to
//! the incoming event
//! * If the event is a command, note down the command ID returned from the
//! [`HubEvent`](crate::actors::hub::HubEvent) constructor
//! * Pass the event to [`Hub::handle_event`](crate::actors::hub::Hub::handle_event)
//! * Examine the returned [`HubResults`](crate::actors::hub::HubResults)
//! * If any command is completed (in
//! [`HubResults::completed_commands`](crate::actors::hub::HubResults::completed_commands))
//! note the result of the command completion to notify the application
//! of the result
//! * Dispatch any new storage or network events
//! * If any document actors need to be spawned, somehow enqueue spawning
//! of the actor
//! * Route any messages from the hub to document actors to the inboxes of
//! the document actors
//!
//! #### Document Actor Loop
//!
//! The document actor loop is a bit different to the hub actor loop because it
//! doesn't need to handle commands. However, there is the additional
//! complication that the application will typically need to interact with the
//! automerge document the actor manages. This means that the document actor
//! does not have a single `handle_event` method. Instead, there are three
//! methods which are used to interact with the actor:
//!
//! * [`DocumentActor::handle_message`](crate::actors::document::DocumentActor::handle_message) which is used to handle a message from
//! the hub actor
//! * [`DocumentActor::handle_io_complete`](crate::actors::document::DocumentActor::handle_io_complete) which is used to handle completed
//! storage requests
//! * [`DocumentActor::with_document`](crate::actors::document::DocumentActor::with_document) which is passed a closure that has access
//! to the document
//!
//! The "loop" for a document actor then often looks a bit different to the hub
//! loop. Typically the document actor is inside a mutex which holds both the
//! actor, and a reference to whatever channel is used to communicate with the
//! hub actor and IO. The ways we interact with the actor are then twofold,
//! firstly in a control loop waiting for messages from the hub and IO, and
//! secondly in some kind of lock on the mutex which the application uses.
//!
//! In the loop that would be something like this:
//!
//! * Wait for message from the hub or completed IO
//! * Lock the mutex
//! * Call either [`DocumentActor::handle_message`](crate::actors::document::DocumentActor::handle_message) or
//! [`DocumentActor::handle_io_complete`](crate::actors::document::DocumentActor::handle_io_complete)
//! * Check the returned [`DocActorResult`](crate::actors::document::DocActorResult) and dispatch any new IO
//! * Unlock the mutex
//!
//! Whilst the application driven interactions would be something like this:
//!
//! * Lock the mutex
//! * Call [`DocumentActor::with_document`](crate::actors::document::DocumentActor::with_document) with a closure that performs the desired action
//! * Check the returned [`DocActorResult`](crate::actors::document::DocActorResult) and dispatch any new IO
//! * Unlock the mutex
//!
//! ## Commands
//!
//! When you want to perform some kind of action on the hub actor you create a
//! [`HubEvent`](crate::actors::hub::HubEvent) and pass it to
//! [`Hub::handle_event`](crate::actors::hub::Hub::handle_event). Some kinds of
//! event are "commands", these are created via static methods on the
//! [`HubEvent`](crate::actors::hub::HubEvent) and will return a
//! [`DispatchedCommand`](crate::actors::hub::DispatchedCommand) which includes
//! both a [`HubEvent`](crate::actors::hub::HubEvent) to pass to
//! [`Hub::handle_event`](crate::actors::hub::Hub::handle_event) and a
//! [`CommandId`]. At some point in the future the
//! [`HubResults`](crate::actors::hub::HubResults) returned by
//! [`Hub::handle_event`](crate::actors::hub::Hub::handle_event) will contain
//! the result of the given command. One example of this would be
//! [`HubEvent::create_document`](crate::actors::hub::HubEvent::create_document)
//! which returns a command ID which will be marked as completed when the
//! document is created and saved to storage.
//!
//! ## IO
//!
//! There are two kinds of IO which `samod-core` performs - interacting with
//! storage and sending messages over the network. In both cases there is some
//! kind of task which `samod-core` requests completion of, the runtime then
//! performs the task and at a later date informs `samod-core` of the result. To
//! track ongoing tasks we use the [`IoTask`](crate::io::IoTask) struct. This
//! contains a [`IoTaskId`](crate::io::IoTaskId) which is used to identify the
//! ongoing task and some kind of action to perform. When the task is completed
//! the runtime constructs an [`IoResult`](crate::io::IoResult) using the
//! original task ID and passes it to the actor which requested the task.
//!
//! ### Storage
//!
//! One very important part of IO is storage. `samod-core` assumes a very simple
//! storage model. Storage is assumed to be a key-value store with range
//! queries. The keys are lists of strings and the values are bytes. For
//! example, a key might be `["documents", "1234567890abcdef"]` and the value
//! might be the bytes of a document. Keys are represented by the [`StorageKey`]
//! type and the operations which storage must support are represented by the
//! [`StorageTask`](crate::io::StorageTask) type.
//!
//! ### Network Connections
//!
//! Network connections are assumed to be stream oriented and are represented by
//! the [`ConnectionId`] type. You obtain a connection by passing a
//! [`HubEvent::create_dialer_connection`](crate::actors::hub::HubEvent::create_dialer_connection)
//! or
//! [`HubEvent::create_listener_connection`](crate::actors::hub::HubEvent::create_listener_connection)
//! to the [`Hub`](crate::actors::hub::Hub) and waiting for the create command
//! to complete, returning a [`ConnectionId`]. Messages can now be passed to the
//! hub using [`HubEvent::receive`](crate::actors::hub::HubEvent::receive) and
//! outbound messages will be found in
//! [`HubResults::new_tasks`](crate::actors::hub::HubResults::new_tasks).
//! Finally, the connection can be closed using
//! [`HubEvent::connection_lost`](crate::actors::hub::HubEvent::connection_lost)
//! or, if it is closed by some other event, the notification of that closure
//! will be found in
//! [`HubResults::connection_events`](crate::actors::hub::HubResults::connection_events).
pub use ;
pub use AutomergeUrl;
pub use ConnectionId;
pub use ;
pub use ;
pub use DocumentChanged;
pub use ;
pub use ;
pub use ;
pub use ;
pub use UnixTimestamp;
pub use ;
pub use ;