{"id":16221757,"url":"https://github.com/noriller/easy-filter","last_synced_at":"2025-03-19T11:31:11.294Z","repository":{"id":43009964,"uuid":"382627314","full_name":"Noriller/easy-filter","owner":"Noriller","description":"EasyFilter is a lightweight ☁️, just one dependency 🚢, minimal setup 😮, intuitive 😃 and powerful 💪 filter for all your filter needs.","archived":false,"fork":false,"pushed_at":"2023-05-01T15:40:59.000Z","size":993,"stargazers_count":14,"open_issues_count":2,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-02-28T18:27:44.937Z","etag":null,"topics":["filter","filter-lists","javascript","match","matching-engine","query","query-parser","search","search-engine","sort"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/@noriller/easy-filter","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/Noriller.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-07-03T13:52:19.000Z","updated_at":"2024-02-05T12:58:59.000Z","dependencies_parsed_at":"2024-10-27T20:32:14.635Z","dependency_job_id":"2d493919-0b4b-44a8-987f-9acbc12c978e","html_url":"https://github.com/Noriller/easy-filter","commit_stats":{"total_commits":132,"total_committers":2,"mean_commits":66.0,"dds":0.007575757575757569,"last_synced_commit":"af727ff90699c3fabdbcdabd4ec8c3067e2958ec"},"previous_names":[],"tags_count":11,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Noriller%2Feasy-filter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Noriller%2Feasy-filter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Noriller%2Feasy-filter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Noriller%2Feasy-filter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Noriller","download_url":"https://codeload.github.com/Noriller/easy-filter/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243988956,"owners_count":20379649,"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":["filter","filter-lists","javascript","match","matching-engine","query","query-parser","search","search-engine","sort"],"created_at":"2024-10-10T12:09:45.144Z","updated_at":"2025-03-19T11:31:11.287Z","avatar_url":"https://github.com/Noriller.png","language":"TypeScript","funding_links":["https://www.paypal.com/donate/?business=VWNG7KZD9SS4S\u0026no_recurring=0\u0026currency_code=USD\u0026amount=5","https://www.paypal.com/donate/?business=VWNG7KZD9SS4S\u0026no_recurring=0\u0026currency_code=USD\u0026amount=10","https://www.paypal.com/donate/?business=VWNG7KZD9SS4S\u0026no_recurring=0\u0026currency_code=USD\u0026amount=42","https://www.paypal.com/donate/?business=VWNG7KZD9SS4S\u0026no_recurring=0\u0026currency_code=USD\u0026amount=1000","https://www.paypal.com/donate/?business=VWNG7KZD9SS4S\u0026no_recurring=0\u0026currency_code=USD\u0026amount=5000","https://www.paypal.com/donate/?business=VWNG7KZD9SS4S\u0026no_recurring=0\u0026currency_code=USD\u0026amount=10000","https://www.paypal.com/donate/?business=VWNG7KZD9SS4S\u0026no_recurring=0\u0026currency_code=USD"],"categories":[],"sub_categories":[],"readme":"# EasyFilter\n\n🎈 Welcome to **EasyFilter**! 👋\n\nEasyFilter is a lightweight ☁️, just one dependency 🚢, minimal setup 😮, intuitive 😃 and powerful 💪 filter for all your filter needs.\n\nIt's as easy as this:\n```js\n  const filter = EasyFilter(sourceArray)\n  const filteredResult = filter.search('your query')\n```\n\nThe one dependency is [EasyFilterParser](https://www.npmjs.com/package/@noriller/easy-filter-parser) that was part of this package before. 😉\n## Get Started\n\n### Use your choice of package manager\n\n```bash\nnpm install @noriller/easy-filter\n```\n\n```bash\nyarn add @noriller/easy-filter\n```\n\n### Then import it with the syntax of your choice\n\n```js\nimport EasyFilter from '@noriller/easy-filter'\n```\n\n```js\nconst EasyFilter = require('@noriller/easy-filter')\n```\n\n### Finally, to actually use it\n\n```js\n  const filter = EasyFilter(sourceArray)\n  const filteredResult = filter.search('your query')\n```\n\nThat's it! 🧙‍♂️\n\nCheck out the section [EasyFilter Operators](#easyfilter-operators) to see all that you can pass to the filter, the real ✨magic✨ is there!\n\n```js\n✨ Magic like turning this:\n  `search for something \"this between quotes\" and then here:\"you search for this\"`\n✨\n  Into something that works for single values, quoted values and even values nested inside keys. AND MORE!\n✨\n```\n\n#### Really? That's it?\n\nOk. If you need more options, here's the full setup you can do using all options available:\n\n```js\nconst filter = EasyFilter(sourceArray, {\n    filterOptions: {\n      dateFormat: 'DD-MM-YYYY',\n      normalize: true,\n      indexing: true,\n      limit: 10,\n    },\n    tagAliases: {\n      tag: ['tag1', 'tag2', 'tag3'],\n    }\n  })\n\nconst filteredResult = filter.search('your query')\n```\n\nIt's still that simple. 👨‍💻\n\nAll the options will be explained in [EasyFilter Options](#easyfilter-options).\nAnd most of them you can pass in the `search` 🔎 string when you need.\n\n## Inspirations and motivation\n\nIn corporate scenarios, sometimes we have too much information 😵. We make pages with endless columns and if we need to filter that data, we either use something generic like `Object.keys(object).join(' ').includes('string')` or we have to make a custom search... for. each. table. 😫\n\nMeanwhile I saw awesome (and probably custom solutions) in things we use everyday.\n\nCheck out the ones I was aiming for 🌟:\n* Github\n* Stackoverflow\n* Gmail/Google Search\n\nIn the latter, users can use their UI to create their queries while powerusers can just type that and much more.\n\nI too needed to provide a way to users to filter the data and ended up settling at a simpler version of this project. Mostly because of all the solutions I was able to find were neither user or developer friendly. 😢\n\n(Also a little rant: `search`, `filter`, `matcher` and the like are a nightmare to search for... too many hits and too little relevant results 😕)\n\nThis is what I'm trying to offer here: a powerful engine to make your queries. 😎👍\n\nThen it's up to you to offer a UI for what makes sense for your data. And it's still intuitive for common users and powerful for powerusers.\n\n## Why should you use\n\nUnless searching and filtering data IS your business... each table, each data source you need to filter based on any criteria could be another source of frustration. 👨‍🏭\n\nWhile sometimes this can be easy as `dataSource.filter(x =\u003e x.attribute === 'something')`, usually it's more complicated than that.\n\nAnd when you have to factor what the users might want at any given time, it can become a nightmare. 😫\n\nAnd if you're seeing yourself here... then you probably should use it.\n\n## When should you use, or better yet, when you shouldn't use?\n\n* If something like `Object.keys(object).join(' ').includes('string')` or `dataSource.filter(x =\u003e x.attribute === 'something')` is enough for your need, you probably wouldn't want to bother using it.\n\n* EasyFilter is not a fuzzy filter (at least not yet, who knows? 🤔), so if you're expecting clumsy typers searching your data... they might have a hard time.\n\n* EasyFilter also don't care about upper or lowercase and/or ordering of the words in your queries (again: at least not yet), so if that's important... I'm sorry? 🙇‍♂️\n\n* This one is a little relative: if you need maximum performance... well, test it out if it work for you! The more options you use and the more your data objects branches out on objects upon objects... the more time it needs to traverse everything. (And even then, I believe you can use it for prototyping and/or as a crutch while you make your own custom filter. Don't worry, I understand. 😉)\n\nThe trade off is clear: we give you a powerful engine that will return the data following what you're searched for, but it comes at a cost. \n\nFor your everyday use, you're probably fine and your users will love it. 😎\n\n(As a side note, I would love to know how EasyFilter fare against any other solution you might try. ❤😍❤)\n\n## EasyFilter Operators\n\nMost of this should be intuitive for most users... that's what I was aiming for after all. 🧐\n### OR query\n\nAny word or operators are, primarily and lastly, treated as `OR` queries.\n\nTo be a match, the data needs only to match any of them to be returned.\n\n#### OR Example:\n```js\nfilter.search('word1 word2 tag:value \"quoted value\"')\n```\n`word1`, `word2`, `tag:value` and `\"quoted value\"` each become separated entities and a match of any one of those will return.\n\n### AND query\n\nAnything inside quotes (either double `\"` or single `'`) will be treated as one entity.\n\nEasyFilter relies heavily on recursion and this one entity will be split into multiple entities, following those entities rules.\n\nTo be a match, the data must get a match from each subquery inside quotes.\n\n#### AND Remarks\n\nAn `AND` query can contain: `OR`, `TAG` and even nested `AND` queries.\n\nIn case of nested `TAG` and `AND` queries, the nested quote must not match the parent quote.\n\n#### AND Example:\n```js\nfilter.search('\"quoted value tag:value\"')\n```\n`quoted`, `value` and `tag:value` first are an `AND` query and will match only if all of the subqueries match.\n\nIn this case, both `quoted` and `value` become `OR` queries and `tag:value` becomes `TAG` query.\n\n### TAG query\n\nTAG here is equivalent to any `key` of a Javascript object.\n\n`TAG`, like `AND` queries, return subqueries where they target a slice of the object, namely the `key`.\n\nTo be a match, the data must get a match from the subquery after the `TAG`.\n\n#### TAG Remarks\n\nA `TAG` query can contain: `OR`, `AND` and then `NULL` and `RANGE`/`DATE_RANGE` queries.\n\n`TAG` doesn't support nested `TAG` queries. (As of now.)\n\n#### Types of TAG queries with Examples:\n##### TAG - Simple\n```js\nfilter.search('tag:value')\n```\nJust the `TAG` followed by a colon and the `value`.\n\n`value` in this example will become an `OR` query.\n\n##### TAG - OR\n```js\nfilter.search('tag:(value1 value2 value3)')\n```\nBy using brackets, you can have an `OR` query with multiple values at once.\n\n##### TAG - AND\n```js\nfilter.search('tag:\"value1 value2 value3\"')\n```\nBy using quotes (single/double), you can have an `AND` query.\n\n##### TAG - Null Values\n```js\nfilter.search('tag:null tag:nil tag:none tag:nothing')\n```\nBy passing, alone, any of the words: `NULL`, `NIL`, `NONE` or `NOTHING` as the value of the `TAG`, it will match only if the `key` doesn't exist in the object (or if the key is `null` or `undefined`).\n\n`tag:(nothing)`, in contrast, will match only if the `key` contains the string \"nothing\".\n\n##### TAG - Chaining Tags\n```js\nfilter.search('tag.subTag.thirdTag:value')\n```\nYou can chain tags together using a `.` (full stop/period).\n\nThis would be equivalent to nested `TAGs`. (Nested tags aren't supported.)\n\n##### TAG - Arrays\n```js\nfilter.search('tag.0:value tag2.*.subTag:value')\n```\nWhile the most common use case for EasyFilter would be, indeed, common objects (think JSON Objects with key/value pairs), arrays are supported.\n\nThe main difference is that the `key` they use are numerical and ordered.\n\n`tag.0:value` will search inside the \"tag\" key, then in the element \"0\" for the \"value\".\n\n`tag2.*.subTag:value` will search inside the \"tag2\" key, then in ALL elements for the \"subTag\" and then for the \"value\".\n\nSo, if the position in the array matters, use the index. Otherwise, use `*` (asterisk) to search all elements in the array.\n\n##### TAG - RANGE\n```js\nfilter.search('tag:range(0,5)')\n```\nBy passing, alone, the operator `RANGE()` you can pass one or two arguments that will filter based on the numbers.\n\n`RANGE` can only be used inside `TAG` and with `number` values.\n\nThe first argument is the lower bound (`-Infinity`) and the second argument is the upper bound (`Infinity`).\n\nPassing only one argument sets only the lower bound. To set only the upper bound, pass it empty: `RANGE(,5)`.\n\n##### TAG - DATE_RANGE\n```js\nfilter.search('tag:dateRange(2020-05-01, 2021-09-05)')\n```\nBy passing, alone, the operator `DATERANGE()` you can pass one or two arguments that will filter based on the dates.\n\n`DATERANGE` can only be used inside `TAG` and with `date` values.\n\nThe first argument is the lower bound (`0000-01-01`) and the second argument is the upper bound (`9999-01-01`).\n\nPassing only one argument sets only the lower bound. To set only the upper bound, pass it empty: `DATERANGE(,2021-09-05)`.\n\nMore on accepted `Date Formats` in [Date Format (Query)](#dateformat-query), but you can use all the common formats like `DD/MM/YYYY`, `MM/DD/YYYY` and `YYYY/MM/DD` as long as you pass it as an `OPTION`. If no `Date Format` is provided, the Javascript default implementation of `new Date('your date string')` will be used.\n\n### NOT query\n\nBy nesting any and multiple queries inside the syntax `NOT()` you can invert those and it will NOT return anything that matches.\n\nIf there's a match in the `NOT` query, it won't return even if there's a match in other queries.\n\nIf your query contains only `NOT` queries, it will return everything that don't have a match.\n\nWhen combining with other queries, `NOT` queries will filter out matches from those.\n\n#### NOT Remarks\n\nA `NOT` query can contain: `OR`, `AND` and `TAG` queries.\n\nAll `NOT` are parsed at the same level, nesting it inside other queries will just remove them from the query.\n\n#### NOT Example:\n```js\nfilter.search('not(\"quoted value tag:value\")')\n```\nAny element with `quoted`, `value` and `tag:value` will not be returned.\n\n### EasyFilter Options\n\nThere's three types of options:\n* Those that can be passed any time:\n  * [Normalize](#normalize)\n  * [Indexing](#indexing)\n  * [Limit](#limit)\n* Those that can only be passed in the setup:\n  * [Filter Options](#filter-options)\n    * [Date Format (Setup)](#dateformat-setup)\n  * [Tag Aliases](#tag-aliases)\n* Those that can only be passed with the query:\n  * [Date Format (Query)](#dateformat-query)\n#### OPTION keyword\n\nUsing the syntax `OPTION()` or `OPTIONS()` you can pass the following options inside your search string.\n\nThe `OPTION` keyword is parsed first, it will be just removed if nested in other queries and anything else inside will be either parsed as an option or ignored.\n\n##### DATEFORMAT (Query)\n\nWhen passed as an `OPTION`, `DateFormat` will be used to parse the dates used in [`DATE_RANGE`](#tag---date_range).\n\nThis way your users can use their locale date format in their query.\n\nWhen using `DATE_RANGE`, if no `DateFormat` is passed as an option the Javascript default implementation of `new Date('your date string')` will be used.\n\nThe formats can be: `YYYY-MM-DD`, `DD-MM-YYYY` and `MM-DD-YYYY` while the separators can be: `-`, `.`, `,` and `/`.\n\n###### DateFormat Example\n```js\nfilter.search('tag:dateRange(30-12-2020,30-12-2022) option(dateFormat:DD.MM.YYYY)')\n```\n\n##### NORMALIZE\n\nWhen the `NORMALIZE` option is used, `EasyFilter` will discard/ignore every and all diacritics when making comparisons. It's FALSE by default.\n\nThis means that with `NORMALIZE`: `Crème brûlée` is equal to `Creme brulee`.\n\n`EasyFilter` uses the [`string.normalize('NFD')`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/normalize) javascript API to decompose the strings and then remove all [Combining Diacritical Marks](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks).\n\n`NORMALIZE` uses a `boolean` flag, and when used in `OPTIONS` alone like `option(normalize)` it will assume the TRUE value, but you can explicitly use: `normalize:true`.\n\nYou can also use `normalize:false` to disable a [setup default](#filter-options) normalization for a specific query.\n\n##### INDEXING\n\nWhen the `INDEXING` option is used, `EasyFilter` will use a numerical ranking system and if there's a match, it will return a copy of the element with an additional key `_EasyFilterIndex` that will have a \"relevance score\" that you can use to sort the results. It's FALSE by default.\n\n`INDEXING` uses a `boolean` flag, and when used in `OPTIONS` alone like `option(index)` or `option(indexing)` it will assume the TRUE value, but you can explicitly use: `index:true`.\n\nYou can also use `normalize:false` to disable a [setup default](#filter-options) indexing for a specific query.\n\n###### How it indexing works in EasyFilter\n\n`EasyFilter` gives a number based on the types of queries and multiplies the results returned by nested queries.\n* OR = 1\n* AND = 3\n* TAG = 5\n* RANGE/DATE_RANGE/NULL = 10\n\nThis way, using more generic queries, those with the more specific parameters will have a higher indexing value.\n\n##### LIMIT\n\nWhen the `LIMIT` option is used, `EasyFilter` will return only the `LIMIT` number of results. It's Zero/FALSE by default.\n\n`LIMIT` needs a `number` value, when used in `OPTIONS` you need to also pass a `number` value: `option(limit:1)`.\n\nYou can also use `limit:0` to disable a [setup default](#filter-options) limit for a specific query.\n#### Setup Options\n\nIn the setup you may pass:\n##### Filter Options\n\nThe following options works the same way as if passing in the query:\n  * [Normalize](#normalize)\n  * [Indexing](#indexing)\n  * [Limit](#limit)\n\nBy passing it in the setup, they will be used in every `search`.\n\n###### DATEFORMAT (Setup)\n\nWhen passed in the `Setup`, `DateFormat` will be used to parse the dates in your `Source Array`.\n\nIf no `DateFormat` is passed in the `setup`, the Javascript default implementation of `new Date('your date string')` will be used.\n\nIf that default implementation wouldn't work with your `source array`, then provide a `DateFormat`.\n\nThe formats can be: `YYYY-MM-DD`, `DD-MM-YYYY` and `MM-DD-YYYY` while the separators can be: `-`, `.`, `,` and `/` (you can use the provided typing).\n\n##### Tag Aliases\n\nPass `TAG Aliases` in the setup to expose to users more friendly (or broader) terms that they can call your data using `TAG`.\n\n`Tag Aliases` should be a dictionary with `key`/`value` pairs where the `key` is what your users can use and the `value` is a array of strings that will refer to your actual data.\n\nOur `data sources` might not always be the most user friendly, or something important might be nested where users `Tag Aliases` couldn't possibly know. This is where you use `Tag Aliases`.\n\n###### Aliases Examples\n\n```js\nconst filter = EasyFilter(sourceArray, {\n    tagAliases: {\n      // if you want more friendly aliases\n      data: ['DT_0001X420'],\n      name: ['nm_first', 'nm_last'],\n      // if the important data is nested\n      age: ['person.info.age'],\n      // if your users expect to find everything related to a word\n      address: ['address', 'city', 'country', 'province', 'zip_code'],\n      // and you have no idea which words they will search for\n      // just create multiple aliases with the same tags\n      city: ['address', 'city', 'country', 'province', 'zip_code'],\n      country: ['address', 'city', 'country', 'province', 'zip_code'],\n      province: ['address', 'city', 'country', 'province', 'zip_code'],\n      zip: ['address', 'city', 'country', 'province', 'zip_code'],\n      location: ['address', 'city', 'country', 'province', 'zip_code'],\n      where: ['address', 'city', 'country', 'province', 'zip_code'],\n      position: ['address', 'city', 'country', 'province', 'zip_code'],\n    }\n  })\n```\n\n## What's next?\n\nHere's something you can expect in the future:\n\n* New objective: `EasyFilter` will now become a trilogy! They will all share the same parser, so you will be able to filter values already buffered and values in your databases all the same, simple way.\n  * This, which filter values in objects.\n  * `EasyFilter-SQL` - That will create SQL queries.\n  * `EasyFilter-Mongo` - That will create Mongo queries.\n* (TBD) Streams support ⚡: be it from an API or from a data source, data usually comes as a stream. If the Javascript Engine can handle streaming fine, why wait for it to buffer everything?\n\n## There's a problem or it could be better\n\nEither if you're encountered a problem: 😢 or if you're have an idea to make it better: 🤩\n\nFeel free to contribute, to open issues, bug reports or just to say hello! 🤜🤛\n\n\nIn case of bugs or errors, if possible, send an example of your data, of the query you're using and what you've expected.\n\nSince it supports any kind of data or queries... who knows what can happen?\n\nI do have one remark to say: It will probably err on the side of caution... maybe returning more than you might have expected and when using the negation query, filtering more than intended, so keep that in mind.\n\n## Work with me!\n\nhttps://www.linkedin.com/in/noriller/\n\n### Hit me up at Discord!\n\nhttps://discord.gg/XtNPk7HeCa\n### Or Donate:\n\n* [$5 Nice job! Keep it up.](https://www.paypal.com/donate/?business=VWNG7KZD9SS4S\u0026no_recurring=0\u0026currency_code=USD\u0026amount=5)\n* [$10 I really liked that, thank you!](https://www.paypal.com/donate/?business=VWNG7KZD9SS4S\u0026no_recurring=0\u0026currency_code=USD\u0026amount=10)\n* [$42 This is exactly what I was looking for.](https://www.paypal.com/donate/?business=VWNG7KZD9SS4S\u0026no_recurring=0\u0026currency_code=USD\u0026amount=42)\n* [$1K WOW. Did not know javascript could do that!](https://www.paypal.com/donate/?business=VWNG7KZD9SS4S\u0026no_recurring=0\u0026currency_code=USD\u0026amount=1000)\n* [$5K I need something done ASAP! Can you do it for yesterday?](https://www.paypal.com/donate/?business=VWNG7KZD9SS4S\u0026no_recurring=0\u0026currency_code=USD\u0026amount=5000)\n* [$10K Please consider this: quit your job and work with me!](https://www.paypal.com/donate/?business=VWNG7KZD9SS4S\u0026no_recurring=0\u0026currency_code=USD\u0026amount=10000)\n* [$??? Shut up and take my money!](https://www.paypal.com/donate/?business=VWNG7KZD9SS4S\u0026no_recurring=0\u0026currency_code=USD)\n\n## That’s it! 👏","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnoriller%2Feasy-filter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnoriller%2Feasy-filter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnoriller%2Feasy-filter/lists"}