@se-studio/search 1.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/CHANGELOG.md +11 -0
- package/README.md +185 -0
- package/dist/api/index.d.ts +2 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +2 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/route-handler.d.ts +53 -0
- package/dist/api/route-handler.d.ts.map +1 -0
- package/dist/api/route-handler.js +135 -0
- package/dist/api/route-handler.js.map +1 -0
- package/dist/client/index.d.ts +2 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +2 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/search-client.d.ts +12 -0
- package/dist/client/search-client.d.ts.map +1 -0
- package/dist/client/search-client.js +72 -0
- package/dist/client/search-client.js.map +1 -0
- package/dist/debug.d.ts +2 -0
- package/dist/debug.d.ts.map +1 -0
- package/dist/debug.js +12 -0
- package/dist/debug.js.map +1 -0
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +2 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/useSearch.d.ts +26 -0
- package/dist/hooks/useSearch.d.ts.map +1 -0
- package/dist/hooks/useSearch.js +73 -0
- package/dist/hooks/useSearch.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/indexing/content-extractor.d.ts +20 -0
- package/dist/indexing/content-extractor.d.ts.map +1 -0
- package/dist/indexing/content-extractor.js +81 -0
- package/dist/indexing/content-extractor.js.map +1 -0
- package/dist/indexing/document-builder.d.ts +33 -0
- package/dist/indexing/document-builder.d.ts.map +1 -0
- package/dist/indexing/document-builder.js +117 -0
- package/dist/indexing/document-builder.js.map +1 -0
- package/dist/indexing/index.d.ts +4 -0
- package/dist/indexing/index.d.ts.map +1 -0
- package/dist/indexing/index.js +4 -0
- package/dist/indexing/index.js.map +1 -0
- package/dist/indexing/rebuild.d.ts +19 -0
- package/dist/indexing/rebuild.d.ts.map +1 -0
- package/dist/indexing/rebuild.js +133 -0
- package/dist/indexing/rebuild.js.map +1 -0
- package/dist/types.d.ts +112 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/webhook/handler.d.ts +31 -0
- package/dist/webhook/handler.d.ts.map +1 -0
- package/dist/webhook/handler.js +133 -0
- package/dist/webhook/handler.js.map +1 -0
- package/dist/webhook/index.d.ts +2 -0
- package/dist/webhook/index.d.ts.map +1 -0
- package/dist/webhook/index.js +2 -0
- package/dist/webhook/index.js.map +1 -0
- package/package.json +70 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
3
|
+
/**
|
|
4
|
+
* Client-side hook for searching via the app's `/api/search` endpoint.
|
|
5
|
+
* Manages query state, debouncing, loading, and error handling.
|
|
6
|
+
* Apps build their own search UI on top of this hook.
|
|
7
|
+
*/
|
|
8
|
+
export function useSearch(options) {
|
|
9
|
+
const debounceMs = options?.debounceMs ?? 300;
|
|
10
|
+
const limit = options?.limit ?? 20;
|
|
11
|
+
const apiPath = options?.apiPath ?? '/api/search';
|
|
12
|
+
const [query, setQuery] = useState(options?.initialQuery ?? '');
|
|
13
|
+
const [results, setResults] = useState([]);
|
|
14
|
+
const [totalCount, setTotalCount] = useState(0);
|
|
15
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
16
|
+
const [error, setError] = useState(null);
|
|
17
|
+
const abortRef = useRef(null);
|
|
18
|
+
const timerRef = useRef(null);
|
|
19
|
+
const executeSearch = useCallback(async (q) => {
|
|
20
|
+
abortRef.current?.abort();
|
|
21
|
+
if (!q.trim()) {
|
|
22
|
+
setResults([]);
|
|
23
|
+
setTotalCount(0);
|
|
24
|
+
setError(null);
|
|
25
|
+
setIsLoading(false);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const controller = new AbortController();
|
|
29
|
+
abortRef.current = controller;
|
|
30
|
+
setIsLoading(true);
|
|
31
|
+
setError(null);
|
|
32
|
+
try {
|
|
33
|
+
const params = new URLSearchParams({ q, limit: String(limit) });
|
|
34
|
+
const res = await fetch(`${apiPath}?${params.toString()}`, {
|
|
35
|
+
signal: controller.signal,
|
|
36
|
+
});
|
|
37
|
+
if (!res.ok) {
|
|
38
|
+
throw new Error(`Search failed (${res.status})`);
|
|
39
|
+
}
|
|
40
|
+
const data = (await res.json());
|
|
41
|
+
setResults(data.results);
|
|
42
|
+
setTotalCount(data.totalCount);
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
if (err instanceof DOMException && err.name === 'AbortError')
|
|
46
|
+
return;
|
|
47
|
+
setError(err instanceof Error ? err.message : 'Search failed');
|
|
48
|
+
setResults([]);
|
|
49
|
+
setTotalCount(0);
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
setIsLoading(false);
|
|
53
|
+
}
|
|
54
|
+
}, [limit, apiPath]);
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
if (timerRef.current)
|
|
57
|
+
clearTimeout(timerRef.current);
|
|
58
|
+
timerRef.current = setTimeout(() => {
|
|
59
|
+
executeSearch(query);
|
|
60
|
+
}, debounceMs);
|
|
61
|
+
return () => {
|
|
62
|
+
if (timerRef.current)
|
|
63
|
+
clearTimeout(timerRef.current);
|
|
64
|
+
};
|
|
65
|
+
}, [query, debounceMs, executeSearch]);
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
return () => {
|
|
68
|
+
abortRef.current?.abort();
|
|
69
|
+
};
|
|
70
|
+
}, []);
|
|
71
|
+
return { query, setQuery, results, isLoading, error, totalCount };
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=useSearch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useSearch.js","sourceRoot":"","sources":["../../src/hooks/useSearch.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,sBAAsB,EACtB,aAAa,EACb,qBAAqB,EACrB,YAAY,EACZ,cAAc,EACd,sBAAsB,EACtB,iBAAiB,EACjB,oBAAoB,EACpB,aAAa,EACb,cAAc,EACd,YAAY,GACb,MAAM,SAAS,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ContentData, MarkdownConverterContext } from '@se-studio/markdown-renderer';
|
|
2
|
+
/**
|
|
3
|
+
* Calculates the maximum body length given the title and description
|
|
4
|
+
* that will also be included in the Upstash content object.
|
|
5
|
+
*/
|
|
6
|
+
export declare function calculateMaxBodyLength(titleLength: number, descriptionLength: number): number;
|
|
7
|
+
/**
|
|
8
|
+
* Converts CMS content to plain text, then splits it into chunks that each
|
|
9
|
+
* fit within `maxCharsPerChunk`, with `CHUNK_OVERLAP` characters of overlap
|
|
10
|
+
* between consecutive chunks for semantic continuity.
|
|
11
|
+
*
|
|
12
|
+
* @returns Array of body strings — one per chunk. Short content returns a single element.
|
|
13
|
+
*/
|
|
14
|
+
export declare function extractSearchableChunks(contentData: ContentData, converterContext: MarkdownConverterContext, maxCharsPerChunk: number): string[];
|
|
15
|
+
/**
|
|
16
|
+
* Extracts a minimal text body from just the title and description
|
|
17
|
+
* (no deep component traversal). Used when `includeComponents` is false.
|
|
18
|
+
*/
|
|
19
|
+
export declare function extractShallowText(title: string, description: string): string;
|
|
20
|
+
//# sourceMappingURL=content-extractor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content-extractor.d.ts","sourceRoot":"","sources":["../../src/indexing/content-extractor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AAY1F;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,WAAW,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,GAAG,MAAM,CAE7F;AAuCD;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,WAAW,EAAE,WAAW,EACxB,gBAAgB,EAAE,wBAAwB,EAC1C,gBAAgB,EAAE,MAAM,GACvB,MAAM,EAAE,CA8BV;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAE7E"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { cleanMarkdownText, MarkdownConverter } from '@se-studio/markdown-renderer';
|
|
2
|
+
import { debugLog } from '../debug';
|
|
3
|
+
/** Upstash Search hard limit for total content per document. */
|
|
4
|
+
const UPSTASH_CONTENT_LIMIT = 4096;
|
|
5
|
+
/** Buffer for JSON key names and structural overhead in the content object. */
|
|
6
|
+
const JSON_OVERHEAD = 100;
|
|
7
|
+
const CHUNK_OVERLAP = 300;
|
|
8
|
+
/**
|
|
9
|
+
* Calculates the maximum body length given the title and description
|
|
10
|
+
* that will also be included in the Upstash content object.
|
|
11
|
+
*/
|
|
12
|
+
export function calculateMaxBodyLength(titleLength, descriptionLength) {
|
|
13
|
+
return Math.max(0, UPSTASH_CONTENT_LIMIT - titleLength - descriptionLength - JSON_OVERHEAD);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Strips markdown syntax to produce plain text suitable for search indexing.
|
|
17
|
+
* Removes headings markers, bold/italic markers, links, images, and frontmatter.
|
|
18
|
+
*/
|
|
19
|
+
function stripMarkdown(md) {
|
|
20
|
+
let text = md;
|
|
21
|
+
// Remove YAML frontmatter
|
|
22
|
+
text = text.replace(/^---[\s\S]*?---\n*/m, '');
|
|
23
|
+
// Remove images: 
|
|
24
|
+
text = text.replace(/!\[.*?\]\(.*?\)/g, '');
|
|
25
|
+
// Convert links to text: [text](url) → text
|
|
26
|
+
text = text.replace(/\[([^\]]*)\]\([^)]*\)/g, '$1');
|
|
27
|
+
// Remove heading markers: ### Heading → Heading
|
|
28
|
+
text = text.replace(/^#{1,6}\s+/gm, '');
|
|
29
|
+
// Remove bold/italic markers
|
|
30
|
+
text = text.replace(/\*{1,3}([^*]+)\*{1,3}/g, '$1');
|
|
31
|
+
text = text.replace(/_{1,3}([^_]+)_{1,3}/g, '$1');
|
|
32
|
+
// Remove horizontal rules
|
|
33
|
+
text = text.replace(/^[-*_]{3,}\s*$/gm, '');
|
|
34
|
+
// Remove list markers
|
|
35
|
+
text = text.replace(/^[\s]*[-*+]\s+/gm, '');
|
|
36
|
+
text = text.replace(/^[\s]*\d+\.\s+/gm, '');
|
|
37
|
+
// Collapse whitespace
|
|
38
|
+
text = text.replace(/\n{3,}/g, '\n\n');
|
|
39
|
+
text = text.trim();
|
|
40
|
+
return text;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Converts CMS content to plain text, then splits it into chunks that each
|
|
44
|
+
* fit within `maxCharsPerChunk`, with `CHUNK_OVERLAP` characters of overlap
|
|
45
|
+
* between consecutive chunks for semantic continuity.
|
|
46
|
+
*
|
|
47
|
+
* @returns Array of body strings — one per chunk. Short content returns a single element.
|
|
48
|
+
*/
|
|
49
|
+
export function extractSearchableChunks(contentData, converterContext, maxCharsPerChunk) {
|
|
50
|
+
const converter = new MarkdownConverter();
|
|
51
|
+
const markdown = converter.convert(contentData, converterContext);
|
|
52
|
+
debugLog('extractor', `Markdown length: ${markdown.length} chars`);
|
|
53
|
+
const plainText = stripMarkdown(cleanMarkdownText(markdown));
|
|
54
|
+
debugLog('extractor', `Plain text length: ${plainText.length} chars (chunk limit ${maxCharsPerChunk})`);
|
|
55
|
+
if (plainText.length <= maxCharsPerChunk) {
|
|
56
|
+
return [plainText];
|
|
57
|
+
}
|
|
58
|
+
if (maxCharsPerChunk <= CHUNK_OVERLAP || maxCharsPerChunk <= 0) {
|
|
59
|
+
return [plainText.slice(0, Math.max(maxCharsPerChunk, 1))];
|
|
60
|
+
}
|
|
61
|
+
const chunks = [];
|
|
62
|
+
let offset = 0;
|
|
63
|
+
const step = maxCharsPerChunk - CHUNK_OVERLAP;
|
|
64
|
+
while (offset < plainText.length) {
|
|
65
|
+
const chunk = plainText.slice(offset, offset + maxCharsPerChunk);
|
|
66
|
+
chunks.push(chunk);
|
|
67
|
+
offset += step;
|
|
68
|
+
if (offset + CHUNK_OVERLAP >= plainText.length)
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
debugLog('extractor', `Split into ${chunks.length} chunks (overlap ${CHUNK_OVERLAP})`);
|
|
72
|
+
return chunks;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Extracts a minimal text body from just the title and description
|
|
76
|
+
* (no deep component traversal). Used when `includeComponents` is false.
|
|
77
|
+
*/
|
|
78
|
+
export function extractShallowText(title, description) {
|
|
79
|
+
return cleanMarkdownText(`${title}\n${description}`).trim();
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=content-extractor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content-extractor.js","sourceRoot":"","sources":["../../src/indexing/content-extractor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ContentData, MarkdownConverterContext } from '@se-studio/markdown-renderer';
|
|
2
|
+
import type { SearchableContentType, SearchDocument } from '../types';
|
|
3
|
+
/**
|
|
4
|
+
* Generates the document ID for a given chunk.
|
|
5
|
+
* Chunk 0 uses the bare entry ID for backwards compatibility;
|
|
6
|
+
* subsequent chunks use `{entryId}__chunk_{n}`.
|
|
7
|
+
*/
|
|
8
|
+
export declare function chunkDocumentId(entryId: string, chunkIndex: number): string;
|
|
9
|
+
/**
|
|
10
|
+
* Generates all possible chunk IDs for an entry, used for cleanup on delete/re-publish.
|
|
11
|
+
*/
|
|
12
|
+
export declare function allChunkIds(entryId: string, totalChunks: number): string[];
|
|
13
|
+
/**
|
|
14
|
+
* Extracts the base entry ID from a (possibly chunked) document ID.
|
|
15
|
+
*/
|
|
16
|
+
export declare function baseEntryId(documentId: string): string;
|
|
17
|
+
/**
|
|
18
|
+
* Builds one or more `SearchDocument` chunks from CMS content data.
|
|
19
|
+
* Long content is split into multiple documents with overlapping body text.
|
|
20
|
+
* Short content produces a single document.
|
|
21
|
+
*
|
|
22
|
+
* @returns Array of search documents — one per chunk.
|
|
23
|
+
*/
|
|
24
|
+
export declare function buildSearchDocuments(contentData: ContentData, contentType: SearchableContentType, converterContext: MarkdownConverterContext, includeComponents: boolean): SearchDocument[];
|
|
25
|
+
/**
|
|
26
|
+
* Checks whether a content entry should be included in the search index
|
|
27
|
+
* based on its `indexed` and `hidden` flags.
|
|
28
|
+
*/
|
|
29
|
+
export declare function shouldIndex(data: {
|
|
30
|
+
indexed?: boolean | null;
|
|
31
|
+
hidden?: boolean | null;
|
|
32
|
+
}, respectIndexed: boolean, respectHidden: boolean): boolean;
|
|
33
|
+
//# sourceMappingURL=document-builder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"document-builder.d.ts","sourceRoot":"","sources":["../../src/indexing/document-builder.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,WAAW,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AAE1F,OAAO,KAAK,EAAE,qBAAqB,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAoDtE;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAE3E;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,EAAE,CAE1E;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAGtD;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,WAAW,EAAE,WAAW,EACxB,WAAW,EAAE,qBAAqB,EAClC,gBAAgB,EAAE,wBAAwB,EAC1C,iBAAiB,EAAE,OAAO,GACzB,cAAc,EAAE,CAwClB;AAED;;;GAGG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE;IAAE,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,GAAG,IAAI,CAAA;CAAE,EAC3D,cAAc,EAAE,OAAO,EACvB,aAAa,EAAE,OAAO,GACrB,OAAO,CAIT"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { debugLog } from '../debug';
|
|
2
|
+
import { calculateMaxBodyLength, extractSearchableChunks, extractShallowText, } from './content-extractor';
|
|
3
|
+
function getImageUrl(featuredImage) {
|
|
4
|
+
if (!featuredImage?.image)
|
|
5
|
+
return undefined;
|
|
6
|
+
const img = featuredImage.image;
|
|
7
|
+
if (img.type === 'Picture')
|
|
8
|
+
return img.src;
|
|
9
|
+
if (img.type === 'Svg image')
|
|
10
|
+
return img.svgSrc;
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
13
|
+
function getImageAlt(featuredImage) {
|
|
14
|
+
return featuredImage?.image?.name ?? undefined;
|
|
15
|
+
}
|
|
16
|
+
function getImageDimensions(featuredImage) {
|
|
17
|
+
if (!featuredImage?.image)
|
|
18
|
+
return undefined;
|
|
19
|
+
const img = featuredImage.image;
|
|
20
|
+
if ('width' in img && 'height' in img) {
|
|
21
|
+
return { width: img.width, height: img.height };
|
|
22
|
+
}
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
function extractTags(data) {
|
|
26
|
+
const withTags = data;
|
|
27
|
+
if (!withTags.tags || withTags.tags.length === 0)
|
|
28
|
+
return undefined;
|
|
29
|
+
return withTags.tags.map((t) => t.title || t.name || t.slug).filter(Boolean);
|
|
30
|
+
}
|
|
31
|
+
function extractArticleType(data) {
|
|
32
|
+
const article = data;
|
|
33
|
+
return article.articleType?.name ?? article.articleType?.slug ?? undefined;
|
|
34
|
+
}
|
|
35
|
+
function extractAuthor(data) {
|
|
36
|
+
const article = data;
|
|
37
|
+
return article.author?.name ?? undefined;
|
|
38
|
+
}
|
|
39
|
+
function extractDate(data) {
|
|
40
|
+
const article = data;
|
|
41
|
+
if (article.date)
|
|
42
|
+
return article.date;
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Generates the document ID for a given chunk.
|
|
47
|
+
* Chunk 0 uses the bare entry ID for backwards compatibility;
|
|
48
|
+
* subsequent chunks use `{entryId}__chunk_{n}`.
|
|
49
|
+
*/
|
|
50
|
+
export function chunkDocumentId(entryId, chunkIndex) {
|
|
51
|
+
return chunkIndex === 0 ? entryId : `${entryId}__chunk_${chunkIndex}`;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Generates all possible chunk IDs for an entry, used for cleanup on delete/re-publish.
|
|
55
|
+
*/
|
|
56
|
+
export function allChunkIds(entryId, totalChunks) {
|
|
57
|
+
return Array.from({ length: totalChunks }, (_, i) => chunkDocumentId(entryId, i));
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Extracts the base entry ID from a (possibly chunked) document ID.
|
|
61
|
+
*/
|
|
62
|
+
export function baseEntryId(documentId) {
|
|
63
|
+
const idx = documentId.indexOf('__chunk_');
|
|
64
|
+
return idx === -1 ? documentId : documentId.slice(0, idx);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Builds one or more `SearchDocument` chunks from CMS content data.
|
|
68
|
+
* Long content is split into multiple documents with overlapping body text.
|
|
69
|
+
* Short content produces a single document.
|
|
70
|
+
*
|
|
71
|
+
* @returns Array of search documents — one per chunk.
|
|
72
|
+
*/
|
|
73
|
+
export function buildSearchDocuments(contentData, contentType, converterContext, includeComponents) {
|
|
74
|
+
const data = contentData.data;
|
|
75
|
+
const title = data.title ?? '';
|
|
76
|
+
const description = data.description ?? '';
|
|
77
|
+
const maxBody = calculateMaxBodyLength(title.length, description.length);
|
|
78
|
+
const bodyChunks = includeComponents
|
|
79
|
+
? extractSearchableChunks(contentData, converterContext, maxBody)
|
|
80
|
+
: [extractShallowText(title, description)];
|
|
81
|
+
const totalChunks = bodyChunks.length;
|
|
82
|
+
const imageDimensions = getImageDimensions(data.featuredImage);
|
|
83
|
+
debugLog('builder', `${data.slug}: ${totalChunks} chunk(s) for entry ${data.id}`);
|
|
84
|
+
return bodyChunks.map((body, i) => ({
|
|
85
|
+
id: chunkDocumentId(data.id, i),
|
|
86
|
+
content: { title, description, body },
|
|
87
|
+
metadata: {
|
|
88
|
+
type: contentType,
|
|
89
|
+
slug: data.slug,
|
|
90
|
+
href: data.href,
|
|
91
|
+
tags: extractTags(data),
|
|
92
|
+
articleType: extractArticleType(data),
|
|
93
|
+
date: extractDate(data),
|
|
94
|
+
lastModified: data.lastUpdated?.toString(),
|
|
95
|
+
author: extractAuthor(data),
|
|
96
|
+
imageUrl: getImageUrl(data.featuredImage),
|
|
97
|
+
imageAlt: getImageAlt(data.featuredImage),
|
|
98
|
+
imageWidth: imageDimensions?.width,
|
|
99
|
+
imageHeight: imageDimensions?.height,
|
|
100
|
+
entryId: data.id,
|
|
101
|
+
chunkIndex: i,
|
|
102
|
+
totalChunks,
|
|
103
|
+
},
|
|
104
|
+
}));
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Checks whether a content entry should be included in the search index
|
|
108
|
+
* based on its `indexed` and `hidden` flags.
|
|
109
|
+
*/
|
|
110
|
+
export function shouldIndex(data, respectIndexed, respectHidden) {
|
|
111
|
+
if (respectIndexed && data.indexed === false)
|
|
112
|
+
return false;
|
|
113
|
+
if (respectHidden && data.hidden === true)
|
|
114
|
+
return false;
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=document-builder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"document-builder.js","sourceRoot":"","sources":["../../src/indexing/document-builder.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAEpC,OAAO,EACL,sBAAsB,EACtB,uBAAuB,EACvB,kBAAkB,GACnB,MAAM,qBAAqB,CAAC;AAE7B,SAAS,WAAW,CAAC,aAAkC;IACrD,IAAI,CAAC,aAAa,EAAE,KAAK;QAAE,OAAO,SAAS,CAAC;IAC5C,MAAM,GAAG,GAAG,aAAa,CAAC,KAAK,CAAC;IAChC,IAAI,GAAG,CAAC,IAAI,KAAK,SAAS;QAAE,OAAQ,GAAgB,CAAC,GAAG,CAAC;IACzD,IAAI,GAAG,CAAC,IAAI,KAAK,WAAW;QAAE,OAAQ,GAAiB,CAAC,MAAM,CAAC;IAC/D,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,SAAS,WAAW,CAAC,aAAkC;IACrD,OAAO,aAAa,EAAE,KAAK,EAAE,IAAI,IAAI,SAAS,CAAC;AACjD,CAAC;AAED,SAAS,kBAAkB,CACzB,aAAkC;IAElC,IAAI,CAAC,aAAa,EAAE,KAAK;QAAE,OAAO,SAAS,CAAC;IAC5C,MAAM,GAAG,GAAG,aAAa,CAAC,KAAK,CAAC;IAChC,IAAI,OAAO,IAAI,GAAG,IAAI,QAAQ,IAAI,GAAG,EAAE,CAAC;QACtC,OAAO,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC;IAClD,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,SAAS,WAAW,CAAC,IAAgB;IACnC,MAAM,QAAQ,GAAG,IAAgC,CAAC;IAClD,IAAI,CAAC,QAAQ,CAAC,IAAI,IAAI,QAAQ,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IACnE,OAAO,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;AAC/E,CAAC;AAED,SAAS,kBAAkB,CAAC,IAAgB;IAC1C,MAAM,OAAO,GAAG,IAAoB,CAAC;IACrC,OAAO,OAAO,CAAC,WAAW,EAAE,IAAI,IAAI,OAAO,CAAC,WAAW,EAAE,IAAI,IAAI,SAAS,CAAC;AAC7E,CAAC;AAED,SAAS,aAAa,CAAC,IAAgB;IACrC,MAAM,OAAO,GAAG,IAAoB,CAAC;IACrC,OAAO,OAAO,CAAC,MAAM,EAAE,IAAI,IAAI,SAAS,CAAC;AAC3C,CAAC;AAED,SAAS,WAAW,CAAC,IAAgB;IACnC,MAAM,OAAO,GAAG,IAAoB,CAAC;IACrC,IAAI,OAAO,CAAC,IAAI;QAAE,OAAO,OAAO,CAAC,IAAI,CAAC;IACtC,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,OAAe,EAAE,UAAkB;IACjE,OAAO,UAAU,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,WAAW,UAAU,EAAE,CAAC;AACxE,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,WAAW,CAAC,OAAe,EAAE,WAAmB;IAC9D,OAAO,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC;AACpF,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,WAAW,CAAC,UAAkB;IAC5C,MAAM,GAAG,GAAG,UAAU,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IAC3C,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;AAC5D,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAClC,WAAwB,EACxB,WAAkC,EAClC,gBAA0C,EAC1C,iBAA0B;IAE1B,MAAM,IAAI,GAAG,WAAW,CAAC,IAAkB,CAAC;IAE5C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;IAC/B,MAAM,WAAW,GAAG,IAAI,CAAC,WAAW,IAAI,EAAE,CAAC;IAC3C,MAAM,OAAO,GAAG,sBAAsB,CAAC,KAAK,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAEzE,MAAM,UAAU,GAAG,iBAAiB;QAClC,CAAC,CAAC,uBAAuB,CAAC,WAAW,EAAE,gBAAgB,EAAE,OAAO,CAAC;QACjE,CAAC,CAAC,CAAC,kBAAkB,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC,CAAC;IAE7C,MAAM,WAAW,GAAG,UAAU,CAAC,MAAM,CAAC;IAEtC,MAAM,eAAe,GAAG,kBAAkB,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;IAE/D,QAAQ,CAAC,SAAS,EAAE,GAAG,IAAI,CAAC,IAAI,KAAK,WAAW,uBAAuB,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;IAElF,OAAO,UAAU,CAAC,GAAG,CACnB,CAAC,IAAI,EAAE,CAAC,EAAkB,EAAE,CAAC,CAAC;QAC5B,EAAE,EAAE,eAAe,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;QAC/B,OAAO,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE;QACrC,QAAQ,EAAE;YACR,IAAI,EAAE,WAAW;YACjB,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,IAAI,EAAE,WAAW,CAAC,IAAI,CAAC;YACvB,WAAW,EAAE,kBAAkB,CAAC,IAAI,CAAC;YACrC,IAAI,EAAE,WAAW,CAAC,IAAI,CAAC;YACvB,YAAY,EAAE,IAAI,CAAC,WAAW,EAAE,QAAQ,EAAE;YAC1C,MAAM,EAAE,aAAa,CAAC,IAAI,CAAC;YAC3B,QAAQ,EAAE,WAAW,CAAC,IAAI,CAAC,aAAa,CAAC;YACzC,QAAQ,EAAE,WAAW,CAAC,IAAI,CAAC,aAAa,CAAC;YACzC,UAAU,EAAE,eAAe,EAAE,KAAK;YAClC,WAAW,EAAE,eAAe,EAAE,MAAM;YACpC,OAAO,EAAE,IAAI,CAAC,EAAE;YAChB,UAAU,EAAE,CAAC;YACb,WAAW;SACZ;KACF,CAAC,CACH,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW,CACzB,IAA2D,EAC3D,cAAuB,EACvB,aAAsB;IAEtB,IAAI,cAAc,IAAI,IAAI,CAAC,OAAO,KAAK,KAAK;QAAE,OAAO,KAAK,CAAC;IAC3D,IAAI,aAAa,IAAI,IAAI,CAAC,MAAM,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IACxD,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { calculateMaxBodyLength, extractSearchableChunks, extractShallowText, } from './content-extractor';
|
|
2
|
+
export { allChunkIds, baseEntryId, buildSearchDocuments, chunkDocumentId, shouldIndex, } from './document-builder';
|
|
3
|
+
export { rebuildSearchIndex } from './rebuild';
|
|
4
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/indexing/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,EACtB,uBAAuB,EACvB,kBAAkB,GACnB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,WAAW,EACX,WAAW,EACX,oBAAoB,EACpB,eAAe,EACf,WAAW,GACZ,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { calculateMaxBodyLength, extractSearchableChunks, extractShallowText, } from './content-extractor';
|
|
2
|
+
export { allChunkIds, baseEntryId, buildSearchDocuments, chunkDocumentId, shouldIndex, } from './document-builder';
|
|
3
|
+
export { rebuildSearchIndex } from './rebuild';
|
|
4
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/indexing/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,EACtB,uBAAuB,EACvB,kBAAkB,GACnB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,WAAW,EACX,WAAW,EACX,oBAAoB,EACpB,eAAe,EACf,WAAW,GACZ,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { BaseConverterContext, ContentfulConfig, FetchOptions, UrlCalculators } from '@se-studio/contentful-rest-api';
|
|
2
|
+
import type { SiteConfig } from '@se-studio/markdown-renderer';
|
|
3
|
+
import type { SearchClient } from '../client/search-client';
|
|
4
|
+
import type { RebuildResult, SearchIndexingConfig } from '../types';
|
|
5
|
+
/**
|
|
6
|
+
* Performs a full rebuild of the search index for all configured content types.
|
|
7
|
+
* Enumerates content from Contentful, extracts text, and upserts to Upstash Search.
|
|
8
|
+
*/
|
|
9
|
+
export declare function rebuildSearchIndex(config: {
|
|
10
|
+
client: SearchClient;
|
|
11
|
+
indexName: string;
|
|
12
|
+
indexingConfig: SearchIndexingConfig;
|
|
13
|
+
converterContext: BaseConverterContext;
|
|
14
|
+
contentfulConfig: ContentfulConfig;
|
|
15
|
+
fetchOptions: FetchOptions;
|
|
16
|
+
urlCalculators: UrlCalculators;
|
|
17
|
+
siteConfig: SiteConfig;
|
|
18
|
+
}): Promise<RebuildResult>;
|
|
19
|
+
//# sourceMappingURL=rebuild.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rebuild.d.ts","sourceRoot":"","sources":["../../src/indexing/rebuild.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,oBAAoB,EACpB,gBAAgB,EAChB,YAAY,EACZ,cAAc,EACf,MAAM,gCAAgC,CAAC;AAQxC,OAAO,KAAK,EAA4B,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAEzF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAE5D,OAAO,KAAK,EAEV,aAAa,EAEb,oBAAoB,EACrB,MAAM,UAAU,CAAC;AA4HlB;;;GAGG;AACH,wBAAsB,kBAAkB,CAAC,MAAM,EAAE;IAC/C,MAAM,EAAE,YAAY,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,oBAAoB,CAAC;IACrC,gBAAgB,EAAE,oBAAoB,CAAC;IACvC,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,YAAY,EAAE,YAAY,CAAC;IAC3B,cAAc,EAAE,cAAc,CAAC;IAC/B,UAAU,EAAE,UAAU,CAAC;CACxB,GAAG,OAAO,CAAC,aAAa,CAAC,CA0DzB"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { contentfulAllArticleLinks, contentfulAllArticleTypeLinks, contentfulAllPageLinks, contentfulAllPersonLinks, } from '@se-studio/contentful-rest-api';
|
|
2
|
+
import { MarkdownExporter } from '@se-studio/markdown-renderer';
|
|
3
|
+
import { debugLog } from '../debug';
|
|
4
|
+
import { buildSearchDocuments, shouldIndex } from './document-builder';
|
|
5
|
+
function getEnabledConfig(indexingConfig, type) {
|
|
6
|
+
return indexingConfig.contentTypes.find((ct) => ct.type === type && ct.enabled);
|
|
7
|
+
}
|
|
8
|
+
function resolveIncludeComponents(ctConfig, indexingConfig) {
|
|
9
|
+
return ctConfig.includeComponents ?? indexingConfig.indexComponents ?? true;
|
|
10
|
+
}
|
|
11
|
+
async function fetchLinks(type, ctx) {
|
|
12
|
+
const { converterContext, contentfulConfig, fetchOptions } = ctx;
|
|
13
|
+
switch (type) {
|
|
14
|
+
case 'page': {
|
|
15
|
+
const res = await contentfulAllPageLinks(converterContext, contentfulConfig, fetchOptions);
|
|
16
|
+
return res.data;
|
|
17
|
+
}
|
|
18
|
+
case 'article': {
|
|
19
|
+
const res = await contentfulAllArticleLinks(converterContext, contentfulConfig, fetchOptions);
|
|
20
|
+
return res.data;
|
|
21
|
+
}
|
|
22
|
+
case 'articleType': {
|
|
23
|
+
const res = await contentfulAllArticleTypeLinks(converterContext, contentfulConfig, fetchOptions);
|
|
24
|
+
return res.data;
|
|
25
|
+
}
|
|
26
|
+
case 'person': {
|
|
27
|
+
const res = await contentfulAllPersonLinks(converterContext, contentfulConfig, fetchOptions);
|
|
28
|
+
return res.data;
|
|
29
|
+
}
|
|
30
|
+
default:
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
async function indexContentType(type, ctx, result) {
|
|
35
|
+
const ctConfig = getEnabledConfig(ctx.indexingConfig, type);
|
|
36
|
+
if (!ctConfig)
|
|
37
|
+
return [];
|
|
38
|
+
const respectIndexed = ctx.indexingConfig.respectIndexedFlag ?? true;
|
|
39
|
+
const respectHidden = ctx.indexingConfig.respectHiddenFlag ?? true;
|
|
40
|
+
const includeComponents = resolveIncludeComponents(ctConfig, ctx.indexingConfig);
|
|
41
|
+
const links = await fetchLinks(type, ctx);
|
|
42
|
+
debugLog('rebuild', `${type}: fetched ${links.length} links`);
|
|
43
|
+
const exporter = new MarkdownExporter(ctx.contentfulConfig, ctx.converterContext, ctx.fetchOptions);
|
|
44
|
+
const documents = [];
|
|
45
|
+
for (const link of links) {
|
|
46
|
+
if (!shouldIndex(link, respectIndexed, respectHidden)) {
|
|
47
|
+
debugLog('rebuild', `${type}/${link.slug}: skipped (indexed/hidden)`);
|
|
48
|
+
result.skipped++;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
const articleType = type === 'article' && 'articleType' in link
|
|
53
|
+
? (link.articleType?.slug ?? 'blog')
|
|
54
|
+
: undefined;
|
|
55
|
+
debugLog('rebuild', `${type}/${link.slug}: fetching content...`);
|
|
56
|
+
const contentData = await exporter.fetchContent(type, link.slug, {
|
|
57
|
+
articleType,
|
|
58
|
+
});
|
|
59
|
+
if (!contentData) {
|
|
60
|
+
debugLog('rebuild', `${type}/${link.slug}: no content data returned, skipping`);
|
|
61
|
+
result.skipped++;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
const docs = buildSearchDocuments(contentData, ctConfig.type, ctx.converterCtx, includeComponents);
|
|
65
|
+
debugLog('rebuild', `${type}/${link.slug}: built ${docs.length} chunk(s)`);
|
|
66
|
+
documents.push(...docs);
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
70
|
+
debugLog('rebuild', `${type}/${link.slug}: ERROR - ${message}`);
|
|
71
|
+
result.errors.push(`${type}/${link.slug}: ${message}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
debugLog('rebuild', `${type}: ${documents.length} documents built`);
|
|
75
|
+
return documents;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Performs a full rebuild of the search index for all configured content types.
|
|
79
|
+
* Enumerates content from Contentful, extracts text, and upserts to Upstash Search.
|
|
80
|
+
*/
|
|
81
|
+
export async function rebuildSearchIndex(config) {
|
|
82
|
+
debugLog('rebuild', 'Starting full rebuild', {
|
|
83
|
+
indexName: config.indexName,
|
|
84
|
+
enabledTypes: config.indexingConfig.contentTypes
|
|
85
|
+
.filter((ct) => ct.enabled)
|
|
86
|
+
.map((ct) => ct.type),
|
|
87
|
+
hasUrl: !!config.contentfulConfig,
|
|
88
|
+
});
|
|
89
|
+
const result = { indexed: 0, skipped: 0, errors: [] };
|
|
90
|
+
// Minimal content context for indexing — page-specific fields (article, person, etc.)
|
|
91
|
+
// are not needed since the MarkdownExporter fetches each entry independently.
|
|
92
|
+
const indexingContentContext = {
|
|
93
|
+
pageContext: {},
|
|
94
|
+
current: { type: 'Component', id: '' },
|
|
95
|
+
};
|
|
96
|
+
const converterCtx = {
|
|
97
|
+
contentContext: indexingContentContext,
|
|
98
|
+
config: config.contentfulConfig,
|
|
99
|
+
siteConfig: config.siteConfig,
|
|
100
|
+
urlCalculators: config.urlCalculators,
|
|
101
|
+
};
|
|
102
|
+
const ctx = {
|
|
103
|
+
client: config.client,
|
|
104
|
+
indexName: config.indexName,
|
|
105
|
+
converterContext: config.converterContext,
|
|
106
|
+
contentfulConfig: config.contentfulConfig,
|
|
107
|
+
fetchOptions: config.fetchOptions,
|
|
108
|
+
converterCtx,
|
|
109
|
+
indexingConfig: config.indexingConfig,
|
|
110
|
+
};
|
|
111
|
+
const contentTypes = ['page', 'article', 'articleType', 'person'];
|
|
112
|
+
const allDocuments = [];
|
|
113
|
+
for (const type of contentTypes) {
|
|
114
|
+
debugLog('rebuild', `Processing content type: ${type}`);
|
|
115
|
+
const docs = await indexContentType(type, ctx, result);
|
|
116
|
+
allDocuments.push(...docs);
|
|
117
|
+
}
|
|
118
|
+
debugLog('rebuild', `Total documents to upsert: ${allDocuments.length}`);
|
|
119
|
+
try {
|
|
120
|
+
// client.upsert handles batching internally (100 docs per request)
|
|
121
|
+
await config.client.upsert(config.indexName, allDocuments);
|
|
122
|
+
result.indexed = allDocuments.length;
|
|
123
|
+
debugLog('rebuild', `Upserted ${allDocuments.length} docs to "${config.indexName}"`);
|
|
124
|
+
}
|
|
125
|
+
catch (err) {
|
|
126
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
127
|
+
debugLog('rebuild', `Upsert FAILED: ${message}`);
|
|
128
|
+
result.errors.push(`Upsert: ${message}`);
|
|
129
|
+
}
|
|
130
|
+
debugLog('rebuild', 'Rebuild complete', result);
|
|
131
|
+
return result;
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=rebuild.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rebuild.js","sourceRoot":"","sources":["../../src/indexing/rebuild.ts"],"names":[],"mappings":"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"}
|