Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(core): add cursor-based pagination via em.findByCursor() #3975

Merged
merged 11 commits into from
Jan 31, 2023

Conversation

B4nan
Copy link
Member

@B4nan B4nan commented Jan 27, 2023

As an alternative to the offset based pagination with limit and offset, we can paginate based on a cursor. A cursor is an opaque string that defines specific place in ordered entity graph. You can use em.findByCursor() to access those options. Under the hood, it will call em.find() and em.count() just like the em.findAndCount() method, but will use the cursor options instead.

Supports before, after, first and last options while disallowing limit and offset. Explicit orderBy option is required.

Use first and after for forward pagination, or last and before for backward pagination.

  • first and last are numbers and serve as an alternative to offset, those options are mutually exclusive, use only one at a time
  • before and after specify the previous cursor value, it can be one of the:
    • Cursor instance
    • opaque string provided by startCursor/endCursor properties
    • POJO/entity instance
const currentCursor = await em.findByCursor(User, {}, {
  first: 10,
  after: previousCursor, // cursor instance
  orderBy: { id: 'desc' },
});

// to fetch next page
const nextCursor = await em.findByCursor(User, {}, {
  first: 10,
  after: currentCursor.endCursor, // opaque string
  orderBy: { id: 'desc' },
});

// to fetch next page
const nextCursor2 = await em.findByCursor(User, {}, {
  first: 10,
  after: { id: lastSeenId }, // entity-like POJO
  orderBy: { id: 'desc' },
});

The Cursor object provides following interface:

Cursor<User> {
  items: [
    User { ... },
    User { ... },
    User { ... },
    ...
  ],
  totalCount: 50,
  length: 10,
  startCursor: 'WzRd',
  endCursor: 'WzZd',
  hasPrevPage: true,
  hasNextPage: true,
}

TODO:
- [ ] more tests
- [ ] how to handle relations?
- [ ] docs
@codecov-commenter
Copy link

codecov-commenter commented Jan 28, 2023

Codecov Report

Base: 99.66% // Head: 99.64% // Decreases project coverage by -0.02% ⚠️

Coverage data is based on head (3a3a975) compared to base (c97958e).
Patch coverage: 98.15% of modified lines in pull request are covered.

📣 This organization is not using Codecov’s GitHub App Integration. We recommend you install it so Codecov can continue to function properly for your repositories. Learn more

Additional details and impacted files
@@            Coverage Diff             @@
##               v6    #3975      +/-   ##
==========================================
- Coverage   99.66%   99.64%   -0.02%     
==========================================
  Files         215      216       +1     
  Lines       13683    13833     +150     
  Branches     3208     3256      +48     
==========================================
+ Hits        13637    13784     +147     
- Misses         44       47       +3     
  Partials        2        2              
Impacted Files Coverage Δ
packages/core/src/drivers/IDatabaseDriver.ts 100.00% <ø> (ø)
packages/core/src/typings.ts 100.00% <ø> (ø)
packages/core/src/utils/Cursor.ts 96.55% <96.55%> (ø)
packages/core/src/drivers/DatabaseDriver.ts 99.46% <98.07%> (-0.54%) ⬇️
packages/core/src/EntityManager.ts 97.08% <100.00%> (+0.03%) ⬆️
packages/core/src/entity/EntityRepository.ts 100.00% <100.00%> (ø)
packages/core/src/utils/Utils.ts 99.79% <100.00%> (+<0.01%) ⬆️
packages/core/src/utils/index.ts 100.00% <100.00%> (ø)
packages/knex/src/AbstractSqlDriver.ts 100.00% <100.00%> (ø)
packages/mongodb/src/MongoDriver.ts 100.00% <100.00%> (ø)

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

☔ View full report at Codecov.
📢 Do you have feedback about the report comment? Let us know in this issue.

@productdevbook
Copy link

https://opensource.huntersofbook.com/global/relay-cursor

Great, now I can use it directly with graphql and the library I wrote.

Thank you Martin

@B4nan B4nan marked this pull request as ready for review January 31, 2023 18:06
@B4nan B4nan merged commit 60f0e04 into v6 Jan 31, 2023
@B4nan B4nan deleted the cursor-based-pagination branch January 31, 2023 18:44
B4nan added a commit that referenced this pull request Feb 5, 2023
As an alternative to the offset based pagination with `limit` and
`offset`, we can paginate based on a cursor. A cursor is an opaque
string that defines specific place in ordered entity graph. You can use
`em.findByCursor()` to access those options. Under the hood, it will
call `em.find()` and `em.count()` just like the `em.findAndCount()`
method, but will use the cursor options instead.

Supports `before`, `after`, `first` and `last` options while disallowing
`limit` and `offset`. Explicit `orderBy` option is required.

Use `first` and `after` for forward pagination, or `last` and `before`
for backward pagination.

- `first` and `last` are numbers and serve as an alternative to
`offset`, those options are mutually exclusive, use only one at a time
- `before` and `after` specify the previous cursor value, it can be one
of the:
	- `Cursor` instance
	- opaque string provided by `startCursor/endCursor` properties
	- POJO/entity instance

```ts
const currentCursor = await em.findByCursor(User, {}, {
  first: 10,
  after: previousCursor, // cursor instance
  orderBy: { id: 'desc' },
});

// to fetch next page
const nextCursor = await em.findByCursor(User, {}, {
  first: 10,
  after: currentCursor.endCursor, // opaque string
  orderBy: { id: 'desc' },
});

// to fetch next page
const nextCursor2 = await em.findByCursor(User, {}, {
  first: 10,
  after: { id: lastSeenId }, // entity-like POJO
  orderBy: { id: 'desc' },
});
```

The `Cursor` object provides following interface:

```ts
Cursor<User> {
  items: [
    User { ... },
    User { ... },
    User { ... },
    ...
  ],
  totalCount: 50,
  length: 10,
  startCursor: 'WzRd',
  endCursor: 'WzZd',
  hasPrevPage: true,
  hasNextPage: true,
}
```
B4nan added a commit that referenced this pull request Feb 12, 2023
As an alternative to the offset based pagination with `limit` and
`offset`, we can paginate based on a cursor. A cursor is an opaque
string that defines specific place in ordered entity graph. You can use
`em.findByCursor()` to access those options. Under the hood, it will
call `em.find()` and `em.count()` just like the `em.findAndCount()`
method, but will use the cursor options instead.

Supports `before`, `after`, `first` and `last` options while disallowing
`limit` and `offset`. Explicit `orderBy` option is required.

Use `first` and `after` for forward pagination, or `last` and `before`
for backward pagination.

