@devisfuture/mega-collection 2.3.5 → 2.4.1
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 +216 -44
- 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.
|
|
@@ -53,14 +64,67 @@ Unused modules are not included.
|
|
|
53
64
|
|
|
54
65
|
## Features
|
|
55
66
|
|
|
56
|
-
| Capability | Strategy
|
|
57
|
-
| ---------------------------- |
|
|
58
|
-
| **Indexed filter** | Hash-Map index (`Map<value, T[]>`)
|
|
59
|
-
| **Multi-value filter** | Index intersection + `Set` membership
|
|
60
|
-
| **Nested collection filter** | Pre-built nested index + `Set` lookup
|
|
61
|
-
| **Text search** (contains) |
|
|
62
|
-
| **Nested collection search** | Nested
|
|
63
|
-
| **Sorting** | Pre-sorted index (cached) /
|
|
67
|
+
| Capability | Strategy | Complexity |
|
|
68
|
+
| ---------------------------- | ------------------------------------------ | ---------------------------------- |
|
|
69
|
+
| **Indexed filter** | Hash-Map index (`Map<value, T[]>`) | **O(1)** |
|
|
70
|
+
| **Multi-value filter** | Index intersection + `Set` membership | **O(k)** indexed / **O(n)** linear |
|
|
71
|
+
| **Nested collection filter** | Pre-built nested index + `Set` lookup | **O(k)** indexed / **O(n)** linear |
|
|
72
|
+
| **Text search** (contains) | N-gram (2–3 chars) inverted index + verify | **O(candidates)** |
|
|
73
|
+
| **Nested collection search** | Nested n-gram index + verify | **O(candidates)** |
|
|
74
|
+
| **Sorting** | Pre-sorted index (cached) / radix sort | **O(n)** cached / **O(n log n)** |
|
|
75
|
+
|
|
76
|
+
## How it works
|
|
77
|
+
|
|
78
|
+
### Search
|
|
79
|
+
|
|
80
|
+
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.
|
|
81
|
+
|
|
82
|
+
`TextSearchEngine` avoids this by building an **n-gram inverted index** upfront:
|
|
83
|
+
|
|
84
|
+
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"`.
|
|
85
|
+
2. For every n-gram the engine keeps a set of item positions that contain it.
|
|
86
|
+
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.
|
|
87
|
+
4. Each surviving candidate is checked with a fast `String.includes` to confirm the full substring match.
|
|
88
|
+
|
|
89
|
+
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.
|
|
90
|
+
|
|
91
|
+
### Filter
|
|
92
|
+
|
|
93
|
+
Native `Array.prototype.filter` with `===` still checks every item on every call.
|
|
94
|
+
|
|
95
|
+
`FilterEngine` builds a **hash-map** for each indexed field:
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
field "city" → { "New York": [item0, item4, ...], "Miami": [item1, ...], ... }
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
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`.
|
|
102
|
+
|
|
103
|
+
When the `fields` option is not provided, the engine falls back to a linear scan — which works but is slower.
|
|
104
|
+
|
|
105
|
+
### Sort
|
|
106
|
+
|
|
107
|
+
Native `Array.prototype.sort` re-sorts the whole array from scratch every call.
|
|
108
|
+
|
|
109
|
+
`SortEngine` pre-sorts and stores results in a `Uint32Array` of positions:
|
|
110
|
+
|
|
111
|
+
```
|
|
112
|
+
cache["age"] = [index of youngest item, index of next, ..., index of oldest]
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
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.
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Benchmarks
|
|
120
|
+
|
|
121
|
+
Benchmarks for `TextSearchEngine`, `FilterEngine`, and `SortEngine` are collected in [`BENCHMARKS`](./BENCHMARKS.md).
|
|
122
|
+
|
|
123
|
+
Run the benchmark scripts locally to regenerate the numbers:
|
|
124
|
+
|
|
125
|
+
- `npm run search-bench`
|
|
126
|
+
- `npm run filter-bench`
|
|
127
|
+
- `npm run sort-bench`
|
|
64
128
|
|
|
65
129
|
## React demo
|
|
66
130
|
|
|
@@ -103,12 +167,12 @@ Use `MergeEngines` when you want one class that works with one dataset.
|
|
|
103
167
|
Add needed engines to `imports`. Only those engines will be created.
|
|
104
168
|
|
|
105
169
|
You can create many engine instances in one project for different collections.
|
|
106
|
-
Each instance
|
|
170
|
+
Each instance keeps its own dataset and runtime indexes inside an internal shared `State`, so separate instances do not affect each other.
|
|
107
171
|
|
|
108
172
|
Each engine can receive an optional `fields` array through `search`, `filter`, or `sort` options.
|
|
109
173
|
These fields are used for indexes.
|
|
110
174
|
|
|
111
|
-
Indexes are built lazily on first use, so engine creation stays fast.
|
|
175
|
+
Indexes are built lazily on first use inside that shared state, so engine creation stays fast.
|
|
112
176
|
If you skip `fields`, everything still works, but the engine may scan the full array.
|
|
113
177
|
|
|
114
178
|
```ts
|
|
@@ -120,8 +184,9 @@ import { FilterEngine } from "@devisfuture/mega-collection/filter";
|
|
|
120
184
|
const engine = new MergeEngines<User>({
|
|
121
185
|
imports: [TextSearchEngine, SortEngine, FilterEngine],
|
|
122
186
|
data: users,
|
|
187
|
+
filterByPreviousResult: true,
|
|
123
188
|
search: { fields: ["name", "city"], minQueryLength: 2 },
|
|
124
|
-
filter: { fields: ["city", "age"]
|
|
189
|
+
filter: { fields: ["city", "age"] },
|
|
125
190
|
sort: { fields: ["age", "name", "city"] },
|
|
126
191
|
});
|
|
127
192
|
|
|
@@ -137,10 +202,19 @@ engine
|
|
|
137
202
|
.sort([{ field: "age", direction: "asc" }])
|
|
138
203
|
.filter([{ field: "city", values: ["Miami", "New York"] }]);
|
|
139
204
|
|
|
205
|
+
// Separate calls also continue from the last result when
|
|
206
|
+
// `filterByPreviousResult` is enabled on MergeEngines.
|
|
207
|
+
const searchResult = engine.search("john");
|
|
208
|
+
const filteredResult = engine.filter([
|
|
209
|
+
{ field: "city", values: ["Miami", "New York"] },
|
|
210
|
+
]);
|
|
211
|
+
const sortedResult = engine.sort([{ field: "age", direction: "asc" }]);
|
|
212
|
+
|
|
140
213
|
// Example with nested fields, for example `orders` inside each user.
|
|
141
214
|
const nestedEngine = new MergeEngines<UserWithOrders>({
|
|
142
215
|
imports: [TextSearchEngine, SortEngine, FilterEngine],
|
|
143
216
|
data: usersWithOrders,
|
|
217
|
+
filterByPreviousResult: true,
|
|
144
218
|
search: {
|
|
145
219
|
fields: ["name", "city"],
|
|
146
220
|
nestedFields: ["orders.status"],
|
|
@@ -149,7 +223,6 @@ const nestedEngine = new MergeEngines<UserWithOrders>({
|
|
|
149
223
|
filter: {
|
|
150
224
|
fields: ["city", "age"],
|
|
151
225
|
nestedFields: ["orders.status"],
|
|
152
|
-
filterByPreviousResult: true,
|
|
153
226
|
},
|
|
154
227
|
sort: { fields: ["age", "name", "city"] },
|
|
155
228
|
});
|
|
@@ -188,7 +261,12 @@ This is different from `data(...)`:
|
|
|
188
261
|
- `data(...)` replaces the whole stored dataset.
|
|
189
262
|
- `add([])` appends new items to the existing stored dataset.
|
|
190
263
|
|
|
191
|
-
If indexes are already built,
|
|
264
|
+
If indexes are already built, `add()` updates them incrementally for the new items only:
|
|
265
|
+
|
|
266
|
+
- **TextSearchEngine / FilterEngine**: O(k) — only the new items are written into the n-gram or hash-map index (existing index entries are untouched).
|
|
267
|
+
- **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.
|
|
268
|
+
|
|
269
|
+
If indexes have not been built yet (first `sort()` has not been called), `add()` appends the items without touching any index.
|
|
192
270
|
If you cleared indexes with `clearIndexes()`, `add([])` does not rebuild them automatically.
|
|
193
271
|
|
|
194
272
|
```ts
|
|
@@ -255,6 +333,62 @@ sortEngine.add([
|
|
|
255
333
|
|
|
256
334
|
---
|
|
257
335
|
|
|
336
|
+
### Update items with `update(...)`
|
|
337
|
+
|
|
338
|
+
Use `update(...)` when you need to replace one stored item by a unique field such as `id`.
|
|
339
|
+
|
|
340
|
+
- `update(...)` keeps the same stored array reference.
|
|
341
|
+
- `update(...)` replaces only the matched item in stored data.
|
|
342
|
+
- configured indexes or caches refresh only the affected item instead of rebuilding the whole dataset.
|
|
343
|
+
|
|
344
|
+
> **Notes on `update()`:**
|
|
345
|
+
>
|
|
346
|
+
> - The lookup field value in `data[field]` must already exist in the stored dataset. If it is `null`, `undefined`, or not found, `update()` is a silent no-op — no error is thrown and no data is changed.
|
|
347
|
+
> - The lookup field value must not change between the old and new item. For example, calling `update({ field: 'id', data: { id: 99, ... } })` when no item has `id: 99` will do nothing. Always use the current value of the lookup field.
|
|
348
|
+
> - When using `FilterEngine` with `filterByPreviousResult: true`, every `update()` resets the sequential criteria cache, so the next `filter()` will re-evaluate from the full dataset.
|
|
349
|
+
|
|
350
|
+
```ts
|
|
351
|
+
import { MergeEngines } from "@devisfuture/mega-collection";
|
|
352
|
+
import { TextSearchEngine } from "@devisfuture/mega-collection/search";
|
|
353
|
+
import { SortEngine } from "@devisfuture/mega-collection/sort";
|
|
354
|
+
import { FilterEngine } from "@devisfuture/mega-collection/filter";
|
|
355
|
+
|
|
356
|
+
const merge = new MergeEngines<User>({
|
|
357
|
+
imports: [TextSearchEngine, SortEngine, FilterEngine],
|
|
358
|
+
data: users,
|
|
359
|
+
search: { fields: ["name", "city"], minQueryLength: 2 },
|
|
360
|
+
filter: { fields: ["city", "age"] },
|
|
361
|
+
sort: { fields: ["age", "name"] },
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
merge.update({
|
|
365
|
+
field: "id",
|
|
366
|
+
data: { id: 2, name: "Bob", city: "Paris", age: 19 },
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
merge.search("Paris");
|
|
370
|
+
merge.filter([{ field: "city", values: ["Paris"] }]);
|
|
371
|
+
merge.sort([{ field: "age", direction: "asc" }]);
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
The same method works in each engine:
|
|
375
|
+
|
|
376
|
+
```ts
|
|
377
|
+
import { TextSearchEngine } from "@devisfuture/mega-collection/search";
|
|
378
|
+
|
|
379
|
+
const searchEngine = new TextSearchEngine<User>({
|
|
380
|
+
data: users,
|
|
381
|
+
fields: ["name", "city"],
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
searchEngine.update({
|
|
385
|
+
field: "id",
|
|
386
|
+
data: { id: 2, name: "Bob", city: "Paris", age: 19 },
|
|
387
|
+
});
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
---
|
|
391
|
+
|
|
258
392
|
### Search only
|
|
259
393
|
|
|
260
394
|
Use `TextSearchEngine` when you only need text search.
|
|
@@ -279,10 +413,17 @@ const engine = new TextSearchEngine<User>({
|
|
|
279
413
|
|
|
280
414
|
engine.search("john"); // searches all indexed fields, deduplicated
|
|
281
415
|
engine.search("name", "john"); // searches a specific field
|
|
416
|
+
engine.search("john", { limit: 20, offset: 20 }); // paginate broad result sets
|
|
282
417
|
|
|
283
418
|
// replace dataset without re-initializing
|
|
284
419
|
engine.data(users);
|
|
285
420
|
|
|
421
|
+
// replace one stored item by unique field
|
|
422
|
+
engine.update({
|
|
423
|
+
field: "id",
|
|
424
|
+
data: { id: 2, name: "Bob", city: "Paris", age: 19 },
|
|
425
|
+
});
|
|
426
|
+
|
|
286
427
|
// access original dataset stored in the engine
|
|
287
428
|
engine.getOriginData();
|
|
288
429
|
|
|
@@ -335,6 +476,12 @@ engine.filter([
|
|
|
335
476
|
// Replace dataset without creating a new engine.
|
|
336
477
|
engine.data(users);
|
|
337
478
|
|
|
479
|
+
// Replace one stored item by unique field.
|
|
480
|
+
engine.update({
|
|
481
|
+
field: "id",
|
|
482
|
+
data: { id: 2, name: "Bob", city: "Paris", age: 19, active: true },
|
|
483
|
+
});
|
|
484
|
+
|
|
338
485
|
// Get original stored dataset.
|
|
339
486
|
engine.getOriginData();
|
|
340
487
|
|
|
@@ -487,6 +634,12 @@ engine.sort([{ field: "age", direction: "asc" }]);
|
|
|
487
634
|
// replace dataset without re-initializing
|
|
488
635
|
engine.data(users);
|
|
489
636
|
|
|
637
|
+
// replace one stored item by unique field
|
|
638
|
+
engine.update({
|
|
639
|
+
field: "id",
|
|
640
|
+
data: { id: 2, name: "Bob", city: "Paris", age: 19 },
|
|
641
|
+
});
|
|
642
|
+
|
|
490
643
|
// access original dataset stored in the engine
|
|
491
644
|
engine.getOriginData();
|
|
492
645
|
|
|
@@ -511,13 +664,14 @@ One class that combines search, filter, and sort for the same dataset.
|
|
|
511
664
|
|
|
512
665
|
**Constructor options:**
|
|
513
666
|
|
|
514
|
-
| Option
|
|
515
|
-
|
|
|
516
|
-
| `imports`
|
|
517
|
-
| `data`
|
|
518
|
-
| `
|
|
519
|
-
| `
|
|
520
|
-
| `
|
|
667
|
+
| Option | Type | Description |
|
|
668
|
+
| ------------------------ | ----------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
|
|
669
|
+
| `imports` | `(typeof TextSearchEngine \| SortEngine \| FilterEngine)[]` | Engine classes to create |
|
|
670
|
+
| `data` | `T[]` | Shared dataset — passed once at construction |
|
|
671
|
+
| `filterByPreviousResult` | `boolean` | When `true`, separate `filter(...)` and `sort(...)` calls continue from the last result stored in shared State |
|
|
672
|
+
| `search` | `{ fields, nestedFields?, minQueryLength? }` | Config for TextSearchEngine |
|
|
673
|
+
| `filter` | `{ fields, nestedFields?, mutableExcludeField? }` | Config for FilterEngine |
|
|
674
|
+
| `sort` | `{ fields }` | Config for SortEngine |
|
|
521
675
|
|
|
522
676
|
**Methods:**
|
|
523
677
|
|
|
@@ -531,13 +685,16 @@ One class that combines search, filter, and sort for the same dataset.
|
|
|
531
685
|
| `filter(data, criteria)` | Filter with an explicit dataset |
|
|
532
686
|
| `getOriginData()` | Get the shared original dataset |
|
|
533
687
|
| `add(items)` | Append multiple items to the stored dataset and update existing indexes or caches for new items only |
|
|
688
|
+
| `update({ field, data })` | Replace one stored item by a unique field and refresh only the affected cached or indexed data |
|
|
534
689
|
| `data(data)` | Replace stored dataset for all imported modules, rebuilding configured indexes and resetting filter state where applicable |
|
|
535
690
|
| `clearIndexes(module)` | Clear indexes for one module (`"search"`, `"sort"`, `"filter"`) |
|
|
536
|
-
| `clearData(module)` | Clear stored
|
|
691
|
+
| `clearData(module)` | Clear the shared stored dataset through one imported module (`"search"`, `"sort"`, `"filter"`) |
|
|
537
692
|
|
|
538
693
|
If `filter.mutableExcludeField` is configured, `filter([{ field, exclude }])` on that field removes items from the stored filter dataset with swap-pop.
|
|
539
694
|
This changes the stored filter dataset and does not preserve order.
|
|
540
695
|
|
|
696
|
+
`filter.filterByPreviousResult` is not supported inside `MergeEngines`. Use the root `filterByPreviousResult` option instead.
|
|
697
|
+
|
|
541
698
|
---
|
|
542
699
|
|
|
543
700
|
### `TextSearchEngine<T>` (search module)
|
|
@@ -546,15 +703,27 @@ Text search engine.
|
|
|
546
703
|
It supports `nestedFields` if you need to search inside nested collections such as `["orders.status"]`.
|
|
547
704
|
Search methods return plain arrays.
|
|
548
705
|
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
|
552
|
-
|
|
|
553
|
-
| `
|
|
554
|
-
| `
|
|
555
|
-
|
|
556
|
-
|
|
|
557
|
-
|
|
|
706
|
+
Main constructor options:
|
|
707
|
+
|
|
708
|
+
| Option | Type | Description |
|
|
709
|
+
| ------------------------ | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
710
|
+
| `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. |
|
|
711
|
+
| `nestedFields` | `string[]` | Nested field paths in dot notation, for example `["orders.status"]`. |
|
|
712
|
+
|
|
713
|
+
| Method | Description |
|
|
714
|
+
| -------------------------------- | -------------------------------------------------------------------- |
|
|
715
|
+
| `search(query, options?)` | Search all indexed fields (including nested), deduplicated |
|
|
716
|
+
| `search(field, query, options?)` | Search a specific indexed field or nested field path |
|
|
717
|
+
| `searchAll(query, options?)` | Explicit all-fields alias when you want pagination on broad searches |
|
|
718
|
+
| `resetSearchState()` | Reset previous-result state for sequential narrowing search |
|
|
719
|
+
| `getOriginData()` | Get the original stored dataset |
|
|
720
|
+
| `add(items)` | Append multiple items to the stored dataset |
|
|
721
|
+
| `update({ field, data })` | Replace one stored item by a unique field |
|
|
722
|
+
| `data(data)` | Replace stored dataset and rebuild configured indexes |
|
|
723
|
+
| `clearIndexes()` | Clear n-gram indexes (including nested) |
|
|
724
|
+
| `clearData()` | Clear stored data |
|
|
725
|
+
|
|
726
|
+
`options.limit` and `options.offset` are useful for broad result sets where you only need the current page.
|
|
558
727
|
|
|
559
728
|
### `FilterEngine<T>` (filter module)
|
|
560
729
|
|
|
@@ -570,16 +739,17 @@ Main constructor options:
|
|
|
570
739
|
| `mutableExcludeField` | `string` | Optional field for removing items from stored data with swap-pop. This changes the stored dataset and does not preserve order. |
|
|
571
740
|
| `nestedFields` | `string[]` | Nested field paths in dot notation, for example `["orders.status"]`. |
|
|
572
741
|
|
|
573
|
-
| Method
|
|
574
|
-
|
|
|
575
|
-
| `filter(criteria)`
|
|
576
|
-
| `filter(data, criteria)`
|
|
577
|
-
| `getOriginData()`
|
|
578
|
-
| `add(items)`
|
|
579
|
-
| `
|
|
580
|
-
| `
|
|
581
|
-
| `
|
|
582
|
-
| `
|
|
742
|
+
| Method | Description |
|
|
743
|
+
| ------------------------- | -------------------------------------------------------------------------- |
|
|
744
|
+
| `filter(criteria)` | Filter using stored dataset (supports nested field criteria) |
|
|
745
|
+
| `filter(data, criteria)` | Filter with an explicit dataset |
|
|
746
|
+
| `getOriginData()` | Get the original stored dataset |
|
|
747
|
+
| `add(items)` | Append multiple items to the stored dataset |
|
|
748
|
+
| `update({ field, data })` | Replace one stored item by a unique field |
|
|
749
|
+
| `data(data)` | Replace stored dataset, rebuild configured indexes, and reset filter state |
|
|
750
|
+
| `resetFilterState()` | Reset previous-result state for sequential filtering |
|
|
751
|
+
| `clearIndexes()` | Free all index memory (including nested indexes) |
|
|
752
|
+
| `clearData()` | Clear stored data |
|
|
583
753
|
|
|
584
754
|
### `SortEngine<T>` (sort module)
|
|
585
755
|
|
|
@@ -592,6 +762,7 @@ Sort methods return plain arrays.
|
|
|
592
762
|
| `sort(data, descriptors, inPlace?)` | Sort with an explicit dataset |
|
|
593
763
|
| `getOriginData()` | Get the original stored dataset |
|
|
594
764
|
| `add(items)` | Append multiple items to the stored dataset |
|
|
765
|
+
| `update({ field, data })` | Replace one stored item by a unique field |
|
|
595
766
|
| `data(data)` | Replace stored dataset and rebuild configured indexes |
|
|
596
767
|
| `clearIndexes()` | Free all cached indexes |
|
|
597
768
|
| `clearData()` | Clear stored data |
|
|
@@ -611,6 +782,7 @@ import type {
|
|
|
611
782
|
FilterCriterion,
|
|
612
783
|
SortDescriptor,
|
|
613
784
|
SortDirection,
|
|
785
|
+
UpdateDescriptor,
|
|
614
786
|
MergeEnginesOptions,
|
|
615
787
|
} from "@devisfuture/mega-collection";
|
|
616
788
|
```
|
|
@@ -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;
|