wizard-ql 3.0.0 → 4.0.1
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.
- package/README.md +6 -3
- package/dist/index.d.ts +0 -1
- package/dist/index.js +104 -87
- package/dist/parser.d.ts +28 -10
- package/dist/spec.d.ts +15 -2
- package/package.json +1 -4
- package/dist/execute.d.ts +0 -10
package/README.md
CHANGED
|
@@ -20,6 +20,9 @@
|
|
|
20
20
|
|
|
21
21
|
[Demo](https://exoRift.github.io/wizard-ql)
|
|
22
22
|
|
|
23
|
+
## Installation
|
|
24
|
+
`npm i wizard-ql`
|
|
25
|
+
|
|
23
26
|
## Examples
|
|
24
27
|
`WizardParser.parse('(rank >= 10 & role = admin) | banned')`
|
|
25
28
|
|
|
@@ -327,13 +330,13 @@ A record mapping field names to (`boolean`, `string`, `number`, `date`). The val
|
|
|
327
330
|
> ```
|
|
328
331
|
|
|
329
332
|
> [!NOTE]
|
|
330
|
-
> A value will only be attempted to be parsed as a date if `'date'` is included in the field's type record.
|
|
331
|
-
>
|
|
333
|
+
> A value will only be attempted to be parsed as a date if `'date'` is included in the field's type record or if the operator is exclusively a date operator.
|
|
334
|
+
> According to the default date parser, if the value is a number, it will be parsed as number of milliseconds. Any non-number character will cause the value to be parsed as a string (See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/Date)
|
|
332
335
|
|
|
333
336
|
The type coercion priority chain is as follows:
|
|
334
337
|
`boolean` -> `date` -> `number` -> `string`
|
|
335
338
|
> [!WARNING]
|
|
336
|
-
> This can cause issues for numbers with leading zeros. If this is a potential problem, make sure to exclude `number` from the types and include `string`
|
|
339
|
+
> This can cause issues for numbers with leading zeros. If this is a potential problem, make sure to exclude `number` from the field types and include `string`
|
|
337
340
|
|
|
338
341
|
### Regarding constraints: `validated` property
|
|
339
342
|
When a field has matched either a key in `types` or a field in `restricted`, the `validated` property on the parsed condition will be true. This is due to a limitation with TypeScript's type checking.
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -185,14 +185,14 @@ class WizardParser {
|
|
|
185
185
|
NOTMATCH: "≇"
|
|
186
186
|
}
|
|
187
187
|
};
|
|
188
|
-
static
|
|
189
|
-
static
|
|
190
|
-
static
|
|
191
|
-
static
|
|
188
|
+
static DEFAULT_QUOTES = ['"', "'", "`"];
|
|
189
|
+
static DEFAULT_NEGATORS = ["!"];
|
|
190
|
+
static DEFAULT_ARRAY_DELIMITERS = [","];
|
|
191
|
+
static DEFAULT_ARRAY_BRACKETS = [
|
|
192
192
|
["[", "]"],
|
|
193
193
|
["{", "}"]
|
|
194
194
|
];
|
|
195
|
-
static
|
|
195
|
+
static DEFAULT_GROUP_BRACKETS = [
|
|
196
196
|
["(", ")"]
|
|
197
197
|
];
|
|
198
198
|
OPERATOR_DICTIONARY;
|
|
@@ -216,6 +216,38 @@ class WizardParser {
|
|
|
216
216
|
if (!config.dialects)
|
|
217
217
|
config.dialects = WizardParser.DEFAULT_DIALECTS;
|
|
218
218
|
}
|
|
219
|
+
const symbolPool = new Set;
|
|
220
|
+
config.symbols ??= {};
|
|
221
|
+
config.symbols.quotes ??= WizardParser.DEFAULT_QUOTES;
|
|
222
|
+
config.symbols.negators ??= WizardParser.DEFAULT_NEGATORS;
|
|
223
|
+
config.symbols.arrayDelimiters ??= WizardParser.DEFAULT_ARRAY_DELIMITERS;
|
|
224
|
+
config.symbols.arrayBrackets ??= WizardParser.DEFAULT_ARRAY_BRACKETS;
|
|
225
|
+
config.symbols.groupBrackets ??= WizardParser.DEFAULT_GROUP_BRACKETS;
|
|
226
|
+
for (const [opening, closing] of config.symbols.arrayBrackets) {
|
|
227
|
+
if (opening === closing)
|
|
228
|
+
throw Error(`Opening array bracket cannot be the same as closing array bracket ("${opening}")`);
|
|
229
|
+
if (symbolPool.has(opening))
|
|
230
|
+
throw Error(`Opening array bracket "${opening}" uses a symbol already defined elsewhere in the parser`);
|
|
231
|
+
if (symbolPool.has(closing))
|
|
232
|
+
throw Error(`Closing array bracket "${closing}" uses a symbol already defined elsewhere in the parser`);
|
|
233
|
+
symbolPool.add(opening);
|
|
234
|
+
symbolPool.add(closing);
|
|
235
|
+
}
|
|
236
|
+
for (const [opening, closing] of config.symbols.groupBrackets) {
|
|
237
|
+
if (opening === closing)
|
|
238
|
+
throw Error(`Opening group bracket cannot be the same as closing group bracket ("${opening}")`);
|
|
239
|
+
if (symbolPool.has(opening))
|
|
240
|
+
throw Error(`Opening group bracket "${opening}" uses a symbol already defined elsewhere in the parser`);
|
|
241
|
+
if (symbolPool.has(closing))
|
|
242
|
+
throw Error(`Closing group bracket "${closing}" uses a symbol already defined elsewhere in the parser`);
|
|
243
|
+
symbolPool.add(opening);
|
|
244
|
+
symbolPool.add(closing);
|
|
245
|
+
}
|
|
246
|
+
for (const quote of config.symbols.quotes) {
|
|
247
|
+
if (symbolPool.has(quote))
|
|
248
|
+
throw Error(`Quote "${quote}" uses a symbol already defined elsewhere in the parser`);
|
|
249
|
+
symbolPool.add(quote);
|
|
250
|
+
}
|
|
219
251
|
this.FIELD_LIST = new Map;
|
|
220
252
|
if (config.caseInsensitive) {
|
|
221
253
|
if (config.types) {
|
|
@@ -230,6 +262,24 @@ class WizardParser {
|
|
|
230
262
|
this.OPERATOR_DICTIONARY = {};
|
|
231
263
|
for (const operationName in config.operators) {
|
|
232
264
|
const operation = config.operators[operationName];
|
|
265
|
+
for (const symbol of symbolPool) {
|
|
266
|
+
if (operationName.includes(symbol))
|
|
267
|
+
throw Error(`Operator "${operationName}" contains symbol "${symbol}"`);
|
|
268
|
+
if (operation.aliases) {
|
|
269
|
+
for (const alias of operation.aliases) {
|
|
270
|
+
if (alias.includes(symbol))
|
|
271
|
+
throw Error(`Operator "${operationName}" alias "${alias}" contains symbol "${symbol}"`);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
if (operation.negationName.includes(symbol))
|
|
275
|
+
throw Error(`Operator "${operation.negationName}" contains symbol "${symbol}"`);
|
|
276
|
+
if (operation.negationAliases) {
|
|
277
|
+
for (const alias of operation.negationAliases) {
|
|
278
|
+
if (alias.includes(symbol))
|
|
279
|
+
throw Error(`Operator "${operation.negationName}" alias "${alias}" contains symbol "${symbol}"`);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
233
283
|
const opDef = {
|
|
234
284
|
name: operationName,
|
|
235
285
|
type: operation.type,
|
|
@@ -263,11 +313,16 @@ class WizardParser {
|
|
|
263
313
|
}
|
|
264
314
|
}
|
|
265
315
|
}
|
|
266
|
-
|
|
267
|
-
|
|
316
|
+
for (const negator of config.symbols.negators) {
|
|
317
|
+
if (symbolPool.has(negator))
|
|
318
|
+
throw Error(`Negator "${negator}" uses a symbol already defined elsewhere in the parser`);
|
|
319
|
+
symbolPool.add(negator);
|
|
320
|
+
}
|
|
321
|
+
this.TOKEN_REGEX = new RegExp(createTokenRegexString(Object.keys(this.OPERATOR_DICTIONARY).concat(config.symbols.groupBrackets.flat()).concat(config.symbols.arrayBrackets.flat()).concat(config.symbols.negators).concat(config.symbols.arrayDelimiters), config.symbols.quotes), "g");
|
|
322
|
+
const quoteRegexStr = createQuoteRegexString(config.symbols.quotes);
|
|
268
323
|
this.QUOTE_REGEX = new RegExp(quoteRegexStr);
|
|
269
324
|
this.QUOTE_EDGE_REGEX = new RegExp(`^${quoteRegexStr}$`);
|
|
270
|
-
this.ARRAY_DELIMITER_REGEX = new RegExp(createArrayDelimitRegexString(
|
|
325
|
+
this.ARRAY_DELIMITER_REGEX = new RegExp(createArrayDelimitRegexString(config.symbols.quotes, config.symbols.arrayDelimiters), "g");
|
|
271
326
|
Object.freeze(config);
|
|
272
327
|
}
|
|
273
328
|
static pushSanitized(array, item, index) {
|
|
@@ -293,7 +348,7 @@ class WizardParser {
|
|
|
293
348
|
let lastMatchEnd = null;
|
|
294
349
|
for (const match of matches) {
|
|
295
350
|
WizardParser.pushSanitized(tokens, expression.slice(lastMatchEnd ?? 0, match.index), lastMatchEnd === null ? 0 : lastMatchEnd);
|
|
296
|
-
if (
|
|
351
|
+
if (this.CONFIG.symbols.arrayBrackets.some(([o]) => match[0] === o)) {
|
|
297
352
|
const startToken = {
|
|
298
353
|
content: match[0],
|
|
299
354
|
index: match.index
|
|
@@ -304,7 +359,7 @@ class WizardParser {
|
|
|
304
359
|
for (const submatch of matches) {
|
|
305
360
|
if (match[0] === submatch[0] || match[0] === submatch[0])
|
|
306
361
|
++subopenings;
|
|
307
|
-
if (
|
|
362
|
+
if (this.CONFIG.symbols.arrayBrackets.some(([o, c]) => match[0] === o && submatch[0] === c)) {
|
|
308
363
|
if (subopenings)
|
|
309
364
|
--subopenings;
|
|
310
365
|
else {
|
|
@@ -419,7 +474,7 @@ class WizardParser {
|
|
|
419
474
|
throw new Error("operator");
|
|
420
475
|
if (!fieldHits)
|
|
421
476
|
throw new Error("field");
|
|
422
|
-
throw new Error("
|
|
477
|
+
throw new Error("disagreement");
|
|
423
478
|
}
|
|
424
479
|
validateCondition(condition, valueIsImplicit, ctx) {
|
|
425
480
|
let validated = false;
|
|
@@ -505,7 +560,9 @@ class WizardParser {
|
|
|
505
560
|
case "operator":
|
|
506
561
|
throw new ConstraintError(`Value "${condition.value.toString()}" not allowed for operation "${condition.operation}" which only allows for "${operationType}" type`, ctx?.tokens, ctx?.startToken, ctx?.startIndex, ctx?.endToken, ctx?.endIndex);
|
|
507
562
|
case "field":
|
|
508
|
-
throw new ConstraintError(`Value "${condition.value.toString()}"
|
|
563
|
+
throw new ConstraintError(`Value "${condition.value.toString()}" only has types not permitted for field "${condition.field}". Allowed types: ${(fieldTypes ?? opTypes).join(", ")}`, ctx?.tokens, ctx?.startToken, ctx?.startIndex, ctx?.endToken, ctx?.endIndex);
|
|
564
|
+
case "disagreement":
|
|
565
|
+
throw new ConstraintError(`Value "${condition.value.toString()}" using operation "${condition.operation}" cannot be coerced into a valid type for "${condition.field}". Allowed field types: ${(fieldTypes ?? ["ALL"]).join(", ")}; Allowed operator types: ${opTypes.join(", ")}`, ctx?.tokens, ctx?.startToken, ctx?.startIndex, ctx?.endToken, ctx?.endIndex);
|
|
509
566
|
}
|
|
510
567
|
}
|
|
511
568
|
}
|
|
@@ -579,11 +636,11 @@ class WizardParser {
|
|
|
579
636
|
};
|
|
580
637
|
for (let t = 0;t < tokens.length; ++t) {
|
|
581
638
|
const token = tokens[t];
|
|
582
|
-
if (
|
|
639
|
+
if (this.CONFIG.symbols.groupBrackets.some(([, c]) => token.content === c))
|
|
583
640
|
throw new ParseError("Unexpected closing parenthesis", tokens, token, _offset + t);
|
|
584
|
-
if (
|
|
641
|
+
if (this.CONFIG.symbols.arrayBrackets.some(([, c]) => token.content === c))
|
|
585
642
|
throw new ParseError("Unexpected closing bracket/brace", tokens, token, _offset + t);
|
|
586
|
-
const paren =
|
|
643
|
+
const paren = this.CONFIG.symbols.groupBrackets.find(([o]) => token.content === o);
|
|
587
644
|
if (paren) {
|
|
588
645
|
if (field || comparisonOperation || value)
|
|
589
646
|
throw new ParseError("Tried to open a group during an operation", tokens, token, _offset + t);
|
|
@@ -665,9 +722,9 @@ class WizardParser {
|
|
|
665
722
|
}
|
|
666
723
|
continue;
|
|
667
724
|
}
|
|
668
|
-
if (
|
|
725
|
+
if (this.CONFIG.symbols.negators.includes(token.content)) {
|
|
669
726
|
const nextToken = tokens[t + 1];
|
|
670
|
-
const nextParen =
|
|
727
|
+
const nextParen = this.CONFIG.symbols.groupBrackets.find(([o]) => nextToken?.content === o);
|
|
671
728
|
if (nextParen) {
|
|
672
729
|
resolveCondition({
|
|
673
730
|
tokens,
|
|
@@ -716,7 +773,7 @@ class WizardParser {
|
|
|
716
773
|
throw new ParseError("Expected a comparison operator", tokens, field.token, field.index, token, _offset + t);
|
|
717
774
|
}
|
|
718
775
|
if (!field) {
|
|
719
|
-
if (
|
|
776
|
+
if (this.CONFIG.symbols.negators.includes(token.content)) {
|
|
720
777
|
if (!this.CONFIG.implicitCondition)
|
|
721
778
|
throw new ParseError("Could not attempt a negative implicit condition as one is not defined in the config", tokens, token, _offset + t);
|
|
722
779
|
const nextToken = tokens[t + 1];
|
|
@@ -759,7 +816,7 @@ class WizardParser {
|
|
|
759
816
|
continue;
|
|
760
817
|
}
|
|
761
818
|
if (!value) {
|
|
762
|
-
const bracket =
|
|
819
|
+
const bracket = this.CONFIG.symbols.arrayBrackets.find(([o]) => token.content === o);
|
|
763
820
|
if (bracket) {
|
|
764
821
|
const closingIndex = WizardParser.getClosingIndex(tokens, t, bracket[0], bracket[1]);
|
|
765
822
|
if (closingIndex === -1)
|
|
@@ -792,7 +849,7 @@ class WizardParser {
|
|
|
792
849
|
};
|
|
793
850
|
for (let ct = 0;ct < arrayContents.length; ++ct) {
|
|
794
851
|
const contentToken = arrayContents[ct];
|
|
795
|
-
if (
|
|
852
|
+
if (this.CONFIG.symbols.arrayDelimiters.includes(contentToken.content)) {
|
|
796
853
|
if (!workingEntry)
|
|
797
854
|
throw new ParseError("Unexpected blank entry in array", tokens, contentToken, _offset + t + ct);
|
|
798
855
|
resolveEntry(contentToken, _offset + t + ct);
|
|
@@ -867,20 +924,21 @@ class WizardParser {
|
|
|
867
924
|
return this._parse(tokens, 0);
|
|
868
925
|
}
|
|
869
926
|
addQuotesIfNecessary(value) {
|
|
927
|
+
const QUOTE = this.CONFIG.symbols.quotes[0];
|
|
870
928
|
if (value instanceof Date) {
|
|
871
|
-
return this.CONFIG.dateSerializer ? this.CONFIG.dateSerializer(value) : value.toISOString();
|
|
929
|
+
return this.CONFIG.dateSerializer ? this.CONFIG.dateSerializer(value) : QUOTE + value.toISOString() + QUOTE;
|
|
872
930
|
}
|
|
873
931
|
if (typeof value !== "string")
|
|
874
932
|
return value.toString();
|
|
875
933
|
if (value === "true")
|
|
876
|
-
return
|
|
934
|
+
return `${QUOTE}true${QUOTE}`;
|
|
877
935
|
if (value === "false")
|
|
878
|
-
return
|
|
936
|
+
return `${QUOTE}false${QUOTE}`;
|
|
879
937
|
if (!isNaN(Number(value)))
|
|
880
|
-
return
|
|
938
|
+
return `${QUOTE}${value}${QUOTE}`;
|
|
881
939
|
const escaped = value.replaceAll("\\", "\\\\");
|
|
882
940
|
if (new RegExp(this.TOKEN_REGEX, "gi").test(escaped))
|
|
883
|
-
return
|
|
941
|
+
return `${QUOTE}${escaped.replaceAll(QUOTE, `\\${QUOTE}`)}${QUOTE}`;
|
|
884
942
|
return escaped;
|
|
885
943
|
}
|
|
886
944
|
stringify(expression, opts) {
|
|
@@ -908,25 +966,25 @@ class WizardParser {
|
|
|
908
966
|
string += " ";
|
|
909
967
|
}
|
|
910
968
|
if (alwaysParenthesize && constituent.type === "group" || expression.operation === "AND" && constituent.operation === "OR")
|
|
911
|
-
string +=
|
|
969
|
+
string += this.CONFIG.symbols.groupBrackets[0][0];
|
|
912
970
|
string += this.stringify(constituent, opts);
|
|
913
971
|
if (alwaysParenthesize && constituent.type === "group" || expression.operation === "AND" && constituent.operation === "OR")
|
|
914
|
-
string +=
|
|
972
|
+
string += this.CONFIG.symbols.groupBrackets[0][1];
|
|
915
973
|
}
|
|
916
974
|
break;
|
|
917
975
|
case "condition": {
|
|
918
976
|
const resolveNonImplicit = () => {
|
|
919
977
|
const transformed = this.CONFIG.dialects[dialect][expression.operation];
|
|
920
978
|
const alpha = isAlpha(transformed);
|
|
921
|
-
string += expression.field;
|
|
979
|
+
string += expression.field.toString();
|
|
922
980
|
if (!compact || alpha)
|
|
923
981
|
string += " ";
|
|
924
982
|
string += transformed;
|
|
925
983
|
if (!compact || alpha)
|
|
926
984
|
string += " ";
|
|
927
985
|
if (Array.isArray(expression.value)) {
|
|
928
|
-
const join = expression.value.map((v) => this.addQuotesIfNecessary(v)).join(compact ?
|
|
929
|
-
string += `${
|
|
986
|
+
const join = expression.value.map((v) => this.addQuotesIfNecessary(v)).join(compact ? this.CONFIG.symbols.arrayDelimiters[0] : this.CONFIG.symbols.arrayDelimiters[0] + " ");
|
|
987
|
+
string += `${this.CONFIG.symbols.arrayBrackets[0][0]}${join}${this.CONFIG.symbols.arrayBrackets[0][1]}`;
|
|
930
988
|
} else
|
|
931
989
|
string += this.addQuotesIfNecessary(expression.value);
|
|
932
990
|
};
|
|
@@ -938,12 +996,18 @@ class WizardParser {
|
|
|
938
996
|
const fieldTypes = fieldType && (Array.isArray(fieldType) ? fieldType : [fieldType]);
|
|
939
997
|
const opTypes = Array.isArray(this.CONFIG.implicitCondition.asType) ? this.CONFIG.implicitCondition.asType : [this.CONFIG.implicitCondition.asType];
|
|
940
998
|
const processed = this.processToken(this.CONFIG.implicitCondition.value);
|
|
941
|
-
|
|
999
|
+
let coerced;
|
|
1000
|
+
try {
|
|
1001
|
+
coerced = this.coerceType(processed, fieldTypes ?? opTypes, opTypes);
|
|
1002
|
+
} catch {
|
|
1003
|
+
resolveNonImplicit();
|
|
1004
|
+
break;
|
|
1005
|
+
}
|
|
942
1006
|
const bothBoolean = typeof coerced === "boolean" && typeof expression.value === "boolean";
|
|
943
1007
|
if (coerced.valueOf() === expression.value || bothBoolean) {
|
|
944
1008
|
if (bothBoolean && coerced !== expression.value)
|
|
945
1009
|
isNegation = !isNegation;
|
|
946
|
-
string += `${isNegation ?
|
|
1010
|
+
string += `${isNegation ? this.CONFIG.symbols.negators[0] : ""}${expression.field.toString()}`;
|
|
947
1011
|
} else
|
|
948
1012
|
resolveNonImplicit();
|
|
949
1013
|
} else
|
|
@@ -991,23 +1055,23 @@ class WizardParser {
|
|
|
991
1055
|
return "quoted";
|
|
992
1056
|
if (!isNaN(Number(segment)))
|
|
993
1057
|
return "number";
|
|
994
|
-
for (const [opening, closing] of
|
|
1058
|
+
for (const [opening, closing] of this.CONFIG.symbols.arrayBrackets) {
|
|
995
1059
|
if (!activeArrayOpeningBracket && segment === opening)
|
|
996
1060
|
return "openingarraybracket";
|
|
997
1061
|
else if (activeArrayOpeningBracket === opening && segment === closing)
|
|
998
1062
|
return "closingarraybracket";
|
|
999
1063
|
}
|
|
1000
1064
|
if (!activeArrayOpeningBracket) {
|
|
1001
|
-
for (const [opening, closing] of
|
|
1065
|
+
for (const [opening, closing] of this.CONFIG.symbols.groupBrackets) {
|
|
1002
1066
|
if (segment === opening)
|
|
1003
1067
|
return "openinggroupbracket";
|
|
1004
1068
|
else if (segment === closing)
|
|
1005
1069
|
return "closinggroupbracket";
|
|
1006
1070
|
}
|
|
1007
1071
|
}
|
|
1008
|
-
if (activeArrayOpeningBracket &&
|
|
1072
|
+
if (activeArrayOpeningBracket && this.CONFIG.symbols.arrayDelimiters.includes(segment))
|
|
1009
1073
|
return "arraydelimiter";
|
|
1010
|
-
if (!activeArrayOpeningBracket &&
|
|
1074
|
+
if (!activeArrayOpeningBracket && this.CONFIG.symbols.negators.includes(segment))
|
|
1011
1075
|
return "negator";
|
|
1012
1076
|
if (!activeArrayOpeningBracket && segment in this.OPERATOR_DICTIONARY) {
|
|
1013
1077
|
const type = this.OPERATOR_DICTIONARY[segment].type;
|
|
@@ -1022,52 +1086,6 @@ class WizardParser {
|
|
|
1022
1086
|
return this.OPERATOR_DICTIONARY[alias]?.name;
|
|
1023
1087
|
}
|
|
1024
1088
|
}
|
|
1025
|
-
// src/execute.ts
|
|
1026
|
-
function executeAsKnex(query, expression) {
|
|
1027
|
-
switch (expression.type) {
|
|
1028
|
-
case "group": {
|
|
1029
|
-
let firstHappened = false;
|
|
1030
|
-
for (const constituent of expression.constituents) {
|
|
1031
|
-
query[firstHappened ? expression.operation === "AND" ? "andWhere" : "orWhere" : "where"]((clause) => executeAsKnex(clause, constituent));
|
|
1032
|
-
firstHappened = true;
|
|
1033
|
-
}
|
|
1034
|
-
break;
|
|
1035
|
-
}
|
|
1036
|
-
case "condition":
|
|
1037
|
-
switch (expression.operation) {
|
|
1038
|
-
case "EQUAL":
|
|
1039
|
-
query.where(expression.field, "=", expression.value);
|
|
1040
|
-
break;
|
|
1041
|
-
case "NOTEQUAL":
|
|
1042
|
-
query.where(expression.field, "!=", expression.value);
|
|
1043
|
-
break;
|
|
1044
|
-
case "GEQ":
|
|
1045
|
-
query.where(expression.field, ">=", expression.value);
|
|
1046
|
-
break;
|
|
1047
|
-
case "LEQ":
|
|
1048
|
-
query.where(expression.field, "<=", expression.value);
|
|
1049
|
-
break;
|
|
1050
|
-
case "GREATER":
|
|
1051
|
-
query.where(expression.field, ">", expression.value);
|
|
1052
|
-
break;
|
|
1053
|
-
case "LESS":
|
|
1054
|
-
query.where(expression.field, "<", expression.value);
|
|
1055
|
-
break;
|
|
1056
|
-
case "IN":
|
|
1057
|
-
query.whereIn(expression.field, expression.value);
|
|
1058
|
-
break;
|
|
1059
|
-
case "NOTIN":
|
|
1060
|
-
query.whereNotIn(expression.field, expression.value);
|
|
1061
|
-
break;
|
|
1062
|
-
case "MATCH":
|
|
1063
|
-
query.whereRaw("?? ~* ?", [expression.field, expression.value]);
|
|
1064
|
-
break;
|
|
1065
|
-
case "NOTMATCH":
|
|
1066
|
-
query.whereRaw("?? !~* ?", [expression.field, expression.value]);
|
|
1067
|
-
break;
|
|
1068
|
-
}
|
|
1069
|
-
}
|
|
1070
|
-
}
|
|
1071
1089
|
// src/dominput.ts
|
|
1072
1090
|
function getCursorIndex(element) {
|
|
1073
1091
|
const selection = window.getSelection();
|
|
@@ -1259,10 +1277,9 @@ function createDOMInput({ input, config, onUpdate, parseOnInitialize }) {
|
|
|
1259
1277
|
};
|
|
1260
1278
|
}
|
|
1261
1279
|
export {
|
|
1262
|
-
|
|
1263
|
-
createDOMInput,
|
|
1264
|
-
WizardParser,
|
|
1265
|
-
TYPE_PRIORITY,
|
|
1280
|
+
ConstraintError,
|
|
1266
1281
|
ParseError,
|
|
1267
|
-
|
|
1282
|
+
TYPE_PRIORITY,
|
|
1283
|
+
WizardParser,
|
|
1284
|
+
createDOMInput
|
|
1268
1285
|
};
|
package/dist/parser.d.ts
CHANGED
|
@@ -58,6 +58,7 @@ type ValidateUppercaseKeys<O extends OperatorRecord> = {
|
|
|
58
58
|
[K in keyof O]: K extends Uppercase<K & string> ? O[K] : ValidationError<`Operator Record contains non-uppercase key: '${K & string}'`>;
|
|
59
59
|
};
|
|
60
60
|
type InternalOperationDictionary<O extends OperatorRecord> = Record<GetConditionOperators<O> | GetJunctionOperators<O> | string, InternalConditionOperatorDefinition<O> | InternalJunctionOperatorDefinition<O>>;
|
|
61
|
+
type ReadonlyNonemptyArray<T> = readonly [T, ...T[]];
|
|
61
62
|
export interface WizardParserConfig<F extends FieldTypeRecord, O extends OperatorRecord, out V extends boolean, D extends string> {
|
|
62
63
|
/**
|
|
63
64
|
* Restricted fields.\
|
|
@@ -69,7 +70,7 @@ export interface WizardParserConfig<F extends FieldTypeRecord, O extends Operato
|
|
|
69
70
|
* If the query value is of array type, it will check all entries of the array and ensure they're all allowed.\
|
|
70
71
|
* This check runs before type coercion so the value checked will always be a string. However, quotes and escapes WILL be removed
|
|
71
72
|
*/
|
|
72
|
-
restricted?: Partial<Record<keyof F | (string & {}), boolean | ['allow' | 'deny',
|
|
73
|
+
restricted?: Partial<Record<keyof F | (string & {}), boolean | ['allow' | 'deny', ReadonlyArray<string | RegExp>]>>;
|
|
73
74
|
/**
|
|
74
75
|
* The types of fields\
|
|
75
76
|
* Either provide the field type singularly or permit multiple types with an array of field types\
|
|
@@ -111,9 +112,24 @@ export interface WizardParserConfig<F extends FieldTypeRecord, O extends Operato
|
|
|
111
112
|
/** The default implicit value in string form */
|
|
112
113
|
value: string;
|
|
113
114
|
/** How the value should be parsed (as a type) */
|
|
114
|
-
asType: FieldType | FieldType[];
|
|
115
|
+
asType: FieldType | readonly FieldType[];
|
|
115
116
|
};
|
|
117
|
+
/**
|
|
118
|
+
* A specification of how to stringify a parsed expression.\
|
|
119
|
+
* Provide a record that maps dialect names to a record which maps operator names to their stringified expressions
|
|
120
|
+
*/
|
|
116
121
|
dialects?: Record<D, Record<GetOperators<O>, string>>;
|
|
122
|
+
/**
|
|
123
|
+
* Symbol overrides.\
|
|
124
|
+
* Stringification will use the first symbol entry of each symbol type.
|
|
125
|
+
*/
|
|
126
|
+
symbols?: {
|
|
127
|
+
quotes?: ReadonlyNonemptyArray<string>;
|
|
128
|
+
negators?: ReadonlyNonemptyArray<string>;
|
|
129
|
+
arrayDelimiters?: ReadonlyNonemptyArray<string>;
|
|
130
|
+
arrayBrackets?: ReadonlyNonemptyArray<[opening: string, closing: string]>;
|
|
131
|
+
groupBrackets?: ReadonlyNonemptyArray<[opening: string, closing: string]>;
|
|
132
|
+
};
|
|
117
133
|
}
|
|
118
134
|
export interface StringifyOptions<D extends string> {
|
|
119
135
|
/**
|
|
@@ -237,13 +253,15 @@ export declare class WizardParser<const F extends FieldTypeRecord, const O exten
|
|
|
237
253
|
readonly NOTMATCH: "≇";
|
|
238
254
|
};
|
|
239
255
|
};
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
256
|
+
static readonly DEFAULT_QUOTES: readonly ["\"", "'", "`"];
|
|
257
|
+
static readonly DEFAULT_NEGATORS: readonly ["!"];
|
|
258
|
+
static readonly DEFAULT_ARRAY_DELIMITERS: readonly [","];
|
|
259
|
+
static readonly DEFAULT_ARRAY_BRACKETS: [["[", "]"], ["{", "}"]];
|
|
260
|
+
static readonly DEFAULT_GROUP_BRACKETS: [["(", ")"]];
|
|
245
261
|
protected readonly OPERATOR_DICTIONARY: InternalOperationDictionary<O>;
|
|
246
|
-
protected readonly CONFIG: WizardParserConfig<F, O, V, D> & Required<Pick<WizardParserConfig<F, O, V, D>, 'operators'
|
|
262
|
+
protected readonly CONFIG: WizardParserConfig<F, O, V, D> & Required<Pick<WizardParserConfig<F, O, V, D>, 'operators'>> & {
|
|
263
|
+
symbols: Required<Exclude<WizardParserConfig<F, O, V, D>['symbols'], undefined>>;
|
|
264
|
+
};
|
|
247
265
|
protected readonly TOKEN_REGEX: RegExp;
|
|
248
266
|
protected readonly QUOTE_REGEX: RegExp;
|
|
249
267
|
protected readonly QUOTE_EDGE_REGEX: RegExp;
|
|
@@ -310,7 +328,7 @@ export declare class WizardParser<const F extends FieldTypeRecord, const O exten
|
|
|
310
328
|
* @param fieldTypes The supported field types
|
|
311
329
|
* @param operatorTypes The supported operator types
|
|
312
330
|
* @returns The coerced type
|
|
313
|
-
* @throws {Error} Message is 'operator' if the operator type cannot be coerced
|
|
331
|
+
* @throws {Error} Message is 'operator' if the operator type cannot be coerced, 'field' if the field type cannot be coerced and 'disagreement' if the allowed types for the field and operator have no overlap
|
|
314
332
|
*/
|
|
315
333
|
protected coerceType(processedToken: ProcessedToken, fieldTypes: FieldType[], operatorTypes: FieldType[]): Primitive;
|
|
316
334
|
/**
|
|
@@ -353,7 +371,7 @@ export declare class WizardParser<const F extends FieldTypeRecord, const O exten
|
|
|
353
371
|
* @throws {Error} If no dialect dictionary is defined or dialect supplied is unknown
|
|
354
372
|
* @returns The formatted string
|
|
355
373
|
*/
|
|
356
|
-
stringify(expression: Expression<
|
|
374
|
+
stringify(expression: Expression<F, O>, opts: D | StringifyOptions<D>): string;
|
|
357
375
|
/**
|
|
358
376
|
* (Get an array entry from a map or insert one) and return it
|
|
359
377
|
* @param map The map
|
package/dist/spec.d.ts
CHANGED
|
@@ -51,18 +51,31 @@ export type GetConditionTSType<T extends ConditionOperatorComparisonType> = {
|
|
|
51
51
|
numeric: number | Date;
|
|
52
52
|
array: Primitive[];
|
|
53
53
|
}[T];
|
|
54
|
+
/**
|
|
55
|
+
* The TS type of a condition value belonging to a field with no declared types\
|
|
56
|
+
* Values are only parsed into dates for fields declared as dates, so a date can only occur when the operator itself is exclusively date-typed
|
|
57
|
+
*/
|
|
58
|
+
export type GetUndeclaredConditionTSType<T extends ConditionOperatorComparisonType> = {
|
|
59
|
+
primitive: Exclude<Primitive, Date>;
|
|
60
|
+
boolean: boolean;
|
|
61
|
+
string: string;
|
|
62
|
+
number: number;
|
|
63
|
+
date: Date;
|
|
64
|
+
numeric: number;
|
|
65
|
+
array: Array<Exclude<Primitive, Date>>;
|
|
66
|
+
}[T];
|
|
54
67
|
export interface CheckedCondition<F extends FieldTypeRecord = FieldTypeRecord, O extends OperatorRecord = OperatorRecord, I extends keyof F = keyof F, P extends GetConditionOperators<O> = GetConditionOperators<O>> {
|
|
55
68
|
type: 'condition';
|
|
56
69
|
operation: P;
|
|
57
70
|
field: I;
|
|
58
|
-
value: GetOperatorDefinition<O, P>['type'] extends 'array' ? Array<GetFieldTSType<F[I]>> : Extract<GetConditionTSType<GetOperatorDefinition<O, P>['type'] & ConditionOperatorComparisonType>, GetFieldTSType<F[I]>>;
|
|
71
|
+
value: string extends I ? GetUndeclaredConditionTSType<GetOperatorDefinition<O, P>['type'] & ConditionOperatorComparisonType> : GetOperatorDefinition<O, P>['type'] extends 'array' ? Array<GetFieldTSType<F[I]>> : Extract<GetConditionTSType<GetOperatorDefinition<O, P>['type'] & ConditionOperatorComparisonType>, GetFieldTSType<F[I]>>;
|
|
59
72
|
validated: true;
|
|
60
73
|
}
|
|
61
74
|
export interface UncheckedCondition<O extends OperatorRecord = OperatorRecord, P extends GetConditionOperators<O> = GetConditionOperators<O>> {
|
|
62
75
|
type: 'condition';
|
|
63
76
|
operation: P;
|
|
64
77
|
field: string;
|
|
65
|
-
value:
|
|
78
|
+
value: GetUndeclaredConditionTSType<GetOperatorDefinition<O, P>['type'] & ConditionOperatorComparisonType>;
|
|
66
79
|
validated: false;
|
|
67
80
|
}
|
|
68
81
|
export interface Group<F extends FieldTypeRecord = FieldTypeRecord, O extends OperatorRecord = OperatorRecord, V extends boolean = false> {
|
package/package.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
},
|
|
7
7
|
"homepage": "https://github.com/exoRift/wizard-ql",
|
|
8
8
|
"description": "WizardQL is a natural-language-like query language for constructing data queries for resources that meet conditions.",
|
|
9
|
-
"version": "
|
|
9
|
+
"version": "4.0.1",
|
|
10
10
|
"repository": {
|
|
11
11
|
"type": "git",
|
|
12
12
|
"url": "git+https://github.com/exoRift/wizard-ql.git"
|
|
@@ -45,9 +45,6 @@
|
|
|
45
45
|
"eslint-plugin-jsdoc": "^50.6.11",
|
|
46
46
|
"tsd": "^0.33.0"
|
|
47
47
|
},
|
|
48
|
-
"optionalDependencies": {
|
|
49
|
-
"knex": "^3.1.0"
|
|
50
|
-
},
|
|
51
48
|
"peerDependencies": {
|
|
52
49
|
"typescript": "^5.8.3"
|
|
53
50
|
}
|
package/dist/execute.d.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import type { Knex } from 'knex';
|
|
2
|
-
import type { Expression, FieldTypeRecord } from './spec';
|
|
3
|
-
import type { WizardParser } from './parser';
|
|
4
|
-
/**
|
|
5
|
-
* Execute a Wizard expression as a SQL query\
|
|
6
|
-
* This mutates the passed query
|
|
7
|
-
* @param query The Knex query to append conditions to
|
|
8
|
-
* @param expression The Wizard expression
|
|
9
|
-
*/
|
|
10
|
-
export declare function executeAsKnex(query: Knex.QueryBuilder, expression: Expression<FieldTypeRecord, typeof WizardParser.DEFAULT_OPERATORS>): void;
|