wizard-ql 2.1.1 → 4.0.0

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.
@@ -0,0 +1,402 @@
1
+ import { type ConditionOperatorComparisonType, type Expression, type FieldType, type FieldTypeRecord, type GetConditionOperators, type GetConditionTSType, type GetJunctionOperators, type GetOperators, type Group, type JunctionOperatorType, type OperatorRecord, type OperatorRecordEntry, type PartType, type Primitive, type Token, type UncheckedCondition } from './spec';
2
+ interface InternalConditionOperatorDefinition<O extends OperatorRecord> {
3
+ name: GetConditionOperators<O>;
4
+ negation: GetConditionOperators<O>;
5
+ type: ConditionOperatorComparisonType;
6
+ exclusionary: boolean;
7
+ }
8
+ interface InternalJunctionOperatorDefinition<O extends OperatorRecord> {
9
+ name: GetJunctionOperators<O>;
10
+ negation: GetJunctionOperators<O>;
11
+ type: JunctionOperatorType;
12
+ exclusionary: boolean;
13
+ }
14
+ interface Context {
15
+ tokens: readonly Token[];
16
+ startToken: Token | undefined;
17
+ startIndex: number | undefined;
18
+ endToken?: Token | undefined;
19
+ endIndex?: number | undefined;
20
+ }
21
+ interface ProcessedToken {
22
+ /** The raw token */
23
+ raw: string;
24
+ /** The token's quote contents, if surrounded by quotes */
25
+ unquoted: string | undefined;
26
+ /** The token's resolved contents or quote contents, including escape backslashes */
27
+ escaped: string;
28
+ /** The token's resolved contents, with the escape backslashes removed */
29
+ unescaped: string;
30
+ }
31
+ type StringsInOtherEntries<T, CurrentK> = {
32
+ [K in keyof T]: K extends CurrentK ? never : AllStringsInEntry<T[K]> | K;
33
+ }[keyof T];
34
+ type AllStringsInEntry<E> = E extends OperatorRecordEntry ? E['negationName'] | (E['aliases'] extends ReadonlyArray<infer A> ? A : never) | (E['negationAliases'] extends ReadonlyArray<infer NA> ? NA : never) : never;
35
+ type ValidationError<Msg extends string> = string & {
36
+ __error: `Error: ${Msg}`;
37
+ };
38
+ type ValidateGlobalUniqueness<O extends OperatorRecord> = {
39
+ [K in keyof O]: O[K] extends OperatorRecordEntry ? O[K] & ({
40
+ negationName: O[K]['negationName'] extends K | StringsInOtherEntries<O, K> ? ValidationError<`Negation '${O[K]['negationName'] & string}' is already used as a key or in another entry (or not all uppercase) ${(K | StringsInOtherEntries<O, K>) & string}`> : O[K]['negationName'];
41
+ aliases?: O[K] extends (OperatorRecordEntry & {
42
+ aliases: ReadonlyArray<infer A>;
43
+ }) ? {
44
+ [I in keyof O[K]['aliases']]: O[K]['aliases'][I] extends K | O[K]['negationName'] | StringsInOtherEntries<O, K> | Exclude<A, O[K]['aliases'][I]> ? ValidationError<`Alias '${O[K]['aliases'][I] & string}' is already used as a key or in another entry (or not all uppercase) ${(K | O[K]['negationName'] | StringsInOtherEntries<O, K> | Exclude<A, O[K]['aliases'][I]>) & string}`> : O[K]['aliases'][I];
45
+ } : never;
46
+ negationAliases?: O[K] extends (OperatorRecordEntry & {
47
+ negationAliases: ReadonlyArray<infer NA>;
48
+ }) ? {
49
+ [I in keyof O[K]['negationAliases']]: O[K]['negationAliases'][I] extends K | O[K]['negationName'] | (O[K] extends {
50
+ aliases: ReadonlyArray<infer A>;
51
+ } ? A : never) | StringsInOtherEntries<O, K> | Exclude<NA, O[K]['negationAliases'][I]> ? ValidationError<`NegationAlias '${O[K]['negationAliases'][I] & string}' is already used as a key or in another entry (or not all uppercase) ${(K | O[K]['negationName'] | (O[K] extends {
52
+ aliases: ReadonlyArray<infer A>;
53
+ } ? A : never) | StringsInOtherEntries<O, K> | Exclude<NA, O[K]['negationAliases'][I]>) & string}`> : O[K]['negationAliases'][I];
54
+ } : never;
55
+ }) : ValidationError<`Invalid operator record key '${K & string}' (is it all uppercase?)`>;
56
+ };
57
+ type ValidateUppercaseKeys<O extends OperatorRecord> = {
58
+ [K in keyof O]: K extends Uppercase<K & string> ? O[K] : ValidationError<`Operator Record contains non-uppercase key: '${K & string}'`>;
59
+ };
60
+ type InternalOperationDictionary<O extends OperatorRecord> = Record<GetConditionOperators<O> | GetJunctionOperators<O> | string, InternalConditionOperatorDefinition<O> | InternalJunctionOperatorDefinition<O>>;
61
+ type ReadonlyNonemptyArray<T> = readonly [T, ...T[]];
62
+ export interface WizardParserConfig<F extends FieldTypeRecord, O extends OperatorRecord, out V extends boolean, D extends string> {
63
+ /**
64
+ * Restricted fields.\
65
+ * Restrict an entire field by setting it to true.\
66
+ * Restrict an exact value by providing a string.\
67
+ * Restrict a pattern by providing a Regex expression.\
68
+ * By default allow any value and restrict a collection of values by passing ['deny', VALUES[]]\
69
+ * By default restrict any value and allow a collection of values by passing ['allow', VALUES[]]\
70
+ * If the query value is of array type, it will check all entries of the array and ensure they're all allowed.\
71
+ * This check runs before type coercion so the value checked will always be a string. However, quotes and escapes WILL be removed
72
+ */
73
+ restricted?: Partial<Record<keyof F | (string & {}), boolean | ['allow' | 'deny', ReadonlyArray<string | RegExp>]>>;
74
+ /**
75
+ * The types of fields\
76
+ * Either provide the field type singularly or permit multiple types with an array of field types\
77
+ * A field with type 'date' will parse the value into a Date object if possible (Wizard will not attempt to do this otherwise)\
78
+ * Type coercion priority: boolean -> date -> number -> string
79
+ */
80
+ types?: F;
81
+ /**
82
+ * Field names in restriction checks and type checks are case insensitive
83
+ * @note If enabled, all fields will be returned as their casing denoted by the types or restricted record
84
+ * @warn Mismatching casing between the restricted record and the type record will prioritize the restricted record
85
+ */
86
+ caseInsensitive?: boolean;
87
+ /**
88
+ * Disallow fields that are not present in the "restricted" record or the "types" record
89
+ */
90
+ disallowUnvalidated?: V;
91
+ /**
92
+ * A callback that determines how dates are interpreted\
93
+ * By default, uses `new Date()`
94
+ */
95
+ dateInterpreter?: (v: string | number) => Date;
96
+ /**
97
+ * A callback that determines how dates are stringified\
98
+ * By default, uses `.toISOString()`
99
+ */
100
+ dateSerializer?: (v: Date) => string;
101
+ operators?: (O | ValidateGlobalUniqueness<O> | ValidateUppercaseKeys<O>) & ValidateGlobalUniqueness<O> & ValidateUppercaseKeys<O>;
102
+ /**
103
+ * The default operator and value (in string form) for an implicit condition (positive variant)\
104
+ * Example: `field` or negative: `!field`\
105
+ * The negative will be taken by taking the complement of the default condition\
106
+ * By default, on the default operator definition, this is "EQUAL true". Thus, `field` -> `field EQUAL true` and `!field` -> `field NOTEQUAL true`\
107
+ * If undefined with a custom operator definition or specified false, implicit conditions will not be parsed
108
+ */
109
+ implicitCondition?: false | {
110
+ /** The default implicit operator */
111
+ operator: GetConditionOperators<O>;
112
+ /** The default implicit value in string form */
113
+ value: string;
114
+ /** How the value should be parsed (as a type) */
115
+ asType: FieldType | readonly FieldType[];
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
+ */
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
+ };
133
+ }
134
+ export interface StringifyOptions<D extends string> {
135
+ /**
136
+ * The dialect to use for expressing operations
137
+ */
138
+ dialect: D;
139
+ /**
140
+ * Always surround a group with parentheses, even if unnecessary
141
+ * @default false
142
+ */
143
+ alwaysParenthesize?: boolean;
144
+ /**
145
+ * Don't include spaces (Spaces will be included around operators that are "linguistic")
146
+ * @default false
147
+ */
148
+ compact?: boolean;
149
+ /**
150
+ * Condense implicit-able value conditions to their implicit denotations\
151
+ * With the default operator definitions, this is booleans
152
+ * @example 'field'
153
+ * @example '!field'
154
+ * @default false
155
+ */
156
+ condenseImplicit?: boolean;
157
+ }
158
+ export interface AggregationValue<O extends OperatorRecord = OperatorRecord> {
159
+ /** The value being checked */
160
+ value: GetConditionTSType<(O[GetConditionOperators<O>] & OperatorRecordEntry & {
161
+ type: ConditionOperatorComparisonType;
162
+ })['type']>;
163
+ /** The operator being used */
164
+ operation: GetOperators<O>;
165
+ /** Is this an exclusionary operator? (The negation of a defined operator) */
166
+ exclusionary: boolean;
167
+ }
168
+ /**
169
+ * A WizardQL parser instance
170
+ */
171
+ export declare class WizardParser<const F extends FieldTypeRecord, const O extends OperatorRecord = typeof WizardParser.DEFAULT_OPERATORS, const V extends boolean = false, const D extends string = typeof WizardParser.DEFAULT_OPERATORS extends O ? keyof typeof WizardParser.DEFAULT_DIALECTS : string> {
172
+ static readonly DEFAULT_OPERATORS: {
173
+ readonly AND: {
174
+ readonly negationName: "OR";
175
+ readonly type: "productjunction";
176
+ readonly aliases: readonly ["&&", "&", "^", "∧"];
177
+ readonly negationAliases: readonly ["||", "|", "V", "∨"];
178
+ };
179
+ readonly EQUAL: {
180
+ readonly negationName: "NOTEQUAL";
181
+ readonly type: "primitive";
182
+ readonly aliases: readonly ["EQUALS", "EQ", "IS", "=", "=="];
183
+ readonly negationAliases: readonly ["NOTEQUALS", "NEQ", "ISNT", "!=", "!==", "≠"];
184
+ };
185
+ readonly LESS: {
186
+ readonly negationName: "GEQ";
187
+ readonly type: "numeric";
188
+ readonly aliases: readonly ["<"];
189
+ readonly negationAliases: readonly [">=", "=>", "≥"];
190
+ readonly exclusionary: true;
191
+ };
192
+ readonly GREATER: {
193
+ readonly negationName: "LEQ";
194
+ readonly type: "numeric";
195
+ readonly aliases: readonly [">", "MORE", "MORETHAN"];
196
+ readonly negationAliases: readonly ["<=", "=<", "≤"];
197
+ readonly exclusionary: true;
198
+ };
199
+ readonly IN: {
200
+ readonly negationName: "NOTIN";
201
+ readonly type: "array";
202
+ readonly aliases: readonly [":", "∈"];
203
+ readonly negationAliases: readonly ["!:", "∉"];
204
+ };
205
+ readonly MATCH: {
206
+ readonly negationName: "NOTMATCH";
207
+ readonly type: "string";
208
+ readonly aliases: readonly ["MATCHES", "~", "≅"];
209
+ readonly negationAliases: readonly ["NOTMATCHES", "!~", "≇"];
210
+ };
211
+ };
212
+ static readonly DEFAULT_DIALECTS: {
213
+ readonly programmatic: {
214
+ readonly AND: "&";
215
+ readonly OR: "|";
216
+ readonly EQUAL: "=";
217
+ readonly NOTEQUAL: "!=";
218
+ readonly GEQ: ">=";
219
+ readonly GREATER: ">";
220
+ readonly LEQ: "<=";
221
+ readonly LESS: "<";
222
+ readonly IN: ":";
223
+ readonly NOTIN: "!:";
224
+ readonly MATCH: "~";
225
+ readonly NOTMATCH: "!~";
226
+ };
227
+ readonly linguistic: {
228
+ readonly AND: "AND";
229
+ readonly OR: "OR";
230
+ readonly EQUAL: "EQUALS";
231
+ readonly NOTEQUAL: "NOTEQUALS";
232
+ readonly GEQ: "GEQ";
233
+ readonly GREATER: "GREATER";
234
+ readonly LEQ: "LEQ";
235
+ readonly LESS: "LESS";
236
+ readonly IN: "IN";
237
+ readonly NOTIN: "NOTIN";
238
+ readonly MATCH: "MATCHES";
239
+ readonly NOTMATCH: "NOTMATCHES";
240
+ };
241
+ readonly formal: {
242
+ readonly AND: "∧";
243
+ readonly OR: "∨";
244
+ readonly EQUAL: "=";
245
+ readonly NOTEQUAL: "≠";
246
+ readonly GEQ: "≥";
247
+ readonly GREATER: ">";
248
+ readonly LEQ: "≤";
249
+ readonly LESS: "<";
250
+ readonly IN: "∈";
251
+ readonly NOTIN: "∉";
252
+ readonly MATCH: "≅";
253
+ readonly NOTMATCH: "≇";
254
+ };
255
+ };
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: [["(", ")"]];
261
+ protected readonly OPERATOR_DICTIONARY: InternalOperationDictionary<O>;
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
+ };
265
+ protected readonly TOKEN_REGEX: RegExp;
266
+ protected readonly QUOTE_REGEX: RegExp;
267
+ protected readonly QUOTE_EDGE_REGEX: RegExp;
268
+ protected readonly ARRAY_DELIMITER_REGEX: RegExp;
269
+ protected readonly FIELD_LIST: Map<string, string>;
270
+ /**
271
+ * Construct a WizardQL parser
272
+ * @param config The parser configuration
273
+ */
274
+ constructor(config?: WizardParserConfig<F, O, V, D>);
275
+ /**
276
+ * Take a string, sanitize it, and push it to an array if it has a length
277
+ * @param array The array to push to
278
+ * @param item The item to sanitize and push
279
+ * @param index The index of the token in the original string
280
+ * @returns The token, if pushed
281
+ */
282
+ protected static pushSanitized(array: Token[], item: string, index: number): Token | undefined;
283
+ /**
284
+ * Given an input, parse a date using the provided parse function or the default method
285
+ * @param v The input to parse
286
+ * @returns The date value
287
+ */
288
+ protected interpretDate(v: string | number): Date;
289
+ /**
290
+ * Take a string and tokenize it for parsing with a specific pattern
291
+ * @param expression The expression to tokenize
292
+ * @param pattern The tokenization pattern
293
+ * @returns An array of tokens
294
+ */
295
+ protected _tokenize(expression: string, pattern: RegExp): Token[];
296
+ /**
297
+ * Take a string and tokenize it for parsing
298
+ * @param expression The expression to tokenize
299
+ * @returns An array of tokens
300
+ */
301
+ tokenize(expression: string): Token[];
302
+ /**
303
+ * Get an opening closure's closing index
304
+ * @param tokens The token array
305
+ * @param start The index of the opening closure
306
+ * @param opening The token to consider as opening
307
+ * @param closing The token to consider as closing
308
+ * @returns The index of the closing token or -1 if not found
309
+ */
310
+ protected static getClosingIndex(tokens: readonly Token[], start: number, opening: string, closing: string): number;
311
+ /**
312
+ * Process a token to get its unquoted, escaped, and unescaped varients\
313
+ * Order: unquote -> escaped -> unescaped
314
+ * @param token The token to process
315
+ * @returns An object containing the variants
316
+ */
317
+ protected processToken(token: string): ProcessedToken;
318
+ /**
319
+ * Apply De Morgan's Law to an expression and complement it\
320
+ * (Mutating operation)
321
+ * @param expression The expression
322
+ * @throws {Error} If the inverse operation cannot be found
323
+ */
324
+ protected complementExpression(expression: Expression<F, O, V>): void;
325
+ /**
326
+ * Coerce a string into the appropriate type for the operation based on the field type and operator
327
+ * @param processedToken The value to coerce (already processed)
328
+ * @param fieldTypes The supported field types
329
+ * @param operatorTypes The supported operator types
330
+ * @returns The coerced type
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
332
+ */
333
+ protected coerceType(processedToken: ProcessedToken, fieldTypes: FieldType[], operatorTypes: FieldType[]): Primitive;
334
+ /**
335
+ * Validate that a condition meets constraints
336
+ * @warn This operation mutates the condition to apply the validated field and perform type coercion
337
+ * @param condition The condition to validate
338
+ * @param valueIsImplicit Was this value implicitly inferred? If so, don't attempt stringification
339
+ * @param ctx Error Context
340
+ * @throws {ConstraintError}
341
+ * @returns The same reference to the condition
342
+ */
343
+ protected validateCondition(condition: Omit<UncheckedCondition<O>, 'validated' | 'value'> & {
344
+ value: string | string[];
345
+ }, valueIsImplicit?: boolean, ctx?: Context): Exclude<Expression<F, O, V>, Group<F, O, V>>;
346
+ /**
347
+ * Parse tokens into an object expression
348
+ * @param tokens The tokens to parse into an object expression
349
+ * @param _offset The token offset
350
+ * @returns An expression
351
+ * @throws {ParseError | ConstraintError}
352
+ */
353
+ protected _parse(tokens: readonly Token[], _offset: number): Expression<F, O, V> | null;
354
+ /**
355
+ * Parse a Wizard expression into its object form
356
+ * @param expression The Wizard expression as a string or as an array of tokens
357
+ * @returns The object representation
358
+ * @throws {ParseError | ConstraintError}
359
+ */
360
+ parse(expression: string | string[] | Token[]): Expression<F, O, V> | null;
361
+ /**
362
+ * Add quotes to a string value if it resembles another primitive
363
+ * @param value The value
364
+ * @returns The value, possibly quoted, stringified
365
+ */
366
+ protected addQuotesIfNecessary(value: Primitive): string;
367
+ /**
368
+ * Convert a parsed WizardQL expression to a string
369
+ * @param expression The expression
370
+ * @param opts Formatting options
371
+ * @throws {Error} If no dialect dictionary is defined or dialect supplied is unknown
372
+ * @returns The formatted string
373
+ */
374
+ stringify(expression: Expression<F, O>, opts: D | StringifyOptions<D>): string;
375
+ /**
376
+ * (Get an array entry from a map or insert one) and return it
377
+ * @param map The map
378
+ * @param key The key the access
379
+ * @returns The entry array
380
+ */
381
+ protected static getOrPutArrayInMap<T, U>(map: Map<T, U[]>, key: T): U[];
382
+ /**
383
+ * Summarize a parsed expression by aggregating its queries by field
384
+ * @param expressions The expression or expressions to aggregate
385
+ * @returns The aggregation as a map, mapping field name to queries
386
+ */
387
+ summarize(expressions: Expression<F, O> | Array<Expression<F, O>>): Map<string, Array<AggregationValue<O>>>;
388
+ /**
389
+ * Given an expression part, return its type
390
+ * @param segment The segment/part
391
+ * @param activeArrayOpeningBracket If actively in an array, the opening bracket
392
+ * @returns The part type
393
+ */
394
+ getPartType(segment: string, activeArrayOpeningBracket?: string): PartType;
395
+ /**
396
+ * Resolve an alias to get an operator's core name
397
+ * @param alias The alias
398
+ * @returns The core name
399
+ */
400
+ resolveOperatorAlias(alias: string): GetOperators<O> | undefined;
401
+ }
402
+ export {};
package/dist/regex.d.ts CHANGED
@@ -1,11 +1,30 @@
1
1
  export declare const ESCAPE_REGEX = "(?<=(?<!\\\\)(?:\\\\\\\\)*)";
