@openspecui/search 1.1.0 → 2.3.5

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/index.mjs CHANGED
@@ -1,5 +1,87 @@
1
- import { a as SearchHitSchema, c as SearchWorkerResponseSchema, d as normalizeText, f as searchIndex, i as SearchDocumentSchema, l as buildSearchIndex, n as buildWebWorkerSource, o as SearchQuerySchema, p as splitTerms, r as SearchDocumentKindSchema, s as SearchWorkerRequestSchema, u as createSnippet } from "./worker-source-BxMlTiAB.mjs";
1
+ import { a as SearchHitSchema, c as SearchWorkerResponseSchema, i as SearchDocumentSchema, n as buildWebWorkerSource, o as SearchQuerySchema, r as SearchDocumentKindSchema, s as SearchWorkerRequestSchema } from "./worker-source-CMPZlh9-.mjs";
2
2
 
3
+ //#region src/engine.ts
4
+ const DEFAULT_LIMIT = 50;
5
+ const MAX_LIMIT = 200;
6
+ const SNIPPET_SIZE = 180;
7
+ function normalizeText(input) {
8
+ return input.toLowerCase().replace(/\s+/g, " ").trim();
9
+ }
10
+ function splitTerms(query) {
11
+ return normalizeText(query).split(" ").map((term) => term.trim()).filter((term) => term.length > 0);
12
+ }
13
+ function toSearchIndexDocument(doc) {
14
+ return {
15
+ ...doc,
16
+ normalizedTitle: normalizeText(doc.title),
17
+ normalizedPath: normalizeText(doc.path),
18
+ normalizedContent: normalizeText(doc.content)
19
+ };
20
+ }
21
+ function buildSearchIndex(docs) {
22
+ return { documents: docs.map(toSearchIndexDocument) };
23
+ }
24
+ function resolveLimit(limit) {
25
+ if (typeof limit !== "number" || Number.isNaN(limit)) return DEFAULT_LIMIT;
26
+ return Math.min(MAX_LIMIT, Math.max(1, Math.trunc(limit)));
27
+ }
28
+ function isDocumentMatch(doc, terms) {
29
+ return terms.every((term) => doc.normalizedTitle.includes(term) || doc.normalizedPath.includes(term) || doc.normalizedContent.includes(term));
30
+ }
31
+ function scoreDocument(doc, terms) {
32
+ let score = 0;
33
+ for (const term of terms) {
34
+ if (doc.normalizedTitle.includes(term)) score += 30;
35
+ if (doc.normalizedPath.includes(term)) score += 20;
36
+ const contentIdx = doc.normalizedContent.indexOf(term);
37
+ if (contentIdx >= 0) {
38
+ score += 8;
39
+ if (contentIdx < 160) score += 4;
40
+ }
41
+ }
42
+ return score;
43
+ }
44
+ function createSnippet(content, terms) {
45
+ const source = content.trim();
46
+ if (!source) return "";
47
+ const normalizedSource = normalizeText(source);
48
+ let matchIndex = -1;
49
+ for (const term of terms) {
50
+ const idx = normalizedSource.indexOf(term);
51
+ if (idx >= 0 && (matchIndex < 0 || idx < matchIndex)) matchIndex = idx;
52
+ }
53
+ if (matchIndex < 0) return source.slice(0, SNIPPET_SIZE);
54
+ const start = Math.max(0, matchIndex - Math.floor(SNIPPET_SIZE / 3));
55
+ const end = Math.min(source.length, start + SNIPPET_SIZE);
56
+ const prefix = start > 0 ? "..." : "";
57
+ const suffix = end < source.length ? "..." : "";
58
+ return `${prefix}${source.slice(start, end)}${suffix}`;
59
+ }
60
+ function searchIndex(index, query) {
61
+ const terms = splitTerms(query.query);
62
+ if (terms.length === 0) return [];
63
+ const hits = [];
64
+ for (const doc of index.documents) {
65
+ if (!isDocumentMatch(doc, terms)) continue;
66
+ hits.push({
67
+ documentId: doc.id,
68
+ kind: doc.kind,
69
+ title: doc.title,
70
+ href: doc.href,
71
+ path: doc.path,
72
+ score: scoreDocument(doc, terms),
73
+ snippet: createSnippet(doc.content, terms),
74
+ updatedAt: doc.updatedAt
75
+ });
76
+ }
77
+ hits.sort((a, b) => {
78
+ if (b.score !== a.score) return b.score - a.score;
79
+ return b.updatedAt - a.updatedAt;
80
+ });
81
+ return hits.slice(0, resolveLimit(query.limit));
82
+ }
83
+
84
+ //#endregion
3
85
  //#region src/webworker-provider.ts
