toeverything/AFFiNE · error · Error
a should be smaller than b
Error message
a should be smaller than b
What it means
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.
Source
Thrown at packages/common/infra/src/utils/fractional-indexing.ts:33
export function generateFractionalIndexingKeyBetween(
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;
}
View on GitHub (pinned to b4c8548c09)
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.
Example fix
// before (moveUp branch inverted the pair)
const key = generateFractionalIndexingKeyBetween(below.key, above.key);
// after
const key =
above.key === null
? generateFractionalIndexingKeyBetween(null, below.key) // move to top
: generateFractionalIndexingKeyBetween(above.key, below.key); Defensive patterns
Strategy: validation
Validate before calling
function safeMiddleKey(a: string | null, b: string | null): string {
if (a !== null && b !== null && a >= b) {
// inputs inverted or equal — pick boundary semantics instead of throwing
return a === b ? generateFractionalIndexingKeyBetween(a, null) : generateFractionalIndexingKeyBetween(b, a);
}
return generateFractionalIndexingKeyBetween(a, b);
} Type guard
const isOrdered = (a: string | null, b: string | null): boolean => a === null || b === null || a < b; // assert isOrdered(aboveKey, belowKey) before generating
Try / catch
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; } Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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).
Related errors
- a should be smaller than b
- user_not_found
- action_forbidden
- Invalid config for module [${module}] with key [${key}] Valu
- bad_request
AI-assisted analysis of toeverything/AFFiNE@b4c8548c09 (2026-08-18).
Data as JSON: /api/errors/400b73591cae2fae.
Report an issue: GitHub.