groq-js

A TypeScript implementation of GROQ, the query language for filtering, joining, and shaping JSON documents.

Library
npm
v2.0.0
344stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
72/100Good
Development Activity68
Maintenance68
Community72
Maturity60
Momentum20

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
69/100Good
Architecture85
Code Quality87
Innovation78
Learning Curve25

groq-js implements GROQ (Graph-Relational Object Queries), the JSON query language originally built for Sanity’s structured content platform, entirely in TypeScript. It parses GROQ query strings into an ESTree-inspired syntax tree, then evaluates that tree against an in-memory dataset of JSON documents, supporting filters, projections, joins via dereferencing, scoring, and dozens of built-in functions across namespaces like array, string, math, dateTime, geo, and pt (Portable Text).

Beyond evaluation, groq-js ships a standalone type evaluator that can statically infer the TypeScript-shaped result type of a GROQ query when given a Sanity schema, including narrowing through filters and joins, useful for generating typed query results without ever running the query.

What You Get

  • A GROQ parser producing an ESTree-inspired syntax tree via parse()
  • Synchronous and asynchronous evaluators (evaluate/evaluateSync) that run a parsed query against an in-memory dataset
  • A type evaluator (typeEvaluate) that infers and narrows the TypeScript result type of a query from a Sanity schema
  • An extensive standard library of GROQ functions across array, string, math, dateTime, geo, delta/diff, and Portable Text namespaces
  • An unparse() utility to turn a syntax tree back into GROQ query text

Common Use Cases

  • Testing Sanity Studio queries offline - run GROQ queries against fixture datasets in unit tests without hitting the Content Lake API
  • Building GROQ-powered tools - editors, linters, and playgrounds that need to parse or evaluate GROQ outside of Sanity’s hosted query engine
  • Generating typed query results - use typeEvaluate to derive TypeScript types for a query’s output ahead of time, for codegen or IDE tooling
  • Implementing GROQ support in other datastores - reuse the parser/evaluator to run GROQ queries against non-Sanity JSON stores

Under The Hood

Architecture The library is organized as a pipeline: rawParser.js (a hand-rolled, dependency-free recursive-descent tokenizer/parser) feeds src/parser.ts, which builds a typed ESTree-inspired AST (shared/nodeTypes.ts) via createExpressionBuilder and validates custom function declarations with walk.ts; the AST is then handed to src/evaluator/evaluate.ts, which dispatches each node type to a per-type Executor (an EXECUTORS map keyed by node.type) exposing both executeSync and executeAsync paths, so the same tree can run synchronously for simple in-memory data or asynchronously when dereferencing/streaming is required. A Scope class (src/evaluator/scope.ts) threads params/source/value/context through nested and hidden scopes as traversal.ts walks arrays and projections, while a fully separate src/typeEvaluator/typeEvaluate.ts mirrors the same AST-driven dispatch pattern but statically narrows Sanity schema types instead of producing runtime values, sharing only the parser/AST layer with the evaluator. This split (parse-then-execute vs. parse-then-type-narrow) means the shared AST shape is the one place a change could ripple into both consumers, while the per-namespace function modules stay additive and isolated.

Tech Stack groq-js is authored in strict TypeScript (extending @sanity/tsconfig/strictest) targeting Node 22.12+, built to ESM-only output via tsdown, producing separate entry points for the current API, a frozen legacy v1-compatible surface, and an experimental surface. Its only runtime dependency is obug, a small debug-logging helper, keeping the parser and evaluator themselves dependency-free. Linting runs through ESLint with eslint-config-sanity and simple-import-sort, formatting via a shared Sanity Prettier config, and releases are fully automated with semantic-release driven by conventional commits, publishing through a dedicated CI & Release GitHub Actions workflow plus a separate canary-publish workflow on every push.

Code Quality Testing is extensive and runs on tap with direct TypeScript execution, spanning parser snapshot tests, evaluator behavior tests, function-level tests, and several dedicated type-evaluator test suites, plus a generator script that pulls the official cross-language GROQ test suite and turns it into local fixtures, meaning the library is validated against the canonical spec rather than only its own hand-written cases. Error handling is explicit and typed, with a dedicated syntax-error class carrying position information for parse failures, and executors raising explicit errors when a streamed value reaches a synchronous code path instead of silently returning the wrong result. TypeScript runs at its strictest configuration, CI covers all maintained Node LTS versions, and ESLint/Prettier enforce consistent style throughout.

API Design The public API is deliberately small and stable, with parse, evaluate/evaluateSync, and typeEvaluate covering the entire surface, and the README’s own quickstart mirrors the same call shape used throughout the test suite, so there is very little boilerplate to get a query running against an in-memory dataset. Namespaced exports let consumers pin to a frozen legacy contract while still reaching newer experimental functions, and typeEvaluate is notable developer-experience work: it gives tooling authors compile-time-accurate TypeScript types for a query’s result without ever running it, something most query-language implementations don’t attempt. Documentation is comparatively modest relative to the size of the function library, so discovering the full set of GROQ namespaces means reading source more than docs.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search