{"id":23843493,"url":"https://github.com/marianmeres/condition-builder","last_synced_at":"2026-07-20T17:32:59.535Z","repository":{"id":270671142,"uuid":"864442441","full_name":"marianmeres/condition-builder","owner":"marianmeres","description":"For building sql where statements programatically.","archived":false,"fork":false,"pushed_at":"2026-04-18T09:53:50.000Z","size":68,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2026-04-18T11:42:29.183Z","etag":null,"topics":["conditions","sql","where"],"latest_commit_sha":null,"homepage":"","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/marianmeres.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2024-09-28T08:23:01.000Z","updated_at":"2026-04-18T09:53:52.000Z","dependencies_parsed_at":"2025-01-02T09:41:38.158Z","dependency_job_id":"df89dd6e-ca29-42ae-b8bf-45e438c964fe","html_url":"https://github.com/marianmeres/condition-builder","commit_stats":null,"previous_names":["marianmeres/condition-builder"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/marianmeres/condition-builder","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marianmeres%2Fcondition-builder","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marianmeres%2Fcondition-builder/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marianmeres%2Fcondition-builder/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marianmeres%2Fcondition-builder/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/marianmeres","download_url":"https://codeload.github.com/marianmeres/condition-builder/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marianmeres%2Fcondition-builder/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35695246,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-07-20T02:08:10.276Z","status":"ssl_error","status_checked_at":"2026-07-20T02:08:09.736Z","response_time":111,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":["conditions","sql","where"],"created_at":"2025-01-02T19:47:35.544Z","updated_at":"2026-07-20T17:32:59.523Z","avatar_url":"https://github.com/marianmeres.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# @marianmeres/condition-builder\n\n[![NPM version](https://img.shields.io/npm/v/@marianmeres/condition-builder.svg)](https://www.npmjs.com/package/@marianmeres/condition-builder)\n[![JSR version](https://jsr.io/badges/@marianmeres/condition-builder)](https://jsr.io/@marianmeres/condition-builder)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n\nA tool for creating hierarchical logical _conditions_ and _expressions_, mainly\nto be used in - but not limited to - an SQL _where_ statement.\n\n## Terminology\n\n### Expression\n\n_Expression_ is the base building block. It consists of `key`, `operator` and `value`.\n\n```ts\n`a=b`;\n```\n\n### Condition\n\n_Condition_ is a hierarchical collection of one or more _expressions_ or _conditions_\njoined by a logical join operator: `and`, `or`, `andNot`, or `orNot`.\n\n```ts\n// condition with one expression\n`a=b`;\n\n// condition with 2 expressions joined by `or`\n`a=b or c=d`;\n\n// condition of multiple hierarchically structured expressions and conditions\n`a=b or (c\u003ed and (e\u003cf or g!=h))`;\n```\n\n## Installation\n\ndeno\n\n```sh\ndeno add jsr:@marianmeres/condition-builder\n```\n\nnodejs\n\n```sh\nnpm i @marianmeres/condition-builder\n```\n\n## Usage\n\n```ts\nimport { Condition } from \"@marianmeres/condition-builder\";\n```\n\n## Example\n\nThe core api consists of 2 methods `and(...)` and `or(...)`. For the _first_ call you can\nuse any one of them.\n\n```ts\nconst c = new Condition();\n\nc.and(\"a\", OPERATOR.eq, \"b\");\n// c.or(\"a\", OPERATOR.eq, \"b\"); // same effect as above for the first call\nassertEquals(c.toString(), \"a=b\");\n\nc.or(\"c\", OPERATOR.neq, \"d\");\nassertEquals(c.toString(), \"a=b or c!=d\");\n\nc.or(\n\tnew Condition()\n\t\t.and(\"e\", OPERATOR.lt, \"f\")\n\t\t.and(\"g\", OPERATOR.eq, \"h\")\n\t\t.or(\n\t\t\tnew Condition()\n\t\t\t\t.and(\"i\", OPERATOR.match, \"j\")\n\t\t\t\t.and(\"k\", OPERATOR.nmatch, \"l\"),\n\t\t),\n);\n\nassertEquals(c.toString(), \"a=b or c!=d or (e\u003cf and g=h or (i~*j and k!~*l))\");\n\n// dump \u0026 restore\nconst c2 = Condition.restore(c.dump());\nassertEquals(c2.toString(), \"a=b or c!=d or (e\u003cf and g=h or (i~*j and k!~*l))\");\n\n// or export the condition as POJO structure for manual processing (eg evaluation)\nconst structure = c.toJSON();\n```\n\n## Operator precedence\n\nSQL's `AND` binds tighter than `OR`. Since the builder API is left-associative\n(each chained call folds into a running accumulator), the rendered string is\nauto-parenthesized to preserve the order in which calls were made:\n\n```ts\nconst c = new Condition()\n\t.and(\"a\", OPERATOR.eq, \"1\")\n\t.or(\"b\", OPERATOR.eq, \"2\")\n\t.and(\"c\", OPERATOR.eq, \"3\");\n\nc.toString(); // \"(a=1 or b=2) and c=3\"\n```\n\nWithout the extra parentheses the string would be `a=1 or b=2 and c=3`, which\nSQL parses as `a=1 OR (b=2 AND c=3)` — a different query. The library emits\nonly the parentheses that are necessary to disambiguate.\n\n## Array values for `in` / `nin`\n\nWhen the operator is `in` or `nin` and the value is an array, it is rendered\nas a parenthesized, comma-separated list. Each element passes through\n`renderValue` individually, so escaping/parameterization applies.\n\n```ts\nnew Condition()\n\t.and(\"id\", OPERATOR.in, [1, 2, 3])\n\t.toString(); // \"id in (1,2,3)\"\n```\n\n## Expression validation and rendering\n\nPoint of this package is to create a textual representation of the logical conditions\nblocks to be used in an sql _where_ statement. By default, the package is content and\ndialect agnostic. Just renders the input as is, which may not be always desired.\n\n### Validation\n\nTo _validate_ the condition, you must provide the `validate` function which will validate\nevery expression before being added to the condition.\n\n```ts\nconst c = new Condition({\n\t// this example will allow only a known keys to be set\n\tvalidate: (ctx: ExpressionContext) =\u003e {\n\t\tconst { key } = ctx;\n\t\tconst keyWhitelist = [\"foo\"];\n\t\tif (!keyWhitelist.includes(key)) {\n\t\t\tthrow new TypeError(`Key '${key}' not allowed`);\n\t\t}\n\t},\n});\n\n// `foo` key is allowed\nc.and(\"foo\", OPERATOR.eq, \"1\");\n\n// `bar` is not\nassertThrows(() =\u003e c.and(\"bar\", OPERATOR.neq, \"2\"));\n```\n\n### Rendering\n\nTo match the textual representation for any specific format you must provide any of the\n`renderKey`, `renderValue`, or `renderOperator` functions.\n\nFor example for postgresql dialect you may use something like this:\n\n```ts\nconst c = new Condition({\n\t// escape identifiers in postgresql dialect\n\trenderKey: (ctx: ExpressionContext) =\u003e `\"${ctx.key.replaceAll('\"', '\"\"')}\"`,\n\t// escape values in postgresql dialect\n\trenderValue: (ctx: ExpressionContext) =\u003e\n\t\t`'${ctx.value.toString().replaceAll(\"'\", \"''\")}'`,\n\t// read below\n\t// renderOperator(ctx: ExpressionContext): string\n});\nc.and('fo\"o', OPERATOR.eq, \"ba'r\");\nassertEquals(c.toString(), `\"fo\"\"o\"='ba''r'`);\n```\n\n#### Built-in operators rendering\n\nThere is a default built-in operator-to-symbol replacement logic (targeting postgresql\ndialect), loosely inspired by\n[postgrest](https://docs.postgrest.org/en/v12/references/api/tables_views.html).\n\nAny found operator in the map below will be replaced with its symbol. If the operator is\nnot found in the map, no replacement will happen. You can customize this logic by\nproviding your own custom `renderOperator` function.\n\n```ts\n// default opinionated conversion map of operators to operator symbols.\n{\n    eq: \"=\",\n    neq: \"!=\",\n    gt: \"\u003e\",\n    gte: \"\u003e=\",\n    lt: \"\u003c\",\n    lte: \"\u003c=\",\n    like: \" ilike \",\n    nlike: \" not ilike \",\n    match: \"~*\",\n    nmatch: \"!~*\",\n    is: \" is \",\n    nis: \" is not \",\n    in: \" in \",\n    nin: \" not in \",\n    // PostgreSQL ltree operators\n    ltree: \"~\",\n    ancestor: \"@\u003e\",\n    descendant: \"\u003c@\",\n};\n\n// but you can safely use any operator you see fit...\nconst e = new Expression(\"foo\", \"==\", \"bar\");\nassertEquals(e.toString(), \"foo==bar\");\n```\n\n## PostgreSQL presets\n\nFor PostgreSQL output, two presets are shipped:\n\n### Inline SQL (`pgRenderers`)\n\nDouble-quotes identifiers and single-quote-escapes string literals. Handles\n`null`, booleans, numbers, and bigints natively.\n\n```ts\nimport { Condition, OPERATOR, pgRenderers } from \"@marianmeres/condition-builder\";\n\nconst c = new Condition()\n\t.and('fo\"o', OPERATOR.eq, \"ba'r\")\n\t.and(\"id\", OPERATOR.in, [1, 2, 3])\n\t.or(\"active\", OPERATOR.is, null);\n\nc.toString(pgRenderers);\n// \"fo\"\"o\"='ba''r' and \"id\" in (1,2,3) or \"active\" is null\n```\n\n### Parameterized queries (`pgParameterized`) — recommended\n\nEmits `$1`, `$2`, … placeholders and collects values into a shared array.\nValues never touch the SQL string, which makes this the safest option for\ninput you don't control.\n\n```ts\nimport { Condition, OPERATOR, pgParameterized } from \"@marianmeres/condition-builder\";\n\nconst { options, params } = pgParameterized();\n\nconst c = new Condition()\n\t.and(\"id\", OPERATOR.in, [1, 2, 3])\n\t.and(\"name\", OPERATOR.eq, \"'; drop table users; --\");\n\nconst sql = c.toString(options);\n// \"id\" in ($1,$2,$3) and \"name\"=$4\n// params: [1, 2, 3, \"'; drop table users; --\"]\n\n// Pass to your driver:\n// await client.query(`select * from users where ${sql}`, params);\n```\n\nPass a custom `startIndex` if you're stitching the condition into a larger\nquery that already has parameters.\n\n## Changelog\n\nSee [CHANGELOG.md](./CHANGELOG.md) for release notes and breaking-change\ndetails.\n\n## API Reference\n\nSee [API.md](./API.md) for the complete API documentation.\n\n## Related\n\n[@marianmeres/condition-parser](https://github.com/marianmeres/condition-parser)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarianmeres%2Fcondition-builder","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmarianmeres%2Fcondition-builder","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarianmeres%2Fcondition-builder/lists"}