{"id":17321172,"url":"https://github.com/lambda-fairy/juxta","last_synced_at":"2025-08-15T19:18:34.820Z","repository":{"id":57287596,"uuid":"101455171","full_name":"lambda-fairy/juxta","owner":"lambda-fairy","description":"Composable comparisons for TypeScript","archived":false,"fork":false,"pushed_at":"2019-02-08T08:21:58.000Z","size":60,"stargazers_count":10,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-08-07T00:37:17.584Z","etag":null,"topics":["compare","typescript"],"latest_commit_sha":null,"homepage":"https://npm.im/juxta","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lambda-fairy.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-08-26T01:52:22.000Z","updated_at":"2023-02-27T15:48:30.000Z","dependencies_parsed_at":"2022-08-29T12:10:40.952Z","dependency_job_id":null,"html_url":"https://github.com/lambda-fairy/juxta","commit_stats":null,"previous_names":["lfairy/juxta"],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/lambda-fairy/juxta","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lambda-fairy%2Fjuxta","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lambda-fairy%2Fjuxta/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lambda-fairy%2Fjuxta/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lambda-fairy%2Fjuxta/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lambda-fairy","download_url":"https://codeload.github.com/lambda-fairy/juxta/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lambda-fairy%2Fjuxta/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":269734145,"owners_count":24466554,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-10T02:00:08.965Z","response_time":71,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["compare","typescript"],"created_at":"2024-10-15T13:35:37.790Z","updated_at":"2025-08-15T19:18:34.789Z","avatar_url":"https://github.com/lambda-fairy.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# juxta\n\nA library for writing composable comparison functions.\n\n*juxta* has the following features:\n\n* **Composable**. You can express \"sort by X then by Y\" and \"sort X before Y\" using a uniform API.\n* **Type-safe**. *juxta* has complete TypeScript definitions. If it compiles, it (probably) works.\n* **Readable**. Code that uses *juxta* is much easier to understand and audit than the equivalent written out in full.\n\n\n## Example\n\n```typescript\nimport compare, { Comparator } from 'juxta';\nimport _ from 'lodash';\nimport moment from 'moment';\n\ninterface SearchResult {\n    name: string | null;\n    rank: number;\n    time: Moment;\n}\n\nconst compareNames: Comparator\u003cstring | null\u003e =\n    compare\u003cstring\u003e()\n        .append\u003cnull\u003e(_.isNull);\n\nconst compareSearchResults: Comparator\u003cSearchResult\u003e =\n    compare.on((s: SearchResult) =\u003e s.rank)\n        .then(compare.on((s: SearchResult) =\u003e s.time).reverse())\n        .then(compareNames.from((s: SearchResult) =\u003e s.name));\n\nlet results: SearchResult[] = [\n    {\n        name: \"Humanity Has Declined\",\n        rank: 2,\n        time: moment('2012-07-02'),\n    },\n    {\n        name: null,\n        rank: 1,\n        time: moment('1818-05-05'),\n    },\n    {\n        name: \"Ping Pong The Animation\",\n        rank: 0,\n        time: moment('2014-04-11'),\n    },\n];\n\nresults.sort(compareSearchResults);\n```\n\n\n## Why *juxta*?\n\nJavaScript provides a built-in method to sort arrays, called [`Array.prototype.sort`][Array.prototype.sort]. You can customize how it compares elements by passing a *comparison function* to the method.\n\n[Array.prototype.sort]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort\n\nUnfortunately, these comparison functions can be hard to write and understand. For example, here's a function that compares nullable strings, sorting `null` values last:\n\n```typescript\nfunction compareStringsNullLast(a: string | null, b: string | null): number {\n    if (a === null \u0026\u0026 b === null) {\n        return 0;\n    } else if (a === null \u0026\u0026 b !== null) {\n        return -1;\n    } else if (a !== null \u0026\u0026 b === null) {\n        return 1;\n    } else {\n        return a \u003c b ? -1 : a \u003e b ? 1 : 0;\n    }\n}\n```\n\nI'd hate to be the person reviewing that code.\n\n**Fun fact!** There's a bug in that example! Can you find it?\n\nWith *juxta*, this function can be written as follows:\n\n```typescript\nconst compareStringsNullLast = compare\u003cstring\u003e().append\u003cnull\u003e(_.isNull);\n```\n\nThat's much less typing -- and more importantly, it is guaranteed correct.\n\n\n## Creating comparison functions\n\n*juxta* exposes its API through a default export. By convention we name it `compare`:\n\n```typescript\nimport compare from 'juxta';\n```\n\nThere are three main ways to create a comparison function:\n\n* Use `compare\u003cT\u003e()` to compare values of type `T` using the built-in `\u003c` and `\u003e` operators. For example, `compare\u003cnumber\u003e()` compares values of type `number`.\n* Use `compare(existingFunction)` to wrap an existing comparison function in a *juxta* object. This lets you use the helper methods detailed below. For example, `compare((s: string, t: string) =\u003e s.localeCompare(t))` compares strings case-insensitively using the current locale.\n* Use `compare.on(...)` to transform the input before comparing it. For example, `compare.on((x: any[]) =\u003e x.length)` compares arrays by length.\n\n\n## Using comparison functions\n\nAll *juxta* objects are functions, so you can pass them directly to `.sort()`:\n\n```typescript\nconst compareNumbers = compare\u003cnumber\u003e();\nconsole.log([3, 2, 1].sort(compareNumbers));  // [1, 2, 3]\n```\n\n\n## Ascending vs descending order\n\n`compare()` and `compare.on()` use ascending order (smallest first) by default.\n\nTo sort by descending order (largest first) instead, use the `.reverse()` method: `compare\u003cnumber\u003e().reverse()`.\n\nCalling `.reverse()` twice gives the same result as calling it zero times.\n\n\n## Transforming the input\n\nEach elf has a hat, and each hat has a [bauble]. We want to sort elves by the baubles on their hats. (The comparison of baubles is a solved problem and has been defined elsewhere.)\n\nThis can be written as follows:\n\n```typescript\nconst compareElvesByBaubles = compareBaubles.from((e: Elf) =\u003e e.hat.bauble);\n```\n\n[bauble]: http://www.dictionary.com/browse/bauble\n\nNote that since we're transforming *inputs*, not outputs, the method calls may look \"backwards\" to what you would expect. This is apparent when using more than one `.from()` call:\n\n```typescript\nconst compareElvesByBaubles = compareBaubles\n    .from((h: Hat) =\u003e h.bauble)\n    .from((e: Elf) =\u003e e.hat);\n```\n\n\n## Sorting by multiple fields\n\nOn testing, it was found that there are elves with identical baubles. In this case, they can be distinguished by the colors of their socks.\n\nTo sort by more than one property, chain the comparison functions using `.then()`:\n\n```typescript\nconst compareElvesByBaublesAndSockColor = compareElvesByBaubles\n    .then(compare.on((e: Elf) =\u003e e.sock.color));\n```\n\n\n## Partitioning the input into groups\n\nOh no! Some elves have rebelled against the social order, and replaced the baubles on their hats with [trinkets]. Your assistant has provided you with two options: either punish the \"trinketeers\" by sorting them last, or cede to their demands and sort them first. Luckily, *juxta* allows for both:\n\n[trinkets]: https://www.merriam-webster.com/dictionary/trinket\n\n```typescript\n// TODO: implement sock colors under the new regime\n\nconst compareTrinketsFirst = compareElvesByBaubles\n    .prepend((e: Elf) =\u003e e.hasTrinket(), compareElvesByTrinkets);\n\nconst compareTrinketsLast = compareElvesByBaubles\n    .append((e: Elf) =\u003e e.hasTrinket(), compareElvesByTrinkets);\n```\n\nIn more peaceful times, `.prepend()` and `.append()` can be used for separating `null`, `undefined`, and `NaN` values as well:\n\n```typescript\nimport _ from 'lodash';\n\nconst compareNumbers = compare\u003cnumber\u003e().append(isNaN);\n\nconst compareStrings = compare\u003cstring\u003e()\n    .prepend\u003cnull\u003e(_.isNull);\n    .append\u003cundefined\u003e(_.isUndefined);\n\nconst compareNumbersBeforeStrings = compareStrings\n    .prepend\u003cnumber\u003e(_.isNumber, compareNumbers);\n```\n\nIn the definition of `compareStrings`, the `.prepend()` and `.append()` calls together extend the input type from `string` to `string | null | undefined`. These type changes can confuse the TypeScript compiler; writing out the generic parameters explicitly (`\u003cnull\u003e` and `\u003cundefined\u003e`) helps it along.\n\n\n## Type annotations\n\n*juxta* uses advanced TypeScript features to model its API. This means that you may need to write more type annotations than usual when using the library. Here are some general tips for using TypeScript with *juxta*:\n\n* Enable `noImplicitAny`. This ensures that if TypeScript fails to infer a type, it will raise an error instead of defaulting to `any`.\n\n* If a method takes a callback, give explicit types to each of the callback's arguments. If a method takes generic parameters, fill out each parameter. The examples in this documentation tend to follow these rules, so you're okay if you copy from them.\n\n* If you use an IDE such as Visual Studio Code, you can inspect the inferred type of any expression by hovering over it. This can help debug confusing type errors.\n\n\n## Case-insensitive string comparisons\n\nUnlike some other comparison libraries, *juxta* does not provide a simple way to compare strings case-insensitively. This is because string comparison is a subtle topic that many people get wrong. I do not want to add foot-guns by presenting things as less complex than they really are.\n\n*juxta* does provide `compare.locale()`, which wraps the built-in [`Intl.Collator`][Intl.Collator] object. For example, `compare.locale('en', { sensitivity: 'base' })` will compare case-insensitively according to English sorting rules.\n\n[Intl.Collator]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Collator\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flambda-fairy%2Fjuxta","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flambda-fairy%2Fjuxta","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flambda-fairy%2Fjuxta/lists"}