All Projects → eldh → credt

eldh / credt

Licence: other
CRDT-like data structures for building distributed, offline-first applications

Programming Languages

reason
219 projects
javascript
184084 projects - #8 most used programming language
C++
36643 projects - #6 most used programming language

Projects that are alternatives of or similar to credt

LifeTime
LifeTime app
Stars: ✭ 35 (+9.38%)
Mutual labels:  reasonml, reason-react
bs-react-is-visible
A small library that lets you know whether a component is visible on screen or not.
Stars: ✭ 15 (-53.12%)
Mutual labels:  reasonml, reason-react
Reason-react-hooks
🧶 Some hooks in ReasonML for reason-react that can be useful
Stars: ✭ 14 (-56.25%)
Mutual labels:  reasonml, reason-react
c-crdtlib
The Concordant Conflict-Free Replicated Datatypes (CRDT) library.
Stars: ✭ 21 (-34.37%)
Mutual labels:  crdt, crdts
state
A Redux-based state container for local-first software, offering seamless synchronization using Automerge CRDTs. (Formerly known as 🐟 Cevitxe).
Stars: ✭ 126 (+293.75%)
Mutual labels:  offline-first, crdt
re-cite
Manage citations from your colleagues , friends, movies, your cat or even yourself.
Stars: ✭ 20 (-37.5%)
Mutual labels:  reasonml, reason-react
rdoc
Conflict-free replicated JSON implementation in native Go
Stars: ✭ 76 (+137.5%)
Mutual labels:  crdt, crdts
re-use
⚛️ 🎣 A collection of hooks for ReasonReact
Stars: ✭ 27 (-15.62%)
Mutual labels:  reasonml, reason-react
timerlab
⏰ A simple and customizable timer
Stars: ✭ 94 (+193.75%)
Mutual labels:  reasonml, reason-react
dokusho
Simple Japanese reading stats tracker
Stars: ✭ 12 (-62.5%)
Mutual labels:  reasonml, reason-react
SyncedStore
SyncedStore CRDT is an easy-to-use library for building live, collaborative applications that sync automatically.
Stars: ✭ 1,053 (+3190.63%)
Mutual labels:  offline-first, crdt
onetricks.net
(WIP) kayn-powered (typescript node.js) ReasonReact app presenting you a dashboard of high ELO one trick ponies in League of Legends
Stars: ✭ 13 (-59.37%)
Mutual labels:  reasonml, reason-react
reason-hooks-testing-library
ReasonML bindings for react-hooks-testing-library
Stars: ✭ 24 (-25%)
Mutual labels:  reasonml, reason-react
reason-react-lazy-loading
Example project to show how to use components lazy loading in ReasonReact
Stars: ✭ 41 (+28.13%)
Mutual labels:  reasonml, reason-react
re-typescript
An opinionated attempt at finally solving typescript interop for ReasonML / OCaml.
Stars: ✭ 68 (+112.5%)
Mutual labels:  reasonml, reason-react
reason-catstagram
🐈 Catstagram made with ReasonReact!
Stars: ✭ 31 (-3.12%)
Mutual labels:  reasonml, reason-react
app-template-rescript-react
Adding ReScript with rescript-react on top of @snowpack/app-template-react
Stars: ✭ 44 (+37.5%)
Mutual labels:  reasonml, reason-react
bs-rsuite-ui-react
Reason bindings for React Suite UI library
Stars: ✭ 26 (-18.75%)
Mutual labels:  reasonml, reason-react
qnd
Quick and Dirty development builds
Stars: ✭ 19 (-40.62%)
Mutual labels:  reasonml, reason-react
reason-rt-binding-generator
Reason binding generator for react-toolbox
Stars: ✭ 18 (-43.75%)
Mutual labels:  reasonml, reason-react

credt

CRDT-ish data structures built for local-first, distributed applications.

Why would I want this?

Credt makes it easier to build interactive, distributed apps.

  • All data changes are applied atomically, which means most conflicts between different clients are avoided.
  • In cases where there are conflicts, they are automatically resolved.
  • Undo/redo is built-in.

What do you mean CRDT-ish?

Credt is similar to operation-based CRDT:s, but it suports some operations that can result in conflicts. The handling of those conflicts is however automatic, so even if an operation failed (for example when attempting to update a record that had been deleted), the result should "make sense" to he user.

How does it work?

Credt is built on top of native data structures. But they don't have any functions that let's you operate on them. Instead, to modify your data you apply operations on it. Operations are serializable, undoable and atomic.

