@aikotools/datafilter 1.1.4 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -117,6 +117,9 @@ class FilterEngine {
117
117
  return this.checkTimeRange(data, criterion.path, check);
118
118
  }
119
119
  }
120
+ if ("oneOf" in check) {
121
+ return this.checkOneOf(data, criterion.path, check);
122
+ }
120
123
  return {
121
124
  status: false,
122
125
  checkType: "unknown",
@@ -459,6 +462,45 @@ class FilterEngine {
459
462
  }
460
463
  };
461
464
  }
465
+ /**
466
+ * Checks if a value is one of a set of allowed values.
467
+ *
468
+ * @param data - The data object
469
+ * @param path - Path to the value
470
+ * @param check - The oneOf check specification
471
+ * @returns FilterCheckResult
472
+ */
473
+ checkOneOf(data, path, check) {
474
+ const accessResult = getValueFromPath(data, path);
475
+ if (!accessResult.found) {
476
+ return {
477
+ status: false,
478
+ checkType: "checkOneOf",
479
+ reason: {
480
+ message: accessResult.error || "Path not found",
481
+ path
482
+ }
483
+ };
484
+ }
485
+ const actual = accessResult.value;
486
+ const found = check.oneOf.some((allowed) => this.deepEqual(actual, allowed));
487
+ if (found) {
488
+ return {
489
+ status: true,
490
+ checkType: "checkOneOf"
491
+ };
492
+ }
493
+ return {
494
+ status: false,
495
+ checkType: "checkOneOf",
496
+ reason: {
497
+ message: `Value not in allowed set`,
498
+ path,
499
+ actual,
500
+ allowedValues: check.oneOf
501
+ }
502
+ };
503
+ }
462
504
  /**
463
505
  * Deep equality comparison.
464
506
  * Compares two values recursively for equality.
@@ -735,7 +777,7 @@ class Matcher {
735
777
  if (!usedFlexibleRules.has(ruleIndex)) {
736
778
  usedFlexibleRules.set(ruleIndex, /* @__PURE__ */ new Set());
737
779
  }
738
- usedFlexibleRules.get(ruleIndex).add(subRuleIndex);
780
+ usedFlexibleRules.get(ruleIndex)?.add(subRuleIndex);
739
781
  }
740
782
  break;
741
783
  }
