{"record":{"id":"48da8c9103fdf107","repo":"laurent22/joplin","slug":"validation-error-user-updated-time-and-user-creat","errorCode":null,"errorMessage":"Validation error: user_updated_time and user_created_time must be numbers greater than 0","messagePattern":"Validation error: user_updated_time and user_created_time must be numbers greater than 0","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/lib/BaseModel.ts","lineNumber":617,"sourceCode":"\t\t\tdelete temp.id;\n\n\t\t\tquery = Database.updateQuery(this.tableName(), temp, where);\n\t\t}\n\n\t\tquery.id = modelId;\n\t\tquery.modObject = o;\n\n\t\treturn query;\n\t}\n\n\tpublic static userSideValidation(o: Record<string, unknown>) {\n\t\tif (typeof o.id === 'string' && !o.id.match(/^[a-f0-9]{32}$/)) {\n\t\t\tthrow new Error('Validation error: ID must a 32-characters lowercase hexadecimal string');\n\t\t}\n\n\t\tconst timestamps = ['user_updated_time', 'user_created_time'] as const;\n\t\tfor (const k of timestamps) {\n\t\t\tif ((k in o) && (typeof o[k] !== 'number' || isNaN(o[k] as number) || (o[k] as number) < 0)) throw new Error('Validation error: user_updated_time and user_created_time must be numbers greater than 0');\n\t\t}\n\n\t\tconst maxTitleLength = 4096;\n\t\tif (typeof o.title === 'string' && o.title.length > maxTitleLength) {\n\t\t\tthrow new Error(`Validation error: title must be ${maxTitleLength} characters or less`);\n\t\t}\n\n\t\t// Null bytes break Joplin's serialised note format and can cause silent\n\t\t// truncation in some HTTP clients (notably React Native on iOS).\n\t\tconst nul = String.fromCharCode(0);\n\t\tfor (const k of Object.keys(o)) {\n\t\t\tconst v = o[k];\n\t\t\tif (typeof v === 'string' && v.includes(nul)) {\n\t\t\t\tthrow new Error(`Validation error: ${k} cannot contain a null byte`);\n\t\t\t}\n\t\t}\n\t}\n","sourceCodeStart":599,"sourceCodeEnd":635,"githubUrl":"https://github.com/laurent22/joplin/blob/2654b33620775080d1d59c552259d41e33dad3d2/packages/lib/BaseModel.ts#L599-L635","documentation":"Thrown by BaseModel.userSideValidation() for the keys user_updated_time and user_created_time. Each must be a number, not NaN, and not negative. These timestamps drive sync conflict resolution, so corrupt values would corrupt replication. The check uses `k in o` so presence (not value) triggers validation.","triggerScenarios":"o contains user_updated_time or user_created_time that is a string, NaN, undefined-coerced, or a negative number — e.g. parsing a timestamp from JSON without Number() conversion.","commonSituations":"Importing data where timestamps are ISO strings; arithmetic that produced NaN; clock-skew producing negative deltas; deserialised payload with stringified numbers.","solutions":["Ensure both timestamps are millisecond epoch numbers (Number type) before save.","Coerce with `Number(value)` and validate with `!isNaN(v) && v >= 0` upstream.","If you do not have a real timestamp, omit the key entirely (the `k in o` check only fires when present)."],"exampleFix":"// before\nawait Folder.save({ id, user_updated_time: '2024-01-01T00:00:00Z' });\n// after\nawait Folder.save({ id, user_updated_time: Date.parse('2024-01-01T00:00:00Z') });","handlingStrategy":"validation","validationCode":"for (const k of ['user_updated_time', 'user_created_time']) {\n  if (k in entity) {\n    const v = entity[k];\n    if (typeof v !== 'number' || isNaN(v) || v < 0) throw new Error(`${k} must be a non-negative number`);\n  }\n}","typeGuard":"const isValidTimestamp = (v) => typeof v === 'number' && !isNaN(v) && v >= 0;","tryCatchPattern":"try { BaseModel.userSideValidation(entity); }\ncatch (e) { if (/user_updated_time and user_created_time/.test(e.message)) { /* coerce or drop the key */ } else throw e; }","preventionTips":["Always store timestamps as millisecond epoch numbers, never ISO strings.","If a timestamp is unknown, omit the key rather than passing 0/-1/string."],"tags":["validation","data-integrity","typescript","joplin-core"],"backgroundTag":null,"analyzedSha":"2654b33620775080d1d59c552259d41e33dad3d2","analyzedAt":"2026-08-12T14:26:46.263Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}