- `first` and `last` are numbers and serve as an alternative to
`offset`, those options are mutually exclusive, use only one at a time
- `before` and `after` specify the previous cursor value, it can be one
of the:
	- `Cursor` instance
	- opaque string provided by `startCursor/endCursor` properties
	- POJO/entity instance

```ts
const currentCursor = await em.findByCursor(User, {}, {
  first: 10,
  after: previousCursor, // cursor instance
  orderBy: { id: 'desc' },
});

// to fetch next page
const nextCursor = await em.findByCursor(User, {}, {
  first: 10,
  after: currentCursor.endCursor, // opaque string
  orderBy: { id: 'desc' },
});

// to fetch next page
const nextCursor2 = await em.findByCursor(User, {}, {
  first: 10,
  after: { id: lastSeenId }, // entity-like POJO
  orderBy: { id: 'desc' },
});
```

The `Cursor` object provides following interface:

```ts
Cursor<User> {
  items: [
    User { ... },
    User { ... },
    User { ... },
    ...
  ],
  totalCount: 50,
  length: 10,
  startCursor: 'WzRd',
  endCursor: 'WzZd',
  hasPrevPage: true,
  hasNextPage: true,
}
```
B4nan added a commit that referenced this pull request Feb 17, 2023
As an alternative to the offset based pagination with `limit` and
`offset`, we can paginate based on a cursor. A cursor is an opaque
string that defines specific place in ordered entity graph. You can use
`em.findByCursor()` to access those options. Under the hood, it will
call `em.find()` and `em.count()` just like the `em.findAndCount()`
method, but will use the cursor options instead.

Supports `before`, `after`, `first` and `last` options while disallowing
`limit` and `offset`. Explicit `orderBy` option is required.

Use `first` and `after` for forward pagination, or `last` and `before`
for backward pagination.

- `first` and `last` are numbers and serve as an alternative to
`offset`, those options are mutually exclusive, use only one at a time
- `before` and `after` specify the previous cursor value, it can be one
of the:
	- `Cursor` instance
	- opaque string provided by `startCursor/endCursor` properties
	- POJO/entity instance

```ts
const currentCursor = await em.findByCursor(User, {}, {
  first: 10,
  after: previousCursor, // cursor instance
  orderBy: { id: 'desc' },
});

// to fetch next page
const nextCursor = await em.findByCursor(User, {}, {
  first: 10,
  after: currentCursor.endCursor, // opaque string
  orderBy: { id: 'desc' },
});

// to fetch next page
const nextCursor2 = await em.findByCursor(User, {}, {
  first: 10,
  after: { id: lastSeenId }, // entity-like POJO
  orderBy: { id: 'desc' },
});
```

The `Cursor` object provides following interface:

```ts
Cursor<User> {
  items: [
    User { ... },
    User { ... },
    User { ... },
    ...
  ],
  totalCount: 50,
  length: 10,
  startCursor: 'WzRd',
  endCursor: 'WzZd',
  hasPrevPage: true,
  hasNextPage: true,
}
```
B4nan added a commit that referenced this pull request Feb 17, 2023
As an alternative to the offset based pagination with `limit` and
`offset`, we can paginate based on a cursor. A cursor is an opaque
string that defines specific place in ordered entity graph. You can use
`em.findByCursor()` to access those options. Under the hood, it will
call `em.find()` and `em.count()` just like the `em.findAndCount()`
method, but will use the cursor options instead.

Supports `before`, `after`, `first` and `last` options while disallowing
`limit` and `offset`. Explicit `orderBy` option is required.

Use `first` and `after` for forward pagination, or `last` and `before`
for backward pagination.

- `first` and `last` are numbers and serve as an alternative to
`offset`, those options are mutually exclusive, use only one at a time
- `before` and `after` specify the previous cursor value, it can be one
of the:
	- `Cursor` instance
	- opaque string provided by `startCursor/endCursor` properties
	- POJO/entity instance

```ts
const currentCursor = await em.findByCursor(User, {}, {
  first: 10,
  after: previousCursor, // cursor instance
  orderBy: { id: 'desc' },
});

// to fetch next page
const nextCursor = await em.findByCursor(User, {}, {
  first: 10,
  after: currentCursor.endCursor, // opaque string
  orderBy: { id: 'desc' },
});

// to fetch next page
const nextCursor2 = await em.findByCursor(User, {}, {
  first: 10,
  after: { id: lastSeenId }, // entity-like POJO
  orderBy: { id: 'desc' },
});
```

The `Cursor` object provides following interface:

```ts
Cursor<User> {
  items: [
    User { ... },
    User { ... },
    User { ... },
    ...
  ],
  totalCount: 50,
  length: 10,
  startCursor: 'WzRd',
  endCursor: 'WzZd',
  hasPrevPage: true,
  hasNextPage: true,
}
```
B4nan added a commit that referenced this pull request Feb 26, 2023
As an alternative to the offset based pagination with `limit` and
`offset`, we can paginate based on a cursor. A cursor is an opaque
string that defines specific place in ordered entity graph. You can use
`em.findByCursor()` to access those options. Under the hood, it will
call `em.find()` and `em.count()` just like the `em.findAndCount()`
method, but will use the cursor options instead.

Supports `before`, `after`, `first` and `last` options while disallowing
`limit` and `offset`. Explicit `orderBy` option is required.

Use `first` and `after` for forward pagination, or `last` and `before`
for backward pagination.

- `first` and `last` are numbers and serve as an alternative to
`offset`, those options are mutually exclusive, use only one at a time
- `before` and `after` specify the previous cursor value, it can be one
of the:
	- `Cursor` instance
	- opaque string provided by `startCursor/endCursor` properties
	- POJO/entity instance

```ts
const currentCursor = await em.findByCursor(User, {}, {
  first: 10,
  after: previousCursor, // cursor instance
  orderBy: { id: 'desc' },
});

// to fetch next page
const nextCursor = await em.findByCursor(User, {}, {
  first: 10,
  after: currentCursor.endCursor, // opaque string
  orderBy: { id: 'desc' },
});

// to fetch next page
const nextCursor2 = await em.findByCursor(User, {}, {
  first: 10,
  after: { id: lastSeenId }, // entity-like POJO
  orderBy: { id: 'desc' },
});
```

The `Cursor` object provides following interface:

```ts
Cursor<User> {
  items: [
    User { ... },
    User { ... },
    User { ... },
    ...
  ],
  totalCount: 50,
  length: 10,
  startCursor: 'WzRd',
  endCursor: 'WzZd',
  hasPrevPage: true,
  hasNextPage: true,
}
```
B4nan added a commit that referenced this pull request Mar 19, 2023
As an alternative to the offset based pagination with `limit` and
`offset`, we can paginate based on a cursor. A cursor is an opaque
string that defines specific place in ordered entity graph. You can use
`em.findByCursor()` to access those options. Under the hood, it will
call `em.find()` and `em.count()` just like the `em.findAndCount()`
method, but will use the cursor options instead.

