Skip to content
Hooney
한국어

Engines & modules · In use

kuery

A filter engine that defines conditions in one shared format, so on-screen filter chips, URLs, in-app filtering, and database queries all read them the same way.

1Filter format shared by chips, URLs, in-app filtering, and database queries

Period
2026.07 ~ Present
Organization
Firstage
Role
Design and implementation (solo)
The kuery filter builder inside the CRM example in the grids Storybook. The text condition 'status:churned (mrr:>=800 OR plan:Free)' unfolds into a status chip and an OR group of MRR at least 800 or the Free plan, and the header shows 1,189 of 10,000 contacts.

Core value

Records are filtered exactly as the chips show, and a selection never widens the scope of an action.

Outcomes

  • Powers the Journey shared-space gallery filters since September 2026
  • Database queries are built only from registered fields, which keeps them safe
  • Reads and writes text conditions such as 'status:churned' and a storage format (JSON) from one definition

Tech stack

  • TypeScript
  • React
  • Drizzle ORM

Context

List screens tend to build the filter UI, the server’s WHERE clause, and a URL that survives a refresh separately. Then the chips, the actual query, and the results a shared link opens drift apart. I built this engine to define what a filter means in one place and to handle the scope of actions on the matching records the same way.

Decisions and implementation

  • Filters are limited to a normal form (CNF), a top-level AND over OR groups. Negation is not part of the model, and a condition from outside that contains one is rejected.
  • Each field type defines a value codec and its allowed operators, and the same registry reads and writes both the text grammar and JSON.
  • The Drizzle compiler that produces SQL conditions adds no operation the IR lacks and accepts only registered fields. A filter IR is treated as untrusted input from the start.
  • A selection compiles beside the filter rather than inside it. An explicit list becomes IN, all matching records minus exclusions becomes NOT IN, and an empty list becomes a condition that is never true, so a selection never widens an action.
  • Typed text becomes a draft once typing pauses or focus leaves, and only Apply changes the condition the host runs. Widget copy ships in Korean, English, and Japanese.

Current state

Since September 2026, the shared-space gallery in the Journey app has built its place, date, and “only my photos” filters as kuery-core conditions and applied them to the photo list. The personal gallery, which pages by cursor, still filters in its service request. URL and browser history sync is next, and wiring it into the Dashboard together with grids has not started.