Defining a data structure

Currently, this is what defining a credt list looks like:

module UserList = {
  type t = {
    id: Credt.Util.id,
    name: string,
    email: string,
    age: int,
  };

  type update =
    | SetEmail(string)
    | SetName(string)
    | SetAge(int);

  let reducer = user =>
    fun
    | SetEmail(email) => ({...user, email}, SetEmail(user.email))
    | SetName(name) => ({...user, name}, SetName(user.name))
    | SetAge(age) => ({...user, age}, SetAge(user.age));

  include Credt.List.Make({
    type nonrec t = t;
    type nonrec update = update;
    let getId = u => u.id;
    let moduleId = "UserList" |> Credt.Util.idOfString;
    let reducer = reducer;
  });
};

This might look like a lot – and there will be a ppx to remove the boilerplate – but it helps to understand what's going on inside credt. Let's go through it:

First we create the base type. This should be a record. The id field is required, and id:s are required to be unique. Credt has its own id type, and provides a function to generate id:s.

type t = {
  id: Credt.Util.id,
  name: string,
  email: string,
  age: int,
};

Then we define actions for the type, ie how the type can be modified. This will be used in the reducer later on.

type update =
  | SetEmail(string)
  | SetName(string)
  | SetAge(int);

Then the reducer, which takes a record of type t and an update. It returns a tuple (t, update) where t is a new object with the update applied, and update is the undo update, which is used if the operation for some reason needs to be rolled back.

let reducer = user =>
  fun
  | SetEmail(email) => ({...user, email}, SetEmail(user.email))
  | SetName(name) => ({...user, name}, SetName(user.name))
  | SetAge(age) => ({...user, age}, SetAge(user.age));

Finally, we pass this into Credt.List.Make which is a functor that returns a Credt List. The include keyword means that everything defined in Credt.List will be included in our UserList module.

include Credt.List.Make({
  // Base (t) and update types.
  type nonrec t = t;
  type nonrec update = update;

  // A function to get the uniqueid from a record
  let getId = u => u.id;

  // A unique id for the module itself.
  let moduleId = "UserList" |> Credt.Util.idOfString;

  // The reducer we defined above
  let reducer = reducer;
});

Modifying data

To make changes to the list we just defined, we apply operations on it. For example, adding a user looks like this:

let myUser = { ... };

let result = UserList.apply([Append(myUser)]);

To change the user's name:

let result = UserList.apply([Update(myUser.id, SetName("Maggie Simpson"))]);

Append and Update are variants that belong to Credt.List, and SetName is the variant we defined in our update type.

The result of an apply call is a result(unit, list(failedOperations)), so if some operations failed you can inform the user. This can happen if some other client had removed the record you tried to update for example, or if you yourself batched incompatible updates.

Reading data

UserList.getSnapshot() will return the current content of UserList, and UserList.get(id) will return a specific item. To use this in an app, you'd probably listen to updates and use getSnapshot() to pass data into your app.

Manager

An app will likely consist of a numer of different credt data structures. Some things, like undo/redo and transactions, are inherently global concerns, which is why credt has a "manager".

Transactions

Transactions ensure that dependant operations are handled as one when it comes to undo/redo and that none of the changes are applied if one operation fails.

Consider for example an app where you have a list of issues and a map of labels. If you want to remove a label you have to remove it from the label map, and also remove the reference from all issues that have that label applied.

Some psuedo code of how this would be done with Credt:

// Add all operations removing the label from issues
IssueList.(
  issues
    |> List.keep(issueHasLabel(labelId))
    |> List.map(issue => Update(RemoveLabel(labelId), issue))
    |> addToTransaction
);

// Remove the label from the collection of labels
LabelMap.(
  [Remove(labelId)] |> addToTransaction
);

// Apply the transaction
let result = Credt.Manager.applyTransaction() // OK()

Undo & redo

Credt has global undo & redo functionality built in. Just call Credt.Manager.undo() to revert the latest operation. Credt.Manager.redo() will redo the last operation that was undone (if any).

Developing:

npm install -g esy
git clone <this-repo>
esy install
esy build

Running Tests:

Tests currently only run against the native build.

# Runs the "test" command in `package.json`.
esy test

Building

Credt is cross platform and compiles to native (with esy & dune) and javascript (with bucklescript).

esy x TestCredt.exe # Runs the native test build
yarn bsb -make-world -clean-world # Runs the bucklescript build

Current status

Credt is under active development. Some parts are missing and the api is very likely to change.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].