diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6579b48..f79ba98 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -74,11 +74,11 @@ jobs: - name: Make target directories if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') - run: mkdir -p examples/target unidocs/target jvm/target core/js/target js/target core/jvm/target target project/target + run: mkdir -p examples/target unidocs/target jvm/target core/js/target h2/target js/target core/jvm/target target project/target - name: Compress target directories if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') - run: tar cf targets.tar examples/target unidocs/target jvm/target core/js/target js/target core/jvm/target target project/target + run: tar cf targets.tar examples/target unidocs/target jvm/target core/js/target h2/target js/target core/jvm/target target project/target - name: Upload target directories if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') diff --git a/build.sbt b/build.sbt index b8d1f41..eb389dd 100644 --- a/build.sbt +++ b/build.sbt @@ -82,6 +82,7 @@ lazy val kropJs = lazy val rootJvm = krop.jvm.aggregate( core.jvm, + h2, examples, unidocs ) @@ -95,6 +96,17 @@ lazy val core = crossProject(JSPlatform, JVMPlatform) ) .jvmSettings(libraryDependencies += Dependencies.log4catsSlf4j.value) +lazy val h2 = project + .in(file("h2")) + .settings( + commonSettings, + moduleName := "krop-h2", + libraryDependencies ++= Seq( + Dependencies.doobieCore.value, + Dependencies.doobieH2.value + ) + ) + lazy val docs = project .in(file("docs")) diff --git a/h2/src/main/scala/krop/h2/Transactor.scala b/h2/src/main/scala/krop/h2/Transactor.scala new file mode 100644 index 0000000..bfd466d --- /dev/null +++ b/h2/src/main/scala/krop/h2/Transactor.scala @@ -0,0 +1,96 @@ +/* + * Copyright 2023 Creative Scala + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package krop.h2 + +import cats.effect.IO +import cats.effect.Resource +import doobie.h2.H2Transactor +import doobie.util.* + +import scala.concurrent.ExecutionContext + +enum DatabaseType { + case Embedded(name: String, directory: Directory) + case InMemory(name: String) + + def toJdbcString: String = + this match { + case Embedded(name, directory) => + directory match { + case Directory.CurrentWorkingDirectory => + s"jdbc:h2:./${name}" + case Directory.HomeDirectory(path) => + s"jdbc:h2:~${path}/${name}" + case Directory.AbsoluteDirectory(path) => + s"jdbc:h2:${path}/${name}" + } + + case InMemory(name) => + s"jdbc:h2:mem:${name};DB_CLOSE_DELAY=-1" + } +} + +enum Directory { + case CurrentWorkingDirectory + + /** Relative to the current user's home directory. Include a / at the start + * but not at the end. + */ + case HomeDirectory(path: String) + + /** An absolute directory. Include a / at the start but not at the end. */ + case AbsoluteDirectory(path: String) +} + +final case class Transactor( + ec: Resource[IO, ExecutionContext], + username: String, + password: String, + database: DatabaseType +) { + def withEc(ec: Resource[IO, ExecutionContext]): Transactor = + this.copy(ec = ec) + + def withUsername(username: String): Transactor = + this.copy(username = username) + + def withPassword(password: String): Transactor = + this.copy(password = password) + + def withDatabase(database: DatabaseType): Transactor = + this.copy(database = database) + + def build: Resource[IO, H2Transactor[IO]] = + for { + ctxt <- ec + xa <- H2Transactor.newH2Transactor[IO]( + database.toJdbcString, + username, + password, + ctxt + ) + } yield xa +} +object Transactor { + val default: Transactor = + Transactor( + ec = ExecutionContexts.fixedThreadPool[IO](32), + username = "", + password = "", + database = DatabaseType.InMemory("default") + ) +} diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 33168f0..99bed87 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -7,6 +7,7 @@ object Dependencies { // Library Versions val catsVersion = "2.10.0" val catsEffectVersion = "3.5.1" + val doobieVersion = "1.0.0-RC6" val fs2Version = "3.6.1" val http4sVersion = "1.0.0-M44" val scalaJsDomVersion = "2.4.0" @@ -22,6 +23,9 @@ object Dependencies { Def.setting("org.typelevel" %%% "cats-effect" % catsEffectVersion) val catsCore = Def.setting("org.typelevel" %%% "cats-core" % catsVersion) + val doobieCore = Def.setting("org.tpolecat" %% "doobie-core" % doobieVersion) + val doobieH2 = Def.setting("org.tpolecat" %% "doobie-h2" % doobieVersion) + val fs2Core = Def.setting("co.fs2" %%% "fs2-core" % fs2Version) val log4cats =