porsager / porsager/postgres

Timestamp ordering and cursor pagination with 13-digit Unix timestamps

Open
#1,200 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
JavaScript
Stars
8.7k
Forks
374
Avg merge
11d 16h
Merged PRs (30d)
1

Description

im having trouble implementing cursor-based pagination with timestamp ordering in an Apollo Server application using postgres.js.

My posts table has a created_at column of type timestamp. I want to paginate posts using after and before cursors and order the results by created_at.

The after and before values are received as strings containing 13-digit Unix timestamps in milliseconds, for example:

1787758015041

However, the query below does not behave as expected. Depending on the parameters, it either returns no results or returns the same results instead of correctly paginating/sorting by created_at.

async getPosts(
  first?: number | null,
  after?: string | null,
  last?: number | null,
  before?: string | null
) {
  try {
    const result = await this.dbConnection`
      select
        content, created_at, creator, detected_language, id, is_suspended, response_root_post, type
      from posts
      where is_suspended = false and type = 'public'
        ${after ? this.dbConnection`and created_at > to_timestamp(${after})` : this.dbConnection``}
        ${before ? this.dbConnection`and created_at < to_timestamp(${before})` : this.dbConnection``}
      order by created_at ${last ?? before ? this.dbConnection`desc` : this.dbConnection`asc`}
      limit ${first ?? last ?? 10}
    `;
    return result;
  } catch (error) {
    throw new Error(this.dictionary.errors.retrievePosts);
  }
}

I have also tried passing the value explicitly as a number:

to_timestamp(${Number(after)})

and:

to_timestamp(${after})

Neither approach produced the expected pagination/order behavior.

I suspect the main issue may be related to the fact that to_timestamp() expects Unix time in seconds, while my cursors contain Unix time in milliseconds, but I'm not sure what the correct approach should be when using postgres.js.

What I would like to know

Could someone please explain the recommended way to implement this?

Specifically:

How should I convert a 13-digit Unix timestamp in milliseconds to a PostgreSQL timestamp for comparison?
What is the correct way to implement after / before cursor pagination with created_at?
Is there anything incorrect about the ORDER BY expression or the way I'm using postgres.js to dynamically specify ASC / DESC?
Should I use created_at alone as the cursor, or should I include id as a secondary cursor to handle posts with identical timestamps?

Any example query or recommended implementation for postgres.js would be greatly appreciated.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the posted getPosts query and the PostgreSQL to_timestamp and cursor-pagination behavior it relies on; no repository file or test is identified. Determine whether the issue reflects postgres.js behavior or application-level SQL usage, then document the supported timestamp conversion, ordering, cursor semantics, and tie-breaking expectations with a reproducible example.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, nodejs, postgresql
Domain
backend-api-design, databases
Issue type
Documentation
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Needs clarification
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.