@@ -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 // Track which standalone rules have been used (to ensure each rule matches only once)\n const usedStandaloneRules = new 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\n // Check if this standalone single match rule has already been used\n if (isSingleMatchRule(rule) && usedStandaloneRules.has(ruleIndex)) {\n // This rule was already matched - skip to next rule\n ruleIndex++\n continue\n }\n\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 // Mark this standalone rule as used\n usedStandaloneRules.add(ruleIndex)\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 // Track which standalone single match rules have been used in flexible arrays\n const usedFlexibleRules = new Map<number, Set<number>>()\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 const isFlexibleArray = Array.isArray(ruleOrRules)\n let found = false\n\n // For flexible arrays, keep trying to match until no more subrules can be matched\n let continueFlexibleArray = true\n while (continueFlexibleArray) {\n continueFlexibleArray = 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 (isFlexibleArray) {\n // Try each rule in the flexible array\n for (let subRuleIndex = 0; subRuleIndex < ruleOrRules.length; subRuleIndex++) {\n const rule = ruleOrRules[subRuleIndex]\n\n // Check if this subrule was already used\n if (isSingleMatchRule(rule)) {\n const usedSubRules = usedFlexibleRules.get(ruleIndex)\n if (usedSubRules && usedSubRules.has(subRuleIndex)) {\n // This subrule was already used, skip it\n continue\n }\n }\n\n const result = this.matchFile(file, rule)\n if (result.matched) {\n matchResult = result\n matchedRule = rule\n\n // Mark this subrule as used if it's a single match rule\n if (isSingleMatchRule(rule)) {\n if (!usedFlexibleRules.has(ruleIndex)) {\n usedFlexibleRules.set(ruleIndex, new Set())\n }\n usedFlexibleRules.get(ruleIndex)!.add(subRuleIndex)\n }\n\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\n // For flexible arrays, continue trying to match more subrules\n if (isFlexibleArray) {\n continueFlexibleArray = true\n }\n\n break\n }\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 * Check if a value is one of a set of allowed values\n */\nexport interface CheckOneOf {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n oneOf: any[]\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 | CheckOneOf\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 CheckOneOf,\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 if ('oneOf' in check) {\n return this.checkOneOf(data, criterion.path, check as CheckOneOf)\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 * Checks if a value is one of a set of allowed values.\n *\n * @param data - The data object\n * @param path - Path to the value\n * @param check - The oneOf check specification\n * @returns FilterCheckResult\n */\n private checkOneOf(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n data: any,\n path: (string | number)[],\n check: CheckOneOf\n ): FilterCheckResult {\n const accessResult = getValueFromPath(data, path)\n\n if (!accessResult.found) {\n return {\n status: false,\n checkType: 'checkOneOf',\n reason: {\n message: accessResult.error || 'Path not found',\n path,\n },\n }\n }\n\n const actual = accessResult.value\n const found = check.oneOf.some(allowed => this.deepEqual(actual, allowed))\n\n if (found) {\n return {\n status: true,\n checkType: 'checkOneOf',\n }\n }\n\n return {\n status: false,\n checkType: 'checkOneOf',\n reason: {\n message: `Value not in allowed set`,\n path,\n actual,\n allowedValues: check.oneOf,\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 // Track which standalone rules have been used (to ensure each rule matches only once)\n const usedStandaloneRules = new 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\n // Check if this standalone single match rule has already been used\n if (isSingleMatchRule(rule) && usedStandaloneRules.has(ruleIndex)) {\n // This rule was already matched - skip to next rule\n ruleIndex++\n continue\n }\n\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 // Mark this standalone rule as used\n usedStandaloneRules.add(ruleIndex)\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 // Track which standalone single match rules have been used in flexible arrays\n const usedFlexibleRules = new Map<number, Set<number>>()\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 const isFlexibleArray = Array.isArray(ruleOrRules)\n let found = false\n\n // For flexible arrays, keep trying to match until no more subrules can be matched\n let continueFlexibleArray = true\n while (continueFlexibleArray) {\n continueFlexibleArray = 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 (isFlexibleArray) {\n // Try each rule in the flexible array\n for (let subRuleIndex = 0; subRuleIndex < ruleOrRules.length; subRuleIndex++) {\n const rule = ruleOrRules[subRuleIndex]\n\n // Check if this subrule was already used\n if (isSingleMatchRule(rule)) {\n const usedSubRules = usedFlexibleRules.get(ruleIndex)\n if (usedSubRules && usedSubRules.has(subRuleIndex)) {\n // This subrule was already used, skip it\n continue\n }\n }\n\n const result = this.matchFile(file, rule)\n if (result.matched) {\n matchResult = result\n matchedRule = rule\n\n // Mark this subrule as used if it's a single match rule\n if (isSingleMatchRule(rule)) {\n if (!usedFlexibleRules.has(ruleIndex)) {\n usedFlexibleRules.set(ruleIndex, new Set())\n }\n usedFlexibleRules.get(ruleIndex)?.add(subRuleIndex)\n }\n\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\n // For flexible arrays, continue trying to match more subrules\n if (isFlexibleArray) {\n continueFlexibleArray = true\n }\n\n break\n }\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 CheckOneOf,\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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"}
@@ -115,6 +115,9 @@ class FilterEngine {
115
115
  return this.checkTimeRange(data, criterion.path, check);
116
116
  }
117
117
  }
118
+ if ("oneOf" in check) {
119
+ return this.checkOneOf(data, criterion.path, check);
120
+ }
118
121
  return {
119
122
  status: false,
120
123
  checkType: "unknown",
@@ -457,6 +460,45 @@ class FilterEngine {
457
460
  }
458
461
  };
459
462
  }
