Git Product home page Git Product logo

sequelize-cursor-pagination's Introduction

➡️ sequelize-cursor-pagination

Test npm

Cursor-based pagination queries for Sequelize models. Some motivation and background.

Install

With npm:

npm install sequelize-cursor-pagination

With Yarn:

yarn add sequelize-cursor-pagination

How to use?

Define a static pagination method for a Sequelize model with the makePaginate function:

const { makePaginate } = require('sequelize-cursor-pagination');

const Counter = sequelize.define('counter', {
  id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true },
  value: Sequelize.INTEGER,
});

Counter.paginate = makePaginate(Counter);

The first argument of the makePaginate function is the model class. The function also has a second, optional argument, which is the options object. The options object has the following properties:

  • primaryKeyField: The primary key field of the model. With a composite primary key, provide an array containing the keys, for example ['key1', 'key2']. If not provided, the primary key is resolved from the model's attributes (attributes with primaryKey: true). This is the desired behavior in most cases.
  • omitPrimaryKeyFromOrder: By default, the primary key is automatically included in the order if it is missing. Setting this option to true will override this behavior. The default value is false.

Call the paginate method:

const result = await Counter.paginate({
  where: { value: { [Op.gt]: 2 } },
  limit: 10,
});

The paginate method returns a promise, which resolves an object with the following properties:

  • edges: An array containing the results of the query. Each item in the array contains an object with the following properties:
    • node: The model instance
    • cursor: Cursor for the model instance
  • totalCount: The total numbers rows matching the query
  • pageInfo: An object containing the pagination related data with the following properties:
    • startCursor: The cursor for the first node in the result edges
    • endCursor: The cursor for the last node in the result edges
    • hasNextPage: A boolean that indicates whether there are edges after the endCursor (false indicates that there are no more edges after the endCursor)
    • hasPreviousPage: A boolean that indicates whether there are edges before the startCursor (false indicates that there are no more edges before the startCursor)

The paginate method has the following options:

  • after: The cursor that indicates after which edge the next set of edges should be fetched
  • before: The cursor that indicates before which edge next set of edges should be fetched
  • limit: The maximum number of edges returned

Other options passed to the paginate method will be directly passed to the model's findAll method.

⚠️ NB: The order option format only supports the ['field'] and ['field', 'DESC'] variations (field name and the optional order direction). For example, ordering by an associated model's field won't work.

Examples

The examples use the Counter model defined above.

Fetch the first 20 edges ordered by the id field (the primaryKeyField field) in ascending order:

const result = await Counter.paginate({
  limit: 20,
});

First, fetch the first 10 edges ordered by the value field in a descending order. Second, fetch the first 10 edges after the endCursor. Third, fetch the last 10 edges before startCursor:

const firstResult = await Counter.paginate({
  order: [['value', 'DESC']],
  limit: 10,
});

const secondResult = await Counter.paginate({
  order: [['value', 'DESC']],
  limit: 10,
  after: firstResult.pageInfo.endCursor,
});

const thirdResult = await Counter.paginate({
  order: [['value', 'DESC']],
  limit: 10,
  before: secondResult.pageInfo.startCursor,
});

TypeScript

The library is written in TypeScript, so types are on the house!

If you are using a static method like in the previous examples, just declare the method on your model class:

import {
  PaginateOptions,
  PaginationConnection,
  makePaginate,
} from 'sequelize-cursor-pagination';

export class Counter extends Model<
  InferAttributes<Counter>,
  InferCreationAttributes<Counter>
> {
  declare id: CreationOptional<number>;
  declare value: number;

  declare static paginate: (
    options: PaginateOptions<Counter>,
  ) => Promise<PaginationConnection<Counter>>;
}

// ...

Counter.paginate = makePaginate(Counter);

Migrating from version 2

The withPagination function is deprecated starting from version 3, but the migration is fairly simple.

Version 2:

const withPagination = require('sequelize-cursor-pagination');

const Counter = sequelize.define('counter', {
  id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true },
  value: Sequelize.INTEGER,
});

