wizard-ql 2.1.1 → 3.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,384 @@
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
+ export interface WizardParserConfig<F extends FieldTypeRecord, O extends OperatorRecord, out V extends boolean, D extends string> {
62
+ /**
63
+ * Restricted fields.\
64
+ * Restrict an entire field by setting it to true.\
65
+ * Restrict an exact value by providing a string.\
66
+ * Restrict a pattern by providing a Regex expression.\
67
+ * By default allow any value and restrict a collection of values by passing ['deny', VALUES[]]\
68
+ * By default restrict any value and allow a collection of values by passing ['allow', VALUES[]]\
69
+ * If the query value is of array type, it will check all entries of the array and ensure they're all allowed.\
70
+ * This check runs before type coercion so the value checked will always be a string. However, quotes and escapes WILL be removed
71
+ */
72
+ restricted?: Partial<Record<keyof F | (string & {}), boolean | ['allow' | 'deny', Array<string | RegExp>]>>;
73
+ /**
74
+ * The types of fields\
75
+ * Either provide the field type singularly or permit multiple types with an array of field types\
76
+ * A field with type 'date' will parse the value into a Date object if possible (Wizard will not attempt to do this otherwise)\
77
+ * Type coercion priority: boolean -> date -> number -> string
78
+ */
79
+ types?: F;
80
+ /**
81
+ * Field names in restriction checks and type checks are case insensitive
82
+ * @note If enabled, all fields will be returned as their casing denoted by the types or restricted record
83
+ * @warn Mismatching casing between the restricted record and the type record will prioritize the restricted record
84
+ */
85
+ caseInsensitive?: boolean;
86
+ /**
87
+ * Disallow fields that are not present in the "restricted" record or the "types" record
88
+ */
89
+ disallowUnvalidated?: V;
90
+ /**
91
+ * A callback that determines how dates are interpreted\
92
+ * By default, uses `new Date()`
93
+ */
94
+ dateInterpreter?: (v: string | number) => Date;
95
+ /**
96
+ * A callback that determines how dates are stringified\
97
+ * By default, uses `.toISOString()`
98
+ */
99
+ dateSerializer?: (v: Date) => string;
100
+ operators?: (O | ValidateGlobalUniqueness<O> | ValidateUppercaseKeys<O>) & ValidateGlobalUniqueness<O> & ValidateUppercaseKeys<O>;
101
+ /**
102
+ * The default operator and value (in string form) for an implicit condition (positive variant)\
103
+ * Example: `field` or negative: `!field`\
104
+ * The negative will be taken by taking the complement of the default condition\
105
+ * By default, on the default operator definition, this is "EQUAL true". Thus, `field` -> `field EQUAL true` and `!field` -> `field NOTEQUAL true`\
106
+ * If undefined with a custom operator definition or specified false, implicit conditions will not be parsed
107
+ */
108
+ implicitCondition?: false | {
109
+ /** The default implicit operator */
110
+ operator: GetConditionOperators<O>;
111
+ /** The default implicit value in string form */
112
+ value: string;
113
+ /** How the value should be parsed (as a type) */
114
+ asType: FieldType | FieldType[];
115
+ };
116
+ dialects?: Record<D, Record<GetOperators<O>, string>>;
117
+ }
118
+ export interface StringifyOptions<D extends string> {
119
+ /**
120
+ * The dialect to use for expressing operations
121
+ */
122
+ dialect: D;
123
+ /**
124
+ * Always surround a group with parentheses, even if unnecessary
125
+ * @default false
126
+ */
127
+ alwaysParenthesize?: boolean;
128
+ /**
129
+ * Don't include spaces (Spaces will be included around operators that are "linguistic")
130
+ * @default false
131
+ */
132
+ compact?: boolean;
133
+ /**
134
+ * Condense implicit-able value conditions to their implicit denotations\
135
+ * With the default operator definitions, this is booleans
136
+ * @example 'field'
137
+ * @example '!field'
138
+ * @default false
139
+ */
140
+ condenseImplicit?: boolean;
141
+ }
142
+ export interface AggregationValue<O extends OperatorRecord = OperatorRecord> {
143
+ /** The value being checked */
144
+ value: GetConditionTSType<(O[GetConditionOperators<O>] & OperatorRecordEntry & {
145
+ type: ConditionOperatorComparisonType;
146
+ })['type']>;
147
+ /** The operator being used */
148
+ operation: GetOperators<O>;
149
+ /** Is this an exclusionary operator? (The negation of a defined operator) */
150
+ exclusionary: boolean;
151
+ }
152
+ /**
153
+ * A WizardQL parser instance
154
+ */
155
+ 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> {
156
+ static readonly DEFAULT_OPERATORS: {
157
+ readonly AND: {
158
+ readonly negationName: "OR";
159
+ readonly type: "productjunction";
160
+ readonly aliases: readonly ["&&", "&", "^", "∧"];
161
+ readonly negationAliases: readonly ["||", "|", "V", "∨"];
162
+ };
163
+ readonly EQUAL: {
164
+ readonly negationName: "NOTEQUAL";
165
+ readonly type: "primitive";
166
+ readonly aliases: readonly ["EQUALS", "EQ", "IS", "=", "=="];
167
+ readonly negationAliases: readonly ["NOTEQUALS", "NEQ", "ISNT", "!=", "!==", "≠"];
168
+ };
169
+ readonly LESS: {
170
+ readonly negationName: "GEQ";
171
+ readonly type: "numeric";
172
+ readonly aliases: readonly ["<"];
173
+ readonly negationAliases: readonly [">=", "=>", "≥"];
174
+ readonly exclusionary: true;
175
+ };
176
+ readonly GREATER: {
177
+ readonly negationName: "LEQ";
178
+ readonly type: "numeric";
179
+ readonly aliases: readonly [">", "MORE", "MORETHAN"];
180
+ readonly negationAliases: readonly ["<=", "=<", "≤"];
181
+ readonly exclusionary: true;
182
+ };
183
+ readonly IN: {
184
+ readonly negationName: "NOTIN";
185
+ readonly type: "array";
186
+ readonly aliases: readonly [":", "∈"];
187
+ readonly negationAliases: readonly ["!:", "∉"];
188
+ };
189
+ readonly MATCH: {
190
+ readonly negationName: "NOTMATCH";
191
+ readonly type: "string";
192
+ readonly aliases: readonly ["MATCHES", "~", "≅"];
193
+ readonly negationAliases: readonly ["NOTMATCHES", "!~", "≇"];
194
+ };
195
+ };
196
+ static readonly DEFAULT_DIALECTS: {
197
+ readonly programmatic: {
198
+ readonly AND: "&";
199
+ readonly OR: "|";
200
+ readonly EQUAL: "=";
201
+ readonly NOTEQUAL: "!=";
202
+ readonly GEQ: ">=";
203
+ readonly GREATER: ">";
204
+ readonly LEQ: "<=";
205
+ readonly LESS: "<";
206
+ readonly IN: ":";
207
+ readonly NOTIN: "!:";
208
+ readonly MATCH: "~";
209
+ readonly NOTMATCH: "!~";
210
+ };
211
+ readonly linguistic: {
212
+ readonly AND: "AND";
213
+ readonly OR: "OR";
214
+ readonly EQUAL: "EQUALS";
215
+ readonly NOTEQUAL: "NOTEQUALS";
216
+ readonly GEQ: "GEQ";
217
+ readonly GREATER: "GREATER";
218
+ readonly LEQ: "LEQ";
219
+ readonly LESS: "LESS";
220
+ readonly IN: "IN";
221
+ readonly NOTIN: "NOTIN";
222
+ readonly MATCH: "MATCHES";
223
+ readonly NOTMATCH: "NOTMATCHES";
224
+ };
225
+ readonly formal: {
226
+ readonly AND: "∧";
227
+ readonly OR: "∨";
228
+ readonly EQUAL: "=";
229
+ readonly NOTEQUAL: "≠";
230
+ readonly GEQ: "≥";
231
+ readonly GREATER: ">";
232
+ readonly LEQ: "≤";
233
+ readonly LESS: "<";
234
+ readonly IN: "∈";
235
+ readonly NOTIN: "∉";
236
+ readonly MATCH: "≅";
237
+ readonly NOTMATCH: "≇";
238
+ };
239
+ };
240
+ protected static readonly QUOTES: readonly ["'", "\"", "`"];
241
+ protected static readonly NEGATORS: readonly ["!"];
242
+ protected static readonly ARRAY_DELIMITERS: readonly [","];
243
+ protected static readonly ARRAY_BRACKETS: [["[", "]"], ["{", "}"]];
244
+ protected static readonly GROUP_BRACKETS: [["(", ")"]];
245
+ protected readonly OPERATOR_DICTIONARY: InternalOperationDictionary<O>;
246
+ protected readonly CONFIG: WizardParserConfig<F, O, V, D> & Required<Pick<WizardParserConfig<F, O, V, D>, 'operators'>>;
247
+ protected readonly TOKEN_REGEX: RegExp;
248
+ protected readonly QUOTE_REGEX: RegExp;
249
+ protected readonly QUOTE_EDGE_REGEX: RegExp;
250
+ protected readonly ARRAY_DELIMITER_REGEX: RegExp;
251
+ protected readonly FIELD_LIST: Map<string, string>;
252
+ /**
253
+ * Construct a WizardQL parser
254
+ * @param config The parser configuration
255
+ */
256
+ constructor(config?: WizardParserConfig<F, O, V, D>);
257
+ /**
258
+ * Take a string, sanitize it, and push it to an array if it has a length
259
+ * @param array The array to push to
260
+ * @param item The item to sanitize and push
261
+ * @param index The index of the token in the original string
262
+ * @returns The token, if pushed
263
+ */
264
+ protected static pushSanitized(array: Token[], item: string, index: number): Token | undefined;
265
+ /**
266
+ * Given an input, parse a date using the provided parse function or the default method
267
+ * @param v The input to parse
268
+ * @returns The date value
269
+ */
270
+ protected interpretDate(v: string | number): Date;
271
+ /**
272
+ * Take a string and tokenize it for parsing with a specific pattern
273
+ * @param expression The expression to tokenize
274
+ * @param pattern The tokenization pattern
275
+ * @returns An array of tokens
276
+ */
277
+ protected _tokenize(expression: string, pattern: RegExp): Token[];
278
+ /**
279
+ * Take a string and tokenize it for parsing
280
+ * @param expression The expression to tokenize
281
+ * @returns An array of tokens
282
+ */
283
+ tokenize(expression: string): Token[];
284
+ /**
285
+ * Get an opening closure's closing index
286
+ * @param tokens The token array
287
+ * @param start The index of the opening closure
288
+ * @param opening The token to consider as opening
289
+ * @param closing The token to consider as closing
290
+ * @returns The index of the closing token or -1 if not found
291
+ */
292
+ protected static getClosingIndex(tokens: readonly Token[], start: number, opening: string, closing: string): number;
293
+ /**
294
+ * Process a token to get its unquoted, escaped, and unescaped varients\
295
+ * Order: unquote -> escaped -> unescaped
296
+ * @param token The token to process
297
+ * @returns An object containing the variants
298
+ */
299
+ protected processToken(token: string): ProcessedToken;
300
+ /**
301
+ * Apply De Morgan's Law to an expression and complement it\
302
+ * (Mutating operation)
303
+ * @param expression The expression
304
+ * @throws {Error} If the inverse operation cannot be found
305
+ */
306
+ protected complementExpression(expression: Expression<F, O, V>): void;
307
+ /**
308
+ * Coerce a string into the appropriate type for the operation based on the field type and operator
309
+ * @param processedToken The value to coerce (already processed)
310
+ * @param fieldTypes The supported field types
311
+ * @param operatorTypes The supported operator types
312
+ * @returns The coerced type
313
+ * @throws {Error} Message is 'operator' if the operator type cannot be coerced and 'field' if the field type cannot be coerced
314
+ */
315
+ protected coerceType(processedToken: ProcessedToken, fieldTypes: FieldType[], operatorTypes: FieldType[]): Primitive;
316
+ /**
317
+ * Validate that a condition meets constraints
318
+ * @warn This operation mutates the condition to apply the validated field and perform type coercion
319
+ * @param condition The condition to validate
320
+ * @param valueIsImplicit Was this value implicitly inferred? If so, don't attempt stringification
321
+ * @param ctx Error Context
322
+ * @throws {ConstraintError}
323
+ * @returns The same reference to the condition
324
+ */
325
+ protected validateCondition(condition: Omit<UncheckedCondition<O>, 'validated' | 'value'> & {
326
+ value: string | string[];
327
+ }, valueIsImplicit?: boolean, ctx?: Context): Exclude<Expression<F, O, V>, Group<F, O, V>>;
328
+ /**
329
+ * Parse tokens into an object expression
330
+ * @param tokens The tokens to parse into an object expression
331
+ * @param _offset The token offset
332
+ * @returns An expression
333
+ * @throws {ParseError | ConstraintError}
334
+ */
335
+ protected _parse(tokens: readonly Token[], _offset: number): Expression<F, O, V> | null;
336
+ /**
337
+ * Parse a Wizard expression into its object form
338
+ * @param expression The Wizard expression as a string or as an array of tokens
339
+ * @returns The object representation
340
+ * @throws {ParseError | ConstraintError}
341
+ */
342
+ parse(expression: string | string[] | Token[]): Expression<F, O, V> | null;
343
+ /**
344
+ * Add quotes to a string value if it resembles another primitive
345
+ * @param value The value
346
+ * @returns The value, possibly quoted, stringified
347
+ */
348
+ protected addQuotesIfNecessary(value: Primitive): string;
349
+ /**
350
+ * Convert a parsed WizardQL expression to a string
351
+ * @param expression The expression
352
+ * @param opts Formatting options
353
+ * @throws {Error} If no dialect dictionary is defined or dialect supplied is unknown
354
+ * @returns The formatted string
355
+ */
356
+ stringify(expression: Expression<FieldTypeRecord, O>, opts: D | StringifyOptions<D>): string;
357
+ /**
358
+ * (Get an array entry from a map or insert one) and return it
359
+ * @param map The map
360
+ * @param key The key the access
361
+ * @returns The entry array
362
+ */
363
+ protected static getOrPutArrayInMap<T, U>(map: Map<T, U[]>, key: T): U[];
364
+ /**
365
+ * Summarize a parsed expression by aggregating its queries by field
366
+ * @param expressions The expression or expressions to aggregate
367
+ * @returns The aggregation as a map, mapping field name to queries
368
+ */
369
+ summarize(expressions: Expression<F, O> | Array<Expression<F, O>>): Map<string, Array<AggregationValue<O>>>;
370
+ /**
371
+ * Given an expression part, return its type
372
+ * @param segment The segment/part
373
+ * @param activeArrayOpeningBracket If actively in an array, the opening bracket
374
+ * @returns The part type
375
+ */
376
+ getPartType(segment: string, activeArrayOpeningBracket?: string): PartType;
377
+ /**
378
+ * Resolve an alias to get an operator's core name
379
+ * @param alias The alias
380
+ * @returns The core name
381
+ */
382
+ resolveOperatorAlias(alias: string): GetOperators<O> | undefined;
383
+ }
384
+ 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;