{"id":"rimuy/roselect","name":"roselect","scope":"rimuy","platform":"roblox","description":"Composable data selectors library","version":"0.1.3","latest":"0.1.3","versions":["0.1.0","0.1.1","0.1.2","0.1.3"],"license":"MIT","licenseRating":"safe","licenseCaveats":[],"licenseVerified":true,"dependencies":{},"integrity":"60aa8aa1d4e062f54c7b4cadfd1c82eb200b1f8f9133686eebf6c87f98a1462a","likes":0,"downloads":0,"install":"forest install rimuy/roselect","url":"https://forest.dev/p/roblox/rimuy/roselect","files":"https://api.forest.dev/ai/package/roblox/rimuy/roselect/files","readme":"<div>\r\n        <img align=\"left\" src=\"https://i.imgur.com/LxEjWku.png\" height=\"200\" width=\"200\">\r\n                <h1><code>roselect</code></h1>\r\n                <p>\r\n\t\t\tComposable data selectors, ported from JS's \r\n\t\t\t<a href=\"https://www.npmjs.com/package/reselect\">reselect</a>.\r\n\t\t</p> \r\n        </img>\r\n</div>\r\n\r\n---\r\n\r\n[![License MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)\r\n[![Package](https://badge.fury.io/js/%40rbxts%2Fgamejoy.svg)](https://www.npmjs.com/package/@rbxts/roselect)\r\n<br/>\r\n<br/>\r\n\r\n## Usability\r\n* Selectors can compute derived data, allowing state managers to store the minimal possible state.\r\n* Selectors are efficient. A selector is not recomputed unless one of its arguments changes.\r\n* Selectors are composable. They can be used as input to other selectors.\r\n\r\n<details open><summary>luau</summary><p>\r\n\r\n```lua\r\nlocal roselect = require(path.to.roselect)\r\nlocal createSelector = roselect.createSelector\r\nlocal reduce = roselect.reduce\r\n\r\nlocal function shopItemsSelector(state)\r\n        return state.shop.items\r\nend\r\n\r\nlocal function taxPercentSelector(state)\r\n        return state.shop.taxPercent\r\nend\r\n\r\nlocal subtotalSelector = createSelector(\r\n\tshopItemsSelector,\r\n\tfunction(items)\r\n                return reduce(items, function(acc, item)\r\n                        return acc + item.value\r\n                end, 0)\r\n        end\r\n)\r\n\r\nlocal taxSelector = createSelector(\r\n        subtotalSelector,\r\n        taxPercentSelector,\r\n        function(subtotal, taxPercent)\r\n                return subtotal * (taxPercent / 100)\r\n        end\r\n)\r\n\r\nlocal totalSelector = createSelector(\r\n        subtotalSelector,\r\n\ttaxSelector,\r\n        function(subtotal, tax)\r\n                return { total = subtotal + tax }\r\n        end\r\n)\r\n\r\nlocal exampleState = {\r\n        shop = {\r\n                taxPercent = 8,\r\n                items = {\r\n\t\t\t{ name = \"apple\", value = 1.20 },\r\n\t\t\t{ name = \"orange\", value = 0.95 }\r\n                }\r\n        }\r\n}\r\n\r\nprint(subtotalSelector(exampleState)) -- 2.15\r\nprint(taxSelector(exampleState))      -- 0.172\r\nprint(totalSelector(exampleState))    -- { total = 2.322 }\r\n```\r\n</p></details>\r\n<details><summary>roblox-ts</summary><p>\r\n\r\n```ts\r\nimport { createSelector } from \"@rbxts/roselect\";\r\n\r\ninterface State {\r\n\tshop: {\r\n\t\ttaxPercent: number;\r\n\t\titems: { name: string; value: number; }[];\r\n\t}\r\n}\r\n\r\nconst shopItemsSelector = (state: State) => state.shop.items;\r\nconst taxPercentSelector = (state: State) => state.shop.taxPercent;\r\n\r\nconst subtotalSelector = createSelector(\r\n\tshopItemsSelector,\r\n\titems => items.reduce((acc, item) => acc + item.value, 0)\r\n);\r\n\r\nconst taxSelector = createSelector(\r\n\tsubtotalSelector,\r\n\ttaxPercentSelector,\r\n\t(subtotal, taxPercent) => subtotal * (taxPercent / 100)\r\n);\r\n\r\nexport const totalSelector = createSelector(\r\n\tsubtotalSelector,\r\n\ttaxSelector,\r\n\t(subtotal, tax) => ({ total: subtotal + tax })\r\n);\r\n\r\nconst exampleState = identity<State>({\r\n\tshop: {\r\n\t\ttaxPercent: 8,\r\n\t\titems: [\r\n\t\t\t{ name: 'apple', value: 1.20 },\r\n\t\t\t{ name: 'orange', value: 0.95 },\r\n\t\t]\r\n\t}\r\n});\r\n\r\nprint(subtotalSelector(exampleState)); // 2.15\r\nprint(taxSelector(exampleState));      // 0.172\r\nprint(totalSelector(exampleState));    // { total: 2.322 }\r\n```\r\n</p></details>\r\n\r\n## Installation\r\n### luau\r\n```bash\r\ngit clone https://github.com/HylianBasement/roselect.git ./modules\r\n```\r\n\r\n### roblox-ts\r\nThe installation can be done via `npm i @rbxts/roselect`.\r\n\r\n## Composing Selectors\r\nIt's important to mention that `createSelector` checks if the first parameter is an array of dependencies.\r\nSince lua tables are both dictionary and list, it treats that parameter as an array of selectors that needs to be composed.\r\nA memoized selector can itself be an input-selector to another memoized selector. Here is `getVisibleTodos` being used as an input-selector to a selector that further filters the todos by keyword:\r\n<details open><summary>luau</summary><p>\r\n\r\n```lua\r\nlocal function getVisibilityFilter(state)\r\n\treturn state.visibilityFilter\r\nend\r\n\r\nlocal function getTodos(state)\r\n\treturn state.todos\r\nend\r\n\r\nlocal function getKeyword(state)\r\n\treturn state.keyword\r\nend\r\n\r\nlocal getVisibleTodos = createSelector(\r\n\t{ getVisibilityFilter, getTodos },\r\n\tfunction(visibilityFilter, todos)\r\n\t\tif visibilityFilter == \"SHOW_ALL\" then\r\n\t\t\treturn todos\r\n\t\telseif visibilityFilter == \"SHOW_COMPLETED\" then\r\n\t\t\tlocal newTodos = {}\r\n\r\n\t\t\tfor k, todo in pairs(todos) do\r\n\t\t\t\tif todo.completed then\r\n\t\t\t\t\tnewTodos[k] = todo\r\n\t\t\t\tend\r\n\t\t\tend\r\n\r\n\t\t\treturn newTodos\r\n\t\telseif visibilityFilter == \"SHOW_ACTIVE\" then\r\n\t\t\tlocal newTodos = {}\r\n\r\n\t\t\tfor k, todo in pairs(todos) do\r\n\t\t\t\tif not todo.completed then\r\n\t\t\t\t\tnewTodos[k] = todo\r\n\t\t\t\tend\r\n\t\t\tend\r\n\r\n\t\t\treturn newTodos\r\n\t\tend\r\n\tend\r\n)\r\n\r\nlocal getVisibleTodosFilteredByKeyword = createSelector(\r\n\t{ getVisibleTodos, getKeyword },\r\n  \tfunction(visibleTodos, keyword)\r\n\t\tlocal newTodos = {}\r\n\r\n\t\tfor k, todo in pairs(visibleTodos) do\r\n\t\t\tif todo.text:find(keyword) then\r\n\t\t\t\tnewTodos[k] = todo\r\n\t\t\tend\r\n\t\tend\r\n\r\n\t\treturn newTodos\r\n  \tend\r\n)\r\n\r\nreturn {\r\n\tgetVisibleTodos = getVisibleTodos\r\n}\r\n```\r\n\r\n</p></details>\r\n\r\n<details><summary>roblox-ts</summary><p>\r\n\r\n```ts\r\nconst getVisibilityFilter = (state: State) => state.visibilityFilter;\r\nconst getTodos = (state: State) => state.todos;\r\nconst getKeyword = (state: State) => state.keyword;\r\n \r\nexport const getVisibleTodos = createSelector(\r\n\t[ getVisibilityFilter, getTodos ],\r\n\t(visibilityFilter, todos) => {\r\n\t\tswitch (visibilityFilter) {\r\n\t\tcase \"SHOW_ALL\":\r\n\t\t\treturn todos;\r\n\t\tcase \"SHOW_COMPLETED\":\r\n\t\t\treturn todos.filter(t => t.completed);\r\n\t\tcase \"SHOW_ACTIVE\":\r\n\t\t\treturn todos.filter(t => !t.completed);\r\n\t\t}\r\n\t}\r\n);\r\n\r\nconst getVisibleTodosFilteredByKeyword = createSelector(\r\n\t[ getVisibleTodos, getKeyword ],\r\n\t(visibleTodos, keyword) => visibleTodos.filter(\r\n\t\ttodo => todo.text.includes(keyword)\r\n\t)\r\n);\r\n```\r\n\r\n</p></details>\r\n\r\n## Can Roselect be used without Rodux?\r\nDefinitely. Just like the JS library, Roselect has no dependencies on any other package,\r\nso it can be used independently.\r\n\r\n## License\r\nThis project is licensed under the [MIT License](LICENSE).","readmeTruncated":false}