An API and plugin interface for treating documents as a series of changes
You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Go to file
Jens Finkhaeuser 08f8673eaf
ci/woodpecker/push/woodpecker Pipeline was successful Details
Minimal README with example usage
4 days ago
include Implement reference counted callbacks 4 days ago
lib Implement reference counted callbacks 4 days ago
subprojects Add better property_value implementation (incomplete) 2 weeks ago
test Implement reference counted callbacks 4 days ago
.woodpecker.yml Run private and public tests separately on the CI and with more verbose output 1 week ago
Pipfile.lock Minimal README with example usage 4 days ago
conandata.yml Extract the value setting code into an invocable merge strategy, and create the naive override merge strategy. Nothing changes about the interfaces; this just allows us to make more elaborate merge strategies. 1 week ago



An API and plugin interface for treating documents as a series of changes, and synchronizing them across nodes.

Wyrd provides access to documents as a tree structure of properties which can have different data types. In that sense, it is semantically similar to the document representation of e.g. a HTML document, or a nested JSON structure, etc.

Properties are conflict-free replicated data types (CRDTs). Since there exist a number of different CRDTs, when setting a property value, you can also specify a merge strategy, which is effectively a choice of CRDT implementation. When overwriting the value, the merge strategy generates an edit, which can be serialized, sent over a network, and applied on a different node.

The basic implementation uses a file for synchronization: edits are written to a file, and the property tree is reconstructed when the file is read.

However, wyrd also integrates with vessel, which is a container format specifically designed for synchronizing across network nodes.

Example Usage

Being a library, the first thing you need to do is instanciate a library handle. This handle contains "global" values, which allows you to run multiple instances in parallel (though that should not be necessary very often):

#include <wyrd/api.h>

/* ... */

struct wyrd_api * api = NULL;
wyrd_error_t err;

err = wyrd_create(&api);

/* use the API instance *

err = wyrd_destroy(&api);

Within the scope of creating and destroying the API instance, you can create as many handles as you need. A handle represents a resource, such as a file or vessel resource.

struct wyrd_handle * handle = NULL;

/* e.g. */
err = wyrd_open_file(api, &handle, "my-file", WYRD_O_RW| WYRD_O_CREATE);

/* use the handle */

err = wyrd_close(&handle);

With an open handle, you can then set and modify properties by name. Property names are strings, but the special . (dot) character separates path segments. Wyrd knows several container property types, such as maps and lists. In this way, a path such as "" accesses the property bar which is a child of the foo property, which is in turn a map. Similarly, "foo.0" would access the first element if foo was a list, etc.

In the following example we use the "naive override" merge strategy, which is the simplest form of CRDT - so simple it barely counts. With this strategy, the last change always wins, naively overriding any previous value. But for demonstrating the API, it is the simplest choice:

err = wyrd_set_property_uint16(handle, "foo", WYRD_MS_NAIVE_OVERRIDE, 42);

assert(WYRD_ERR_SUCCESS == wyrd_check_property_type(handle, "foo", WYRD_PT_UINT16));
assert(WYRD_ERR_SUCCESS == wyrd_check_merge_strategy(handle, "foo", WYRD_MS_NAIVE_OVERRIDE));

Finally, for the purpose of reacting to updates from remote nodes, you can register callbacks to be invoked when a property changes.

void my_callback(struct wyrd_handle * handle, char const * path, wyrd_property_type type,
    void const * value, size_t value_size, void * baton)
  // This callback can only deal with unchanged property *types*; a more realistic
  // scenario should check the type and act accordingly.
  assert(WYRD_PT_UINT16 == type);
  assert(value_size == sizeof(uint16_t));

  uint16_t val = *((uint16_t const *) value);
  // ...

err = wyrd_add_property_callback(handle, "foo", my_callback, NULL);

Whichever you pass as the final parameter to wyrd_add_property_callback will be provided to the callback as the baton parameter. Take care of memory ownership/life cycles with this parameter, as the value cannot be copied.