ordercraft
v0.1.0
Published
Correct mixed-type sorting, natural and locale ordering, indexed search, ranking, and fuzzy matching. Zero runtime dependencies.
Downloads
32
Maintainers
Readme
Ordercraft
Deterministic sorting and search for JavaScript data that refuses to stay one type.
Ordercraft gives every value a stable, explicit place in the order. It handles
mixed columns containing numbers, strings, null, undefined, bigint, dates,
booleans, NaN, and very large numeric text without pair-dependent coercion.
It also includes natural and locale-aware strings, exact decimal comparison,
stable multi-column sorting, radix sorting, binary search, indexes, top-k
selection, ranking, and Unicode fuzzy matching.
Install
npm install ordercraftOrdercraft has zero runtime dependencies. It ships ESM and CommonJS entry points, TypeScript declarations, and tree-shakeable subpaths. Node.js 22 or newer is supported; browser bundles work in modern ES2022 environments.
The mixed-value problem
This comparator is tempting:
function compare(a, b) {
if (typeof a === 'number' && typeof b === 'number') return a - b;
return String(a ?? '').localeCompare(String(b ?? ''), undefined, { numeric: true });
}It chooses a comparison rule from the pair of values. A mixed column can then
contain a cycle: a ≤ b, b ≤ c, and a > c. Array.prototype.sort has no
defined result for an intransitive comparator, so input order and engine version
can change the rendered order.
Ordercraft classifies each value once. The default order is:
boolean < number < bigint < Date < string < null < undefinedNumbers come before strings by product decision. Numeric-looking strings remain
strings unless coercion is explicitly requested. NaN and invalid dates have a
deterministic position, signed zero is handled deliberately, and canonical ties
make equal spellings reproducible.
Quick start
import { createComparator, sort } from 'ordercraft';
const compare = createComparator({ strings: 'natural' });
const values = [1e21, '2', 10, 'file10', 'file2', null, undefined, 3];
const ordered = sort(values, { compare });
console.log(ordered);
// [3, 10, 1e+21, '2', 'file2', 'file10', null, undefined]Pass the comparator directly to native sorting when you already have an array:
import { createComparator } from 'ordercraft';
const compare = createComparator({ strings: 'natural' });
const rows = [
{ label: 'file10', value: 10 },
{ label: 'file2', value: 2 },
{ label: 'file1', value: '1' },
];
rows.sort((a, b) => compare(a.value, b.value));For records, selectors run once per row and prepared keys are reused during the sort:
import { orderBy, createOrder } from 'ordercraft';
const natural = createOrder({ strings: 'natural' });
const ordered = orderBy(rows, [
{ select: row => row.value },
{ select: row => row.label, policy: natural },
]);Ordering policies
createOrder(options) returns a policy with compare, prepare, and
comparePrepared. Use the policy when sorting repeatedly, building an index, or
composing criteria.
| Option | Values | Default | Meaning |
| --- | --- | --- | --- |
| strings | ordinal, codePoint, natural, locale | ordinal | String comparison model |
| numericStrings | preserve, coerce | preserve | Whether complete finite decimal/scientific strings become numbers |
| ties | canonical, equivalent | canonical | Refine or retain equivalent representations |
| missing | first, last | last | Placement of null and undefined |
| direction | asc, desc | asc | Direction for defined values |
Coercion is opt-in and uses a complete ASCII decimal/scientific grammar:
import { createComparator } from 'ordercraft';
const numericText = createComparator({ numericStrings: 'coerce', ties: 'equivalent' });
numericText('2e1', 20); // 0
numericText('2px', 20); // positive: it remains text
numericText('1e9999', 20); // positive: overflow remains textWhen decimal strings must retain precision beyond Number, use the exact
decimal comparator:
import { compareDecimal } from 'ordercraft';
compareDecimal('9007199254740993', '9007199254740992'); // 1Locale sorting is explicit and its Intl.Collator is constructed once:
import { createComparator } from 'ordercraft';
const ro = createComparator({
strings: 'locale',
locale: 'ro',
collator: { sensitivity: 'base', numeric: true },
});Use locale: 'host' only when host-dependent collation is intentional. Ordinal,
code-point, and natural modes do not consult the host locale.
Search, selection, and ranking
import {
createIndex, createPrefixIndex, lowerBound, selectKth, topK, rank,
} from 'ordercraft';
const index = createIndex(values);
index.find('file2');
index.range(2, 10);
const prefix = createPrefixIndex(values.filter(v => typeof v === 'string'), { select: value => value });
prefix.search('file');
lowerBound([1, 2, 2, 4], 2); // 1
selectKth([9, 1, 7, 2], 2); // 7
topK([9, 1, 7, 2], 2); // [1, 2]
rank([20, 10, 20]); // [2, 1, 2]topK supports a bounded heap or selection strategy. selectKth avoids a full
sort when only one position matters. Exact and prefix indexes prepare keys once
and answer repeated queries with logarithmic bounds.
Unicode fuzzy matching uses code-point Levenshtein distance and supports a distance threshold, NFC normalization, explicit case preparation, and bounded results:
import { createFuzzyIndex } from 'ordercraft';
const search = createFuzzyIndex(['café', 'caffè', 'catalog'], {
select: value => value,
normalize: 'NFC',
case: 'fold',
});
search.search('cafe', { maxDistance: 1, limit: 5 });Browser and Node
The root import exposes the complete API. Subpaths keep browser bundles small:
| Import | Use |
| --- | --- |
| ordercraft | Complete API |
| ordercraft/compare | Policies and comparator combinators |
| ordercraft/sort | Stable sorting and radix sorting |
| ordercraft/strings | Ordinal, code-point, and natural string primitives |
| ordercraft/decimal | Exact decimal comparison |
| ordercraft/search | Bounds and immutable indexes |
| ordercraft/selection | selectKth and topK |
| ordercraft/rank | Dense, competition, and ordinal ranks |
| ordercraft/fuzzy | Distance and fuzzy indexes |
Every public entry supports ESM and CommonJS. The package has no Node-only runtime dependency in its browser-facing code.
Performance
The default sort uses the engine's stable native sort after preparing each key
once. An explicit adaptive Powersort path is available for data with useful
natural runs, and stable four-pass radix sorting is available for Int32Array
and Uint32Array.
On an Apple M4 Pro with Node 24, the optimized natural filename path sorted
10,000 rows in 9.36 ms, compared with 12.28 ms for natural-orderby 5.0.0 in the
same run. The result is workload-specific: cached Intl.Collator is faster for
homogeneous locale strings, and the full root bundle is larger than a single
purpose library. Ordercraft's advantage is a lawful mixed-value contract plus
composable search and selection primitives.
See PERFORMANCE.md for the release snapshot. Timing claims include the machine, input shape, warmups, batch count, uncertainty, and bundle cost.
API surface
| Area | Exports |
| --- | --- |
| Policies | createOrder, createComparator, compare, by, chain, reverse, nullsFirst, nullsLast |
| Strings | ordinal, codePoint, natural, prepareNatural, compareNaturalTokens |
| Sorting | sort, sortInPlace, orderBy, adaptiveSortInPlace, radixSort |
| Search | lowerBound, upperBound, equalRange, binarySearch, createIndex, createPrefixIndex |
| Selection | selectKth, topK |
| Ranking | rank |
| Exact numbers | compareDecimal |
| Fuzzy | levenshtein, createFuzzyIndex |
| Diagnostics | auditComparator |
Compatibility
The ordering contract is deterministic for supported JavaScript primitives. The
default policy sorts numbers before strings and leaves numeric-looking strings
as strings. This is a product rule; choose numericStrings: 'coerce' when your
data model says otherwise.
Locale order depends on the selected engine's ICU data. Fuzzy distance counts
Unicode code points, not grapheme clusters, and does not transliterate or perform
substring matching. Bound searches require an array already sorted by the same
comparator. auditComparator is an exhaustive finite-pool diagnostic, not a
mathematical proof for all possible values.
Contributing
Run the full local checks before opening an issue or proposing a change:
npm ci
npm test
npm run typecheck
npm run bench:check
npm run package:check
npm run browser:checkThe release process also validates the exact packed archive and records its integrity before publication. See PERFORMANCE.md for the release benchmark snapshot and CHANGELOG.md for version history.
License
Ordercraft is released under the MIT License. The adaptive merge scheduler includes the notice required for its Python Software Foundation source adaptation; see THIRD_PARTY_NOTICES.