4
86
  function requestId() {
5
87
  return Math.random().toString(36).slice(2);
package/dist/node.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { a as SearchHitSchema, c as SearchWorkerResponseSchema, t as buildNodeWorkerSource } from "./worker-source-BxMlTiAB.mjs";
1
+ import { a as SearchHitSchema, c as SearchWorkerResponseSchema, t as buildNodeWorkerSource } from "./worker-source-CMPZlh9-.mjs";
2
2
  import { Worker } from "node:worker_threads";
3
3
 
4
4
  //#region src/node-worker-provider.ts
@@ -1,87 +1,5 @@
1
1
  import { z } from "zod";
2
2
 
3
- //#region src/engine.ts
4
- const DEFAULT_LIMIT = 50;
5
- const MAX_LIMIT = 200;
6
- const SNIPPET_SIZE = 180;
7
- function normalizeText(input) {
8
- return input.toLowerCase().replace(/\s+/g, " ").trim();
9
- }
10
- function splitTerms(query) {
11
- return normalizeText(query).split(" ").map((term) => term.trim()).filter((term) => term.length > 0);
12
- }
13
- function toSearchIndexDocument(doc) {
14
- return {
15
- ...doc,
16
- normalizedTitle: normalizeText(doc.title),
17
- normalizedPath: normalizeText(doc.path),
18
- normalizedContent: normalizeText(doc.content)
19
- };
20
- }
21
- function buildSearchIndex(docs) {
22
- return { documents: docs.map(toSearchIndexDocument) };
23
- }
24
- function resolveLimit(limit) {
25
- if (typeof limit !== "number" || Number.isNaN(limit)) return DEFAULT_LIMIT;
26
- return Math.min(MAX_LIMIT, Math.max(1, Math.trunc(limit)));
27
- }
28
- function isDocumentMatch(doc, terms) {
29
- return terms.every((term) => doc.normalizedTitle.includes(term) || doc.normalizedPath.includes(term) || doc.normalizedContent.includes(term));
30
- }
31
- function scoreDocument(doc, terms) {
32
- let score = 0;
33
- for (const term of terms) {
34
- if (doc.normalizedTitle.includes(term)) score += 30;
35
- if (doc.normalizedPath.includes(term)) score += 20;
36
- const contentIdx = doc.normalizedContent.indexOf(term);
37
- if (contentIdx >= 0) {
38
- score += 8;
39
- if (contentIdx < 160) score += 4;
40
- }
41
- }
42
- return score;
43
- }
44
- function createSnippet(content, terms) {
45
- const source = content.trim();
46
- if (!source) return "";
47
- const normalizedSource = normalizeText(source);
48
- let matchIndex = -1;
49
- for (const term of terms) {
50
- const idx = normalizedSource.indexOf(term);
51
- if (idx >= 0 && (matchIndex < 0 || idx < matchIndex)) matchIndex = idx;
52
- }
53
- if (matchIndex < 0) return source.slice(0, SNIPPET_SIZE);
54
- const start = Math.max(0, matchIndex - Math.floor(SNIPPET_SIZE / 3));
55
- const end = Math.min(source.length, start + SNIPPET_SIZE);
56
- const prefix = start > 0 ? "..." : "";
57
- const suffix = end < source.length ? "..." : "";
58
- return `${prefix}${source.slice(start, end)}${suffix}`;
59
- }
60
- function searchIndex(index, query) {
61
- const terms = splitTerms(query.query);
62
- if (terms.length === 0) return [];
63
- const hits = [];
64
- for (const doc of index.documents) {
65
- if (!isDocumentMatch(doc, terms)) continue;
66
- hits.push({
67
- documentId: doc.id,
68
- kind: doc.kind,
69
- title: doc.title,
70
- href: doc.href,
71
- path: doc.path,
72
- score: scoreDocument(doc, terms),
73
- snippet: createSnippet(doc.content, terms),
74
- updatedAt: doc.updatedAt
75
- });
76
- }
77
- hits.sort((a, b) => {
78
- if (b.score !== a.score) return b.score - a.score;
79
- return b.updatedAt - a.updatedAt;
80
- });
81
- return hits.slice(0, resolveLimit(query.limit));
82
- }
83
-
84
- //#endregion
85
3
  //#region src/protocol.ts
86
4
  const SearchDocumentKindSchema = z.enum([
87
5
  "spec",
@@ -155,15 +73,128 @@ const sharedRuntimeSource = String.raw`
155
73
  const DEFAULT_LIMIT = 50;
156
74
  const MAX_LIMIT = 200;
157
75
  const SNIPPET_SIZE = 180;
158
- const normalizeText = ${normalizeText.toString()};
159
- const splitTerms = ${splitTerms.toString()};
160
- const toSearchIndexDocument = ${toSearchIndexDocument.toString()};
161
- const buildSearchIndex = ${buildSearchIndex.toString()};
162
- const resolveLimit = ${resolveLimit.toString()};
163
- const isDocumentMatch = ${isDocumentMatch.toString()};
164
- const scoreDocument = ${scoreDocument.toString()};
165
- const createSnippet = ${createSnippet.toString()};
166
- const searchIndex = ${searchIndex.toString()};
76
+
77
+ function normalizeText(input) {
78
+ return String(input || '')
79
+ .toLowerCase()
80
+ .replace(/\s+/g, ' ')
81
+ .trim();
82
+ }
83
+
84
+ function splitTerms(query) {
85
+ return normalizeText(query)
86
+ .split(' ')
87
+ .map((term) => term.trim())
88
+ .filter((term) => term.length > 0);
89
+ }
90
+
91
+ function toSearchIndexDocument(doc) {
92
+ return {
93
+ id: doc.id,
94
+ kind: doc.kind,
95
+ title: doc.title,
96
+ href: doc.href,
97
+ path: doc.path,
98
+ content: doc.content,
99
+ updatedAt: doc.updatedAt,
100
+ normalizedTitle: normalizeText(doc.title),
101
+ normalizedPath: normalizeText(doc.path),
102
+ normalizedContent: normalizeText(doc.content),
103
+ };
104
+ }
105
+
106
+ function buildSearchIndex(docs) {
107
+ return {
108
+ documents: Array.isArray(docs) ? docs.map(toSearchIndexDocument) : [],
109
+ };
110
+ }
111
+
112
+ function resolveLimit(limit) {
113
+ if (typeof limit !== 'number' || Number.isNaN(limit)) return DEFAULT_LIMIT;
114
+ return Math.min(MAX_LIMIT, Math.max(1, Math.trunc(limit)));
115
+ }
116
+
117
+ function isDocumentMatch(doc, terms) {
118
+ return terms.every(
119
+ (term) =>
120
+ doc.normalizedTitle.includes(term) ||
121
+ doc.normalizedPath.includes(term) ||
122
+ doc.normalizedContent.includes(term)
123
+ );
124
+ }
125
+
126
+ function scoreDocument(doc, terms) {
127
+ let score = 0;
128
+
129
+ for (const term of terms) {
130
+ if (doc.normalizedTitle.includes(term)) score += 30;
131
+ if (doc.normalizedPath.includes(term)) score += 20;
132
+
133
+ const contentIdx = doc.normalizedContent.indexOf(term);
134
+ if (contentIdx >= 0) {
135
+ score += 8;
136
+ if (contentIdx < 160) score += 4;
137
+ }
138
+ }
139
+
140
+ return score;
141
+ }
142
+
143
+ function createSnippet(content, terms) {
144
+ const source = String(content || '').trim();
145
+ if (!source) return '';
146
+
147
+ const normalizedSource = normalizeText(source);
148
+ let matchIndex = -1;
149
+
150
+ for (const term of terms) {
151
+ const idx = normalizedSource.indexOf(term);
152
+ if (idx >= 0 && (matchIndex < 0 || idx < matchIndex)) {
153
+ matchIndex = idx;
154
+ }
155
+ }
156
+
157
+ if (matchIndex < 0) {
158
+ return source.slice(0, SNIPPET_SIZE);
159
+ }
160
+
161
+ const start = Math.max(0, matchIndex - Math.floor(SNIPPET_SIZE / 3));
162
+ const end = Math.min(source.length, start + SNIPPET_SIZE);
163
+
164
+ const prefix = start > 0 ? '...' : '';
165
+ const suffix = end < source.length ? '...' : '';
166
+ return prefix + source.slice(start, end) + suffix;
167
+ }
168
+
169
+ function searchIndex(index, query) {
170
+ const terms = splitTerms(query && query.query ? query.query : '');
171
+ if (terms.length === 0) return [];
172
+
173
+ const hits = [];
174
+
175
+ for (const doc of index.documents) {
176
+ if (!isDocumentMatch(doc, terms)) continue;
177
+
178
+ hits.push({
179
+ documentId: doc.id,
180
+ kind: doc.kind,
181
+ title: doc.title,
182
+ href: doc.href,
183
+ path: doc.path,
184
+ score: scoreDocument(doc, terms),
185
+ snippet: createSnippet(doc.content, terms),
186
+ updatedAt: doc.updatedAt,
187
+ });
188
+ }
189
+
190
+ hits.sort((a, b) => {
191
+ if (b.score !== a.score) return b.score - a.score;
192
+ return b.updatedAt - a.updatedAt;
193
+ });
194
+
195
+ return hits.slice(0, resolveLimit(query ? query.limit : undefined));
196
+ }
197
+
167
198
  let index = buildSearchIndex([]);
168
199
 
169
200
  function handleMessage(payload) {
@@ -202,4 +233,4 @@ function buildNodeWorkerSource() {
202
233
  }
203
234
 
204
235
  //#endregion
205
- export { SearchHitSchema as a, SearchWorkerResponseSchema as c, normalizeText as d, searchIndex as f, SearchDocumentSchema as i, buildSearchIndex as l, buildWebWorkerSource as n, SearchQuerySchema as o, splitTerms as p, SearchDocumentKindSchema as r, SearchWorkerRequestSchema as s, buildNodeWorkerSource as t, createSnippet as u };
236
+ export { SearchHitSchema as a, SearchWorkerResponseSchema as c, SearchDocumentSchema as i, buildWebWorkerSource as n, SearchQuerySchema as o, SearchDocumentKindSchema as r, SearchWorkerRequestSchema as s, buildNodeWorkerSource as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openspecui/search",
3
- "version": "1.1.0",
3
+ "version": "2.3.5",
4
4
  "description": "Shared search engine and worker providers for OpenSpecUI",
5
5
  "type": "module",
6
6
  "main": "./dist/index.mjs",
@@ -18,22 +18,26 @@
18
18
  "files": [
19
19
  "dist"
20
20
  ],
21
+ "scripts": {
22
+ "build": "tsdown src/index.ts src/node.ts --format esm --dts",
23
+ "dev": "tsdown src/index.ts src/node.ts --format esm --dts --watch",
24
+ "typecheck": "tsc --noEmit",
25
+ "test": "vitest run",
26
+ "test:watch": "vitest"
27
+ },
21
28
  "dependencies": {
22
29
  "zod": "^3.24.1"
23
30
  },
24
31
  "devDependencies": {
25
32
  "tsdown": "^0.16.6",
26
33
  "typescript": "^5.7.2",
27
- "vitest": "^2.1.8"
34
+ "vitest": "^4.1.0"
28
35
  },
29
36
  "peerDependencies": {
30
37
  "typescript": "^5.0.0"
31
38
  },
32
- "scripts": {
33
- "build": "tsdown src/index.ts src/node.ts --format esm --dts",
34
- "dev": "tsdown src/index.ts src/node.ts --format esm --dts --watch",
35
- "typecheck": "tsc --noEmit",
36
- "test": "vitest run",
37
- "test:watch": "vitest"
39
+ "repository": {
40
+ "type": "git",
41
+ "url": "https://github.com/jixoai/openspecui"
38
42
  }
39
- }
43
+ }