463
+ /**
464
+ * Checks if a value is one of a set of allowed values.
465
+ *
466
+ * @param data - The data object
467
+ * @param path - Path to the value
468
+ * @param check - The oneOf check specification
469
+ * @returns FilterCheckResult
470
+ */
471
+ checkOneOf(data, path, check) {
472
+ const accessResult = getValueFromPath(data, path);
473
+ if (!accessResult.found) {
474
+ return {
475
+ status: false,
476
+ checkType: "checkOneOf",
477
+ reason: {
478
+ message: accessResult.error || "Path not found",
479
+ path
480
+ }
481
+ };
482
+ }
483
+ const actual = accessResult.value;
484
+ const found = check.oneOf.some((allowed) => this.deepEqual(actual, allowed));
485
+ if (found) {
486
+ return {
487
+ status: true,
488
+ checkType: "checkOneOf"
489
+ };
490
+ }
491
+ return {
492
+ status: false,
493
+ checkType: "checkOneOf",
494
+ reason: {
495
+ message: `Value not in allowed set`,
496
+ path,
497
+ actual,
498
+ allowedValues: check.oneOf
499
+ }
500
+ };
501
+ }
460
502
  /**
461
503
  * Deep equality comparison.
462
504
  * Compares two values recursively for equality.
@@ -733,7 +775,7 @@ class Matcher {
733
775
  if (!usedFlexibleRules.has(ruleIndex)) {
734
776
  usedFlexibleRules.set(ruleIndex, /* @__PURE__ */ new Set());
735
777
  }
736
- usedFlexibleRules.get(ruleIndex).add(subRuleIndex);
778
+ usedFlexibleRules.get(ruleIndex)?.add(subRuleIndex);
737
779
  }
738
780
  break;
739
781
  }
@@ -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 // Track which standalone rules have been used (to ensure each rule matches only once)\n const usedStandaloneRules = new 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\n // Check if this standalone single match rule has already been used\n if (isSingleMatchRule(rule) && usedStandaloneRules.has(ruleIndex)) {\n // This rule was already matched - skip to next rule\n ruleIndex++\n continue\n }\n\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 // Mark this standalone rule as used\n usedStandaloneRules.add(ruleIndex)\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 // Track which standalone single match rules have been used in flexible arrays\n const usedFlexibleRules = new Map<number, Set<number>>()\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 const isFlexibleArray = Array.isArray(ruleOrRules)\n let found = false\n\n // For flexible arrays, keep trying to match until no more subrules can be matched\n let continueFlexibleArray = true\n while (continueFlexibleArray) {\n continueFlexibleArray = 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 (isFlexibleArray) {\n // Try each rule in the flexible array\n for (let subRuleIndex = 0; subRuleIndex < ruleOrRules.length; subRuleIndex++) {\n const rule = ruleOrRules[subRuleIndex]\n\n // Check if this subrule was already used\n if (isSingleMatchRule(rule)) {\n const usedSubRules = usedFlexibleRules.get(ruleIndex)\n if (usedSubRules && usedSubRules.has(subRuleIndex)) {\n // This subrule was already used, skip it\n continue\n }\n }\n\n const result = this.matchFile(file, rule)\n if (result.matched) {\n matchResult = result\n matchedRule = rule\n\n // Mark this subrule as used if it's a single match rule\n if (isSingleMatchRule(rule)) {\n if (!usedFlexibleRules.has(ruleIndex)) {\n usedFlexibleRules.set(ruleIndex, new Set())\n }\n usedFlexibleRules.get(ruleIndex)!.add(subRuleIndex)\n }\n\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\n // For flexible arrays, continue trying to match more subrules\n if (isFlexibleArray) {\n continueFlexibleArray = true\n }\n\n break\n }\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 * Check if a value is one of a set of allowed values\n */\nexport interface CheckOneOf {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n oneOf: any[]\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 | CheckOneOf\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 CheckOneOf,\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 if ('oneOf' in check) {\n return this.checkOneOf(data, criterion.path, check as CheckOneOf)\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 * Checks if a value is one of a set of allowed values.\n *\n * @param data - The data object\n * @param path - Path to the value\n * @param check - The oneOf check specification\n * @returns FilterCheckResult\n */\n private checkOneOf(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n data: any,\n path: (string | number)[],\n check: CheckOneOf\n ): FilterCheckResult {\n const accessResult = getValueFromPath(data, path)\n\n if (!accessResult.found) {\n return {\n status: false,\n checkType: 'checkOneOf',\n reason: {\n message: accessResult.error || 'Path not found',\n path,\n },\n }\n }\n\n const actual = accessResult.value\n const found = check.oneOf.some(allowed => this.deepEqual(actual, allowed))\n\n if (found) {\n return {\n status: true,\n checkType: 'checkOneOf',\n }\n }\n\n return {\n status: false,\n checkType: 'checkOneOf',\n reason: {\n message: `Value not in allowed set`,\n path,\n actual,\n allowedValues: check.oneOf,\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 // Track which standalone rules have been used (to ensure each rule matches only once)\n const usedStandaloneRules = new 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\n // Check if this standalone single match rule has already been used\n if (isSingleMatchRule(rule) && usedStandaloneRules.has(ruleIndex)) {\n // This rule was already matched - skip to next rule\n ruleIndex++\n continue\n }\n\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 // Mark this standalone rule as used\n usedStandaloneRules.add(ruleIndex)\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 // Track which standalone single match rules have been used in flexible arrays\n const usedFlexibleRules = new Map<number, Set<number>>()\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 const isFlexibleArray = Array.isArray(ruleOrRules)\n let found = false\n\n // For flexible arrays, keep trying to match until no more subrules can be matched\n let continueFlexibleArray = true\n while (continueFlexibleArray) {\n continueFlexibleArray = 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 (isFlexibleArray) {\n // Try each rule in the flexible array\n for (let subRuleIndex = 0; subRuleIndex < ruleOrRules.length; subRuleIndex++) {\n const rule = ruleOrRules[subRuleIndex]\n\n // Check if this subrule was already used\n if (isSingleMatchRule(rule)) {\n const usedSubRules = usedFlexibleRules.get(ruleIndex)\n if (usedSubRules && usedSubRules.has(subRuleIndex)) {\n // This subrule was already used, skip it\n continue\n }\n }\n\n const result = this.matchFile(file, rule)\n if (result.matched) {\n matchResult = result\n matchedRule = rule\n\n // Mark this subrule as used if it's a single match rule\n if (isSingleMatchRule(rule)) {\n if (!usedFlexibleRules.has(ruleIndex)) {\n usedFlexibleRules.set(ruleIndex, new Set())\n }\n usedFlexibleRules.get(ruleIndex)?.add(subRuleIndex)\n }\n\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\n // For flexible arrays, continue trying to match more subrules\n if (isFlexibleArray) {\n continueFlexibleArray = true\n }\n\n break\n }\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 CheckOneOf,\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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"}
@@ -53,6 +53,12 @@ export interface CheckNumericRange {
53
53
  min: number;
54
54
  max: number;
55
55
  }
