@dogsbay/search 0.2.0-beta.119 → 0.2.0-beta.121

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.js CHANGED
@@ -253,7 +253,17 @@ function htmlText(html) {
253
253
  }
254
254
  function propertyText(list, depth = 0) {
255
255
  if (depth > 10) return [];
256
- return objects(list).flatMap((p) => [str(p.name), str(p.description), ...propertyText(p.properties, depth + 1), ...propertyText(p.items, depth + 1)]);
256
+ return objects(list).flatMap((p) => [str(p.name), specText(p.description), ...propertyText(p.properties, depth + 1), ...propertyText(p.items, depth + 1)]);
257
+ }
258
+ const SPEC_MARKUP = /* @__PURE__ */ new Set(
259
+ [...INLINE_TAGS, ..."br p div ul ol li dl dt dd table thead tbody tfoot tr td th caption h1 h2 h3 h4 h5 h6 blockquote hr img pre section".split(" ")]
260
+ );
261
+ function specText(v) {
262
+ const s = str(v);
263
+ if (!s.includes("<") && !s.includes("&")) return s;
264
+ return s.split(/(`+[^`]*`+)/).map(
265
+ (part, i) => i % 2 ? part : decode(part.replace(/<\/?([a-z][a-z0-9]*)\b[^<>]*>/gi, (m, name) => SPEC_MARKUP.has(name.toLowerCase()) ? INLINE_TAGS.has(name.toLowerCase()) ? "" : " " : m))
266
+ ).join("").replace(/\s+/g, " ").trim();
257
267
  }
258
268
  function extractText(tree) {
259
269
  const headings = [];
@@ -279,11 +289,11 @@ function extractText(tree) {
279
289
  case "endpoint":
280
290
  body.push(
281
291
  `${str(ap.method).toUpperCase()} ${str(ap.path)}`,
282
- str(ap.summary),
283
- str(ap.description),
292
+ specText(ap.summary),
293
+ specText(ap.description),
284
294
  ...propertyText(ap.parameters),
285
295
  ...propertyText(ap.requestBody),
286
- ...objects(ap.responses).flatMap((r) => [str(r.description), ...propertyText(r.schema)])
296
+ ...objects(ap.responses).flatMap((r) => [specText(r.description), ...propertyText(r.schema)])
287
297
  );
288
298
  continue;
289
299
  case "api-class":
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * The indexer: documents in, index files out (see format.ts). Pure and\n * Worker-safe: no Node built-ins, and memory for one page at a time plus\n * the postings.\n */\nimport type { InlineNode, TreeNode } from \"@dogsbay/types\";\nimport { detach } from \"./detach.js\";\nimport { BLOCK, FORMAT, file, key, wrap, wrapJson, type Doc, type Meta } from \"./format.js\";\nimport { shardKey, tokenize } from \"./text.js\";\n\nexport interface SearchDoc {\n title: string;\n url: string;\n section: string;\n excerpt: string;\n /** h2/h3 headings with their anchors, for results within the page. */\n anchors?: [text: string, id: string][];\n /** Tags, for filters (`tag:x`). */\n tags?: string[];\n}\n\ninterface StoredDoc {\n title: string;\n url: string;\n section: string;\n tags: string[];\n /** The document's Fragment, already serialised: one flat string per page instead of an object graph. */\n fragment: string;\n}\n\n/**\n * Ranking: each field is scored on its own and the fields are combined\n * afterwards, BM25-style: term frequency saturates (k1), and a field's\n * frequency is divided by its length relative to the corpus average (b),\n * so a long page that mentions a word fifty times does not outrank a page\n * with the word in its title. The inverse document frequency is applied at\n * query time from the length of the term's posting list.\n */\nexport const RANK = {\n k1: 1.2,\n fields: {\n title: { weight: 6, b: 0.3 },\n keywords: { weight: 4, b: 0 },\n headings: { weight: 2, b: 0.5 },\n body: { weight: 1, b: 0.75 },\n },\n};\n\n// Per-field term counts share one number per posting while the index is\n// built (the same memory as a single score): body in the low 12 bits, then\n// headings (4 bits), keywords (2) and title (2), each capped.\nconst B_HEAD = 4096;\nconst B_KW = 65536;\nconst B_TITLE = 262144;\n\n/** Estimated characters of a shard file above which it is split (about 12 KB gzipped). */\nconst SHARD_CHARS = 40_000;\nconst MAX_DEPTH = 3;\n\n/** Characters of a term a shard key stands for (\"_65e5_\" is one). */\nfunction keyDepth(k: string): number {\n return k.replace(/_[0-9a-f]+_/g, \"x\").length;\n}\n\nexport class SearchIndexBuilder {\n constructor(private opts: { lang?: string } = {}) {}\n private docs: StoredDoc[] = [];\n /** Token counts per document and field (title, headings, body), for length normalisation. */\n private lengths: number[] = [];\n /**\n * term → flat [doc, counts, doc, counts, …]. Documents arrive in order, so\n * each page's counts are summed locally and appended once. Flat arrays\n * cost a fraction of a Map per term, which matters inside a Worker.\n */\n private postings = new Map<string, number[]>();\n\n add(doc: SearchDoc, parts: { title: string; headings: string[]; body: string; keywords?: string[] }): void {\n const id = this.docs.length;\n // Kept until the index is written: copies, so a title or excerpt does not pin the page's text\n // (JSON.stringify builds a new flat string).\n this.docs.push({\n title: detach(doc.title),\n url: detach(doc.url),\n section: detach(doc.section),\n tags: (doc.tags ?? []).map(detach),\n fragment: JSON.stringify([doc.excerpt, doc.anchors ?? []]).replace(/<\\//g, \"<\\\\/\"),\n });\n // Counts go straight into the packed number (no per-term arrays: this runs\n // for every term of every page, inside a 128 MB Worker).\n const counts = new Map<string, number>();\n const len = [0, 0, 0];\n const count = (text: string, unit: number, cap: number, lenIndex: number) => {\n const terms = tokenize(text);\n if (lenIndex >= 0) len[lenIndex] += terms.length;\n for (const t of terms) {\n const v = counts.get(t) ?? 0;\n if (Math.floor(v / unit) % (cap + 1) < cap) counts.set(t, v + unit);\n else if (!counts.has(t)) counts.set(t, v);\n }\n };\n count(parts.title, B_TITLE, 3, 0);\n for (const k of parts.keywords ?? []) count(k, B_KW, 3, -1);\n for (const h of parts.headings) count(h, B_HEAD, 15, 1);\n count(parts.body, 1, 4095, 2);\n this.lengths.push(len[0], len[1], len[2]);\n for (const [t, v] of counts) {\n const list = this.postings.get(t);\n if (list) list.push(id, v);\n // Copy new terms into their own string: a regex match is a slice that\n // would otherwise keep the page's whole text alive for the entire build.\n else this.postings.set(detach(t), [id, v]);\n }\n }\n\n /** The stored score of one posting: saturated, length-normalised field scores, weighted and summed. */\n private scorer(): (doc: number, v: number) => number {\n const n = this.docs.length || 1;\n const avg = [0, 0, 0];\n for (let i = 0; i < this.lengths.length; i++) avg[i % 3] += this.lengths[i];\n for (let f = 0; f < 3; f++) avg[f] = avg[f] / n || 1;\n const { k1, fields: F } = RANK;\n const sat = (tf: number, len: number, avgLen: number, b: number) => {\n if (!tf) return 0;\n const x = tf / (1 - b + (b * len) / avgLen);\n return (x * (k1 + 1)) / (k1 + x); // 1 for a single occurrence in an average-length field\n };\n return (doc, v) => {\n const body = v % B_HEAD;\n const head = Math.floor(v / B_HEAD) % 16;\n const kw = Math.floor(v / B_KW) % 4;\n const title = Math.floor(v / B_TITLE);\n const L = this.lengths;\n const s =\n F.title.weight * sat(title, L[doc * 3], avg[0], F.title.b) +\n F.keywords.weight * sat(kw, 1, 1, F.keywords.b) +\n F.headings.weight * sat(head, L[doc * 3 + 1], avg[1], F.headings.b) +\n F.body.weight * sat(body, L[doc * 3 + 2], avg[2], F.body.b);\n return Math.max(1, Math.round(10 * s));\n };\n }\n\n get size(): number {\n return this.docs.length;\n }\n\n /**\n * Terms grouped into shards: by first character, and a group larger than\n * SHARD_CHARS split on the next character (up to MAX_DEPTH), so a query\n * loads small files on large sites. Terms no longer than a group's key stay\n * in the group's own file.\n */\n private shardTerms(): Map<string, string[]> {\n const out = new Map<string, string[]>();\n const size = (terms: string[]) => terms.reduce((n, t) => n + t.length + 4 + this.postings.get(t)!.length * 4, 0);\n const place = (terms: string[], depth: number) => {\n const groups = new Map<string, string[]>();\n for (const t of terms) {\n const k = shardKey(t, depth);\n const g = groups.get(k);\n if (g) g.push(t);\n else groups.set(k, [t]);\n }\n for (const [k, g] of groups) {\n if (depth < MAX_DEPTH && size(g) > SHARD_CHARS) {\n const short = g.filter((t) => t.length <= depth);\n if (short.length) out.set(k, short);\n place(g.filter((t) => t.length > depth), depth + 1);\n } else out.set(k, g);\n }\n };\n place([...this.postings.keys()], 1);\n return out;\n }\n\n /** Emit the index files, keyed by path relative to the unit's `_dogsbay/search/` dir. */\n emit(): Map<string, string> {\n return new Map(this.emitFiles());\n }\n\n /**\n * The index files one at a time: the document table, the fragments, then\n * one shard per term prefix. Each shard is built, serialised and handed\n * over before the next, and its postings are dropped, so writing the index\n * needs memory for one shard rather than for every shard and its JSON.\n */\n *emitFiles(): Generator<[string, string]> {\n const byShard = this.shardTerms();\n const depth = Math.max(1, ...[...byShard.keys()].map((k) => keyDepth(k)));\n const score = this.scorer();\n const sections: string[] = [];\n const sectionIndex = new Map<string, number>();\n const tags: string[] = [];\n const tagIndex = new Map<string, number>();\n const docs: Doc[] = this.docs.map((d) => {\n let i = sectionIndex.get(d.section);\n if (i === undefined) sectionIndex.set(d.section, (i = sections.push(d.section) - 1));\n if (!d.tags.length) return [d.title, d.url, i];\n const t = d.tags.map((tag) => {\n let j = tagIndex.get(tag);\n if (j === undefined) tagIndex.set(tag, (j = tags.push(tag) - 1));\n return j;\n });\n return [d.title, d.url, i, t];\n });\n const meta: Meta = { format: FORMAT, depth, count: this.docs.length, shards: [...byShard.keys()].sort(), sections, tags, block: BLOCK, lang: this.opts.lang, docs };\n yield [file.docs, wrap(key.docs, meta)];\n for (let b = 0; b * BLOCK < this.docs.length; b++) {\n const part = this.docs.slice(b * BLOCK, (b + 1) * BLOCK);\n yield [file.fragment(b), wrapJson(key.fragment(b), `[${part.map((d) => d.fragment).join(\",\")}]`)];\n }\n for (const [k, terms] of byShard) {\n const data: Record<string, number[]> = {};\n for (const term of terms) {\n const flat = this.postings.get(term)!;\n // Highest score first, as small integers (ten times the field-weighted score).\n const pairs: [number, number][] = [];\n for (let i = 0; i < flat.length; i += 2) pairs.push([flat[i], score(flat[i], flat[i + 1])]);\n pairs.sort((a, b) => b[1] - a[1]);\n const out: number[] = [];\n for (const [d, sc] of pairs) out.push(d, sc);\n data[term] = out;\n this.postings.delete(term);\n }\n yield [file.shard(k), wrap(key.shard(k), data)];\n }\n }\n}\n\ntype Json = Record<string, unknown>;\nconst str = (v: unknown): string => (typeof v === \"string\" ? v : \"\");\nconst objects = (v: unknown): Json[] => (Array.isArray(v) ? v.filter((x): x is Json => !!x && typeof x === \"object\") : []);\n\nconst INLINE_TAGS = new Set(\"a abbr b bdi bdo cite code data del dfn em i ins kbd mark q s samp small span strong sub sup time u var wbr\".split(\" \"));\nconst ENTITY: Record<string, string> = { lt: \"<\", gt: \">\", quot: '\"', \"#39\": \"'\", \"#x27\": \"'\", apos: \"'\", nbsp: \" \", amp: \"&\" };\nconst decode = (s: string): string => s.replace(/&(lt|gt|quot|#39|#x27|apos|nbsp|amp);/g, (_, e: string) => ENTITY[e]);\nconst squash = (s: string): string => decode(s).replace(/\\s+/g, \" \").trim();\n\n/** Longest start tag read as a tag; past it a `<` is text (keeps the scan linear on unclosed quotes). */\nconst MAX_TAG = 4096;\n/** Elements whose content is not text to index: their content is skipped to the closing tag. */\nconst RAW_TEXT = new Set([\"script\", \"style\", \"textarea\"]);\n\n/**\n * The end of the start or end tag at `lt` (the index after its `>`), or -1\n * when the `<` does not open one: a tag name must follow `<` or `</`\n * directly (`1 < 2` and `< pre>` are text, as in a browser), a quoted\n * attribute value may hold `>` or `<`, and an unquoted `<` ends the attempt.\n */\nfunction tagEnd(html: string, lt: number): number {\n const first = html.charCodeAt(lt + 1) === 47 /* / */ ? lt + 2 : lt + 1;\n if (!/[a-z]/i.test(html[first] ?? \"\")) return -1;\n let quote = \"\";\n let prev = \"\";\n const stop = Math.min(html.length, lt + MAX_TAG);\n for (let k = first; k < stop; k++) {\n const c = html[k];\n if (quote) {\n if (c === quote) quote = \"\";\n continue;\n }\n if ((c === '\"' || c === \"'\") && prev === \"=\") quote = c;\n else if (c === \">\") return k + 1;\n else if (c === \"<\") return -1;\n if (c !== \" \" && c !== \"\\t\" && c !== \"\\n\" && c !== \"\\r\") prev = c;\n }\n return -1;\n}\n\n/**\n * HTML as text in one linear pass (regexes over raw HTML were quadratic on\n * unclosed tags: 100 KB of `<` took ten seconds). Inline tags join their\n * text (`un<em>believ</em>able`), other tags separate it, `<pre>` is code\n * and skipped, comments and script/style content are dropped, and heading\n * text is returned apart. A `<` that does not open a tag is text.\n */\nfunction htmlParts(html: string): { text: string; headings: string[] } {\n let text = \"\";\n const headings: string[] = [];\n let heading: string | undefined;\n let pre = 0;\n let lower: string | undefined;\n const emit = (t: string) => {\n if (pre) return;\n if (heading !== undefined) heading += t;\n else text += t;\n };\n const endHeading = () => {\n if (heading !== undefined) headings.push(squash(heading));\n heading = undefined;\n };\n let i = 0;\n while (i < html.length) {\n const lt = html.indexOf(\"<\", i);\n emit(html.slice(i, lt < 0 ? html.length : lt));\n if (lt < 0) break;\n if (html.startsWith(\"<!--\", lt)) {\n const end = html.indexOf(\"-->\", lt + 4);\n i = end < 0 ? html.length : end + 3;\n emit(\" \");\n continue;\n }\n const end = tagEnd(html, lt);\n if (end < 0) {\n emit(\"<\");\n i = lt + 1;\n continue;\n }\n i = end;\n const tag = /^<(\\/?)([a-z][a-z0-9-]*)/i.exec(html.slice(lt, Math.min(end, lt + 64)))!;\n const closing = tag[1] === \"/\";\n const name = tag[2].toLowerCase();\n if (!closing && RAW_TEXT.has(name)) {\n lower ??= html.toLowerCase();\n const close = lower.indexOf(`</${name}`, i);\n i = close < 0 ? html.length : close;\n emit(\" \");\n } else if (name === \"pre\") {\n pre = Math.max(0, pre + (closing ? -1 : 1));\n text += \" \";\n } else if (/^h[1-6]$/.test(name)) {\n // An unclosed heading ends where the next one starts.\n endHeading();\n if (!closing) heading = \"\";\n text += \" \";\n } else if (!INLINE_TAGS.has(name)) emit(\" \");\n }\n endHeading();\n return { text: squash(text), headings: headings.filter(Boolean) };\n}\n\n/** HTML as body text, its headings included. */\nfunction htmlText(html: string): string {\n const { text, headings } = htmlParts(html);\n return [text, ...headings].join(\" \");\n}\n\n/** Parameter or schema property names and descriptions, nested `properties` / `items` included. */\nfunction propertyText(list: unknown, depth = 0): string[] {\n if (depth > 10) return [];\n return objects(list).flatMap((p) => [str(p.name), str(p.description), ...propertyText(p.properties, depth + 1), ...propertyText(p.items, depth + 1)]);\n}\n\n/**\n * Text for indexing: headings separated from body, code blocks skipped.\n *\n * API reference nodes keep their text in props, so they are read here:\n * an OpenAPI operation (`endpoint`) by its method and path, summary,\n * description, parameters, request body and responses; autodoc symbols by\n * their names (as headings, as the page shows them) and docstrings. Their\n * code (signatures, source, samples) is skipped like code blocks. An\n * embedded spec's descriptions (`prose` with HTML) count as body, and their\n * headings as headings.\n */\nexport function extractText(tree: TreeNode[]): { headings: string[]; body: string } {\n const headings: string[] = [];\n const body: string[] = [];\n const inl = (a?: InlineNode[]): string => {\n if (!a) return \"\";\n let s = \"\";\n for (const n of a) {\n if (n.type === \"text\" || n.type === \"code\") s += n.text;\n else if (n.type === \"link\" || n.type === \"highlight\") s += inl(n.children);\n else if (n.type === \"kbd\") s += n.keys.join(\" \");\n else if (n.type === \"badge\") s += n.text;\n else if (n.type === \"image\") s += n.alt ?? \"\";\n else if (n.type === \"html-inline\") s += \" \";\n }\n return s;\n };\n const walk = (nodes?: TreeNode[]) => {\n for (const n of nodes ?? []) {\n if (n.type === \"code\" || n.type === \"api-source\") continue;\n const ap = (n.props ?? {}) as Json;\n switch (n.type) {\n case \"endpoint\":\n body.push(\n `${str(ap.method).toUpperCase()} ${str(ap.path)}`,\n str(ap.summary),\n str(ap.description),\n ...propertyText(ap.parameters),\n ...propertyText(ap.requestBody),\n ...objects(ap.responses).flatMap((r) => [str(r.description), ...propertyText(r.schema)]),\n );\n continue;\n case \"api-class\":\n case \"api-member\":\n headings.push(str(ap.name));\n walk(n.children);\n continue;\n case \"api-doc\":\n body.push(htmlText(str(ap.html)));\n walk(n.children);\n continue;\n case \"api-params\":\n for (const p of objects(ap.params)) body.push(str(p.name), htmlText(str(p.docHtml)) || str(p.doc) || str(p.description));\n continue;\n case \"prose\":\n if (!n.inline && typeof n.html === \"string\") {\n const parts = htmlParts(n.html);\n headings.push(...parts.headings);\n body.push(parts.text);\n continue;\n }\n break;\n }\n if (n.type === \"heading\") {\n headings.push(inl(n.inline));\n continue;\n }\n if (n.type === \"html\" && n.html) body.push(htmlText(n.html));\n if (n.inline) body.push(inl(n.inline));\n const p = n.props as Record<string, unknown> | undefined;\n for (const k of [\"title\", \"label\", \"caption\", \"description\"]) if (typeof p?.[k] === \"string\") body.push(p[k] as string);\n walk(n.children);\n }\n };\n walk(tree);\n return { headings, body: body.join(\" \").replace(/\\s+/g, \" \").trim() };\n}\n"],"mappings":"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","names":["end","p"]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * The indexer: documents in, index files out (see format.ts). Pure and\n * Worker-safe: no Node built-ins, and memory for one page at a time plus\n * the postings.\n */\nimport type { InlineNode, TreeNode } from \"@dogsbay/types\";\nimport { detach } from \"./detach.js\";\nimport { BLOCK, FORMAT, file, key, wrap, wrapJson, type Doc, type Meta } from \"./format.js\";\nimport { shardKey, tokenize } from \"./text.js\";\n\nexport interface SearchDoc {\n title: string;\n url: string;\n section: string;\n excerpt: string;\n /** h2/h3 headings with their anchors, for results within the page. */\n anchors?: [text: string, id: string][];\n /** Tags, for filters (`tag:x`). */\n tags?: string[];\n}\n\ninterface StoredDoc {\n title: string;\n url: string;\n section: string;\n tags: string[];\n /** The document's Fragment, already serialised: one flat string per page instead of an object graph. */\n fragment: string;\n}\n\n/**\n * Ranking: each field is scored on its own and the fields are combined\n * afterwards, BM25-style: term frequency saturates (k1), and a field's\n * frequency is divided by its length relative to the corpus average (b),\n * so a long page that mentions a word fifty times does not outrank a page\n * with the word in its title. The inverse document frequency is applied at\n * query time from the length of the term's posting list.\n */\nexport const RANK = {\n k1: 1.2,\n fields: {\n title: { weight: 6, b: 0.3 },\n keywords: { weight: 4, b: 0 },\n headings: { weight: 2, b: 0.5 },\n body: { weight: 1, b: 0.75 },\n },\n};\n\n// Per-field term counts share one number per posting while the index is\n// built (the same memory as a single score): body in the low 12 bits, then\n// headings (4 bits), keywords (2) and title (2), each capped.\nconst B_HEAD = 4096;\nconst B_KW = 65536;\nconst B_TITLE = 262144;\n\n/** Estimated characters of a shard file above which it is split (about 12 KB gzipped). */\nconst SHARD_CHARS = 40_000;\nconst MAX_DEPTH = 3;\n\n/** Characters of a term a shard key stands for (\"_65e5_\" is one). */\nfunction keyDepth(k: string): number {\n return k.replace(/_[0-9a-f]+_/g, \"x\").length;\n}\n\nexport class SearchIndexBuilder {\n constructor(private opts: { lang?: string } = {}) {}\n private docs: StoredDoc[] = [];\n /** Token counts per document and field (title, headings, body), for length normalisation. */\n private lengths: number[] = [];\n /**\n * term → flat [doc, counts, doc, counts, …]. Documents arrive in order, so\n * each page's counts are summed locally and appended once. Flat arrays\n * cost a fraction of a Map per term, which matters inside a Worker.\n */\n private postings = new Map<string, number[]>();\n\n add(doc: SearchDoc, parts: { title: string; headings: string[]; body: string; keywords?: string[] }): void {\n const id = this.docs.length;\n // Kept until the index is written: copies, so a title or excerpt does not pin the page's text\n // (JSON.stringify builds a new flat string).\n this.docs.push({\n title: detach(doc.title),\n url: detach(doc.url),\n section: detach(doc.section),\n tags: (doc.tags ?? []).map(detach),\n fragment: JSON.stringify([doc.excerpt, doc.anchors ?? []]).replace(/<\\//g, \"<\\\\/\"),\n });\n // Counts go straight into the packed number (no per-term arrays: this runs\n // for every term of every page, inside a 128 MB Worker).\n const counts = new Map<string, number>();\n const len = [0, 0, 0];\n const count = (text: string, unit: number, cap: number, lenIndex: number) => {\n const terms = tokenize(text);\n if (lenIndex >= 0) len[lenIndex] += terms.length;\n for (const t of terms) {\n const v = counts.get(t) ?? 0;\n if (Math.floor(v / unit) % (cap + 1) < cap) counts.set(t, v + unit);\n else if (!counts.has(t)) counts.set(t, v);\n }\n };\n count(parts.title, B_TITLE, 3, 0);\n for (const k of parts.keywords ?? []) count(k, B_KW, 3, -1);\n for (const h of parts.headings) count(h, B_HEAD, 15, 1);\n count(parts.body, 1, 4095, 2);\n this.lengths.push(len[0], len[1], len[2]);\n for (const [t, v] of counts) {\n const list = this.postings.get(t);\n if (list) list.push(id, v);\n // Copy new terms into their own string: a regex match is a slice that\n // would otherwise keep the page's whole text alive for the entire build.\n else this.postings.set(detach(t), [id, v]);\n }\n }\n\n /** The stored score of one posting: saturated, length-normalised field scores, weighted and summed. */\n private scorer(): (doc: number, v: number) => number {\n const n = this.docs.length || 1;\n const avg = [0, 0, 0];\n for (let i = 0; i < this.lengths.length; i++) avg[i % 3] += this.lengths[i];\n for (let f = 0; f < 3; f++) avg[f] = avg[f] / n || 1;\n const { k1, fields: F } = RANK;\n const sat = (tf: number, len: number, avgLen: number, b: number) => {\n if (!tf) return 0;\n const x = tf / (1 - b + (b * len) / avgLen);\n return (x * (k1 + 1)) / (k1 + x); // 1 for a single occurrence in an average-length field\n };\n return (doc, v) => {\n const body = v % B_HEAD;\n const head = Math.floor(v / B_HEAD) % 16;\n const kw = Math.floor(v / B_KW) % 4;\n const title = Math.floor(v / B_TITLE);\n const L = this.lengths;\n const s =\n F.title.weight * sat(title, L[doc * 3], avg[0], F.title.b) +\n F.keywords.weight * sat(kw, 1, 1, F.keywords.b) +\n F.headings.weight * sat(head, L[doc * 3 + 1], avg[1], F.headings.b) +\n F.body.weight * sat(body, L[doc * 3 + 2], avg[2], F.body.b);\n return Math.max(1, Math.round(10 * s));\n };\n }\n\n get size(): number {\n return this.docs.length;\n }\n\n /**\n * Terms grouped into shards: by first character, and a group larger than\n * SHARD_CHARS split on the next character (up to MAX_DEPTH), so a query\n * loads small files on large sites. Terms no longer than a group's key stay\n * in the group's own file.\n */\n private shardTerms(): Map<string, string[]> {\n const out = new Map<string, string[]>();\n const size = (terms: string[]) => terms.reduce((n, t) => n + t.length + 4 + this.postings.get(t)!.length * 4, 0);\n const place = (terms: string[], depth: number) => {\n const groups = new Map<string, string[]>();\n for (const t of terms) {\n const k = shardKey(t, depth);\n const g = groups.get(k);\n if (g) g.push(t);\n else groups.set(k, [t]);\n }\n for (const [k, g] of groups) {\n if (depth < MAX_DEPTH && size(g) > SHARD_CHARS) {\n const short = g.filter((t) => t.length <= depth);\n if (short.length) out.set(k, short);\n place(g.filter((t) => t.length > depth), depth + 1);\n } else out.set(k, g);\n }\n };\n place([...this.postings.keys()], 1);\n return out;\n }\n\n /** Emit the index files, keyed by path relative to the unit's `_dogsbay/search/` dir. */\n emit(): Map<string, string> {\n return new Map(this.emitFiles());\n }\n\n /**\n * The index files one at a time: the document table, the fragments, then\n * one shard per term prefix. Each shard is built, serialised and handed\n * over before the next, and its postings are dropped, so writing the index\n * needs memory for one shard rather than for every shard and its JSON.\n */\n *emitFiles(): Generator<[string, string]> {\n const byShard = this.shardTerms();\n const depth = Math.max(1, ...[...byShard.keys()].map((k) => keyDepth(k)));\n const score = this.scorer();\n const sections: string[] = [];\n const sectionIndex = new Map<string, number>();\n const tags: string[] = [];\n const tagIndex = new Map<string, number>();\n const docs: Doc[] = this.docs.map((d) => {\n let i = sectionIndex.get(d.section);\n if (i === undefined) sectionIndex.set(d.section, (i = sections.push(d.section) - 1));\n if (!d.tags.length) return [d.title, d.url, i];\n const t = d.tags.map((tag) => {\n let j = tagIndex.get(tag);\n if (j === undefined) tagIndex.set(tag, (j = tags.push(tag) - 1));\n return j;\n });\n return [d.title, d.url, i, t];\n });\n const meta: Meta = { format: FORMAT, depth, count: this.docs.length, shards: [...byShard.keys()].sort(), sections, tags, block: BLOCK, lang: this.opts.lang, docs };\n yield [file.docs, wrap(key.docs, meta)];\n for (let b = 0; b * BLOCK < this.docs.length; b++) {\n const part = this.docs.slice(b * BLOCK, (b + 1) * BLOCK);\n yield [file.fragment(b), wrapJson(key.fragment(b), `[${part.map((d) => d.fragment).join(\",\")}]`)];\n }\n for (const [k, terms] of byShard) {\n const data: Record<string, number[]> = {};\n for (const term of terms) {\n const flat = this.postings.get(term)!;\n // Highest score first, as small integers (ten times the field-weighted score).\n const pairs: [number, number][] = [];\n for (let i = 0; i < flat.length; i += 2) pairs.push([flat[i], score(flat[i], flat[i + 1])]);\n pairs.sort((a, b) => b[1] - a[1]);\n const out: number[] = [];\n for (const [d, sc] of pairs) out.push(d, sc);\n data[term] = out;\n this.postings.delete(term);\n }\n yield [file.shard(k), wrap(key.shard(k), data)];\n }\n }\n}\n\ntype Json = Record<string, unknown>;\nconst str = (v: unknown): string => (typeof v === \"string\" ? v : \"\");\nconst objects = (v: unknown): Json[] => (Array.isArray(v) ? v.filter((x): x is Json => !!x && typeof x === \"object\") : []);\n\nconst INLINE_TAGS = new Set(\"a abbr b bdi bdo cite code data del dfn em i ins kbd mark q s samp small span strong sub sup time u var wbr\".split(\" \"));\nconst ENTITY: Record<string, string> = { lt: \"<\", gt: \">\", quot: '\"', \"#39\": \"'\", \"#x27\": \"'\", apos: \"'\", nbsp: \" \", amp: \"&\" };\nconst decode = (s: string): string => s.replace(/&(lt|gt|quot|#39|#x27|apos|nbsp|amp);/g, (_, e: string) => ENTITY[e]);\nconst squash = (s: string): string => decode(s).replace(/\\s+/g, \" \").trim();\n\n/** Longest start tag read as a tag; past it a `<` is text (keeps the scan linear on unclosed quotes). */\nconst MAX_TAG = 4096;\n/** Elements whose content is not text to index: their content is skipped to the closing tag. */\nconst RAW_TEXT = new Set([\"script\", \"style\", \"textarea\"]);\n\n/**\n * The end of the start or end tag at `lt` (the index after its `>`), or -1\n * when the `<` does not open one: a tag name must follow `<` or `</`\n * directly (`1 < 2` and `< pre>` are text, as in a browser), a quoted\n * attribute value may hold `>` or `<`, and an unquoted `<` ends the attempt.\n */\nfunction tagEnd(html: string, lt: number): number {\n const first = html.charCodeAt(lt + 1) === 47 /* / */ ? lt + 2 : lt + 1;\n if (!/[a-z]/i.test(html[first] ?? \"\")) return -1;\n let quote = \"\";\n let prev = \"\";\n const stop = Math.min(html.length, lt + MAX_TAG);\n for (let k = first; k < stop; k++) {\n const c = html[k];\n if (quote) {\n if (c === quote) quote = \"\";\n continue;\n }\n if ((c === '\"' || c === \"'\") && prev === \"=\") quote = c;\n else if (c === \">\") return k + 1;\n else if (c === \"<\") return -1;\n if (c !== \" \" && c !== \"\\t\" && c !== \"\\n\" && c !== \"\\r\") prev = c;\n }\n return -1;\n}\n\n/**\n * HTML as text in one linear pass (regexes over raw HTML were quadratic on\n * unclosed tags: 100 KB of `<` took ten seconds). Inline tags join their\n * text (`un<em>believ</em>able`), other tags separate it, `<pre>` is code\n * and skipped, comments and script/style content are dropped, and heading\n * text is returned apart. A `<` that does not open a tag is text.\n */\nfunction htmlParts(html: string): { text: string; headings: string[] } {\n let text = \"\";\n const headings: string[] = [];\n let heading: string | undefined;\n let pre = 0;\n let lower: string | undefined;\n const emit = (t: string) => {\n if (pre) return;\n if (heading !== undefined) heading += t;\n else text += t;\n };\n const endHeading = () => {\n if (heading !== undefined) headings.push(squash(heading));\n heading = undefined;\n };\n let i = 0;\n while (i < html.length) {\n const lt = html.indexOf(\"<\", i);\n emit(html.slice(i, lt < 0 ? html.length : lt));\n if (lt < 0) break;\n if (html.startsWith(\"<!--\", lt)) {\n const end = html.indexOf(\"-->\", lt + 4);\n i = end < 0 ? html.length : end + 3;\n emit(\" \");\n continue;\n }\n const end = tagEnd(html, lt);\n if (end < 0) {\n emit(\"<\");\n i = lt + 1;\n continue;\n }\n i = end;\n const tag = /^<(\\/?)([a-z][a-z0-9-]*)/i.exec(html.slice(lt, Math.min(end, lt + 64)))!;\n const closing = tag[1] === \"/\";\n const name = tag[2].toLowerCase();\n if (!closing && RAW_TEXT.has(name)) {\n lower ??= html.toLowerCase();\n const close = lower.indexOf(`</${name}`, i);\n i = close < 0 ? html.length : close;\n emit(\" \");\n } else if (name === \"pre\") {\n pre = Math.max(0, pre + (closing ? -1 : 1));\n text += \" \";\n } else if (/^h[1-6]$/.test(name)) {\n // An unclosed heading ends where the next one starts.\n endHeading();\n if (!closing) heading = \"\";\n text += \" \";\n } else if (!INLINE_TAGS.has(name)) emit(\" \");\n }\n endHeading();\n return { text: squash(text), headings: headings.filter(Boolean) };\n}\n\n/** HTML as body text, its headings included. */\nfunction htmlText(html: string): string {\n const { text, headings } = htmlParts(html);\n return [text, ...headings].join(\" \");\n}\n\n/** Parameter or schema property names and descriptions, nested `properties` / `items` included. */\nfunction propertyText(list: unknown, depth = 0): string[] {\n if (depth > 10) return [];\n return objects(list).flatMap((p) => [str(p.name), specText(p.description), ...propertyText(p.properties, depth + 1), ...propertyText(p.items, depth + 1)]);\n}\n\n/** HTML elements a spec description uses as markup; anything else in angle brackets is text. */\nconst SPEC_MARKUP = new Set(\n [...INLINE_TAGS, ...\"br p div ul ol li dl dt dd table thead tbody tfoot tr td th caption h1 h2 h3 h4 h5 h6 blockquote hr img pre section\".split(\" \")],\n);\n\n/**\n * A spec's text (CommonMark) as search text. The page renders it with raw\n * HTML off, so every `<…>` shows as written: `List<String>`, `Map<K, V>` and\n * `<T extends Foo>` are words a reader sees and searches for. Only tags\n * naming an HTML element are dropped (`<br>`, `<code>`: noise in a\n * snippet), outside backtick code, and entities decoded. Linear: a tag\n * match cannot run past the next `<` or `>`.\n */\nfunction specText(v: unknown): string {\n const s = str(v);\n if (!s.includes(\"<\") && !s.includes(\"&\")) return s;\n return s\n .split(/(`+[^`]*`+)/)\n .map((part, i) =>\n i % 2\n ? part\n : decode(part.replace(/<\\/?([a-z][a-z0-9]*)\\b[^<>]*>/gi, (m, name: string) => (SPEC_MARKUP.has(name.toLowerCase()) ? (INLINE_TAGS.has(name.toLowerCase()) ? \"\" : \" \") : m))),\n )\n .join(\"\")\n .replace(/\\s+/g, \" \")\n .trim();\n}\n\n/**\n * Text for indexing: headings separated from body, code blocks skipped.\n *\n * API reference nodes keep their text in props, so they are read here:\n * an OpenAPI operation (`endpoint`) by its method and path, summary,\n * description, parameters, request body and responses; autodoc symbols by\n * their names (as headings, as the page shows them) and docstrings. Their\n * code (signatures, source, samples) is skipped like code blocks. An\n * embedded spec's descriptions (`prose` with HTML) count as body, and their\n * headings as headings.\n */\nexport function extractText(tree: TreeNode[]): { headings: string[]; body: string } {\n const headings: string[] = [];\n const body: string[] = [];\n const inl = (a?: InlineNode[]): string => {\n if (!a) return \"\";\n let s = \"\";\n for (const n of a) {\n if (n.type === \"text\" || n.type === \"code\") s += n.text;\n else if (n.type === \"link\" || n.type === \"highlight\") s += inl(n.children);\n else if (n.type === \"kbd\") s += n.keys.join(\" \");\n else if (n.type === \"badge\") s += n.text;\n else if (n.type === \"image\") s += n.alt ?? \"\";\n else if (n.type === \"html-inline\") s += \" \";\n }\n return s;\n };\n const walk = (nodes?: TreeNode[]) => {\n for (const n of nodes ?? []) {\n if (n.type === \"code\" || n.type === \"api-source\") continue;\n const ap = (n.props ?? {}) as Json;\n switch (n.type) {\n case \"endpoint\":\n body.push(\n `${str(ap.method).toUpperCase()} ${str(ap.path)}`,\n specText(ap.summary),\n specText(ap.description),\n ...propertyText(ap.parameters),\n ...propertyText(ap.requestBody),\n ...objects(ap.responses).flatMap((r) => [specText(r.description), ...propertyText(r.schema)]),\n );\n continue;\n case \"api-class\":\n case \"api-member\":\n headings.push(str(ap.name));\n walk(n.children);\n continue;\n case \"api-doc\":\n body.push(htmlText(str(ap.html)));\n walk(n.children);\n continue;\n case \"api-params\":\n for (const p of objects(ap.params)) body.push(str(p.name), htmlText(str(p.docHtml)) || str(p.doc) || str(p.description));\n continue;\n case \"prose\":\n if (!n.inline && typeof n.html === \"string\") {\n const parts = htmlParts(n.html);\n headings.push(...parts.headings);\n body.push(parts.text);\n continue;\n }\n break;\n }\n if (n.type === \"heading\") {\n headings.push(inl(n.inline));\n continue;\n }\n if (n.type === \"html\" && n.html) body.push(htmlText(n.html));\n if (n.inline) body.push(inl(n.inline));\n const p = n.props as Record<string, unknown> | undefined;\n for (const k of [\"title\", \"label\", \"caption\", \"description\"]) if (typeof p?.[k] === \"string\") body.push(p[k] as string);\n walk(n.children);\n }\n };\n walk(tree);\n return { headings, body: body.join(\" \").replace(/\\s+/g, \" \").trim() };\n}\n"],"mappings":"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","names":["end","p"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dogsbay/search",
3
- "version": "0.2.0-beta.119",
3
+ "version": "0.2.0-beta.121",
4
4
  "description": "Static search for docs sites: builds in a Worker, queries from file://",
5
5
  "type": "module",
6
6
  "main": "./dist/entry.js",
@@ -42,7 +42,7 @@
42
42
  ],
43
43
  "dependencies": {
44
44
  "stemmer": "^2.0.1",
45
- "@dogsbay/types": "0.2.0-beta.119"
45
+ "@dogsbay/types": "0.2.0-beta.121"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@playwright/test": "1.61.1",
@@ -52,7 +52,7 @@
52
52
  "tsup": "^8.5.1",
53
53
  "typescript": "^5.9.3",
54
54
  "vitest": "^4.1.10",
55
- "@dogsbay/format-openapi": "0.2.0-beta.119"
55
+ "@dogsbay/format-openapi": "0.2.0-beta.121"
56
56
  },
57
57
  "license": "MIT",
58
58
  "repository": {