
Product
Socket Brings Supply Chain Security to skills.sh
Socket is now scanning AI agent skills across multiple languages and ecosystems, detecting malicious behavior before developers install, starting with skills.sh's 60,000+ skills.
CRDT-ish data structures built for local-first, distributed applications.
Credt makes it easier to build interactive, distributed apps.
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.
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.
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;
});
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.
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.
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 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()
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).
npm install -g esy
git clone <this-repo>
esy install
esy build
Tests currently only run against the native build.
# Runs the "test" command in `package.json`.
esy test
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
Credt is under active development. Some parts are missing and the api is very likely to change.
FAQs
CRDT-like data structures in reasonml
The npm package credt receives a total of 6 weekly downloads. As such, credt popularity was classified as not popular.
We found that credt demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Product
Socket is now scanning AI agent skills across multiple languages and ecosystems, detecting malicious behavior before developers install, starting with skills.sh's 60,000+ skills.

Product
Socket now supports PHP with full Composer and Packagist integration, enabling developers to search packages, generate SBOMs, and protect their PHP dependencies from supply chain threats.

Security News
An AI agent is merging PRs into major OSS projects and cold-emailing maintainers to drum up more work.