@tellaro/tql 3.3.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -161,6 +161,24 @@ export declare const TYPE_HINTS: TqlTypeHint[];
161
161
  * enforcement in a place a reader of this file would not think to look.
162
162
  */
163
163
  export declare const VISUALIZATION_TYPES: TqlVisualizationType[];
164
+ /**
165
+ * Function-style group-by bucket levels: `| stats count() by date_histogram(@timestamp, 1h)`.
166
+ *
167
+ * A name here is a `bucketFunction` token only when the next non-space character is `(`, so a
168
+ * FIELD with the same name (`by date_histogram`, `range eq 5`) still lexes as a field — both
169
+ * engines' grammars make the same distinction. `tests/statsBucketKinds.spec.ts` reads the Rust
170
+ * and Python grammars and fails when this list and theirs disagree.
171
+ */
172
+ export declare const GROUP_BY_BUCKET_FUNCTIONS: readonly ["date_histogram", "geotile", "geohash", "range", "filters"];
173
+ /** `[min, max, default]` precision of each geo grid (`stats_buckets.GEO_PRECISION` in both engines). */
174
+ export declare const GEO_GRID_PRECISION: {
175
+ readonly geotile: readonly [0, 29, 7];
176
+ readonly geohash: readonly [1, 12, 5];
177
+ };
178
+ /** `date_histogram` calendar intervals (`stats_buckets.CALENDAR_WORDS` in both engines). */
179
+ export declare const DATE_HISTOGRAM_CALENDAR_INTERVALS: readonly ["minute", "hour", "day", "week", "month", "quarter", "year"];
180
+ /** `date_histogram` fixed-interval units: a positive whole number then one of these (`30s`, `1h`). */
181
+ export declare const DATE_HISTOGRAM_FIXED_UNITS: readonly ["ms", "s", "m", "h", "d"];
164
182
  /**
165
183
  * Keywords used in TQL.
166
184
  */