Supports `before`, `after`, `first` and `last` options while disallowing
`limit` and `offset`. Explicit `orderBy` option is required.

Use `first` and `after` for forward pagination, or `last` and `before`
for backward pagination.

- `first` and `last` are numbers and serve as an alternative to
`offset`, those options are mutually exclusive, use only one at a time
- `before` and `after` specify the previous cursor value, it can be one
of the:
	- `Cursor` instance
	- opaque string provided by `startCursor/endCursor` properties
	- POJO/entity instance

```ts
const currentCursor = await em.findByCursor(User, {}, {
  first: 10,
  after: previousCursor, // cursor instance
  orderBy: { id: 'desc' },
});

// to fetch next page
const nextCursor = await em.findByCursor(User, {}, {
  first: 10,
  after: currentCursor.endCursor, // opaque string
  orderBy: { id: 'desc' },
});

// to fetch next page
const nextCursor2 = await em.findByCursor(User, {}, {
  first: 10,
  after: { id: lastSeenId }, // entity-like POJO
  orderBy: { id: 'desc' },
});
```

The `Cursor` object provides following interface:

```ts
Cursor<User> {
  items: [
    User { ... },
    User { ... },
    User { ... },
    ...
  ],
  totalCount: 50,
  length: 10,
  startCursor: 'WzRd',
  endCursor: 'WzZd',
  hasPrevPage: true,
  hasNextPage: true,
}
```
B4nan added a commit that referenced this pull request Apr 6, 2023
As an alternative to the offset based pagination with `limit` and
`offset`, we can paginate based on a cursor. A cursor is an opaque
string that defines specific place in ordered entity graph. You can use
`em.findByCursor()` to access those options. Under the hood, it will
call `em.find()` and `em.count()` just like the `em.findAndCount()`
method, but will use the cursor options instead.

Supports `before`, `after`, `first` and `last` options while disallowing
`limit` and `offset`. Explicit `orderBy` option is required.

Use `first` and `after` for forward pagination, or `last` and `before`
for backward pagination.

- `first` and `last` are numbers and serve as an alternative to
`offset`, those options are mutually exclusive, use only one at a time
- `before` and `after` specify the previous cursor value, it can be one
of the:
	- `Cursor` instance
	- opaque string provided by `startCursor/endCursor` properties
	- POJO/entity instance

```ts
const currentCursor = await em.findByCursor(User, {}, {
  first: 10,
  after: previousCursor, // cursor instance
  orderBy: { id: 'desc' },
});

// to fetch next page
const nextCursor = await em.findByCursor(User, {}, {
  first: 10,
  after: currentCursor.endCursor, // opaque string
  orderBy: { id: 'desc' },
});

// to fetch next page
const nextCursor2 = await em.findByCursor(User, {}, {
  first: 10,
  after: { id: lastSeenId }, // entity-like POJO
  orderBy: { id: 'desc' },
});
```

The `Cursor` object provides following interface:

```ts
Cursor<User> {
  items: [
    User { ... },
    User { ... },
    User { ... },
    ...
  ],
  totalCount: 50,
  length: 10,
  startCursor: 'WzRd',
  endCursor: 'WzZd',
  hasPrevPage: true,
  hasNextPage: true,
}
```
B4nan added a commit that referenced this pull request Apr 10, 2023
As an alternative to the offset based pagination with `limit` and
`offset`, we can paginate based on a cursor. A cursor is an opaque
string that defines specific place in ordered entity graph. You can use
`em.findByCursor()` to access those options. Under the hood, it will
call `em.find()` and `em.count()` just like the `em.findAndCount()`
method, but will use the cursor options instead.

Supports `before`, `after`, `first` and `last` options while disallowing
`limit` and `offset`. Explicit `orderBy` option is required.

Use `first` and `after` for forward pagination, or `last` and `before`
for backward pagination.

- `first` and `last` are numbers and serve as an alternative to
`offset`, those options are mutually exclusive, use only one at a time
- `before` and `after` specify the previous cursor value, it can be one
of the:
	- `Cursor` instance
	- opaque string provided by `startCursor/endCursor` properties
	- POJO/entity instance

```ts
const currentCursor = await em.findByCursor(User, {}, {
  first: 10,
  after: previousCursor, // cursor instance
  orderBy: { id: 'desc' },
});

// to fetch next page
const nextCursor = await em.findByCursor(User, {}, {
  first: 10,
  after: currentCursor.endCursor, // opaque string
  orderBy: { id: 'desc' },
});

// to fetch next page
const nextCursor2 = await em.findByCursor(User, {}, {
  first: 10,
  after: { id: lastSeenId }, // entity-like POJO
  orderBy: { id: 'desc' },
});
```

The `Cursor` object provides following interface:

```ts
Cursor<User> {
  items: [
    User { ... },
    User { ... },
    User { ... },
    ...
  ],
  totalCount: 50,
  length: 10,
  startCursor: 'WzRd',
  endCursor: 'WzZd',
  hasPrevPage: true,
  hasNextPage: true,
}
```
B4nan added a commit that referenced this pull request Apr 12, 2023
As an alternative to the offset based pagination with `limit` and
`offset`, we can paginate based on a cursor. A cursor is an opaque
string that defines specific place in ordered entity graph. You can use
`em.findByCursor()` to access those options. Under the hood, it will
call `em.find()` and `em.count()` just like the `em.findAndCount()`
method, but will use the cursor options instead.

Supports `before`, `after`, `first` and `last` options while disallowing
`limit` and `offset`. Explicit `orderBy` option is required.

Use `first` and `after` for forward pagination, or `last` and `before`
for backward pagination.

- `first` and `last` are numbers and serve as an alternative to
`offset`, those options are mutually exclusive, use only one at a time
- `before` and `after` specify the previous cursor value, it can be one
of the:
	- `Cursor` instance
	- opaque string provided by `startCursor/endCursor` properties
	- POJO/entity instance

```ts
const currentCursor = await em.findByCursor(User, {}, {
  first: 10,
  after: previousCursor, // cursor instance
  orderBy: { id: 'desc' },
});

// to fetch next page
const nextCursor = await em.findByCursor(User, {}, {
  first: 10,
  after: currentCursor.endCursor, // opaque string
  orderBy: { id: 'desc' },
});

// to fetch next page
const nextCursor2 = await em.findByCursor(User, {}, {
  first: 10,
  after: { id: lastSeenId }, // entity-like POJO
  orderBy: { id: 'desc' },
});
```

The `Cursor` object provides following interface:

