searchsocket 0.4.0 → 0.5.0

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/dist/client.cjs CHANGED
@@ -1,60 +1,50 @@
1
1
  'use strict';
2
2
 
3
- // src/merge.ts
4
- function mergeSearchResults(initial, reranked, options) {
5
- const maxDisplacement = options?.maxDisplacement ?? 3;
6
- const initialUrls = initial.results.map((r) => r.url);
7
- const rerankedUrls = reranked.results.map((r) => r.url);
8
- const initialPos = /* @__PURE__ */ new Map();
9
- for (let i = 0; i < initialUrls.length; i++) {
10
- initialPos.set(initialUrls[i], i);
11
- }
12
- const rerankedPos = /* @__PURE__ */ new Map();
13
- for (let i = 0; i < rerankedUrls.length; i++) {
14
- rerankedPos.set(rerankedUrls[i], i);
15
- }
16
- const displacements = [];
17
- for (const url of initialUrls) {
18
- const iPos = initialPos.get(url);
19
- const rPos = rerankedPos.get(url);
20
- const displacement = rPos !== void 0 ? Math.abs(iPos - rPos) : 0;
21
- displacements.push({ url, displacement });
3
+ // src/client.ts
4
+ var SNIPPET_TARGET_MAX_WORDS = 12;
5
+ function normalizeTargetText(value) {
6
+ return value.replace(/\u2026/g, " ").replace(/\.{3,}/g, " ").replace(/\s+/g, " ").trim();
7
+ }
8
+ function shortenForTarget(value) {
9
+ const words = value.split(/\s+/).filter(Boolean);
10
+ if (words.length <= SNIPPET_TARGET_MAX_WORDS) {
11
+ return value;
22
12
  }
23
- const totalResults = displacements.length;
24
- if (totalResults === 0) {
25
- return {
26
- response: reranked,
27
- usedRerankedOrder: true,
28
- displacements
29
- };
13
+ return words.slice(0, SNIPPET_TARGET_MAX_WORDS).join(" ");
14
+ }
15
+ function selectTextTarget(result) {
16
+ const sectionTitle = normalizeTargetText(result.sectionTitle ?? "");
17
+ const snippetCandidate = normalizeTargetText(result.chunks?.[0]?.snippet ?? result.snippet);
18
+ if (snippetCandidate) {
19
+ if (!sectionTitle || snippetCandidate.toLowerCase().includes(sectionTitle.toLowerCase())) {
20
+ return shortenForTarget(snippetCandidate);
21
+ }
30
22
  }
31
- const hasLargeDisplacement = displacements.some((d) => d.displacement > maxDisplacement);
32
- if (hasLargeDisplacement) {
33
- return {
34
- response: reranked,
35
- usedRerankedOrder: true,
36
- displacements
37
- };
23
+ return sectionTitle;
24
+ }
25
+ function buildResultUrl(result) {
26
+ const textTarget = selectTextTarget(result);
27
+ if (!textTarget) {
28
+ return result.url;
38
29
  }
39
- const rerankedScoreMap = /* @__PURE__ */ new Map();
40
- for (const result of reranked.results) {
41
- rerankedScoreMap.set(result.url, result.score);
30
+ const hashIdx = result.url.indexOf("#");
31
+ const beforeHash = hashIdx >= 0 ? result.url.slice(0, hashIdx) : result.url;
32
+ const existingHash = hashIdx >= 0 ? result.url.slice(hashIdx) : "";
33
+ const queryIdx = beforeHash.indexOf("?");
34
+ const path = queryIdx >= 0 ? beforeHash.slice(0, queryIdx) : beforeHash;
35
+ const existingQuery = queryIdx >= 0 ? beforeHash.slice(queryIdx + 1) : "";
36
+ const params = new URLSearchParams(existingQuery);
37
+ if (result.sectionTitle) {
38
+ params.set("_ssk", result.sectionTitle);
39
+ } else {
40
+ params.delete("_ssk");
42
41
  }
43
- const mergedResults = initial.results.map((result) => ({
44
- ...result,
45
- score: rerankedScoreMap.get(result.url) ?? result.score
46
- }));
47
- return {
48
- response: {
49
- ...reranked,
50
- results: mergedResults
51
- },
52
- usedRerankedOrder: false,
53
- displacements
54
- };
42
+ params.set("_sskt", textTarget);
43
+ const textFragment = `:~:text=${encodeURIComponent(textTarget)}`;
44
+ const hashWithoutTextFragment = existingHash.replace(/:~:text=.*$/u, "");
45
+ const hash = existingHash ? `${hashWithoutTextFragment}${textFragment}` : `#${textFragment}`;
46
+ return `${path}?${params.toString()}${hash}`;
55
47
  }
56
-
57
- // src/client.ts
58
48
  function createSearchClient(options = {}) {
59
49
  const endpoint = options.endpoint ?? "/api/search";
60
50
  const fetchImpl = options.fetchImpl ?? fetch;
@@ -81,77 +71,11 @@ function createSearchClient(options = {}) {
81
71
  throw new Error(message);
82
72
  }
83
73
  return payload;
84
- },
85
- async streamSearch(request, onPhase) {
86
- const response = await fetchImpl(endpoint, {
87
- method: "POST",
88
- headers: {
89
- "content-type": "application/json"
90
- },
91
- body: JSON.stringify(request)
92
- });
93
- if (!response.ok) {
94
- let payload;
95
- try {
96
- payload = await response.json();
97
- } catch {
98
- throw new Error("Search failed");
99
- }
100
- const message = payload.error?.message ?? "Search failed";
101
- throw new Error(message);
102
- }
103
- const contentType = response.headers.get("content-type") ?? "";
104
- if (contentType.includes("application/json")) {
105
- const data = await response.json();
106
- onPhase({ phase: "initial", data });
107
- return data;
108
- }
109
- if (!response.body) {
110
- throw new Error("Response body is not readable");
111
- }
112
- const reader = response.body.getReader();
113
- const decoder = new TextDecoder();
114
- let buffer = "";
115
- let lastResponse = null;
116
- for (; ; ) {
117
- const { done, value } = await reader.read();
118
- if (done) break;
119
- buffer += decoder.decode(value, { stream: true });
120
- let newlineIdx;
121
- while ((newlineIdx = buffer.indexOf("\n")) !== -1) {
122
- const line = buffer.slice(0, newlineIdx).trim();
123
- buffer = buffer.slice(newlineIdx + 1);
124
- if (line.length === 0) continue;
125
- const event = JSON.parse(line);
126
- if (event.phase === "error") {
127
- const errData = event.data;
128
- throw new Error(errData.error.message ?? "Streaming search error");
129
- }
130
- const searchEvent = event;
131
- onPhase(searchEvent);
132
- lastResponse = searchEvent.data;
133
- }
134
- }
135
- const remaining = buffer.trim();
136
- if (remaining.length > 0) {
137
- const event = JSON.parse(remaining);
138
- if (event.phase === "error") {
139
- const errData = event.data;
140
- throw new Error(errData.error.message ?? "Streaming search error");
141
- }
142
- const searchEvent = event;
143
- onPhase(searchEvent);
144
- lastResponse = searchEvent.data;
145
- }
146
- if (!lastResponse) {
147
- throw new Error("No search results received");
148
- }
149
- return lastResponse;
150
74
  }
151
75
  };
152
76
  }
153
77
 
78
+ exports.buildResultUrl = buildResultUrl;
154
79
  exports.createSearchClient = createSearchClient;
155
- exports.mergeSearchResults = mergeSearchResults;
156
80
  //# sourceMappingURL=client.cjs.map
157
81
  //# sourceMappingURL=client.cjs.map
package/dist/client.d.cts CHANGED
@@ -1,26 +1,12 @@
1
- import { S as SearchResponse, M as MergeSearchOptions, a as MergeSearchResult, b as SearchRequest, c as StreamSearchEvent } from './types-z2dw3H6E.cjs';
2
-
3
- /**
4
- * Smart merge of initial (pre-rerank) and reranked search results.
5
- *
6
- * If the reranker barely changed the ordering, we keep the initial order
7
- * (which the user already saw) and just update scores from the reranked response.
8
- * If the reranker moved any single result by more than `maxDisplacement`
9
- * positions, we adopt the reranked order — the reranker is semantic and
10
- * expensive, so if it strongly disagrees on even one result, trust it.
11
- */
12
- declare function mergeSearchResults(initial: SearchResponse, reranked: SearchResponse, options?: MergeSearchOptions): MergeSearchResult;
1
+ import { S as SearchRequest, a as SearchResponse, b as SearchResult } from './types-Dk43uz25.cjs';
13
2
 
3
+ declare function buildResultUrl(result: SearchResult): string;
14
4
  interface SearchClientOptions {
15
5
  endpoint?: string;
16
6
  fetchImpl?: typeof fetch;
17
7
  }
18
8
  declare function createSearchClient(options?: SearchClientOptions): {
19
9
  search(request: SearchRequest): Promise<SearchResponse>;
20
- streamSearch(request: SearchRequest & {
21
- stream: true;
22
- rerank: true;
23
- }, onPhase: (event: StreamSearchEvent) => void): Promise<SearchResponse>;
24
10
  };
25
11
 
26
- export { type SearchClientOptions, createSearchClient, mergeSearchResults };
12
+ export { type SearchClientOptions, buildResultUrl, createSearchClient };
package/dist/client.d.ts CHANGED
@@ -1,26 +1,12 @@
1
- import { S as SearchResponse, M as MergeSearchOptions, a as MergeSearchResult, b as SearchRequest, c as StreamSearchEvent } from './types-z2dw3H6E.js';
2
-
3
- /**
4
- * Smart merge of initial (pre-rerank) and reranked search results.
5
- *
6
- * If the reranker barely changed the ordering, we keep the initial order
7
- * (which the user already saw) and just update scores from the reranked response.
8
- * If the reranker moved any single result by more than `maxDisplacement`
9
- * positions, we adopt the reranked order — the reranker is semantic and
10
- * expensive, so if it strongly disagrees on even one result, trust it.
11
- */
12
- declare function mergeSearchResults(initial: SearchResponse, reranked: SearchResponse, options?: MergeSearchOptions): MergeSearchResult;
1
+ import { S as SearchRequest, a as SearchResponse, b as SearchResult } from './types-Dk43uz25.js';
13
2
 
3
+ declare function buildResultUrl(result: SearchResult): string;
14
4
  interface SearchClientOptions {
15
5
  endpoint?: string;
16
6
  fetchImpl?: typeof fetch;
17
7
  }
18
8
  declare function createSearchClient(options?: SearchClientOptions): {
19
9
  search(request: SearchRequest): Promise<SearchResponse>;
20
- streamSearch(request: SearchRequest & {
21
- stream: true;
22
- rerank: true;
23
- }, onPhase: (event: StreamSearchEvent) => void): Promise<SearchResponse>;
24
10
  };
25
11
 
26
- export { type SearchClientOptions, createSearchClient, mergeSearchResults };
12
+ export { type SearchClientOptions, buildResultUrl, createSearchClient };
package/dist/client.js CHANGED
@@ -1,58 +1,48 @@
1
- // src/merge.ts
2
- function mergeSearchResults(initial, reranked, options) {
3
- const maxDisplacement = options?.maxDisplacement ?? 3;
4
- const initialUrls = initial.results.map((r) => r.url);
5
- const rerankedUrls = reranked.results.map((r) => r.url);
6
- const initialPos = /* @__PURE__ */ new Map();
7
- for (let i = 0; i < initialUrls.length; i++) {
8
- initialPos.set(initialUrls[i], i);
9
- }
10
- const rerankedPos = /* @__PURE__ */ new Map();
11
- for (let i = 0; i < rerankedUrls.length; i++) {
12
- rerankedPos.set(rerankedUrls[i], i);
13
- }
14
- const displacements = [];
15
- for (const url of initialUrls) {
16
- const iPos = initialPos.get(url);
17
- const rPos = rerankedPos.get(url);
18
- const displacement = rPos !== void 0 ? Math.abs(iPos - rPos) : 0;
19
- displacements.push({ url, displacement });
1
+ // src/client.ts
2
+ var SNIPPET_TARGET_MAX_WORDS = 12;
3
+ function normalizeTargetText(value) {
4
+ return value.replace(/\u2026/g, " ").replace(/\.{3,}/g, " ").replace(/\s+/g, " ").trim();
5
+ }
6
+ function shortenForTarget(value) {
7
+ const words = value.split(/\s+/).filter(Boolean);
8
+ if (words.length <= SNIPPET_TARGET_MAX_WORDS) {
9
+ return value;
20
10
  }
21
- const totalResults = displacements.length;
22
- if (totalResults === 0) {
23
- return {
24
- response: reranked,
25
- usedRerankedOrder: true,
26
- displacements
27
- };
11
+ return words.slice(0, SNIPPET_TARGET_MAX_WORDS).join(" ");
12
+ }
13
+ function selectTextTarget(result) {
14
+ const sectionTitle = normalizeTargetText(result.sectionTitle ?? "");
15
+ const snippetCandidate = normalizeTargetText(result.chunks?.[0]?.snippet ?? result.snippet);
16
+ if (snippetCandidate) {
17
+ if (!sectionTitle || snippetCandidate.toLowerCase().includes(sectionTitle.toLowerCase())) {
18
+ return shortenForTarget(snippetCandidate);
19
+ }
28
20
  }
29
- const hasLargeDisplacement = displacements.some((d) => d.displacement > maxDisplacement);
30
- if (hasLargeDisplacement) {
31
- return {
32
- response: reranked,
33
- usedRerankedOrder: true,
34
- displacements
35
- };
21
+ return sectionTitle;
22
+ }
23
+ function buildResultUrl(result) {
24
+ const textTarget = selectTextTarget(result);
25
+ if (!textTarget) {
26
+ return result.url;
36
27
  }
37
- const rerankedScoreMap = /* @__PURE__ */ new Map();
38
- for (const result of reranked.results) {
39
- rerankedScoreMap.set(result.url, result.score);
28
+ const hashIdx = result.url.indexOf("#");
29
+ const beforeHash = hashIdx >= 0 ? result.url.slice(0, hashIdx) : result.url;
30
+ const existingHash = hashIdx >= 0 ? result.url.slice(hashIdx) : "";
31
+ const queryIdx = beforeHash.indexOf("?");
32
+ const path = queryIdx >= 0 ? beforeHash.slice(0, queryIdx) : beforeHash;
33
+ const existingQuery = queryIdx >= 0 ? beforeHash.slice(queryIdx + 1) : "";
34
+ const params = new URLSearchParams(existingQuery);
35
+ if (result.sectionTitle) {
36
+ params.set("_ssk", result.sectionTitle);
37
+ } else {
38
+ params.delete("_ssk");
40
39
  }
41
- const mergedResults = initial.results.map((result) => ({
42
- ...result,
43
- score: rerankedScoreMap.get(result.url) ?? result.score
44
- }));
45
- return {
46
- response: {
47
- ...reranked,
48
- results: mergedResults
49
- },
50
- usedRerankedOrder: false,
51
- displacements
52
- };
40
+ params.set("_sskt", textTarget);
41
+ const textFragment = `:~:text=${encodeURIComponent(textTarget)}`;
42
+ const hashWithoutTextFragment = existingHash.replace(/:~:text=.*$/u, "");
43
+ const hash = existingHash ? `${hashWithoutTextFragment}${textFragment}` : `#${textFragment}`;
44
+ return `${path}?${params.toString()}${hash}`;
53
45
  }
54
-
55
- // src/client.ts
56
46
  function createSearchClient(options = {}) {
57
47
  const endpoint = options.endpoint ?? "/api/search";
58
48
  const fetchImpl = options.fetchImpl ?? fetch;
@@ -79,76 +69,10 @@ function createSearchClient(options = {}) {
79
69
  throw new Error(message);
80
70
  }
81
71
  return payload;
82
- },
83
- async streamSearch(request, onPhase) {
84
- const response = await fetchImpl(endpoint, {
85
- method: "POST",
86
- headers: {
87
- "content-type": "application/json"
88
- },
89
- body: JSON.stringify(request)
90
- });
91
- if (!response.ok) {
92
- let payload;
93
- try {
94
- payload = await response.json();
95
- } catch {
96
- throw new Error("Search failed");
97
- }
98
- const message = payload.error?.message ?? "Search failed";
99
- throw new Error(message);
100
- }
101
- const contentType = response.headers.get("content-type") ?? "";
102
- if (contentType.includes("application/json")) {
103
- const data = await response.json();
104
- onPhase({ phase: "initial", data });
105
- return data;
106
- }
107
- if (!response.body) {
108
- throw new Error("Response body is not readable");
109
- }
110
- const reader = response.body.getReader();
111
- const decoder = new TextDecoder();
112
- let buffer = "";
113
- let lastResponse = null;
114
- for (; ; ) {
115
- const { done, value } = await reader.read();
116
- if (done) break;
117
- buffer += decoder.decode(value, { stream: true });
118
- let newlineIdx;
119
- while ((newlineIdx = buffer.indexOf("\n")) !== -1) {
120
- const line = buffer.slice(0, newlineIdx).trim();
121
- buffer = buffer.slice(newlineIdx + 1);
122
- if (line.length === 0) continue;
123
- const event = JSON.parse(line);
124
- if (event.phase === "error") {
125
- const errData = event.data;
126
- throw new Error(errData.error.message ?? "Streaming search error");
127
- }
128
- const searchEvent = event;
129
- onPhase(searchEvent);
130
- lastResponse = searchEvent.data;
131
- }
132
- }
133
- const remaining = buffer.trim();
134
- if (remaining.length > 0) {
135
- const event = JSON.parse(remaining);
136
- if (event.phase === "error") {
137
- const errData = event.data;
138
- throw new Error(errData.error.message ?? "Streaming search error");
139
- }
140
- const searchEvent = event;
141
- onPhase(searchEvent);
142
- lastResponse = searchEvent.data;
143
- }
144
- if (!lastResponse) {
145
- throw new Error("No search results received");
146
- }
147
- return lastResponse;
148
72
  }
149
73
  };
150
74
  }
151
75
 
152
- export { createSearchClient, mergeSearchResults };
76
+ export { buildResultUrl, createSearchClient };
153
77
  //# sourceMappingURL=client.js.map
154
78
  //# sourceMappingURL=client.js.map