56
+ /**
57
+ * Check if a value is one of a set of allowed values
58
+ */
59
+ export interface CheckOneOf {
60
+ oneOf: any[];
61
+ }
56
62
  /**
57
63
  * A single filter criterion that checks one aspect of the data
58
64
  */
@@ -64,7 +70,7 @@ export interface FilterCriterion {
64
70
  /**
65
71
  * The check to perform on the value
66
72
  */
67
- check: CheckValue | CheckExists | CheckArrayElement | CheckArraySize | CheckTimeRange | CheckNumericRange;
73
+ check: CheckValue | CheckExists | CheckArrayElement | CheckArraySize | CheckTimeRange | CheckNumericRange | CheckOneOf;
68
74
  }
69
75
  /**
70
76
  * A single match rule that defines how to match a file
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/core/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/core/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;;;;GAMG;AACH,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,MAAM,CAAA;AAEzC;;GAEG;AACH,MAAM,MAAM,QAAQ,GAChB,cAAc,GACd,SAAS,GACT,SAAS,GACT,OAAO,GACP,MAAM,GACN,OAAO,GACP,QAAQ,GACR,OAAO,CAAA;AAEX;;GAEG;AACH,MAAM,WAAW,UAAU;IAEzB,KAAK,EAAE,GAAG,CAAA;CACX;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,OAAO,CAAA;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,OAAO,CAAA;IAEnB,IAAI,EAAE,GAAG,CAAA;CACV;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,OAAO,GAAG,UAAU,GAAG,aAAa,CAAA;IAC1C,IAAI,EAAE,MAAM,CAAA;CACb;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;CACZ;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;CACZ;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IAEzB,KAAK,EAAE,GAAG,EAAE,CAAA;CACb;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B;;OAEG;IACH,IAAI,EAAE,WAAW,EAAE,CAAA;IAEnB;;OAEG;IACH,KAAK,EACD,UAAU,GACV,WAAW,GACX,iBAAiB,GACjB,cAAc,GACd,cAAc,GACd,iBAAiB,GACjB,UAAU,CAAA;CACf;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B;;OAEG;IACH,KAAK,EAAE,eAAe,EAAE,CAAA;IAExB;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAA;IAEhB;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;IAElB;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAC/B;AAED;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC;;OAEG;IACH,QAAQ,EAAE,eAAe,EAAE,CAAA;IAE3B;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAA;IAEhB;;OAEG;IACH,QAAQ,EAAE,IAAI,CAAA;IAEd;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAC/B;AAED;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG,eAAe,GAAG,iBAAiB,CAAA;AAE3D;;GAEG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI,IAAI,iBAAiB,CAEzE;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI,IAAI,eAAe,CAE1E;AAED;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B;;;OAGG;IACH,WAAW,EAAE,eAAe,EAAE,CAAA;IAE9B;;OAEG;IACH,KAAK,EAAE,SAAS,EAAE,CAAA;IAElB;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAC/B;AAED;;GAEG;AACH,MAAM,WAAW,QAAQ;IACvB;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAA;IAEhB;;OAEG;IAEH,IAAI,EAAE,GAAG,CAAA;IAET;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACnC;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC;;OAEG;IACH,MAAM,EAAE,OAAO,CAAA;IAEf;;OAEG;IACH,SAAS,EAAE,MAAM,CAAA;IAEjB;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAC1C;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B;;OAEG;IACH,OAAO,EAAE,OAAO,CAAA;IAEhB;;OAEG;IACH,MAAM,EAAE,iBAAiB,EAAE,CAAA;IAE3B;;OAEG;IACH,IAAI,EAAE,SAAS,CAAA;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAA;IAEhB;;OAEG;IACH,IAAI,EAAE,QAAQ,CAAA;IAEd;;OAEG;IACH,WAAW,EAAE,WAAW,CAAA;IAExB;;OAEG;IACH,QAAQ,EAAE,OAAO,CAAA;IAEjB;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAC/B;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC;;OAEG;IACH,IAAI,EAAE,QAAQ,CAAA;IAEd;;OAEG;IACH,WAAW,EAAE,WAAW,CAAA;IAExB;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAC/B;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B;;OAEG;IACH,IAAI,EAAE,QAAQ,CAAA;IAEd;;OAEG;IACH,cAAc,EAAE,WAAW,EAAE,CAAA;CAC9B;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B;;OAEG;IACH,IAAI,EAAE,QAAQ,CAAA;IAEd;;OAEG;IACH,YAAY,EAAE,iBAAiB,EAAE,CAAA;CAClC;AAED;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAA;IAEhB;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAA;IAEhB;;OAEG;IACH,OAAO,CAAC,EAAE;QACR,SAAS,EAAE,MAAM,CAAA;QACjB,UAAU,EAAE,MAAM,CAAA;KACnB,CAAA;IAED;;;OAGG;IACH,aAAa,EAAE,WAAW,EAAE,CAAA;CAC7B;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B;;OAEG;IACH,MAAM,EAAE,UAAU,EAAE,CAAA;IAEpB;;OAEG;IACH,eAAe,EAAE,kBAAkB,EAAE,CAAA;IAErC;;;;;;OAMG;IACH,aAAa,EAAE,YAAY,EAAE,CAAA;IAE7B;;;OAGG;IACH,QAAQ,EAAE,YAAY,EAAE,CAAA;IAExB;;OAEG;IACH,WAAW,EAAE,eAAe,EAAE,CAAA;IAE9B;;OAEG;IACH,KAAK,EAAE;QACL,UAAU,EAAE,MAAM,CAAA;QAClB,WAAW,EAAE,MAAM,CAAA;QACnB,oBAAoB,EAAE,MAAM,CAAA;QAC5B,aAAa,EAAE,MAAM,CAAA;QACrB,aAAa,EAAE,MAAM,CAAA;QACrB,gBAAgB,EAAE,MAAM,CAAA;QACxB,UAAU,EAAE,MAAM,CAAA;QAClB,cAAc,EAAE,MAAM,CAAA;QACtB,aAAa,EAAE,MAAM,CAAA;KACtB,CAAA;CACF;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B;;OAEG;IACH,KAAK,EAAE,QAAQ,EAAE,CAAA;IAEjB;;;;OAIG;IACH,KAAK,CAAC,EAAE,CAAC,SAAS,GAAG,SAAS,EAAE,CAAC,EAAE,CAAA;IAEnC;;;;OAIG;IACH,MAAM,CAAC,EAAE,WAAW,EAAE,CAAA;IAEtB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,KAAK,MAAM,CAAA;IAE7C;;;;OAIG;IACH,SAAS,CAAC,EAAE,eAAe,EAAE,CAAA;IAE7B;;OAEG;IACH,OAAO,CAAC,EAAE;QACR,eAAe,CAAC,EAAE,MAAM,CAAA;QACxB,aAAa,CAAC,EAAE,MAAM,CAAA;QACtB,QAAQ,CAAC,EAAE,WAAW,EAAE,CAAA;KACzB,CAAA;IAED;;;;;;;;OAQG;IACH,IAAI,CAAC,EAAE,QAAQ,GAAG,iBAAiB,GAAG,UAAU,CAAA;CACjD"}
@@ -74,6 +74,15 @@ export declare class FilterEngine {
74
74
  * @returns FilterCheckResult
75
75
  */
