@tinacms/search 0.0.0-ef282d9-20241024212433 → 0.0.0-f1cec43-20251216232909
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 +55 -0
- package/dist/client/index.d.ts +28 -13
- package/dist/fuzzy/cache.d.ts +11 -0
- package/dist/fuzzy/distance.d.ts +15 -0
- package/dist/fuzzy/index.d.ts +4 -0
- package/dist/fuzzy/types.d.ts +19 -0
- package/dist/fuzzy-search-wrapper.d.ts +46 -0
- package/dist/index-client.d.ts +30 -13
- package/dist/index-client.js +189 -205
- package/dist/index.d.ts +6 -1
- package/dist/index.js +545 -209
- package/dist/indexer/index.d.ts +1 -0
- package/dist/indexer/utils.d.ts +1 -1
- package/dist/pagination.d.ts +22 -0
- package/dist/types.d.ts +30 -11
- package/package.json +18 -19
- package/dist/index-client.mjs +0 -195
package/dist/index-client.js
CHANGED
|
@@ -1,215 +1,199 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
"
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
n.default = e;
|
|
19
|
-
return Object.freeze(n);
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
|
+
var __publicField = (obj, key, value) => {
|
|
4
|
+
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
5
|
+
return value;
|
|
6
|
+
};
|
|
7
|
+
import * as sw from "stopword";
|
|
8
|
+
const INDEXABLE_NODE_TYPES = ["text", "code_block", "html"];
|
|
9
|
+
class StringBuilder {
|
|
10
|
+
constructor(limit) {
|
|
11
|
+
__publicField(this, "buffer", []);
|
|
12
|
+
__publicField(this, "limit");
|
|
13
|
+
__publicField(this, "length", 0);
|
|
14
|
+
this.limit = limit;
|
|
20
15
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
16
|
+
append(str) {
|
|
17
|
+
if (this.length + str.length > this.limit)
|
|
18
|
+
return true;
|
|
19
|
+
this.buffer.push(str);
|
|
20
|
+
this.length += str.length;
|
|
21
|
+
return this.length > this.limit;
|
|
22
|
+
}
|
|
23
|
+
toString() {
|
|
24
|
+
return this.buffer.join(" ");
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const tokenizeString = (str) => {
|
|
28
|
+
return str.split(/[\s\.,]+/).map((s) => s.toLowerCase()).filter((s) => s);
|
|
29
|
+
};
|
|
30
|
+
const extractText = (data, builder, nodeTypes) => {
|
|
31
|
+
var _a;
|
|
32
|
+
if (!data)
|
|
33
|
+
return;
|
|
34
|
+
if (nodeTypes.includes(data.type ?? "") && (data.text || data.value)) {
|
|
35
|
+
const tokens = tokenizeString(data.text || data.value || "");
|
|
36
|
+
for (const token of tokens) {
|
|
37
|
+
if (builder.append(token))
|
|
38
|
+
return;
|
|
42
39
|
}
|
|
43
40
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
41
|
+
(_a = data.children) == null ? void 0 : _a.forEach((child) => extractText(child, builder, nodeTypes));
|
|
42
|
+
};
|
|
43
|
+
const getRelativePath = (path, collection) => {
|
|
44
|
+
return path.replace(/\\/g, "/").replace(collection.path, "").replace(/^\/|\/$/g, "");
|
|
45
|
+
};
|
|
46
|
+
const processTextField = (value, maxLength) => {
|
|
47
|
+
const tokens = tokenizeString(value);
|
|
48
|
+
const builder = new StringBuilder(maxLength);
|
|
49
|
+
for (const part of tokens) {
|
|
50
|
+
if (builder.append(part))
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
return builder.toString();
|
|
54
|
+
};
|
|
55
|
+
const processRichTextField = (value, maxLength) => {
|
|
56
|
+
const builder = new StringBuilder(maxLength);
|
|
57
|
+
extractText(value, builder, INDEXABLE_NODE_TYPES);
|
|
58
|
+
return builder.toString();
|
|
59
|
+
};
|
|
60
|
+
const processObjectField = (data, path, collection, textIndexLength, field) => {
|
|
61
|
+
if (field.list) {
|
|
62
|
+
return data.map(
|
|
63
|
+
(obj) => processDocumentForIndexing(obj, path, collection, textIndexLength, field)
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
return processDocumentForIndexing(
|
|
67
|
+
data,
|
|
68
|
+
path,
|
|
69
|
+
collection,
|
|
70
|
+
textIndexLength,
|
|
71
|
+
field
|
|
72
|
+
);
|
|
73
|
+
};
|
|
74
|
+
const processStringField = (data, maxLength, isList) => {
|
|
75
|
+
if (isList) {
|
|
76
|
+
return data.map(
|
|
77
|
+
(value) => processTextField(value, maxLength)
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
return processTextField(data, maxLength);
|
|
81
|
+
};
|
|
82
|
+
const processRichTextFieldData = (data, maxLength, isList) => {
|
|
83
|
+
if (isList) {
|
|
84
|
+
return data.map(
|
|
85
|
+
(value) => processRichTextField(value, maxLength)
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
return processRichTextField(data, maxLength);
|
|
89
|
+
};
|
|
90
|
+
const processDocumentForIndexing = (data, path, collection, textIndexLength, field) => {
|
|
91
|
+
if (!field) {
|
|
92
|
+
const relativePath = getRelativePath(path, collection);
|
|
93
|
+
data["_id"] = `${collection.name}:${relativePath}`;
|
|
94
|
+
data["_relativePath"] = relativePath;
|
|
95
|
+
}
|
|
96
|
+
const fields = (field == null ? void 0 : field.fields) || collection.fields || [];
|
|
97
|
+
for (const f of fields) {
|
|
98
|
+
if (!f.searchable) {
|
|
99
|
+
delete data[f.name];
|
|
100
|
+
continue;
|
|
59
101
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
102
|
+
if (!data[f.name])
|
|
103
|
+
continue;
|
|
104
|
+
const fieldMaxLength = f.maxSearchIndexFieldLength || textIndexLength;
|
|
105
|
+
const isList = Boolean(f.list);
|
|
106
|
+
switch (f.type) {
|
|
107
|
+
case "object":
|
|
108
|
+
data[f.name] = processObjectField(
|
|
109
|
+
data[f.name],
|
|
110
|
+
path,
|
|
111
|
+
collection,
|
|
112
|
+
textIndexLength,
|
|
113
|
+
f
|
|
114
|
+
);
|
|
115
|
+
break;
|
|
116
|
+
case "string":
|
|
117
|
+
data[f.name] = processStringField(
|
|
118
|
+
data[f.name],
|
|
119
|
+
fieldMaxLength,
|
|
120
|
+
isList
|
|
121
|
+
);
|
|
122
|
+
break;
|
|
123
|
+
case "rich-text":
|
|
124
|
+
data[f.name] = processRichTextFieldData(
|
|
125
|
+
data[f.name],
|
|
126
|
+
fieldMaxLength,
|
|
127
|
+
isList
|
|
128
|
+
);
|
|
72
129
|
break;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
return builder.toString();
|
|
76
|
-
};
|
|
77
|
-
const processDocumentForIndexing = (data, path, collection, textIndexLength, field) => {
|
|
78
|
-
if (!field) {
|
|
79
|
-
const relPath = relativePath(path, collection);
|
|
80
|
-
data["_id"] = `${collection.name}:${relPath}`;
|
|
81
|
-
data["_relativePath"] = relPath;
|
|
82
|
-
}
|
|
83
|
-
for (const f of (field == null ? void 0 : field.fields) || collection.fields || []) {
|
|
84
|
-
if (!f.searchable) {
|
|
85
|
-
delete data[f.name];
|
|
86
|
-
continue;
|
|
87
|
-
}
|
|
88
|
-
const isList = f.list;
|
|
89
|
-
if (data[f.name]) {
|
|
90
|
-
if (f.type === "object") {
|
|
91
|
-
if (isList) {
|
|
92
|
-
data[f.name] = data[f.name].map(
|
|
93
|
-
(obj) => processDocumentForIndexing(
|
|
94
|
-
obj,
|
|
95
|
-
path,
|
|
96
|
-
collection,
|
|
97
|
-
textIndexLength,
|
|
98
|
-
f
|
|
99
|
-
)
|
|
100
|
-
);
|
|
101
|
-
} else {
|
|
102
|
-
data[f.name] = processDocumentForIndexing(
|
|
103
|
-
data[f.name],
|
|
104
|
-
path,
|
|
105
|
-
collection,
|
|
106
|
-
textIndexLength,
|
|
107
|
-
f
|
|
108
|
-
);
|
|
109
|
-
}
|
|
110
|
-
} else if (f.type === "string") {
|
|
111
|
-
const fieldTextIndexLength = f.maxSearchIndexFieldLength || textIndexLength;
|
|
112
|
-
if (isList) {
|
|
113
|
-
data[f.name] = data[f.name].map(
|
|
114
|
-
(value) => processTextFieldValue(value, fieldTextIndexLength)
|
|
115
|
-
);
|
|
116
|
-
} else {
|
|
117
|
-
data[f.name] = processTextFieldValue(
|
|
118
|
-
data[f.name],
|
|
119
|
-
fieldTextIndexLength
|
|
120
|
-
);
|
|
121
|
-
}
|
|
122
|
-
} else if (f.type === "rich-text") {
|
|
123
|
-
const fieldTextIndexLength = f.maxSearchIndexFieldLength || textIndexLength;
|
|
124
|
-
if (isList) {
|
|
125
|
-
data[f.name] = data[f.name].map((value) => {
|
|
126
|
-
const acc = new StringBuilder(fieldTextIndexLength);
|
|
127
|
-
extractText(value, acc, ["text", "code_block", "html"]);
|
|
128
|
-
return acc.toString();
|
|
129
|
-
});
|
|
130
|
-
} else {
|
|
131
|
-
const acc = new StringBuilder(fieldTextIndexLength);
|
|
132
|
-
extractText(data[f.name], acc, ["text", "code_block", "html"]);
|
|
133
|
-
data[f.name] = acc.toString();
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
return data;
|
|
139
|
-
};
|
|
140
|
-
const memo = {};
|
|
141
|
-
const lookupStopwords = (keys, defaultStopWords = sw__namespace.eng) => {
|
|
142
|
-
let stopwords = defaultStopWords;
|
|
143
|
-
if (keys) {
|
|
144
|
-
if (memo[keys.join(",")]) {
|
|
145
|
-
return memo[keys.join(",")];
|
|
146
|
-
}
|
|
147
|
-
stopwords = [];
|
|
148
|
-
for (const key of keys) {
|
|
149
|
-
stopwords.push(...sw__namespace[key]);
|
|
150
|
-
}
|
|
151
|
-
memo[keys.join(",")] = stopwords;
|
|
152
130
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
(part) => part.toLowerCase() !== "and" && stopwords.indexOf(part.toLowerCase()) === -1
|
|
165
|
-
)
|
|
166
|
-
};
|
|
131
|
+
}
|
|
132
|
+
return data;
|
|
133
|
+
};
|
|
134
|
+
const stopwordCache = {};
|
|
135
|
+
const PRESERVED_WORDS = ["about"];
|
|
136
|
+
const lookupStopwords = (keys, defaultStopWords = sw.eng) => {
|
|
137
|
+
let stopwords = defaultStopWords;
|
|
138
|
+
if (keys) {
|
|
139
|
+
const cacheKey = keys.join(",");
|
|
140
|
+
if (stopwordCache[cacheKey]) {
|
|
141
|
+
return stopwordCache[cacheKey];
|
|
167
142
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
143
|
+
stopwords = keys.flatMap((key) => sw[key] || []);
|
|
144
|
+
stopwordCache[cacheKey] = stopwords;
|
|
145
|
+
}
|
|
146
|
+
return stopwords.filter((word) => !PRESERVED_WORDS.includes(word));
|
|
147
|
+
};
|
|
148
|
+
const queryToSearchIndexQuery = (query, stopwordLanguages) => {
|
|
149
|
+
const parts = query.split(" ");
|
|
150
|
+
const stopwords = lookupStopwords(stopwordLanguages);
|
|
151
|
+
if (parts.length === 1) {
|
|
152
|
+
return { AND: [parts[0]] };
|
|
153
|
+
}
|
|
154
|
+
const filteredParts = parts.filter(
|
|
155
|
+
(part) => part.toLowerCase() !== "and" && !stopwords.includes(part.toLowerCase())
|
|
156
|
+
);
|
|
157
|
+
return { AND: filteredParts };
|
|
158
|
+
};
|
|
159
|
+
const optionsToSearchIndexOptions = (options) => {
|
|
160
|
+
if (!(options == null ? void 0 : options.limit))
|
|
161
|
+
return {};
|
|
162
|
+
return {
|
|
163
|
+
PAGE: {
|
|
164
|
+
SIZE: options.limit,
|
|
165
|
+
NUMBER: options.cursor ? parseInt(options.cursor) : 0
|
|
177
166
|
}
|
|
178
|
-
return opt;
|
|
179
167
|
};
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
total,
|
|
205
|
-
prevCursor: null,
|
|
206
|
-
nextCursor: null
|
|
207
|
-
};
|
|
208
|
-
}
|
|
168
|
+
};
|
|
169
|
+
const parseSearchIndexResponse = (data, options) => {
|
|
170
|
+
const results = data.RESULT;
|
|
171
|
+
const total = data.RESULT_LENGTH;
|
|
172
|
+
const fuzzyMatches = data.FUZZY_MATCHES;
|
|
173
|
+
if (data.NEXT_CURSOR !== void 0 || data.PREV_CURSOR !== void 0) {
|
|
174
|
+
return {
|
|
175
|
+
results,
|
|
176
|
+
total,
|
|
177
|
+
prevCursor: data.PREV_CURSOR ?? null,
|
|
178
|
+
nextCursor: data.NEXT_CURSOR ?? null,
|
|
179
|
+
fuzzyMatches
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
const currentPage = (options == null ? void 0 : options.cursor) ? parseInt(options.cursor) : 0;
|
|
183
|
+
const pageSize = options == null ? void 0 : options.limit;
|
|
184
|
+
const hasPreviousPage = currentPage > 0;
|
|
185
|
+
const hasNextPage = pageSize ? total > (currentPage + 1) * pageSize : false;
|
|
186
|
+
return {
|
|
187
|
+
results,
|
|
188
|
+
total,
|
|
189
|
+
prevCursor: hasPreviousPage ? (currentPage - 1).toString() : null,
|
|
190
|
+
nextCursor: hasNextPage ? (currentPage + 1).toString() : null,
|
|
191
|
+
fuzzyMatches
|
|
209
192
|
};
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
193
|
+
};
|
|
194
|
+
export {
|
|
195
|
+
optionsToSearchIndexOptions,
|
|
196
|
+
parseSearchIndexResponse,
|
|
197
|
+
processDocumentForIndexing,
|
|
198
|
+
queryToSearchIndexQuery
|
|
199
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import si from 'search-index';
|
|
2
2
|
export { SearchIndexer } from './indexer';
|
|
3
3
|
export { LocalSearchIndexClient, TinaCMSSearchIndexClient } from './client';
|
|
4
|
-
export type { SearchClient } from './types';
|
|
4
|
+
export type { SearchClient, SearchOptions, SearchResult, SearchQueryResponse, IndexableDocument, SearchIndexResult, } from './types';
|
|
5
|
+
export type { FuzzySearchOptions, FuzzyMatch } from './fuzzy';
|
|
6
|
+
export { levenshteinDistance, similarityScore, damerauLevenshteinDistance, findSimilarTerms, FuzzyCache, DEFAULT_FUZZY_OPTIONS, } from './fuzzy';
|
|
7
|
+
export { FuzzySearchWrapper } from './fuzzy-search-wrapper';
|
|
8
|
+
export { buildPageOptions, buildPaginationCursors } from './pagination';
|
|
9
|
+
export type { PaginationOptions, PageOptions, PaginationCursors, } from './pagination';
|
|
5
10
|
export { si };
|