withPagination({ primaryKeyField: 'id' })(Counter);

Version 3 onwards:

const { makePaginate } = require('sequelize-cursor-pagination');

const Counter = sequelize.define('counter', {
  id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true },
  value: Sequelize.INTEGER,
});

Counter.paginate = makePaginate(Counter);

sequelize-cursor-pagination's People

Contributors

aheuermann avatar antony avatar benjamin658 avatar borodean avatar dependabot[bot] avatar endangeredmassa avatar hrasoa avatar iamhere avatar jadhavmanoj avatar kalleilv avatar kaltsoon avatar louim avatar modood avatar pilagod avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

sequelize-cursor-pagination's Issues

Support ordering on associated models

As of now, this lib does not support ordering on associated models. But this can be achieved quickly imo.

Proposed solution:

We should just look for the omitPrimaryKeyFromOrder argument, and then add primaryKeyField to the 'orderOption' variable and give that object to the model.paginate function.

We should not parse anything else. paginate parses the order argument and constructs the query.

Below code block is from normalizedOrder function -

const normalizeOrder = (order, primaryKeyField, omitPrimaryKeyFromOrder) => {

We should remove this. ordering parameter for associated model can be nested many level.
ex. ordering = [[{model:user, as: "users"}, "name", "ASC" ]]

 if (Array.isArray(order)) {
    normalized = order.map((o) => {
      if (typeof o === 'string') {
        return [o, 'ASC'];
      }

      if (Array.isArray(o)) {
        const [field, direction] = o;

        return [field, direction || 'ASC'];
      }

      return o;
    });
  }

Let me know if you have better approach to implement this feature.

I tried implementing this approach, worked for me.

Option to override ordering parameters

Currently, ASC ordering is used by default on primaryKeyField. When we use UUID, it might produce unexpected results when combined with other ordering parameters. In short I want to skip ordering on primaryKeyField.

Is there a way to override the ordering field?

I can see similar issue reported #24 and closed without any comment 🤔

` after: pagination.cursors.after` in the option always throw error "pagination was use before it was defined"

I want to do pagination with link to specific page like. the front end will look like this

                                  **|<<|1|2|3|4|5|>>|**

click on a number will have as effect bring to the specific page. sequelize-cursor-pagination seems to start always from the first page.

This is my source

const whatIget = await Model.paginate({
limit: 2,
desc: true
});

To go to the next/previous page i do this

const whatIget = await Model.paginate({
limit: 2,
desc: true,
after: whatIget .cursors.after
});

this line after: whatIget .cursors.after in the option generate error whatIget is invalid, which is normal.

question 1: How jump to a straight page number , for example if the current page is 1, by clicking on 3 reach the page 3

**question 2: how to avoid the error generate by this whatIget .cursors.after in the option **

UUID PK and order generates unwanted results

Hi,

If my pk is a UUID and I add an order field the returned data is not what is should be (see examples).

I am aware that I could simply override the pk field on model initialisation, but I am using the auto import that comes with sequelize-cli (rewriten in es8 syntax) which makes that almost impossible.

If I can help in any way let me know!

~/database/models/index.js
import path from 'path';
import Sequelize from 'sequelize';
import conf from '~/config/database.json';
import logger from '~/utils/logger';
import withPagination from 'sequelize-cursor-pagination';


const env = process.env.NODE_ENV || 'development';
const config = {
  logging: msg => logger.log('silly', msg),
  retry: {
    max: 10,
  },
  ...conf[env],
};

let sequelize;
if (config.use_env_variable) {
  sequelize = new Sequelize(process.env[config.use_env_variable], config);
} else {
  sequelize = new Sequelize(config.database, config.username, config.password, config);
}

const paginationOptions = {
  methodName: 'paginate',
  primaryKeyField: 'id',
};

const requireModels = require.context('.', true, /\.js$/);
const models = {};
requireModels.keys().forEach((key) => {
  if (key.includes(__filename)) return;

  const extension = path.extname(key);
  const modelName = path.basename(key, extension);
  const model = sequelize.import(modelName, requireModels(key));

  withPagination(paginationOptions)(model);

  models[model.name] = model;
});

Object.keys(models).forEach((modelName) => {
  if (models[modelName].associate) {
    models[modelName].associate(models);
  }
});

export default {
  sequelize,
  Sequelize,
  ...models,
};

Examples

in the example below the 'sonarrId' field is added to the order table. I am using graphql as the data fetcher so thats why the data is restructured (it should however still be clear what is happening)

Results without cursor:

{
  "data": {
    "shows": {
      "pageInfo": {
        "hasPreviousPage": false,
        "hasNextPage": true,
        "startCursor": "WyJiMzM5ZmI1Zi04N2VhLTRlYzgtYWYxYS0xYzk4MmVhNzE4NjEiXQ==",
        "endCursor": "WyIyMjQ2Y2M3ZS1lODFkLTQxNjAtOTEwNi1jZTMwMGRjNWE1OWMiXQ=="
      },
      "edges": [
        {
          "cursor": "WyJiMzM5ZmI1Zi04N2VhLTRlYzgtYWYxYS0xYzk4MmVhNzE4NjEiXQ==",
          "node": {
            "id": "b339fb5f-87ea-4ec8-af1a-1c982ea71861",
            "sonarrId": 1
          }
        },
        {
          "cursor": "WyI0OTdhYjk0OC1kZjUyLTQ3MWUtYTRlZS0xNDQ1NTM2NDVhMTMiXQ==",
          "node": {
            "id": "497ab948-df52-471e-a4ee-144553645a13",
            "sonarrId": 2
          }
        },
        {
          "cursor": "WyI2Njk1ZjE0MC0zODMxLTRiMzYtOTRkNi1kY2QwNWJkMjQ2YTAiXQ==",
          "node": {
            "id": "6695f140-3831-4b36-94d6-dcd05bd246a0",
            "sonarrId": 3
          }
        },
        {
          "cursor": "WyIyMjQ2Y2M3ZS1lODFkLTQxNjAtOTEwNi1jZTMwMGRjNWE1OWMiXQ==",
          "node": {
            "id": "2246cc7e-e81d-4160-9106-ce300dc5a59c",
            "sonarrId": 4
          }
        }
      ]
    }
  }
}

Result with cursor (cursor of element 'sonarrId = 2')

{
  "data": {
    "shows": {
      "pageInfo": {
        "hasPreviousPage": true,
        "hasNextPage": true,
        "startCursor": "WyJiMzM5ZmI1Zi04N2VhLTRlYzgtYWYxYS0xYzk4MmVhNzE4NjEiXQ==",
        "endCursor": "WyJjMDA5NGI1My1kZGE1LTQzYWQtOWRhMS1kN2QzZWY3NTYzMzYiXQ=="
      },
      "edges": [
        {
          "cursor": "WyJiMzM5ZmI1Zi04N2VhLTRlYzgtYWYxYS0xYzk4MmVhNzE4NjEiXQ==",
          "node": {
            "id": "b339fb5f-87ea-4ec8-af1a-1c982ea71861",
            "sonarrId": 1
          }
        },
        {
          "cursor": "WyI2Njk1ZjE0MC0zODMxLTRiMzYtOTRkNi1kY2QwNWJkMjQ2YTAiXQ==",
          "node": {
            "id": "6695f140-3831-4b36-94d6-dcd05bd246a0",
            "sonarrId": 3
          }
        },
        {
          "cursor": "WyI4YzczMzNmNi0yMDdjLTQxYmQtYjczZC0xYmM1ZDM3MmZmMWIiXQ==",
          "node": {
            "id": "8c7333f6-207c-41bd-b73d-1bc5d372ff1b",
            "sonarrId": 5
          }
        },
        {
          "cursor": "WyJjMDA5NGI1My1kZGE1LTQzYWQtOWRhMS1kN2QzZWY3NTYzMzYiXQ==",
          "node": {
            "id": "c0094b53-dda5-43ad-9da1-d7d3ef756336",
            "sonarrId": 6
          }
        }
      ]
    }
  }
}

Expected result

{
  "data": {
    "shows": {
      "pageInfo": {
        "hasPreviousPage": false,
        "hasNextPage": true,
        "startCursor": "WyJiMzM5ZmI1Zi04N2VhLTRlYzgtYWYxYS0xYzk4MmVhNzE4NjEiXQ==",
        "endCursor": "WyJmNmJjYjY5MC1iZjUyLTRiOWItYWRjZi03ZDhiOTdkZmUyZDEiXQ=="
      },
      "edges": [
        {
          "cursor": "WyI2Njk1ZjE0MC0zODMxLTRiMzYtOTRkNi1kY2QwNWJkMjQ2YTAiXQ==",
          "node": {
            "id": "6695f140-3831-4b36-94d6-dcd05bd246a0",
            "sonarrId": 3
          }
        },
        {
          "cursor": "WyIyMjQ2Y2M3ZS1lODFkLTQxNjAtOTEwNi1jZTMwMGRjNWE1OWMiXQ==",
          "node": {
            "id": "2246cc7e-e81d-4160-9106-ce300dc5a59c",
            "sonarrId": 4
          }
        },
        {
          "cursor": "WyI4YzczMzNmNi0yMDdjLTQxYmQtYjczZC0xYmM1ZDM3MmZmMWIiXQ==",
          "node": {
            "id": "8c7333f6-207c-41bd-b73d-1bc5d372ff1b",
            "sonarrId": 5
          }
        },
        {
          "cursor": "WyJjMDA5NGI1My1kZGE1LTQzYWQtOWRhMS1kN2QzZWY3NTYzMzYiXQ==",
          "node": {
            "id": "c0094b53-dda5-43ad-9da1-d7d3ef756336",
            "sonarrId": 6
          }
        }
      ]
    }
  }
}

Support ordering with DESC / ASC NULL FIRST

When we pass DESC NULL FIRST in order list, it choose different operator & unexpected data returns.

Probable fix for this is - src/utils.ts

const currentOp = order[0][1].toLowerCase().split(" ")[0] === 'desc' ? Op.lt : Op.gt;

Let me know if you have any other suggestion. So I will create a PR.

totalCount?

Is it possible to add a totalCount property in results?

Suggestion

Can I recommend using native base64 encoding/decoding to prevent adding the dependency.

the code would look like

function encodeCursor(cursor) {
  return cursor ? Buffer.from(JSON.stringify(cursor)).toString('base64') : null;
}

function decodeCursor(cursor) {
  return cursor ? JSON.parse(Buffer.from(cursor, 'base64').toString('utf8')) : null;
}

Thank you

composite primary keys

const options = {
  methodName: 'paginate',
  primaryKeyField: 'id',
};

How can I set primaryKeyField for composite primary keys?

Add option to disable count

Counting all rows in a table with a complex query condition could be very slow. If we could disable totalCount output we would save the query execution time.

Incorrect comparison of primary key breaks pagination of common values

Background

Polling an external data source leads to a large number of objects being detected as "created" at the same time and therefore they share common time values in the database.

Sample:

mysql> SELECT `externalID`, `created` FROM `Bans` AS `Ban` ORDER BY `Ban`.`created` DESC, `Ban`.`created` DESC, `Ban`.`externalID`;
+---------------------------------+---------------------+
| externalID                      | created             |
+---------------------------------+---------------------+
| 76561199012028299,null          | 2020-09-03 09:33:25 |
| 76561199070347487,null          | 2020-09-03 09:33:25 |
| 76561199073505820,null          | 2020-09-03 09:33:25 |
| 76561199073698749,null          | 2020-09-03 09:33:25 |
| 76561197966981054,null          | 2020-09-03 09:33:24 |
| 76561197989853261,1575158400000 | 2020-09-03 09:33:24 |
| 76561198011452299,null          | 2020-09-03 09:33:24 |
| 76561198016934901,null          | 2020-09-03 09:33:24 |
| 76561198055048431,1575158400000 | 2020-09-03 09:33:24 |
| 76561198061177398,null          | 2020-09-03 09:33:24 |
| 76561198103661436,null          | 2020-09-03 09:33:24 |
| 76561198132548555,null          | 2020-09-03 09:33:24 |
| 76561198138858154,null          | 2020-09-03 09:33:24 |
| 76561198155763440,null          | 2020-09-03 09:33:24 |
| 76561198171455539,null          | 2020-09-03 09:33:24 |
| 76561198278884160,null          | 2020-09-03 09:33:24 |
| 76561198293156671,null          | 2020-09-03 09:33:24 |
| 76561198396074645,1569628800000 | 2020-09-03 09:33:24 |
| 76561198809827520,null          | 2020-09-03 09:33:24 |
| 76561198885259555,null          | 2020-09-03 09:33:24 |
| 76561199071149935,null          | 2020-09-03 09:33:24 |
+---------------------------------+---------------------+
21 rows in set (0.00 sec)

I am interested in displaying this data in a paginated format by the created field ordered DESC, i.e. a list of the most recently created objects.

To do this I use the following code:

const page = await Ban.paginate({
  order: ['created', 'DESC'],
  limit: 2,
  paginationField: 'created',
  after: cursor,
  desc: true
});

Each page iteration I supply the after cursor to the next iteration.

In Theory

Splitting this into small pages for demo purposes would give us:

--- Page 1 ---
| 76561199012028299,null          | 2020-09-03 09:33:25 |
| 76561199070347487,null          | 2020-09-03 09:33:25 |

--- Page 2 ---
| 76561199073505820,null          | 2020-09-03 09:33:25 |
| 76561199073698749,null          | 2020-09-03 09:33:25 |

--- Page 3 ---
| 76561197966981054,null          | 2020-09-03 09:33:24 |
| 76561197989853261,1575158400000 | 2020-09-03 09:33:24 |

In Practise

The first iteration has no cursor, so the following is returned:

mysql> SELECT `externalID`, `created` FROM `Bans` AS `Ban` ORDER BY `Ban`.`created` DESC, `Ban`.`created` DESC, `Ban`.`externalID` LIMIT 3;
+------------------------+---------------------+
| externalID             | created             |
+------------------------+---------------------+
| 76561199012028299,null | 2020-09-03 09:33:25 |
| 76561199070347487,null | 2020-09-03 09:33:25 |
| 76561199073505820,null | 2020-09-03 09:33:25 | <-- Not shown
+------------------------+---------------------+
3 rows in set (0.00 sec)

"after": "WyIyMDIwLTA5LTAzVDA5OjMzOjI1LjAwMFoiLCI3NjU2MTE5OTA3MDM0NzQ4NyxudWxsIl0=",
"before": "WyIyMDIwLTA5LTAzVDA5OjMzOjI1LjAwMFoiLCI3NjU2MTE5OTAxMjAyODI5OSxudWxsIl0=",

The second iteration uses the after cursor above and produces the results:

mysql> SELECT `externalID`, `created` FROM `Bans` AS `Ban` WHERE ((`Ban`.`created` < '2020-09-03 09:33:25' OR (`Ban`.`created` = '2020-09-03 09:33:25' AND `Ban`.`externalID` < '76561199070347487,null'))) ORDER BY `Ban`.`created` DESC, `Ban`.`created` DESC, `Ban`.`externalID` LIMIT 3;
+---------------------------------+---------------------+
| externalID                      | created             |
+---------------------------------+---------------------+
| 76561199012028299,null          | 2020-09-03 09:33:25 |
| 76561197966981054,null          | 2020-09-03 09:33:24 |
| 76561197989853261,1575158400000 | 2020-09-03 09:33:24 | <-- Not shown
+---------------------------------+---------------------+
3 rows in set (0.00 sec)

after: "WyIyMDIwLTA5LTAzVDA5OjMzOjI0LjAwMFoiLCI3NjU2MTE5Nzk2Njk4MTA1NCxudWxsIl0=",
before: "WyIyMDIwLTA5LTAzVDA5OjMzOjI1LjAwMFoiLCI3NjU2MTE5OTAxMjAyODI5OSxudWxsIl0=",

The third iteration uses the after cursor above and produces the results:

mysql> SELECT `externalID`, `created` FROM `Bans` AS `Ban` WHERE ((`Ban`.`created` < '2020-09-03 09:33:24' OR (`Ban`.`created` = '2020-09-03 09:33:24' AND `Ban`.`externalID` < '76561197966981054,null'))) ORDER BY `Ban`.`created` DESC, `Ban`.`created` DESC, `Ban`.`externalID` LIMIT 3;
Empty set (0.00 sec)

Clearly, the theory and practical results do not align.

Comparison of what should have been selected:

+---------------------------------+---------------------+
| externalID                      | created             |
+---------------------------------+---------------------+
| 76561199012028299,null          | 2020-09-03 09:33:25 | <--- T1, P1, P2
| 76561199070347487,null          | 2020-09-03 09:33:25 | <--- T1, P1
| 76561199073505820,null          | 2020-09-03 09:33:25 | <--- T2
| 76561199073698749,null          | 2020-09-03 09:33:25 | <--- T2
| 76561197966981054,null          | 2020-09-03 09:33:24 | <--- P2
| 76561197989853261,1575158400000 | 2020-09-03 09:33:24 |
| 76561198011452299,null          | 2020-09-03 09:33:24 |
| 76561198016934901,null          | 2020-09-03 09:33:24 |
| 76561198055048431,1575158400000 | 2020-09-03 09:33:24 |
| 76561198061177398,null          | 2020-09-03 09:33:24 |

What's going wrong?

The externalID field, the second field the table is ordered by, is not being ordered DESC like the other fields, however, it is being compared like the other fields as if it was ordered DESC, i.e. if cursor > row. This means that when a cursor shares common values it incorrectly selected values and to some extent grabs items from the previous page.

You can see this in the example above. 76561199012028299,null has an equal value to the cursor's created field so it checked whether the externalID was less than the cursor's external ID of 76561199070347487,null. This would be correct behaviour if the table was ordered by both fields DESC, but as externalID is ordered ASC and we're looking for smaller values it goes up the table and thus back onto the previous page. 76561197966981054,null was shown as only 76561199012028299,null shared created and met the broken criteria, as opposed to the correct values 76561199073505820,null and 76561199073698749,null, so it took the next value non-shared value, i.e. a smaller created field, of 2020-09-03 09:33:24.

Fix

The is simple, but can be done in two ways:

  • Order the table by the primary key ASC/DESC based on the other values.
  • Always compare the primary key as if it's being sorted ASC, i.e. always look for larger primary key values, after checking the sorted value first.

I can probably work on a fix, but I was to check this repo still being actively maintained first? Otherwise, I think I will start afresh as I then I can also improve the generated cursors, without breaking dependent's code, to reduce the number of inputs to the paginate function that would make it simpler to use, especially over APIs where users are less familiar with the inner workings of the system.

Add support for GROUP BY clause

Is there any particular reason why you do not support the group clause?

sequelize/sequelize#1585

To my understanding and based on your library you just have to forward that to the findAll call

this.model
            .paginate({
                attributes: options.attributes,
                group: options.group,
                having: options.having,
                where: options.whereClause,
                after: options.after,
                before: options.before,
                limit: options.limit || this.limit,
                raw: true
            })

Group by clause is ignored in the above query.

Pagination of relations?

Hello,

The docs and tests show pagination on a single Sequelize model.
But is it possible to paginate on model associations like author.getBooks()?

(In node's GraphQL implementation of connections it is transparently (for the user, at least) possible to paginate models and associations.)

On sequelize model option Paranoid: true, cursor generating different query.

Hi,

On sequelize model, if paranoid is set to true explicitly, cursor is generating different query.

When paranoid set to false:

SELECT id, visitor_id, visiting_id, property_id, verification_code, verification_token, createdAt, updatedAt, deletedAt, unit_id, have_vehicle, vehicle_no, vehicle_type, purposeFROMregular_visitorsASregular_visitors WHERE (regular_visitors.id> 169 ANDregular_visitors.property_id= 32) ORDER BYregular_visitors.id LIMIT 6;

when paranoid set to true:

SELECT id, visitor_id, visiting_id, property_id, verification_code, verification_token, createdAt, updatedAt, deletedAt, unit_id, have_vehicle, vehicle_no, vehicle_type, purposeFROMregular_visitorsASregular_visitors WHERE (regular_visitors.deletedAt> CURRENT_TIMESTAMP ORregular_visitors.deletedAtIS NULL) ORDER BYregular_visitors.id LIMIT 6;

Thanks in advance.

Using "group" option

Using group_by makes the totalCount and hasNextPage to not work properly. Could you extract group_by option from the count query?

Looking at the code:
modelClass.count(totalCountQueryOptions) this would not return a number if you are using postgres and group_by

multiple order generates invalid query

As far as i can see in the test, this extension only allows for single ordering options.

test('paginates correctly when findAll attributes are provided', async t => {
  const data = await generateTestData();

    let pagination = await Test.paginate({ order: [ 'counter', 'DESC' ], limit: 5, attributes: ['counter'], paginationField: 'counter' });
    t.deepEqual(pagination.results.map(r => r.counter), [ 4, 4, 3, 2, 1 ]);
});

However I need multiple stage of ordering for my business logic.
The code below is accepted in sequelize, but not accepted in this extension

order: [
                ['Promo', 'dateStart', 'DESC'],
                ['AcademyCourse','dateAdded', 'DESC']
            ]

The above logic is necessary for me, because i need to show OLD data if it's included in a newer promo.

Another question also arise, will this require me to change the pagination field somehow? or do i need to tackle this issue by reorganizing my db structure somehow?

Can't handle one-to-many

It looks like your algorithm for hasNext doesn't work.

If you have a one-to-many (includes) relation, your result-set length is shrunk before sequelize returns the resultset to you.

What this means is that hasNext looks at the length of the resultset after it has been turned into models, which is often lower than the limit query, and declares that there are no more records.

Module '"sequelize"' has no exported member 'Attributes'.

Sorry for my english. :)

Project on TypeScript. On boostrap I catch an error:

node_modules/sequelize-cursor-pagination/types.d.ts:1:30 - error TS2305: Module '"sequelize"' has no exported member 'Attributes'.

1 import { Model, FindOptions, Attributes } from 'sequelize';

Sequelize - 6.11.0. Also I check it on latest 6.21.0 version.
Sequelize-cursor-pagination - 3.0.2

Suggestion:
Change Attributes -> ModelAttributes

Add support for HAVING clause

Is there any particular reason why you do not support the having clause?

sequelize/sequelize#1585

To my understanding and based on your library you just have to forward that to the findAll call

this.model
            .paginate({
                attributes: options.attributes,
                group: options.group,
                having: options.having,
                where: options.whereClause,
                after: options.after,
                before: options.before,
                limit: options.limit || this.limit,
                raw: true
            })

Having clause is ignored in the above query.

Add UUID support

Hey @Kaltsoon this library is great! I'm trying to implement it but I've now seen that it is not possible to have UUID's as primary key (as postgresql allows you to) bcs the paginationQuery orders only by Op.gt or Op.lt .

Would be great if this could be done.
If I have time I will create a pull request.

Cheers

Include the before/after cursor record in the returned edges list.

It would be nice to have the option to specify whether the current cursor record is included in the returned edges list.

Either by:

  • Specifying an includeCurrent / includeCursor boolean property in the query.
  • or by introducing beforeAndIncluding / afterAndIncluding cursor lookups.

It might be as simple to implement as changing Op.gt to Op.gte when includeCursor is set?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.