{"id":23613694,"url":"https://github.com/freckle/yesod-page-cursor","last_synced_at":"2025-11-06T09:30:34.322Z","repository":{"id":40289243,"uuid":"181357269","full_name":"freckle/yesod-page-cursor","owner":"freckle","description":"Cursor based pagination for yesod","archived":false,"fork":false,"pushed_at":"2023-06-27T15:45:37.000Z","size":94,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":22,"default_branch":"main","last_synced_at":"2024-04-25T23:31:23.269Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Haskell","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/freckle.png","metadata":{"files":{"readme":"README.lhs","changelog":"ChangeLog.md","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":"2019-04-14T19:16:02.000Z","updated_at":"2022-03-30T14:33:57.000Z","dependencies_parsed_at":"2024-12-12T20:31:59.237Z","dependency_job_id":"a5a64064-d69d-4a2c-b44f-2fa93c383552","html_url":"https://github.com/freckle/yesod-page-cursor","commit_stats":{"total_commits":93,"total_committers":5,"mean_commits":18.6,"dds":"0.30107526881720426","last_synced_commit":"c4491faebe41df185b6d862db491b62b084767a1"},"previous_names":[],"tags_count":14,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/freckle%2Fyesod-page-cursor","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/freckle%2Fyesod-page-cursor/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/freckle%2Fyesod-page-cursor/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/freckle%2Fyesod-page-cursor/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/freckle","download_url":"https://codeload.github.com/freckle/yesod-page-cursor/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":239491352,"owners_count":19647811,"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":[],"created_at":"2024-12-27T17:18:48.206Z","updated_at":"2025-11-06T09:30:34.285Z","avatar_url":"https://github.com/freckle.png","language":"Haskell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# yesod-page-cursor\n\n[![Hackage](https://img.shields.io/hackage/v/yesod-page-cursor.svg?style=flat)](https://hackage.haskell.org/package/yesod-page-cursor)\n[![Stackage Nightly](http://stackage.org/package/yesod-page-cursor/badge/nightly)](http://stackage.org/nightly/package/yesod-page-cursor)\n[![Stackage LTS](http://stackage.org/package/yesod-page-cursor/badge/lts)](http://stackage.org/lts/package/yesod-page-cursor)\n[![CI](https://github.com/freckle/yesod-page-cursor/actions/workflows/ci.yml/badge.svg)](https://github.com/freckle/yesod-page-cursor/actions/workflows/ci.yml)\n\nCursor based pagination for `yesod` using index friendly keyset cursors.\n\nPrimer: [No Offset](https://use-the-index-luke.com/no-offset)\n\n\u003c!--\n```haskell\n{-# LANGUAGE DataKinds #-}\n{-# LANGUAGE DerivingStrategies #-}\n{-# LANGUAGE FlexibleInstances #-}\n{-# LANGUAGE GADTs #-}\n{-# LANGUAGE GeneralizedNewtypeDeriving #-}\n{-# LANGUAGE LambdaCase #-}\n{-# LANGUAGE MultiParamTypeClasses #-}\n{-# LANGUAGE QuasiQuotes #-}\n{-# LANGUAGE RecordWildCards #-}\n{-# LANGUAGE StandaloneDeriving #-}\n{-# LANGUAGE TemplateHaskell #-}\n{-# LANGUAGE TypeFamilies #-}\n{-# LANGUAGE UndecidableInstances #-}\n\nmodule Main (module Main) where\n\nimport Prelude\n\nimport Data.Aeson\nimport Data.Maybe (catMaybes)\nimport Data.Time (UTCTime)\nimport Database.Persist\nimport Database.Persist.Sql\nimport Database.Persist.TH\nimport Text.Markdown.Unlit ()\nimport Yesod.Core\nimport Yesod.Page\n\ndata App = App\n\nmkYesodData \"App\" [parseRoutes|\n/ SomeR GET\n|]\n\ninstance Yesod App\n\nrequiredParam :: String -\u003e Handler a\nrequiredParam = undefined\n\noptionalParam :: String -\u003e Handler (Maybe a)\noptionalParam = undefined\n\nrunDB :: SqlPersistT m a -\u003e m a\nrunDB = undefined\n\n```\n--\u003e\n\n```haskell\nmkPersist sqlSettings [persistLowerCase|\nSomeAssignment json\n  createdAt     UTCTime\n  teacherId     Int\n  courseId      Int\n|]\n\ngetSomeR :: Handler Value\ngetSomeR = do\n  teacherId \u003c- requiredParam \"teacherId\"\n  mCourseId \u003c- optionalParam \"courseId\"\n  page \u003c- withPageAbsolute 100 entityKey $ \\Cursor {..} -\u003e do\n    fmap (sort cursorPosition) . runDB $ selectList\n      (catMaybes\n        [ Just $ SomeAssignmentTeacherId ==. teacherId\n        , (SomeAssignmentCourseId ==.) \u003c$\u003e mCourseId\n        , whereClause cursorPosition\n        ]\n      )\n      [LimitTo $ unLimit cursorLimit, orderBy cursorPosition]\n  returnJson $ keyValueEntityToJSON \u003c$\u003e page\n where\n  whereClause = \\case\n    First -\u003e Nothing\n    Previous p -\u003e Just $ persistIdField \u003c. p\n    Next p -\u003e Just $ persistIdField \u003e. p\n    Last -\u003e Nothing\n  orderBy = \\case\n    First -\u003e Asc persistIdField\n    Previous _ -\u003e Desc persistIdField\n    Next _ -\u003e Asc persistIdField\n    Last -\u003e Desc persistIdField\n  sort = \\case\n    First -\u003e id\n    Previous _ -\u003e reverse\n    Next _ -\u003e id\n    Last -\u003e reverse\n```\n\n`cursorLastPosition` is configurable. A page sorted by `created_at` may look\nlike:\n\n```haskell\ngetSortedSomeR :: Handler Value\ngetSortedSomeR = do\n  page \u003c- withPageAbsolute 100 (someAssignmentCreatedAt . entityVal)\n    $ \\Cursor {..} -\u003e do\n      fmap (sort cursorPosition) . runDB $ selectList\n        (whereClause cursorPosition)\n        [ LimitTo $ unLimit cursorLimit\n        , orderBy cursorPosition\n        ]\n  returnJson $ keyValueEntityToJSON \u003c$\u003e page\n where\n  whereClause = \\case\n    First -\u003e []\n    Previous createdAt -\u003e\n      [ SomeAssignmentCreatedAt \u003c=. createdAt\n      ]\n    Next createdAt -\u003e\n      [ SomeAssignmentCreatedAt \u003e=. createdAt\n      ]\n    Last -\u003e []\n  orderBy = \\case\n    First -\u003e Asc SomeAssignmentCreatedAt\n    Previous _ -\u003e Desc SomeAssignmentCreatedAt\n    Next _ -\u003e Asc SomeAssignmentCreatedAt\n    Last -\u003e Desc SomeAssignmentCreatedAt\n  sort = \\case\n    First -\u003e id\n    Previous _ -\u003e reverse\n    Next _ -\u003e id\n    Last -\u003e reverse\n```\n\n## Usage\n\nPaginated requests return a single page and a link with a cursor token to\nretrieve the next page.\n\n```console\n% curl 'some-rest.com/endpoint?limit=3'\n{\n  \"first\": : \"https://some-rest.com/endpoint?next=eyJsYXN0UG9zaXRpb24iOjMsInBhcmFtcyI6WzEsbnVsbF0sImxpbWl0IjozfQ==\",\n  \"previous\": null,\n  \"next\": \"https://some-rest.com/endpoint?next=eyJsYXN0UG9zaXRpb24iOjMsInBhcmFtcyI6WzEsbnVsbF0sImxpbWl0IjozfQ==\",\n  \"data\": [...]\n}\n```\n\nThe link can be used to retrieve the next page.\n\n```console\n% curl 'some-rest.com/endpoint?next=eyJsYXN0UG9zaXRpb24iOjMsInBhcmFtcyI6WzEsbnVsbF0sImxpbWl0IjozfQ=='\n{\n  \"first\": : \"https://some-rest.com/endpoint?next=eyJsYXN0UG9zaXRpb24iOjMsInBhcmFtcyI6WzEsbnVsbF0sImxpbWl0IjozfQ==\",\n  \"previous\": \"https://some-rest.com/endpoint?next=eyJsYXN0UG9zaXRpb24iOjMsInBhcmFtcyI6WzEsbnVsbF0sImxpbWl0IjozfQ==\",\n  \"next\": \"https://some-rest.com/endpoint?next=eyJsYXN0UG9zaXRpb24iOjMsInBhcmFtcyI6WzEsbnVsbF0sImxpbWl0IjozfQ==\",\n  \"data\": [...]\n}\n```\n\nIf no pages remain then no link is returned\n\n```console\n% curl 'some-rest.com/endpoint?next=eyJsYXN0UG9zaXRpb24iOjMsInBhcmFtcyI6WzEsbnVsbF0sImxpbWl0IjozfQ=='\n{\n  \"first\": : \"https://some-rest.com/endpoint?next=eyJsYXN0UG9zaXRpb24iOjMsInBhcmFtcyI6WzEsbnVsbF0sImxpbWl0IjozfQ==\",\n  \"previous\": \"https://some-rest.com/endpoint?next=eyJsYXN0UG9zaXRpb24iOjMsInBhcmFtcyI6WzEsbnVsbF0sImxpbWl0IjozfQ==\",\n  \"next\": null,\n  \"data\": [...]\n}\n```\n\n\u003c!--\n```haskell\nmain :: IO ()\nmain = pure ()\n```\n--\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffreckle%2Fyesod-page-cursor","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffreckle%2Fyesod-page-cursor","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffreckle%2Fyesod-page-cursor/lists"}