{"record":{"id":"58821102ffbe69b5","repo":"toeverything/AFFiNE","slug":"invalid-indexer-input","errorCode":"invalid_indexer_input","errorMessage":"Invalid indexer input: ${reason}","messagePattern":"Invalid indexer input: (.+?)","errorType":"exception","errorClass":"InvalidIndexerInput","httpStatus":400,"severity":"error","filePath":"packages/backend/server/src/plugins/indexer/service.ts","lineNumber":147,"sourceCode":"        if (!doc.title) doc.title = titles.get(doc.docId) ?? '';\n      }\n    }\n    const users = await this.models.user.getPublicUsersMap(userIds);\n    for (const doc of docs) {\n      doc.createdByUser = users.get(doc.createdByUserId);\n      doc.updatedByUser = users.get(doc.updatedByUserId);\n    }\n    return docs;\n  }\n\n  private unwrap<T>(output: SearchOperationOutput, workspaceId: string): T {\n    if (output.ok) return output.value as T;\n    switch (output.errorCode) {\n      case 'workspace_denied':\n        throw new SpaceAccessDenied({ spaceId: workspaceId });\n      case 'invalid_request':\n      case 'unsupported_query':\n        throw new InvalidIndexerInput({ reason: output.errorCode });\n      case 'provider_unavailable':\n        throw new SearchProviderNotFound();\n      case 'permission_unavailable':\n        throw new WorkspacePermissionNotFound({ spaceId: workspaceId });\n      default:\n        throw new InternalServerError();\n    }\n  }\n}\n","sourceCodeStart":129,"sourceCodeEnd":157,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/591f874dad30887a80143a061a44bd3ca7ee3299/packages/backend/server/src/plugins/indexer/service.ts#L129-L157","documentation":"While building the search/aggregate DSL, the indexer service rejects pagination.limit > 10000 with InvalidIndexerInput (invalid_input, reason 'limit must be less than 10000'). The cap protects the search backend from oversized result sets and deep pagination.","triggerScenarios":"Passing options.pagination.limit greater than 10000 to the indexer search or aggregate APIs - typically code that passes a total count (e.g. docs count from another query) straight in as the limit.","commonSituations":"'Fetch everything' export code paths; clients computing limit = totalCount; migrating from an API that had no cap.","solutions":["Clamp the limit to 10000 (or omit it entirely to use the default page size)","Use pagination.cursor for iterating large result sets instead of one big limit","For exports, loop with skip/cursor and accumulate results"],"exampleFix":"// before\nconst dsl = service.search({ table, query, options: { pagination: { limit: totalCount } } });\n\n// after\nconst MAX = 10000;\nconst dsl = service.search({ table, query, options: { pagination: { limit: Math.min(totalCount, MAX) } } });","handlingStrategy":"validation","validationCode":"const MAX_LIMIT = 10000;\nconst limit = Math.min(requestedLimit ?? DEFAULT_PAGE, MAX_LIMIT);\nconst dsl = service.search({ table, query, options: { pagination: { limit } } });","typeGuard":"const isValidLimit = (limit: number | undefined): boolean =>\n  limit === undefined || (Number.isInteger(limit) && limit <= 10000 && limit > 0);","tryCatchPattern":"try {\n  return await search(input);\n} catch (e) {\n  if (e?.code === 'invalid_indexer_input' && /limit/.test(e.reason)) {\n    input.options.pagination.limit = 10000;\n    return search(input);\n  }\n  throw e;\n}","preventionTips":["Never pass a total count as pagination.limit; clamp inputs at the API boundary","Default to cursor pagination for anything that may exceed 10000 rows","Centralize limit constants shared between client and server"],"tags":["indexer","pagination","limit","validation","search"],"backgroundTag":"query-limit-exceeded","analyzedSha":"591f874dad30887a80143a061a44bd3ca7ee3299","analyzedAt":"2026-08-21T18:20:45.039Z","contentChangedAt":"2026-08-21T18:20:45.039Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}