thenBy.js
A tiny JavaScript library for chaining multiple sort comparators with a firstBy().thenBy() syntax.
Repository Health
Technical Analysis
thenBy is a micro-library that solves a specific pain point in JavaScript sorting: composing multiple comparison criteria into a single comparator function without writing deeply nested conditional logic by hand. It exposes a firstBy().thenBy().thenBy() chain that returns a valid Array.prototype.sort() comparator, falling back to each subsequent criterion only when the previous one reports equality.
Beyond raw compare functions, it accepts property names and unary key-selector functions as shortcuts, and layers on options for descending order, case-insensitive string comparison, custom cmp functions (e.g. domain-specific ordering like card ranks), and native Intl.Collator locale-aware comparison. The library ships as CommonJS, ESM, and a minified UMD build with hand-maintained TypeScript declarations, and has no runtime dependencies.
What You Get
- A chainable firstBy().thenBy().thenBy() API that composes multiple comparator functions into one, applying each only when the prior one returns equality.
- Shortcut syntax for sorting by property name (“population”) or unary key-selector function (v => v.population) instead of hand-writing two-argument compare functions.
- Built-in direction and ignoreCase options plus support for custom cmp functions and native Intl.Collator locale-aware comparison.
- CommonJS, ESM, and minified UMD builds shipped together with hand-maintained TypeScript declarations.
Common Use Cases
- Sorting a table of records by multiple columns (e.g. country, then population) where later columns only matter as tiebreakers.
- Building user-configurable multi-column sort UIs where each column adds another .thenBy() call.
- Locale-aware string sorting via Intl.Collator passed in as a custom cmp function.
- Domain-specific ordering (like card rank/suit) using a custom comparator lookup instead of the default </> compare.
Under The Hood
Architecture
Single-file micro-library built around one closure (thenBy.js), all logic hand-edited in that file; build.mjs mechanically derives thenBy.module.js (CommonJS via a regex swap of var firstBy = to module.exports =), thenBy.esm.js (source plus export/export default appended), and a Terser-minified UMD bundle (thenBy.min.js) wrapped by a hand-written UMD helper — so the true source of truth is a single small IIFE (tb/makeCompareFunction/identity/ignoreCase) with no internal module boundaries, no dependency injection, and a flat, closure-based data flow: each .thenBy() call composes the accumulated comparator with a newly built one via short-circuit OR, returning a new function carrying its own .thenBy method so the chain is entirely self-referential. Changing the core tb/makeCompareFunction pair would ripple through every generated artifact since they’re all mechanical transforms of the same source, making this an unusually low-risk, single-point-of-truth architecture for its size.
Tech Stack Zero runtime dependencies; the only dependencies are devDependencies terser (for minification) and typescript (used solely to type-check the hand-maintained thenBy.module.d.ts against the build output). Build tooling is a single Node ESM script with no bundler beyond Terser; tests run via Node’s built-in test runner and strict assert module (no third-party test framework). The package is published to npm with an exports map that resolves ESM vs CommonJS automatically, and ships hand-maintained .d.ts types for editor/TypeScript integration. No web framework, ORM, database, or external service integration — this is a pure computational utility. CI runs across multiple current Node versions on push and pull request.
Code Quality Tests exist and are comprehensive for the library’s surface area — covering function-based comparators, unary key-selector functions, property-name shortcuts (including missing-property handling), ascending/descending via multiple accepted direction tokens, case-insensitive sorting, custom cmp functions (card-rank ordering, Intl.Collator), numeric-string coercion, and even an unasserted performance-comparison suite documented as intentionally non-flaky. Tests run against the built CommonJS output, so the test script chains build, type-check, and test run, meaning a broken build or type error fails CI before any test executes. Error handling is minimal by design (a small pure function with no I/O, no async, no external failure modes to catch); naming is consistent though terse inside the closure, while outer-facing names are clear. There is no linter or formatter configured — an editor-config file is the only enforced style, with TypeScript type-checking of the accompanying declarations substituting for full static typing of the implementation itself.
API Design Its distinguishing design choice is inferring compare-function shape from arity — a function with a single parameter triggers automatic wrapping of a unary key-selector into a full two-argument comparator — so callers never need to remember whether to write a two-argument or one-argument function; the library picks the right one from a function’s declared parameter count. Combined with accepting bare property-name strings and an options object that also doubles as a bare direction shorthand when passed a primitive, the public API compresses what’s normally several utility functions (sortBy, orderBy, compare-chain) into one small overload set, all while returning a plain function compatible with native Array.prototype.sort (no proprietary sort call needed) and self-attaching a .thenBy method so the chain requires no imports beyond the initial call. This isn’t algorithmically novel (comparator composition has precedent, e.g. Lodash’s multi-iteratee sortBy) but the ergonomics — zero dependencies, tiny source, arity-based dispatch, native-sort compatibility — are unusually well-executed for such a small surface.
Used by 2 apps in this directory
Baserow
No Code Platforms · Databases
Open-source no-code platform to build databases, apps, automations, and AI agents — self-hosted or cloud, with full data ownership.
Umami
Analytics
Privacy-first web analytics that respects your users — self-hosted, cookieless, and GDPR compliant out of the box.