{"record":{"id":"a503b22a0e56e93d","repo":"toeverything/AFFiNE","slug":"bad-request","errorCode":"bad_request","errorMessage":"pagination.after and pagination.offset cannot be used together","messagePattern":"pagination\\.after and pagination\\.offset cannot be used together","errorType":"validation","errorClass":"BadRequest","httpStatus":400,"severity":"error","filePath":"packages/backend/server/src/base/graphql/pagination.ts","lineNumber":66,"sourceCode":"  })\n  after?: string | null;\n\n  // NOT IMPLEMENTED YET\n  // @Field(() => String, {\n  //   nullable: true,\n  //   description:\n  //     'returns the elements in the list that come before the specified cursor.',\n  // })\n  // before?: string | null;\n}\n\nexport function assertPaginationInput(paginationInput?: PaginationInput) {\n  if (!paginationInput) {\n    return;\n  }\n\n  if (paginationInput.after && paginationInput.offset > 0) {\n    throw new BadRequest(\n      'pagination.after and pagination.offset cannot be used together'\n    );\n  }\n}\n\nconst encode = (input: unknown) => {\n  let inputStr: string;\n  if (input instanceof Date) {\n    inputStr = input.toISOString();\n  } else if (typeof input === 'string') {\n    inputStr = input;\n  } else {\n    inputStr = String(input);\n  }\n  return Buffer.from(inputStr).toString('base64');\n};\nconst decode = (base64String?: string | null) =>\n  base64String ? Buffer.from(base64String, 'base64').toString('utf-8') : null;","sourceCodeStart":48,"sourceCodeEnd":84,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/b4c8548c09da21b2898443559a5b846f0ccf5dd8/packages/backend/server/src/base/graphql/pagination.ts#L48-L84","documentation":"assertPaginationInput rejects GraphQL pagination input that combines a cursor (after) with offset > 0 by throwing a BadRequest (code bad_request). AFFiNE list queries support either cursor-based paging (first/after) or offset-based paging (first/offset), never both at once — mixing them makes the requested window ambiguous.","triggerScenarios":"A query with pagination: { first: 10, after: '<cursor>', offset: 20 }; a client that keeps the cursor from the previous page while also appending an offset; UI code switching pagination modes without clearing the other parameter.","commonSituations":"Relay-style clients adding an offset fallback; pagination components that merge state from two modes; scripts generated from query builders that include every optional field.","solutions":["Remove offset whenever after is set — use { first, after } only.","Clear after whenever you set offset.","Build pagination input through a single helper that enforces one mode at a time."],"exampleFix":"# before\npagination: { first: 10, after: cursor, offset: 20 }\n\n# after\npagination: { first: 10, after: cursor }  # offset removed; cursor and offset are mutually exclusive","handlingStrategy":"validation","validationCode":"function buildPagination(mode: 'cursor' | 'offset', args: { first: number; cursor?: string; offset?: number }) {\n  if (mode === 'cursor') return { first: args.first, after: args.cursor };\n  return { first: args.first, offset: args.offset ?? 0 };\n}\n// never emits both `after` and a positive `offset`","typeGuard":"const isSingleModePagination = (p?: PaginationInput): boolean =>\n  !p || !(p.after && p.offset > 0);","tryCatchPattern":"try {\n  const result = await client.request(query, { pagination });\n} catch (e) {\n  if (e instanceof Error && e.message.includes('after and pagination.offset cannot be used together')) {\n    delete pagination.offset; // fall back to cursor-only paging and retry\n    return client.request(query, { pagination });\n  } else throw e;\n}","preventionTips":["Build pagination args through one helper that enforces cursor XOR offset mode.","Clear `after` whenever the user jumps to a page via offset, and vice versa.","Add a client-side assertion rejecting combined after/offset input before sending the query."],"tags":["graphql","pagination","bad-request","affine","api-misuse"],"backgroundTag":"conflicting-pagination-parameters","analyzedSha":"b4c8548c09da21b2898443559a5b846f0ccf5dd8","analyzedAt":"2026-08-18T21:16:52.546Z","contentChangedAt":"2026-08-18T21:16:52.546Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}