2
2
  /**
3
3
  * Create a Regex to look for quotes
4
- * @returns The regular expression
4
+ * @param quotes Characters to consider quotes
5
+ * @throws {Error} If the runtime does not have a RegExp.escape function
6
+ * @returns The regular expression string
5
7
  */
6
- export declare function createQuoteRegexString(): string;
8
+ export declare function createQuoteRegexString(quotes: readonly string[]): string;
9
+ /**
10
+ * Create a Regex to look for array delimiting
11
+ * @param quotes Characters to consider quotes
12
+ * @param delimiters Characters to consider array delimiters
13
+ * @throws {Error} If the runtime does not have a RegExp.escape function
14
+ * @returns The regular expression string
15
+ */
16
+ export declare function createArrayDelimitRegexString(quotes: readonly string[], delimiters: readonly string[]): string;
17
+ /**
18
+ * Check if a string is entirely alpha characters
19
+ * @param text The text
20
+ * @returns Whether the text is solely alpha or not
21
+ */
22
+ export declare function isAlpha(text: string): boolean;
7
23
  /**
8
24
  * Create a Regex to find tokens
9
- * @returns The regular expression
25
+ * @param keywords Keywords to isolate
26
+ * @param quotes Characters to consider quotes
27
+ * @throws {Error} If the runtime does not have a RegExp.escape function
28
+ * @returns The regular expression string
10
29
  */
11
- export declare function createTokenRegexString(): string;
30
+ export declare function createTokenRegexString(keywords: string[], quotes: readonly string[]): string;