76
76
  private checkNumericRange;
77
+ /**
78
+ * Checks if a value is one of a set of allowed values.
79
+ *
80
+ * @param data - The data object
81
+ * @param path - Path to the value
82
+ * @param check - The oneOf check specification
83
+ * @returns FilterCheckResult
84
+ */
85
+ private checkOneOf;
77
86
  /**
78
87
  * Deep equality comparison.
79
88
  * Compares two values recursively for equality.
@@ -1 +1 @@
1
- {"version":3,"file":"FilterEngine.d.ts","sourceRoot":"","sources":["../../../src/engine/FilterEngine.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,eAAe,EACf,iBAAiB,EAOlB,MAAM,eAAe,CAAA;AAGtB;;;GAGG;AACH,qBAAa,YAAY;IACvB;;OAEG;IACH,OAAO,CAAC,OAAO,CAAC,CAGf;gBAEW,OAAO,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,MAAM,CAAC;QAAC,aAAa,CAAC,EAAE,MAAM,CAAA;KAAE;IAI1E;;;;;;OAMG;IAEH,iBAAiB,CAAC,IAAI,EAAE,GAAG,EAAE,SAAS,EAAE,eAAe,GAAG,iBAAiB;IAoC3E;;;;;;;OAOG;IACH,OAAO,CAAC,UAAU;IAyClB;;;;;;;OAOG;IACH,OAAO,CAAC,WAAW;IA4BnB;;;;;;;OAOG;IACH,OAAO,CAAC,iBAAiB;IAwDzB;;;;;;;OAOG;IACH,OAAO,CAAC,cAAc;IAyEtB;;;;;;;OAOG;IACH,OAAO,CAAC,cAAc;IAqHtB;;;;;;;OAOG;IACH,OAAO,CAAC,iBAAiB;IAqDzB;;;OAGG;IAEH,OAAO,CAAC,SAAS;CAmClB"}
1
+ {"version":3,"file":"FilterEngine.d.ts","sourceRoot":"","sources":["../../../src/engine/FilterEngine.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,eAAe,EACf,iBAAiB,EAQlB,MAAM,eAAe,CAAA;AAGtB;;;GAGG;AACH,qBAAa,YAAY;IACvB;;OAEG;IACH,OAAO,CAAC,OAAO,CAAC,CAGf;gBAEW,OAAO,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,MAAM,CAAC;QAAC,aAAa,CAAC,EAAE,MAAM,CAAA;KAAE;IAI1E;;;;;;OAMG;IAEH,iBAAiB,CAAC,IAAI,EAAE,GAAG,EAAE,SAAS,EAAE,eAAe,GAAG,iBAAiB;IAwC3E;;;;;;;OAOG;IACH,OAAO,CAAC,UAAU;IAyClB;;;;;;;OAOG;IACH,OAAO,CAAC,WAAW;IA4BnB;;;;;;;OAOG;IACH,OAAO,CAAC,iBAAiB;IAwDzB;;;;;;;OAOG;IACH,OAAO,CAAC,cAAc;IAyEtB;;;;;;;OAOG;IACH,OAAO,CAAC,cAAc;IAqHtB;;;;;;;OAOG;IACH,OAAO,CAAC,iBAAiB;IAqDzB;;;;;;;OAOG;IACH,OAAO,CAAC,UAAU;IAyClB;;;OAGG;IAEH,OAAO,CAAC,SAAS;CAmClB"}
@@ -4,7 +4,7 @@ import { FilterRequest, FilterResult } from './core/types';
4
4
  *
5
5
  * Advanced data filtering engine for JSON file matching in E2E testing
6
6
  */
