@aikotools/datafilter 1.0.4 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,83 +1,90 @@
1
- import { DateTime as E } from "luxon";
2
- function R(i) {
3
- return "matchAny" in i;
1
+ import { DateTime } from "luxon";
2
+ function isWildcardRule(rule) {
3
+ return "matchAny" in rule;
4
4
  }
5
- function w(i) {
6
- return "match" in i && "expected" in i;
5
+ function isSingleMatchRule(rule) {
6
+ return "match" in rule && "expected" in rule;
7
7
  }
8
- function k(i, s) {
9
- const t = [];
10
- if (s.length === 0)
8
+ function getValueFromPath(object, path) {
9
+ const validPath = [];
10
+ if (path.length === 0) {
11
11
  return {
12
- value: i,
13
- found: !0,
12
+ value: object,
13
+ found: true,
14
14
  validPath: []
15
15
  };
16
- let e = i;
17
- for (let n = 0; n < s.length; n++) {
18
- const r = s[n];
19
- if (e == null)
16
+ }
17
+ let current = object;
18
+ for (let i = 0; i < path.length; i++) {
19
+ const segment = path[i];
20
+ if (current === null || current === void 0) {
20
21
  return {
21
22
  value: void 0,
22
- found: !1,
23
- error: `Cannot read property '${r}' of ${e}`,
24
- validPath: t
23
+ found: false,
24
+ error: `Cannot read property '${segment}' of ${current}`,
25
+ validPath
25
26
  };
26
- if (Array.isArray(e)) {
27
- const a = typeof r == "number" ? r : parseInt(String(r), 10);
28
- if (isNaN(a))
27
+ }
28
+ if (Array.isArray(current)) {
29
+ const index = typeof segment === "number" ? segment : parseInt(String(segment), 10);
30
+ if (isNaN(index)) {
29
31
  return {
30
32
  value: void 0,
31
- found: !1,
32
- error: `Array index must be a number, got '${r}'`,
33
- validPath: t
33
+ found: false,
34
+ error: `Array index must be a number, got '${segment}'`,
35
+ validPath
34
36
  };
35
- if (a < 0 || a >= e.length)
37
+ }
38
+ if (index < 0 || index >= current.length) {
36
39
  return {
37
40
  value: void 0,
38
- found: !1,
39
- error: `Array index ${a} out of bounds (length: ${e.length})`,
40
- validPath: t
41
+ found: false,
42
+ error: `Array index ${index} out of bounds (length: ${current.length})`,
43
+ validPath
41
44
  };
42
- t.push(a), e = e[a];
45
+ }
46
+ validPath.push(index);
47
+ current = current[index];
43
48
  continue;
44
49
  }
45
- if (typeof e == "object") {
46
- const a = String(r);
47
- if (!(a in e))
50
+ if (typeof current === "object") {
51
+ const key = String(segment);
52
+ if (!(key in current)) {
48
53
  return {
49
54
  value: void 0,
50
- found: !1,
51
- error: `Property '${a}' does not exist`,
52
- validPath: t
55
+ found: false,
56
+ error: `Property '${key}' does not exist`,
57
+ validPath
53
58
  };
54
- t.push(a), e = e[a];
59
+ }
60
+ validPath.push(key);
61
+ current = current[key];
55
62
  continue;
56
63
  }
57
64
  return {
58
65
  value: void 0,
59
- found: !1,
60
- error: `Cannot access property '${r}' of primitive type ${typeof e}`,
61
- validPath: t
66
+ found: false,
67
+ error: `Cannot access property '${segment}' of primitive type ${typeof current}`,
68
+ validPath
62
69
  };
63
70
  }
64
71
  return {
65
- value: e,
66
- found: !0,
67
- validPath: t
72
+ value: current,
73
+ found: true,
74
+ validPath
68
75
  };
69
76
  }
70
- function I(i, s) {
71
- const t = k(i, s);
72
- return t.found && t.value !== void 0;
77
+ function pathExists(object, path) {
78
+ const result = getValueFromPath(object, path);
79
+ return result.found && result.value !== void 0;
73
80
  }
74
- function N(i, s, t) {
75
- const e = k(i, s);
76
- return e.found && e.value !== void 0 ? e.value : t;
81
+ function getValueOr(object, path, defaultValue) {
82
+ const result = getValueFromPath(object, path);
83
+ return result.found && result.value !== void 0 ? result.value : defaultValue;
77
84
  }
78
- class S {
79
- constructor(s) {
80
- this.context = s;
85
+ class FilterEngine {
86
+ constructor(context) {
87
+ this.context = context;
81
88
  }
82
89
  /**
83
90
  * Evaluates a single filter criterion against a data object.
@@ -87,12 +94,31 @@ class S {
87
94
  * @returns FilterCheckResult indicating success or failure
88
95
  */
89
96
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
90
- evaluateCriterion(s, t) {
91
- const e = t.check;
92
- return "value" in e ? this.checkValue(s, t.path, e) : "exists" in e ? this.checkExists(s, t.path, e) : "itemExists" in e && "item" in e ? this.checkArrayElement(s, t.path, e) : "type" in e && "size" in e ? this.checkArraySize(s, t.path, e) : "min" in e && "max" in e ? typeof e.min == "number" && typeof e.max == "number" ? this.checkNumericRange(s, t.path, e) : this.checkTimeRange(s, t.path, e) : {
93
- status: !1,
97
+ evaluateCriterion(data, criterion) {
98
+ const check = criterion.check;
99
+ if ("value" in check) {
100
+ return this.checkValue(data, criterion.path, check);
101
+ }
102
+ if ("exists" in check) {
103
+ return this.checkExists(data, criterion.path, check);
104
+ }
105
+ if ("itemExists" in check && "item" in check) {
106
+ return this.checkArrayElement(data, criterion.path, check);
107
+ }
108
+ if ("type" in check && "size" in check) {
109
+ return this.checkArraySize(data, criterion.path, check);
110
+ }
111
+ if ("min" in check && "max" in check) {
112
+ if (typeof check.min === "number" && typeof check.max === "number") {
113
+ return this.checkNumericRange(data, criterion.path, check);
114
+ } else {
115
+ return this.checkTimeRange(data, criterion.path, check);
116
+ }
117
+ }
118
+ return {
119
+ status: false,
94
120
  checkType: "unknown",
95
- reason: `Unknown check type: ${JSON.stringify(e)}`
121
+ reason: `Unknown check type: ${JSON.stringify(check)}`
96
122
  };
97
123
  }
98
124
  /**
@@ -103,29 +129,34 @@ class S {
103
129
  * @param check - The value check specification
104
130
  * @returns FilterCheckResult
105
131
  */
106
- checkValue(s, t, e) {
107
- const n = k(s, t);
108
- if (!n.found)
132
+ checkValue(data, path, check) {
133
+ const accessResult = getValueFromPath(data, path);
134
+ if (!accessResult.found) {
109
135
  return {
110
- status: !1,
136
+ status: false,
111
137
  checkType: "checkValue",
112
138
  reason: {
113
- message: n.error || "Path not found",
114
- path: t
139
+ message: accessResult.error || "Path not found",
140
+ path
115
141
  }
116
142
  };
117
- const r = n.value, a = e.value;
118
- return this.deepEqual(r, a) ? {
119
- status: !0,
120
- checkType: "checkValue"
121
- } : {
122
- status: !1,
143
+ }
144
+ const actual = accessResult.value;
145
+ const expected = check.value;
146
+ if (this.deepEqual(actual, expected)) {
147
+ return {
148
+ status: true,
149
+ checkType: "checkValue"
150
+ };
151
+ }
152
+ return {
153
+ status: false,
123
154
  checkType: "checkValue",
124
155
  reason: {
125
156
  message: "Value mismatch",
126
- path: t,
127
- expected: a,
128
- actual: r
157
+ path,
158
+ expected,
159
+ actual
129
160
  }
130
161
  };
131
162
  }
@@ -137,17 +168,21 @@ class S {
137
168
  * @param check - The exists check specification
138
169
  * @returns FilterCheckResult
139
170
  */
140
- checkExists(s, t, e) {
141
- const n = k(s, t), r = n.found && n.value !== void 0;
142
- return e.exists === r ? {
143
- status: !0,
144
- checkType: "checkExists"
145
- } : {
146
- status: !1,
171
+ checkExists(data, path, check) {
172
+ const accessResult = getValueFromPath(data, path);
173
+ const exists = accessResult.found && accessResult.value !== void 0;
174
+ if (check.exists === exists) {
175
+ return {
176
+ status: true,
177
+ checkType: "checkExists"
178
+ };
179
+ }
180
+ return {
181
+ status: false,
147
182
  checkType: "checkExists",
148
183
  reason: {
149
- message: e.exists ? `Path should exist but doesn't: ${n.error}` : "Path should not exist but does",
150
- path: t
184
+ message: check.exists ? `Path should exist but doesn't: ${accessResult.error}` : "Path should not exist but does",
185
+ path
151
186
  }
152
187
  };
153
188
  }
@@ -159,39 +194,44 @@ class S {
159
194
  * @param check - The array element check specification
160
195
  * @returns FilterCheckResult
161
196
  */
162
- checkArrayElement(s, t, e) {
163
- const n = k(s, t);
164
- if (!n.found)
197
+ checkArrayElement(data, path, check) {
198
+ const accessResult = getValueFromPath(data, path);
199
+ if (!accessResult.found) {
165
200
  return {
166
- status: !1,
201
+ status: false,
167
202
  checkType: "checkArrayElement",
168
203
  reason: {
169
- message: n.error || "Path not found",
170
- path: t
204
+ message: accessResult.error || "Path not found",
205
+ path
171
206
  }
172
207
  };
173
- const r = n.value;
174
- if (!Array.isArray(r))
208
+ }
209
+ const array = accessResult.value;
210
+ if (!Array.isArray(array)) {
175
211
  return {
176
- status: !1,
212
+ status: false,
177
213
  checkType: "checkArrayElement",
178
214
  reason: {
179
215
  message: "Value is not an array",
180
- path: t,
181
- actualType: typeof r
216
+ path,
217
+ actualType: typeof array
182
218
  }
183
219
  };
184
- const a = r.some((u) => this.deepEqual(u, e.item));
185
- return e.itemExists === a ? {
186
- status: !0,
187
- checkType: "checkArrayElement"
188
- } : {
189
- status: !1,
220
+ }
221
+ const found = array.some((elem) => this.deepEqual(elem, check.item));
222
+ if (check.itemExists === found) {
223
+ return {
224
+ status: true,
225
+ checkType: "checkArrayElement"
226
+ };
227
+ }
228
+ return {
229
+ status: false,
190
230
  checkType: "checkArrayElement",
191
231
  reason: {
192
- message: e.itemExists ? "Item should exist in array but doesn't" : "Item should not exist in array but does",
193
- path: t,
194
- item: e.item
232
+ message: check.itemExists ? "Item should exist in array but doesn't" : "Item should not exist in array but does",
233
+ path,
234
+ item: check.item
195
235
  }
196
236
  };
197
237
  }
@@ -203,52 +243,62 @@ class S {
203
243
  * @param check - The array size check specification
204
244
  * @returns FilterCheckResult
205
245
  */
206
- checkArraySize(s, t, e) {
207
- const n = k(s, t);
208
- if (!n.found)
246
+ checkArraySize(data, path, check) {
247
+ const accessResult = getValueFromPath(data, path);
248
+ if (!accessResult.found) {
209
249
  return {
210
- status: !1,
250
+ status: false,
211
251
  checkType: "checkArraySize",
212
252
  reason: {
213
- message: n.error || "Path not found",
214
- path: t
253
+ message: accessResult.error || "Path not found",
254
+ path
215
255
  }
216
256
  };
217
- const r = n.value;
218
- if (!Array.isArray(r))
257
+ }
258
+ const array = accessResult.value;
259
+ if (!Array.isArray(array)) {
219
260
  return {
220
- status: !1,
261
+ status: false,
221
262
  checkType: "checkArraySize",
222
263
  reason: {
223
264
  message: "Value is not an array",
224
- path: t,
225
- actualType: typeof r
265
+ path,
266
+ actualType: typeof array
226
267
  }
227
268
  };
228
- const a = r.length, u = e.size;
229
- let l = !1, h = "";
230
- switch (e.type) {
269
+ }
270
+ const actualSize = array.length;
271
+ const expectedSize = check.size;
272
+ let passes = false;
273
+ let message = "";
274
+ switch (check.type) {
231
275
  case "equal":
232
- l = a === u, h = `Array length should be ${u} but is ${a}`;
276
+ passes = actualSize === expectedSize;
277
+ message = `Array length should be ${expectedSize} but is ${actualSize}`;
233
278
  break;
234
279
  case "lessThan":
235
- l = a < u, h = `Array length should be less than ${u} but is ${a}`;
280
+ passes = actualSize < expectedSize;
281
+ message = `Array length should be less than ${expectedSize} but is ${actualSize}`;
236
282
  break;
237
283
  case "greaterThan":
238
- l = a > u, h = `Array length should be greater than ${u} but is ${a}`;
284
+ passes = actualSize > expectedSize;
285
+ message = `Array length should be greater than ${expectedSize} but is ${actualSize}`;
239
286
  break;
240
287
  }
241
- return l ? {
242
- status: !0,
243
- checkType: "checkArraySize"
244
- } : {
245
- status: !1,
288
+ if (passes) {
289
+ return {
290
+ status: true,
291
+ checkType: "checkArraySize"
292
+ };
293
+ }
294
+ return {
295
+ status: false,
246
296
  checkType: "checkArraySize",
247
297
  reason: {
248
- message: h,
249
- path: t,
250
- expected: u,
251
- actual: a
298
+ message,
299
+ path,
300
+ expected: expectedSize,
301
+ actual: actualSize
252
302
  }
253
303
  };
254
304
  }
@@ -260,81 +310,100 @@ class S {
260
310
  * @param check - The time range check specification
261
311
  * @returns FilterCheckResult
262
312
  */
263
- checkTimeRange(s, t, e) {
264
- const n = k(s, t);
265
- if (!n.found)
313
+ checkTimeRange(data, path, check) {
314
+ const accessResult = getValueFromPath(data, path);
315
+ if (!accessResult.found) {
266
316
  return {
267
- status: !1,
317
+ status: false,
268
318
  checkType: "checkTimeRange",
269
319
  reason: {
270
- message: n.error || "Path not found",
271
- path: t
320
+ message: accessResult.error || "Path not found",
321
+ path
272
322
  }
273
323
  };
274
- const r = n.value;
275
- if (typeof r == "number") {
276
- const a = parseInt(e.min), u = parseInt(e.max);
277
- return isNaN(a) || isNaN(u) ? {
278
- status: !1,
279
- checkType: "checkTimeRange",
280
- reason: {
281
- message: "Min or max is not a valid number",
282
- min: e.min,
283
- max: e.max
284
- }
285
- } : r >= a && r <= u ? {
286
- status: !0,
287
- checkType: "checkTimeRange"
288
- } : {
289
- status: !1,
324
+ }
325
+ const value = accessResult.value;
326
+ if (typeof value === "number") {
327
+ const min = parseInt(check.min);
328
+ const max = parseInt(check.max);
329
+ if (isNaN(min) || isNaN(max)) {
330
+ return {
331
+ status: false,
332
+ checkType: "checkTimeRange",
333
+ reason: {
334
+ message: "Min or max is not a valid number",
335
+ min: check.min,
336
+ max: check.max
337
+ }
338
+ };
339
+ }
340
+ if (value >= min && value <= max) {
341
+ return {
342
+ status: true,
343
+ checkType: "checkTimeRange"
344
+ };
345
+ }
346
+ return {
347
+ status: false,
290
348
  checkType: "checkTimeRange",
291
349
  reason: {
292
- message: `Timestamp ${r} is outside range [${a}, ${u}]`,
293
- path: t,
294
- actual: r,
295
- min: a,
296
- max: u
350
+ message: `Timestamp ${value} is outside range [${min}, ${max}]`,
351
+ path,
352
+ actual: value,
353
+ min,
354
+ max
297
355
  }
298
356
  };
299
357
  }
300
- if (typeof r == "string") {
301
- const a = E.fromISO(r), u = E.fromISO(e.min), l = E.fromISO(e.max);
302
- return a.isValid ? !u.isValid || !l.isValid ? {
303
- status: !1,
304
- checkType: "checkTimeRange",
305
- reason: {
306
- message: "Invalid min or max time",
307
- min: e.min,
308
- max: e.max
309
- }
310
- } : a >= u && a <= l ? {
311
- status: !0,
312
- checkType: "checkTimeRange"
313
- } : {
314
- status: !1,
315
- checkType: "checkTimeRange",
316
- reason: {
317
- message: `Timestamp ${r} is outside range [${e.min}, ${e.max}]`,
318
- path: t,
319
- actual: r,
320
- min: e.min,
321
- max: e.max
322
- }
323
- } : {
324
- status: !1,
358
+ if (typeof value === "string") {
359
+ const timestamp = DateTime.fromISO(value);
360
+ const minTime = DateTime.fromISO(check.min);
361
+ const maxTime = DateTime.fromISO(check.max);
362
+ if (!timestamp.isValid) {
363
+ return {
364
+ status: false,
365
+ checkType: "checkTimeRange",
366
+ reason: {
367
+ message: `Invalid timestamp: ${value}`,
368
+ path
369
+ }
370
+ };
371
+ }
372
+ if (!minTime.isValid || !maxTime.isValid) {
373
+ return {
374
+ status: false,
375
+ checkType: "checkTimeRange",
376
+ reason: {
377
+ message: "Invalid min or max time",
378
+ min: check.min,
379
+ max: check.max
380
+ }
381
+ };
382
+ }
383
+ if (timestamp >= minTime && timestamp <= maxTime) {
384
+ return {
385
+ status: true,
386
+ checkType: "checkTimeRange"
387
+ };
388
+ }
389
+ return {
390
+ status: false,
325
391
  checkType: "checkTimeRange",
326
392
  reason: {
327
- message: `Invalid timestamp: ${r}`,
328
- path: t
393
+ message: `Timestamp ${value} is outside range [${check.min}, ${check.max}]`,
394
+ path,
395
+ actual: value,
396
+ min: check.min,
397
+ max: check.max
329
398
  }
330
399
  };
331
400
  }
332
401
  return {
333
- status: !1,
402
+ status: false,
334
403
  checkType: "checkTimeRange",
335
404
  reason: {
336
- message: `Timestamp must be a string or number, got ${typeof r}`,
337
- path: t
405
+ message: `Timestamp must be a string or number, got ${typeof value}`,
406
+ path
338
407
  }
339
408
  };
340
409
  }
@@ -346,38 +415,45 @@ class S {
346
415
  * @param check - The numeric range check specification
347
416
  * @returns FilterCheckResult
348
417
  */
349
- checkNumericRange(s, t, e) {
350
- const n = k(s, t);
351
- if (!n.found)
418
+ checkNumericRange(data, path, check) {
419
+ const accessResult = getValueFromPath(data, path);
420
+ if (!accessResult.found) {
352
421
  return {
353
- status: !1,
422
+ status: false,
354
423
  checkType: "checkNumericRange",
355
424
  reason: {
356
- message: n.error || "Path not found",
357
- path: t
425
+ message: accessResult.error || "Path not found",
426
+ path
358
427
  }
359
428
  };
360
- const r = n.value;
361
- return typeof r != "number" ? {
362
- status: !1,
363
- checkType: "checkNumericRange",
364
- reason: {
365
- message: `Value must be a number, got ${typeof r}`,
366
- path: t,
367
- actual: r
368
- }
369
- } : r >= e.min && r <= e.max ? {
370
- status: !0,
371
- checkType: "checkNumericRange"
372
- } : {
373
- status: !1,
429
+ }
430
+ const value = accessResult.value;
431
+ if (typeof value !== "number") {
432
+ return {
433
+ status: false,
434
+ checkType: "checkNumericRange",
435
+ reason: {
436
+ message: `Value must be a number, got ${typeof value}`,
437
+ path,
438
+ actual: value
439
+ }
440
+ };
441
+ }
442
+ if (value >= check.min && value <= check.max) {
443
+ return {
444
+ status: true,
445
+ checkType: "checkNumericRange"
446
+ };
447
+ }
448
+ return {
449
+ status: false,
374
450
  checkType: "checkNumericRange",
375
451
  reason: {
376
- message: `Value ${r} is outside range [${e.min}, ${e.max}]`,
377
- path: t,
378
- actual: r,
379
- min: e.min,
380
- max: e.max
452
+ message: `Value ${value} is outside range [${check.min}, ${check.max}]`,
453
+ path,
454
+ actual: value,
455
+ min: check.min,
456
+ max: check.max
381
457
  }
382
458
  };
383
459
  }
@@ -386,24 +462,30 @@ class S {
386
462
  * Compares two values recursively for equality.
387
463
  */
388
464
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
389
- deepEqual(s, t) {
390
- if (s === t) return !0;
391
- if (s === null || t === null || s === void 0 || t === void 0) return s === t;
392
- if (typeof s != typeof t) return !1;
393
- if (s instanceof Date && t instanceof Date)
394
- return s.getTime() === t.getTime();
395
- if (Array.isArray(s) && Array.isArray(t))
396
- return s.length !== t.length ? !1 : s.every((e, n) => this.deepEqual(e, t[n]));
397
- if (typeof s == "object" && typeof t == "object") {
398
- const e = Object.keys(s), n = Object.keys(t);
399
- return e.length !== n.length ? !1 : e.every((r) => this.deepEqual(s[r], t[r]));
400
- }
401
- return s === t;
465
+ deepEqual(a, b) {
466
+ if (a === b) return true;
467
+ if (a === null || b === null) return a === b;
468
+ if (a === void 0 || b === void 0) return a === b;
469
+ if (typeof a !== typeof b) return false;
470
+ if (a instanceof Date && b instanceof Date) {
471
+ return a.getTime() === b.getTime();
472
+ }
473
+ if (Array.isArray(a) && Array.isArray(b)) {
474
+ if (a.length !== b.length) return false;
475
+ return a.every((val, idx) => this.deepEqual(val, b[idx]));
476
+ }
477
+ if (typeof a === "object" && typeof b === "object") {
478
+ const keysA = Object.keys(a);
479
+ const keysB = Object.keys(b);
480
+ if (keysA.length !== keysB.length) return false;
481
+ return keysA.every((key) => this.deepEqual(a[key], b[key]));
482
+ }
483
+ return a === b;
402
484
  }
403
485
  }
404
- class M {
405
- constructor(s) {
406
- this.engine = new S(s);
486
+ class Matcher {
487
+ constructor(context) {
488
+ this.engine = new FilterEngine(context);
407
489
  }
408
490
  /**
409
491
  * Matches a single file against a rule.
@@ -412,12 +494,16 @@ class M {
412
494
  * @param rule - The rule to match against
413
495
  * @returns MatchResult indicating if all criteria matched
414
496
  */
415
- matchFile(s, t) {
416
- const n = (R(t) ? t.matchAny : t.match).map((a) => this.engine.evaluateCriterion(s.data, a));
497
+ matchFile(file, rule) {
498
+ const criteria = isWildcardRule(rule) ? rule.matchAny : rule.match;
499
+ const checks = criteria.map((criterion) => {
500
+ return this.engine.evaluateCriterion(file.data, criterion);
501
+ });
502
+ const matched = checks.every((check) => check.status);
417
503
  return {
418
- matched: n.every((a) => a.status),
419
- checks: n,
420
- rule: t
504
+ matched,
505
+ checks,
506
+ rule
421
507
  };
422
508
  }
423
509
  /**
@@ -427,16 +513,23 @@ class M {
427
513
  * @param preFilter - Filter criteria that all files must match
428
514
  * @returns Object with matched files and excluded files (with failed checks)
429
515
  */
430
- applyPreFilter(s, t) {
431
- const e = [], n = [];
432
- for (const r of s) {
433
- const a = t.map((u) => this.engine.evaluateCriterion(r.data, u));
434
- a.every((u) => u.status) ? e.push(r) : n.push({
435
- file: r,
436
- failedChecks: a.filter((u) => !u.status)
516
+ applyPreFilter(files, preFilter) {
517
+ const matched = [];
518
+ const excluded = [];
519
+ for (const file of files) {
520
+ const checks = preFilter.map((criterion) => {
521
+ return this.engine.evaluateCriterion(file.data, criterion);
437
522
  });
523
+ if (checks.every((check) => check.status)) {
524
+ matched.push(file);
525
+ } else {
526
+ excluded.push({
527
+ file,
528
+ failedChecks: checks.filter((check) => !check.status)
529
+ });
530
+ }
438
531
  }
439
- return { matched: e, excluded: n };
532
+ return { matched, excluded };
440
533
  }
441
534
  /**
442
535
  * Main filtering function that processes files according to rules.
@@ -445,100 +538,292 @@ class M {
445
538
  * @param rules - Matching rules (can include arrays for flexible ordering)
446
539
  * @param sortFn - Optional sort function for file ordering
447
540
  * @param preFilter - Optional pre-filter criteria (files not matching are excluded)
448
- * @returns FilterResult with mapped, wildcardMatched, and unmapped files
541
+ * @param mode - Matching mode ('strict', 'optional', or 'strict-optional')
542
+ * @returns FilterResult with mapped, wildcardMatched, optionalFiles and unmapped files
449
543
  */
450
- filterFiles(s, t, e, n) {
451
- let r, a = [];
452
- if (n) {
453
- const f = this.applyPreFilter(s, n);
454
- r = f.matched, a = f.excluded;
455
- } else
456
- r = s;
457
- const u = e ? [...r].sort(e) : [...r], l = [], h = [], y = [], g = /* @__PURE__ */ new Set(), x = /* @__PURE__ */ new Map();
458
- let o = 0, c = 0;
459
- for (; o < u.length; ) {
460
- const f = u[o];
461
- if (c >= t.length) {
462
- g.has(o) || y.push({
463
- file: f,
464
- attemptedRules: []
465
- }), o++;
544
+ filterFiles(files, rules, sortFn, preFilter, mode = "strict") {
545
+ let filteredFiles;
546
+ let preFiltered = [];
547
+ if (preFilter) {
548
+ const preFilterResult = this.applyPreFilter(files, preFilter);
549
+ filteredFiles = preFilterResult.matched;
550
+ preFiltered = preFilterResult.excluded;
551
+ } else {
552
+ filteredFiles = files;
553
+ }
554
+ const sortedFiles = sortFn ? [...filteredFiles].sort(sortFn) : [...filteredFiles];
555
+ const mapped = [];
556
+ const wildcardMatched = [];
557
+ const unmapped = [];
558
+ const optionalFiles = [];
559
+ const matchedFileIndices = /* @__PURE__ */ new Set();
560
+ if (mode === "optional" || mode === "strict-optional") {
561
+ return this.filterFilesOptionalMode(sortedFiles, rules, preFiltered, files.length, mode);
562
+ }
563
+ const usedFlexibleRules = /* @__PURE__ */ new Map();
564
+ let fileIndex = 0;
565
+ let ruleIndex = 0;
566
+ while (fileIndex < sortedFiles.length) {
567
+ const file = sortedFiles[fileIndex];
568
+ if (ruleIndex >= rules.length) {
569
+ if (!matchedFileIndices.has(fileIndex)) {
570
+ unmapped.push({
571
+ file,
572
+ attemptedRules: []
573
+ });
574
+ }
575
+ fileIndex++;
466
576
  continue;
467
577
  }
468
- const m = t[c], A = [];
469
- let T = !1;
470
- if (Array.isArray(m)) {
471
- for (let p = 0; p < m.length; p++) {
472
- if (x.get(c)?.has(p))
578
+ const ruleOrRules = rules[ruleIndex];
579
+ const attemptedMatches = [];
580
+ let fileMatched = false;
581
+ if (Array.isArray(ruleOrRules)) {
582
+ for (let subIndex = 0; subIndex < ruleOrRules.length; subIndex++) {
583
+ const used = usedFlexibleRules.get(ruleIndex)?.has(subIndex);
584
+ if (used) {
473
585
  continue;
474
- const v = m[p], $ = this.matchFile(f, v);
475
- if (A.push($), $.matched && w(v)) {
476
- x.has(c) || x.set(c, /* @__PURE__ */ new Set());
477
- const b = x.get(c);
478
- b && b.add(p), l.push({
479
- expected: v.expected,
480
- file: f,
481
- matchResult: $,
482
- optional: v.optional || !1,
483
- info: v.info
484
- }), g.add(o), T = !0;
586
+ }
587
+ const rule = ruleOrRules[subIndex];
588
+ const matchResult = this.matchFile(file, rule);
589
+ attemptedMatches.push(matchResult);
590
+ if (matchResult.matched && isSingleMatchRule(rule)) {
591
+ if (!usedFlexibleRules.has(ruleIndex)) {
592
+ usedFlexibleRules.set(ruleIndex, /* @__PURE__ */ new Set());
593
+ }
594
+ const usedSet = usedFlexibleRules.get(ruleIndex);
595
+ if (usedSet) {
596
+ usedSet.add(subIndex);
597
+ }
598
+ mapped.push({
599
+ expected: rule.expected,
600
+ file,
601
+ matchResult,
602
+ optional: rule.optional || false,
603
+ info: rule.info
604
+ });
605
+ matchedFileIndices.add(fileIndex);
606
+ fileMatched = true;
485
607
  break;
486
608
  }
487
609
  }
488
- x.get(c)?.size === m.length && c++, o++;
610
+ const allUsed = usedFlexibleRules.get(ruleIndex)?.size === ruleOrRules.length;
611
+ if (allUsed) {
612
+ ruleIndex++;
613
+ }
614
+ if (fileMatched) {
615
+ fileIndex++;
616
+ } else {
617
+ fileIndex++;
618
+ }
489
619
  } else {
490
- const d = m, p = this.matchFile(f, d);
491
- A.push(p), p.matched ? w(d) ? (l.push({
492
- expected: d.expected,
493
- file: f,
494
- matchResult: p,
495
- optional: d.optional || !1,
496
- info: d.info
497
- }), g.add(o), c++, o++) : R(d) && (h.push({
498
- file: f,
499
- matchResult: p,
500
- info: d.info
501
- }), g.add(o), d.greedy || c++, o++) : d.optional || R(d) ? c++ : (y.push({
502
- file: f,
503
- attemptedRules: A
504
- }), o++);
620
+ const rule = ruleOrRules;
621
+ const matchResult = this.matchFile(file, rule);
622
+ attemptedMatches.push(matchResult);
623
+ if (matchResult.matched) {
624
+ if (isSingleMatchRule(rule)) {
625
+ mapped.push({
626
+ expected: rule.expected,
627
+ file,
628
+ matchResult,
629
+ optional: rule.optional || false,
630
+ info: rule.info
631
+ });
632
+ matchedFileIndices.add(fileIndex);
633
+ ruleIndex++;
634
+ fileIndex++;
635
+ } else if (isWildcardRule(rule)) {
636
+ wildcardMatched.push({
637
+ file,
638
+ matchResult,
639
+ info: rule.info
640
+ });
641
+ matchedFileIndices.add(fileIndex);
642
+ if (rule.greedy) {
643
+ fileIndex++;
644
+ } else {
645
+ ruleIndex++;
646
+ fileIndex++;
647
+ }
648
+ }
649
+ } else {
650
+ if (rule.optional) {
651
+ ruleIndex++;
652
+ } else if (isWildcardRule(rule)) {
653
+ ruleIndex++;
654
+ } else {
655
+ unmapped.push({
656
+ file,
657
+ attemptedRules: attemptedMatches
658
+ });
659
+ fileIndex++;
660
+ }
661
+ }
505
662
  }
506
663
  }
507
- const F = {
508
- totalFiles: s.length,
509
- mappedFiles: l.length,
510
- wildcardMatchedFiles: h.length,
511
- unmappedFiles: y.length,
512
- preFilteredFiles: a.length,
513
- totalRules: this.countRules(t),
514
- mandatoryRules: this.countMandatoryRules(t),
515
- optionalRules: this.countOptionalRules(t)
664
+ const stats = {
665
+ totalFiles: files.length,
666
+ mappedFiles: mapped.length,
667
+ wildcardMatchedFiles: wildcardMatched.length,
668
+ unmappedFiles: unmapped.length,
669
+ optionalFiles: optionalFiles.length,
670
+ preFilteredFiles: preFiltered.length,
671
+ totalRules: this.countRules(rules),
672
+ mandatoryRules: this.countMandatoryRules(rules),
673
+ optionalRules: this.countOptionalRules(rules)
516
674
  };
517
675
  return {
518
- mapped: l,
519
- wildcardMatched: h,
520
- unmapped: y,
521
- preFiltered: a,
522
- stats: F
676
+ mapped,
677
+ wildcardMatched,
678
+ optionalFiles,
679
+ unmapped,
680
+ preFiltered,
681
+ stats
682
+ };
683
+ }
684
+ /**
685
+ * Filtering with optional mode - uses scan-forward algorithm
686
+ */
687
+ filterFilesOptionalMode(sortedFiles, rules, preFiltered, totalFiles, mode) {
688
+ const mapped = [];
689
+ const wildcardMatched = [];
690
+ const optionalFiles = [];
691
+ let currentFileIndex = 0;
692
+ let lastMatchedIndex = -1;
693
+ let lastMatchedRule = null;
694
+ for (let ruleIndex = 0; ruleIndex < rules.length; ruleIndex++) {
695
+ const ruleOrRules = rules[ruleIndex];
696
+ let found = false;
697
+ for (let fileIndex = currentFileIndex; fileIndex < sortedFiles.length; fileIndex++) {
698
+ const file = sortedFiles[fileIndex];
699
+ let matchResult = null;
700
+ let matchedRule = null;
701
+ if (Array.isArray(ruleOrRules)) {
702
+ for (const rule of ruleOrRules) {
703
+ const result = this.matchFile(file, rule);
704
+ if (result.matched) {
705
+ matchResult = result;
706
+ matchedRule = rule;
707
+ break;
708
+ }
709
+ }
710
+ } else {
711
+ matchResult = this.matchFile(file, ruleOrRules);
712
+ if (matchResult.matched) {
713
+ matchedRule = ruleOrRules;
714
+ }
715
+ }
716
+ if (matchResult && matchResult.matched && matchedRule) {
717
+ for (let i = lastMatchedIndex + 1; i < fileIndex; i++) {
718
+ const optionalFile = sortedFiles[i];
719
+ optionalFiles.push({
720
+ fileName: optionalFile.fileName,
721
+ position: i,
722
+ between: {
723
+ afterRule: lastMatchedRule || "(start)",
724
+ beforeRule: isSingleMatchRule(matchedRule) ? matchedRule.expected : "(wildcard)"
725
+ },
726
+ failedMatches: []
727
+ // TODO: collect actual failed matches
728
+ });
729
+ }
730
+ if (isSingleMatchRule(matchedRule)) {
731
+ mapped.push({
732
+ expected: matchedRule.expected,
733
+ file,
734
+ matchResult,
735
+ optional: matchedRule.optional || false,
736
+ info: matchedRule.info
737
+ });
738
+ lastMatchedRule = matchedRule.expected;
739
+ } else if (isWildcardRule(matchedRule)) {
740
+ wildcardMatched.push({
741
+ file,
742
+ matchResult,
743
+ info: matchedRule.info
744
+ });
745
+ lastMatchedRule = "(wildcard)";
746
+ }
747
+ lastMatchedIndex = fileIndex;
748
+ currentFileIndex = fileIndex + 1;
749
+ found = true;
750
+ break;
751
+ }
752
+ }
753
+ if (!found) {
754
+ const isMandatory = Array.isArray(ruleOrRules) ? ruleOrRules.some((r) => !r.optional && !isWildcardRule(r)) : !ruleOrRules.optional && !isWildcardRule(ruleOrRules);
755
+ if (isMandatory && mode === "strict-optional") {
756
+ break;
757
+ }
758
+ }
759
+ }
760
+ for (let i = lastMatchedIndex + 1; i < sortedFiles.length; i++) {
761
+ const optionalFile = sortedFiles[i];
762
+ optionalFiles.push({
763
+ fileName: optionalFile.fileName,
764
+ position: i,
765
+ between: {
766
+ afterRule: lastMatchedRule || "(start)",
767
+ beforeRule: "(end)"
768
+ },
769
+ failedMatches: []
770
+ // TODO: collect actual failed matches
771
+ });
772
+ }
773
+ const stats = {
774
+ totalFiles,
775
+ mappedFiles: mapped.length,
776
+ wildcardMatchedFiles: wildcardMatched.length,
777
+ unmappedFiles: 0,
778
+ // Always 0 in optional modes
779
+ optionalFiles: optionalFiles.length,
780
+ preFilteredFiles: preFiltered.length,
781
+ totalRules: this.countRules(rules),
782
+ mandatoryRules: this.countMandatoryRules(rules),
783
+ optionalRules: this.countOptionalRules(rules)
784
+ };
785
+ return {
786
+ mapped,
787
+ wildcardMatched,
788
+ optionalFiles,
789
+ unmapped: [],
790
+ // Always empty in optional modes
791
+ preFiltered,
792
+ stats
523
793
  };
524
794
  }
525
795
  /**
526
796
  * Counts total number of rules (flattening arrays)
527
797
  */
528
- countRules(s) {
529
- return s.reduce((t, e) => Array.isArray(e) ? t + e.length : t + 1, 0);
798
+ countRules(rules) {
799
+ return rules.reduce((count, rule) => {
800
+ if (Array.isArray(rule)) {
801
+ return count + rule.length;
802
+ }
803
+ return count + 1;
804
+ }, 0);
530
805
  }
531
806
  /**
532
807
  * Counts mandatory rules
533
808
  */
534
- countMandatoryRules(s) {
535
- return s.reduce((t, e) => Array.isArray(e) ? t + e.filter((n) => !n.optional && !R(n)).length : t + (e.optional || R(e) ? 0 : 1), 0);
809
+ countMandatoryRules(rules) {
810
+ return rules.reduce((count, rule) => {
811
+ if (Array.isArray(rule)) {
812
+ return count + rule.filter((r) => !r.optional && !isWildcardRule(r)).length;
813
+ }
814
+ return count + (rule.optional || isWildcardRule(rule) ? 0 : 1);
815
+ }, 0);
536
816
  }
537
817
  /**
538
818
  * Counts optional rules
539
819
  */
540
- countOptionalRules(s) {
541
- return s.reduce((t, e) => Array.isArray(e) ? t + e.filter((n) => n.optional || R(n)).length : t + (e.optional || R(e) ? 1 : 0), 0);
820
+ countOptionalRules(rules) {
821
+ return rules.reduce((count, rule) => {
822
+ if (Array.isArray(rule)) {
823
+ return count + rule.filter((r) => r.optional || isWildcardRule(r)).length;
824
+ }
825
+ return count + (rule.optional || isWildcardRule(rule) ? 1 : 0);
826
+ }, 0);
542
827
  }
543
828
  /**
544
829
  * Filtering function for grouped rules with common filter criteria.
@@ -549,66 +834,104 @@ class M {
549
834
  * @param groups - Filter groups with common criteria and rules
550
835
  * @param sortFn - Optional sort function for file ordering
551
836
  * @param preFilter - Optional pre-filter criteria (files not matching are excluded)
552
- * @returns FilterResult with mapped, wildcardMatched, and unmapped files
837
+ * @param mode - Matching mode ('strict', 'optional', or 'strict-optional')
838
+ * @returns FilterResult with mapped, wildcardMatched, optionalFiles and unmapped files
553
839
  */
554
- filterFilesWithGroups(s, t, e, n) {
555
- let r, a = [];
556
- if (n) {
557
- const o = this.applyPreFilter(s, n);
558
- r = o.matched, a = o.excluded;
559
- } else
560
- r = s;
561
- const u = e ? [...r].sort(e) : [...r], l = [], h = [], y = [];
562
- for (const o of t) {
563
- const c = u.filter((m) => o.groupFilter.map((T) => this.engine.evaluateCriterion(m.data, T)).every((T) => T.status));
564
- if (c.length === 0)
840
+ filterFilesWithGroups(files, groups, sortFn, preFilter, mode = "strict") {
841
+ let filteredFiles;
842
+ let preFiltered = [];
843
+ if (preFilter) {
844
+ const preFilterResult = this.applyPreFilter(files, preFilter);
845
+ filteredFiles = preFilterResult.matched;
846
+ preFiltered = preFilterResult.excluded;
847
+ } else {
848
+ filteredFiles = files;
849
+ }
850
+ const sortedFiles = sortFn ? [...filteredFiles].sort(sortFn) : [...filteredFiles];
851
+ const mapped = [];
852
+ const wildcardMatched = [];
853
+ const unmapped = [];
854
+ const optionalFiles = [];
855
+ for (const group of groups) {
856
+ const groupFiles = sortedFiles.filter((file) => {
857
+ const checks = group.groupFilter.map((criterion) => {
858
+ return this.engine.evaluateCriterion(file.data, criterion);
859
+ });
860
+ return checks.every((check) => check.status);
861
+ });
862
+ if (groupFiles.length === 0) {
565
863
  continue;
566
- const F = o.rules.map((m) => (Array.isArray(m), m)), f = this.filterFiles(c, F);
567
- l.push(...f.mapped), h.push(...f.wildcardMatched), y.push(...f.unmapped);
568
- }
569
- const g = t.flatMap((o) => o.rules), x = {
570
- totalFiles: s.length,
571
- mappedFiles: l.length,
572
- wildcardMatchedFiles: h.length,
573
- unmappedFiles: y.length,
574
- preFilteredFiles: a.length,
575
- totalRules: this.countRules(g),
576
- mandatoryRules: this.countMandatoryRules(g),
577
- optionalRules: this.countOptionalRules(g)
864
+ }
865
+ const rulesArray = group.rules.map((rule) => {
866
+ if (Array.isArray(rule)) {
867
+ return rule;
868
+ }
869
+ return rule;
870
+ });
871
+ const groupResult = this.filterFiles(groupFiles, rulesArray, void 0, void 0, mode);
872
+ mapped.push(...groupResult.mapped);
873
+ wildcardMatched.push(...groupResult.wildcardMatched);
874
+ unmapped.push(...groupResult.unmapped);
875
+ optionalFiles.push(...groupResult.optionalFiles);
876
+ }
877
+ const allRules = groups.flatMap((g) => g.rules);
878
+ const stats = {
879
+ totalFiles: files.length,
880
+ mappedFiles: mapped.length,
881
+ wildcardMatchedFiles: wildcardMatched.length,
882
+ unmappedFiles: unmapped.length,
883
+ optionalFiles: optionalFiles.length,
884
+ preFilteredFiles: preFiltered.length,
885
+ totalRules: this.countRules(allRules),
886
+ mandatoryRules: this.countMandatoryRules(allRules),
887
+ optionalRules: this.countOptionalRules(allRules)
578
888
  };
579
889
  return {
580
- mapped: l,
581
- wildcardMatched: h,
582
- unmapped: y,
583
- preFiltered: a,
584
- stats: x
890
+ mapped,
891
+ wildcardMatched,
892
+ optionalFiles,
893
+ unmapped,
894
+ preFiltered,
895
+ stats
585
896
  };
586
897
  }
587
898
  }
588
- function z(i) {
589
- const s = new M(i.context);
590
- if (i.rules && i.groups)
899
+ function filterFiles(request) {
900
+ const matcher = new Matcher(request.context);
901
+ if (request.rules && request.groups) {
591
902
  throw new Error('FilterRequest: Provide either "rules" or "groups", not both');
592
- if (!i.rules && !i.groups)
903
+ }
904
+ if (!request.rules && !request.groups) {
593
905
  throw new Error('FilterRequest: Must provide either "rules" or "groups"');
594
- if (i.groups)
595
- return s.filterFilesWithGroups(
596
- i.files,
597
- i.groups,
598
- i.sortFn,
599
- i.preFilter
906
+ }
907
+ if (request.groups) {
908
+ return matcher.filterFilesWithGroups(
909
+ request.files,
910
+ request.groups,
911
+ request.sortFn,
912
+ request.preFilter,
913
+ request.mode
600
914
  );
601
- if (!i.rules)
915
+ }
916
+ if (!request.rules) {
602
917
  throw new Error("FilterRequest: Rules are required");
603
- return s.filterFiles(i.files, i.rules, i.sortFn, i.preFilter);
918
+ }
919
+ return matcher.filterFiles(
920
+ request.files,
921
+ request.rules,
922
+ request.sortFn,
923
+ request.preFilter,
924
+ request.mode
925
+ );
604
926
  }
605
927
  export {
606
- S as FilterEngine,
607
- M as Matcher,
608
- z as filterFiles,
609
- k as getValueFromPath,
610
- N as getValueOr,
611
- w as isSingleMatchRule,
612
- R as isWildcardRule,
613
- I as pathExists
928
+ FilterEngine,
929
+ Matcher,
930
+ filterFiles,
931
+ getValueFromPath,
932
+ getValueOr,
933
+ isSingleMatchRule,
934
+ isWildcardRule,
935
+ pathExists
614
936
  };
937
+ //# sourceMappingURL=aikotools-datafilter.mjs.map