⚡ Zero Dependencies  ·  MIT License

Fractional Indexing
for JavaScript

Generate lexicographically ordered indexes for inserting items between existing ones — without reindexing your entire list. Built for collaborative apps, drag-and-drop, and real-time sync.

Get Started → View on GitHub
$ npm install frac-indexes
3
Core Functions
0
Dependencies
15+
Decimal Precision
100%
Test Coverage
Why frac-indexes

Everything you need for ordered lists

A focused, battle-tested library that handles the hard parts of fractional indexing so you don't have to.

🏭

Production-Ready

Tested against edge cases including small gaps, bulk insertions, and move operations under high-frequency concurrent load.

🛡️

Boundary-Safe

Multiple validation layers prevent range violations. Stays within valid bounds and produces moderate-sized index values with bounded jitter.

Bulk Operations

Efficient insertion and relocation of multiple items in a single call with generateBulkIndexes and generateRelocationIndexes.

🔄

Real-Time Friendly

Handles high-frequency collaborative editing reliably. 100% reliability under 50+ concurrent operations.

📦

Zero Dependencies

Lightweight with no external dependencies. Works with Node.js (ES6+) and browsers (ES5 compatible build included).

💎

TypeScript Ready

Full type definitions included out of the box. Drop it into any TypeScript project with no extra setup required.


Quick Start

Up and running in seconds

Import the functions you need and start generating indexes immediately.

Node.js ES6+ Basic usage
const { generateFractionalIndex, generateBulkIndexes }
  = require('frac-indexes');

// Create first item in an empty list
const first = generateFractionalIndex(null, null);
// → "0.0005489960"

// Add item at the end
const last = generateFractionalIndex(first, null);
// → "0.0016129989"

// Insert between two items
const middle = generateFractionalIndex(first, last);
// → "0.001092958115464"

// Bulk insert 5 items at once
const bulk = generateBulkIndexes(first, last, 5);
Browser ES5 Script tag usage
<!-- Include the ES5 compatible build -->
<script src="node_modules/frac-indexes/src/es5-indexing.js"></script>

<script>
// All functions under FractionalIndexing namespace
var first = FractionalIndexing
  .generateFractionalIndex(null, null);

var last = FractionalIndexing
  .generateFractionalIndex(first, null);

var middle = FractionalIndexing
  .generateFractionalIndex(first, last);
</script>

// Or with Node.js ES5:
var fi = require('frac-indexes/src/es5-indexing');
var idx = fi.generateFractionalIndex(null, null);

API Reference

Three powerful functions

A focused API with clear, predictable behavior and full TypeScript support.

generateFractionalIndex
(prevIndex: string|null, nextIndex: string|null) → string

Generates a single fractional index that sorts between two existing indexes. Pass null for either boundary to indicate the start or end of the list.

ParameterTypeDescription
prevIndexstring | nullThe index of the item before the desired position. Pass null to insert at the beginning.
nextIndexstring | nullThe index of the item after the desired position. Pass null to insert at the end.
Returnsstring — a new fractional index that sorts between the two inputs
generateBulkIndexes
(prevIndex: string|null, nextIndex: string|null, count: number) → string[]

Generates multiple fractional indexes between two existing indexes in a single efficient call. Items are sequentially spaced within the gap.

ParameterTypeDescription
prevIndexstring | nullThe lower boundary index (or null for start of list).
nextIndexstring | nullThe upper boundary index (or null for end of list).
countnumberNumber of indexes to generate. Returns an empty array if count ≤ 0.
Returnsstring[] — array of new indexes in ascending order
generateRelocationIndexes
(targetPrev: string|null, targetNext: string|null, count: number, distributeEvenly?: boolean) → string[]

Generates indexes for moving multiple items to a new position. Supports both evenly-distributed and sequential placement strategies.

ParameterTypeDescription
targetPrevIndexstring | nullIndex before the destination position.
targetNextIndexstring | nullIndex after the destination position.
countnumberNumber of items being relocated.
distributeEvenlybooleanIf true (default), items are spread evenly across the target gap. If false, uses sequential bulk insertion.
Returnsstring[] — array of new indexes for the relocated items

Use Cases

Built for real-world scenarios

Common patterns you can drop straight into your application.

📋

Ordered Task Lists

const tasks = [
  { id: 1, index: generateFractionalIndex(null, null),
    title: "First task" },
  { id: 2, index: generateFractionalIndex(tasks[0].index, null),
    title: "Second task" }
];

// Insert between existing tasks
const newIdx = generateFractionalIndex(
  tasks[0].index,
  tasks[1].index
);
🤝

Collaborative Editing

function insertMultipleItems(prev, next, items) {
  const indexes = generateBulkIndexes(
    prev?.index || null,
    next?.index || null,
    items.length
  );
  return items.map((item, i) => ({
    ...item,
    index: indexes[i]
  }));
}
🖱️

Drag & Drop Reordering

function moveItems(moving, target) {
  const newIndexes = generateRelocationIndexes(
    target.previous?.index || null,
    target.next?.index || null,
    moving.length
  );
  return moving.map((item, i) => ({
    ...item, index: newIndexes[i]
  }));
}

Internals

How it works

Precise math under the hood to keep your indexes always valid and always ordered.

Lexicographic Order

All indexes are high-precision decimal strings formatted so string comparison always matches numeric order.

Bounded Jitter

Random variation is capped at 25% of the available gap on each side of the midpoint — so boundaries are never crossed.

Precision Scaling

Uses 15+ decimal places to gracefully handle microscopic gaps down to floating-point precision limits.

Boundary Guards

Multiple validation layers with IEEE 754 safety margins catch edge cases and fall back to safe midpoints automatically.


Battle-Tested

Production safety tests

Verified against the scenarios that break most fractional indexing implementations.

Small Boundary Scenario

Ensures small gaps never produce out-of-order indexes.

Death by 1000 Cuts

Survives 15+ sequential subdivisions of the same gap.

Microscopic Gaps

Handles gaps down to the floating-point precision limit.

High Frequency

100% reliability under 50+ concurrent operations.