Scribble is a simple database built on top of Git. You can use it in a distributed fashion, e.g. with a GitHub repo. Changes made from different working copies will be eventually coalesced into a coherent, agreed-upon state.
Scribble can be used to store objects:
import (
"fmt"
"github.com/danslimmon/scribble"
)
func main() {
// New takes a path to an already initialized Git working copy with an origin remote already
// configured.
db := scribble.New("/path/to/your/git/repo")
// Add a record to the database
db.Write(scribble.Record{
// Records may be assigned a type, to assist in grouping and querying.
Type: "note",
// You can query for all records with a given set of tags.
Tags: []string{"foo", "bar"},
// Content can be any object that can by Marshaled and Unmarshaled with the
// encoding/json package.
Content: map[string]string{"hello": "world!"},
})
// Iterate over all records of type "note"
db.Each(scribble.Query{Type: "note"}, func(r scribble.Record) error {
fmt.Printf("Content of note %s: '%v'\n", r.ID(), r.Content)
}
// Modify the record with the given ID. This creates a new record and deletes the old record.
newID, err := db.Alter("bcdef012-3456-789a-bcde-f0123456789a", func(r scribble.Record) (scribble.Record, error) {
r.Content = struct{X, Y float64}{X: 3.14, Y: 2.72}
return r, nil
})
// Delete the record with the given ID
db.Delete("abcdef01-2345-6789-abcd-ef0123456789")
}
The other thing you can do with Scribble is operate on a tree:
func main() {
db := scribble.New("/path/to/your/git/repo")
// TreeHandle takes the name of a tree, which is this example already exists in the database.
tree := db.TreeHandle("mytree")
// Iterate (depth-first) over the nodes of the tree. Node labels are strings.
tree.Walk(func(node scribble.TreeNode) error {
fmt.Printf(
"Node with ID '%s' has label '%s' and %d child nodes\n",
node.ID(),
node.Label,
len(node.Children),
)
return nil
)}
// Add a child node to the root of the tree
nodeID, _ := tree.AddChild(scribble.RootNodeID, scribble.TreeNode{
Label: "this node is a child of the root node",
})
// Change the node's label
tree.AlterNode(nodeID, func(node scribble.TreeNode) (scribble.TreeNode, error) {
node.Label = "check out my cool new label!"
return node, nil
})
// Add a child to the node we just added (this new node will be a grandchild of the root node)
tree.AddChild(nodeID, scribble.TreeNode{Label: "this node is two levels down from the root node"})
// Delete the node that we initially created
tree.DeleteNode(nodeID)
}
By default, every change you make with Scribble gets immediately committed to Git and pushed
(asynchronously) to the origin. You may instead choose to batch up your changes into a single commit
(to do: write an example for this)
For record storage (DB.Each
, DB.Delete
, and so on), there is no possibility of a conflict. If
two instances of Scribble concurrently call DB.Alter
against the same record ID, then the database
just ends up with two distinct copies of that record. If a record is altered by one Scribble
instance and deleted by another, the deletion is ignored.
Tree nodes work similarly. If a tree node's label is concurrently altered by two Scribble instances, the change with the later wall clock time wins. If a tree node is altered by one instance and deleted by another instance, the deletion is ignored. If one Scribble instance adds a child to a tree node, and another instance also adds a child, both children end up in the tree, with their ordering determined by the wall clock times of the changes.