@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.
- package/README.md +396 -0
- package/dist/aikotools-datafilter.mjs +591 -0
- package/dist/src/core/index.d.ts +5 -0
- package/dist/src/core/index.d.ts.map +1 -0
- package/dist/src/core/types.d.ts +321 -0
- package/dist/src/core/types.d.ts.map +1 -0
- package/dist/src/engine/FilterEngine.d.ts +84 -0
- package/dist/src/engine/FilterEngine.d.ts.map +1 -0
- package/dist/src/engine/index.d.ts +5 -0
- package/dist/src/engine/index.d.ts.map +1 -0
- package/dist/src/index.d.ts +48 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/matcher/Matcher.d.ts +64 -0
- package/dist/src/matcher/Matcher.d.ts.map +1 -0
- package/dist/src/matcher/index.d.ts +5 -0
- package/dist/src/matcher/index.d.ts.map +1 -0
- package/dist/src/utils/ObjectAccess.d.ts +72 -0
- package/dist/src/utils/ObjectAccess.d.ts.map +1 -0
- package/dist/src/utils/index.d.ts +5 -0
- package/dist/src/utils/index.d.ts.map +1 -0
- package/license +9 -0
- package/package.json +52 -0
package/README.md
ADDED
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
# @aikotools/datafilter
|
|
2
|
+
|
|
3
|
+
Advanced data filtering engine for JSON file matching in E2E testing.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@aikotools/datafilter)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
`@aikotools/datafilter` provides sophisticated file filtering and matching capabilities specifically designed for E2E testing scenarios where you need to map actual result files to expected files.
|
|
11
|
+
|
|
12
|
+
### Key Features
|
|
13
|
+
|
|
14
|
+
- **Flexible Matching**: Match files using various filter criteria (value, exists, array checks, time ranges)
|
|
15
|
+
- **Order Handling**: Support for both strict and flexible ordering of expected files
|
|
16
|
+
- **🆕 Wildcard Optionals**: Match arbitrary number of optional files without explicit specification
|
|
17
|
+
- **Greedy vs. Non-Greedy**: Control whether wildcards match once or multiple times
|
|
18
|
+
- **Deep Object Access**: Path-based navigation through nested JSON structures
|
|
19
|
+
- **Sort Integration**: Custom sort functions for file ordering before matching
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install @aikotools/datafilter
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
import { filterFiles } from '@aikotools/datafilter';
|
|
31
|
+
|
|
32
|
+
const result = filterFiles({
|
|
33
|
+
files: [
|
|
34
|
+
{ fileName: 'event1.json', data: { type: 'event1', value: 42 } },
|
|
35
|
+
{ fileName: 'optional1.json', data: { type: 'optional' } },
|
|
36
|
+
{ fileName: 'optional2.json', data: { type: 'optional' } },
|
|
37
|
+
{ fileName: 'event2.json', data: { type: 'event2', value: 100 } },
|
|
38
|
+
],
|
|
39
|
+
rules: [
|
|
40
|
+
// Match specific file
|
|
41
|
+
{ match: [{ path: ['type'], check: { value: 'event1' } }], expected: 'event1' },
|
|
42
|
+
|
|
43
|
+
// 🆕 NEW: Match arbitrary optionals with wildcard
|
|
44
|
+
{
|
|
45
|
+
matchAny: [{ path: ['type'], check: { value: 'optional' } }],
|
|
46
|
+
optional: true,
|
|
47
|
+
greedy: true, // Match all optionals
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
// Match another specific file
|
|
51
|
+
{ match: [{ path: ['type'], check: { value: 'event2' } }], expected: 'event2' },
|
|
52
|
+
],
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
console.log(result.mapped.length); // 2 (event1, event2)
|
|
56
|
+
console.log(result.wildcardMatched.length); // 2 (both optionals)
|
|
57
|
+
console.log(result.unmapped.length); // 0
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Core Concepts
|
|
61
|
+
|
|
62
|
+
### Filter Criteria
|
|
63
|
+
|
|
64
|
+
All filter criteria use path-based access and various check types:
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
{
|
|
68
|
+
path: ['data', 'user', 'name'], // Navigate through object
|
|
69
|
+
check: { value: 'John' } // Check type
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Match Rules
|
|
74
|
+
|
|
75
|
+
#### Single Match Rule
|
|
76
|
+
|
|
77
|
+
Maps exactly one file to an expected identifier:
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
{
|
|
81
|
+
match: [
|
|
82
|
+
{ path: ['type'], check: { value: 'event1' } },
|
|
83
|
+
{ path: ['status'], check: { value: 'active' } }
|
|
84
|
+
],
|
|
85
|
+
expected: 'event1',
|
|
86
|
+
optional: false // Default: false
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
#### 🆕 Wildcard Match Rule (NEW)
|
|
91
|
+
|
|
92
|
+
Matches arbitrary number of files without explicit specification:
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
{
|
|
96
|
+
matchAny: [
|
|
97
|
+
{ path: ['category'], check: { value: 'optional' } }
|
|
98
|
+
],
|
|
99
|
+
optional: true, // Always true for wildcards
|
|
100
|
+
greedy: true // Match all (true) or just one (false)
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
#### Flexible Ordering
|
|
105
|
+
|
|
106
|
+
Use arrays of rules when order is not deterministic:
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
[
|
|
110
|
+
{ match: [{ path: ['type'], check: { value: 'event3' } }], expected: 'event3' },
|
|
111
|
+
{ match: [{ path: ['type'], check: { value: 'event4' } }], expected: 'event4' }
|
|
112
|
+
]
|
|
113
|
+
// Either event3 or event4 can come first
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Filter Check Types
|
|
117
|
+
|
|
118
|
+
### 1. Value Check
|
|
119
|
+
|
|
120
|
+
Deep equality comparison:
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
{ path: ['user', 'name'], check: { value: 'John' } }
|
|
124
|
+
{ path: ['user', 'address'], check: { value: { city: 'Berlin', zip: '10115' } } }
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### 2. Exists Check
|
|
128
|
+
|
|
129
|
+
Check if a path exists:
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
{ path: ['optional', 'field'], check: { exists: true } }
|
|
133
|
+
{ path: ['deprecated'], check: { exists: false } }
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### 3. Array Element Check
|
|
137
|
+
|
|
138
|
+
Check if array contains (or doesn't contain) an element:
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
{ path: ['tags'], check: { itemExists: true, item: 'typescript' } }
|
|
142
|
+
{ path: ['status'], check: { itemExists: false, item: 'banned' } }
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### 4. Array Size Check
|
|
146
|
+
|
|
147
|
+
Validate array length:
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
{ path: ['items'], check: { type: 'equal', size: 3 } }
|
|
151
|
+
{ path: ['errors'], check: { type: 'lessThan', size: 5 } }
|
|
152
|
+
{ path: ['records'], check: { type: 'greaterThan', size: 10 } }
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### 5. Time Range Check
|
|
156
|
+
|
|
157
|
+
Validate timestamps (ISO strings or numeric):
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
// ISO timestamp
|
|
161
|
+
{
|
|
162
|
+
path: ['timestamp'],
|
|
163
|
+
check: {
|
|
164
|
+
min: '2023-11-08T15:00:00+01:00',
|
|
165
|
+
max: '2023-11-08T16:00:00+01:00'
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Numeric timestamp (milliseconds)
|
|
170
|
+
{
|
|
171
|
+
path: ['timestamp'],
|
|
172
|
+
check: {
|
|
173
|
+
min: '1699452000000',
|
|
174
|
+
max: '1699455600000'
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Complete Example
|
|
180
|
+
|
|
181
|
+
### Scenario from Requirements
|
|
182
|
+
|
|
183
|
+
```
|
|
184
|
+
event1
|
|
185
|
+
- event1.1 (optional)
|
|
186
|
+
event2
|
|
187
|
+
- event3, event4 (flexible order)
|
|
188
|
+
event5
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Without Wildcard (Old Way)
|
|
192
|
+
|
|
193
|
+
Every optional file must be explicitly specified:
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
const result = filterFiles({
|
|
197
|
+
files: actualFiles,
|
|
198
|
+
rules: [
|
|
199
|
+
{ match: [{ path: ['type'], check: { value: 'event1' } }], expected: 'event1' },
|
|
200
|
+
{ match: [{ path: ['type'], check: { value: 'event1.1' } }], expected: 'event1.1', optional: true },
|
|
201
|
+
{ match: [{ path: ['type'], check: { value: 'event2' } }], expected: 'event2' },
|
|
202
|
+
[
|
|
203
|
+
{ match: [{ path: ['type'], check: { value: 'event3' } }], expected: 'event3' },
|
|
204
|
+
{ match: [{ path: ['type'], check: { value: 'event4' } }], expected: 'event4' }
|
|
205
|
+
],
|
|
206
|
+
{ match: [{ path: ['type'], check: { value: 'event5' } }], expected: 'event5' },
|
|
207
|
+
],
|
|
208
|
+
});
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### With Wildcard (New Way) 🆕
|
|
212
|
+
|
|
213
|
+
Arbitrary number of optional files without explicit specification:
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
const result = filterFiles({
|
|
217
|
+
files: actualFiles,
|
|
218
|
+
rules: [
|
|
219
|
+
{ match: [{ path: ['type'], check: { value: 'event1' } }], expected: 'event1' },
|
|
220
|
+
|
|
221
|
+
// 🆕 NEW: Match ANY number of optional files
|
|
222
|
+
{
|
|
223
|
+
matchAny: [{ path: ['category'], check: { value: 'optional' } }],
|
|
224
|
+
optional: true,
|
|
225
|
+
greedy: true
|
|
226
|
+
},
|
|
227
|
+
|
|
228
|
+
{ match: [{ path: ['type'], check: { value: 'event2' } }], expected: 'event2' },
|
|
229
|
+
[
|
|
230
|
+
{ match: [{ path: ['type'], check: { value: 'event3' } }], expected: 'event3' },
|
|
231
|
+
{ match: [{ path: ['type'], check: { value: 'event4' } }], expected: 'event4' }
|
|
232
|
+
],
|
|
233
|
+
{ match: [{ path: ['type'], check: { value: 'event5' } }], expected: 'event5' },
|
|
234
|
+
],
|
|
235
|
+
sortFn: (a, b) => a.data.timestamp - b.data.timestamp
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
console.log(result.mapped); // event1, event2, event3, event4, event5
|
|
239
|
+
console.log(result.wildcardMatched); // All files matching optional criteria
|
|
240
|
+
console.log(result.unmapped); // Files that didn't match any rule
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
## API Reference
|
|
244
|
+
|
|
245
|
+
### filterFiles(request: FilterRequest): FilterResult
|
|
246
|
+
|
|
247
|
+
Main filtering function.
|
|
248
|
+
|
|
249
|
+
**FilterRequest:**
|
|
250
|
+
- `files: JsonFile[]` - Files to filter
|
|
251
|
+
- `rules: (MatchRule | MatchRule[])[]` - Matching rules
|
|
252
|
+
- `sortFn?: (a, b) => number` - Optional sort function
|
|
253
|
+
- `context?: { startTimeScript?, startTimeTest?, pathTime? }` - Optional context
|
|
254
|
+
|
|
255
|
+
**FilterResult:**
|
|
256
|
+
- `mapped: MappedFile[]` - Successfully mapped files
|
|
257
|
+
- `wildcardMatched: WildcardMappedFile[]` - Files matched by wildcards
|
|
258
|
+
- `unmapped: UnmappedFile[]` - Files that couldn't be matched
|
|
259
|
+
- `stats: { totalFiles, mappedFiles, wildcardMatchedFiles, unmappedFiles, ... }`
|
|
260
|
+
|
|
261
|
+
### Matcher Class
|
|
262
|
+
|
|
263
|
+
For advanced usage with multiple filter operations:
|
|
264
|
+
|
|
265
|
+
```typescript
|
|
266
|
+
import { Matcher } from '@aikotools/datafilter';
|
|
267
|
+
|
|
268
|
+
const matcher = new Matcher({ startTimeScript, startTimeTest });
|
|
269
|
+
const result = matcher.filterFiles(files, rules, sortFn);
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### FilterEngine Class
|
|
273
|
+
|
|
274
|
+
For custom filter criterion evaluation:
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
import { FilterEngine } from '@aikotools/datafilter';
|
|
278
|
+
|
|
279
|
+
const engine = new FilterEngine();
|
|
280
|
+
const checkResult = engine.evaluateCriterion(data, criterion);
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### Utility Functions
|
|
284
|
+
|
|
285
|
+
```typescript
|
|
286
|
+
import { getValueFromPath, pathExists, getValueOr } from '@aikotools/datafilter';
|
|
287
|
+
|
|
288
|
+
// Get value from path
|
|
289
|
+
const result = getValueFromPath(obj, ['data', 'user', 'name']);
|
|
290
|
+
// result: { value: 'John', found: true, validPath: ['data', 'user', 'name'] }
|
|
291
|
+
|
|
292
|
+
// Check if path exists
|
|
293
|
+
const exists = pathExists(obj, ['data', 'user']);
|
|
294
|
+
// exists: true
|
|
295
|
+
|
|
296
|
+
// Get value with default fallback
|
|
297
|
+
const value = getValueOr(obj, ['data', 'missing'], 'default');
|
|
298
|
+
// value: 'default'
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
## Migration from e2e-tool-util-result-mapper
|
|
302
|
+
|
|
303
|
+
### Key Differences
|
|
304
|
+
|
|
305
|
+
1. **Wildcard Support**: New wildcard rules eliminate need to specify every optional file
|
|
306
|
+
2. **Simpler API**: Focused on filtering, not entire pipeline (dedupe, sort, etc.)
|
|
307
|
+
3. **Type Safety**: Full TypeScript support with comprehensive types
|
|
308
|
+
4. **Modern Structure**: ESM modules, Vite build, Vitest testing
|
|
309
|
+
|
|
310
|
+
### Migration Example
|
|
311
|
+
|
|
312
|
+
**Old (result-mapper):**
|
|
313
|
+
```typescript
|
|
314
|
+
// Every optional must be explicitly specified
|
|
315
|
+
{
|
|
316
|
+
"mappingCriteria": [
|
|
317
|
+
{ "expected": "event1.json", "optional": false, "filter": {...} },
|
|
318
|
+
{ "expected": "optional1.json", "optional": true, "filter": {...} },
|
|
319
|
+
{ "expected": "optional2.json", "optional": true, "filter": {...} },
|
|
320
|
+
{ "expected": "optional3.json", "optional": true, "filter": {...} },
|
|
321
|
+
{ "expected": "event2.json", "optional": false, "filter": {...} }
|
|
322
|
+
]
|
|
323
|
+
}
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
**New (datafilter):**
|
|
327
|
+
```typescript
|
|
328
|
+
// Wildcard matches all optionals
|
|
329
|
+
{
|
|
330
|
+
rules: [
|
|
331
|
+
{ match: [...], expected: 'event1' },
|
|
332
|
+
{ matchAny: [...], optional: true, greedy: true }, // 🆕 All optionals
|
|
333
|
+
{ match: [...], expected: 'event2' }
|
|
334
|
+
]
|
|
335
|
+
}
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
## Test Results
|
|
339
|
+
|
|
340
|
+
✅ **21/21 tests passing**
|
|
341
|
+
- ✅ Basic filtering with single matches
|
|
342
|
+
- ✅ Flexible ordering (array of rules)
|
|
343
|
+
- ✅ Optional rules
|
|
344
|
+
- ✅ 🆕 Wildcard matches (greedy & non-greedy)
|
|
345
|
+
- ✅ All filter check types (value, exists, array, time)
|
|
346
|
+
- ✅ Complex real-world scenarios
|
|
347
|
+
- ✅ Sort function integration
|
|
348
|
+
|
|
349
|
+
## Development
|
|
350
|
+
|
|
351
|
+
```bash
|
|
352
|
+
# Install dependencies
|
|
353
|
+
npm install
|
|
354
|
+
|
|
355
|
+
# Build
|
|
356
|
+
npm run build
|
|
357
|
+
|
|
358
|
+
# Test
|
|
359
|
+
npm test
|
|
360
|
+
|
|
361
|
+
# Lint
|
|
362
|
+
npm run lint
|
|
363
|
+
|
|
364
|
+
# Format
|
|
365
|
+
npm run format
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
## Architecture
|
|
369
|
+
|
|
370
|
+
```
|
|
371
|
+
FilterEngine
|
|
372
|
+
├── Evaluates individual filter criteria
|
|
373
|
+
├── Supports: value, exists, array checks, time ranges
|
|
374
|
+
└── Deep equality comparison
|
|
375
|
+
|
|
376
|
+
Matcher
|
|
377
|
+
├── Orchestrates file-to-rule matching
|
|
378
|
+
├── Handles: single rules, arrays, wildcards
|
|
379
|
+
├── Tracks: matched files, exhausted rules
|
|
380
|
+
└── Produces detailed results
|
|
381
|
+
|
|
382
|
+
Utilities
|
|
383
|
+
└── Path-based object access (getValueFromPath, pathExists)
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
## License
|
|
387
|
+
|
|
388
|
+
MIT - See [LICENSE](LICENSE) file for details.
|
|
389
|
+
|
|
390
|
+
## Contributing
|
|
391
|
+
|
|
392
|
+
This is part of the @aikotools ecosystem. For issues and contributions, please see the repository.
|
|
393
|
+
|
|
394
|
+
---
|
|
395
|
+
|
|
396
|
+
**Built for E2E Testing** 🚀
|