@redocly/realm 0.138.0-next.5 → 0.138.0-next.6
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/CHANGELOG.md +6 -0
- package/dist/server/plugins/search/engines/flexsearch/index.js +2 -1
- package/dist/server/plugins/search/engines/flexsearch/search-index.d.ts +2 -2
- package/dist/server/plugins/search/engines/flexsearch/search-index.js +1 -1
- package/dist/server/types/plugins/search.d.ts +6 -4
- package/dist/server/web-server/middleware/ensureSearchData.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{readdir as S,appendFile as A}from"node:fs/promises";import l from"node:path";import{createReadStream as v,existsSync as w}from"node:fs";import{createInterface as O}from"node:readline";import{SEARCH_GROUP_FACET_FIELD as C,SEARCH_PRODUCT_FIELD as N,SEARCH_VERSION_FIELD as j}from"../../../../../constants/common.js";import{SEARCH_INDEX_FIELDS as H,SEARCH_DATA_EXPORT_FOLDER as g,SEARCH_MAX_INMEMORY_DOCUMENTS_COUNT as L}from"../../../../constants/plugins/search.js";import{telemetryTraceStep as T}from"../../../../telemetry/helpers/trace-step.js";import{envConfig as M}from"../../../../config/env-config.js";import{ensureDir as u}from"../../../../utils/index.js";import{FlexSearchIndex as b}from"./search-index.js";const F=".jsonl";function R(m){return`${JSON.stringify(m)}
|
|
2
|
+
`}async function*P(m){const t=O({input:v(m,{encoding:"utf8"}),crlfDelay:1/0});for await(const s of t)s&&(yield JSON.parse(s))}class K{#e=new Map;#s;#t=0;async initIndexSchema(t){this.#s={document:{id:"id",tag:"tags",index:H},worker:!1,tokenize:"forward",context:{depth:2,resolution:9}}}#i(t,s){const n=this.#e.get(t);if(n)return n.find(i=>i.id===s)}#n(t,s){let n=this.#i(t,s);return n||(n=new b(s,this.#s),this.#e.set(t,[...this.#e.get(t)??[],n])),n}async addDocuments(t,s){if(t.length){const{group:n,locale:i,outDir:c}=s,o=this.#n(i,n);for(const r of t)o.add(r),M.isBuildMode&&(this.#t++,this.#t>=L&&await this.exportDocuments(c))}}getIndexesSize(){return this.#e.size}async search(t,s,n){return await T("com.redocly.search.performed",async i=>{i?.setAttribute("id","searchQueryId"),i?.setAttribute("object","searchQuery"),i?.setAttribute("uri","urn:redocly:realm:api:searchQuery:searchQueryId");const{query:c,locale:o,filter:r,loadMore:a}=t,d=this.#e.get(o)??[];let h=[],D="",y=["v:default"];if(i?.setAttribute("locale",o),r)for(const e of r)e.field===C?h=[...h,...e.values]:e.field===N?D=`p:${e.values[0]}`:e.field===j&&e.values.length&&e.values.length===2&&(y=[...y,`v:${e.values[0]}:${e.values[1]}`]);const _={auth:s,query:c,offset:0,SEARCH_GROUP_FACET_FIELD:C,product:D,versions:y},x=[];let f={facets:{},documents:{}};if(a){const e=this.#i(o,a.groupKey);e&&x.push(e.search({..._,offset:a.offset}))}else for(const e of d)h&&h.length?h.includes(e.id)&&x.push(e.search(_)):x.push(e.search(_));const E=await Promise.all(x),$=E.some(e=>e.hasCoveringHit);for(const e of E)if(!($&&!e.hasCoveringHit)){f.documents={...f.documents,...e.documents};for(const[p,I]of Object.entries(e.facets))f.facets[p]?f.facets[p]=[...f.facets[p],...I]:f.facets[p]=I}return f})}async exportDocuments(t){const s=u(l.join(t,g));for(const[n,i]of this.#e){const c=u(l.join(s,n));for(const o of i){const r=Array.from(o.documents,d=>R({document:d}));if(r.length===0)continue;const a=u(l.join(c,`${o.id}${F}`));await A(a,r.join(""),{encoding:"utf8"}),o.clearDocuments()}}this.#t=0}async exportIndexes(t){const s=u(l.join(t,g));for(const[n,i]of this.#e){const c=u(l.join(s,n));for(const o of i){const r=u(l.join(c,`${o.id}${F}`));await o.export((a,d)=>A(r,R({index:[String(a),d]}),{encoding:"utf8"}))}}this.#e.clear()}async import(t){const s=`${t}/${g}`;if(!w(s))return;const n=await S(s);for(const i of n){const c=await S(`${s}/${i}`);for(const o of c){const r=`${s}/${i}/${o}`,a=l.parse(o).name;await this.#n(i,a).import(P(r))}}}countFacets(t,s,n){const i={};for(const[c,o]of n)i[c]=o.values.map(r=>({value:r,count:0,isCounterVisible:!1}));return i}cleanupFacetValues(t){}}export{K as FlexSearch};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ExportHandler, IndexOptionsForDocumentSearch } from 'flexsearch';
|
|
2
|
-
import type {
|
|
2
|
+
import type { FlexSearchDataEntry, IndexedSearchDocument, SearchIndex, SearchParameters, SearchDocument } from '../../../../types';
|
|
3
3
|
import type { SearchResponse } from '../../../../../types/index.js';
|
|
4
4
|
/** One group's results plus whether any of them holds every query word in a single value. */
|
|
5
5
|
export type IndexSearchResponse = SearchResponse & {
|
|
@@ -13,7 +13,7 @@ export declare class FlexSearchIndex implements SearchIndex {
|
|
|
13
13
|
get documentsCount(): number;
|
|
14
14
|
clearDocuments(): void;
|
|
15
15
|
export(handler: ExportHandler<string | IndexedSearchDocument>): Promise<void>;
|
|
16
|
-
import(
|
|
16
|
+
import(entries: AsyncIterable<FlexSearchDataEntry>): Promise<void>;
|
|
17
17
|
add(document: SearchDocument): void;
|
|
18
18
|
search(params: SearchParameters): Promise<IndexSearchResponse>;
|
|
19
19
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import _ from"flexsearch";import{REDOCLY_TEAMS_RBAC as L}from"@redocly/config";import{DISABLE_DEEP_LINK_IF_FIELDS_EXIST as
|
|
1
|
+
import _ from"flexsearch";import{REDOCLY_TEAMS_RBAC as L}from"@redocly/config";import{DISABLE_DEEP_LINK_IF_FIELDS_EXIST as C,SEARCH_LIMIT as R}from"../../../../constants/plugins/search.js";import{telemetryTraceStep as b}from"../../../../telemetry/helpers/trace-step.js";import{highlightTextForSearch as m,markMatchedValue as D}from"../../../../utils/search/highlight-text-for-search.js";import{MIN_QUERY_WORD_LENGTH as M,containsQueryWord as S,matchableQueryWords as j}from"../../../../utils/search/text-match.js";function k(I){const{name:e,description:t,place:s,path:i,deepLink:r,example:n,enum:a}=I,o=r?.indexOf("#")??-1;return{name:e,description:t,place:s,...i&&{path:i},...r&&{deepLink:o>=0?r.slice(o+1):r},example:n,enum:a}}class T{#t;#e=new Map;#s=1;id;constructor(e,t){this.id=e,this.#t=new _.Document(t)}get documents(){return this.#e.entries()}get documentsCount(){return this.#s}clearDocuments(){this.#e.clear()}async export(e){await this.#t.export(e)}async import(e){this.#e.clear();for await(const t of e)if("document"in t){const[s,i]=t.document;this.#e.set(s,i)}else{const[s,i]=t.index;await this.#t.import(s,i)}}add(e){const t={...e,tags:e.tags??[],[L]:e[L],...e.parameters&&{parameters:e.parameters.map(k)}};this.#e.set(this.#s,t),this.#t.add(this.#s,t),this.#s++}async search(e){return await b("com.redocly.search.performed",async t=>{t?.setAttribute("id","searchQueryId"),t?.setAttribute("object","searchQuery"),t?.setAttribute("uri","urn:redocly:realm:api:searchQuery:searchQueryId");const{query:s,offset:i,auth:r,product:n,versions:a}=e,o=[],l=r.teams,p={limit:R,offset:i},c=this.#r(s);if(!c.length&&s.trim())return{documents:{[this.id]:[]},facets:{},hasCoveringHit:!1};let d=new Map,g=await this.#t.searchAsync(s,{...p,tag:l});if(n){const h=await this.#t.searchAsync(s,{...p,tag:n});g=this.#a(g,h),t?.setAttribute("product",n)}if(a&&a.length){const h=await this.#t.searchAsync(s,{...p,tag:a});g=this.#a(g,h),t?.setAttribute("versions",a.join(","))}for(const h of g)for(const u of h.result){const f=d.get(u)?.fields||[];d.set(u,{fields:h.field?[...f,h.field]:[...f]})}!i&&c.length&&!d.size&&(d=this.#l(c,l,n,a));const x=[];for(const[h,u]of d.entries()){const f=this.#e.get(h);f&&!this.#d(f,a)&&x.push({document:f,fields:u.fields,score:this.#m(c,f)})}const y=x.filter(h=>h.score>=c.length),A=y.length?y:x;A.sort((h,u)=>u.score-h.score);for(const h of A.slice(0,R))o.push({document:this.#o(h.document,s,h.fields),highlight:this.#h(h.document,s,h.fields)});return{documents:{[this.id]:o},facets:{},hasCoveringHit:y.length>0}})}#o(e,t,s){return this.#g(e,t,s)}#h(e,t,s){const i={};let r=!1;for(const n of s)if(n==="path")i.path=e.path?e.path.map(a=>m(t,a)):[];else if(n.includes("parameters")){if(!r){const a=n.split(":")[1],o=this.#n(t,a,e.parameters);o&&(i.parameters=[{name:m(t,o.name),description:m(t,o.description),place:m(t,o.place),path:o.path?o.path.map(l=>m(t,l)):[],...this.#c(t,o)}],r=!0)}}else n!=="httpMethod"&&(i[n]=m(t,e[n]));return i}#c(e,t){return{enum:D(e,(t.enum??[]).join(", ")),example:D(e,t.example??"")}}#r(e){return j(e)}#l(e,t,s,i){const r=new Map;if(!e.length)return r;for(const[n,a]of this.#e)this.#p(a,t,s,i)&&this.#f(e,a)&&r.set(n,{fields:this.#u(e,a)});return r}#f(e,t){const s=i=>{if(i==null)return!1;const r=(Array.isArray(i)?i.join(" "):String(i)).toLowerCase();return!!r&&e.every(n=>S(r,n))};return s(t.title)||s(t.text)||s(t.httpPath)||s(t.path)?!0:(t.parameters??[]).some(i=>s(i.name)||s(i.description)||s(i.example)||s(i.enum))}#p(e,t,s,i){const r=new Set(e.tags??[]);return!(t.length&&!t.some(n=>r.has(n))||s&&!r.has(s)||i&&i.length&&!i.some(n=>r.has(n)))}#u(e,t){const s=r=>{if(r==null)return!1;const n=(Array.isArray(r)?r.join(" "):String(r)).toLowerCase();return e.some(a=>S(n,a))},i=[];s(t.title)&&i.push("title"),s(t.text)&&i.push("text"),s(t.path)&&i.push("path"),t.httpMethod&&e.some(r=>r.length>=M&&t.httpMethod?.toLowerCase().startsWith(r))&&i.push("httpMethod"),s(t.httpPath)&&i.push("httpPath");for(const r of["name","description","path","example","enum"])t.parameters?.some(n=>s(n[r]))&&i.push(`parameters[]:${r}`);return i}#i(e,t){const s=t.toLowerCase().replace(/\s+/g," "),i=e.filter(n=>S(s,n)).length;let r=0;for(let n=1;n<e.length;n++)s.includes(`${e[n-1]} ${e[n]}`)&&r++;return i+r/e.length}#m(e,t){let s=0;const i=r=>{if(r==null)return;const n=this.#i(e,String(r));n>s&&(s=n)};if(i(t.title),i(t.text),i(t.httpPath),i(t.path?.join(" ")),t.httpMethod){const r=t.httpMethod.toLowerCase(),n=e.filter(a=>a.length>=M&&r.startsWith(a)).length;n>s&&(s=n)}for(const r of t.parameters??[])i([r.name,r.description,r.place,r.example,r.enum?.join(" ")].filter(Boolean).join(" "));return s}#n(e,t,s){const i=this.#r(e);let r,n=0;for(const a of s??[]){const o=a[t],l=(Array.isArray(o)?o:[o]).filter(c=>c!=null).map(c=>String(c)).join(" "),p=this.#i(i,l);p>n&&(n=p,r=a)}return r}#a(e,t){const s=[];for(const i of e){const r=[];for(const n of t)for(const a of i.result){const o=n.result.find(l=>l===a);o&&r.push(o)}r.length&&s.push({result:r,field:i.field})}return s}#d(e,t){return!!(t&&t.length&&e.isDefaultVersion&&e.versionFolderId&&e.version&&t.some(s=>s.includes(e.versionFolderId??""))&&!t.some(s=>s.includes(e.version??"")))}#g(e,t,s){for(const r of C)if(s.some(n=>n===r))return e;let i;for(const r of s)if(r.includes("parameters")){const n=r.split(":")[1],a=this.#n(t,n,e.parameters);if(a){i=a.deepLink;break}}return i?{...e,url:`${e.url}#${i}`}:e}}export{T as FlexSearchIndex};
|
|
@@ -31,7 +31,7 @@ export type SearchIndex = {
|
|
|
31
31
|
add(document: SearchDocument): void;
|
|
32
32
|
search(params: SearchParameters): Promise<SearchResponse>;
|
|
33
33
|
export(handler: ExportHandler<string | IndexedSearchDocument>): void;
|
|
34
|
-
import(
|
|
34
|
+
import(entries: AsyncIterable<FlexSearchDataEntry>): Promise<void>;
|
|
35
35
|
clearDocuments(): void;
|
|
36
36
|
};
|
|
37
37
|
export type AddDocumentsParams = {
|
|
@@ -39,9 +39,11 @@ export type AddDocumentsParams = {
|
|
|
39
39
|
locale: string;
|
|
40
40
|
outDir: string;
|
|
41
41
|
};
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
/** One line of an exported FlexSearch group file: a stored document or one FlexSearch export key. */
|
|
43
|
+
export type FlexSearchDataEntry = {
|
|
44
|
+
document: [number, IndexedSearchDocument];
|
|
45
|
+
} | {
|
|
46
|
+
index: [string, string];
|
|
45
47
|
};
|
|
46
48
|
export type TypesenseSearchData = {
|
|
47
49
|
documents: SearchDocument[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{DEFAULT_SEARCH_ENGINE as
|
|
1
|
+
import{DEFAULT_SEARCH_ENGINE as o}from"@redocly/config";import{logger as e}from"../../tools/notifiers/logger.js";import{SearchEngine as h}from"../../plugins/search/engines/search-engine.js";function l(a){let i;const c=async()=>{e.info("Loading search data...");const t=e.startTiming();try{const n=a.config?.search?.engine??o,r=new h(n,a.globalData.l10n);await r.initIndexSchema(a.searchFacets),await r.import(a.outdir),a.setSearchEngine(r),e.infoTime(t,"Search data loaded")}catch(n){throw e.error("Failed to load search data: %s",n),n}};return async(t,n)=>{!a.searchEngine&&!a.config?.search?.hide&&(i??=c().finally(()=>{i=void 0}),await i),await n()}}export{l as ensureSearchData};
|