```ts
Cursor<User> {
  items: [
    User { ... },
    User { ... },
    User { ... },
    ...
  ],
  totalCount: 50,
  length: 10,
  startCursor: 'WzRd',
  endCursor: 'WzZd',
  hasPrevPage: true,
  hasNextPage: true,
}
```
B4nan added a commit that referenced this pull request Apr 26, 2023
As an alternative to the offset based pagination with `limit` and
`offset`, we can paginate based on a cursor. A cursor is an opaque
string that defines specific place in ordered entity graph. You can use
`em.findByCursor()` to access those options. Under the hood, it will
call `em.find()` and `em.count()` just like the `em.findAndCount()`
method, but will use the cursor options instead.

Supports `before`, `after`, `first` and `last` options while disallowing
`limit` and `offset`. Explicit `orderBy` option is required.

Use `first` and `after` for forward pagination, or `last` and `before`
for backward pagination.

- `first` and `last` are numbers and serve as an alternative to
`offset`, those options are mutually exclusive, use only one at a time
- `before` and `after` specify the previous cursor value, it can be one
of the:
	- `Cursor` instance
	- opaque string provided by `startCursor/endCursor` properties
	- POJO/entity instance

```ts
const currentCursor = await em.findByCursor(User, {}, {
  first: 10,
  after: previousCursor, // cursor instance
  orderBy: { id: 'desc' },
});

// to fetch next page
const nextCursor = await em.findByCursor(User, {}, {
  first: 10,
  after: currentCursor.endCursor, // opaque string
  orderBy: { id: 'desc' },
});

// to fetch next page
const nextCursor2 = await em.findByCursor(User, {}, {
  first: 10,
  after: { id: lastSeenId }, // entity-like POJO
  orderBy: { id: 'desc' },
});
```

The `Cursor` object provides following interface:

```ts
Cursor<User> {
  items: [
    User { ... },
    User { ... },
    User { ... },
    ...
  ],
  totalCount: 50,
  length: 10,
  startCursor: 'WzRd',
  endCursor: 'WzZd',
  hasPrevPage: true,
  hasNextPage: true,
}
```
jsprw pushed a commit to jsprw/mikro-orm-full-text-operators that referenced this pull request May 7, 2023
…o-orm#3975)

As an alternative to the offset based pagination with `limit` and
`offset`, we can paginate based on a cursor. A cursor is an opaque
string that defines specific place in ordered entity graph. You can use
`em.findByCursor()` to access those options. Under the hood, it will
call `em.find()` and `em.count()` just like the `em.findAndCount()`
method, but will use the cursor options instead.

Supports `before`, `after`, `first` and `last` options while disallowing
`limit` and `offset`. Explicit `orderBy` option is required.

Use `first` and `after` for forward pagination, or `last` and `before`
for backward pagination.

- `first` and `last` are numbers and serve as an alternative to
`offset`, those options are mutually exclusive, use only one at a time
- `before` and `after` specify the previous cursor value, it can be one
of the:
	- `Cursor` instance
	- opaque string provided by `startCursor/endCursor` properties
	- POJO/entity instance

```ts
const currentCursor = await em.findByCursor(User, {}, {
  first: 10,
  after: previousCursor, // cursor instance
  orderBy: { id: 'desc' },
});

// to fetch next page
const nextCursor = await em.findByCursor(User, {}, {
  first: 10,
  after: currentCursor.endCursor, // opaque string
  orderBy: { id: 'desc' },
});

// to fetch next page
const nextCursor2 = await em.findByCursor(User, {}, {
  first: 10,
  after: { id: lastSeenId }, // entity-like POJO
  orderBy: { id: 'desc' },
});
```

The `Cursor` object provides following interface:

```ts
Cursor<User> {
  items: [
    User { ... },
    User { ... },
    User { ... },
    ...
  ],
  totalCount: 50,
  length: 10,
  startCursor: 'WzRd',
  endCursor: 'WzZd',
  hasPrevPage: true,
  hasNextPage: true,
}
```
jsprw pushed a commit to jsprw/mikro-orm-full-text-operators that referenced this pull request May 7, 2023
…o-orm#3975)

As an alternative to the offset based pagination with `limit` and
`offset`, we can paginate based on a cursor. A cursor is an opaque
string that defines specific place in ordered entity graph. You can use
`em.findByCursor()` to access those options. Under the hood, it will
call `em.find()` and `em.count()` just like the `em.findAndCount()`
method, but will use the cursor options instead.

Supports `before`, `after`, `first` and `last` options while disallowing
`limit` and `offset`. Explicit `orderBy` option is required.

Use `first` and `after` for forward pagination, or `last` and `before`
for backward pagination.

- `first` and `last` are numbers and serve as an alternative to
`offset`, those options are mutually exclusive, use only one at a time
- `before` and `after` specify the previous cursor value, it can be one
of the:
	- `Cursor` instance
	- opaque string provided by `startCursor/endCursor` properties
	- POJO/entity instance

```ts
const currentCursor = await em.findByCursor(User, {}, {
  first: 10,
  after: previousCursor, // cursor instance
  orderBy: { id: 'desc' },
});

// to fetch next page
const nextCursor = await em.findByCursor(User, {}, {
  first: 10,
  after: currentCursor.endCursor, // opaque string
  orderBy: { id: 'desc' },
});

// to fetch next page
const nextCursor2 = await em.findByCursor(User, {}, {
  first: 10,
  after: { id: lastSeenId }, // entity-like POJO
  orderBy: { id: 'desc' },
});
```

The `Cursor` object provides following interface:

```ts
Cursor<User> {
  items: [
    User { ... },
    User { ... },
    User { ... },
    ...
  ],
  totalCount: 50,
  length: 10,
  startCursor: 'WzRd',
  endCursor: 'WzZd',
  hasPrevPage: true,
  hasNextPage: true,
}
```
B4nan added a commit that referenced this pull request May 14, 2023
As an alternative to the offset based pagination with `limit` and
`offset`, we can paginate based on a cursor. A cursor is an opaque
string that defines specific place in ordered entity graph. You can use
`em.findByCursor()` to access those options. Under the hood, it will
call `em.find()` and `em.count()` just like the `em.findAndCount()`
method, but will use the cursor options instead.

Supports `before`, `after`, `first` and `last` options while disallowing
`limit` and `offset`. Explicit `orderBy` option is required.

Use `first` and `after` for forward pagination, or `last` and `before`
for backward pagination.

- `first` and `last` are numbers and serve as an alternative to
`offset`, those options are mutually exclusive, use only one at a time
- `before` and `after` specify the previous cursor value, it can be one
of the:
	- `Cursor` instance
	- opaque string provided by `startCursor/endCursor` properties
	- POJO/entity instance

