toeverything/AFFiNE · error · Error

a should be smaller than b

Error message

a should be smaller than b

What it means

generateKeyBetweenV2() in blocksuite/framework/std/src/utils/fractional-indexing.ts creates a sort key strictly between a and b (used for ordering edgeless/surface blocks). It validates that when both neighbors are given, a >= b must fail-fast — fractional indexing only defines a key for an open interval (a, b). A plain Error (not BlockSuiteError) is thrown because this is a caller contract violation.

Source

Thrown at blocksuite/framework/std/src/utils/fractional-indexing.ts:28

 * make sure a and b are generated by this function.
 *
 * @param customPostfix custom postfix for the key, only letters and numbers are allowed
 */
export function generateKeyBetweenV2(a: string | null, b: string | null) {
  const randomSize = 32;
  function postfix(length: number = randomSize) {
    const chars =
      '123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
    const values = new Uint8Array(length);
    crypto.getRandomValues(values);
    let result = '';
    for (let i = 0; i < length; i++) {
      result += chars.charAt(values[i] % chars.length);
    }
    return result;
  }
  if (a !== null && b !== null && a >= b) {
    throw new Error('a should be smaller than b');
  }
  // get the subkey in full key
  // e.g.
  // a0xxxx -> a
  // a0x0xxxx -> a0x
  function subkey(key: string | null) {
    if (key === null) {
      return null;
    }
    if (key.length <= randomSize + 1) {
      // no subkey
      return key;
    }
    const splitAt = key.substring(0, key.length - randomSize - 1);
    return splitAt;
  }
  const aSubkey = subkey(a);
  const bSubkey = subkey(b);

View on GitHub (pinned to b4c8548c09)

Solutions

  1. Always call it as generateKeyBetweenV2(prevKey, nextKey) with prevKey strictly less than nextKey; use null for the open ends.
  2. Guard before calling: if (a !== null && b !== null && a >= b) throw/swap or fall back to inserting at an end.
  3. When keys come from stored data, re-validate ordering (sort neighbors before picking bounds).
  4. Never mix keys produced by different generators in the same index space.

Example fix

// before
const key = generateKeyBetweenV2(nextKey, prevKey);

// after
const key = generateKeyBetweenV2(prevKey, nextKey);
// or guard:
const key =
  prevKey && nextKey && prevKey >= nextKey
    ? generateKeyBetweenV2(null, nextKey)
    : generateKeyBetweenV2(prevKey, nextKey);
Defensive patterns

Strategy: validation

Validate before calling

function safeGenerate(a: string | null, b: string | null): string {
  if (a !== null && b !== null && a >= b) {
    // caller mixed up order or keys are equal — insert at an end instead of crashing
    return generateKeyBetweenV2(null, b);
  }
  return generateKeyBetweenV2(a, b);
}

Prevention

When it happens

Trigger: Calling generateKeyBetweenV2(a, b) with a >= b: passing (nextKey, prevKey) in the wrong order when inserting/moving an element, passing the same key for both arguments, or feeding legacy/hand-built keys that don't compare as expected lexicographically.

Common situations: Implementing drag-to-reorder in edgeless mode and swapping the prev/next key arguments; duplicating a block and reusing its key for both bounds; mixed key formats (some keys from the older generateNKeysBetween or hand-written) where lexicographic order surprises the caller.

Related errors


AI-assisted analysis of toeverything/AFFiNE@b4c8548c09 (2026-08-18). Data as JSON: /api/errors/e87de899f58d7904. Report an issue: GitHub.