@tellaro/tql 0.1.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.
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @tellaro/tql
3
+ *
4
+ * Tellaro Query Language (TQL) parser, validator, and autocomplete for JavaScript/TypeScript.
5
+ * Synchronized with tellaro-query-language Python/Rust implementations.
6
+ *
7
+ * @packageDocumentation
8
+ */
9
+ export type { TqlToken, TqlTokenType, TqlExpression, TqlBinaryExpression, TqlUnaryExpression, TqlComparisonExpression, TqlCollectionExpression, TqlMutatorExpression, TqlAggregationExpression, TqlStatsExpression, TqlGroupExpression, TqlLiteral, TqlFieldReference, TqlComparisonOperator, TqlLogicalOperator, TqlCollectionOperator, TqlMutator, TqlAggregationFunction, TqlTypeHint, TqlVisualizationType, TqlParseResult, TqlParseError, TqlParseWarning, TqlErrorCode, TqlWarningCode, TqlValidationResult, TqlValidationOptions, TqlFieldType, TqlCompletionItem, TqlCompletionKind, TqlCompletionContext, TqlCompletionOptions, TqlFieldSchema, TqlSnippet, TqlInputProps, TqlInputEmits, TqlInputExpose } from './types';
10
+ export { TqlTokenizer, tokenizeTql, tokenizeTqlNoWhitespace, findTokenAtPosition, findTokensInRange, findTokenBefore, findPreviousToken, COMPARISON_OPERATORS, SYMBOL_OPERATORS, NEGATION_SYMBOL_OPERATORS, LOGICAL_OPERATORS, COLLECTION_OPERATORS, MUTATORS, AGGREGATION_FUNCTIONS, TYPE_HINTS, VISUALIZATION_TYPES, KEYWORDS } from './tokenizer';
11
+ export { TqlParser, parseTql, isValidTql, getTqlErrors, formatParseError } from './parser';
12
+ export { TqlValidator, validateTql, isValidTqlSyntax, getTqlIssues, formatValidationResult } from './validator';
13
+ export { getCompletions, getWordAtPosition, getCompletionTriggerCharacters, shouldTriggerCompletion } from './completionProvider';
@@ -0,0 +1,81 @@
1
+ import { TqlParseError, TqlParseResult } from './types';
2
+
3
+ export declare class TqlParser {
4
+ private tokens;
5
+ private allTokens;
6
+ private current;
7
+ private errors;
8
+ private warnings;
9
+ private depth;
10
+ constructor(input: string);
11
+ /**
12
+ * Parse the input and return the result
13
+ */
14
+ parse(): TqlParseResult;
15
+ /**
16
+ * Parse a full expression (lowest precedence)
17
+ */
18
+ private parseExpression;
19
+ /**
20
+ * Parse OR expressions
21
+ */
22
+ private parseOr;
23
+ /**
24
+ * Parse AND expressions
25
+ */
26
+ private parseAnd;
27
+ /**
28
+ * Parse NOT expressions
29
+ */
30
+ private parseUnary;
31
+ /**
32
+ * Parse primary expressions (comparisons, groups, literals)
33
+ */
34
+ private parsePrimary;
35
+ /**
36
+ * Parse a grouped expression
37
+ */
38
+ private parseGroup;
39
+ /**
40
+ * Parse a comparison expression (field [::type] [| mutator]* operator value)
41
+ * Supports:
42
+ * - Optional type hint: field::string eq "value"
43
+ * - Optional mutator chain: field | lowercase | trim eq "value"
44
+ */
45
+ private parseComparison;
46
+ /**
47
+ * Parse a value (string, number, boolean, null)
48
+ */
49
+ private parseValue;
50
+ /**
51
+ * Parse a list of values for 'in' operator
52
+ */
53
+ private parseValueList;
54
+ private createFieldReference;
55
+ private normalizeOperator;
56
+ private isAtEnd;
57
+ private peek;
58
+ private previous;
59
+ private advance;
60
+ private check;
61
+ private match;
62
+ private matchLogical;
63
+ private addError;
64
+ private addWarning;
65
+ }
66
+ /**
67
+ * Parse a TQL query string
68
+ */
69
+ export declare function parseTql(query: string): TqlParseResult;
70
+ /**
71
+ * Check if a TQL query is syntactically valid
72
+ */
73
+ export declare function isValidTql(query: string): boolean;
74
+ /**
75
+ * Get parse errors for a TQL query
76
+ */
77
+ export declare function getTqlErrors(query: string): TqlParseError[];
78
+ /**
79
+ * Format a parse error for display
80
+ */
81
+ export declare function formatParseError(error: TqlParseError): string;
@@ -0,0 +1,511 @@
1
+ var c = Object.defineProperty;
2
+ var p = (r, e, t) => e in r ? c(r, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : r[e] = t;
3
+ var o = (r, e, t) => p(r, typeof e != "symbol" ? e + "" : e, t);
4
+ import { tokenizeTql as d, COMPARISON_OPERATORS as v } from "./tokenizer.js";
5
+ const k = 50;
6
+ class f {
7
+ constructor(e) {
8
+ o(this, "tokens");
9
+ o(this, "allTokens");
10
+ // Including whitespace for highlighting
11
+ o(this, "current");
12
+ o(this, "errors");
13
+ o(this, "warnings");
14
+ o(this, "depth");
15
+ this.allTokens = d(e), this.tokens = this.allTokens.filter((t) => t.type !== "whitespace"), this.current = 0, this.errors = [], this.warnings = [], this.depth = 0;
16
+ }
17
+ /**
18
+ * Parse the input and return the result
19
+ */
20
+ parse() {
21
+ if (this.current = 0, this.errors = [], this.warnings = [], this.depth = 0, this.tokens.length === 0)
22
+ return {
23
+ ast: null,
24
+ tokens: this.allTokens,
25
+ errors: [],
26
+ warnings: [],
27
+ success: !0
28
+ };
29
+ try {
30
+ const e = this.parseExpression();
31
+ if (!this.isAtEnd()) {
32
+ const t = this.peek();
33
+ this.addError(
34
+ `Unexpected token '${t.value}'`,
35
+ t,
36
+ "UNEXPECTED_TOKEN",
37
+ "Check for missing operators or extra characters"
38
+ );
39
+ }
40
+ return {
41
+ ast: e,
42
+ tokens: this.allTokens,
43
+ errors: this.errors,
44
+ warnings: this.warnings,
45
+ success: this.errors.length === 0
46
+ };
47
+ } catch {
48
+ return {
49
+ ast: null,
50
+ tokens: this.allTokens,
51
+ errors: this.errors,
52
+ warnings: this.warnings,
53
+ success: !1
54
+ };
55
+ }
56
+ }
57
+ // ============================================================================
58
+ // Expression Parsing (Precedence: OR < AND < NOT < Comparison)
59
+ // ============================================================================
60
+ /**
61
+ * Parse a full expression (lowest precedence)
62
+ */
63
+ parseExpression() {
64
+ if (this.depth++, this.depth > k)
65
+ throw new Error("Maximum query depth exceeded");
66
+ try {
67
+ return this.parseOr();
68
+ } finally {
69
+ this.depth--;
70
+ }
71
+ }
72
+ /**
73
+ * Parse OR expressions
74
+ */
75
+ parseOr() {
76
+ let e = this.parseAnd();
77
+ for (; this.matchLogical("OR"); ) {
78
+ const t = this.parseAnd();
79
+ e = {
80
+ type: "binary",
81
+ operator: "OR",
82
+ left: e,
83
+ right: t,
84
+ start: e.start,
85
+ end: t.end
86
+ };
87
+ }
88
+ return e;
89
+ }
90
+ /**
91
+ * Parse AND expressions
92
+ */
93
+ parseAnd() {
94
+ let e = this.parseUnary();
95
+ for (; this.matchLogical("AND"); ) {
96
+ const t = this.parseUnary();
97
+ e = {
98
+ type: "binary",
99
+ operator: "AND",
100
+ left: e,
101
+ right: t,
102
+ start: e.start,
103
+ end: t.end
104
+ };
105
+ }
106
+ return e;
107
+ }
108
+ /**
109
+ * Parse NOT expressions
110
+ */
111
+ parseUnary() {
112
+ if (this.matchLogical("NOT")) {
113
+ const e = this.previous(), t = this.parseUnary();
114
+ return {
115
+ type: "unary",
116
+ operator: "NOT",
117
+ operand: t,
118
+ start: e.start,
119
+ end: t.end
120
+ };
121
+ }
122
+ return this.parsePrimary();
123
+ }
124
+ /**
125
+ * Parse primary expressions (comparisons, groups, literals)
126
+ */
127
+ parsePrimary() {
128
+ if (this.check("paren") && this.peek().value === "(")
129
+ return this.parseGroup();
130
+ if (this.check("field") || this.check("aggregation") || this.check("mutator") || this.check("typeHint"))
131
+ return this.parseComparison();
132
+ const e = this.peek();
133
+ if (this.addError(
134
+ `Expected field name or '(' but found '${e.value}'`,
135
+ e,
136
+ "UNEXPECTED_TOKEN",
137
+ "Start with a field name like 'hostname' or 'status'"
138
+ ), this.advance(), !this.isAtEnd())
139
+ return this.parsePrimary();
140
+ throw new Error("Parser recovery failed");
141
+ }
142
+ /**
143
+ * Parse a grouped expression
144
+ */
145
+ parseGroup() {
146
+ const e = this.advance(), t = this.parseExpression();
147
+ return !this.check("paren") || this.peek().value !== ")" ? this.addError(
148
+ "Missing closing parenthesis ')'",
149
+ this.isAtEnd() ? this.previous() : this.peek(),
150
+ "UNBALANCED_PARENS",
151
+ "Add a closing ')' to match the opening '('"
152
+ ) : this.advance(), {
153
+ type: "group",
154
+ expression: t,
155
+ start: e.start,
156
+ end: this.previous().end
157
+ };
158
+ }
159
+ /**
160
+ * Parse a comparison expression (field [::type] [| mutator]* operator value)
161
+ * Supports:
162
+ * - Optional type hint: field::string eq "value"
163
+ * - Optional mutator chain: field | lowercase | trim eq "value"
164
+ */
165
+ parseComparison() {
166
+ const e = this.advance();
167
+ let t = this.createFieldReference(e);
168
+ if (this.check("doubleColon"))
169
+ if (this.advance(), this.check("typeHint") || this.check("field")) {
170
+ const s = this.advance();
171
+ t = {
172
+ ...t,
173
+ typeHint: s.value.toLowerCase(),
174
+ end: s.end
175
+ };
176
+ } else {
177
+ const s = this.isAtEnd() ? this.previous() : this.peek();
178
+ this.addWarning(
179
+ "Expected type hint after '::'",
180
+ s,
181
+ "Valid types: string, int, float, date, bool, array"
182
+ );
183
+ }
184
+ const i = [];
185
+ for (; this.check("pipe"); )
186
+ if (this.advance(), this.check("mutator")) {
187
+ const s = this.advance();
188
+ i.push(s.value.toLowerCase());
189
+ } else if (this.check("field")) {
190
+ const s = this.advance();
191
+ i.push(s.value.toLowerCase()), this.addWarning(`Unknown mutator '${s.value}'`, s, "Check the mutator name spelling");
192
+ } else {
193
+ const s = this.isAtEnd() ? this.previous() : this.peek();
194
+ this.addError(
195
+ "Expected mutator name after '|'",
196
+ s,
197
+ "MISSING_MUTATOR",
198
+ "Add a mutator like 'lowercase', 'uppercase', 'trim', etc."
199
+ );
200
+ break;
201
+ }
202
+ if (i.length > 0 && (t = {
203
+ ...t,
204
+ mutators: i,
205
+ end: this.previous().end
206
+ }), !this.check("operator")) {
207
+ const s = this.isAtEnd() ? this.previous() : this.peek(), h = i.length > 0 ? `'${e.value}' with mutator` : `'${e.value}'`;
208
+ return this.addError(
209
+ `Expected operator after field ${h}`,
210
+ s,
211
+ "MISSING_OPERATOR",
212
+ "Add an operator like 'eq', 'contains', '==', etc."
213
+ ), {
214
+ type: "comparison",
215
+ field: t,
216
+ operator: "exists",
217
+ value: {
218
+ type: "literal",
219
+ valueType: "boolean",
220
+ value: !0,
221
+ start: t.end,
222
+ end: t.end
223
+ },
224
+ start: t.start,
225
+ end: t.end
226
+ };
227
+ }
228
+ const n = this.advance(), a = this.normalizeOperator(n.value);
229
+ if (a === "exists" || a === "not_exists")
230
+ return {
231
+ type: "comparison",
232
+ field: t,
233
+ operator: a,
234
+ value: {
235
+ type: "literal",
236
+ valueType: "boolean",
237
+ value: !0,
238
+ start: n.end,
239
+ end: n.end
240
+ },
241
+ start: t.start,
242
+ end: n.end
243
+ };
244
+ if ((a === "is" || a === "is_not") && this.check("null")) {
245
+ const s = this.advance();
246
+ return {
247
+ type: "comparison",
248
+ field: t,
249
+ operator: a,
250
+ value: {
251
+ type: "literal",
252
+ valueType: "null",
253
+ value: null,
254
+ start: s.start,
255
+ end: s.end
256
+ },
257
+ start: t.start,
258
+ end: s.end
259
+ };
260
+ }
261
+ if (a === "in" || a === "not_in") {
262
+ const s = this.parseValueList();
263
+ return {
264
+ type: "comparison",
265
+ field: t,
266
+ operator: a,
267
+ value: s,
268
+ start: t.start,
269
+ end: this.previous().end
270
+ };
271
+ }
272
+ if (a === "between" || a === "not_between") {
273
+ const s = this.parseValue();
274
+ this.matchLogical("AND") || this.addError(
275
+ "Expected 'AND' between values in 'between' operator",
276
+ this.isAtEnd() ? this.previous() : this.peek(),
277
+ "INVALID_SYNTAX",
278
+ "Use format: field between value1 AND value2"
279
+ );
280
+ const h = this.parseValue();
281
+ return {
282
+ type: "comparison",
283
+ field: t,
284
+ operator: a,
285
+ value: [s, h],
286
+ start: t.start,
287
+ end: h.end
288
+ };
289
+ }
290
+ if (this.isAtEnd())
291
+ return this.addError(
292
+ `Expected a value after operator '${a}'`,
293
+ n,
294
+ "MISSING_VALUE",
295
+ 'Add a value like "text", 123, or true'
296
+ ), {
297
+ type: "comparison",
298
+ field: t,
299
+ operator: a,
300
+ value: {
301
+ type: "literal",
302
+ valueType: "string",
303
+ value: "",
304
+ start: n.end,
305
+ end: n.end
306
+ },
307
+ start: t.start,
308
+ end: n.end
309
+ };
310
+ const l = this.parseValue();
311
+ return {
312
+ type: "comparison",
313
+ field: t,
314
+ operator: a,
315
+ value: l,
316
+ start: t.start,
317
+ end: l.end
318
+ };
319
+ }
320
+ /**
321
+ * Parse a value (string, number, boolean, null)
322
+ */
323
+ parseValue() {
324
+ if (this.isAtEnd()) {
325
+ const t = this.previous();
326
+ return this.addError(
327
+ "Expected a value",
328
+ t,
329
+ "MISSING_VALUE",
330
+ 'Add a value like "text", 123, or true'
331
+ ), {
332
+ type: "literal",
333
+ valueType: "string",
334
+ value: "",
335
+ start: t.end,
336
+ end: t.end
337
+ };
338
+ }
339
+ const e = this.peek();
340
+ return this.check("string") ? (this.advance(), {
341
+ type: "literal",
342
+ valueType: "string",
343
+ value: e.value,
344
+ start: e.start,
345
+ end: e.end
346
+ }) : this.check("number") ? (this.advance(), {
347
+ type: "literal",
348
+ valueType: "number",
349
+ value: e.value.includes(".") ? parseFloat(e.value) : parseInt(e.value, 10),
350
+ start: e.start,
351
+ end: e.end
352
+ }) : this.check("boolean") ? (this.advance(), {
353
+ type: "literal",
354
+ valueType: "boolean",
355
+ value: e.value.toLowerCase() === "true",
356
+ start: e.start,
357
+ end: e.end
358
+ }) : this.check("null") ? (this.advance(), {
359
+ type: "literal",
360
+ valueType: "null",
361
+ value: null,
362
+ start: e.start,
363
+ end: e.end
364
+ }) : this.check("field") ? (this.advance(), this.addWarning(`Unquoted value '${e.value}' - consider using quotes`, e, "Consider wrapping string values in quotes"), {
365
+ type: "literal",
366
+ valueType: "string",
367
+ value: e.value,
368
+ start: e.start,
369
+ end: e.end
370
+ }) : (this.addError(
371
+ "Expected a value (string, number, or boolean)",
372
+ e,
373
+ "MISSING_VALUE",
374
+ 'Add a value like "text", 123, or true'
375
+ ), {
376
+ type: "literal",
377
+ valueType: "string",
378
+ value: "",
379
+ start: e.start,
380
+ end: e.start
381
+ });
382
+ }
383
+ /**
384
+ * Parse a list of values for 'in' operator
385
+ */
386
+ parseValueList() {
387
+ const e = [];
388
+ this.check("bracket") && this.peek().value === "[" ? this.advance() : this.check("paren") && this.peek().value === "(" && this.advance();
389
+ do {
390
+ if (this.check("bracket") && this.peek().value === "]" || this.check("paren") && this.peek().value === ")") break;
391
+ e.push(this.parseValue());
392
+ } while (this.match("comma"));
393
+ return this.check("bracket") && this.peek().value === "]" ? this.advance() : this.check("paren") && this.peek().value === ")" && this.advance(), e;
394
+ }
395
+ // ============================================================================
396
+ // Helper Methods
397
+ // ============================================================================
398
+ createFieldReference(e) {
399
+ return {
400
+ type: "field",
401
+ name: e.value,
402
+ path: e.value.split("."),
403
+ start: e.start,
404
+ end: e.end
405
+ };
406
+ }
407
+ normalizeOperator(e) {
408
+ const t = e.toLowerCase(), i = {
409
+ "==": "eq",
410
+ "=": "eq",
411
+ "!=": "ne",
412
+ ">": "gt",
413
+ ">=": "gte",
414
+ "<": "lt",
415
+ "<=": "lte"
416
+ };
417
+ if (e in i)
418
+ return i[e];
419
+ const n = {
420
+ "!in": "not_in",
421
+ "!contains": "not_contains",
422
+ "!startswith": "not_startswith",
423
+ "!endswith": "not_endswith",
424
+ "!regexp": "not_regexp",
425
+ "!matches": "not_matches",
426
+ "!between": "not_between",
427
+ "!exists": "not_exists",
428
+ "!is": "is_not",
429
+ "!cidr": "not_cidr",
430
+ "!any": "not_any",
431
+ "!all": "not_all",
432
+ "!none": "not_none"
433
+ };
434
+ if (t in n)
435
+ return n[t];
436
+ const a = {
437
+ regex: "regexp",
438
+ starts_with: "startswith",
439
+ ends_with: "endswith"
440
+ };
441
+ return t in a ? a[t] : (v.includes(t), t);
442
+ }
443
+ isAtEnd() {
444
+ return this.current >= this.tokens.length;
445
+ }
446
+ peek() {
447
+ return this.tokens[this.current];
448
+ }
449
+ previous() {
450
+ return this.tokens[this.current - 1];
451
+ }
452
+ advance() {
453
+ return this.isAtEnd() || this.current++, this.previous();
454
+ }
455
+ check(e) {
456
+ return this.isAtEnd() ? !1 : this.peek().type === e;
457
+ }
458
+ match(...e) {
459
+ for (const t of e)
460
+ if (this.check(t))
461
+ return this.advance(), !0;
462
+ return !1;
463
+ }
464
+ matchLogical(e) {
465
+ return this.check("logicalOp") && this.peek().value.toUpperCase() === e ? (this.advance(), !0) : !1;
466
+ }
467
+ addError(e, t, i, n) {
468
+ this.errors.push({
469
+ message: e,
470
+ start: t.start,
471
+ end: t.end,
472
+ line: t.line,
473
+ column: t.column,
474
+ severity: "error",
475
+ code: i,
476
+ suggestion: n
477
+ });
478
+ }
479
+ addWarning(e, t, i) {
480
+ this.warnings.push({
481
+ message: e,
482
+ start: t.start,
483
+ end: t.end,
484
+ line: t.line,
485
+ column: t.column,
486
+ severity: "warning",
487
+ code: "TYPE_MISMATCH",
488
+ suggestion: i
489
+ });
490
+ }
491
+ }
492
+ function u(r) {
493
+ return new f(r).parse();
494
+ }
495
+ function g(r) {
496
+ return u(r).success;
497
+ }
498
+ function E(r) {
499
+ return u(r).errors;
500
+ }
501
+ function w(r) {
502
+ let e = `Error at line ${r.line}, column ${r.column}: ${r.message}`;
503
+ return r.suggestion && (e += ` (${r.suggestion})`), e;
504
+ }
505
+ export {
506
+ f as TqlParser,
507
+ w as formatParseError,
508
+ E as getTqlErrors,
509
+ g as isValidTql,
510
+ u as parseTql
511
+ };