```ts
const currentCursor = await em.findByCursor(User, {}, {
  first: 10,
  after: previousCursor, // cursor instance
  orderBy: { id: 'desc' },
});

// to fetch next page
const nextCursor = await em.findByCursor(User, {}, {
  first: 10,
  after: currentCursor.endCursor, // opaque string
  orderBy: { id: 'desc' },
});

// to fetch next page
const nextCursor2 = await em.findByCursor(User, {}, {
  first: 10,
  after: { id: lastSeenId }, // entity-like POJO
  orderBy: { id: 'desc' },
});
```

The `Cursor` object provides following interface:

```ts
Cursor<User> {
  items: [
    User { ... },
    User { ... },
    User { ... },
    ...
  ],
  totalCount: 50,
  length: 10,
  startCursor: 'WzRd',
  endCursor: 'WzZd',
  hasPrevPage: true,
  hasNextPage: true,
}
```
B4nan added a commit that referenced this pull request May 14, 2023
As an alternative to the offset based pagination with `limit` and
`offset`, we can paginate based on a cursor. A cursor is an opaque
string that defines specific place in ordered entity graph. You can use
`em.findByCursor()` to access those options. Under the hood, it will
call `em.find()` and `em.count()` just like the `em.findAndCount()`
method, but will use the cursor options instead.

Supports `before`, `after`, `first` and `last` options while disallowing
`limit` and `offset`. Explicit `orderBy` option is required.

Use `first` and `after` for forward pagination, or `last` and `before`
for backward pagination.

- `first` and `last` are numbers and serve as an alternative to
`offset`, those options are mutually exclusive, use only one at a time
- `before` and `after` specify the previous cursor value, it can be one
of the:
	- `Cursor` instance
	- opaque string provided by `startCursor/endCursor` properties
	- POJO/entity instance

```ts
const currentCursor = await em.findByCursor(User, {}, {
  first: 10,
  after: previousCursor, // cursor instance
  orderBy: { id: 'desc' },
});

// to fetch next page
const nextCursor = await em.findByCursor(User, {}, {
  first: 10,
  after: currentCursor.endCursor, // opaque string
  orderBy: { id: 'desc' },
});

// to fetch next page
const nextCursor2 = await em.findByCursor(User, {}, {
  first: 10,
  after: { id: lastSeenId }, // entity-like POJO
  orderBy: { id: 'desc' },
});
```

The `Cursor` object provides following interface:

```ts
Cursor<User> {
  items: [
    User { ... },
    User { ... },
    User { ... },
    ...
  ],
  totalCount: 50,
  length: 10,
  startCursor: 'WzRd',
  endCursor: 'WzZd',
  hasPrevPage: true,
  hasNextPage: true,
}
```
B4nan added a commit that referenced this pull request May 24, 2023
As an alternative to the offset based pagination with `limit` and
`offset`, we can paginate based on a cursor. A cursor is an opaque
string that defines specific place in ordered entity graph. You can use
`em.findByCursor()` to access those options. Under the hood, it will
call `em.find()` and `em.count()` just like the `em.findAndCount()`
method, but will use the cursor options instead.

Supports `before`, `after`, `first` and `last` options while disallowing
`limit` and `offset`. Explicit `orderBy` option is required.

Use `first` and `after` for forward pagination, or `last` and `before`
for backward pagination.

- `first` and `last` are numbers and serve as an alternative to
`offset`, those options are mutually exclusive, use only one at a time
- `before` and `after` specify the previous cursor value, it can be one
of the:
	- `Cursor` instance
	- opaque string provided by `startCursor/endCursor` properties
	- POJO/entity instance

```ts
const currentCursor = await em.findByCursor(User, {}, {
  first: 10,
  after: previousCursor, // cursor instance
  orderBy: { id: 'desc' },
});

// to fetch next page
const nextCursor = await em.findByCursor(User, {}, {
  first: 10,
  after: currentCursor.endCursor, // opaque string
  orderBy: { id: 'desc' },
});

// to fetch next page
const nextCursor2 = await em.findByCursor(User, {}, {
  first: 10,
  after: { id: lastSeenId }, // entity-like POJO
  orderBy: { id: 'desc' },
});
```

The `Cursor` object provides following interface:

```ts
Cursor<User> {
  items: [
    User { ... },
    User { ... },
    User { ... },
    ...
  ],
  totalCount: 50,
  length: 10,
  startCursor: 'WzRd',
  endCursor: 'WzZd',
  hasPrevPage: true,
  hasNextPage: true,
}
```
B4nan added a commit that referenced this pull request May 26, 2023
As an alternative to the offset based pagination with `limit` and
`offset`, we can paginate based on a cursor. A cursor is an opaque
string that defines specific place in ordered entity graph. You can use
`em.findByCursor()` to access those options. Under the hood, it will
call `em.find()` and `em.count()` just like the `em.findAndCount()`
method, but will use the cursor options instead.

Supports `before`, `after`, `first` and `last` options while disallowing
`limit` and `offset`. Explicit `orderBy` option is required.

Use `first` and `after` for forward pagination, or `last` and `before`
for backward pagination.

- `first` and `last` are numbers and serve as an alternative to
`offset`, those options are mutually exclusive, use only one at a time
- `before` and `after` specify the previous cursor value, it can be one
of the:
	- `Cursor` instance
	- opaque string provided by `startCursor/endCursor` properties
	- POJO/entity instance

```ts
const currentCursor = await em.findByCursor(User, {}, {
  first: 10,
  after: previousCursor, // cursor instance
  orderBy: { id: 'desc' },
});

// to fetch next page
const nextCursor = await em.findByCursor(User, {}, {
  first: 10,
  after: currentCursor.endCursor, // opaque string
  orderBy: { id: 'desc' },
});

// to fetch next page
const nextCursor2 = await em.findByCursor(User, {}, {
  first: 10,
  after: { id: lastSeenId }, // entity-like POJO
  orderBy: { id: 'desc' },
});
```

The `Cursor` object provides following interface:

```ts
Cursor<User> {
  items: [
    User { ... },
    User { ... },
    User { ... },
    ...
  ],
  totalCount: 50,
  length: 10,
  startCursor: 'WzRd',
  endCursor: 'WzZd',
  hasPrevPage: true,
  hasNextPage: true,
}
```
B4nan added a commit that referenced this pull request Jun 11, 2023
As an alternative to the offset based pagination with `limit` and
`offset`, we can paginate based on a cursor. A cursor is an opaque
string that defines specific place in ordered entity graph. You can use
`em.findByCursor()` to access those options. Under the hood, it will
call `em.find()` and `em.count()` just like the `em.findAndCount()`
method, but will use the cursor options instead.

