import { generateKeyBetween, generateNKeysBetween } from 'fractional-indexing'; /** * Fractional-index keys for manual page ordering (issue #45, data-model.md * `sort_key`). A move recomputes only the moved page's key as a value strictly * between its two new neighbours, so no sibling is rewritten. Repeatedly * inserting between two very close keys grows the key length; once a fresh key * would exceed {@link MAX_SORT_KEY_LENGTH} the caller rebalances the whole pond * to evenly-spaced keys instead. These helpers are pure so the rebalance * behaviour can be property-tested without a database. */ /** * Length at which a newly generated key triggers a rebalance. Fractional keys * only grow under adversarial "always insert between the same tight pair" * sequences; a healthy tree stays far below this. 40 leaves generous headroom * over normal use while capping unbounded growth. */ export const MAX_SORT_KEY_LENGTH = 40; /** A key strictly between `after` and `before` (either `null` for an open end). */ export function keyBetween(after: string | null, before: string | null): string { return generateKeyBetween(after, before); } /** `n` evenly-spaced keys spanning the whole range — used to rebalance a pond. */ export function evenlySpacedKeys(n: number): string[] { if (n <= 0) return []; return generateNKeysBetween(null, null, n); } /** * The key for a page moved between `afterKey` and `beforeKey`, or `null` when * the result would be too long (or the neighbours are out of order, e.g. from a * stale client) and the caller must rebalance instead. Never throws. */ export function nextKeyOrRebalance( afterKey: string | null, beforeKey: string | null, ): string | null { // Guard reversed/equal neighbours ourselves: generateKeyBetween throws only // on equal keys and silently returns a wrong key when after > before, so a // stale client could otherwise corrupt the order — force a rebalance instead. if (afterKey !== null && beforeKey !== null && afterKey >= beforeKey) return null; let key: string; try { key = keyBetween(afterKey, beforeKey); } catch { return null; } return key.length > MAX_SORT_KEY_LENGTH ? null : key; }