{"id":25478453,"url":"https://github.com/bhosale-ajay/dotless","last_synced_at":"2025-11-06T09:30:32.178Z","repository":{"id":40827495,"uuid":"156312176","full_name":"bhosale-ajay/dotless","owner":"bhosale-ajay","description":"A JavaScript library to write linq style functional code","archived":false,"fork":false,"pushed_at":"2024-02-27T03:14:02.000Z","size":1978,"stargazers_count":5,"open_issues_count":1,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-02-12T15:53:09.801Z","etag":null,"topics":["advent-of-code","functional-programming","javascript","linq","typescript"],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bhosale-ajay.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2018-11-06T02:11:11.000Z","updated_at":"2022-11-27T20:20:33.000Z","dependencies_parsed_at":"2023-12-07T03:22:36.323Z","dependency_job_id":"604a5ab0-67f0-4b6c-9846-dba1c5970576","html_url":"https://github.com/bhosale-ajay/dotless","commit_stats":{"total_commits":47,"total_committers":2,"mean_commits":23.5,"dds":"0.44680851063829785","last_synced_commit":"94eb41b82b3f5833c924c89a88db8fece92a49ac"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bhosale-ajay%2Fdotless","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bhosale-ajay%2Fdotless/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bhosale-ajay%2Fdotless/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bhosale-ajay%2Fdotless/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bhosale-ajay","download_url":"https://codeload.github.com/bhosale-ajay/dotless/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":239489193,"owners_count":19647349,"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","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":["advent-of-code","functional-programming","javascript","linq","typescript"],"created_at":"2025-02-18T14:33:03.814Z","updated_at":"2025-11-06T09:30:32.144Z","avatar_url":"https://github.com/bhosale-ajay.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# dotless\nA JavaScript library to write linq style functional code\n\n## Why dotless?\nIts an experimental library providing functions required to write functional style programs with JavaScript, and which can work with [generator functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*), providing capability for lazy evaluation. I developed this library to solve puzzles from [Advent Of Code](https://adventofcode.com/).\n\nAll the functions are independent to each other and not defined as part of one single class. They can be composed along with other functions.\n\n## Installation\n```\nnpm i dotless --save\n\nor\n\nyarn add dotless\n```\n\n## Example\n```JavaScript\nconst items = query(\n    // Range returns numbers from 1 to 10\n    range(1, 10),\n    // Take which are divided by 3\n    filter(n =\u003e n % 3 === 0),\n    // Multiply them by 2\n    map(n =\u003e n * 2),\n    // Convert to Array\n    toArray\n);\n// [6, 12, 18]\nconsole.log(items);\n```\n\n## Functions\n1. any\n   ```JavaScript\n   // returns true as list contains even number\n   any(x =\u003e x % 2 === 0)([1, 2, 4])\n   // returns false as the list is empty\n   any()([]);\n   // return true as the list has items\n   any()([1, 2, 3]);\n   ```\n1. count\n   ```JavaScript\n   /*\n   returns 1\n   */\n   count(n =\u003e n % 2 === 0)([1, 2, 3]);\n   ```\n1. countBy\n   ```JavaScript\n   /*\n   returns {\n    \"1\" : 4,\n    \"2\" : 2,\n    \"3\" : 1,\n    \"4\" : 2\n   } */\n   countBy()([1, 2, 2, 1, 1, 1, 3, 4, 4]);\n\n   const oddOrEven = countBy(n =\u003e n % 2 ? \"odd\" : \"even\"); \n   /* returns {\n       \"odd\" : 5,\n       \"even\" : 4\n   } */\n   oddOrEven([1, 2, 3, 4, 5, 6, 7, 8, 9])\n   ```\n1. cycle\n   ```JavaScript\n   /*\n   Iterates infinitely over a collection\n\n   returns [1, 2, 3, 1, 2, 3, 1, 2, 3, 1]\n   */\n   query(cycle([1, 2, 3]), \n         take(10),\n         toArray);\n   ```\n1. each\n   ```JavaScript\n   /*\n   Calls a method for every item in iterator\n   The iterator must be consumed (toArray, count)\n   Updates every property \"v\" for every item\n   */\n   const input = [{ n: 1, v: false}, \n                  { n: 2, v: false}];\n   query(input,\n      each(p =\u003e { p.v = true; }),\n      count(p =\u003e p.v)\n   );\n   ```\n1. filter\n   ```JavaScript\n   // works similar to Array.filter\n   const onlyOdd = filter(n =\u003e n % 2 !== 0)\n   // Returns [1, 3, 5, 7, 9]\n   onlyOdd([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])\n   ```\n1. findPairs\n   ```JavaScript\n   // helps finding pairs from source\n   // pairs can be\n   // - mutuallyExclusive like a can divide b, but b can't divide b\n   // - not exclusive like a === b or a = reverse(b)\n   const itemsWithTheirDoubles = findPairs(\n        (a, b) =\u003e a * 2 === b,\n        // The list is mutually exclusive\n        true\n   );\n   /* returns array of [\n         a,  b, index of A, index of B\n        [2,  4, 0, 2],\n        [3,  6, 3, 1],\n        [7, 14, 5, 4]\n   ]; */\n   itemsWithTheirDoubles([2, 6, 4, 3, 14, 7]);\n\n   const duplicateItems = findPairs((a, b) =\u003e a === b);\n   /* returns array of [\n         a,  b, index of A, index of B\n        [2, 2, 0, 6],\n        [6, 6, 1, 3],\n        [1, 1, 4, 7]\n   ]; */\n   duplicateItems([2, 6, 4, 6, 1, 8, 2, 1]);\n\n   const duplicateItemsMutuallyExclusive = findPairs((a, b) =\u003e a === b, true);\n   /* returns array of [\n         a,  b, index of A, index of B\n        [2, 2, 0, 6],\n        [6, 6, 1, 3],\n        [6, 6, 3, 1],\n        [1, 1, 4, 7],\n        [2, 2, 6, 0],\n        [1, 1, 7, 4]\n   ]; */\n   duplicateItemsMutuallyExclusive([2, 6, 4, 6, 1, 8, 2, 1]);\n   ```\n1. first\n   ```JavaScript\n   // returns 11\n   first()([11, 12, 13]);\n\n   // returns null\n   first()([]);\n\n   // returns 2\n   first(x =\u003e x % 2 === 0)([1, 2, 4]);\n\n   // the predicate can maintain its state\n   const seenBefore = () =\u003e {\n        const seen = {};\n        return (n) =\u003e {\n            if (seen[n]) {\n                return true;\n            } else {\n                seen[n] = true;\n                return false;\n            }\n        };\n   };\n   const firstDuplicate = first(seenBefore());\n   // returns 2\n   firstDuplicate([1, 2, 4, 2, 5, 4]);\n   ```\n1. groupBy\n   ```JavaScript\n   // groupBy by property\n   const timNY = { n: \"Tim\",  s : \"NY\"};\n   const billWA = { n: \"Bill\", s : \"WA\"};\n   const steveCA = { n: \"Steve\", s : \"CA\"};\n   const andrewWA = { n: \"Andrew\", s : \"WA\"};\n   const input = [\n       timNY,\n       billWA,\n       steveCA,\n       andrewWA\n   ];\n   /* returns {\n      \"NY\" : [timNY],\n      \"CA\" : [steveCA],\n      \"WA\" : [billWA, andrewWA]\n   } */\n   groupBy(\"s\")(input);\n\n   // groupBy by function\n   const input = [1, 2, 3, 4, 5, 6, 7, 8, 9];\n   /* returns {\n       \"odd\" : [1, 3, 5, 7, 9],\n       \"even\" : [2, 4, 6, 8]\n   } */\n   groupBy(n =\u003e n % 2 ? \"odd\" : \"even\")(input);\n   ```\n1. iterate\n   ```JavaScript\n   // acts as an infinite loop\n   // first it yields the default value\n   // and calls iterator to generate values\n   // returns [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]\n   query(\n      iterate(([a, b]) =\u003e [b, a + b], [0, 1]),\n      map(([a, _]) =\u003e a),\n      take(10),\n      toArray\n   );\n   ```\n1. map\n   ```JavaScript\n   // similar to Array.map\n   const doubleIt = map(n =\u003e n * 2);\n   // returns [4, 6, 8, 10];\n   doubleIt([2, 3, 4, 5]);\n   ```\n1. mapMany\n   ```JavaScript\n    const expand = mapMany(function*(n) {\n        for (let i = 0; i \u003c= n; i++) {\n            yield i;\n        }\n   });\n   // returns [0, 1, 2, 0, 1, 2, 3];\n   expand([2, 3]);\n   ```\n1. mapWithLast\n   ```JavaScript\n   // Combines reduce and map\n   // instead of returning a single accumulator returns iterator of accumulator\n   const location = { distance : 0, hops : 0};\n   const strides = [1, 4, 2];\n   const run = mapWithLast(({distance, hops}, stride: number) =\u003e ({\n       distance: distance + stride,\n       hops: hops + 1\n   }), location);\n   // returns [{distance: 1, hops: 1}, {distance: 5, hops: 2}, {distance: 7, hops: 3}]\n   run(strides);\n   ```\n1. matchesToArray\n   ```JavaScript\n   // Applies regex to string and maps matches to array\n   // matches can be converted using an optional convertor\n   const input = \"p=\u003c3088,2748,-1039\u003e, v=\u003c-103,-136,94\u003e\";\n   const regex = /-*\\d+/g;\n   // returns [3088, 2748, -1039, -103, -136, 94]\n   matchesToArray(input, regex, m =\u003e +m[0]);\n   ```\n1. query\n   ```JavaScript\n   // The engine of the library\n   // Takes array of functions\n   // The first parameter can be argument or a function returning a value\n   // query(1, a, b, c) === c(b(a(1)))\n   // returns 36\n   query(\n      range(1, 10),\n      filter(n =\u003e n % 3 === 0),\n      map(n =\u003e n * 2),\n      reduce((acc, n) =\u003e acc + n, 0)\n   );\n   ```\n1. range\n   ```JavaScript\n   // returns [1, 3, 5, 7]\n   range(1, 8, 2)\n\n   // returns [11, 12, 13, 14]\n   range(11, 14)\n\n   // returns [8, 6, 4, 2]\n   range(8, 1, -2)\n   ```\n1. reduce\n   ```JavaScript\n   // works similar to Array.reduce, with a little change the seed value is not optional\n   const sumIt = reduce((acc, n) =\u003e acc + n, 0);\n   // returns 55\n   const actual = sumIt([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);\n   ```\n1. sort, ascendingBy, ascendingByLocale, descendingBy, descendingByLocale, mergeCompareFns\n   ```JavaScript\n   const tim12 = { name : \"Tim\",  age : 12 };\n   const abe21 = { name : \"Abe\",  age : 21 };\n   const mark21 = { name : \"Mark\", age : 21 };\n   const bill22 = { name : \"Bill\", age : 22 };\n   const items = [ tim12, abe21, mark21, bill22 ];\n   const sorter = sort(descendingBy(\"age\"), ascendingBy(\"name\"));\n   // returns [ bill22, abe21, mark21, tim12 ]\n   sorter(items);\n   // sort the items array as follows [ bill22, abe21, mark21, tim12 ]\n   // mergeCompareFns merge functions to use them with inbuilt \"sort\" method\n   items.sort(mergeCompareFns(descendingBy(i =\u003e i.age), ascendingBy(\"name\")))\n\n   const f1 = { name: \"AB\", online: true, surname: \"G\" };\n   const f2 = { name: \"CD\", online: false, surname: \"G\" };\n   const f3 = { name: \"EF\", online: true, surname: \"K\" };\n   const f4 = { name: \"GH\", online: false, surname: \"K\" };\n   const f5 = { name: \"IJ\", online: true, surname: \"G\" };\n   const fs = [f1, f2, f3, f4, f5];\n\n   const onlineSorter = (a: Friend, b: Friend) =\u003e\n       a.online === b.online ? 0 : a.online ? -1 : 1;\n   const friendSorter = mergeCompareFns(\n       onlineSorter,\n       // Only properties of String type can be passed here\n       ascendingByLocale(\"surname\"),\n       // You can also pass, two more parameters\n       // locales and options\n       // Refer https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare \n       ascendingByLocale(\"name\", 'de', { sensitivity: 'base' })\n   );\n\n   // returns [f1, f5, f3, f2, f4]\n   fs.sort(friendSorter);\n   ```\n1. take\n   ```JavaScript\n   const takeTwo = take(2);\n   // returns [11, 12]\n   takeTwo([11, 12, 13, 14]);\n   ```\n1. toArray\n   ```JavaScript\n   // Just an alias for Array.from\n   // Returns [1, 2, 3, 4] by consuming iterator returned by range\n   toArray(range(1, 4))\n   ```\n1. Result\u0026lt;T\u0026gt;\n\n   A type simile to Maybe, it combines two types `SuccessResult\u003cT\u003e` and `ErrorResult`.\n   ```JavaScript\n   function divide(\n     a: number, \n     b: number) : Result\u003cnumber\u003e {\n      if(b === 0) {\n        return new ErrorResult(\"Can not divide by 0.\");\n      }\n      return new SuccessResult(a / b);\n   }\n   const r = divide(4, 2);\n   if(r.IsSuccess) {\n       console.log(`Result is ${r.value}`);\n   } else {\n       console.log(r.message);\n   }\n   ```\n\n1. PromiseResult\u0026lt;T\u0026gt;\n\n   A type which combines Promise and Result\u0026lt;T\u0026gt;.\n\n1. keptPromise\n\n   A function which wraps, creating Promise which returns PromiseResult.\n\n   ```JavaScript\n   function divide(\n       a: number, \n       b: number) : PromiseResult\u003cnumber\u003e {\n       return keptPromise((success, failure) =\u003e {\n           if(b === 0) {\n               failure(\"Can not divide by 0.\");\n           } else {\n               success(a / b);\n           }\n       });\n   }\n   ```\n\n1. buildTrain\n\n   Executes functions returning Result\u0026lt;T\u0026gt; and returns their `SuccessResult` values as Array. If a function returns `ErrorResult`, then it halts the execution and returns that `ErrorResult`. \n\n   ```JavaScript\n   const a = 12;\n   const b = true;\n   const c = \"done\";\n   const r = buildTrain(\n      () =\u003e new SuccessResult(a),\n      () =\u003e new SuccessResult(b),\n      () =\u003e new SuccessResult(c)\n   );\n   expect(r.IsSuccess).toEqual(true);\n   if (r.IsSuccess) {\n      const [x, y, z] = r.value;\n      expect(x).toEqual(a);\n      expect(y).toEqual(b);\n      expect(z).toEqual(c);\n   }\n   ```\n\n1. relayTrain\n\n   This is similar to `query`, but takes functions returning `Result\u003cT\u003e`. It pass the result from previous function to next function. Halts processing if a function returns `ErrorResult` and returns that `ErrorResult`.\n\n   ```JavaScript\n   const r = relayTrain(\n       () =\u003e new SuccessResult(\"1234567890\"),\n       data =\u003e new SuccessResult(data.length),\n       len =\u003e new SuccessResult(len % 2 === 0)\n   );\n   expect(r.IsSuccess).toEqual(true);\n   if (r.IsSuccess) {\n    expect(r.value).toEqual(true);\n   }\n   ```\n\n1. relayTrainAsync\n\n   This is similar to `relayTrain`, but takes functions returning `PromiseResult\u003cT\u003e`. Useful to run chained async functions. Same can be achieved by chaining them with '.then', but has advantage of simplifying the state to check status of PromiseResult.\n\n   ```JavaScript\n   const r = await relayTrainAsync(\n     // Returns PromiseResult\u003cUser\u003e\n     () =\u003e getUserProfileAsync(),\n     // Returns PromiseResult\u003cPostId[]\u003e\n     (user) =\u003e getPostsAsync(u.id),\n     // Returns PromiseResult\u003cPostDetails[]\u003e\n     (ps) =\u003e getPostDetails(ps)\n   );\n   ```\n\n*Refer tests for more examples, samples folder contain AOC puzzles solved with `query` functions.*\n\nRead [this article](https://medium.com/@ajay.bhosale/linq-style-declarative-and-functional-programming-with-javascript-using-currying-and-generator-9e266e0e32fa) which explains query and related operators.\n\nRead [this article](https://dev.to/bhosaleajay/async-await-and-keeping-your-promises-2h5e) which explains keptPromise.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbhosale-ajay%2Fdotless","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbhosale-ajay%2Fdotless","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbhosale-ajay%2Fdotless/lists"}