@tellaro/tql 3.0.1 → 3.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -276,7 +276,16 @@ export interface TqlValidationResult {
276
276
  }
277
277
  export interface TqlValidationOptions {
278
278
  availableFields?: string[];
279
- fieldTypes?: Record<string, TqlFieldType>;
279
+ /**
280
+ * Declared type per field name.
281
+ *
282
+ * An ARRAY means the field is mapped more than one way across the index pattern the
283
+ * schema describes -- `text` in one index and `keyword` in another is the ordinary way
284
+ * to get there, and the engines answer such a field with a disjunction over both forms
285
+ * (see `TypeDivergence` in the Rust engine). A single type is the common case and
286
+ * behaves exactly as before.
287
+ */
288
+ fieldTypes?: Record<string, TqlFieldType | TqlFieldType[]>;
280
289
  strictMode?: boolean;
281
290
  allowAggregations?: boolean;
282
291
  allowMutators?: boolean;
@@ -82,40 +82,7 @@ export declare const CASE_SENSITIVE_WHOLE_VALUE_OPERATORS: TqlComparisonOperator
82
82
  export declare const CASE_SENSITIVE_MEMBERSHIP_OPERATORS: TqlComparisonOperator[];
83
83
  /** Every spelling of every case-SENSITIVE operator — the union of the two families above. */
84
84
  export declare const CASE_SENSITIVE_OPERATORS: TqlComparisonOperator[];
85
- /**
86
- * Whether a field offers a CASE-PRESERVING form, as far as the supplied schema shows.
87
- *
88
- * This is the field half of the engines' rule, on its own because the two `_cs` operator
89
- * families consume it differently (see `caseSensitiveSupport`, which is the function you
90
- * probably want).
91
- *
92
- * * `"present"` — the field itself is `keyword`, or it has a `keyword` subfield. One whole
93
- * value, one indexed term, case intact.
94
- * * `"absent"` — no such form. That covers analyzed `text` with no `keyword` sibling AND
95
- * every non-string type: a `long`, `ip`, `date` or `boolean` field has no un-analyzed
96
- * STRING form either. Whether that matters depends on the operator, which is why this
97
- * function does not decide it.
98
- * * `"unknown"` — no `fieldTypes` was supplied, or this field is not in it.
99
- *
100
- * Mirrors `FieldMappings::unanalyzed_form` (Rust) and `FieldMapping.unanalyzed_field`
101
- * (Python) as closely as a flat name→type map allows. Two documented divergences, both of
102
- * which fail quiet — see `caseSensitiveSupport`.
103
- *
104
- * ## The sibling test is on TYPE, not on the name `.keyword`
105
- *
106
- * Both engines scan a field's declared subfields for one whose TYPE is `keyword` (Rust:
107
- * `subfield_of(field, |t| *t == FieldType::Keyword)`; Python: `keyword_field`, set for any
108
- * subfield with `"type": "keyword"`). A multifield named `.raw` or `.kw` answers exactly as
109
- * well as one named `.keyword`, and hard-coding the name would report `"absent"` on a working
110
- * mapping. So this walks the map for `${field}.` prefixed entries and looks at their type.
111
- *
112
- * ## One level down, matching the engines
113
- *
114
- * A subfield is a DIRECT child: both engines read a declared `subfields` map, so
115
- * `f.sub.deep` is not a subfield of `f` to either of them. Scanning descendants at any depth
116
- * would report `"present"` for a mapping the engines refuse.
117
- */
118
- export declare function casePreservingForm(field: string, fieldTypes?: Record<string, TqlFieldType>): "present" | "absent" | "unknown";
85
+ export declare function casePreservingForm(field: string, fieldTypes?: Record<string, TqlFieldType | TqlFieldType[]>): "present" | "absent" | "unknown";
119
86
  /**
120
87
  * Whether a field can answer a case-SENSITIVE operator, as far as the supplied schema shows.
121
88
  *
@@ -180,7 +147,7 @@ export declare function casePreservingForm(field: string, fieldTypes?: Record<st
180
147
  * distinguish the two `text` shapes it must ALSO contain the multifield entries
181
148
  * (`message.keyword`), which is what an index-mapping flattener naturally produces.
182
149
  */
183
- export declare function caseSensitiveSupport(field: string, operator: TqlComparisonOperator, fieldTypes?: Record<string, TqlFieldType>): CaseSensitiveSupport;
150
+ export declare function caseSensitiveSupport(field: string, operator: TqlComparisonOperator, fieldTypes?: Record<string, TqlFieldType | TqlFieldType[]>): CaseSensitiveSupport;
184
151
  export declare class TqlValidator {
185
152
  private options;
186
153
  private errors;
@@ -1,5 +1,5 @@
1
- import { parseTql as w } from "./parser.js";
2
- const u = 1e3, r = [
1
+ import { parseTql as p } from "./parser.js";
2
+ const g = 1e3, r = [
3
3
  "text",
4
4
  "keyword",
5
5
  "long",
@@ -17,7 +17,7 @@ const u = 1e3, r = [
17
17
  "nested",
18
18
  "object",
19
19
  "geo_point"
20
- ], h = [
20
+ ], _ = [
21
21
  "long",
22
22
  "integer",
23
23
  "short",
@@ -27,7 +27,7 @@ const u = 1e3, r = [
27
27
  "half_float",
28
28
  "scaled_float",
29
29
  "unsigned_long"
30
- ], l = [...h, "date", "keyword"], c = [...h, "date"], y = {
30
+ ], l = [..._, "date", "keyword"], u = [..._, "date"], b = {
31
31
  // Equality (all types) — and now that is what it says.
32
32
  //
33
33
  // The transcribed twelve-element list this replaces claimed "all types" while omitting
@@ -157,9 +157,9 @@ const u = 1e3, r = [
157
157
  // `keyword` and `text` stay out, and that is a decision rather than an omission: Python's
158
158
  // `keyword_ops` excludes `between` while including `gte`, which is the divergence pinned
159
159
  // in `tests/collectionAndNullSemantics.spec.ts`.
160
- between: c,
161
- not_between: c,
162
- "!between": c,
160
+ between: u,
161
+ not_between: u,
162
+ "!between": u,
163
163
  // Membership — the same set as `eq`, because `in` IS a disjunction of equalities and both
164
164
  // evaluators implement it that way. The transcribed list this replaces omitted the five
165
165
  // narrow numeric widths plus `nested`, `object` and `geo_point`.
@@ -226,11 +226,11 @@ const u = 1e3, r = [
226
226
  "!any": r,
227
227
  "!all": r,
228
228
  "!none": r
229
- }, g = {
229
+ }, h = {
230
230
  cidr: ["ip"],
231
231
  not_cidr: ["ip"],
232
232
  "!cidr": ["ip"]
233
- }, p = [
233
+ }, m = [
234
234
  "contains_cs",
235
235
  "not_contains_cs",
236
236
  "!contains_cs",
@@ -240,35 +240,44 @@ const u = 1e3, r = [
240
240
  "endswith_cs",
241
241
  "not_endswith_cs",
242
242
  "!endswith_cs"
243
- ], _ = [
243
+ ], f = [
244
244
  "in_cs",
245
245
  "not_in_cs",
246
246
  "!in_cs"
247
- ], b = [
248
- ...p,
249
- ..._
247
+ ], v = [
248
+ ...m,
249
+ ...f
250
250
  ];
251
- function m(o, t) {
251
+ function c(o, t) {
252
+ const e = t?.[o];
253
+ return e === void 0 ? [] : Array.isArray(e) ? e : [e];
254
+ }
255
+ function E(o, t) {
252
256
  if (!t)
253
257
  return "unknown";
254
- const e = t[o];
255
- if (!e)
258
+ const e = c(o, t);
259
+ if (e.length === 0)
256
260
  return "unknown";
257
- if (e === "keyword" || t[`${o}.keyword`] === "keyword")
261
+ if (e.includes("keyword") || c(`${o}.keyword`, t).includes("keyword"))
258
262
  return "present";
259
263
  const i = `${o}.`;
260
264
  for (const [n, s] of Object.entries(t))
261
- if (!(s !== "keyword" || !n.startsWith(i)) && !n.slice(i.length).includes("."))
265
+ if (!(!(Array.isArray(s) ? s : [s]).includes("keyword") || !n.startsWith(i)) && !n.slice(i.length).includes("."))
262
266
  return "present";
263
267
  return "absent";
264
268
  }
265
- function v(o, t, e) {
266
- if (!b.includes(t))
269
+ function k(o, t, e) {
270
+ if (!v.includes(t))
267
271
  return "supported";
268
- const i = m(o, e);
269
- return i !== "absent" ? i === "present" ? "supported" : "unknown" : _.includes(t) ? e?.[o] === "text" ? "unsupported" : "supported" : "unsupported";
272
+ const i = E(o, e);
273
+ if (i !== "absent")
274
+ return i === "present" ? "supported" : "unknown";
275
+ if (!f.includes(t))
276
+ return "unsupported";
277
+ const n = c(o, e);
278
+ return n.length > 0 && n.every((s) => s === "text") ? "unsupported" : "supported";
270
279
  }
271
- const E = {
280
+ const T = {
272
281
  eq: ["string", "number", "boolean"],
273
282
  ne: ["string", "number", "boolean"],
274
283
  "=": ["string", "number", "boolean"],
@@ -337,7 +346,7 @@ const E = {
337
346
  "!all": ["string", "number", "boolean"],
338
347
  "!none": ["string", "number", "boolean"]
339
348
  };
340
- class k {
349
+ class A {
341
350
  options;
342
351
  errors;
343
352
  warnings;
@@ -350,7 +359,7 @@ class k {
350
359
  validate(t) {
351
360
  if (this.errors = [], this.warnings = [], !t)
352
361
  return { valid: !0, errors: [], warnings: [] };
353
- const e = w(t);
362
+ const e = p(t);
354
363
  return this.errors.push(...e.errors), this.warnings.push(...e.warnings), e.ast && this.validateExpression(e.ast), {
355
364
  valid: this.errors.length === 0,
356
365
  errors: this.errors,
@@ -477,22 +486,22 @@ class k {
477
486
  "UNKNOWN_FIELD",
478
487
  `Did you mean: ${this.suggestField(e)}`
479
488
  ), this.options.fieldTypes && t.field.type === "field") {
480
- const n = this.options.fieldTypes[e];
481
- if (n) {
482
- const s = g[t.operator];
483
- s && !s.includes(n) && this.addError(
484
- `Operator '${t.operator}' requires field type ${s.join(" or ")}, but '${e}' is type '${n}'`,
489
+ const n = c(e, this.options.fieldTypes), s = n.join(" or ");
490
+ if (n.length > 0) {
491
+ const a = h[t.operator];
492
+ a && !n.some((d) => a.includes(d)) && this.addError(
493
+ `Operator '${t.operator}' requires field type ${a.join(" or ")}, but '${e}' is type '${s}'`,
485
494
  t.start,
486
495
  t.end,
487
496
  1,
488
497
  1,
489
498
  "TYPE_MISMATCH",
490
- `Use '${t.operator}' only with ${s.join(" or ")} fields`
499
+ `Use '${t.operator}' only with ${a.join(" or ")} fields`
491
500
  );
492
501
  }
493
502
  }
494
503
  let i = !1;
495
- if (t.field.type === "field" && v(e, t.operator, this.options.fieldTypes) === "unsupported") {
504
+ if (t.field.type === "field" && k(e, t.operator, this.options.fieldTypes) === "unsupported") {
496
505
  i = !0;
497
506
  const n = t.operator.replace(/_cs$/, ""), s = this.options.fieldTypes?.[e], a = s === "text" ? `'${e}' is an analyzed text field with no case-preserving subfield in the supplied schema, so the analyzer lowercased every token it indexed and the case '${t.operator}' asks about is not in the index` : `'${e}' is type '${s}' and has no un-analyzed string form, which is what '${t.operator}' matches against`;
498
507
  this.addWarning(
@@ -513,22 +522,22 @@ class k {
513
522
  );
514
523
  }
515
524
  if (this.options.fieldTypes && t.field.type === "field") {
516
- const n = this.options.fieldTypes[e];
517
- if (n) {
518
- const s = y[t.operator], a = g[t.operator];
519
- !i && !a && s && !s.includes(n) && this.addWarning(
520
- `Operator '${t.operator}' may not work well with field type '${n}'`,
525
+ const n = c(e, this.options.fieldTypes), s = n.join(" or ");
526
+ if (n.length > 0) {
527
+ const a = b[t.operator], d = h[t.operator];
528
+ !i && !d && a && !n.some((y) => a.includes(y)) && this.addWarning(
529
+ `Operator '${t.operator}' may not work well with field type '${s}'`,
521
530
  t.start,
522
531
  t.end,
523
532
  1,
524
533
  1,
525
534
  "TYPE_MISMATCH",
526
- `Consider using a different operator for ${n} fields`
535
+ `Consider using a different operator for ${s} fields`
527
536
  );
528
537
  }
529
538
  }
530
539
  if (!Array.isArray(t.value) && t.value.type === "literal") {
531
- const n = E[t.operator];
540
+ const n = T[t.operator];
532
541
  n && !n.includes(t.value.valueType) && this.addWarning(
533
542
  `Operator '${t.operator}' expects ${n.join(" or ")} value`,
534
543
  t.value.start,
@@ -540,9 +549,9 @@ class k {
540
549
  }
541
550
  if ((t.operator === "regexp" || t.operator === "regex" || t.operator === "not_regexp" || t.operator === "not_regex" || t.operator === "matches" || t.operator === "not_matches") && !Array.isArray(t.value) && t.value.valueType === "string" && typeof t.value.value == "string") {
542
551
  const n = Array.from(t.value.value).length;
543
- if (n > u)
552
+ if (n > g)
544
553
  this.addError(
545
- `Regex pattern is ${n} characters, which exceeds the maximum supported length of ${u}. The query cannot run as written.`,
554
+ `Regex pattern is ${n} characters, which exceeds the maximum supported length of ${g}. The query cannot run as written.`,
546
555
  t.value.start,
547
556
  t.value.end,
548
557
  1,
@@ -622,17 +631,17 @@ class k {
622
631
  });
623
632
  }
624
633
  }
625
- function f(o, t) {
626
- return new k(t).validate(o);
634
+ function w(o, t) {
635
+ return new A(t).validate(o);
627
636
  }
628
- function $(o) {
629
- return f(o).valid;
637
+ function I(o) {
638
+ return w(o).valid;
630
639
  }
631
- function A(o, t) {
632
- const e = f(o, t);
640
+ function S(o, t) {
641
+ const e = w(o, t);
633
642
  return [...e.errors, ...e.warnings];
634
643
  }
635
- function I(o) {
644
+ function R(o) {
636
645
  const t = [];
637
646
  o.valid ? t.push("Query is valid") : t.push("Query has errors:");
638
647
  for (const e of o.errors)
@@ -644,18 +653,18 @@ function I(o) {
644
653
  }
645
654
  export {
646
655
  r as ALL_FIELD_TYPES,
647
- c as BETWEEN_FIELD_TYPES,
648
- _ as CASE_SENSITIVE_MEMBERSHIP_OPERATORS,
649
- b as CASE_SENSITIVE_OPERATORS,
650
- p as CASE_SENSITIVE_WHOLE_VALUE_OPERATORS,
651
- u as MAX_REGEX_PATTERN_LENGTH,
652
- h as NUMERIC_FIELD_TYPES,
656
+ u as BETWEEN_FIELD_TYPES,
657
+ f as CASE_SENSITIVE_MEMBERSHIP_OPERATORS,
658
+ v as CASE_SENSITIVE_OPERATORS,
659
+ m as CASE_SENSITIVE_WHOLE_VALUE_OPERATORS,
660
+ g as MAX_REGEX_PATTERN_LENGTH,
661
+ _ as NUMERIC_FIELD_TYPES,
653
662
  l as ORDERED_FIELD_TYPES,
654
- k as TqlValidator,
655
- m as casePreservingForm,
656
- v as caseSensitiveSupport,
657
- I as formatValidationResult,
658
- A as getTqlIssues,
659
- $ as isValidTqlSyntax,
660
- f as validateTql
663
+ A as TqlValidator,
664
+ E as casePreservingForm,
665
+ k as caseSensitiveSupport,
666
+ R as formatValidationResult,
667
+ S as getTqlIssues,
668
+ I as isValidTqlSyntax,
669
+ w as validateTql
661
670
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tellaro/tql",
3
- "version": "3.0.1",
3
+ "version": "3.0.2",
4
4
  "description": "Tellaro Query Language (TQL) parser, validator, and autocomplete for JavaScript/TypeScript",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",