@stainless-api/docs-search 0.1.0-beta.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.
@@ -0,0 +1,83 @@
1
+ import { n as generateIndex, t as generateChatIndex } from "./indexer-DBU0POrK.js";
2
+ import { i as SearchableAttributesProse, n as SearchableAttributes, r as SearchableAttributesChat } from "./types-BhJLoaNF.js";
3
+ import { searchClient } from "@algolia/client-search";
4
+
5
+ //#region src/providers/algolia.ts
6
+ async function buildIndex(appId, indexName, writeKey, spec, renderMarkdown) {
7
+ if (!appId || !indexName || !writeKey) return;
8
+ const objects = Array.from(generateIndex(spec, renderMarkdown));
9
+ const client = searchClient(appId, writeKey);
10
+ await client.setSettings({
11
+ indexName,
12
+ indexSettings: {
13
+ highlightPreTag: "<mark>",
14
+ highlightPostTag: "</mark>",
15
+ customRanking: ["asc(priority)"],
16
+ attributesForFaceting: ["language", "kind"],
17
+ searchableAttributes: [...SearchableAttributes]
18
+ }
19
+ });
20
+ await client.replaceAllObjects({
21
+ indexName,
22
+ objects
23
+ });
24
+ }
25
+ async function buildChatIndex(appId, indexName, writeKey, spec) {
26
+ if (!appId || !indexName || !writeKey) return;
27
+ const objects = Array.from(generateChatIndex(spec));
28
+ const client = searchClient(appId, writeKey);
29
+ await client.setSettings({
30
+ indexName,
31
+ indexSettings: {
32
+ attributesForFaceting: ["language"],
33
+ attributeForDistinct: "stainlessPath",
34
+ searchableAttributes: SearchableAttributesChat
35
+ }
36
+ });
37
+ await client.replaceAllObjects({
38
+ indexName,
39
+ objects
40
+ });
41
+ }
42
+ async function buildProseIndex(appId, indexName, writeKey, objects) {
43
+ if (!appId || !indexName || !writeKey) return;
44
+ const client = searchClient(appId, writeKey);
45
+ await client.setSettings({
46
+ indexName,
47
+ indexSettings: { searchableAttributes: SearchableAttributesProse }
48
+ });
49
+ await client.replaceAllObjects({
50
+ indexName,
51
+ objects
52
+ });
53
+ }
54
+ async function search({ settings: { appId, indexName, searchKey }, params: { query, language, kind } }) {
55
+ const client = searchClient(appId, searchKey);
56
+ const filters = language ? `language:${language}` : void 0;
57
+ const facetFilters = kind ? [`kind:${kind}`] : void 0;
58
+ const { results } = await client.search({ requests: [{
59
+ query,
60
+ indexName,
61
+ filters,
62
+ hitsPerPage: 5,
63
+ facets: ["kind"]
64
+ }, {
65
+ query,
66
+ indexName,
67
+ filters,
68
+ facetFilters,
69
+ facets: ["kind"],
70
+ hitsPerPage: 50
71
+ }] });
72
+ if ("hits" in results[0] && "hits" in results[1]) {
73
+ const [{ nbHits, facets }, { hits }] = results;
74
+ return {
75
+ hits,
76
+ nbHits: nbHits ?? 0,
77
+ facets
78
+ };
79
+ }
80
+ }
81
+
82
+ //#endregion
83
+ export { search as i, buildIndex as n, buildProseIndex as r, buildChatIndex as t };
@@ -0,0 +1,32 @@
1
+ import { i as search } from "./algolia-BOY-OcxU.js";
2
+ import * as React from "react";
3
+ import { jsx } from "react/jsx-runtime";
4
+
5
+ //#region src/context.tsx
6
+ function createStrictContext(displayName) {
7
+ const Context = React.createContext(null);
8
+ Context.displayName = displayName;
9
+ function useStrictContext() {
10
+ const context = React.useContext(Context);
11
+ if (context === null) throw new Error(`use${displayName} must be used within a ${displayName}Provider`);
12
+ return context;
13
+ }
14
+ return [Context.Provider, useStrictContext];
15
+ }
16
+ const [Provider, useSearchContext] = createStrictContext("SearchContext");
17
+ function useSearch() {
18
+ const { settings } = useSearchContext();
19
+ return (params) => search({
20
+ settings,
21
+ params
22
+ });
23
+ }
24
+ function SearchProvider({ children, ...props }) {
25
+ return /* @__PURE__ */ jsx(Provider, {
26
+ value: props,
27
+ children
28
+ });
29
+ }
30
+
31
+ //#endregion
32
+ export { useSearch as n, useSearchContext as r, SearchProvider as t };
@@ -0,0 +1,21 @@
1
+ import { f as ResultType, g as SearchSettings, h as SearchParams } from "./types-Gg968wOz.js";
2
+ import * as React from "react";
3
+ import * as react_jsx_runtime0 from "react/jsx-runtime";
4
+
5
+ //#region src/context.d.ts
6
+ type SearchContextType = {
7
+ settings: SearchSettings;
8
+ onSelect?: (stainlessPath: string) => void;
9
+ pageFind?: string;
10
+ };
11
+ declare const useSearchContext: () => SearchContextType;
12
+ declare function useSearch(): (params: SearchParams) => Promise<ResultType | undefined>;
13
+ type SearchProviderProps = SearchContextType & {
14
+ children: React.ReactNode;
15
+ };
16
+ declare function SearchProvider({
17
+ children,
18
+ ...props
19
+ }: SearchProviderProps): react_jsx_runtime0.JSX.Element;
20
+ //#endregion
21
+ export { SearchContextType, SearchProvider, SearchProviderProps, useSearch, useSearchContext };
@@ -0,0 +1,5 @@
1
+ import "./indexer-DBU0POrK.js";
2
+ import "./algolia-BOY-OcxU.js";
3
+ import { n as useSearch, r as useSearchContext, t as SearchProvider } from "./context-CBTWkDal.js";
4
+
5
+ export { SearchProvider, useSearch, useSearchContext };
@@ -0,0 +1,25 @@
1
+ import { l as QueryKindsType, u as ResultData } from "./types-Gg968wOz.js";
2
+ import * as react_jsx_runtime0 from "react/jsx-runtime";
3
+
4
+ //#region src/form.d.ts
5
+ declare function SearchForm(): react_jsx_runtime0.JSX.Element;
6
+ type SearchFilterProps = {
7
+ results: ResultData;
8
+ filterKind: QueryKindsType;
9
+ onChange: (filterKind: QueryKindsType) => void;
10
+ };
11
+ declare function SearchFilter({
12
+ results,
13
+ filterKind,
14
+ onChange
15
+ }: SearchFilterProps): react_jsx_runtime0.JSX.Element;
16
+ type SearchModalProps = {
17
+ id?: string;
18
+ open?: boolean;
19
+ };
20
+ declare function SearchModal({
21
+ id,
22
+ open: isOpen
23
+ }: SearchModalProps): react_jsx_runtime0.JSX.Element;
24
+ //#endregion
25
+ export { SearchFilter, SearchFilterProps, SearchForm, SearchModal, SearchModalProps };
package/dist/index.js ADDED
@@ -0,0 +1,328 @@
1
+ import "./indexer-DBU0POrK.js";
2
+ import { t as QueryKinds } from "./types-BhJLoaNF.js";
3
+ import "./algolia-BOY-OcxU.js";
4
+ import { n as useSearch, r as useSearchContext } from "./context-CBTWkDal.js";
5
+ import { t as guideSearch } from "./pagefind-Dcn-gjDe.js";
6
+ import * as React from "react";
7
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
8
+ import { BookOpenText, Box, Code, Folder, Search, Wrench, X } from "lucide-react";
9
+ import { useLanguage } from "@stainless-api/docs-ui/contexts";
10
+ import { useComponents } from "@stainless-api/docs-ui/contexts/use-components";
11
+ import style from "@stainless-api/docs-ui/style";
12
+
13
+ //#region src/results.tsx
14
+ const QueryKindDisplay = {
15
+ all: {
16
+ name: "Results",
17
+ icon: Search
18
+ },
19
+ resource: {
20
+ name: "Resources",
21
+ icon: Folder
22
+ },
23
+ http_method: {
24
+ name: "Methods",
25
+ icon: Box
26
+ },
27
+ model: {
28
+ name: "Types",
29
+ icon: Code
30
+ },
31
+ property: {
32
+ name: "Properties",
33
+ icon: Wrench
34
+ },
35
+ guide: {
36
+ name: "Guide",
37
+ icon: BookOpenText
38
+ }
39
+ };
40
+ function Highlight({ result, name }) {
41
+ const value = result._highlightResult[name]?.value;
42
+ if (value) return /* @__PURE__ */ jsx("span", { dangerouslySetInnerHTML: { __html: value } });
43
+ }
44
+ function SearchResultBreadcrumb({ result }) {
45
+ const Docs = useComponents();
46
+ const Icon = QueryKindDisplay[result.kind].icon;
47
+ const items = result.crumbs?.map((crumb) => /* @__PURE__ */ jsx("span", {
48
+ className: style.SearchBreadcrumbItem,
49
+ children: crumb
50
+ }, crumb));
51
+ return /* @__PURE__ */ jsxs("div", {
52
+ className: style.SearchBreadcrumb,
53
+ children: [/* @__PURE__ */ jsx(Icon, {
54
+ className: style.Icon,
55
+ size: 14
56
+ }), Array.isArray(result.crumbs) && /* @__PURE__ */ jsx(Docs.Join, {
57
+ items,
58
+ children: /* @__PURE__ */ jsx("span", {
59
+ className: style.SearchBreadcrumbDivider,
60
+ children: "›"
61
+ })
62
+ })]
63
+ });
64
+ }
65
+ function SearchResult({ result }) {
66
+ return /* @__PURE__ */ jsxs("div", {
67
+ className: style.SearchResult,
68
+ "data-stldocs-search-result": result.kind,
69
+ children: [/* @__PURE__ */ jsx(SearchResultBreadcrumb, { result }), /* @__PURE__ */ jsx(SearchResultContent, { result })]
70
+ });
71
+ }
72
+ function GuideResult({ result }) {
73
+ const Docs = useComponents();
74
+ const Icon = QueryKindDisplay["guide"].icon;
75
+ const path = result.data.url.slice(1, -1).split("/").map((crumb) => /* @__PURE__ */ jsx("span", {
76
+ className: style.SearchBreadcrumbItem,
77
+ children: crumb
78
+ }, crumb));
79
+ const crumbs = path.length > 1 ? path : [/* @__PURE__ */ jsx("span", {
80
+ className: style.SearchBreadcrumbItem,
81
+ children: "Overview"
82
+ }, "overview")];
83
+ return /* @__PURE__ */ jsxs("div", {
84
+ className: style.SearchResult,
85
+ "data-stldocs-search-result": "guide",
86
+ children: [
87
+ /* @__PURE__ */ jsxs("div", {
88
+ className: style.SearchBreadcrumb,
89
+ children: [/* @__PURE__ */ jsx(Icon, {
90
+ className: style.Icon,
91
+ size: 14
92
+ }), /* @__PURE__ */ jsx(Docs.Join, {
93
+ items: crumbs,
94
+ children: /* @__PURE__ */ jsx("span", {
95
+ className: style.SearchBreadcrumbDivider,
96
+ children: "›"
97
+ })
98
+ })]
99
+ }),
100
+ /* @__PURE__ */ jsx("h3", {
101
+ className: style.SearchResultGuideTitle,
102
+ children: result.data.meta.title
103
+ }),
104
+ /* @__PURE__ */ jsx("div", {
105
+ className: style.SearchResultGuideExcerpt,
106
+ dangerouslySetInnerHTML: { __html: result.data.excerpt }
107
+ })
108
+ ]
109
+ });
110
+ }
111
+ function SearchResultContent({ result }) {
112
+ const Docs = useComponents();
113
+ const language = useLanguage();
114
+ switch (result.kind) {
115
+ case "http_method": return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Docs.MethodHeader, {
116
+ level: "h5",
117
+ title: /* @__PURE__ */ jsx(Highlight, {
118
+ result,
119
+ name: result.summary ? "summary" : "title"
120
+ }),
121
+ signature: result["qualified"] && /* @__PURE__ */ jsx(Highlight, {
122
+ result,
123
+ name: "qualified"
124
+ }),
125
+ children: /* @__PURE__ */ jsx(Docs.MethodRoute, {
126
+ httpMethod: result.httpMethod,
127
+ endpoint: /* @__PURE__ */ jsx(Highlight, {
128
+ result,
129
+ name: "endpoint"
130
+ })
131
+ })
132
+ }), /* @__PURE__ */ jsx("div", {
133
+ className: `${style.MethodDescription} ${style.Content}`,
134
+ children: /* @__PURE__ */ jsx(Highlight, {
135
+ result,
136
+ name: "description"
137
+ })
138
+ })] });
139
+ case "model": {
140
+ const properties = result.children?.map((child, index) => /* @__PURE__ */ jsx("span", {
141
+ className: style.TextIdentifier,
142
+ children: child
143
+ }, index)) ?? [];
144
+ return /* @__PURE__ */ jsxs("div", {
145
+ className: style.Property,
146
+ "data-stldocs-language": language,
147
+ children: [/* @__PURE__ */ jsx("div", {
148
+ className: style.PropertyHeader,
149
+ children: /* @__PURE__ */ jsx("span", {
150
+ className: style.PropertyName,
151
+ children: /* @__PURE__ */ jsx(Highlight, {
152
+ result,
153
+ name: result.title ? "title" : "name"
154
+ })
155
+ })
156
+ }), /* @__PURE__ */ jsxs("span", {
157
+ className: style.PropertyDeclaration,
158
+ children: [
159
+ /* @__PURE__ */ jsx(Highlight, {
160
+ result,
161
+ name: "ident"
162
+ }),
163
+ ":",
164
+ " ",
165
+ /* @__PURE__ */ jsx(Docs.Join, {
166
+ items: properties,
167
+ limit: 3,
168
+ children: /* @__PURE__ */ jsx("span", {
169
+ className: style.TextPunctuation,
170
+ children: ", "
171
+ })
172
+ })
173
+ ]
174
+ })]
175
+ });
176
+ }
177
+ case "resource": return /* @__PURE__ */ jsxs("div", {
178
+ className: style.SearchResultResourceInfo,
179
+ children: [/* @__PURE__ */ jsx("span", {
180
+ className: style.SearchResultResourceTitle,
181
+ children: /* @__PURE__ */ jsx(Highlight, {
182
+ result,
183
+ name: "title"
184
+ })
185
+ }), /* @__PURE__ */ jsx("span", {
186
+ className: style.SearchResultResourcePath,
187
+ children: /* @__PURE__ */ jsx(Highlight, {
188
+ result,
189
+ name: "QualifiedName"
190
+ })
191
+ })]
192
+ });
193
+ case "property": return /* @__PURE__ */ jsxs("div", {
194
+ className: style.Property,
195
+ "data-stldocs-language": language,
196
+ children: [/* @__PURE__ */ jsxs("div", {
197
+ className: style.PropertyHeader,
198
+ children: [/* @__PURE__ */ jsx("span", {
199
+ className: style.PropertyName,
200
+ children: /* @__PURE__ */ jsx(Highlight, {
201
+ result,
202
+ name: "name"
203
+ })
204
+ }), /* @__PURE__ */ jsx("span", {
205
+ className: style.PropertyTypeName,
206
+ children: /* @__PURE__ */ jsx("span", { dangerouslySetInnerHTML: { __html: result.type ?? "" } })
207
+ })]
208
+ }), result.docstring && /* @__PURE__ */ jsx("span", {
209
+ className: style.PropertyDescription,
210
+ children: /* @__PURE__ */ jsx(Highlight, {
211
+ result,
212
+ name: "docstring"
213
+ })
214
+ })]
215
+ });
216
+ }
217
+ }
218
+
219
+ //#endregion
220
+ //#region src/form.tsx
221
+ function SearchForm() {
222
+ const Docs = useComponents();
223
+ const search = useSearch();
224
+ const language = useLanguage();
225
+ const { onSelect, pageFind } = useSearchContext();
226
+ const [results, setResults] = React.useState(null);
227
+ const [filterKind, setFilterKind] = React.useState("all");
228
+ const [searchQuery, setSearchQuery] = React.useState("");
229
+ const inputRef = React.useRef(null);
230
+ async function performSearch() {
231
+ const guideLimit = filterKind === "guide" ? 25 : 5;
232
+ const kind = ["all", "guide"].includes(filterKind) ? void 0 : filterKind;
233
+ const [guideResults, apiResults] = await Promise.all([pageFind ? guideSearch(pageFind, searchQuery, guideLimit) : [], search({
234
+ query: searchQuery,
235
+ kind,
236
+ language
237
+ })]);
238
+ setResults({
239
+ items: filterKind === "guide" ? guideResults : filterKind === "all" ? [...guideResults.slice(0, 5), ...apiResults?.hits ?? []] : apiResults?.hits ?? [],
240
+ counts: {
241
+ ...apiResults?.facets?.["kind"],
242
+ guide: guideResults.length,
243
+ all: apiResults?.nbHits
244
+ }
245
+ });
246
+ }
247
+ function clearInput() {
248
+ setSearchQuery("");
249
+ inputRef?.current?.focus();
250
+ }
251
+ React.useEffect(() => void performSearch(), [
252
+ searchQuery,
253
+ filterKind,
254
+ language
255
+ ]);
256
+ return /* @__PURE__ */ jsxs("div", {
257
+ className: style.SearchForm,
258
+ children: [
259
+ /* @__PURE__ */ jsx(Docs.Input, {
260
+ ref: inputRef,
261
+ autoFocus: true,
262
+ onChange: (ev) => setSearchQuery(ev.target.value),
263
+ left: /* @__PURE__ */ jsx(Search, {
264
+ size: 16,
265
+ className: style.Icon
266
+ }),
267
+ right: searchQuery && /* @__PURE__ */ jsx(X, {
268
+ cursor: "pointer",
269
+ onClick: () => clearInput(),
270
+ size: 16,
271
+ className: style.Icon
272
+ }),
273
+ value: searchQuery,
274
+ placeholder: "Search"
275
+ }),
276
+ /* @__PURE__ */ jsx(SearchFilter, {
277
+ results,
278
+ filterKind,
279
+ onChange: (filterKind$1) => setFilterKind(filterKind$1)
280
+ }),
281
+ /* @__PURE__ */ jsx(Docs.ListView, {
282
+ items: results?.items ?? [],
283
+ itemDelegate: (item) => "kind" in item ? /* @__PURE__ */ jsx(SearchResult, { result: item }) : /* @__PURE__ */ jsx(GuideResult, { result: item }),
284
+ onSelectListItem: (item) => onSelect?.(item["data"]?.["url"] ?? item["stainlessPath"])
285
+ })
286
+ ]
287
+ });
288
+ }
289
+ function SearchFilter({ results, filterKind, onChange }) {
290
+ const Docs = useComponents();
291
+ const { pageFind } = useSearchContext();
292
+ const toggles = pageFind ? QueryKinds : QueryKinds.slice(0, -1);
293
+ return /* @__PURE__ */ jsx("div", {
294
+ className: style.SearchFilter,
295
+ children: toggles.map((kind, index) => /* @__PURE__ */ jsxs(Docs.ToggleButton, {
296
+ selected: filterKind === kind,
297
+ onClick: () => onChange?.(kind),
298
+ children: [
299
+ React.createElement(QueryKindDisplay[kind].icon, {
300
+ size: 16,
301
+ className: style.Icon
302
+ }),
303
+ /* @__PURE__ */ jsx("span", {
304
+ className: style.SearchFilterLabel,
305
+ children: QueryKindDisplay[kind].name
306
+ }),
307
+ /* @__PURE__ */ jsx("span", {
308
+ className: style.SearchFilterCount,
309
+ children: results?.counts?.[kind] ?? 0
310
+ })
311
+ ]
312
+ }, index))
313
+ });
314
+ }
315
+ function SearchModal({ id, open: isOpen }) {
316
+ const [open, setOpen] = React.useState(isOpen ?? false);
317
+ return /* @__PURE__ */ jsx("div", {
318
+ id,
319
+ onToggle: (ev) => setOpen(ev.newState === "open"),
320
+ className: style.SearchModal,
321
+ popover: "auto",
322
+ "data-stldocs-modal-open": open,
323
+ children: open && /* @__PURE__ */ jsx(SearchForm, {})
324
+ });
325
+ }
326
+
327
+ //#endregion
328
+ export { SearchFilter, SearchForm, SearchModal };
@@ -0,0 +1,181 @@
1
+ import { Languages, generateRoute, parseStainlessPath, walkTree } from "@stainless-api/docs-ui/routing";
2
+ import { printer, renderMarkdown } from "@stainless-api/docs-ui/markdown";
3
+
4
+ //#region src/indexer.ts
5
+ function getResourceNames(resourceIds, topResources) {
6
+ let element = void 0;
7
+ let resources = topResources;
8
+ const resourceName = [];
9
+ for (const resource of resourceIds) {
10
+ element = resources?.[resource];
11
+ if (!element) break;
12
+ resourceName.push(element.title);
13
+ resources = element?.subresources;
14
+ }
15
+ return resourceName;
16
+ }
17
+ function chunkByLines(content, maxSize = 6e4) {
18
+ if (Buffer.byteLength(content, "utf8") < maxSize) return [content];
19
+ const lines = content.split("\n");
20
+ const chunks = [];
21
+ let currentChunk = [];
22
+ let currentSize = 0;
23
+ for (const line of lines) {
24
+ const lineSize = Buffer.byteLength(line + "\n", "utf8");
25
+ if (currentSize + lineSize > maxSize) {
26
+ chunks.push(currentChunk.join("\n"));
27
+ currentChunk = [];
28
+ currentSize = 0;
29
+ }
30
+ currentChunk.push(line);
31
+ currentSize += lineSize;
32
+ }
33
+ if (currentChunk.length > 0) chunks.push(currentChunk.join("\n"));
34
+ return chunks;
35
+ }
36
+ function* generateChatIndex(spec) {
37
+ for (const [language, readme] of Object.entries(spec.readme)) {
38
+ const chunks = chunkByLines(readme);
39
+ for (const chunk of chunks) yield {
40
+ language,
41
+ title: "Overview",
42
+ content: chunk,
43
+ url: `docs://BASE_PATH/${language}`
44
+ };
45
+ }
46
+ for (const { data } of walkTree(spec)) {
47
+ if (data.kind !== "http_method") continue;
48
+ const { title, name, stainlessPath, httpMethod, summary, description } = data;
49
+ const endpoint = data.endpoint.slice(httpMethod.length).trim();
50
+ for (const language of Languages) {
51
+ const decl = spec.decls[language]?.[stainlessPath];
52
+ if (!decl) continue;
53
+ const chunks = chunkByLines(renderMarkdown({
54
+ spec,
55
+ language,
56
+ options: { includeModelProperties: true }
57
+ }, data));
58
+ for (const chunk of chunks) yield {
59
+ language,
60
+ title,
61
+ name,
62
+ endpoint,
63
+ httpMethod,
64
+ summary,
65
+ description,
66
+ stainlessPath,
67
+ qualified: "qualified" in decl ? decl["qualified"] : void 0,
68
+ ident: "ident" in decl ? decl["ident"] : void 0,
69
+ content: chunk,
70
+ url: generateRoute("docs://BASE_PATH", language, stainlessPath)
71
+ };
72
+ }
73
+ }
74
+ }
75
+ function* generateIndex(spec, renderMarkdownFn, includeTypes) {
76
+ const parentCrumbs = {};
77
+ for (const { data } of walkTree(spec, true)) {
78
+ const { kind, name, title, stainlessPath } = data;
79
+ const common = {
80
+ name,
81
+ title,
82
+ stainlessPath
83
+ };
84
+ const crumbs = getResourceNames(parseStainlessPath(stainlessPath).resource, spec.resources);
85
+ switch (kind) {
86
+ case "resource":
87
+ for (const language of Languages) {
88
+ if (!data[language]) continue;
89
+ parentCrumbs[stainlessPath] = crumbs;
90
+ const { Name, QualifiedName } = data[language];
91
+ yield {
92
+ kind,
93
+ crumbs,
94
+ language,
95
+ Name,
96
+ QualifiedName,
97
+ priority: 0,
98
+ ...common
99
+ };
100
+ }
101
+ break;
102
+ case "http_method": {
103
+ const { summary, endpoint, httpMethod } = data;
104
+ for (const language of Languages) {
105
+ const found = spec.decls[language]?.[stainlessPath];
106
+ if (!found) continue;
107
+ parentCrumbs[stainlessPath] = [...crumbs, title];
108
+ const qualified = "qualified" in found ? found["qualified"] : void 0;
109
+ const ident = qualified?.split(".")?.at(-1);
110
+ yield {
111
+ kind,
112
+ crumbs: [...crumbs, title],
113
+ ident,
114
+ qualified,
115
+ language,
116
+ description: data.description ? renderMarkdownFn?.(data.description) ?? data.description : void 0,
117
+ endpoint: endpoint.slice(httpMethod.length).trim(),
118
+ httpMethod,
119
+ summary,
120
+ priority: 0,
121
+ ...common
122
+ };
123
+ }
124
+ break;
125
+ }
126
+ case "model": for (const language of Languages) {
127
+ if (!spec.decls[language]) continue;
128
+ parentCrumbs[stainlessPath] = [...crumbs, title];
129
+ const schema = spec.decls[language]?.[`${stainlessPath} > (schema)`];
130
+ const children = (schema && "children" in schema ? schema?.["children"] : void 0)?.map((childPath) => {
131
+ const child = spec.decls?.[language]?.[childPath];
132
+ return child?.["ident"] ?? child?.["name"] ?? child?.["key"] ?? child?.["type"]?.["literal"]?.["value"] ?? child?.["type"]?.["literal"] ?? child?.["type"]?.["value"];
133
+ })?.filter((child) => child) ?? [];
134
+ yield {
135
+ kind,
136
+ crumbs: [...crumbs, title],
137
+ children,
138
+ language,
139
+ priority: 2,
140
+ ident: schema && "ident" in schema ? schema?.["ident"] : void 0,
141
+ ...common
142
+ };
143
+ }
144
+ }
145
+ }
146
+ for (const language of Languages) {
147
+ const decls = spec.decls?.[language];
148
+ if (!decls) continue;
149
+ for (const decl of Object.values(decls)) switch (decl.kind) {
150
+ case "JavaDeclProperty":
151
+ case "GoDeclProperty":
152
+ case "PythonDeclProperty":
153
+ case "RubyDeclProperty":
154
+ case "HttpDeclProperty":
155
+ case "TSDeclProperty":
156
+ {
157
+ const parsedPath = parseStainlessPath(decl.stainlessPath);
158
+ const type = includeTypes === false ? void 0 : printer.typeName(language, decl.type);
159
+ const name = decl["ident"] ?? decl["name"] ?? decl["key"];
160
+ const parent = parentCrumbs[parsedPath.routable];
161
+ if (parent === void 0) continue;
162
+ const matches = decl.stainlessPath.matchAll(/\((property|params|param)\) ([^\s]+)/g);
163
+ const props = Array.from(matches).map((p) => p[2]).filter((p) => p !== void 0);
164
+ yield {
165
+ kind: "property",
166
+ name,
167
+ stainlessPath: decl.stainlessPath,
168
+ crumbs: [...parent, ...props],
169
+ docstring: decl.docstring ? renderMarkdownFn?.(decl.docstring) ?? decl.docstring : void 0,
170
+ type,
171
+ language,
172
+ priority: 3
173
+ };
174
+ }
175
+ break;
176
+ }
177
+ }
178
+ }
179
+
180
+ //#endregion
181
+ export { generateIndex as n, generateChatIndex as t };
package/dist/mcp.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ import { n as IndexEntry, r as IndexMethod } from "./types-Gg968wOz.js";
2
+ import { DocsLanguage } from "@stainless-api/docs-ui/routing";
3
+ import * as SDKJSON from "@stainless/sdk-json";
4
+
5
+ //#region src/indexer.d.ts
6
+
7
+ declare function generateIndex(spec: SDKJSON.Spec, renderMarkdownFn?: (_: string) => string | null, includeTypes?: boolean): Generator<IndexEntry>;
8
+ //#endregion
9
+ //#region src/mcp.d.ts
10
+ type Item = IndexEntry & IndexMethod;
11
+ declare function render(spec: SDKJSON.Spec, language: DocsLanguage, items: Item[], includeModelProperties: boolean): any;
12
+ //#endregion
13
+ export { generateIndex, render };
package/dist/mcp.js ADDED
@@ -0,0 +1,36 @@
1
+ import { n as generateIndex } from "./indexer-DBU0POrK.js";
2
+ import { parseStainlessPath } from "@stainless-api/docs-ui/routing";
3
+ import { renderMarkdown } from "@stainless-api/docs-ui/markdown";
4
+ import { getResourceFromSpec } from "@stainless-api/docs-ui/utils";
5
+
6
+ //#region src/mcp.ts
7
+ function consolidate(results) {
8
+ const resources = /* @__PURE__ */ new Set();
9
+ const methods = /* @__PURE__ */ new Set();
10
+ for (const entry of results) {
11
+ const parsed = parseStainlessPath(entry.stainlessPath);
12
+ if (parsed.method) methods.add(parsed.routable);
13
+ else resources.add(parsed.routable);
14
+ }
15
+ const filtered = Array.from(methods).filter((path) => !resources.has(path.split(" >").at(0)));
16
+ return [...resources, ...filtered];
17
+ }
18
+ function render(spec, language, items, includeModelProperties) {
19
+ const env = {
20
+ spec,
21
+ language,
22
+ options: {
23
+ renderNestedResources: false,
24
+ includeModelProperties
25
+ }
26
+ };
27
+ const output = consolidate(items).map((entry) => {
28
+ const parsed = parseStainlessPath(entry);
29
+ const resource = getResourceFromSpec(parsed.resource, spec);
30
+ return [entry, renderMarkdown(env, parsed.method ? resource.methods[parsed.method] : resource)];
31
+ });
32
+ return Object.fromEntries(output);
33
+ }
34
+
35
+ //#endregion
36
+ export { generateIndex, render };
@@ -0,0 +1,15 @@
1
+ //#region src/providers/pagefind.ts
2
+ async function loadPagefind(path) {
3
+ return await import(new URL(path, import.meta.url).href);
4
+ }
5
+ async function guideSearch(loadPath, query, limit) {
6
+ const response = await (await loadPagefind(loadPath)).search(query);
7
+ const items = limit ? response.results.slice(0, limit) : response.results;
8
+ return Promise.all(items.map((result) => result.data().then((data) => ({
9
+ ...result,
10
+ data
11
+ }))));
12
+ }
13
+
14
+ //#endregion
15
+ export { guideSearch as t };
@@ -0,0 +1,24 @@
1
+ import { f as ResultType, g as SearchSettings, h as SearchParams, s as ProseIndexEntry } from "../types-Gg968wOz.js";
2
+ import * as SDKJSON from "@stainless/sdk-json";
3
+
4
+ //#region src/providers/algolia.d.ts
5
+ declare function buildIndex(appId: string, indexName: string, writeKey: string, spec: SDKJSON.Spec, renderMarkdown: (_: string) => string | null): Promise<void>;
6
+ declare function buildChatIndex(appId: string, indexName: string, writeKey: string, spec: SDKJSON.Spec): Promise<void>;
7
+ declare function buildProseIndex(appId: string, indexName: string, writeKey: string, objects: ProseIndexEntry[]): Promise<void>;
8
+ declare function search({
9
+ settings: {
10
+ appId,
11
+ indexName,
12
+ searchKey
13
+ },
14
+ params: {
15
+ query,
16
+ language,
17
+ kind
18
+ }
19
+ }: {
20
+ params: SearchParams;
21
+ settings: SearchSettings;
22
+ }): Promise<ResultType | undefined>;
23
+ //#endregion
24
+ export { buildChatIndex, buildIndex, buildProseIndex, search };
@@ -0,0 +1,4 @@
1
+ import "../indexer-DBU0POrK.js";
2
+ import { i as search, n as buildIndex, r as buildProseIndex, t as buildChatIndex } from "../algolia-BOY-OcxU.js";
3
+
4
+ export { buildChatIndex, buildIndex, buildProseIndex, search };
@@ -0,0 +1,18 @@
1
+ import { n as IndexEntry } from "../types-Gg968wOz.js";
2
+ import { DocsLanguage } from "@stainless-api/docs-ui/routing";
3
+ import * as fuse_js0 from "fuse.js";
4
+ import { FuseIndex } from "fuse.js";
5
+ import * as SDKJSON from "@stainless/sdk-json";
6
+
7
+ //#region src/providers/fuse.d.ts
8
+ type FuseIndexData = {
9
+ content: IndexEntry[];
10
+ index: FuseIndex<IndexEntry>;
11
+ };
12
+ declare function buildIndex(spec: SDKJSON.Spec, language?: DocsLanguage): FuseIndexData;
13
+ declare function search({
14
+ content,
15
+ index
16
+ }: FuseIndexData, query: string, limit?: number): fuse_js0.FuseResult<IndexEntry>[];
17
+ //#endregion
18
+ export { FuseIndexData, buildIndex, search };
@@ -0,0 +1,19 @@
1
+ import { n as generateIndex } from "../indexer-DBU0POrK.js";
2
+ import { n as SearchableAttributes } from "../types-BhJLoaNF.js";
3
+ import Fuse from "fuse.js";
4
+
5
+ //#region src/providers/fuse.ts
6
+ function buildIndex(spec, language) {
7
+ const idx = Array.from(generateIndex(spec, void 0, false));
8
+ const content = language ? idx.filter((entry) => entry.language === language) : idx;
9
+ return {
10
+ content,
11
+ index: Fuse.createIndex([...SearchableAttributes], content)
12
+ };
13
+ }
14
+ function search({ content, index }, query, limit = 100) {
15
+ return new Fuse(content, { keys: [...SearchableAttributes] }, index).search(query).slice(0, limit);
16
+ }
17
+
18
+ //#endregion
19
+ export { buildIndex, search };
@@ -0,0 +1,6 @@
1
+ import { t as GuideResultType } from "../types-Gg968wOz.js";
2
+
3
+ //#region src/providers/pagefind.d.ts
4
+ declare function guideSearch(loadPath: string, query: string, limit?: number): Promise<GuideResultType[]>;
5
+ //#endregion
6
+ export { guideSearch };
@@ -0,0 +1,3 @@
1
+ import { t as guideSearch } from "../pagefind-Dcn-gjDe.js";
2
+
3
+ export { guideSearch };
@@ -0,0 +1,9 @@
1
+ import { n as IndexEntry } from "../types-Gg968wOz.js";
2
+ import { DocsLanguage } from "@stainless-api/docs-ui/routing";
3
+ import * as SDKJSON from "@stainless/sdk-json";
4
+
5
+ //#region src/providers/walker.d.ts
6
+ declare function buildIndex(spec: SDKJSON.Spec): Generator<IndexEntry, any, any>;
7
+ declare function search(index: Generator<IndexEntry>, language: DocsLanguage, query: string, limit?: number): IndexEntry[];
8
+ //#endregion
9
+ export { buildIndex, search };
@@ -0,0 +1,23 @@
1
+ import { n as generateIndex } from "../indexer-DBU0POrK.js";
2
+ import { n as SearchableAttributes } from "../types-BhJLoaNF.js";
3
+
4
+ //#region src/providers/walker.ts
5
+ function buildIndex(spec) {
6
+ return generateIndex(spec, void 0, false);
7
+ }
8
+ function* findEntryInIndex(index, language, query) {
9
+ for (const entry of index) {
10
+ if (entry.language !== language) continue;
11
+ for (const attr of SearchableAttributes) {
12
+ const attr_ = attr in entry ? attr : null;
13
+ if (attr_ && entry[attr_] && typeof entry[attr_] === "string" && entry[attr_].includes(query)) yield entry;
14
+ }
15
+ }
16
+ }
17
+ function search(index, language, query, limit = 100) {
18
+ const results = findEntryInIndex(index, language, query);
19
+ return Array.from(results).sort((a, b) => a.priority - b.priority).slice(0, limit);
20
+ }
21
+
22
+ //#endregion
23
+ export { buildIndex, search };
@@ -0,0 +1,35 @@
1
+ //#region src/types.ts
2
+ const QueryKinds = [
3
+ "all",
4
+ "resource",
5
+ "http_method",
6
+ "model",
7
+ "property",
8
+ "guide"
9
+ ];
10
+ const SearchableAttributes = [
11
+ "name",
12
+ "title",
13
+ "ident",
14
+ "Name",
15
+ "qualified",
16
+ "QualifiedName",
17
+ "endpoint",
18
+ "summary",
19
+ "description",
20
+ "docstring"
21
+ ];
22
+ const SearchableAttributesChat = [
23
+ "title",
24
+ "name",
25
+ "endpoint",
26
+ "summary",
27
+ "description",
28
+ "qualified",
29
+ "ident",
30
+ "content"
31
+ ];
32
+ const SearchableAttributesProse = ["content"];
33
+
34
+ //#endregion
35
+ export { SearchableAttributesProse as i, SearchableAttributes as n, SearchableAttributesChat as r, QueryKinds as t };
@@ -0,0 +1,91 @@
1
+ import { DocsLanguage } from "@stainless-api/docs-ui/routing";
2
+ import * as SDKJSON from "@stainless/sdk-json";
3
+
4
+ //#region src/types.d.ts
5
+ type SearchSettings = {
6
+ appId: string;
7
+ searchKey: string;
8
+ indexName: string;
9
+ assistant?: string;
10
+ };
11
+ type SearchParams = {
12
+ query: string;
13
+ language?: DocsLanguage | null;
14
+ kind?: QueryKindsType | null;
15
+ };
16
+ declare const QueryKinds: readonly ["all", "resource", "http_method", "model", "property", "guide"];
17
+ type QueryKindsType = (typeof QueryKinds)[number];
18
+ type IndexModel = {
19
+ kind: 'model';
20
+ title: string;
21
+ children?: string[];
22
+ ident?: string;
23
+ };
24
+ type IndexProperty = {
25
+ kind: 'property';
26
+ docstring?: string;
27
+ type?: string;
28
+ };
29
+ type IndexResource = {
30
+ kind: 'resource';
31
+ title: string;
32
+ Name: string;
33
+ QualifiedName: string;
34
+ };
35
+ type IndexMethod = Pick<SDKJSON.Method, 'kind' | 'summary' | 'description' | 'endpoint' | 'httpMethod'> & {
36
+ title: string;
37
+ qualified?: string;
38
+ ident?: string;
39
+ };
40
+ declare const SearchableAttributes: readonly ["name", "title", "ident", "Name", "qualified", "QualifiedName", "endpoint", "summary", "description", "docstring"];
41
+ declare const SearchableAttributesChat: string[];
42
+ declare const SearchableAttributesProse: string[];
43
+ type SearchAttributeNames = (typeof SearchableAttributes)[number];
44
+ type RoutableJsonNode = SDKJSON.Method | SDKJSON.Model | SDKJSON.Resource;
45
+ type IndexEntry = Pick<RoutableJsonNode, 'name' | 'stainlessPath'> & (IndexProperty | IndexModel | IndexResource | IndexMethod) & {
46
+ language: DocsLanguage;
47
+ priority: number;
48
+ crumbs: string[];
49
+ };
50
+ type ResultRecordType = IndexEntry & {
51
+ objectID: string;
52
+ _highlightResult: Record<SearchAttributeNames, {
53
+ value: string;
54
+ }>;
55
+ };
56
+ type ResultType = {
57
+ hits: ResultRecordType[];
58
+ facets?: Record<string, Record<string, number>>;
59
+ nbHits: number;
60
+ };
61
+ type GuideResultType = {
62
+ id: string;
63
+ score: number;
64
+ words: number[];
65
+ data: {
66
+ excerpt: string;
67
+ url: string;
68
+ word_count: number;
69
+ meta: {
70
+ title: string;
71
+ };
72
+ sub_results: {
73
+ url: string;
74
+ title: string;
75
+ excerpt: string;
76
+ }[];
77
+ };
78
+ };
79
+ type ResultData = {
80
+ items: Array<ResultRecordType | GuideResultType>;
81
+ counts: Partial<Record<QueryKindsType, number>>;
82
+ };
83
+ type ProseIndexEntry = {
84
+ id?: string;
85
+ tag: string;
86
+ content: string;
87
+ source?: string;
88
+ [additional: string]: unknown;
89
+ };
90
+ //#endregion
91
+ export { SearchableAttributes as _, IndexProperty as a, QueryKinds as c, ResultRecordType as d, ResultType as f, SearchSettings as g, SearchParams as h, IndexModel as i, QueryKindsType as l, SearchAttributeNames as m, IndexEntry as n, IndexResource as o, RoutableJsonNode as p, IndexMethod as r, ProseIndexEntry as s, GuideResultType as t, ResultData as u, SearchableAttributesChat as v, SearchableAttributesProse as y };
@@ -0,0 +1,2 @@
1
+ import { _ as SearchableAttributes, a as IndexProperty, c as QueryKinds, d as ResultRecordType, f as ResultType, g as SearchSettings, h as SearchParams, i as IndexModel, l as QueryKindsType, m as SearchAttributeNames, n as IndexEntry, o as IndexResource, p as RoutableJsonNode, r as IndexMethod, s as ProseIndexEntry, t as GuideResultType, u as ResultData, v as SearchableAttributesChat, y as SearchableAttributesProse } from "./types-Gg968wOz.js";
2
+ export { GuideResultType, IndexEntry, IndexMethod, IndexModel, IndexProperty, IndexResource, ProseIndexEntry, QueryKinds, QueryKindsType, ResultData, ResultRecordType, ResultType, RoutableJsonNode, SearchAttributeNames, SearchParams, SearchSettings, SearchableAttributes, SearchableAttributesChat, SearchableAttributesProse };
package/dist/types.js ADDED
@@ -0,0 +1,3 @@
1
+ import { i as SearchableAttributesProse, n as SearchableAttributes, r as SearchableAttributesChat, t as QueryKinds } from "./types-BhJLoaNF.js";
2
+
3
+ export { QueryKinds, SearchableAttributes, SearchableAttributesChat, SearchableAttributesProse };
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@stainless-api/docs-search",
3
+ "version": "0.1.0-beta.1",
4
+ "publishConfig": {
5
+ "access": "public"
6
+ },
7
+ "type": "module",
8
+ "files": [
9
+ "dist"
10
+ ],
11
+ "peerDependencies": {
12
+ "react": ">=19.0.0",
13
+ "react-dom": ">=19.0.0"
14
+ },
15
+ "dependencies": {
16
+ "@algolia/client-search": "^5.25.0",
17
+ "@markdoc/markdoc": "^0.5.4",
18
+ "cheerio": "^1.1.2",
19
+ "fuse.js": "^7.1.0",
20
+ "htmlparser2": "^10.0.0",
21
+ "lucide-react": "^0.561.0",
22
+ "@stainless-api/docs-ui": "0.1.0-beta.49"
23
+ },
24
+ "devDependencies": {
25
+ "@types/node": "24.10.1",
26
+ "@types/react": "19.2.7",
27
+ "@types/react-dom": "^19.2.3",
28
+ "dotenv": "17.2.3",
29
+ "react": "^19.2.3",
30
+ "react-dom": "^19.2.3",
31
+ "tsdown": "^0.17.3",
32
+ "typescript": "5.9.3",
33
+ "@stainless/eslint-config": "0.1.0-beta.0",
34
+ "@stainless/sdk-json": "^0.1.0-beta.2"
35
+ },
36
+ "exports": {
37
+ ".": {
38
+ "default": "./dist/index.js"
39
+ },
40
+ "./context": {
41
+ "default": "./dist/context.js"
42
+ },
43
+ "./mcp": {
44
+ "default": "./dist/mcp.js"
45
+ },
46
+ "./types": {
47
+ "default": "./dist/types.js"
48
+ },
49
+ "./providers/*": {
50
+ "default": "./dist/providers/*.js"
51
+ }
52
+ },
53
+ "scripts": {
54
+ "build": "tsdown",
55
+ "clean": "rm -rf dist",
56
+ "lint": "eslint .",
57
+ "check:types": "tsc --noEmit"
58
+ }
59
+ }