svedocs 0.1.0-beta.2
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 +5 -0
- package/README.md +38 -0
- package/dist/ai/openai-compatible.d.ts +13 -0
- package/dist/ai/openai-compatible.d.ts.map +1 -0
- package/dist/ai/openai-compatible.js +92 -0
- package/dist/ai/providers.d.ts +19 -0
- package/dist/ai/providers.d.ts.map +1 -0
- package/dist/ai/providers.js +185 -0
- package/dist/ai/rate-limit.d.ts +18 -0
- package/dist/ai/rate-limit.d.ts.map +1 -0
- package/dist/ai/rate-limit.js +52 -0
- package/dist/ai/response.d.ts +4 -0
- package/dist/ai/response.d.ts.map +1 -0
- package/dist/ai/response.js +151 -0
- package/dist/ai/runtime.d.ts +30 -0
- package/dist/ai/runtime.d.ts.map +1 -0
- package/dist/ai/runtime.js +105 -0
- package/dist/ai/types.d.ts +93 -0
- package/dist/ai/types.d.ts.map +1 -0
- package/dist/ai/types.js +1 -0
- package/dist/ai.d.ts +7 -0
- package/dist/ai.d.ts.map +1 -0
- package/dist/ai.js +6 -0
- package/dist/ambient.d.ts +5 -0
- package/dist/cloudflare.d.ts +27 -0
- package/dist/cloudflare.d.ts.map +1 -0
- package/dist/cloudflare.js +146 -0
- package/dist/config.d.ts +311 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +243 -0
- package/dist/core/checks.d.ts +4 -0
- package/dist/core/checks.d.ts.map +1 -0
- package/dist/core/checks.js +252 -0
- package/dist/core/config.d.ts +5 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +182 -0
- package/dist/core/content.d.ts +7 -0
- package/dist/core/content.d.ts.map +1 -0
- package/dist/core/content.js +211 -0
- package/dist/core/links.d.ts +8 -0
- package/dist/core/links.d.ts.map +1 -0
- package/dist/core/links.js +94 -0
- package/dist/core/navigation.d.ts +5 -0
- package/dist/core/navigation.d.ts.map +1 -0
- package/dist/core/navigation.js +144 -0
- package/dist/core/search.d.ts +5 -0
- package/dist/core/search.d.ts.map +1 -0
- package/dist/core/search.js +41 -0
- package/dist/core/types.d.ts +269 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +4 -0
- package/dist/core/utils.d.ts +12 -0
- package/dist/core/utils.d.ts.map +1 -0
- package/dist/core/utils.js +52 -0
- package/dist/core.d.ts +8 -0
- package/dist/core.d.ts.map +1 -0
- package/dist/core.js +7 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/mdx/ast.d.ts +32 -0
- package/dist/mdx/ast.d.ts.map +1 -0
- package/dist/mdx/ast.js +148 -0
- package/dist/mdx/code.d.ts +14 -0
- package/dist/mdx/code.d.ts.map +1 -0
- package/dist/mdx/code.js +315 -0
- package/dist/mdx/compile.d.ts +26 -0
- package/dist/mdx/compile.d.ts.map +1 -0
- package/dist/mdx/compile.js +63 -0
- package/dist/mdx/diff.d.ts +5 -0
- package/dist/mdx/diff.d.ts.map +1 -0
- package/dist/mdx/diff.js +160 -0
- package/dist/mdx/utils.d.ts +10 -0
- package/dist/mdx/utils.d.ts.map +1 -0
- package/dist/mdx/utils.js +64 -0
- package/dist/og/image.d.ts +21 -0
- package/dist/og/image.d.ts.map +1 -0
- package/dist/og/image.js +216 -0
- package/dist/og/metadata.d.ts +7 -0
- package/dist/og/metadata.d.ts.map +1 -0
- package/dist/og/metadata.js +160 -0
- package/dist/og/types.d.ts +59 -0
- package/dist/og/types.d.ts.map +1 -0
- package/dist/og/types.js +1 -0
- package/dist/og.d.ts +4 -0
- package/dist/og.d.ts.map +1 -0
- package/dist/og.js +3 -0
- package/dist/search/algolia.d.ts +10 -0
- package/dist/search/algolia.d.ts.map +1 -0
- package/dist/search/algolia.js +82 -0
- package/dist/search/cloudflare-sync.d.ts +5 -0
- package/dist/search/cloudflare-sync.d.ts.map +1 -0
- package/dist/search/cloudflare-sync.js +169 -0
- package/dist/search/cloudflare.d.ts +9 -0
- package/dist/search/cloudflare.d.ts.map +1 -0
- package/dist/search/cloudflare.js +115 -0
- package/dist/search/local.d.ts +9 -0
- package/dist/search/local.d.ts.map +1 -0
- package/dist/search/local.js +122 -0
- package/dist/search/runtime.d.ts +41 -0
- package/dist/search/runtime.d.ts.map +1 -0
- package/dist/search/runtime.js +151 -0
- package/dist/search/types.d.ts +172 -0
- package/dist/search/types.d.ts.map +1 -0
- package/dist/search/types.js +1 -0
- package/dist/search/typesense.d.ts +11 -0
- package/dist/search/typesense.d.ts.map +1 -0
- package/dist/search/typesense.js +91 -0
- package/dist/search/utils.d.ts +10 -0
- package/dist/search/utils.d.ts.map +1 -0
- package/dist/search/utils.js +55 -0
- package/dist/search.d.ts +8 -0
- package/dist/search.d.ts.map +1 -0
- package/dist/search.js +7 -0
- package/dist/svelte.d.ts +24 -0
- package/dist/svelte.d.ts.map +1 -0
- package/dist/svelte.js +51 -0
- package/dist/testing.d.ts +3 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +23 -0
- package/dist/theme/AskAiPanel.svelte +433 -0
- package/dist/theme/AskAiPanel.svelte.d.ts +27 -0
- package/dist/theme/AskAiPanel.svelte.d.ts.map +1 -0
- package/dist/theme/Button.svelte +34 -0
- package/dist/theme/Button.svelte.d.ts +41 -0
- package/dist/theme/Button.svelte.d.ts.map +1 -0
- package/dist/theme/Checkbox.svelte +63 -0
- package/dist/theme/Checkbox.svelte.d.ts +45 -0
- package/dist/theme/Checkbox.svelte.d.ts.map +1 -0
- package/dist/theme/CodeBlock.svelte +6 -0
- package/dist/theme/CodeBlock.svelte.d.ts +22 -0
- package/dist/theme/CodeBlock.svelte.d.ts.map +1 -0
- package/dist/theme/DocPage.svelte +121 -0
- package/dist/theme/DocPage.svelte.d.ts +33 -0
- package/dist/theme/DocPage.svelte.d.ts.map +1 -0
- package/dist/theme/DocsApp.svelte +78 -0
- package/dist/theme/DocsApp.svelte.d.ts +63 -0
- package/dist/theme/DocsApp.svelte.d.ts.map +1 -0
- package/dist/theme/DocsLayout.svelte +167 -0
- package/dist/theme/DocsLayout.svelte.d.ts +40 -0
- package/dist/theme/DocsLayout.svelte.d.ts.map +1 -0
- package/dist/theme/FloatingToolbar.svelte +73 -0
- package/dist/theme/FloatingToolbar.svelte.d.ts +22 -0
- package/dist/theme/FloatingToolbar.svelte.d.ts.map +1 -0
- package/dist/theme/FormField.svelte +41 -0
- package/dist/theme/FormField.svelte.d.ts +35 -0
- package/dist/theme/FormField.svelte.d.ts.map +1 -0
- package/dist/theme/HomePage.svelte +228 -0
- package/dist/theme/HomePage.svelte.d.ts +58 -0
- package/dist/theme/HomePage.svelte.d.ts.map +1 -0
- package/dist/theme/Input.svelte +36 -0
- package/dist/theme/Input.svelte.d.ts +34 -0
- package/dist/theme/Input.svelte.d.ts.map +1 -0
- package/dist/theme/PageLayout.svelte +36 -0
- package/dist/theme/PageLayout.svelte.d.ts +31 -0
- package/dist/theme/PageLayout.svelte.d.ts.map +1 -0
- package/dist/theme/RootLayout.svelte +330 -0
- package/dist/theme/RootLayout.svelte.d.ts +40 -0
- package/dist/theme/RootLayout.svelte.d.ts.map +1 -0
- package/dist/theme/ScopeSwitcher.svelte +103 -0
- package/dist/theme/ScopeSwitcher.svelte.d.ts +24 -0
- package/dist/theme/ScopeSwitcher.svelte.d.ts.map +1 -0
- package/dist/theme/SearchDialog.svelte +253 -0
- package/dist/theme/SearchDialog.svelte.d.ts +28 -0
- package/dist/theme/SearchDialog.svelte.d.ts.map +1 -0
- package/dist/theme/Select.svelte +37 -0
- package/dist/theme/Select.svelte.d.ts +42 -0
- package/dist/theme/Select.svelte.d.ts.map +1 -0
- package/dist/theme/SidebarTree.svelte +136 -0
- package/dist/theme/SidebarTree.svelte.d.ts +24 -0
- package/dist/theme/SidebarTree.svelte.d.ts.map +1 -0
- package/dist/theme/Textarea.svelte +33 -0
- package/dist/theme/Textarea.svelte.d.ts +33 -0
- package/dist/theme/Textarea.svelte.d.ts.map +1 -0
- package/dist/theme/ThemeToggle.svelte +39 -0
- package/dist/theme/ThemeToggle.svelte.d.ts +21 -0
- package/dist/theme/ThemeToggle.svelte.d.ts.map +1 -0
- package/dist/theme/forms.d.ts +4 -0
- package/dist/theme/forms.d.ts.map +1 -0
- package/dist/theme/forms.js +3 -0
- package/dist/theme/index.d.ts +21 -0
- package/dist/theme/index.d.ts.map +1 -0
- package/dist/theme/index.js +19 -0
- package/dist/theme/portal.d.ts +5 -0
- package/dist/theme/portal.d.ts.map +1 -0
- package/dist/theme/portal.js +19 -0
- package/dist/theme/styles.css +3697 -0
- package/dist/virtual.d.ts +59 -0
- package/dist/vite.d.ts +10 -0
- package/dist/vite.d.ts.map +1 -0
- package/dist/vite.js +304 -0
- package/package.json +118 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export interface OgImageInput {
|
|
2
|
+
title: string;
|
|
3
|
+
description?: string;
|
|
4
|
+
siteName?: string;
|
|
5
|
+
}
|
|
6
|
+
export type OgRenderer = 'svg' | 'satori';
|
|
7
|
+
export interface OgFont {
|
|
8
|
+
name: string;
|
|
9
|
+
data: ArrayBuffer | Uint8Array;
|
|
10
|
+
weight?: 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900;
|
|
11
|
+
style?: 'normal' | 'italic';
|
|
12
|
+
}
|
|
13
|
+
export interface OgTemplateNode {
|
|
14
|
+
type: string;
|
|
15
|
+
props: {
|
|
16
|
+
style?: Record<string, string | number>;
|
|
17
|
+
children?: string | OgTemplateNode | OgTemplateNode[];
|
|
18
|
+
[key: string]: unknown;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export type OgTemplate = (input: OgImageInput) => OgTemplateNode;
|
|
22
|
+
export interface OgRenderOptions {
|
|
23
|
+
renderer?: OgRenderer;
|
|
24
|
+
fonts?: OgFont[];
|
|
25
|
+
template?: OgTemplate;
|
|
26
|
+
}
|
|
27
|
+
export interface OgImageOptions extends OgRenderOptions {
|
|
28
|
+
format?: 'svg' | 'png';
|
|
29
|
+
}
|
|
30
|
+
export interface SvedocsPageMetadata {
|
|
31
|
+
title: string;
|
|
32
|
+
description: string;
|
|
33
|
+
canonical?: string;
|
|
34
|
+
image?: string;
|
|
35
|
+
openGraph: {
|
|
36
|
+
title: string;
|
|
37
|
+
description: string;
|
|
38
|
+
type: string;
|
|
39
|
+
url?: string;
|
|
40
|
+
image?: string;
|
|
41
|
+
siteName: string;
|
|
42
|
+
author?: string;
|
|
43
|
+
publishedTime?: string;
|
|
44
|
+
updatedTime?: string;
|
|
45
|
+
};
|
|
46
|
+
twitter: {
|
|
47
|
+
card: 'summary' | 'summary_large_image';
|
|
48
|
+
title: string;
|
|
49
|
+
description: string;
|
|
50
|
+
image?: string;
|
|
51
|
+
};
|
|
52
|
+
jsonLd: Record<string, unknown>;
|
|
53
|
+
}
|
|
54
|
+
export interface SvedocsPageAlternate {
|
|
55
|
+
lang: string;
|
|
56
|
+
href: string;
|
|
57
|
+
locale?: string;
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/og/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,QAAQ,CAAC;AAE1C,MAAM,WAAW,MAAM;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,WAAW,GAAG,UAAU,CAAC;IAC/B,MAAM,CAAC,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;IAC7D,KAAK,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAC;CAC7B;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE;QACL,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC;QACxC,QAAQ,CAAC,EAAE,MAAM,GAAG,cAAc,GAAG,cAAc,EAAE,CAAC;QACtD,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KACxB,CAAC;CACH;AAED,MAAM,MAAM,UAAU,GAAG,CAAC,KAAK,EAAE,YAAY,KAAK,cAAc,CAAC;AAEjE,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,UAAU,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,CAAC,EAAE,UAAU,CAAC;CACvB;AAED,MAAM,WAAW,cAAe,SAAQ,eAAe;IACrD,MAAM,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC;CACxB;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACb,SAAS,EAAE;QACT,KAAK,EAAE,MAAM,CAAC;QACd,WAAW,EAAE,MAAM,CAAC;QACpB,IAAI,EAAE,MAAM,CAAC;QACb,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,CAAC;IACJ,OAAO,EAAE;QACP,IAAI,EAAE,SAAS,GAAG,qBAAqB,CAAC;QACxC,KAAK,EAAE,MAAM,CAAC;QACd,WAAW,EAAE,MAAM,CAAC;QACpB,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB"}
|
package/dist/og/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/og.d.ts
ADDED
package/dist/og.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"og.d.ts","sourceRoot":"","sources":["../src/og.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC;AACjC,cAAc,eAAe,CAAC"}
|
package/dist/og.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { SearchProvider, SearchQuery } from './types.js';
|
|
2
|
+
export interface AlgoliaSearchProviderOptions {
|
|
3
|
+
appId: string;
|
|
4
|
+
apiKey: string;
|
|
5
|
+
indexName: string;
|
|
6
|
+
filters?: string | ((query: SearchQuery) => string | undefined);
|
|
7
|
+
fetch?: typeof fetch;
|
|
8
|
+
}
|
|
9
|
+
export declare function createAlgoliaSearchProvider(options: AlgoliaSearchProviderOptions): SearchProvider;
|
|
10
|
+
//# sourceMappingURL=algolia.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"algolia.d.ts","sourceRoot":"","sources":["../../src/search/algolia.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAgB,MAAM,YAAY,CAAC;AAG5E,MAAM,WAAW,4BAA4B;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,KAAK,EAAE,WAAW,KAAK,MAAM,GAAG,SAAS,CAAC,CAAC;IAChE,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;CACtB;AAyBD,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,4BAA4B,GAAG,cAAc,CAgCjG"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { matchesSearchScope } from './local.js';
|
|
2
|
+
import { createExcerpt, stringMetadata, stringifyMetadata } from './utils.js';
|
|
3
|
+
export function createAlgoliaSearchProvider(options) {
|
|
4
|
+
return {
|
|
5
|
+
name: 'algolia',
|
|
6
|
+
index() {
|
|
7
|
+
// Algolia indexing is usually handled by DocSearch crawlers or user CI.
|
|
8
|
+
},
|
|
9
|
+
async search(query) {
|
|
10
|
+
const requestFetch = options.fetch ?? fetch;
|
|
11
|
+
const filters = createAlgoliaFilters(options, query);
|
|
12
|
+
const response = await requestFetch(createAlgoliaQueryEndpoint(options), {
|
|
13
|
+
method: 'POST',
|
|
14
|
+
headers: {
|
|
15
|
+
'content-type': 'application/json',
|
|
16
|
+
'x-algolia-application-id': options.appId,
|
|
17
|
+
'x-algolia-api-key': options.apiKey
|
|
18
|
+
},
|
|
19
|
+
body: JSON.stringify({
|
|
20
|
+
query: query.query,
|
|
21
|
+
hitsPerPage: query.limit ?? 10,
|
|
22
|
+
...(filters ? { filters } : {}),
|
|
23
|
+
attributesToSnippet: ['content:30']
|
|
24
|
+
})
|
|
25
|
+
});
|
|
26
|
+
if (!response.ok) {
|
|
27
|
+
throw new Error(`Algolia search returned ${response.status}: ${(await response.text()).slice(0, 500)}`);
|
|
28
|
+
}
|
|
29
|
+
const payload = await response.json();
|
|
30
|
+
return (payload.hits ?? [])
|
|
31
|
+
.map((hit, index) => createAlgoliaResult(hit, index, query.query))
|
|
32
|
+
.filter((result) => matchesSearchScope({ metadata: result.metadata }, query));
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function createAlgoliaQueryEndpoint(options) {
|
|
37
|
+
return `https://${encodeURIComponent(options.appId)}-dsn.algolia.net/1/indexes/${encodeURIComponent(options.indexName)}/query`;
|
|
38
|
+
}
|
|
39
|
+
function createAlgoliaFilters(options, query) {
|
|
40
|
+
return typeof options.filters === 'function' ? options.filters(query) : options.filters;
|
|
41
|
+
}
|
|
42
|
+
function createAlgoliaResult(hit, index, query) {
|
|
43
|
+
const metadata = stringifyMetadata({
|
|
44
|
+
...(hit.metadata ?? {}),
|
|
45
|
+
locale: hit.locale ?? hit.metadata?.locale,
|
|
46
|
+
kind: hit.kind ?? hit.metadata?.kind
|
|
47
|
+
});
|
|
48
|
+
const hierarchyTitle = readHierarchy(hit.hierarchy, ['lvl0', 'lvl1', 'lvl2']);
|
|
49
|
+
const title = hit.title ?? hierarchyTitle ?? 'Result';
|
|
50
|
+
const section = hit.section ?? readHierarchy(hit.hierarchy, ['lvl6', 'lvl5', 'lvl4', 'lvl3', 'lvl2', 'lvl1']);
|
|
51
|
+
const content = stripHtml(readSnippet(hit) ?? hit.content ?? title);
|
|
52
|
+
return {
|
|
53
|
+
id: hit.objectID ?? `algolia:${index}`,
|
|
54
|
+
title,
|
|
55
|
+
url: createAlgoliaUrl(hit),
|
|
56
|
+
...(section && section !== title ? { section } : {}),
|
|
57
|
+
excerpt: createExcerpt(content, query),
|
|
58
|
+
score: 1 / (index + 1),
|
|
59
|
+
metadata
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function createAlgoliaUrl(hit) {
|
|
63
|
+
const url = hit.url ?? '#';
|
|
64
|
+
if (!hit.anchor || url.includes('#'))
|
|
65
|
+
return url;
|
|
66
|
+
return `${url}#${hit.anchor}`;
|
|
67
|
+
}
|
|
68
|
+
function readSnippet(hit) {
|
|
69
|
+
return stringMetadata(hit._snippetResult?.content?.value)
|
|
70
|
+
?? stringMetadata(hit._highlightResult?.content?.value);
|
|
71
|
+
}
|
|
72
|
+
function readHierarchy(hierarchy, keys) {
|
|
73
|
+
for (const key of keys) {
|
|
74
|
+
const value = stringMetadata(hierarchy?.[key]);
|
|
75
|
+
if (value)
|
|
76
|
+
return stripHtml(value);
|
|
77
|
+
}
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
function stripHtml(value) {
|
|
81
|
+
return value.replace(/<[^>]+>/g, '').replace(/ /g, ' ').trim();
|
|
82
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { SvedocsSearchRecord } from '../core.js';
|
|
2
|
+
import type { CloudflareAiSearchSyncDocument, CloudflareAiSearchSyncOptions, CloudflareAiSearchSyncResult } from './types.js';
|
|
3
|
+
export declare function createCloudflareAiSearchDocuments(records: SvedocsSearchRecord[]): CloudflareAiSearchSyncDocument[];
|
|
4
|
+
export declare function syncCloudflareAiSearchIndex(options: CloudflareAiSearchSyncOptions): Promise<CloudflareAiSearchSyncResult>;
|
|
5
|
+
//# sourceMappingURL=cloudflare-sync.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloudflare-sync.d.ts","sourceRoot":"","sources":["../../src/search/cloudflare-sync.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACtD,OAAO,KAAK,EACV,8BAA8B,EAC9B,6BAA6B,EAC7B,4BAA4B,EAC7B,MAAM,YAAY,CAAC;AAGpB,wBAAgB,iCAAiC,CAAC,OAAO,EAAE,mBAAmB,EAAE,GAAG,8BAA8B,EAAE,CAwBlH;AAED,wBAAsB,2BAA2B,CAC/C,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,4BAA4B,CAAC,CAgEvC"}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { chunkArray, stringifyMetadata, uniqueStrings, wait } from './utils.js';
|
|
2
|
+
export function createCloudflareAiSearchDocuments(records) {
|
|
3
|
+
return records.map((record) => ({
|
|
4
|
+
id: record.id,
|
|
5
|
+
title: record.section ? `${record.title}: ${record.section}` : record.title,
|
|
6
|
+
url: record.url,
|
|
7
|
+
content: [
|
|
8
|
+
`# ${record.section ? `${record.title}: ${record.section}` : record.title}`,
|
|
9
|
+
'',
|
|
10
|
+
`URL: ${record.url}`,
|
|
11
|
+
'',
|
|
12
|
+
record.content
|
|
13
|
+
].join('\n'),
|
|
14
|
+
metadata: stringifyMetadata({
|
|
15
|
+
svedocs: JSON.stringify({
|
|
16
|
+
id: record.id,
|
|
17
|
+
pageId: record.pageId,
|
|
18
|
+
title: record.title,
|
|
19
|
+
section: record.section ?? '',
|
|
20
|
+
url: record.url
|
|
21
|
+
}),
|
|
22
|
+
locale: record.metadata.locale,
|
|
23
|
+
kind: record.metadata.kind
|
|
24
|
+
})
|
|
25
|
+
}));
|
|
26
|
+
}
|
|
27
|
+
export async function syncCloudflareAiSearchIndex(options) {
|
|
28
|
+
const endpoint = options.endpoint ?? createCloudflareAiSearchItemsEndpoint(options);
|
|
29
|
+
const dryRun = options.dryRun ?? (!options.accountId || !options.apiToken);
|
|
30
|
+
const documents = createCloudflareAiSearchDocuments(options.records);
|
|
31
|
+
const uploadIds = documents.map((document) => document.id);
|
|
32
|
+
const deleteIds = createCloudflareDeletePlan(options, uploadIds);
|
|
33
|
+
const result = {
|
|
34
|
+
provider: 'cloudflare-ai-search',
|
|
35
|
+
instanceName: options.instanceName,
|
|
36
|
+
endpoint,
|
|
37
|
+
dryRun,
|
|
38
|
+
indexed: dryRun ? documents.length : 0,
|
|
39
|
+
deleted: dryRun ? deleteIds.length : 0,
|
|
40
|
+
failed: 0,
|
|
41
|
+
planned: {
|
|
42
|
+
uploadIds,
|
|
43
|
+
deleteIds
|
|
44
|
+
},
|
|
45
|
+
errors: []
|
|
46
|
+
};
|
|
47
|
+
if (dryRun)
|
|
48
|
+
return result;
|
|
49
|
+
if (!options.apiToken) {
|
|
50
|
+
throw new Error('CLOUDFLARE_API_TOKEN is required to index Cloudflare AI Search.');
|
|
51
|
+
}
|
|
52
|
+
const requestFetch = options.fetch ?? fetch;
|
|
53
|
+
const batchSize = options.batchSize ?? 10;
|
|
54
|
+
const maxRetries = options.maxRetries ?? 2;
|
|
55
|
+
const retryDelayMs = options.retryDelayMs ?? 250;
|
|
56
|
+
await processBatches(deleteIds, batchSize, async (itemId) => {
|
|
57
|
+
try {
|
|
58
|
+
await withRetry(() => deleteCloudflareAiSearchItem(endpoint, itemId, options.apiToken, requestFetch), {
|
|
59
|
+
maxRetries,
|
|
60
|
+
retryDelayMs
|
|
61
|
+
});
|
|
62
|
+
result.deleted += 1;
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
result.failed += 1;
|
|
66
|
+
result.errors.push({
|
|
67
|
+
id: itemId,
|
|
68
|
+
message: error instanceof Error ? error.message : 'Cloudflare AI Search delete failed.'
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
await processBatches(documents, batchSize, async (document) => {
|
|
73
|
+
try {
|
|
74
|
+
await withRetry(() => uploadCloudflareAiSearchDocument(endpoint, document, options, requestFetch), {
|
|
75
|
+
maxRetries,
|
|
76
|
+
retryDelayMs
|
|
77
|
+
});
|
|
78
|
+
result.indexed += 1;
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
result.failed += 1;
|
|
82
|
+
result.errors.push({
|
|
83
|
+
id: document.id,
|
|
84
|
+
message: error instanceof Error ? error.message : 'Cloudflare AI Search upload failed.'
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
90
|
+
function createCloudflareAiSearchItemsEndpoint(options) {
|
|
91
|
+
if (!options.accountId)
|
|
92
|
+
return 'https://api.cloudflare.com/client/v4/accounts/<account_id>/ai-search/instances/<instance_name>/items';
|
|
93
|
+
const account = encodeURIComponent(options.accountId);
|
|
94
|
+
const instance = encodeURIComponent(options.instanceName);
|
|
95
|
+
if (options.namespace) {
|
|
96
|
+
return `https://api.cloudflare.com/client/v4/accounts/${account}/ai-search/namespaces/${encodeURIComponent(options.namespace)}/instances/${instance}/items`;
|
|
97
|
+
}
|
|
98
|
+
return `https://api.cloudflare.com/client/v4/accounts/${account}/ai-search/instances/${instance}/items`;
|
|
99
|
+
}
|
|
100
|
+
function createCloudflareAiSearchItemEndpoint(itemsEndpoint, itemId) {
|
|
101
|
+
return `${itemsEndpoint.replace(/\/$/, '')}/${encodeURIComponent(itemId)}`;
|
|
102
|
+
}
|
|
103
|
+
function createCloudflareDeletePlan(options, uploadIds) {
|
|
104
|
+
const explicitDeletes = options.deleteIds ?? [];
|
|
105
|
+
if (options.strategy !== 'replace')
|
|
106
|
+
return uniqueStrings(explicitDeletes);
|
|
107
|
+
const uploadSet = new Set(uploadIds);
|
|
108
|
+
return uniqueStrings([
|
|
109
|
+
...explicitDeletes,
|
|
110
|
+
...(options.existingIds ?? []).filter((itemId) => !uploadSet.has(itemId))
|
|
111
|
+
]);
|
|
112
|
+
}
|
|
113
|
+
function createCloudflareItemFilename(document) {
|
|
114
|
+
const safe = document.id.replace(/[^a-zA-Z0-9_-]+/g, '-').replace(/^-|-$/g, '') || 'svedocs-page';
|
|
115
|
+
return `${safe.slice(0, 124)}.md`;
|
|
116
|
+
}
|
|
117
|
+
async function deleteCloudflareAiSearchItem(endpoint, itemId, apiToken, requestFetch) {
|
|
118
|
+
const response = await requestFetch(createCloudflareAiSearchItemEndpoint(endpoint, itemId), {
|
|
119
|
+
method: 'DELETE',
|
|
120
|
+
headers: {
|
|
121
|
+
authorization: `Bearer ${apiToken}`
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
if (!response.ok) {
|
|
125
|
+
throw new Error(await createCloudflareApiErrorMessage(response, 'delete'));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
async function uploadCloudflareAiSearchDocument(endpoint, document, options, requestFetch) {
|
|
129
|
+
const form = new FormData();
|
|
130
|
+
form.append('file', new Blob([document.content], { type: 'text/markdown' }), createCloudflareItemFilename(document));
|
|
131
|
+
form.append('metadata', JSON.stringify(document.metadata));
|
|
132
|
+
if (typeof options.waitForCompletion === 'boolean') {
|
|
133
|
+
form.append('wait_for_completion', String(options.waitForCompletion));
|
|
134
|
+
}
|
|
135
|
+
const response = await requestFetch(endpoint, {
|
|
136
|
+
method: 'POST',
|
|
137
|
+
headers: {
|
|
138
|
+
authorization: `Bearer ${options.apiToken}`
|
|
139
|
+
},
|
|
140
|
+
body: form
|
|
141
|
+
});
|
|
142
|
+
if (!response.ok) {
|
|
143
|
+
throw new Error(await createCloudflareApiErrorMessage(response, 'upload'));
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
async function createCloudflareApiErrorMessage(response, action) {
|
|
147
|
+
const body = await response.text().catch(() => '');
|
|
148
|
+
return `Cloudflare AI Search ${action} returned ${response.status}${body ? `: ${body.slice(0, 500)}` : ''}`;
|
|
149
|
+
}
|
|
150
|
+
async function processBatches(items, batchSize, handler) {
|
|
151
|
+
for (const batch of chunkArray(items, batchSize)) {
|
|
152
|
+
await Promise.all(batch.map((item) => handler(item)));
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
async function withRetry(action, options) {
|
|
156
|
+
let attempt = 0;
|
|
157
|
+
while (true) {
|
|
158
|
+
try {
|
|
159
|
+
await action();
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
if (attempt >= options.maxRetries)
|
|
164
|
+
throw error;
|
|
165
|
+
attempt += 1;
|
|
166
|
+
await wait(options.retryDelayMs * attempt);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { CloudflareAiSearchInput, CloudflareAiSearchInstance, CloudflareAiSearchOutput, CloudflareAiSearchNamespace, SearchResult, SearchProvider } from './types.js';
|
|
2
|
+
export declare function createCloudflareAiSearchProvider(input: {
|
|
3
|
+
binding: CloudflareAiSearchInstance | CloudflareAiSearchNamespace;
|
|
4
|
+
instanceName?: string;
|
|
5
|
+
}): SearchProvider;
|
|
6
|
+
export declare function runCloudflareAiSearch(binding: CloudflareAiSearchInstance | CloudflareAiSearchNamespace, instanceName: string | undefined, input: CloudflareAiSearchInput): Promise<CloudflareAiSearchOutput>;
|
|
7
|
+
export declare function normalizeCloudflareAiSearchResults(result: CloudflareAiSearchOutput, query: string): SearchResult[];
|
|
8
|
+
export declare function resolveAiSearchInstance(binding: CloudflareAiSearchInstance | CloudflareAiSearchNamespace, instanceName?: string): CloudflareAiSearchInstance;
|
|
9
|
+
//# sourceMappingURL=cloudflare.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloudflare.d.ts","sourceRoot":"","sources":["../../src/search/cloudflare.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAEV,uBAAuB,EACvB,0BAA0B,EAE1B,wBAAwB,EACxB,2BAA2B,EAC3B,YAAY,EACZ,cAAc,EACf,MAAM,YAAY,CAAC;AAIpB,wBAAgB,gCAAgC,CAAC,KAAK,EAAE;IACtD,OAAO,EAAE,0BAA0B,GAAG,2BAA2B,CAAC;IAClE,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,GAAG,cAAc,CA0BjB;AAED,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,0BAA0B,GAAG,2BAA2B,EACjE,YAAY,oBAAY,EACxB,KAAK,EAAE,uBAAuB,GAC7B,OAAO,CAAC,wBAAwB,CAAC,CASnC;AAED,wBAAgB,kCAAkC,CAAC,MAAM,EAAE,wBAAwB,EAAE,KAAK,EAAE,MAAM,GAAG,YAAY,EAAE,CAKlH;AAED,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,0BAA0B,GAAG,2BAA2B,EACjE,YAAY,SAAY,GACvB,0BAA0B,CAE5B"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { matchesSearchScope } from './local.js';
|
|
2
|
+
import { createExcerpt, stringMetadata, stringifyMetadata } from './utils.js';
|
|
3
|
+
export function createCloudflareAiSearchProvider(input) {
|
|
4
|
+
return {
|
|
5
|
+
name: 'cloudflare-ai-search',
|
|
6
|
+
index() {
|
|
7
|
+
// Cloudflare AI Search indexing is intentionally handled by CLI/provider sync.
|
|
8
|
+
},
|
|
9
|
+
async search(query) {
|
|
10
|
+
const maxResults = query.limit ?? 10;
|
|
11
|
+
const result = await runCloudflareAiSearch(input.binding, input.instanceName, {
|
|
12
|
+
messages: [
|
|
13
|
+
{
|
|
14
|
+
role: 'user',
|
|
15
|
+
content: query.query
|
|
16
|
+
}
|
|
17
|
+
],
|
|
18
|
+
ai_search_options: {
|
|
19
|
+
retrieval: {
|
|
20
|
+
retrieval_type: 'hybrid',
|
|
21
|
+
max_num_results: maxResults
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
return normalizeCloudflareAiSearchResults(result, query.query)
|
|
26
|
+
.filter((record) => matchesSearchScope({ metadata: record.metadata }, query));
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export async function runCloudflareAiSearch(binding, instanceName = 'svedocs', input) {
|
|
31
|
+
const instance = resolveAiSearchInstance(binding, instanceName);
|
|
32
|
+
const aiSearchOptions = createAiSearchOptions(input);
|
|
33
|
+
const messages = input.messages;
|
|
34
|
+
return instance.search({
|
|
35
|
+
...(messages ? { messages } : {}),
|
|
36
|
+
...(!messages && input.query ? { query: input.query } : {}),
|
|
37
|
+
...(aiSearchOptions ? { ai_search_options: aiSearchOptions } : {})
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
export function normalizeCloudflareAiSearchResults(result, query) {
|
|
41
|
+
if (result.data?.length) {
|
|
42
|
+
return result.data.map((item, index) => createResultFromItem(item, index, query, result.response ?? result.answer));
|
|
43
|
+
}
|
|
44
|
+
return (result.chunks ?? []).map((chunk, index) => createResultFromChunk(chunk, index, query, result.response ?? result.answer));
|
|
45
|
+
}
|
|
46
|
+
export function resolveAiSearchInstance(binding, instanceName = 'svedocs') {
|
|
47
|
+
return 'get' in binding ? binding.get(instanceName) : binding;
|
|
48
|
+
}
|
|
49
|
+
function mergeRetrieval(options, retrieval) {
|
|
50
|
+
return {
|
|
51
|
+
...(options ?? {}),
|
|
52
|
+
retrieval: {
|
|
53
|
+
...(options?.retrieval ?? {}),
|
|
54
|
+
...retrieval
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function createAiSearchOptions(input) {
|
|
59
|
+
const retrieval = typeof input.max_num_results === 'number' ? { max_num_results: input.max_num_results } : {};
|
|
60
|
+
return input.ai_search_options || Object.keys(retrieval).length > 0
|
|
61
|
+
? mergeRetrieval(input.ai_search_options, retrieval)
|
|
62
|
+
: undefined;
|
|
63
|
+
}
|
|
64
|
+
function createResultFromItem(item, index, query, fallbackContent = '') {
|
|
65
|
+
const metadata = normalizeCloudflareMetadata(item.metadata ?? {});
|
|
66
|
+
const section = stringMetadata(metadata.section);
|
|
67
|
+
return {
|
|
68
|
+
id: item.id ?? `cloudflare:${index}`,
|
|
69
|
+
title: item.title ?? stringMetadata(metadata.title) ?? 'Result',
|
|
70
|
+
url: item.url ?? stringMetadata(metadata.url) ?? '#',
|
|
71
|
+
...(section ? { section } : {}),
|
|
72
|
+
excerpt: createExcerpt(item.content ?? item.text ?? fallbackContent, query),
|
|
73
|
+
score: typeof item.score === 'number' ? item.score : 1 / (index + 1),
|
|
74
|
+
metadata
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function createResultFromChunk(chunk, index, query, fallbackContent = '') {
|
|
78
|
+
const metadata = normalizeCloudflareMetadata({
|
|
79
|
+
...(chunk.attributes ?? {}),
|
|
80
|
+
...(chunk.metadata ?? {}),
|
|
81
|
+
...(chunk.item?.metadata ?? {})
|
|
82
|
+
});
|
|
83
|
+
const section = stringMetadata(metadata.section);
|
|
84
|
+
return {
|
|
85
|
+
id: chunk.id ?? stringMetadata(chunk.item?.id) ?? stringMetadata(chunk.item?.key) ?? `cloudflare:${index}`,
|
|
86
|
+
title: stringMetadata(metadata.title) ??
|
|
87
|
+
stringMetadata(chunk.item?.title) ??
|
|
88
|
+
stringMetadata(chunk.item?.filename) ??
|
|
89
|
+
stringMetadata(chunk.item?.key) ??
|
|
90
|
+
'Result',
|
|
91
|
+
url: stringMetadata(metadata.url) ?? stringMetadata(metadata.source_url) ?? '#',
|
|
92
|
+
...(section ? { section } : {}),
|
|
93
|
+
excerpt: createExcerpt(chunk.text ?? chunk.content ?? stringMetadata(metadata.content) ?? fallbackContent, query),
|
|
94
|
+
score: typeof chunk.score === 'number' ? chunk.score : 1 / (index + 1),
|
|
95
|
+
metadata
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function normalizeCloudflareMetadata(metadata) {
|
|
99
|
+
const stringified = stringifyMetadata(metadata);
|
|
100
|
+
const embedded = stringMetadata(metadata.svedocs);
|
|
101
|
+
if (!embedded)
|
|
102
|
+
return stringified;
|
|
103
|
+
try {
|
|
104
|
+
const parsed = JSON.parse(embedded);
|
|
105
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
|
|
106
|
+
return stringified;
|
|
107
|
+
return {
|
|
108
|
+
...stringified,
|
|
109
|
+
...stringifyMetadata(parsed)
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
return stringified;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { SvedocsSearchRecord } from '../core.js';
|
|
2
|
+
import type { SearchProvider, SearchQuery, SearchResult, SearchScope } from './types.js';
|
|
3
|
+
export declare function createLocalSearchProvider(records?: SvedocsSearchRecord[]): SearchProvider;
|
|
4
|
+
export declare const localSearchProvider: SearchProvider;
|
|
5
|
+
export declare function searchRecords(records: SvedocsSearchRecord[], input: SearchQuery): SearchResult[];
|
|
6
|
+
export declare function filterSearchRecords(records: SvedocsSearchRecord[], scope?: SearchScope): SvedocsSearchRecord[];
|
|
7
|
+
export declare function matchesSearchScope(record: SvedocsSearchRecord, scope?: SearchScope): boolean;
|
|
8
|
+
export declare function createSearchResponse(records: SvedocsSearchRecord[], request: Request): Promise<Response>;
|
|
9
|
+
//# sourceMappingURL=local.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local.d.ts","sourceRoot":"","sources":["../../src/search/local.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACtD,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAoBzF,wBAAgB,yBAAyB,CAAC,OAAO,GAAE,mBAAmB,EAAO,GAAG,cAAc,CAW7F;AAED,eAAO,MAAM,mBAAmB,gBAA8B,CAAC;AAE/D,wBAAgB,aAAa,CAAC,OAAO,EAAE,mBAAmB,EAAE,EAAE,KAAK,EAAE,WAAW,GAAG,YAAY,EAAE,CAkChG;AAED,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,mBAAmB,EAAE,EAC9B,KAAK,GAAE,WAAgB,GACtB,mBAAmB,EAAE,CAEvB;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,mBAAmB,EAAE,KAAK,GAAE,WAAgB,GAAG,OAAO,CAGhG;AAED,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,mBAAmB,EAAE,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAa9G"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import MiniSearch from 'minisearch';
|
|
2
|
+
import { createExcerpt, jsonResponse, normalizeSearchText } from './utils.js';
|
|
3
|
+
const miniSearchCache = new WeakMap();
|
|
4
|
+
export function createLocalSearchProvider(records = []) {
|
|
5
|
+
let indexed = records;
|
|
6
|
+
return {
|
|
7
|
+
name: 'local-json',
|
|
8
|
+
index(nextRecords) {
|
|
9
|
+
indexed = nextRecords;
|
|
10
|
+
},
|
|
11
|
+
search(input) {
|
|
12
|
+
return searchRecords(indexed, input);
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export const localSearchProvider = createLocalSearchProvider();
|
|
17
|
+
export function searchRecords(records, input) {
|
|
18
|
+
const query = normalizeSearchText(input.query);
|
|
19
|
+
if (!query)
|
|
20
|
+
return [];
|
|
21
|
+
const entry = getMiniSearchIndex(records);
|
|
22
|
+
const scoped = entry.index.search(input.query, {
|
|
23
|
+
prefix: true,
|
|
24
|
+
fuzzy: (term) => term.length > 4 ? 0.18 : false,
|
|
25
|
+
combineWith: 'AND',
|
|
26
|
+
boost: {
|
|
27
|
+
title: 5,
|
|
28
|
+
section: 4,
|
|
29
|
+
url: 1.5,
|
|
30
|
+
metadataText: 0.8,
|
|
31
|
+
content: 1
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
return scoped
|
|
35
|
+
.map((hit) => {
|
|
36
|
+
const record = entry.recordsById.get(hit.id);
|
|
37
|
+
return record ? { record, score: Number(hit.score.toFixed(4)) } : undefined;
|
|
38
|
+
})
|
|
39
|
+
.filter((item) => Boolean(item))
|
|
40
|
+
.filter((item) => matchesSearchScope(item.record, input))
|
|
41
|
+
.slice(0, input.limit ?? 10)
|
|
42
|
+
.map(({ record, score }) => ({
|
|
43
|
+
id: record.id,
|
|
44
|
+
title: record.title,
|
|
45
|
+
url: record.url,
|
|
46
|
+
...(record.section ? { section: record.section } : {}),
|
|
47
|
+
excerpt: createExcerpt(record.content, input.query),
|
|
48
|
+
score,
|
|
49
|
+
metadata: record.metadata
|
|
50
|
+
}));
|
|
51
|
+
}
|
|
52
|
+
export function filterSearchRecords(records, scope = {}) {
|
|
53
|
+
return records.filter((record) => matchesSearchScope(record, scope));
|
|
54
|
+
}
|
|
55
|
+
export function matchesSearchScope(record, scope = {}) {
|
|
56
|
+
return matchesMetadata(record.metadata.locale, scope.locale)
|
|
57
|
+
&& matchesMetadata(record.metadata.kind, scope.kind);
|
|
58
|
+
}
|
|
59
|
+
export async function createSearchResponse(records, request) {
|
|
60
|
+
const url = new URL(request.url);
|
|
61
|
+
const query = url.searchParams.get('q') ?? url.searchParams.get('query') ?? '';
|
|
62
|
+
const limit = clampLimit(Number(url.searchParams.get('limit') ?? 10));
|
|
63
|
+
const locale = readScopeParam(url, 'locale');
|
|
64
|
+
const kind = readScopeParam(url, 'kind');
|
|
65
|
+
const results = searchRecords(records, {
|
|
66
|
+
query,
|
|
67
|
+
limit,
|
|
68
|
+
...(locale ? { locale } : {}),
|
|
69
|
+
...(kind ? { kind } : {})
|
|
70
|
+
});
|
|
71
|
+
return jsonResponse({ query, results });
|
|
72
|
+
}
|
|
73
|
+
function getMiniSearchIndex(records) {
|
|
74
|
+
const cached = miniSearchCache.get(records);
|
|
75
|
+
if (cached)
|
|
76
|
+
return cached;
|
|
77
|
+
const documents = records.map(toMiniSearchDocument);
|
|
78
|
+
const index = new MiniSearch({
|
|
79
|
+
fields: ['title', 'section', 'url', 'content', 'metadataText'],
|
|
80
|
+
storeFields: ['id', 'title', 'section', 'url', 'content', 'metadata'],
|
|
81
|
+
idField: 'id',
|
|
82
|
+
searchOptions: {
|
|
83
|
+
prefix: true,
|
|
84
|
+
fuzzy: 0.18
|
|
85
|
+
},
|
|
86
|
+
processTerm: (term) => normalizeSearchText(term)
|
|
87
|
+
});
|
|
88
|
+
index.addAll(documents);
|
|
89
|
+
const entry = {
|
|
90
|
+
index,
|
|
91
|
+
recordsById: new Map(records.map((record) => [record.id, record]))
|
|
92
|
+
};
|
|
93
|
+
miniSearchCache.set(records, entry);
|
|
94
|
+
return entry;
|
|
95
|
+
}
|
|
96
|
+
function toMiniSearchDocument(record) {
|
|
97
|
+
return {
|
|
98
|
+
id: record.id,
|
|
99
|
+
title: record.title,
|
|
100
|
+
section: record.section ?? '',
|
|
101
|
+
url: record.url,
|
|
102
|
+
content: record.content,
|
|
103
|
+
metadataText: Object.values(record.metadata).flat().join(' '),
|
|
104
|
+
metadata: record.metadata
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
function matchesMetadata(value, expected) {
|
|
108
|
+
if (!expected)
|
|
109
|
+
return true;
|
|
110
|
+
if (Array.isArray(value))
|
|
111
|
+
return value.some((item) => String(item) === expected);
|
|
112
|
+
return value === expected;
|
|
113
|
+
}
|
|
114
|
+
function readScopeParam(url, key) {
|
|
115
|
+
const value = url.searchParams.get(key);
|
|
116
|
+
return value?.trim() || undefined;
|
|
117
|
+
}
|
|
118
|
+
function clampLimit(value) {
|
|
119
|
+
if (!Number.isFinite(value))
|
|
120
|
+
return 10;
|
|
121
|
+
return Math.min(50, Math.max(1, Math.floor(value)));
|
|
122
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { SvedocsResolvedConfig, SvedocsSearchRecord } from '../core.js';
|
|
2
|
+
import type { CloudflareAiSearchInstance, CloudflareAiSearchNamespace, SearchProvider, SearchQuery } from './types.js';
|
|
3
|
+
export interface SvedocsSearchRuntimeEnv {
|
|
4
|
+
SVEDOCS_AI_SEARCH?: CloudflareAiSearchInstance | CloudflareAiSearchNamespace | undefined;
|
|
5
|
+
ALGOLIA_APP_ID?: string | undefined;
|
|
6
|
+
ALGOLIA_SEARCH_KEY?: string | undefined;
|
|
7
|
+
ALGOLIA_API_KEY?: string | undefined;
|
|
8
|
+
ALGOLIA_INDEX_NAME?: string | undefined;
|
|
9
|
+
SVEDOCS_ALGOLIA_APP_ID?: string | undefined;
|
|
10
|
+
SVEDOCS_ALGOLIA_SEARCH_KEY?: string | undefined;
|
|
11
|
+
SVEDOCS_ALGOLIA_API_KEY?: string | undefined;
|
|
12
|
+
SVEDOCS_ALGOLIA_INDEX_NAME?: string | undefined;
|
|
13
|
+
TYPESENSE_HOST?: string | undefined;
|
|
14
|
+
TYPESENSE_URL?: string | undefined;
|
|
15
|
+
TYPESENSE_SEARCH_KEY?: string | undefined;
|
|
16
|
+
TYPESENSE_API_KEY?: string | undefined;
|
|
17
|
+
TYPESENSE_COLLECTION?: string | undefined;
|
|
18
|
+
TYPESENSE_COLLECTION_NAME?: string | undefined;
|
|
19
|
+
SVEDOCS_TYPESENSE_HOST?: string | undefined;
|
|
20
|
+
SVEDOCS_TYPESENSE_URL?: string | undefined;
|
|
21
|
+
SVEDOCS_TYPESENSE_SEARCH_KEY?: string | undefined;
|
|
22
|
+
SVEDOCS_TYPESENSE_API_KEY?: string | undefined;
|
|
23
|
+
SVEDOCS_TYPESENSE_COLLECTION?: string | undefined;
|
|
24
|
+
SVEDOCS_TYPESENSE_COLLECTION_NAME?: string | undefined;
|
|
25
|
+
[key: string]: unknown;
|
|
26
|
+
}
|
|
27
|
+
export interface CreateConfiguredSearchProviderOptions {
|
|
28
|
+
config: Pick<SvedocsResolvedConfig, 'search' | 'cloudflare'>;
|
|
29
|
+
records?: SvedocsSearchRecord[];
|
|
30
|
+
env?: SvedocsSearchRuntimeEnv;
|
|
31
|
+
provider?: string;
|
|
32
|
+
fetch?: typeof fetch;
|
|
33
|
+
}
|
|
34
|
+
export interface CreateConfiguredSearchResponseOptions extends Omit<CreateConfiguredSearchProviderOptions, 'config' | 'records' | 'provider'> {
|
|
35
|
+
provider?: string;
|
|
36
|
+
}
|
|
37
|
+
export declare function createConfiguredSearchProvider(options: CreateConfiguredSearchProviderOptions): SearchProvider;
|
|
38
|
+
export declare function createConfiguredSearchResponse(config: Pick<SvedocsResolvedConfig, 'search' | 'cloudflare'>, records: SvedocsSearchRecord[], request: Request, options?: CreateConfiguredSearchResponseOptions): Promise<Response>;
|
|
39
|
+
export declare function readSearchQuery(request: Request): SearchQuery;
|
|
40
|
+
export declare function readProviderParam(request: Request): string | undefined;
|
|
41
|
+
//# sourceMappingURL=runtime.d.ts.map
|