{"id":15917610,"url":"https://github.com/dsfields/spleen-elasticsearch-node","last_synced_at":"2025-08-31T08:32:44.829Z","repository":{"id":50530109,"uuid":"108497347","full_name":"dsfields/spleen-elasticsearch-node","owner":"dsfields","description":"Convert spleen filters into Elasticsearch queries.","archived":false,"fork":false,"pushed_at":"2022-07-29T20:50:38.000Z","size":15,"stargazers_count":1,"open_issues_count":1,"forks_count":5,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-08-15T16:45:54.121Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","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/dsfields.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-10-27T03:57:59.000Z","updated_at":"2022-03-10T07:16:27.000Z","dependencies_parsed_at":"2022-09-07T12:01:55.729Z","dependency_job_id":null,"html_url":"https://github.com/dsfields/spleen-elasticsearch-node","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/dsfields/spleen-elasticsearch-node","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dsfields%2Fspleen-elasticsearch-node","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dsfields%2Fspleen-elasticsearch-node/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dsfields%2Fspleen-elasticsearch-node/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dsfields%2Fspleen-elasticsearch-node/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dsfields","download_url":"https://codeload.github.com/dsfields/spleen-elasticsearch-node/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dsfields%2Fspleen-elasticsearch-node/sbom","scorecard":{"id":357652,"data":{"date":"2025-08-11","repo":{"name":"github.com/dsfields/spleen-elasticsearch-node","commit":"d1829691ef68072f679cbf71efbad52521308eab"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":2.8,"checks":[{"name":"Code-Review","score":0,"reason":"Found 0/1 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: containerImage not pinned by hash: test/integration/services/elasticsearch/Dockerfile:1","Info:   0 out of   1 containerImage dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}}]},"last_synced_at":"2025-08-18T10:01:54.018Z","repository_id":50530109,"created_at":"2025-08-18T10:01:54.018Z","updated_at":"2025-08-18T10:01:54.018Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":272958329,"owners_count":25022051,"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-31T02:00:09.071Z","response_time":79,"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":[],"created_at":"2024-10-06T18:11:52.713Z","updated_at":"2025-08-31T08:32:44.782Z","avatar_url":"https://github.com/dsfields.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# spleen-elasticsearch\n\nThe [`spleen`](https://www.npmjs.com/package/spleen) module provides high-level abstractions for dynamic filters.  This module will convert a `spleen` [`Filter`](https://www.npmjs.com/package/spleen#class-filter) into an [Elasticsearch Query DSL](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl.html) object..\n\n__Contents__\n* [Usage](#usage)\n* [API](#api)\n* [Conversion Behavior](#conversion-behavior)\n* [Mapping Considerations](#mapping-considerations)\n\n## Usage\n\nAdd `spleen-elasticsearch` to your `package.json` file's `dependencies`:\n\n```sh\n$ npm install spleen-elasticsearch -S\n```\n\nThen use it in your code:\n\n```js\nconst spelastic = require('spleen-elasticsearch');\nconst spleen = require('spleen');\n\nconst filter = spleen.parse('/foo/bar eq 42 and /baz in [1,2,3] or /qux gt 0');\nconst result = spelastic.convert(filter);\n\nconsole.log(result);\n// {\n//   \"value\": {\n//     \"filter\": {\n//       \"bool\": {\n//         \"should\": [\n//           {\n//             \"bool\": {\n//               \"must\": [\n//                 {\n//                   \"term\": { \"foo.bar\": 42 }\n//                 },\n//                 {\n//                   \"terms\": \"baz\": [1, 2, 3]\n//                 }\n//               ]\n//             }\n//           },\n//           {\n//             \"range\": {\n//               \"qux\": { \"gt\": 0 }\n//             }\n//           }\n//         ]\n//       }\n//     }\n//   }\n// }\n```\n\n## API\n\nThe `spleen-elasticsearch` module provides the following interface:\n\n* __Properties__\n\n  + `errors`: an object that contains references to the various possible errors thrown by `spleen-elasticsearch`.  This object has the following keys:\n\n    - `ConvertError`: a general error thrown when `spleen-elasticsearch` is unable to convert a given `Filter` instance into a Query DSL object.  This should generally never happen, and is here as a safeguard in the event a `Filter` instance is corrupted.\n\n    - `DeniedFieldError`: thrown when a field is encountered that has been explicitly black-listed by the `deny` option.\n\n    - `InvalidTargetError`: thrown if a target is encountered with an invalid format.  For example, if a segment of the path contains disallowed characters.\n\n    - `NonallowedFieldError`: thrown when a field is encountered that not been white-listed by the `allow` option.\n\n    - `RequiredFieldError`: thrown when a field that has been required by the `require` option is not present in the given `Filter`.\n\n  + `Strategy`: a reference to the [`Strategy`](#class-strategy) class.\n\n* __Methods__\n\n  + `convert(filter [, strategy])`: converts an instance of `spleen`'s `Filter`' class into an Elasticsearch Query DSL object.\n\n    _Parameters_\n\n    - `filter`: _(required)_ the instance of `Filter` to convert.\n\n    - `strategy`: _(optional)_ an instance of `Strategy`.\n\n    This method returns an object with the following key:\n\n    - `fields`: an array containing all of the fields (in [RFC 6901 JSON pointer](https://tools.ietf.org/html/rfc6901) format) included in the filter.\n\n    - `value`: a string containing the N1QL filter statement.\n\n### Class: `Strategy`\n\nCompiles a `spleen` to Elasticsearch Query DSL conversion strategy, which is easily read by the `convert()` method.\n\n* `new Strategy(settings)`\n\n  Creates a new instance of `Strategy`.\n\n  _Parameters_\n\n  + `settings`: _(required)_ an object that controls various aspects of the conversion process.  This object can have the keys:\n\n    - `allow`: _(optional)_ an array of RFC 6901 JSON pointer strings that are allowed to be in a `Filter`'s list of targets.  Any targets in a `Filter` instance not found in the `allow` or `require` lists will result in an error being thrown.  This list functions as a white list, and can only be present if `deny` is absent.  An empty array is the logical equivalent of the `allow` key being absent.\n\n    - `deny`: _(optional)_ an array of RFC 6901 JSON pointer strings that are not allowed to be in a `Filter`'s list of targets.  Any targets in a `Filter` instance found in this list will result in an error being thrown.  This list functions as a black list, and can only be present if `allow` is absent.\n\n    - `discriminator`: _(optional)_ an object that configures a discriminator field, which is used for determining the Elasticsearch type to query at runtime.  This feature works similarly to discriminator columns found in RDBMS table designs that utilize inheritance.  If you do not wish to assign a discriminator leave this key `null` or `undefined`.  This object has the following keys:\n\n      - `target`: _(optional)_ an RFC 6901 JSON pointer string that specifies a target field to use as the discriminator.\n\n      - `map`: _(optional)_ an object whose keys are possible values for the discriminator field, and the value is the name of an Elasticsearch type.  The value of a discriminator must be a string or number.\n\n    - `require`: _(optional)_ an array of RFC 6901 JSON pointer strings that are required to be in a `Filter`'s list of targets (`Filter.prototype.targets`).  If a required target is missing, an error is thrown.\n\n## Conversion Behavior\n\nA `spleen` filter is essentially an Boolean algebraic expression (`AND`, `OR`, `NOT`), and answers questions in a binary fashion — either _yes_ or _no_.  This contrasts with Elasticsearch's probabilistic matching, which generates a score representing the likelihood of a match.  While Elasticsearch's Query DSL (EQD) provides methods for executing queries using Boolean algebra, there are some limitations.\n\nIt is worth noting that `spleen` filters converted using `spleen-elasticsearch` are designed to answer questions about filtering in a binary fashion, and, so, none of Elasticsearch's fuzzy and probabilistic matching features.  Thus, a converted `spleen` filter is nested in a `filter`, and all clauses are represented as a `bool` query.\n\n### `AND`s, `OR`s, and `NOT`s\n\nThe EQD does not include `AND`, `OR`, or `NOT` operators.  Instead, we are provided with `must`, `should`, and `must_not`.  The challenge is converting `spleen.Filter` instance while preserving its Boolean logic.  To do this, the `spleen-elasticsearch` module follows a few rules:\n\n1. The `AND` operator is given precedence over `OR`.\n\n2. Clauses chained together with `AND` are treated as a single group, with `OR` being the delimiter between groups.\n\n3. All clauses in an `AND` group are nested in a `must`.\n\n4. If there is more than one `AND` group in the filter, then all `must` queries are nested in a `should`.\n\n### Operators\n\nUnder the hood, Elasticsearch is utilizing the [Apache Lucene](https://lucene.apache.org/core/) project to create and query indexes.  Lucene does not have a concept of comparison operators.  Searching Lucene indexes using different kinds of comparisons translate into different types of queries.  Elasticsearch's Query DSL provides a high level abstraction of Lucene query types, and different `spleen` operators must be translated accordingly.\n\n| Operator   | Elasticsearch Query DSL                                          |\n| ---------- | ---------------------------------------------------------------- |\n| `eq`       | `{ \"term\": { \"key\": \"value\" } }`                                 |\n| `neq`      | `{ \"bool\": { \"must_not\": { \"term\": { \"key\": \"value\" } } } }`     |\n| `gt`       | `{ \"range\": { \"key\": { \"gt\": \"value\" } } }`                      |\n| `gte`      | `{ \"range\": { \"key\": { \"gte\": \"value\" } } }`                     |\n| `lt`       | `{ \"range\": { \"key\": { \"lt\": \"value\" } } }`                      |\n| `lte`      | `{ \"range\": { \"key\": { \"lte\": \"value\" } } }`                     |\n| `between`  | `{ \"range\": { \"key\": { \"gte\": \"value1\", \"lte\": \"value2\" } } }`   |\n| `nbetween` | `{ \"bool\": { \"must_not\": { \"range\": { \"key\": { \"gte\": \"value1\", \"lte\": \"value2\" } } } } }` |\n| `in`       | `{ \"terms\": { \"key\": [\"value1\", \"value2\", \"valueN\"] } }`         |\n| `nin`      | `{ \"bool\": { \"must_not\": { \"terms\": { \"key\": [\"value1\", \"value2\", \"valueN\"] } } } }` |\n| `like`     | `{ \"regex\": { \"key\": \"like value converted to regex\" } }` |\n| `nlike`    | `{ \"bool\": { \"must_not\": { \"regex\": { \"key\": \"like value converted to regex\" } } } }` |\n\n### Pattern Matching Conversion to Regex\n\nElasticsearch can perform pattern matching usig regular expressions.  The `spleen-elasticsearch` module converts `like` patterns to regex in the following way.\n\n| `like` Char | Regex Operator |\n| ----------- | -------------- |\n| `*`         | `.*`           |\n| `_`         | `.{1}`         |\n\nAll `like` statements converted to regex begin with `^` and `$`.  For example, the `like` pattern `*Hello World_` is converted into the regex `^.*Hello World.{1}$`.\n\n### Range Comparisons\n\nElasticsearch's Query DSL does not support queries where the document property is evaluated on to the right of a literal value (i.e. `42 gt /foo`).  In cases where `gt`, `gte`, `lt`, or `lte` comparisons are performed with the target on the right and the literal on the left, the operator used in the Elasticsearch Query DSL `range` query is inverted (`gt` is replaced with `lt`, `gte` is replaced with `lte`, or visa versa).\n\n### Handling `nil` Literals\n\nWhen a `spleen` filter includes a comparison between a target and a `nil` literal, the `exists` query DSL is used.  The `spleen` expression dialect allows for a variety of operators to be used when comparing against a `nil`.  Different operators result in different Elasticsearch Query DSL...\n\n| Operator   | Elasticsearch Query DSL                                        |\n| ---------- | -------------------------------------------------------------- |\n| `eq nil`   | `{ \"bool\": { \"must_not\": { \"exists\": { \"field\": \"key\" } } } }` |\n| `neq nil`  | `{ \"exists\": { \"field\": \"key\" } }`                             |\n| `gt nil`   | `{ \"exists\": { \"field\": \"key\" } }`                             |\n| `gte nil`  | `{ \"exists\": { \"field\": \"key\" } }`                             |\n| `lt nil`   | `{ \"bool\": { \"must_not\": { \"exists\": { \"field\": \"key\" } } } }` |\n| `lte nil`  | `{ \"bool\": { \"must_not\": { \"exists\": { \"field\": \"key\" } } } }` |\n\n### Comparing Two Properties/Literals\n\nComparison between properties on a document does not exist as a first-class citizen in Elasticsearch Query DSL.  However, it is possible using `script` queries.  Thus, clauses that are a comparison between two targets will be translated to a `script` query.\n\nFor example, the `spleen` expression...\n\n```\n/foo eq /bar\n```\n\n...is translated to...\n\n```json\n{\n  \"script\": {\n    \"script\": \"doc['foo'].value == doc['bar'].value\"\n  }\n}\n```\n\nThough not a typical use case, comparisons between two literal values are handled the same way.\n\n## Mapping Considerations\n\nIn order for `spleen` filters to work properly when converted into Elasticsearch's Query DSL there are a couple of things to consider when creating index mappings.\n\n### String Properties\n\nBecause `spleen-elasticsearch` uses `term` and `terms` for comparisons, Elasticsearch will attempt to make exact comparisons of values in its inverted index.  Document property mappings of type `text` are \"analyzed,\" and the entire value of a property may not be in the index.  For example, _stopwords_ and most punctuation will not be indexed.   For this reason it is recommended that you map string values as `keyword` for indexes you intend to run converted `spleen` filters against.\n\n### Referencing Array Values by Index\n\nIn a future release, support for referencing array items by index will be added.  In order to make this possible, you will need to create a computed property mapping using the [`token_count`](https://www.elastic.co/guide/en/elasticsearch/reference/current/token-count.html).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdsfields%2Fspleen-elasticsearch-node","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdsfields%2Fspleen-elasticsearch-node","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdsfields%2Fspleen-elasticsearch-node/lists"}