Supports `before`, `after`, `first` and `last` options while disallowing
`limit` and `offset`. Explicit `orderBy` option is required.

Use `first` and `after` for forward pagination, or `last` and `before`
for backward pagination.

- `first` and `last` are numbers and serve as an alternative to
`offset`, those options are mutually exclusive, use only one at a time
- `before` and `after` specify the previous cursor value, it can be one
of the:
	- `Cursor` instance
	- opaque string provided by `startCursor/endCursor` properties
	- POJO/entity instance

```ts
const currentCursor = await em.findByCursor(User, {}, {
  first: 10,
  after: previousCursor, // cursor instance
  orderBy: { id: 'desc' },
});

// to fetch next page
const nextCursor = await em.findByCursor(User, {}, {
  first: 10,
  after: currentCursor.endCursor, // opaque string
  orderBy: { id: 'desc' },
});

// to fetch next page
const nextCursor2 = await em.findByCursor(User, {}, {
  first: 10,
  after: { id: lastSeenId }, // entity-like POJO
  orderBy: { id: 'desc' },
});
```

The `Cursor` object provides following interface:

```ts
Cursor<User> {
  items: [
    User { ... },
    User { ... },
    User { ... },
    ...
  ],
  totalCount: 50,
  length: 10,
  startCursor: 'WzRd',
  endCursor: 'WzZd',
  hasPrevPage: true,
  hasNextPage: true,
}
```
@B4nan B4nan mentioned this pull request Jul 24, 2023
@bakura10
Copy link

bakura10 commented Aug 7, 2023

This is a wonderful feature. Just a quick feedback @B4nan : in most API I have used with cursor based, the totalCount is not returned (what we are interested is only knowing if there is a next or prev page), which avoids a potentially costly SQL count. What about removing the count by default, and add an optional {includeCount: true} option to the cursor based pagination, for users who need the count?

@B4nan
Copy link
Member Author

B4nan commented Aug 7, 2023

I am not sure if this should be disabled by default, but I am open to having an option to control it.

B4nan added a commit that referenced this pull request Sep 10, 2023
As an alternative to the offset based pagination with `limit` and
`offset`, we can paginate based on a cursor. A cursor is an opaque
string that defines specific place in ordered entity graph. You can use
`em.findByCursor()` to access those options. Under the hood, it will
call `em.find()` and `em.count()` just like the `em.findAndCount()`
method, but will use the cursor options instead.

Supports `before`, `after`, `first` and `last` options while disallowing
`limit` and `offset`. Explicit `orderBy` option is required.

Use `first` and `after` for forward pagination, or `last` and `before`
for backward pagination.

- `first` and `last` are numbers and serve as an alternative to
`offset`, those options are mutually exclusive, use only one at a time
- `before` and `after` specify the previous cursor value, it can be one
of the:
	- `Cursor` instance
	- opaque string provided by `startCursor/endCursor` properties
	- POJO/entity instance

```ts
const currentCursor = await em.findByCursor(User, {}, {
  first: 10,
  after: previousCursor, // cursor instance
  orderBy: { id: 'desc' },
});

// to fetch next page
const nextCursor = await em.findByCursor(User, {}, {
  first: 10,
  after: currentCursor.endCursor, // opaque string
  orderBy: { id: 'desc' },
});

// to fetch next page
const nextCursor2 = await em.findByCursor(User, {}, {
  first: 10,
  after: { id: lastSeenId }, // entity-like POJO
  orderBy: { id: 'desc' },
});
```

The `Cursor` object provides following interface:

```ts
Cursor<User> {
  items: [
    User { ... },
    User { ... },
    User { ... },
    ...
  ],
  totalCount: 50,
  length: 10,
  startCursor: 'WzRd',
  endCursor: 'WzZd',
  hasPrevPage: true,
  hasNextPage: true,
}
```
B4nan added a commit that referenced this pull request Sep 20, 2023
As an alternative to the offset based pagination with `limit` and
`offset`, we can paginate based on a cursor. A cursor is an opaque
string that defines specific place in ordered entity graph. You can use
`em.findByCursor()` to access those options. Under the hood, it will
call `em.find()` and `em.count()` just like the `em.findAndCount()`
method, but will use the cursor options instead.

Supports `before`, `after`, `first` and `last` options while disallowing
`limit` and `offset`. Explicit `orderBy` option is required.

Use `first` and `after` for forward pagination, or `last` and `before`
for backward pagination.

- `first` and `last` are numbers and serve as an alternative to
`offset`, those options are mutually exclusive, use only one at a time
- `before` and `after` specify the previous cursor value, it can be one
of the:
	- `Cursor` instance
	- opaque string provided by `startCursor/endCursor` properties
	- POJO/entity instance

```ts
const currentCursor = await em.findByCursor(User, {}, {
  first: 10,
  after: previousCursor, // cursor instance
  orderBy: { id: 'desc' },
});

// to fetch next page
const nextCursor = await em.findByCursor(User, {}, {
  first: 10,
  after: currentCursor.endCursor, // opaque string
  orderBy: { id: 'desc' },
});

// to fetch next page
const nextCursor2 = await em.findByCursor(User, {}, {
  first: 10,
  after: { id: lastSeenId }, // entity-like POJO
  orderBy: { id: 'desc' },
});
```

The `Cursor` object provides following interface:

```ts
Cursor<User> {
  items: [
    User { ... },
    User { ... },
    User { ... },
    ...
  ],
  totalCount: 50,
  length: 10,
  startCursor: 'WzRd',
  endCursor: 'WzZd',
  hasPrevPage: true,
  hasNextPage: true,
}
```
B4nan added a commit that referenced this pull request Sep 24, 2023
As an alternative to the offset based pagination with `limit` and
`offset`, we can paginate based on a cursor. A cursor is an opaque
string that defines specific place in ordered entity graph. You can use
`em.findByCursor()` to access those options. Under the hood, it will
call `em.find()` and `em.count()` just like the `em.findAndCount()`
method, but will use the cursor options instead.

Supports `before`, `after`, `first` and `last` options while disallowing
`limit` and `offset`. Explicit `orderBy` option is required.

Use `first` and `after` for forward pagination, or `last` and `before`
for backward pagination.

- `first` and `last` are numbers and serve as an alternative to
`offset`, those options are mutually exclusive, use only one at a time
- `before` and `after` specify the previous cursor value, it can be one
of the:
	- `Cursor` instance
	- opaque string provided by `startCursor/endCursor` properties
	- POJO/entity instance

