{"record":{"id":"89823232edd27021","repo":"slopus/happy","slug":"invalid-cursor-format","errorCode":null,"errorMessage":"Invalid cursor format","messagePattern":"Invalid cursor format","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/happy-server/sources/app/feed/feedGet.ts","lineNumber":26,"sourceCode":" * Returns items in reverse chronological order (newest first).\n * Supports cursor-based pagination using the counter field.\n */\nexport async function feedGet(\n    tx: Tx,\n    ctx: Context,\n    options?: FeedOptions\n): Promise<FeedResult> {\n    const limit = options?.limit ?? 100;\n    const cursor = options?.cursor;\n\n    // Build where clause for cursor pagination\n    const where: Prisma.UserFeedItemWhereInput = { userId: ctx.uid };\n\n    if (cursor?.before !== undefined) {\n        if (cursor.before.startsWith('0-')) {\n            where.counter = { lt: parseInt(cursor.before.substring(2), 10) };\n        } else {\n            throw new Error('Invalid cursor format');\n        }\n    } else if (cursor?.after !== undefined) {\n        if (cursor.after.startsWith('0-')) {\n            where.counter = { gt: parseInt(cursor.after.substring(2), 10) };\n        } else {\n            throw new Error('Invalid cursor format');\n        }\n    }\n\n    // Fetch items + 1 to determine hasMore\n    const items = await tx.userFeedItem.findMany({\n        where,\n        orderBy: { counter: 'desc' },\n        take: limit + 1\n    });\n\n    // Check if there are more items\n    const hasMore = items.length > limit;","sourceCodeStart":8,"sourceCodeEnd":44,"githubUrl":"https://github.com/slopus/happy/blob/b824cd0a4681d41af631a8e422a813873e4455b0/packages/happy-server/sources/app/feed/feedGet.ts#L8-L44","documentation":"feedGet() paginates user feed items by a cursor that must have the form '0-<counter>' (a Storm/MySQL-style log sequence number prefix). A 'before' cursor that does not start with '0-' is rejected with 'Invalid cursor format' because the counter cannot be parsed for a lt comparison.","triggerScenarios":"Calling the feed items query with cursor.before set to a string not starting with '0-', e.g. 'abc', '1-100', an opaque id, or an empty-but-defined string.","commonSituations":"Clients caching cursors from a previous backend version that used a different format; hand-constructing cursors from item ids instead of the counter; passing a base64/encoded cursor where a plain '0-N' string is expected.","solutions":["Use cursors exactly as returned by the previous page (the raw counter-based string, e.g. '0-42').","Fix the client to build cursors as `0-${counter}` from the last item's counter field.","Clear stale persisted pagination state and restart pagination from the beginning (omit cursor).","Add client-side validation: cursor must match /^0-\\d+$/ before sending."],"exampleFix":"// before\nfetchFeed({ before: lastItem.id });\n// after\nfetchFeed({ before: `0-${lastItem.counter}` });","handlingStrategy":"validation","validationCode":"function isValidCursor(c) {\n  return c === undefined || c.before === undefined || /^0-\\d+$/.test(c.before);\n}\nif (!isValidCursor(cursor)) throw new Error('before cursor must be \"0-<counter>\"');","typeGuard":"function isBeforeCursor(c) {\n  return typeof c === 'object' && c !== null &&\n    typeof c.before === 'string' && /^0-\\d+$/.test(c.before);\n}","tryCatchPattern":"try {\n  return await api.feed.items({ cursor });\n} catch (e) {\n  if (e.message === 'Invalid cursor format') {\n    resetPagination();\n    return await api.feed.items({});\n  }\n  throw e;\n}","preventionTips":["Store cursors exactly as returned by the API; never hand-construct them.","Version cursor format in persisted client state and reset on mismatch.","Build cursors only from the item's counter field: `0-${counter}`.","Add client-side regex validation before sending pagination requests."],"tags":["pagination","cursor","validation","api"],"backgroundTag":"invalid-cursor-format","analyzedSha":"b824cd0a4681d41af631a8e422a813873e4455b0","analyzedAt":"2026-08-31T23:12:36.205Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}