{"_id":"@bosek/rsql-optimizer","name":"@bosek/rsql-optimizer","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@bosek/rsql-optimizer","version":"0.1.0","description":"Optimizer for @rsql/ast","license":"MIT","author":{"name":"Tomas Bosek","url":"https://github.com/bosek"},"repository":{"type":"git","url":"git+https://github.com/bosek/rsql-optimizer.git"},"bugs":{"url":"https://github.com/bosek/rsql-optimizer/issues"},"type":"module","exports":{".":{"types":"./dist/types/index.d.ts","browser":{"import":"./dist/browser/index.js","require":"./dist/browser/index.cjs"},"node":{"import":"./dist/node/index.js","require":"./dist/node/index.cjs"},"import":"./dist/node/index.js","require":"./dist/node/index.cjs","default":"./dist/node/index.js"},"./package.json":"./package.json"},"main":"./dist/node/index.cjs","module":"./dist/node/index.mjs","types":"./dist/types/index.d.ts","sideEffects":false,"engines":{"node":">=18"},"scripts":{"build":"tsup --config tsup.config.ts","build:watch":"pnpm build -- --watch","clean":"rimraf dist","test":"jest --passWithNoTests","test:watch":"jest --watch","test:cov":"jest --coverage","test:debug":"node --inspect-brk ./node_modules/jest/bin/jest.js --runInBand","lint":"eslint . --ext .ts,.tsx","format":"prettier --write .","prepare":"pnpm build","prepublishOnly":"pnpm clean && pnpm build && pnpm test && pnpm lint","release":"npm publish --access public"},"keywords":[],"devDependencies":{"@eslint/js":"^9.34.0","@ianvs/prettier-plugin-sort-imports":"^4.7.0","@rsql/emitter":"^1.6.0","@rsql/parser":"^1.6.0","@types/jest":"^30.0.0","@typescript-eslint/eslint-plugin":"^8.41.0","@typescript-eslint/parser":"^8.41.0","eslint":"^9.34.0","eslint-config-prettier":"^10.1.8","eslint-plugin-jest":"^29.0.1","globals":"^16.3.0","jest":"^30.1.1","prettier":"^3.6.2","rimraf":"^6.0.1","ts-jest":"^29.4.1","tsup":"^8.5.0","typescript":"^5.9.2","typescript-eslint":"^8.42.0"},"dependencies":{"@rsql/ast":"^1.6.0"},"_id":"@bosek/rsql-optimizer@0.1.0","gitHead":"e811a78f63bd173b9f228f4eba24073488ba72da","homepage":"https://github.com/bosek/rsql-optimizer#readme","_nodeVersion":"24.5.0","_npmVersion":"11.5.1","dist":{"integrity":"sha512-28yTrbZZmjzoQA0dOLvJJqskWZObPAc5pTrPs+cR09hyrw1IIFGRVY0ouCHp53xDrUIdAW9PAHOFPEZGQxvnpw==","shasum":"d5aa8fe18ba99da917436643cb91ba12a71b1852","tarball":"https://registry.npmjs.org/@bosek/rsql-optimizer/-/rsql-optimizer-0.1.0.tgz","fileCount":15,"unpackedSize":279046,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDecnkJKhzOXQjC4cXi2OtJ+yCQE5ZOTRbDHDy/Lx2vpAIgNAdUcUXxTBnhp1SzBpOVdnIXfe887vGkQVswyj4tjJ0="}]},"_npmUser":{"name":"bosek","email":"bosektom@gmail.com"},"directories":{},"maintainers":[{"name":"bosek","email":"bosektom@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/rsql-optimizer_0.1.0_1756829176506_0.4824079885547099"},"_hasShrinkwrap":false}},"time":{"created":"2025-09-02T16:06:16.338Z","0.1.0":"2025-09-02T16:06:16.684Z","modified":"2025-09-02T16:06:17.012Z"},"maintainers":[{"name":"bosek","email":"bosektom@gmail.com"}],"description":"Optimizer for @rsql/ast","homepage":"https://github.com/bosek/rsql-optimizer#readme","keywords":[],"repository":{"type":"git","url":"git+https://github.com/bosek/rsql-optimizer.git"},"author":{"name":"Tomas Bosek","url":"https://github.com/bosek"},"bugs":{"url":"https://github.com/bosek/rsql-optimizer/issues"},"license":"MIT","readme":"# @bosek/rsql-optimizer\n\nOptimizer written in TypeScript for RSQL ASTs. Feed it the output of [`@rsql/parser`](https://github.com/piotr-oles/rsql) and get back a smaller, equivalent tree that’s easier to evaluate or translate to SQL/ORM queries. The real benefit is getting rid of tautologies and contradictions to lower the amount of DB calls(see example #5).\n\n- **Runtime targets Node(CJS, ESM) & Browser(CJS, ESM, IIFE)**\n- **~150 tests**\n- **Single dependency [`@rsql/ast`](https://github.com/piotr-oles/rsql)**  \n\n## Install\n\n```bash\nnpm i @bosek/rsql-optimizer\n# or\npnpm add @bosek/rsql-optimizer\n```\n\n## Quick start\n\n```ts\nimport { emit } from '@rsql/emitter';\nimport { parse } from '@rsql/parser';\nimport { optimize } from '@bosek/rsql-optimizer';\n\nconst fieldSchema = {\n    year: createField(\"number\"),\n    genre: createField(\"string\"),\n};\n\nconst input = \"year<=1980;genre=in=(fantasy,scifi);year<=2000;genre!=fantasy\";\n\nconst ast = parse(input);\nconst optimized = optimize(ast, schema);\nconst output = emit(optimized);\n//    input  = \"year<=1980;genre=in=(fantasy,scifi);year<=2000;genre!=fantasy\"\n//    output = \"year<=1980;genre==scifi\"\n```\n\n## Features\n\n* **Operator normalization** – unifies logical/comparison verbose operator variants\n* **Deduplication** – removes duplicate conditions in AND/OR groups\n* **Range & set merging** – combines bounds, equalities, and IN/OUT into minimal form\n* **Contradiction / tautology detection** – spots impossible or always-true filters\n* **OR simplifications** – unions equalities into `IN`, non-equalities into `OUT`, keeps weakest bounds, applies safe `!=` rules\n\n*Not an exhaustive list. Please take a look at tests to see it in action!\n\n## Disclaimer\nThis library is not battle-tested and as you can probably imagine is hard to write tests for. I am sure there are still some edge cases that are not properly handled. I welcome any feedback, bug reports or pull requests.\n\n## API\n```ts\nimport type { ExpressionNode } from '@rsql/ast';\n\nexport type FieldType = \"number\" | \"string\" | \"date\";\nexport type Field = { type: FieldType; caseInsensitive?: boolean };\nexport type FieldSchema = {\n    [key: string]: Field;\n};\n\nexport function createField(type: FieldType, caseInsensitive?: boolean): Field;\nexport function optimize(node: ExpressionNode, schema: FieldSchema): ExpressionNode | undefined;\n```\n\nThe optimizer does **not** evaluate data or talk to your DB. It only rewrites the AST to hopefuly reduce the number of DB calls. Zero assumptions.\n\n## Examples\n### 1. OR dedupe (with `caseInsensitive: true`)\nDuplicate comparisons collapse, case insensitive in this case.\n```\ninput:  city==Brno,city==brno\noutput: city==brno\n```\n### 2. List dedupe\nDoes not care about order of elements in lists.  \n\nWith `caseInsensitive: true`:\n```\ninput:  city=in=(Ostrava,Brno,Prague),city=in=(Prague,brno,Ostrava)\noutput: city=in=(Ostrava,Brno,Prague)\n```\nWith `caseInsensitive: false`:\n```\ninput:  city=in=(Ostrava,Brno,Prague),city=in=(Prague,brno,Ostrava)\noutput: city=in=(Ostrava,Brno,Prague,brno)\n```\n### 3. AND range merge\nTightest lower bound kept and merged with upper bound. Operator normalization.\n```\ninput:  age>=18 and age>21 and age<=65\noutput: age>=21;age<=65\n```\n### 4. Bound dominance\nTightest lower bound kept and other fields(`status`) are unchanged.\n```\ninput:  (age==18,age==21,status==active),age>=18,age>18\noutput: age>=18,status==active\n```\n### 5. NEQ behavior in OR + IN interaction\nRemoves redundant IN when NEQ present(and IN does not include NEQ value).\n```\ninput:  age!=13,age=in=(18,19),city==brno\noutput: age!=13,city==brno\n```\nCompare to:\n```\ninput:  age!=13,age=in=(13,19),city==brno\noutput: undefined\n```\nThis example would lead to tautology - always true. So our string would return all records if translated to SQL query.\n### 5. Mixed nesting, contradictions and bounds\nPer-field OR(bounds + points) and a separate AND branch\n```\ninput:  (salary>100000,salary>=100000,salary==100000),(department==eng;salary>=90000),(department=in=(eng,sales))\noutput: department==eng;salary>=90000,salary>=100000,department=in=(eng,sales)\n```\nParentheses around AND groups are redundant(and cosmetic) as according to [**RSQL spec**](https://github.com/jirutka/rsql-parser?tab=readme-ov-file#grammar-and-semantic) AND has precedence over OR by default:  \n>By default, the AND operator takes precedence (i.e. it’s evaluated before any OR operators are). However, a parenthesized expression can be used to change the precedence, yielding whatever the contained expression yields.\n\n## Plans\nDistinction between tautology/contradiction can be implemented. I am not sure right now if it needs to be there. Please let me know if you'd find it useful.\n\n## License\nMIT © 2025 Tomas Bosek","readmeFilename":"README.md","_rev":"1-e1409ad8a5c087db4e0eda9c0c1cdfd6"}