7
- export type { PathElement, TimeUnit, CheckValue, CheckExists, CheckArrayElement, CheckArraySize, CheckTimeRange, CheckNumericRange, FilterCriterion, SingleMatchRule, WildcardMatchRule, MatchRule, FilterGroup, JsonFile, FilterCheckResult, MatchResult, MappedFile, WildcardMappedFile, UnmappedFile, OptionalFile, PreFilteredFile, FilterResult, FilterRequest, } from './core/types';
7
+ export type { PathElement, TimeUnit, CheckValue, CheckExists, CheckArrayElement, CheckArraySize, CheckTimeRange, CheckNumericRange, CheckOneOf, FilterCriterion, SingleMatchRule, WildcardMatchRule, MatchRule, FilterGroup, JsonFile, FilterCheckResult, MatchResult, MappedFile, WildcardMappedFile, UnmappedFile, OptionalFile, PreFilteredFile, FilterResult, FilterRequest, } from './core/types';
8
8
  export { isWildcardRule, isSingleMatchRule } from './core/types';
9
9
  export { FilterEngine } from './engine';
10
10
  export { Matcher } from './matcher';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,YAAY,EACV,WAAW,EACX,QAAQ,EACR,UAAU,EACV,WAAW,EACX,iBAAiB,EACjB,cAAc,EACd,cAAc,EACd,iBAAiB,EACjB,eAAe,EACf,eAAe,EACf,iBAAiB,EACjB,SAAS,EACT,WAAW,EACX,QAAQ,EACR,iBAAiB,EACjB,WAAW,EACX,UAAU,EACV,kBAAkB,EAClB,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,YAAY,EACZ,aAAa,GACd,MAAM,cAAc,CAAA;AAErB,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAA;AAGhE,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AAGvC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAGnC,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AAClE,YAAY,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAI3C,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAE/D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,aAAa,GAAG,YAAY,CAmChE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,YAAY,EACV,WAAW,EACX,QAAQ,EACR,UAAU,EACV,WAAW,EACX,iBAAiB,EACjB,cAAc,EACd,cAAc,EACd,iBAAiB,EACjB,UAAU,EACV,eAAe,EACf,eAAe,EACf,iBAAiB,EACjB,SAAS,EACT,WAAW,EACX,QAAQ,EACR,iBAAiB,EACjB,WAAW,EACX,UAAU,EACV,kBAAkB,EAClB,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,YAAY,EACZ,aAAa,GACd,MAAM,cAAc,CAAA;AAErB,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAA;AAGhE,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AAGvC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAGnC,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AAClE,YAAY,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAI3C,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAE/D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,aAAa,GAAG,YAAY,CAmChE"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aikotools/datafilter",
