l15k4 / scalajs-rx-idb

DBMS for IndexedDB engine, based on Rx principles (type safety, resilience, backpressure, user friendly (reduces all Idb CRUD features to 4 API methods)
MIT License
9 stars 2 forks source link
legacy-code

scalajs-rx-idb

(Legacy software - not maintained anymore due to many breaking changes in dependencies)

Indexed Database reactive (Rx) wrapper written in scala.js using monix, uPickle and uTest.

resolvers in ThisBuild ++= Seq(
  Resolver.bintrayRepo("pragmaxim", "maven")
)
libraryDependencies ++= Seq("com.pragmaxim" %%% "scalajs-rx-idb" % "0.0.9")

Primarily it is trying to be :

type safe

user friendly because

handling asynchrony, resilience and back-pressure the Rx way because

In other words, doing complicated stuff with IndexedDb directly is not that easy as one might expect. I came to conclusion that IndexedDb is rather a db engine that is meant to be used by Databases built around it

NOTE

Struggles

Important API - Store

There is lot to abstract over in regards to querying IDB, especially key autogeneration, key being on value's keypath, KeyRanges, Indexes, operations on Last and First record etc. I could use scala Marcos to generate the API based on DB Schema, but unfortunately I decided not to, there are just 4 methods that basically do everything based on type of input.

//      v - either store Value OR (Key,Value) type     v - type class abstracting over the possibility of key being on value keypath, autogenerated or explicitly specified
def add[I, C[X] <: Iterable[X]](values: C[I])(implicit p: StoreKeyPolicy[I], tx: Tx[C]): Observable[(K,V)]
//         ^ - type constructor of any type that is iterable                  ^ - type class for ad-hoc polymorphism regarding transaction handling 

//       v - type constructor that might be either an Iterable or KeyRange of Keys                           
def get[C[_]](keys: C[K])(implicit e: Tx[C]): Observable[(K,V)]
//                                 ^ - type class allows you to add a custom logic for the request, there is just an evidence for Iterable and KeyRange

//                                                   v - usually an observable of Key Value pairs is returned, delete just completes
def delete[C[_]](keys: C[K])(implicit e: Tx[C]): Observable[Nothing]

// update works similar to add except it supports KeyRange - beware you must supply KeyRange entries
def update[I, C[_]](input: C[I])(implicit p: StoreKeyPolicy[I], e: Tx[C]): Observable[(K,V)]

Examples

val obj1 = Map("x" -> 0) // store values might be anything that upickle manages to serialize
val obj2 = Map("y" -> 1)
val db = IndexedDb( // you may create new db, open, upgrade or recreate existing one
  new NewDb("dbName", db => db.createObjectStore("storeName", lit("autoIncrement" -> true)))
)
val store = db.openStore[Int,Map[String, Int]]("storeName") //declare Store's key and value type information
// db requests should be combined with `onCompleteNewTx` combinator which honors idb transaction boundaries
store.add(List(obj1, obj2)).onCompleteNewTx { appendTuples =>
  assert(appendTuples.length == 2)
  val (keys, values) = appendTuples.unzip
  assert(values.head == Map("x" -> 0))
  store.get(keys).onCompleteNewTx { getTuples =>
    val (keys2, _) = getTuples.unzip
    store.delete(keys2).onCompleteNewTx { empty =>
      store.count.onCompleteNewTx { counts =>
        assert(counts(0) == 0)
        db.close()
      }
    }
  }
}
val store = db.openStore[Int, Int](storeName)
store.add(1 to 10).onCompleteNewTx { tuples =>
  store.delete(store.lastKey).onCompleteNewTx { empty =>
    store.count.map { count =>
      assert(count == 9)
    }
    store.delete(store.firstKey).onCompleteNewTx { empty =>
      store.count.map { count =>
        assert(count == 8)
      }
      store.delete(store.rangedKey(IDBKeyRange.bound(3,5), Direction.Prev)).onCompleteNewTx { empty =>
        store.count.map { count =>
          assert(count == 5)
        }
        db.close()
      }
    }
  }
}
val db = IndexedDb(recreateDB(dbName))
val store = db.openStore[Int,AnInstance](dbName)
val index = store.index[String]("testIndex")
store.add(List(obj)).onCompleteNewTx { appendTuples =>
  index.get(List("index")).onCompleteNewTx { tuples =>
    db.close()
  }
}