wizard-ql 1.0.0 → 2.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 +13 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.js +100 -81
- package/dist/parse.d.ts +13 -9
- package/dist/spec.d.ts +19 -12
- package/package.json +13 -4
package/README.md
CHANGED
|
@@ -258,7 +258,7 @@ Values can be direct values (string, number, boolean) or regex expressions
|
|
|
258
258
|
"deny" will deny the values/patterns and allow all others
|
|
259
259
|
|
|
260
260
|
### `types`
|
|
261
|
-
A record mapping field names to (`boolean`, `string`, `number`). The value in the record can either be a single allowed type or an array of allowed types. Only operators that can function on that type can be used for that field. By default, fields will be treated as being able to be any of the three types.
|
|
261
|
+
A record mapping field names to (`boolean`, `string`, `number`, `date`). The value in the record can either be a single allowed type or an array of allowed types. Only operators that can function on that type can be used for that field. By default, fields will be treated as being able to be any of the three types.
|
|
262
262
|
|
|
263
263
|
> Example:
|
|
264
264
|
> ```js
|
|
@@ -270,6 +270,15 @@ A record mapping field names to (`boolean`, `string`, `number`). The value in th
|
|
|
270
270
|
> })
|
|
271
271
|
> ```
|
|
272
272
|
|
|
273
|
+
> [!NOTE]
|
|
274
|
+
> A value will only be attempted to be parsed as a date if `'date'` is included in the field's type record.
|
|
275
|
+
> 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
|
|
276
|
+
|
|
277
|
+
The type coercion priority chain is as follows:
|
|
278
|
+
`boolean` -> `date` -> `number` -> `string`
|
|
279
|
+
> [!WARNING]
|
|
280
|
+
> 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`
|
|
281
|
+
|
|
273
282
|
### Regarding constraints: `validated` property
|
|
274
283
|
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.
|
|
275
284
|
|
|
@@ -306,10 +315,10 @@ Type/constraint checks will be case-insensitive on the field name
|
|
|
306
315
|
### `disallowUnvalidated`
|
|
307
316
|
Fields that are not present in the type or restriction record will considered invalid fields
|
|
308
317
|
|
|
309
|
-
### `
|
|
310
|
-
|
|
318
|
+
### `dateInterpreter`
|
|
319
|
+
A callback that determines how WizardQL interprets dates. Wizard will attempt to parse a value as a date if `'date'` is supplied in its type record
|
|
311
320
|
|
|
312
|
-
|
|
321
|
+
By default, this is simply `(v) => new Date(v)`
|
|
313
322
|
|
|
314
323
|
## Stringification
|
|
315
324
|
Parsed expressions can be converted back into strings using the `stringify` function. The stringify function comes with its own slew of options as its second parameter
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { type Operation, type ComparisonOperation, type JunctionOperation, type Expression, type Condition, type UncheckedCondition, type Group, type Primitive, type TypeRecord, type Token, type ComparisonTypeToTSType, OPERATION_PURPOSE_DICTIONARY, COMPARISON_TYPE_DICTIONARY, OPERATION_ALIAS_DICTIONARY } from './spec';
|
|
1
|
+
export { type Operation, type ComparisonOperation, type JunctionOperation, type Expression, type UncheckedExpression, type Condition, type UncheckedCondition, type Group, type Primitive, type TypeRecord, type Token, type ComparisonTypeToTSType, OPERATION_PURPOSE_DICTIONARY, COMPARISON_TYPE_DICTIONARY, OPERATION_ALIAS_DICTIONARY } from './spec';
|
|
2
2
|
export { type ExpressionConstraints, QUOTE_EDGE_REGEX, parse, tokenize } from './parse';
|
|
3
3
|
export { executeAsKnex } from './execute';
|
|
4
4
|
export { type AggregationValue, summarize } from './summarize';
|
package/dist/index.js
CHANGED
|
@@ -60,15 +60,16 @@ var OPERATION_PURPOSE_DICTIONARY = {
|
|
|
60
60
|
var COMPARISON_TYPE_DICTIONARY = {
|
|
61
61
|
EQUAL: "primitive",
|
|
62
62
|
NOTEQUAL: "primitive",
|
|
63
|
-
GEQ: "
|
|
64
|
-
GREATER: "
|
|
65
|
-
LEQ: "
|
|
66
|
-
LESS: "
|
|
63
|
+
GEQ: "numeric",
|
|
64
|
+
GREATER: "numeric",
|
|
65
|
+
LEQ: "numeric",
|
|
66
|
+
LESS: "numeric",
|
|
67
67
|
IN: "array",
|
|
68
68
|
NOTIN: "array",
|
|
69
69
|
MATCH: "string",
|
|
70
70
|
NOTMATCH: "string"
|
|
71
71
|
};
|
|
72
|
+
var TYPE_PRIORITY = ["boolean", "date", "number", "string"];
|
|
72
73
|
// src/regex.ts
|
|
73
74
|
var ESCAPE_REGEX = "(?<=(?<!\\\\)(?:\\\\\\\\)*)";
|
|
74
75
|
var QUOTES = ["'", '"', "`"];
|
|
@@ -194,10 +195,6 @@ function _tokenize(expression, pattern) {
|
|
|
194
195
|
function tokenize(expression) {
|
|
195
196
|
return _tokenize(expression, TOKEN_REGEX);
|
|
196
197
|
}
|
|
197
|
-
function parseValue(token, isQuoted) {
|
|
198
|
-
const number = Number(token);
|
|
199
|
-
return isQuoted || isNaN(number) ? token === "true" ? true : token === "false" ? false : token : number;
|
|
200
|
-
}
|
|
201
198
|
function processToken(token) {
|
|
202
199
|
const unquoted = token.match(QUOTE_EDGE_REGEX)?.groups?.quotecontent;
|
|
203
200
|
const escaped = unquoted ?? token;
|
|
@@ -273,6 +270,53 @@ function complementExpression(expression) {
|
|
|
273
270
|
break;
|
|
274
271
|
}
|
|
275
272
|
}
|
|
273
|
+
function coerceType(value, fieldTypes, operatorTypes, dateInterpreter) {
|
|
274
|
+
const { unescaped } = processToken(value);
|
|
275
|
+
let operatorHits = 0;
|
|
276
|
+
let fieldHits = 0;
|
|
277
|
+
for (const type of TYPE_PRIORITY) {
|
|
278
|
+
const operatorHit = operatorTypes.includes(type);
|
|
279
|
+
const fieldHit = fieldTypes.includes(type);
|
|
280
|
+
if (operatorHit)
|
|
281
|
+
++operatorHits;
|
|
282
|
+
if (fieldHit)
|
|
283
|
+
++fieldHits;
|
|
284
|
+
if (!operatorHit || !fieldHit)
|
|
285
|
+
continue;
|
|
286
|
+
switch (type) {
|
|
287
|
+
case "boolean":
|
|
288
|
+
if (value === "true")
|
|
289
|
+
return true;
|
|
290
|
+
else if (value === "false")
|
|
291
|
+
return false;
|
|
292
|
+
break;
|
|
293
|
+
case "number": {
|
|
294
|
+
const num = Number(value);
|
|
295
|
+
if (isNaN(num))
|
|
296
|
+
break;
|
|
297
|
+
return num;
|
|
298
|
+
}
|
|
299
|
+
case "string":
|
|
300
|
+
return unescaped;
|
|
301
|
+
case "date": {
|
|
302
|
+
const num = Number(value);
|
|
303
|
+
const date = dateInterpreter(isNaN(num) ? unescaped : num);
|
|
304
|
+
if (isNaN(+date))
|
|
305
|
+
break;
|
|
306
|
+
return date;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
if (operatorTypes.includes(type))
|
|
310
|
+
--operatorHits;
|
|
311
|
+
if (fieldTypes.includes(type))
|
|
312
|
+
--fieldHits;
|
|
313
|
+
}
|
|
314
|
+
if (!operatorHits)
|
|
315
|
+
throw new Error("operator");
|
|
316
|
+
if (!fieldHits)
|
|
317
|
+
throw new Error("field");
|
|
318
|
+
throw new Error("operator");
|
|
319
|
+
}
|
|
276
320
|
function validateCondition(condition, constraints, valueIsImplicit, ctx) {
|
|
277
321
|
let validated = false;
|
|
278
322
|
const field = constraints?.caseInsensitive ? [...Object.keys(constraints.types ?? {}), ...Object.keys(constraints.restricted ?? {})].find((k) => k.toLowerCase() === condition.field.toLowerCase()) ?? condition.field : condition.field;
|
|
@@ -282,26 +326,6 @@ function validateCondition(condition, constraints, valueIsImplicit, ctx) {
|
|
|
282
326
|
throw new ConstraintError(`Unknown field "${condition.field}"`, ctx?.startToken, ctx?.startIndex, ctx?.endToken, ctx?.endIndex);
|
|
283
327
|
const operationType = COMPARISON_TYPE_DICTIONARY[condition.operation];
|
|
284
328
|
const types = type && (Array.isArray(type) ? type : [type]);
|
|
285
|
-
if (!valueIsImplicit && constraints?.interpretDates && (!types || types.includes("number")) && ["primitive", "number"].includes(operationType)) {
|
|
286
|
-
const validator = constraints.interpretDates === true ? (v) => +new Date(v) : constraints.interpretDates;
|
|
287
|
-
if (Array.isArray(condition.value)) {
|
|
288
|
-
for (let v = 0;v < condition.value.length; ++v) {
|
|
289
|
-
const val = condition.value[v];
|
|
290
|
-
if (typeof val === "string") {
|
|
291
|
-
const numberized = validator(val);
|
|
292
|
-
if (!isNaN(numberized))
|
|
293
|
-
condition.value[v] = numberized;
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
} else {
|
|
297
|
-
const val = condition.value;
|
|
298
|
-
if (typeof val === "string") {
|
|
299
|
-
const numberized = validator(val);
|
|
300
|
-
if (!isNaN(numberized))
|
|
301
|
-
condition.value = numberized;
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
329
|
const values = Array.isArray(condition.value) ? condition.value : [condition.value];
|
|
306
330
|
if (restriction === true)
|
|
307
331
|
throw new ConstraintError(`Field "${condition.field}" is restricted`, ctx?.startToken, ctx?.startIndex, ctx?.endToken, ctx?.endIndex);
|
|
@@ -328,50 +352,50 @@ function validateCondition(condition, constraints, valueIsImplicit, ctx) {
|
|
|
328
352
|
}
|
|
329
353
|
validated = true;
|
|
330
354
|
}
|
|
331
|
-
if (
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
355
|
+
if (operationType === "array" && !Array.isArray(condition.value))
|
|
356
|
+
throw new ConstraintError(`Value "${condition.value.toString()}" is not permitted for "${condition.operation}" which expects an array`, ctx?.startToken, ctx?.startIndex, ctx?.endToken, ctx?.endIndex);
|
|
357
|
+
else if (operationType !== "array" && Array.isArray(condition.value))
|
|
358
|
+
throw new ConstraintError(`Value "${condition.value.toString()}" is not permitted for "${condition.operation}" which expects a non-array value`, ctx?.startToken, ctx?.startIndex, ctx?.endToken, ctx?.endIndex);
|
|
359
|
+
const dateInterpreter = constraints?.dateInterpreter ?? ((v) => new Date(v));
|
|
360
|
+
for (let i = 0;i < values.length; ++i) {
|
|
361
|
+
const v = values[i];
|
|
362
|
+
let opTypes;
|
|
363
|
+
if (valueIsImplicit)
|
|
364
|
+
opTypes = ["boolean"];
|
|
365
|
+
else {
|
|
366
|
+
switch (operationType) {
|
|
367
|
+
case "primitive":
|
|
368
|
+
case "array":
|
|
369
|
+
opTypes = ["boolean", "number", "string"];
|
|
370
|
+
if (types?.includes("date"))
|
|
371
|
+
opTypes.push("date");
|
|
372
|
+
break;
|
|
373
|
+
case "numeric":
|
|
374
|
+
opTypes = ["number"];
|
|
375
|
+
if (types?.includes("date"))
|
|
376
|
+
opTypes.push("date");
|
|
377
|
+
break;
|
|
378
|
+
case "string":
|
|
379
|
+
opTypes = ["string"];
|
|
380
|
+
break;
|
|
357
381
|
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
throw new ConstraintError(`Value "${condition.value.toString()}" includes a type not permitted for field "${condition.field}". Allowed types: ${types.join(", ")}`, ctx?.startToken, ctx?.startIndex, ctx?.endToken, ctx?.endIndex);
|
|
373
|
-
validated = true;
|
|
382
|
+
}
|
|
383
|
+
try {
|
|
384
|
+
const value = coerceType(v, types ?? opTypes, opTypes, dateInterpreter);
|
|
385
|
+
values[i] = value;
|
|
386
|
+
if (!Array.isArray(condition.value))
|
|
387
|
+
condition.value = value;
|
|
388
|
+
} catch (err) {
|
|
389
|
+
switch (err.message) {
|
|
390
|
+
case "operator":
|
|
391
|
+
throw new ConstraintError(`Value "${condition.value.toString()}" not allowed for operation "${condition.operation}" which only allows for "${operationType}" type`, ctx?.startToken, ctx?.startIndex, ctx?.endToken, ctx?.endIndex);
|
|
392
|
+
case "field":
|
|
393
|
+
throw new ConstraintError(`Value "${condition.value.toString()}" includes a type not permitted for field "${condition.field}". Allowed types: ${(types ?? opTypes).join(", ")}`, ctx?.startToken, ctx?.startIndex, ctx?.endToken, ctx?.endIndex);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
374
396
|
}
|
|
397
|
+
if (types)
|
|
398
|
+
validated = true;
|
|
375
399
|
const edit = condition;
|
|
376
400
|
edit.field = field;
|
|
377
401
|
edit.validated = validated;
|
|
@@ -424,7 +448,7 @@ function _parse(tokens, _offset, constraints) {
|
|
|
424
448
|
type: "condition",
|
|
425
449
|
field: field.content,
|
|
426
450
|
operation: "EQUAL",
|
|
427
|
-
value: true
|
|
451
|
+
value: "true"
|
|
428
452
|
}, constraints, true, baseCtx));
|
|
429
453
|
inConjunction = false;
|
|
430
454
|
expectingExpression = false;
|
|
@@ -436,10 +460,6 @@ function _parse(tokens, _offset, constraints) {
|
|
|
436
460
|
}
|
|
437
461
|
for (let t = 0;t < tokens.length; ++t) {
|
|
438
462
|
const token = tokens[t];
|
|
439
|
-
const {
|
|
440
|
-
unquoted,
|
|
441
|
-
unescaped
|
|
442
|
-
} = processToken(token.content);
|
|
443
463
|
if (token.content === ")")
|
|
444
464
|
throw new ParseError("Unexpected closing parenthesis", token, _offset + t);
|
|
445
465
|
if (["]", "}"].includes(token.content))
|
|
@@ -588,7 +608,7 @@ function _parse(tokens, _offset, constraints) {
|
|
|
588
608
|
content: "EQUAL"
|
|
589
609
|
};
|
|
590
610
|
value = {
|
|
591
|
-
content: false,
|
|
611
|
+
content: "false",
|
|
592
612
|
implicit: true
|
|
593
613
|
};
|
|
594
614
|
resolveCondition({
|
|
@@ -599,7 +619,7 @@ function _parse(tokens, _offset, constraints) {
|
|
|
599
619
|
});
|
|
600
620
|
} else {
|
|
601
621
|
field = {
|
|
602
|
-
content: unescaped,
|
|
622
|
+
content: processToken(token.content).unescaped,
|
|
603
623
|
token,
|
|
604
624
|
index: _offset + t
|
|
605
625
|
};
|
|
@@ -611,15 +631,14 @@ function _parse(tokens, _offset, constraints) {
|
|
|
611
631
|
let resolveEntry = function(subtoken, subindex) {
|
|
612
632
|
if (workingEntry) {
|
|
613
633
|
const {
|
|
614
|
-
unquoted: unquotedWorkingEntry
|
|
615
|
-
unescaped: unescapedWorkingEntry
|
|
634
|
+
unquoted: unquotedWorkingEntry
|
|
616
635
|
} = processToken(workingEntry);
|
|
617
636
|
const subquotes = workingEntry.match(QUOTE_REGEX);
|
|
618
637
|
if (subquotes && (subquotes.index !== 0 || subquotes[0].length !== workingEntry.length))
|
|
619
638
|
throw new ParseError("Quotes must surround entire values in arrays", firstEntryToken ?? subtoken, firstEntryTokenIndex ?? subindex, lastEntryToken ?? subtoken, lastEntryTokenIndex ?? subindex);
|
|
620
639
|
if (!unquotedWorkingEntry && (token.content === "[" && new RegExp(`${ESCAPE_REGEX}(?:\\[|\\])`).test(workingEntry) || token.content === "{" && new RegExp(`${ESCAPE_REGEX}(?:\\{|\\})`).test(workingEntry)))
|
|
621
640
|
throw new ParseError("Unescaped bracket in an array value", firstEntryToken ?? subtoken, firstEntryTokenIndex ?? subindex, lastEntryToken ?? subtoken, lastEntryTokenIndex ?? subindex);
|
|
622
|
-
arr.push(
|
|
641
|
+
arr.push(workingEntry);
|
|
623
642
|
workingEntry = "";
|
|
624
643
|
firstEntryToken = undefined;
|
|
625
644
|
firstEntryTokenIndex = undefined;
|
|
@@ -666,7 +685,7 @@ function _parse(tokens, _offset, constraints) {
|
|
|
666
685
|
t = closingIndex;
|
|
667
686
|
} else {
|
|
668
687
|
value = {
|
|
669
|
-
content:
|
|
688
|
+
content: token.content,
|
|
670
689
|
token,
|
|
671
690
|
index: _offset + t
|
|
672
691
|
};
|
package/dist/parse.d.ts
CHANGED
|
@@ -12,15 +12,19 @@ export interface ExpressionConstraints<T extends TypeRecord, V extends boolean>
|
|
|
12
12
|
/**
|
|
13
13
|
* Restricted fields.\
|
|
14
14
|
* Restrict an entire field by setting it to true.\
|
|
15
|
+
* Restrict an exact value by providing a string.\
|
|
16
|
+
* Restrict a pattern by providing a Regex expression.\
|
|
15
17
|
* By default allow any value and restrict a collection of values by passing ['deny', VALUES[]]\
|
|
16
18
|
* By default restrict any value and allow a collection of values by passing ['allow', VALUES[]]\
|
|
17
|
-
* If the
|
|
18
|
-
*
|
|
19
|
+
* If the query value is of array type, it will check all entries of the array and ensure they're all allowed.\
|
|
20
|
+
* This check runs before type coercsion so the value checked will always be a string.
|
|
19
21
|
*/
|
|
20
|
-
restricted?: Partial<Record<keyof T | (string & {}), boolean | ['allow' | 'deny', Array<
|
|
22
|
+
restricted?: Partial<Record<keyof T | (string & {}), boolean | ['allow' | 'deny', Array<string | RegExp>]>>;
|
|
21
23
|
/**
|
|
22
24
|
* The types of fields\
|
|
23
|
-
* Either provide the field type singularly or permit multiple types with an array of field types
|
|
25
|
+
* Either provide the field type singularly or permit multiple types with an array of field types\
|
|
26
|
+
* A field with type 'date' will parse the value into a Date object if possible (Wizard will not attempt to do this otherwise)\
|
|
27
|
+
* Type coercion priority: boolean -> date -> number -> string
|
|
24
28
|
*/
|
|
25
29
|
types?: T;
|
|
26
30
|
/**
|
|
@@ -34,11 +38,10 @@ export interface ExpressionConstraints<T extends TypeRecord, V extends boolean>
|
|
|
34
38
|
*/
|
|
35
39
|
disallowUnvalidated?: V;
|
|
36
40
|
/**
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* If a callback is passed, use that. A value of `NaN` denotes an invalid date
|
|
41
|
+
* A callback that determines how dates are interpreted\
|
|
42
|
+
* By default, uses `new Date()`
|
|
40
43
|
*/
|
|
41
|
-
|
|
44
|
+
dateInterpreter?: (v: string | number) => Date;
|
|
42
45
|
}
|
|
43
46
|
/**
|
|
44
47
|
* Parse a Wizard expression into its object form
|
|
@@ -48,4 +51,5 @@ export interface ExpressionConstraints<T extends TypeRecord, V extends boolean>
|
|
|
48
51
|
* @returns The object representation
|
|
49
52
|
* @throws {ParseError | ConstraintError}
|
|
50
53
|
*/
|
|
51
|
-
export declare function parse<const T extends TypeRecord
|
|
54
|
+
export declare function parse<const T extends TypeRecord = {}, // eslint-disable-line @typescript-eslint/no-empty-object-type
|
|
55
|
+
const V extends boolean = false>(expression: string | string[] | Token[], constraints?: ExpressionConstraints<T, V>): Expression<ConvertTypeRecord<T>, V> | null;
|
package/dist/spec.d.ts
CHANGED
|
@@ -74,10 +74,10 @@ export type ComparisonOperation = KeysWhereValue<typeof OPERATION_PURPOSE_DICTIO
|
|
|
74
74
|
export declare const COMPARISON_TYPE_DICTIONARY: {
|
|
75
75
|
readonly EQUAL: "primitive";
|
|
76
76
|
readonly NOTEQUAL: "primitive";
|
|
77
|
-
readonly GEQ: "
|
|
78
|
-
readonly GREATER: "
|
|
79
|
-
readonly LEQ: "
|
|
80
|
-
readonly LESS: "
|
|
77
|
+
readonly GEQ: "numeric";
|
|
78
|
+
readonly GREATER: "numeric";
|
|
79
|
+
readonly LEQ: "numeric";
|
|
80
|
+
readonly LESS: "numeric";
|
|
81
81
|
readonly IN: "array";
|
|
82
82
|
readonly NOTIN: "array";
|
|
83
83
|
readonly MATCH: "string";
|
|
@@ -89,27 +89,31 @@ export type ComparisonTypeToTSType<T extends keyof typeof COMPARISON_TYPE_DICTIO
|
|
|
89
89
|
boolean: boolean;
|
|
90
90
|
string: string;
|
|
91
91
|
number: number;
|
|
92
|
+
date: Date;
|
|
93
|
+
numeric: number | Date;
|
|
92
94
|
array: Primitive[];
|
|
93
95
|
}[typeof COMPARISON_TYPE_DICTIONARY[T]];
|
|
94
|
-
export type FieldType = 'boolean' | 'string' | 'number';
|
|
96
|
+
export type FieldType = 'boolean' | 'string' | 'number' | 'date';
|
|
95
97
|
export type TypeRecord = Record<string, FieldType | FieldType[]>;
|
|
96
98
|
/** Convert a field type string to a language server type */
|
|
97
99
|
export type FieldTypeToTSType<T extends FieldType> = {
|
|
98
100
|
boolean: boolean;
|
|
99
101
|
string: string;
|
|
100
102
|
number: number;
|
|
103
|
+
date: Date;
|
|
101
104
|
}[T];
|
|
102
105
|
/** Primitive values that can be used in comparisons */
|
|
103
106
|
export type Primitive = FieldTypeToTSType<FieldType>;
|
|
104
107
|
/** Convert input type record to language server type record */
|
|
105
108
|
export type ConvertTypeRecord<T extends TypeRecord> = {
|
|
106
|
-
[K in keyof T]:
|
|
109
|
+
[K in keyof T]: T[K] extends FieldType[] ? FieldTypeToTSType<T[K][number]> : T[K] extends FieldType ? FieldTypeToTSType<T[K]> : Primitive;
|
|
107
110
|
};
|
|
111
|
+
export declare const TYPE_PRIORITY: ["boolean", "date", "number", "string"];
|
|
108
112
|
/**
|
|
109
113
|
* A group of conditions joined by a junction operator
|
|
110
114
|
* @template R A record mapping field names to values
|
|
111
115
|
*/
|
|
112
|
-
export interface Group<R extends Record<string,
|
|
116
|
+
export interface Group<R extends Record<string, unknown> = Record<string, Primitive>, V extends boolean = false> {
|
|
113
117
|
type: 'group';
|
|
114
118
|
/** The junction operator */
|
|
115
119
|
operation: JunctionOperation;
|
|
@@ -121,14 +125,14 @@ export interface Group<R extends Record<string, Primitive> = Record<string, Prim
|
|
|
121
125
|
* @template R A record mapping field names to values
|
|
122
126
|
* @template F The name of the field being queried
|
|
123
127
|
*/
|
|
124
|
-
export interface Condition<R extends Record<string,
|
|
128
|
+
export interface Condition<R extends Record<string, unknown>, F extends keyof R, O extends ComparisonOperation> {
|
|
125
129
|
type: 'condition';
|
|
126
130
|
/** The operation */
|
|
127
131
|
operation: O;
|
|
128
132
|
/** The name of the field */
|
|
129
133
|
field: F;
|
|
130
134
|
/** The value being checked */
|
|
131
|
-
value: typeof COMPARISON_TYPE_DICTIONARY[O] extends 'array' ? Array<R[F]> : R[F]
|
|
135
|
+
value: typeof COMPARISON_TYPE_DICTIONARY[O] extends 'array' ? Array<R[F]> : Extract<ComparisonTypeToTSType<O>, R[F]>;
|
|
132
136
|
/** Was this condition validated by the constraints or is its type unknown? */
|
|
133
137
|
validated: true;
|
|
134
138
|
}
|
|
@@ -142,7 +146,7 @@ export interface UncheckedCondition<O extends ComparisonOperation = ComparisonOp
|
|
|
142
146
|
/** The name of the field */
|
|
143
147
|
field: string;
|
|
144
148
|
/** The value being checked */
|
|
145
|
-
value: ComparisonTypeToTSType<O>;
|
|
149
|
+
value: Exclude<ComparisonTypeToTSType<O>, Date>;
|
|
146
150
|
/** Was this condition validated by the constraints or is its type unknown? */
|
|
147
151
|
validated: false;
|
|
148
152
|
}
|
|
@@ -156,7 +160,7 @@ type ReverseAggregate<T extends Record<any, any>> = {
|
|
|
156
160
|
};
|
|
157
161
|
type ReverseAggregatedTypes = ReverseAggregate<typeof COMPARISON_TYPE_DICTIONARY>;
|
|
158
162
|
/** Create a union of conditions; an intersection of the operation type validation and constraint type validation */
|
|
159
|
-
export type CheckedConditionSpread<R extends Record<string,
|
|
163
|
+
export type CheckedConditionSpread<R extends Record<string, unknown>> = {
|
|
160
164
|
[K in keyof R]: {
|
|
161
165
|
[O in keyof ReverseAggregatedTypes]: Condition<R, K, ReverseAggregatedTypes[O]>;
|
|
162
166
|
}[keyof ReverseAggregatedTypes];
|
|
@@ -165,5 +169,8 @@ export type CheckedConditionSpread<R extends Record<string, Primitive>> = {
|
|
|
165
169
|
export type UncheckedConditionSpread = {
|
|
166
170
|
[O in keyof ReverseAggregatedTypes]: UncheckedCondition<ReverseAggregatedTypes[O]>;
|
|
167
171
|
}[keyof ReverseAggregatedTypes];
|
|
168
|
-
export type Expression<R extends Record<string,
|
|
172
|
+
export type Expression<R extends Record<string, unknown> = Record<string, Primitive>, V extends boolean = false> = Group<R, V> | (V extends true ? CheckedConditionSpread<R> : CheckedConditionSpread<R> | UncheckedConditionSpread);
|
|
173
|
+
export type UncheckedExpression = (Omit<Group, 'constituents'> & {
|
|
174
|
+
constituents: UncheckedExpression[];
|
|
175
|
+
}) | UncheckedConditionSpread;
|
|
169
176
|
export {};
|
package/package.json
CHANGED
|
@@ -6,18 +6,27 @@
|
|
|
6
6
|
},
|
|
7
7
|
"homepage": "https://github.com/guidance-analytics/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": "2.0.1",
|
|
10
10
|
"repository": {
|
|
11
11
|
"type": "git",
|
|
12
|
-
"url": "https://github.com/guidance-analytics/wizard-ql.git"
|
|
12
|
+
"url": "git+https://github.com/guidance-analytics/wizard-ql.git"
|
|
13
13
|
},
|
|
14
|
-
"keywords": [
|
|
14
|
+
"keywords": [
|
|
15
|
+
"query",
|
|
16
|
+
"language",
|
|
17
|
+
"querylanguage",
|
|
18
|
+
"parser",
|
|
19
|
+
"cel",
|
|
20
|
+
"expression"
|
|
21
|
+
],
|
|
15
22
|
"license": "Apache-2.0",
|
|
16
23
|
"main": "dist/index.js",
|
|
17
24
|
"module": "dist/index.js",
|
|
18
25
|
"type": "module",
|
|
19
26
|
"types": "dist/index.d.ts",
|
|
20
|
-
"files": [
|
|
27
|
+
"files": [
|
|
28
|
+
"dist/"
|
|
29
|
+
],
|
|
21
30
|
"scripts": {
|
|
22
31
|
"build": "[ -d dist ] && rm -r dist; bun build --target=node --format=esm --packages external src/index.ts --outfile dist/index.js && tsc --project tsconfig.build.json",
|
|
23
32
|
"lint": "eslint ."
|