@devisfuture/mega-collection 1.0.18 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md 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};
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,38 @@
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
+ constructor(options: MergeEnginesOptions<T>);
25
+ private getModuleInitOptions;
26
+ private getMethodNames;
27
+ private hasMethod;
28
+ private isRecord;
29
+ private callEngineMethod;
30
+ search(query: string): T[];
31
+ search(field: keyof T & string, query: string): T[];
32
+ sort(descriptors: SortDescriptor<T>[]): T[];
33
+ sort(data: T[], descriptors: SortDescriptor<T>[], inPlace?: boolean): T[];
34
+ filter(criteria: FilterCriterion<T>[]): T[];
35
+ filter(data: T[], criteria: FilterCriterion<T>[]): T[];
36
+ }
37
+
38
+ 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';
@@ -19,9 +19,13 @@ declare class TextSearchEngine<T extends CollectionItem> {
19
19
  private buildIndex;
20
20
  search(query: string): T[];
21
21
  search(field: keyof T & string, query: string): T[];
22
+ private normalizeQuery;
23
+ private isQuerySearchable;
22
24
  private searchAllFields;
23
25
  private searchField;
24
26
  private searchFieldWithPreparedQuery;
27
+ private searchAllFieldsLinear;
28
+ private searchFieldLinear;
25
29
  clear(): void;
26
30
  }
27
31
 
@@ -1 +1 @@
1
- export{a as TextSearchEngine}from'../chunk-4TBU4VWK.mjs';
1
+ export{a as TextSearchEngine}from'../chunk-VSCCECO2.mjs';
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.0",
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};