@devisfuture/mega-collection 2.3.3 → 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 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
- Sometimes the server returns a very large array, for example 100K+ items.
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
- This package helps do these operations faster than plain JavaScript methods.
45
- Usually this happens before data is shown in the UI.
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 | Complexity |
57
- | ---------------------------- | -------------------------------------- | ---------------------------------- |
58
- | **Indexed filter** | Hash-Map index (`Map<value, T[]>`) | **O(1)** |
59
- | **Multi-value filter** | Index intersection + `Set` membership | **O(k)** indexed / **O(n)** linear |
60
- | **Nested collection filter** | Pre-built nested index + `Set` lookup | **O(k)** indexed / **O(n)** linear |
61
- | **Text search** (contains) | Trigram inverted index + verify | **O(candidates)** |
62
- | **Nested collection search** | Nested trigram index + verify | **O(candidates)** |
63
- | **Sorting** | Pre-sorted index (cached) / V8 TimSort | **O(n)** cached / **O(n log n)** |
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 stores its own data and indexes, so they do not affect each other.
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"], filterByPreviousResult: true },
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, the engine updates only the new items instead of rebuilding the whole dataset.
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 | Type | Description |
515
- | --------- | -------------------------------------------------------------------------- | -------------------------------------------- |
516
- | `imports` | `(typeof TextSearchEngine \| SortEngine \| FilterEngine)[]` | Engine classes to create |
517
- | `data` | `T[]` | Shared dataset — passed once at construction |
518
- | `search` | `{ fields, nestedFields?, minQueryLength? }` | Config for TextSearchEngine |
519
- | `filter` | `{ fields, nestedFields?, filterByPreviousResult?, mutableExcludeField? }` | Config for FilterEngine |
520
- | `sort` | `{ fields }` | Config for SortEngine |
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 data for one module (`"search"`, `"sort"`, `"filter"`) |
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
- | Method | Description |
550
- | ---------------------- | ---------------------------------------------------------- |
551
- | `search(query)` | Search all indexed fields (including nested), deduplicated |
552
- | `search(field, query)` | Search a specific indexed field or nested field path |
553
- | `getOriginData()` | Get the original stored dataset |
554
- | `add(items)` | Append multiple items to the stored dataset |
555
- | `data(data)` | Replace stored dataset and rebuild configured indexes |
556
- | `clearIndexes()` | Clear n-gram indexes (including nested) |
557
- | `clearData()` | Clear stored data |
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 | Description |
574
- | ------------------------ | -------------------------------------------------------------------------- |
575
- | `filter(criteria)` | Filter using stored dataset (supports nested field criteria) |
576
- | `filter(data, criteria)` | Filter with an explicit dataset |
577
- | `getOriginData()` | Get the original stored dataset |
578
- | `add(items)` | Append multiple items to the stored dataset |
579
- | `data(data)` | Replace stored dataset, rebuild configured indexes, and reset filter state |
580
- | `resetFilterState()` | Reset previous-result state for sequential filtering |
581
- | `clearIndexes()` | Free all index memory (including nested indexes) |
582
- | `clearData()` | Clear stored data |
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};
@@ -1,11 +1,13 @@
1
- import { C as CollectionItem, F as FilterCriterion } from '../types-4YEflcxU.mjs';
2
- export { I as IndexableKey } from '../types-4YEflcxU.mjs';
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 dataset;
32
- private readonly datasetPositions;
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 removeStoredItem;
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;