{"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":"warning","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/26c515e050211269e911f7d9cfe162a26c83ed98/packages/backend/server/src/base/graphql/pagination.ts#L48-L84","documentation":"A BadRequest error (HTTP 400) thrown by assertPaginationInput() when a GraphQL pagination input provides both a cursor ('after') and a numeric offset simultaneously. Cursor-based and offset-based pagination are mutually exclusive strategies; combining them is ambiguous. The check runs inside the PaginationInput.decode pipe transform, so it fires before the resolver executes.","triggerScenarios":"Sending a GraphQL query with pagination input containing both 'after' (a base64 cursor) and 'offset > 0'. For example: { first: 10, after: 'Y3Vyc29y', offset: 5 }. The PaginationInput.decode pipe transform calls assertPaginationInput which rejects this combination.","commonSituations":"Client code migrating from offset-based to cursor-based pagination that forgot to remove the offset field. Frontend pagination components that send all available parameters defensively. API client libraries that auto-populate offset with a non-zero default.","solutions":["Use only cursor-based pagination (set 'after', omit 'offset' or keep it at 0).","Or use only offset-based pagination (set 'offset', omit 'after').","Review client-side pagination logic to ensure it doesn't send both parameters together."],"exampleFix":"// before (GraphQL variables)\n{ \"input\": { \"first\": 10, \"after\": \"Y3Vyc29y\", \"offset\": 5 } } // throws\n\n// after (cursor-based)\n{ \"input\": { \"first\": 10, \"after\": \"Y3Vyc29y\" } }\n// or (offset-based)\n{ \"input\": { \"first\": 10, \"offset\": 5 } }","handlingStrategy":"validation","validationCode":"function validatePaginationInput(input?: PaginationInput) {\n  if (input?.after && (input.offset ?? 0) > 0) {\n    throw new Error('Use either cursor (after) or offset, not both.');\n  }\n}\nvalidatePaginationInput(paginationInput);","typeGuard":"const isExclusivePagination = (\n  input?: PaginationInput\n): boolean => !(input?.after && (input.offset ?? 0) > 0);","tryCatchPattern":"try {\n  const result = await client.query({\n    query: MY_QUERY,\n    variables: { input: paginationInput },\n  });\n} catch (e) {\n  if (e.message?.includes('pagination.after and pagination.offset')) {\n    // remove offset or after and retry\n    const fixed = { ...paginationInput, offset: 0 };\n    await client.query({ query: MY_QUERY, variables: { input: fixed } });\n  }\n}","preventionTips":["Decide on one pagination strategy (cursor or offset) and don't mix them.","In client code, only populate the fields relevant to the chosen strategy.","Add client-side validation that warns before sending both parameters."],"tags":["graphql","affine-backend","pagination","validation","bad-request"],"backgroundTag":null,"analyzedSha":"26c515e050211269e911f7d9cfe162a26c83ed98","analyzedAt":"2026-08-12T13:15:16.447Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}