{"record":{"id":"622f91e6b05609d6","repo":"decolua/9router","slug":"invalid-database-payload","errorCode":null,"errorMessage":"Invalid database payload","messagePattern":"Invalid database payload","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/lib/db/index.js","lineNumber":98,"sourceCode":"    apiKeys: db.all(`SELECT * FROM apiKeys`).map((r) => ({ id: r.id, key: r.key, name: r.name, machineId: r.machineId, isActive: r.isActive === 1, createdAt: r.createdAt })),\n    combos: db.all(`SELECT * FROM combos`).map((r) => ({ id: r.id, name: r.name, kind: r.kind, models: parseJson(r.models, []), createdAt: r.createdAt, updatedAt: r.updatedAt })),\n    modelAliases: {},\n    customModels: [],\n    mitmAlias: {},\n    pricing: {},\n  };\n\n  for (const r of db.all(`SELECT key, value FROM kv WHERE scope = 'modelAliases'`)) out.modelAliases[r.key] = parseJson(r.value);\n  for (const r of db.all(`SELECT key, value FROM kv WHERE scope = 'customModels'`)) out.customModels.push(parseJson(r.value));\n  for (const r of db.all(`SELECT key, value FROM kv WHERE scope = 'mitmAlias'`)) out.mitmAlias[r.key] = parseJson(r.value);\n  for (const r of db.all(`SELECT key, value FROM kv WHERE scope = 'pricing'`)) out.pricing[r.key] = parseJson(r.value);\n\n  return out;\n}\n\nexport async function importDb(payload) {\n  if (!payload || typeof payload !== \"object\" || Array.isArray(payload)) {\n    throw new Error(\"Invalid database payload\");\n  }\n  const db = await getAdapter();\n\n  db.transaction(() => {\n    // Wipe all tables (keep _meta)\n    db.run(`DELETE FROM settings`);\n    db.run(`DELETE FROM providerConnections`);\n    db.run(`DELETE FROM providerNodes`);\n    db.run(`DELETE FROM proxyPools`);\n    db.run(`DELETE FROM apiKeys`);\n    db.run(`DELETE FROM combos`);\n    db.run(`DELETE FROM kv WHERE scope IN ('modelAliases', 'customModels', 'mitmAlias', 'pricing')`);\n\n    // Settings\n    if (payload.settings) {\n      db.run(`INSERT INTO settings(id, data) VALUES(1, ?) ON CONFLICT(id) DO UPDATE SET data = excluded.data`, [stringifyJson(payload.settings)]);\n    }\n","sourceCodeStart":80,"sourceCodeEnd":116,"githubUrl":"https://github.com/decolua/9router/blob/90b52e06ffd666b7929554211474d01588f6b1f8/src/lib/db/index.js#L80-L116","documentation":"importDb performs a destructive restore: it wipes all tables (keeping _meta) inside a transaction and repopulates them from the given payload. Because the wipe is irreversible, the payload is validated first — it must be a non-null, non-array object; anything else (null, undefined, arrays, strings, numbers) throws this error before the database is touched.","triggerScenarios":"POSTing a database-import request whose JSON body is null, an array (e.g. a bare export of rows), a plain string/number, or an empty body so the handler passes undefined into importDb.","commonSituations":"A client uploaded an export file whose top level is an array instead of the expected {settings:[...], ...} object; a fetch call forgot `Content-Type: application/json` so the handler received an unparsed string; an empty-file upload produced null; a script piped the wrong file (e.g. usage.json instead of the DB export).","solutions":["Send the original export object (as produced by the export endpoint / exportDb) — an object keyed by table names — not an array or raw string.","Parse the uploaded file as JSON on the client and pass reqBody as an object: `const payload = JSON.parse(fileText); await fetch('/api/db/import', {method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(payload)})`.","Check the export file's top-level shape: it must start with `{`; if it starts with `[`, wrap or re-export it correctly before importing.","In the API handler, validate the parsed body and return 400 before calling importDb so users get a friendly message instead of a thrown error."],"exampleFix":"// before\nawait fetch('/api/db/import', { method: 'POST', body: JSON.stringify(rows) }); // rows is an array\n// after\nconst payload = JSON.parse(exportText); // { settings: [...], accounts: [...], ... }\nif (!payload || typeof payload !== 'object' || Array.isArray(payload)) throw new Error('Export file is not a valid DB export');\nawait fetch('/api/db/import', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) });","handlingStrategy":"validation","validationCode":"function isImportableDbPayload(p) {\n  return !!p && typeof p === 'object' && !Array.isArray(p);\n}\n// in the POST handler:\nconst payload = await req.json().catch(() => null);\nif (!isImportableDbPayload(payload)) return res.status(400).json({ error: 'Body must be a DB export object' });","typeGuard":"function isDbExportPayload(v) {\n  return typeof v === 'object' && v !== null && !Array.isArray(v)\n    && Object.values(v).every(t => t === undefined || Array.isArray(t));\n}","tryCatchPattern":"try {\n  await importDb(payload);\n} catch (err) {\n  if (String(err.message) === 'Invalid database payload') {\n    return res.status(400).json({ error: 'Upload a DB export object (JSON object keyed by table), not an array or string' });\n  }\n  throw err;\n}","preventionTips":["Always import from a file produced by the matching export endpoint; never hand-roll the payload shape.","Parse and sanity-check the JSON client-side (top level must be `{`) before POSTing.","Set Content-Type: application/json on import requests so the body parses into an object, not a string.","Warn users before import that all tables are wiped — combine with a backup/export call so a bad payload costs nothing.","Reject empty or zero-byte uploads at the UI level before reaching the API."],"tags":["database","import","validation","payload"],"backgroundTag":"invalid-database-payload","analyzedSha":"90b52e06ffd666b7929554211474d01588f6b1f8","analyzedAt":"2026-08-30T21:05:45.952Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}