```ts
const currentCursor = await em.findByCursor(User, {}, {
  first: 10,
  after: previousCursor, // cursor instance
  orderBy: { id: 'desc' },
});

// to fetch next page
const nextCursor = await em.findByCursor(User, {}, {
  first: 10,
  after: currentCursor.endCursor, // opaque string
  orderBy: { id: 'desc' },
});

// to fetch next page
const nextCursor2 = await em.findByCursor(User, {}, {
  first: 10,
  after: { id: lastSeenId }, // entity-like POJO
  orderBy: { id: 'desc' },
});
```

The `Cursor` object provides following interface:

```ts
Cursor<User> {
  items: [
    User { ... },
    User { ... },
    User { ... },
    ...
  ],
  totalCount: 50,
  length: 10,
  startCursor: 'WzRd',
  endCursor: 'WzZd',
  hasPrevPage: true,
  hasNextPage: true,
}
```
B4nan added a commit that referenced this pull request Sep 30, 2023
As an alternative to the offset based pagination with `limit` and
`offset`, we can paginate based on a cursor. A cursor is an opaque
string that defines specific place in ordered entity graph. You can use
`em.findByCursor()` to access those options. Under the hood, it will
call `em.find()` and `em.count()` just like the `em.findAndCount()`
method, but will use the cursor options instead.

Supports `before`, `after`, `first` and `last` options while disallowing
`limit` and `offset`. Explicit `orderBy` option is required.

Use `first` and `after` for forward pagination, or `last` and `before`
for backward pagination.

- `first` and `last` are numbers and serve as an alternative to
`offset`, those options are mutually exclusive, use only one at a time
- `before` and `after` specify the previous cursor value, it can be one
of the:
	- `Cursor` instance
	- opaque string provided by `startCursor/endCursor` properties
	- POJO/entity instance

```ts
const currentCursor = await em.findByCursor(User, {}, {
  first: 10,
  after: previousCursor, // cursor instance
  orderBy: { id: 'desc' },
});

// to fetch next page
const nextCursor = await em.findByCursor(User, {}, {
  first: 10,
  after: currentCursor.endCursor, // opaque string
  orderBy: { id: 'desc' },
});

// to fetch next page
const nextCursor2 = await em.findByCursor(User, {}, {
  first: 10,
  after: { id: lastSeenId }, // entity-like POJO
  orderBy: { id: 'desc' },
});
```

The `Cursor` object provides following interface:

```ts
Cursor<User> {
  items: [
    User { ... },
    User { ... },
    User { ... },
    ...
  ],
  totalCount: 50,
  length: 10,
  startCursor: 'WzRd',
  endCursor: 'WzZd',
  hasPrevPage: true,
  hasNextPage: true,
}
```
@B4nan B4nan mentioned this pull request Sep 30, 2023
22 tasks
B4nan added a commit that referenced this pull request Oct 2, 2023
As an alternative to the offset based pagination with `limit` and
`offset`, we can paginate based on a cursor. A cursor is an opaque
string that defines specific place in ordered entity graph. You can use
`em.findByCursor()` to access those options. Under the hood, it will
call `em.find()` and `em.count()` just like the `em.findAndCount()`
method, but will use the cursor options instead.

Supports `before`, `after`, `first` and `last` options while disallowing
`limit` and `offset`. Explicit `orderBy` option is required.

Use `first` and `after` for forward pagination, or `last` and `before`
for backward pagination.

- `first` and `last` are numbers and serve as an alternative to
`offset`, those options are mutually exclusive, use only one at a time
- `before` and `after` specify the previous cursor value, it can be one
of the:
	- `Cursor` instance
	- opaque string provided by `startCursor/endCursor` properties
	- POJO/entity instance

```ts
const currentCursor = await em.findByCursor(User, {}, {
  first: 10,
  after: previousCursor, // cursor instance
  orderBy: { id: 'desc' },
});

// to fetch next page
const nextCursor = await em.findByCursor(User, {}, {
  first: 10,
  after: currentCursor.endCursor, // opaque string
  orderBy: { id: 'desc' },
});

// to fetch next page
const nextCursor2 = await em.findByCursor(User, {}, {
  first: 10,
  after: { id: lastSeenId }, // entity-like POJO
  orderBy: { id: 'desc' },
});
```

The `Cursor` object provides following interface:

```ts
Cursor<User> {
  items: [
    User { ... },
    User { ... },
    User { ... },
    ...
  ],
  totalCount: 50,
  length: 10,
  startCursor: 'WzRd',
  endCursor: 'WzZd',
  hasPrevPage: true,
  hasNextPage: true,
}
```
B4nan added a commit that referenced this pull request Oct 17, 2023
As an alternative to the offset based pagination with `limit` and
`offset`, we can paginate based on a cursor. A cursor is an opaque
string that defines specific place in ordered entity graph. You can use
`em.findByCursor()` to access those options. Under the hood, it will
call `em.find()` and `em.count()` just like the `em.findAndCount()`
method, but will use the cursor options instead.

Supports `before`, `after`, `first` and `last` options while disallowing
`limit` and `offset`. Explicit `orderBy` option is required.

Use `first` and `after` for forward pagination, or `last` and `before`
for backward pagination.

- `first` and `last` are numbers and serve as an alternative to
`offset`, those options are mutually exclusive, use only one at a time
- `before` and `after` specify the previous cursor value, it can be one
of the:
	- `Cursor` instance
	- opaque string provided by `startCursor/endCursor` properties
	- POJO/entity instance

```ts
const currentCursor = await em.findByCursor(User, {}, {
  first: 10,
  after: previousCursor, // cursor instance
  orderBy: { id: 'desc' },
});

// to fetch next page
const nextCursor = await em.findByCursor(User, {}, {
  first: 10,
  after: currentCursor.endCursor, // opaque string
  orderBy: { id: 'desc' },
});

// to fetch next page
const nextCursor2 = await em.findByCursor(User, {}, {
  first: 10,
  after: { id: lastSeenId }, // entity-like POJO
  orderBy: { id: 'desc' },
});
```

The `Cursor` object provides following interface:

```ts
Cursor<User> {
  items: [
    User { ... },
    User { ... },
    User { ... },
    ...
  ],
  totalCount: 50,
  length: 10,
  startCursor: 'WzRd',
  endCursor: 'WzZd',
  hasPrevPage: true,
  hasNextPage: true,
}
```
B4nan added a commit that referenced this pull request Oct 21, 2023
As an alternative to the offset based pagination with `limit` and
`offset`, we can paginate based on a cursor. A cursor is an opaque
string that defines specific place in ordered entity graph. You can use
`em.findByCursor()` to access those options. Under the hood, it will
call `em.find()` and `em.count()` just like the `em.findAndCount()`
method, but will use the cursor options instead.

Supports `before`, `after`, `first` and `last` options while disallowing
`limit` and `offset`. Explicit `orderBy` option is required.

