@aikotools/datafilter 1.1.0 → 1.1.2
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
CHANGED
|
@@ -187,6 +187,7 @@ Optional mode allows you to focus on matching critical files while automatically
|
|
|
187
187
|
|
|
188
188
|
**Use Case:** Event streams, log files, or test executions where you want to validate specific checkpoints but allow arbitrary intermediate files.
|
|
189
189
|
|
|
190
|
+
|
|
190
191
|
#### Three Matching Modes
|
|
191
192
|
|
|
192
193
|
```typescript
|
|
@@ -616,6 +616,13 @@ class Matcher {
|
|
|
616
616
|
if (fileMatched) {
|
|
617
617
|
fileIndex++;
|
|
618
618
|
} else {
|
|
619
|
+
const hasMandatoryRule = ruleOrRules.some((r) => isSingleMatchRule(r) && !r.optional);
|
|
620
|
+
if (hasMandatoryRule) {
|
|
621
|
+
unmapped.push({
|
|
622
|
+
file,
|
|
623
|
+
attemptedRules: attemptedMatches
|
|
624
|
+
});
|
|
625
|
+
}
|
|
619
626
|
fileIndex++;
|
|
620
627
|
}
|
|
621
628
|
} else {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"aikotools-datafilter.cjs","sources":["../src/core/types.ts","../src/utils/ObjectAccess.ts","../src/engine/FilterEngine.ts","../src/matcher/Matcher.ts","../src/index.ts"],"sourcesContent":["/**\n * Core type definitions for the datafilter engine\n */\n\n/**\n * A single element in a path through a JSON structure.\n * Can be a string for object properties or a number for array indices.\n *\n * @example\n * ['data', 'stations', 0, 'name'] → data.stations[0].name\n */\nexport type PathElement = string | number\n\n/**\n * Time unit for time-based comparisons\n */\nexport type TimeUnit =\n | 'milliseconds'\n | 'seconds'\n | 'minutes'\n | 'hours'\n | 'days'\n | 'weeks'\n | 'months'\n | 'years'\n\n/**\n * Check if a value matches an expected value (deep equality)\n */\nexport interface CheckValue {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n value: any\n}\n\n/**\n * Check if a path exists in the object\n */\nexport interface CheckExists {\n exists: boolean\n}\n\n/**\n * Check if an array contains (or doesn't contain) a specific item\n */\nexport interface CheckArrayElement {\n itemExists: boolean\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n item: any\n}\n\n/**\n * Check if an array has a specific size\n */\nexport interface CheckArraySize {\n type: 'equal' | 'lessThan' | 'greaterThan'\n size: number\n}\n\n/**\n * Check if a timestamp is within a time range\n */\nexport interface CheckTimeRange {\n min: string\n max: string\n}\n\n/**\n * Check if a numeric value is within a numeric range\n */\nexport interface CheckNumericRange {\n min: number\n max: number\n}\n\n/**\n * A single filter criterion that checks one aspect of the data\n */\nexport interface FilterCriterion {\n /**\n * Path to the value to check\n */\n path: PathElement[]\n\n /**\n * The check to perform on the value\n */\n check:\n | CheckValue\n | CheckExists\n | CheckArrayElement\n | CheckArraySize\n | CheckTimeRange\n | CheckNumericRange\n}\n\n/**\n * A single match rule that defines how to match a file\n */\nexport interface SingleMatchRule {\n /**\n * Filter criteria - all must match (AND logic)\n */\n match: FilterCriterion[]\n\n /**\n * Expected file name/identifier\n */\n expected: string\n\n /**\n * Whether this match is optional\n */\n optional?: boolean\n\n /**\n * Additional metadata for reporting\n */\n info?: Record<string, unknown>\n}\n\n/**\n * A wildcard match rule that can match multiple files\n * NEW: Allows matching arbitrary number of optional files without explicit specification\n */\nexport interface WildcardMatchRule {\n /**\n * Filter criteria for wildcard matching\n */\n matchAny: FilterCriterion[]\n\n /**\n * Whether to match greedily (as many as possible) or stop after first match\n * Default: false (stop after first match)\n */\n greedy?: boolean\n\n /**\n * Wildcard matches are always optional\n */\n optional: true\n\n /**\n * Additional metadata for reporting\n */\n info?: Record<string, unknown>\n}\n\n/**\n * A match rule can be either a single match or a wildcard match\n */\nexport type MatchRule = SingleMatchRule | WildcardMatchRule\n\n/**\n * Type guard to check if a rule is a wildcard rule\n */\nexport function isWildcardRule(rule: MatchRule): rule is WildcardMatchRule {\n return 'matchAny' in rule\n}\n\n/**\n * Type guard to check if a rule is a single match rule\n */\nexport function isSingleMatchRule(rule: MatchRule): rule is SingleMatchRule {\n return 'match' in rule && 'expected' in rule\n}\n\n/**\n * A group of rules with common filter criteria\n * NEW: Allows hierarchical filtering where a group of files shares common properties\n */\nexport interface FilterGroup {\n /**\n * Common filter criteria that all files in this group must match\n * These are checked before evaluating individual rules\n */\n groupFilter: FilterCriterion[]\n\n /**\n * Rules to apply to files that match the group filter\n */\n rules: MatchRule[]\n\n /**\n * Additional metadata for reporting\n */\n info?: Record<string, unknown>\n}\n\n/**\n * A file to be filtered\n */\nexport interface JsonFile {\n /**\n * File name or identifier\n */\n fileName: string\n\n /**\n * The JSON data\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n data: any\n\n /**\n * Optional metadata\n */\n metadata?: Record<string, unknown>\n}\n\n/**\n * Result of filtering a single file against a criterion\n */\nexport interface FilterCheckResult {\n /**\n * Whether the check passed\n */\n status: boolean\n\n /**\n * The check that was performed\n */\n checkType: string\n\n /**\n * Reason for failure (if status is false)\n */\n reason?: string | Record<string, unknown>\n}\n\n/**\n * Result of matching a file against a rule\n */\nexport interface MatchResult {\n /**\n * Whether all criteria matched\n */\n matched: boolean\n\n /**\n * Individual check results\n */\n checks: FilterCheckResult[]\n\n /**\n * The rule that was tested\n */\n rule: MatchRule\n}\n\n/**\n * A mapped file with its expected identifier\n */\nexport interface MappedFile {\n /**\n * Expected identifier\n */\n expected: string\n\n /**\n * The actual file that was matched\n */\n file: JsonFile\n\n /**\n * Match result details\n */\n matchResult: MatchResult\n\n /**\n * Optional flag from the rule\n */\n optional: boolean\n\n /**\n * Additional info from the rule\n */\n info?: Record<string, unknown>\n}\n\n/**\n * A wildcard-matched file (matched via matchAny)\n */\nexport interface WildcardMappedFile {\n /**\n * The file that was matched\n */\n file: JsonFile\n\n /**\n * Match result details\n */\n matchResult: MatchResult\n\n /**\n * Additional info from the rule\n */\n info?: Record<string, unknown>\n}\n\n/**\n * An unmapped file that didn't match any rule\n */\nexport interface UnmappedFile {\n /**\n * The file that couldn't be matched\n */\n file: JsonFile\n\n /**\n * All rules that were tried\n */\n attemptedRules: MatchResult[]\n}\n\n/**\n * A pre-filtered file that was excluded by preFilter criteria\n */\nexport interface PreFilteredFile {\n /**\n * The file that was excluded\n */\n file: JsonFile\n\n /**\n * The preFilter checks that failed\n */\n failedChecks: FilterCheckResult[]\n}\n\n/**\n * A file that was treated as optional (not matched to any mandatory rule)\n * Contains information about why it failed to match\n */\nexport interface OptionalFile {\n /**\n * File name of the optional file\n */\n fileName: string\n\n /**\n * Position in the original file sequence\n */\n position: number\n\n /**\n * Between which matched rules this file appeared\n */\n between?: {\n afterRule: string // Name of the rule before this file\n beforeRule: string // Name of the rule after this file\n }\n\n /**\n * All rules that were attempted and failed\n * Shows why this file didn't match any rule\n */\n failedMatches: MatchResult[]\n}\n\n/**\n * Result of the entire filtering operation\n */\nexport interface FilterResult {\n /**\n * Successfully mapped files (expected identifier → actual file)\n */\n mapped: MappedFile[]\n\n /**\n * Files matched by wildcard rules\n */\n wildcardMatched: WildcardMappedFile[]\n\n /**\n * Files that were treated as optional (not matched to any rule)\n * Contains match attempt information\n *\n * Note: When optional mode is enabled, unmapped will always be empty\n * All non-matched files will appear here instead\n */\n optionalFiles: OptionalFile[]\n\n /**\n * Files that couldn't be matched to any rule\n * Will be EMPTY when optional mode is enabled\n */\n unmapped: UnmappedFile[]\n\n /**\n * Files that were excluded by preFilter criteria\n */\n preFiltered: PreFilteredFile[]\n\n /**\n * Statistics\n */\n stats: {\n totalFiles: number\n mappedFiles: number\n wildcardMatchedFiles: number\n unmappedFiles: number // Always 0 when mode='optional' or mode='strict-optional'\n optionalFiles: number // NEW\n preFilteredFiles: number\n totalRules: number\n mandatoryRules: number\n optionalRules: number\n }\n}\n\n/**\n * Request for filtering files\n */\nexport interface FilterRequest {\n /**\n * Files to be filtered\n */\n files: JsonFile[]\n\n /**\n * Matching rules in order (flat structure)\n * Can be a single rule or an array of rules (for flexible ordering)\n * Either 'rules' or 'groups' should be provided, not both\n */\n rules?: (MatchRule | MatchRule[])[]\n\n /**\n * Grouped rules with common filter criteria (hierarchical structure)\n * Either 'rules' or 'groups' should be provided, not both\n * NEW: Allows organizing rules by common criteria (e.g., by line number, event type)\n */\n groups?: FilterGroup[]\n\n /**\n * Sort function for ordering files before matching\n * @param a - First file\n * @param b - Second file\n * @returns Negative if a < b, positive if a > b, zero if equal\n */\n sortFn?: (a: JsonFile, b: JsonFile) => number\n\n /**\n * Optional pre-filter criteria that all files must match before rule/group matching\n * Files that don't match the pre-filter are excluded entirely (not added to unmapped)\n * Applied before group filters\n */\n preFilter?: FilterCriterion[]\n\n /**\n * Optional context for time-based filtering\n */\n context?: {\n startTimeScript?: string\n startTimeTest?: string\n pathTime?: PathElement[]\n }\n\n /**\n * Matching mode for handling files that don't match rules\n *\n * - 'strict' (default): Current behavior - all files must match a rule, non-matching → unmapped\n * - 'optional': Files that don't match any rule → optionalFiles (permissive mode)\n * - 'strict-optional': Only allow files as optional if they match an optional rule\n *\n * When 'optional' or 'strict-optional', unmapped will always be empty (all non-matched files go to optionalFiles)\n */\n mode?: 'strict' | 'strict-optional' | 'optional'\n}\n","import type { PathElement } from '../core/types'\n\n/**\n * Result of accessing a value from an object\n */\nexport interface AccessResult {\n /**\n * The value found at the path (undefined if not found)\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n value: any\n\n /**\n * Whether the path was successfully resolved\n */\n found: boolean\n\n /**\n * Error message if path couldn't be resolved\n */\n error?: string\n\n /**\n * The portion of the path that was successfully resolved\n */\n validPath: PathElement[]\n}\n\n/**\n * Retrieves a value from an object following the specified path.\n * Supports nested objects and arrays.\n *\n * @param object - The object to access\n * @param path - Array of property names and array indices\n * @returns AccessResult containing the value and status\n *\n * @example\n * ```typescript\n * const obj = { data: { stations: [{ name: 'Berlin' }, { name: 'Munich' }] } };\n * const result = getValueFromPath(obj, ['data', 'stations', 1, 'name']);\n * // result.value === 'Munich'\n * // result.found === true\n * ```\n */\nexport function getValueFromPath(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n object: any,\n path: PathElement[]\n): AccessResult {\n const validPath: PathElement[] = []\n\n // Handle empty path\n if (path.length === 0) {\n return {\n value: object,\n found: true,\n validPath: [],\n }\n }\n\n // Navigate through the path\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let current: any = object\n\n for (let i = 0; i < path.length; i++) {\n const segment = path[i]\n\n // Handle null or undefined\n if (current === null || current === undefined) {\n return {\n value: undefined,\n found: false,\n error: `Cannot read property '${segment}' of ${current}`,\n validPath,\n }\n }\n\n // Handle array access\n if (Array.isArray(current)) {\n const index = typeof segment === 'number' ? segment : parseInt(String(segment), 10)\n\n if (isNaN(index)) {\n return {\n value: undefined,\n found: false,\n error: `Array index must be a number, got '${segment}'`,\n validPath,\n }\n }\n\n if (index < 0 || index >= current.length) {\n return {\n value: undefined,\n found: false,\n error: `Array index ${index} out of bounds (length: ${current.length})`,\n validPath,\n }\n }\n\n validPath.push(index)\n current = current[index]\n continue\n }\n\n // Handle object access\n if (typeof current === 'object') {\n const key = String(segment)\n\n if (!(key in current)) {\n return {\n value: undefined,\n found: false,\n error: `Property '${key}' does not exist`,\n validPath,\n }\n }\n\n validPath.push(key)\n current = current[key]\n continue\n }\n\n // Cannot navigate further\n return {\n value: undefined,\n found: false,\n error: `Cannot access property '${segment}' of primitive type ${typeof current}`,\n validPath,\n }\n }\n\n return {\n value: current,\n found: true,\n validPath,\n }\n}\n\n/**\n * Checks if a path exists in an object.\n *\n * @param object - The object to check\n * @param path - Array of property names and array indices\n * @returns true if the path exists and has a defined value\n *\n * @example\n * ```typescript\n * const obj = { data: { value: 42 } };\n * pathExists(obj, ['data', 'value']); // true\n * pathExists(obj, ['data', 'missing']); // false\n * ```\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function pathExists(object: any, path: PathElement[]): boolean {\n const result = getValueFromPath(object, path)\n return result.found && result.value !== undefined\n}\n\n/**\n * Gets a value from an object with a default fallback.\n *\n * @param object - The object to access\n * @param path - Array of property names and array indices\n * @param defaultValue - Value to return if path doesn't exist\n * @returns The value at the path, or defaultValue if not found\n *\n * @example\n * ```typescript\n * const obj = { data: { value: 42 } };\n * getValueOr(obj, ['data', 'value'], 0); // 42\n * getValueOr(obj, ['data', 'missing'], 0); // 0\n * ```\n */\nexport function getValueOr<T>(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n object: any,\n path: PathElement[],\n defaultValue: T\n): T {\n const result = getValueFromPath(object, path)\n return result.found && result.value !== undefined ? result.value : defaultValue\n}\n","import { DateTime } from 'luxon'\nimport type {\n FilterCriterion,\n FilterCheckResult,\n CheckValue,\n CheckExists,\n CheckArrayElement,\n CheckArraySize,\n CheckTimeRange,\n CheckNumericRange,\n} from '../core/types'\nimport { getValueFromPath } from '../utils/ObjectAccess'\n\n/**\n * Filter engine that evaluates filter criteria against data objects.\n * Provides various check methods for different types of comparisons.\n */\nexport class FilterEngine {\n /**\n * Context for time-based filtering\n */\n private context?: {\n startTimeScript?: string\n startTimeTest?: string\n }\n\n constructor(context?: { startTimeScript?: string; startTimeTest?: string }) {\n this.context = context\n }\n\n /**\n * Evaluates a single filter criterion against a data object.\n *\n * @param data - The data object to check\n * @param criterion - The filter criterion to evaluate\n * @returns FilterCheckResult indicating success or failure\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n evaluateCriterion(data: any, criterion: FilterCriterion): FilterCheckResult {\n const check = criterion.check\n\n // Determine check type and delegate to appropriate method\n if ('value' in check) {\n return this.checkValue(data, criterion.path, check as CheckValue)\n }\n\n if ('exists' in check) {\n return this.checkExists(data, criterion.path, check as CheckExists)\n }\n\n if ('itemExists' in check && 'item' in check) {\n return this.checkArrayElement(data, criterion.path, check as CheckArrayElement)\n }\n\n if ('type' in check && 'size' in check) {\n return this.checkArraySize(data, criterion.path, check as CheckArraySize)\n }\n\n if ('min' in check && 'max' in check) {\n // Distinguish between numeric and time ranges based on type\n if (typeof check.min === 'number' && typeof check.max === 'number') {\n return this.checkNumericRange(data, criterion.path, check as CheckNumericRange)\n } else {\n return this.checkTimeRange(data, criterion.path, check as CheckTimeRange)\n }\n }\n\n return {\n status: false,\n checkType: 'unknown',\n reason: `Unknown check type: ${JSON.stringify(check)}`,\n }\n }\n\n /**\n * Checks if a value matches an expected value using deep equality.\n *\n * @param data - The data object\n * @param path - Path to the value\n * @param check - The value check specification\n * @returns FilterCheckResult\n */\n private checkValue(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n data: any,\n path: (string | number)[],\n check: CheckValue\n ): FilterCheckResult {\n const accessResult = getValueFromPath(data, path)\n\n if (!accessResult.found) {\n return {\n status: false,\n checkType: 'checkValue',\n reason: {\n message: accessResult.error || 'Path not found',\n path,\n },\n }\n }\n\n const actual = accessResult.value\n const expected = check.value\n\n if (this.deepEqual(actual, expected)) {\n return {\n status: true,\n checkType: 'checkValue',\n }\n }\n\n return {\n status: false,\n checkType: 'checkValue',\n reason: {\n message: 'Value mismatch',\n path,\n expected,\n actual,\n },\n }\n }\n\n /**\n * Checks if a path exists in the data object.\n *\n * @param data - The data object\n * @param path - Path to check\n * @param check - The exists check specification\n * @returns FilterCheckResult\n */\n private checkExists(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n data: any,\n path: (string | number)[],\n check: CheckExists\n ): FilterCheckResult {\n const accessResult = getValueFromPath(data, path)\n const exists = accessResult.found && accessResult.value !== undefined\n\n if (check.exists === exists) {\n return {\n status: true,\n checkType: 'checkExists',\n }\n }\n\n return {\n status: false,\n checkType: 'checkExists',\n reason: {\n message: check.exists\n ? `Path should exist but doesn't: ${accessResult.error}`\n : 'Path should not exist but does',\n path,\n },\n }\n }\n\n /**\n * Checks if an array contains (or doesn't contain) a specific element.\n *\n * @param data - The data object\n * @param path - Path to the array\n * @param check - The array element check specification\n * @returns FilterCheckResult\n */\n private checkArrayElement(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n data: any,\n path: (string | number)[],\n check: CheckArrayElement\n ): FilterCheckResult {\n const accessResult = getValueFromPath(data, path)\n\n if (!accessResult.found) {\n return {\n status: false,\n checkType: 'checkArrayElement',\n reason: {\n message: accessResult.error || 'Path not found',\n path,\n },\n }\n }\n\n const array = accessResult.value\n\n if (!Array.isArray(array)) {\n return {\n status: false,\n checkType: 'checkArrayElement',\n reason: {\n message: 'Value is not an array',\n path,\n actualType: typeof array,\n },\n }\n }\n\n // Check if item exists in array\n const found = array.some(elem => this.deepEqual(elem, check.item))\n\n if (check.itemExists === found) {\n return {\n status: true,\n checkType: 'checkArrayElement',\n }\n }\n\n return {\n status: false,\n checkType: 'checkArrayElement',\n reason: {\n message: check.itemExists\n ? \"Item should exist in array but doesn't\"\n : 'Item should not exist in array but does',\n path,\n item: check.item,\n },\n }\n }\n\n /**\n * Checks if an array has the expected size.\n *\n * @param data - The data object\n * @param path - Path to the array\n * @param check - The array size check specification\n * @returns FilterCheckResult\n */\n private checkArraySize(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n data: any,\n path: (string | number)[],\n check: CheckArraySize\n ): FilterCheckResult {\n const accessResult = getValueFromPath(data, path)\n\n if (!accessResult.found) {\n return {\n status: false,\n checkType: 'checkArraySize',\n reason: {\n message: accessResult.error || 'Path not found',\n path,\n },\n }\n }\n\n const array = accessResult.value\n\n if (!Array.isArray(array)) {\n return {\n status: false,\n checkType: 'checkArraySize',\n reason: {\n message: 'Value is not an array',\n path,\n actualType: typeof array,\n },\n }\n }\n\n const actualSize = array.length\n const expectedSize = check.size\n\n let passes = false\n let message = ''\n\n switch (check.type) {\n case 'equal':\n passes = actualSize === expectedSize\n message = `Array length should be ${expectedSize} but is ${actualSize}`\n break\n case 'lessThan':\n passes = actualSize < expectedSize\n message = `Array length should be less than ${expectedSize} but is ${actualSize}`\n break\n case 'greaterThan':\n passes = actualSize > expectedSize\n message = `Array length should be greater than ${expectedSize} but is ${actualSize}`\n break\n }\n\n if (passes) {\n return {\n status: true,\n checkType: 'checkArraySize',\n }\n }\n\n return {\n status: false,\n checkType: 'checkArraySize',\n reason: {\n message,\n path,\n expected: expectedSize,\n actual: actualSize,\n },\n }\n }\n\n /**\n * Checks if a timestamp is within the expected time range.\n *\n * @param data - The data object\n * @param path - Path to the timestamp\n * @param check - The time range check specification\n * @returns FilterCheckResult\n */\n private checkTimeRange(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n data: any,\n path: (string | number)[],\n check: CheckTimeRange\n ): FilterCheckResult {\n const accessResult = getValueFromPath(data, path)\n\n if (!accessResult.found) {\n return {\n status: false,\n checkType: 'checkTimeRange',\n reason: {\n message: accessResult.error || 'Path not found',\n path,\n },\n }\n }\n\n const value = accessResult.value\n\n // Handle numeric timestamps (milliseconds or seconds)\n if (typeof value === 'number') {\n const min = parseInt(check.min)\n const max = parseInt(check.max)\n\n if (isNaN(min) || isNaN(max)) {\n return {\n status: false,\n checkType: 'checkTimeRange',\n reason: {\n message: 'Min or max is not a valid number',\n min: check.min,\n max: check.max,\n },\n }\n }\n\n if (value >= min && value <= max) {\n return {\n status: true,\n checkType: 'checkTimeRange',\n }\n }\n\n return {\n status: false,\n checkType: 'checkTimeRange',\n reason: {\n message: `Timestamp ${value} is outside range [${min}, ${max}]`,\n path,\n actual: value,\n min,\n max,\n },\n }\n }\n\n // Handle ISO timestamps\n if (typeof value === 'string') {\n const timestamp = DateTime.fromISO(value)\n const minTime = DateTime.fromISO(check.min)\n const maxTime = DateTime.fromISO(check.max)\n\n if (!timestamp.isValid) {\n return {\n status: false,\n checkType: 'checkTimeRange',\n reason: {\n message: `Invalid timestamp: ${value}`,\n path,\n },\n }\n }\n\n if (!minTime.isValid || !maxTime.isValid) {\n return {\n status: false,\n checkType: 'checkTimeRange',\n reason: {\n message: 'Invalid min or max time',\n min: check.min,\n max: check.max,\n },\n }\n }\n\n if (timestamp >= minTime && timestamp <= maxTime) {\n return {\n status: true,\n checkType: 'checkTimeRange',\n }\n }\n\n return {\n status: false,\n checkType: 'checkTimeRange',\n reason: {\n message: `Timestamp ${value} is outside range [${check.min}, ${check.max}]`,\n path,\n actual: value,\n min: check.min,\n max: check.max,\n },\n }\n }\n\n return {\n status: false,\n checkType: 'checkTimeRange',\n reason: {\n message: `Timestamp must be a string or number, got ${typeof value}`,\n path,\n },\n }\n }\n\n /**\n * Checks if a numeric value is within the expected range.\n *\n * @param data - The data object\n * @param path - Path to the numeric value\n * @param check - The numeric range check specification\n * @returns FilterCheckResult\n */\n private checkNumericRange(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n data: any,\n path: (string | number)[],\n check: CheckNumericRange\n ): FilterCheckResult {\n const accessResult = getValueFromPath(data, path)\n\n if (!accessResult.found) {\n return {\n status: false,\n checkType: 'checkNumericRange',\n reason: {\n message: accessResult.error || 'Path not found',\n path,\n },\n }\n }\n\n const value = accessResult.value\n\n if (typeof value !== 'number') {\n return {\n status: false,\n checkType: 'checkNumericRange',\n reason: {\n message: `Value must be a number, got ${typeof value}`,\n path,\n actual: value,\n },\n }\n }\n\n if (value >= check.min && value <= check.max) {\n return {\n status: true,\n checkType: 'checkNumericRange',\n }\n }\n\n return {\n status: false,\n checkType: 'checkNumericRange',\n reason: {\n message: `Value ${value} is outside range [${check.min}, ${check.max}]`,\n path,\n actual: value,\n min: check.min,\n max: check.max,\n },\n }\n }\n\n /**\n * Deep equality comparison.\n * Compares two values recursively for equality.\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private deepEqual(a: any, b: any): boolean {\n // Same reference\n if (a === b) return true\n\n // Null checks\n if (a === null || b === null) return a === b\n if (a === undefined || b === undefined) return a === b\n\n // Type check\n if (typeof a !== typeof b) return false\n\n // Dates\n if (a instanceof Date && b instanceof Date) {\n return a.getTime() === b.getTime()\n }\n\n // Arrays\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false\n return a.every((val, idx) => this.deepEqual(val, b[idx]))\n }\n\n // Objects\n if (typeof a === 'object' && typeof b === 'object') {\n const keysA = Object.keys(a)\n const keysB = Object.keys(b)\n\n if (keysA.length !== keysB.length) return false\n\n return keysA.every(key => this.deepEqual(a[key], b[key]))\n }\n\n // Primitives\n return a === b\n }\n}\n","import type {\n JsonFile,\n MatchRule,\n FilterResult,\n MappedFile,\n WildcardMappedFile,\n UnmappedFile,\n OptionalFile,\n PreFilteredFile,\n MatchResult,\n FilterCriterion,\n FilterGroup,\n} from '../core/types'\nimport { isWildcardRule, isSingleMatchRule } from '../core/types'\nimport { FilterEngine } from '../engine/FilterEngine'\n\n/**\n * Matcher system that matches files against rules.\n * Supports single matches, flexible ordering, and wildcard matches.\n */\nexport class Matcher {\n private engine: FilterEngine\n\n constructor(context?: { startTimeScript?: string; startTimeTest?: string }) {\n this.engine = new FilterEngine(context)\n }\n\n /**\n * Matches a single file against a rule.\n *\n * @param file - The file to match\n * @param rule - The rule to match against\n * @returns MatchResult indicating if all criteria matched\n */\n matchFile(file: JsonFile, rule: MatchRule): MatchResult {\n const criteria = isWildcardRule(rule) ? rule.matchAny : rule.match\n\n const checks = criteria.map(criterion => {\n return this.engine.evaluateCriterion(file.data, criterion)\n })\n\n const matched = checks.every(check => check.status)\n\n return {\n matched,\n checks,\n rule,\n }\n }\n\n /**\n * Applies pre-filter criteria to files, separating files that match from those that don't.\n *\n * @param files - Files to filter\n * @param preFilter - Filter criteria that all files must match\n * @returns Object with matched files and excluded files (with failed checks)\n */\n private applyPreFilter(\n files: JsonFile[],\n preFilter: FilterCriterion[]\n ): {\n matched: JsonFile[]\n excluded: PreFilteredFile[]\n } {\n const matched: JsonFile[] = []\n const excluded: PreFilteredFile[] = []\n\n for (const file of files) {\n const checks = preFilter.map(criterion => {\n return this.engine.evaluateCriterion(file.data, criterion)\n })\n\n if (checks.every(check => check.status)) {\n matched.push(file)\n } else {\n excluded.push({\n file,\n failedChecks: checks.filter(check => !check.status),\n })\n }\n }\n\n return { matched, excluded }\n }\n\n /**\n * Main filtering function that processes files according to rules.\n *\n * @param files - Files to filter\n * @param rules - Matching rules (can include arrays for flexible ordering)\n * @param sortFn - Optional sort function for file ordering\n * @param preFilter - Optional pre-filter criteria (files not matching are excluded)\n * @param mode - Matching mode ('strict', 'optional', or 'strict-optional')\n * @returns FilterResult with mapped, wildcardMatched, optionalFiles and unmapped files\n */\n filterFiles(\n files: JsonFile[],\n rules: (MatchRule | MatchRule[])[],\n sortFn?: (a: JsonFile, b: JsonFile) => number,\n preFilter?: FilterCriterion[],\n mode: 'strict' | 'strict-optional' | 'optional' = 'strict'\n ): FilterResult {\n // Apply pre-filter if provided\n let filteredFiles: JsonFile[]\n let preFiltered: PreFilteredFile[] = []\n\n if (preFilter) {\n const preFilterResult = this.applyPreFilter(files, preFilter)\n filteredFiles = preFilterResult.matched\n preFiltered = preFilterResult.excluded\n } else {\n filteredFiles = files\n }\n\n // Sort files if sort function provided\n const sortedFiles = sortFn ? [...filteredFiles].sort(sortFn) : [...filteredFiles]\n\n const mapped: MappedFile[] = []\n const wildcardMatched: WildcardMappedFile[] = []\n const unmapped: UnmappedFile[] = []\n const optionalFiles: OptionalFile[] = []\n\n // Track which files have been matched\n const matchedFileIndices = new Set<number>()\n\n // For optional modes, use scan-forward algorithm\n if (mode === 'optional' || mode === 'strict-optional') {\n return this.filterFilesOptionalMode(sortedFiles, rules, preFiltered, files.length, mode)\n }\n\n // Track which flexible rules have been used\n const usedFlexibleRules = new Map<number, Set<number>>()\n\n let fileIndex = 0\n let ruleIndex = 0\n\n while (fileIndex < sortedFiles.length) {\n const file = sortedFiles[fileIndex]\n\n if (ruleIndex >= rules.length) {\n // No more rules - file is unmapped\n if (!matchedFileIndices.has(fileIndex)) {\n unmapped.push({\n file,\n attemptedRules: [],\n })\n }\n fileIndex++\n continue\n }\n\n const ruleOrRules = rules[ruleIndex]\n const attemptedMatches: MatchResult[] = []\n let fileMatched = false\n\n // Handle array of rules (flexible ordering)\n if (Array.isArray(ruleOrRules)) {\n // Try each rule in the array\n for (let subIndex = 0; subIndex < ruleOrRules.length; subIndex++) {\n // Check if this subrule was already used\n const used = usedFlexibleRules.get(ruleIndex)?.has(subIndex)\n if (used) {\n continue\n }\n\n const rule = ruleOrRules[subIndex]\n const matchResult = this.matchFile(file, rule)\n attemptedMatches.push(matchResult)\n\n if (matchResult.matched && isSingleMatchRule(rule)) {\n // Mark this subrule as used\n if (!usedFlexibleRules.has(ruleIndex)) {\n usedFlexibleRules.set(ruleIndex, new Set())\n }\n const usedSet = usedFlexibleRules.get(ruleIndex)\n if (usedSet) {\n usedSet.add(subIndex)\n }\n\n mapped.push({\n expected: rule.expected,\n file,\n matchResult,\n optional: rule.optional || false,\n info: rule.info,\n })\n\n matchedFileIndices.add(fileIndex)\n fileMatched = true\n break\n }\n }\n\n // Check if all subrules are exhausted\n const allUsed = usedFlexibleRules.get(ruleIndex)?.size === ruleOrRules.length\n if (allUsed) {\n ruleIndex++\n }\n\n if (fileMatched) {\n fileIndex++\n } else {\n // Try next file with same rule group\n fileIndex++\n }\n } else {\n // Single rule\n const rule = ruleOrRules\n const matchResult = this.matchFile(file, rule)\n attemptedMatches.push(matchResult)\n\n if (matchResult.matched) {\n if (isSingleMatchRule(rule)) {\n // Regular single match\n mapped.push({\n expected: rule.expected,\n file,\n matchResult,\n optional: rule.optional || false,\n info: rule.info,\n })\n\n matchedFileIndices.add(fileIndex)\n ruleIndex++\n fileIndex++\n } else if (isWildcardRule(rule)) {\n // Wildcard match\n wildcardMatched.push({\n file,\n matchResult,\n info: rule.info,\n })\n\n matchedFileIndices.add(fileIndex)\n\n if (rule.greedy) {\n // Stay on same rule, move to next file\n fileIndex++\n } else {\n // Non-greedy: match once and move to next rule\n ruleIndex++\n fileIndex++\n }\n }\n } else {\n // No match\n if (rule.optional) {\n // Optional rule didn't match - skip to next rule\n ruleIndex++\n } else if (isWildcardRule(rule)) {\n // Wildcard rule (always optional) didn't match - skip to next rule\n ruleIndex++\n } else {\n // Mandatory rule didn't match - file is unmapped\n unmapped.push({\n file,\n attemptedRules: attemptedMatches,\n })\n fileIndex++\n }\n }\n }\n }\n\n // Generate statistics\n const stats = {\n totalFiles: files.length,\n mappedFiles: mapped.length,\n wildcardMatchedFiles: wildcardMatched.length,\n unmappedFiles: unmapped.length,\n optionalFiles: optionalFiles.length,\n preFilteredFiles: preFiltered.length,\n totalRules: this.countRules(rules),\n mandatoryRules: this.countMandatoryRules(rules),\n optionalRules: this.countOptionalRules(rules),\n }\n\n return {\n mapped,\n wildcardMatched,\n optionalFiles,\n unmapped,\n preFiltered,\n stats,\n }\n }\n\n /**\n * Filtering with optional mode - uses scan-forward algorithm\n */\n private filterFilesOptionalMode(\n sortedFiles: JsonFile[],\n rules: (MatchRule | MatchRule[])[],\n preFiltered: PreFilteredFile[],\n totalFiles: number,\n mode: 'optional' | 'strict-optional'\n ): FilterResult {\n const mapped: MappedFile[] = []\n const wildcardMatched: WildcardMappedFile[] = []\n const optionalFiles: OptionalFile[] = []\n\n let currentFileIndex = 0\n let lastMatchedIndex = -1\n let lastMatchedRule: string | null = null\n\n for (let ruleIndex = 0; ruleIndex < rules.length; ruleIndex++) {\n const ruleOrRules = rules[ruleIndex]\n let found = false\n\n // Scan forward from current position to find a matching file\n for (let fileIndex = currentFileIndex; fileIndex < sortedFiles.length; fileIndex++) {\n const file = sortedFiles[fileIndex]\n\n // Try to match against this rule\n let matchResult: MatchResult | null = null\n let matchedRule: MatchRule | null = null\n\n if (Array.isArray(ruleOrRules)) {\n // Try each rule in the flexible array\n for (const rule of ruleOrRules) {\n const result = this.matchFile(file, rule)\n if (result.matched) {\n matchResult = result\n matchedRule = rule\n break\n }\n }\n } else {\n matchResult = this.matchFile(file, ruleOrRules)\n if (matchResult.matched) {\n matchedRule = ruleOrRules\n }\n }\n\n if (matchResult && matchResult.matched && matchedRule) {\n // Found a match! Mark all files between last match and this match as optional\n for (let i = lastMatchedIndex + 1; i < fileIndex; i++) {\n const optionalFile = sortedFiles[i]\n optionalFiles.push({\n fileName: optionalFile.fileName,\n position: i,\n between: {\n afterRule: lastMatchedRule || '(start)',\n beforeRule: isSingleMatchRule(matchedRule) ? matchedRule.expected : '(wildcard)',\n },\n failedMatches: [], // TODO: collect actual failed matches\n })\n }\n\n // Add the matched file\n if (isSingleMatchRule(matchedRule)) {\n mapped.push({\n expected: matchedRule.expected,\n file,\n matchResult,\n optional: matchedRule.optional || false,\n info: matchedRule.info,\n })\n lastMatchedRule = matchedRule.expected\n } else if (isWildcardRule(matchedRule)) {\n wildcardMatched.push({\n file,\n matchResult,\n info: matchedRule.info,\n })\n lastMatchedRule = '(wildcard)'\n }\n\n lastMatchedIndex = fileIndex\n currentFileIndex = fileIndex + 1\n found = true\n break\n }\n }\n\n // If rule not found and it's mandatory, that's an error in strict-optional mode\n if (!found) {\n const isMandatory = Array.isArray(ruleOrRules)\n ? ruleOrRules.some(r => !r.optional && !isWildcardRule(r))\n : !ruleOrRules.optional && !isWildcardRule(ruleOrRules)\n\n if (isMandatory && mode === 'strict-optional') {\n // In strict-optional mode, failing to find a mandatory rule is still an error\n // But we mark remaining files as optional\n break\n }\n // In pure optional mode, we just continue\n }\n }\n\n // Mark all remaining files as optional\n for (let i = lastMatchedIndex + 1; i < sortedFiles.length; i++) {\n const optionalFile = sortedFiles[i]\n optionalFiles.push({\n fileName: optionalFile.fileName,\n position: i,\n between: {\n afterRule: lastMatchedRule || '(start)',\n beforeRule: '(end)',\n },\n failedMatches: [], // TODO: collect actual failed matches\n })\n }\n\n const stats = {\n totalFiles,\n mappedFiles: mapped.length,\n wildcardMatchedFiles: wildcardMatched.length,\n unmappedFiles: 0, // Always 0 in optional modes\n optionalFiles: optionalFiles.length,\n preFilteredFiles: preFiltered.length,\n totalRules: this.countRules(rules),\n mandatoryRules: this.countMandatoryRules(rules),\n optionalRules: this.countOptionalRules(rules),\n }\n\n return {\n mapped,\n wildcardMatched,\n optionalFiles,\n unmapped: [], // Always empty in optional modes\n preFiltered,\n stats,\n }\n }\n\n /**\n * Counts total number of rules (flattening arrays)\n */\n private countRules(rules: (MatchRule | MatchRule[])[]): number {\n return rules.reduce((count, rule) => {\n if (Array.isArray(rule)) {\n return count + rule.length\n }\n return count + 1\n }, 0)\n }\n\n /**\n * Counts mandatory rules\n */\n private countMandatoryRules(rules: (MatchRule | MatchRule[])[]): number {\n return rules.reduce((count, rule) => {\n if (Array.isArray(rule)) {\n return count + rule.filter(r => !r.optional && !isWildcardRule(r)).length\n }\n return count + (rule.optional || isWildcardRule(rule) ? 0 : 1)\n }, 0)\n }\n\n /**\n * Counts optional rules\n */\n private countOptionalRules(rules: (MatchRule | MatchRule[])[]): number {\n return rules.reduce((count, rule) => {\n if (Array.isArray(rule)) {\n return count + rule.filter(r => r.optional || isWildcardRule(r)).length\n }\n return count + (rule.optional || isWildcardRule(rule) ? 1 : 0)\n }, 0)\n }\n\n /**\n * Filtering function for grouped rules with common filter criteria.\n * Files are first filtered by preFilter (if provided), then matched against\n * group filters, and finally processed by the group's rules.\n *\n * @param files - Files to filter\n * @param groups - Filter groups with common criteria and rules\n * @param sortFn - Optional sort function for file ordering\n * @param preFilter - Optional pre-filter criteria (files not matching are excluded)\n * @param mode - Matching mode ('strict', 'optional', or 'strict-optional')\n * @returns FilterResult with mapped, wildcardMatched, optionalFiles and unmapped files\n */\n filterFilesWithGroups(\n files: JsonFile[],\n groups: FilterGroup[],\n sortFn?: (a: JsonFile, b: JsonFile) => number,\n preFilter?: FilterCriterion[],\n mode: 'strict' | 'strict-optional' | 'optional' = 'strict'\n ): FilterResult {\n // Apply pre-filter if provided\n let filteredFiles: JsonFile[]\n let preFiltered: PreFilteredFile[] = []\n\n if (preFilter) {\n const preFilterResult = this.applyPreFilter(files, preFilter)\n filteredFiles = preFilterResult.matched\n preFiltered = preFilterResult.excluded\n } else {\n filteredFiles = files\n }\n\n // Sort files if sort function provided\n const sortedFiles = sortFn ? [...filteredFiles].sort(sortFn) : [...filteredFiles]\n\n const mapped: MappedFile[] = []\n const wildcardMatched: WildcardMappedFile[] = []\n const unmapped: UnmappedFile[] = []\n const optionalFiles: OptionalFile[] = []\n\n // Process each group\n for (const group of groups) {\n // Find files that match this group's filter\n const groupFiles = sortedFiles.filter(file => {\n const checks = group.groupFilter.map(criterion => {\n return this.engine.evaluateCriterion(file.data, criterion)\n })\n return checks.every(check => check.status)\n })\n\n if (groupFiles.length === 0) {\n // No files match this group - continue to next group\n continue\n }\n\n // Apply the group's rules to the matched files\n // Convert rules to the format expected by filterFiles\n const rulesArray: (MatchRule | MatchRule[])[] = group.rules.map(rule => {\n if (Array.isArray(rule)) {\n return rule\n }\n return rule\n })\n\n // Use the existing filterFiles logic (without preFilter, already applied)\n const groupResult = this.filterFiles(groupFiles, rulesArray, undefined, undefined, mode)\n\n // Merge results\n mapped.push(...groupResult.mapped)\n wildcardMatched.push(...groupResult.wildcardMatched)\n unmapped.push(...groupResult.unmapped)\n optionalFiles.push(...groupResult.optionalFiles)\n }\n\n // Calculate total rules across all groups\n const allRules = groups.flatMap(g => g.rules)\n const stats = {\n totalFiles: files.length,\n mappedFiles: mapped.length,\n wildcardMatchedFiles: wildcardMatched.length,\n unmappedFiles: unmapped.length,\n optionalFiles: optionalFiles.length,\n preFilteredFiles: preFiltered.length,\n totalRules: this.countRules(allRules),\n mandatoryRules: this.countMandatoryRules(allRules),\n optionalRules: this.countOptionalRules(allRules),\n }\n\n return {\n mapped,\n wildcardMatched,\n optionalFiles,\n unmapped,\n preFiltered,\n stats,\n }\n }\n}\n","/**\n * @aikotools/datafilter\n *\n * Advanced data filtering engine for JSON file matching in E2E testing\n */\n\n// Core exports\nexport type {\n PathElement,\n TimeUnit,\n CheckValue,\n CheckExists,\n CheckArrayElement,\n CheckArraySize,\n CheckTimeRange,\n CheckNumericRange,\n FilterCriterion,\n SingleMatchRule,\n WildcardMatchRule,\n MatchRule,\n FilterGroup,\n JsonFile,\n FilterCheckResult,\n MatchResult,\n MappedFile,\n WildcardMappedFile,\n UnmappedFile,\n OptionalFile,\n PreFilteredFile,\n FilterResult,\n FilterRequest,\n} from './core/types'\n\nexport { isWildcardRule, isSingleMatchRule } from './core/types'\n\n// Engine exports\nexport { FilterEngine } from './engine'\n\n// Matcher exports\nexport { Matcher } from './matcher'\n\n// Utility exports\nexport { getValueFromPath, pathExists, getValueOr } from './utils'\nexport type { AccessResult } from './utils'\n\n// Convenience function\nimport { Matcher } from './matcher'\nimport type { FilterRequest, FilterResult } from './core/types'\n\n/**\n * Convenience function for filtering files with a single call.\n * Creates a Matcher instance and performs the filtering operation.\n *\n * @param request - Filter request containing files, rules/groups, sort function, and context\n * @returns FilterResult with mapped, wildcardMatched, and unmapped files\n *\n * @example\n * ```typescript\n * // Using flat rules structure\n * const result = filterFiles({\n * files: jsonFiles,\n * rules: [\n * { match: [{path: ['type'], check: {value: 'event1'}}], expected: 'event1.json' },\n * { matchAny: [{path: ['optional'], check: {exists: true}}], optional: true, greedy: true },\n * { match: [{path: ['type'], check: {value: 'event2'}}], expected: 'event2.json' }\n * ],\n * sortFn: (a, b) => a.data.timestamp - b.data.timestamp\n * });\n *\n * // Using grouped structure\n * const result = filterFiles({\n * files: jsonFiles,\n * preFilter: [{path: ['eventType'], check: {value: 'RiFahrt'}}],\n * groups: [\n * {\n * groupFilter: [{path: ['linie'], check: {value: '88888'}}],\n * rules: [...]\n * }\n * ],\n * sortFn: (a, b) => a.data.timestamp - b.data.timestamp\n * });\n * ```\n */\nexport function filterFiles(request: FilterRequest): FilterResult {\n const matcher = new Matcher(request.context)\n\n // Validate: either rules or groups should be provided\n if (request.rules && request.groups) {\n throw new Error('FilterRequest: Provide either \"rules\" or \"groups\", not both')\n }\n\n if (!request.rules && !request.groups) {\n throw new Error('FilterRequest: Must provide either \"rules\" or \"groups\"')\n }\n\n // Use groups-based filtering if groups are provided\n if (request.groups) {\n return matcher.filterFilesWithGroups(\n request.files,\n request.groups,\n request.sortFn,\n request.preFilter,\n request.mode\n )\n }\n\n // Use traditional flat rules filtering\n // At this point, we know request.rules exists because we validated above\n if (!request.rules) {\n throw new Error('FilterRequest: Rules are required')\n }\n return matcher.filterFiles(\n request.files,\n request.rules,\n request.sortFn,\n request.preFilter,\n request.mode\n )\n}\n"],"names":["DateTime","Matcher"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"aikotools-datafilter.cjs","sources":["../src/core/types.ts","../src/utils/ObjectAccess.ts","../src/engine/FilterEngine.ts","../src/matcher/Matcher.ts","../src/index.ts"],"sourcesContent":["/**\n * Core type definitions for the datafilter engine\n */\n\n/**\n * A single element in a path through a JSON structure.\n * Can be a string for object properties or a number for array indices.\n *\n * @example\n * ['data', 'stations', 0, 'name'] → data.stations[0].name\n */\nexport type PathElement = string | number\n\n/**\n * Time unit for time-based comparisons\n */\nexport type TimeUnit =\n | 'milliseconds'\n | 'seconds'\n | 'minutes'\n | 'hours'\n | 'days'\n | 'weeks'\n | 'months'\n | 'years'\n\n/**\n * Check if a value matches an expected value (deep equality)\n */\nexport interface CheckValue {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n value: any\n}\n\n/**\n * Check if a path exists in the object\n */\nexport interface CheckExists {\n exists: boolean\n}\n\n/**\n * Check if an array contains (or doesn't contain) a specific item\n */\nexport interface CheckArrayElement {\n itemExists: boolean\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n item: any\n}\n\n/**\n * Check if an array has a specific size\n */\nexport interface CheckArraySize {\n type: 'equal' | 'lessThan' | 'greaterThan'\n size: number\n}\n\n/**\n * Check if a timestamp is within a time range\n */\nexport interface CheckTimeRange {\n min: string\n max: string\n}\n\n/**\n * Check if a numeric value is within a numeric range\n */\nexport interface CheckNumericRange {\n min: number\n max: number\n}\n\n/**\n * A single filter criterion that checks one aspect of the data\n */\nexport interface FilterCriterion {\n /**\n * Path to the value to check\n */\n path: PathElement[]\n\n /**\n * The check to perform on the value\n */\n check:\n | CheckValue\n | CheckExists\n | CheckArrayElement\n | CheckArraySize\n | CheckTimeRange\n | CheckNumericRange\n}\n\n/**\n * A single match rule that defines how to match a file\n */\nexport interface SingleMatchRule {\n /**\n * Filter criteria - all must match (AND logic)\n */\n match: FilterCriterion[]\n\n /**\n * Expected file name/identifier\n */\n expected: string\n\n /**\n * Whether this match is optional\n */\n optional?: boolean\n\n /**\n * Additional metadata for reporting\n */\n info?: Record<string, unknown>\n}\n\n/**\n * A wildcard match rule that can match multiple files\n * NEW: Allows matching arbitrary number of optional files without explicit specification\n */\nexport interface WildcardMatchRule {\n /**\n * Filter criteria for wildcard matching\n */\n matchAny: FilterCriterion[]\n\n /**\n * Whether to match greedily (as many as possible) or stop after first match\n * Default: false (stop after first match)\n */\n greedy?: boolean\n\n /**\n * Wildcard matches are always optional\n */\n optional: true\n\n /**\n * Additional metadata for reporting\n */\n info?: Record<string, unknown>\n}\n\n/**\n * A match rule can be either a single match or a wildcard match\n */\nexport type MatchRule = SingleMatchRule | WildcardMatchRule\n\n/**\n * Type guard to check if a rule is a wildcard rule\n */\nexport function isWildcardRule(rule: MatchRule): rule is WildcardMatchRule {\n return 'matchAny' in rule\n}\n\n/**\n * Type guard to check if a rule is a single match rule\n */\nexport function isSingleMatchRule(rule: MatchRule): rule is SingleMatchRule {\n return 'match' in rule && 'expected' in rule\n}\n\n/**\n * A group of rules with common filter criteria\n * NEW: Allows hierarchical filtering where a group of files shares common properties\n */\nexport interface FilterGroup {\n /**\n * Common filter criteria that all files in this group must match\n * These are checked before evaluating individual rules\n */\n groupFilter: FilterCriterion[]\n\n /**\n * Rules to apply to files that match the group filter\n */\n rules: MatchRule[]\n\n /**\n * Additional metadata for reporting\n */\n info?: Record<string, unknown>\n}\n\n/**\n * A file to be filtered\n */\nexport interface JsonFile {\n /**\n * File name or identifier\n */\n fileName: string\n\n /**\n * The JSON data\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n data: any\n\n /**\n * Optional metadata\n */\n metadata?: Record<string, unknown>\n}\n\n/**\n * Result of filtering a single file against a criterion\n */\nexport interface FilterCheckResult {\n /**\n * Whether the check passed\n */\n status: boolean\n\n /**\n * The check that was performed\n */\n checkType: string\n\n /**\n * Reason for failure (if status is false)\n */\n reason?: string | Record<string, unknown>\n}\n\n/**\n * Result of matching a file against a rule\n */\nexport interface MatchResult {\n /**\n * Whether all criteria matched\n */\n matched: boolean\n\n /**\n * Individual check results\n */\n checks: FilterCheckResult[]\n\n /**\n * The rule that was tested\n */\n rule: MatchRule\n}\n\n/**\n * A mapped file with its expected identifier\n */\nexport interface MappedFile {\n /**\n * Expected identifier\n */\n expected: string\n\n /**\n * The actual file that was matched\n */\n file: JsonFile\n\n /**\n * Match result details\n */\n matchResult: MatchResult\n\n /**\n * Optional flag from the rule\n */\n optional: boolean\n\n /**\n * Additional info from the rule\n */\n info?: Record<string, unknown>\n}\n\n/**\n * A wildcard-matched file (matched via matchAny)\n */\nexport interface WildcardMappedFile {\n /**\n * The file that was matched\n */\n file: JsonFile\n\n /**\n * Match result details\n */\n matchResult: MatchResult\n\n /**\n * Additional info from the rule\n */\n info?: Record<string, unknown>\n}\n\n/**\n * An unmapped file that didn't match any rule\n */\nexport interface UnmappedFile {\n /**\n * The file that couldn't be matched\n */\n file: JsonFile\n\n /**\n * All rules that were tried\n */\n attemptedRules: MatchResult[]\n}\n\n/**\n * A pre-filtered file that was excluded by preFilter criteria\n */\nexport interface PreFilteredFile {\n /**\n * The file that was excluded\n */\n file: JsonFile\n\n /**\n * The preFilter checks that failed\n */\n failedChecks: FilterCheckResult[]\n}\n\n/**\n * A file that was treated as optional (not matched to any mandatory rule)\n * Contains information about why it failed to match\n */\nexport interface OptionalFile {\n /**\n * File name of the optional file\n */\n fileName: string\n\n /**\n * Position in the original file sequence\n */\n position: number\n\n /**\n * Between which matched rules this file appeared\n */\n between?: {\n afterRule: string // Name of the rule before this file\n beforeRule: string // Name of the rule after this file\n }\n\n /**\n * All rules that were attempted and failed\n * Shows why this file didn't match any rule\n */\n failedMatches: MatchResult[]\n}\n\n/**\n * Result of the entire filtering operation\n */\nexport interface FilterResult {\n /**\n * Successfully mapped files (expected identifier → actual file)\n */\n mapped: MappedFile[]\n\n /**\n * Files matched by wildcard rules\n */\n wildcardMatched: WildcardMappedFile[]\n\n /**\n * Files that were treated as optional (not matched to any rule)\n * Contains match attempt information\n *\n * Note: When optional mode is enabled, unmapped will always be empty\n * All non-matched files will appear here instead\n */\n optionalFiles: OptionalFile[]\n\n /**\n * Files that couldn't be matched to any rule\n * Will be EMPTY when optional mode is enabled\n */\n unmapped: UnmappedFile[]\n\n /**\n * Files that were excluded by preFilter criteria\n */\n preFiltered: PreFilteredFile[]\n\n /**\n * Statistics\n */\n stats: {\n totalFiles: number\n mappedFiles: number\n wildcardMatchedFiles: number\n unmappedFiles: number // Always 0 when mode='optional' or mode='strict-optional'\n optionalFiles: number // NEW\n preFilteredFiles: number\n totalRules: number\n mandatoryRules: number\n optionalRules: number\n }\n}\n\n/**\n * Request for filtering files\n */\nexport interface FilterRequest {\n /**\n * Files to be filtered\n */\n files: JsonFile[]\n\n /**\n * Matching rules in order (flat structure)\n * Can be a single rule or an array of rules (for flexible ordering)\n * Either 'rules' or 'groups' should be provided, not both\n */\n rules?: (MatchRule | MatchRule[])[]\n\n /**\n * Grouped rules with common filter criteria (hierarchical structure)\n * Either 'rules' or 'groups' should be provided, not both\n * NEW: Allows organizing rules by common criteria (e.g., by line number, event type)\n */\n groups?: FilterGroup[]\n\n /**\n * Sort function for ordering files before matching\n * @param a - First file\n * @param b - Second file\n * @returns Negative if a < b, positive if a > b, zero if equal\n */\n sortFn?: (a: JsonFile, b: JsonFile) => number\n\n /**\n * Optional pre-filter criteria that all files must match before rule/group matching\n * Files that don't match the pre-filter are excluded entirely (not added to unmapped)\n * Applied before group filters\n */\n preFilter?: FilterCriterion[]\n\n /**\n * Optional context for time-based filtering\n */\n context?: {\n startTimeScript?: string\n startTimeTest?: string\n pathTime?: PathElement[]\n }\n\n /**\n * Matching mode for handling files that don't match rules\n *\n * - 'strict' (default): Current behavior - all files must match a rule, non-matching → unmapped\n * - 'optional': Files that don't match any rule → optionalFiles (permissive mode)\n * - 'strict-optional': Only allow files as optional if they match an optional rule\n *\n * When 'optional' or 'strict-optional', unmapped will always be empty (all non-matched files go to optionalFiles)\n */\n mode?: 'strict' | 'strict-optional' | 'optional'\n}\n","import type { PathElement } from '../core/types'\n\n/**\n * Result of accessing a value from an object\n */\nexport interface AccessResult {\n /**\n * The value found at the path (undefined if not found)\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n value: any\n\n /**\n * Whether the path was successfully resolved\n */\n found: boolean\n\n /**\n * Error message if path couldn't be resolved\n */\n error?: string\n\n /**\n * The portion of the path that was successfully resolved\n */\n validPath: PathElement[]\n}\n\n/**\n * Retrieves a value from an object following the specified path.\n * Supports nested objects and arrays.\n *\n * @param object - The object to access\n * @param path - Array of property names and array indices\n * @returns AccessResult containing the value and status\n *\n * @example\n * ```typescript\n * const obj = { data: { stations: [{ name: 'Berlin' }, { name: 'Munich' }] } };\n * const result = getValueFromPath(obj, ['data', 'stations', 1, 'name']);\n * // result.value === 'Munich'\n * // result.found === true\n * ```\n */\nexport function getValueFromPath(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n object: any,\n path: PathElement[]\n): AccessResult {\n const validPath: PathElement[] = []\n\n // Handle empty path\n if (path.length === 0) {\n return {\n value: object,\n found: true,\n validPath: [],\n }\n }\n\n // Navigate through the path\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let current: any = object\n\n for (let i = 0; i < path.length; i++) {\n const segment = path[i]\n\n // Handle null or undefined\n if (current === null || current === undefined) {\n return {\n value: undefined,\n found: false,\n error: `Cannot read property '${segment}' of ${current}`,\n validPath,\n }\n }\n\n // Handle array access\n if (Array.isArray(current)) {\n const index = typeof segment === 'number' ? segment : parseInt(String(segment), 10)\n\n if (isNaN(index)) {\n return {\n value: undefined,\n found: false,\n error: `Array index must be a number, got '${segment}'`,\n validPath,\n }\n }\n\n if (index < 0 || index >= current.length) {\n return {\n value: undefined,\n found: false,\n error: `Array index ${index} out of bounds (length: ${current.length})`,\n validPath,\n }\n }\n\n validPath.push(index)\n current = current[index]\n continue\n }\n\n // Handle object access\n if (typeof current === 'object') {\n const key = String(segment)\n\n if (!(key in current)) {\n return {\n value: undefined,\n found: false,\n error: `Property '${key}' does not exist`,\n validPath,\n }\n }\n\n validPath.push(key)\n current = current[key]\n continue\n }\n\n // Cannot navigate further\n return {\n value: undefined,\n found: false,\n error: `Cannot access property '${segment}' of primitive type ${typeof current}`,\n validPath,\n }\n }\n\n return {\n value: current,\n found: true,\n validPath,\n }\n}\n\n/**\n * Checks if a path exists in an object.\n *\n * @param object - The object to check\n * @param path - Array of property names and array indices\n * @returns true if the path exists and has a defined value\n *\n * @example\n * ```typescript\n * const obj = { data: { value: 42 } };\n * pathExists(obj, ['data', 'value']); // true\n * pathExists(obj, ['data', 'missing']); // false\n * ```\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function pathExists(object: any, path: PathElement[]): boolean {\n const result = getValueFromPath(object, path)\n return result.found && result.value !== undefined\n}\n\n/**\n * Gets a value from an object with a default fallback.\n *\n * @param object - The object to access\n * @param path - Array of property names and array indices\n * @param defaultValue - Value to return if path doesn't exist\n * @returns The value at the path, or defaultValue if not found\n *\n * @example\n * ```typescript\n * const obj = { data: { value: 42 } };\n * getValueOr(obj, ['data', 'value'], 0); // 42\n * getValueOr(obj, ['data', 'missing'], 0); // 0\n * ```\n */\nexport function getValueOr<T>(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n object: any,\n path: PathElement[],\n defaultValue: T\n): T {\n const result = getValueFromPath(object, path)\n return result.found && result.value !== undefined ? result.value : defaultValue\n}\n","import { DateTime } from 'luxon'\nimport type {\n FilterCriterion,\n FilterCheckResult,\n CheckValue,\n CheckExists,\n CheckArrayElement,\n CheckArraySize,\n CheckTimeRange,\n CheckNumericRange,\n} from '../core/types'\nimport { getValueFromPath } from '../utils/ObjectAccess'\n\n/**\n * Filter engine that evaluates filter criteria against data objects.\n * Provides various check methods for different types of comparisons.\n */\nexport class FilterEngine {\n /**\n * Context for time-based filtering\n */\n private context?: {\n startTimeScript?: string\n startTimeTest?: string\n }\n\n constructor(context?: { startTimeScript?: string; startTimeTest?: string }) {\n this.context = context\n }\n\n /**\n * Evaluates a single filter criterion against a data object.\n *\n * @param data - The data object to check\n * @param criterion - The filter criterion to evaluate\n * @returns FilterCheckResult indicating success or failure\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n evaluateCriterion(data: any, criterion: FilterCriterion): FilterCheckResult {\n const check = criterion.check\n\n // Determine check type and delegate to appropriate method\n if ('value' in check) {\n return this.checkValue(data, criterion.path, check as CheckValue)\n }\n\n if ('exists' in check) {\n return this.checkExists(data, criterion.path, check as CheckExists)\n }\n\n if ('itemExists' in check && 'item' in check) {\n return this.checkArrayElement(data, criterion.path, check as CheckArrayElement)\n }\n\n if ('type' in check && 'size' in check) {\n return this.checkArraySize(data, criterion.path, check as CheckArraySize)\n }\n\n if ('min' in check && 'max' in check) {\n // Distinguish between numeric and time ranges based on type\n if (typeof check.min === 'number' && typeof check.max === 'number') {\n return this.checkNumericRange(data, criterion.path, check as CheckNumericRange)\n } else {\n return this.checkTimeRange(data, criterion.path, check as CheckTimeRange)\n }\n }\n\n return {\n status: false,\n checkType: 'unknown',\n reason: `Unknown check type: ${JSON.stringify(check)}`,\n }\n }\n\n /**\n * Checks if a value matches an expected value using deep equality.\n *\n * @param data - The data object\n * @param path - Path to the value\n * @param check - The value check specification\n * @returns FilterCheckResult\n */\n private checkValue(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n data: any,\n path: (string | number)[],\n check: CheckValue\n ): FilterCheckResult {\n const accessResult = getValueFromPath(data, path)\n\n if (!accessResult.found) {\n return {\n status: false,\n checkType: 'checkValue',\n reason: {\n message: accessResult.error || 'Path not found',\n path,\n },\n }\n }\n\n const actual = accessResult.value\n const expected = check.value\n\n if (this.deepEqual(actual, expected)) {\n return {\n status: true,\n checkType: 'checkValue',\n }\n }\n\n return {\n status: false,\n checkType: 'checkValue',\n reason: {\n message: 'Value mismatch',\n path,\n expected,\n actual,\n },\n }\n }\n\n /**\n * Checks if a path exists in the data object.\n *\n * @param data - The data object\n * @param path - Path to check\n * @param check - The exists check specification\n * @returns FilterCheckResult\n */\n private checkExists(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n data: any,\n path: (string | number)[],\n check: CheckExists\n ): FilterCheckResult {\n const accessResult = getValueFromPath(data, path)\n const exists = accessResult.found && accessResult.value !== undefined\n\n if (check.exists === exists) {\n return {\n status: true,\n checkType: 'checkExists',\n }\n }\n\n return {\n status: false,\n checkType: 'checkExists',\n reason: {\n message: check.exists\n ? `Path should exist but doesn't: ${accessResult.error}`\n : 'Path should not exist but does',\n path,\n },\n }\n }\n\n /**\n * Checks if an array contains (or doesn't contain) a specific element.\n *\n * @param data - The data object\n * @param path - Path to the array\n * @param check - The array element check specification\n * @returns FilterCheckResult\n */\n private checkArrayElement(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n data: any,\n path: (string | number)[],\n check: CheckArrayElement\n ): FilterCheckResult {\n const accessResult = getValueFromPath(data, path)\n\n if (!accessResult.found) {\n return {\n status: false,\n checkType: 'checkArrayElement',\n reason: {\n message: accessResult.error || 'Path not found',\n path,\n },\n }\n }\n\n const array = accessResult.value\n\n if (!Array.isArray(array)) {\n return {\n status: false,\n checkType: 'checkArrayElement',\n reason: {\n message: 'Value is not an array',\n path,\n actualType: typeof array,\n },\n }\n }\n\n // Check if item exists in array\n const found = array.some(elem => this.deepEqual(elem, check.item))\n\n if (check.itemExists === found) {\n return {\n status: true,\n checkType: 'checkArrayElement',\n }\n }\n\n return {\n status: false,\n checkType: 'checkArrayElement',\n reason: {\n message: check.itemExists\n ? \"Item should exist in array but doesn't\"\n : 'Item should not exist in array but does',\n path,\n item: check.item,\n },\n }\n }\n\n /**\n * Checks if an array has the expected size.\n *\n * @param data - The data object\n * @param path - Path to the array\n * @param check - The array size check specification\n * @returns FilterCheckResult\n */\n private checkArraySize(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n data: any,\n path: (string | number)[],\n check: CheckArraySize\n ): FilterCheckResult {\n const accessResult = getValueFromPath(data, path)\n\n if (!accessResult.found) {\n return {\n status: false,\n checkType: 'checkArraySize',\n reason: {\n message: accessResult.error || 'Path not found',\n path,\n },\n }\n }\n\n const array = accessResult.value\n\n if (!Array.isArray(array)) {\n return {\n status: false,\n checkType: 'checkArraySize',\n reason: {\n message: 'Value is not an array',\n path,\n actualType: typeof array,\n },\n }\n }\n\n const actualSize = array.length\n const expectedSize = check.size\n\n let passes = false\n let message = ''\n\n switch (check.type) {\n case 'equal':\n passes = actualSize === expectedSize\n message = `Array length should be ${expectedSize} but is ${actualSize}`\n break\n case 'lessThan':\n passes = actualSize < expectedSize\n message = `Array length should be less than ${expectedSize} but is ${actualSize}`\n break\n case 'greaterThan':\n passes = actualSize > expectedSize\n message = `Array length should be greater than ${expectedSize} but is ${actualSize}`\n break\n }\n\n if (passes) {\n return {\n status: true,\n checkType: 'checkArraySize',\n }\n }\n\n return {\n status: false,\n checkType: 'checkArraySize',\n reason: {\n message,\n path,\n expected: expectedSize,\n actual: actualSize,\n },\n }\n }\n\n /**\n * Checks if a timestamp is within the expected time range.\n *\n * @param data - The data object\n * @param path - Path to the timestamp\n * @param check - The time range check specification\n * @returns FilterCheckResult\n */\n private checkTimeRange(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n data: any,\n path: (string | number)[],\n check: CheckTimeRange\n ): FilterCheckResult {\n const accessResult = getValueFromPath(data, path)\n\n if (!accessResult.found) {\n return {\n status: false,\n checkType: 'checkTimeRange',\n reason: {\n message: accessResult.error || 'Path not found',\n path,\n },\n }\n }\n\n const value = accessResult.value\n\n // Handle numeric timestamps (milliseconds or seconds)\n if (typeof value === 'number') {\n const min = parseInt(check.min)\n const max = parseInt(check.max)\n\n if (isNaN(min) || isNaN(max)) {\n return {\n status: false,\n checkType: 'checkTimeRange',\n reason: {\n message: 'Min or max is not a valid number',\n min: check.min,\n max: check.max,\n },\n }\n }\n\n if (value >= min && value <= max) {\n return {\n status: true,\n checkType: 'checkTimeRange',\n }\n }\n\n return {\n status: false,\n checkType: 'checkTimeRange',\n reason: {\n message: `Timestamp ${value} is outside range [${min}, ${max}]`,\n path,\n actual: value,\n min,\n max,\n },\n }\n }\n\n // Handle ISO timestamps\n if (typeof value === 'string') {\n const timestamp = DateTime.fromISO(value)\n const minTime = DateTime.fromISO(check.min)\n const maxTime = DateTime.fromISO(check.max)\n\n if (!timestamp.isValid) {\n return {\n status: false,\n checkType: 'checkTimeRange',\n reason: {\n message: `Invalid timestamp: ${value}`,\n path,\n },\n }\n }\n\n if (!minTime.isValid || !maxTime.isValid) {\n return {\n status: false,\n checkType: 'checkTimeRange',\n reason: {\n message: 'Invalid min or max time',\n min: check.min,\n max: check.max,\n },\n }\n }\n\n if (timestamp >= minTime && timestamp <= maxTime) {\n return {\n status: true,\n checkType: 'checkTimeRange',\n }\n }\n\n return {\n status: false,\n checkType: 'checkTimeRange',\n reason: {\n message: `Timestamp ${value} is outside range [${check.min}, ${check.max}]`,\n path,\n actual: value,\n min: check.min,\n max: check.max,\n },\n }\n }\n\n return {\n status: false,\n checkType: 'checkTimeRange',\n reason: {\n message: `Timestamp must be a string or number, got ${typeof value}`,\n path,\n },\n }\n }\n\n /**\n * Checks if a numeric value is within the expected range.\n *\n * @param data - The data object\n * @param path - Path to the numeric value\n * @param check - The numeric range check specification\n * @returns FilterCheckResult\n */\n private checkNumericRange(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n data: any,\n path: (string | number)[],\n check: CheckNumericRange\n ): FilterCheckResult {\n const accessResult = getValueFromPath(data, path)\n\n if (!accessResult.found) {\n return {\n status: false,\n checkType: 'checkNumericRange',\n reason: {\n message: accessResult.error || 'Path not found',\n path,\n },\n }\n }\n\n const value = accessResult.value\n\n if (typeof value !== 'number') {\n return {\n status: false,\n checkType: 'checkNumericRange',\n reason: {\n message: `Value must be a number, got ${typeof value}`,\n path,\n actual: value,\n },\n }\n }\n\n if (value >= check.min && value <= check.max) {\n return {\n status: true,\n checkType: 'checkNumericRange',\n }\n }\n\n return {\n status: false,\n checkType: 'checkNumericRange',\n reason: {\n message: `Value ${value} is outside range [${check.min}, ${check.max}]`,\n path,\n actual: value,\n min: check.min,\n max: check.max,\n },\n }\n }\n\n /**\n * Deep equality comparison.\n * Compares two values recursively for equality.\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private deepEqual(a: any, b: any): boolean {\n // Same reference\n if (a === b) return true\n\n // Null checks\n if (a === null || b === null) return a === b\n if (a === undefined || b === undefined) return a === b\n\n // Type check\n if (typeof a !== typeof b) return false\n\n // Dates\n if (a instanceof Date && b instanceof Date) {\n return a.getTime() === b.getTime()\n }\n\n // Arrays\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false\n return a.every((val, idx) => this.deepEqual(val, b[idx]))\n }\n\n // Objects\n if (typeof a === 'object' && typeof b === 'object') {\n const keysA = Object.keys(a)\n const keysB = Object.keys(b)\n\n if (keysA.length !== keysB.length) return false\n\n return keysA.every(key => this.deepEqual(a[key], b[key]))\n }\n\n // Primitives\n return a === b\n }\n}\n","import type {\n JsonFile,\n MatchRule,\n FilterResult,\n MappedFile,\n WildcardMappedFile,\n UnmappedFile,\n OptionalFile,\n PreFilteredFile,\n MatchResult,\n FilterCriterion,\n FilterGroup,\n} from '../core/types'\nimport { isWildcardRule, isSingleMatchRule } from '../core/types'\nimport { FilterEngine } from '../engine/FilterEngine'\n\n/**\n * Matcher system that matches files against rules.\n * Supports single matches, flexible ordering, and wildcard matches.\n */\nexport class Matcher {\n private engine: FilterEngine\n\n constructor(context?: { startTimeScript?: string; startTimeTest?: string }) {\n this.engine = new FilterEngine(context)\n }\n\n /**\n * Matches a single file against a rule.\n *\n * @param file - The file to match\n * @param rule - The rule to match against\n * @returns MatchResult indicating if all criteria matched\n */\n matchFile(file: JsonFile, rule: MatchRule): MatchResult {\n const criteria = isWildcardRule(rule) ? rule.matchAny : rule.match\n\n const checks = criteria.map(criterion => {\n return this.engine.evaluateCriterion(file.data, criterion)\n })\n\n const matched = checks.every(check => check.status)\n\n return {\n matched,\n checks,\n rule,\n }\n }\n\n /**\n * Applies pre-filter criteria to files, separating files that match from those that don't.\n *\n * @param files - Files to filter\n * @param preFilter - Filter criteria that all files must match\n * @returns Object with matched files and excluded files (with failed checks)\n */\n private applyPreFilter(\n files: JsonFile[],\n preFilter: FilterCriterion[]\n ): {\n matched: JsonFile[]\n excluded: PreFilteredFile[]\n } {\n const matched: JsonFile[] = []\n const excluded: PreFilteredFile[] = []\n\n for (const file of files) {\n const checks = preFilter.map(criterion => {\n return this.engine.evaluateCriterion(file.data, criterion)\n })\n\n if (checks.every(check => check.status)) {\n matched.push(file)\n } else {\n excluded.push({\n file,\n failedChecks: checks.filter(check => !check.status),\n })\n }\n }\n\n return { matched, excluded }\n }\n\n /**\n * Main filtering function that processes files according to rules.\n *\n * @param files - Files to filter\n * @param rules - Matching rules (can include arrays for flexible ordering)\n * @param sortFn - Optional sort function for file ordering\n * @param preFilter - Optional pre-filter criteria (files not matching are excluded)\n * @param mode - Matching mode ('strict', 'optional', or 'strict-optional')\n * @returns FilterResult with mapped, wildcardMatched, optionalFiles and unmapped files\n */\n filterFiles(\n files: JsonFile[],\n rules: (MatchRule | MatchRule[])[],\n sortFn?: (a: JsonFile, b: JsonFile) => number,\n preFilter?: FilterCriterion[],\n mode: 'strict' | 'strict-optional' | 'optional' = 'strict'\n ): FilterResult {\n // Apply pre-filter if provided\n let filteredFiles: JsonFile[]\n let preFiltered: PreFilteredFile[] = []\n\n if (preFilter) {\n const preFilterResult = this.applyPreFilter(files, preFilter)\n filteredFiles = preFilterResult.matched\n preFiltered = preFilterResult.excluded\n } else {\n filteredFiles = files\n }\n\n // Sort files if sort function provided\n const sortedFiles = sortFn ? [...filteredFiles].sort(sortFn) : [...filteredFiles]\n\n const mapped: MappedFile[] = []\n const wildcardMatched: WildcardMappedFile[] = []\n const unmapped: UnmappedFile[] = []\n const optionalFiles: OptionalFile[] = []\n\n // Track which files have been matched\n const matchedFileIndices = new Set<number>()\n\n // For optional modes, use scan-forward algorithm\n if (mode === 'optional' || mode === 'strict-optional') {\n return this.filterFilesOptionalMode(sortedFiles, rules, preFiltered, files.length, mode)\n }\n\n // Track which flexible rules have been used\n const usedFlexibleRules = new Map<number, Set<number>>()\n\n let fileIndex = 0\n let ruleIndex = 0\n\n while (fileIndex < sortedFiles.length) {\n const file = sortedFiles[fileIndex]\n\n if (ruleIndex >= rules.length) {\n // No more rules - file is unmapped\n if (!matchedFileIndices.has(fileIndex)) {\n unmapped.push({\n file,\n attemptedRules: [],\n })\n }\n fileIndex++\n continue\n }\n\n const ruleOrRules = rules[ruleIndex]\n const attemptedMatches: MatchResult[] = []\n let fileMatched = false\n\n // Handle array of rules (flexible ordering)\n if (Array.isArray(ruleOrRules)) {\n // Try each rule in the array\n for (let subIndex = 0; subIndex < ruleOrRules.length; subIndex++) {\n // Check if this subrule was already used\n const used = usedFlexibleRules.get(ruleIndex)?.has(subIndex)\n if (used) {\n continue\n }\n\n const rule = ruleOrRules[subIndex]\n const matchResult = this.matchFile(file, rule)\n attemptedMatches.push(matchResult)\n\n if (matchResult.matched && isSingleMatchRule(rule)) {\n // Mark this subrule as used\n if (!usedFlexibleRules.has(ruleIndex)) {\n usedFlexibleRules.set(ruleIndex, new Set())\n }\n const usedSet = usedFlexibleRules.get(ruleIndex)\n if (usedSet) {\n usedSet.add(subIndex)\n }\n\n mapped.push({\n expected: rule.expected,\n file,\n matchResult,\n optional: rule.optional || false,\n info: rule.info,\n })\n\n matchedFileIndices.add(fileIndex)\n fileMatched = true\n break\n }\n }\n\n // Check if all subrules are exhausted\n const allUsed = usedFlexibleRules.get(ruleIndex)?.size === ruleOrRules.length\n if (allUsed) {\n ruleIndex++\n }\n\n if (fileMatched) {\n fileIndex++\n } else {\n // File didn't match any rule in the group\n // Check if this is a mandatory group (at least one non-optional rule)\n const hasMandatoryRule = ruleOrRules.some(r => isSingleMatchRule(r) && !r.optional)\n\n if (hasMandatoryRule) {\n // Add file to unmapped since it didn't match a mandatory group\n unmapped.push({\n file,\n attemptedRules: attemptedMatches,\n })\n }\n\n // Try next file with same rule group\n fileIndex++\n }\n } else {\n // Single rule\n const rule = ruleOrRules\n const matchResult = this.matchFile(file, rule)\n attemptedMatches.push(matchResult)\n\n if (matchResult.matched) {\n if (isSingleMatchRule(rule)) {\n // Regular single match\n mapped.push({\n expected: rule.expected,\n file,\n matchResult,\n optional: rule.optional || false,\n info: rule.info,\n })\n\n matchedFileIndices.add(fileIndex)\n ruleIndex++\n fileIndex++\n } else if (isWildcardRule(rule)) {\n // Wildcard match\n wildcardMatched.push({\n file,\n matchResult,\n info: rule.info,\n })\n\n matchedFileIndices.add(fileIndex)\n\n if (rule.greedy) {\n // Stay on same rule, move to next file\n fileIndex++\n } else {\n // Non-greedy: match once and move to next rule\n ruleIndex++\n fileIndex++\n }\n }\n } else {\n // No match\n if (rule.optional) {\n // Optional rule didn't match - skip to next rule\n ruleIndex++\n } else if (isWildcardRule(rule)) {\n // Wildcard rule (always optional) didn't match - skip to next rule\n ruleIndex++\n } else {\n // Mandatory rule didn't match - file is unmapped\n unmapped.push({\n file,\n attemptedRules: attemptedMatches,\n })\n fileIndex++\n }\n }\n }\n }\n\n // Generate statistics\n const stats = {\n totalFiles: files.length,\n mappedFiles: mapped.length,\n wildcardMatchedFiles: wildcardMatched.length,\n unmappedFiles: unmapped.length,\n optionalFiles: optionalFiles.length,\n preFilteredFiles: preFiltered.length,\n totalRules: this.countRules(rules),\n mandatoryRules: this.countMandatoryRules(rules),\n optionalRules: this.countOptionalRules(rules),\n }\n\n return {\n mapped,\n wildcardMatched,\n optionalFiles,\n unmapped,\n preFiltered,\n stats,\n }\n }\n\n /**\n * Filtering with optional mode - uses scan-forward algorithm\n */\n private filterFilesOptionalMode(\n sortedFiles: JsonFile[],\n rules: (MatchRule | MatchRule[])[],\n preFiltered: PreFilteredFile[],\n totalFiles: number,\n mode: 'optional' | 'strict-optional'\n ): FilterResult {\n const mapped: MappedFile[] = []\n const wildcardMatched: WildcardMappedFile[] = []\n const optionalFiles: OptionalFile[] = []\n\n let currentFileIndex = 0\n let lastMatchedIndex = -1\n let lastMatchedRule: string | null = null\n\n for (let ruleIndex = 0; ruleIndex < rules.length; ruleIndex++) {\n const ruleOrRules = rules[ruleIndex]\n let found = false\n\n // Scan forward from current position to find a matching file\n for (let fileIndex = currentFileIndex; fileIndex < sortedFiles.length; fileIndex++) {\n const file = sortedFiles[fileIndex]\n\n // Try to match against this rule\n let matchResult: MatchResult | null = null\n let matchedRule: MatchRule | null = null\n\n if (Array.isArray(ruleOrRules)) {\n // Try each rule in the flexible array\n for (const rule of ruleOrRules) {\n const result = this.matchFile(file, rule)\n if (result.matched) {\n matchResult = result\n matchedRule = rule\n break\n }\n }\n } else {\n matchResult = this.matchFile(file, ruleOrRules)\n if (matchResult.matched) {\n matchedRule = ruleOrRules\n }\n }\n\n if (matchResult && matchResult.matched && matchedRule) {\n // Found a match! Mark all files between last match and this match as optional\n for (let i = lastMatchedIndex + 1; i < fileIndex; i++) {\n const optionalFile = sortedFiles[i]\n optionalFiles.push({\n fileName: optionalFile.fileName,\n position: i,\n between: {\n afterRule: lastMatchedRule || '(start)',\n beforeRule: isSingleMatchRule(matchedRule) ? matchedRule.expected : '(wildcard)',\n },\n failedMatches: [], // TODO: collect actual failed matches\n })\n }\n\n // Add the matched file\n if (isSingleMatchRule(matchedRule)) {\n mapped.push({\n expected: matchedRule.expected,\n file,\n matchResult,\n optional: matchedRule.optional || false,\n info: matchedRule.info,\n })\n lastMatchedRule = matchedRule.expected\n } else if (isWildcardRule(matchedRule)) {\n wildcardMatched.push({\n file,\n matchResult,\n info: matchedRule.info,\n })\n lastMatchedRule = '(wildcard)'\n }\n\n lastMatchedIndex = fileIndex\n currentFileIndex = fileIndex + 1\n found = true\n break\n }\n }\n\n // If rule not found and it's mandatory, that's an error in strict-optional mode\n if (!found) {\n const isMandatory = Array.isArray(ruleOrRules)\n ? ruleOrRules.some(r => !r.optional && !isWildcardRule(r))\n : !ruleOrRules.optional && !isWildcardRule(ruleOrRules)\n\n if (isMandatory && mode === 'strict-optional') {\n // In strict-optional mode, failing to find a mandatory rule is still an error\n // But we mark remaining files as optional\n break\n }\n // In pure optional mode, we just continue\n }\n }\n\n // Mark all remaining files as optional\n for (let i = lastMatchedIndex + 1; i < sortedFiles.length; i++) {\n const optionalFile = sortedFiles[i]\n optionalFiles.push({\n fileName: optionalFile.fileName,\n position: i,\n between: {\n afterRule: lastMatchedRule || '(start)',\n beforeRule: '(end)',\n },\n failedMatches: [], // TODO: collect actual failed matches\n })\n }\n\n const stats = {\n totalFiles,\n mappedFiles: mapped.length,\n wildcardMatchedFiles: wildcardMatched.length,\n unmappedFiles: 0, // Always 0 in optional modes\n optionalFiles: optionalFiles.length,\n preFilteredFiles: preFiltered.length,\n totalRules: this.countRules(rules),\n mandatoryRules: this.countMandatoryRules(rules),\n optionalRules: this.countOptionalRules(rules),\n }\n\n return {\n mapped,\n wildcardMatched,\n optionalFiles,\n unmapped: [], // Always empty in optional modes\n preFiltered,\n stats,\n }\n }\n\n /**\n * Counts total number of rules (flattening arrays)\n */\n private countRules(rules: (MatchRule | MatchRule[])[]): number {\n return rules.reduce((count, rule) => {\n if (Array.isArray(rule)) {\n return count + rule.length\n }\n return count + 1\n }, 0)\n }\n\n /**\n * Counts mandatory rules\n */\n private countMandatoryRules(rules: (MatchRule | MatchRule[])[]): number {\n return rules.reduce((count, rule) => {\n if (Array.isArray(rule)) {\n return count + rule.filter(r => !r.optional && !isWildcardRule(r)).length\n }\n return count + (rule.optional || isWildcardRule(rule) ? 0 : 1)\n }, 0)\n }\n\n /**\n * Counts optional rules\n */\n private countOptionalRules(rules: (MatchRule | MatchRule[])[]): number {\n return rules.reduce((count, rule) => {\n if (Array.isArray(rule)) {\n return count + rule.filter(r => r.optional || isWildcardRule(r)).length\n }\n return count + (rule.optional || isWildcardRule(rule) ? 1 : 0)\n }, 0)\n }\n\n /**\n * Filtering function for grouped rules with common filter criteria.\n * Files are first filtered by preFilter (if provided), then matched against\n * group filters, and finally processed by the group's rules.\n *\n * @param files - Files to filter\n * @param groups - Filter groups with common criteria and rules\n * @param sortFn - Optional sort function for file ordering\n * @param preFilter - Optional pre-filter criteria (files not matching are excluded)\n * @param mode - Matching mode ('strict', 'optional', or 'strict-optional')\n * @returns FilterResult with mapped, wildcardMatched, optionalFiles and unmapped files\n */\n filterFilesWithGroups(\n files: JsonFile[],\n groups: FilterGroup[],\n sortFn?: (a: JsonFile, b: JsonFile) => number,\n preFilter?: FilterCriterion[],\n mode: 'strict' | 'strict-optional' | 'optional' = 'strict'\n ): FilterResult {\n // Apply pre-filter if provided\n let filteredFiles: JsonFile[]\n let preFiltered: PreFilteredFile[] = []\n\n if (preFilter) {\n const preFilterResult = this.applyPreFilter(files, preFilter)\n filteredFiles = preFilterResult.matched\n preFiltered = preFilterResult.excluded\n } else {\n filteredFiles = files\n }\n\n // Sort files if sort function provided\n const sortedFiles = sortFn ? [...filteredFiles].sort(sortFn) : [...filteredFiles]\n\n const mapped: MappedFile[] = []\n const wildcardMatched: WildcardMappedFile[] = []\n const unmapped: UnmappedFile[] = []\n const optionalFiles: OptionalFile[] = []\n\n // Process each group\n for (const group of groups) {\n // Find files that match this group's filter\n const groupFiles = sortedFiles.filter(file => {\n const checks = group.groupFilter.map(criterion => {\n return this.engine.evaluateCriterion(file.data, criterion)\n })\n return checks.every(check => check.status)\n })\n\n if (groupFiles.length === 0) {\n // No files match this group - continue to next group\n continue\n }\n\n // Apply the group's rules to the matched files\n // Convert rules to the format expected by filterFiles\n const rulesArray: (MatchRule | MatchRule[])[] = group.rules.map(rule => {\n if (Array.isArray(rule)) {\n return rule\n }\n return rule\n })\n\n // Use the existing filterFiles logic (without preFilter, already applied)\n const groupResult = this.filterFiles(groupFiles, rulesArray, undefined, undefined, mode)\n\n // Merge results\n mapped.push(...groupResult.mapped)\n wildcardMatched.push(...groupResult.wildcardMatched)\n unmapped.push(...groupResult.unmapped)\n optionalFiles.push(...groupResult.optionalFiles)\n }\n\n // Calculate total rules across all groups\n const allRules = groups.flatMap(g => g.rules)\n const stats = {\n totalFiles: files.length,\n mappedFiles: mapped.length,\n wildcardMatchedFiles: wildcardMatched.length,\n unmappedFiles: unmapped.length,\n optionalFiles: optionalFiles.length,\n preFilteredFiles: preFiltered.length,\n totalRules: this.countRules(allRules),\n mandatoryRules: this.countMandatoryRules(allRules),\n optionalRules: this.countOptionalRules(allRules),\n }\n\n return {\n mapped,\n wildcardMatched,\n optionalFiles,\n unmapped,\n preFiltered,\n stats,\n }\n }\n}\n","/**\n * @aikotools/datafilter\n *\n * Advanced data filtering engine for JSON file matching in E2E testing\n */\n\n// Core exports\nexport type {\n PathElement,\n TimeUnit,\n CheckValue,\n CheckExists,\n CheckArrayElement,\n CheckArraySize,\n CheckTimeRange,\n CheckNumericRange,\n FilterCriterion,\n SingleMatchRule,\n WildcardMatchRule,\n MatchRule,\n FilterGroup,\n JsonFile,\n FilterCheckResult,\n MatchResult,\n MappedFile,\n WildcardMappedFile,\n UnmappedFile,\n OptionalFile,\n PreFilteredFile,\n FilterResult,\n FilterRequest,\n} from './core/types'\n\nexport { isWildcardRule, isSingleMatchRule } from './core/types'\n\n// Engine exports\nexport { FilterEngine } from './engine'\n\n// Matcher exports\nexport { Matcher } from './matcher'\n\n// Utility exports\nexport { getValueFromPath, pathExists, getValueOr } from './utils'\nexport type { AccessResult } from './utils'\n\n// Convenience function\nimport { Matcher } from './matcher'\nimport type { FilterRequest, FilterResult } from './core/types'\n\n/**\n * Convenience function for filtering files with a single call.\n * Creates a Matcher instance and performs the filtering operation.\n *\n * @param request - Filter request containing files, rules/groups, sort function, and context\n * @returns FilterResult with mapped, wildcardMatched, and unmapped files\n *\n * @example\n * ```typescript\n * // Using flat rules structure\n * const result = filterFiles({\n * files: jsonFiles,\n * rules: [\n * { match: [{path: ['type'], check: {value: 'event1'}}], expected: 'event1.json' },\n * { matchAny: [{path: ['optional'], check: {exists: true}}], optional: true, greedy: true },\n * { match: [{path: ['type'], check: {value: 'event2'}}], expected: 'event2.json' }\n * ],\n * sortFn: (a, b) => a.data.timestamp - b.data.timestamp\n * });\n *\n * // Using grouped structure\n * const result = filterFiles({\n * files: jsonFiles,\n * preFilter: [{path: ['eventType'], check: {value: 'RiFahrt'}}],\n * groups: [\n * {\n * groupFilter: [{path: ['linie'], check: {value: '88888'}}],\n * rules: [...]\n * }\n * ],\n * sortFn: (a, b) => a.data.timestamp - b.data.timestamp\n * });\n * ```\n */\nexport function filterFiles(request: FilterRequest): FilterResult {\n const matcher = new Matcher(request.context)\n\n // Validate: either rules or groups should be provided\n if (request.rules && request.groups) {\n throw new Error('FilterRequest: Provide either \"rules\" or \"groups\", not both')\n }\n\n if (!request.rules && !request.groups) {\n throw new Error('FilterRequest: Must provide either \"rules\" or \"groups\"')\n }\n\n // Use groups-based filtering if groups are provided\n if (request.groups) {\n return matcher.filterFilesWithGroups(\n request.files,\n request.groups,\n request.sortFn,\n request.preFilter,\n request.mode\n )\n }\n\n // Use traditional flat rules filtering\n // At this point, we know request.rules exists because we validated above\n if (!request.rules) {\n throw new Error('FilterRequest: Rules are required')\n }\n return matcher.filterFiles(\n request.files,\n request.rules,\n request.sortFn,\n request.preFilter,\n request.mode\n )\n}\n"],"names":["DateTime","Matcher"],"mappings":"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"}
|
|
@@ -614,6 +614,13 @@ class Matcher {
|
|
|
614
614
|
if (fileMatched) {
|
|
615
615
|
fileIndex++;
|
|
616
616
|
} else {
|
|
617
|
+
const hasMandatoryRule = ruleOrRules.some((r) => isSingleMatchRule(r) && !r.optional);
|
|
618
|
+
if (hasMandatoryRule) {
|
|
619
|
+
unmapped.push({
|
|
620
|
+
file,
|
|
621
|
+
attemptedRules: attemptedMatches
|
|
622
|
+
});
|
|
623
|
+
}
|
|
617
624
|
fileIndex++;
|
|
618
625
|
}
|
|
619
626
|
} else {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"aikotools-datafilter.mjs","sources":["../src/core/types.ts","../src/utils/ObjectAccess.ts","../src/engine/FilterEngine.ts","../src/matcher/Matcher.ts","../src/index.ts"],"sourcesContent":["/**\n * Core type definitions for the datafilter engine\n */\n\n/**\n * A single element in a path through a JSON structure.\n * Can be a string for object properties or a number for array indices.\n *\n * @example\n * ['data', 'stations', 0, 'name'] → data.stations[0].name\n */\nexport type PathElement = string | number\n\n/**\n * Time unit for time-based comparisons\n */\nexport type TimeUnit =\n | 'milliseconds'\n | 'seconds'\n | 'minutes'\n | 'hours'\n | 'days'\n | 'weeks'\n | 'months'\n | 'years'\n\n/**\n * Check if a value matches an expected value (deep equality)\n */\nexport interface CheckValue {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n value: any\n}\n\n/**\n * Check if a path exists in the object\n */\nexport interface CheckExists {\n exists: boolean\n}\n\n/**\n * Check if an array contains (or doesn't contain) a specific item\n */\nexport interface CheckArrayElement {\n itemExists: boolean\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n item: any\n}\n\n/**\n * Check if an array has a specific size\n */\nexport interface CheckArraySize {\n type: 'equal' | 'lessThan' | 'greaterThan'\n size: number\n}\n\n/**\n * Check if a timestamp is within a time range\n */\nexport interface CheckTimeRange {\n min: string\n max: string\n}\n\n/**\n * Check if a numeric value is within a numeric range\n */\nexport interface CheckNumericRange {\n min: number\n max: number\n}\n\n/**\n * A single filter criterion that checks one aspect of the data\n */\nexport interface FilterCriterion {\n /**\n * Path to the value to check\n */\n path: PathElement[]\n\n /**\n * The check to perform on the value\n */\n check:\n | CheckValue\n | CheckExists\n | CheckArrayElement\n | CheckArraySize\n | CheckTimeRange\n | CheckNumericRange\n}\n\n/**\n * A single match rule that defines how to match a file\n */\nexport interface SingleMatchRule {\n /**\n * Filter criteria - all must match (AND logic)\n */\n match: FilterCriterion[]\n\n /**\n * Expected file name/identifier\n */\n expected: string\n\n /**\n * Whether this match is optional\n */\n optional?: boolean\n\n /**\n * Additional metadata for reporting\n */\n info?: Record<string, unknown>\n}\n\n/**\n * A wildcard match rule that can match multiple files\n * NEW: Allows matching arbitrary number of optional files without explicit specification\n */\nexport interface WildcardMatchRule {\n /**\n * Filter criteria for wildcard matching\n */\n matchAny: FilterCriterion[]\n\n /**\n * Whether to match greedily (as many as possible) or stop after first match\n * Default: false (stop after first match)\n */\n greedy?: boolean\n\n /**\n * Wildcard matches are always optional\n */\n optional: true\n\n /**\n * Additional metadata for reporting\n */\n info?: Record<string, unknown>\n}\n\n/**\n * A match rule can be either a single match or a wildcard match\n */\nexport type MatchRule = SingleMatchRule | WildcardMatchRule\n\n/**\n * Type guard to check if a rule is a wildcard rule\n */\nexport function isWildcardRule(rule: MatchRule): rule is WildcardMatchRule {\n return 'matchAny' in rule\n}\n\n/**\n * Type guard to check if a rule is a single match rule\n */\nexport function isSingleMatchRule(rule: MatchRule): rule is SingleMatchRule {\n return 'match' in rule && 'expected' in rule\n}\n\n/**\n * A group of rules with common filter criteria\n * NEW: Allows hierarchical filtering where a group of files shares common properties\n */\nexport interface FilterGroup {\n /**\n * Common filter criteria that all files in this group must match\n * These are checked before evaluating individual rules\n */\n groupFilter: FilterCriterion[]\n\n /**\n * Rules to apply to files that match the group filter\n */\n rules: MatchRule[]\n\n /**\n * Additional metadata for reporting\n */\n info?: Record<string, unknown>\n}\n\n/**\n * A file to be filtered\n */\nexport interface JsonFile {\n /**\n * File name or identifier\n */\n fileName: string\n\n /**\n * The JSON data\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n data: any\n\n /**\n * Optional metadata\n */\n metadata?: Record<string, unknown>\n}\n\n/**\n * Result of filtering a single file against a criterion\n */\nexport interface FilterCheckResult {\n /**\n * Whether the check passed\n */\n status: boolean\n\n /**\n * The check that was performed\n */\n checkType: string\n\n /**\n * Reason for failure (if status is false)\n */\n reason?: string | Record<string, unknown>\n}\n\n/**\n * Result of matching a file against a rule\n */\nexport interface MatchResult {\n /**\n * Whether all criteria matched\n */\n matched: boolean\n\n /**\n * Individual check results\n */\n checks: FilterCheckResult[]\n\n /**\n * The rule that was tested\n */\n rule: MatchRule\n}\n\n/**\n * A mapped file with its expected identifier\n */\nexport interface MappedFile {\n /**\n * Expected identifier\n */\n expected: string\n\n /**\n * The actual file that was matched\n */\n file: JsonFile\n\n /**\n * Match result details\n */\n matchResult: MatchResult\n\n /**\n * Optional flag from the rule\n */\n optional: boolean\n\n /**\n * Additional info from the rule\n */\n info?: Record<string, unknown>\n}\n\n/**\n * A wildcard-matched file (matched via matchAny)\n */\nexport interface WildcardMappedFile {\n /**\n * The file that was matched\n */\n file: JsonFile\n\n /**\n * Match result details\n */\n matchResult: MatchResult\n\n /**\n * Additional info from the rule\n */\n info?: Record<string, unknown>\n}\n\n/**\n * An unmapped file that didn't match any rule\n */\nexport interface UnmappedFile {\n /**\n * The file that couldn't be matched\n */\n file: JsonFile\n\n /**\n * All rules that were tried\n */\n attemptedRules: MatchResult[]\n}\n\n/**\n * A pre-filtered file that was excluded by preFilter criteria\n */\nexport interface PreFilteredFile {\n /**\n * The file that was excluded\n */\n file: JsonFile\n\n /**\n * The preFilter checks that failed\n */\n failedChecks: FilterCheckResult[]\n}\n\n/**\n * A file that was treated as optional (not matched to any mandatory rule)\n * Contains information about why it failed to match\n */\nexport interface OptionalFile {\n /**\n * File name of the optional file\n */\n fileName: string\n\n /**\n * Position in the original file sequence\n */\n position: number\n\n /**\n * Between which matched rules this file appeared\n */\n between?: {\n afterRule: string // Name of the rule before this file\n beforeRule: string // Name of the rule after this file\n }\n\n /**\n * All rules that were attempted and failed\n * Shows why this file didn't match any rule\n */\n failedMatches: MatchResult[]\n}\n\n/**\n * Result of the entire filtering operation\n */\nexport interface FilterResult {\n /**\n * Successfully mapped files (expected identifier → actual file)\n */\n mapped: MappedFile[]\n\n /**\n * Files matched by wildcard rules\n */\n wildcardMatched: WildcardMappedFile[]\n\n /**\n * Files that were treated as optional (not matched to any rule)\n * Contains match attempt information\n *\n * Note: When optional mode is enabled, unmapped will always be empty\n * All non-matched files will appear here instead\n */\n optionalFiles: OptionalFile[]\n\n /**\n * Files that couldn't be matched to any rule\n * Will be EMPTY when optional mode is enabled\n */\n unmapped: UnmappedFile[]\n\n /**\n * Files that were excluded by preFilter criteria\n */\n preFiltered: PreFilteredFile[]\n\n /**\n * Statistics\n */\n stats: {\n totalFiles: number\n mappedFiles: number\n wildcardMatchedFiles: number\n unmappedFiles: number // Always 0 when mode='optional' or mode='strict-optional'\n optionalFiles: number // NEW\n preFilteredFiles: number\n totalRules: number\n mandatoryRules: number\n optionalRules: number\n }\n}\n\n/**\n * Request for filtering files\n */\nexport interface FilterRequest {\n /**\n * Files to be filtered\n */\n files: JsonFile[]\n\n /**\n * Matching rules in order (flat structure)\n * Can be a single rule or an array of rules (for flexible ordering)\n * Either 'rules' or 'groups' should be provided, not both\n */\n rules?: (MatchRule | MatchRule[])[]\n\n /**\n * Grouped rules with common filter criteria (hierarchical structure)\n * Either 'rules' or 'groups' should be provided, not both\n * NEW: Allows organizing rules by common criteria (e.g., by line number, event type)\n */\n groups?: FilterGroup[]\n\n /**\n * Sort function for ordering files before matching\n * @param a - First file\n * @param b - Second file\n * @returns Negative if a < b, positive if a > b, zero if equal\n */\n sortFn?: (a: JsonFile, b: JsonFile) => number\n\n /**\n * Optional pre-filter criteria that all files must match before rule/group matching\n * Files that don't match the pre-filter are excluded entirely (not added to unmapped)\n * Applied before group filters\n */\n preFilter?: FilterCriterion[]\n\n /**\n * Optional context for time-based filtering\n */\n context?: {\n startTimeScript?: string\n startTimeTest?: string\n pathTime?: PathElement[]\n }\n\n /**\n * Matching mode for handling files that don't match rules\n *\n * - 'strict' (default): Current behavior - all files must match a rule, non-matching → unmapped\n * - 'optional': Files that don't match any rule → optionalFiles (permissive mode)\n * - 'strict-optional': Only allow files as optional if they match an optional rule\n *\n * When 'optional' or 'strict-optional', unmapped will always be empty (all non-matched files go to optionalFiles)\n */\n mode?: 'strict' | 'strict-optional' | 'optional'\n}\n","import type { PathElement } from '../core/types'\n\n/**\n * Result of accessing a value from an object\n */\nexport interface AccessResult {\n /**\n * The value found at the path (undefined if not found)\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n value: any\n\n /**\n * Whether the path was successfully resolved\n */\n found: boolean\n\n /**\n * Error message if path couldn't be resolved\n */\n error?: string\n\n /**\n * The portion of the path that was successfully resolved\n */\n validPath: PathElement[]\n}\n\n/**\n * Retrieves a value from an object following the specified path.\n * Supports nested objects and arrays.\n *\n * @param object - The object to access\n * @param path - Array of property names and array indices\n * @returns AccessResult containing the value and status\n *\n * @example\n * ```typescript\n * const obj = { data: { stations: [{ name: 'Berlin' }, { name: 'Munich' }] } };\n * const result = getValueFromPath(obj, ['data', 'stations', 1, 'name']);\n * // result.value === 'Munich'\n * // result.found === true\n * ```\n */\nexport function getValueFromPath(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n object: any,\n path: PathElement[]\n): AccessResult {\n const validPath: PathElement[] = []\n\n // Handle empty path\n if (path.length === 0) {\n return {\n value: object,\n found: true,\n validPath: [],\n }\n }\n\n // Navigate through the path\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let current: any = object\n\n for (let i = 0; i < path.length; i++) {\n const segment = path[i]\n\n // Handle null or undefined\n if (current === null || current === undefined) {\n return {\n value: undefined,\n found: false,\n error: `Cannot read property '${segment}' of ${current}`,\n validPath,\n }\n }\n\n // Handle array access\n if (Array.isArray(current)) {\n const index = typeof segment === 'number' ? segment : parseInt(String(segment), 10)\n\n if (isNaN(index)) {\n return {\n value: undefined,\n found: false,\n error: `Array index must be a number, got '${segment}'`,\n validPath,\n }\n }\n\n if (index < 0 || index >= current.length) {\n return {\n value: undefined,\n found: false,\n error: `Array index ${index} out of bounds (length: ${current.length})`,\n validPath,\n }\n }\n\n validPath.push(index)\n current = current[index]\n continue\n }\n\n // Handle object access\n if (typeof current === 'object') {\n const key = String(segment)\n\n if (!(key in current)) {\n return {\n value: undefined,\n found: false,\n error: `Property '${key}' does not exist`,\n validPath,\n }\n }\n\n validPath.push(key)\n current = current[key]\n continue\n }\n\n // Cannot navigate further\n return {\n value: undefined,\n found: false,\n error: `Cannot access property '${segment}' of primitive type ${typeof current}`,\n validPath,\n }\n }\n\n return {\n value: current,\n found: true,\n validPath,\n }\n}\n\n/**\n * Checks if a path exists in an object.\n *\n * @param object - The object to check\n * @param path - Array of property names and array indices\n * @returns true if the path exists and has a defined value\n *\n * @example\n * ```typescript\n * const obj = { data: { value: 42 } };\n * pathExists(obj, ['data', 'value']); // true\n * pathExists(obj, ['data', 'missing']); // false\n * ```\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function pathExists(object: any, path: PathElement[]): boolean {\n const result = getValueFromPath(object, path)\n return result.found && result.value !== undefined\n}\n\n/**\n * Gets a value from an object with a default fallback.\n *\n * @param object - The object to access\n * @param path - Array of property names and array indices\n * @param defaultValue - Value to return if path doesn't exist\n * @returns The value at the path, or defaultValue if not found\n *\n * @example\n * ```typescript\n * const obj = { data: { value: 42 } };\n * getValueOr(obj, ['data', 'value'], 0); // 42\n * getValueOr(obj, ['data', 'missing'], 0); // 0\n * ```\n */\nexport function getValueOr<T>(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n object: any,\n path: PathElement[],\n defaultValue: T\n): T {\n const result = getValueFromPath(object, path)\n return result.found && result.value !== undefined ? result.value : defaultValue\n}\n","import { DateTime } from 'luxon'\nimport type {\n FilterCriterion,\n FilterCheckResult,\n CheckValue,\n CheckExists,\n CheckArrayElement,\n CheckArraySize,\n CheckTimeRange,\n CheckNumericRange,\n} from '../core/types'\nimport { getValueFromPath } from '../utils/ObjectAccess'\n\n/**\n * Filter engine that evaluates filter criteria against data objects.\n * Provides various check methods for different types of comparisons.\n */\nexport class FilterEngine {\n /**\n * Context for time-based filtering\n */\n private context?: {\n startTimeScript?: string\n startTimeTest?: string\n }\n\n constructor(context?: { startTimeScript?: string; startTimeTest?: string }) {\n this.context = context\n }\n\n /**\n * Evaluates a single filter criterion against a data object.\n *\n * @param data - The data object to check\n * @param criterion - The filter criterion to evaluate\n * @returns FilterCheckResult indicating success or failure\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n evaluateCriterion(data: any, criterion: FilterCriterion): FilterCheckResult {\n const check = criterion.check\n\n // Determine check type and delegate to appropriate method\n if ('value' in check) {\n return this.checkValue(data, criterion.path, check as CheckValue)\n }\n\n if ('exists' in check) {\n return this.checkExists(data, criterion.path, check as CheckExists)\n }\n\n if ('itemExists' in check && 'item' in check) {\n return this.checkArrayElement(data, criterion.path, check as CheckArrayElement)\n }\n\n if ('type' in check && 'size' in check) {\n return this.checkArraySize(data, criterion.path, check as CheckArraySize)\n }\n\n if ('min' in check && 'max' in check) {\n // Distinguish between numeric and time ranges based on type\n if (typeof check.min === 'number' && typeof check.max === 'number') {\n return this.checkNumericRange(data, criterion.path, check as CheckNumericRange)\n } else {\n return this.checkTimeRange(data, criterion.path, check as CheckTimeRange)\n }\n }\n\n return {\n status: false,\n checkType: 'unknown',\n reason: `Unknown check type: ${JSON.stringify(check)}`,\n }\n }\n\n /**\n * Checks if a value matches an expected value using deep equality.\n *\n * @param data - The data object\n * @param path - Path to the value\n * @param check - The value check specification\n * @returns FilterCheckResult\n */\n private checkValue(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n data: any,\n path: (string | number)[],\n check: CheckValue\n ): FilterCheckResult {\n const accessResult = getValueFromPath(data, path)\n\n if (!accessResult.found) {\n return {\n status: false,\n checkType: 'checkValue',\n reason: {\n message: accessResult.error || 'Path not found',\n path,\n },\n }\n }\n\n const actual = accessResult.value\n const expected = check.value\n\n if (this.deepEqual(actual, expected)) {\n return {\n status: true,\n checkType: 'checkValue',\n }\n }\n\n return {\n status: false,\n checkType: 'checkValue',\n reason: {\n message: 'Value mismatch',\n path,\n expected,\n actual,\n },\n }\n }\n\n /**\n * Checks if a path exists in the data object.\n *\n * @param data - The data object\n * @param path - Path to check\n * @param check - The exists check specification\n * @returns FilterCheckResult\n */\n private checkExists(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n data: any,\n path: (string | number)[],\n check: CheckExists\n ): FilterCheckResult {\n const accessResult = getValueFromPath(data, path)\n const exists = accessResult.found && accessResult.value !== undefined\n\n if (check.exists === exists) {\n return {\n status: true,\n checkType: 'checkExists',\n }\n }\n\n return {\n status: false,\n checkType: 'checkExists',\n reason: {\n message: check.exists\n ? `Path should exist but doesn't: ${accessResult.error}`\n : 'Path should not exist but does',\n path,\n },\n }\n }\n\n /**\n * Checks if an array contains (or doesn't contain) a specific element.\n *\n * @param data - The data object\n * @param path - Path to the array\n * @param check - The array element check specification\n * @returns FilterCheckResult\n */\n private checkArrayElement(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n data: any,\n path: (string | number)[],\n check: CheckArrayElement\n ): FilterCheckResult {\n const accessResult = getValueFromPath(data, path)\n\n if (!accessResult.found) {\n return {\n status: false,\n checkType: 'checkArrayElement',\n reason: {\n message: accessResult.error || 'Path not found',\n path,\n },\n }\n }\n\n const array = accessResult.value\n\n if (!Array.isArray(array)) {\n return {\n status: false,\n checkType: 'checkArrayElement',\n reason: {\n message: 'Value is not an array',\n path,\n actualType: typeof array,\n },\n }\n }\n\n // Check if item exists in array\n const found = array.some(elem => this.deepEqual(elem, check.item))\n\n if (check.itemExists === found) {\n return {\n status: true,\n checkType: 'checkArrayElement',\n }\n }\n\n return {\n status: false,\n checkType: 'checkArrayElement',\n reason: {\n message: check.itemExists\n ? \"Item should exist in array but doesn't\"\n : 'Item should not exist in array but does',\n path,\n item: check.item,\n },\n }\n }\n\n /**\n * Checks if an array has the expected size.\n *\n * @param data - The data object\n * @param path - Path to the array\n * @param check - The array size check specification\n * @returns FilterCheckResult\n */\n private checkArraySize(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n data: any,\n path: (string | number)[],\n check: CheckArraySize\n ): FilterCheckResult {\n const accessResult = getValueFromPath(data, path)\n\n if (!accessResult.found) {\n return {\n status: false,\n checkType: 'checkArraySize',\n reason: {\n message: accessResult.error || 'Path not found',\n path,\n },\n }\n }\n\n const array = accessResult.value\n\n if (!Array.isArray(array)) {\n return {\n status: false,\n checkType: 'checkArraySize',\n reason: {\n message: 'Value is not an array',\n path,\n actualType: typeof array,\n },\n }\n }\n\n const actualSize = array.length\n const expectedSize = check.size\n\n let passes = false\n let message = ''\n\n switch (check.type) {\n case 'equal':\n passes = actualSize === expectedSize\n message = `Array length should be ${expectedSize} but is ${actualSize}`\n break\n case 'lessThan':\n passes = actualSize < expectedSize\n message = `Array length should be less than ${expectedSize} but is ${actualSize}`\n break\n case 'greaterThan':\n passes = actualSize > expectedSize\n message = `Array length should be greater than ${expectedSize} but is ${actualSize}`\n break\n }\n\n if (passes) {\n return {\n status: true,\n checkType: 'checkArraySize',\n }\n }\n\n return {\n status: false,\n checkType: 'checkArraySize',\n reason: {\n message,\n path,\n expected: expectedSize,\n actual: actualSize,\n },\n }\n }\n\n /**\n * Checks if a timestamp is within the expected time range.\n *\n * @param data - The data object\n * @param path - Path to the timestamp\n * @param check - The time range check specification\n * @returns FilterCheckResult\n */\n private checkTimeRange(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n data: any,\n path: (string | number)[],\n check: CheckTimeRange\n ): FilterCheckResult {\n const accessResult = getValueFromPath(data, path)\n\n if (!accessResult.found) {\n return {\n status: false,\n checkType: 'checkTimeRange',\n reason: {\n message: accessResult.error || 'Path not found',\n path,\n },\n }\n }\n\n const value = accessResult.value\n\n // Handle numeric timestamps (milliseconds or seconds)\n if (typeof value === 'number') {\n const min = parseInt(check.min)\n const max = parseInt(check.max)\n\n if (isNaN(min) || isNaN(max)) {\n return {\n status: false,\n checkType: 'checkTimeRange',\n reason: {\n message: 'Min or max is not a valid number',\n min: check.min,\n max: check.max,\n },\n }\n }\n\n if (value >= min && value <= max) {\n return {\n status: true,\n checkType: 'checkTimeRange',\n }\n }\n\n return {\n status: false,\n checkType: 'checkTimeRange',\n reason: {\n message: `Timestamp ${value} is outside range [${min}, ${max}]`,\n path,\n actual: value,\n min,\n max,\n },\n }\n }\n\n // Handle ISO timestamps\n if (typeof value === 'string') {\n const timestamp = DateTime.fromISO(value)\n const minTime = DateTime.fromISO(check.min)\n const maxTime = DateTime.fromISO(check.max)\n\n if (!timestamp.isValid) {\n return {\n status: false,\n checkType: 'checkTimeRange',\n reason: {\n message: `Invalid timestamp: ${value}`,\n path,\n },\n }\n }\n\n if (!minTime.isValid || !maxTime.isValid) {\n return {\n status: false,\n checkType: 'checkTimeRange',\n reason: {\n message: 'Invalid min or max time',\n min: check.min,\n max: check.max,\n },\n }\n }\n\n if (timestamp >= minTime && timestamp <= maxTime) {\n return {\n status: true,\n checkType: 'checkTimeRange',\n }\n }\n\n return {\n status: false,\n checkType: 'checkTimeRange',\n reason: {\n message: `Timestamp ${value} is outside range [${check.min}, ${check.max}]`,\n path,\n actual: value,\n min: check.min,\n max: check.max,\n },\n }\n }\n\n return {\n status: false,\n checkType: 'checkTimeRange',\n reason: {\n message: `Timestamp must be a string or number, got ${typeof value}`,\n path,\n },\n }\n }\n\n /**\n * Checks if a numeric value is within the expected range.\n *\n * @param data - The data object\n * @param path - Path to the numeric value\n * @param check - The numeric range check specification\n * @returns FilterCheckResult\n */\n private checkNumericRange(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n data: any,\n path: (string | number)[],\n check: CheckNumericRange\n ): FilterCheckResult {\n const accessResult = getValueFromPath(data, path)\n\n if (!accessResult.found) {\n return {\n status: false,\n checkType: 'checkNumericRange',\n reason: {\n message: accessResult.error || 'Path not found',\n path,\n },\n }\n }\n\n const value = accessResult.value\n\n if (typeof value !== 'number') {\n return {\n status: false,\n checkType: 'checkNumericRange',\n reason: {\n message: `Value must be a number, got ${typeof value}`,\n path,\n actual: value,\n },\n }\n }\n\n if (value >= check.min && value <= check.max) {\n return {\n status: true,\n checkType: 'checkNumericRange',\n }\n }\n\n return {\n status: false,\n checkType: 'checkNumericRange',\n reason: {\n message: `Value ${value} is outside range [${check.min}, ${check.max}]`,\n path,\n actual: value,\n min: check.min,\n max: check.max,\n },\n }\n }\n\n /**\n * Deep equality comparison.\n * Compares two values recursively for equality.\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private deepEqual(a: any, b: any): boolean {\n // Same reference\n if (a === b) return true\n\n // Null checks\n if (a === null || b === null) return a === b\n if (a === undefined || b === undefined) return a === b\n\n // Type check\n if (typeof a !== typeof b) return false\n\n // Dates\n if (a instanceof Date && b instanceof Date) {\n return a.getTime() === b.getTime()\n }\n\n // Arrays\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false\n return a.every((val, idx) => this.deepEqual(val, b[idx]))\n }\n\n // Objects\n if (typeof a === 'object' && typeof b === 'object') {\n const keysA = Object.keys(a)\n const keysB = Object.keys(b)\n\n if (keysA.length !== keysB.length) return false\n\n return keysA.every(key => this.deepEqual(a[key], b[key]))\n }\n\n // Primitives\n return a === b\n }\n}\n","import type {\n JsonFile,\n MatchRule,\n FilterResult,\n MappedFile,\n WildcardMappedFile,\n UnmappedFile,\n OptionalFile,\n PreFilteredFile,\n MatchResult,\n FilterCriterion,\n FilterGroup,\n} from '../core/types'\nimport { isWildcardRule, isSingleMatchRule } from '../core/types'\nimport { FilterEngine } from '../engine/FilterEngine'\n\n/**\n * Matcher system that matches files against rules.\n * Supports single matches, flexible ordering, and wildcard matches.\n */\nexport class Matcher {\n private engine: FilterEngine\n\n constructor(context?: { startTimeScript?: string; startTimeTest?: string }) {\n this.engine = new FilterEngine(context)\n }\n\n /**\n * Matches a single file against a rule.\n *\n * @param file - The file to match\n * @param rule - The rule to match against\n * @returns MatchResult indicating if all criteria matched\n */\n matchFile(file: JsonFile, rule: MatchRule): MatchResult {\n const criteria = isWildcardRule(rule) ? rule.matchAny : rule.match\n\n const checks = criteria.map(criterion => {\n return this.engine.evaluateCriterion(file.data, criterion)\n })\n\n const matched = checks.every(check => check.status)\n\n return {\n matched,\n checks,\n rule,\n }\n }\n\n /**\n * Applies pre-filter criteria to files, separating files that match from those that don't.\n *\n * @param files - Files to filter\n * @param preFilter - Filter criteria that all files must match\n * @returns Object with matched files and excluded files (with failed checks)\n */\n private applyPreFilter(\n files: JsonFile[],\n preFilter: FilterCriterion[]\n ): {\n matched: JsonFile[]\n excluded: PreFilteredFile[]\n } {\n const matched: JsonFile[] = []\n const excluded: PreFilteredFile[] = []\n\n for (const file of files) {\n const checks = preFilter.map(criterion => {\n return this.engine.evaluateCriterion(file.data, criterion)\n })\n\n if (checks.every(check => check.status)) {\n matched.push(file)\n } else {\n excluded.push({\n file,\n failedChecks: checks.filter(check => !check.status),\n })\n }\n }\n\n return { matched, excluded }\n }\n\n /**\n * Main filtering function that processes files according to rules.\n *\n * @param files - Files to filter\n * @param rules - Matching rules (can include arrays for flexible ordering)\n * @param sortFn - Optional sort function for file ordering\n * @param preFilter - Optional pre-filter criteria (files not matching are excluded)\n * @param mode - Matching mode ('strict', 'optional', or 'strict-optional')\n * @returns FilterResult with mapped, wildcardMatched, optionalFiles and unmapped files\n */\n filterFiles(\n files: JsonFile[],\n rules: (MatchRule | MatchRule[])[],\n sortFn?: (a: JsonFile, b: JsonFile) => number,\n preFilter?: FilterCriterion[],\n mode: 'strict' | 'strict-optional' | 'optional' = 'strict'\n ): FilterResult {\n // Apply pre-filter if provided\n let filteredFiles: JsonFile[]\n let preFiltered: PreFilteredFile[] = []\n\n if (preFilter) {\n const preFilterResult = this.applyPreFilter(files, preFilter)\n filteredFiles = preFilterResult.matched\n preFiltered = preFilterResult.excluded\n } else {\n filteredFiles = files\n }\n\n // Sort files if sort function provided\n const sortedFiles = sortFn ? [...filteredFiles].sort(sortFn) : [...filteredFiles]\n\n const mapped: MappedFile[] = []\n const wildcardMatched: WildcardMappedFile[] = []\n const unmapped: UnmappedFile[] = []\n const optionalFiles: OptionalFile[] = []\n\n // Track which files have been matched\n const matchedFileIndices = new Set<number>()\n\n // For optional modes, use scan-forward algorithm\n if (mode === 'optional' || mode === 'strict-optional') {\n return this.filterFilesOptionalMode(sortedFiles, rules, preFiltered, files.length, mode)\n }\n\n // Track which flexible rules have been used\n const usedFlexibleRules = new Map<number, Set<number>>()\n\n let fileIndex = 0\n let ruleIndex = 0\n\n while (fileIndex < sortedFiles.length) {\n const file = sortedFiles[fileIndex]\n\n if (ruleIndex >= rules.length) {\n // No more rules - file is unmapped\n if (!matchedFileIndices.has(fileIndex)) {\n unmapped.push({\n file,\n attemptedRules: [],\n })\n }\n fileIndex++\n continue\n }\n\n const ruleOrRules = rules[ruleIndex]\n const attemptedMatches: MatchResult[] = []\n let fileMatched = false\n\n // Handle array of rules (flexible ordering)\n if (Array.isArray(ruleOrRules)) {\n // Try each rule in the array\n for (let subIndex = 0; subIndex < ruleOrRules.length; subIndex++) {\n // Check if this subrule was already used\n const used = usedFlexibleRules.get(ruleIndex)?.has(subIndex)\n if (used) {\n continue\n }\n\n const rule = ruleOrRules[subIndex]\n const matchResult = this.matchFile(file, rule)\n attemptedMatches.push(matchResult)\n\n if (matchResult.matched && isSingleMatchRule(rule)) {\n // Mark this subrule as used\n if (!usedFlexibleRules.has(ruleIndex)) {\n usedFlexibleRules.set(ruleIndex, new Set())\n }\n const usedSet = usedFlexibleRules.get(ruleIndex)\n if (usedSet) {\n usedSet.add(subIndex)\n }\n\n mapped.push({\n expected: rule.expected,\n file,\n matchResult,\n optional: rule.optional || false,\n info: rule.info,\n })\n\n matchedFileIndices.add(fileIndex)\n fileMatched = true\n break\n }\n }\n\n // Check if all subrules are exhausted\n const allUsed = usedFlexibleRules.get(ruleIndex)?.size === ruleOrRules.length\n if (allUsed) {\n ruleIndex++\n }\n\n if (fileMatched) {\n fileIndex++\n } else {\n // Try next file with same rule group\n fileIndex++\n }\n } else {\n // Single rule\n const rule = ruleOrRules\n const matchResult = this.matchFile(file, rule)\n attemptedMatches.push(matchResult)\n\n if (matchResult.matched) {\n if (isSingleMatchRule(rule)) {\n // Regular single match\n mapped.push({\n expected: rule.expected,\n file,\n matchResult,\n optional: rule.optional || false,\n info: rule.info,\n })\n\n matchedFileIndices.add(fileIndex)\n ruleIndex++\n fileIndex++\n } else if (isWildcardRule(rule)) {\n // Wildcard match\n wildcardMatched.push({\n file,\n matchResult,\n info: rule.info,\n })\n\n matchedFileIndices.add(fileIndex)\n\n if (rule.greedy) {\n // Stay on same rule, move to next file\n fileIndex++\n } else {\n // Non-greedy: match once and move to next rule\n ruleIndex++\n fileIndex++\n }\n }\n } else {\n // No match\n if (rule.optional) {\n // Optional rule didn't match - skip to next rule\n ruleIndex++\n } else if (isWildcardRule(rule)) {\n // Wildcard rule (always optional) didn't match - skip to next rule\n ruleIndex++\n } else {\n // Mandatory rule didn't match - file is unmapped\n unmapped.push({\n file,\n attemptedRules: attemptedMatches,\n })\n fileIndex++\n }\n }\n }\n }\n\n // Generate statistics\n const stats = {\n totalFiles: files.length,\n mappedFiles: mapped.length,\n wildcardMatchedFiles: wildcardMatched.length,\n unmappedFiles: unmapped.length,\n optionalFiles: optionalFiles.length,\n preFilteredFiles: preFiltered.length,\n totalRules: this.countRules(rules),\n mandatoryRules: this.countMandatoryRules(rules),\n optionalRules: this.countOptionalRules(rules),\n }\n\n return {\n mapped,\n wildcardMatched,\n optionalFiles,\n unmapped,\n preFiltered,\n stats,\n }\n }\n\n /**\n * Filtering with optional mode - uses scan-forward algorithm\n */\n private filterFilesOptionalMode(\n sortedFiles: JsonFile[],\n rules: (MatchRule | MatchRule[])[],\n preFiltered: PreFilteredFile[],\n totalFiles: number,\n mode: 'optional' | 'strict-optional'\n ): FilterResult {\n const mapped: MappedFile[] = []\n const wildcardMatched: WildcardMappedFile[] = []\n const optionalFiles: OptionalFile[] = []\n\n let currentFileIndex = 0\n let lastMatchedIndex = -1\n let lastMatchedRule: string | null = null\n\n for (let ruleIndex = 0; ruleIndex < rules.length; ruleIndex++) {\n const ruleOrRules = rules[ruleIndex]\n let found = false\n\n // Scan forward from current position to find a matching file\n for (let fileIndex = currentFileIndex; fileIndex < sortedFiles.length; fileIndex++) {\n const file = sortedFiles[fileIndex]\n\n // Try to match against this rule\n let matchResult: MatchResult | null = null\n let matchedRule: MatchRule | null = null\n\n if (Array.isArray(ruleOrRules)) {\n // Try each rule in the flexible array\n for (const rule of ruleOrRules) {\n const result = this.matchFile(file, rule)\n if (result.matched) {\n matchResult = result\n matchedRule = rule\n break\n }\n }\n } else {\n matchResult = this.matchFile(file, ruleOrRules)\n if (matchResult.matched) {\n matchedRule = ruleOrRules\n }\n }\n\n if (matchResult && matchResult.matched && matchedRule) {\n // Found a match! Mark all files between last match and this match as optional\n for (let i = lastMatchedIndex + 1; i < fileIndex; i++) {\n const optionalFile = sortedFiles[i]\n optionalFiles.push({\n fileName: optionalFile.fileName,\n position: i,\n between: {\n afterRule: lastMatchedRule || '(start)',\n beforeRule: isSingleMatchRule(matchedRule) ? matchedRule.expected : '(wildcard)',\n },\n failedMatches: [], // TODO: collect actual failed matches\n })\n }\n\n // Add the matched file\n if (isSingleMatchRule(matchedRule)) {\n mapped.push({\n expected: matchedRule.expected,\n file,\n matchResult,\n optional: matchedRule.optional || false,\n info: matchedRule.info,\n })\n lastMatchedRule = matchedRule.expected\n } else if (isWildcardRule(matchedRule)) {\n wildcardMatched.push({\n file,\n matchResult,\n info: matchedRule.info,\n })\n lastMatchedRule = '(wildcard)'\n }\n\n lastMatchedIndex = fileIndex\n currentFileIndex = fileIndex + 1\n found = true\n break\n }\n }\n\n // If rule not found and it's mandatory, that's an error in strict-optional mode\n if (!found) {\n const isMandatory = Array.isArray(ruleOrRules)\n ? ruleOrRules.some(r => !r.optional && !isWildcardRule(r))\n : !ruleOrRules.optional && !isWildcardRule(ruleOrRules)\n\n if (isMandatory && mode === 'strict-optional') {\n // In strict-optional mode, failing to find a mandatory rule is still an error\n // But we mark remaining files as optional\n break\n }\n // In pure optional mode, we just continue\n }\n }\n\n // Mark all remaining files as optional\n for (let i = lastMatchedIndex + 1; i < sortedFiles.length; i++) {\n const optionalFile = sortedFiles[i]\n optionalFiles.push({\n fileName: optionalFile.fileName,\n position: i,\n between: {\n afterRule: lastMatchedRule || '(start)',\n beforeRule: '(end)',\n },\n failedMatches: [], // TODO: collect actual failed matches\n })\n }\n\n const stats = {\n totalFiles,\n mappedFiles: mapped.length,\n wildcardMatchedFiles: wildcardMatched.length,\n unmappedFiles: 0, // Always 0 in optional modes\n optionalFiles: optionalFiles.length,\n preFilteredFiles: preFiltered.length,\n totalRules: this.countRules(rules),\n mandatoryRules: this.countMandatoryRules(rules),\n optionalRules: this.countOptionalRules(rules),\n }\n\n return {\n mapped,\n wildcardMatched,\n optionalFiles,\n unmapped: [], // Always empty in optional modes\n preFiltered,\n stats,\n }\n }\n\n /**\n * Counts total number of rules (flattening arrays)\n */\n private countRules(rules: (MatchRule | MatchRule[])[]): number {\n return rules.reduce((count, rule) => {\n if (Array.isArray(rule)) {\n return count + rule.length\n }\n return count + 1\n }, 0)\n }\n\n /**\n * Counts mandatory rules\n */\n private countMandatoryRules(rules: (MatchRule | MatchRule[])[]): number {\n return rules.reduce((count, rule) => {\n if (Array.isArray(rule)) {\n return count + rule.filter(r => !r.optional && !isWildcardRule(r)).length\n }\n return count + (rule.optional || isWildcardRule(rule) ? 0 : 1)\n }, 0)\n }\n\n /**\n * Counts optional rules\n */\n private countOptionalRules(rules: (MatchRule | MatchRule[])[]): number {\n return rules.reduce((count, rule) => {\n if (Array.isArray(rule)) {\n return count + rule.filter(r => r.optional || isWildcardRule(r)).length\n }\n return count + (rule.optional || isWildcardRule(rule) ? 1 : 0)\n }, 0)\n }\n\n /**\n * Filtering function for grouped rules with common filter criteria.\n * Files are first filtered by preFilter (if provided), then matched against\n * group filters, and finally processed by the group's rules.\n *\n * @param files - Files to filter\n * @param groups - Filter groups with common criteria and rules\n * @param sortFn - Optional sort function for file ordering\n * @param preFilter - Optional pre-filter criteria (files not matching are excluded)\n * @param mode - Matching mode ('strict', 'optional', or 'strict-optional')\n * @returns FilterResult with mapped, wildcardMatched, optionalFiles and unmapped files\n */\n filterFilesWithGroups(\n files: JsonFile[],\n groups: FilterGroup[],\n sortFn?: (a: JsonFile, b: JsonFile) => number,\n preFilter?: FilterCriterion[],\n mode: 'strict' | 'strict-optional' | 'optional' = 'strict'\n ): FilterResult {\n // Apply pre-filter if provided\n let filteredFiles: JsonFile[]\n let preFiltered: PreFilteredFile[] = []\n\n if (preFilter) {\n const preFilterResult = this.applyPreFilter(files, preFilter)\n filteredFiles = preFilterResult.matched\n preFiltered = preFilterResult.excluded\n } else {\n filteredFiles = files\n }\n\n // Sort files if sort function provided\n const sortedFiles = sortFn ? [...filteredFiles].sort(sortFn) : [...filteredFiles]\n\n const mapped: MappedFile[] = []\n const wildcardMatched: WildcardMappedFile[] = []\n const unmapped: UnmappedFile[] = []\n const optionalFiles: OptionalFile[] = []\n\n // Process each group\n for (const group of groups) {\n // Find files that match this group's filter\n const groupFiles = sortedFiles.filter(file => {\n const checks = group.groupFilter.map(criterion => {\n return this.engine.evaluateCriterion(file.data, criterion)\n })\n return checks.every(check => check.status)\n })\n\n if (groupFiles.length === 0) {\n // No files match this group - continue to next group\n continue\n }\n\n // Apply the group's rules to the matched files\n // Convert rules to the format expected by filterFiles\n const rulesArray: (MatchRule | MatchRule[])[] = group.rules.map(rule => {\n if (Array.isArray(rule)) {\n return rule\n }\n return rule\n })\n\n // Use the existing filterFiles logic (without preFilter, already applied)\n const groupResult = this.filterFiles(groupFiles, rulesArray, undefined, undefined, mode)\n\n // Merge results\n mapped.push(...groupResult.mapped)\n wildcardMatched.push(...groupResult.wildcardMatched)\n unmapped.push(...groupResult.unmapped)\n optionalFiles.push(...groupResult.optionalFiles)\n }\n\n // Calculate total rules across all groups\n const allRules = groups.flatMap(g => g.rules)\n const stats = {\n totalFiles: files.length,\n mappedFiles: mapped.length,\n wildcardMatchedFiles: wildcardMatched.length,\n unmappedFiles: unmapped.length,\n optionalFiles: optionalFiles.length,\n preFilteredFiles: preFiltered.length,\n totalRules: this.countRules(allRules),\n mandatoryRules: this.countMandatoryRules(allRules),\n optionalRules: this.countOptionalRules(allRules),\n }\n\n return {\n mapped,\n wildcardMatched,\n optionalFiles,\n unmapped,\n preFiltered,\n stats,\n }\n }\n}\n","/**\n * @aikotools/datafilter\n *\n * Advanced data filtering engine for JSON file matching in E2E testing\n */\n\n// Core exports\nexport type {\n PathElement,\n TimeUnit,\n CheckValue,\n CheckExists,\n CheckArrayElement,\n CheckArraySize,\n CheckTimeRange,\n CheckNumericRange,\n FilterCriterion,\n SingleMatchRule,\n WildcardMatchRule,\n MatchRule,\n FilterGroup,\n JsonFile,\n FilterCheckResult,\n MatchResult,\n MappedFile,\n WildcardMappedFile,\n UnmappedFile,\n OptionalFile,\n PreFilteredFile,\n FilterResult,\n FilterRequest,\n} from './core/types'\n\nexport { isWildcardRule, isSingleMatchRule } from './core/types'\n\n// Engine exports\nexport { FilterEngine } from './engine'\n\n// Matcher exports\nexport { Matcher } from './matcher'\n\n// Utility exports\nexport { getValueFromPath, pathExists, getValueOr } from './utils'\nexport type { AccessResult } from './utils'\n\n// Convenience function\nimport { Matcher } from './matcher'\nimport type { FilterRequest, FilterResult } from './core/types'\n\n/**\n * Convenience function for filtering files with a single call.\n * Creates a Matcher instance and performs the filtering operation.\n *\n * @param request - Filter request containing files, rules/groups, sort function, and context\n * @returns FilterResult with mapped, wildcardMatched, and unmapped files\n *\n * @example\n * ```typescript\n * // Using flat rules structure\n * const result = filterFiles({\n * files: jsonFiles,\n * rules: [\n * { match: [{path: ['type'], check: {value: 'event1'}}], expected: 'event1.json' },\n * { matchAny: [{path: ['optional'], check: {exists: true}}], optional: true, greedy: true },\n * { match: [{path: ['type'], check: {value: 'event2'}}], expected: 'event2.json' }\n * ],\n * sortFn: (a, b) => a.data.timestamp - b.data.timestamp\n * });\n *\n * // Using grouped structure\n * const result = filterFiles({\n * files: jsonFiles,\n * preFilter: [{path: ['eventType'], check: {value: 'RiFahrt'}}],\n * groups: [\n * {\n * groupFilter: [{path: ['linie'], check: {value: '88888'}}],\n * rules: [...]\n * }\n * ],\n * sortFn: (a, b) => a.data.timestamp - b.data.timestamp\n * });\n * ```\n */\nexport function filterFiles(request: FilterRequest): FilterResult {\n const matcher = new Matcher(request.context)\n\n // Validate: either rules or groups should be provided\n if (request.rules && request.groups) {\n throw new Error('FilterRequest: Provide either \"rules\" or \"groups\", not both')\n }\n\n if (!request.rules && !request.groups) {\n throw new Error('FilterRequest: Must provide either \"rules\" or \"groups\"')\n }\n\n // Use groups-based filtering if groups are provided\n if (request.groups) {\n return matcher.filterFilesWithGroups(\n request.files,\n request.groups,\n request.sortFn,\n request.preFilter,\n request.mode\n )\n }\n\n // Use traditional flat rules filtering\n // At this point, we know request.rules exists because we validated above\n if (!request.rules) {\n throw new Error('FilterRequest: Rules are required')\n }\n return matcher.filterFiles(\n request.files,\n request.rules,\n request.sortFn,\n request.preFilter,\n request.mode\n )\n}\n"],"names":["Matcher"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"aikotools-datafilter.mjs","sources":["../src/core/types.ts","../src/utils/ObjectAccess.ts","../src/engine/FilterEngine.ts","../src/matcher/Matcher.ts","../src/index.ts"],"sourcesContent":["/**\n * Core type definitions for the datafilter engine\n */\n\n/**\n * A single element in a path through a JSON structure.\n * Can be a string for object properties or a number for array indices.\n *\n * @example\n * ['data', 'stations', 0, 'name'] → data.stations[0].name\n */\nexport type PathElement = string | number\n\n/**\n * Time unit for time-based comparisons\n */\nexport type TimeUnit =\n | 'milliseconds'\n | 'seconds'\n | 'minutes'\n | 'hours'\n | 'days'\n | 'weeks'\n | 'months'\n | 'years'\n\n/**\n * Check if a value matches an expected value (deep equality)\n */\nexport interface CheckValue {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n value: any\n}\n\n/**\n * Check if a path exists in the object\n */\nexport interface CheckExists {\n exists: boolean\n}\n\n/**\n * Check if an array contains (or doesn't contain) a specific item\n */\nexport interface CheckArrayElement {\n itemExists: boolean\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n item: any\n}\n\n/**\n * Check if an array has a specific size\n */\nexport interface CheckArraySize {\n type: 'equal' | 'lessThan' | 'greaterThan'\n size: number\n}\n\n/**\n * Check if a timestamp is within a time range\n */\nexport interface CheckTimeRange {\n min: string\n max: string\n}\n\n/**\n * Check if a numeric value is within a numeric range\n */\nexport interface CheckNumericRange {\n min: number\n max: number\n}\n\n/**\n * A single filter criterion that checks one aspect of the data\n */\nexport interface FilterCriterion {\n /**\n * Path to the value to check\n */\n path: PathElement[]\n\n /**\n * The check to perform on the value\n */\n check:\n | CheckValue\n | CheckExists\n | CheckArrayElement\n | CheckArraySize\n | CheckTimeRange\n | CheckNumericRange\n}\n\n/**\n * A single match rule that defines how to match a file\n */\nexport interface SingleMatchRule {\n /**\n * Filter criteria - all must match (AND logic)\n */\n match: FilterCriterion[]\n\n /**\n * Expected file name/identifier\n */\n expected: string\n\n /**\n * Whether this match is optional\n */\n optional?: boolean\n\n /**\n * Additional metadata for reporting\n */\n info?: Record<string, unknown>\n}\n\n/**\n * A wildcard match rule that can match multiple files\n * NEW: Allows matching arbitrary number of optional files without explicit specification\n */\nexport interface WildcardMatchRule {\n /**\n * Filter criteria for wildcard matching\n */\n matchAny: FilterCriterion[]\n\n /**\n * Whether to match greedily (as many as possible) or stop after first match\n * Default: false (stop after first match)\n */\n greedy?: boolean\n\n /**\n * Wildcard matches are always optional\n */\n optional: true\n\n /**\n * Additional metadata for reporting\n */\n info?: Record<string, unknown>\n}\n\n/**\n * A match rule can be either a single match or a wildcard match\n */\nexport type MatchRule = SingleMatchRule | WildcardMatchRule\n\n/**\n * Type guard to check if a rule is a wildcard rule\n */\nexport function isWildcardRule(rule: MatchRule): rule is WildcardMatchRule {\n return 'matchAny' in rule\n}\n\n/**\n * Type guard to check if a rule is a single match rule\n */\nexport function isSingleMatchRule(rule: MatchRule): rule is SingleMatchRule {\n return 'match' in rule && 'expected' in rule\n}\n\n/**\n * A group of rules with common filter criteria\n * NEW: Allows hierarchical filtering where a group of files shares common properties\n */\nexport interface FilterGroup {\n /**\n * Common filter criteria that all files in this group must match\n * These are checked before evaluating individual rules\n */\n groupFilter: FilterCriterion[]\n\n /**\n * Rules to apply to files that match the group filter\n */\n rules: MatchRule[]\n\n /**\n * Additional metadata for reporting\n */\n info?: Record<string, unknown>\n}\n\n/**\n * A file to be filtered\n */\nexport interface JsonFile {\n /**\n * File name or identifier\n */\n fileName: string\n\n /**\n * The JSON data\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n data: any\n\n /**\n * Optional metadata\n */\n metadata?: Record<string, unknown>\n}\n\n/**\n * Result of filtering a single file against a criterion\n */\nexport interface FilterCheckResult {\n /**\n * Whether the check passed\n */\n status: boolean\n\n /**\n * The check that was performed\n */\n checkType: string\n\n /**\n * Reason for failure (if status is false)\n */\n reason?: string | Record<string, unknown>\n}\n\n/**\n * Result of matching a file against a rule\n */\nexport interface MatchResult {\n /**\n * Whether all criteria matched\n */\n matched: boolean\n\n /**\n * Individual check results\n */\n checks: FilterCheckResult[]\n\n /**\n * The rule that was tested\n */\n rule: MatchRule\n}\n\n/**\n * A mapped file with its expected identifier\n */\nexport interface MappedFile {\n /**\n * Expected identifier\n */\n expected: string\n\n /**\n * The actual file that was matched\n */\n file: JsonFile\n\n /**\n * Match result details\n */\n matchResult: MatchResult\n\n /**\n * Optional flag from the rule\n */\n optional: boolean\n\n /**\n * Additional info from the rule\n */\n info?: Record<string, unknown>\n}\n\n/**\n * A wildcard-matched file (matched via matchAny)\n */\nexport interface WildcardMappedFile {\n /**\n * The file that was matched\n */\n file: JsonFile\n\n /**\n * Match result details\n */\n matchResult: MatchResult\n\n /**\n * Additional info from the rule\n */\n info?: Record<string, unknown>\n}\n\n/**\n * An unmapped file that didn't match any rule\n */\nexport interface UnmappedFile {\n /**\n * The file that couldn't be matched\n */\n file: JsonFile\n\n /**\n * All rules that were tried\n */\n attemptedRules: MatchResult[]\n}\n\n/**\n * A pre-filtered file that was excluded by preFilter criteria\n */\nexport interface PreFilteredFile {\n /**\n * The file that was excluded\n */\n file: JsonFile\n\n /**\n * The preFilter checks that failed\n */\n failedChecks: FilterCheckResult[]\n}\n\n/**\n * A file that was treated as optional (not matched to any mandatory rule)\n * Contains information about why it failed to match\n */\nexport interface OptionalFile {\n /**\n * File name of the optional file\n */\n fileName: string\n\n /**\n * Position in the original file sequence\n */\n position: number\n\n /**\n * Between which matched rules this file appeared\n */\n between?: {\n afterRule: string // Name of the rule before this file\n beforeRule: string // Name of the rule after this file\n }\n\n /**\n * All rules that were attempted and failed\n * Shows why this file didn't match any rule\n */\n failedMatches: MatchResult[]\n}\n\n/**\n * Result of the entire filtering operation\n */\nexport interface FilterResult {\n /**\n * Successfully mapped files (expected identifier → actual file)\n */\n mapped: MappedFile[]\n\n /**\n * Files matched by wildcard rules\n */\n wildcardMatched: WildcardMappedFile[]\n\n /**\n * Files that were treated as optional (not matched to any rule)\n * Contains match attempt information\n *\n * Note: When optional mode is enabled, unmapped will always be empty\n * All non-matched files will appear here instead\n */\n optionalFiles: OptionalFile[]\n\n /**\n * Files that couldn't be matched to any rule\n * Will be EMPTY when optional mode is enabled\n */\n unmapped: UnmappedFile[]\n\n /**\n * Files that were excluded by preFilter criteria\n */\n preFiltered: PreFilteredFile[]\n\n /**\n * Statistics\n */\n stats: {\n totalFiles: number\n mappedFiles: number\n wildcardMatchedFiles: number\n unmappedFiles: number // Always 0 when mode='optional' or mode='strict-optional'\n optionalFiles: number // NEW\n preFilteredFiles: number\n totalRules: number\n mandatoryRules: number\n optionalRules: number\n }\n}\n\n/**\n * Request for filtering files\n */\nexport interface FilterRequest {\n /**\n * Files to be filtered\n */\n files: JsonFile[]\n\n /**\n * Matching rules in order (flat structure)\n * Can be a single rule or an array of rules (for flexible ordering)\n * Either 'rules' or 'groups' should be provided, not both\n */\n rules?: (MatchRule | MatchRule[])[]\n\n /**\n * Grouped rules with common filter criteria (hierarchical structure)\n * Either 'rules' or 'groups' should be provided, not both\n * NEW: Allows organizing rules by common criteria (e.g., by line number, event type)\n */\n groups?: FilterGroup[]\n\n /**\n * Sort function for ordering files before matching\n * @param a - First file\n * @param b - Second file\n * @returns Negative if a < b, positive if a > b, zero if equal\n */\n sortFn?: (a: JsonFile, b: JsonFile) => number\n\n /**\n * Optional pre-filter criteria that all files must match before rule/group matching\n * Files that don't match the pre-filter are excluded entirely (not added to unmapped)\n * Applied before group filters\n */\n preFilter?: FilterCriterion[]\n\n /**\n * Optional context for time-based filtering\n */\n context?: {\n startTimeScript?: string\n startTimeTest?: string\n pathTime?: PathElement[]\n }\n\n /**\n * Matching mode for handling files that don't match rules\n *\n * - 'strict' (default): Current behavior - all files must match a rule, non-matching → unmapped\n * - 'optional': Files that don't match any rule → optionalFiles (permissive mode)\n * - 'strict-optional': Only allow files as optional if they match an optional rule\n *\n * When 'optional' or 'strict-optional', unmapped will always be empty (all non-matched files go to optionalFiles)\n */\n mode?: 'strict' | 'strict-optional' | 'optional'\n}\n","import type { PathElement } from '../core/types'\n\n/**\n * Result of accessing a value from an object\n */\nexport interface AccessResult {\n /**\n * The value found at the path (undefined if not found)\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n value: any\n\n /**\n * Whether the path was successfully resolved\n */\n found: boolean\n\n /**\n * Error message if path couldn't be resolved\n */\n error?: string\n\n /**\n * The portion of the path that was successfully resolved\n */\n validPath: PathElement[]\n}\n\n/**\n * Retrieves a value from an object following the specified path.\n * Supports nested objects and arrays.\n *\n * @param object - The object to access\n * @param path - Array of property names and array indices\n * @returns AccessResult containing the value and status\n *\n * @example\n * ```typescript\n * const obj = { data: { stations: [{ name: 'Berlin' }, { name: 'Munich' }] } };\n * const result = getValueFromPath(obj, ['data', 'stations', 1, 'name']);\n * // result.value === 'Munich'\n * // result.found === true\n * ```\n */\nexport function getValueFromPath(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n object: any,\n path: PathElement[]\n): AccessResult {\n const validPath: PathElement[] = []\n\n // Handle empty path\n if (path.length === 0) {\n return {\n value: object,\n found: true,\n validPath: [],\n }\n }\n\n // Navigate through the path\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let current: any = object\n\n for (let i = 0; i < path.length; i++) {\n const segment = path[i]\n\n // Handle null or undefined\n if (current === null || current === undefined) {\n return {\n value: undefined,\n found: false,\n error: `Cannot read property '${segment}' of ${current}`,\n validPath,\n }\n }\n\n // Handle array access\n if (Array.isArray(current)) {\n const index = typeof segment === 'number' ? segment : parseInt(String(segment), 10)\n\n if (isNaN(index)) {\n return {\n value: undefined,\n found: false,\n error: `Array index must be a number, got '${segment}'`,\n validPath,\n }\n }\n\n if (index < 0 || index >= current.length) {\n return {\n value: undefined,\n found: false,\n error: `Array index ${index} out of bounds (length: ${current.length})`,\n validPath,\n }\n }\n\n validPath.push(index)\n current = current[index]\n continue\n }\n\n // Handle object access\n if (typeof current === 'object') {\n const key = String(segment)\n\n if (!(key in current)) {\n return {\n value: undefined,\n found: false,\n error: `Property '${key}' does not exist`,\n validPath,\n }\n }\n\n validPath.push(key)\n current = current[key]\n continue\n }\n\n // Cannot navigate further\n return {\n value: undefined,\n found: false,\n error: `Cannot access property '${segment}' of primitive type ${typeof current}`,\n validPath,\n }\n }\n\n return {\n value: current,\n found: true,\n validPath,\n }\n}\n\n/**\n * Checks if a path exists in an object.\n *\n * @param object - The object to check\n * @param path - Array of property names and array indices\n * @returns true if the path exists and has a defined value\n *\n * @example\n * ```typescript\n * const obj = { data: { value: 42 } };\n * pathExists(obj, ['data', 'value']); // true\n * pathExists(obj, ['data', 'missing']); // false\n * ```\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function pathExists(object: any, path: PathElement[]): boolean {\n const result = getValueFromPath(object, path)\n return result.found && result.value !== undefined\n}\n\n/**\n * Gets a value from an object with a default fallback.\n *\n * @param object - The object to access\n * @param path - Array of property names and array indices\n * @param defaultValue - Value to return if path doesn't exist\n * @returns The value at the path, or defaultValue if not found\n *\n * @example\n * ```typescript\n * const obj = { data: { value: 42 } };\n * getValueOr(obj, ['data', 'value'], 0); // 42\n * getValueOr(obj, ['data', 'missing'], 0); // 0\n * ```\n */\nexport function getValueOr<T>(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n object: any,\n path: PathElement[],\n defaultValue: T\n): T {\n const result = getValueFromPath(object, path)\n return result.found && result.value !== undefined ? result.value : defaultValue\n}\n","import { DateTime } from 'luxon'\nimport type {\n FilterCriterion,\n FilterCheckResult,\n CheckValue,\n CheckExists,\n CheckArrayElement,\n CheckArraySize,\n CheckTimeRange,\n CheckNumericRange,\n} from '../core/types'\nimport { getValueFromPath } from '../utils/ObjectAccess'\n\n/**\n * Filter engine that evaluates filter criteria against data objects.\n * Provides various check methods for different types of comparisons.\n */\nexport class FilterEngine {\n /**\n * Context for time-based filtering\n */\n private context?: {\n startTimeScript?: string\n startTimeTest?: string\n }\n\n constructor(context?: { startTimeScript?: string; startTimeTest?: string }) {\n this.context = context\n }\n\n /**\n * Evaluates a single filter criterion against a data object.\n *\n * @param data - The data object to check\n * @param criterion - The filter criterion to evaluate\n * @returns FilterCheckResult indicating success or failure\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n evaluateCriterion(data: any, criterion: FilterCriterion): FilterCheckResult {\n const check = criterion.check\n\n // Determine check type and delegate to appropriate method\n if ('value' in check) {\n return this.checkValue(data, criterion.path, check as CheckValue)\n }\n\n if ('exists' in check) {\n return this.checkExists(data, criterion.path, check as CheckExists)\n }\n\n if ('itemExists' in check && 'item' in check) {\n return this.checkArrayElement(data, criterion.path, check as CheckArrayElement)\n }\n\n if ('type' in check && 'size' in check) {\n return this.checkArraySize(data, criterion.path, check as CheckArraySize)\n }\n\n if ('min' in check && 'max' in check) {\n // Distinguish between numeric and time ranges based on type\n if (typeof check.min === 'number' && typeof check.max === 'number') {\n return this.checkNumericRange(data, criterion.path, check as CheckNumericRange)\n } else {\n return this.checkTimeRange(data, criterion.path, check as CheckTimeRange)\n }\n }\n\n return {\n status: false,\n checkType: 'unknown',\n reason: `Unknown check type: ${JSON.stringify(check)}`,\n }\n }\n\n /**\n * Checks if a value matches an expected value using deep equality.\n *\n * @param data - The data object\n * @param path - Path to the value\n * @param check - The value check specification\n * @returns FilterCheckResult\n */\n private checkValue(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n data: any,\n path: (string | number)[],\n check: CheckValue\n ): FilterCheckResult {\n const accessResult = getValueFromPath(data, path)\n\n if (!accessResult.found) {\n return {\n status: false,\n checkType: 'checkValue',\n reason: {\n message: accessResult.error || 'Path not found',\n path,\n },\n }\n }\n\n const actual = accessResult.value\n const expected = check.value\n\n if (this.deepEqual(actual, expected)) {\n return {\n status: true,\n checkType: 'checkValue',\n }\n }\n\n return {\n status: false,\n checkType: 'checkValue',\n reason: {\n message: 'Value mismatch',\n path,\n expected,\n actual,\n },\n }\n }\n\n /**\n * Checks if a path exists in the data object.\n *\n * @param data - The data object\n * @param path - Path to check\n * @param check - The exists check specification\n * @returns FilterCheckResult\n */\n private checkExists(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n data: any,\n path: (string | number)[],\n check: CheckExists\n ): FilterCheckResult {\n const accessResult = getValueFromPath(data, path)\n const exists = accessResult.found && accessResult.value !== undefined\n\n if (check.exists === exists) {\n return {\n status: true,\n checkType: 'checkExists',\n }\n }\n\n return {\n status: false,\n checkType: 'checkExists',\n reason: {\n message: check.exists\n ? `Path should exist but doesn't: ${accessResult.error}`\n : 'Path should not exist but does',\n path,\n },\n }\n }\n\n /**\n * Checks if an array contains (or doesn't contain) a specific element.\n *\n * @param data - The data object\n * @param path - Path to the array\n * @param check - The array element check specification\n * @returns FilterCheckResult\n */\n private checkArrayElement(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n data: any,\n path: (string | number)[],\n check: CheckArrayElement\n ): FilterCheckResult {\n const accessResult = getValueFromPath(data, path)\n\n if (!accessResult.found) {\n return {\n status: false,\n checkType: 'checkArrayElement',\n reason: {\n message: accessResult.error || 'Path not found',\n path,\n },\n }\n }\n\n const array = accessResult.value\n\n if (!Array.isArray(array)) {\n return {\n status: false,\n checkType: 'checkArrayElement',\n reason: {\n message: 'Value is not an array',\n path,\n actualType: typeof array,\n },\n }\n }\n\n // Check if item exists in array\n const found = array.some(elem => this.deepEqual(elem, check.item))\n\n if (check.itemExists === found) {\n return {\n status: true,\n checkType: 'checkArrayElement',\n }\n }\n\n return {\n status: false,\n checkType: 'checkArrayElement',\n reason: {\n message: check.itemExists\n ? \"Item should exist in array but doesn't\"\n : 'Item should not exist in array but does',\n path,\n item: check.item,\n },\n }\n }\n\n /**\n * Checks if an array has the expected size.\n *\n * @param data - The data object\n * @param path - Path to the array\n * @param check - The array size check specification\n * @returns FilterCheckResult\n */\n private checkArraySize(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n data: any,\n path: (string | number)[],\n check: CheckArraySize\n ): FilterCheckResult {\n const accessResult = getValueFromPath(data, path)\n\n if (!accessResult.found) {\n return {\n status: false,\n checkType: 'checkArraySize',\n reason: {\n message: accessResult.error || 'Path not found',\n path,\n },\n }\n }\n\n const array = accessResult.value\n\n if (!Array.isArray(array)) {\n return {\n status: false,\n checkType: 'checkArraySize',\n reason: {\n message: 'Value is not an array',\n path,\n actualType: typeof array,\n },\n }\n }\n\n const actualSize = array.length\n const expectedSize = check.size\n\n let passes = false\n let message = ''\n\n switch (check.type) {\n case 'equal':\n passes = actualSize === expectedSize\n message = `Array length should be ${expectedSize} but is ${actualSize}`\n break\n case 'lessThan':\n passes = actualSize < expectedSize\n message = `Array length should be less than ${expectedSize} but is ${actualSize}`\n break\n case 'greaterThan':\n passes = actualSize > expectedSize\n message = `Array length should be greater than ${expectedSize} but is ${actualSize}`\n break\n }\n\n if (passes) {\n return {\n status: true,\n checkType: 'checkArraySize',\n }\n }\n\n return {\n status: false,\n checkType: 'checkArraySize',\n reason: {\n message,\n path,\n expected: expectedSize,\n actual: actualSize,\n },\n }\n }\n\n /**\n * Checks if a timestamp is within the expected time range.\n *\n * @param data - The data object\n * @param path - Path to the timestamp\n * @param check - The time range check specification\n * @returns FilterCheckResult\n */\n private checkTimeRange(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n data: any,\n path: (string | number)[],\n check: CheckTimeRange\n ): FilterCheckResult {\n const accessResult = getValueFromPath(data, path)\n\n if (!accessResult.found) {\n return {\n status: false,\n checkType: 'checkTimeRange',\n reason: {\n message: accessResult.error || 'Path not found',\n path,\n },\n }\n }\n\n const value = accessResult.value\n\n // Handle numeric timestamps (milliseconds or seconds)\n if (typeof value === 'number') {\n const min = parseInt(check.min)\n const max = parseInt(check.max)\n\n if (isNaN(min) || isNaN(max)) {\n return {\n status: false,\n checkType: 'checkTimeRange',\n reason: {\n message: 'Min or max is not a valid number',\n min: check.min,\n max: check.max,\n },\n }\n }\n\n if (value >= min && value <= max) {\n return {\n status: true,\n checkType: 'checkTimeRange',\n }\n }\n\n return {\n status: false,\n checkType: 'checkTimeRange',\n reason: {\n message: `Timestamp ${value} is outside range [${min}, ${max}]`,\n path,\n actual: value,\n min,\n max,\n },\n }\n }\n\n // Handle ISO timestamps\n if (typeof value === 'string') {\n const timestamp = DateTime.fromISO(value)\n const minTime = DateTime.fromISO(check.min)\n const maxTime = DateTime.fromISO(check.max)\n\n if (!timestamp.isValid) {\n return {\n status: false,\n checkType: 'checkTimeRange',\n reason: {\n message: `Invalid timestamp: ${value}`,\n path,\n },\n }\n }\n\n if (!minTime.isValid || !maxTime.isValid) {\n return {\n status: false,\n checkType: 'checkTimeRange',\n reason: {\n message: 'Invalid min or max time',\n min: check.min,\n max: check.max,\n },\n }\n }\n\n if (timestamp >= minTime && timestamp <= maxTime) {\n return {\n status: true,\n checkType: 'checkTimeRange',\n }\n }\n\n return {\n status: false,\n checkType: 'checkTimeRange',\n reason: {\n message: `Timestamp ${value} is outside range [${check.min}, ${check.max}]`,\n path,\n actual: value,\n min: check.min,\n max: check.max,\n },\n }\n }\n\n return {\n status: false,\n checkType: 'checkTimeRange',\n reason: {\n message: `Timestamp must be a string or number, got ${typeof value}`,\n path,\n },\n }\n }\n\n /**\n * Checks if a numeric value is within the expected range.\n *\n * @param data - The data object\n * @param path - Path to the numeric value\n * @param check - The numeric range check specification\n * @returns FilterCheckResult\n */\n private checkNumericRange(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n data: any,\n path: (string | number)[],\n check: CheckNumericRange\n ): FilterCheckResult {\n const accessResult = getValueFromPath(data, path)\n\n if (!accessResult.found) {\n return {\n status: false,\n checkType: 'checkNumericRange',\n reason: {\n message: accessResult.error || 'Path not found',\n path,\n },\n }\n }\n\n const value = accessResult.value\n\n if (typeof value !== 'number') {\n return {\n status: false,\n checkType: 'checkNumericRange',\n reason: {\n message: `Value must be a number, got ${typeof value}`,\n path,\n actual: value,\n },\n }\n }\n\n if (value >= check.min && value <= check.max) {\n return {\n status: true,\n checkType: 'checkNumericRange',\n }\n }\n\n return {\n status: false,\n checkType: 'checkNumericRange',\n reason: {\n message: `Value ${value} is outside range [${check.min}, ${check.max}]`,\n path,\n actual: value,\n min: check.min,\n max: check.max,\n },\n }\n }\n\n /**\n * Deep equality comparison.\n * Compares two values recursively for equality.\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private deepEqual(a: any, b: any): boolean {\n // Same reference\n if (a === b) return true\n\n // Null checks\n if (a === null || b === null) return a === b\n if (a === undefined || b === undefined) return a === b\n\n // Type check\n if (typeof a !== typeof b) return false\n\n // Dates\n if (a instanceof Date && b instanceof Date) {\n return a.getTime() === b.getTime()\n }\n\n // Arrays\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false\n return a.every((val, idx) => this.deepEqual(val, b[idx]))\n }\n\n // Objects\n if (typeof a === 'object' && typeof b === 'object') {\n const keysA = Object.keys(a)\n const keysB = Object.keys(b)\n\n if (keysA.length !== keysB.length) return false\n\n return keysA.every(key => this.deepEqual(a[key], b[key]))\n }\n\n // Primitives\n return a === b\n }\n}\n","import type {\n JsonFile,\n MatchRule,\n FilterResult,\n MappedFile,\n WildcardMappedFile,\n UnmappedFile,\n OptionalFile,\n PreFilteredFile,\n MatchResult,\n FilterCriterion,\n FilterGroup,\n} from '../core/types'\nimport { isWildcardRule, isSingleMatchRule } from '../core/types'\nimport { FilterEngine } from '../engine/FilterEngine'\n\n/**\n * Matcher system that matches files against rules.\n * Supports single matches, flexible ordering, and wildcard matches.\n */\nexport class Matcher {\n private engine: FilterEngine\n\n constructor(context?: { startTimeScript?: string; startTimeTest?: string }) {\n this.engine = new FilterEngine(context)\n }\n\n /**\n * Matches a single file against a rule.\n *\n * @param file - The file to match\n * @param rule - The rule to match against\n * @returns MatchResult indicating if all criteria matched\n */\n matchFile(file: JsonFile, rule: MatchRule): MatchResult {\n const criteria = isWildcardRule(rule) ? rule.matchAny : rule.match\n\n const checks = criteria.map(criterion => {\n return this.engine.evaluateCriterion(file.data, criterion)\n })\n\n const matched = checks.every(check => check.status)\n\n return {\n matched,\n checks,\n rule,\n }\n }\n\n /**\n * Applies pre-filter criteria to files, separating files that match from those that don't.\n *\n * @param files - Files to filter\n * @param preFilter - Filter criteria that all files must match\n * @returns Object with matched files and excluded files (with failed checks)\n */\n private applyPreFilter(\n files: JsonFile[],\n preFilter: FilterCriterion[]\n ): {\n matched: JsonFile[]\n excluded: PreFilteredFile[]\n } {\n const matched: JsonFile[] = []\n const excluded: PreFilteredFile[] = []\n\n for (const file of files) {\n const checks = preFilter.map(criterion => {\n return this.engine.evaluateCriterion(file.data, criterion)\n })\n\n if (checks.every(check => check.status)) {\n matched.push(file)\n } else {\n excluded.push({\n file,\n failedChecks: checks.filter(check => !check.status),\n })\n }\n }\n\n return { matched, excluded }\n }\n\n /**\n * Main filtering function that processes files according to rules.\n *\n * @param files - Files to filter\n * @param rules - Matching rules (can include arrays for flexible ordering)\n * @param sortFn - Optional sort function for file ordering\n * @param preFilter - Optional pre-filter criteria (files not matching are excluded)\n * @param mode - Matching mode ('strict', 'optional', or 'strict-optional')\n * @returns FilterResult with mapped, wildcardMatched, optionalFiles and unmapped files\n */\n filterFiles(\n files: JsonFile[],\n rules: (MatchRule | MatchRule[])[],\n sortFn?: (a: JsonFile, b: JsonFile) => number,\n preFilter?: FilterCriterion[],\n mode: 'strict' | 'strict-optional' | 'optional' = 'strict'\n ): FilterResult {\n // Apply pre-filter if provided\n let filteredFiles: JsonFile[]\n let preFiltered: PreFilteredFile[] = []\n\n if (preFilter) {\n const preFilterResult = this.applyPreFilter(files, preFilter)\n filteredFiles = preFilterResult.matched\n preFiltered = preFilterResult.excluded\n } else {\n filteredFiles = files\n }\n\n // Sort files if sort function provided\n const sortedFiles = sortFn ? [...filteredFiles].sort(sortFn) : [...filteredFiles]\n\n const mapped: MappedFile[] = []\n const wildcardMatched: WildcardMappedFile[] = []\n const unmapped: UnmappedFile[] = []\n const optionalFiles: OptionalFile[] = []\n\n // Track which files have been matched\n const matchedFileIndices = new Set<number>()\n\n // For optional modes, use scan-forward algorithm\n if (mode === 'optional' || mode === 'strict-optional') {\n return this.filterFilesOptionalMode(sortedFiles, rules, preFiltered, files.length, mode)\n }\n\n // Track which flexible rules have been used\n const usedFlexibleRules = new Map<number, Set<number>>()\n\n let fileIndex = 0\n let ruleIndex = 0\n\n while (fileIndex < sortedFiles.length) {\n const file = sortedFiles[fileIndex]\n\n if (ruleIndex >= rules.length) {\n // No more rules - file is unmapped\n if (!matchedFileIndices.has(fileIndex)) {\n unmapped.push({\n file,\n attemptedRules: [],\n })\n }\n fileIndex++\n continue\n }\n\n const ruleOrRules = rules[ruleIndex]\n const attemptedMatches: MatchResult[] = []\n let fileMatched = false\n\n // Handle array of rules (flexible ordering)\n if (Array.isArray(ruleOrRules)) {\n // Try each rule in the array\n for (let subIndex = 0; subIndex < ruleOrRules.length; subIndex++) {\n // Check if this subrule was already used\n const used = usedFlexibleRules.get(ruleIndex)?.has(subIndex)\n if (used) {\n continue\n }\n\n const rule = ruleOrRules[subIndex]\n const matchResult = this.matchFile(file, rule)\n attemptedMatches.push(matchResult)\n\n if (matchResult.matched && isSingleMatchRule(rule)) {\n // Mark this subrule as used\n if (!usedFlexibleRules.has(ruleIndex)) {\n usedFlexibleRules.set(ruleIndex, new Set())\n }\n const usedSet = usedFlexibleRules.get(ruleIndex)\n if (usedSet) {\n usedSet.add(subIndex)\n }\n\n mapped.push({\n expected: rule.expected,\n file,\n matchResult,\n optional: rule.optional || false,\n info: rule.info,\n })\n\n matchedFileIndices.add(fileIndex)\n fileMatched = true\n break\n }\n }\n\n // Check if all subrules are exhausted\n const allUsed = usedFlexibleRules.get(ruleIndex)?.size === ruleOrRules.length\n if (allUsed) {\n ruleIndex++\n }\n\n if (fileMatched) {\n fileIndex++\n } else {\n // File didn't match any rule in the group\n // Check if this is a mandatory group (at least one non-optional rule)\n const hasMandatoryRule = ruleOrRules.some(r => isSingleMatchRule(r) && !r.optional)\n\n if (hasMandatoryRule) {\n // Add file to unmapped since it didn't match a mandatory group\n unmapped.push({\n file,\n attemptedRules: attemptedMatches,\n })\n }\n\n // Try next file with same rule group\n fileIndex++\n }\n } else {\n // Single rule\n const rule = ruleOrRules\n const matchResult = this.matchFile(file, rule)\n attemptedMatches.push(matchResult)\n\n if (matchResult.matched) {\n if (isSingleMatchRule(rule)) {\n // Regular single match\n mapped.push({\n expected: rule.expected,\n file,\n matchResult,\n optional: rule.optional || false,\n info: rule.info,\n })\n\n matchedFileIndices.add(fileIndex)\n ruleIndex++\n fileIndex++\n } else if (isWildcardRule(rule)) {\n // Wildcard match\n wildcardMatched.push({\n file,\n matchResult,\n info: rule.info,\n })\n\n matchedFileIndices.add(fileIndex)\n\n if (rule.greedy) {\n // Stay on same rule, move to next file\n fileIndex++\n } else {\n // Non-greedy: match once and move to next rule\n ruleIndex++\n fileIndex++\n }\n }\n } else {\n // No match\n if (rule.optional) {\n // Optional rule didn't match - skip to next rule\n ruleIndex++\n } else if (isWildcardRule(rule)) {\n // Wildcard rule (always optional) didn't match - skip to next rule\n ruleIndex++\n } else {\n // Mandatory rule didn't match - file is unmapped\n unmapped.push({\n file,\n attemptedRules: attemptedMatches,\n })\n fileIndex++\n }\n }\n }\n }\n\n // Generate statistics\n const stats = {\n totalFiles: files.length,\n mappedFiles: mapped.length,\n wildcardMatchedFiles: wildcardMatched.length,\n unmappedFiles: unmapped.length,\n optionalFiles: optionalFiles.length,\n preFilteredFiles: preFiltered.length,\n totalRules: this.countRules(rules),\n mandatoryRules: this.countMandatoryRules(rules),\n optionalRules: this.countOptionalRules(rules),\n }\n\n return {\n mapped,\n wildcardMatched,\n optionalFiles,\n unmapped,\n preFiltered,\n stats,\n }\n }\n\n /**\n * Filtering with optional mode - uses scan-forward algorithm\n */\n private filterFilesOptionalMode(\n sortedFiles: JsonFile[],\n rules: (MatchRule | MatchRule[])[],\n preFiltered: PreFilteredFile[],\n totalFiles: number,\n mode: 'optional' | 'strict-optional'\n ): FilterResult {\n const mapped: MappedFile[] = []\n const wildcardMatched: WildcardMappedFile[] = []\n const optionalFiles: OptionalFile[] = []\n\n let currentFileIndex = 0\n let lastMatchedIndex = -1\n let lastMatchedRule: string | null = null\n\n for (let ruleIndex = 0; ruleIndex < rules.length; ruleIndex++) {\n const ruleOrRules = rules[ruleIndex]\n let found = false\n\n // Scan forward from current position to find a matching file\n for (let fileIndex = currentFileIndex; fileIndex < sortedFiles.length; fileIndex++) {\n const file = sortedFiles[fileIndex]\n\n // Try to match against this rule\n let matchResult: MatchResult | null = null\n let matchedRule: MatchRule | null = null\n\n if (Array.isArray(ruleOrRules)) {\n // Try each rule in the flexible array\n for (const rule of ruleOrRules) {\n const result = this.matchFile(file, rule)\n if (result.matched) {\n matchResult = result\n matchedRule = rule\n break\n }\n }\n } else {\n matchResult = this.matchFile(file, ruleOrRules)\n if (matchResult.matched) {\n matchedRule = ruleOrRules\n }\n }\n\n if (matchResult && matchResult.matched && matchedRule) {\n // Found a match! Mark all files between last match and this match as optional\n for (let i = lastMatchedIndex + 1; i < fileIndex; i++) {\n const optionalFile = sortedFiles[i]\n optionalFiles.push({\n fileName: optionalFile.fileName,\n position: i,\n between: {\n afterRule: lastMatchedRule || '(start)',\n beforeRule: isSingleMatchRule(matchedRule) ? matchedRule.expected : '(wildcard)',\n },\n failedMatches: [], // TODO: collect actual failed matches\n })\n }\n\n // Add the matched file\n if (isSingleMatchRule(matchedRule)) {\n mapped.push({\n expected: matchedRule.expected,\n file,\n matchResult,\n optional: matchedRule.optional || false,\n info: matchedRule.info,\n })\n lastMatchedRule = matchedRule.expected\n } else if (isWildcardRule(matchedRule)) {\n wildcardMatched.push({\n file,\n matchResult,\n info: matchedRule.info,\n })\n lastMatchedRule = '(wildcard)'\n }\n\n lastMatchedIndex = fileIndex\n currentFileIndex = fileIndex + 1\n found = true\n break\n }\n }\n\n // If rule not found and it's mandatory, that's an error in strict-optional mode\n if (!found) {\n const isMandatory = Array.isArray(ruleOrRules)\n ? ruleOrRules.some(r => !r.optional && !isWildcardRule(r))\n : !ruleOrRules.optional && !isWildcardRule(ruleOrRules)\n\n if (isMandatory && mode === 'strict-optional') {\n // In strict-optional mode, failing to find a mandatory rule is still an error\n // But we mark remaining files as optional\n break\n }\n // In pure optional mode, we just continue\n }\n }\n\n // Mark all remaining files as optional\n for (let i = lastMatchedIndex + 1; i < sortedFiles.length; i++) {\n const optionalFile = sortedFiles[i]\n optionalFiles.push({\n fileName: optionalFile.fileName,\n position: i,\n between: {\n afterRule: lastMatchedRule || '(start)',\n beforeRule: '(end)',\n },\n failedMatches: [], // TODO: collect actual failed matches\n })\n }\n\n const stats = {\n totalFiles,\n mappedFiles: mapped.length,\n wildcardMatchedFiles: wildcardMatched.length,\n unmappedFiles: 0, // Always 0 in optional modes\n optionalFiles: optionalFiles.length,\n preFilteredFiles: preFiltered.length,\n totalRules: this.countRules(rules),\n mandatoryRules: this.countMandatoryRules(rules),\n optionalRules: this.countOptionalRules(rules),\n }\n\n return {\n mapped,\n wildcardMatched,\n optionalFiles,\n unmapped: [], // Always empty in optional modes\n preFiltered,\n stats,\n }\n }\n\n /**\n * Counts total number of rules (flattening arrays)\n */\n private countRules(rules: (MatchRule | MatchRule[])[]): number {\n return rules.reduce((count, rule) => {\n if (Array.isArray(rule)) {\n return count + rule.length\n }\n return count + 1\n }, 0)\n }\n\n /**\n * Counts mandatory rules\n */\n private countMandatoryRules(rules: (MatchRule | MatchRule[])[]): number {\n return rules.reduce((count, rule) => {\n if (Array.isArray(rule)) {\n return count + rule.filter(r => !r.optional && !isWildcardRule(r)).length\n }\n return count + (rule.optional || isWildcardRule(rule) ? 0 : 1)\n }, 0)\n }\n\n /**\n * Counts optional rules\n */\n private countOptionalRules(rules: (MatchRule | MatchRule[])[]): number {\n return rules.reduce((count, rule) => {\n if (Array.isArray(rule)) {\n return count + rule.filter(r => r.optional || isWildcardRule(r)).length\n }\n return count + (rule.optional || isWildcardRule(rule) ? 1 : 0)\n }, 0)\n }\n\n /**\n * Filtering function for grouped rules with common filter criteria.\n * Files are first filtered by preFilter (if provided), then matched against\n * group filters, and finally processed by the group's rules.\n *\n * @param files - Files to filter\n * @param groups - Filter groups with common criteria and rules\n * @param sortFn - Optional sort function for file ordering\n * @param preFilter - Optional pre-filter criteria (files not matching are excluded)\n * @param mode - Matching mode ('strict', 'optional', or 'strict-optional')\n * @returns FilterResult with mapped, wildcardMatched, optionalFiles and unmapped files\n */\n filterFilesWithGroups(\n files: JsonFile[],\n groups: FilterGroup[],\n sortFn?: (a: JsonFile, b: JsonFile) => number,\n preFilter?: FilterCriterion[],\n mode: 'strict' | 'strict-optional' | 'optional' = 'strict'\n ): FilterResult {\n // Apply pre-filter if provided\n let filteredFiles: JsonFile[]\n let preFiltered: PreFilteredFile[] = []\n\n if (preFilter) {\n const preFilterResult = this.applyPreFilter(files, preFilter)\n filteredFiles = preFilterResult.matched\n preFiltered = preFilterResult.excluded\n } else {\n filteredFiles = files\n }\n\n // Sort files if sort function provided\n const sortedFiles = sortFn ? [...filteredFiles].sort(sortFn) : [...filteredFiles]\n\n const mapped: MappedFile[] = []\n const wildcardMatched: WildcardMappedFile[] = []\n const unmapped: UnmappedFile[] = []\n const optionalFiles: OptionalFile[] = []\n\n // Process each group\n for (const group of groups) {\n // Find files that match this group's filter\n const groupFiles = sortedFiles.filter(file => {\n const checks = group.groupFilter.map(criterion => {\n return this.engine.evaluateCriterion(file.data, criterion)\n })\n return checks.every(check => check.status)\n })\n\n if (groupFiles.length === 0) {\n // No files match this group - continue to next group\n continue\n }\n\n // Apply the group's rules to the matched files\n // Convert rules to the format expected by filterFiles\n const rulesArray: (MatchRule | MatchRule[])[] = group.rules.map(rule => {\n if (Array.isArray(rule)) {\n return rule\n }\n return rule\n })\n\n // Use the existing filterFiles logic (without preFilter, already applied)\n const groupResult = this.filterFiles(groupFiles, rulesArray, undefined, undefined, mode)\n\n // Merge results\n mapped.push(...groupResult.mapped)\n wildcardMatched.push(...groupResult.wildcardMatched)\n unmapped.push(...groupResult.unmapped)\n optionalFiles.push(...groupResult.optionalFiles)\n }\n\n // Calculate total rules across all groups\n const allRules = groups.flatMap(g => g.rules)\n const stats = {\n totalFiles: files.length,\n mappedFiles: mapped.length,\n wildcardMatchedFiles: wildcardMatched.length,\n unmappedFiles: unmapped.length,\n optionalFiles: optionalFiles.length,\n preFilteredFiles: preFiltered.length,\n totalRules: this.countRules(allRules),\n mandatoryRules: this.countMandatoryRules(allRules),\n optionalRules: this.countOptionalRules(allRules),\n }\n\n return {\n mapped,\n wildcardMatched,\n optionalFiles,\n unmapped,\n preFiltered,\n stats,\n }\n }\n}\n","/**\n * @aikotools/datafilter\n *\n * Advanced data filtering engine for JSON file matching in E2E testing\n */\n\n// Core exports\nexport type {\n PathElement,\n TimeUnit,\n CheckValue,\n CheckExists,\n CheckArrayElement,\n CheckArraySize,\n CheckTimeRange,\n CheckNumericRange,\n FilterCriterion,\n SingleMatchRule,\n WildcardMatchRule,\n MatchRule,\n FilterGroup,\n JsonFile,\n FilterCheckResult,\n MatchResult,\n MappedFile,\n WildcardMappedFile,\n UnmappedFile,\n OptionalFile,\n PreFilteredFile,\n FilterResult,\n FilterRequest,\n} from './core/types'\n\nexport { isWildcardRule, isSingleMatchRule } from './core/types'\n\n// Engine exports\nexport { FilterEngine } from './engine'\n\n// Matcher exports\nexport { Matcher } from './matcher'\n\n// Utility exports\nexport { getValueFromPath, pathExists, getValueOr } from './utils'\nexport type { AccessResult } from './utils'\n\n// Convenience function\nimport { Matcher } from './matcher'\nimport type { FilterRequest, FilterResult } from './core/types'\n\n/**\n * Convenience function for filtering files with a single call.\n * Creates a Matcher instance and performs the filtering operation.\n *\n * @param request - Filter request containing files, rules/groups, sort function, and context\n * @returns FilterResult with mapped, wildcardMatched, and unmapped files\n *\n * @example\n * ```typescript\n * // Using flat rules structure\n * const result = filterFiles({\n * files: jsonFiles,\n * rules: [\n * { match: [{path: ['type'], check: {value: 'event1'}}], expected: 'event1.json' },\n * { matchAny: [{path: ['optional'], check: {exists: true}}], optional: true, greedy: true },\n * { match: [{path: ['type'], check: {value: 'event2'}}], expected: 'event2.json' }\n * ],\n * sortFn: (a, b) => a.data.timestamp - b.data.timestamp\n * });\n *\n * // Using grouped structure\n * const result = filterFiles({\n * files: jsonFiles,\n * preFilter: [{path: ['eventType'], check: {value: 'RiFahrt'}}],\n * groups: [\n * {\n * groupFilter: [{path: ['linie'], check: {value: '88888'}}],\n * rules: [...]\n * }\n * ],\n * sortFn: (a, b) => a.data.timestamp - b.data.timestamp\n * });\n * ```\n */\nexport function filterFiles(request: FilterRequest): FilterResult {\n const matcher = new Matcher(request.context)\n\n // Validate: either rules or groups should be provided\n if (request.rules && request.groups) {\n throw new Error('FilterRequest: Provide either \"rules\" or \"groups\", not both')\n }\n\n if (!request.rules && !request.groups) {\n throw new Error('FilterRequest: Must provide either \"rules\" or \"groups\"')\n }\n\n // Use groups-based filtering if groups are provided\n if (request.groups) {\n return matcher.filterFilesWithGroups(\n request.files,\n request.groups,\n request.sortFn,\n request.preFilter,\n request.mode\n )\n }\n\n // Use traditional flat rules filtering\n // At this point, we know request.rules exists because we validated above\n if (!request.rules) {\n throw new Error('FilterRequest: Rules are required')\n }\n return matcher.filterFiles(\n request.files,\n request.rules,\n request.sortFn,\n request.preFilter,\n request.mode\n )\n}\n"],"names":["Matcher"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Matcher.d.ts","sourceRoot":"","sources":["../../../src/matcher/Matcher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,QAAQ,EACR,SAAS,EACT,YAAY,EAMZ,WAAW,EACX,eAAe,EACf,WAAW,EACZ,MAAM,eAAe,CAAA;AAItB;;;GAGG;AACH,qBAAa,OAAO;IAClB,OAAO,CAAC,MAAM,CAAc;gBAEhB,OAAO,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,MAAM,CAAC;QAAC,aAAa,CAAC,EAAE,MAAM,CAAA;KAAE;IAI1E;;;;;;OAMG;IACH,SAAS,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,GAAG,WAAW;IAgBvD;;;;;;OAMG;IACH,OAAO,CAAC,cAAc;IA4BtB;;;;;;;;;OASG;IACH,WAAW,CACT,KAAK,EAAE,QAAQ,EAAE,EACjB,KAAK,EAAE,CAAC,SAAS,GAAG,SAAS,EAAE,CAAC,EAAE,EAClC,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,KAAK,MAAM,EAC7C,SAAS,CAAC,EAAE,eAAe,EAAE,EAC7B,IAAI,GAAE,QAAQ,GAAG,iBAAiB,GAAG,UAAqB,GACzD,YAAY;
|
|
1
|
+
{"version":3,"file":"Matcher.d.ts","sourceRoot":"","sources":["../../../src/matcher/Matcher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,QAAQ,EACR,SAAS,EACT,YAAY,EAMZ,WAAW,EACX,eAAe,EACf,WAAW,EACZ,MAAM,eAAe,CAAA;AAItB;;;GAGG;AACH,qBAAa,OAAO;IAClB,OAAO,CAAC,MAAM,CAAc;gBAEhB,OAAO,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,MAAM,CAAC;QAAC,aAAa,CAAC,EAAE,MAAM,CAAA;KAAE;IAI1E;;;;;;OAMG;IACH,SAAS,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,GAAG,WAAW;IAgBvD;;;;;;OAMG;IACH,OAAO,CAAC,cAAc;IA4BtB;;;;;;;;;OASG;IACH,WAAW,CACT,KAAK,EAAE,QAAQ,EAAE,EACjB,KAAK,EAAE,CAAC,SAAS,GAAG,SAAS,EAAE,CAAC,EAAE,EAClC,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,KAAK,MAAM,EAC7C,SAAS,CAAC,EAAE,eAAe,EAAE,EAC7B,IAAI,GAAE,QAAQ,GAAG,iBAAiB,GAAG,UAAqB,GACzD,YAAY;IAsMf;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAwI/B;;OAEG;IACH,OAAO,CAAC,UAAU;IASlB;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAS3B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAS1B;;;;;;;;;;;OAWG;IACH,qBAAqB,CACnB,KAAK,EAAE,QAAQ,EAAE,EACjB,MAAM,EAAE,WAAW,EAAE,EACrB,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,KAAK,MAAM,EAC7C,SAAS,CAAC,EAAE,eAAe,EAAE,EAC7B,IAAI,GAAE,QAAQ,GAAG,iBAAiB,GAAG,UAAqB,GACzD,YAAY;CA8EhB"}
|