@tellaro/tql 0.1.0 → 0.1.2

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.
@@ -1,9 +1,9 @@
1
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 {
2
+ var d = (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) => d(r, typeof e != "symbol" ? e + "" : e, t);
4
+ import { tokenizeTql as p, COMPARISON_OPERATORS as v } from "./tokenizer.js";
5
+ const f = 50, k = /* @__PURE__ */ new Set(["logicalOp", "paren"]);
6
+ class y {
7
7
  constructor(e) {
8
8
  o(this, "tokens");
9
9
  o(this, "allTokens");
@@ -12,7 +12,7 @@ class f {
12
12
  o(this, "errors");
13
13
  o(this, "warnings");
14
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;
15
+ this.allTokens = p(e), this.tokens = this.allTokens.filter((t) => t.type !== "whitespace"), this.current = 0, this.errors = [], this.warnings = [], this.depth = 0;
16
16
  }
17
17
  /**
18
18
  * Parse the input and return the result
@@ -61,7 +61,7 @@ class f {
61
61
  * Parse a full expression (lowest precedence)
62
62
  */
63
63
  parseExpression() {
64
- if (this.depth++, this.depth > k)
64
+ if (this.depth++, this.depth > f)
65
65
  throw new Error("Maximum query depth exceeded");
66
66
  try {
67
67
  return this.parseOr();
@@ -75,14 +75,22 @@ class f {
75
75
  parseOr() {
76
76
  let e = this.parseAnd();
77
77
  for (; this.matchLogical("OR"); ) {
78
- const t = this.parseAnd();
78
+ const t = this.previous();
79
+ if (this.isAtEnd())
80
+ return this.addError(
81
+ "Expected expression after 'OR'",
82
+ t,
83
+ "MISSING_OPERAND",
84
+ "Add a condition after 'OR'"
85
+ ), e;
86
+ const n = this.parseAnd();
79
87
  e = {
80
88
  type: "binary",
81
89
  operator: "OR",
82
90
  left: e,
83
- right: t,
91
+ right: n,
84
92
  start: e.start,
85
- end: t.end
93
+ end: n.end
86
94
  };
87
95
  }
88
96
  return e;
@@ -93,14 +101,22 @@ class f {
93
101
  parseAnd() {
94
102
  let e = this.parseUnary();
95
103
  for (; this.matchLogical("AND"); ) {
96
- const t = this.parseUnary();
104
+ const t = this.previous();
105
+ if (this.isAtEnd())
106
+ return this.addError(
107
+ "Expected expression after 'AND'",
108
+ t,
109
+ "MISSING_OPERAND",
110
+ "Add a condition after 'AND'"
111
+ ), e;
112
+ const n = this.parseUnary();
97
113
  e = {
98
114
  type: "binary",
99
115
  operator: "AND",
100
116
  left: e,
101
- right: t,
117
+ right: n,
102
118
  start: e.start,
103
- end: t.end
119
+ end: n.end
104
120
  };
105
121
  }
106
122
  return e;
@@ -130,14 +146,14 @@ class f {
130
146
  if (this.check("field") || this.check("aggregation") || this.check("mutator") || this.check("typeHint"))
131
147
  return this.parseComparison();
132
148
  const e = this.peek();
133
- if (this.addError(
149
+ this.addError(
134
150
  `Expected field name or '(' but found '${e.value}'`,
135
151
  e,
136
152
  "UNEXPECTED_TOKEN",
137
153
  "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");
154
+ );
155
+ const t = this.createPlaceholderExpression(e);
156
+ return this.synchronize(), t;
141
157
  }
142
158
  /**
143
159
  * Parse a grouped expression
@@ -181,14 +197,14 @@ class f {
181
197
  "Valid types: string, int, float, date, bool, array"
182
198
  );
183
199
  }
184
- const i = [];
200
+ const n = [];
185
201
  for (; this.check("pipe"); )
186
202
  if (this.advance(), this.check("mutator")) {
187
203
  const s = this.advance();
188
- i.push(s.value.toLowerCase());
204
+ n.push(s.value.toLowerCase());
189
205
  } else if (this.check("field")) {
190
206
  const s = this.advance();
191
- i.push(s.value.toLowerCase()), this.addWarning(`Unknown mutator '${s.value}'`, s, "Check the mutator name spelling");
207
+ n.push(s.value.toLowerCase()), this.addWarning(`Unknown mutator '${s.value}'`, s, "Check the mutator name spelling");
192
208
  } else {
193
209
  const s = this.isAtEnd() ? this.previous() : this.peek();
194
210
  this.addError(
@@ -199,12 +215,12 @@ class f {
199
215
  );
200
216
  break;
201
217
  }
202
- if (i.length > 0 && (t = {
218
+ if (n.length > 0 && (t = {
203
219
  ...t,
204
- mutators: i,
220
+ mutators: n,
205
221
  end: this.previous().end
206
222
  }), !this.check("operator")) {
207
- const s = this.isAtEnd() ? this.previous() : this.peek(), h = i.length > 0 ? `'${e.value}' with mutator` : `'${e.value}'`;
223
+ const s = this.isAtEnd() ? this.previous() : this.peek(), h = n.length > 0 ? `'${e.value}' with mutator` : `'${e.value}'`;
208
224
  return this.addError(
209
225
  `Expected operator after field ${h}`,
210
226
  s,
@@ -225,7 +241,7 @@ class f {
225
241
  end: t.end
226
242
  };
227
243
  }
228
- const n = this.advance(), a = this.normalizeOperator(n.value);
244
+ const i = this.advance(), a = this.normalizeOperator(i.value);
229
245
  if (a === "exists" || a === "not_exists")
230
246
  return {
231
247
  type: "comparison",
@@ -235,11 +251,11 @@ class f {
235
251
  type: "literal",
236
252
  valueType: "boolean",
237
253
  value: !0,
238
- start: n.end,
239
- end: n.end
254
+ start: i.end,
255
+ end: i.end
240
256
  },
241
257
  start: t.start,
242
- end: n.end
258
+ end: i.end
243
259
  };
244
260
  if ((a === "is" || a === "is_not") && this.check("null")) {
245
261
  const s = this.advance();
@@ -290,7 +306,7 @@ class f {
290
306
  if (this.isAtEnd())
291
307
  return this.addError(
292
308
  `Expected a value after operator '${a}'`,
293
- n,
309
+ i,
294
310
  "MISSING_VALUE",
295
311
  'Add a value like "text", 123, or true'
296
312
  ), {
@@ -301,11 +317,11 @@ class f {
301
317
  type: "literal",
302
318
  valueType: "string",
303
319
  value: "",
304
- start: n.end,
305
- end: n.end
320
+ start: i.end,
321
+ end: i.end
306
322
  },
307
323
  start: t.start,
308
- end: n.end
324
+ end: i.end
309
325
  };
310
326
  const l = this.parseValue();
311
327
  return {
@@ -405,7 +421,7 @@ class f {
405
421
  };
406
422
  }
407
423
  normalizeOperator(e) {
408
- const t = e.toLowerCase(), i = {
424
+ const t = e.toLowerCase(), n = {
409
425
  "==": "eq",
410
426
  "=": "eq",
411
427
  "!=": "ne",
@@ -414,9 +430,9 @@ class f {
414
430
  "<": "lt",
415
431
  "<=": "lte"
416
432
  };
417
- if (e in i)
418
- return i[e];
419
- const n = {
433
+ if (e in n)
434
+ return n[e];
435
+ const i = {
420
436
  "!in": "not_in",
421
437
  "!contains": "not_contains",
422
438
  "!startswith": "not_startswith",
@@ -431,8 +447,8 @@ class f {
431
447
  "!all": "not_all",
432
448
  "!none": "not_none"
433
449
  };
434
- if (t in n)
435
- return n[t];
450
+ if (t in i)
451
+ return i[t];
436
452
  const a = {
437
453
  regex: "regexp",
438
454
  starts_with: "startswith",
@@ -464,7 +480,7 @@ class f {
464
480
  matchLogical(e) {
465
481
  return this.check("logicalOp") && this.peek().value.toUpperCase() === e ? (this.advance(), !0) : !1;
466
482
  }
467
- addError(e, t, i, n) {
483
+ addError(e, t, n, i) {
468
484
  this.errors.push({
469
485
  message: e,
470
486
  start: t.start,
@@ -472,11 +488,11 @@ class f {
472
488
  line: t.line,
473
489
  column: t.column,
474
490
  severity: "error",
475
- code: i,
476
- suggestion: n
491
+ code: n,
492
+ suggestion: i
477
493
  });
478
494
  }
479
- addWarning(e, t, i) {
495
+ addWarning(e, t, n) {
480
496
  this.warnings.push({
481
497
  message: e,
482
498
  start: t.start,
@@ -485,27 +501,64 @@ class f {
485
501
  column: t.column,
486
502
  severity: "warning",
487
503
  code: "TYPE_MISMATCH",
488
- suggestion: i
504
+ suggestion: n
489
505
  });
490
506
  }
507
+ /**
508
+ * Synchronize parser state after an error by advancing to a recovery point.
509
+ * Recovery points are: logical operators (AND, OR, NOT), closing parens, or EOF.
510
+ */
511
+ synchronize() {
512
+ for (this.advance(); !this.isAtEnd(); ) {
513
+ if (k.has(this.peek().type) || this.peek().type === "paren" && this.peek().value === ")")
514
+ return;
515
+ this.advance();
516
+ }
517
+ }
518
+ /**
519
+ * Create a placeholder expression for error recovery.
520
+ * This allows the parser to continue and find additional errors.
521
+ */
522
+ createPlaceholderExpression(e) {
523
+ return {
524
+ type: "comparison",
525
+ field: {
526
+ type: "field",
527
+ name: "_error_",
528
+ path: ["_error_"],
529
+ start: e.start,
530
+ end: e.end
531
+ },
532
+ operator: "exists",
533
+ value: {
534
+ type: "literal",
535
+ valueType: "boolean",
536
+ value: !0,
537
+ start: e.start,
538
+ end: e.end
539
+ },
540
+ start: e.start,
541
+ end: e.end
542
+ };
543
+ }
491
544
  }
492
545
  function u(r) {
493
- return new f(r).parse();
546
+ return new y(r).parse();
494
547
  }
495
548
  function g(r) {
496
549
  return u(r).success;
497
550
  }
498
- function E(r) {
551
+ function A(r) {
499
552
  return u(r).errors;
500
553
  }
501
- function w(r) {
554
+ function T(r) {
502
555
  let e = `Error at line ${r.line}, column ${r.column}: ${r.message}`;
503
556
  return r.suggestion && (e += ` (${r.suggestion})`), e;
504
557
  }
505
558
  export {
506
- f as TqlParser,
507
- w as formatParseError,
508
- E as getTqlErrors,
559
+ y as TqlParser,
560
+ T as formatParseError,
561
+ A as getTqlErrors,
509
562
  g as isValidTql,
510
563
  u as parseTql
511
564
  };
@@ -0,0 +1,75 @@
1
+ import { TqlFieldSchema, TqlFieldType } from './types';
2
+
3
+ /**
4
+ * Cached schema data with O(1) lookups
5
+ */
6
+ export interface CachedSchema {
7
+ /** Original schema array */
8
+ fields: TqlFieldSchema[];
9
+ /** Map of field name to schema for O(1) lookup */
10
+ fieldMap: Map<string, TqlFieldSchema>;
11
+ /** Map of field name to type for O(1) type lookup */
12
+ typeMap: Map<string, TqlFieldType>;
13
+ /** All field names including nested fields */
14
+ allFieldNames: string[];
15
+ /** Hash of the schema for change detection */
16
+ hash: string;
17
+ /** Timestamp when cache was created */
18
+ timestamp: number;
19
+ }
20
+ /**
21
+ * Schema cache manager
22
+ */
23
+ export declare class SchemaCache {
24
+ private cache;
25
+ /**
26
+ * Get cached schema data, rebuilding if schema has changed.
27
+ * Returns null if no schema provided.
28
+ */
29
+ get(schema?: TqlFieldSchema[]): CachedSchema | null;
30
+ /**
31
+ * Invalidate the cache
32
+ */
33
+ invalidate(): void;
34
+ /**
35
+ * Check if cache is valid for a given schema
36
+ */
37
+ isValid(schema?: TqlFieldSchema[]): boolean;
38
+ /**
39
+ * Get field by name with O(1) lookup
40
+ */
41
+ getField(schema: TqlFieldSchema[] | undefined, fieldName: string): TqlFieldSchema | undefined;
42
+ /**
43
+ * Get field type by name with O(1) lookup
44
+ */
45
+ getFieldType(schema: TqlFieldSchema[] | undefined, fieldName: string): TqlFieldType | undefined;
46
+ /**
47
+ * Build the cache from a schema
48
+ */
49
+ private buildCache;
50
+ /**
51
+ * Compute a hash for schema change detection.
52
+ * Uses a simple string-based hash for performance.
53
+ */
54
+ private computeHash;
55
+ }
56
+ /**
57
+ * Get the global schema cache instance
58
+ */
59
+ export declare function getSchemaCache(): SchemaCache;
60
+ /**
61
+ * Create a new schema cache instance (useful for isolated testing)
62
+ */
63
+ export declare function createSchemaCache(): SchemaCache;
64
+ /**
65
+ * Utility: Get field names from schema using cache
66
+ */
67
+ export declare function getCachedFieldNames(schema?: TqlFieldSchema[]): string[];
68
+ /**
69
+ * Utility: Get field type from schema using cache
70
+ */
71
+ export declare function getCachedFieldType(schema: TqlFieldSchema[] | undefined, fieldName: string): TqlFieldType | undefined;
72
+ /**
73
+ * Utility: Get field from schema using cache
74
+ */
75
+ export declare function getCachedField(schema: TqlFieldSchema[] | undefined, fieldName: string): TqlFieldSchema | undefined;
@@ -0,0 +1,104 @@
1
+ var f = Object.defineProperty;
2
+ var m = (s, e, t) => e in s ? f(s, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : s[e] = t;
3
+ var d = (s, e, t) => m(s, typeof e != "symbol" ? e + "" : e, t);
4
+ class p {
5
+ constructor() {
6
+ d(this, "cache", null);
7
+ }
8
+ /**
9
+ * Get cached schema data, rebuilding if schema has changed.
10
+ * Returns null if no schema provided.
11
+ */
12
+ get(e) {
13
+ if (!e || e.length === 0)
14
+ return null;
15
+ const t = this.computeHash(e);
16
+ return this.cache && this.cache.hash === t ? this.cache : (this.cache = this.buildCache(e, t), this.cache);
17
+ }
18
+ /**
19
+ * Invalidate the cache
20
+ */
21
+ invalidate() {
22
+ this.cache = null;
23
+ }
24
+ /**
25
+ * Check if cache is valid for a given schema
26
+ */
27
+ isValid(e) {
28
+ return !e || e.length === 0 ? this.cache === null : this.cache ? this.cache.hash === this.computeHash(e) : !1;
29
+ }
30
+ /**
31
+ * Get field by name with O(1) lookup
32
+ */
33
+ getField(e, t) {
34
+ const n = this.get(e);
35
+ return n == null ? void 0 : n.fieldMap.get(t);
36
+ }
37
+ /**
38
+ * Get field type by name with O(1) lookup
39
+ */
40
+ getFieldType(e, t) {
41
+ const n = this.get(e);
42
+ return n == null ? void 0 : n.typeMap.get(t);
43
+ }
44
+ /**
45
+ * Build the cache from a schema
46
+ */
47
+ buildCache(e, t) {
48
+ const n = /* @__PURE__ */ new Map(), h = /* @__PURE__ */ new Map(), i = [], c = (a, o) => {
49
+ const r = o ? `${o}.${a.name}` : a.name;
50
+ if (n.set(r, a), h.set(r, a.type), i.push(r), a.nested)
51
+ for (const g of a.nested)
52
+ c(g, r);
53
+ };
54
+ for (const a of e)
55
+ c(a);
56
+ return {
57
+ fields: e,
58
+ fieldMap: n,
59
+ typeMap: h,
60
+ allFieldNames: i,
61
+ hash: t,
62
+ timestamp: Date.now()
63
+ };
64
+ }
65
+ /**
66
+ * Compute a hash for schema change detection.
67
+ * Uses a simple string-based hash for performance.
68
+ */
69
+ computeHash(e) {
70
+ const t = (i) => i.map((c) => {
71
+ let a = `${c.name}:${c.type}`;
72
+ return c.nested && (a += `[${t(c.nested)}]`), a;
73
+ }).join(","), n = t(e);
74
+ let h = 5381;
75
+ for (let i = 0; i < n.length; i++)
76
+ h = h * 33 ^ n.charCodeAt(i);
77
+ return (h >>> 0).toString(36);
78
+ }
79
+ }
80
+ let l = null;
81
+ function u() {
82
+ return l || (l = new p()), l;
83
+ }
84
+ function C() {
85
+ return new p();
86
+ }
87
+ function y(s) {
88
+ const e = u().get(s);
89
+ return (e == null ? void 0 : e.allFieldNames) ?? [];
90
+ }
91
+ function M(s, e) {
92
+ return u().getFieldType(s, e);
93
+ }
94
+ function w(s, e) {
95
+ return u().getField(s, e);
96
+ }
97
+ export {
98
+ p as SchemaCache,
99
+ C as createSchemaCache,
100
+ w as getCachedField,
101
+ y as getCachedFieldNames,
102
+ M as getCachedFieldType,
103
+ u as getSchemaCache
104
+ };
@@ -0,0 +1,72 @@
1
+ import { FieldUsage, LogicTreeNode, PreviewResult } from './types';
2
+
3
+ export declare class SemanticPreview {
4
+ /**
5
+ * Generate a complete preview for a TQL query
6
+ */
7
+ generatePreview(query: string): PreviewResult;
8
+ /**
9
+ * Extract field usage information from an expression
10
+ */
11
+ private extractFieldUsage;
12
+ private extractFromBinary;
13
+ private extractFromUnary;
14
+ private extractFromGroup;
15
+ private extractFromComparison;
16
+ /**
17
+ * Get field name and mutators from field reference or mutator expression
18
+ */
19
+ private getFieldInfo;
20
+ /**
21
+ * Determine the match type based on operator and value
22
+ */
23
+ private determineMatchType;
24
+ /**
25
+ * Extract the actual value(s) from literal(s)
26
+ */
27
+ private extractValue;
28
+ /**
29
+ * Build a logic tree from the AST
30
+ */
31
+ private buildLogicTree;
32
+ private buildBinaryNode;
33
+ private buildUnaryNode;
34
+ private buildComparisonNode;
35
+ private buildGroupNode;
36
+ }
37
+ /**
38
+ * Generate a preview for a TQL query
39
+ */
40
+ export declare function generatePreview(query: string): PreviewResult;
41
+ /**
42
+ * Get field usage information from a query
43
+ */
44
+ export declare function getFieldUsage(query: string): FieldUsage[];
45
+ /**
46
+ * Get the logic tree for a query
47
+ */
48
+ export declare function getLogicTree(query: string): LogicTreeNode | null;
49
+ /**
50
+ * Get unique field names used in a query
51
+ */
52
+ export declare function getUniqueFields(query: string): string[];
53
+ /**
54
+ * Get operator summary for a query
55
+ */
56
+ export declare function getOperatorSummary(query: string): Record<string, number>;
57
+ /**
58
+ * Format a logic tree node as a human-readable string
59
+ */
60
+ export declare function formatLogicTreeNode(node: LogicTreeNode): string;
61
+ /**
62
+ * Generate an ASCII tree representation of the logic tree
63
+ */
64
+ export declare function formatLogicTreeAscii(node: LogicTreeNode | null, indent?: string): string;
65
+ /**
66
+ * Flatten a logic tree to just the comparison nodes
67
+ */
68
+ export declare function flattenLogicTree(node: LogicTreeNode | null): LogicTreeNode[];
69
+ /**
70
+ * Get the depth of a logic tree
71
+ */
72
+ export declare function getLogicTreeDepth(node: LogicTreeNode | null): number;