{"record":{"id":"c3197430d2ec3604","repo":"jackwener/OpenCLI","slug":"chess-com-stats-payload-for-kind-is-not-an-obje","errorCode":null,"errorMessage":"Chess.com stats payload for ${kind} is not an object","messagePattern":"Chess\\.com stats payload for (.+?) is not an object","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/chess/utils.js","lineNumber":73,"sourceCode":"    if (!resp.ok) throw new CommandExecutionError(`Chess.com API returned HTTP ${resp.status} for ${url}`);\n    let payload;\n    try {\n        payload = await resp.json();\n    } catch (error) {\n        throw new CommandExecutionError(`Chess.com API returned malformed JSON for ${url}: ${error?.message || error}`);\n    }\n    if (!isPlainObject(payload)) {\n        throw new CommandExecutionError(`Chess.com API returned an unexpected payload shape for ${url}`);\n    }\n    return payload;\n}\n\n/** Pull rating + record fields out of a stats sub-object (`chess_rapid` etc). */\nexport function summarizeStats(stats, kind) {\n    const k = stats?.[kind];\n    if (!k) return null;\n    if (!isPlainObject(k)) {\n        throw new CommandExecutionError(`Chess.com stats payload for ${kind} is not an object`);\n    }\n    if (!isOptionalPlainObject(k.last)) {\n        throw new CommandExecutionError(`Chess.com stats payload for ${kind}.last is not an object`);\n    }\n    if (!isOptionalPlainObject(k.best)) {\n        throw new CommandExecutionError(`Chess.com stats payload for ${kind}.best is not an object`);\n    }\n    if (!isOptionalPlainObject(k.record)) {\n        throw new CommandExecutionError(`Chess.com stats payload for ${kind}.record is not an object`);\n    }\n    const record = isPlainObject(k.record) ? k.record : {};\n    return {\n        kind: kind.replace(/^chess_/, ''),\n        rating_current: k.last?.rating ?? '',\n        rating_best: k.best?.rating ?? '',\n        wins: record.win ?? '',\n        losses: record.loss ?? '',\n        draws: record.draw ?? '',","sourceCodeStart":55,"sourceCodeEnd":91,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/chess/utils.js#L55-L91","documentation":"summarizeStats() extracts a time-class sub-object (e.g. stats.chess_rapid) from a Chess.com player stats payload. If that sub-key exists and is truthy but is not a plain object (array, string, number), the library throws rather than silently producing empty columns. It indicates the stats payload does not match the documented Chess.com /pub/player/{user}/stats schema for that kind.","triggerScenarios":"stats[kind] is truthy but not an object — e.g. chess_rapid is a string, number, or array in the payload fetched from https://api.chess.com/pub/player/{username}/stats and passed through rows()/row().","commonSituations":"Chess.com schema drift for a specific time-class key; a hand-built or cached stats object passed to summarizeStats in tests; a wrapper that stores serialized/JSON-string values under the kind key; caller passing the wrong parent object (whole payload instead of payload stats).","solutions":["Re-fetch the stats payload fresh from https://api.chess.com/pub/player/{username}/stats to rule out stale/corrupt cached data","Log typeof stats[kind] and JSON.stringify(stats[kind]) to identify the actual shape","Pass the correct object: the stats payload itself, not a nested or stringified value","If Chess.com changed the schema, update summarizeStats mapping for that kind"],"exampleFix":"// before\nconst row = summarizeStats(JSON.parse(fs.readFileSync(cacheFile, 'utf8')).chess_rapid, 'chess_rapid');\n// after\nconst payload = JSON.parse(fs.readFileSync(cacheFile, 'utf8'));\nconst row = isPlainObject(payload) ? summarizeStats(payload, 'chess_rapid') : null;","handlingStrategy":"type-guard","validationCode":"// Guard before calling summarizeStats\nif (!stats || typeof stats !== 'object' || Array.isArray(stats)) {\n  throw new Error('stats payload must be a plain object');\n}\nconst k = stats[kind];\nif (k != null && (typeof k !== 'object' || Array.isArray(k))) {\n  throw new Error(`${kind} must be an object, got ${typeof k}`);\n}","typeGuard":"function isStatSection(v) {\n  return v === null || v === undefined ||\n    (typeof v === 'object' && !Array.isArray(v));\n}\n// usage: if (!isStatSection(stats?.[kind])) { ...skip or handle... }","tryCatchPattern":"try {\n  const row = summarizeStats(stats, kind);\n} catch (err) {\n  if (String(err.message).includes('is not an object')) {\n    console.warn(`Skipping ${kind}: malformed stats section`);\n  } else { throw err; }\n}","preventionTips":["Feed summarizeStats only payloads fetched fresh from /pub/player/{user}/stats","Never store stringified or flattened values under chess_* keys in caches","Validate cached JSON shapes before reusing them","Keep test fixtures aligned with the live pub API schema"],"tags":["api","schema-validation","chess-com","stats"],"backgroundTag":"schema-validation-failed","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}