ql
@webda/ql — WebdaQL
A structured query language (WQL) for filtering and paginating Webda Store results. WQL queries are parsed by an ANTLR4 grammar and translated to native backend queries by each Store implementation (in-memory, MongoDB, PostgreSQL, DynamoDB).
When to use it
You need WQL when you call store.query() or a relation's .query() method. Every Webda Store accepts a WQL string; no raw SQL, MongoDB filter objects, or DynamoDB conditions needed.
// Query the User repository
const { results } = await User.getRepository().query(`email = 'alice@example.com' LIMIT 1`);
// Query a relation
const { results: posts } = await user.posts.query(`status = 'published' ORDER BY createdAt DESC LIMIT 10`);
Install
npm install @webda/ql
This package is a dependency of
@webda/core— you rarely need to install it directly.
Syntax overview
expression? orderExpression? limitExpression? offsetExpression? -- a filter query (implicit SELECT)
SELECT f1, f2 [WHERE expression] [ORDER BY ...] [LIMIT n] [OFFSET t]
SELECT g, COUNT(*) AS n, SUM(f) AS s [WHERE expression] [GROUP BY g] [ORDER BY ...] [LIMIT n]
DELETE [WHERE expression] [LIMIT n]
UPDATE SET a = v, b.c = v [WHERE expression] [LIMIT n]
Keywords are uppercase only: select, delete, set, where... in lowercase are field names. Since the
aggregation syntax, uppercase COUNT, SUM, AVG, MIN, MAX, AS, DISTINCT and GROUP BY are reserved too
(breaking for attributes with those uppercase names).
Filter expressions support:
- Comparison:
field = value,field != value,field > value,field >= value,field < value,field <= value - Time function:
field > NOW(),field < NOW('-10m'),field >= NOW('+1d')(millisecond timestamp resolution) - Pattern match:
field LIKE "pattern"(_= single char,%= any chars) - Set membership:
field IN [value, value, ...] - Array contains:
field CONTAINS value - Null checks:
field IS NULL,field IS NOT NULL(missing,undefinedornull) - Logic:
AND,OR,( ... )
Pagination / ordering:
ORDER BY field [ASC|DESC], ...LIMIT <integer>OFFSET "<continuationToken>"
Quick examples
import * as WebdaQL from "@webda/ql";
// Parse and evaluate against an in-memory object
const validator = new WebdaQL.QueryValidator(`status = 'published' AND viewCount >= 100`);
const post = { status: "published", viewCount: 150 };
console.log(validator.eval(post)); // true
// Prepend a mandatory condition to a user-supplied query
const merged = WebdaQL.PrependCondition(`status = 'published' ORDER BY title LIMIT 10`, `authorId = 'u-123'`);
// => 'status = "published" AND authorId = "u-123" ORDER BY title ASC LIMIT 10'
Parameters
Never concatenate user input into a query. Pass values as parameters instead, with ? and an array or :name and an object:
await Task.query("owner = ? AND status IN ?", [user, ["open", "late"]]);
await Task.query("owner = :owner OR reviewer = :owner", { owner: user });
WebdaQL.bind("priority >= ? LIMIT ?", [2, 10]); // priority >= 2 LIMIT 10
Placeholders are only allowed where a value is expected, values are escaped by type, and = ? / != ? with null become IS NULL / IS NOT NULL. Template literals passed straight to a query method are escaped the same way at compile time by webdac. See Parameters.
NOW() time function
WebdaQL supports NOW() to produce dynamic numeric timestamps (milliseconds since Unix epoch):
NOW(): current timestamp (Date.now()).NOW('-10m')/NOW('+1d'): current timestamp offset by duration string.- Units supported:
ms,s/sec,m/min,h/hr,d/day,mo/mon/month,y/yr/year. Multi-part durations likeNOW('-1d2h')are supported. - Numeric millisecond offsets:
NOW(-60000)orNOW(60000). - Parameters:
NOW(?)andNOW(:offset)can be bound usingbind(). - Updates:
UPDATE SET lastLoginAt = NOW(). - Dynamic evaluation: Expressions with
NOW()evaluate dynamically at query execution or access time, allowing cached queries and operation permissions to track real time:
@Operation({ permission: "mfa = 'verified' AND mfaAt > NOW('-10m')" })
Statements: SELECT, DELETE, UPDATE
WebdaQL parses SELECT, DELETE and UPDATE statements; the parsed Query carries type ("SELECT" for a plain filter), fields (SELECT) and assignments (UPDATE), and toString() round-trips them.
- Field lists are parse-level only: the Query operations (REST, gRPC, MCP, GraphQL) refuse a
SELECTfield list with a 400, and repositories refuse it inquery(). - DELETE and UPDATE are repository-level operations:
useRepository(Model).deleteMany("DELETE WHERE ...")andupdateMany("UPDATE SET ... WHERE ...")run them in bulk and return the affected count. They bypass events, hooks, validation andcanAct. The Query operations refuse them with a 400, andquery()/iterate()refuse them.
const q = WebdaQL.parse("UPDATE SET status = 'archived' WHERE owner = 'bob' LIMIT 10");
q.type; // "UPDATE"
q.assignments; // [{ field: "status", value: "archived" }]
await useRepository(Task).updateMany("UPDATE SET status = ? WHERE owner = ?", ["archived", "bob"]); // count
See Statements.
Aggregations
A SELECT with aggregate functions or GROUP BY parses into an AggregationQuery (parse(q).aggregation), the
same AST toAggregationQuery() builds from the object form used by Model.aggregate(). Stores translate it
natively, or stream objects through the in-memory Aggregator.
const q = WebdaQL.parse("SELECT status, COUNT(*) AS n, SUM(points) AS total GROUP BY status ORDER BY total DESC");
q.aggregation.metrics; // { n: { fn: "COUNT" }, total: { fn: "SUM", field: "points" } }
See Statements and Repositories.
Code completion
WebdaQL provides grammar-driven code completion powered by antlr4-c3. getCompletions returns candidates for fields, keywords, operators, enum/boolean values, and parameters for a query and caret offset:
import { getCompletions, fieldsFromSchema } from "@webda/ql";
const fields = [
{ name: "status", type: "string", enum: ["draft", "published"] },
{ name: "viewCount", type: "integer" }
];
const { items, prefix } = getCompletions("status = 'dr", 12, { fields });
// => items offer "'draft'", replacing range [9, 13]
See Code Completion.
API reference
| Export | Description |
|---|---|
QueryValidator | Parses a WQL string; eval(obj) evaluates it, toString() normalizes it |
bind(query, params) | Binds ? / :name placeholders to escaped values |
escape(parts, values) | Escapes template literal values (used by the compile-time rewrite) |
validateSyntax(query) | Checks a query against the grammar without evaluating it; placeholders allowed |
PrependCondition(query, condition) | Merges a condition in front of an existing query, preserving ORDER BY / LIMIT / OFFSET |
ExpressionBuilder | ANTLR visitor that builds the optimized expression AST |
AndExpression | Logic AND node |
OrExpression | Logic OR node |
ComparisonExpression | Comparison leaf node |
parse(query, allowedFields?) | Parses a filter or statement; allowedFields checks SELECT fields and SET targets |
validateQueryFields(query, allowed) | Checks SELECT fields and SET targets against a field list |
assertFilterQuery(query) | Refuses DELETE, UPDATE, SELECT field lists and aggregations |
toAggregationQuery(input, params?) | Converts the object form of an aggregation to a validated AggregationQuery |
validateAggregation(query) | Validates an AggregationQuery (paths, aliases, functions, ORDER BY, LIMIT) |
Aggregator | In-memory aggregation engine: add(item) then rows() |
compareValues(a, b) | The cross-backend ordering of aggregated values (null < numbers < strings) |
AggregationError | Runtime aggregation error with a code (AGGREGATION_NOT_NATIVE, AGGREGATION_TOO_MANY_GROUPS) |
Query | Parsed query result: { type, filter, fields?, assignments?, aggregation?, orderBy?, limit?, continuationToken? } |
OrderBy | { field: string; direction: "ASC" | "DESC" } |
getCompletions(query, caret, opts) | Returns grammar-driven code completion candidates at a caret offset |
fieldsFromSchema(schema, maxDepth?) | Flattens a JSON schema into completion fields with dotted paths |
See also
- WQL Syntax reference
- Operators
- Parameters — binding values safely
- Statements — SELECT, DELETE, UPDATE and repository bulk operations
- Code Completion — autocomplete and suggestions
- Store Translators — how each Store backend converts WQL
Classes
- AggregationError
- Aggregator
- AndExpression
- BooleanExpression
- ComparisonExpression
- DynamicValue
- Expression
- ExpressionBuilder
- LogicalExpression
- NowValue
- OrExpression
- PartialComparisonExpression
- PartialExpressionBuilder
- PartialValidator
- QueryValidator
- SetterValidator
- WebdaQLError
Interfaces
- AggregationInput
- AggregationOrder
- AggregationQuery
- AggregationResult
- Assignment
- CompletionField
- CompletionItem
- CompletionOptions
- CompletionResult
- CreateParserOptions
- FindOptions
- JSONSchema7Like
- Metric
- OrderBy
- Query
Type Aliases
- AggregateFunction
- AggregationErrorCode
- ComparisonOperator
- CompletionKind
- FilterOperators
- MetricInput
- ModelFilter
- QueryParameters
- QueryType
- WebdaQLStatement
- WebdaQLString
Variables
Functions
- assertFilterQuery
- bind
- checkAlias
- checkFieldPath
- compareValues
- createParser
- createTolerantParser
- escape
- escapeValue
- fieldsFromSchema
- filterToExpression
- getCompletions
- normalizeQuery
- parse
- parseDurationToMs
- PrependCondition
- stringifyQuery
- toAggregationQuery
- toWebdaQL
- unescapeStringLiteral
- unsanitize
- validateAggregation
- validateQueryFields
- validateSyntax