Package Exports
- @moon7/sort
Readme
🌙 @moon7/sort
A lightweight, functional utility library providing composable sorting functions for arrays and collections in JavaScript and TypeScript applications.
✨ Features
- 🔄 Basic Comparators - Ascending, descending, and random sorting functions
- 🔍 Property-based Sorting - Sort by specific object properties
- 🔀 Composable API - Combine multiple sort criteria effortlessly
- 📊 Natural Sorting - Intelligent string sorting with proper handling of numbers
- ⛓️ Chaining - Order, group, and prioritize items with composable functions
🧩 Motivation
Sorting functions in JavaScript compare two values and return a numeric result: negative (-1) when the first value is less than the second, positive (1) when greater, and zero (0) when they're equal.
// A sorting function's type signature
type Comparator<T> = (a: T, b: T) => number;
// Common imperative sorting pattern
numberList.sort((a, b) => a - b);While simple cases are straightforward, complex sorting logic can quickly become unwieldy when written imperatively. This library takes a functional approach, providing composable building blocks that you can combine to create sophisticated sorting behaviors with minimal code.
import { ascending, descending } from "@moon7/sort";
// Simple sorting using ascending/descending comparators
list.sort(ascending);
// How it works:
// const ascending: Comparator<T> = (a, b) => a === b ? 0 : a < b ? -1 : 1;
// const descending: Comparator<T> = (a, b) => a === b ? 0 : a < b ? 1 : -1;Most functions in this library are higher-order functions - they accept other functions as arguments and return new functions, enabling powerful composition patterns.
import { by, order, naturally, descending } from "@moon7/sort";
// Sort by name, ascending
list.sort(by(x => x.name));
// Sort by name, descending
list.sort(by(x => x.name, descending));
// How it works:
// by takes a mapping function and an optional comparator, returning a new comparator
// const by = (map, cmp: Comparator<T> = ascending): Comparator<T> => (a, b) => cmp(map(a), map(b));
// Combining multiple sort criteria
// Notice how this reads as a declaration of our sorting requirements
list.sort(
order(
by(x => x.name, naturally),
by(x => x.age, descending),
by(x => x.lastLogin),
)
);
// How it works:
// order takes multiple comparators and returns a new comparator that applies them in sequenceTraditional imperative approach would require nested if statements or complex logic. With functional composition, we can express the sorting intent declaratively.
📦 Installation
# Using npm
npm install @moon7/sort
# Using yarn
yarn add @moon7/sort
# Using pnpm
pnpm add @moon7/sort🚀 Usage
🔄 Basic Sorting
import { ascending, descending, dir, ASC, DESC, random, randomly } from '@moon7/sort';
// Sort an array in ascending order
const numbers = [3, 1, 4, 2];
numbers.sort(ascending);
// [1, 2, 3, 4]
// Sort in descending order
numbers.sort(descending);
// [4, 3, 2, 1]
// Sort using a direction flag (boolean)
numbers.sort(dir(ASC)); // ascending
numbers.sort(dir(DESC)); // descending
// Sort in random order
// Note: this has bias, not for statistical applications
numbers.sort(random(0.5));
// Same as above, with default probability threshold
numbers.sort(randomly);⚠️ Note: The
random()andrandomlyfunctions produce biased results and are not suitable for statistical or cryptographic applications. For proper random shuffling, use the Fisher-Yates algorithm instead.
🔍 Sorting Objects by Properties
import { by, order, descending } from '@moon7/sort';
const people = [
{ name: 'Alice', age: 30 },
{ name: 'Bob', age: 25 },
{ name: 'Charlie', age: 30 }
];
// Sort by age (ascending by default)
people.sort(by(p => p.age));
// [
// { name: 'Bob', age: 25 },
// { name: 'Alice', age: 30 },
// { name: 'Charlie', age: 30 }
// ]
// Sort by age descending
people.sort(by(p => p.age, descending));
// Sort by age, then by name descending
people.sort(order(
by(p => p.age),
by(p => p.name, descending)
));
// [
// { name: 'Bob', age: 25 },
// { name: 'Charlie', age: 30 },
// { name: 'Alice', age: 30 }
// ]📊 Natural Sorting
import { natural, naturally, by, Sensitivity } from '@moon7/sort';
const versions = ['v1.10', 'v1.2', 'v1.1'];
// Sort with natural comparison (1.2 comes before 1.10)
versions.sort(natural());
// ['v1.1', 'v1.2', 'v1.10']
// Using pre-configured naturally constant (same as natural() with default settings)
versions.sort(naturally);
// ['v1.1', 'v1.2', 'v1.10']
// Sort strings differently based on their case sensitivity
const names = ['alice', 'Alice', 'bob', 'Bob'];
names.sort(natural(Sensitivity.Case));
// ['alice', 'Alice', 'bob', 'Bob']
// Sort objects with string properties using natural sort
const files = [
{ name: 'file10.txt' },
{ name: 'file2.txt' }
];
files.sort(by(f => f.name, naturally));
// [
// { name: 'file2.txt' },
// { name: 'file10.txt' }
// ]⛓️ Advanced Sorting
import { where, nullable, group, reverse, conditional } from '@moon7/sort';
// Sort active items first, then by name
const items = [
{ name: 'Task 1', active: false },
{ name: 'Task 2', active: true },
{ name: 'Task 3', active: false },
{ name: 'Task 4', active: true },
];
items.sort(where(x => x.active, by(x => x.name)));
// [
// { name: 'Task 2', active: true },
// { name: 'Task 4', active: true },
// { name: 'Task 1', active: false },
// { name: 'Task 3', active: false },
// ]
// Sort with null values first
const products = [
{ name: 'Product A', price: 10 },
{ name: 'Product B', price: null }
];
products.sort(nullable(p => p.price));
// [
// { name: 'Product B', price: null },
// { name: 'Product A', price: 10 }
// ]
// Group items by status, then sort by date within groups
const tasks = [
{ status: 'pending', created: new Date(2023, 1, 1) },
{ status: 'active', created: new Date(2023, 2, 1) },
{ status: 'active', created: new Date(2023, 1, 15) }
];
tasks.sort(group(
x => x.status,
// active at the top, archived at the bottom
by(status => ['active', 'pending', 'archived'].indexOf(status)),
// within each group, sort by created
by(x => x.created)
));
// [
// { status: 'active', created: new Date(2023, 1, 15) },
// { status: 'active', created: new Date(2023, 2, 1) },
// { status: 'pending', created: new Date(2023, 1, 1) }
// ]
// Conditional sorting
const numbers = [-5, -2, 3, 1];
numbers.sort(conditional(
(a, b) => a < 0 && b < 0, // If both numbers are negative
descending, // Sort negative numbers in descending order
ascending // Sort other numbers in ascending order
));
// [-2, -5, 1, 3]📖 API Reference
The library provides these key functions:
| Function | Description |
|---|---|
ascending(a, b) |
Compares values in ascending order |
descending(a, b) |
Compares values in descending order |
dir(isAscending) |
Creates a comparator for a specific direction |
random(p) |
Creates a comparator that sorts randomly with given probability (biased) |
randomly |
Pre-configured random sort comparator with default settings (biased) |
natural(sensitivity?) |
Creates a comparator for natural string sorting |
naturally |
Pre-configured natural sort comparator with default settings |
by(map, cmp?) |
Creates a comparator based on a property or derived value |
where(predicate, cmp?) |
Creates a comparator that prioritizes items matching a predicate |
nullable(get, cmp?) |
Creates a comparator that prioritizes null/undefined values |
group(selector, groupOrder?, itemOrder?) |
Groups items and orders both groups and items within groups |
order(...fns) |
Combines multiple comparators in sequence |
reverse(fn, ignore?) |
Reverses the result of another comparator |
conditional(condition, ifTrue, ifFalse) |
Selects between comparators based on a condition |
sort(items, cmp?) |
Creates a sorted copy of an iterable |
🔗 Related Libraries
| Library | Description | npm |
|---|---|---|
| @moon7/async | Asynchronous utilities for managing promises, concurrent operations, and timing | |
| @moon7/inspect | Runtime type checking with powerful, composable type inspectors | |
| @moon7/result | Functional error handling with Result and Maybe types | |
| @moon7/signals | Reactive programming with Signals, Sources, and Streams |
🤝 Contributing
We welcome contributions from everyone! See our contributing guide for more details on how to get involved. Please feel free to submit a Pull Request.
📝 License
This project is licensed under the MIT License - see the LICENSE file for details.
🌟 Acknowledgements
Created and maintained by Munir Hussin.