qbit is a ACID [kotlin-]multiplatform embeddable distributed DB with lazy replication and flexible write conflicts resolution toolset. Heavily inspired by Datomic
qbit - it's storage and replication technology. qbit implements:
- Automatic synchronization of user data with cloud, if cloud is presented in the system;
- Automatic synchronization of user data between devices, when direct connection between devices is available;
- Automatic write conflicts resolution with sane defaults and rich custom policies;
- CRDT on DB level;
- Encryption of user data with user provided password, so it's is protected from access by third-parties including cloud provider.
qbit stores data locally and uses entity graph information model, so for developers it's means that there are no usual persistence points of pain:
- There are no more monstrous queries for round-trips minimization, since there is no more round-trips;
- There are no more object-relational mappers, since there is no more object-relational mismatch.
Make internet decentralized again. And make development fun again.
-
Datastore
- ✅
Fetch entity by id - ✅
FileSystem storage - ✅
Reference attributes - ✅
Multivalue attributes - Component attributes
- ✅
-
DataBase
- ✅
Query by attribute value - ✅
Schema - ✅
Unique constraints - ✅
Programmatic range queries - ✅
Pull entites via reference attributes - ✅
Typed entities - ✅
Local ACID transactions - Programmatic joins
- Query language (Datalog and/or something SQL-like)
- ✅
-
p2p DataBase
- Concurrent writes support
- Automatic conflict resolution
- CRDTs
- p2p data synchronization
-
Cloud platform
- qbit DBMS
-
Supported
- JVM >= 8
- Android >= 21
-
Planned
- JS
- Kotlin/Native
- iOS
// schema
val tweetNs = Namespace.of("demo", "tweet")
val userNs = Namespace.of("demo", "user")
object Users {
val name = ScalarAttr(userNs["name"], QString, unique = true)
val lastLogin = ScalarAttr(userNs["lastLogin"], QInstant)
}
object Tweets {
val content = ScalarAttr(tweetNs["content"], QString)
val author = RefAttr(tweetNs["author"])
val date = ScalarAttr(tweetNs["date"], QZonedDateTime)
val likes = RefListAttr(tweetNs["likes"])
}
// Typed wrapper
class User<E : EID?>(entity: Entity<E>) : TypedEntity<E>(entity) {
var name: String by AttrDelegate(Users.name)
val lastLogin: Instant by AttrDelegate(Users.lastLogin)
}
// open connection
val conn = qbit(MemStorage())
// create schema
conn.persist(Users.name, lastLogin, content, author, date, likes)
// store data
val user = Entity(Users.name eq "@azhidkov", lastLogin eq Instants.now())
val tweet = Entity(content eq "Hello @HackDay",
author eq user,
date eq ZonedDateTimes.now())
// updateData
val storedUser = conn.persist(tweet).createdEntities.getValue(user)
conn.persist(storedUser.with(lastLogin, Instants.now()))
// query data
val storedTweet = conn.db.query(attrIs(content, "Hello @HackDay")).first()
println("${storedTweet[date].format(HHmm)} | ${storedTweet[author][Users.name]}: ${storedTweet[content]}")
// store list
val cris = Entity(Users.name eq "@cris", lastLogin eq Instants.now())
var updatedTweet: StoredEntity = storedTweet.with(content eq "List with works", likes eq listOf(storedUser, cris))
updatedTweet = conn.persist(cris, updatedTweet).persistedEntities[1]
println(updatedTweet[content])
println(updatedTweet[likes].map { it[Users.name] })
// Typed API
val users: List<User<EID>> = updatedTweet.getAs(likes)
// typed access
println(users.map { p -> "${p.name}: ${p.lastLogin}" })
// typed modification
users[0].name = "@reflection_rulezz"
conn.persist(users[0])
assertEquals("@reflection_rulezz", conn.db.pullAs<User<EID>>(users[0].eid)!!.name)