{"id":18576851,"url":"https://github.com/niqdev/caliban-extras","last_synced_at":"2025-04-10T09:30:43.812Z","repository":{"id":39987875,"uuid":"281372067","full_name":"niqdev/caliban-extras","owner":"niqdev","description":"GraphQL pagination and filters with Caliban","archived":false,"fork":false,"pushed_at":"2024-07-29T20:19:54.000Z","size":221,"stargazers_count":25,"open_issues_count":24,"forks_count":3,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-03-24T19:08:31.735Z","etag":null,"topics":["caliban","droste","graphql","graphql-filter","graphql-pagination","graphql-relay","scala"],"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/niqdev.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-07-21T10:54:22.000Z","updated_at":"2024-02-06T19:55:22.000Z","dependencies_parsed_at":"2024-11-06T23:40:24.096Z","dependency_job_id":null,"html_url":"https://github.com/niqdev/caliban-extras","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/niqdev%2Fcaliban-extras","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/niqdev%2Fcaliban-extras/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/niqdev%2Fcaliban-extras/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/niqdev%2Fcaliban-extras/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/niqdev","download_url":"https://codeload.github.com/niqdev/caliban-extras/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248191627,"owners_count":21062540,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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","droste","graphql","graphql-filter","graphql-pagination","graphql-relay","scala"],"created_at":"2024-11-06T23:26:52.559Z","updated_at":"2025-04-10T09:30:42.609Z","avatar_url":"https://github.com/niqdev.png","language":"Scala","funding_links":[],"categories":[],"sub_categories":[],"readme":"# caliban-extras\n\n[![Build Status][build-image]][build-url]\n[![Sonatype Nexus (Releases)][nexus-image]][nexus-url]\n[![Sonatype Nexus (Snapshots)][nexus-snapshot-image]][nexus-snapshot-url]\n[![Scala Steward badge][scala-steward-image]][scala-steward-url]\n\n[build-image]: https://travis-ci.org/niqdev/caliban-extras.svg?branch=master\n[build-url]: https://travis-ci.org/niqdev/caliban-extras\n[nexus-image]: https://img.shields.io/nexus/r/com.github.niqdev/caliban-refined_2.13?color=blueviolet\u0026server=https%3A%2F%2Foss.sonatype.org\u0026style=popout-square\n[nexus-url]: https://oss.sonatype.org/content/repositories/releases/com/github/niqdev/caliban-refined_2.13/\n[nexus-snapshot-image]: https://img.shields.io/nexus/s/com.github.niqdev/caliban-refined_2.13?label=nexus-snapshot\u0026server=https%3A%2F%2Foss.sonatype.org\u0026style=flat-square\n[nexus-snapshot-url]: https://oss.sonatype.org/content/repositories/snapshots/com/github/niqdev/caliban-refined_2.13/\n[scala-steward-image]: https://img.shields.io/badge/Scala_Steward-helping-blue.svg?style=popout-square\u0026logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAQCAMAAAARSr4IAAAAVFBMVEUAAACHjojlOy5NWlrKzcYRKjGFjIbp293YycuLa3pYY2LSqql4f3pCUFTgSjNodYRmcXUsPD/NTTbjRS+2jomhgnzNc223cGvZS0HaSD0XLjbaSjElhIr+AAAAAXRSTlMAQObYZgAAAHlJREFUCNdNyosOwyAIhWHAQS1Vt7a77/3fcxxdmv0xwmckutAR1nkm4ggbyEcg/wWmlGLDAA3oL50xi6fk5ffZ3E2E3QfZDCcCN2YtbEWZt+Drc6u6rlqv7Uk0LdKqqr5rk2UCRXOk0vmQKGfc94nOJyQjouF9H/wCc9gECEYfONoAAAAASUVORK5CYII=\n[scala-steward-url]: https://scala-steward.org\n\n* [caliban-refined](#caliban-refined)\n* [Example](#example)\n* [Resources](#resources)\n* [TODO](#todo)\n\n\u003c!-- https://mobile.twitter.com/ghostdogpr/status/1299967550663438337 --\u003e\n\n## caliban-refined\n\nA tiny module to add [refined](https://github.com/fthomas/refined) and [newtype](https://github.com/estatico/scala-newtype) support for [Schema](https://ghostdogpr.github.io/caliban/docs/schema.html#schemas) and [ArgBuilder](https://ghostdogpr.github.io/caliban/docs/schema.html#arguments)\n\nAdd the following line in `build.sbt`\n\n```sbt\nlibraryDependencies += \"com.github.niqdev\" %% \"caliban-refined\" % \"0.1.5\"\n```\n\nReplace all the custom implementations like\n```scala\nimplicit val nonEmptyStringSchema: Schema[Any, NonEmptyString] =\n  Schema.stringSchema.contramap(_.value)\n\nimplicit val nonNegIntArgBuilder: ArgBuilder[NonNegInt] = {\t\n  case value: IntValue =\u003e\t\n    NonNegInt.from(value.toInt).leftMap(CalibanError.ExecutionError(_))\t\n  case other =\u003e\n    Left(CalibanError.ExecutionError(s\"Can't build a NonNegInt from input $other\"))\t\n}\n```\n\nwith an import\n\n```scala\n// add import\nimport caliban.refined._\n\n@newtype case class Id(int: PosInt)\n@newtype case class Name(string: NonEmptyString)\ncase class User(id: Id, name: Name)\ncase class UserArg(id: Id)\ncase class Query(user: UserArg =\u003e User)\n\nval resolver = Query(arg =\u003e User(arg.id, Name(\"myName\")))\nval api      = GraphQL.graphQL(RootResolver(resolver))\n```\n\nSee the [tests](https://github.com/niqdev/caliban-extras/blob/master/modules/refined/src/test/scala/caliban/refined/RefinedSpec.scala) for a complete example\n\n## Example\n\nA minimalistic version of GraphQL [GitHub](https://developer.github.com/v4/explorer) api with pagination, *filters and authentication (TODO)*\n\n```bash\n# run example\nsbt -jvm-debug 5005 \"examples/runMain com.github.niqdev.caliban.Main\"\n\n# verify endpoint\nhttp -v :8080/api/graphql query='{users(first:1){totalCount}}'\n```\n\n### Sample queries\n\n```graphql\nquery counts {\n  countUsers: users(first: 1) {\n    totalCount\n  }\n  countRepositories: repositories(first: 1) {\n    totalCount\n  }\n  countIssues: issues(first: 1) {\n    totalCount\n  }\n}\n```\n```json\n{\n  \"data\": {\n    \"countUsers\": {\n      \"totalCount\": 2\n    },\n    \"countRepositories\": {\n      \"totalCount\": 40\n    },\n    \"countIssues\": {\n      \"totalCount\": 40\n    }\n  }\n}\n```\n\n```graphql\nquery nodes {\n  userNodes: users(first: 1) {\n    nodes {\n      id\n      name\n      createdAt\n      updatedAt\n      #repository \u003e issue | issues\n      #repositories \u003e issue | issues\n    }\n  }\n  repositoryNodes: repositories(first: 1) {\n    nodes {\n      id\n      name\n      url\n      isFork\n      createdAt\n      updatedAt\n      #issue\n      #issues\n    }\n  }\n  issueNodes: issues(first: 1) {\n    nodes {\n      id\n      number\n      status\n      title\n      body\n      createdAt\n      updatedAt\n    }\n  }\n}\n```\n```json\n{\n  \"data\": {\n    \"userNodes\": {\n      \"nodes\": [\n        {\n          \"id\": \"dXNlcjp2MTpmMGZiZTEzMS0zZjY1LTQxNDUtYjM3My01YmJmYzFjOWExYWU=\",\n          \"name\": \"zio\",\n          \"createdAt\": \"2020-08-11T18:25:25.411363Z\",\n          \"updatedAt\": \"2020-08-11T18:25:25.411363Z\"\n        }\n      ]\n    },\n    \"repositoryNodes\": {\n      \"nodes\": [\n        {\n          \"id\": \"cmVwb3NpdG9yeTp2MToxOTQ2MDQ4Ny01ZmZkLTRkMzgtYjBlOS0xNmRiNDQ4NDYxNTU=\",\n          \"name\": \"zio-s3\",\n          \"url\": \"https://github.com/zio/zio-s3\",\n          \"isFork\": false,\n          \"createdAt\": \"2020-08-11T18:25:25.496188Z\",\n          \"updatedAt\": \"2020-08-11T18:25:25.496188Z\"\n        }\n      ]\n    },\n    \"issueNodes\": {\n      \"nodes\": [\n        {\n          \"id\": \"aXNzdWU6djE6MjMzNTlhMjktZDQxYy00ODAxLWE2MjMtNGM2YzNmMGU5NjMy\",\n          \"number\": 27,\n          \"status\": \"CLOSE\",\n          \"title\": \"title6\",\n          \"body\": \"body6\",\n          \"createdAt\": \"2020-08-11T18:25:25.571263Z\",\n          \"updatedAt\": \"2020-08-11T18:25:25.571263Z\"\n        }\n      ]\n    }\n  }\n}\n```\n```bash\n# user:v1:f0fbe131-3f65-4145-b373-5bbfc1c9a1ae\necho \"dXNlcjp2MTpmMGZiZTEzMS0zZjY1LTQxNDUtYjM3My01YmJmYzFjOWExYWU=\" | base64 --decode\n\n# repository:v1:19460487-5ffd-4d38-b0e9-16db44846155\necho \"cmVwb3NpdG9yeTp2MToxOTQ2MDQ4Ny01ZmZkLTRkMzgtYjBlOS0xNmRiNDQ4NDYxNTU=\" | base64 --decode\n\n# issue:v1:23359a29-d41c-4801-a623-4c6c3f0e9632\necho \"aXNzdWU6djE6MjMzNTlhMjktZDQxYy00ODAxLWE2MjMtNGM2YzNmMGU5NjMy\" | base64 --decode\n```\n\n```graphql\nquery findByParameter {\n  user(name: \"zio\") {\n    name\n    # it doesn't verify user ownership\n    repository(name: \"shapeless\") {\n      name\n      url\n      isFork\n      # it doesn't verify repository ownership\n      issue(number: 1) {\n        number\n        status\n        title\n        body\n      }\n    }\n  }\n}\n```\n```json\n{\n  \"data\": {\n    \"user\": {\n      \"name\": \"zio\",\n      \"repository\": {\n        \"name\": \"shapeless\",\n        \"url\": \"https://github.com/milessabin/shapeless\",\n        \"isFork\": false,\n        \"issue\": {\n          \"number\": 1,\n          \"status\": \"OPEN\",\n          \"title\": \"title0\",\n          \"body\": \"body0\"\n        }\n      }\n    }\n  }\n}\n```\n\n```graphql\nquery findUserPaginated {\n  users(first: 10, after: \"opaqueCursor\") {\n    edges {\n      cursor\n      node {\n        name\n        repositories(first: 4) {\n          nodes {\n            name\n          }\n        }\n      }\n    }\n    pageInfo {\n      hasNextPage\n      hasPreviousPage\n      startCursor\n      endCursor\n    }\n    totalCount\n  }\n}\n```\n```json\n{\n  \"data\": {\n    \"users\": {\n      \"edges\": [\n        {\n          \"cursor\": \"Y3Vyc29yOnYxOjI=\",\n          \"node\": {\n            \"name\": \"zio\",\n            \"repositories\": {\n              \"nodes\": [\n                {\n                  \"name\": \"zio-lambda\"\n                },\n                {\n                  \"name\": \"zio-web\"\n                },\n                {\n                  \"name\": \"zio-ftp\"\n                },\n                {\n                  \"name\": \"zio-actors\"\n                }\n              ]\n            }\n          }\n        },\n        {\n          \"cursor\": \"Y3Vyc29yOnYxOjE=\",\n          \"node\": {\n            \"name\": \"typelevel\",\n            \"repositories\": {\n              \"nodes\": [\n                {\n                  \"name\": \"scodec\"\n                },\n                {\n                  \"name\": \"ciris\"\n                },\n                {\n                  \"name\": \"refined\"\n                },\n                {\n                  \"name\": \"shapeless\"\n                }\n              ]\n            }\n          }\n        }\n      ],\n      \"pageInfo\": {\n        \"hasNextPage\": false,\n        \"hasPreviousPage\": false,\n        \"startCursor\": \"Y3Vyc29yOnYxOjI=\",\n        \"endCursor\": \"Y3Vyc29yOnYxOjE=\"\n      },\n      \"totalCount\": 2\n    }\n  }\n}\n```\n\n```graphql\nquery findByNodeIds {\n  nodes(\n    ids: [\n      \"dXNlcjp2MTpmMGZiZTEzMS0zZjY1LTQxNDUtYjM3My01YmJmYzFjOWExYWU=\",\n      \"cmVwb3NpdG9yeTp2MToyOTBlYzI2NS1lYzkxLTRhOWItYmRkYS03YjA5NzBkYjk5Y2I=\",\n      \"aXNzdWU6djE6MjMzNTlhMjktZDQxYy00ODAxLWE2MjMtNGM2YzNmMGU5NjMy\"\n    ]\n  ) {\n    id\n    ... on User {\n      name\n    }\n    ... on Repository {\n      name\n      url\n      isFork\n    }\n    ... on Issue {\n      number\n      status\n      title\n      body\n    }\n  }\n}\n```\n```json\n{\n  \"data\": {\n    \"nodes\": [\n      {\n        \"id\": \"dXNlcjp2MTpmMGZiZTEzMS0zZjY1LTQxNDUtYjM3My01YmJmYzFjOWExYWU=\",\n        \"name\": \"zio\"\n      },\n      {\n        \"id\": \"cmVwb3NpdG9yeTp2MToyOTBlYzI2NS1lYzkxLTRhOWItYmRkYS03YjA5NzBkYjk5Y2I=\",\n        \"name\": \"refined\",\n        \"url\": \"https://github.com/fthomas/refined\",\n        \"isFork\": false\n      },\n      {\n        \"id\": \"aXNzdWU6djE6MjMzNTlhMjktZDQxYy00ODAxLWE2MjMtNGM2YzNmMGU5NjMy\",\n        \"number\": 27,\n        \"status\": \"CLOSE\",\n        \"title\": \"title6\",\n        \"body\": \"body6\"\n      }\n    ]\n  }\n}\n```\n\n## Resources\n\n* GraphQL\n    - [GraphQL](https://graphql.org) (Documentation)\n    - [Specification](http://spec.graphql.org)\n    - [GraphQL Playground](https://www.graphqlbin.com)\n    - [The Fullstack Tutorial for GraphQL](https://www.howtographql.com)\n    - [awesome-graphql](https://github.com/chentsulin/awesome-graphql)\n    - [Public GraphQL APIs](https://github.com/APIs-guru/graphql-apis)\n    - [graphqurl](https://github.com/hasura/graphqurl) (cli)\n* GraphiQL\n    - [GraphiQL](https://github.com/graphql/graphiql)\n    - [Altair GraphQL Client](https://altair.sirmuel.design) (GUI)\n    - [GraphiQL.app](https://github.com/skevy/graphiql-app) (GUI)\n    - [graphiql](https://github.com/friendsofgo/graphiql) (Docker)\n* Pagination\n    - Relay [GraphQL Cursor Connections Specification](https://relay.dev/graphql/connections.htm)\n    - [GraphQL Pagination](https://graphql.org/learn/pagination)\n    - [Global Object Identification](https://graphql.org/learn/global-object-identification)\n    - [GraphQL Server Specification](https://relay.dev/docs/en/graphql-server-specification)\n    - [GraphQL Pagination best practices](https://medium.com/javascript-in-plain-english/graphql-pagination-using-edges-vs-nodes-in-connections-f2ddb8edffa0)\n    - [Evolving API Pagination at Slack](https://slack.engineering/evolving-api-pagination-at-slack-1c1f644f8e12)\n* Filter\n    - Drupal [Filters](https://drupal-graphql.gitbook.io/graphql/queries/filters)\n    - [Droste](https://github.com/higherkindness/droste)\n* Caliban\n    - [Caliban](https://ghostdogpr.github.io/caliban) (Documentation)\n    - [Caliban: Designing a Functional GraphQL Library](https://www.youtube.com/watch?v=OC8PbviYUlQ) by Pierre Ricadat (Video)\n    - [GraphQL in Scala with Caliban](https://medium.com/@ghostdogpr/graphql-in-scala-with-caliban-part-1-8ceb6099c3c2)\n* Other\n    - [GraphQLite](https://graphqlite.com)\n\n## TODO\n\n* [x] cats [example](https://github.com/niqdev/scala-fp/pull/85)\n    - query `node` and `nodes`\n    - query `user` and `users`\n    - query `repository` and `repositories`\n    - query `issue` and `issues`\n* [x] abstract node\n* [x] abstract pagination (relay spec)\n* [ ] abstract filters (drupal spec with droste) [example](https://github.com/niqdev/scala-fp/pull/96)\n    - [doobie](https://tpolecat.github.io/doobie/index.html) support\n    - [skunk](https://tpolecat.github.io/skunk) support\n    - [slick](https://scala-slick.org) support\n* [ ] pagination module - issue with `Node` interface, see possible [solution](https://gist.github.com/paulpdaniels/d8e932b9faee19812d2de8f56dd77a51)\n* [ ] filters module\n* [x] refined/newtype module\n* [ ] migrate from cats to zio\n* [ ] mutations example\n* [ ] subscriptions example\n* [ ] enumeratum module (?)\n* [ ] TLS\n* [ ] JWT auth\n* [ ] static GraphiQL\n* [ ] static doc (markdown)\n    - https://github.com/2fd/graphdoc\n    - https://github.com/wayfair/dociql\n    - https://github.com/gjtorikian/graphql-docs\n    - https://github.com/edno/docusaurus2-graphql-doc-generator\n* [ ] helm chart + argocd deployment (live demo)\n* [ ] tests !!!\n\n\u003c!--\n\n# Offset Pagination in SQL\n\nSELECT ... FROM table\nOFFSET \u003cpage-size * page-number\u003e\nLIMIT \u003cpage-size\u003e\n\n# Keyset Pagination in SQL\n\nSELECT ... FROM table\nORDER BY create_time DESC, id ASC\nLIMIT \u003cpage-size\u003e\n\nSELECT ... FROM table\nORDER BY create_time DESC, id ASC\nWHERE (create_time \u003c last.create_time) OR (create_time = last.create_time AND id \u003e last.id)\nLIMIT \u003cpage-size\u003e\n\n--\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fniqdev%2Fcaliban-extras","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fniqdev%2Fcaliban-extras","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fniqdev%2Fcaliban-extras/lists"}