{"id":51050020,"url":"https://github.com/lukethacoder/query-builder-ts","last_synced_at":"2026-06-22T16:32:26.766Z","repository":{"id":366598519,"uuid":"1276922084","full_name":"lukethacoder/query-builder-ts","owner":"lukethacoder","description":"🎯 TypeScript Query Builder implementation","archived":false,"fork":false,"pushed_at":"2026-06-22T15:09:50.000Z","size":58,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-22T15:10:44.265Z","etag":null,"topics":["query-builder"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/@lukethacoder/query-builder","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/lukethacoder.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":null,"dco":null,"cla":null}},"created_at":"2026-06-22T12:19:39.000Z","updated_at":"2026-06-22T15:09:56.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/lukethacoder/query-builder-ts","commit_stats":null,"previous_names":["lukethacoder/query-builder-ts"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/lukethacoder/query-builder-ts","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lukethacoder%2Fquery-builder-ts","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lukethacoder%2Fquery-builder-ts/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lukethacoder%2Fquery-builder-ts/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lukethacoder%2Fquery-builder-ts/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lukethacoder","download_url":"https://codeload.github.com/lukethacoder/query-builder-ts/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lukethacoder%2Fquery-builder-ts/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34657893,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-22T02:00:06.391Z","response_time":106,"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":["query-builder"],"created_at":"2026-06-22T16:32:26.674Z","updated_at":"2026-06-22T16:32:26.750Z","avatar_url":"https://github.com/lukethacoder.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# @lukethacoder/query-builder\n\nA TypeScript implementation of the [QueryBuilder Spec](https://github.com/lukethacoder/query-builder-spec) — a language-neutral standard for representing, addressing, manipulating, validating, and serializing structured filter queries.\n\n## Installation\n\n```sh\npnpm add @lukethacoder/query-builder\n```\n\n## Overview\n\nA query is a tree. The root is a **group**; groups contain **rules** (single conditions) and nested groups. This library gives you:\n\n- **Types** — full TypeScript types for the entire data model\n- **Manipulation** — immutable operations to build and edit query trees (`add`, `remove`, `update`, `move`, `insert`, `group`)\n- **Validation** — a default validator plus utilities for custom validators\n- **Serialization** — export to JSON, SQL, parameterized SQL, MongoDB query objects, and JsonLogic\n\n## Quick start\n\n```ts\nimport { add, formatSql, remove, update } from \"@lukethacoder/query-builder\";\nimport type { RuleGroup } from \"@lukethacoder/query-builder\";\n\n// Start with an empty query\nconst empty: RuleGroup = { combinator: \"and\", rules: [] };\n\n// Add rules\nlet query = add(empty, { field: \"firstName\", operator: \"=\", value: \"Steve\" }, []);\nquery = add(query, { field: \"lastName\", operator: \"=\", value: \"Vai\" }, []);\n\n// Export to SQL\nconsole.log(formatSql(query));\n// → (firstName = 'Steve' and lastName = 'Vai')\n```\n\n## Data model\n\n### Rule\n\nA single condition: a field, an operator, and a value.\n\n```ts\ninterface Rule {\n  id?: string;\n  field: string;\n  operator: string;\n  value: unknown;\n  valueSource?: \"value\" | \"field\";\n  disabled?: boolean;\n}\n```\n\n### RuleGroup (standard)\n\nJoins all children with a single combinator.\n\n```ts\ninterface RuleGroup {\n  id?: string;\n  combinator: string;                   // e.g. \"and\" | \"or\"\n  rules: Array\u003cRule | RuleGroup\u003e;\n  not?: boolean;\n  disabled?: boolean;\n}\n```\n\n### RuleGroupIC (independent combinators)\n\nAllows mixed combinators within one group by interleaving combinator strings between children.\n\n```ts\ninterface RuleGroupIC {\n  id?: string;\n  rules: Array\u003cRule | RuleGroupIC | string\u003e; // strings are combinators\n  not?: boolean;\n  disabled?: boolean;\n}\n```\n\n## Manipulation\n\nAll operations are **immutable** — they return a new query and never mutate the input.\n\nOperations accept either a `Path` (integer array) or a node `id` string as the location argument.\n\n### `add(query, ruleOrGroup, parentPath, options?)`\n\nAppends a rule or group to the end of the target group's children.\n\n```ts\nimport { add } from \"@lukethacoder/query-builder\";\n\nquery = add(query, { field: \"age\", operator: \"\u003e\", value: 18 }, []);\n```\n\n### `remove(query, path)`\n\nRemoves the node at the given path.\n\n```ts\nimport { remove } from \"@lukethacoder/query-builder\";\n\nquery = remove(query, [0]); // remove first child of root\n```\n\n### `update(query, property, value, path, options?)`\n\nSets a single property on the node at the given path.\n\n```ts\nimport { update } from \"@lukethacoder/query-builder\";\n\nquery = update(query, \"operator\", \"\u003e=\", [0]);\nquery = update(query, \"combinator\", \"or\", []); // update root combinator\n```\n\nChanging `field` automatically resets `operator`, `valueSource`, and `value` by default (`resetOnFieldChange: true`). Pass `options` to override.\n\n### `move(query, fromPath, toPath, options?)`\n\nMoves a node to another position. `toPath` can be a `Path`, `\"up\"`, or `\"down\"`.\n\n```ts\nimport { move } from \"@lukethacoder/query-builder\";\n\nquery = move(query, [0], [2]);\nquery = move(query, [1], \"up\");\nquery = move(query, [0], [1], { clone: true }); // copy instead of move\n```\n\n### `insert(query, ruleOrGroup, path, options?)`\n\nInserts a node at an exact position (always regenerates IDs on the inserted node).\n\n```ts\nimport { insert } from \"@lukethacoder/query-builder\";\n\nquery = insert(query, { field: \"city\", operator: \"=\", value: \"London\" }, [1]);\n```\n\n### `group(query, sourcePath, targetPath, options?)`\n\nWraps the nodes at `sourcePath` and `targetPath` together inside a new nested group at `targetPath`.\n\n```ts\nimport { group } from \"@lukethacoder/query-builder\";\n\nquery = group(query, [1], [0]);\n```\n\n### IC (independent combinator) queries\n\nConvert between standard and IC forms:\n\n```ts\nimport { convertToIC, convertFromIC } from \"@lukethacoder/query-builder\";\n\nconst ic = convertToIC(standardQuery);\nconst standard = convertFromIC(icQuery);\n```\n\n## Validation\n\n### Default validator\n\nValidates group structure: flags empty groups, invalid combinators, and malformed IC arrays.\n\n```ts\nimport { defaultValidator } from \"@lukethacoder/query-builder\";\n\nconst result = defaultValidator(query);\n// → Record\u003cnodeId, { valid: boolean; reasons?: string[] }\u003e\n```\n\n### Custom validators\n\n```ts\nimport type { QueryValidator, RuleValidator } from \"@lukethacoder/query-builder\";\n\n// Rule-level: attached to a field definition\nconst ageValidator: RuleValidator = (rule) =\u003e {\n  return Number(rule.value) \u003e= 0\n    ? true\n    : { valid: false, reasons: [\"age must be non-negative\"] };\n};\n\n// Query-level: passed to serializers or your own pipeline\nconst queryValidator: QueryValidator = (query) =\u003e {\n  // return boolean or ValidationMap keyed by node id\n};\n```\n\n## Serialization\n\nAll serializers accept a `validator` option — invalid rules/groups are dropped from non-JSON output.\n\n### JSON\n\n```ts\nimport { formatJson, formatJsonWithoutIds, parseJson } from \"@lukethacoder/query-builder\";\n\nconst json = formatJson(query);              // pretty JSON, includes ids\nconst stable = formatJsonWithoutIds(query);  // single-line, strips ids and paths\nconst parsed = parseJson(json);              // round-trip back to a query tree\n```\n\n### SQL\n\n```ts\nimport { formatSql } from \"@lukethacoder/query-builder\";\n\nformatSql(query);\n// → (firstName = 'Steve' and lastName = 'Vai')\n\n// Dialect presets\nformatSql(query, { preset: \"postgresql\" });\nformatSql(query, { preset: \"mssql\" });\nformatSql(query, { preset: \"mysql\" });\nformatSql(query, { preset: \"sqlite\" });\n```\n\n### Parameterized SQL\n\n```ts\nimport { formatParameterized, formatParameterizedNamed } from \"@lukethacoder/query-builder\";\n\nconst { sql, params } = formatParameterized(query);\n// → { sql: \"(firstName = ? and lastName = ?)\", params: [\"Steve\", \"Vai\"] }\n\nconst named = formatParameterizedNamed(query);\n// → { sql: \"(firstName = :firstName_1 and lastName = :lastName_1)\", params: { firstName_1: \"Steve\", lastName_1: \"Vai\" } }\n\n// PostgreSQL numbered params\nformatParameterized(query, { preset: \"postgresql\" });\n// → { sql: \"(firstName = $1 and lastName = $2)\", params: [\"Steve\", \"Vai\"] }\n```\n\n### MongoDB query\n\n```ts\nimport { formatMongodbQuery } from \"@lukethacoder/query-builder\";\n\nformatMongodbQuery(query);\n// → { \"$and\": [{ \"firstName\": \"Steve\" }, { \"lastName\": \"Vai\" }] }\n```\n\n### JsonLogic\n\n```ts\nimport { formatJsonLogic } from \"@lukethacoder/query-builder\";\n\nformatJsonLogic(query);\n// → { \"and\": [{ \"==\": [{ \"var\": \"firstName\" }, \"Steve\"] }, { \"==\": [{ \"var\": \"lastName\" }, \"Vai\"] }] }\n```\n\n## Default operators\n\n| Identifier | Label | Arity |\n|---|---|---|\n| `=` | `=` | binary |\n| `!=` | `!=` | binary |\n| `\u003c` | `\u003c` | binary |\n| `\u003e` | `\u003e` | binary |\n| `\u003c=` | `\u003c=` | binary |\n| `\u003e=` | `\u003e=` | binary |\n| `contains` | `contains` | binary |\n| `beginsWith` | `begins with` | binary |\n| `endsWith` | `ends with` | binary |\n| `doesNotContain` | `does not contain` | binary |\n| `doesNotBeginWith` | `does not begin with` | binary |\n| `doesNotEndWith` | `does not end with` | binary |\n| `null` | `is null` | unary |\n| `notNull` | `is not null` | unary |\n| `in` | `in` | multi-value |\n| `notIn` | `not in` | multi-value |\n| `between` | `between` | ternary |\n| `notBetween` | `not between` | ternary |\n\n## Path addressing\n\nPaths are integer arrays describing a node's location in the tree. The root is `[]`; `[0]` is the first child of the root; `[2, 1]` is the second child of the third child.\n\nIn IC queries, children occupy **even** indices (0, 2, 4 …) and combinator strings occupy odd indices.\n\n```ts\nimport { findPath, getParentPath, isAncestor } from \"@lukethacoder/query-builder\";\n\nconst node = findPath([0, 1], query);\nconst parent = getParentPath([0, 1]);         // → [0]\nconst ancestor = isAncestor([0], [0, 1, 2]); // → true\n```\n\n## Spec conformance\n\nThis library implements **core + baseline export conformance** as defined by the QueryBuilder Spec:\n\n- **Core** — data model (ch. 01), configuration model (ch. 02), defaults (ch. 03), paths (ch. 04), query manipulation (ch. 05), validation (ch. 06), canonical JSON (§7.2)\n- **Baseline exports** — `json`, `json_without_ids`, `sql`, `parameterized`, `parameterized_named`, `mongodb_query`, `jsonlogic`\n\nOptional extended formats (CEL, SPEL, Elasticsearch, ORM adapters, import parsers) are not yet implemented.\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flukethacoder%2Fquery-builder-ts","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flukethacoder%2Fquery-builder-ts","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flukethacoder%2Fquery-builder-ts/lists"}