typesense-nextra-adapter 0.0.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/LICENSE +201 -0
- package/README.md +313 -0
- package/dist/client/RealSearch.d.ts +3 -0
- package/dist/client/RealSearch.js +45 -0
- package/dist/client/Search.css +184 -0
- package/dist/client/Search.d.ts +22 -0
- package/dist/client/Search.js +134 -0
- package/dist/client/index.d.ts +3 -0
- package/dist/client/index.js +2 -0
- package/dist/client/locales.d.ts +8 -0
- package/dist/client/locales.js +130 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.js +137 -0
- package/dist/indexFromHtml.d.ts +19 -0
- package/dist/indexFromHtml.js +213 -0
- package/dist/types.d.ts +44 -0
- package/dist/types.js +0 -0
- package/dist/typesenseHelper.d.ts +67 -0
- package/dist/typesenseHelper.js +236 -0
- package/package.json +60 -0
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import crypto_0 from "crypto";
|
|
2
|
+
import * as __rspack_external_cheerio from "cheerio";
|
|
3
|
+
function _define_property(obj, key, value) {
|
|
4
|
+
if (key in obj) Object.defineProperty(obj, key, {
|
|
5
|
+
value: value,
|
|
6
|
+
enumerable: true,
|
|
7
|
+
configurable: true,
|
|
8
|
+
writable: true
|
|
9
|
+
});
|
|
10
|
+
else obj[key] = value;
|
|
11
|
+
return obj;
|
|
12
|
+
}
|
|
13
|
+
class IndexFromHtml {
|
|
14
|
+
getRecords(html, url, lang) {
|
|
15
|
+
const $ = __rspack_external_cheerio.load(html);
|
|
16
|
+
$('[data-pagefind-ignore]').remove();
|
|
17
|
+
const records = [];
|
|
18
|
+
const getGlobalText = (selector)=>{
|
|
19
|
+
const el = $(selector).first();
|
|
20
|
+
return el.length ? this.cleanText(el) : null;
|
|
21
|
+
};
|
|
22
|
+
const globalHierarchy = {
|
|
23
|
+
lvl0: getGlobalText(this.selectors.lvl0?.selector) || getGlobalText(this.selectors.lvl1?.selector) || getGlobalText('header nav [aria-current=true]') || $('title').text().trim() || 'Documentation'
|
|
24
|
+
};
|
|
25
|
+
const selectorString = Object.values(this.selectors).filter((s)=>!s.global).map((s)=>s.selector).join(', ');
|
|
26
|
+
const nodes = $(selectorString);
|
|
27
|
+
let previousHierarchy = this.generateEmptyHierarchy();
|
|
28
|
+
previousHierarchy.lvl0 = globalHierarchy.lvl0 || null;
|
|
29
|
+
const anchors = this.generateEmptyHierarchy();
|
|
30
|
+
nodes.each((index, element)=>{
|
|
31
|
+
const el = $(element);
|
|
32
|
+
const isNestedMatch = el.parents(selectorString).length > 0;
|
|
33
|
+
if (isNestedMatch) return;
|
|
34
|
+
const tagName = element.tagName.toLowerCase();
|
|
35
|
+
const currentLevel = this.getLevelFromTag(tagName);
|
|
36
|
+
const hierarchy = {
|
|
37
|
+
...previousHierarchy
|
|
38
|
+
};
|
|
39
|
+
const currentLevelInt = this.levels.indexOf(currentLevel);
|
|
40
|
+
if ('content' !== currentLevel) {
|
|
41
|
+
const text = this.cleanText(el);
|
|
42
|
+
hierarchy[currentLevel] = text;
|
|
43
|
+
anchors[currentLevel] = this.getAnchor(el);
|
|
44
|
+
for(let i = currentLevelInt + 1; i < 7; i++){
|
|
45
|
+
const lvlKey = `lvl${i}`;
|
|
46
|
+
hierarchy[lvlKey] = null;
|
|
47
|
+
anchors[lvlKey] = null;
|
|
48
|
+
}
|
|
49
|
+
previousHierarchy = {
|
|
50
|
+
...hierarchy
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
let content = null;
|
|
54
|
+
if ('content' === currentLevel) {
|
|
55
|
+
content = this.cleanText(el);
|
|
56
|
+
if (!content) return;
|
|
57
|
+
}
|
|
58
|
+
if (currentLevel.startsWith('lvl') && !hierarchy[currentLevel]) return;
|
|
59
|
+
const resolvedAnchor = this.getClosestAnchor(anchors);
|
|
60
|
+
let levelWeight;
|
|
61
|
+
if ('content' !== currentLevel) levelWeight = 100 - 10 * currentLevelInt;
|
|
62
|
+
else {
|
|
63
|
+
const isCodeBlock = 'code' === tagName || el.parents('pre').length > 0;
|
|
64
|
+
levelWeight = isCodeBlock ? 0 : 10;
|
|
65
|
+
}
|
|
66
|
+
const weight = {
|
|
67
|
+
page_rank: 0,
|
|
68
|
+
level: levelWeight,
|
|
69
|
+
position: index,
|
|
70
|
+
position_descending: nodes.length - index
|
|
71
|
+
};
|
|
72
|
+
const record = {
|
|
73
|
+
objectID: '',
|
|
74
|
+
url_without_anchor: url,
|
|
75
|
+
url: resolvedAnchor ? `${url}#${resolvedAnchor}` : url,
|
|
76
|
+
anchor: resolvedAnchor,
|
|
77
|
+
content: content,
|
|
78
|
+
hierarchy: hierarchy,
|
|
79
|
+
hierarchy_radio: this.getHierarchyRadio(hierarchy, currentLevel),
|
|
80
|
+
type: currentLevel,
|
|
81
|
+
weight: weight,
|
|
82
|
+
language: lang
|
|
83
|
+
};
|
|
84
|
+
record.objectID = this.getObjectID(record);
|
|
85
|
+
records.push(record);
|
|
86
|
+
});
|
|
87
|
+
return records;
|
|
88
|
+
}
|
|
89
|
+
getLevelFromTag(tagName) {
|
|
90
|
+
if ([
|
|
91
|
+
'h1',
|
|
92
|
+
'h2',
|
|
93
|
+
'h3',
|
|
94
|
+
'h4',
|
|
95
|
+
'h5',
|
|
96
|
+
'h6'
|
|
97
|
+
].includes(tagName)) return `lvl${tagName.replace('h', '')}`;
|
|
98
|
+
return 'content';
|
|
99
|
+
}
|
|
100
|
+
generateEmptyHierarchy() {
|
|
101
|
+
return {
|
|
102
|
+
lvl0: null,
|
|
103
|
+
lvl1: null,
|
|
104
|
+
lvl2: null,
|
|
105
|
+
lvl3: null,
|
|
106
|
+
lvl4: null,
|
|
107
|
+
lvl5: null,
|
|
108
|
+
lvl6: null
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
getHierarchyRadio(hierarchy, currentLevel) {
|
|
112
|
+
const radio = this.generateEmptyHierarchy();
|
|
113
|
+
let isFound = false;
|
|
114
|
+
for(let i = 6; i >= 0; i--){
|
|
115
|
+
const level = `lvl${i}`;
|
|
116
|
+
const value = hierarchy[level];
|
|
117
|
+
if (!isFound && null !== value) {
|
|
118
|
+
if ('content' === currentLevel || level === currentLevel) {
|
|
119
|
+
radio[level] = value;
|
|
120
|
+
isFound = true;
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
radio[level] = null;
|
|
125
|
+
}
|
|
126
|
+
return radio;
|
|
127
|
+
}
|
|
128
|
+
getAnchor(el) {
|
|
129
|
+
const id = el.attr('id');
|
|
130
|
+
return id || null;
|
|
131
|
+
}
|
|
132
|
+
getClosestAnchor(anchors) {
|
|
133
|
+
for(let i = 6; i >= 0; i--){
|
|
134
|
+
const val = anchors[`lvl${i}`];
|
|
135
|
+
if (val) return val;
|
|
136
|
+
}
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
getObjectID(record) {
|
|
140
|
+
const hierarchyToHash = {};
|
|
141
|
+
Object.keys(record.hierarchy).forEach((k)=>{
|
|
142
|
+
const val = record.hierarchy[k];
|
|
143
|
+
if (val) hierarchyToHash[k] = val;
|
|
144
|
+
});
|
|
145
|
+
const payload = {
|
|
146
|
+
hierarchy: hierarchyToHash,
|
|
147
|
+
url: record.url_without_anchor,
|
|
148
|
+
position: record.weight.position
|
|
149
|
+
};
|
|
150
|
+
const jsonStr = JSON.stringify(payload, Object.keys(payload).sort());
|
|
151
|
+
return crypto_0.createHash('sha1').update(jsonStr).digest('hex');
|
|
152
|
+
}
|
|
153
|
+
cleanText(el) {
|
|
154
|
+
const clone = el.clone();
|
|
155
|
+
clone.find('.rp-header-anchor').remove();
|
|
156
|
+
clone.find('br').replaceWith(' ');
|
|
157
|
+
clone.find('li, p, div, tr').append(' ');
|
|
158
|
+
let text = clone.text();
|
|
159
|
+
text = text.replace(/\u00A0/g, ' ').replace(/[\u200B-\u200D\uFEFF]/g, '').replace(/\s+/g, ' ').trim();
|
|
160
|
+
if (text.length > 10000) text = text.slice(0, 10000) + '...';
|
|
161
|
+
return text;
|
|
162
|
+
}
|
|
163
|
+
constructor(options){
|
|
164
|
+
_define_property(this, "levels", [
|
|
165
|
+
'lvl0',
|
|
166
|
+
'lvl1',
|
|
167
|
+
'lvl2',
|
|
168
|
+
'lvl3',
|
|
169
|
+
'lvl4',
|
|
170
|
+
'lvl5',
|
|
171
|
+
'lvl6'
|
|
172
|
+
]);
|
|
173
|
+
_define_property(this, "selectors", void 0);
|
|
174
|
+
const docClass = '[data-pagefind-body]';
|
|
175
|
+
let contentSelector = `${docClass} p, ${docClass} li, ${docClass} td, ${docClass} th`;
|
|
176
|
+
if (options?.indexCodeBlocks) contentSelector += `${docClass} pre > code`;
|
|
177
|
+
this.selectors = {
|
|
178
|
+
lvl0: {
|
|
179
|
+
selector: 'header nav [aria-current=true]',
|
|
180
|
+
global: true
|
|
181
|
+
},
|
|
182
|
+
lvl1: {
|
|
183
|
+
selector: `${docClass} h1`,
|
|
184
|
+
global: false
|
|
185
|
+
},
|
|
186
|
+
lvl2: {
|
|
187
|
+
selector: `${docClass} h2`,
|
|
188
|
+
global: false
|
|
189
|
+
},
|
|
190
|
+
lvl3: {
|
|
191
|
+
selector: `${docClass} h3`,
|
|
192
|
+
global: false
|
|
193
|
+
},
|
|
194
|
+
lvl4: {
|
|
195
|
+
selector: `${docClass} h4`,
|
|
196
|
+
global: false
|
|
197
|
+
},
|
|
198
|
+
lvl5: {
|
|
199
|
+
selector: `${docClass} h5`,
|
|
200
|
+
global: false
|
|
201
|
+
},
|
|
202
|
+
lvl6: {
|
|
203
|
+
selector: `${docClass} h6`,
|
|
204
|
+
global: false
|
|
205
|
+
},
|
|
206
|
+
content: {
|
|
207
|
+
selector: contentSelector,
|
|
208
|
+
global: false
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
export { IndexFromHtml };
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { CollectionFieldSchema } from 'typesense/lib/Typesense/Collection';
|
|
2
|
+
import type { CollectionCreateSchema } from 'typesense/lib/Typesense/Collections';
|
|
3
|
+
export interface Hierarchy {
|
|
4
|
+
[key: string]: string | null | undefined;
|
|
5
|
+
lvl0: string | null;
|
|
6
|
+
lvl1: string | null;
|
|
7
|
+
lvl2: string | null;
|
|
8
|
+
lvl3: string | null;
|
|
9
|
+
lvl4: string | null;
|
|
10
|
+
lvl5: string | null;
|
|
11
|
+
lvl6: string | null;
|
|
12
|
+
}
|
|
13
|
+
export interface RecordWeight {
|
|
14
|
+
page_rank: number;
|
|
15
|
+
level: number;
|
|
16
|
+
position: number;
|
|
17
|
+
position_descending: number;
|
|
18
|
+
}
|
|
19
|
+
export interface DocSearchRecord {
|
|
20
|
+
objectID: string;
|
|
21
|
+
url: string;
|
|
22
|
+
url_without_anchor: string;
|
|
23
|
+
anchor: string | null;
|
|
24
|
+
content: string | null;
|
|
25
|
+
hierarchy: Hierarchy;
|
|
26
|
+
hierarchy_radio: Hierarchy;
|
|
27
|
+
type: string;
|
|
28
|
+
weight: RecordWeight;
|
|
29
|
+
version?: string;
|
|
30
|
+
language?: string;
|
|
31
|
+
[key: string]: any;
|
|
32
|
+
}
|
|
33
|
+
export interface FieldsParams {
|
|
34
|
+
locale: string;
|
|
35
|
+
isVersioned: boolean;
|
|
36
|
+
}
|
|
37
|
+
export interface CustomCollectionSettings {
|
|
38
|
+
token_separators?: CollectionCreateSchema['token_separators'];
|
|
39
|
+
symbols_to_index?: CollectionCreateSchema['symbols_to_index'];
|
|
40
|
+
fields?: (params: FieldsParams) => CollectionFieldSchema[];
|
|
41
|
+
enable_nested_fields?: CollectionCreateSchema['enable_nested_fields'];
|
|
42
|
+
}
|
|
43
|
+
/** Allows a single global config OR a map of configs keyed by language (e.g. `{ en: {...}, zh: {...} }`) */
|
|
44
|
+
export type CustomCollectionSettingsConfig = CustomCollectionSettings | Record<string, CustomCollectionSettings>;
|
package/dist/types.js
ADDED
|
File without changes
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { ConfigurationOptions } from 'typesense/lib/Typesense/Configuration';
|
|
2
|
+
import type { DocSearchRecord, CustomCollectionSettings, FieldsParams } from './types.js';
|
|
3
|
+
import type { CollectionFieldSchema } from 'typesense/lib/Typesense/Collection';
|
|
4
|
+
export interface TypesenseHelperOptions {
|
|
5
|
+
config: ConfigurationOptions;
|
|
6
|
+
aliasName: string;
|
|
7
|
+
collectionNameTmp: string;
|
|
8
|
+
customSettings: CustomCollectionSettings | null;
|
|
9
|
+
locale: string;
|
|
10
|
+
isVersioned: boolean;
|
|
11
|
+
batchSize: number;
|
|
12
|
+
}
|
|
13
|
+
export declare class TypesenseHelper {
|
|
14
|
+
private typesenseClient;
|
|
15
|
+
private aliasName;
|
|
16
|
+
private collectionNameTmp;
|
|
17
|
+
private collectionLocale;
|
|
18
|
+
private customSettings;
|
|
19
|
+
private isVersioned;
|
|
20
|
+
private typesenseVersion;
|
|
21
|
+
private recordBatch;
|
|
22
|
+
private readonly batchSize;
|
|
23
|
+
constructor(options: TypesenseHelperOptions);
|
|
24
|
+
init(): Promise<void>;
|
|
25
|
+
createTmpCollection(): Promise<void>;
|
|
26
|
+
/**
|
|
27
|
+
* Adds records to an internal batch queue and logs the file stats instantly.
|
|
28
|
+
* Uploads to Typesense automatically when the queue hits batchSize.
|
|
29
|
+
*/
|
|
30
|
+
addRecords(records: DocSearchRecord[], fileName: string, padLength?: number): Promise<number>;
|
|
31
|
+
/**
|
|
32
|
+
* Internal method that actually performs the network request to Typesense.
|
|
33
|
+
* Throws a specific error so the main asyncPool loop knows it's a batch failure, not a file parsing failure.
|
|
34
|
+
*/
|
|
35
|
+
private uploadBatch;
|
|
36
|
+
/**
|
|
37
|
+
* Force uploads any remaining records that did not cleanly hit the batchSize limit.
|
|
38
|
+
* Must be called before `commitTmpCollection()`.
|
|
39
|
+
*/
|
|
40
|
+
flushBatch(): Promise<void>;
|
|
41
|
+
commitTmpCollection(): Promise<void>;
|
|
42
|
+
static transformRecord(record: DocSearchRecord, isVersioned: boolean): any;
|
|
43
|
+
private getOldCollectionName;
|
|
44
|
+
private transferSynonyms;
|
|
45
|
+
private transferOverrides;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Returns the default collection fields used when creating a Typesense collection.
|
|
49
|
+
*
|
|
50
|
+
* Use this when you need to customize specific fields without redefining the entire schema.
|
|
51
|
+
* The `version` field is automatically included when `isVersioned` is `true`.
|
|
52
|
+
*
|
|
53
|
+
* @param locale - The locale used for text fields (e.g. `'en'`, `'zh'`).
|
|
54
|
+
* @param isVersioned - When `true`, appends the `version` field to the returned fields.
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* // Adding a custom field while keeping defaults:
|
|
58
|
+
* customLocaleCollectionSettings: {
|
|
59
|
+
* en: {
|
|
60
|
+
* fields: (params) => [
|
|
61
|
+
* ...getDefaultCollectionFields(params),
|
|
62
|
+
* { name: 'my_custom_field', type: 'string', locale },
|
|
63
|
+
* ],
|
|
64
|
+
* },
|
|
65
|
+
* }
|
|
66
|
+
*/
|
|
67
|
+
export declare function getDefaultCollectionFields({ locale, isVersioned, }: FieldsParams): CollectionFieldSchema[];
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { Client } from "typesense";
|
|
2
|
+
function _define_property(obj, key, value) {
|
|
3
|
+
if (key in obj) Object.defineProperty(obj, key, {
|
|
4
|
+
value: value,
|
|
5
|
+
enumerable: true,
|
|
6
|
+
configurable: true,
|
|
7
|
+
writable: true
|
|
8
|
+
});
|
|
9
|
+
else obj[key] = value;
|
|
10
|
+
return obj;
|
|
11
|
+
}
|
|
12
|
+
class TypesenseHelper {
|
|
13
|
+
async init() {
|
|
14
|
+
const debugInfo = await this.typesenseClient.debug.retrieve();
|
|
15
|
+
const version = debugInfo.version;
|
|
16
|
+
if ('nightly' === version) this.typesenseVersion = 30;
|
|
17
|
+
else this.typesenseVersion = parseInt(version.split('.')[0], 10);
|
|
18
|
+
}
|
|
19
|
+
async createTmpCollection() {
|
|
20
|
+
if (0 === this.typesenseVersion) await this.init();
|
|
21
|
+
try {
|
|
22
|
+
await this.typesenseClient.collections(this.collectionNameTmp).delete();
|
|
23
|
+
} catch (error) {
|
|
24
|
+
if (error?.httpStatus !== 404) {
|
|
25
|
+
console.error('[TypesensePlugin] Error:');
|
|
26
|
+
throw error;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const schema = {
|
|
30
|
+
name: this.collectionNameTmp,
|
|
31
|
+
fields: getDefaultCollectionFields({
|
|
32
|
+
locale: this.collectionLocale,
|
|
33
|
+
isVersioned: this.isVersioned
|
|
34
|
+
}),
|
|
35
|
+
default_sorting_field: 'item_priority',
|
|
36
|
+
token_separators: [
|
|
37
|
+
'_',
|
|
38
|
+
'-'
|
|
39
|
+
]
|
|
40
|
+
};
|
|
41
|
+
if (this.customSettings) {
|
|
42
|
+
if (this.customSettings.token_separators) schema.token_separators = this.customSettings.token_separators;
|
|
43
|
+
if (this.customSettings.fields) schema.fields = this.customSettings.fields({
|
|
44
|
+
locale: this.collectionLocale,
|
|
45
|
+
isVersioned: this.isVersioned
|
|
46
|
+
});
|
|
47
|
+
if (this.customSettings.symbols_to_index) schema.symbols_to_index = this.customSettings.symbols_to_index;
|
|
48
|
+
if (void 0 !== this.customSettings.enable_nested_fields) schema.enable_nested_fields = this.customSettings.enable_nested_fields;
|
|
49
|
+
}
|
|
50
|
+
await this.typesenseClient.collections().create(schema);
|
|
51
|
+
}
|
|
52
|
+
async addRecords(records, fileName, padLength = 40) {
|
|
53
|
+
const transformedRecords = records.map((r)=>TypesenseHelper.transformRecord(r, this.isVersioned));
|
|
54
|
+
const recordCount = transformedRecords.length;
|
|
55
|
+
const fillerLength = Math.max(2, padLength - fileName.length);
|
|
56
|
+
const filler = '\x1b[90m' + '.'.repeat(fillerLength) + '\x1b[0m';
|
|
57
|
+
const paddedCount = recordCount.toString().padStart(4, ' ');
|
|
58
|
+
console.log(` \x1b[32m✔\x1b[0m \x1b[37m${fileName}\x1b[0m ${filler} \x1b[33m${paddedCount}\x1b[0m \x1b[90mrecords\x1b[0m`);
|
|
59
|
+
this.recordBatch.push(...transformedRecords);
|
|
60
|
+
if (this.recordBatch.length >= this.batchSize) {
|
|
61
|
+
const batchToUpload = this.recordBatch.splice(0, this.batchSize);
|
|
62
|
+
await this.uploadBatch(batchToUpload);
|
|
63
|
+
}
|
|
64
|
+
return recordCount;
|
|
65
|
+
}
|
|
66
|
+
async uploadBatch(batch) {
|
|
67
|
+
if (0 === batch.length) return;
|
|
68
|
+
try {
|
|
69
|
+
const results = await this.typesenseClient.collections(this.collectionNameTmp).documents().import(batch, {
|
|
70
|
+
action: 'create'
|
|
71
|
+
});
|
|
72
|
+
const failedItems = results.filter((r)=>false === r.success);
|
|
73
|
+
if (failedItems.length > 0) {
|
|
74
|
+
console.error('\n\x1b[31m[Batch Error]\x1b[0m Typesense rejected some records in this batch.');
|
|
75
|
+
console.error(JSON.stringify(failedItems.slice(0, 5), null, 2));
|
|
76
|
+
throw new Error('TYPESENSE_BATCH_ERROR');
|
|
77
|
+
}
|
|
78
|
+
} catch (err) {
|
|
79
|
+
if ('TYPESENSE_BATCH_ERROR' !== err.message) console.error('\n\x1b[31m[Network Error]\x1b[0m Failed to reach Typesense server during batch upload.');
|
|
80
|
+
throw new Error('TYPESENSE_BATCH_ERROR');
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
async flushBatch() {
|
|
84
|
+
if (this.recordBatch.length > 0) {
|
|
85
|
+
const batchToUpload = this.recordBatch.splice(0, this.recordBatch.length);
|
|
86
|
+
await this.uploadBatch(batchToUpload);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
async commitTmpCollection() {
|
|
90
|
+
const oldCollectionName = await this.getOldCollectionName();
|
|
91
|
+
if (oldCollectionName) {
|
|
92
|
+
await this.transferSynonyms(oldCollectionName);
|
|
93
|
+
await this.transferOverrides(oldCollectionName);
|
|
94
|
+
}
|
|
95
|
+
await this.typesenseClient.aliases().upsert(this.aliasName, {
|
|
96
|
+
collection_name: this.collectionNameTmp
|
|
97
|
+
});
|
|
98
|
+
if (oldCollectionName) await this.typesenseClient.collections(oldCollectionName).delete();
|
|
99
|
+
}
|
|
100
|
+
static transformRecord(record, isVersioned) {
|
|
101
|
+
const transformedRecord = {};
|
|
102
|
+
const excludeKeys = [
|
|
103
|
+
'weight',
|
|
104
|
+
'language',
|
|
105
|
+
'tags',
|
|
106
|
+
'.*_tag'
|
|
107
|
+
];
|
|
108
|
+
for(const key in record)if (null !== record[key] && void 0 !== record[key] && !excludeKeys.includes(key)) transformedRecord[key] = record[key];
|
|
109
|
+
if (record.weight) transformedRecord['item_priority'] = 1000000000 * record.weight.page_rank + 1000 * record.weight.level + record.weight.position_descending;
|
|
110
|
+
else transformedRecord['item_priority'] = 0;
|
|
111
|
+
for(let x = 0; x < 7; x++){
|
|
112
|
+
const lvlKey = `lvl${x}`;
|
|
113
|
+
if (record.hierarchy && null != record.hierarchy[lvlKey]) transformedRecord[`hierarchy.lvl${x}`] = record.hierarchy[lvlKey];
|
|
114
|
+
if (record.hierarchy_radio && null != record.hierarchy_radio[lvlKey]) transformedRecord[`hierarchy_radio.lvl${x}`] = record.hierarchy_radio[lvlKey];
|
|
115
|
+
}
|
|
116
|
+
if (isVersioned && record.version && 'string' == typeof record.version) transformedRecord['version'] = record.version;
|
|
117
|
+
else if (!isVersioned) delete transformedRecord['version'];
|
|
118
|
+
return transformedRecord;
|
|
119
|
+
}
|
|
120
|
+
async getOldCollectionName() {
|
|
121
|
+
try {
|
|
122
|
+
const alias = await this.typesenseClient.aliases(this.aliasName).retrieve();
|
|
123
|
+
return alias.collection_name;
|
|
124
|
+
} catch (error) {
|
|
125
|
+
if (error?.httpStatus === 404) return null;
|
|
126
|
+
throw error;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
async transferSynonyms(oldCollectionName) {
|
|
130
|
+
if (this.typesenseVersion >= 30) {
|
|
131
|
+
const oldCollection = await this.typesenseClient.collections(oldCollectionName).retrieve();
|
|
132
|
+
const synonyms = oldCollection.synonym_sets || [];
|
|
133
|
+
for (const syn of synonyms)await this.typesenseClient.collections(this.collectionNameTmp).synonyms().upsert(syn.id, syn);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const result = await this.typesenseClient.collections(oldCollectionName).synonyms().retrieve();
|
|
137
|
+
const synonyms = result.synonyms || [];
|
|
138
|
+
for (const synonym of synonyms){
|
|
139
|
+
const { id, ...synonymKeys } = synonym;
|
|
140
|
+
await this.typesenseClient.collections(this.collectionNameTmp).synonyms().upsert(synonym.id, synonymKeys);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
async transferOverrides(oldCollectionName) {
|
|
144
|
+
if (this.typesenseVersion >= 30) {
|
|
145
|
+
const oldCollection = await this.typesenseClient.collections(oldCollectionName).retrieve();
|
|
146
|
+
const curations = oldCollection.curation_sets || [];
|
|
147
|
+
for (const cur of curations)await this.typesenseClient.collections(this.collectionNameTmp).overrides().upsert(cur.id, cur);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const result = await this.typesenseClient.collections(oldCollectionName).overrides().retrieve();
|
|
151
|
+
const overrides = result.overrides || [];
|
|
152
|
+
for (const override of overrides){
|
|
153
|
+
const { id, ...overrideKeys } = override;
|
|
154
|
+
await this.typesenseClient.collections(this.collectionNameTmp).overrides().upsert(override.id, overrideKeys);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
constructor(options){
|
|
158
|
+
_define_property(this, "typesenseClient", void 0);
|
|
159
|
+
_define_property(this, "aliasName", void 0);
|
|
160
|
+
_define_property(this, "collectionNameTmp", void 0);
|
|
161
|
+
_define_property(this, "collectionLocale", void 0);
|
|
162
|
+
_define_property(this, "customSettings", void 0);
|
|
163
|
+
_define_property(this, "isVersioned", void 0);
|
|
164
|
+
_define_property(this, "typesenseVersion", 0);
|
|
165
|
+
_define_property(this, "recordBatch", []);
|
|
166
|
+
_define_property(this, "batchSize", void 0);
|
|
167
|
+
const clientConfig = {
|
|
168
|
+
...options.config
|
|
169
|
+
};
|
|
170
|
+
clientConfig.connectionTimeoutSeconds = clientConfig.connectionTimeoutSeconds || 1800;
|
|
171
|
+
clientConfig.retryIntervalSeconds = clientConfig.retryIntervalSeconds || 1;
|
|
172
|
+
clientConfig.numRetries = clientConfig.numRetries || 3;
|
|
173
|
+
clientConfig.healthcheckIntervalSeconds = clientConfig.healthcheckIntervalSeconds || 60;
|
|
174
|
+
clientConfig.logLevel = clientConfig.logLevel || 'error';
|
|
175
|
+
this.typesenseClient = new Client(clientConfig);
|
|
176
|
+
this.aliasName = options.aliasName;
|
|
177
|
+
this.collectionNameTmp = options.collectionNameTmp;
|
|
178
|
+
this.collectionLocale = options.locale || process.env.TYPESENSE_COLLECTION_LOCALE || 'en';
|
|
179
|
+
this.customSettings = options.customSettings;
|
|
180
|
+
this.isVersioned = options.isVersioned;
|
|
181
|
+
this.batchSize = options.batchSize;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
function getDefaultCollectionFields({ locale, isVersioned }) {
|
|
185
|
+
const baseFields = [
|
|
186
|
+
{
|
|
187
|
+
name: 'anchor',
|
|
188
|
+
type: 'string',
|
|
189
|
+
optional: true
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
name: 'content',
|
|
193
|
+
type: 'string',
|
|
194
|
+
locale,
|
|
195
|
+
optional: true
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
name: 'url',
|
|
199
|
+
type: 'string',
|
|
200
|
+
facet: true
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
name: 'url_without_anchor',
|
|
204
|
+
type: 'string',
|
|
205
|
+
facet: true,
|
|
206
|
+
optional: true
|
|
207
|
+
},
|
|
208
|
+
...Array.from({
|
|
209
|
+
length: 7
|
|
210
|
+
}).map((_, i)=>({
|
|
211
|
+
name: `hierarchy.lvl${i}`,
|
|
212
|
+
type: 'string',
|
|
213
|
+
facet: true,
|
|
214
|
+
locale,
|
|
215
|
+
optional: true
|
|
216
|
+
})),
|
|
217
|
+
{
|
|
218
|
+
name: 'type',
|
|
219
|
+
type: 'string',
|
|
220
|
+
facet: true,
|
|
221
|
+
optional: true
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
name: 'item_priority',
|
|
225
|
+
type: 'int64'
|
|
226
|
+
}
|
|
227
|
+
];
|
|
228
|
+
if (isVersioned) baseFields.push({
|
|
229
|
+
name: 'version',
|
|
230
|
+
type: 'string',
|
|
231
|
+
facet: true,
|
|
232
|
+
optional: true
|
|
233
|
+
});
|
|
234
|
+
return baseFields;
|
|
235
|
+
}
|
|
236
|
+
export { TypesenseHelper, getDefaultCollectionFields };
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "typesense-nextra-adapter",
|
|
3
|
+
"description": "An adapter that adds lightning-fast, typo-tolerant, Typesense-powered search to your Nextra site",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"version": "0.0.1",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"build": "rslib build",
|
|
8
|
+
"dev": "rslib build --watch"
|
|
9
|
+
},
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"./client": {
|
|
16
|
+
"types": "./dist/client/index.d.ts",
|
|
17
|
+
"default": "./dist/client/index.js"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"cheerio": "1.2.0",
|
|
26
|
+
"typesense": "3.0.6",
|
|
27
|
+
"typesense-docsearch-react": "3.4.1"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@changesets/cli": "2.31.0",
|
|
31
|
+
"@rsbuild/plugin-react": "2.0.0",
|
|
32
|
+
"@rslib/core": "0.20.1",
|
|
33
|
+
"@types/bun": "latest",
|
|
34
|
+
"@types/react": "19.2.15",
|
|
35
|
+
"@types/react-dom": "19.2.3",
|
|
36
|
+
"rsbuild-plugin-publint": "0.3.4"
|
|
37
|
+
},
|
|
38
|
+
"peerDependencies": {
|
|
39
|
+
"typescript": "5"
|
|
40
|
+
},
|
|
41
|
+
"keywords": [
|
|
42
|
+
"typesense",
|
|
43
|
+
"documentation-search",
|
|
44
|
+
"search",
|
|
45
|
+
"nextra-plugin",
|
|
46
|
+
"nextra-adapter",
|
|
47
|
+
"nextra-search",
|
|
48
|
+
"nextra",
|
|
49
|
+
"plugin"
|
|
50
|
+
],
|
|
51
|
+
"repository": {
|
|
52
|
+
"type": "git",
|
|
53
|
+
"url": "git+https://github.com/typesense/typesense-nextra-adapter.git"
|
|
54
|
+
},
|
|
55
|
+
"bugs": "https://github.com/typesense/typesense-nextra-adapter/issues",
|
|
56
|
+
"license": "Apache-2.0",
|
|
57
|
+
"publishConfig": {
|
|
58
|
+
"access": "public"
|
|
59
|
+
}
|
|
60
|
+
}
|