@devisfuture/mega-collection 2.3.5 → 2.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +191 -90
- package/dist/State-2vKzwNrQ.d.mts +44 -0
- package/dist/chunk-IZXO22NQ.mjs +1 -0
- package/dist/chunk-MB56OSRQ.mjs +1 -0
- package/dist/filter/index.d.mts +29 -13
- package/dist/filter/index.mjs +1 -1
- package/dist/index.d.mts +1 -4
- package/dist/index.mjs +1 -1
- package/dist/merge/index.d.mts +42 -12
- package/dist/merge/index.mjs +1 -1
- package/dist/search/index.d.mts +142 -17
- package/dist/search/index.mjs +5 -1
- package/dist/sort/index.d.mts +28 -11
- package/dist/sort/index.mjs +1 -1
- package/dist/types-DONld7xY.d.mts +74 -0
- package/package.json +6 -1
- package/dist/chunk-EYZOY3HS.mjs +0 -1
- package/dist/chunk-ITIBHZNJ.mjs +0 -1
- package/dist/chunk-U3IU4NI6.mjs +0 -1
- package/dist/chunk-Y7NSBYLS.mjs +0 -3
- package/dist/types-4YEflcxU.d.mts +0 -16
package/README.md
CHANGED
|
@@ -10,11 +10,14 @@ If this package saved you some time, a ⭐ on GitHub would be much appreciated.
|
|
|
10
10
|
|
|
11
11
|
- [What does this package solve](#what-does-this-package-solve) – what problem this package helps with
|
|
12
12
|
- [Features](#features) – what the package can do
|
|
13
|
+
- [How it works](#how-it-works) – plain-English explanation of how each engine works internally
|
|
14
|
+
- [Benchmarks](#benchmarks) – performance numbers and how to run them
|
|
13
15
|
- [React demo](#react-demo) – example project and live demo
|
|
14
16
|
- [Install](#install) – how to install the package
|
|
15
17
|
- [Quick Start](#quick-start) – basic usage examples
|
|
16
18
|
- [All-in-one: `MergeEngines`](#all-in-one-mergeengines) – use search, filter, and sort from one engine
|
|
17
19
|
- [Add items with `add([])`](#add-items-with-add) – append multiple items to stored data
|
|
20
|
+
- [Update items with `update(...)`](#update-items-with-update) – replace one stored item by unique field
|
|
18
21
|
- [Search only](#search-only) – use only text search
|
|
19
22
|
- [Flat collections search](#flat-collections-search) – search simple fields like `name` or `city`
|
|
20
23
|
- [Nested collections search](#nested-collections-search) – search inside nested arrays like `orders.status`
|
|
@@ -38,11 +41,19 @@ If this package saved you some time, a ⭐ on GitHub would be much appreciated.
|
|
|
38
41
|
|
|
39
42
|
## What does this package solve
|
|
40
43
|
|
|
41
|
-
|
|
42
|
-
Most often you then need to search, filter, or sort this data.
|
|
44
|
+
When your API returns thousands of items, you usually need to let the user search, filter, or sort them on the client side.
|
|
43
45
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
+
The typical way to do this is with built-in array methods:
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
const results = users.filter((u) => u.city === "New York");
|
|
50
|
+
const sorted = [...users].sort((a, b) => a.age - b.age);
|
|
51
|
+
const found = users.filter((u) => u.name.toLowerCase().includes(query));
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
This works fine for small arrays. But with 10 000–100 000+ items, every call to `filter` or `sort` scans the whole array from scratch. If you run it on every keystroke, it adds up.
|
|
55
|
+
|
|
56
|
+
This package solves this by building indexes ahead of time — special data structures that let you look up results without scanning the full array every time. You pay the cost once when the data arrives, and then each search, filter, or sort is much cheaper.
|
|
46
57
|
|
|
47
58
|
The package has no dependencies.
|
|
48
59
|
You can import only the parts you need.
|
|
@@ -51,16 +62,52 @@ Each engine has its own entry point: `/search`, `/filter`, `/sort`.
|
|
|
51
62
|
If you import only `@devisfuture/mega-collection/search`, only search code goes into the bundle.
|
|
52
63
|
Unused modules are not included.
|
|
53
64
|
|
|
54
|
-
##
|
|
65
|
+
## How it works
|
|
66
|
+
|
|
67
|
+
### Search
|
|
68
|
+
|
|
69
|
+
Native `Array.prototype.filter` with `String.includes` checks every item in the array on each keystroke. For 50 000 items that's 50 000 string comparisons per call.
|
|
55
70
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
71
|
+
`TextSearchEngine` avoids this by building an **n-gram inverted index** upfront:
|
|
72
|
+
|
|
73
|
+
1. Each string value is split into overlapping 2- and 3-character pieces called n-grams. For example, `"hello"` produces `"he"`, `"hel"`, `"el"`, `"ell"`, `"ll"`, `"llo"`, `"lo"`.
|
|
74
|
+
2. For every n-gram the engine keeps a set of item positions that contain it.
|
|
75
|
+
3. When you search for `"john"`, the engine splits that query into the same n-gram pieces, then intersects the sets — only items that share all query n-grams survive. This candidate set is usually tiny even for 100 000 items.
|
|
76
|
+
4. Each surviving candidate is checked with a fast `String.includes` to confirm the full substring match.
|
|
77
|
+
|
|
78
|
+
For very short queries (fewer than 2 characters) the engine falls back to a linear scan — n-grams that short would match too many items to be useful.
|
|
79
|
+
|
|
80
|
+
### Filter
|
|
81
|
+
|
|
82
|
+
Native `Array.prototype.filter` with `===` still checks every item on every call.
|
|
83
|
+
|
|
84
|
+
`FilterEngine` builds a **hash-map** for each indexed field:
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
field "city" → { "New York": [item0, item4, ...], "Miami": [item1, ...], ... }
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
A filter call becomes a map lookup: `index.get("New York")` returns the array of matches in O(1). Multiple values from the same field are concatenated. Multiple fields are intersected using a `Set`.
|
|
91
|
+
|
|
92
|
+
When the `fields` option is not provided, the engine falls back to a linear scan — which works but is slower.
|
|
93
|
+
|
|
94
|
+
### Sort
|
|
95
|
+
|
|
96
|
+
Native `Array.prototype.sort` re-sorts the whole array from scratch every call.
|
|
97
|
+
|
|
98
|
+
`SortEngine` pre-sorts and stores results in a `Uint32Array` of positions:
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
cache["age"] = [index of youngest item, index of next, ..., index of oldest]
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
The first sort call builds this index. Subsequent calls just read it in O(n). The cache is invalidated on mutations and rebuilt lazily on the next sort call.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Benchmarks
|
|
109
|
+
|
|
110
|
+
Benchmarks for `TextSearchEngine`, `FilterEngine`, and `SortEngine` are collected in [BENCHMARKS](./BENCHMARKS.md).
|
|
64
111
|
|
|
65
112
|
## React demo
|
|
66
113
|
|
|
@@ -103,12 +150,12 @@ Use `MergeEngines` when you want one class that works with one dataset.
|
|
|
103
150
|
Add needed engines to `imports`. Only those engines will be created.
|
|
104
151
|
|
|
105
152
|
You can create many engine instances in one project for different collections.
|
|
106
|
-
Each instance
|
|
153
|
+
Each instance keeps its own dataset and runtime indexes inside an internal shared `State`, so separate instances do not affect each other.
|
|
107
154
|
|
|
108
155
|
Each engine can receive an optional `fields` array through `search`, `filter`, or `sort` options.
|
|
109
156
|
These fields are used for indexes.
|
|
110
157
|
|
|
111
|
-
Indexes are built lazily on first use, so engine creation stays fast.
|
|
158
|
+
Indexes are built lazily on first use inside that shared state, so engine creation stays fast.
|
|
112
159
|
If you skip `fields`, everything still works, but the engine may scan the full array.
|
|
113
160
|
|
|
114
161
|
```ts
|
|
@@ -120,8 +167,9 @@ import { FilterEngine } from "@devisfuture/mega-collection/filter";
|
|
|
120
167
|
const engine = new MergeEngines<User>({
|
|
121
168
|
imports: [TextSearchEngine, SortEngine, FilterEngine],
|
|
122
169
|
data: users,
|
|
170
|
+
filterByPreviousResult: true,
|
|
123
171
|
search: { fields: ["name", "city"], minQueryLength: 2 },
|
|
124
|
-
filter: { fields: ["city", "age"]
|
|
172
|
+
filter: { fields: ["city", "age"] },
|
|
125
173
|
sort: { fields: ["age", "name", "city"] },
|
|
126
174
|
});
|
|
127
175
|
|
|
@@ -137,10 +185,19 @@ engine
|
|
|
137
185
|
.sort([{ field: "age", direction: "asc" }])
|
|
138
186
|
.filter([{ field: "city", values: ["Miami", "New York"] }]);
|
|
139
187
|
|
|
188
|
+
// Separate calls also continue from the last result when
|
|
189
|
+
// `filterByPreviousResult` is enabled on MergeEngines.
|
|
190
|
+
const searchResult = engine.search("john");
|
|
191
|
+
const filteredResult = engine.filter([
|
|
192
|
+
{ field: "city", values: ["Miami", "New York"] },
|
|
193
|
+
]);
|
|
194
|
+
const sortedResult = engine.sort([{ field: "age", direction: "asc" }]);
|
|
195
|
+
|
|
140
196
|
// Example with nested fields, for example `orders` inside each user.
|
|
141
197
|
const nestedEngine = new MergeEngines<UserWithOrders>({
|
|
142
198
|
imports: [TextSearchEngine, SortEngine, FilterEngine],
|
|
143
199
|
data: usersWithOrders,
|
|
200
|
+
filterByPreviousResult: true,
|
|
144
201
|
search: {
|
|
145
202
|
fields: ["name", "city"],
|
|
146
203
|
nestedFields: ["orders.status"],
|
|
@@ -149,7 +206,6 @@ const nestedEngine = new MergeEngines<UserWithOrders>({
|
|
|
149
206
|
filter: {
|
|
150
207
|
fields: ["city", "age"],
|
|
151
208
|
nestedFields: ["orders.status"],
|
|
152
|
-
filterByPreviousResult: true,
|
|
153
209
|
},
|
|
154
210
|
sort: { fields: ["age", "name", "city"] },
|
|
155
211
|
});
|
|
@@ -188,7 +244,12 @@ This is different from `data(...)`:
|
|
|
188
244
|
- `data(...)` replaces the whole stored dataset.
|
|
189
245
|
- `add([])` appends new items to the existing stored dataset.
|
|
190
246
|
|
|
191
|
-
If indexes are already built,
|
|
247
|
+
If indexes are already built, `add()` updates them incrementally for the new items only:
|
|
248
|
+
|
|
249
|
+
- **TextSearchEngine / FilterEngine**: O(k) — only the new items are written into the n-gram or hash-map index (existing index entries are untouched).
|
|
250
|
+
- **SortEngine**: the sort cache for each configured field is invalidated on `add()` and rebuilt lazily on the next `sort()` call. This avoids O(N) work per add and is optimal when multiple adds happen between sorts.
|
|
251
|
+
|
|
252
|
+
If indexes have not been built yet (first `sort()` has not been called), `add()` appends the items without touching any index.
|
|
192
253
|
If you cleared indexes with `clearIndexes()`, `add([])` does not rebuild them automatically.
|
|
193
254
|
|
|
194
255
|
```ts
|
|
@@ -255,6 +316,56 @@ sortEngine.add([
|
|
|
255
316
|
|
|
256
317
|
---
|
|
257
318
|
|
|
319
|
+
### Update items with `update(...)`
|
|
320
|
+
|
|
321
|
+
Use `update(...)` when you need to replace one stored item by a unique field such as `id`.
|
|
322
|
+
|
|
323
|
+
- `update(...)` keeps the same stored array reference.
|
|
324
|
+
- `update(...)` replaces only the matched item in stored data.
|
|
325
|
+
- configured indexes or caches refresh only the affected item instead of rebuilding the whole dataset.
|
|
326
|
+
|
|
327
|
+
```ts
|
|
328
|
+
import { MergeEngines } from "@devisfuture/mega-collection";
|
|
329
|
+
import { TextSearchEngine } from "@devisfuture/mega-collection/search";
|
|
330
|
+
import { SortEngine } from "@devisfuture/mega-collection/sort";
|
|
331
|
+
import { FilterEngine } from "@devisfuture/mega-collection/filter";
|
|
332
|
+
|
|
333
|
+
const merge = new MergeEngines<User>({
|
|
334
|
+
imports: [TextSearchEngine, SortEngine, FilterEngine],
|
|
335
|
+
data: users,
|
|
336
|
+
search: { fields: ["name", "city"], minQueryLength: 2 },
|
|
337
|
+
filter: { fields: ["city", "age"] },
|
|
338
|
+
sort: { fields: ["age", "name"] },
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
merge.update({
|
|
342
|
+
field: "id",
|
|
343
|
+
data: { id: 2, name: "Bob", city: "Paris", age: 19 },
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
merge.search("Paris");
|
|
347
|
+
merge.filter([{ field: "city", values: ["Paris"] }]);
|
|
348
|
+
merge.sort([{ field: "age", direction: "asc" }]);
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
The same method works in each engine:
|
|
352
|
+
|
|
353
|
+
```ts
|
|
354
|
+
import { TextSearchEngine } from "@devisfuture/mega-collection/search";
|
|
355
|
+
|
|
356
|
+
const searchEngine = new TextSearchEngine<User>({
|
|
357
|
+
data: users,
|
|
358
|
+
fields: ["name", "city"],
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
searchEngine.update({
|
|
362
|
+
field: "id",
|
|
363
|
+
data: { id: 2, name: "Bob", city: "Paris", age: 19 },
|
|
364
|
+
});
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
---
|
|
368
|
+
|
|
258
369
|
### Search only
|
|
259
370
|
|
|
260
371
|
Use `TextSearchEngine` when you only need text search.
|
|
@@ -279,10 +390,17 @@ const engine = new TextSearchEngine<User>({
|
|
|
279
390
|
|
|
280
391
|
engine.search("john"); // searches all indexed fields, deduplicated
|
|
281
392
|
engine.search("name", "john"); // searches a specific field
|
|
393
|
+
engine.search("john", { limit: 20, offset: 20 }); // paginate broad result sets
|
|
282
394
|
|
|
283
395
|
// replace dataset without re-initializing
|
|
284
396
|
engine.data(users);
|
|
285
397
|
|
|
398
|
+
// replace one stored item by unique field
|
|
399
|
+
engine.update({
|
|
400
|
+
field: "id",
|
|
401
|
+
data: { id: 2, name: "Bob", city: "Paris", age: 19 },
|
|
402
|
+
});
|
|
403
|
+
|
|
286
404
|
// access original dataset stored in the engine
|
|
287
405
|
engine.getOriginData();
|
|
288
406
|
|
|
@@ -335,6 +453,12 @@ engine.filter([
|
|
|
335
453
|
// Replace dataset without creating a new engine.
|
|
336
454
|
engine.data(users);
|
|
337
455
|
|
|
456
|
+
// Replace one stored item by unique field.
|
|
457
|
+
engine.update({
|
|
458
|
+
field: "id",
|
|
459
|
+
data: { id: 2, name: "Bob", city: "Paris", age: 19, active: true },
|
|
460
|
+
});
|
|
461
|
+
|
|
338
462
|
// Get original stored dataset.
|
|
339
463
|
engine.getOriginData();
|
|
340
464
|
|
|
@@ -487,6 +611,12 @@ engine.sort([{ field: "age", direction: "asc" }]);
|
|
|
487
611
|
// replace dataset without re-initializing
|
|
488
612
|
engine.data(users);
|
|
489
613
|
|
|
614
|
+
// replace one stored item by unique field
|
|
615
|
+
engine.update({
|
|
616
|
+
field: "id",
|
|
617
|
+
data: { id: 2, name: "Bob", city: "Paris", age: 19 },
|
|
618
|
+
});
|
|
619
|
+
|
|
490
620
|
// access original dataset stored in the engine
|
|
491
621
|
engine.getOriginData();
|
|
492
622
|
|
|
@@ -511,13 +641,14 @@ One class that combines search, filter, and sort for the same dataset.
|
|
|
511
641
|
|
|
512
642
|
**Constructor options:**
|
|
513
643
|
|
|
514
|
-
| Option
|
|
515
|
-
|
|
|
516
|
-
| `imports`
|
|
517
|
-
| `data`
|
|
518
|
-
| `
|
|
519
|
-
| `
|
|
520
|
-
| `
|
|
644
|
+
| Option | Type | Description |
|
|
645
|
+
| ------------------------ | ----------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
|
|
646
|
+
| `imports` | `(typeof TextSearchEngine \| SortEngine \| FilterEngine)[]` | Engine classes to create |
|
|
647
|
+
| `data` | `T[]` | Shared dataset — passed once at construction |
|
|
648
|
+
| `filterByPreviousResult` | `boolean` | When `true`, separate `filter(...)` and `sort(...)` calls continue from the last result stored in shared State |
|
|
649
|
+
| `search` | `{ fields, nestedFields?, minQueryLength? }` | Config for TextSearchEngine |
|
|
650
|
+
| `filter` | `{ fields, nestedFields?, mutableExcludeField? }` | Config for FilterEngine |
|
|
651
|
+
| `sort` | `{ fields }` | Config for SortEngine |
|
|
521
652
|
|
|
522
653
|
**Methods:**
|
|
523
654
|
|
|
@@ -531,12 +662,10 @@ One class that combines search, filter, and sort for the same dataset.
|
|
|
531
662
|
| `filter(data, criteria)` | Filter with an explicit dataset |
|
|
532
663
|
| `getOriginData()` | Get the shared original dataset |
|
|
533
664
|
| `add(items)` | Append multiple items to the stored dataset and update existing indexes or caches for new items only |
|
|
665
|
+
| `update({ field, data })` | Replace one stored item by a unique field and refresh only the affected cached or indexed data |
|
|
534
666
|
| `data(data)` | Replace stored dataset for all imported modules, rebuilding configured indexes and resetting filter state where applicable |
|
|
535
667
|
| `clearIndexes(module)` | Clear indexes for one module (`"search"`, `"sort"`, `"filter"`) |
|
|
536
|
-
| `clearData(module)` | Clear stored
|
|
537
|
-
|
|
538
|
-
If `filter.mutableExcludeField` is configured, `filter([{ field, exclude }])` on that field removes items from the stored filter dataset with swap-pop.
|
|
539
|
-
This changes the stored filter dataset and does not preserve order.
|
|
668
|
+
| `clearData(module)` | Clear the shared stored dataset through one imported module (`"search"`, `"sort"`, `"filter"`) |
|
|
540
669
|
|
|
541
670
|
---
|
|
542
671
|
|
|
@@ -546,15 +675,27 @@ Text search engine.
|
|
|
546
675
|
It supports `nestedFields` if you need to search inside nested collections such as `["orders.status"]`.
|
|
547
676
|
Search methods return plain arrays.
|
|
548
677
|
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
|
552
|
-
|
|
|
553
|
-
| `
|
|
554
|
-
| `
|
|
555
|
-
|
|
556
|
-
|
|
|
557
|
-
|
|
|
678
|
+
Main constructor options:
|
|
679
|
+
|
|
680
|
+
| Option | Type | Description |
|
|
681
|
+
| ------------------------ | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
682
|
+
| `filterByPreviousResult` | `boolean` | When `true`, a query that narrows the previous one (new query includes old query) searches only the previous result instead of the full dataset. Any mutation resets the state. |
|
|
683
|
+
| `nestedFields` | `string[]` | Nested field paths in dot notation, for example `["orders.status"]`. |
|
|
684
|
+
|
|
685
|
+
| Method | Description |
|
|
686
|
+
| -------------------------------- | -------------------------------------------------------------------- |
|
|
687
|
+
| `search(query, options?)` | Search all indexed fields (including nested), deduplicated |
|
|
688
|
+
| `search(field, query, options?)` | Search a specific indexed field or nested field path |
|
|
689
|
+
| `searchAll(query, options?)` | Explicit all-fields alias when you want pagination on broad searches |
|
|
690
|
+
| `resetSearchState()` | Reset previous-result state for sequential narrowing search |
|
|
691
|
+
| `getOriginData()` | Get the original stored dataset |
|
|
692
|
+
| `add(items)` | Append multiple items to the stored dataset |
|
|
693
|
+
| `update({ field, data })` | Replace one stored item by a unique field |
|
|
694
|
+
| `data(data)` | Replace stored dataset and rebuild configured indexes |
|
|
695
|
+
| `clearIndexes()` | Clear n-gram indexes (including nested) |
|
|
696
|
+
| `clearData()` | Clear stored data |
|
|
697
|
+
|
|
698
|
+
`options.limit` and `options.offset` are useful for broad result sets where you only need the current page.
|
|
558
699
|
|
|
559
700
|
### `FilterEngine<T>` (filter module)
|
|
560
701
|
|
|
@@ -570,16 +711,17 @@ Main constructor options:
|
|
|
570
711
|
| `mutableExcludeField` | `string` | Optional field for removing items from stored data with swap-pop. This changes the stored dataset and does not preserve order. |
|
|
571
712
|
| `nestedFields` | `string[]` | Nested field paths in dot notation, for example `["orders.status"]`. |
|
|
572
713
|
|
|
573
|
-
| Method
|
|
574
|
-
|
|
|
575
|
-
| `filter(criteria)`
|
|
576
|
-
| `filter(data, criteria)`
|
|
577
|
-
| `getOriginData()`
|
|
578
|
-
| `add(items)`
|
|
579
|
-
| `
|
|
580
|
-
| `
|
|
581
|
-
| `
|
|
582
|
-
| `
|
|
714
|
+
| Method | Description |
|
|
715
|
+
| ------------------------- | -------------------------------------------------------------------------- |
|
|
716
|
+
| `filter(criteria)` | Filter using stored dataset (supports nested field criteria) |
|
|
717
|
+
| `filter(data, criteria)` | Filter with an explicit dataset |
|
|
718
|
+
| `getOriginData()` | Get the original stored dataset |
|
|
719
|
+
| `add(items)` | Append multiple items to the stored dataset |
|
|
720
|
+
| `update({ field, data })` | Replace one stored item by a unique field |
|
|
721
|
+
| `data(data)` | Replace stored dataset, rebuild configured indexes, and reset filter state |
|
|
722
|
+
| `resetFilterState()` | Reset previous-result state for sequential filtering |
|
|
723
|
+
| `clearIndexes()` | Free all index memory (including nested indexes) |
|
|
724
|
+
| `clearData()` | Clear stored data |
|
|
583
725
|
|
|
584
726
|
### `SortEngine<T>` (sort module)
|
|
585
727
|
|
|
@@ -592,54 +734,13 @@ Sort methods return plain arrays.
|
|
|
592
734
|
| `sort(data, descriptors, inPlace?)` | Sort with an explicit dataset |
|
|
593
735
|
| `getOriginData()` | Get the original stored dataset |
|
|
594
736
|
| `add(items)` | Append multiple items to the stored dataset |
|
|
737
|
+
| `update({ field, data })` | Replace one stored item by a unique field |
|
|
595
738
|
| `data(data)` | Replace stored dataset and rebuild configured indexes |
|
|
596
739
|
| `clearIndexes()` | Free all cached indexes |
|
|
597
740
|
| `clearData()` | Clear stored data |
|
|
598
741
|
|
|
599
742
|
---
|
|
600
743
|
|
|
601
|
-
**Note on `data` method:** Calling `data` updates the stored dataset. It also rebuilds configured indexes and resets internal state when needed, so usually you do not need to call `clearIndexes` before it.
|
|
602
|
-
|
|
603
|
-
## Types
|
|
604
|
-
|
|
605
|
-
All types are exported from the root package and from each sub-module:
|
|
606
|
-
|
|
607
|
-
```ts
|
|
608
|
-
import type {
|
|
609
|
-
CollectionItem,
|
|
610
|
-
IndexableKey,
|
|
611
|
-
FilterCriterion,
|
|
612
|
-
SortDescriptor,
|
|
613
|
-
SortDirection,
|
|
614
|
-
MergeEnginesOptions,
|
|
615
|
-
} from "@devisfuture/mega-collection";
|
|
616
|
-
```
|
|
617
|
-
|
|
618
|
-
You can also import them from individual sub-modules:
|
|
619
|
-
|
|
620
|
-
```ts
|
|
621
|
-
import type {
|
|
622
|
-
CollectionItem,
|
|
623
|
-
IndexableKey,
|
|
624
|
-
} from "@devisfuture/mega-collection/search";
|
|
625
|
-
import type { FilterCriterion } from "@devisfuture/mega-collection/filter";
|
|
626
|
-
import type {
|
|
627
|
-
SortDescriptor,
|
|
628
|
-
SortDirection,
|
|
629
|
-
} from "@devisfuture/mega-collection/sort";
|
|
630
|
-
```
|
|
631
|
-
|
|
632
|
-
---
|
|
633
|
-
|
|
634
|
-
## Build
|
|
635
|
-
|
|
636
|
-
```bash
|
|
637
|
-
npm install
|
|
638
|
-
npm run build # Build ESM + declarations
|
|
639
|
-
npm run typecheck # Type-check without emitting
|
|
640
|
-
npm run dev # Watch mode
|
|
641
|
-
```
|
|
642
|
-
|
|
643
744
|
## Contributing
|
|
644
745
|
|
|
645
746
|
See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { C as CollectionItem, S as StateOptions, a as StateListener, b as StatePreviousResult, c as StateRegistryFactory, U as UpdateDescriptor, I as IndexableKey } from './types-DONld7xY.mjs';
|
|
2
|
+
|
|
3
|
+
declare class State<T extends CollectionItem> {
|
|
4
|
+
private originData;
|
|
5
|
+
private filterByPreviousResult;
|
|
6
|
+
private itemIndexLookup;
|
|
7
|
+
private mutationVersion;
|
|
8
|
+
private namespaceSequence;
|
|
9
|
+
private previousResultState;
|
|
10
|
+
private readonly indexMaps;
|
|
11
|
+
private readonly scopedRegistry;
|
|
12
|
+
private readonly listeners;
|
|
13
|
+
constructor(data?: T[], options?: StateOptions);
|
|
14
|
+
subscribe(listener: StateListener<T>): () => void;
|
|
15
|
+
getOriginData(): T[];
|
|
16
|
+
getItemIndex(item: T): number | undefined;
|
|
17
|
+
getMutationVersion(): number;
|
|
18
|
+
isFilterByPreviousResultEnabled(): boolean;
|
|
19
|
+
setFilterByPreviousResult(enabled: boolean): void;
|
|
20
|
+
getPreviousResult(): T[] | null;
|
|
21
|
+
getPreviousResultState(): StatePreviousResult<T> | null;
|
|
22
|
+
setPreviousResult(result: T[], sourceData: T[]): void;
|
|
23
|
+
clearPreviousResult(): void;
|
|
24
|
+
createNamespace(prefix?: string): string;
|
|
25
|
+
getScopedValue<TValue>(namespace: string, key: string): TValue | undefined;
|
|
26
|
+
getOrCreateScopedValue<TValue>(namespace: string, key: string, createValue: StateRegistryFactory<TValue>): TValue;
|
|
27
|
+
setScopedValue<TValue>(namespace: string, key: string, value: TValue): TValue;
|
|
28
|
+
deleteScopedValue(namespace: string, key: string): void;
|
|
29
|
+
clearScope(namespace: string): void;
|
|
30
|
+
data(data: T[]): void;
|
|
31
|
+
add(items: T[]): void;
|
|
32
|
+
update(descriptor: UpdateDescriptor<T>): void;
|
|
33
|
+
clearData(): void;
|
|
34
|
+
removeByFieldValue(field: IndexableKey<T> & string, value: any): void;
|
|
35
|
+
removeByFieldValues(field: IndexableKey<T> & string, values: any[]): void;
|
|
36
|
+
private emit;
|
|
37
|
+
private getOrCreateScope;
|
|
38
|
+
private bumpMutationVersion;
|
|
39
|
+
private getOrCreateIndexMap;
|
|
40
|
+
private rebuildIndexMap;
|
|
41
|
+
private rebuildItemIndexLookup;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export { State as S };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import {a,b}from'./chunk-MB56OSRQ.mjs';function p(o){return {value:o,enumerable:false,configurable:true,writable:true}}var f=class{constructor(e){this.callbacks=e;}create(e){let t=e;return Object.defineProperties(t,{search:p((i,r)=>r===void 0?this.callbacks.search(i):this.callbacks.search(i,r)),sort:p((i,r,n)=>r===void 0?this.callbacks.sort(e,i,n):this.callbacks.sort(i,r,n)),filter:p((i,r)=>r===void 0?this.callbacks.filter(e,i):this.callbacks.filter(i,r)),add:p(i=>this.callbacks.add(i)),update:p(i=>this.callbacks.update(i)),clearIndexes:p(i=>(this.callbacks.clearIndexes(i),this.create(e))),clearData:p(i=>(this.callbacks.clearData(i),this.create(e))),data:p(i=>this.callbacks.data(i)),getOriginData:p(()=>this.callbacks.getOriginData())}),t}};function T(o){return typeof o=="object"&&o!==null}function M(o,e){return T(o)&&typeof o[e]=="function"}function F(o){return M(o,"search")&&M(o,"getOriginData")}function m(o){return M(o,"sort")&&M(o,"getOriginData")}function D(o){return M(o,"rawFilter")&&M(o,"getOriginData")}var I=o=>{let{prototype:e}=o;return D(e)?"filter":m(e)?"sort":F(e)?"search":null},x=(o,e,t,i)=>{let r=new o({data:e,state:t,...i});return D(r)?{moduleName:"filter",executeFilter:(n,s)=>s===void 0?r.rawFilter(n):r.rawFilter(n,s),clearIndexes:()=>r.clearIndexes()}:m(r)?{moduleName:"sort",executeSort:(n,s,l)=>s===void 0?r.sort(n):r.sort(n,s,l),clearIndexes:()=>r.clearIndexes()}:F(r)?{moduleName:"search",executeSearch:(n,s)=>s===void 0?r.search(n):r.search(n,s),clearIndexes:()=>r.clearIndexes()}:null};var y="deferSortMutationCacheUpdates",P="deferSearchMutationIndexUpdates",R="deferFilterMutationIndexUpdates",w={search:"TextSearchEngine",sort:"SortEngine",filter:"FilterEngine"};var d=class o extends Error{constructor(e){super(e),this.name="MergeEnginesError";}static unavailableEngine(e){let t=w[e];return new o(`MergeEngines: ${t} is not available.`)}static unavailableGetOriginData(){return new o("MergeEngines: getOriginData is not available.")}static invalidFilterByPreviousResultOption(){return new o('MergeEngines: "filter.filterByPreviousResult" is not supported. Configure "filterByPreviousResult" on the MergeEngines root options.')}};var C=class{constructor(e){this.previousSearchState=null;this.previousFilterState=null;this.previousSortState=null;let{imports:t,data:i,filterByPreviousResult:r=false,...n}=e;this.validateFilterByPreviousResultOptions(n.filter),this.state=new a(i,{filterByPreviousResult:r});let s=new Set(t),l=null,u=null,c=null;for(let a of s){let h=I(a);if(!h)continue;let v=this.getModuleInitOptions(h,a.name,n),g=x(a,i,this.state,v);g&&(g.moduleName==="search"&&!l&&(l=g),g.moduleName==="sort"&&!u&&(u=g),g.moduleName==="filter"&&!c&&(c=g));}this.searchModule=l,this.sortModule=u,this.filterModule=c,this.sortModule&&this.state.setScopedValue(b,y,true),this.searchModule&&this.state.setScopedValue(b,P,true),this.filterModule&&this.state.setScopedValue(b,R,true),this.state.subscribe(a=>this.handleStateMutation(a)),this.chainBuilder=new f({search:(a,h)=>h===void 0?this.search(a):this.search(a,h),sort:(a,h,v)=>this.sort(a,h,v),filter:(a,h)=>this.filter(a,h),getOriginData:()=>this.getOriginData(),add:a=>this.add(a),update:a=>this.update(a),data:a=>this.data(a),clearIndexes:a=>this.clearIndexes(a),clearData:a=>this.clearData(a)});}getAdapter(e){return e==="search"?this.searchModule:e==="sort"?this.sortModule:this.filterModule}getModuleInitOptions(e,t,i){let r={};for(let n of [e,t]){let s=i[n];T(s)&&Object.assign(r,s);}return r}validateFilterByPreviousResultOptions(e){if(T(e)&&Object.prototype.hasOwnProperty.call(e,"filterByPreviousResult"))throw d.invalidFilterByPreviousResultOption()}isPreviousResultEnabled(){return this.state.isFilterByPreviousResultEnabled()}getPreviousResultInput(){return this.isPreviousResultEnabled()?this.state.getPreviousResult():null}trackPreviousResult(e,t){return this.isPreviousResultEnabled()&&this.state.setPreviousResult(e,t),e}clearOperationState(e){(!e||e==="search")&&(this.previousSearchState=null),(!e||e==="filter")&&(this.previousFilterState=null),(!e||e==="sort")&&(this.previousSortState=null);}createSearchCacheKey(e,t){return JSON.stringify([e,t??null])}createSortCacheKey(e,t){return JSON.stringify({descriptors:e,inPlace:t??false})}createFilterCacheKey(e){return JSON.stringify(e)}handleStateMutation(e){switch(e.type){case "add":this.queueSearchCacheMutation(e),this.queueFilterCacheMutation(e),this.queueSortCacheMutation(e);return;case "update":this.queueSearchCacheMutation(e),this.queueFilterCacheMutation(e),this.queueSortCacheMutation(e);return;case "remove":this.previousSearchState=null,this.previousFilterState=null,this.queueSortCacheMutation(e);return;case "removeMany":this.previousSearchState=null,this.previousFilterState=null,this.queueSortCacheMutation(e);return;case "data":case "clearData":this.previousSearchState=null,this.previousFilterState=null,this.previousSortState=null;return}}queueSearchCacheMutation(e){let t=this.previousSearchState;if(t!==null){if(!this.canPatchStoredDatasetSearch(t)){this.previousSearchState=null;return}this.previousSearchState={...t,version:this.state.getMutationVersion(),pendingMutations:t.pendingMutations.concat(e)};}}queueFilterCacheMutation(e){let t=this.previousFilterState;if(t!==null){if(!this.canPatchStoredDatasetFilter(t)){this.previousFilterState=null;return}this.previousFilterState={...t,version:this.state.getMutationVersion(),pendingMutations:t.pendingMutations.concat(e)};}}queueSortCacheMutation(e){let t=this.previousSortState;if(t!==null){if(!this.canPatchStoredDatasetSort(t)){this.previousSortState=null;return}this.previousSortState={...t,version:this.state.getMutationVersion(),pendingMutations:t.pendingMutations.concat(e)};}}resolvePendingSortCache(e){if(e.pendingMutations.length===0)return e;let t={...e,pendingMutations:[]};for(let i=0;i<e.pendingMutations.length;i++)if(t=this.applySortCacheMutation(t,e.pendingMutations[i]),t===null)return null;return t}resolvePendingSearchCache(e){if(e.pendingMutations.length===0)return e;let t={...e,pendingMutations:[]};for(let i=0;i<e.pendingMutations.length;i++)if(t=this.applySearchCacheMutation(t,e.pendingMutations[i]),t===null)return null;return t}resolvePendingFilterCache(e){if(e.pendingMutations.length===0)return e;let t={...e,pendingMutations:[]};for(let i=0;i<e.pendingMutations.length;i++)if(t=this.applyFilterCacheMutation(t,e.pendingMutations[i]),t===null)return null;return t}applySortCacheMutation(e,t){switch(t.type){case "add":return this.patchSortCacheForAddedItems(e,t.items);case "update":return this.patchSortCacheForUpdatedItem(e,t.previousItem,t.nextItem);case "remove":return this.patchSortCacheForRemovedItems(e,[t.removedItem]);case "removeMany":return this.patchSortCacheForRemovedItems(e,t.entries.map(i=>i.removedItem));case "data":case "clearData":return null}}applySearchCacheMutation(e,t){switch(t.type){case "add":return this.patchSearchCacheForAddedItems(e,t.items);case "update":return this.patchSearchCacheForUpdatedItem(e,t.previousItem,t.nextItem);case "remove":case "removeMany":case "data":case "clearData":return null}}applyFilterCacheMutation(e,t){switch(t.type){case "add":return this.patchFilterCacheForAddedItems(e,t.items);case "update":return this.patchFilterCacheForUpdatedItem(e,t.previousItem,t.nextItem);case "remove":case "removeMany":case "data":case "clearData":return null}}canPatchStoredDatasetSearch(e){return e.originData===this.state.getOriginData()&&e.field!==null}canPatchStoredDatasetFilter(e){if(e.sourceData!==this.state.getOriginData())return false;for(let t=0;t<e.criteria.length;t++)if(!this.isPatchableFilterCriterion(e.criteria[t]))return false;return true}canPatchStoredDatasetSort(e){return e.sourceData===this.state.getOriginData()}compareItemsByDescriptors(e,t,i){for(let r=0;r<i.length;r++){let{field:n,direction:s}=i[r],l=e[n],u=t[n];if(l<u)return s==="asc"?-1:1;if(l>u)return s==="asc"?1:-1}return (this.state.getItemIndex(e)??-1)-(this.state.getItemIndex(t)??-1)}findSortInsertPosition(e,t,i){let r=0,n=e.length;for(;r<n;){let s=r+n>>1;this.compareItemsByDescriptors(e[s],t,i)<=0?r=s+1:n=s;}return r}findDatasetInsertPosition(e,t){let i=this.state.getItemIndex(t)??Number.MAX_SAFE_INTEGER;for(let r=0;r<e.length;r++)if((this.state.getItemIndex(e[r])??Number.MAX_SAFE_INTEGER)>i)return r;return e.length}doesItemMatchSearchCache(e,t){if(e.field===null||e.lowerQuery.length===0)return false;let i=t[e.field];return typeof i=="string"&&i.toLowerCase().includes(e.lowerQuery)}patchSearchCacheForAddedItems(e,t){if(!this.canPatchStoredDatasetSearch(e))return null;let i=e.result.slice();for(let r=0;r<t.length;r++){let n=t[r];this.doesItemMatchSearchCache(e,n)&&i.push(n);}return {...e,result:i,pendingMutations:[],version:this.state.getMutationVersion()}}patchSearchCacheForUpdatedItem(e,t,i){if(!this.canPatchStoredDatasetSearch(e))return null;let r=e.result.slice(),n=r.indexOf(t),s=this.doesItemMatchSearchCache(e,i);if(n!==-1)s?r[n]=i:r.splice(n,1);else if(s){let l=this.findDatasetInsertPosition(r,i);r.splice(l,0,i);}return {...e,result:r,pendingMutations:[],version:this.state.getMutationVersion()}}isPatchableFilterCriterion(e){return !String(e.field).includes(".")}doesItemMatchFilterCache(e,t){for(let i=0;i<e.criteria.length;i++){let r=e.criteria[i];if(!this.isPatchableFilterCriterion(r))return false;let n=t[r.field];if(r.values!==void 0&&r.values.length>0&&!r.values.includes(n)||r.exclude!==void 0&&r.exclude.length>0&&r.exclude.includes(n))return false}return true}patchFilterCacheForAddedItems(e,t){if(!this.canPatchStoredDatasetFilter(e))return null;let i=e.result.slice();for(let r=0;r<t.length;r++){let n=t[r];this.doesItemMatchFilterCache(e,n)&&i.push(n);}return {...e,result:i,pendingMutations:[],version:this.state.getMutationVersion()}}patchFilterCacheForUpdatedItem(e,t,i){if(!this.canPatchStoredDatasetFilter(e))return null;let r=e.result.slice(),n=r.indexOf(t),s=this.doesItemMatchFilterCache(e,i);if(n!==-1)s?r[n]=i:r.splice(n,1);else if(s){let l=this.findDatasetInsertPosition(r,i);r.splice(l,0,i);}return {...e,result:r,pendingMutations:[],version:this.state.getMutationVersion()}}patchSortCacheForAddedItems(e,t){if(!this.canPatchStoredDatasetSort(e))return null;let i=e.result.slice();for(let r=0;r<t.length;r++){let n=t[r],s=this.findSortInsertPosition(i,n,e.descriptors);i.splice(s,0,n);}return {...e,result:i,pendingMutations:[],version:this.state.getMutationVersion()}}patchSortCacheForUpdatedItem(e,t,i){if(!this.canPatchStoredDatasetSort(e))return null;let r=e.result.slice(),n=r.indexOf(t);if(n===-1)return e.result.indexOf(i)!==-1?{...e,pendingMutations:[],version:this.state.getMutationVersion()}:null;if(!e.descriptors.some(({field:u})=>t[u]!==i[u]))return r[n]=i,{...e,result:r,pendingMutations:[],version:this.state.getMutationVersion()};r.splice(n,1);let l=this.findSortInsertPosition(r,i,e.descriptors);return r.splice(l,0,i),{...e,result:r,pendingMutations:[],version:this.state.getMutationVersion()}}patchSortCacheForRemovedItems(e,t){if(!this.canPatchStoredDatasetSort(e))return null;let i=new Set(t);return {...e,result:Array.prototype.filter.call(e.result,r=>!i.has(r)),pendingMutations:[],version:this.state.getMutationVersion()}}search(e,t){if(!this.searchModule)throw d.unavailableEngine("search");let i=this.state.getOriginData(),r=this.state.getMutationVersion(),n=this.createSearchCacheKey(e,t),s=this.previousSearchState;if(s?.originData===i&&s.key===n&&s.version===r){let u=this.resolvePendingSearchCache(s);if(u!==null)return this.previousSearchState=u,this.withChain(this.trackPreviousResult(u.result,i));this.previousSearchState=null;}let l=t===void 0?this.searchModule.executeSearch(e):this.searchModule.executeSearch(e,t);return this.previousSearchState={key:n,originData:i,result:l,version:r,field:t===void 0?null:e,lowerQuery:(t??e).trim().toLowerCase(),pendingMutations:[]},this.withChain(this.trackPreviousResult(l,i))}sort(e,t,i){if(!this.sortModule)throw d.unavailableEngine("sort");let r,n;if(t===void 0?(n=e,r=this.getPreviousResultInput()??this.state.getOriginData()):(r=e,n=t),!i){let s=this.state.getMutationVersion(),l=this.createSortCacheKey(n,i),u=this.previousSortState;if(u?.sourceData===r&&u.key===l&&u.version===s){let a=this.resolvePendingSortCache(u);if(a!==null)return this.previousSortState=a,this.withChain(this.trackPreviousResult(a.result,r));this.previousSortState=null;}let c=t===void 0&&r===this.state.getOriginData()?this.sortModule.executeSort(n):this.sortModule.executeSort(r,n,i);return this.previousSortState={key:l,sourceData:r,result:c,version:s,descriptors:n,pendingMutations:[]},this.withChain(this.trackPreviousResult(c,r))}return this.withChain(this.trackPreviousResult(this.sortModule.executeSort(r,n,i),r))}filter(e,t){if(!this.filterModule)throw d.unavailableEngine("filter");if(t===void 0){let r=this.getPreviousResultInput(),n=r??this.state.getOriginData(),s=e,l=this.state.getMutationVersion(),u=this.createFilterCacheKey(s),c=this.previousFilterState;if(c?.sourceData===n&&c.key===u&&c.version===l){let h=this.resolvePendingFilterCache(c);if(h!==null)return this.previousFilterState=h,this.withChain(this.trackPreviousResult(h.result,n));this.previousFilterState=null;}let a=r===null?this.filterModule.executeFilter(s):this.filterModule.executeFilter(r,s);return this.previousFilterState={key:u,sourceData:n,result:a,version:l,criteria:s,pendingMutations:[]},this.withChain(this.trackPreviousResult(a,n))}let i=e;return this.withChain(this.trackPreviousResult(this.filterModule.executeFilter(i,t),i))}withChain(e){return this.chainBuilder.create(e)}getOriginData(){if(this.searchModule||this.sortModule||this.filterModule)return this.state.getOriginData();throw d.unavailableGetOriginData()}add(e){return e.length===0?this:(this.state.add(e),this)}update(e){return this.state.update(e),this}clearIndexes(e){let t=this.getAdapter(e);if(t)return t.clearIndexes(),this.clearOperationState(e),this;throw d.unavailableEngine(e)}data(e){return this.state.data(e),this}clearData(e){if(this.getAdapter(e))return this.state.clearData(),this;throw d.unavailableEngine(e)}};export{C as a};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var g=class{constructor(e=[],t={}){this.itemIndexLookup=new WeakMap;this.mutationVersion=0;this.namespaceSequence=0;this.previousResultState=null;this.indexMaps=new Map;this.scopedRegistry=new Map;this.listeners=new Set;this.originData=e,this.filterByPreviousResult=t.filterByPreviousResult??false,this.rebuildItemIndexLookup();}subscribe(e){return this.listeners.add(e),()=>{this.listeners.delete(e);}}getOriginData(){return this.originData}getItemIndex(e){return this.itemIndexLookup.get(e)}getMutationVersion(){return this.mutationVersion}isFilterByPreviousResultEnabled(){return this.filterByPreviousResult}setFilterByPreviousResult(e){this.filterByPreviousResult=e,e||this.clearPreviousResult();}getPreviousResult(){return this.getPreviousResultState()?.result??null}getPreviousResultState(){return this.previousResultState===null||this.previousResultState.version!==this.mutationVersion?null:this.previousResultState}setPreviousResult(e,t){this.previousResultState={result:e,sourceData:t,version:this.mutationVersion};}clearPreviousResult(){this.previousResultState=null;}createNamespace(e="scope"){return this.namespaceSequence+=1,`${e}:${this.namespaceSequence}`}getScopedValue(e,t){return this.scopedRegistry.get(e)?.get(t)}getOrCreateScopedValue(e,t,i){let n=this.getScopedValue(e,t);if(n!==void 0)return n;let o=this.getOrCreateScope(e),r=i();return o.set(t,r),r}setScopedValue(e,t,i){return this.getOrCreateScope(e).set(t,i),i}deleteScopedValue(e,t){let i=this.scopedRegistry.get(e);i&&(i.delete(t),i.size===0&&this.scopedRegistry.delete(e));}clearScope(e){this.scopedRegistry.delete(e);}data(e){this.originData=e,this.bumpMutationVersion(),this.rebuildItemIndexLookup();for(let t of this.indexMaps.keys())this.rebuildIndexMap(t);this.emit({type:"data",data:e});}add(e){if(e.length===0)return;let t=this.originData.length;for(let i=0;i<e.length;i++)this.originData.push(e[i]),this.itemIndexLookup.set(e[i],t+i);this.bumpMutationVersion();for(let[i,n]of this.indexMaps)for(let o=0;o<e.length;o++)n.set(e[o][i],t+o);this.emit({type:"add",items:e,startIndex:t});}update(e){let{field:t,data:i}=e,n=i[t];if(n==null)return;let r=this.getOrCreateIndexMap(t).get(n);if(r===void 0)return;let s=this.originData[r];this.originData[r]=i,this.itemIndexLookup.delete(s),this.itemIndexLookup.set(i,r),this.bumpMutationVersion();for(let[d,u]of this.indexMaps){let a=s[d],l=i[d];a!==l&&(u.get(a)===r&&u.delete(a),u.set(l,r));}this.emit({type:"update",field:t,index:r,previousItem:s,nextItem:i});}clearData(){let e=[];this.originData=e,this.itemIndexLookup=new WeakMap,this.bumpMutationVersion();for(let t of this.indexMaps.values())t.clear();this.emit({type:"clearData",data:e});}removeByFieldValue(e,t){let n=this.getOrCreateIndexMap(e).get(t);if(n===void 0)return;let o=this.originData.length-1,r=this.originData[n],s=n===o?null:this.originData[o];n!==o&&s&&(this.originData[n]=s,this.itemIndexLookup.set(s,n)),this.originData.pop(),this.itemIndexLookup.delete(r),this.bumpMutationVersion();for(let[d,u]of this.indexMaps){let a=r[d];u.get(a)===n&&u.delete(a),s&&u.set(s[d],n);}this.emit({type:"remove",field:e,value:t,removedItem:r,removedIndex:n,movedItem:s,movedFromIndex:s?o:null});}removeByFieldValues(e,t){if(t.length===0)return;let i=this.getOrCreateIndexMap(e),n=[];for(let o=0;o<t.length;o++){let r=t[o],s=i.get(r);if(s===void 0)continue;let d=this.originData.length-1,u=this.originData[s],a=s===d?null:this.originData[d];s!==d&&a&&(this.originData[s]=a,this.itemIndexLookup.set(a,s)),this.originData.pop(),this.itemIndexLookup.delete(u);for(let[l,p]of this.indexMaps){let h=u[l];p.get(h)===s&&p.delete(h),a&&p.set(a[l],s);}n.push({value:r,removedItem:u,removedIndex:s,movedItem:a,movedFromIndex:a?d:null});}n.length!==0&&(this.bumpMutationVersion(),this.emit({type:"removeMany",field:e,entries:n}));}emit(e){for(let t of this.listeners)t(e);}getOrCreateScope(e){let t=this.scopedRegistry.get(e);if(t)return t;let i=new Map;return this.scopedRegistry.set(e,i),i}bumpMutationVersion(){this.mutationVersion+=1,this.clearPreviousResult();}getOrCreateIndexMap(e){let t=this.indexMaps.get(e);return t||this.rebuildIndexMap(e)}rebuildIndexMap(e){let t=new Map;for(let i=0;i<this.originData.length;i++)t.set(this.originData[i][e],i);return this.indexMaps.set(e,t),t}rebuildItemIndexLookup(){this.itemIndexLookup=new WeakMap;for(let e=0;e<this.originData.length;e++)this.itemIndexLookup.set(this.originData[e],e);}};var m="__merge__";export{g as a,m as b};
|
package/dist/filter/index.d.mts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { S as State } from '../State-2vKzwNrQ.mjs';
|
|
2
|
+
import { C as CollectionItem, F as FilterCriterion, U as UpdateDescriptor } from '../types-DONld7xY.mjs';
|
|
3
|
+
export { I as IndexableKey } from '../types-DONld7xY.mjs';
|
|
3
4
|
|
|
4
5
|
interface FilterEngineChain<T extends CollectionItem> {
|
|
5
6
|
filter(criteria: FilterCriterion<T>[]): T[] & FilterEngineChain<T>;
|
|
6
7
|
filter(data: T[], criteria: FilterCriterion<T>[]): T[] & FilterEngineChain<T>;
|
|
7
8
|
getOriginData(): T[];
|
|
8
9
|
add(items: T[]): FilterEngine<T>;
|
|
10
|
+
update(descriptor: UpdateDescriptor<T>): FilterEngine<T>;
|
|
9
11
|
data(data: T[]): FilterEngine<T>;
|
|
10
12
|
clearIndexes(): FilterEngine<T>;
|
|
11
13
|
clearData(): FilterEngine<T>;
|
|
@@ -25,23 +27,27 @@ interface FilterEngineOptions<T extends CollectionItem = CollectionItem> {
|
|
|
25
27
|
*/
|
|
26
28
|
|
|
27
29
|
declare class FilterEngine<T extends CollectionItem> {
|
|
28
|
-
private indexer;
|
|
30
|
+
private readonly indexer;
|
|
29
31
|
private readonly filterByPreviousResult;
|
|
30
32
|
private readonly mutableExcludeField;
|
|
31
|
-
private
|
|
32
|
-
private readonly
|
|
33
|
-
private hasDuplicateMutableExcludeValues;
|
|
34
|
-
private readonly indexedFields;
|
|
33
|
+
private readonly state;
|
|
34
|
+
private readonly namespace;
|
|
35
35
|
private readonly nestedCollection;
|
|
36
|
-
private previousResult;
|
|
37
|
-
private previousCriteria;
|
|
38
|
-
private previousBaseData;
|
|
39
|
-
private readonly previousResultsByCriteria;
|
|
40
36
|
private readonly chainBuilder;
|
|
41
37
|
/**
|
|
42
38
|
* Creates a new FilterEngine with optional data and fields to index.
|
|
43
39
|
*/
|
|
44
|
-
constructor(options?: FilterEngineOptions<T>
|
|
40
|
+
constructor(options?: FilterEngineOptions<T> & {
|
|
41
|
+
state?: State<T>;
|
|
42
|
+
});
|
|
43
|
+
private get dataset();
|
|
44
|
+
private get runtime();
|
|
45
|
+
private get mutableExcludeState();
|
|
46
|
+
private get indexedFields();
|
|
47
|
+
private get sequentialCache();
|
|
48
|
+
private shouldDeferMutationIndexUpdates;
|
|
49
|
+
private markDeferredMutationState;
|
|
50
|
+
private ensureRuntimeReady;
|
|
45
51
|
private rebuildConfiguredIndexes;
|
|
46
52
|
/**
|
|
47
53
|
* Builds an index for the given field.
|
|
@@ -52,6 +58,7 @@ declare class FilterEngine<T extends CollectionItem> {
|
|
|
52
58
|
clearData(): this;
|
|
53
59
|
data(data: T[]): this;
|
|
54
60
|
add(items: T[]): this;
|
|
61
|
+
update(descriptor: UpdateDescriptor<T>): this;
|
|
55
62
|
private applyAddedItems;
|
|
56
63
|
getOriginData(): T[];
|
|
57
64
|
/**
|
|
@@ -61,11 +68,19 @@ declare class FilterEngine<T extends CollectionItem> {
|
|
|
61
68
|
filter(data: T[], criteria: FilterCriterion<T>[]): T[] & FilterEngineChain<T>;
|
|
62
69
|
rawFilter(criteria: FilterCriterion<T>[]): T[];
|
|
63
70
|
rawFilter(data: T[], criteria: FilterCriterion<T>[]): T[];
|
|
71
|
+
private resolveWithSequentialCache;
|
|
64
72
|
private withChain;
|
|
73
|
+
private handleStateMutation;
|
|
65
74
|
private rebuildMutableExcludeState;
|
|
66
75
|
private updateMutableExcludeStateForAddedItems;
|
|
67
76
|
private applyMutableExclude;
|
|
68
|
-
private
|
|
77
|
+
private applyUpdatedItem;
|
|
78
|
+
private applyRemovedItem;
|
|
79
|
+
private applyRemovedItems;
|
|
80
|
+
private updateMutableExcludeStateForUpdatedItem;
|
|
81
|
+
private clearMutableExcludeState;
|
|
82
|
+
private registerMutableExcludeValue;
|
|
83
|
+
private unregisterMutableExcludeValue;
|
|
69
84
|
/**
|
|
70
85
|
* Filters data linearly without index.
|
|
71
86
|
*/
|
|
@@ -82,6 +97,7 @@ declare class FilterEngine<T extends CollectionItem> {
|
|
|
82
97
|
private createEmptyResult;
|
|
83
98
|
private storePreviousResult;
|
|
84
99
|
private createCriteriaCacheKey;
|
|
100
|
+
private createSequentialCacheEntry;
|
|
85
101
|
private createSequentialExecutionCriteria;
|
|
86
102
|
private hasCriteriaBacktrack;
|
|
87
103
|
private canApplySequentiallyToEmptyResult;
|