@@ -200,6 +218,8 @@ export declare class TqlTokenizer {
200
218
  * Scan an identifier (field name, operator keyword, etc.)
201
219
  */
202
220
  private scanIdentifier;
221
+ /** True when `value` names a group-by bucket function and the next non-space character is `(`. */
222
+ private isBucketFunctionCall;
203
223
  /**
204
224
  * Classify an identifier into the appropriate token type
205
225
  */
@@ -125,7 +125,7 @@ const l = [
125
125
  "is_loopback",
126
126
  "is_multicast",
127
127
  "is_link_local"
128
- ], A = new Set(
128
+ ], E = new Set(
129
129
  _
130
130
  ), f = [
131
131
  // Counting
@@ -156,7 +156,7 @@ const l = [
156
156
  "values",
157
157
  "unique",
158
158
  "distinct"
159
- ], m = [
159
+ ], T = [
160
160
  "string",
161
161
  "str",
162
162
  "number",
@@ -173,7 +173,7 @@ const l = [
173
173
  "object",
174
174
  "geo",
175
175
  "ip"
176
- ], T = [
176
+ ], m = [
177
177
  "bar",
178
178
  "barchart",
179
179
  "horizontal_bar",
@@ -196,23 +196,31 @@ const l = [
196
196
  "nested_donut",
197
197
  "chord",
198
198
  "auto"
199
- ], g = ["by", "as", "null", "true", "false", "stats", "top", "bottom"];
200
- class w {
199
+ ], g = ["date_histogram", "geotile", "geohash", "range", "filters"], v = { geotile: [0, 29, 7], geohash: [1, 12, 5] }, I = [
200
+ "minute",
201
+ "hour",
202
+ "day",
203
+ "week",
204
+ "month",
205
+ "quarter",
206
+ "year"
207
+ ], S = ["ms", "s", "m", "h", "d"], w = ["by", "as", "null", "true", "false", "stats", "top", "bottom"];
208
+ class O {
201
209
  input;
202
210
  position;
203
211
  line;
204
212
  column;
205
213
  tokens;
206
- constructor(e) {
207
- this.input = e ?? "", this.position = 0, this.line = 1, this.column = 1, this.tokens = [];
214
+ constructor(t) {
215
+ this.input = t ?? "", this.position = 0, this.line = 1, this.column = 1, this.tokens = [];
208
216
  }
209
217
  /**
210
218
  * Tokenize the entire input string
211
219
  */
212
220
  tokenize() {
213
221
  for (this.tokens = [], this.position = 0, this.line = 1, this.column = 1; !this.isAtEnd(); ) {
214
- const e = this.scanToken();
215
- e && this.tokens.push(e);
222
+ const t = this.scanToken();
223
+ t && this.tokens.push(t);
216
224
  }
217
225
  return this.tokens;
218
226
  }
@@ -222,42 +230,42 @@ class w {
222
230
  scanToken() {
223
231
  if (this.isWhitespace(this.peek()))
224
232
  return this.scanWhitespace();
225
- const e = this.position, n = this.line, i = this.column, t = this.advance();
226
- if ((t === "&" || t === "|") && this.peek() === t)
227
- return this.advance(), this.makeToken("logicalOp", t + t, e, n, i);
228
- switch (t) {
233
+ const t = this.position, n = this.line, i = this.column, e = this.advance();
234
+ if ((e === "&" || e === "|") && this.peek() === e)
235
+ return this.advance(), this.makeToken("logicalOp", e + e, t, n, i);
236
+ switch (e) {
229
237
  case "(":
230
238
  case ")":
231
- return this.makeToken("paren", t, e, n, i);
239
+ return this.makeToken("paren", e, t, n, i);
232
240
  case "[":
233
241
  case "]":
234
- return this.makeToken("bracket", t, e, n, i);
242
+ return this.makeToken("bracket", e, t, n, i);
235
243
  case ",":
236
- return this.makeToken("comma", t, e, n, i);
244
+ return this.makeToken("comma", e, t, n, i);
237
245
  case "|":
238
- return this.makeToken("pipe", t, e, n, i);
246
+ return this.makeToken("pipe", e, t, n, i);
239
247
  }
240
- return t === "=" && this.peek() === ">" ? (this.advance(), this.makeToken("arrow", "=>", e, n, i)) : t === ":" && this.peek() === ":" ? (this.advance(), this.makeToken("doubleColon", "::", e, n, i)) : t === '"' || t === "'" ? this.scanString(t, e, n, i) : this.isDigit(t) || t === "-" && this.isDigit(this.peek()) ? this.scanNumber(e, n, i) : this.isOperatorChar(t) ? this.scanSymbolOperator(e, n, i) : this.isIdentifierStart(t) ? this.scanIdentifier(e, n, i) : this.makeToken("unknown", t, e, n, i);
248
+ return e === "=" && this.peek() === ">" ? (this.advance(), this.makeToken("arrow", "=>", t, n, i)) : e === ":" && this.peek() === ":" ? (this.advance(), this.makeToken("doubleColon", "::", t, n, i)) : e === '"' || e === "'" ? this.scanString(e, t, n, i) : this.isDigit(e) || e === "-" && this.isDigit(this.peek()) ? this.scanNumber(t, n, i) : this.isOperatorChar(e) ? this.scanSymbolOperator(t, n, i) : this.isIdentifierStart(e) ? this.scanIdentifier(t, n, i) : this.makeToken("unknown", e, t, n, i);
241
249
  }
242
250
  /**
243
251
  * Scan whitespace characters
244
252
  */
245
253
  scanWhitespace() {
246
- const e = this.position, n = this.line, i = this.column;
247
- let t = "";
254
+ const t = this.position, n = this.line, i = this.column;
255
+ let e = "";
248
256
  for (; !this.isAtEnd() && this.isWhitespace(this.peek()); ) {
249
257
  const s = this.advance();
250
- t += s;
258
+ e += s;
251
259
  }
252
- return this.makeToken("whitespace", t, e, n, i);
260
+ return this.makeToken("whitespace", e, t, n, i);
253
261
  }
254
262
  /**
255
263
  * Scan a string literal (single or double quoted)
256
264
  */
257
- scanString(e, n, i, t) {
265
+ scanString(t, n, i, e) {
258
266
  let s = "";
259
- const r = e;
260
- for (; !this.isAtEnd() && this.peek() !== e; )
267
+ const r = t;
268
+ for (; !this.isAtEnd() && this.peek() !== t; )
261
269
  if (this.peek() === "\\")
262
270
  if (this.advance(), this.isAtEnd())
263
271
  s += "\\";
@@ -295,7 +303,7 @@ class w {
295
303
  start: n,
296
304
  end: this.position,
297
305
  line: i,
298
- column: t,
306
+ column: e,
299
307
  raw: r + s
300
308
  } : (this.advance(), {
301
309
  type: "string",
@@ -303,88 +311,95 @@ class w {
303
311
  start: n,
304
312
  end: this.position,
305
313
  line: i,
306
- column: t,
307
- raw: e + s + e
314
+ column: e,
315
+ raw: t + s + t
308
316
  });
309
317
  }
310
318
  /**
311
319
  * Scan a numeric literal
312
320
  */
313
- scanNumber(e, n, i) {
314
- this.position = e, this.column = i;
315
- let t = "";
316
- for (this.peek() === "-" && (t += this.advance()); !this.isAtEnd() && this.isDigit(this.peek()); )
317
- t += this.advance();
321
+ scanNumber(t, n, i) {
322
+ this.position = t, this.column = i;
323
+ let e = "";
324
+ for (this.peek() === "-" && (e += this.advance()); !this.isAtEnd() && this.isDigit(this.peek()); )
325
+ e += this.advance();
318
326
  if (this.peek() === "." && this.isDigit(this.peekNext()))
319
- for (t += this.advance(); !this.isAtEnd() && this.isDigit(this.peek()); )
320
- t += this.advance();
327
+ for (e += this.advance(); !this.isAtEnd() && this.isDigit(this.peek()); )
328
+ e += this.advance();
321
329
  if (this.peek() === "e" || this.peek() === "E")
322
- for (t += this.advance(), (this.peek() === "+" || this.peek() === "-") && (t += this.advance()); !this.isAtEnd() && this.isDigit(this.peek()); )
323
- t += this.advance();
324
- return this.makeToken("number", t, e, n, i);
330
+ for (e += this.advance(), (this.peek() === "+" || this.peek() === "-") && (e += this.advance()); !this.isAtEnd() && this.isDigit(this.peek()); )
331
+ e += this.advance();
332
+ return this.makeToken("number", e, t, n, i);
325
333
  }
326
334
  /**
327
335
  * Scan symbol-based operators (==, !=, >, >=, <, <=, !)
328
336
  */
329
- scanSymbolOperator(e, n, i) {
330
- this.position = e, this.column = i;
331
- let t = this.advance();
337
+ scanSymbolOperator(t, n, i) {
338
+ this.position = t, this.column = i;
339
+ let e = this.advance();
332
340
  if (!this.isAtEnd()) {
333
- const s = t + this.peek();
341
+ const s = e + this.peek();
334
342
  if (s === "=>")
335
- return this.advance(), this.makeToken("arrow", "=>", e, n, i);
336
- (s in h || s === "==" || s === "!=") && (t = s, this.advance());
343
+ return this.advance(), this.makeToken("arrow", "=>", t, n, i);
344
+ (s in h || s === "==" || s === "!=") && (e = s, this.advance());
337
345
  }
338
- if (t === "!") {
346
+ if (e === "!") {
339
347
  const s = this.position;
340
348
  let r = "";
341
349
  for (; !this.isAtEnd() && this.isIdentifierChar(this.peek()); )
342
350
  r += this.advance();
343
351
  if (r) {
344
352
  if ("!" + r in u)
345
- return this.makeToken("operator", "!" + r, e, n, i);
353
+ return this.makeToken("operator", "!" + r, t, n, i);
346
354
  this.position = s, this.column = i + 1;
347
355
  }
348
356
  }
349
- return t in h || t === "==" || t === "=" || t === "!=" || t === ">" || t === ">=" || t === "<" || t === "<=" ? this.makeToken("operator", t, e, n, i) : t === "!" ? this.makeToken("logicalOp", t, e, n, i) : this.makeToken("unknown", t, e, n, i);
357
+ return e in h || e === "==" || e === "=" || e === "!=" || e === ">" || e === ">=" || e === "<" || e === "<=" ? this.makeToken("operator", e, t, n, i) : e === "!" ? this.makeToken("logicalOp", e, t, n, i) : this.makeToken("unknown", e, t, n, i);
350
358
  }
351
359
  /**
352
360
  * Scan an identifier (field name, operator keyword, etc.)
353
361
  */
354
- scanIdentifier(e, n, i) {
355
- this.position = e, this.column = i;
356
- let t = "";
362
+ scanIdentifier(t, n, i) {
363
+ this.position = t, this.column = i;
364
+ let e = "";
357
365
  const s = this.advance();
358
- if (t += s, s === "@" && this.peek() === "@")
359
- return this.makeToken("unknown", t, e, n, i);
366
+ if (e += s, s === "@" && this.peek() === "@")
367
+ return this.makeToken("unknown", e, t, n, i);
360
368
  let r = s;
361
369
  for (; !this.isAtEnd() && this.isIdentifierChar(this.peek()); ) {
362
370
  const c = this.advance();
363
371
  if (c === "." && r === ".")
364
- return this.position--, this.column--, t = t.slice(0, -1), this.makeToken("unknown", t, e, n, i);
365
- t += c, r = c;
372
+ return this.position--, this.column--, e = e.slice(0, -1), this.makeToken("unknown", e, t, n, i);
373
+ e += c, r = c;
366
374
  }
367
- t.endsWith(".") && (this.position--, this.column--, t = t.slice(0, -1));
368
- const a = this.classifyIdentifier(t);
369
- return this.makeToken(a, t, e, n, i);
375
+ e.endsWith(".") && (this.position--, this.column--, e = e.slice(0, -1));
376
+ const a = this.isBucketFunctionCall(e) ? "bucketFunction" : this.classifyIdentifier(e);
377
+ return this.makeToken(a, e, t, n, i);
378
+ }
379
+ /** True when `value` names a group-by bucket function and the next non-space character is `(`. */
380
+ isBucketFunctionCall(t) {
381
+ if (!g.includes(t.toLowerCase())) return !1;
382
+ let n = this.position;
383
+ for (; n < this.input.length && /\s/.test(this.input[n]); ) n++;
384
+ return this.input[n] === "(";
370
385
  }
371
386
  /**
372
387
  * Classify an identifier into the appropriate token type
373
388
  */
374
- classifyIdentifier(e) {
375
- const n = e.toUpperCase(), i = e.toLowerCase();
376
- return p.includes(n) ? "logicalOp" : d.includes(n) ? "collectionOp" : i === "true" || i === "false" ? "boolean" : i === "null" ? "null" : i === "stats" ? "keyword" : l.includes(i) ? "operator" : f.includes(i) ? "aggregation" : k.includes(i) ? "mutator" : m.includes(i) ? "typeHint" : T.includes(i) ? "visualization" : g.includes(i) ? "keyword" : "field";
389
+ classifyIdentifier(t) {
390
+ const n = t.toUpperCase(), i = t.toLowerCase();
391
+ return p.includes(n) ? "logicalOp" : d.includes(n) ? "collectionOp" : i === "true" || i === "false" ? "boolean" : i === "null" ? "null" : i === "stats" ? "keyword" : l.includes(i) ? "operator" : f.includes(i) ? "aggregation" : k.includes(i) ? "mutator" : T.includes(i) ? "typeHint" : m.includes(i) ? "visualization" : w.includes(i) ? "keyword" : "field";
377
392
  }
378
393
  // ============================================================================
379
394
  // Helper Methods
380
395
  // ============================================================================
381
- makeToken(e, n, i, t, s) {
396
+ makeToken(t, n, i, e, s) {
382
397
  return {
383
- type: e,
398
+ type: t,
384
399
  value: n,
385
400
  start: i,
386
401
  end: this.position,
387
- line: t,
402
+ line: e,
388
403
  column: s
389
404
  };
390
405
  }
@@ -398,84 +413,88 @@ class w {
398
413
  return this.position + 1 >= this.input.length ? "\0" : this.input[this.position + 1];
399
414
  }
400
415
  advance() {
401
- const e = this.input[this.position];
402
- return this.position++, e === `
403
- ` ? (this.line++, this.column = 1) : this.column++, e;
416
+ const t = this.input[this.position];
417
+ return this.position++, t === `
418
+ ` ? (this.line++, this.column = 1) : this.column++, t;
404
419
  }
405
- isWhitespace(e) {
406
- return e === " " || e === " " || e === `
407
- ` || e === "\r";
420
+ isWhitespace(t) {
421
+ return t === " " || t === " " || t === `
422
+ ` || t === "\r";
408
423
  }
409
- isDigit(e) {
410
- return e >= "0" && e <= "9";
424
+ isDigit(t) {
425
+ return t >= "0" && t <= "9";
411
426
  }
412
- isIdentifierStart(e) {
413
- return e >= "a" && e <= "z" || e >= "A" && e <= "Z" || e === "_" || e === "@";
427
+ isIdentifierStart(t) {
428
+ return t >= "a" && t <= "z" || t >= "A" && t <= "Z" || t === "_" || t === "@";
414
429
  }
415
430
  /**
416
431
  * Check if char can continue an identifier (NOT including @ which is only valid at start)
417
432
  */
418
- isIdentifierChar(e) {
419
- return e >= "a" && e <= "z" || e >= "A" && e <= "Z" || e === "_" || this.isDigit(e) || e === "." || // For nested field access
420
- e === "-";
433
+ isIdentifierChar(t) {
434
+ return t >= "a" && t <= "z" || t >= "A" && t <= "Z" || t === "_" || this.isDigit(t) || t === "." || // For nested field access
435
+ t === "-";
421
436
  }
422
- isOperatorChar(e) {
423
- return e === "=" || e === "!" || e === ">" || e === "<";
437
+ isOperatorChar(t) {
438
+ return t === "=" || t === "!" || t === ">" || t === "<";
424
439
  }
425
440
  }
426
- function O(o) {
427
- return new w(o).tokenize();
441
+ function A(o) {
442
+ return new O(o).tokenize();
428
443
  }
429
- function v(o) {
430
- return O(o).filter((e) => e.type !== "whitespace");
444
+ function R(o) {
445
+ return A(o).filter((t) => t.type !== "whitespace");
431
446
  }
432
- function E(o, e) {
447
+ function N(o, t) {
433
448
  for (const n of o)
434
- if (e >= n.start && e < n.end)
449
+ if (t >= n.start && t < n.end)
435
450
  return n;
436
451
  return null;
437
452
  }
438
- function S(o, e, n) {
439
- return o.filter((i) => i.start < n && i.end > e);
453
+ function y(o, t, n) {
454
+ return o.filter((i) => i.start < n && i.end > t);
440
455
  }
441
- function I(o, e) {
456
+ function x(o, t) {
442
457
  let n = null;
443
458
  for (const i of o)
444
- if (i.end <= e)
459
+ if (i.end <= t)
445
460
  n = i;
446
461
  else
447
462
  break;
448
463
  return n;
449
464
  }
450
- function N(o, e, n = !0) {
465
+ function D(o, t, n = !0) {
451
466
  const i = n ? o.filter((s) => s.type !== "whitespace") : o;
452
- let t = null;
467
+ let e = null;
453
468
  for (const s of i)
454
- if (s.end <= e)
455
- t = s;
469
+ if (s.end <= t)
470
+ e = s;
456
471
  else
457
472
  break;
458
- return t;
473
+ return e;
459
474
  }
460
475
  export {
461
476
  f as AGGREGATION_FUNCTIONS,
462
477
  _ as BOOLEAN_PREDICATE_MUTATORS,
463
- A as BOOLEAN_PREDICATE_MUTATOR_SET,
478
+ E as BOOLEAN_PREDICATE_MUTATOR_SET,
464
479
  d as COLLECTION_OPERATORS,
465
480
  l as COMPARISON_OPERATORS,
466
- g as KEYWORDS,
481
+ I as DATE_HISTOGRAM_CALENDAR_INTERVALS,
482
+ S as DATE_HISTOGRAM_FIXED_UNITS,
483
+ v as GEO_GRID_PRECISION,
484
+ g as GROUP_BY_BUCKET_FUNCTIONS,
485
+ w as KEYWORDS,
467
486
  p as LOGICAL_OPERATORS,
468
487
  k as MUTATORS,
469
488
  u as NEGATION_SYMBOL_OPERATORS,
470
489
  b as SYMBOL_LOGICAL_OPERATORS,
471
490
  h as SYMBOL_OPERATORS,
472
- m as TYPE_HINTS,
473
- w as TqlTokenizer,
474
- T as VISUALIZATION_TYPES,
475
- N as findPreviousToken,
476
- E as findTokenAtPosition,
477
- I as findTokenBefore,
478
- S as findTokensInRange,
479
- O as tokenizeTql,
480
- v as tokenizeTqlNoWhitespace
491
+ T as TYPE_HINTS,
492
+ O as TqlTokenizer,
493
+ m as VISUALIZATION_TYPES,
494
+ D as findPreviousToken,
495
+ N as findTokenAtPosition,
496
+ x as findTokenBefore,
497
+ y as findTokensInRange,
498
+ A as tokenizeTql,
499
+ R as tokenizeTqlNoWhitespace
481
500
  };
@@ -4,7 +4,7 @@
4
4
  * Comprehensive type system for TQL parsing, validation, and autocomplete.
5
5
  * Synchronized with tellaro-query-language Python/Rust implementations.
6
6
  */
7
- export type TqlTokenType = "field" | "operator" | "logicalOp" | "collectionOp" | "string" | "number" | "boolean" | "null" | "paren" | "bracket" | "comma" | "pipe" | "arrow" | "doubleColon" | "mutator" | "aggregation" | "keyword" | "typeHint" | "visualization" | "whitespace" | "unknown" | "error";
7
+ export type TqlTokenType = "field" | "operator" | "logicalOp" | "collectionOp" | "string" | "number" | "boolean" | "null" | "paren" | "bracket" | "comma" | "pipe" | "arrow" | "doubleColon" | "mutator" | "aggregation" | "bucketFunction" | "keyword" | "typeHint" | "visualization" | "whitespace" | "unknown" | "error";
8
8
  export interface TqlToken {
9
9
  type: TqlTokenType;
10
10
  value: string;
@@ -79,10 +79,114 @@ export interface TqlAggregationExpression {
79
79
  start: number;
80
80
  end: number;
81
81
  }
82
+ /**
83
+ * A function-style group-by bucket level, as both engines' ASTs carry it in `bucket`.
84
+ *
85
+ * `| stats count() by date_histogram(@timestamp, 1h)` → `{ type: "date_histogram",
86
+ * interval: "1h", interval_kind: "fixed" }`. The same object is echoed in a grouped
87
+ * result's `group_by[i].bucket`.
88
+ */
89
+ export type TqlGroupByBucket = {
90
+ type: "date_histogram";
91
+ /** `"1h"` (fixed), `"day"` (calendar) or `"auto"` */
92
+ interval: string;
93
+ interval_kind: "fixed" | "calendar" | "auto";
94
+ /** The auto bucket target (`auto(N)`, default 50); only for `interval_kind: "auto"` */
95
+ buckets?: number;
96
+ } | {
97
+ /** Web-Mercator tiles; row keys are `"z/x/y"` */
98
+ type: "geotile";
99
+ precision: number;
100
+ } | {
101
+ /** Geohash cells; row keys are the geohash */
102
+ type: "geohash";
103
+ precision: number;
104
+ } | {
105
+ /** `range(f, 0, 1024)` → `*-0`, `0-1024`, `1024-*`; each bucket is `from <= v < to` */
106
+ type: "range";
107
+ ranges: Array<{
108
+ key: string;
109
+ from?: number;
110
+ to?: number;
111
+ }>;
112
+ } | {
113
+ /**
114
+ * `filters(<filter> as 'label', ...)`. Result metadata carries the labels (in written
115
+ * order); the engines' ASTs carry `filters: [{ label, filter }]` instead.
116
+ */
117
+ type: "filters";
118
+ labels?: string[];
119
+ filters?: Array<{
120
+ label: string;
121
+ filter: unknown;
122
+ }>;
123
+ };
124
+ /** A latitude/longitude pair in degrees. */
125
+ export interface TqlLatLon {
126
+ lat: number;
127
+ lon: number;
128
+ }
129
+ /** One group-by level of a stats expression. */
130
+ export interface TqlGroupByLevel {
131
+ /** The bucketed field */
132
+ field: TqlFieldReference;
133
+ /** Present for a bucket function, absent for a plain terms level */
134
+ bucket?: TqlGroupByBucket;
135
+ /** `by f top N` */
136
+ bucketSize?: number;
137
+ /** `by f top N with other` */
138
+ withOther?: boolean;
139
+ }
140
+ /**
141
+ * One entry of a grouped stats result's `group_by` list (Python and Rust, in memory and
142
+ * from OpenSearch). `key` names the property each row's `key` object stores this level's
143
+ * value under — usually the field, `date_histogram(@timestamp)` when two levels share a field.
144
+ */
145
+ export type TqlGroupByMetadata = {
146
+ field: string;
147
+ key: string;
148
+ bucket_size: number | null;
149
+ with_other: boolean;
150
+ bucket?: undefined;
151
+ } | {
152
+ /** null for `filters(...)`, which buckets no single field (its key is `filters`) */
153
+ field: string | null;
154
+ key: string;
155
+ bucket: TqlGroupByBucket;
156
+ };
157
+ /** Per-level bucket metadata a row carries in `bucket_meta[<level key>]`. */
158
+ export interface TqlRowBucketMeta {
159
+ /** date_histogram: bucket start, epoch milliseconds (UTC) */
160
+ epoch_ms?: number;
161
+ /** date_histogram with an automatic interval: the interval chosen, e.g. `"1h"` */
162
+ interval?: string;
163
+ /** geotile/geohash: cell centre latitude */
164
+ lat?: number;
165
+ /** geotile/geohash: cell centre longitude */
166
+ lon?: number;
167
+ /** geotile/geohash: north-west corner */
168
+ top_left?: TqlLatLon;
169
+ /** geotile/geohash: south-east corner */
170
+ bottom_right?: TqlLatLon;
171
+ /** range: inclusive lower bound (absent for the first bucket) */
172
+ from?: number;
173
+ /** range: exclusive upper bound (absent for the last bucket) */
174
+ to?: number;
175
+ }
176
+ /** One row of a grouped stats result. Time keys are ISO 8601 (`2026-10-14T10:00:00.000Z`). */
177
+ export interface TqlStatsRow {
178
+ key: Record<string, unknown>;
179
+ doc_count: number;
180
+ bucket_meta?: Record<string, TqlRowBucketMeta>;
181
+ aggregations?: Record<string, unknown>;
182
+ /** `with other`: this row holds what the last level's limit dropped; its key there is "Other" */
183
+ other?: boolean;
184
+ [metric: string]: unknown;
185
+ }
82
186
  export interface TqlStatsExpression {
83
187
  type: "stats";
84
188
  aggregations: TqlAggregationExpression[];
85
- groupBy?: TqlFieldReference[];
189
+ groupBy?: TqlGroupByLevel[];
86
190
  topN?: number;
87
191
  bottomN?: number;
88
192
  visualization?: TqlVisualizationType;
@@ -224,7 +328,14 @@ export type TqlErrorCode = "UNEXPECTED_TOKEN" | "UNEXPECTED_END" | "UNBALANCED_P
224
328
  * form on `count(*)` / `count()` (`count(*, top 2)`). A spelling only one engine refuses is
225
329
  * an ENGINE_DIVERGENCE warning instead — never this.
226
330
  */
227
- | "INVALID_STATS_MODIFIER";
331
+ | "INVALID_STATS_MODIFIER"
332
+ /**
333
+ * A group-by bucket function with arguments both engines refuse at parse time: a bad
334
+ * `date_histogram` interval or `auto(N)` target, a geo precision out of range, unsorted or
335
+ * malformed `range` boundaries, a duplicate `filters` label. Mirrors
336
+ * `cross_language_tests/fixtures/test_cases/validation/stats_bucket_errors.json`.
337
+ */
338
+ | "INVALID_STATS_BUCKET";
228
339
  export type TqlWarningCode = "UNKNOWN_FIELD" | "TYPE_MISMATCH" | "DEPRECATED_OPERATOR" | "REDUNDANT_PARENS"
229
340
  /**
230
341
  * The query parses, but only ONE of the two engines accepts it.