toeverything/AFFiNE · error · BadRequest
bad_request
bad_request
Error message
pagination.after and pagination.offset cannot be used together
What it means
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.
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.
Example fix
# before
pagination: { first: 10, after: cursor, offset: 20 }
# after
pagination: { first: 10, after: cursor } # offset removed; cursor and offset are mutually exclusive Defensive patterns
Strategy: validation
Validate before calling
function buildPagination(mode: 'cursor' | 'offset', args: { first: number; cursor?: string; offset?: number }) {
if (mode === 'cursor') return { first: args.first, after: args.cursor };
return { first: args.first, offset: args.offset ?? 0 };
}
// never emits both `after` and a positive `offset` Type guard
const isSingleModePagination = (p?: PaginationInput): boolean => !p || !(p.after && p.offset > 0);
Try / catch
try {
const result = await client.request(query, { pagination });
} catch (e) {
if (e instanceof Error && e.message.includes('after and pagination.offset cannot be used together')) {
delete pagination.offset; // fall back to cursor-only paging and retry
return client.request(query, { pagination });
} else throw e;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: BAD_REQUEST error code: request rejected as invalid (HTTP 400) - causes and fixes across libraries — this error's family across 8 libraries.
Related errors
- action_forbidden
- action_forbidden_on_non_team_workspace
- already_in_space
- can_not_revoke_yourself
- doc_not_found
AI-assisted analysis of toeverything/AFFiNE@b4c8548c09 (2026-08-18).
Data as JSON: /api/errors/a503b22a0e56e93d.
Report an issue: GitHub.
Appendix: source
Thrown at packages/backend/server/src/base/graphql/pagination.ts:66
})
after?: string | null;
// NOT IMPLEMENTED YET
// @Field(() => String, {
// nullable: true,
// description:
// 'returns the elements in the list that come before the specified cursor.',
// })
// before?: string | null;
}
export function assertPaginationInput(paginationInput?: PaginationInput) {
if (!paginationInput) {
return;
}
if (paginationInput.after && paginationInput.offset > 0) {
throw new BadRequest(
'pagination.after and pagination.offset cannot be used together'
);
}
}
const encode = (input: unknown) => {
let inputStr: string;
if (input instanceof Date) {
inputStr = input.toISOString();
} else if (typeof input === 'string') {
inputStr = input;
} else {
inputStr = String(input);
}
return Buffer.from(inputStr).toString('base64');
};
const decode = (base64String?: string | null) =>
base64String ? Buffer.from(base64String, 'base64').toString('utf-8') : null;View on GitHub (pinned to b4c8548c09)