@tellaro/tql 0.1.14 → 0.1.16

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.
@@ -108,68 +108,268 @@ const E = [
108
108
  category: "Stats"
109
109
  }
110
110
  ], U = {
111
- eq: { detail: "Equals", documentation: "Exact match comparison" },
112
- ne: { detail: "Not equals", documentation: "Match any value except this one" },
113
- gt: { detail: "Greater than", documentation: "Numeric/date comparison" },
114
- gte: { detail: "Greater than or equal", documentation: "Numeric/date comparison" },
115
- lt: { detail: "Less than", documentation: "Numeric/date comparison" },
116
- lte: { detail: "Less than or equal", documentation: "Numeric/date comparison" },
117
- contains: { detail: "Contains substring", documentation: "Case-insensitive substring match" },
118
- not_contains: { detail: "Does not contain", documentation: "Negative substring match" },
119
- startswith: { detail: "Starts with", documentation: "Match prefix of field value" },
120
- not_startswith: { detail: "Does not start with", documentation: "Negative prefix match" },
121
- endswith: { detail: "Ends with", documentation: "Match suffix of field value" },
122
- not_endswith: { detail: "Does not end with", documentation: "Negative suffix match" },
123
- regexp: { detail: "Regular expression", documentation: "Match using regex pattern" },
124
- regex: { detail: "Regular expression", documentation: "Alias for regexp" },
125
- not_regexp: { detail: "Not matching regex", documentation: "Negative regex match" },
126
- matches: { detail: "Pattern match", documentation: "Match using pattern" },
127
- not_matches: { detail: "Not matching pattern", documentation: "Negative pattern match" },
128
- between: { detail: "Between range", documentation: "Usage: field between A AND B" },
129
- not_between: { detail: "Not between range", documentation: "Exclude values in range" },
130
- in: { detail: "In list", documentation: 'Usage: field in ["a", "b", "c"]' },
131
- not_in: { detail: "Not in list", documentation: "Exclude values in list" },
132
- exists: { detail: "Field exists", documentation: "Check if field has any value" },
133
- not_exists: { detail: "Field not exists", documentation: "Check if field is missing or null" },
134
- is: { detail: "Is null check", documentation: "Usage: field is null" },
135
- is_not: { detail: "Is not null check", documentation: "Usage: field is_not null" },
136
- cidr: { detail: "CIDR match", documentation: "Match IP addresses in CIDR range (IP fields only)" },
137
- not_cidr: { detail: "Not in CIDR", documentation: "Exclude IPs in CIDR range" },
138
- any: { detail: "Any element matches", documentation: "True if any array element matches" },
139
- all: { detail: "All elements match", documentation: "True if all array elements match" },
140
- none: { detail: "No elements match", documentation: "True if no array elements match" },
141
- not_any: { detail: "Not any matches", documentation: "Negation of any" },
142
- not_all: { detail: "Not all match", documentation: "Negation of all" },
143
- not_none: { detail: "Not none match", documentation: "Negation of none" }
111
+ // Equality operators
112
+ eq: { detail: "Equals", documentation: "Exact match comparison. Case-sensitive for strings.\n\nExamples:\n- status eq 'active'\n- severity eq 3\n- enabled eq true\n\nWorks with: strings, numbers, booleans, dates, IPs" },
113
+ ne: { detail: "Not equals", documentation: "Match any value except the specified one.\n\nExamples:\n- status ne 'error'\n- severity ne 0\n\nUseful for filtering out specific values." },
114
+ // Comparison operators
115
+ gt: { detail: "Greater than", documentation: "Compare if field value is greater than the specified value.\n\nExamples:\n- severity gt 3\n- bytes gt 1000000\n- @timestamp gt '2024-01-01'\n\nWorks with: numbers, dates" },
116
+ gte: { detail: "Greater than or equal", documentation: "Compare if field value is greater than or equal to the specified value.\n\nExamples:\n- risk_score gte 70\n- attempts gte 5\n\nWorks with: numbers, dates" },
117
+ lt: { detail: "Less than", documentation: "Compare if field value is less than the specified value.\n\nExamples:\n- response_time lt 100\n- @timestamp lt 'now-1d'\n\nWorks with: numbers, dates" },
118
+ lte: { detail: "Less than or equal", documentation: "Compare if field value is less than or equal to the specified value.\n\nExamples:\n- confidence lte 50\n- age lte 30\n\nWorks with: numbers, dates" },
119
+ // String operators
120
+ contains: { detail: "Contains substring", documentation: "Check if field contains the specified substring. Case-insensitive.\n\nExamples:\n- message contains 'error'\n- url contains 'admin'\n\nUseful for searching within text fields." },
121
+ not_contains: { detail: "Does not contain", documentation: "Check if field does NOT contain the specified substring.\n\nExamples:\n- message not_contains 'debug'\n- path not_contains 'test'\n\nUseful for excluding noise from results." },
122
+ startswith: { detail: "Starts with", documentation: "Check if field value starts with the specified prefix.\n\nExamples:\n- hostname startswith 'web-'\n- url startswith 'https://'\n\nFaster than contains for prefix matching." },
123
+ not_startswith: { detail: "Does not start with", documentation: "Check if field value does NOT start with the specified prefix.\n\nExamples:\n- hostname not_startswith 'test-'\n- email not_startswith 'noreply'" },
124
+ endswith: { detail: "Ends with", documentation: "Check if field value ends with the specified suffix.\n\nExamples:\n- file.name endswith '.exe'\n- email endswith '@company.com'\n\nUseful for file extension matching." },
125
+ not_endswith: { detail: "Does not end with", documentation: "Check if field value does NOT end with the specified suffix.\n\nExamples:\n- file.name not_endswith '.log'\n- url not_endswith '.jpg'" },
126
+ regexp: { detail: "Regular expression", documentation: "Match field value against a regular expression pattern.\n\nExamples:\n- ip regexp '^192\\\\.168\\\\..*'\n- email regexp '.*@(gmail|yahoo)\\\\.com$'\n\nUses standard regex syntax." },
127
+ regex: { detail: "Regular expression", documentation: "Alias for regexp. Match using regex pattern.\n\nExamples:\n- user.name regex '^admin.*'" },
128
+ not_regexp: { detail: "Not matching regex", documentation: "Match if field value does NOT match the regex.\n\nExamples:\n- file.path not_regexp '.*\\\\.tmp$'\n- message not_regexp 'DEBUG|TRACE'" },
129
+ matches: { detail: "Pattern match", documentation: "Alias for regexp. Match using regex pattern.\n\nExamples:\n- hostname matches 'server[0-9]+'" },
130
+ not_matches: { detail: "Not matching pattern", documentation: "Alias for not_regexp. Does NOT match the pattern." },
131
+ // Range operators
132
+ between: { detail: "Between range", documentation: "Check if field value is within a range (inclusive).\n\nExamples:\n- port between 1024 AND 65535\n- severity between 1 AND 5\n\nIncludes both boundary values." },
133
+ not_between: { detail: "Not between range", documentation: "Check if field value is outside a range.\n\nExamples:\n- port not_between 80 AND 443\n\nExcludes values in the range." },
134
+ // List operators
135
+ in: { detail: "In list", documentation: "Check if field value matches any value in the list.\n\nExamples:\n- status in ['active', 'pending']\n- severity in [3, 4, 5]\n\nMore efficient than multiple OR conditions." },
136
+ not_in: { detail: "Not in list", documentation: "Check if field value does NOT match any value in the list.\n\nExamples:\n- status not_in ['deleted', 'archived']\n\nUseful for exclusion lists." },
137
+ // Existence operators
138
+ exists: { detail: "Field exists", documentation: "Check if field exists and has a non-null value.\n\nExamples:\n- user.email exists\n- threat.indicator exists\n\nReturns false for null values or missing fields." },
139
+ not_exists: { detail: "Field not exists", documentation: "Check if field is missing or has a null value.\n\nExamples:\n- error.code not_exists\n\nTrue when field is missing or explicitly null." },
140
+ is: { detail: "Is null check", documentation: "Check if field value is explicitly null.\n\nExamples:\n- user.phone is null\n\nDifferent from not_exists: returns false for missing fields." },
141
+ is_not: { detail: "Is not null check", documentation: "Check if field exists and is not null.\n\nExamples:\n- user.email is_not null\n\nField must exist and have a non-null value." },
142
+ // Network operators
143
+ cidr: { detail: "CIDR match", documentation: "Match IP addresses within a CIDR range.\n\nExamples:\n- source.ip cidr '192.168.0.0/16'\n- destination.ip cidr '10.0.0.0/8'\n\nWorks with IPv4 and IPv6." },
144
+ not_cidr: { detail: "Not in CIDR", documentation: "Match IP addresses NOT within a CIDR range.\n\nExamples:\n- source.ip not_cidr '192.168.0.0/16'\n\nUseful for identifying external IPs." },
145
+ // Collection operators
146
+ any: { detail: "Any element matches", documentation: "True if ANY element in an array matches the condition.\n\nExamples:\n- tags any eq 'critical'\n- ports any between 1 AND 1024\n\nUseful for searching within array fields." },
147
+ all: { detail: "All elements match", documentation: "True if ALL elements in an array match the condition.\n\nExamples:\n- scores all gte 70\n\nUseful for validation across arrays." },
148
+ none: { detail: "No elements match", documentation: "True if NO elements in an array match the condition.\n\nExamples:\n- tags none eq 'deprecated'\n\nUseful for ensuring no bad values exist." },
149
+ not_any: { detail: "Not any matches", documentation: "Negation of any - equivalent to none.\n\nTrue when no elements match." },
150
+ not_all: { detail: "Not all match", documentation: "Negation of all - at least one element doesn't match.\n\nTrue when at least one element fails." },
151
+ not_none: { detail: "Not none match", documentation: "Negation of none - equivalent to any.\n\nTrue when at least one element matches." }
144
152
  }, z = {
145
- lowercase: { detail: "Convert to lowercase", documentation: "Transform string to lowercase" },
146
- uppercase: { detail: "Convert to uppercase", documentation: "Transform string to uppercase" },
147
- trim: { detail: "Trim whitespace", documentation: "Remove leading/trailing whitespace" },
148
- split: { detail: "Split string", documentation: "Split string into array. Usage: split(delimiter)" },
149
- length: { detail: "Get length", documentation: "Returns string/array length as number" },
150
- replace: { detail: "Replace text", documentation: "Replace substring. Usage: replace(find, replace)" },
151
- b64encode: { detail: "Base64 encode", documentation: "Encode value to Base64" },
152
- b64decode: { detail: "Base64 decode", documentation: "Decode Base64 to string" },
153
- urldecode: { detail: "URL decode", documentation: "Decode URL-encoded string" },
154
- hexencode: { detail: "Hex encode", documentation: "Encode to hexadecimal" },
155
- hexdecode: { detail: "Hex decode", documentation: "Decode from hexadecimal" },
156
- md5: { detail: "MD5 hash", documentation: "Calculate MD5 hash" },
157
- sha256: { detail: "SHA256 hash", documentation: "Calculate SHA256 hash" },
158
- refang: { detail: "Refang IOC", documentation: "Convert defanged URL/IP to normal form" },
159
- defang: { detail: "Defang IOC", documentation: "Convert URL/IP to safe defanged form" },
160
- is_private: { detail: "Is private IP", documentation: "Check if IP is RFC1918/private" },
161
- is_global: { detail: "Is global IP", documentation: "Check if IP is globally routable" },
162
- nslookup: { detail: "DNS lookup", documentation: "Perform DNS lookup on hostname/IP" },
163
- geoip_lookup: { detail: "GeoIP lookup", documentation: "Get geolocation data for IP" },
164
- geoip: { detail: "GeoIP lookup", documentation: "Alias for geoip_lookup" },
165
- geo: { detail: "GeoIP lookup", documentation: "Alias for geoip_lookup with conditions" },
166
- any: { detail: "Any truthy", documentation: "True if any array element is truthy" },
167
- all: { detail: "All truthy", documentation: "True if all array elements are truthy" },
168
- avg: { detail: "Average", documentation: "Calculate average of numeric array" },
169
- average: { detail: "Average", documentation: "Alias for avg" },
170
- sum: { detail: "Sum", documentation: "Sum of numeric array elements" },
171
- min: { detail: "Minimum", documentation: "Minimum value in array" },
172
- max: { detail: "Maximum", documentation: "Maximum value in array" }
153
+ // String mutators
154
+ lowercase: {
155
+ detail: "Convert to lowercase",
156
+ documentation: "Transforms the field value to lowercase.\n\nExample: hostname | lowercase eq 'server01'\n\nUseful for case-insensitive comparisons."
157
+ },
158
+ uppercase: {
159
+ detail: "Convert to uppercase",
160
+ documentation: "Transforms the field value to uppercase.\n\nExample: status | uppercase eq 'ERROR'\n\nUseful for normalizing status codes."
161
+ },
162
+ trim: {
163
+ detail: "Trim whitespace",
164
+ documentation: "Removes leading and trailing whitespace from the field value.\n\nExample: message | trim contains 'error'\n\nUseful for cleaning user input or log messages."
165
+ },
166
+ split: {
167
+ detail: "Split string into array",
168
+ documentation: "Splits a string into an array using a delimiter.\n\nExample: tags | split(delimiter=',') any eq 'critical'\n\nParameters:\n- delimiter: Character(s) to split on (default: ',')\n- field: Field name to save results to"
169
+ },
170
+ length: {
171
+ detail: "Get string/array length",
172
+ documentation: "Returns the length of a string or array as a number.\n\nExample: password | length gte 8\n\nUseful for validation checks."
173
+ },
174
+ replace: {
175
+ detail: "Replace text in string",
176
+ documentation: "Replaces occurrences of a substring with another string.\n\nExample: url | replace(find='http://', replace='https://') startswith 'https'\n\nParameters:\n- find: Text to search for (required)\n- replace: Text to replace with (required)\n- field: Field name to save results to"
177
+ },
178
+ // Encoding mutators
179
+ b64encode: {
180
+ detail: "Base64 encode",
181
+ documentation: "Encodes the field value to Base64.\n\nExample: payload | b64encode eq 'SGVsbG8gV29ybGQ='\n\nParameters:\n- field: Field name to save encoded result"
182
+ },
183
+ b64decode: {
184
+ detail: "Base64 decode",
185
+ documentation: "Decodes a Base64-encoded field value.\n\nExample: encoded_data | b64decode contains 'password'\n\nParameters:\n- field: Field name to save decoded result"
186
+ },
187
+ urldecode: {
188
+ detail: "URL decode",
189
+ documentation: "Decodes URL-encoded (percent-encoded) strings.\n\nExample: query_string | urldecode contains 'SELECT'\n\nUseful for analyzing web requests."
190
+ },
191
+ hexencode: {
192
+ detail: "Hex encode",
193
+ documentation: "Encodes the field value to hexadecimal.\n\nExample: data | hexencode eq '48656c6c6f'\n\nParameters:\n- field: Field name to save encoded result"
194
+ },
195
+ hexdecode: {
196
+ detail: "Hex decode",
197
+ documentation: "Decodes a hexadecimal-encoded field value.\n\nExample: hex_payload | hexdecode contains 'malware'\n\nParameters:\n- field: Field name to save decoded result"
198
+ },
199
+ md5: {
200
+ detail: "MD5 hash",
201
+ documentation: "Calculates the MD5 hash of the field value.\n\nExample: file_content | md5 eq 'd41d8cd98f00b204e9800998ecf8427e'\n\nUseful for file integrity checks."
202
+ },
203
+ sha256: {
204
+ detail: "SHA256 hash",
205
+ documentation: "Calculates the SHA256 hash of the field value.\n\nExample: file_content | sha256 in ['hash1', 'hash2']\n\nUseful for malware signature matching."
206
+ },
207
+ // Security mutators
208
+ refang: {
209
+ detail: "Refang IOC",
210
+ documentation: "Converts defanged indicators back to their original form.\n\nExample: ioc | refang eq '192.168.1.1'\n\nConverts 'hxxp://evil[.]com' to 'http://evil.com'"
211
+ },
212
+ defang: {
213
+ detail: "Defang IOC",
214
+ documentation: "Converts URLs/IPs to a safe defanged form for sharing.\n\nExample: url | defang\n\nConverts 'http://evil.com' to 'hxxp://evil[.]com'"
215
+ },
216
+ // Network mutators
217
+ is_private: {
218
+ detail: "Check if IP is private",
219
+ documentation: "Returns true if the IP address is in a private range (RFC1918).\n\nExample: source.ip | is_private eq true\n\nPrivate ranges: 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16"
220
+ },
221
+ is_global: {
222
+ detail: "Check if IP is globally routable",
223
+ documentation: "Returns true if the IP address is globally routable (not private/reserved).\n\nExample: destination.ip | is_global eq true\n\nUseful for filtering external connections."
224
+ },
225
+ // DNS mutator
226
+ nslookup: {
227
+ detail: "DNS lookup",
228
+ documentation: "Performs DNS lookup on a hostname or reverse lookup on an IP.\n\nExample: hostname | nslookup(types=['A', 'AAAA'])\n\nParameters:\n- servers: DNS servers to query (e.g., ['1.1.1.1'])\n- types: Record types to query (A, AAAA, MX, TXT, etc.)\n- force: Force lookup even if data exists\n- save: Save results to record\n- field: Field name to save results"
229
+ },
230
+ // GeoIP mutators
231
+ geoip_lookup: {
232
+ detail: "GeoIP enrichment",
233
+ documentation: "Enriches IP address with geolocation data (country, city, ASN, etc.).\n\nExample: source.ip | geoip_lookup(force=true)\n\nParameters:\n- db_path: Path to MaxMind database\n- force: Force lookup even if geo data exists\n- save: Save results to record\n- cache: Enable result caching\n- cache_ttl: Cache TTL in seconds"
234
+ },
235
+ geoip: {
236
+ detail: "GeoIP enrichment",
237
+ documentation: "Alias for geoip_lookup. Enriches IP with geolocation data.\n\nExample: destination.ip | geoip"
238
+ },
239
+ geo: {
240
+ detail: "GeoIP enrichment",
241
+ documentation: "Alias for geoip_lookup. Enriches IP with geolocation data.\n\nExample: source.ip | geo(save=true)\n\nAdds: country, city, region, coordinates, ASN info"
242
+ },
243
+ // List mutators
244
+ any: {
245
+ detail: "Any element matches",
246
+ documentation: "Returns true if any element in an array matches the condition.\n\nExample: tags | any eq 'critical'\n\nUseful for checking array fields."
247
+ },
248
+ all: {
249
+ detail: "All elements match",
250
+ documentation: "Returns true if all elements in an array match the condition.\n\nExample: scores | all gte 70\n\nUseful for validation across arrays."
251
+ },
252
+ avg: {
253
+ detail: "Average of array",
254
+ documentation: "Calculates the average of numeric array elements.\n\nExample: response_times | avg lt 100\n\nUseful for performance analysis."
255
+ },
256
+ average: {
257
+ detail: "Average of array",
258
+ documentation: "Alias for avg. Calculates the average of numeric array elements."
259
+ },
260
+ sum: {
261
+ detail: "Sum of array",
262
+ documentation: "Calculates the sum of numeric array elements.\n\nExample: byte_counts | sum gt 1000000\n\nUseful for aggregate analysis."
263
+ },
264
+ min: {
265
+ detail: "Minimum value in array",
266
+ documentation: "Returns the minimum value from a numeric array.\n\nExample: temperatures | min lt 0\n\nUseful for threshold detection."
267
+ },
268
+ max: {
269
+ detail: "Maximum value in array",
270
+ documentation: "Returns the maximum value from a numeric array.\n\nExample: cpu_usage | max gt 90\n\nUseful for peak detection."
271
+ }
272
+ }, aggregationDocs = {
273
+ // Counting aggregations
274
+ count: {
275
+ detail: "Count records",
276
+ documentation: "Counts the number of records or non-null field values.\n\nExamples:\n- | stats count()\n- | stats count(user_id) by status\n\nReturns: Integer count"
277
+ },
278
+ unique_count: {
279
+ detail: "Count unique values",
280
+ documentation: "Counts distinct values in a field.\n\nExamples:\n- | stats unique_count(source.ip)\n- | stats unique_count(user.name) by department\n\nReturns: Integer count of unique values"
281
+ },
282
+ cardinality: {
283
+ detail: "Cardinality (unique count)",
284
+ documentation: "Alias for unique_count. Counts distinct values in a field.\n\nExamples:\n- | stats cardinality(user_id)\n\nReturns: Integer count of unique values"
285
+ },
286
+ // Numeric aggregations
287
+ sum: {
288
+ detail: "Sum of values",
289
+ documentation: "Calculates the sum of numeric field values.\n\nExamples:\n- | stats sum(bytes) by hostname\n- | stats sum(request.size)\n\nReturns: Numeric sum"
290
+ },
291
+ min: {
292
+ detail: "Minimum value",
293
+ documentation: "Returns the minimum value from a numeric or date field.\n\nExamples:\n- | stats min(response_time) by endpoint\n- | stats min(@timestamp)\n\nReturns: Minimum value"
294
+ },
295
+ max: {
296
+ detail: "Maximum value",
297
+ documentation: "Returns the maximum value from a numeric or date field.\n\nExamples:\n- | stats max(cpu_usage) by host\n- | stats max(@timestamp)\n\nReturns: Maximum value"
298
+ },
299
+ avg: {
300
+ detail: "Average value",
301
+ documentation: "Calculates the arithmetic mean of numeric field values.\n\nExamples:\n- | stats avg(response_time) by service\n- | stats avg(score)\n\nReturns: Numeric average"
302
+ },
303
+ average: {
304
+ detail: "Average value",
305
+ documentation: "Alias for avg. Calculates the arithmetic mean.\n\nExamples:\n- | stats average(latency) by region"
306
+ },
307
+ mean: {
308
+ detail: "Mean value",
309
+ documentation: "Alias for avg. Calculates the arithmetic mean.\n\nExamples:\n- | stats mean(temperature) by sensor_id"
310
+ },
311
+ median: {
312
+ detail: "Median value",
313
+ documentation: "Returns the median (50th percentile) of numeric values.\n\nExamples:\n- | stats median(response_time) by endpoint\n\nReturns: Median value (50th percentile)"
314
+ },
315
+ med: {
316
+ detail: "Median value",
317
+ documentation: "Alias for median. Returns the 50th percentile.\n\nExamples:\n- | stats med(latency)"
318
+ },
319
+ std: {
320
+ detail: "Standard deviation",
321
+ documentation: "Calculates the standard deviation of numeric values.\n\nExamples:\n- | stats std(response_time) by service\n\nReturns: Standard deviation"
322
+ },
323
+ standard_deviation: {
324
+ detail: "Standard deviation",
325
+ documentation: "Alias for std. Calculates the standard deviation.\n\nExamples:\n- | stats standard_deviation(score) by category"
326
+ },
327
+ // Percentile functions
328
+ percentile: {
329
+ detail: "Percentile value",
330
+ documentation: "Returns a specific percentile of numeric values.\n\nExamples:\n- | stats percentile(response_time, 95) by endpoint\n- | stats percentile(latency, 99)\n\nParameters: field, percentile (0-100)"
331
+ },
332
+ percentiles: {
333
+ detail: "Multiple percentiles",
334
+ documentation: "Returns multiple percentiles at once.\n\nExamples:\n- | stats percentiles(response_time, [50, 90, 95, 99])\n\nParameters: field, array of percentiles"
335
+ },
336
+ p: {
337
+ detail: "Percentile (shorthand)",
338
+ documentation: "Shorthand for percentile function.\n\nExamples:\n- | stats p(latency, 95) by service"
339
+ },
340
+ pct: {
341
+ detail: "Percentile (shorthand)",
342
+ documentation: "Shorthand for percentile function.\n\nExamples:\n- | stats pct(response_time, 99)"
343
+ },
344
+ percentile_rank: {
345
+ detail: "Percentile rank",
346
+ documentation: "Returns the percentile rank of a specific value.\n\nExamples:\n- | stats percentile_rank(response_time, 100) by endpoint\n\nReturns: What percentile the given value falls into"
347
+ },
348
+ percentile_ranks: {
349
+ detail: "Multiple percentile ranks",
350
+ documentation: "Returns percentile ranks for multiple values.\n\nExamples:\n- | stats percentile_ranks(latency, [50, 100, 200])"
351
+ },
352
+ pct_rank: {
353
+ detail: "Percentile rank (shorthand)",
354
+ documentation: "Shorthand for percentile_rank.\n\nExamples:\n- | stats pct_rank(response_time, 100)"
355
+ },
356
+ pct_ranks: {
357
+ detail: "Percentile ranks (shorthand)",
358
+ documentation: "Shorthand for percentile_ranks.\n\nExamples:\n- | stats pct_ranks(latency, [50, 100])"
359
+ },
360
+ // Unique values
361
+ values: {
362
+ detail: "Unique values list",
363
+ documentation: "Returns a list of unique values for a field.\n\nExamples:\n- | stats values(status) by hostname\n\nReturns: Array of unique values"
364
+ },
365
+ unique: {
366
+ detail: "Unique values list",
367
+ documentation: "Alias for values. Returns a list of unique values.\n\nExamples:\n- | stats unique(user.name) by department"
368
+ },
369
+ distinct: {
370
+ detail: "Distinct values list",
371
+ documentation: "Alias for values. Returns a list of distinct values.\n\nExamples:\n- | stats distinct(source.ip)"
372
+ }
173
373
  }, h = {
174
374
  // String outputs
175
375
  lowercase: "keyword",
@@ -661,23 +861,33 @@ function W(e) {
661
861
  return D.filter((t) => !e || t.toLowerCase().includes(e.toLowerCase())).map(
662
862
  (t, a) => {
663
863
  const i = z[t] || { detail: "Field mutator", documentation: "" };
664
- // Check if this is an enrichment mutator that supports parameters
665
- const isEnrichment = ENRICHMENT_MUTATORS.includes(t);
864
+ // Check if this mutator has parameters defined
666
865
  const hasParams = MUTATOR_PARAMETERS[t] && MUTATOR_PARAMETERS[t].length > 0;
667
- // Enrichment mutators with params get () inserted, cursor inside
668
- // Enrichment mutators without params get () inserted, cursor after
669
- // Regular mutators get no parens
866
+ const isEnrichment = ENRICHMENT_MUTATORS.includes(t);
867
+
868
+ // Build label and insertText
869
+ let label = t;
670
870
  let insertText = t;
671
- if (isEnrichment && hasParams) {
871
+ let detail = i.detail;
872
+
873
+ if (hasParams) {
874
+ // Mutators with parameters show () in label and insert with cursor inside
875
+ label = `${t}()`;
672
876
  insertText = `${t}($1)`;
877
+ // Add param hint to detail
878
+ const paramNames = MUTATOR_PARAMETERS[t].map(p => p.name).join(", ");
879
+ detail = `${i.detail} - params: ${paramNames}`;
673
880
  } else if (isEnrichment) {
881
+ // Enrichment mutators without params still get () for consistency
882
+ label = `${t}()`;
674
883
  insertText = `${t}()`;
675
884
  }
885
+
676
886
  return {
677
- label: t,
887
+ label: label,
678
888
  insertText: insertText,
679
889
  kind: "mutator",
680
- detail: i.detail,
890
+ detail: detail,
681
891
  documentation: i.documentation,
682
892
  sortOrder: 60 + a
683
893
  };
@@ -687,13 +897,17 @@ function W(e) {
687
897
  function O(e) {
688
898
  return R.filter(
689
899
  (t) => !e || t.toLowerCase().includes(e.toLowerCase())
690
- ).map((t, a) => ({
691
- label: t,
692
- insertText: `${t}($1)`,
693
- kind: "aggregation",
694
- detail: "Aggregation function",
695
- sortOrder: 70 + a
696
- }));
900
+ ).map((t, a) => {
901
+ const docs = aggregationDocs[t] || { detail: "Aggregation function", documentation: "" };
902
+ return {
903
+ label: t,
904
+ insertText: `${t}($1)`,
905
+ kind: "aggregation",
906
+ detail: docs.detail,
907
+ documentation: docs.documentation,
908
+ sortOrder: 70 + a
909
+ };
910
+ });
697
911
  }
698
912
  function Z(e) {
699
913
  return P.filter(
@@ -1,7 +1,8 @@
1
1
  var c = Object.defineProperty;
2
2
  var d = (r, e, t) => e in r ? c(r, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : r[e] = t;
3
3
  var o = (r, e, t) => d(r, typeof e != "symbol" ? e + "" : e, t);
4
- import { tokenizeTql as p, COMPARISON_OPERATORS as v, ENRICHMENT_MUTATORS as ENRICHMENT_MUTATORS } from "./tokenizer.js";
4
+ import { tokenizeTql as p, COMPARISON_OPERATORS as v, ENRICHMENT_MUTATORS as ENRICHMENT_MUTATORS, MUTATORS as ALL_MUTATORS } from "./tokenizer.js";
5
+ import { suggestOperator as suggestOp, suggestMutator as suggestMut } from "./validator.js";
5
6
  const f = 50, k = /* @__PURE__ */ new Set(["logicalOp", "paren"]);
6
7
  class y {
7
8
  constructor(e) {
@@ -204,7 +205,9 @@ class y {
204
205
  n.push(s.value.toLowerCase());
205
206
  } else if (this.check("field")) {
206
207
  const s = this.advance();
207
- n.push(s.value.toLowerCase()), this.addWarning(`Unknown mutator '${s.value}'`, s, "Check the mutator name spelling");
208
+ n.push(s.value.toLowerCase());
209
+ const suggestion = suggestMut(s.value);
210
+ this.addWarning(`Unknown mutator '${s.value}'`, s, suggestion ? `Did you mean: ${suggestion}?` : "Valid mutators: lowercase, uppercase, trim, geo, nslookup, etc.");
208
211
  } else {
209
212
  const s = this.isAtEnd() ? this.previous() : this.peek();
210
213
  this.addError(
@@ -257,7 +260,14 @@ class y {
257
260
  end: t.end
258
261
  };
259
262
  }
260
- const i = this.advance(), a = this.normalizeOperator(i.value);
263
+ const i = this.advance();
264
+ let a = this.normalizeOperator(i.value);
265
+ // Handle unknown operator with suggestions
266
+ if (typeof a === 'object' && a.unknown) {
267
+ const suggestionMsg = a.suggestion ? `Did you mean: ${a.suggestion}?` : "Valid operators: eq, ne, contains, startswith, gt, lt, etc.";
268
+ this.addWarning(`Unknown operator '${a.value}'`, i, suggestionMsg);
269
+ a = a.value; // Use the original value
270
+ }
261
271
  if (a === "exists" || a === "not_exists")
262
272
  return {
263
273
  type: "comparison",
@@ -470,7 +480,15 @@ class y {
470
480
  starts_with: "startswith",
471
481
  ends_with: "endswith"
472
482
  };
473
- return t in a ? a[t] : (v.includes(t), t);
483
+ if (t in a) return a[t];
484
+ // Check if this is a valid operator, if not add a warning with suggestions
485
+ if (!v.includes(t)) {
486
+ const suggestion = suggestOp(t);
487
+ // Return the original value but note that it might be invalid
488
+ // The warning will be added by the caller if needed
489
+ return { value: t, unknown: true, suggestion };
490
+ }
491
+ return t;
474
492
  }
475
493
  isAtEnd() {
476
494
  return this.current >= this.tokens.length;
@@ -12,39 +12,95 @@ const ENRICHMENT_MUTATORS = [
12
12
 
13
13
  // Parameters available for mutators that support function call syntax
14
14
  // e.g., geo(force=true, save=true)
15
+ // Synced with Python TQL ALLOWED_MUTATORS definition
15
16
  const MUTATOR_PARAMETERS = {
16
- geo: [
17
- { name: "force", type: "boolean", description: "Force lookup even if geo data already exists" },
18
- { name: "save", type: "boolean", description: "Save results to the record" },
19
- { name: "field", type: "string", description: "Field name to save results to" },
20
- { name: "cache", type: "boolean", description: "Enable caching of lookup results" },
21
- { name: "cache_ttl", type: "number", description: "Cache TTL in seconds" }
17
+ // String transformation mutators
18
+ lowercase: [
19
+ { name: "field", type: "string", description: "Field name to save the lowercase result. Default: transforms value in-place for comparison only (not persisted)" }
22
20
  ],
23
- geoip: [
24
- { name: "force", type: "boolean", description: "Force lookup even if geo data already exists" },
25
- { name: "save", type: "boolean", description: "Save results to the record" },
26
- { name: "field", type: "string", description: "Field name to save results to" },
27
- { name: "cache", type: "boolean", description: "Enable caching of lookup results" },
28
- { name: "cache_ttl", type: "number", description: "Cache TTL in seconds" }
21
+ uppercase: [
22
+ { name: "field", type: "string", description: "Field name to save the uppercase result. Default: transforms value in-place for comparison only (not persisted)" }
29
23
  ],
30
- geoip_lookup: [
31
- { name: "force", type: "boolean", description: "Force lookup even if geo data already exists" },
32
- { name: "save", type: "boolean", description: "Save results to the record" },
33
- { name: "field", type: "string", description: "Field name to save results to" },
34
- { name: "cache", type: "boolean", description: "Enable caching of lookup results" },
35
- { name: "cache_ttl", type: "number", description: "Cache TTL in seconds" }
36
- ],
37
- nslookup: [
38
- { name: "record_type", type: "string", description: "DNS record type (A, AAAA, MX, etc.)" },
39
- { name: "cache", type: "boolean", description: "Enable caching of lookup results" },
40
- { name: "cache_ttl", type: "number", description: "Cache TTL in seconds" }
24
+ trim: [
25
+ { name: "field", type: "string", description: "Field name to save the trimmed result. Default: transforms value in-place for comparison only (not persisted)" }
41
26
  ],
42
27
  split: [
43
- { name: "delimiter", type: "string", description: "Delimiter to split on (default: comma)" }
28
+ { name: "delimiter", type: "string", description: "Character(s) to split on. Default: comma (',')" },
29
+ { name: "field", type: "string", description: "Field name to save array result. Default: returns array for comparison only (not persisted)" }
30
+ ],
31
+ length: [
32
+ { name: "field", type: "string", description: "Field name to save the length. Default: returns length for comparison only (not persisted)" }
44
33
  ],
45
34
  replace: [
46
- { name: "find", type: "string", description: "Text to find" },
47
- { name: "replace", type: "string", description: "Text to replace with" }
35
+ { name: "find", type: "string", description: "Text to search for (required)" },
36
+ { name: "replace", type: "string", description: "Text to replace with (required)" },
37
+ { name: "field", type: "string", description: "Field name to save result. Default: returns replaced string for comparison only (not persisted)" }
38
+ ],
39
+ // Security mutators
40
+ refang: [
41
+ { name: "field", type: "string", description: "Field name to save the refanged result. Default: returns refanged value for comparison only" }
42
+ ],
43
+ defang: [
44
+ { name: "field", type: "string", description: "Field name to save the defanged result. Default: returns defanged value for comparison only" }
45
+ ],
46
+ // Encoding mutators
47
+ b64encode: [
48
+ { name: "field", type: "string", description: "Field name to save encoded result. Default: returns encoded value for comparison only" }
49
+ ],
50
+ b64decode: [
51
+ { name: "field", type: "string", description: "Field name to save decoded result. Default: returns decoded value for comparison only" }
52
+ ],
53
+ urldecode: [
54
+ { name: "field", type: "string", description: "Field name to save decoded result. Default: returns decoded value for comparison only" }
55
+ ],
56
+ hexencode: [
57
+ { name: "field", type: "string", description: "Field name to save hex-encoded result. Default: returns encoded value for comparison only" }
58
+ ],
59
+ hexdecode: [
60
+ { name: "field", type: "string", description: "Field name to save hex-decoded result. Default: returns decoded value for comparison only" }
61
+ ],
62
+ md5: [
63
+ { name: "field", type: "string", description: "Field name to save the MD5 hash. Default: returns hash for comparison only" }
64
+ ],
65
+ sha256: [
66
+ { name: "field", type: "string", description: "Field name to save the SHA256 hash. Default: returns hash for comparison only" }
67
+ ],
68
+ // Network mutators - no parameters, return boolean
69
+ is_private: [], // Returns boolean indicating if IP is in RFC1918 private ranges
70
+ is_global: [], // Returns boolean indicating if IP is globally routable
71
+ // DNS mutator
72
+ nslookup: [
73
+ { name: "servers", type: "array", description: "DNS servers to query. Default: system DNS servers" },
74
+ { name: "types", type: "array", description: "DNS record types to query (A, AAAA, MX, TXT, etc.). Default: ['A']" },
75
+ { name: "append_field", type: "string", description: "Field name to append results to" },
76
+ { name: "force", type: "boolean", description: "Force lookup even if DNS data already exists. Default: false" },
77
+ { name: "save", type: "boolean", description: "Save results to the record. Default: true" },
78
+ { name: "field", type: "string", description: "Field name to save results to. Default: appends .dns to source field" }
79
+ ],
80
+ // GeoIP mutators
81
+ geo: [
82
+ { name: "db_path", type: "string", description: "Path to MaxMind GeoIP database. Default: uses system-configured path" },
83
+ { name: "force", type: "boolean", description: "Force lookup even if geo data exists. Default: false (skips if data exists)" },
84
+ { name: "save", type: "boolean", description: "Save enrichment to the record. Default: true" },
85
+ { name: "field", type: "string", description: "Field name prefix for geo data. Default: appends .geo to source field" },
86
+ { name: "cache", type: "boolean", description: "Enable caching of lookup results. Default: true" },
87
+ { name: "cache_ttl", type: "number", description: "Cache TTL in seconds. Default: 3600 (1 hour)" }
88
+ ],
89
+ geoip: [
90
+ { name: "db_path", type: "string", description: "Path to MaxMind GeoIP database. Default: uses system-configured path" },
91
+ { name: "force", type: "boolean", description: "Force lookup even if geo data exists. Default: false (skips if data exists)" },
92
+ { name: "save", type: "boolean", description: "Save enrichment to the record. Default: true" },
93
+ { name: "field", type: "string", description: "Field name prefix for geo data. Default: appends .geo to source field" },
94
+ { name: "cache", type: "boolean", description: "Enable caching of lookup results. Default: true" },
95
+ { name: "cache_ttl", type: "number", description: "Cache TTL in seconds. Default: 3600 (1 hour)" }
96
+ ],
97
+ geoip_lookup: [
98
+ { name: "db_path", type: "string", description: "Path to MaxMind GeoIP database. Default: uses system-configured path" },
99
+ { name: "force", type: "boolean", description: "Force lookup even if geo data exists. Default: false (skips if data exists)" },
100
+ { name: "save", type: "boolean", description: "Save enrichment to the record. Default: true" },
101
+ { name: "field", type: "string", description: "Field name prefix for geo data. Default: appends .geo to source field" },
102
+ { name: "cache", type: "boolean", description: "Enable caching of lookup results. Default: true" },
103
+ { name: "cache_ttl", type: "number", description: "Cache TTL in seconds. Default: 3600 (1 hour)" }
48
104
  ]
49
105
  };
50
106
  const p = [
@@ -189,7 +245,9 @@ const p = [
189
245
  "array",
190
246
  "list",
191
247
  "object",
192
- "geo"
248
+ "geo",
249
+ "geo_point",
250
+ "ip"
193
251
  ], T = [
194
252
  "bar",
195
253
  "barchart",
@@ -2,6 +2,7 @@ var b = Object.defineProperty;
2
2
  var u = (s, e, t) => e in s ? b(s, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : s[e] = t;
3
3
  var r = (s, e, t) => u(s, typeof e != "symbol" ? e + "" : e, t);
4
4
  import { parseTql as c } from "./parser.js";
5
+ import { COMPARISON_OPERATORS, MUTATORS } from "./tokenizer.js";
5
6
  const f = {
6
7
  // Equality (all types)
7
8
  eq: ["text", "keyword", "long", "integer", "double", "float", "date", "boolean", "ip", "nested", "object", "geo_point"],
@@ -229,6 +230,65 @@ class y {
229
230
  "Check the regex syntax"
230
231
  );
231
232
  }
233
+ // CIDR notation validation
234
+ if ((e.operator === "cidr" || e.operator === "not_cidr" || e.operator === "!cidr") && !Array.isArray(e.value) && e.value.valueType === "string" && typeof e.value.value == "string") {
235
+ const cidrValue = e.value.value;
236
+ const ipv4CidrRegex = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\/(\d{1,2})$/;
237
+ const ipv6CidrRegex = /^([0-9a-fA-F:]+)\/(\d{1,3})$/;
238
+ const ipv4Match = cidrValue.match(ipv4CidrRegex);
239
+ const ipv6Match = cidrValue.match(ipv6CidrRegex);
240
+ if (ipv4Match) {
241
+ // Validate IPv4 octets (0-255) and prefix (0-32)
242
+ const octets = [parseInt(ipv4Match[1]), parseInt(ipv4Match[2]), parseInt(ipv4Match[3]), parseInt(ipv4Match[4])];
243
+ const prefix = parseInt(ipv4Match[5]);
244
+ const invalidOctet = octets.find(o => o > 255);
245
+ if (invalidOctet !== undefined) {
246
+ this.addError(
247
+ `Invalid CIDR notation: octet value ${invalidOctet} exceeds 255`,
248
+ e.value.start,
249
+ e.value.end,
250
+ 1,
251
+ 1,
252
+ "INVALID_CIDR",
253
+ "IPv4 octets must be 0-255 (e.g., 192.168.0.0/24)"
254
+ );
255
+ } else if (prefix > 32) {
256
+ this.addError(
257
+ `Invalid CIDR notation: prefix /${prefix} exceeds maximum /32 for IPv4`,
258
+ e.value.start,
259
+ e.value.end,
260
+ 1,
261
+ 1,
262
+ "INVALID_CIDR",
263
+ "IPv4 CIDR prefix must be 0-32"
264
+ );
265
+ }
266
+ } else if (ipv6Match) {
267
+ // Validate IPv6 prefix (0-128)
268
+ const prefix = parseInt(ipv6Match[2]);
269
+ if (prefix > 128) {
270
+ this.addError(
271
+ `Invalid CIDR notation: prefix /${prefix} exceeds maximum /128 for IPv6`,
272
+ e.value.start,
273
+ e.value.end,
274
+ 1,
275
+ 1,
276
+ "INVALID_CIDR",
277
+ "IPv6 CIDR prefix must be 0-128"
278
+ );
279
+ }
280
+ } else {
281
+ this.addError(
282
+ `Invalid CIDR notation: ${cidrValue}`,
283
+ e.value.start,
284
+ e.value.end,
285
+ 1,
286
+ 1,
287
+ "INVALID_CIDR",
288
+ "Use format like 192.168.0.0/24 (IPv4) or 2001:db8::/32 (IPv6)"
289
+ );
290
+ }
291
+ }
232
292
  }
233
293
  /**
234
294
  * Suggest a similar field name using Levenshtein distance
@@ -243,6 +303,36 @@ class y {
243
303
  }
244
304
  return t;
245
305
  }
306
+ /**
307
+ * Suggest a similar operator using Levenshtein distance
308
+ * @param {string} input - The mistyped operator
309
+ * @param {number} maxDistance - Maximum edit distance to consider (default: 2)
310
+ * @returns {string|null} - Suggested operator(s) or null
311
+ */
312
+ suggestOperator(input, maxDistance = 2) {
313
+ const suggestions = COMPARISON_OPERATORS
314
+ .map(op => ({ op, distance: this.levenshteinDistance(input.toLowerCase(), op.toLowerCase()) }))
315
+ .filter(s => s.distance <= maxDistance && s.distance > 0)
316
+ .sort((a, b) => a.distance - b.distance)
317
+ .slice(0, 3)
318
+ .map(s => s.op);
319
+ return suggestions.length > 0 ? suggestions.join(", ") : null;
320
+ }
321
+ /**
322
+ * Suggest a similar mutator using Levenshtein distance
323
+ * @param {string} input - The mistyped mutator
324
+ * @param {number} maxDistance - Maximum edit distance to consider (default: 2)
325
+ * @returns {string|null} - Suggested mutator(s) or null
326
+ */
327
+ suggestMutator(input, maxDistance = 2) {
328
+ const suggestions = MUTATORS
329
+ .map(m => ({ m, distance: this.levenshteinDistance(input.toLowerCase(), m.toLowerCase()) }))
330
+ .filter(s => s.distance <= maxDistance && s.distance > 0)
331
+ .sort((a, b) => a.distance - b.distance)
332
+ .slice(0, 3)
333
+ .map(s => s.m);
334
+ return suggestions.length > 0 ? suggestions.join(", ") : null;
335
+ }
246
336
  /**
247
337
  * Calculate Levenshtein distance between two strings
248
338
  */
@@ -306,10 +396,34 @@ function v(s) {
306
396
  return e.join(`
307
397
  `);
308
398
  }
399
+ /**
400
+ * Standalone function to suggest similar operators
401
+ * @param {string} input - The mistyped operator
402
+ * @param {number} maxDistance - Maximum edit distance to consider (default: 2)
403
+ * @returns {string|null} - Suggested operator(s) or null
404
+ */
405
+ function suggestOperator(input, maxDistance = 2) {
406
+ const validator = new y();
407
+ return validator.suggestOperator(input, maxDistance);
408
+ }
409
+
410
+ /**
411
+ * Standalone function to suggest similar mutators
412
+ * @param {string} input - The mistyped mutator
413
+ * @param {number} maxDistance - Maximum edit distance to consider (default: 2)
414
+ * @returns {string|null} - Suggested mutator(s) or null
415
+ */
416
+ function suggestMutator(input, maxDistance = 2) {
417
+ const validator = new y();
418
+ return validator.suggestMutator(input, maxDistance);
419
+ }
420
+
309
421
  export {
310
422
  y as TqlValidator,
311
423
  v as formatValidationResult,
312
424
  m as getTqlIssues,
313
425
  _ as isValidTqlSyntax,
314
- g as validateTql
426
+ g as validateTql,
427
+ suggestOperator,
428
+ suggestMutator
315
429
  };
package/dist/style.css CHANGED
@@ -1 +1 @@
1
- .tql-autocomplete-dropdown[data-v-e29dd6a7]{position:fixed;top:var(--dropdown-top);left:var(--dropdown-left);z-index:9999;min-width:280px;max-width:450px;max-height:var(--dropdown-max-height);background:var(--surface-overlay, #fff);border:1px solid var(--surface-border, #ddd);border-radius:var(--border-radius, 6px);box-shadow:0 4px 16px #00000026;overflow:hidden;display:flex;flex-direction:column}.tql-completion-list[data-v-e29dd6a7]{overflow-y:auto;max-height:calc(var(--dropdown-max-height) - 60px)}.tql-completion-item[data-v-e29dd6a7]{display:flex;align-items:center;gap:.5rem;padding:.5rem .75rem;cursor:pointer;transition:background-color .15s}.tql-completion-item[data-v-e29dd6a7]:hover,.tql-completion-item.selected[data-v-e29dd6a7]{background:var(--highlight-bg, #f0f0f0)}.tql-completion-icon[data-v-e29dd6a7]{display:flex;align-items:center;justify-content:center;width:1.5rem;height:1.5rem;border-radius:4px;font-size:.75rem}.tql-completion-icon.tql-completion-kind-field[data-v-e29dd6a7]{background:var(--blue-100, #e3f2fd);color:var(--blue-600, #1e88e5)}.tql-completion-icon.tql-completion-kind-operator[data-v-e29dd6a7]{background:var(--pink-100, #fce4ec);color:var(--pink-600, #d81b60)}.tql-completion-icon.tql-completion-kind-value[data-v-e29dd6a7]{background:var(--green-100, #e8f5e9);color:var(--green-600, #43a047)}.tql-completion-icon.tql-completion-kind-keyword[data-v-e29dd6a7]{background:var(--purple-100, #f3e5f5);color:var(--purple-600, #8e24aa)}.tql-completion-icon.tql-completion-kind-snippet[data-v-e29dd6a7]{background:var(--orange-100, #fff3e0);color:var(--orange-600, #fb8c00)}.tql-completion-icon.tql-completion-kind-mutator[data-v-e29dd6a7]{background:var(--cyan-100, #e0f7fa);color:var(--cyan-600, #00acc1)}.tql-completion-icon.tql-completion-kind-aggregation[data-v-e29dd6a7]{background:var(--indigo-100, #e8eaf6);color:var(--indigo-600, #3949ab)}.tql-completion-icon.tql-completion-kind-logicalOp[data-v-e29dd6a7]{background:var(--yellow-100, #fffde7);color:var(--yellow-700, #fbc02d)}.tql-completion-icon.tql-completion-kind-typeHint[data-v-e29dd6a7]{background:var(--teal-100, #e0f2f1);color:var(--teal-600, #00897b)}.tql-completion-icon.tql-completion-kind-visualization[data-v-e29dd6a7]{background:var(--red-100, #ffebee);color:var(--red-600, #e53935)}.tql-completion-content[data-v-e29dd6a7]{flex:1;display:flex;flex-direction:column;min-width:0}.tql-completion-label[data-v-e29dd6a7]{font-family:Courier New,monospace;font-size:.875rem;font-weight:500;color:var(--text-color, #333);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tql-completion-detail[data-v-e29dd6a7]{font-size:.75rem;color:var(--text-color-secondary, #666);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tql-completion-kind-badge[data-v-e29dd6a7]{font-size:.625rem;padding:.125rem .375rem;background:var(--surface-100, #f5f5f5);border-radius:3px;color:var(--text-color-secondary, #666);text-transform:uppercase;letter-spacing:.025em}.tql-completion-docs[data-v-e29dd6a7]{padding:.5rem .75rem;border-top:1px solid var(--surface-border, #ddd);background:var(--surface-50, #fafafa);font-size:.75rem;color:var(--text-color-secondary, #666);max-height:60px;overflow-y:auto}.tql-preview[data-v-bae811e6]{font-family:ui-monospace,monospace;font-size:13px;line-height:1.5;padding:12px;border:1px solid var(--tql-preview-border, #e0e0e0);border-radius:6px;background:var(--tql-preview-bg, #fafafa)}.tql-preview--compact[data-v-bae811e6]{padding:8px;font-size:12px}.tql-preview--empty[data-v-bae811e6]{color:var(--tql-preview-muted, #888)}.tql-preview__empty[data-v-bae811e6]{text-align:center;padding:20px}.tql-preview__complexity[data-v-bae811e6]{display:flex;align-items:center;gap:12px;margin-bottom:12px}.tql-preview__complexity-badge[data-v-bae811e6]{padding:2px 8px;border-radius:4px;font-size:11px;font-weight:600;text-transform:uppercase}.tql-preview--low .tql-preview__complexity-badge[data-v-bae811e6]{background:var(--tql-preview-low-bg, #e8f5e9);color:var(--tql-preview-low-text, #2e7d32)}.tql-preview--medium .tql-preview__complexity-badge[data-v-bae811e6]{background:var(--tql-preview-medium-bg, #fff3e0);color:var(--tql-preview-medium-text, #e65100)}.tql-preview--high .tql-preview__complexity-badge[data-v-bae811e6]{background:var(--tql-preview-high-bg, #ffebee);color:var(--tql-preview-high-text, #c62828)}.tql-preview__field-count[data-v-bae811e6]{color:var(--tql-preview-muted, #666);font-size:12px}.tql-preview__warnings[data-v-bae811e6]{margin-bottom:12px}.tql-preview__warning[data-v-bae811e6]{display:flex;align-items:flex-start;gap:8px;padding:6px 8px;border-radius:4px;margin-bottom:4px}.tql-preview__warning--info[data-v-bae811e6]{background:var(--tql-preview-info-bg, #e3f2fd);color:var(--tql-preview-info-text, #1565c0)}.tql-preview__warning--warning[data-v-bae811e6]{background:var(--tql-preview-warning-bg, #fff3e0);color:var(--tql-preview-warning-text, #e65100)}.tql-preview__warning--error[data-v-bae811e6]{background:var(--tql-preview-error-bg, #ffebee);color:var(--tql-preview-error-text, #c62828)}.tql-preview__warning-icon[data-v-bae811e6]{font-weight:700;width:16px;text-align:center}.tql-preview__warning-text[data-v-bae811e6]{flex:1}.tql-preview__suggestions[data-v-bae811e6]{margin-bottom:12px;padding:8px;background:var(--tql-preview-suggestion-bg, #f5f5f5);border-radius:4px}.tql-preview__suggestions-header[data-v-bae811e6]{font-weight:600;margin-bottom:4px;color:var(--tql-preview-header, #333)}.tql-preview__suggestions-list[data-v-bae811e6]{margin:0;padding-left:20px}.tql-preview__suggestion[data-v-bae811e6]{color:var(--tql-preview-muted, #666);margin:2px 0}.tql-preview__tree[data-v-bae811e6]{margin-bottom:12px}.tql-preview__tree-header[data-v-bae811e6],.tql-preview__fields-header[data-v-bae811e6],.tql-preview__operators-header[data-v-bae811e6]{font-weight:600;margin-bottom:8px;color:var(--tql-preview-header, #333);font-size:12px;text-transform:uppercase;letter-spacing:.5px}.tql-preview__tree-content[data-v-bae811e6]{border:1px solid var(--tql-preview-tree-border, #e0e0e0);border-radius:4px;background:var(--tql-preview-tree-bg, #fff);padding:8px}.tql-preview__node[data-v-bae811e6]{margin:2px 0}.tql-preview__node-content[data-v-bae811e6]{display:flex;align-items:center;gap:4px;padding:2px 4px;border-radius:3px;cursor:pointer}.tql-preview__node-content[data-v-bae811e6]:hover{background:var(--tql-preview-node-hover, #f0f0f0)}.tql-preview__node-toggle[data-v-bae811e6]{width:16px;height:16px;border:none;background:var(--tql-preview-toggle-bg, #e0e0e0);border-radius:3px;cursor:pointer;font-size:12px;line-height:1;display:flex;align-items:center;justify-content:center}.tql-preview__node-toggle[data-v-bae811e6]:hover{background:var(--tql-preview-toggle-hover, #d0d0d0)}.tql-preview__node-toggle--leaf[data-v-bae811e6]{visibility:hidden}.tql-preview__node-type[data-v-bae811e6]{font-weight:600;color:var(--tql-preview-node-type, #666)}.tql-preview__node--and .tql-preview__node-type[data-v-bae811e6]{color:var(--tql-preview-and, #1976d2)}.tql-preview__node--or .tql-preview__node-type[data-v-bae811e6]{color:var(--tql-preview-or, #7b1fa2)}.tql-preview__node--not .tql-preview__node-type[data-v-bae811e6]{color:var(--tql-preview-not, #c62828)}.tql-preview__node--group .tql-preview__node-type[data-v-bae811e6]{color:var(--tql-preview-group, #666)}.tql-preview__node-label[data-v-bae811e6]{flex:1}.tql-preview__fields-list[data-v-bae811e6]{display:flex;flex-direction:column;gap:4px}.tql-preview__field[data-v-bae811e6]{display:flex;align-items:center;gap:6px;padding:4px 8px;background:var(--tql-preview-field-bg, #fff);border:1px solid var(--tql-preview-field-border, #e0e0e0);border-radius:4px;cursor:pointer}.tql-preview__field[data-v-bae811e6]:hover{background:var(--tql-preview-field-hover, #f5f5f5)}.tql-preview__field-name[data-v-bae811e6]{font-weight:600;color:var(--tql-preview-field-name, #1976d2)}.tql-preview__field-mutators[data-v-bae811e6]{color:var(--tql-preview-mutator, #7b1fa2)}.tql-preview__field-operator[data-v-bae811e6]{color:var(--tql-preview-operator, #666)}.tql-preview__field-value[data-v-bae811e6]{color:var(--tql-preview-value, #2e7d32)}.tql-preview__match-type[data-v-bae811e6]{margin-left:auto;padding:1px 4px;border-radius:3px;font-size:10px;font-weight:600;background:var(--tql-preview-match-bg, #f0f0f0)}.tql-preview__match-type--exact[data-v-bae811e6]{background:var(--tql-preview-match-exact-bg, #e8f5e9);color:var(--tql-preview-match-exact, #2e7d32)}.tql-preview__match-type--wildcard[data-v-bae811e6]{background:var(--tql-preview-match-wildcard-bg, #fff3e0);color:var(--tql-preview-match-wildcard, #e65100)}.tql-preview__match-type--regex[data-v-bae811e6]{background:var(--tql-preview-match-regex-bg, #fce4ec);color:var(--tql-preview-match-regex, #c2185b)}.tql-preview__match-type--range[data-v-bae811e6]{background:var(--tql-preview-match-range-bg, #e3f2fd);color:var(--tql-preview-match-range, #1565c0)}.tql-preview__match-type--cidr[data-v-bae811e6]{background:var(--tql-preview-match-cidr-bg, #f3e5f5);color:var(--tql-preview-match-cidr, #7b1fa2)}.tql-preview__operators-list[data-v-bae811e6]{display:flex;flex-wrap:wrap;gap:8px}.tql-preview__operator[data-v-bae811e6]{padding:2px 6px;background:var(--tql-preview-op-bg, #f0f0f0);border-radius:3px;font-size:11px}.tql-input-wrapper[data-v-c7135df3]{position:relative;display:flex;align-items:flex-start;border:1px solid var(--surface-border, #ddd);border-radius:var(--border-radius, 6px);background:var(--surface-ground, #fff);transition:border-color .2s,box-shadow .2s}.tql-input-wrapper.tql-input-has-min-height[data-v-c7135df3],.tql-input-wrapper.tql-input-has-min-height .tql-input[data-v-c7135df3]{min-height:var(--tql-min-height)}.tql-input-wrapper.tql-input-focused[data-v-c7135df3]{border-color:var(--primary-color, #3b82f6);box-shadow:0 0 0 2px var(--primary-100, rgba(59, 130, 246, .2))}.tql-input-wrapper.tql-input-invalid[data-v-c7135df3]{border-color:var(--red-500, #ef4444)}.tql-input-wrapper.tql-input-invalid.tql-input-focused[data-v-c7135df3]{box-shadow:0 0 0 2px var(--red-100, rgba(239, 68, 68, .2))}.tql-input-wrapper.tql-input-disabled[data-v-c7135df3]{opacity:.6;cursor:not-allowed}.tql-highlight-layer[data-v-c7135df3]{position:absolute;top:0;left:0;right:0;bottom:0;padding:.5rem .75rem;pointer-events:none;white-space:pre-wrap;word-wrap:break-word;font-family:Courier New,monospace;font-size:.875rem;line-height:1.5;overflow:hidden}.tql-input-multiline .tql-highlight-layer[data-v-c7135df3]{overflow-y:auto}.tql-input[data-v-c7135df3]{flex:1;width:100%;padding:.5rem 8rem .5rem .75rem;border:none;background:transparent;font-family:Courier New,monospace;font-size:.875rem;line-height:1.5;color:var(--text-color, #333);outline:none;resize:none}.tql-input.tql-input-highlighted[data-v-c7135df3]{color:transparent;caret-color:var(--text-color, #333)}.tql-input[data-v-c7135df3]::placeholder{color:var(--text-color-secondary, #666);opacity:.7}.tql-input[data-v-c7135df3]:disabled{cursor:not-allowed}.tql-token-field[data-v-c7135df3]{color:var(--tql-field-color, var(--blue-600, #2563eb))}.tql-token-operator[data-v-c7135df3]{color:var(--tql-operator-color, var(--pink-600, #db2777));font-weight:500}.tql-token-logicalOp[data-v-c7135df3]{color:var(--tql-logical-color, var(--purple-600, #9333ea));font-weight:600}.tql-token-collectionOp[data-v-c7135df3]{color:var(--tql-collection-color, var(--indigo-600, #4f46e5));font-weight:600}.tql-token-string[data-v-c7135df3]{color:var(--tql-string-color, var(--green-600, #16a34a))}.tql-token-number[data-v-c7135df3]{color:var(--tql-number-color, var(--orange-600, #ea580c))}.tql-token-boolean[data-v-c7135df3]{color:var(--tql-boolean-color, var(--cyan-600, #0891b2))}.tql-token-null[data-v-c7135df3]{color:var(--tql-null-color, var(--gray-500, #6b7280));font-style:italic}.tql-token-mutator[data-v-c7135df3]{color:var(--tql-mutator-color, var(--teal-600, #0d9488));font-style:italic}.tql-token-aggregation[data-v-c7135df3]{color:var(--tql-aggregation-color, var(--indigo-600, #4f46e5))}.tql-token-keyword[data-v-c7135df3]{color:var(--tql-keyword-color, var(--purple-500, #a855f7))}.tql-token-typeHint[data-v-c7135df3]{color:var(--tql-typehint-color, var(--teal-500, #14b8a6))}.tql-token-visualization[data-v-c7135df3]{color:var(--tql-viz-color, var(--red-500, #ef4444))}.tql-token-arrow[data-v-c7135df3]{color:var(--tql-arrow-color, var(--gray-600, #4b5563));font-weight:600}.tql-token-doubleColon[data-v-c7135df3]{color:var(--tql-colon-color, var(--gray-500, #6b7280))}.tql-token-paren[data-v-c7135df3],.tql-token-bracket[data-v-c7135df3]{color:var(--tql-bracket-color, var(--gray-600, #4b5563))}.tql-token-error[data-v-c7135df3]{color:var(--tql-error-color, var(--red-600, #dc2626));text-decoration:wavy underline;text-decoration-color:var(--red-400, #f87171)}.tql-token-error-range[data-v-c7135df3]{background-color:var(--red-50, #fef2f2)}.tql-token-unknown[data-v-c7135df3]{color:var(--text-color-secondary, #666)}.tql-validation-icon[data-v-c7135df3]{position:absolute;right:.5rem;top:50%;transform:translateY(-50%);display:flex;align-items:center;justify-content:center}.tql-input-multiline .tql-validation-icon[data-v-c7135df3]{top:.75rem;transform:none}.tql-icon-valid[data-v-c7135df3]{color:var(--green-500, #22c55e)}.tql-icon-invalid[data-v-c7135df3]{color:var(--red-500, #ef4444)}.tql-icon-clickable[data-v-c7135df3]{cursor:pointer;transition:transform .2s}.tql-icon-clickable[data-v-c7135df3]:hover{transform:scale(1.2)}.tql-icon-clickable[data-v-c7135df3]:focus{outline:2px solid var(--primary-color, #3b82f6);outline-offset:2px;border-radius:50%}.tql-snippet-button[data-v-c7135df3]{position:absolute;right:2rem;top:50%;transform:translateY(-50%);padding:.25rem;border:none;background:transparent;color:var(--text-color-secondary, #666);cursor:pointer;border-radius:var(--border-radius, 6px);transition:color .2s,background-color .2s}.tql-snippet-button[data-v-c7135df3]:hover{color:var(--primary-color, #3b82f6);background:var(--surface-100, #f5f5f5)}.tql-input-multiline .tql-snippet-button[data-v-c7135df3]{top:.75rem;transform:none}.tql-input-resizable-textarea[data-v-c7135df3]{resize:vertical;min-height:80px}.tql-input-wrapper.tql-input-resizable[data-v-c7135df3]{height:auto}.tql-input-wrapper.tql-input-resizable .tql-highlight-layer[data-v-c7135df3]{height:100%}.tql-validation-messages[data-v-c7135df3]{position:absolute;top:100%;left:0;right:0;margin-top:.25rem;background:var(--surface-overlay, #fff);border:1px solid var(--red-200, #fecaca);border-radius:var(--border-radius, 6px);padding:.5rem;z-index:10;box-shadow:0 2px 8px #0000001a}.tql-input-autocomplete-active .tql-validation-messages[data-v-c7135df3],.tql-input-autocomplete-active .tql-complexity-warnings[data-v-c7135df3],.tql-input-autocomplete-active .tql-history-dropdown[data-v-c7135df3],.tql-input-autocomplete-active .tql-preview-panel[data-v-c7135df3]{display:none!important}.tql-error-message[data-v-c7135df3]{display:flex;align-items:flex-start;gap:.5rem;padding:.25rem 0;font-size:.75rem;color:var(--red-600, #dc2626)}.tql-error-message i[data-v-c7135df3]{margin-top:.125rem;font-size:.75rem}.tql-error-suggestion[data-v-c7135df3]{display:block;color:var(--text-color-secondary, #666);font-style:italic;margin-left:1.25rem}.sr-only[data-v-c7135df3]{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.tql-toolbar[data-v-c7135df3]{position:absolute;right:.5rem;top:50%;transform:translateY(-50%);display:flex;align-items:center;gap:.25rem}.tql-input-multiline .tql-toolbar[data-v-c7135df3]{top:.5rem;transform:none}.tql-toolbar-button[data-v-c7135df3]{padding:.25rem;border:none;background:transparent;color:var(--text-color-secondary, #666);cursor:pointer;border-radius:var(--border-radius, 4px);transition:color .2s,background-color .2s;display:flex;align-items:center;justify-content:center}.tql-toolbar-button[data-v-c7135df3]:hover{color:var(--primary-color, #3b82f6);background:var(--surface-100, #f5f5f5)}.tql-toolbar-button--active[data-v-c7135df3]{color:var(--primary-color, #3b82f6);background:var(--primary-100, rgba(59, 130, 246, .1))}.tql-validation-status[data-v-c7135df3]{display:flex;align-items:center}.tql-complexity-badge[data-v-c7135df3]{display:flex;align-items:center;gap:.25rem;padding:.125rem .5rem;border-radius:9999px;font-size:.625rem;font-weight:600;text-transform:uppercase;letter-spacing:.025em}.tql-complexity-low[data-v-c7135df3]{background:var(--green-100, #dcfce7);color:var(--green-700, #15803d)}.tql-complexity-medium[data-v-c7135df3]{background:var(--yellow-100, #fef9c3);color:var(--yellow-700, #a16207)}.tql-complexity-high[data-v-c7135df3]{background:var(--red-100, #fee2e2);color:var(--red-700, #b91c1c)}.tql-complexity-count[data-v-c7135df3]{display:inline-flex;align-items:center;justify-content:center;min-width:1rem;height:1rem;padding:0 .25rem;border-radius:9999px;background:currentColor;color:#fff;font-size:.5rem}.tql-complexity-low .tql-complexity-count[data-v-c7135df3]{background:var(--green-600, #16a34a)}.tql-complexity-medium .tql-complexity-count[data-v-c7135df3]{background:var(--yellow-600, #ca8a04)}.tql-complexity-high .tql-complexity-count[data-v-c7135df3]{background:var(--red-600, #dc2626)}.tql-complexity-warnings[data-v-c7135df3]{position:absolute;top:100%;left:0;right:0;margin-top:.25rem;background:var(--surface-overlay, #fff);border:1px solid var(--surface-border, #e0e0e0);border-radius:var(--border-radius, 6px);padding:.5rem;z-index:9;box-shadow:0 2px 8px #0000001a}.tql-complexity-warning[data-v-c7135df3]{display:flex;align-items:flex-start;gap:.5rem;padding:.25rem 0;font-size:.75rem}.tql-complexity-warning i[data-v-c7135df3]{margin-top:.125rem;font-size:.75rem}.tql-complexity-warning--info[data-v-c7135df3]{color:var(--blue-600, #2563eb)}.tql-complexity-warning--warning[data-v-c7135df3]{color:var(--yellow-600, #ca8a04)}.tql-complexity-warning--error[data-v-c7135df3]{color:var(--red-600, #dc2626)}.tql-complexity-suggestions[data-v-c7135df3]{margin-top:.5rem;padding-top:.5rem;border-top:1px solid var(--surface-border, #e0e0e0)}.tql-complexity-suggestion[data-v-c7135df3]{display:flex;align-items:flex-start;gap:.5rem;padding:.25rem 0;font-size:.75rem;color:var(--text-color-secondary, #666)}.tql-complexity-suggestion i[data-v-c7135df3]{color:var(--yellow-500, #eab308)}.tql-history-dropdown[data-v-c7135df3]{position:absolute;top:100%;left:0;right:0;margin-top:.25rem;background:var(--surface-overlay, #fff);border:1px solid var(--surface-border, #e0e0e0);border-radius:var(--border-radius, 6px);z-index:20;box-shadow:0 4px 12px #00000026;max-height:300px;overflow:hidden;display:flex;flex-direction:column}.tql-history-header[data-v-c7135df3]{padding:.5rem;border-bottom:1px solid var(--surface-border, #e0e0e0)}.tql-history-search[data-v-c7135df3]{width:100%;padding:.375rem .5rem;border:1px solid var(--surface-border, #e0e0e0);border-radius:var(--border-radius, 4px);font-size:.75rem;outline:none}.tql-history-search[data-v-c7135df3]:focus{border-color:var(--primary-color, #3b82f6)}.tql-history-list[data-v-c7135df3]{overflow-y:auto;flex:1}.tql-history-item[data-v-c7135df3]{display:flex;align-items:flex-start;gap:.5rem;padding:.5rem;cursor:pointer;border-bottom:1px solid var(--surface-50, #f9fafb);transition:background-color .15s}.tql-history-item[data-v-c7135df3]:hover{background:var(--surface-100, #f5f5f5)}.tql-history-item--favorite[data-v-c7135df3]{background:var(--yellow-50, #fefce8)}.tql-history-item--favorite[data-v-c7135df3]:hover{background:var(--yellow-100, #fef9c3)}.tql-history-favorite[data-v-c7135df3]{padding:.125rem;border:none;background:transparent;color:var(--text-color-secondary, #999);cursor:pointer;flex-shrink:0}.tql-history-favorite[data-v-c7135df3]:hover,.tql-history-item--favorite .tql-history-favorite[data-v-c7135df3]{color:var(--yellow-500, #eab308)}.tql-history-content[data-v-c7135df3]{flex:1;min-width:0}.tql-history-label[data-v-c7135df3]{font-size:.6875rem;font-weight:600;color:var(--text-color, #333);margin-bottom:.125rem}.tql-history-query[data-v-c7135df3]{font-family:Courier New,monospace;font-size:.6875rem;color:var(--text-color-secondary, #666);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tql-history-time[data-v-c7135df3]{font-size:.625rem;color:var(--text-color-secondary, #999);flex-shrink:0}.tql-history-empty[data-v-c7135df3]{padding:1rem;text-align:center;color:var(--text-color-secondary, #666);font-size:.75rem}.tql-preview-panel[data-v-c7135df3]{position:absolute;top:100%;left:0;right:0;margin-top:.25rem;background:var(--surface-overlay, #fff);border:1px solid var(--surface-border, #e0e0e0);border-radius:var(--border-radius, 6px);z-index:15;box-shadow:0 4px 12px #00000026;max-height:400px;overflow:hidden;display:flex;flex-direction:column}.tql-preview-header[data-v-c7135df3]{display:flex;align-items:center;justify-content:space-between;padding:.5rem .75rem;border-bottom:1px solid var(--surface-border, #e0e0e0);font-size:.75rem;font-weight:600;color:var(--text-color, #333)}.tql-preview-close[data-v-c7135df3]{padding:.125rem;border:none;background:transparent;color:var(--text-color-secondary, #666);cursor:pointer;border-radius:var(--border-radius, 4px)}.tql-preview-close[data-v-c7135df3]:hover{background:var(--surface-100, #f5f5f5);color:var(--text-color, #333)}.tql-preview-panel[data-v-c7135df3] .tql-preview{border:none;border-radius:0;max-height:350px;overflow-y:auto}.tql-validation-messages-portal{position:fixed;top:var(--tql-msg-top, 0);left:var(--tql-msg-left, 0);width:var(--tql-msg-width, auto);z-index:9998;background:var(--surface-overlay, #fff);border:2px solid var(--red-500, #ef4444);border-radius:var(--border-radius, 6px);padding:.5rem;box-shadow:0 4px 12px #00000026}.tql-validation-messages-portal .tql-error-message{display:flex;align-items:flex-start;gap:.5rem;padding:.25rem 0;font-size:.75rem;color:var(--red-600, #dc2626)}.tql-validation-messages-portal .tql-error-message i{margin-top:.125rem;font-size:.75rem}.tql-validation-messages-portal .tql-error-suggestion{display:block;color:var(--text-color-secondary, #6b7280);font-style:italic;margin-left:1.25rem}
1
+ .tql-autocomplete-dropdown[data-v-e29dd6a7]{position:fixed;top:var(--dropdown-top);left:var(--dropdown-left);z-index:9999;min-width:280px;max-width:450px;max-height:var(--dropdown-max-height);background:var(--surface-overlay, #fff);border:1px solid var(--surface-border, #ddd);border-radius:var(--border-radius, 6px);box-shadow:0 4px 16px #00000026;overflow:hidden;display:flex;flex-direction:column}.tql-completion-list[data-v-e29dd6a7]{overflow-y:auto;max-height:calc(var(--dropdown-max-height) - 60px)}.tql-completion-item[data-v-e29dd6a7]{display:flex;align-items:center;gap:.5rem;padding:.5rem .75rem;cursor:pointer;transition:background-color .15s}.tql-completion-item[data-v-e29dd6a7]:hover,.tql-completion-item.selected[data-v-e29dd6a7]{background:var(--highlight-bg, #f0f0f0)}.tql-completion-icon[data-v-e29dd6a7]{display:flex;align-items:center;justify-content:center;width:1.5rem;height:1.5rem;border-radius:4px;font-size:.75rem}.tql-completion-icon.tql-completion-kind-field[data-v-e29dd6a7]{background:var(--blue-100, #e3f2fd);color:var(--blue-600, #1e88e5)}.tql-completion-icon.tql-completion-kind-operator[data-v-e29dd6a7]{background:var(--pink-100, #fce4ec);color:var(--pink-600, #d81b60)}.tql-completion-icon.tql-completion-kind-value[data-v-e29dd6a7]{background:var(--green-100, #e8f5e9);color:var(--green-600, #43a047)}.tql-completion-icon.tql-completion-kind-keyword[data-v-e29dd6a7]{background:var(--purple-100, #f3e5f5);color:var(--purple-600, #8e24aa)}.tql-completion-icon.tql-completion-kind-snippet[data-v-e29dd6a7]{background:var(--orange-100, #fff3e0);color:var(--orange-600, #fb8c00)}.tql-completion-icon.tql-completion-kind-mutator[data-v-e29dd6a7]{background:var(--cyan-100, #e0f7fa);color:var(--cyan-600, #00acc1)}.tql-completion-icon.tql-completion-kind-aggregation[data-v-e29dd6a7]{background:var(--indigo-100, #e8eaf6);color:var(--indigo-600, #3949ab)}.tql-completion-icon.tql-completion-kind-logicalOp[data-v-e29dd6a7]{background:var(--yellow-100, #fffde7);color:var(--yellow-700, #fbc02d)}.tql-completion-icon.tql-completion-kind-typeHint[data-v-e29dd6a7]{background:var(--teal-100, #e0f2f1);color:var(--teal-600, #00897b)}.tql-completion-icon.tql-completion-kind-visualization[data-v-e29dd6a7]{background:var(--red-100, #ffebee);color:var(--red-600, #e53935)}.tql-completion-content[data-v-e29dd6a7]{flex:1;display:flex;flex-direction:column;min-width:0}.tql-completion-label[data-v-e29dd6a7]{font-family:Courier New,monospace;font-size:.875rem;font-weight:500;color:var(--text-color, #333);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tql-completion-detail[data-v-e29dd6a7]{font-size:.75rem;color:var(--text-color-secondary, #666);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tql-completion-kind-badge[data-v-e29dd6a7]{font-size:.625rem;padding:.125rem .375rem;background:var(--surface-100, #f5f5f5);border-radius:3px;color:var(--text-color-secondary, #666);text-transform:uppercase;letter-spacing:.025em}.tql-completion-docs[data-v-e29dd6a7]{padding:.5rem .75rem;border-top:1px solid var(--surface-border, #ddd);background:var(--surface-50, #fafafa);font-size:.75rem;color:var(--text-color-secondary, #666);max-height:60px;overflow-y:auto}.tql-preview[data-v-bae811e6]{font-family:ui-monospace,monospace;font-size:13px;line-height:1.5;padding:12px;border:1px solid var(--tql-preview-border, #e0e0e0);border-radius:6px;background:var(--tql-preview-bg, #fafafa)}.tql-preview--compact[data-v-bae811e6]{padding:8px;font-size:12px}.tql-preview--empty[data-v-bae811e6]{color:var(--tql-preview-muted, #888)}.tql-preview__empty[data-v-bae811e6]{text-align:center;padding:20px}.tql-preview__complexity[data-v-bae811e6]{display:flex;align-items:center;gap:12px;margin-bottom:12px}.tql-preview__complexity-badge[data-v-bae811e6]{padding:2px 8px;border-radius:4px;font-size:11px;font-weight:600;text-transform:uppercase}.tql-preview--low .tql-preview__complexity-badge[data-v-bae811e6]{background:var(--tql-preview-low-bg, #e8f5e9);color:var(--tql-preview-low-text, #2e7d32)}.tql-preview--medium .tql-preview__complexity-badge[data-v-bae811e6]{background:var(--tql-preview-medium-bg, #fff3e0);color:var(--tql-preview-medium-text, #e65100)}.tql-preview--high .tql-preview__complexity-badge[data-v-bae811e6]{background:var(--tql-preview-high-bg, #ffebee);color:var(--tql-preview-high-text, #c62828)}.tql-preview__field-count[data-v-bae811e6]{color:var(--tql-preview-muted, #666);font-size:12px}.tql-preview__warnings[data-v-bae811e6]{margin-bottom:12px}.tql-preview__warning[data-v-bae811e6]{display:flex;align-items:flex-start;gap:8px;padding:6px 8px;border-radius:4px;margin-bottom:4px}.tql-preview__warning--info[data-v-bae811e6]{background:var(--tql-preview-info-bg, #e3f2fd);color:var(--tql-preview-info-text, #1565c0)}.tql-preview__warning--warning[data-v-bae811e6]{background:var(--tql-preview-warning-bg, #fff3e0);color:var(--tql-preview-warning-text, #e65100)}.tql-preview__warning--error[data-v-bae811e6]{background:var(--tql-preview-error-bg, #ffebee);color:var(--tql-preview-error-text, #c62828)}.tql-preview__warning-icon[data-v-bae811e6]{font-weight:700;width:16px;text-align:center}.tql-preview__warning-text[data-v-bae811e6]{flex:1}.tql-preview__suggestions[data-v-bae811e6]{margin-bottom:12px;padding:8px;background:var(--tql-preview-suggestion-bg, #f5f5f5);border-radius:4px}.tql-preview__suggestions-header[data-v-bae811e6]{font-weight:600;margin-bottom:4px;color:var(--tql-preview-header, #333)}.tql-preview__suggestions-list[data-v-bae811e6]{margin:0;padding-left:20px}.tql-preview__suggestion[data-v-bae811e6]{color:var(--tql-preview-muted, #666);margin:2px 0}.tql-preview__tree[data-v-bae811e6]{margin-bottom:12px}.tql-preview__tree-header[data-v-bae811e6],.tql-preview__fields-header[data-v-bae811e6],.tql-preview__operators-header[data-v-bae811e6]{font-weight:600;margin-bottom:8px;color:var(--tql-preview-header, #333);font-size:12px;text-transform:uppercase;letter-spacing:.5px}.tql-preview__tree-content[data-v-bae811e6]{border:1px solid var(--tql-preview-tree-border, #e0e0e0);border-radius:4px;background:var(--tql-preview-tree-bg, #fff);padding:8px}.tql-preview__node[data-v-bae811e6]{margin:2px 0}.tql-preview__node-content[data-v-bae811e6]{display:flex;align-items:center;gap:4px;padding:2px 4px;border-radius:3px;cursor:pointer}.tql-preview__node-content[data-v-bae811e6]:hover{background:var(--tql-preview-node-hover, #f0f0f0)}.tql-preview__node-toggle[data-v-bae811e6]{width:16px;height:16px;border:none;background:var(--tql-preview-toggle-bg, #e0e0e0);border-radius:3px;cursor:pointer;font-size:12px;line-height:1;display:flex;align-items:center;justify-content:center}.tql-preview__node-toggle[data-v-bae811e6]:hover{background:var(--tql-preview-toggle-hover, #d0d0d0)}.tql-preview__node-toggle--leaf[data-v-bae811e6]{visibility:hidden}.tql-preview__node-type[data-v-bae811e6]{font-weight:600;color:var(--tql-preview-node-type, #666)}.tql-preview__node--and .tql-preview__node-type[data-v-bae811e6]{color:var(--tql-preview-and, #1976d2)}.tql-preview__node--or .tql-preview__node-type[data-v-bae811e6]{color:var(--tql-preview-or, #7b1fa2)}.tql-preview__node--not .tql-preview__node-type[data-v-bae811e6]{color:var(--tql-preview-not, #c62828)}.tql-preview__node--group .tql-preview__node-type[data-v-bae811e6]{color:var(--tql-preview-group, #666)}.tql-preview__node-label[data-v-bae811e6]{flex:1}.tql-preview__fields-list[data-v-bae811e6]{display:flex;flex-direction:column;gap:4px}.tql-preview__field[data-v-bae811e6]{display:flex;align-items:center;gap:6px;padding:4px 8px;background:var(--tql-preview-field-bg, #fff);border:1px solid var(--tql-preview-field-border, #e0e0e0);border-radius:4px;cursor:pointer}.tql-preview__field[data-v-bae811e6]:hover{background:var(--tql-preview-field-hover, #f5f5f5)}.tql-preview__field-name[data-v-bae811e6]{font-weight:600;color:var(--tql-preview-field-name, #1976d2)}.tql-preview__field-mutators[data-v-bae811e6]{color:var(--tql-preview-mutator, #7b1fa2)}.tql-preview__field-operator[data-v-bae811e6]{color:var(--tql-preview-operator, #666)}.tql-preview__field-value[data-v-bae811e6]{color:var(--tql-preview-value, #2e7d32)}.tql-preview__match-type[data-v-bae811e6]{margin-left:auto;padding:1px 4px;border-radius:3px;font-size:10px;font-weight:600;background:var(--tql-preview-match-bg, #f0f0f0)}.tql-preview__match-type--exact[data-v-bae811e6]{background:var(--tql-preview-match-exact-bg, #e8f5e9);color:var(--tql-preview-match-exact, #2e7d32)}.tql-preview__match-type--wildcard[data-v-bae811e6]{background:var(--tql-preview-match-wildcard-bg, #fff3e0);color:var(--tql-preview-match-wildcard, #e65100)}.tql-preview__match-type--regex[data-v-bae811e6]{background:var(--tql-preview-match-regex-bg, #fce4ec);color:var(--tql-preview-match-regex, #c2185b)}.tql-preview__match-type--range[data-v-bae811e6]{background:var(--tql-preview-match-range-bg, #e3f2fd);color:var(--tql-preview-match-range, #1565c0)}.tql-preview__match-type--cidr[data-v-bae811e6]{background:var(--tql-preview-match-cidr-bg, #f3e5f5);color:var(--tql-preview-match-cidr, #7b1fa2)}.tql-preview__operators-list[data-v-bae811e6]{display:flex;flex-wrap:wrap;gap:8px}.tql-preview__operator[data-v-bae811e6]{padding:2px 6px;background:var(--tql-preview-op-bg, #f0f0f0);border-radius:3px;font-size:11px}.tql-input-wrapper[data-v-c7135df3]{position:relative;display:flex;align-items:flex-start;border:1px solid var(--surface-border, #ddd);border-radius:var(--border-radius, 6px);background:var(--surface-ground, #fff);transition:border-color .2s,box-shadow .2s}.tql-input-wrapper.tql-input-has-min-height[data-v-c7135df3],.tql-input-wrapper.tql-input-has-min-height .tql-input[data-v-c7135df3]{min-height:var(--tql-min-height)}.tql-input-wrapper.tql-input-focused[data-v-c7135df3]{border-color:var(--primary-color, #3b82f6);box-shadow:0 0 0 2px var(--primary-100, rgba(59, 130, 246, .2))}.tql-input-wrapper.tql-input-invalid[data-v-c7135df3]{border-color:var(--red-500, #ef4444)}.tql-input-wrapper.tql-input-invalid.tql-input-focused[data-v-c7135df3]{box-shadow:0 0 0 2px var(--red-100, rgba(239, 68, 68, .2))}.tql-input-wrapper.tql-input-valid[data-v-c7135df3]{border-color:var(--tql-valid-color, var(--green-500, #22c55e))}.tql-input-wrapper.tql-input-valid.tql-input-focused[data-v-c7135df3]{box-shadow:0 0 0 2px var(--green-100, rgba(34, 197, 94, .2))}.tql-input-wrapper.tql-input-slow[data-v-c7135df3]{border-color:var(--tql-slow-color, var(--yellow-500, #eab308))}.tql-input-wrapper.tql-input-slow.tql-input-focused[data-v-c7135df3]{box-shadow:0 0 0 2px var(--yellow-100, rgba(234, 179, 8, .2))}.tql-slow-indicator[data-v-c7135df3]{color:var(--tql-slow-color, var(--yellow-500, #eab308));margin-left:4px;cursor:help}.tql-input-wrapper.tql-input-disabled[data-v-c7135df3]{opacity:.6;cursor:not-allowed}.tql-highlight-layer[data-v-c7135df3]{position:absolute;top:0;left:0;right:0;bottom:0;padding:.5rem .75rem;pointer-events:none;white-space:pre-wrap;word-wrap:break-word;font-family:Courier New,monospace;font-size:.875rem;line-height:1.5;overflow:hidden}.tql-input-multiline .tql-highlight-layer[data-v-c7135df3]{overflow-y:auto}.tql-input[data-v-c7135df3]{flex:1;width:100%;padding:.5rem 8rem .5rem .75rem;border:none;background:transparent;font-family:Courier New,monospace;font-size:.875rem;line-height:1.5;color:var(--text-color, #333);outline:none;resize:none}.tql-input.tql-input-highlighted[data-v-c7135df3]{color:transparent;caret-color:var(--text-color, #333)}.tql-input[data-v-c7135df3]::placeholder{color:var(--text-color-secondary, #666);opacity:.7}.tql-input[data-v-c7135df3]:disabled{cursor:not-allowed}.tql-token-field[data-v-c7135df3]{color:var(--tql-field-color, var(--blue-600, #2563eb))}.tql-token-operator[data-v-c7135df3]{color:var(--tql-operator-color, var(--pink-600, #db2777));font-weight:500}.tql-token-logicalOp[data-v-c7135df3]{color:var(--tql-logical-color, var(--purple-600, #9333ea));font-weight:600}.tql-token-collectionOp[data-v-c7135df3]{color:var(--tql-collection-color, var(--indigo-600, #4f46e5));font-weight:600}.tql-token-string[data-v-c7135df3]{color:var(--tql-string-color, var(--green-600, #16a34a))}.tql-token-number[data-v-c7135df3]{color:var(--tql-number-color, var(--orange-600, #ea580c))}.tql-token-boolean[data-v-c7135df3]{color:var(--tql-boolean-color, var(--cyan-600, #0891b2))}.tql-token-null[data-v-c7135df3]{color:var(--tql-null-color, var(--gray-500, #6b7280));font-style:italic}.tql-token-mutator[data-v-c7135df3]{color:var(--tql-mutator-color, var(--teal-600, #0d9488));font-style:italic}.tql-token-aggregation[data-v-c7135df3]{color:var(--tql-aggregation-color, var(--indigo-600, #4f46e5))}.tql-token-keyword[data-v-c7135df3]{color:var(--tql-keyword-color, var(--purple-500, #a855f7))}.tql-token-typeHint[data-v-c7135df3]{color:var(--tql-typehint-color, var(--teal-500, #14b8a6))}.tql-token-visualization[data-v-c7135df3]{color:var(--tql-viz-color, var(--red-500, #ef4444))}.tql-token-arrow[data-v-c7135df3]{color:var(--tql-arrow-color, var(--gray-600, #4b5563));font-weight:600}.tql-token-doubleColon[data-v-c7135df3]{color:var(--tql-colon-color, var(--gray-500, #6b7280))}.tql-token-paren[data-v-c7135df3],.tql-token-bracket[data-v-c7135df3]{color:var(--tql-bracket-color, var(--gray-600, #4b5563))}.tql-token-error[data-v-c7135df3]{color:var(--tql-error-color, var(--red-600, #dc2626));text-decoration:wavy underline;text-decoration-color:var(--red-400, #f87171)}.tql-token-error-range[data-v-c7135df3]{background-color:var(--red-50, #fef2f2)}.tql-token-unknown[data-v-c7135df3]{color:var(--text-color-secondary, #666)}.tql-validation-icon[data-v-c7135df3]{position:absolute;right:.5rem;top:50%;transform:translateY(-50%);display:flex;align-items:center;justify-content:center}.tql-input-multiline .tql-validation-icon[data-v-c7135df3]{top:.75rem;transform:none}.tql-icon-valid[data-v-c7135df3]{color:var(--green-500, #22c55e)}.tql-icon-invalid[data-v-c7135df3]{color:var(--red-500, #ef4444)}.tql-icon-clickable[data-v-c7135df3]{cursor:pointer;transition:transform .2s}.tql-icon-clickable[data-v-c7135df3]:hover{transform:scale(1.2)}.tql-icon-clickable[data-v-c7135df3]:focus{outline:2px solid var(--primary-color, #3b82f6);outline-offset:2px;border-radius:50%}.tql-snippet-button[data-v-c7135df3]{position:absolute;right:2rem;top:50%;transform:translateY(-50%);padding:.25rem;border:none;background:transparent;color:var(--text-color-secondary, #666);cursor:pointer;border-radius:var(--border-radius, 6px);transition:color .2s,background-color .2s}.tql-snippet-button[data-v-c7135df3]:hover{color:var(--primary-color, #3b82f6);background:var(--surface-100, #f5f5f5)}.tql-input-multiline .tql-snippet-button[data-v-c7135df3]{top:.75rem;transform:none}.tql-input-resizable-textarea[data-v-c7135df3]{resize:vertical;min-height:80px}.tql-input-wrapper.tql-input-resizable[data-v-c7135df3]{height:auto}.tql-input-wrapper.tql-input-resizable .tql-highlight-layer[data-v-c7135df3]{height:100%}.tql-validation-messages[data-v-c7135df3]{position:absolute;top:100%;left:0;right:0;margin-top:.25rem;background:var(--surface-overlay, #fff);border:1px solid var(--red-200, #fecaca);border-radius:var(--border-radius, 6px);padding:.5rem;z-index:10;box-shadow:0 2px 8px #0000001a}.tql-input-autocomplete-active .tql-validation-messages[data-v-c7135df3],.tql-input-autocomplete-active .tql-complexity-warnings[data-v-c7135df3],.tql-input-autocomplete-active .tql-history-dropdown[data-v-c7135df3],.tql-input-autocomplete-active .tql-preview-panel[data-v-c7135df3]{display:none!important}.tql-error-message[data-v-c7135df3]{display:flex;align-items:flex-start;gap:.5rem;padding:.25rem 0;font-size:.75rem;color:var(--red-600, #dc2626)}.tql-error-message i[data-v-c7135df3]{margin-top:.125rem;font-size:.75rem}.tql-error-suggestion[data-v-c7135df3]{display:block;color:var(--text-color-secondary, #666);font-style:italic;margin-left:1.25rem}.sr-only[data-v-c7135df3]{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.tql-toolbar[data-v-c7135df3]{position:absolute;right:.5rem;top:50%;transform:translateY(-50%);display:flex;align-items:center;gap:.25rem}.tql-input-multiline .tql-toolbar[data-v-c7135df3]{top:.5rem;transform:none}.tql-toolbar-button[data-v-c7135df3]{padding:.25rem;border:none;background:transparent;color:var(--text-color-secondary, #666);cursor:pointer;border-radius:var(--border-radius, 4px);transition:color .2s,background-color .2s;display:flex;align-items:center;justify-content:center}.tql-toolbar-button[data-v-c7135df3]:hover{color:var(--primary-color, #3b82f6);background:var(--surface-100, #f5f5f5)}.tql-toolbar-button--active[data-v-c7135df3]{color:var(--primary-color, #3b82f6);background:var(--primary-100, rgba(59, 130, 246, .1))}.tql-validation-status[data-v-c7135df3]{display:flex;align-items:center}.tql-complexity-badge[data-v-c7135df3]{display:flex;align-items:center;gap:.25rem;padding:.125rem .5rem;border-radius:9999px;font-size:.625rem;font-weight:600;text-transform:uppercase;letter-spacing:.025em}.tql-complexity-low[data-v-c7135df3]{background:var(--green-100, #dcfce7);color:var(--green-700, #15803d)}.tql-complexity-medium[data-v-c7135df3]{background:var(--yellow-100, #fef9c3);color:var(--yellow-700, #a16207)}.tql-complexity-high[data-v-c7135df3]{background:var(--red-100, #fee2e2);color:var(--red-700, #b91c1c)}.tql-complexity-count[data-v-c7135df3]{display:inline-flex;align-items:center;justify-content:center;min-width:1rem;height:1rem;padding:0 .25rem;border-radius:9999px;background:currentColor;color:#fff;font-size:.5rem}.tql-complexity-low .tql-complexity-count[data-v-c7135df3]{background:var(--green-600, #16a34a)}.tql-complexity-medium .tql-complexity-count[data-v-c7135df3]{background:var(--yellow-600, #ca8a04)}.tql-complexity-high .tql-complexity-count[data-v-c7135df3]{background:var(--red-600, #dc2626)}.tql-complexity-warnings[data-v-c7135df3]{position:absolute;top:100%;left:0;right:0;margin-top:.25rem;background:var(--surface-overlay, #fff);border:1px solid var(--surface-border, #e0e0e0);border-radius:var(--border-radius, 6px);padding:.5rem;z-index:9;box-shadow:0 2px 8px #0000001a}.tql-complexity-warning[data-v-c7135df3]{display:flex;align-items:flex-start;gap:.5rem;padding:.25rem 0;font-size:.75rem}.tql-complexity-warning i[data-v-c7135df3]{margin-top:.125rem;font-size:.75rem}.tql-complexity-warning--info[data-v-c7135df3]{color:var(--blue-600, #2563eb)}.tql-complexity-warning--warning[data-v-c7135df3]{color:var(--yellow-600, #ca8a04)}.tql-complexity-warning--error[data-v-c7135df3]{color:var(--red-600, #dc2626)}.tql-complexity-suggestions[data-v-c7135df3]{margin-top:.5rem;padding-top:.5rem;border-top:1px solid var(--surface-border, #e0e0e0)}.tql-complexity-suggestion[data-v-c7135df3]{display:flex;align-items:flex-start;gap:.5rem;padding:.25rem 0;font-size:.75rem;color:var(--text-color-secondary, #666)}.tql-complexity-suggestion i[data-v-c7135df3]{color:var(--yellow-500, #eab308)}.tql-history-dropdown[data-v-c7135df3]{position:absolute;top:100%;left:0;right:0;margin-top:.25rem;background:var(--surface-overlay, #fff);border:1px solid var(--surface-border, #e0e0e0);border-radius:var(--border-radius, 6px);z-index:20;box-shadow:0 4px 12px #00000026;max-height:300px;overflow:hidden;display:flex;flex-direction:column}.tql-history-header[data-v-c7135df3]{padding:.5rem;border-bottom:1px solid var(--surface-border, #e0e0e0)}.tql-history-search[data-v-c7135df3]{width:100%;padding:.375rem .5rem;border:1px solid var(--surface-border, #e0e0e0);border-radius:var(--border-radius, 4px);font-size:.75rem;outline:none}.tql-history-search[data-v-c7135df3]:focus{border-color:var(--primary-color, #3b82f6)}.tql-history-list[data-v-c7135df3]{overflow-y:auto;flex:1}.tql-history-item[data-v-c7135df3]{display:flex;align-items:flex-start;gap:.5rem;padding:.5rem;cursor:pointer;border-bottom:1px solid var(--surface-50, #f9fafb);transition:background-color .15s}.tql-history-item[data-v-c7135df3]:hover{background:var(--surface-100, #f5f5f5)}.tql-history-item--favorite[data-v-c7135df3]{background:var(--yellow-50, #fefce8)}.tql-history-item--favorite[data-v-c7135df3]:hover{background:var(--yellow-100, #fef9c3)}.tql-history-favorite[data-v-c7135df3]{padding:.125rem;border:none;background:transparent;color:var(--text-color-secondary, #999);cursor:pointer;flex-shrink:0}.tql-history-favorite[data-v-c7135df3]:hover,.tql-history-item--favorite .tql-history-favorite[data-v-c7135df3]{color:var(--yellow-500, #eab308)}.tql-history-content[data-v-c7135df3]{flex:1;min-width:0}.tql-history-label[data-v-c7135df3]{font-size:.6875rem;font-weight:600;color:var(--text-color, #333);margin-bottom:.125rem}.tql-history-query[data-v-c7135df3]{font-family:Courier New,monospace;font-size:.6875rem;color:var(--text-color-secondary, #666);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tql-history-time[data-v-c7135df3]{font-size:.625rem;color:var(--text-color-secondary, #999);flex-shrink:0}.tql-history-empty[data-v-c7135df3]{padding:1rem;text-align:center;color:var(--text-color-secondary, #666);font-size:.75rem}.tql-preview-panel[data-v-c7135df3]{position:absolute;top:100%;left:0;right:0;margin-top:.25rem;background:var(--surface-overlay, #fff);border:1px solid var(--surface-border, #e0e0e0);border-radius:var(--border-radius, 6px);z-index:15;box-shadow:0 4px 12px #00000026;max-height:400px;overflow:hidden;display:flex;flex-direction:column}.tql-preview-header[data-v-c7135df3]{display:flex;align-items:center;justify-content:space-between;padding:.5rem .75rem;border-bottom:1px solid var(--surface-border, #e0e0e0);font-size:.75rem;font-weight:600;color:var(--text-color, #333)}.tql-preview-close[data-v-c7135df3]{padding:.125rem;border:none;background:transparent;color:var(--text-color-secondary, #666);cursor:pointer;border-radius:var(--border-radius, 4px)}.tql-preview-close[data-v-c7135df3]:hover{background:var(--surface-100, #f5f5f5);color:var(--text-color, #333)}.tql-preview-panel[data-v-c7135df3] .tql-preview{border:none;border-radius:0;max-height:350px;overflow-y:auto}.tql-validation-messages-portal{position:fixed;top:var(--tql-msg-top, 0);left:var(--tql-msg-left, 0);width:var(--tql-msg-width, auto);z-index:9998;background:var(--surface-overlay, #fff);border:2px solid var(--red-500, #ef4444);border-radius:var(--border-radius, 6px);padding:.5rem;box-shadow:0 4px 12px #00000026}.tql-validation-messages-portal .tql-error-message{display:flex;align-items:flex-start;gap:.5rem;padding:.25rem 0;font-size:.75rem;color:var(--red-600, #dc2626)}.tql-validation-messages-portal .tql-error-message i{margin-top:.125rem;font-size:.75rem}.tql-validation-messages-portal .tql-error-suggestion{display:block;color:var(--text-color-secondary, #6b7280);font-style:italic;margin-left:1.25rem}
@@ -4,6 +4,7 @@ import st from "./components/TqlPreview.vue.js";
4
4
  import { useTqlInput as ut } from "./useTqlInput.js";
5
5
  import { useQueryHistory as rt } from "./composables/useQueryHistory.js";
6
6
  import { analyzeComplexity as ct } from "../src/complexityAnalyzer.js";
7
+ import { ENRICHMENT_MUTATORS as SLOW_MUTATORS } from "../src/tokenizer.js";
7
8
  const dt = {
8
9
  key: 0,
9
10
  class: "tql-highlight-layer",
@@ -107,7 +108,17 @@ const dt = {
107
108
  low: "Low",
108
109
  medium: "Medium",
109
110
  high: "High"
110
- }[g.value.score] || ""), ve = H(() => $ ? T.value.trim() ? $.searchHistory(T.value).slice(0, 10) : $.getRecentQueries(10) : []);
111
+ }[g.value.score] || ""), queryState = H(() => {
112
+ if (!u.value.trim()) return "empty";
113
+ if (E.value) return "invalid";
114
+ // Check for enrichment/slow mutators in tokens
115
+ const hasSlow = Ie.value.some(t =>
116
+ t.type === "mutator" && SLOW_MUTATORS.includes(t.value.toLowerCase())
117
+ );
118
+ if (hasSlow) return "slow";
119
+ if (ie.value) return "valid";
120
+ return "unknown";
121
+ }), ve = H(() => $ ? T.value.trim() ? $.searchHistory(T.value).slice(0, 10) : $.getRecentQueries(10) : []);
111
122
  function Qe(e) {
112
123
  u.value = e.query, q("update:modelValue", e.query), ce(e.query), D.value = !1, T.value = "", P(() => {
113
124
  var t;
@@ -397,6 +408,8 @@ const dt = {
397
408
  {
398
409
  "tql-input-focused": x.value,
399
410
  "tql-input-invalid": r(E) && n.showValidation,
411
+ "tql-input-valid": queryState.value === "valid" && n.showValidation && u.value.trim(),
412
+ "tql-input-slow": queryState.value === "slow" && n.showValidation && u.value.trim(),
400
413
  "tql-input-disabled": n.disabled,
401
414
  "tql-input-multiline": n.multiline,
402
415
  "tql-input-autocomplete-active": r(C),
@@ -506,8 +519,13 @@ const dt = {
506
519
  c("i", { class: "pi pi-code" }, null, -1)
507
520
  ])])) : m("", !0),
508
521
  n.showValidation && u.value ? (a(), s("div", gt, [
509
- r(ie) ? (a(), s("i", wt)) : (a(), s("i", {
510
- key: 1,
522
+ queryState.value === "slow" ? (a(), s("i", {
523
+ key: 0,
524
+ class: "pi pi-clock tql-slow-indicator",
525
+ title: "This query uses enrichment mutators (geo, nslookup) which require post-processing and may be slower"
526
+ })) : m("", !0),
527
+ r(ie) && queryState.value !== "slow" ? (a(), s("i", { key: 1, class: "pi pi-check-circle tql-icon-valid", "aria-label": "Query is valid" })) : queryState.value === "slow" && r(ie) ? (a(), s("i", { key: 2, class: "pi pi-check-circle tql-icon-valid", "aria-label": "Query is valid (with enrichment)" })) : (a(), s("i", {
528
+ key: 3,
511
529
  class: "pi pi-exclamation-circle tql-icon-invalid tql-icon-clickable",
512
530
  role: "button",
513
531
  tabindex: "0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tellaro/tql",
3
- "version": "0.1.14",
3
+ "version": "0.1.16",
4
4
  "description": "Tellaro Query Language (TQL) parser, validator, and autocomplete for JavaScript/TypeScript",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",