{"id":19466894,"url":"https://github.com/scommons/scommons-websql","last_synced_at":"2026-05-09T03:34:13.187Z","repository":{"id":44686908,"uuid":"272172502","full_name":"scommons/scommons-websql","owner":"scommons","description":"Scala.js facade for WebSQL/SQLite API","archived":false,"fork":false,"pushed_at":"2024-03-09T14:25:36.000Z","size":132,"stargazers_count":1,"open_issues_count":1,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-09-12T01:14:53.584Z","etag":null,"topics":["bindings","facade","node-js","scala","scala-js","scalajs","sql","sql-query","sqlite","websql"],"latest_commit_sha":null,"homepage":"","language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/scommons.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-06-14T09:41:51.000Z","updated_at":"2023-05-10T18:54:58.000Z","dependencies_parsed_at":"2024-11-10T18:32:45.897Z","dependency_job_id":null,"html_url":"https://github.com/scommons/scommons-websql","commit_stats":null,"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"purl":"pkg:github/scommons/scommons-websql","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scommons%2Fscommons-websql","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scommons%2Fscommons-websql/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scommons%2Fscommons-websql/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scommons%2Fscommons-websql/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/scommons","download_url":"https://codeload.github.com/scommons/scommons-websql/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scommons%2Fscommons-websql/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32806001,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-08T08:22:46.396Z","status":"online","status_checked_at":"2026-05-09T02:00:06.633Z","response_time":123,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bindings","facade","node-js","scala","scala-js","scalajs","sql","sql-query","sqlite","websql"],"created_at":"2024-11-10T18:31:26.087Z","updated_at":"2026-05-09T03:34:13.168Z","avatar_url":"https://github.com/scommons.png","language":"Scala","funding_links":[],"categories":[],"sub_categories":[],"readme":"\n[![CI](https://github.com/scommons/scommons-websql/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/scommons/scommons-websql/actions/workflows/ci.yml?query=workflow%3Aci+branch%3Amaster)\n[![Coverage Status](https://coveralls.io/repos/github/scommons/scommons-websql/badge.svg?branch=master)](https://coveralls.io/github/scommons/scommons-websql?branch=master)\n[![scala-index](https://index.scala-lang.org/scommons/scommons-websql/scommons-websql-core/latest.svg)](https://index.scala-lang.org/scommons/scommons-websql/scommons-websql-core)\n[![Scala.js](https://www.scala-js.org/assets/badges/scalajs-1.8.0.svg)](https://www.scala-js.org)\n\n## Scala Commons Web SQL\n[Scala.js](https://www.scala-js.org) facade for [WebSQL API](https://www.w3.org/TR/webdatabase/)\n\nIt's relying on the following reference implementation:\nhttps://github.com/nolanlawson/node-websql\n\nThis API can be backed by [SQLite](https://www.sqlite.org) on Node.js\nand `react-native` platforms.\n\n### How to add it to your project\n\n```scala\nval scommonsWebSqlVer = \"1.0.0-SNAPSHOT\"\n\nlibraryDependencies ++= Seq(\n  \"org.scommons.websql\" %%% \"scommons-websql-core\" % scommonsWebSqlVer,\n  // see migrations/README.md\n  \"org.scommons.websql\" %%% \"scommons-websql-migrations\" % scommonsWebSqlVer,\n\n  // high level IO effect API (already includes core)\n  \"org.scommons.websql\" %%% \"scommons-websql-io\" % scommonsWebSqlVer\n)\n```\n\nLatest `SNAPSHOT` version is published to [Sonatype Repo](https://oss.sonatype.org/content/repositories/snapshots/org/scommons/), just make sure you added\nthe proper dependency resolver to your `build.sbt` settings:\n```scala\nresolvers += \"Sonatype Snapshots\" at \"https://oss.sonatype.org/content/repositories/snapshots/\"\n```\n\n### How to use it\n\n#### Open Database\n\nOn `react-native` project (using\n[scommons-expo](https://github.com/scommons/scommons-react-native#expo-modules)\nmodule):\n```scala\nimport scommons.expo.sqlite.SQLite\n\nval db = SQLite.openDatabase(\"myfirst.db\")\n```\n\nOn `Node.js` project:\n```scala\nimport scommons.websql.WebSQL\n\nval db = WebSQL.openDatabase(\"myfirst.db\")\n\n// or in-memory DB, useful for testing\nval db = WebSQL.openDatabase(\":memory:\")\n```\n\n#### Create DB Schema\n\nYou can use `tx.executeSql` method to run raw SQL queries:\n\n```scala\ndb.transaction { tx =\u003e\n    \n    tx.executeSql(\n      \"\"\"CREATE TABLE IF NOT EXISTS categories (\n        |  id              integer primary key,\n        |  category_name   text NOT NULL,\n        |  created_at      timestamp NOT NULL DEFAULT (strftime('%s','now') * 1000),\n        |  UNIQUE (category_name)\n        |)\n        |\"\"\".stripMargin\n    )\n    \n    tx.executeSql(\n      \"INSERT INTO categories (category_name) VALUES (?), (?)\",\n      Seq(\n        \"test category 1\",\n        \"test category 2\"\n      )\n    )\n}\n```\n\nIt can be fully automated by using\n[migrations](migrations/README.md) module.\n\n#### Create DB Context\n\n[Example](io/src/test/scala/scommons/websql/io/showcase/domain/ShowcaseDBContext.scala)\n`SQLite` DB context:\n\n```scala\nimport scommons.websql.Database\nimport scommons.websql.io.SqliteContext\n\nclass ShowcaseDBContext(db: Database) extends SqliteContext(db) {\n\n  // example of custom encoder\n  implicit val categoryIdToInt: MappedEncoding[CategoryId, Int] = mappedEncoding[CategoryId, Int](_.value)\n  implicit val categoryIdEncoder: Encoder[CategoryId] = mappedEncoder[CategoryId, Int]\n\n  // example of custom decoder\n  implicit val intToCategoryId: MappedEncoding[Int, CategoryId] = mappedEncoding[Int, CategoryId](CategoryId)\n  implicit val categoryIdDecoder: Decoder[CategoryId] = mappedDecoder[Int, CategoryId]\n}\n```\n\n#### Create DB Entity\n\n[Example](io/src/test/scala/scommons/websql/io/showcase/domain/CategoryEntity.scala)\nDB `entity` class:\n\n```scala\ncase class CategoryEntity(id: Int,\n                          categoryName: String)\n```\n\n#### Create DAO\n\nData Access Object (`DAO`) layer has very similar query `IO` API\ninterface as [quill](quill/README.md), except that `SQL` has\nto be written explicitly rather than generated during the build.\n\n[Example 1](io/src/test/scala/scommons/websql/io/showcase/domain/dao/CategoryDao.scala)\n`DAO` class with basic DB queries/actions:\n\n```scala\nimport scommons.websql.io.dao.CommonDao\nimport scommons.websql.io.showcase.domain._\n\nimport scala.concurrent.Future\n\nclass CategoryDao(val ctx: ShowcaseDBContext) extends CommonDao {\n\n  import ctx._\n\n  def getByIdQuery(id: Int): IO[Seq[CategoryEntity], Effect.Read] = {\n    ctx.runQuery(\n      sql = \"SELECT id, category_name FROM categories WHERE id = ?\",\n      args = id,\n      extractor = CategoryEntity.tupled\n    )\n  }\n\n  def getById(id: Int): Future[Option[CategoryEntity]] = {\n    getOne(\"getById\", ctx.performIO(getByIdQuery(id)))\n  }\n\n  def count(): Future[Int] = {\n    ctx.performIO(\n      ctx.runQuerySingle(\"SELECT count(*) FROM categories\", identity[Int])\n    )\n  }\n\n  def list(optOffset: Option[Int],\n           limit: Int,\n           symbols: Option[String]\n          ): Future[(Seq[CategoryEntity], Option[Int])] = {\n\n    val text = s\"%${symbols.getOrElse(\"\")}%\"\n    val offset = optOffset.getOrElse(0)\n\n    val countQuery = optOffset match {\n      case Some(_) =\u003e IO.successful(None)\n      case None =\u003e ctx.runQuerySingle(\n        sql = \"SELECT count(*) FROM categories WHERE category_name LIKE ?\",\n        args = text,\n        extractor = identity[Int]\n      ).map(Some(_))\n    }\n\n    val fetchQuery = ctx.runQuery(\n      sql =\n        \"\"\"SELECT\n          |  id,\n          |  category_name\n          |FROM\n          |  categories\n          |WHERE\n          |  category_name LIKE ?\n          |ORDER BY\n          |  category_name\n          |LIMIT ?\n          |OFFSET ?\n          |\"\"\".stripMargin,\n      args = (text, limit, offset),\n      extractor = CategoryEntity.tupled\n    )\n\n    val q = for {\n      maybeCount \u003c- countQuery\n      results \u003c- fetchQuery\n    } yield {\n      (results, maybeCount)\n    }\n\n    // internally IO is always performed within transaction\n    ctx.performIO(q)\n  }\n\n  def insertQuery(entity: CategoryEntity): IO[Int, Effect.Write] = {\n    ctx.runActionReturning(\n      \"INSERT INTO categories (category_name) VALUES (?)\", entity.categoryName\n    ).map(_.toInt)\n  }\n\n  def insert(entity: CategoryEntity): Future[Int] = {\n    ctx.performIO(insertQuery(entity))\n  }\n\n  def insertMany(list: Seq[CategoryEntity]): Future[Seq[Int]] = {\n    ctx.performIO(IO.sequence(list.map(insertQuery)))\n  }\n\n  def upsert(entity: CategoryEntity): Future[CategoryEntity] = {\n    val q = for {\n      maybeCategory \u003c- ctx.runQuery(\n        sql = \"SELECT id, category_name FROM categories WHERE category_name = ?\",\n        args = entity.categoryName,\n        extractor = CategoryEntity.tupled\n      ).map(_.headOption)\n      id \u003c- maybeCategory match {\n        case None =\u003e insertQuery(entity)\n        case Some(c) =\u003e\n          updateQuery(entity.copy(id = c.id))\n            .map(_ =\u003e c.id)\n      }\n      res \u003c- getByIdQuery(id).map(_.head)\n    } yield res\n\n    ctx.performIO(q)\n  }\n\n  def updateQuery(entity: CategoryEntity): IO[Long, Effect.Write] = {\n    ctx.runAction(\n      sql = \"UPDATE categories SET category_name = ? WHERE id = ?\",\n      args = (entity.categoryName, entity.id)\n    )\n  }\n\n  def update(entity: CategoryEntity): Future[Boolean] = {\n    isUpdated(ctx.performIO(updateQuery(entity)))\n  }\n\n  def updateMany(list: Seq[CategoryEntity]): Future[Seq[Boolean]] = {\n    ctx.performIO(IO.sequence(list.map(updateQuery)).map { results =\u003e\n      results.map(_ \u003e 0)\n    })\n  }\n\n  def deleteAll(): Future[Long] = {\n    ctx.performIO(ctx.runAction(\"DELETE FROM categories\"))\n  }\n}\n```\n\n[Example 2](io/src/test/scala/scommons/websql/io/showcase/domain/dao/ProductDao.scala)\n`DAO` class with more advanced DB queries:\n\n```scala\nimport scommons.websql.io.dao.CommonDao\nimport scommons.websql.io.showcase.domain._\n\nimport scala.concurrent.Future\n\nclass ProductDao(val ctx: ShowcaseDBContext) extends CommonDao {\n\n  import ctx._\n\n  def allProducts(): Future[Seq[ProductEntity]] = {\n    ctx.performIO(ctx.runQuery(\n      \"SELECT id, name, category_id FROM products ORDER BY id\",\n      ProductEntity.tupled\n    ))\n  }\n\n  def joinProducts(): Future[Seq[(ProductEntity, CategoryEntity)]] = {\n    ctx.performIO(ctx.runQuery(\n      sql =\n        \"\"\"SELECT\n          |  p.id             AS _0, -- ******************************* \n          |  p.name           AS _1, -- * NOTE:\n          |  p.category_id    AS _2, -- *   for JOIN queries from different tables\n          |  c.id             AS _3, -- *   ALWAYS specify custom unique fields names !!!\n          |  c.category_name  AS _4  -- *******************************\n          |FROM (\n          |  SELECT id, name, category_id FROM products ORDER BY id\n          |) AS p\n          |INNER JOIN categories c ON p.category_id = c.id\n          |\"\"\".stripMargin,\n      extractor = { case (p, c) =\u003e\n        (ProductEntity.tupled(p), CategoryEntity.tupled(c))\n      }: (((Int, String, Option[Int]), (Int, String))) =\u003e\n        (ProductEntity, CategoryEntity)\n    ))\n  }\n\n  def leftJoinProducts(): Future[Seq[(ProductEntity, Option[CategoryEntity])]] = {\n    ctx.performIO(ctx.runQuery(\n      sql =\n        \"\"\"SELECT\n          |  p.id             AS _0, -- ******************************* \n          |  p.name           AS _1, -- * NOTE:\n          |  p.category_id    AS _2, -- *   for JOIN queries from different tables\n          |  c.id             AS _3, -- *   ALWAYS specify custom unique fields names !!!\n          |  c.category_name  AS _4  -- *******************************\n          |FROM (\n          |  SELECT id, name, category_id FROM products ORDER BY id\n          |) AS p\n          |LEFT JOIN categories c ON p.category_id = c.id\n          |\"\"\".stripMargin,\n      extractor = { case (p, c) =\u003e\n        (ProductEntity.tupled(p), c.map(CategoryEntity.tupled))\n      }: (((Int, String, Option[Int]), Option[(Int, String)])) =\u003e\n        (ProductEntity, Option[CategoryEntity])\n    ))\n  }\n}\n```\n\n#### Running queries\n\n[Example](io/src/test/scala/scommons/websql/io/showcase/CategoryService.scala)\nbusiness logic / service layer:\n\n```scala\nimport scommons.websql.io.showcase.domain.CategoryEntity\nimport scommons.websql.io.showcase.domain.dao.CategoryDao\n\nimport scala.concurrent.ExecutionContext.Implicits.global\nimport scala.concurrent.Future\n\nclass CategoryService(dao: CategoryDao) {\n\n  def getById(id: Int): Future[CategoryEntity] = {\n    dao.getById(id).map(ensureCategory(id, _))\n  }\n\n  def add(entity: CategoryEntity): Future[CategoryEntity] = {\n    for {\n      insertId \u003c- dao.insert(entity)\n      entity \u003c- dao.getById(insertId).map(ensureCategory(insertId, _))\n    } yield entity\n  }\n\n  private def ensureCategory(id: Int, maybeCat: Option[CategoryEntity]): CategoryEntity = {\n    maybeCat.getOrElse {\n      throw new IllegalArgumentException(s\"Category is not found, categoryId: $id\")\n    }\n  }\n}\n```\n\n## Documentation\n\nYou can find more documentation [here](https://scommons.github.io/)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fscommons%2Fscommons-websql","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fscommons%2Fscommons-websql","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fscommons%2Fscommons-websql/lists"}