Use `first` and `after` for forward pagination, or `last` and `before`
for backward pagination.

- `first` and `last` are numbers and serve as an alternative to
`offset`, those options are mutually exclusive, use only one at a time
- `before` and `after` specify the previous cursor value, it can be one
of the:
	- `Cursor` instance
	- opaque string provided by `startCursor/endCursor` properties
	- POJO/entity instance

```ts
const currentCursor = await em.findByCursor(User, {}, {
  first: 10,
  after: previousCursor, // cursor instance
  orderBy: { id: 'desc' },
});

// to fetch next page
const nextCursor = await em.findByCursor(User, {}, {
  first: 10,
  after: currentCursor.endCursor, // opaque string
  orderBy: { id: 'desc' },
});

// to fetch next page
const nextCursor2 = await em.findByCursor(User, {}, {
  first: 10,
  after: { id: lastSeenId }, // entity-like POJO
  orderBy: { id: 'desc' },
});
```

The `Cursor` object provides following interface:

```ts
Cursor<User> {
  items: [
    User { ... },
    User { ... },
    User { ... },
    ...
  ],
  totalCount: 50,
  length: 10,
  startCursor: 'WzRd',
  endCursor: 'WzZd',
  hasPrevPage: true,
  hasNextPage: true,
}
```
B4nan added a commit that referenced this pull request Oct 25, 2023
As an alternative to the offset based pagination with `limit` and
`offset`, we can paginate based on a cursor. A cursor is an opaque
string that defines specific place in ordered entity graph. You can use
`em.findByCursor()` to access those options. Under the hood, it will
call `em.find()` and `em.count()` just like the `em.findAndCount()`
method, but will use the cursor options instead.

Supports `before`, `after`, `first` and `last` options while disallowing
`limit` and `offset`. Explicit `orderBy` option is required.

Use `first` and `after` for forward pagination, or `last` and `before`
for backward pagination.

- `first` and `last` are numbers and serve as an alternative to
`offset`, those options are mutually exclusive, use only one at a time
- `before` and `after` specify the previous cursor value, it can be one
of the:
	- `Cursor` instance
	- opaque string provided by `startCursor/endCursor` properties
	- POJO/entity instance

```ts
const currentCursor = await em.findByCursor(User, {}, {
  first: 10,
  after: previousCursor, // cursor instance
  orderBy: { id: 'desc' },
});

// to fetch next page
const nextCursor = await em.findByCursor(User, {}, {
  first: 10,
  after: currentCursor.endCursor, // opaque string
  orderBy: { id: 'desc' },
});

// to fetch next page
const nextCursor2 = await em.findByCursor(User, {}, {
  first: 10,
  after: { id: lastSeenId }, // entity-like POJO
  orderBy: { id: 'desc' },
});
```

The `Cursor` object provides following interface:

```ts
Cursor<User> {
  items: [
    User { ... },
    User { ... },
    User { ... },
    ...
  ],
  totalCount: 50,
  length: 10,
  startCursor: 'WzRd',
  endCursor: 'WzZd',
  hasPrevPage: true,
  hasNextPage: true,
}
```
B4nan added a commit that referenced this pull request Nov 2, 2023
As an alternative to the offset based pagination with `limit` and
`offset`, we can paginate based on a cursor. A cursor is an opaque
string that defines specific place in ordered entity graph. You can use
`em.findByCursor()` to access those options. Under the hood, it will
call `em.find()` and `em.count()` just like the `em.findAndCount()`
method, but will use the cursor options instead.

Supports `before`, `after`, `first` and `last` options while disallowing
`limit` and `offset`. Explicit `orderBy` option is required.

Use `first` and `after` for forward pagination, or `last` and `before`
for backward pagination.

- `first` and `last` are numbers and serve as an alternative to
`offset`, those options are mutually exclusive, use only one at a time
- `before` and `after` specify the previous cursor value, it can be one
of the:
	- `Cursor` instance
	- opaque string provided by `startCursor/endCursor` properties
	- POJO/entity instance

```ts
const currentCursor = await em.findByCursor(User, {}, {
  first: 10,
  after: previousCursor, // cursor instance
  orderBy: { id: 'desc' },
});

// to fetch next page
const nextCursor = await em.findByCursor(User, {}, {
  first: 10,
  after: currentCursor.endCursor, // opaque string
  orderBy: { id: 'desc' },
});

// to fetch next page
const nextCursor2 = await em.findByCursor(User, {}, {
  first: 10,
  after: { id: lastSeenId }, // entity-like POJO
  orderBy: { id: 'desc' },
});
```

The `Cursor` object provides following interface:

```ts
Cursor<User> {
  items: [
    User { ... },
    User { ... },
    User { ... },
    ...
  ],
  totalCount: 50,
  length: 10,
  startCursor: 'WzRd',
  endCursor: 'WzZd',
  hasPrevPage: true,
  hasNextPage: true,
}
```
B4nan added a commit that referenced this pull request Nov 5, 2023
As an alternative to the offset based pagination with `limit` and
`offset`, we can paginate based on a cursor. A cursor is an opaque
string that defines specific place in ordered entity graph. You can use
`em.findByCursor()` to access those options. Under the hood, it will
call `em.find()` and `em.count()` just like the `em.findAndCount()`
method, but will use the cursor options instead.

Supports `before`, `after`, `first` and `last` options while disallowing
`limit` and `offset`. Explicit `orderBy` option is required.

Use `first` and `after` for forward pagination, or `last` and `before`
for backward pagination.

- `first` and `last` are numbers and serve as an alternative to
`offset`, those options are mutually exclusive, use only one at a time
- `before` and `after` specify the previous cursor value, it can be one
of the:
	- `Cursor` instance
	- opaque string provided by `startCursor/endCursor` properties
	- POJO/entity instance

```ts
const currentCursor = await em.findByCursor(User, {}, {
  first: 10,
  after: previousCursor, // cursor instance
  orderBy: { id: 'desc' },
});

// to fetch next page
const nextCursor = await em.findByCursor(User, {}, {
  first: 10,
  after: currentCursor.endCursor, // opaque string
  orderBy: { id: 'desc' },
});

// to fetch next page
const nextCursor2 = await em.findByCursor(User, {}, {
  first: 10,
  after: { id: lastSeenId }, // entity-like POJO
  orderBy: { id: 'desc' },
});
```

The `Cursor` object provides following interface:

```ts
Cursor<User> {
  items: [
    User { ... },
    User { ... },
    User { ... },
    ...
  ],
  totalCount: 50,
  length: 10,
  startCursor: 'WzRd',
  endCursor: 'WzZd',
  hasPrevPage: true,
  hasNextPage: true,
}
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants