{"id":22782362,"url":"https://github.com/yoohaemin/decrel","last_synced_at":"2026-04-02T11:48:07.504Z","repository":{"id":65610056,"uuid":"537054685","full_name":"yoohaemin/decrel","owner":"yoohaemin","description":"Composable relations for Scala","archived":false,"fork":false,"pushed_at":"2026-03-24T04:05:25.000Z","size":386,"stargazers_count":75,"open_issues_count":28,"forks_count":3,"subscribers_count":2,"default_branch":"master","last_synced_at":"2026-03-25T03:51:58.788Z","etag":null,"topics":["caliban","caliban-graphql","cats","functional-programming","property-based-testing","relations","scala","scalacheck","zio","zio-test"],"latest_commit_sha":null,"homepage":"https://decrel.yoohaemin.com","language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mpl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/yoohaemin.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,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2022-09-15T14:04:14.000Z","updated_at":"2026-03-24T07:26:28.000Z","dependencies_parsed_at":"2023-02-16T20:45:41.135Z","dependency_job_id":"23efc242-bff6-478f-b51d-174314bfa995","html_url":"https://github.com/yoohaemin/decrel","commit_stats":null,"previous_names":[],"tags_count":12,"template":false,"template_full_name":null,"purl":"pkg:github/yoohaemin/decrel","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yoohaemin%2Fdecrel","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yoohaemin%2Fdecrel/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yoohaemin%2Fdecrel/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yoohaemin%2Fdecrel/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/yoohaemin","download_url":"https://codeload.github.com/yoohaemin/decrel/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yoohaemin%2Fdecrel/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31305809,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-02T09:48:21.550Z","status":"ssl_error","status_checked_at":"2026-04-02T09:48:19.196Z","response_time":89,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":["caliban","caliban-graphql","cats","functional-programming","property-based-testing","relations","scala","scalacheck","zio","zio-test"],"created_at":"2024-12-11T21:11:23.670Z","updated_at":"2026-04-02T11:48:07.498Z","avatar_url":"https://github.com/yoohaemin.png","language":"Scala","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Decrel\n\n[![Continuous Integration](https://github.com/yoohaemin/decrel/actions/workflows/ci.yml/badge.svg)](https://github.com/yoohaemin/decrel/actions/workflows/ci.yml)\n[![Project stage: Active][project-stage-badge: Active]](#)\n[![Release Artifacts][Badge-SonatypeReleases]][Link-SonatypeReleases]\n[![Snapshot Artifacts][Badge-SonatypeSnapshots]][Link-SonatypeSnapshots]\n\n[project-stage-badge: Active]: https://img.shields.io/badge/Project%20Stage-Active-blue.svg\n[Link-SonatypeReleases]: https://s01.oss.sonatype.org/content/repositories/releases/com/yoohaemin/decrel-core_3/ \"Sonatype Releases\"\n[Badge-SonatypeReleases]: https://img.shields.io/nexus/r/https/s01.oss.sonatype.org/com.yoohaemin/decrel-core_3.svg \"Sonatype Releases\"\n[Link-SonatypeSnapshots]: https://s01.oss.sonatype.org/content/repositories/snapshots/com/yoohaemin/decrel-core_3/ \"Sonatype Snapshots\"\n[Badge-SonatypeSnapshots]: https://img.shields.io/nexus/s/https/s01.oss.sonatype.org/com.yoohaemin/decrel-core_3.svg \"Sonatype Snapshots\"\n\nDecrel is a Scala library for **dec**larative programming using **rel**ations between your data.\n\nRead on to see how you can fetch data with automatic batching, parallelization, and caching while keeping your business logic clean and readable.\n\n## Problem Statement\n\nFetching data from datasources is an extremely common operation in applications. Usually, this is done by calling methods or functions to fetch data in an imperative manner.\n\nThis pattern is universal across languages and frameworks, from JavaScript to Haskell, and from Spring to Django:\n\n```scala\nval bookId: Book.Id = ???\n\nfor {\n  book \u003c- bookRepository.getById(bookId)\n  author \u003c- authorRepository.getById(book.authorId)\n  price \u003c- priceService.getPrice(book.id)\n  // ... do your stuff with book, author, and price\n} yield ()\n```\n\nThis code works, but has several hidden issues:\n\n* **Sequential Execution**: Fetching the author and price are independent operations but run sequentially, creating unnecessary latency\n* **N+1 Query Problem**: If you have multiple books, you'll end up calling each API N times, or need to manually implement \"joins\"\n* **No Caching by Default**: Cache access typically requires additional code for each operation\n* **Complexity Escalation**: Combining these concerns quickly increases code complexity\n\n## What is Decrel?\n\nDecrel enables:\n\n### 1. Declarative Data Access\n\nExpress relationships between your data models as first-class values:\n* \"A `Book` has one `Author`\"\n* \"A `User` may or may not have a `PremiumSubscription`\"\n\n```scala\nobject Book {\n  object author extends Relation.Single[Book, Author]\n}\n\nobject User {\n  object subscription extends Relation.Optional[User, PremiumSubscription]\n}\n```\n\n### 2. Implementation Control\n\nYou decide how to fulfill each relation with actual data access logic:\n\n```scala\n// ZIO implementation\nimplementSingleDatasource(Book.author) { books =\u003e\n  ZIO.succeed(books.map(book =\u003e book -\u003e authorMap(book.authorId)))\n}\n\n// Cats Effect implementation\nimplementSingleDatasource(Book.author) { books =\u003e\n  IO.pure(books.map(book =\u003e book -\u003e authorMap(book.authorId)))\n}\n```\n\n### 3. Composition of Relations\n\nCombine simple relations to express complex access patterns:\n\n```scala\n// Get the publisher of a book's author (sequential composition)\nval bookAuthorPublisher = Book.author \u003c\u003e: Author.publisher\n\n// Get both the author and the price of a book (parallel composition)\nval bookDetails = Book.author \u0026 Book.price\n```\n\n### 4. Efficient Execution\n\nThe composed relations are efficiently executed against your datasource, with automatic batching and parallelization through integrations with ZQuery and Fetch.\n\n### 5. Testing Support\n\nThe same relations can be used to generate random test data:\n\n```scala\n// For ScalaCheck\nval bookGen: Gen[Book] = Book.arbitrary\nval bookWithAuthorGen: Gen[(Book, Author)] = (Book.Self \u0026 Book.author).arbitrary\n\n// For ZIO Test\nval bookGen: Gen[Any, Book] = Book.gen\nval bookWithAuthorGen: Gen[Any, (Book, Author)] = (Book.Self \u0026 Book.author).gen\n```\n\n## Examples\n\nWith Decrel, you can express the same operation more clearly and efficiently:\n\n```scala\nval bookId: Book.Id = ???\n\nfor {\n  (book: Book, author: Author, price: Price) \u003c-\n    (Book.Self \u0026 Book.author \u0026 Book.price).toZIO(bookId)\n                                       // ^ .toF for cats-effect\n  // ... do your stuff with book, author, and price\n} yield ()\n```\n\n### Batching and Parallelism by Default, not an Optimization\n\nDecrel integrates with ZQuery (ZIO) or Fetch (cats-effect) to provide efficient batching and parallelism by default. Independent data fetching operations (like getting author and price) run concurrently.\n\n### No N+1 Problem\n\nWhen dealing with multiple items, Decrel handles batching efficiently:\n\n```scala\nval bookIds: List[Book.Id] = ???\n\nfor {\n  bookDetails: List[(Book, Author, Price)] \u003c- \n    (Book.Self \u0026 Book.author \u0026 Book.price).toZIOMany(bookIds)\n                                       // ^ .toFMany for cats-effect\n  // ... do your stuff with the list\n} yield ()\n```\n\nThe return type is a list of tuples, making it easy to process the results. Decrel preserves your collection type - if you use `Vector`, you get `Vector` back; same works for `List`, `Array`, `zio.Chunk` etc.\n\nUnderlying calls are automatically batched and parallelized. With proper batch implementations of your datasources, this code will call the underlying APIs at most 3 times, regardless of how many books you're retrieving.\n\n### Advanced Optimization and Caching\n\nDecrel gives you complete control over how data is accessed. You can implement sophisticated caching strategies.\n\nRefer to the below pseudocode to see an example, showcasing what you can do with decrel:\n\n```scala\nobject BookRelations extends zquery[Any] {\n  implicit val bookAuthorProof: Proof.Single[Book.author.type, Book, Nothing, Author] =\n    implementSingleDatasource(Book.author) { books =\u003e\n      for {\n        // Check cache first\n        cachedAuthors \u003c- checkCache(books.map(_.authorId))\n        // Find which IDs aren't in cache\n        missingIds = books.map(_.authorId).filterNot(cachedAuthors.contains)\n        // Fetch missing authors from DB\n        fetchedAuthors \u003c- if (missingIds.isEmpty) ZIO.succeed(Chunk.empty) else fetchAuthors(missingIds) \n        // Update cache with newly fetched authors\n        _ \u003c- updateCache(fetchedAuthors)\n        // Combine cached and fetched results\n        results = books.map(book =\u003e book -\u003e (cachedAuthors.get(book.authorId) orElse fetchedAuthors.get(book.authorId)).get)\n      } yield results\n    }\n}\n```\n\nYour domain logic remains clean and unaware of these optimizations.\n\n## Getting Started\n\nAdd Decrel to your build:\n\n[![Release Artifacts][Badge-SonatypeReleases]][Link-SonatypeReleases]\n\n```scala\n// For ZIO users\n\"com.yoohaemin\" %% \"decrel-zquery\" % \"x.y.z\"\n\"com.yoohaemin\" %% \"decrel-zquery-next\" % \"x.y.z\" // Scala 3.8.2+ overlay for .expand\n\n// For Cats Effect users\n\"com.yoohaemin\" %% \"decrel-fetch\" % \"x.y.z\"\n\n// For testing\n\"com.yoohaemin\" %% \"decrel-scalacheck\" % \"x.y.z\" % Test\n\"com.yoohaemin\" %% \"decrel-ziotest\" % \"x.y.z\" % Test\n```\n\nFor Scala 3.8.2 users who want `expand`, mix `zqueryNextSyntax` into your `zquery` object:\n\n```scala\nimport decrel.reify.{ zquery, zqueryNextSyntax }\n\nobject BookRelations extends zquery[Any] with zqueryNextSyntax[Any]\n\nimport BookRelations.*\n\nval bookId: Book.Id = ???\n\nfor {\n  book \u003c- bookId.expand(Book.fetch)\n} yield book\n```\n\n## Documentation\n\nFor comprehensive documentation, examples, and guides, please visit the [Decrel Documentation](https://yoohaemin.github.io/decrel).\n\n## Conceptual Explanation\n\nOn a fundamental level, Decrel is a structured way to compose `flatMap`/`traverse` operations:\n\n* Relations are like arrows with three \"kinds\" — Single, Optional, and Many\n* You provide implementations as functions: `In =\u003e F[Kind[Out]]` (where `Kind` is `Id`, `Option`, or `Collection[A]`)\n* Decrel handles the composition of these operations according to the relation structure\n\n## Contributing\n\nContributions are welcome! Please feel free to submit a Pull Request.\n\n## License\n\ndecrel is copyright Haemin Yoo, and is licensed under Mozilla Public License v2.0\n\n`modules/core/src/main/scala/decrel/Zippable.scala` is based on https://github.com/zio/zio/blob/v2.0.2/core/shared/src/main/scala/zio/Zippable.scala , \nlicensed under the Apache License v2.0\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyoohaemin%2Fdecrel","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fyoohaemin%2Fdecrel","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyoohaemin%2Fdecrel/lists"}