{"id":21816985,"url":"https://github.com/maartenvanvliet/quarto","last_synced_at":"2025-04-05T01:08:44.058Z","repository":{"id":38375736,"uuid":"258771698","full_name":"maartenvanvliet/quarto","owner":"maartenvanvliet","description":"Quarto is a keyset (cursor) based pagination library for Ecto https://hexdocs.pm/quarto/Quarto.html","archived":false,"fork":false,"pushed_at":"2024-11-02T20:34:56.000Z","size":129,"stargazers_count":44,"open_issues_count":7,"forks_count":5,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-03-29T00:12:00.558Z","etag":null,"topics":["ecto","keyset","pagination-library"],"latest_commit_sha":null,"homepage":"","language":"Elixir","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/maartenvanvliet.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-04-25T12:35:57.000Z","updated_at":"2025-01-08T03:29:25.000Z","dependencies_parsed_at":"2024-11-02T21:21:24.622Z","dependency_job_id":"89515553-1fe5-45b0-8831-76340c50744a","html_url":"https://github.com/maartenvanvliet/quarto","commit_stats":{"total_commits":88,"total_committers":5,"mean_commits":17.6,"dds":0.5340909090909092,"last_synced_commit":"92de4d3fc57011fe8bad47f8eb872705b7bf2f8c"},"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maartenvanvliet%2Fquarto","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maartenvanvliet%2Fquarto/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maartenvanvliet%2Fquarto/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maartenvanvliet%2Fquarto/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/maartenvanvliet","download_url":"https://codeload.github.com/maartenvanvliet/quarto/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247271532,"owners_count":20911587,"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":["ecto","keyset","pagination-library"],"created_at":"2024-11-27T15:38:34.698Z","updated_at":"2025-04-05T01:08:44.038Z","avatar_url":"https://github.com/maartenvanvliet.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Quarto\n\n## [![Hex pm](http://img.shields.io/hexpm/v/quarto.svg?style=flat)](https://hex.pm/packages/quarto) [![Hex Docs](https://img.shields.io/badge/hex-docs-9768d1.svg)](https://hexdocs.pm/quarto) [![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)![.github/workflows/elixir.yml](https://github.com/maartenvanvliet/quarto/workflows/.github/workflows/elixir.yml/badge.svg)\n\n---\n\n\u003e Quarto (abbreviated Qto, 4to or 4º) is a book or pamphlet produced from full \"blanksheets\", each of which is printed with eight pages of text, four to a side, then folded twice to produce four leaves. The leaves are then trimmed along the folds to produce eight book pages. Each printed page presents as one-fourth size of the full blanksheet. (https://en.wikipedia.org/wiki/Quarto)\n\nQuarto is a is a _keyset-based_ pagination library for `Ecto` but confusingly the it is also referred to as cursor-based pagination. (Though cursor based pagination also means something else).\n\nIn this case it means that the pagination relies on an opaque cursor to figure out where to start with the next batch of records. See https://use-the-index-luke.com/no-offset\n\nThis library is ported from https://github.com/duffelhq/paginator/ but has some important backwards incompatible differences that warrant a new library.\n\n## Installation\n\nIf [available in Hex](https://hex.pm/docs/publish), the package can be installed\nby adding `quarto` to your list of dependencies in `mix.exs`:\n\n```elixir\ndef deps do\n  [\n    {:quarto, \"~\u003e 1.0.0\"}\n  ]\nend\n```\n\n## Usage\n\nYou can add `Quarto` to your `Ecto.Repo`.\n\n```elixir\n  defmodule MyApp.Repo do\n    use Ecto.Repo, otp_app: :my_app\n    use Quarto, limit: 10\n  end\n```\n\nIt adds the `paginate/3` function to your repository which you can use to paginate through a resultset. See `Quarto.paginate/4` on the options that can be passed to `use Quarto`.\n\nTo paginate you can query an Ecto schema like you normally would. It is important that the columns you order by are deterministically ordered. If there are null values in the columns or if two rows have the same values in the columns you order by the results may be wrong. To fix this you can always add an extra unique column to order by.\n\nThe columns you order by will also be used to construct the opaque cursor that can be used to retrieve the next set of results.\n\n```elixir\n# First set of results\n%{entries: entries, metadata: metadata} = Post |\u003e order_by(desc: :user_id) |\u003e order_by({^order,:published_at}) |\u003e MyApp.Repo.paginate\n\nafter_cursor = metadata.after\n# Next set of results\n%{entries: entries, metadata: metadata} = Post |\u003e order_by(desc: :user_id) |\u003e order_by({^order,:published_at}) |\u003e MyApp.Repo.paginate(after: after_cursor)\n\n# Get the before cursor\nbefore_cursor = metadata.before\n\n# Previous set of results\n%{entries: entries, metadata: metadata} = Post |\u003e order_by(desc: :user_id) |\u003e order_by({^order,:published_at}) |\u003e MyApp.Repo.paginate(before: before_cursor)\n\n\n%{entries: entries, metadata: metadata} = Post |\u003e order_by(desc: :user_id) |\u003e order_by({^order,:published_at}) |\u003e MyApp.Repo.paginate(include_total_count: true)\n\n# Total count of rows satisfying the query\nmetadata.total_count\n```\n\n## Advanced usage\n\nSome more advanced use cases are shown below.\n\n#### Without a macro\n\nIf you cannot use the macro in the repo you can still use Quarto.\n\n```elixir\nQuarto.Post\n|\u003e order_by(desc: :published_at)\n|\u003e Quarto.paginate([], MyApp.Repo)\n```\n\nSimply call `Quarto.paginate(queryable, opts, MyApp.Repo, repo_opts)` with your repo as the third argument.\n\n#### Joining on related tables\n\nIn addition to ordering by a field on the current table it's also possible to order on the field of a related table.\n\nE.g. in this example the ordering of the posts is by the name of the related user and the published_at date of the post.\n\n```elixir\nQuarto.Post\n|\u003e join(:left, [p], u in assoc(p, :user), as: :user)\n|\u003e preload([p, u], user: u)\n|\u003e order_by([p, u], desc: u.name)\n|\u003e order_by(desc: :published_at)\n|\u003e Repo.paginate\n```\n\nWhat's important is that the `preload` function loads the user. This is necessary\nfor when the cursor for the next result set is created as it needs the name of the user and the published_at of the post to create the new cursor.\n\n#### Passing in the cursor\n\nAlthough in normal usage the encoded cursor can be passed in. It's possible to\nuse a normal erlang term as well.\n\n```elixir\nQuarto.Post\n|\u003e join(:left, [p], u in assoc(p, :user), as: :user)\n|\u003e preload([p, u], user: u)\n|\u003e order_by([p, u], desc: u.name)\n|\u003e order_by(desc: :id)\n|\u003e Repo.paginate(after: [\"Alice\", 10])\n```\n\n#### Implementing a custom cursor\n\nA default cursor implementation is provided but you can provide a custom implementation. E.g. to sign the cursors. The codec needs to implement the\n`Quarto.Cursor` behaviour. See `Quarto.Cursor.Base64` for an example\n\n#### Encoding/decoding cursor values\n\nNormally a cursor is composed out of a list of values of one or more fields in the database. How each field is encoded/decoded depends on its type and can be\ncontrolled with the `Quarto.Cursor.Decode` and `Quarto.Cursor.Encode` protocols.\nSee those modules for examples how you can override this\n\n#### Fragments\n\nEcto fragments provide ways to use custom SQL expressions in your ecto queries.\nQuarto does not support them in the `order_by` clause.\n\n#### NULL values\n\nNULL values in columns can be problematic for Quarto. Using coalescing it's possible to order by NULL values by coalescing them into a known value.\nE.g. when there are NULL values in a datetime column, the NULL values can be coalesced into the max or min datetime your database supports, depending on where you want them in your resultset.\n\nIn the following example the `title` field has NULL values and therefore hard to order by. If we order alphabetically and asc and decide to place the rows with NULL values at the end we need to coalesce the NULL values to e.g. `ZZZZZZZ`. It's important that it's a value higher than any other in that column.\n\n```elixir\ncoalesce = fn field, position, value -\u003e\n  case field do\n    :title -\u003e \"ZZZZZZZ\"\n    _ -\u003e value\n  end\nend\n\nQuarto.Post |\u003e order_by({:asc, :title}) |\u003e order_by({:asc, :position}) |\u003e MyApp.Repo.paginate(limit: 4, coalesce: coalesce)\n\n```\n\nThe field with the name\n\nDocumentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)\nand published on [HexDocs](https://hexdocs.pm). Once published, the docs can\nbe found at [https://hexdocs.pm/quarto](https://hexdocs.pm/quarto).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmaartenvanvliet%2Fquarto","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmaartenvanvliet%2Fquarto","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmaartenvanvliet%2Fquarto/lists"}