3
- "version": "1.1.4",
3
+ "version": "1.2.0",
4
4
  "description": "Advanced data filtering engine for JSON file matching in E2E testing",
5
5
  "type": "module",
6
6
  "main": "dist/aikotools-datafilter.cjs",
@@ -21,8 +21,13 @@
21
21
  "files": [
22
22
  "dist/**/*"
23
23
  ],
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "git+https://github.com/aikotools/datafilter.git"
27
+ },
24
28
  "publishConfig": {
25
- "access": "public"
29
+ "access": "public",
30
+ "provenance": true
26
31
  },
27
32
  "scripts": {
28
33
  "dev": "vite",
@@ -51,15 +56,15 @@
51
56
  },
52
57
  "devDependencies": {
53
58
  "@types/luxon": "^3.7.1",
54
- "@types/node": "^24.9.1",
55
- "@typescript-eslint/eslint-plugin": "^8.46.2",
56
- "@typescript-eslint/parser": "^8.46.2",
57
- "@vitest/coverage-v8": "^3.2.4",
59
+ "@types/node": "^25.2.2",
60
+ "@typescript-eslint/eslint-plugin": "^8.55.0",
61
+ "@typescript-eslint/parser": "^8.55.0",
62
+ "@vitest/coverage-v8": "^4.0.18",
58
63
  "eslint": "^9.38.0",
59
- "prettier": "^3.6.2",
64
+ "prettier": "^3.8.1",
60
65
  "typescript": "^5.9.3",
61
- "vite": "^7.1.11",
66
+ "vite": "^7.3.1",
62
67
  "vite-plugin-dts": "^4.5.4",
63
- "vitest": "^3.2.4"
68
+ "vitest": "^4.0.18"
64
69
  }
65
70
  }