@allurereport/aql 3.1.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.
package/README.md ADDED
@@ -0,0 +1,482 @@
1
+ # Allure Query Language for TypeScript
2
+
3
+ [<img src="https://allurereport.org/public/img/allure-report.svg" height="85px" alt="Allure Report logo" align="right" />](https://allurereport.org "Allure Report")
4
+
5
+ - Learn more about Allure Report at https://allurereport.org
6
+ - πŸ“š [Documentation](https://allurereport.org/docs/) – discover official documentation for Allure Report
7
+ - ❓ [Questions and Support](https://github.com/orgs/allure-framework/discussions/categories/questions-support) – get help from the team and community
8
+ - πŸ“’ [Official announcements](https://github.com/orgs/allure-framework/discussions/categories/announcements) – be in touch with the latest updates
9
+ - πŸ’¬ [General Discussion ](https://github.com/orgs/allure-framework/discussions/categories/general-discussion) – engage in casual conversations, share insights and ideas with the community
10
+
11
+ ---
12
+
13
+ ## What is AQL?
14
+
15
+ Allure Query Language (AQL) is a domain-specific language designed for querying and filtering objects. It provides a simple, SQL-like syntax for expressing complex filtering conditions.
16
+
17
+ **Key characteristics:**
18
+
19
+ - **Simple syntax** - Easy to read and write, similar to SQL WHERE clauses
20
+ - **Type-safe** - Full TypeScript support with typed AST nodes
21
+ - **Extensible** - Support for custom functions and context values
22
+ - **Compatible** - Fully compatible with Allure TestOps Java implementation
23
+
24
+ **Example AQL expressions:**
25
+
26
+ - `'status = "passed"'` - Simple equality check
27
+ - `'status = "passed" AND duration < 100'` - Multiple conditions
28
+ - `'status IN ["passed", "failed"]'` - Array membership
29
+ - `'cf["Priority"] = "High"'` - Nested property access
30
+
31
+ ## Overview
32
+
33
+ This package provides a TypeScript parser that converts AQL expressions into an Abstract Syntax Tree (AST), enabling programmatic filtering and validation of test data. The parser is fully compatible with the Allure TestOps implementation and supports all standard AQL features including operations, logical operators, parentheses, and context functions.
34
+
35
+ ## Install
36
+
37
+ Use your favorite package manager to install the package:
38
+
39
+ ```shell
40
+ npm add @allurereport/aql
41
+ yarn add @allurereport/aql
42
+ pnpm add @allurereport/aql
43
+ ```
44
+
45
+ ## Features
46
+
47
+ - βœ… **Full AQL expression parsing** - Parse any valid AQL expression into an AST
48
+ - βœ… **All comparison operations** - Support for `>`, `>=`, `<`, `<=`, `=`, `!=`, `~=`, `IN`
49
+ - Example: `'status = "passed" AND age > 25'`
50
+ - βœ… **Logical operators** - Complete support for `AND`, `OR`, `NOT`
51
+ - Example: `'(status = "passed" OR status = "failed") AND name ~= "test"'`
52
+ - βœ… **Parentheses for grouping** - Control operator precedence with parentheses
53
+ - Example: `'(a = 1 OR b = 2) AND c = 3'`
54
+ - βœ… **Array/object access** - Access nested properties via `identifier[key]`
55
+ - Example: `'cf["Custom Field"] = "value"'`
56
+ - βœ… **Context functions** - Support for dynamic values like `now()`, `currentUser()`
57
+ - Example: `"createdDate >= now()"`
58
+ - βœ… **Type-safe validation** - Typed errors with detailed information for localization
59
+
60
+ ## Usage
61
+
62
+ ### Basic example
63
+
64
+ ```typescript
65
+ import { parseAql } from "@allurereport/aql";
66
+
67
+ // Parse a simple AQL expression
68
+ const result = parseAql('status = "passed"');
69
+
70
+ // result.expression contains the parsed AST
71
+ console.log(result.expression);
72
+ // {
73
+ // type: "condition",
74
+ // left: { identifier: "status" },
75
+ // operator: "EQ",
76
+ // right: { value: "passed", type: "STRING" }
77
+ // }
78
+ ```
79
+
80
+ ### With context
81
+
82
+ ```typescript
83
+ import { parseAql } from "@allurereport/aql";
84
+
85
+ // Provide context for function calls like now() or currentUser()
86
+ const context = {
87
+ "now()": Date.now(), // Current timestamp
88
+ "currentUser()": "admin", // Current user identifier
89
+ };
90
+
91
+ // Functions in AQL will be resolved using the context
92
+ const result = parseAql("createdDate >= now()", context);
93
+ // The parser replaces now() with the value from context
94
+ ```
95
+
96
+ ### With configuration
97
+
98
+ ```typescript
99
+ import { parseAql } from "@allurereport/aql";
100
+
101
+ // Restrict available features for security or validation
102
+ const config = {
103
+ operations: ["EQ", "NEQ"], // Only allow equality checks
104
+ identifiers: ["status", "name"], // Only allow specific fields
105
+ logicalOperators: ["AND"], // Only allow AND operator
106
+ };
107
+
108
+ // This will succeed
109
+ const result = parseAql('status = "passed" AND name = "test"', undefined, config);
110
+
111
+ // This will throw an error (OR is not allowed)
112
+ // parseAql('status = "passed" OR name = "test"', undefined, config);
113
+ ```
114
+
115
+ ### Validation
116
+
117
+ ```typescript
118
+ import { isAqlError, parseAql } from "@allurereport/aql";
119
+
120
+ try {
121
+ const result = parseAql('status = "passed"');
122
+ } catch (error) {
123
+ if (isAqlError(error)) {
124
+ console.error("Error code:", error.code);
125
+ console.error("Error message:", error.message);
126
+ console.error("Error details:", error.details);
127
+ }
128
+ }
129
+ ```
130
+
131
+ ## Supported Operations
132
+
133
+ - `>` - greater than (e.g., `age > 25`)
134
+ - `>=` - greater than or equal (e.g., `score >= 80`)
135
+ - `<` - less than (e.g., `duration < 100`)
136
+ - `<=` - less than or equal (e.g., `price <= 1000`)
137
+ - `=` or `is` - equal (e.g., `status = "passed"` or `status is "passed"`)
138
+ - `!=` - not equal (e.g., `status != "broken"`)
139
+ - `~=` - contains (substring match, e.g., `name ~= "test"`)
140
+ - `IN` - in array (e.g., `status IN ["passed", "failed"]`)
141
+
142
+ ## AQL Expression Examples
143
+
144
+ ```typescript
145
+ // Simple condition - check if status equals "passed"
146
+ 'status = "passed"';
147
+
148
+ // Logical operators - combine multiple conditions
149
+ 'status = "passed" AND name ~= "test"';
150
+
151
+ // Parentheses - control operator precedence
152
+ // This means: (status is passed OR failed) AND name contains "test"
153
+ '(status = "passed" OR status = "failed") AND name ~= "test"';
154
+
155
+ // Comparison operations - numeric comparisons
156
+ "createdDate >= 1234567890";
157
+
158
+ // Array condition - check if value is in a list
159
+ 'status IN ["passed", "failed", "broken"]';
160
+
161
+ // Element access - access nested properties or array elements
162
+ 'cf["Custom Field"] = "value"';
163
+
164
+ // NOT operator - negate a condition
165
+ 'NOT status = "passed"';
166
+
167
+ // Functions - use dynamic values from context
168
+ "createdDate >= now()";
169
+
170
+ // Boolean value - standalone boolean expression
171
+ "true";
172
+
173
+ // NULL values - check for null or empty values
174
+ "status = null";
175
+ ```
176
+
177
+ ## AST Structure
178
+
179
+ The parser returns an AST (Abstract Syntax Tree) with the following structure:
180
+
181
+ ```typescript
182
+ type AqlExpression =
183
+ | AqlConditionExpression // condition: left operator right
184
+ | AqlArrayConditionExpression // array condition: left IN [values]
185
+ | AqlBinaryExpression // binary operation: left AND/OR right
186
+ | AqlNotExpression // negation: NOT expression
187
+ | AqlParenExpression // parentheses: (expression)
188
+ | AqlBooleanExpression; // boolean value: true/false
189
+ ```
190
+
191
+ Each expression node contains type information and the necessary data to evaluate or transform the query. The AST can be used for:
192
+
193
+ - **Filtering** - Evaluate expressions against objects
194
+ - **Validation** - Check if expressions are valid
195
+ - **Transformation** - Convert to other query languages
196
+ - **Analysis** - Extract identifiers, operations, and values
197
+
198
+ ## API
199
+
200
+ ### `parseAql(aql: string, context?: Map | Record, config?: AqlParserConfig): AqlParseResult`
201
+
202
+ Parses an AQL string and returns an AST (Abstract Syntax Tree).
203
+
204
+ **Parameters:**
205
+
206
+ - `aql` - AQL string to parse (e.g., `'status = "passed"'`)
207
+ - `context` - optional context with functions (Map or object), e.g., `{ "now()": Date.now() }`
208
+ - `config` - optional parser configuration to restrict available features
209
+
210
+ **Returns:**
211
+
212
+ - `AqlParseResult` object with `expression` field:
213
+ ```typescript
214
+ {
215
+ expression: AqlExpression | null; // null for empty strings
216
+ }
217
+ ```
218
+
219
+ **Example return value:**
220
+
221
+ ```typescript
222
+ const result = parseAql('status = "passed" AND age > 25');
223
+ // result.expression = {
224
+ // type: "binary",
225
+ // operator: "AND",
226
+ // left: {
227
+ // type: "condition",
228
+ // left: { identifier: "status" },
229
+ // operator: "EQ",
230
+ // right: { value: "passed", type: "STRING" }
231
+ // },
232
+ // right: {
233
+ // type: "condition",
234
+ // left: { identifier: "age" },
235
+ // operator: "GT",
236
+ // right: { value: "25", type: "NUMBER" }
237
+ // }
238
+ // }
239
+ ```
240
+
241
+ **Throws:**
242
+
243
+ - `AqlParserError` if the AQL string is invalid or uses forbidden features
244
+
245
+ ### `includesAll(aql: string | null | undefined): boolean`
246
+
247
+ Checks if AQL includes all records (empty string or "true").
248
+
249
+ ## Parser Configuration
250
+
251
+ You can restrict available features in the parser using `AqlParserConfig`:
252
+
253
+ ```typescript
254
+ import { parseAql } from "@allurereport/aql";
255
+
256
+ const config = {
257
+ // Allow only specific operations
258
+ operations: ["EQ", "NEQ"],
259
+
260
+ // Allow only specific logical operators
261
+ logicalOperators: ["AND", "OR"],
262
+
263
+ // Allow only specific identifiers
264
+ identifiers: ["status", "name", "age"],
265
+
266
+ // Or use a validation function
267
+ identifiers: (id: string) => id.startsWith("custom_"),
268
+
269
+ // Allow only specific value types
270
+ valueTypes: ["STRING", "NUMBER"],
271
+
272
+ // Disable parentheses
273
+ parentheses: false,
274
+
275
+ // Disable bracket access (identifier[key])
276
+ indexAccess: false,
277
+ };
278
+
279
+ const result = parseAql('status = "passed"', undefined, config);
280
+ ```
281
+
282
+ ### Configuration Options
283
+
284
+ - `operations?: AqlOperations[]` - Allowed operations (GT, GE, LT, LE, EQ, NEQ, CONTAINS, IN)
285
+ - `logicalOperators?: AqlLogicalOperators[]` - Allowed logical operators (AND, OR, NOT)
286
+ - `identifiers?: string[] | ((identifier: string) => boolean)` - Allowed identifiers (array or validation function)
287
+ - `valueTypes?: AqlValueKind[]` - Allowed value types (NULL, BOOLEAN, NUMBER, STRING, FUNCTION)
288
+ - `parentheses?: boolean` - Whether parentheses are allowed (default: `true`)
289
+ - `indexAccess?: boolean` - Whether bracket access is allowed (default: `true`)
290
+
291
+ If a configuration option is not specified, all features are available.
292
+
293
+ ## Filtering Objects
294
+
295
+ You can filter arrays of objects using AQL expressions. This is useful for filtering test results, user data, or any structured objects:
296
+
297
+ ```typescript
298
+ import { filterByAql } from "@allurereport/aql";
299
+
300
+ // Example: Filter test results
301
+ const testResults = [
302
+ { id: 1, name: "Login test", status: "passed", duration: 150, tags: ["smoke"] },
303
+ { id: 2, name: "Logout test", status: "failed", duration: 200, tags: ["regression"] },
304
+ { id: 3, name: "API test", status: "passed", duration: 100, tags: ["smoke", "api"] },
305
+ ];
306
+
307
+ // Filter by simple condition - get all passed tests
308
+ const passedTests = filterByAql(testResults, 'status = "passed"');
309
+ // Returns: [{ id: 1, ... }, { id: 3, ... }]
310
+
311
+ // Filter with AND - passed tests with duration less than 150ms
312
+ const fastPassedTests = filterByAql(testResults, 'status = "passed" AND duration < 150');
313
+ // Returns: [{ id: 3, ... }]
314
+
315
+ // Filter with OR - get passed or failed tests (exclude broken)
316
+ const activeTests = filterByAql(testResults, 'status = "passed" OR status = "failed"');
317
+ // Returns: all items
318
+
319
+ // Filter with IN - get tests with specific statuses
320
+ const relevantTests = filterByAql(testResults, 'status IN ["passed", "failed"]');
321
+ // Returns: all items
322
+
323
+ // Filter with nested properties - access custom fields
324
+ const itemsWithCustomFields = [
325
+ { id: 1, cf: { Priority: "High", Component: "Auth" } },
326
+ { id: 2, cf: { Priority: "Low", Component: "UI" } },
327
+ ];
328
+ const highPriorityAuth = filterByAql(itemsWithCustomFields, 'cf["Priority"] = "High" AND cf["Component"] = "Auth"');
329
+ // Returns: [{ id: 1, ... }]
330
+ ```
331
+
332
+ ### Filter API
333
+
334
+ #### `filterByAql<T>(items: T[], aql: string, context?: Map | Record, config?: AqlParserConfig): T[]`
335
+
336
+ Filters an array of objects based on an AQL expression string.
337
+
338
+ **Parameters:**
339
+
340
+ - `items` - array of objects to filter
341
+ - `aql` - AQL expression string
342
+ - `context` - optional context with functions
343
+ - `config` - optional parser configuration to restrict available features
344
+
345
+ **Returns:**
346
+
347
+ - Filtered array of objects
348
+
349
+ #### `filterByAql<T>(items: T[], expression: AqlExpression): T[]`
350
+
351
+ Filters an array of objects using a pre-parsed AQL expression. Useful when you need to filter multiple arrays with the same expression.
352
+
353
+ **Parameters:**
354
+
355
+ - `items` - array of objects to filter
356
+ - `expression` - parsed AQL expression (from `parseAql()`)
357
+
358
+ **Returns:**
359
+
360
+ - Filtered array of objects
361
+
362
+ **Example:**
363
+
364
+ ```typescript
365
+ // Parse once
366
+ const parseResult = parseAql('status = "passed"');
367
+ if (parseResult.expression) {
368
+ // Use multiple times
369
+ const filtered1 = filterByAql(items1, parseResult.expression);
370
+ const filtered2 = filterByAql(items2, parseResult.expression);
371
+ const filtered3 = filterByAql(items3, parseResult.expression);
372
+ }
373
+ ```
374
+
375
+ ## Error Handling
376
+
377
+ AQL parser uses typed error classes for better error handling and localization support. All errors include position information and detailed context.
378
+
379
+ ### Error Types
380
+
381
+ ```typescript
382
+ import { AqlError, AqlErrorCode, isAqlError } from "@allurereport/aql";
383
+
384
+ try {
385
+ parseAql("invalid aql");
386
+ } catch (error) {
387
+ if (isAqlError(error)) {
388
+ console.log("Error code:", error.code);
389
+ // Example: "EXPECTED_TOKEN"
390
+
391
+ console.log("Error message:", error.message);
392
+ // Example: "Expected '=' at position 7"
393
+
394
+ console.log("Error details:", error.fullDetails);
395
+ // Example: { expected: "=", actual: "!", position: 7, context: "status !" }
396
+
397
+ // Use error.code for translation keys in your UI
398
+ }
399
+ }
400
+ ```
401
+
402
+ ### Common Error Examples
403
+
404
+ ```typescript
405
+ // Syntax error - unexpected character
406
+ try {
407
+ parseAql('status @ "passed"');
408
+ } catch (error) {
409
+ // error.code = "UNEXPECTED_CHARACTER"
410
+ // error.message = "Unexpected character '@' at position 7"
411
+ }
412
+
413
+ // Unterminated string
414
+ try {
415
+ parseAql('status = "passed');
416
+ } catch (error) {
417
+ // error.code = "UNTERMINATED_STRING"
418
+ // error.message = "Unterminated string at position 7"
419
+ }
420
+
421
+ // Expected token
422
+ try {
423
+ parseAql("status =");
424
+ } catch (error) {
425
+ // error.code = "EXPECTED_TOKEN"
426
+ // error.message = "Expected value at position 9"
427
+ }
428
+
429
+ // Forbidden operation (with config)
430
+ try {
431
+ const config = { operations: ["EQ"] };
432
+ parseAql('status > "passed"', undefined, config);
433
+ } catch (error) {
434
+ // error.code = "FORBIDDEN_OPERATION"
435
+ // error.message = "Operation 'GT' is not allowed at position 7"
436
+ }
437
+ ```
438
+
439
+ ### Error Codes
440
+
441
+ - `UNEXPECTED_CHARACTER` - Unexpected character in input
442
+ - `UNTERMINATED_STRING` - String literal not properly closed
443
+ - `INVALID_UNICODE_ESCAPE` - Invalid Unicode escape sequence
444
+ - `EXPECTED_TOKEN` - Expected specific token
445
+ - `EXPECTED_OPERATION` - Expected operation
446
+ - `EXPECTED_VALUE` - Expected value
447
+ - `EXPECTED_ACCESSOR` - Expected accessor
448
+ - `INVALID_INPUT` - Invalid input
449
+ - `INVALID_SYNTAX` - Invalid syntax
450
+
451
+ ### Error Classes
452
+
453
+ - `AqlError` - Base error class
454
+ - `AqlTokenizerError` - Tokenizer-specific errors
455
+ - `AqlParserError` - Parser-specific errors
456
+
457
+ ### Localization Example
458
+
459
+ ```typescript
460
+ import { AqlError, AqlErrorCode } from "@allurereport/aql";
461
+
462
+ function translateError(error: AqlError, locale: string): string {
463
+ const translations = {
464
+ en: {
465
+ [AqlErrorCode.EXPECTED_TOKEN]: "Expected {expected} at position {position}",
466
+ // ... more translations
467
+ },
468
+ es: {
469
+ [AqlErrorCode.EXPECTED_TOKEN]: "Se esperaba {expected} en la posiciΓ³n {position}",
470
+ // ... more translations
471
+ },
472
+ };
473
+
474
+ const template = translations[locale]?.[error.code];
475
+ const details = error.fullDetails;
476
+ return template.replace(/\{(\w+)\}/g, (_, key) => details[key]?.toString() || "");
477
+ }
478
+ ```
479
+
480
+ ## Compatibility
481
+
482
+ The parser is compatible with the Java implementation in `allure-query-language` and uses the same ANTLR grammar.
@@ -0,0 +1,105 @@
1
+ /**
2
+ * AQL error types and classes
3
+ */
4
+ /**
5
+ * Extend ErrorConstructor to include captureStackTrace (V8/Node.js specific)
6
+ */
7
+ declare global {
8
+ interface ErrorConstructor {
9
+ captureStackTrace?(error: Error, constructorOpt?: Function): void;
10
+ }
11
+ }
12
+ export declare enum AqlErrorCode {
13
+ /** Tokenizer errors */
14
+ UNEXPECTED_CHARACTER = "UNEXPECTED_CHARACTER",
15
+ UNTERMINATED_STRING = "UNTERMINATED_STRING",
16
+ INVALID_UNICODE_ESCAPE = "INVALID_UNICODE_ESCAPE",
17
+ /** Parser errors */
18
+ EXPECTED_TOKEN = "EXPECTED_TOKEN",
19
+ EXPECTED_OPERATION = "EXPECTED_OPERATION",
20
+ EXPECTED_VALUE = "EXPECTED_VALUE",
21
+ EXPECTED_ACCESSOR = "EXPECTED_ACCESSOR",
22
+ INVALID_SYNTAX = "INVALID_SYNTAX",
23
+ INVALID_INPUT = "INVALID_INPUT",
24
+ INVALID_IDENTIFIER = "INVALID_IDENTIFIER",
25
+ /** Configuration errors */
26
+ FORBIDDEN_LOGICAL_OPERATOR = "FORBIDDEN_LOGICAL_OPERATOR",
27
+ FORBIDDEN_OPERATION = "FORBIDDEN_OPERATION",
28
+ FORBIDDEN_ARRAY_OPERATION = "FORBIDDEN_ARRAY_OPERATION",
29
+ FORBIDDEN_IDENTIFIER = "FORBIDDEN_IDENTIFIER",
30
+ FORBIDDEN_VALUE_TYPE = "FORBIDDEN_VALUE_TYPE",
31
+ FORBIDDEN_PARENTHESES = "FORBIDDEN_PARENTHESES",
32
+ FORBIDDEN_BRACKET_ACCESS = "FORBIDDEN_BRACKET_ACCESS"
33
+ }
34
+ export interface AqlErrorDetails {
35
+ position?: number;
36
+ expected?: string;
37
+ got?: string;
38
+ context?: string;
39
+ character?: string;
40
+ [key: string]: any;
41
+ }
42
+ /**
43
+ * Base error class for AQL errors
44
+ */
45
+ export declare class AqlError extends Error {
46
+ readonly code: AqlErrorCode;
47
+ readonly details: AqlErrorDetails;
48
+ constructor(code: AqlErrorCode, message: string, details?: AqlErrorDetails);
49
+ /**
50
+ * Gets error details for translation/localization
51
+ * Includes error code and all context information
52
+ */
53
+ get fullDetails(): AqlErrorDetails & {
54
+ code: AqlErrorCode;
55
+ };
56
+ /**
57
+ * Gets error code for translation key generation
58
+ */
59
+ get translationKey(): string;
60
+ }
61
+ /**
62
+ * Tokenizer error
63
+ */
64
+ export declare class AqlTokenizerError extends AqlError {
65
+ constructor(code: AqlErrorCode, message: string, details?: AqlErrorDetails);
66
+ }
67
+ /**
68
+ * Parser error
69
+ */
70
+ export declare class AqlParserError extends AqlError {
71
+ constructor(code: AqlErrorCode, message: string, details?: AqlErrorDetails);
72
+ }
73
+ /**
74
+ * Helper functions to create specific errors
75
+ */
76
+ export declare const AqlErrors: {
77
+ unexpectedCharacter: (character: string, position: number) => AqlTokenizerError;
78
+ unterminatedString: (position: number) => AqlTokenizerError;
79
+ invalidUnicodeEscape: (position: number) => AqlTokenizerError;
80
+ expectedToken: (expected: string, got: string, position: number, context?: string) => AqlParserError;
81
+ expectedOperation: (position: number) => AqlParserError;
82
+ expectedValue: (position: number) => AqlParserError;
83
+ expectedAccessor: (position: number, expected?: string) => AqlParserError;
84
+ invalidInput: (reason: string) => AqlParserError;
85
+ invalidIdentifier: (identifier: string, position: number) => AqlParserError;
86
+ forbiddenLogicalOperator: (operator: string, position: number) => AqlParserError;
87
+ forbiddenOperation: (operation: string, position: number) => AqlParserError;
88
+ forbiddenArrayOperation: (operation: string, position: number) => AqlParserError;
89
+ forbiddenIdentifier: (identifier: string, position: number) => AqlParserError;
90
+ forbiddenValueType: (valueType: string, position: number) => AqlParserError;
91
+ forbiddenParentheses: (position: number) => AqlParserError;
92
+ forbiddenBracketAccess: (position: number) => AqlParserError;
93
+ };
94
+ /**
95
+ * Type guard to check if error is AqlError
96
+ */
97
+ export declare function isAqlError(error: unknown): error is AqlError;
98
+ /**
99
+ * Type guard to check if error is AqlTokenizerError
100
+ */
101
+ export declare function isAqlTokenizerError(error: unknown): error is AqlTokenizerError;
102
+ /**
103
+ * Type guard to check if error is AqlParserError
104
+ */
105
+ export declare function isAqlParserError(error: unknown): error is AqlParserError;