minisearch
Advanced tools
Comparing version 0.1.6 to 0.1.7
@@ -20,2 +20,5 @@ { | ||
"author": "https://twitter.com/lucaongaro" | ||
}, | ||
"manual": { | ||
"files": ["README.md", "CONTRIBUTING.md"] | ||
} | ||
@@ -22,0 +25,0 @@ } |
@@ -1,1 +0,1 @@ | ||
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define("minisearch",[],t):"object"==typeof exports?exports.minisearch=t():e.minisearch=t()}("undefined"!=typeof self?self:this,function(){return function(e){var t={};function n(i){if(t[i])return t[i].exports;var s=t[i]={i:i,l:!1,exports:{}};return e[i].call(s.exports,s,s.exports,n),s.l=!0,s.exports}return n.m=e,n.c=t,n.d=function(e,t,i){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:i})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var i=Object.create(null);if(n.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var s in e)n.d(i,s,function(t){return e[t]}.bind(null,s));return i},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=0)}([function(e,t,n){"use strict";n.r(t);class i{constructor(e,t=s){const n=e._tree,i=Object.keys(n);this.set=e,this.type=t,this.path=i.length>0?[{node:n,keys:i}]:[]}next(){const e=this.dive();return this.backtrack(),e}dive(){if(0===this.path.length)return{done:!0};const{node:e,keys:t}=u(this.path);return u(t)===c?{done:!1,value:this.result()}:(this.path.push({node:e[u(t)],keys:Object.keys(e[u(t)])}),this.dive())}backtrack(){0!==this.path.length&&(u(this.path).keys.pop(),u(this.path).keys.length>0||(this.path.pop(),this.backtrack()))}key(){return this.set._prefix+this.path.map(({keys:e})=>u(e)).filter(e=>e!==c).join("")}value(){return u(this.path).node[c]}result(){return this.type===o?this.value():this.type===r?this.key():[this.key(),this.value()]}[Symbol.iterator](){return this}}const s="ENTRIES",r="KEYS",o="VALUES",c="",u=function(e){return e[e.length-1]},d=function(e,t,n,i=0,s=h){const r=[{distance:0,ia:i,ib:0,edit:s}],o=[],c=[];for(;r.length>0;){const{distance:i,ia:s,ib:u,edit:d}=r.pop();if(o[s]=o[s]||[],!(o[s][u]&&o[s][u]<=i))if(o[s][u]=i,u!==t.length)if(e[s]===t[u])r.push({distance:i,ia:s+1,ib:u+1,edit:h});else{if(i>=n)continue;d!==f&&r.push({distance:i+1,ia:s,ib:u+1,edit:a}),s<e.length&&(d!==a&&r.push({distance:i+1,ia:s+1,ib:u,edit:f}),d!==a&&d!==f&&r.push({distance:i+1,ia:s+1,ib:u+1,edit:l}))}else c.push({distance:i,i:s,edit:d})}return c},h=0,l=1,f=2,a=3;var p=function(e,t,n){const i=[{distance:0,i:0,key:"",node:e}],s={};for(;i.length>0;){const{node:e,distance:r,key:o,i:u,edit:h}=i.pop();Object.keys(e).forEach(l=>{if(l===c){const i=r+(t.length-u),[,c]=s[o]||[null,1/0];i<=n&&i<c&&(s[o]=[e[l],i])}else d(t,l,n-r,u,h).forEach(({distance:t,i:n,edit:s})=>{i.push({node:e[l],distance:r+t,key:o+l,i:n,edit:s})})})}return s};class m{constructor(e={},t=""){this._tree=e,this._prefix=t}atPrefix(e){if(!e.startsWith(this._prefix))throw new Error("Mismatched prefix");const[t,n]=_(this._tree,e.slice(this._prefix.length));if(void 0===t){const[t,i]=k(n),s=Object.keys(t).find(e=>e!==c&&e.startsWith(i));if(void 0!==s)return new m({[s.slice(i.length)]:t[s]},e)}return new m(t||{},e)}clear(){delete this._size,this._tree={}}delete(e){return delete this._size,v(this._tree,e)}entries(){return new i(this,s)}forEach(e){for(let[t,n]of this)e(t,n,this)}fuzzyGet(e,t){return p(this._tree,e,t)}get(e){const t=y(this._tree,e);return void 0!==t?t[c]:void 0}has(e){const t=y(this._tree,e);return void 0!==t&&t.hasOwnProperty(c)}keys(){return new i(this,r)}set(e,t){if("string"!=typeof e)throw new Error("key must be a string");return delete this._size,b(this._tree,e)[c]=t,this}get size(){return this._size?this._size:(this._size=0,this.forEach(()=>{this._size+=1}),this._size)}update(e,t){if("string"!=typeof e)throw new Error("key must be a string");delete this._size;const n=b(this._tree,e);return n[c]=t(n[c]),this}values(){return new i(this,o)}[Symbol.iterator](){return this.entries()}}m.from=function(e){const t=new m;for(let[n,i]of e)t.set(n,i);return t},m.fromObject=function(e){return m.from(Object.entries(e))};const _=function(e,t,n=[]){if(0===t.length)return[e,n];const i=Object.keys(e).find(e=>e!==c&&t.startsWith(e));return void 0===i?_(void 0,"",[...n,[e,t]]):_(e[i],t.slice(i.length),[...n,[e,i]])},y=function(e,t){if(0===t.length)return e;const n=Object.keys(e).find(e=>e!==c&&t.startsWith(e));return void 0!==n?y(e[n],t.slice(n.length)):void 0},b=function(e,t){if(0===t.length)return e;const n=Object.keys(e).find(e=>e!==c&&t.startsWith(e));if(void 0===n){const n=Object.keys(e).find(e=>e!==c&&e.startsWith(t[0]));if(void 0!==n){const i=g(t,n);return e[i]={[n.slice(i.length)]:e[n]},delete e[n],b(e[i],t.slice(i.length))}return e[t]={},e[t]}return b(e[n],t.slice(n.length))},g=function(e,t,n=0,i=Math.min(e.length,t.length),s=""){return n>=i?s:e[n]!==t[n]?s:g(e,t,n+1,i,s+e[n])},v=function(e,t){const[n,i]=_(e,t);if(void 0===n)return;delete n[c];const s=Object.keys(n);0===s.length&&O(i),1===s.length&&x(i,s[0],n[s[0]])},O=function(e){if(0===e.length)return;const[t,n]=k(e);delete t[n],0===Object.keys(t).length&&O(e.slice(0,-1))},x=function(e,t,n){if(0===e.length)return;const[i,s]=k(e);i[s+t]=n,delete i[s]},k=function(e){return e[e.length-1]};var j=m;const w="or";class z{constructor(e={}){this._options={...J,...e},this._options.searchOptions={...D,...this._options.searchOptions||{}};const{fields:t}=this._options;if(null==t)throw new Error('Option "fields" must be provided');this._index=new j,this._documentCount=0,this._documentIds={},this._fieldIds={},M(this,t)}add(e){const{tokenize:t,processTerm:n,fields:i,idField:s}=this._options;if(null==e[s])throw new Error(`Document does not have ID field "${s}"`);const r=C(this,e[s]);i.filter(t=>null!=e[t]).forEach(i=>{t(e[i]).forEach(e=>{E(this,this._fieldIds[i],r,n(e))})})}addAll(e){e.forEach(e=>this.add(e))}remove(e){const{tokenize:t,processTerm:n,fields:i,idField:s}=this._options;if(null==e[s])throw new Error(`Document does not have ID field "${s}"`);const[r]=Object.entries(this._documentIds).find(([t,n])=>e[s]===n)||[];if(null==r)throw new Error(`Cannot remove document with ID ${e[s]}: it is not in the index`);i.filter(t=>null!=e[t]).forEach(i=>{t(e[i]).forEach(e=>{I(this,this._fieldIds[i],r,n(e))})}),this._documentCount-=1}search(e,t={}){const{tokenize:n,processTerm:i,searchOptions:s}=this._options;t={...s,...t};const r=n(e).map(i).map(t.termToQuery).map(e=>this.executeQuery(e,t)),o=this.combineResults(r,t.combineWith);return Object.entries(o).map(([e,{score:t,match:n}])=>({id:this._documentIds[e],score:t,match:n})).sort(({score:e},{score:t})=>e<t?1:-1)}get documentCount(){return this._documentCount}executeQuery(e,t={}){const n=((t={...this._options.defaultSearchOptions,...t}).fields||this._options.fields).reduce((e,t)=>({...e,[t]:e[t]||1}),t.boost||{});if(!e.fuzzy&&!e.prefix)return T(this,e.term,n,this._index.get(e.term));const i=[];if(e.fuzzy){const t=e.fuzzy<1?Math.round(e.term.length*e.fuzzy):e.fuzzy;Object.entries(this._index.fuzzyGet(e.term,t)).forEach(([e,[t,s]])=>{i.push(T(this,e,n,t,s))})}return e.prefix&&this._index.atPrefix(e.term).forEach((t,s)=>{i.push(T(this,t,n,s,t.length-e.term.length))}),i.reduce(W[w],{})}combineResults(e,t=w){if(0===e.length)return{};const n=t.toLowerCase();return e.reduce(W[n],null)}toJSON(){return{index:this._index,documentCount:this._documentCount,documentIds:this._documentIds,fieldIds:this._fieldIds}}toJS(){this.toJSON()}}z.loadJSON=function(e,t={}){return z.loadJS(JSON.parse(e),t)},z.loadJS=function(e,t={}){const{index:{_tree:n,_prefix:i},documentCount:s,documentIds:r,fieldIds:o}=e,c=new z(t);return c._index=new j(n,i),c._documentCount=s,c._documentIds=r,c._fieldIds=o,c},z.SearchableMap=j;const E=function(e,t,n,i){e._index.update(i,e=>{const i=(e=e||{})[t]||{df:0,ds:{}};return null==i.ds[n]&&(i.df+=1),i.ds[n]=(i.ds[n]||0)+1,{...e,[t]:i}})},I=function(e,t,n,i){e._index.has(i)?(e._index.update(i,s=>{const r=s[t];return null==r||null==r.ds[n]?(S(e,n,t,i),s):r.df<=1?(delete s[t],s):(r.df-=1,r.ds[n]<=1?(delete r.ds[n],s):(r.ds[n]-=1,{...s,[t]:r}))}),0===Object.keys(e._index.get(i)).length&&e._index.delete(i)):S(e,n,t,i)},S=function(e,t,n,i){if(null==console||null==console.warn)return;const s=Object.entries(e._fieldIds).find(([e,t])=>t===n)[0];console.warn(`MiniSearch: document with ID ${e._documentIds[t]} has changed before removal: term "${i}" was not present in field "${s}". Removing a document after it has changed can corrupt the index!`)},C=function(e,t){const n=e._documentCount;return e._documentIds[n]=t,e._documentCount+=1,n},M=function(e,t){t.forEach((t,n)=>{e._fieldIds[t]=n})},T=function(e,t,n,i,s=0){return null==i?{}:Object.entries(n).reduce((n,[r,o])=>{const{df:c,ds:u}=i[e._fieldIds[r]]||{ds:{}};return Object.entries(u).forEach(([i,u])=>{const d=o/(1+.333*o*s);n[i]=n[i]||{score:0,match:{}},n[i].match[t]=n[i].match[t]||[],n[i].score+=d*P(u,c,e._documentCount),n[i].match[t].push(r)}),n},{})},W={[w]:function(e,t){return Object.entries(t).reduce((e,[t,{score:n,match:i}])=>(null==e[t]?e[t]={score:n,match:i}:(e[t].score+=n,Object.assign(e[t].match,i)),e),e||{})},and:function(e,t){return null==e?t:Object.entries(t).reduce((t,[n,{score:i,match:s}])=>void 0===e[n]?t:(t[n]=t[n]||{},t[n].score=Math.min(e[n].score,i),t[n].match={...e[n].match,...s},t),{})}},P=function(e,t,n){return e*Math.log(n/t)},J={idField:"id",tokenize:e=>e.split(/\W+/).filter(e=>e.length>1),processTerm:e=>e.toLowerCase()},D={termToQuery:e=>({term:e}),combineWith:w};var $=z;t.default=$}]).default}); | ||
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define("minisearch",[],t):"object"==typeof exports?exports.minisearch=t():e.minisearch=t()}("undefined"!=typeof self?self:this,function(){return function(e){var t={};function n(i){if(t[i])return t[i].exports;var s=t[i]={i:i,l:!1,exports:{}};return e[i].call(s.exports,s,s.exports,n),s.l=!0,s.exports}return n.m=e,n.c=t,n.d=function(e,t,i){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:i})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var i=Object.create(null);if(n.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var s in e)n.d(i,s,function(t){return e[t]}.bind(null,s));return i},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=0)}([function(e,t,n){"use strict";n.r(t);class i{constructor(e,t=s){const n=e._tree,i=Object.keys(n);this.set=e,this.type=t,this.path=i.length>0?[{node:n,keys:i}]:[]}next(){const e=this.dive();return this.backtrack(),e}dive(){if(0===this.path.length)return{done:!0};const{node:e,keys:t}=u(this.path);return u(t)===c?{done:!1,value:this.result()}:(this.path.push({node:e[u(t)],keys:Object.keys(e[u(t)])}),this.dive())}backtrack(){0!==this.path.length&&(u(this.path).keys.pop(),u(this.path).keys.length>0||(this.path.pop(),this.backtrack()))}key(){return this.set._prefix+this.path.map(({keys:e})=>u(e)).filter(e=>e!==c).join("")}value(){return u(this.path).node[c]}result(){return this.type===o?this.value():this.type===r?this.key():[this.key(),this.value()]}[Symbol.iterator](){return this}}const s="ENTRIES",r="KEYS",o="VALUES",c="",u=function(e){return e[e.length-1]},d=function(e,t,n,i=0,s=h){const r=[{distance:0,ia:i,ib:0,edit:s}],o=[],c=[];for(;r.length>0;){const{distance:i,ia:s,ib:u,edit:d}=r.pop();if(o[s]=o[s]||[],!(o[s][u]&&o[s][u]<=i))if(o[s][u]=i,u!==t.length)if(e[s]===t[u])r.push({distance:i,ia:s+1,ib:u+1,edit:h});else{if(i>=n)continue;d!==l&&r.push({distance:i+1,ia:s,ib:u+1,edit:a}),s<e.length&&(d!==a&&r.push({distance:i+1,ia:s+1,ib:u,edit:l}),d!==a&&d!==l&&r.push({distance:i+1,ia:s+1,ib:u+1,edit:f}))}else c.push({distance:i,i:s,edit:d})}return c},h=0,f=1,l=2,a=3;var p=function(e,t,n){const i=[{distance:0,i:0,key:"",node:e}],s={};for(;i.length>0;){const{node:e,distance:r,key:o,i:u,edit:h}=i.pop();Object.keys(e).forEach(f=>{if(f===c){const i=r+(t.length-u),[,c]=s[o]||[null,1/0];i<=n&&i<c&&(s[o]=[e[f],i])}else d(t,f,n-r,u,h).forEach(({distance:t,i:n,edit:s})=>{i.push({node:e[f],distance:r+t,key:o+f,i:n,edit:s})})})}return s};class m{constructor(e={},t=""){this._tree=e,this._prefix=t}atPrefix(e){if(!e.startsWith(this._prefix))throw new Error("Mismatched prefix");const[t,n]=_(this._tree,e.slice(this._prefix.length));if(void 0===t){const[t,i]=k(n),s=Object.keys(t).find(e=>e!==c&&e.startsWith(i));if(void 0!==s)return new m({[s.slice(i.length)]:t[s]},e)}return new m(t||{},e)}clear(){delete this._size,this._tree={}}delete(e){return delete this._size,x(this._tree,e)}entries(){return new i(this,s)}forEach(e){for(let[t,n]of this)e(t,n,this)}fuzzyGet(e,t){return p(this._tree,e,t)}get(e){const t=y(this._tree,e);return void 0!==t?t[c]:void 0}has(e){const t=y(this._tree,e);return void 0!==t&&t.hasOwnProperty(c)}keys(){return new i(this,r)}set(e,t){if("string"!=typeof e)throw new Error("key must be a string");return delete this._size,b(this._tree,e)[c]=t,this}get size(){return this._size?this._size:(this._size=0,this.forEach(()=>{this._size+=1}),this._size)}update(e,t){if("string"!=typeof e)throw new Error("key must be a string");delete this._size;const n=b(this._tree,e);return n[c]=t(n[c]),this}values(){return new i(this,o)}[Symbol.iterator](){return this.entries()}}m.from=function(e){const t=new m;for(let[n,i]of e)t.set(n,i);return t},m.fromObject=function(e){return m.from(Object.entries(e))};const _=function(e,t,n=[]){if(0===t.length)return[e,n];const i=Object.keys(e).find(e=>e!==c&&t.startsWith(e));return void 0===i?_(void 0,"",[...n,[e,t]]):_(e[i],t.slice(i.length),[...n,[e,i]])},y=function(e,t){if(0===t.length)return e;const n=Object.keys(e).find(e=>e!==c&&t.startsWith(e));return void 0!==n?y(e[n],t.slice(n.length)):void 0},b=function(e,t){if(0===t.length)return e;const n=Object.keys(e).find(e=>e!==c&&t.startsWith(e));if(void 0===n){const n=Object.keys(e).find(e=>e!==c&&e.startsWith(t[0]));if(void 0!==n){const i=g(t,n);return e[i]={[n.slice(i.length)]:e[n]},delete e[n],b(e[i],t.slice(i.length))}return e[t]={},e[t]}return b(e[n],t.slice(n.length))},g=function(e,t,n=0,i=Math.min(e.length,t.length),s=""){return n>=i?s:e[n]!==t[n]?s:g(e,t,n+1,i,s+e[n])},x=function(e,t){const[n,i]=_(e,t);if(void 0===n)return;delete n[c];const s=Object.keys(n);0===s.length&&v(i),1===s.length&&O(i,s[0],n[s[0]])},v=function(e){if(0===e.length)return;const[t,n]=k(e);delete t[n],0===Object.keys(t).length&&v(e.slice(0,-1))},O=function(e,t,n){if(0===e.length)return;const[i,s]=k(e);i[s+t]=n,delete i[s]},k=function(e){return e[e.length-1]};var z=m;const j="or";class w{constructor(e={}){this._options={...D,...e},this._options.searchOptions={...$,...this._options.searchOptions||{}};const{fields:t}=this._options;if(null==t)throw new Error('Option "fields" must be provided');this._index=new z,this._documentCount=0,this._documentIds={},this._fieldIds={},M(this,t)}add(e){const{tokenize:t,processTerm:n,fields:i,idField:s}=this._options;if(null==e[s])throw new Error(`Document does not have ID field "${s}"`);const r=C(this,e[s]);i.filter(t=>null!=e[t]).forEach(i=>{t(e[i]).forEach(e=>{E(this,this._fieldIds[i],r,n(e))})})}addAll(e){e.forEach(e=>this.add(e))}remove(e){const{tokenize:t,processTerm:n,fields:i,idField:s}=this._options;if(null==e[s])throw new Error(`Document does not have ID field "${s}"`);const[r]=Object.entries(this._documentIds).find(([t,n])=>e[s]===n)||[];if(null==r)throw new Error(`Cannot remove document with ID ${e[s]}: it is not in the index`);i.filter(t=>null!=e[t]).forEach(i=>{t(e[i]).forEach(e=>{I(this,this._fieldIds[i],r,n(e))})}),this._documentCount-=1}search(e,t={}){const{tokenize:n,processTerm:i,searchOptions:s}=this._options;t={...s,...t};const r=n(e).map(i).map(T(t)).map(e=>this.executeQuery(e,t)),o=this.combineResults(r,t.combineWith);return Object.entries(o).map(([e,{score:t,match:n}])=>({id:this._documentIds[e],score:t,match:n})).sort(({score:e},{score:t})=>e<t?1:-1)}get documentCount(){return this._documentCount}executeQuery(e,t={}){const n=((t={...this._options.defaultSearchOptions,...t}).fields||this._options.fields).reduce((e,t)=>({...e,[t]:e[t]||1}),t.boost||{});if(!e.fuzzy&&!e.prefix)return W(this,e.term,n,this._index.get(e.term));const i=[];if(e.fuzzy){const t=e.fuzzy<1?Math.round(e.term.length*e.fuzzy):e.fuzzy;Object.entries(this._index.fuzzyGet(e.term,t)).forEach(([e,[t,s]])=>{i.push(W(this,e,n,t,s))})}return e.prefix&&this._index.atPrefix(e.term).forEach((t,s)=>{i.push(W(this,t,n,s,t.length-e.term.length))}),i.reduce(P[j],{})}combineResults(e,t=j){if(0===e.length)return{};const n=t.toLowerCase();return e.reduce(P[n],null)}toJSON(){return{index:this._index,documentCount:this._documentCount,documentIds:this._documentIds,fieldIds:this._fieldIds}}toJS(){this.toJSON()}}w.loadJSON=function(e,t={}){return w.loadJS(JSON.parse(e),t)},w.loadJS=function(e,t={}){const{index:{_tree:n,_prefix:i},documentCount:s,documentIds:r,fieldIds:o}=e,c=new w(t);return c._index=new z(n,i),c._documentCount=s,c._documentIds=r,c._fieldIds=o,c},w.SearchableMap=z;const E=function(e,t,n,i){e._index.update(i,e=>{const i=(e=e||{})[t]||{df:0,ds:{}};return null==i.ds[n]&&(i.df+=1),i.ds[n]=(i.ds[n]||0)+1,{...e,[t]:i}})},I=function(e,t,n,i){e._index.has(i)?(e._index.update(i,s=>{const r=s[t];return null==r||null==r.ds[n]?(S(e,n,t,i),s):r.df<=1?(delete s[t],s):(r.df-=1,r.ds[n]<=1?(delete r.ds[n],s):(r.ds[n]-=1,{...s,[t]:r}))}),0===Object.keys(e._index.get(i)).length&&e._index.delete(i)):S(e,n,t,i)},S=function(e,t,n,i){if(null==console||null==console.warn)return;const s=Object.entries(e._fieldIds).find(([e,t])=>t===n)[0];console.warn(`MiniSearch: document with ID ${e._documentIds[t]} has changed before removal: term "${i}" was not present in field "${s}". Removing a document after it has changed can corrupt the index!`)},C=function(e,t){const n=e._documentCount;return e._documentIds[n]=t,e._documentCount+=1,n},M=function(e,t){t.forEach((t,n)=>{e._fieldIds[t]=n})},W=function(e,t,n,i,s=0){return null==i?{}:Object.entries(n).reduce((n,[r,o])=>{const{df:c,ds:u}=i[e._fieldIds[r]]||{ds:{}};return Object.entries(u).forEach(([i,u])=>{const d=o/(1+.333*o*s);n[i]=n[i]||{score:0,match:{}},n[i].match[t]=n[i].match[t]||[],n[i].score+=d*J(u,c,e._documentCount),n[i].match[t].push(r)}),n},{})},P={[j]:function(e,t){return Object.entries(t).reduce((e,[t,{score:n,match:i}])=>(null==e[t]?e[t]={score:n,match:i}:(e[t].score+=n,Object.assign(e[t].match,i)),e),e||{})},and:function(e,t){return null==e?t:Object.entries(t).reduce((t,[n,{score:i,match:s}])=>void 0===e[n]?t:(t[n]=t[n]||{},t[n].score=Math.min(e[n].score,i),t[n].match={...e[n].match,...s},t),{})}},J=function(e,t,n){return e*Math.log(n/t)},T=e=>t=>{return{term:t,fuzzy:"function"==typeof e.fuzzy?e.fuzzy(t):e.fuzzy,prefix:"function"==typeof e.prefix?e.prefix(t):e.prefix}},D={idField:"id",tokenize:e=>e.split(/\W+/).filter(e=>e.length>1),processTerm:e=>e.toLowerCase()},$={combineWith:j};var N=w;t.default=N}]).default}); |
{ | ||
"name": "minisearch", | ||
"version": "0.1.6", | ||
"version": "0.1.7", | ||
"description": "fun with fulltext search", | ||
@@ -5,0 +5,0 @@ "main": "dist/minisearch.js", |
@@ -7,3 +7,3 @@ # MiniSearch | ||
## Use case: | ||
## Use case | ||
@@ -21,18 +21,21 @@ `MiniSearch` addresses use cases where full-text search features are needed | ||
## Design goals: | ||
* Memory-efficient index, able to store tens of thousands of documents and | ||
terms in memory, even in the browser. | ||
## Features | ||
* Memory-efficient index, smaller than most other libraries, designed to | ||
support memory-constrained use cases like mobile browsers. | ||
* Exact match, prefix match and fuzzy match, all with a single performant and | ||
multi-purpose index data structure. | ||
* Small and maintainable code base, well tested, with no external dependency. | ||
* Mutable index, capable of adding and removing documents at any time | ||
* Provide good building blocks that empower developers to build solutions to | ||
their specific problems, rather than try to offer a general-purpose tool to | ||
satisfy every use-case at the cost of complexity. | ||
* Simple API, providing building blocks to build solutions to | ||
their specific problems | ||
## Installation: | ||
* Zero external dependencies, small code-base, well tested | ||
## Installation | ||
With `npm`: | ||
@@ -50,9 +53,13 @@ | ||
## Usage: | ||
## Usage | ||
[API documentation](https://lucaong.github.io/minisearch/identifiers.html) is | ||
available, but here are some quick examples: | ||
Refer to the [API | ||
documentation](https://lucaong.github.io/minisearch/identifiers.html) for | ||
details, but here are some quick examples. All the examples use the `ES6` | ||
syntax. | ||
### Basic usage | ||
```javascript | ||
// A collection of documents for our example | ||
// A collection of documents for our examples | ||
const documents = [ | ||
@@ -74,21 +81,23 @@ { id: 1, title: 'Moby Dick', text: 'Call me Ishmael. Some years ago...' }, | ||
// => [ { id: 2, score: 2.77258, match: { ... } }, { id: 4, score: 1.38629, match: { ... } } ] | ||
``` | ||
### Search options | ||
`MiniSearch` supports several options for more advanced search behavior: | ||
```javascript | ||
// Search only specific fields | ||
results = miniSearch.search('zen', { fields: ['title'] }) | ||
miniSearch.search('zen', { fields: ['title'] }) | ||
// Boost fields | ||
results = miniSearch.search('zen', { boost: { title: 2 } }) | ||
// Boost some fields (here "title") | ||
miniSearch.search('zen', { boost: { title: 2 } }) | ||
// Prefix search | ||
results = miniSearch.search('moto', { | ||
termToQuery: term => ({ term, prefix: true }) | ||
}) | ||
// Prefix search (so that 'moto' will match 'motorcycle') | ||
miniSearch.search('moto', { prefix: true }) | ||
// Fuzzy search (in this example, with a max edit distance of 0.2 * term length, | ||
// rounded to nearest integer) | ||
results = miniSearch.search('ismael', { | ||
termToQuery: term => ({ term, fuzzy: 0.2 }) | ||
}) | ||
// Fuzzy search, in this example, with a max edit distance of 0.2 * term length, | ||
// rounded to nearest integer. The mispelled 'ismael' will match 'ishmael'. | ||
miniSearch.search('ismael', { fuzzy: 0.2 }) | ||
// Set default search options upon initialization | ||
// You can set the default search options upon initialization | ||
miniSearch = new MiniSearch({ | ||
@@ -98,3 +107,3 @@ fields: ['title', 'text'], | ||
boost: { title: 2 }, | ||
termToQuery: term => ({ term, fuzzy: 0.2 }) | ||
fuzzy: 0.2 | ||
} | ||
@@ -104,4 +113,8 @@ }) | ||
// Will now by default perform fuzzy search and boost "title": | ||
results = miniSearch.search('zen and motorcycles') | ||
// It will now by default perform fuzzy search and boost "title": | ||
miniSearch.search('zen and motorcycles') | ||
``` | ||
The [API documentation](https://lucaong.github.io/minisearch/identifiers.html) | ||
has more details about other configuration options (tokenization, term | ||
processing, etc.) |
@@ -7,3 +7,4 @@ import Benchmark from 'benchmark' | ||
ms.search('virtute e conoscienza', { | ||
termToQuery: term => ({ term, fuzzy: 0.2, prefix: true }) | ||
fuzzy: 0.2, | ||
prefix: true | ||
}) | ||
@@ -10,0 +11,0 @@ }) |
@@ -166,3 +166,4 @@ import SearchableMap from './SearchableMap/SearchableMap.js' | ||
* @param {Object<string, number>} [options.boost] - Key-value object of boosting values for fields | ||
* @param {function(term: string): {term: !string, prefix: ?boolean, fuzzy: ?number}} [options.termToQuery] - Function specifying how a term is turned into a query (whether to perform fuzzy search, prefix search, etc.) | ||
* @param {boolean|function} [options.prefix] - Whether to perform prefix search. Value can be a boolean, or a function computing the boolean from the term. | ||
* @param {number|function} [options.fuzzy] - If set to a number greater than or equal 1, it performs fuzzy search within a maximum edit distance equal to that value. If set to a number less than 1, it performs fuzzy search with a maximum edit distance equal to the term length times the value, rouded at the nearest integer. If set to a function, it calls the function passing each search term and expects a numeric value indicating the maximum edit distance, or a falsy falue if fuzzy search should not be performed. | ||
* @param {string} [options.combineWith='OR'] - How to combine term queries (it can be 'OR' or 'AND') | ||
@@ -188,5 +189,3 @@ * @return {Array<{ id: any, score: number, match: Object }>} A sorted array of scored document IDs matching the search | ||
* // containing terms that start with "moto" or "neuro") | ||
* miniSearch.search('moto neuro', { | ||
* termToQuery: term => ({ term, prefix: true }) | ||
* }) | ||
* miniSearch.search('moto neuro', { prefix: true }) | ||
* | ||
@@ -197,5 +196,3 @@ * @example | ||
* // (rounded to nearest integer) | ||
* miniSearch.search('ismael', { | ||
* termToQuery: term => ({ term, fuzzy: 0.2 }) | ||
* }) | ||
* miniSearch.search('ismael', { fuzzy: 0.2 }) | ||
* | ||
@@ -205,6 +202,15 @@ * @example | ||
* miniSearch.search('ismael mob', { | ||
* termToQuery: term => ({ term, prefix: true, fuzzy: 0.2 }) | ||
* prefix: true, | ||
* fuzzy: 0.2 | ||
* }) | ||
* | ||
* @example | ||
* // Perform fuzzy and prefix search depending on the search term. Here | ||
* // performing prefix and fuzzy search only on terms longer than 3 characters | ||
* miniSearch.search('ismael mob', { | ||
* prefix: term => term.length > 3 | ||
* fuzzy: term => term.length > 3 ? 0.2 : null | ||
* }) | ||
* | ||
* @example | ||
* // Combine search terms with AND (to match only documents that contain both | ||
@@ -217,3 +223,3 @@ * // "motorcycle" and "art") | ||
options = { ...searchOptions, ...options } | ||
const queries = tokenize(queryString).map(processTerm).map(options.termToQuery) | ||
const queries = tokenize(queryString).map(processTerm).map(termToQuery(options)) | ||
const results = queries.map(query => this.executeQuery(query, options)) | ||
@@ -422,2 +428,12 @@ const combinedResults = this.combineResults(results, options.combineWith) | ||
const termToQuery = (options) => (term) => { | ||
const fuzzy = (typeof options.fuzzy === 'function') | ||
? options.fuzzy(term) | ||
: options.fuzzy | ||
const prefix = (typeof options.prefix === 'function') | ||
? options.prefix(term) | ||
: options.prefix | ||
return { term, fuzzy, prefix } | ||
} | ||
const defaultOptions = { | ||
@@ -430,3 +446,2 @@ idField: 'id', | ||
const defaultSearchOptions = { | ||
termToQuery: term => ({ term }), | ||
combineWith: OR | ||
@@ -433,0 +448,0 @@ } |
@@ -148,3 +148,3 @@ /* eslint-env jest */ | ||
it('executes fuzzy search', () => { | ||
const results = ms.search('camin memory', { termToQuery: term => ({ term, fuzzy: 2 }) }) | ||
const results = ms.search('camin memory', { fuzzy: 2 }) | ||
expect(results.length).toEqual(2) | ||
@@ -155,3 +155,3 @@ expect(results.map(({ id }) => id)).toEqual([1, 3]) | ||
it('executes prefix search', () => { | ||
const results = ms.search('que', { termToQuery: term => ({ term, prefix: true }) }) | ||
const results = ms.search('que', { prefix: true }) | ||
expect(results.length).toEqual(2) | ||
@@ -162,3 +162,3 @@ expect(results.map(({ id }) => id)).toEqual([2, 3]) | ||
it('combines prefix search and fuzzy search', () => { | ||
const results = ms.search('cammino quel', { termToQuery: term => ({ term, fuzzy: 0.25, prefix: true }) }) | ||
const results = ms.search('cammino quel', { fuzzy: 0.25, prefix: true }) | ||
expect(results.length).toEqual(3) | ||
@@ -168,2 +168,11 @@ expect(results.map(({ id }) => id)).toEqual([2, 1, 3]) | ||
it('accepts a function to compute fuzzy and prefix options from term', () => { | ||
const results = ms.search('quel comedia', { | ||
fuzzy: term => term.length > 4 ? 2 : false, | ||
prefix: term => term.length > 4 | ||
}) | ||
expect(results.length).toEqual(2) | ||
expect(results.map(({ id }) => id)).toEqual([2, 1]) | ||
}) | ||
describe('match data', () => { | ||
@@ -195,3 +204,3 @@ const documents = [ | ||
it('reports correct info for fuzzy and prefix queries', () => { | ||
const results = ms.search('vi nuova', { termToQuery: term => ({ term, fuzzy: 0.2, prefix: true }) }) | ||
const results = ms.search('vi nuova', { fuzzy: 0.2, prefix: true }) | ||
expect(results.map(({ match }) => match)).toEqual([ | ||
@@ -198,0 +207,0 @@ { vita: ['title', 'text'], nova: ['title'] }, |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
1446064
66
9641
115