Postun altında 5000 şərh yığılıb. Onları bir cavabda göndərmək olmaz: server, şəbəkə və brauzer boğular. Kitabla necə rəftar edirlərsə, eləcə: səhifə-səhifə oxuyurlar, əlfəcin isə harada dayandığınızı göstərir. Bu, səhifələmədir, əlfəcinin adı isə cursor-dur. İkinci məsələ də var: siz oxuyarkən postun altında yeni şərhlər görünür. Serverdən hər beş saniyədən bir soruşmaq (polling) yorucudur, zəngi eşitmək daha yaxşıdır. Bu, subscription-dur (abunə). Bu dərsdə «Daha çox yüklə» düyməsi olan və yeni şərhləri real vaxtda göstərən şərh lenti düzəldəcəksiniz.

Offset yoxsa cursor

Ən sadə üsul offset-dir: «10-nu keç, növbəti 10-u ver» (skip və take, Prisma mövzusundakı kimi). O, dərhal 50-ci səhifəyə keçməyə imkan verir, amma iki çatışmazlığı var. Dərin səhifələr yavaşdır: verilənlər bazası keçilən sətirləri yenə də gəzir. Oxuyarkən nəsə əlavə olunsa, səhifələr «sürüşür»: elementlər təkrarlanır və ya itir.

Cursor isə başqa cür deyir: «bu əlfəcindən sonrakı 10 qeydi ver». Sorğu indeksdən keçir, dərinlikdən asılı deyil və yeni qeydlərdən pozulmur. Əvəzində 50-ci səhifəyə dərhal keçmək olmur: yalnız «irəli».

Offset əlavə edildikdən sonra bir şərhi iki dəfə göstərir, cursor isə lazım olan yerdən davam edir
Offset əlavə edildikdən sonra bir şərhi iki dəfə göstərir, cursor isə lazım olan yerdən davam edir

Relay connection: edges, node, cursor, pageInfo

GraphQL-də cursor ilə səhifələmənin ümumi razılaşması var — Relay spesifikasiyasındakı connection. Sahə siyahı yox, üç hissəli obyekt qaytarır:

  • edges — «zərflər» siyahısı; hər birində node (şərhin özü) və cursor (məhz ona qoyulmuş əlfəcin) var. Zərf obyekt haqqında yox, əlaqə haqqında məlumat saxlamaq üçündür.
  • pageInfo — naviqasiya: hasNextPage («daha var?») və endCursor («son elementin əlfəcini»).
  • totalCount — spesifikasiyaya tez-tez edilən əlavə: cəmi neçə element olduğu (bizim sxemdə sahə məcburidir).

Spesifikasiyada pageInfo-da geriyə hərəkət üçün hasPreviousPage və startCursor da var; bizə iki sahə bəs edir. Əvvəlki dərslərdəki Post.comments sahəsi indi connection qaytarır (tədris layihəsində kənar müştəri yoxdur, canlı API-də sxemi belə pozmaq olmaz):

graphql
type PageInfo {
  hasNextPage: Boolean!
  endCursor: String
}

type CommentEdge {
  cursor: String!
  node: Comment!
}

type CommentConnection {
  edges: [CommentEdge!]!
  pageInfo: PageInfo!
  totalCount: Int!
}

type Post {
  # ...the other fields as before
  comments(first: Int = 10, after: String): CommentConnection!
}
Connection: cursor və node olan edges, endCursor olan pageInfo, növbəti sorğu after ilə gedir
Connection: cursor və node olan edges, endCursor olan pageInfo, növbəti sorğu after ilə gedir

Server: Prisma-da cursor

Müştəri üçün cursor şəffaf olmayan sətirdir: onu təhlil etməməli və məzmununu təxmin etməməlidir. Biz şərhin id-sini base64url ilə kodlaşdırırıq. Üç qayda: sıralama unikal və dəyişməyən sahəyə (id) söykənir, səhifənin ölçüsü məhduddur, hasNextPage-i bilmək üçün isə bir qeyd artıq oxuyuruq. Prisma sorğusu Prisma mövzusundakı ilə eynidir: cursor, skip: 1, take.

ts
// src/resolvers/post.ts
const encodeCursor = (id: number) =>
  Buffer.from(String(id)).toString('base64url');

function decodeCursor(cursor: string): number {
  const id = Number(Buffer.from(cursor, 'base64url').toString());
  if (!Number.isInteger(id)) {
    throw new GraphQLError('Invalid cursor', {
      extensions: { code: 'BAD_USER_INPUT' },
    });
  }
  return id;
}