pishen / store4s   0.14.0

Apache License 2.0 GitHub

A Scala library for Google Cloud Datastore

Scala versions: 2.12 2.13


Maven Central javadoc

A Scala library for Google Cloud Datastore, providing compile-time mappings between case classes and Datastore entities, and a type-safe query DSL.


For regular use:

libraryDependencies += "net.pishen" %% "store4s" % "<version>"

For datastore-v1 (compatible with Apache Beam):

libraryDependencies += "net.pishen" %% "store4s-v1" % "<version>"


Convert a case class to entity using asEntity:

import store4s._

case class Zombie(number: Int, name: String, girl: Boolean)

implicit val ds = Datastore.defaultInstance

// create an Entity without name/id
val z6 = Zombie(6, "Lily Hoshikawa", false).asEntity
// create an Entity with name
val z1 = Zombie(1, "Sakura Minamoto", true).asEntity("heroine")
// create an Entity with id
val z2 = Zombie(2, "Saki Nikaido", true).asEntity(2)
// create an Entity with case class property as name/id
val z3 = Zombie(3, "Ai Mizuno", true).asEntity(_.name)

The basic data types, Seq, Option, and nested case classes are supported.

Custom types

To support custom types, one can create a ValueEncoder from an existing ValueEncoder using contramap:

val enc: ValueEncoder[LocalDate] =

Interact with Datastore

To insert, upsert, or update the entity into datastore:


Exclude from indexes

To exclude properties from indexes, use the excludeFromIndexes function from EntityEncoder:

implicit val enc = EntityEncoder[Zombie].excludeFromIndexes(_.name, _.girl)

// z1 will have 'name' and 'girl' properties excluded
val z1 = Zombie(1, "Sakura Minamoto", true).asEntity("heroine")


Get an entity from datastore:

import store4s._
case class Zombie(number: Int, name: String, girl: Boolean)

val ds = Datastore.defaultInstance

val key1 = ds.keyFactory[Zombie].newKey("heroine")
val e1: Option[Entity] = ds.get(key1)

Decode an entity into case class using decodeEntity:

val zE: Either[Throwable, Zombie] = decodeEntity[Zombie](e1.get)

If you want to decode the entity directly and throw the Exception when it fail:

val zOpt: Option[Zombie] = ds.getRight[Zombie]("heroine")

To support custom types, one can create a ValueDecoder from an existing ValueDecoder using map or emap:

val dec: ValueDecoder[LocalDate] =


A query can be built using the Query object:

import store4s._
case class Zombie(number: Int, name: String, girl: Boolean)

implicit val ds = Datastore.defaultInstance

val q = Query[Zombie]
  .filter(_.number > 1)

val r1: EntityQuery = q.builder().build()
val r2: Seq[Entity] = q.run.getEntities
val r3: Seq[Either[Throwable, Zombie]] = q.run.getEithers
val r4: Seq[Zombie] = q.run.getRights

Use getRights to decode the Entities and throw Exceptions if any decoding failed.

For querying on array type values, which corresponds to Seq, an exists function is available:

import store4s._
case class Task(tags: Seq[String])

implicit val ds = Datastore.defaultInstance

  .filter(_.tags.exists(_ == "Scala"))
  .filter(_.tags.exists(_ == "rocks"))

For querying on the properties of embedded entity (which can be referred using .):

import store4s._
case class Hometown(country: String, city: String)
case class Zombie(name: String, hometown: Hometown)

implicit val ds = Datastore.defaultInstance

  .filter(_.hometown.city == "Saga")

Check the testing code for more supported features.

ADT (Algebraic Data Types)

Support for encoding/decoding ADT is achieved by adding a property named _type into entities. When encoding a trait like this:

sealed trait Member
case class Zombie(name: String) extends Member
case class Human(name: String) extends Member

val member: Member = Human("Maimai Yuzuriha")


The result entity will be:

key {
  path {
    kind: "Member"
properties {
  key: "_type"
  value {
    string_value: "Human"
properties {
  key: "name"
  value {
    string_value: "Maimai Yuzuriha"

Which can then be decoded using


The property name _type can be configured using typeIdentifier in Datastore:

implicit val ds = Datastore.defaultInstance.copy(typeIdentifier = "typeName")


Use transaction to create a Transaction:

implicit val ds = Datastore.defaultInstance

val zOpt = ds.transaction { implicit tx =>
  val zOpt = tx.getRight[Zombie]("heroine")
  val qRes = Query[Zombie]
    .filter(_.hometown.city == "Saga")
  (zOpt, qRes)

The Transaction will be committed once the function is completed, or rollbacked if an Exception is thrown.