{"record":{"id":"400b73591cae2fae","repo":"toeverything/AFFiNE","slug":"a-should-be-smaller-than-b-400b73","errorCode":null,"errorMessage":"a should be smaller than b","messagePattern":"a should be smaller than b","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/common/infra/src/utils/fractional-indexing.ts","lineNumber":33,"sourceCode":"export function generateFractionalIndexingKeyBetween(\n  a: string | null,\n  b: string | null\n) {\n  const randomSize = 32;\n  function postfix(length: number = randomSize) {\n    const chars =\n      '123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';\n    const values = new Uint8Array(length);\n    crypto.getRandomValues(values);\n    let result = '';\n    for (let i = 0; i < length; i++) {\n      result += chars.charAt(values[i] % chars.length);\n    }\n    return result;\n  }\n\n  if (a !== null && b !== null && a >= b) {\n    throw new Error('a should be smaller than b');\n  }\n\n  // get the subkey in full key\n  // e.g.\n  // a0xxxx -> a\n  // a0x0xxxx -> a0x\n  function subkey(key: string | null) {\n    if (key === null) {\n      return null;\n    }\n    if (key.length <= randomSize + 1) {\n      // no subkey\n      return key;\n    }\n    const splitAt = key.substring(0, key.length - randomSize - 1);\n    return splitAt;\n  }\n","sourceCodeStart":15,"sourceCodeEnd":51,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/b4c8548c09da21b2898443559a5b846f0ccf5dd8/packages/common/infra/src/utils/fractional-indexing.ts#L15-L51","documentation":"generateFractionalIndexingKeyBetween(a, b) builds an ordering key strictly between a and b (used for drag-to-reorder). It asserts its own contract: when both neighbors are non-null, a must compare strictly less than b as strings. Passing an inverted or equal pair violates the ordering invariant and throws immediately.","triggerScenarios":"Calling generateFractionalIndexingKeyBetween(highKey, lowKey) after swapping the neighbor arguments; passing the same key for both a and b (a >= b includes equality); reorder logic that reads the wrong 'before/after' indices, e.g. when moving a block upward and fetching rows in descending order.","commonSituations":"Drag-and-drop reorder handlers where the moveUp branch accidentally passes (below, above) instead of (above, below); duplicate ordering keys in existing data making a === b; passing full keys from a different generator (keys must come from this function, per its doc comment).","solutions":["Check the call site: the first argument must be the key of the row ABOVE the drop position (smaller), the second the row BELOW (larger). For moveUp vs moveDown make sure the row pair is not inverted.","Handle boundary moves: when moving to the very top pass (null, firstKey); to the very bottom pass (lastKey, null) — null is allowed and avoids equality/inversion traps.","If a === b can occur (duplicate keys), de-duplicate or regenerate keys before computing the middle key.","Unit-test reorder with adjacent rows, equal keys, and both directions."],"exampleFix":"// before (moveUp branch inverted the pair)\nconst key = generateFractionalIndexingKeyBetween(below.key, above.key);\n\n// after\nconst key =\n  above.key === null\n    ? generateFractionalIndexingKeyBetween(null, below.key) // move to top\n    : generateFractionalIndexingKeyBetween(above.key, below.key);","handlingStrategy":"validation","validationCode":"function safeMiddleKey(a: string | null, b: string | null): string {\n  if (a !== null && b !== null && a >= b) {\n    // inputs inverted or equal — pick boundary semantics instead of throwing\n    return a === b ? generateFractionalIndexingKeyBetween(a, null) : generateFractionalIndexingKeyBetween(b, a);\n  }\n  return generateFractionalIndexingKeyBetween(a, b);\n}","typeGuard":"const isOrdered = (a: string | null, b: string | null): boolean => a === null || b === null || a < b;\n// assert isOrdered(aboveKey, belowKey) before generating","tryCatchPattern":"try { return generateFractionalIndexingKeyBetween(prev, next); } catch (e) { if (e instanceof Error && e.message === 'a should be smaller than b') { /* swap or null-out the inverted bound, then retry */ } throw e; }","preventionTips":["Encode the invariant at the call layer: (aboveKey, belowKey), never (below, above)","Handle move-to-top/move-to-bottom with null bounds","Test reorder for adjacent rows and both directions","Never feed keys from other generators into this function"],"tags":["fractional-indexing","ordering","sorting","affine"],"backgroundTag":"fractional-indexing-order","analyzedSha":"b4c8548c09da21b2898443559a5b846f0ccf5dd8","analyzedAt":"2026-08-18T21:16:52.546Z","contentChangedAt":"2026-08-18T21:16:52.546Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}