{"record":{"id":"bb70c079774f97d5","repo":"overleaf/overleaf","slug":"insertion-must-be-a-string","errorCode":null,"errorMessage":"insertion must be a string","messagePattern":"insertion must be a string","errorType":"validation","errorClass":"InvalidInsertionError","httpStatus":null,"severity":"error","filePath":"libraries/overleaf-editor-core/lib/operation/scan_op.js","lineNumber":97,"sourceCode":"    throw new Error('abstract method')\n  }\n\n  toString() {\n    'ScanOp'\n  }\n}\n\nclass InsertOp extends ScanOp {\n  /**\n   *\n   * @param {string} insertion\n   * @param {TrackingProps | undefined} tracking\n   * @param {string[] | undefined} commentIds\n   */\n  constructor(insertion, tracking = undefined, commentIds = undefined) {\n    super()\n    if (typeof insertion !== 'string') {\n      throw new InvalidInsertionError('insertion must be a string')\n    }\n    if (containsNonBmpChars(insertion)) {\n      throw new InvalidInsertionError('insertion contains non-BMP characters')\n    }\n    /** @type {string} */\n    this.insertion = insertion\n    /** @type {TrackingProps | undefined} */\n    this.tracking = tracking\n    /** @type {string[] | undefined} */\n    this.commentIds = commentIds\n  }\n\n  /**\n   *\n   * @param {RawInsertOp} op\n   * @returns {InsertOp}\n   */\n  static fromJSON(op) {","sourceCodeStart":79,"sourceCodeEnd":115,"githubUrl":"https://github.com/overleaf/overleaf/blob/28ad3b03b71cb4311decdcb55c36b33ec10d72db/libraries/overleaf-editor-core/lib/operation/scan_op.js#L79-L115","documentation":"InsertOp represents an insertion of text into a document, so its `insertion` payload must be a string. The constructor explicitly type-checks and throws InvalidInsertionError when given a non-string (number, undefined, object, etc.). This fails fast so a malformed insert never propagates into document transforms.","triggerScenarios":"Calling `new InsertOp(value)` where value is not a string: `new InsertOp(42)`, `new InsertOp(undefined)` after a failed lookup, or passing raw JSON like `{ i: 5 }` fields directly instead of going through fromJSON.","commonSituations":"Deserializing JSON where the insertion field is a number/null; constructing ops programmatically from computed values that turned out undefined; form/API input not coerced to string before building ops.","solutions":["Coerce or validate the insertion to a string before constructing: String(value) or a typeof check.","If the value comes from JSON, use InsertOp.fromJSON(op) which validates the `i` property and gives a clearer error path.","Fix the upstream code path producing undefined/null insertion values (often a missing field in fetched data)."],"exampleFix":"// before\nnew InsertOp(payload.insertion) // payload.insertion may be undefined\n// after\nif (typeof payload.insertion !== 'string') throw new TypeError('insertion required')\nnew InsertOp(payload.insertion)","handlingStrategy":"validation","validationCode":"if (typeof insertion !== 'string') {\n  throw new TypeError('insertion must be a string before building InsertOp')\n}","typeGuard":"function isStringInsertion(v) { return typeof v === 'string' }","tryCatchPattern":"try {\n  op = new InsertOp(insertion)\n} catch (e) {\n  if (e instanceof InvalidInsertionError && e.message.includes('must be a string')) {\n    op = new InsertOp(String(insertion ?? ''))\n  } else throw e\n}","preventionTips":["Coerce API/form values with String(value) before building ops.","Use InsertOp.fromJSON for untrusted data — it validates the raw shape.","Typecheck code paths with JSDoc/TS so non-strings are caught statically."],"tags":["type-error","validation","insert","scan-op"],"backgroundTag":"invalid-insertion-type","analyzedSha":"28ad3b03b71cb4311decdcb55c36b33ec10d72db","analyzedAt":"2026-09-03T02:10:22.807Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T07:17:11.731Z"}