Skip to content

feat(promql): typed PromQL parser, AST builders, and deparser#95

Merged
pyramation merged 1 commit into
mainfrom
feat/promql-ast
Jul 18, 2026
Merged

feat(promql): typed PromQL parser, AST builders, and deparser#95
pyramation merged 1 commit into
mainfrom
feat/promql-ast

Conversation

@pyramation

Copy link
Copy Markdown
Contributor

Summary

Adds a new dependency-free package promql (naked npm name, confirmed available) to the dev-utils monorepo, following the exact shape of the sibling AST packages (nginx-ast, toml-ast, bash-ast, docker-parser): lexer → parser → deparser → types → clean + parser/deparser/roundtrip tests, makage build, published to npm.

Motivation: composing PromQL with template strings is error-prone (label escaping, operator precedence, parentheses). This package lets you build queries as a typed AST and render them safely.

import { sum, rate, range, metric, neq, by, deparse } from 'promql';

deparse(
  sum(rate(range(metric('container_cpu_usage_seconds_total', [neq('container', '')]), '5m')), by('namespace'))
);
// => 'sum by (namespace) (rate(container_cpu_usage_seconds_total{container!=""}[5m]))'

What's in it

  • lexer.ts — bracket-aware tokenizer. : is a subquery separator inside [...] but part of a (recording-rule) metric name outside it; durations (5m, 1h30m, 100ms) vs numbers (5, 5e3) disambiguated at lex time.
  • parser.ts — precedence-climbing parser: vector/matrix selectors, subqueries, offset/@ modifiers, unary, binary ops (with bool + on/ignoring/group_left/group_right vector matching), aggregations (grouping clause accepted both before sum by (x) (...) and after sum(...) by (x), plus topk/bottomk/quantile/count_values params), function calls, Inf/NaN.
  • deparser.ts — AST → canonical PromQL. Inserts parentheses from operator precedence, so ASTs built by hand (no explicit ParenExpr) still render to a string that reparses to the same tree.
  • builders.ts — the ergonomic layer the other packages don't have: metric/selector/eq/neq/re/nre, range/subquery, rate/irate/increase/*_over_time, sum/min/max/avg/count/... + topk/bottomk/quantile/countValues, by/without, add/sub/mul/.../and/or/unless, on/ignoring, offset/at.
  • clean.tscleanTree/astEqual/printAst.

Note on the range field name

The sibling packages use range on BaseNode for source position. PromQL's own range (the [5m] duration on matrix selectors / subqueries) collides with that, and cleanTree was stripping it. Resolved by naming the position field loc (still stripped by cleanTree) and keeping the idiomatic semantic range field intact.

Tests / CI

  • 48 tests across parser / deparser / roundtrip / builders — all green. Roundtrip covers precedence, ^ right-assoc, bool+matching, subqueries, modifiers, colon metric names.
  • tsc --noEmit clean; makage build (CJS+ESM) clean.
  • Added promql to the CI test matrix in .github/workflows/ci.yml.

Lint note: eslint . fails repo-wide because ESLint 9 is installed while the repo still uses legacy .eslintrc.json (same for all sibling packages); CI does not run a lint step. Not addressed here as it's a repo-wide config issue.

Follow-up (separate PR)

Refactor the constructive-db compute-usage-collector to build its namespace CPU/memory PromQL via these builders + deparse() instead of template literals (it already consumes dev-utils packages from npm, e.g. schema-typescript).

Link to Devin session: https://app.devin.ai/sessions/16409487e97d44828dfb46277767604b
Requested by: @pyramation

@pyramation pyramation self-assigned this Jul 18, 2026
@devin-ai-integration

Copy link
Copy Markdown

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@pyramation
pyramation merged commit 2688697 into main Jul 18, 2026
48 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant