@devisfuture/mega-collection 1.0.18 → 1.1.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
@@ -60,6 +60,12 @@ interface User {
60
60
  Use `MergeEngines` to combine search, filter and sort around a single shared dataset.
61
61
  Declare which engines you need in `imports` — only those are initialised.
62
62
 
63
+ Each engine accepts an optional `fields` array (set via the `search`,
64
+ `filter` or `sort` option) which tells it which properties should be indexed up
65
+ front. Indexes power the fast paths used throughout the library; you can leave
66
+ these options out and everything still works, but the code will fall back to
67
+ simple linear scans.
68
+
63
69
  ```ts
64
70
  import { MergeEngines } from "@devisfuture/mega-collection";
65
71
  import { TextSearchEngine } from "@devisfuture/mega-collection/search";
@@ -87,6 +93,9 @@ engine.filter([{ field: "city", values: ["Kyiv", "Lviv"] }]);
87
93
  ```ts
88
94
  import { TextSearchEngine } from "@devisfuture/mega-collection/search";
89
95
 
96
+ // `fields` tells the engine which properties to index for fast lookups. The
97
+ // index is built once during construction; if you omit `fields` the engine
98
+ // still works but every search will scan the entire dataset.
90
99
  const engine = new TextSearchEngine<User>({
91
100
  data: users,
92
101
  fields: ["name", "city"],
@@ -102,6 +111,10 @@ engine.search("name", "john"); // searches a specific field
102
111
  ```ts
103
112
  import { FilterEngine } from "@devisfuture/mega-collection/filter";
104
113
 
114
+ // `fields` config tells the filter engine which properties should have an
115
+ // index built. Indexed lookups are O(1) per value, so multi-criteria queries
116
+ // can be orders of magnitude faster. Without `fields` the engine still filters
117
+ // correctly but always does a linear scan.
105
118
  const engine = new FilterEngine<User>({
106
119
  data: users,
107
120
  fields: ["city", "age"],
@@ -125,6 +138,11 @@ const byCityAndAge = engine.filter([{ field: "age", values: [22] }]);
125
138
  ```ts
126
139
  import { SortEngine } from "@devisfuture/mega-collection/sort";
127
140
 
141
+ // `fields` instructs the engine to pre-build a sorted index for each property.
142
+ // When you later run a single-field sort the result can be pulled directly
143
+ // from that index in linear time. If you leave out `fields` the engine still
144
+ // sorts correctly, it merely falls back to standard `Array.prototype.sort`
145
+ // (O(n log n)).
128
146
  const engine = new SortEngine<User>({
129
147
  data: users,
130
148
  fields: ["age", "name", "city"],
@@ -0,0 +1 @@
1
+ var l=class{constructor(n){let{imports:t,data:e,...i}=n,s=new Set(t),r={};for(let o of s){let d=o.prototype,a=this.getMethodNames(d);if(a.length===0)continue;let u=this.getModuleInitOptions(o.name,a,i),g=new o({data:e,...u});for(let c of a)r[c]||this.hasMethod(g,c)&&(r[c]=g[c].bind(g));}this.engine=Object.keys(r).length>0?r:null;}getModuleInitOptions(n,t,e){let i={},s=e[n];this.isRecord(s)&&Object.assign(i,s);for(let r of t){let o=e[r];this.isRecord(o)&&Object.assign(i,o);}return i}getMethodNames(n){let t=n;return Object.getOwnPropertyNames(t).filter(e=>e==="constructor"?false:typeof t[e]=="function")}hasMethod(n,t){return typeof n=="object"&&n!==null&&typeof n[t]=="function"}isRecord(n){return typeof n=="object"&&n!==null}callEngineMethod(n,t){let e=this.engine?.[n];if(!e)throw new Error(`MergeEngines: Method "${n}" is not available. Add module with method "${n}" to the \`imports\` array.`);return e(...t)}search(n,t){if(!this.engine?.search)throw new Error("MergeEngines: TextSearchEngine is not available. Add TextSearchEngine to the `imports` array.");return t===void 0?this.callEngineMethod("search",[n]):this.callEngineMethod("search",[n,t])}sort(n,t,e){if(!this.engine?.sort)throw new Error("MergeEngines: SortEngine is not available. Add SortEngine to the `imports` array.");return t===void 0?this.callEngineMethod("sort",[n]):this.callEngineMethod("sort",[n,t,e])}filter(n,t){if(!this.engine?.filter)throw new Error("MergeEngines: FilterEngine is not available. Add FilterEngine to the `imports` array.");return t===void 0?this.callEngineMethod("filter",[n]):this.callEngineMethod("filter",[n,t])}};export{l as a};
@@ -0,0 +1 @@
1
+ var T=3,f=12;function x(h){let e=h.toLowerCase(),n=Math.min(T,e.length),t=e.length-n+1,r=new Array(t);for(let s=0;s<t;s++)r[s]=e.substring(s,s+n);return r}function m(h){let e=x(h);if(e.length<=f)return new Set(e);let n=new Set,t=e.length-1,r=f-1;for(let s=0;s<=r;s++){let i=Math.round(s*t/r);n.add(e[i]);}return n}function L(h,e){let n=h.get(e);if(n)return n;let t=new Set;return h.set(e,t),t}var y=class{constructor(e={}){this.ngramIndexes=new Map;this.data=[];if(this.minQueryLength=e.minQueryLength??1,!!e.data&&(this.data=e.data,!!e.fields?.length))for(let n of e.fields)this.buildIndex(e.data,n);}buildIndex(e,n){let t,r;if(Array.isArray(e))t=e,r=n;else {if(!this.data.length)throw new Error("TextSearchEngine: no dataset in memory. Either pass `data` in the constructor options, or call buildIndex(data, field).");t=this.data,r=e;}this.data=t;let s=new Map;for(let i=0,u=t.length;i<u;i++){let l=t[i][r];if(typeof l!="string")continue;let o=l.toLowerCase();for(let a=0,d=o.length;a<d;a++){let c=d-a,p=Math.min(T,c);for(let g=1;g<=p;g++){let I=o.substring(a,a+g);L(s,I).add(i);}}}return this.ngramIndexes.set(r,s),this}search(e,n){return n===void 0?this.searchAllFields(e):this.searchField(e,n)}normalizeQuery(e){return e.trim().toLowerCase()}isQuerySearchable(e){return !(!e||e.length<this.minQueryLength)}searchAllFields(e){let n=[...this.ngramIndexes.keys()],t=this.normalizeQuery(e);if(!this.isQuerySearchable(t))return [];if(!n.length)return this.searchAllFieldsLinear(t);let r=m(t);if(!r.size)return [];let s=new Set,i=[];for(let u of n)for(let l of this.searchFieldWithPreparedQuery(u,t,r))s.has(l)||(s.add(l),i.push(l));return i}searchField(e,n){let t=this.normalizeQuery(n);if(!this.isQuerySearchable(t))return [];if(!this.ngramIndexes.size)return this.searchFieldLinear(e,t);let r=m(t);return r.size?this.searchFieldWithPreparedQuery(e,t,r):[]}searchFieldWithPreparedQuery(e,n,t){let r=this.ngramIndexes.get(e);if(!r)return [];let s=[];for(let o of t){let a=r.get(o);if(!a)return [];s.push(a);}s.sort((o,a)=>o.size-a.size);let i=s[0],u=s.length,l=[];for(let o of i){let a=true;for(let c=1;c<u;c++)if(!s[c].has(o)){a=false;break}if(!a)continue;let d=this.data[o][e];typeof d=="string"&&d.toLowerCase().includes(n)&&l.push(this.data[o]);}return l}searchAllFieldsLinear(e){if(!this.data.length)return [];let n=[];for(let t=0;t<this.data.length;t++){let r=this.data[t],s=false;for(let i of Object.values(r))if(typeof i=="string"&&i.toLowerCase().includes(e)){s=true;break}s&&n.push(r);}return n}searchFieldLinear(e,n){if(!this.data.length)return [];let t=[];for(let r=0;r<this.data.length;r++){let s=this.data[r][e];typeof s=="string"&&s.toLowerCase().includes(n)&&t.push(this.data[r]);}return t}clear(){this.ngramIndexes.clear(),this.data=[];}};export{y as a};
@@ -18,18 +18,45 @@ declare class FilterEngine<T extends CollectionItem> {
18
18
  private previousResult;
19
19
  private previousCriteria;
20
20
  private previousBaseData;
21
+ /**
22
+ * Creates a new FilterEngine with optional data and fields to index.
23
+ */
21
24
  constructor(options?: FilterEngineOptions<T>);
25
+ /**
26
+ * Builds an index for the given field.
27
+ */
22
28
  private buildIndex;
23
29
  clearIndexes(): void;
24
30
  resetFilterState(): void;
31
+ /**
32
+ * Filters the data based on the given criteria.
33
+ */
25
34
  filter(criteria: FilterCriterion<T>[]): T[];
26
35
  filter(data: T[], criteria: FilterCriterion<T>[]): T[];
27
36
  private cloneCriteria;
37
+ /**
38
+ * Checks if there are new criteria added.
39
+ */
28
40
  private hasCriteriaAdditions;
41
+ /**
42
+ * Checks if there are criteria removed.
43
+ */
29
44
  private hasCriteriaRemovals;
45
+ /**
46
+ * Gets the newly added criteria.
47
+ */
30
48
  private getAddedCriteria;
49
+ /**
50
+ * Filters data linearly without index.
51
+ */
31
52
  private linearFilter;
53
+ /**
54
+ * Filters data using the index.
55
+ */
32
56
  private filterViaIndex;
57
+ /**
58
+ * Estimates the size of the index for a criterion.
59
+ */
33
60
  private estimateIndexSize;
34
61
  }
35
62
 
package/dist/index.d.mts CHANGED
@@ -1,44 +1,5 @@
1
- import { TextSearchEngine, TextSearchEngineOptions } from './search/index.mjs';
2
- import { FilterEngine } from './filter/index.mjs';
3
- import { SortEngine } from './sort/index.mjs';
4
- import { C as CollectionItem, S as SortDescriptor, F as FilterCriterion } from './types-D24zQWME.mjs';
5
- export { I as IndexableKey, a as SortDirection } from './types-D24zQWME.mjs';
6
-
7
- /**
8
- * MergeEngines class that provides a unified interface for text search,
9
- * sorting, and filtering operations on collections.
10
- */
11
-
12
- interface MergeSearchConfig<T extends CollectionItem> {
13
- fields: (keyof T & string)[];
14
- minQueryLength?: TextSearchEngineOptions<T>["minQueryLength"];
15
- }
16
- interface MergeFilterConfig<T extends CollectionItem> {
17
- fields: (keyof T & string)[];
18
- filterByPreviousResult?: boolean;
19
- }
20
- interface MergeSortConfig<T extends CollectionItem> {
21
- fields: (keyof T & string)[];
22
- }
23
- type EngineConstructor = typeof TextSearchEngine | typeof SortEngine | typeof FilterEngine;
24
- interface MergeEnginesOptions<T extends CollectionItem> {
25
- imports: EngineConstructor[];
26
- data: T[];
27
- search?: MergeSearchConfig<T>;
28
- filter?: MergeFilterConfig<T>;
29
- sort?: MergeSortConfig<T>;
30
- }
31
- declare class MergeEngines<T extends CollectionItem> {
32
- private readonly searchEngine;
33
- private readonly sortEngine;
34
- private readonly filterEngine;
35
- constructor(options: MergeEnginesOptions<T>);
36
- search(query: string): T[];
37
- search(field: keyof T & string, query: string): T[];
38
- sort(descriptors: SortDescriptor<T>[]): T[];
39
- sort(data: T[], descriptors: SortDescriptor<T>[], inPlace?: boolean): T[];
40
- filter(criteria: FilterCriterion<T>[]): T[];
41
- filter(data: T[], criteria: FilterCriterion<T>[]): T[];
42
- }
43
-
44
- export { CollectionItem, type EngineConstructor, FilterCriterion, FilterEngine, MergeEngines, type MergeEnginesOptions, type MergeFilterConfig, type MergeSearchConfig, type MergeSortConfig, SortDescriptor, SortEngine, TextSearchEngine };
1
+ export { TextSearchEngine } from './search/index.mjs';
2
+ export { FilterEngine } from './filter/index.mjs';
3
+ export { SortEngine } from './sort/index.mjs';
4
+ export { EngineApi, EngineConstructor, MergeEngines, MergeEnginesOptions } from './merge/index.mjs';
5
+ export { C as CollectionItem, F as FilterCriterion, I as IndexableKey, S as SortDescriptor, a as SortDirection } from './types-D24zQWME.mjs';
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- export{a as MergeEngines}from'./chunk-OE5GVXOE.mjs';export{a as TextSearchEngine}from'./chunk-4TBU4VWK.mjs';export{a as FilterEngine}from'./chunk-XFQ56UZU.mjs';export{a as SortEngine}from'./chunk-DBAABXBP.mjs';
1
+ export{a as TextSearchEngine}from'./chunk-VSCCECO2.mjs';export{a as FilterEngine}from'./chunk-XFQ56UZU.mjs';export{a as SortEngine}from'./chunk-DBAABXBP.mjs';export{a as MergeEngines}from'./chunk-KMSFPZKF.mjs';
@@ -1,5 +1,54 @@
1
- export { EngineConstructor, MergeEngines, MergeEnginesOptions, MergeFilterConfig, MergeSearchConfig, MergeSortConfig } from '../index.mjs';
2
- export { C as CollectionItem, F as FilterCriterion, I as IndexableKey, S as SortDescriptor, a as SortDirection } from '../types-D24zQWME.mjs';
3
- import '../search/index.mjs';
4
- import '../filter/index.mjs';
5
- import '../sort/index.mjs';
1
+ import { C as CollectionItem, S as SortDescriptor, F as FilterCriterion } from '../types-D24zQWME.mjs';
2
+ export { I as IndexableKey, a as SortDirection } from '../types-D24zQWME.mjs';
3
+
4
+ /**
5
+ * MergeEngines class that provides a unified interface for text search,
6
+ * sorting, and filtering operations on collections.
7
+ */
8
+
9
+ interface EngineConstructor {
10
+ new (options: Record<string, unknown>): object;
11
+ prototype: object;
12
+ name: string;
13
+ }
14
+ interface EngineApi {
15
+ [methodName: string]: ((...args: unknown[]) => unknown) | undefined;
16
+ }
17
+ interface MergeEnginesOptions<T extends CollectionItem> {
18
+ imports: EngineConstructor[];
19
+ data: T[];
20
+ [key: string]: unknown;
21
+ }
22
+ declare class MergeEngines<T extends CollectionItem> {
23
+ private readonly engine;
24
+ /**
25
+ * Creates a new MergeEngines instance with the given options.
26
+ * Collects all modules from imports.
27
+ */
28
+ constructor(options: MergeEnginesOptions<T>);
29
+ /**
30
+ * Gets the initialization options for a module.
31
+ */
32
+ private getModuleInitOptions;
33
+ /**
34
+ * Gets the method names from a prototype.
35
+ */
36
+ private getMethodNames;
37
+ /**
38
+ * Checks if an object has a specific method.
39
+ */
40
+ private hasMethod;
41
+ private isRecord;
42
+ /**
43
+ * Calls a method on the engine.
44
+ */
45
+ private callEngineMethod;
46
+ search(query: string): T[];
47
+ search(field: keyof T & string, query: string): T[];
48
+ sort(descriptors: SortDescriptor<T>[]): T[];
49
+ sort(data: T[], descriptors: SortDescriptor<T>[], inPlace?: boolean): T[];
50
+ filter(criteria: FilterCriterion<T>[]): T[];
51
+ filter(data: T[], criteria: FilterCriterion<T>[]): T[];
52
+ }
53
+
54
+ export { CollectionItem, type EngineApi, type EngineConstructor, FilterCriterion, MergeEngines, type MergeEnginesOptions, SortDescriptor };
@@ -1 +1 @@
1
- export{a as MergeEngines}from'../chunk-OE5GVXOE.mjs';import'../chunk-4TBU4VWK.mjs';import'../chunk-XFQ56UZU.mjs';import'../chunk-DBAABXBP.mjs';
1
+ export{a as MergeEngines}from'../chunk-KMSFPZKF.mjs';
@@ -15,13 +15,41 @@ declare class TextSearchEngine<T extends CollectionItem> {
15
15
  private ngramIndexes;
16
16
  private data;
17
17
  private readonly minQueryLength;
18
+ /**
19
+ * Creates a new TextSearchEngine with optional data and fields to index.
20
+ */
18
21
  constructor(options?: TextSearchEngineOptions<T>);
22
+ /**
23
+ * Builds an n-gram index for the given field.
24
+ */
19
25
  private buildIndex;
20
26
  search(query: string): T[];
21
27
  search(field: keyof T & string, query: string): T[];
28
+ private normalizeQuery;
29
+ /**
30
+ * Checks if the query is long enough to search.
31
+ */
32
+ private isQuerySearchable;
33
+ /**
34
+ * Searches all indexed fields.
35
+ */
22
36
  private searchAllFields;
37
+ /**
38
+ * Searches a specific field.
39
+ */
23
40
  private searchField;
41
+ /**
42
+ * Searches a field using prepared query grams.
43
+ */
24
44
  private searchFieldWithPreparedQuery;
45
+ /**
46
+ * Searches all fields linearly without index.
47
+ */
48
+ private searchAllFieldsLinear;
49
+ /**
50
+ * Searches a specific field linearly without index.
51
+ */
52
+ private searchFieldLinear;
25
53
  clear(): void;
26
54
  }
27
55
 
@@ -1 +1 @@
1
- export{a as TextSearchEngine}from'../chunk-4TBU4VWK.mjs';
1
+ export{a as TextSearchEngine}from'../chunk-VSCCECO2.mjs';
@@ -13,14 +13,38 @@ interface SortEngineOptions<T extends CollectionItem = CollectionItem> {
13
13
  declare class SortEngine<T extends CollectionItem> {
14
14
  private cache;
15
15
  private data;
16
+ /**
17
+ * Creates a new SortEngine with optional data and fields to index.
18
+ */
16
19
  constructor(options?: SortEngineOptions<T>);
20
+ /**
21
+ * Builds an index for sorting the given field.
22
+ */
17
23
  private buildIndex;
24
+ /**
25
+ * Clears all cached indexes.
26
+ */
18
27
  clearIndexes(): void;
28
+ /**
29
+ * Sorts the data based on the given descriptors.
30
+ */
19
31
  sort(descriptors: SortDescriptor<T>[]): T[];
20
32
  sort(data: T[], descriptors: SortDescriptor<T>[], inPlace?: boolean): T[];
33
+ /**
34
+ * Reconstructs the sorted array from the cached index.
35
+ */
21
36
  private reconstructFromIndex;
37
+ /**
38
+ * Checks if the field snapshot is still valid.
39
+ */
22
40
  private isFieldSnapshotValid;
41
+ /**
42
+ * Builds a comparator function for sorting.
43
+ */
23
44
  private buildComparator;
45
+ /**
46
+ * Sorts numeric data using radix sort.
47
+ */
24
48
  private radixSortNumeric;
25
49
  }
26
50
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devisfuture/mega-collection",
3
- "version": "1.0.18",
3
+ "version": "1.1.1",
4
4
  "description": "High-performance search, filter & sort engine for 100K+ item collections in JavaScript/TypeScript",
5
5
  "exports": {
6
6
  ".": {
@@ -1 +0,0 @@
1
- var T=3,f=12;function I(d){let t=d.toLowerCase(),n=Math.min(T,t.length),e=t.length-n+1,s=new Array(e);for(let r=0;r<e;r++)s[r]=t.substring(r,r+n);return s}function m(d){let t=I(d);if(t.length<=f)return new Set(t);let n=new Set,e=t.length-1,s=f-1;for(let r=0;r<=s;r++){let a=Math.round(r*e/s);n.add(t[a]);}return n}function L(d,t){let n=d.get(t);if(n)return n;let e=new Set;return d.set(t,e),e}var y=class{constructor(t={}){this.ngramIndexes=new Map;this.data=[];if(this.minQueryLength=t.minQueryLength??1,!!t.data&&(this.data=t.data,!!t.fields?.length))for(let n of t.fields)this.buildIndex(t.data,n);}buildIndex(t,n){let e,s;if(Array.isArray(t))e=t,s=n;else {if(!this.data.length)throw new Error("TextSearchEngine: no dataset in memory. Either pass `data` in the constructor options, or call buildIndex(data, field).");e=this.data,s=t;}this.data=e;let r=new Map;for(let a=0,l=e.length;a<l;a++){let g=e[a][s];if(typeof g!="string")continue;let o=g.toLowerCase();for(let i=0,u=o.length;i<u;i++){let c=u-i,p=Math.min(T,c);for(let h=1;h<=p;h++){let x=o.substring(i,i+h);L(r,x).add(a);}}}return this.ngramIndexes.set(s,r),this}search(t,n){return n===void 0?this.searchAllFields(t):this.searchField(t,n)}searchAllFields(t){let n=[...this.ngramIndexes.keys()];if(!n.length)return [];let e=t.trim().toLowerCase();if(!e)return [];if(e.length<this.minQueryLength)return [];let s=m(e);if(!s.size)return [];let r=new Set,a=[];for(let l of n)for(let g of this.searchFieldWithPreparedQuery(l,e,s))r.has(g)||(r.add(g),a.push(g));return a}searchField(t,n){let e=n.trim().toLowerCase();if(!e)return [];if(e.length<this.minQueryLength)return [];let s=m(e);return s.size?this.searchFieldWithPreparedQuery(t,e,s):[]}searchFieldWithPreparedQuery(t,n,e){let s=this.ngramIndexes.get(t);if(!s)return [];let r=[];for(let o of e){let i=s.get(o);if(!i)return [];r.push(i);}r.sort((o,i)=>o.size-i.size);let a=r[0],l=r.length,g=[];for(let o of a){let i=true;for(let c=1;c<l;c++)if(!r[c].has(o)){i=false;break}if(!i)continue;let u=this.data[o][t];typeof u=="string"&&u.toLowerCase().includes(n)&&g.push(this.data[o]);}return g}clear(){this.ngramIndexes.clear(),this.data=[];}};export{y as a};
@@ -1 +0,0 @@
1
- import {a as a$1}from'./chunk-4TBU4VWK.mjs';import {a as a$3}from'./chunk-XFQ56UZU.mjs';import {a as a$2}from'./chunk-DBAABXBP.mjs';var a=class{constructor(e){let{imports:r,data:t,search:l,filter:g,sort:f}=e,i=new Set(r),T=i.has(a$1),c=i.has(a$2),h=i.has(a$3);this.searchEngine=T?new a$1({data:t,fields:l?.fields,minQueryLength:l?.minQueryLength}):null,this.sortEngine=c?new a$2({data:t,fields:f?.fields}):null,this.filterEngine=h?new a$3({data:t,fields:g?.fields,filterByPreviousResult:g?.filterByPreviousResult}):null;}search(e,r){if(!this.searchEngine)throw new Error("MergeEngines: TextSearchEngine is not available. Add TextSearchEngine to the `imports` array.");return r===void 0?this.searchEngine.search(e):this.searchEngine.search(e,r)}sort(e,r,t){if(!this.sortEngine)throw new Error("MergeEngines: SortEngine is not available. Add SortEngine to the `imports` array.");return r===void 0?this.sortEngine.sort(e):this.sortEngine.sort(e,r,t)}filter(e,r){if(!this.filterEngine)throw new Error("MergeEngines: FilterEngine is not available. Add FilterEngine to the `imports` array.");return r===void 0?this.filterEngine.filter(e):this.filterEngine.filter(e,r)}};export{a};