{"record":{"id":"c7e712cb5b929ed0","repo":"jackwener/OpenCLI","slug":"chess-com-game-archive-entry-is-not-an-object","errorCode":null,"errorMessage":"Chess.com game archive entry is not an object","messagePattern":"Chess\\.com game archive entry is not an object","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/chess/utils.js","lineNumber":121,"sourceCode":" * (`https://www.chess.com/openings/Reti-Opening-Nimzo-Larsen-Variation-2...g6-...`).\n * Returns '' for short-code eco values (`A01`) where no name is encoded.\n */\nexport function openingName(eco) {\n    if (typeof eco !== 'string' || !eco.startsWith('http')) return '';\n    const tail = eco.replace(/\\/+$/, '').split('/').pop() || '';\n    if (!tail) return '';\n    const namePart = tail.match(/^([^.]+?)(?:-\\d|\\.\\.\\.|$)/);\n    const cleaned = (namePart ? namePart[1] : tail).replace(/-/g, ' ').trim();\n    return cleaned;\n}\n\n/**\n * Map a Chess.com game record (from the monthly archive) to a flat row.\n * The viewer perspective controls win/loss orientation.\n */\nexport function mapGameRow(game, viewerUsername) {\n    if (!isPlainObject(game)) {\n        throw new CommandExecutionError('Chess.com game archive entry is not an object');\n    }\n    if (typeof game.url !== 'string' || !/^https:\\/\\/www\\.chess\\.com\\/game\\/(?:live|daily)\\/\\d+(?:$|[/?#])/i.test(game.url)) {\n        throw new CommandExecutionError('Chess.com game archive entry is missing a stable game URL');\n    }\n    const white = game?.white || {};\n    const black = game?.black || {};\n    if (!isPlainObject(white) || !isPlainObject(black)) {\n        throw new CommandExecutionError('Chess.com game archive entry has malformed player objects');\n    }\n    if (typeof white.username !== 'string' || !white.username.trim() || typeof black.username !== 'string' || !black.username.trim()) {\n        throw new CommandExecutionError('Chess.com game archive entry is missing stable player identities');\n    }\n    const viewerLower = String(viewerUsername || '').toLowerCase();\n    const viewerIsWhite = String(white.username || '').toLowerCase() === viewerLower;\n    const viewerIsBlack = String(black.username || '').toLowerCase() === viewerLower;\n    if (!viewerIsWhite && !viewerIsBlack) {\n        throw new CommandExecutionError('Chess.com game archive entry does not include the requested player');\n    }","sourceCodeStart":103,"sourceCodeEnd":139,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/chess/utils.js#L103-L139","documentation":"mapGameRow() maps one entry from a Chess.com monthly games archive into a flat row. The first guard requires each game entry to be a plain object; if an entry in the games array is null, an array, or a scalar, this error is thrown. It protects the rest of the mapper from dereferencing malformed entries.","triggerScenarios":"An element of the games array returned by /pub/player/{user}/games/{yyyy}/{mm} (iterated by row()) is not a plain object — e.g. null entries, or the caller passed the whole archive object/array instead of a single game entry.","commonSituations":"Caller accidentally passes the entire {games:[...]} wrapper or the archive array instead of one element; Chess.com includes null entries in an archive; a custom fetch/mock returns entries as JSON strings that were never parsed.","solutions":["Ensure each element passed to mapGameRow is a single game object — map over data.games and filter with isPlainObject first","Parse JSON entries before mapping if your data source returns strings","Filter null/malformed entries from the archive array before mapping","Re-fetch the archive if Chess.com returned an unexpected structure"],"exampleFix":"// before\nconst rows = archive.games.map((g) => mapGameRow(g, user));\n// after\nconst rows = archive.games.filter(isPlainObject).map((g) => mapGameRow(g, user));","handlingStrategy":"type-guard","validationCode":"const games = archive?.games ?? [];\nfor (const g of games) {\n  if (g === null || typeof g !== 'object' || Array.isArray(g)) {\n    throw new Error('archive contains a non-object game entry');\n  }\n}","typeGuard":"function isGameEntry(v) {\n  return v !== null && typeof v === 'object' && !Array.isArray(v) &&\n    typeof v.url === 'string' && v.url.startsWith('https://www.chess.com/game/');\n}","tryCatchPattern":"try {\n  rows = games.map((g) => mapGameRow(g, user));\n} catch (err) {\n  if (String(err.message).includes('archive entry is not an object')) {\n    rows = games.filter(isGameEntry).map((g) => mapGameRow(g, user));\n  } else { throw err; }\n}","preventionTips":["Always pass a single game object, never the archive wrapper or array","Parse JSON-string entries before mapping","Filter null/malformed entries out of archive.games first","Use isPlainObject as a standard pre-map filter in pipelines"],"tags":["api","schema-validation","chess-com","games"],"backgroundTag":"schema-validation-failed","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}