@aikotools/datafilter 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,591 @@
1
+ import { DateTime as E } from "luxon";
2
+ function R(i) {
3
+ return "matchAny" in i;
4
+ }
5
+ function S(i) {
6
+ return "match" in i && "expected" in i;
7
+ }
8
+ function k(i, s) {
9
+ const t = [];
10
+ if (s.length === 0)
11
+ return {
12
+ value: i,
13
+ found: !0,
14
+ validPath: []
15
+ };
16
+ let e = i;
17
+ for (let n = 0; n < s.length; n++) {
18
+ const r = s[n];
19
+ if (e == null)
20
+ return {
21
+ value: void 0,
22
+ found: !1,
23
+ error: `Cannot read property '${r}' of ${e}`,
24
+ validPath: t
25
+ };
26
+ if (Array.isArray(e)) {
27
+ const a = typeof r == "number" ? r : parseInt(String(r), 10);
28
+ if (isNaN(a))
29
+ return {
30
+ value: void 0,
31
+ found: !1,
32
+ error: `Array index must be a number, got '${r}'`,
33
+ validPath: t
34
+ };
35
+ if (a < 0 || a >= e.length)
36
+ return {
37
+ value: void 0,
38
+ found: !1,
39
+ error: `Array index ${a} out of bounds (length: ${e.length})`,
40
+ validPath: t
41
+ };
42
+ t.push(a), e = e[a];
43
+ continue;
44
+ }
45
+ if (typeof e == "object") {
46
+ const a = String(r);
47
+ if (!(a in e))
48
+ return {
49
+ value: void 0,
50
+ found: !1,
51
+ error: `Property '${a}' does not exist`,
52
+ validPath: t
53
+ };
54
+ t.push(a), e = e[a];
55
+ continue;
56
+ }
57
+ return {
58
+ value: void 0,
59
+ found: !1,
60
+ error: `Cannot access property '${r}' of primitive type ${typeof e}`,
61
+ validPath: t
62
+ };
63
+ }
64
+ return {
65
+ value: e,
66
+ found: !0,
67
+ validPath: t
68
+ };
69
+ }
70
+ function N(i, s) {
71
+ const t = k(i, s);
72
+ return t.found && t.value !== void 0;
73
+ }
74
+ function z(i, s, t) {
75
+ const e = k(i, s);
76
+ return e.found && e.value !== void 0 ? e.value : t;
77
+ }
78
+ class M {
79
+ constructor(s) {
80
+ this.context = s;
81
+ }
82
+ /**
83
+ * Evaluates a single filter criterion against a data object.
84
+ *
85
+ * @param data - The data object to check
86
+ * @param criterion - The filter criterion to evaluate
87
+ * @returns FilterCheckResult indicating success or failure
88
+ */
89
+ // 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,
94
+ checkType: "unknown",
95
+ reason: `Unknown check type: ${JSON.stringify(e)}`
96
+ };
97
+ }
98
+ /**
99
+ * Checks if a value matches an expected value using deep equality.
100
+ *
101
+ * @param data - The data object
102
+ * @param path - Path to the value
103
+ * @param check - The value check specification
104
+ * @returns FilterCheckResult
105
+ */
106
+ checkValue(s, t, e) {
107
+ const n = k(s, t);
108
+ if (!n.found)
109
+ return {
110
+ status: !1,
111
+ checkType: "checkValue",
112
+ reason: {
113
+ message: n.error || "Path not found",
114
+ path: t
115
+ }
116
+ };
117
+ const r = n.value, a = e.value;
118
+ return this.deepEqual(r, a) ? {
119
+ status: !0,
120
+ checkType: "checkValue"
121
+ } : {
122
+ status: !1,
123
+ checkType: "checkValue",
124
+ reason: {
125
+ message: "Value mismatch",
126
+ path: t,
127
+ expected: a,
128
+ actual: r
129
+ }
130
+ };
131
+ }
132
+ /**
133
+ * Checks if a path exists in the data object.
134
+ *
135
+ * @param data - The data object
136
+ * @param path - Path to check
137
+ * @param check - The exists check specification
138
+ * @returns FilterCheckResult
139
+ */
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,
147
+ checkType: "checkExists",
148
+ reason: {
149
+ message: e.exists ? `Path should exist but doesn't: ${n.error}` : "Path should not exist but does",
150
+ path: t
151
+ }
152
+ };
153
+ }
154
+ /**
155
+ * Checks if an array contains (or doesn't contain) a specific element.
156
+ *
157
+ * @param data - The data object
158
+ * @param path - Path to the array
159
+ * @param check - The array element check specification
160
+ * @returns FilterCheckResult
161
+ */
162
+ checkArrayElement(s, t, e) {
163
+ const n = k(s, t);
164
+ if (!n.found)
165
+ return {
166
+ status: !1,
167
+ checkType: "checkArrayElement",
168
+ reason: {
169
+ message: n.error || "Path not found",
170
+ path: t
171
+ }
172
+ };
173
+ const r = n.value;
174
+ if (!Array.isArray(r))
175
+ return {
176
+ status: !1,
177
+ checkType: "checkArrayElement",
178
+ reason: {
179
+ message: "Value is not an array",
180
+ path: t,
181
+ actualType: typeof r
182
+ }
183
+ };
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,
190
+ checkType: "checkArrayElement",
191
+ 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
195
+ }
196
+ };
197
+ }
198
+ /**
199
+ * Checks if an array has the expected size.
200
+ *
201
+ * @param data - The data object
202
+ * @param path - Path to the array
203
+ * @param check - The array size check specification
204
+ * @returns FilterCheckResult
205
+ */
206
+ checkArraySize(s, t, e) {
207
+ const n = k(s, t);
208
+ if (!n.found)
209
+ return {
210
+ status: !1,
211
+ checkType: "checkArraySize",
212
+ reason: {
213
+ message: n.error || "Path not found",
214
+ path: t
215
+ }
216
+ };
217
+ const r = n.value;
218
+ if (!Array.isArray(r))
219
+ return {
220
+ status: !1,
221
+ checkType: "checkArraySize",
222
+ reason: {
223
+ message: "Value is not an array",
224
+ path: t,
225
+ actualType: typeof r
226
+ }
227
+ };
228
+ const a = r.length, u = e.size;
229
+ let c = !1, f = "";
230
+ switch (e.type) {
231
+ case "equal":
232
+ c = a === u, f = `Array length should be ${u} but is ${a}`;
233
+ break;
234
+ case "lessThan":
235
+ c = a < u, f = `Array length should be less than ${u} but is ${a}`;
236
+ break;
237
+ case "greaterThan":
238
+ c = a > u, f = `Array length should be greater than ${u} but is ${a}`;
239
+ break;
240
+ }
241
+ return c ? {
242
+ status: !0,
243
+ checkType: "checkArraySize"
244
+ } : {
245
+ status: !1,
246
+ checkType: "checkArraySize",
247
+ reason: {
248
+ message: f,
249
+ path: t,
250
+ expected: u,
251
+ actual: a
252
+ }
253
+ };
254
+ }
255
+ /**
256
+ * Checks if a timestamp is within the expected time range.
257
+ *
258
+ * @param data - The data object
259
+ * @param path - Path to the timestamp
260
+ * @param check - The time range check specification
261
+ * @returns FilterCheckResult
262
+ */
263
+ checkTimeRange(s, t, e) {
264
+ const n = k(s, t);
265
+ if (!n.found)
266
+ return {
267
+ status: !1,
268
+ checkType: "checkTimeRange",
269
+ reason: {
270
+ message: n.error || "Path not found",
271
+ path: t
272
+ }
273
+ };
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,
290
+ checkType: "checkTimeRange",
291
+ reason: {
292
+ message: `Timestamp ${r} is outside range [${a}, ${u}]`,
293
+ path: t,
294
+ actual: r,
295
+ min: a,
296
+ max: u
297
+ }
298
+ };
299
+ }
300
+ if (typeof r == "string") {
301
+ const a = E.fromISO(r), u = E.fromISO(e.min), c = E.fromISO(e.max);
302
+ return a.isValid ? !u.isValid || !c.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 <= c ? {
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,
325
+ checkType: "checkTimeRange",
326
+ reason: {
327
+ message: `Invalid timestamp: ${r}`,
328
+ path: t
329
+ }
330
+ };
331
+ }
332
+ return {
333
+ status: !1,
334
+ checkType: "checkTimeRange",
335
+ reason: {
336
+ message: `Timestamp must be a string or number, got ${typeof r}`,
337
+ path: t
338
+ }
339
+ };
340
+ }
341
+ /**
342
+ * Checks if a numeric value is within the expected range.
343
+ *
344
+ * @param data - The data object
345
+ * @param path - Path to the numeric value
346
+ * @param check - The numeric range check specification
347
+ * @returns FilterCheckResult
348
+ */
349
+ checkNumericRange(s, t, e) {
350
+ const n = k(s, t);
351
+ if (!n.found)
352
+ return {
353
+ status: !1,
354
+ checkType: "checkNumericRange",
355
+ reason: {
356
+ message: n.error || "Path not found",
357
+ path: t
358
+ }
359
+ };
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,
374
+ checkType: "checkNumericRange",
375
+ 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
381
+ }
382
+ };
383
+ }
384
+ /**
385
+ * Deep equality comparison.
386
+ * Compares two values recursively for equality.
387
+ */
388
+ // 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;
402
+ }
403
+ }
404
+ class P {
405
+ constructor(s) {
406
+ this.engine = new M(s);
407
+ }
408
+ /**
409
+ * Matches a single file against a rule.
410
+ *
411
+ * @param file - The file to match
412
+ * @param rule - The rule to match against
413
+ * @returns MatchResult indicating if all criteria matched
414
+ */
415
+ matchFile(s, t) {
416
+ const n = (R(t) ? t.matchAny : t.match).map((a) => this.engine.evaluateCriterion(s.data, a));
417
+ return {
418
+ matched: n.every((a) => a.status),
419
+ checks: n,
420
+ rule: t
421
+ };
422
+ }
423
+ /**
424
+ * Applies pre-filter criteria to files, returning only files that match all criteria.
425
+ *
426
+ * @param files - Files to filter
427
+ * @param preFilter - Filter criteria that all files must match
428
+ * @returns Filtered files that match all preFilter criteria
429
+ */
430
+ applyPreFilter(s, t) {
431
+ return s.filter((e) => t.map((r) => this.engine.evaluateCriterion(e.data, r)).every((r) => r.status));
432
+ }
433
+ /**
434
+ * Main filtering function that processes files according to rules.
435
+ *
436
+ * @param files - Files to filter
437
+ * @param rules - Matching rules (can include arrays for flexible ordering)
438
+ * @param sortFn - Optional sort function for file ordering
439
+ * @param preFilter - Optional pre-filter criteria (files not matching are excluded)
440
+ * @returns FilterResult with mapped, wildcardMatched, and unmapped files
441
+ */
442
+ filterFiles(s, t, e, n) {
443
+ var T, y;
444
+ const r = n ? this.applyPreFilter(s, n) : s, a = e ? [...r].sort(e) : [...r], u = [], c = [], f = [], p = /* @__PURE__ */ new Set(), x = /* @__PURE__ */ new Map();
445
+ let o = 0, l = 0;
446
+ for (; o < a.length; ) {
447
+ const g = a[o];
448
+ if (l >= t.length) {
449
+ p.has(o) || f.push({
450
+ file: g,
451
+ attemptedRules: []
452
+ }), o++;
453
+ continue;
454
+ }
455
+ const d = t[l], F = [];
456
+ let b = !1;
457
+ if (Array.isArray(d)) {
458
+ for (let m = 0; m < d.length; m++) {
459
+ if ((T = x.get(l)) == null ? void 0 : T.has(m))
460
+ continue;
461
+ const v = d[m], $ = this.matchFile(g, v);
462
+ if (F.push($), $.matched && S(v)) {
463
+ x.has(l) || x.set(l, /* @__PURE__ */ new Set());
464
+ const w = x.get(l);
465
+ w && w.add(m), u.push({
466
+ expected: v.expected,
467
+ file: g,
468
+ matchResult: $,
469
+ optional: v.optional || !1,
470
+ info: v.info
471
+ }), p.add(o), b = !0;
472
+ break;
473
+ }
474
+ }
475
+ ((y = x.get(l)) == null ? void 0 : y.size) === d.length && l++, o++;
476
+ } else {
477
+ const h = d, m = this.matchFile(g, h);
478
+ F.push(m), m.matched ? S(h) ? (u.push({
479
+ expected: h.expected,
480
+ file: g,
481
+ matchResult: m,
482
+ optional: h.optional || !1,
483
+ info: h.info
484
+ }), p.add(o), l++, o++) : R(h) && (c.push({
485
+ file: g,
486
+ matchResult: m,
487
+ info: h.info
488
+ }), p.add(o), h.greedy || l++, o++) : h.optional || R(h) ? l++ : (f.push({
489
+ file: g,
490
+ attemptedRules: F
491
+ }), o++);
492
+ }
493
+ }
494
+ const A = {
495
+ totalFiles: s.length,
496
+ mappedFiles: u.length,
497
+ wildcardMatchedFiles: c.length,
498
+ unmappedFiles: f.length,
499
+ totalRules: this.countRules(t),
500
+ mandatoryRules: this.countMandatoryRules(t),
501
+ optionalRules: this.countOptionalRules(t)
502
+ };
503
+ return {
504
+ mapped: u,
505
+ wildcardMatched: c,
506
+ unmapped: f,
507
+ stats: A
508
+ };
509
+ }
510
+ /**
511
+ * Counts total number of rules (flattening arrays)
512
+ */
513
+ countRules(s) {
514
+ return s.reduce((t, e) => Array.isArray(e) ? t + e.length : t + 1, 0);
515
+ }
516
+ /**
517
+ * Counts mandatory rules
518
+ */
519
+ countMandatoryRules(s) {
520
+ 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);
521
+ }
522
+ /**
523
+ * Counts optional rules
524
+ */
525
+ countOptionalRules(s) {
526
+ 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);
527
+ }
528
+ /**
529
+ * Filtering function for grouped rules with common filter criteria.
530
+ * Files are first filtered by preFilter (if provided), then matched against
531
+ * group filters, and finally processed by the group's rules.
532
+ *
533
+ * @param files - Files to filter
534
+ * @param groups - Filter groups with common criteria and rules
535
+ * @param sortFn - Optional sort function for file ordering
536
+ * @param preFilter - Optional pre-filter criteria (files not matching are excluded)
537
+ * @returns FilterResult with mapped, wildcardMatched, and unmapped files
538
+ */
539
+ filterFilesWithGroups(s, t, e, n) {
540
+ const r = n ? this.applyPreFilter(s, n) : s, a = e ? [...r].sort(e) : [...r], u = [], c = [], f = [];
541
+ for (const o of t) {
542
+ const l = a.filter((y) => o.groupFilter.map((d) => this.engine.evaluateCriterion(y.data, d)).every((d) => d.status));
543
+ if (l.length === 0)
544
+ continue;
545
+ const A = o.rules.map((y) => (Array.isArray(y), y)), T = this.filterFiles(l, A);
546
+ u.push(...T.mapped), c.push(...T.wildcardMatched), f.push(...T.unmapped);
547
+ }
548
+ const p = t.flatMap((o) => o.rules), x = {
549
+ totalFiles: s.length,
550
+ mappedFiles: u.length,
551
+ wildcardMatchedFiles: c.length,
552
+ unmappedFiles: f.length,
553
+ totalRules: this.countRules(p),
554
+ mandatoryRules: this.countMandatoryRules(p),
555
+ optionalRules: this.countOptionalRules(p)
556
+ };
557
+ return {
558
+ mapped: u,
559
+ wildcardMatched: c,
560
+ unmapped: f,
561
+ stats: x
562
+ };
563
+ }
564
+ }
565
+ function O(i) {
566
+ const s = new P(i.context);
567
+ if (i.rules && i.groups)
568
+ throw new Error('FilterRequest: Provide either "rules" or "groups", not both');
569
+ if (!i.rules && !i.groups)
570
+ throw new Error('FilterRequest: Must provide either "rules" or "groups"');
571
+ if (i.groups)
572
+ return s.filterFilesWithGroups(
573
+ i.files,
574
+ i.groups,
575
+ i.sortFn,
576
+ i.preFilter
577
+ );
578
+ if (!i.rules)
579
+ throw new Error("FilterRequest: Rules are required");
580
+ return s.filterFiles(i.files, i.rules, i.sortFn, i.preFilter);
581
+ }
582
+ export {
583
+ M as FilterEngine,
584
+ P as Matcher,
585
+ O as filterFiles,
586
+ k as getValueFromPath,
587
+ z as getValueOr,
588
+ S as isSingleMatchRule,
589
+ R as isWildcardRule,
590
+ N as pathExists
591
+ };
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Core exports for datafilter
3
+ */
4
+ export * from './types';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,cAAc,SAAS,CAAA"}