searchsocket 0.3.3 → 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/sveltekit.js CHANGED
@@ -2,13 +2,13 @@ import fs from 'fs';
2
2
  import path from 'path';
3
3
  import { createJiti } from 'jiti';
4
4
  import { z } from 'zod';
5
- import pLimit2 from 'p-limit';
6
5
  import { execSync, spawn } from 'child_process';
7
6
  import { createHash } from 'crypto';
8
7
  import { load } from 'cheerio';
9
8
  import matter from 'gray-matter';
10
- import fs4 from 'fs/promises';
11
9
  import fg from 'fast-glob';
10
+ import pLimit from 'p-limit';
11
+ import fs3 from 'fs/promises';
12
12
  import net from 'net';
13
13
  import { gunzipSync } from 'zlib';
14
14
 
@@ -2755,12 +2755,12 @@ var require_ChildNode = __commonJS({
2755
2755
  "node_modules/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/ChildNode.js"(exports$1, module) {
2756
2756
  var Node2 = require_Node();
2757
2757
  var LinkedList = require_LinkedList();
2758
- var createDocumentFragmentFromArguments = function(document, args) {
2759
- var docFrag = document.createDocumentFragment();
2758
+ var createDocumentFragmentFromArguments = function(document2, args) {
2759
+ var docFrag = document2.createDocumentFragment();
2760
2760
  for (var i = 0; i < args.length; i++) {
2761
2761
  var argItem = args[i];
2762
2762
  var isNode = argItem instanceof Node2;
2763
- docFrag.appendChild(isNode ? argItem : document.createTextNode(String(argItem)));
2763
+ docFrag.appendChild(isNode ? argItem : document2.createTextNode(String(argItem)));
2764
2764
  }
2765
2765
  return docFrag;
2766
2766
  };
@@ -2918,7 +2918,7 @@ var require_NamedNodeMap = __commonJS({
2918
2918
  // node_modules/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/Element.js
2919
2919
  var require_Element = __commonJS({
2920
2920
  "node_modules/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/Element.js"(exports$1, module) {
2921
- module.exports = Element;
2921
+ module.exports = Element2;
2922
2922
  var xml = require_xmlnames();
2923
2923
  var utils = require_utils();
2924
2924
  var NAMESPACE = utils.NAMESPACE;
@@ -2935,7 +2935,7 @@ var require_Element = __commonJS({
2935
2935
  var NonDocumentTypeChildNode = require_NonDocumentTypeChildNode();
2936
2936
  var NamedNodeMap = require_NamedNodeMap();
2937
2937
  var uppercaseCache = /* @__PURE__ */ Object.create(null);
2938
- function Element(doc, localName, namespaceURI, prefix) {
2938
+ function Element2(doc, localName, namespaceURI, prefix) {
2939
2939
  ContainerNode.call(this);
2940
2940
  this.nodeType = Node2.ELEMENT_NODE;
2941
2941
  this.ownerDocument = doc;
@@ -2955,7 +2955,7 @@ var require_Element = __commonJS({
2955
2955
  recursiveGetText(node.childNodes[i], a);
2956
2956
  }
2957
2957
  }
2958
- Element.prototype = Object.create(ContainerNode.prototype, {
2958
+ Element2.prototype = Object.create(ContainerNode.prototype, {
2959
2959
  isHTML: { get: function isHTML() {
2960
2960
  return this.namespaceURI === NAMESPACE.HTML && this.ownerDocument.isHTML;
2961
2961
  } },
@@ -3025,7 +3025,7 @@ var require_Element = __commonJS({
3025
3025
  return NodeUtils.serializeOne(this, { nodeType: 0 });
3026
3026
  },
3027
3027
  set: function(v) {
3028
- var document = this.ownerDocument;
3028
+ var document2 = this.ownerDocument;
3029
3029
  var parent = this.parentNode;
3030
3030
  if (parent === null) {
3031
3031
  return;
@@ -3036,8 +3036,8 @@ var require_Element = __commonJS({
3036
3036
  if (parent.nodeType === Node2.DOCUMENT_FRAGMENT_NODE) {
3037
3037
  parent = parent.ownerDocument.createElement("body");
3038
3038
  }
3039
- var parser = document.implementation.mozHTMLParser(
3040
- document._address,
3039
+ var parser = document2.implementation.mozHTMLParser(
3040
+ document2._address,
3041
3041
  parent
3042
3042
  );
3043
3043
  parser.parse(v === null ? "" : String(v), true);
@@ -3096,7 +3096,7 @@ var require_Element = __commonJS({
3096
3096
  default:
3097
3097
  utils.SyntaxError();
3098
3098
  }
3099
- if (!(context instanceof Element) || context.ownerDocument.isHTML && context.localName === "html" && context.namespaceURI === NAMESPACE.HTML) {
3099
+ if (!(context instanceof Element2) || context.ownerDocument.isHTML && context.localName === "html" && context.namespaceURI === NAMESPACE.HTML) {
3100
3100
  context = context.ownerDocument.createElementNS(NAMESPACE.HTML, "body");
3101
3101
  }
3102
3102
  var parser = this.ownerDocument.implementation.mozHTMLParser(
@@ -3704,10 +3704,10 @@ var require_Element = __commonJS({
3704
3704
  return nodes.item ? nodes : new NodeList(nodes);
3705
3705
  } }
3706
3706
  });
3707
- Object.defineProperties(Element.prototype, ChildNode);
3708
- Object.defineProperties(Element.prototype, NonDocumentTypeChildNode);
3707
+ Object.defineProperties(Element2.prototype, ChildNode);
3708
+ Object.defineProperties(Element2.prototype, NonDocumentTypeChildNode);
3709
3709
  attributes.registerChangeHandler(
3710
- Element,
3710
+ Element2,
3711
3711
  "id",
3712
3712
  function(element, lname, oldval, newval) {
3713
3713
  if (element.rooted) {
@@ -3721,7 +3721,7 @@ var require_Element = __commonJS({
3721
3721
  }
3722
3722
  );
3723
3723
  attributes.registerChangeHandler(
3724
- Element,
3724
+ Element2,
3725
3725
  "class",
3726
3726
  function(element, lname, oldval, newval) {
3727
3727
  if (element._classList) {
@@ -3820,7 +3820,7 @@ var require_Element = __commonJS({
3820
3820
  }
3821
3821
  }
3822
3822
  });
3823
- Element._Attr = Attr;
3823
+ Element2._Attr = Attr;
3824
3824
  function AttributesArray(elt) {
3825
3825
  NamedNodeMap.call(this, elt);
3826
3826
  for (var name in elt._attrsByQName) {
@@ -4222,7 +4222,7 @@ var require_DocumentFragment = __commonJS({
4222
4222
  var Node2 = require_Node();
4223
4223
  var NodeList = require_NodeList();
4224
4224
  var ContainerNode = require_ContainerNode();
4225
- var Element = require_Element();
4225
+ var Element2 = require_Element();
4226
4226
  var select = require_select();
4227
4227
  var utils = require_utils();
4228
4228
  function DocumentFragment(doc) {
@@ -4240,9 +4240,9 @@ var require_DocumentFragment = __commonJS({
4240
4240
  }
4241
4241
  },
4242
4242
  // Copy the text content getter/setter from Element
4243
- textContent: Object.getOwnPropertyDescriptor(Element.prototype, "textContent"),
4243
+ textContent: Object.getOwnPropertyDescriptor(Element2.prototype, "textContent"),
4244
4244
  // Copy the text content getter/setter from Element
4245
- innerText: Object.getOwnPropertyDescriptor(Element.prototype, "innerText"),
4245
+ innerText: Object.getOwnPropertyDescriptor(Element2.prototype, "innerText"),
4246
4246
  querySelector: { value: function(selector) {
4247
4247
  var nodes = this.querySelectorAll(selector);
4248
4248
  return nodes.length ? nodes[0] : null;
@@ -4250,8 +4250,8 @@ var require_DocumentFragment = __commonJS({
4250
4250
  querySelectorAll: { value: function(selector) {
4251
4251
  var context = Object.create(this);
4252
4252
  context.isHTML = true;
4253
- context.getElementsByTagName = Element.prototype.getElementsByTagName;
4254
- context.nextElement = Object.getOwnPropertyDescriptor(Element.prototype, "firstElementChild").get;
4253
+ context.getElementsByTagName = Element2.prototype.getElementsByTagName;
4254
+ context.nextElement = Object.getOwnPropertyDescriptor(Element2.prototype, "firstElementChild").get;
4255
4255
  var nodes = select(selector, context);
4256
4256
  return nodes.item ? nodes : new NodeList(nodes);
4257
4257
  } },
@@ -4333,7 +4333,7 @@ var require_ProcessingInstruction = __commonJS({
4333
4333
  // node_modules/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/NodeFilter.js
4334
4334
  var require_NodeFilter = __commonJS({
4335
4335
  "node_modules/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/NodeFilter.js"(exports$1, module) {
4336
- var NodeFilter = {
4336
+ var NodeFilter2 = {
4337
4337
  // Constants for acceptNode()
4338
4338
  FILTER_ACCEPT: 1,
4339
4339
  FILTER_REJECT: 2,
@@ -4358,7 +4358,7 @@ var require_NodeFilter = __commonJS({
4358
4358
  SHOW_NOTATION: 2048
4359
4359
  // historical
4360
4360
  };
4361
- module.exports = NodeFilter.constructor = NodeFilter.prototype = NodeFilter;
4361
+ module.exports = NodeFilter2.constructor = NodeFilter2.prototype = NodeFilter2;
4362
4362
  }
4363
4363
  });
4364
4364
 
@@ -4433,7 +4433,7 @@ var require_TreeWalker = __commonJS({
4433
4433
  "node_modules/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/TreeWalker.js"(exports$1, module) {
4434
4434
  module.exports = TreeWalker;
4435
4435
  var Node2 = require_Node();
4436
- var NodeFilter = require_NodeFilter();
4436
+ var NodeFilter2 = require_NodeFilter();
4437
4437
  var NodeTraversal = require_NodeTraversal();
4438
4438
  var utils = require_utils();
4439
4439
  var mapChild = {
@@ -4453,11 +4453,11 @@ var require_TreeWalker = __commonJS({
4453
4453
  node = tw._currentNode[mapChild[type]];
4454
4454
  while (node !== null) {
4455
4455
  result = tw._internalFilter(node);
4456
- if (result === NodeFilter.FILTER_ACCEPT) {
4456
+ if (result === NodeFilter2.FILTER_ACCEPT) {
4457
4457
  tw._currentNode = node;
4458
4458
  return node;
4459
4459
  }
4460
- if (result === NodeFilter.FILTER_SKIP) {
4460
+ if (result === NodeFilter2.FILTER_SKIP) {
4461
4461
  child = node[mapChild[type]];
4462
4462
  if (child !== null) {
4463
4463
  node = child;
@@ -4491,12 +4491,12 @@ var require_TreeWalker = __commonJS({
4491
4491
  while (sibling !== null) {
4492
4492
  node = sibling;
4493
4493
  result = tw._internalFilter(node);
4494
- if (result === NodeFilter.FILTER_ACCEPT) {
4494
+ if (result === NodeFilter2.FILTER_ACCEPT) {
4495
4495
  tw._currentNode = node;
4496
4496
  return node;
4497
4497
  }
4498
4498
  sibling = node[mapChild[type]];
4499
- if (result === NodeFilter.FILTER_REJECT || sibling === null) {
4499
+ if (result === NodeFilter2.FILTER_REJECT || sibling === null) {
4500
4500
  sibling = node[mapSibling[type]];
4501
4501
  }
4502
4502
  }
@@ -4504,7 +4504,7 @@ var require_TreeWalker = __commonJS({
4504
4504
  if (node === null || node === tw.root) {
4505
4505
  return null;
4506
4506
  }
4507
- if (tw._internalFilter(node) === NodeFilter.FILTER_ACCEPT) {
4507
+ if (tw._internalFilter(node) === NodeFilter2.FILTER_ACCEPT) {
4508
4508
  return null;
4509
4509
  }
4510
4510
  }
@@ -4552,11 +4552,11 @@ var require_TreeWalker = __commonJS({
4552
4552
  utils.InvalidStateError();
4553
4553
  }
4554
4554
  if (!(1 << node.nodeType - 1 & this._whatToShow)) {
4555
- return NodeFilter.FILTER_SKIP;
4555
+ return NodeFilter2.FILTER_SKIP;
4556
4556
  }
4557
4557
  filter = this._filter;
4558
4558
  if (filter === null) {
4559
- result = NodeFilter.FILTER_ACCEPT;
4559
+ result = NodeFilter2.FILTER_ACCEPT;
4560
4560
  } else {
4561
4561
  this._active = true;
4562
4562
  try {
@@ -4585,7 +4585,7 @@ var require_TreeWalker = __commonJS({
4585
4585
  if (node === null) {
4586
4586
  return null;
4587
4587
  }
4588
- if (this._internalFilter(node) === NodeFilter.FILTER_ACCEPT) {
4588
+ if (this._internalFilter(node) === NodeFilter2.FILTER_ACCEPT) {
4589
4589
  this._currentNode = node;
4590
4590
  return node;
4591
4591
  }
@@ -4638,17 +4638,17 @@ var require_TreeWalker = __commonJS({
4638
4638
  for (previousSibling = node.previousSibling; previousSibling; previousSibling = node.previousSibling) {
4639
4639
  node = previousSibling;
4640
4640
  result = this._internalFilter(node);
4641
- if (result === NodeFilter.FILTER_REJECT) {
4641
+ if (result === NodeFilter2.FILTER_REJECT) {
4642
4642
  continue;
4643
4643
  }
4644
4644
  for (lastChild = node.lastChild; lastChild; lastChild = node.lastChild) {
4645
4645
  node = lastChild;
4646
4646
  result = this._internalFilter(node);
4647
- if (result === NodeFilter.FILTER_REJECT) {
4647
+ if (result === NodeFilter2.FILTER_REJECT) {
4648
4648
  break;
4649
4649
  }
4650
4650
  }
4651
- if (result === NodeFilter.FILTER_ACCEPT) {
4651
+ if (result === NodeFilter2.FILTER_ACCEPT) {
4652
4652
  this._currentNode = node;
4653
4653
  return node;
4654
4654
  }
@@ -4657,7 +4657,7 @@ var require_TreeWalker = __commonJS({
4657
4657
  return null;
4658
4658
  }
4659
4659
  node = node.parentNode;
4660
- if (this._internalFilter(node) === NodeFilter.FILTER_ACCEPT) {
4660
+ if (this._internalFilter(node) === NodeFilter2.FILTER_ACCEPT) {
4661
4661
  this._currentNode = node;
4662
4662
  return node;
4663
4663
  }
@@ -4674,26 +4674,26 @@ var require_TreeWalker = __commonJS({
4674
4674
  nextNode: { value: function nextNode() {
4675
4675
  var node, result, firstChild, nextSibling;
4676
4676
  node = this._currentNode;
4677
- result = NodeFilter.FILTER_ACCEPT;
4677
+ result = NodeFilter2.FILTER_ACCEPT;
4678
4678
  CHILDREN:
4679
4679
  while (true) {
4680
4680
  for (firstChild = node.firstChild; firstChild; firstChild = node.firstChild) {
4681
4681
  node = firstChild;
4682
4682
  result = this._internalFilter(node);
4683
- if (result === NodeFilter.FILTER_ACCEPT) {
4683
+ if (result === NodeFilter2.FILTER_ACCEPT) {
4684
4684
  this._currentNode = node;
4685
4685
  return node;
4686
- } else if (result === NodeFilter.FILTER_REJECT) {
4686
+ } else if (result === NodeFilter2.FILTER_REJECT) {
4687
4687
  break;
4688
4688
  }
4689
4689
  }
4690
4690
  for (nextSibling = NodeTraversal.nextSkippingChildren(node, this.root); nextSibling; nextSibling = NodeTraversal.nextSkippingChildren(node, this.root)) {
4691
4691
  node = nextSibling;
4692
4692
  result = this._internalFilter(node);
4693
- if (result === NodeFilter.FILTER_ACCEPT) {
4693
+ if (result === NodeFilter2.FILTER_ACCEPT) {
4694
4694
  this._currentNode = node;
4695
4695
  return node;
4696
- } else if (result === NodeFilter.FILTER_SKIP) {
4696
+ } else if (result === NodeFilter2.FILTER_SKIP) {
4697
4697
  continue CHILDREN;
4698
4698
  }
4699
4699
  }
@@ -4712,7 +4712,7 @@ var require_TreeWalker = __commonJS({
4712
4712
  var require_NodeIterator = __commonJS({
4713
4713
  "node_modules/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/NodeIterator.js"(exports$1, module) {
4714
4714
  module.exports = NodeIterator;
4715
- var NodeFilter = require_NodeFilter();
4715
+ var NodeFilter2 = require_NodeFilter();
4716
4716
  var NodeTraversal = require_NodeTraversal();
4717
4717
  var utils = require_utils();
4718
4718
  function move(node, stayWithin, directionIsNext) {
@@ -4747,7 +4747,7 @@ var require_NodeIterator = __commonJS({
4747
4747
  }
4748
4748
  }
4749
4749
  var result = ni._internalFilter(node);
4750
- if (result === NodeFilter.FILTER_ACCEPT) {
4750
+ if (result === NodeFilter2.FILTER_ACCEPT) {
4751
4751
  break;
4752
4752
  }
4753
4753
  }
@@ -4795,11 +4795,11 @@ var require_NodeIterator = __commonJS({
4795
4795
  utils.InvalidStateError();
4796
4796
  }
4797
4797
  if (!(1 << node.nodeType - 1 & this._whatToShow)) {
4798
- return NodeFilter.FILTER_SKIP;
4798
+ return NodeFilter2.FILTER_SKIP;
4799
4799
  }
4800
4800
  filter = this._filter;
4801
4801
  if (filter === null) {
4802
- result = NodeFilter.FILTER_ACCEPT;
4802
+ result = NodeFilter2.FILTER_ACCEPT;
4803
4803
  } else {
4804
4804
  this._active = true;
4805
4805
  try {
@@ -5009,32 +5009,32 @@ var require_URL = __commonJS({
5009
5009
  else
5010
5010
  return basepath.substring(0, lastslash + 1) + refpath;
5011
5011
  }
5012
- function remove_dot_segments(path14) {
5013
- if (!path14) return path14;
5012
+ function remove_dot_segments(path13) {
5013
+ if (!path13) return path13;
5014
5014
  var output = "";
5015
- while (path14.length > 0) {
5016
- if (path14 === "." || path14 === "..") {
5017
- path14 = "";
5015
+ while (path13.length > 0) {
5016
+ if (path13 === "." || path13 === "..") {
5017
+ path13 = "";
5018
5018
  break;
5019
5019
  }
5020
- var twochars = path14.substring(0, 2);
5021
- var threechars = path14.substring(0, 3);
5022
- var fourchars = path14.substring(0, 4);
5020
+ var twochars = path13.substring(0, 2);
5021
+ var threechars = path13.substring(0, 3);
5022
+ var fourchars = path13.substring(0, 4);
5023
5023
  if (threechars === "../") {
5024
- path14 = path14.substring(3);
5024
+ path13 = path13.substring(3);
5025
5025
  } else if (twochars === "./") {
5026
- path14 = path14.substring(2);
5026
+ path13 = path13.substring(2);
5027
5027
  } else if (threechars === "/./") {
5028
- path14 = "/" + path14.substring(3);
5029
- } else if (twochars === "/." && path14.length === 2) {
5030
- path14 = "/";
5031
- } else if (fourchars === "/../" || threechars === "/.." && path14.length === 3) {
5032
- path14 = "/" + path14.substring(4);
5028
+ path13 = "/" + path13.substring(3);
5029
+ } else if (twochars === "/." && path13.length === 2) {
5030
+ path13 = "/";
5031
+ } else if (fourchars === "/../" || threechars === "/.." && path13.length === 3) {
5032
+ path13 = "/" + path13.substring(4);
5033
5033
  output = output.replace(/\/?[^\/]*$/, "");
5034
5034
  } else {
5035
- var segment = path14.match(/(\/?([^\/]*))/)[0];
5035
+ var segment = path13.match(/(\/?([^\/]*))/)[0];
5036
5036
  output += segment;
5037
- path14 = path14.substring(segment.length);
5037
+ path13 = path13.substring(segment.length);
5038
5038
  }
5039
5039
  }
5040
5040
  return output;
@@ -5599,9 +5599,9 @@ var require_defineElement = __commonJS({
5599
5599
  });
5600
5600
  return c;
5601
5601
  };
5602
- function EventHandlerBuilder(body, document, form, element) {
5602
+ function EventHandlerBuilder(body, document2, form, element) {
5603
5603
  this.body = body;
5604
- this.document = document;
5604
+ this.document = document2;
5605
5605
  this.form = form;
5606
5606
  this.element = element;
5607
5607
  }
@@ -5635,7 +5635,7 @@ var require_defineElement = __commonJS({
5635
5635
  var require_htmlelts = __commonJS({
5636
5636
  "node_modules/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/htmlelts.js"(exports$1) {
5637
5637
  var Node2 = require_Node();
5638
- var Element = require_Element();
5638
+ var Element2 = require_Element();
5639
5639
  var CSSStyleDeclaration = require_CSSStyleDeclaration();
5640
5640
  var utils = require_utils();
5641
5641
  var URLUtils = require_URLUtils();
@@ -5703,10 +5703,10 @@ var require_htmlelts = __commonJS({
5703
5703
  this._form = null;
5704
5704
  };
5705
5705
  var HTMLElement = exports$1.HTMLElement = define({
5706
- superclass: Element,
5706
+ superclass: Element2,
5707
5707
  name: "HTMLElement",
5708
5708
  ctor: function HTMLElement2(doc, localName, prefix) {
5709
- Element.call(this, doc, localName, utils.NAMESPACE.HTML, prefix);
5709
+ Element2.call(this, doc, localName, utils.NAMESPACE.HTML, prefix);
5710
5710
  },
5711
5711
  props: {
5712
5712
  dangerouslySetInnerHTML: {
@@ -7188,7 +7188,7 @@ var require_htmlelts = __commonJS({
7188
7188
  // node_modules/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/svg.js
7189
7189
  var require_svg = __commonJS({
7190
7190
  "node_modules/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/svg.js"(exports$1) {
7191
- var Element = require_Element();
7191
+ var Element2 = require_Element();
7192
7192
  var defineElement = require_defineElement();
7193
7193
  var utils = require_utils();
7194
7194
  var CSSStyleDeclaration = require_CSSStyleDeclaration();
@@ -7202,10 +7202,10 @@ var require_svg = __commonJS({
7202
7202
  return defineElement(spec, SVGElement, svgElements, svgNameToImpl);
7203
7203
  }
7204
7204
  var SVGElement = define({
7205
- superclass: Element,
7205
+ superclass: Element2,
7206
7206
  name: "SVGElement",
7207
7207
  ctor: function SVGElement2(doc, localName, prefix) {
7208
- Element.call(this, doc, localName, utils.NAMESPACE.SVG, prefix);
7208
+ Element2.call(this, doc, localName, utils.NAMESPACE.SVG, prefix);
7209
7209
  },
7210
7210
  props: {
7211
7211
  style: { get: function() {
@@ -7340,7 +7340,7 @@ var require_Document = __commonJS({
7340
7340
  var Node2 = require_Node();
7341
7341
  var NodeList = require_NodeList();
7342
7342
  var ContainerNode = require_ContainerNode();
7343
- var Element = require_Element();
7343
+ var Element2 = require_Element();
7344
7344
  var Text = require_Text();
7345
7345
  var Comment = require_Comment();
7346
7346
  var Event = require_Event();
@@ -7349,7 +7349,7 @@ var require_Document = __commonJS({
7349
7349
  var DOMImplementation = require_DOMImplementation();
7350
7350
  var TreeWalker = require_TreeWalker();
7351
7351
  var NodeIterator = require_NodeIterator();
7352
- var NodeFilter = require_NodeFilter();
7352
+ var NodeFilter2 = require_NodeFilter();
7353
7353
  var URL2 = require_URL();
7354
7354
  var select = require_select();
7355
7355
  var events = require_events();
@@ -7488,13 +7488,13 @@ var require_Document = __commonJS({
7488
7488
  if (this.isHTML) {
7489
7489
  localName = utils.toASCIILowerCase(localName);
7490
7490
  }
7491
- return new Element._Attr(null, localName, null, null, "");
7491
+ return new Element2._Attr(null, localName, null, null, "");
7492
7492
  } },
7493
7493
  createAttributeNS: { value: function(namespace, qualifiedName) {
7494
7494
  namespace = namespace === null || namespace === void 0 || namespace === "" ? null : String(namespace);
7495
7495
  qualifiedName = String(qualifiedName);
7496
7496
  var ve = validateAndExtract(namespace, qualifiedName);
7497
- return new Element._Attr(null, ve.localName, ve.prefix, ve.namespace, "");
7497
+ return new Element2._Attr(null, ve.localName, ve.prefix, ve.namespace, "");
7498
7498
  } },
7499
7499
  createElement: { value: function(localName) {
7500
7500
  localName = String(localName);
@@ -7506,7 +7506,7 @@ var require_Document = __commonJS({
7506
7506
  } else if (this.contentType === "application/xhtml+xml") {
7507
7507
  return html.createElement(this, localName, null);
7508
7508
  } else {
7509
- return new Element(this, localName, null, null);
7509
+ return new Element2(this, localName, null, null);
7510
7510
  }
7511
7511
  }, writable: isApiWritable },
7512
7512
  createElementNS: { value: function(namespace, qualifiedName) {
@@ -7523,7 +7523,7 @@ var require_Document = __commonJS({
7523
7523
  } else if (namespace === NAMESPACE.SVG) {
7524
7524
  return svg.createElement(this, localName, prefix);
7525
7525
  }
7526
- return new Element(this, localName, namespace, prefix);
7526
+ return new Element2(this, localName, namespace, prefix);
7527
7527
  } },
7528
7528
  createEvent: { value: function createEvent(interfaceName) {
7529
7529
  interfaceName = interfaceName.toLowerCase();
@@ -7545,7 +7545,7 @@ var require_Document = __commonJS({
7545
7545
  if (!(root3 instanceof Node2)) {
7546
7546
  throw new TypeError("root not a node");
7547
7547
  }
7548
- whatToShow = whatToShow === void 0 ? NodeFilter.SHOW_ALL : +whatToShow;
7548
+ whatToShow = whatToShow === void 0 ? NodeFilter2.SHOW_ALL : +whatToShow;
7549
7549
  filter = filter === void 0 ? null : filter;
7550
7550
  return new TreeWalker(root3, whatToShow, filter);
7551
7551
  } },
@@ -7557,7 +7557,7 @@ var require_Document = __commonJS({
7557
7557
  if (!(root3 instanceof Node2)) {
7558
7558
  throw new TypeError("root not a node");
7559
7559
  }
7560
- whatToShow = whatToShow === void 0 ? NodeFilter.SHOW_ALL : +whatToShow;
7560
+ whatToShow = whatToShow === void 0 ? NodeFilter2.SHOW_ALL : +whatToShow;
7561
7561
  filter = filter === void 0 ? null : filter;
7562
7562
  return new NodeIterator(root3, whatToShow, filter);
7563
7563
  } },
@@ -7618,10 +7618,10 @@ var require_Document = __commonJS({
7618
7618
  return this.byId[id] instanceof MultiId;
7619
7619
  } },
7620
7620
  // Just copy this method from the Element prototype
7621
- getElementsByName: { value: Element.prototype.getElementsByName },
7622
- getElementsByTagName: { value: Element.prototype.getElementsByTagName },
7623
- getElementsByTagNameNS: { value: Element.prototype.getElementsByTagNameNS },
7624
- getElementsByClassName: { value: Element.prototype.getElementsByClassName },
7621
+ getElementsByName: { value: Element2.prototype.getElementsByName },
7622
+ getElementsByTagName: { value: Element2.prototype.getElementsByTagName },
7623
+ getElementsByTagNameNS: { value: Element2.prototype.getElementsByTagNameNS },
7624
+ getElementsByClassName: { value: Element2.prototype.getElementsByClassName },
7625
7625
  adoptNode: { value: function adoptNode(node) {
7626
7626
  if (node.nodeType === Node2.DOCUMENT_NODE) utils.NotSupportedError();
7627
7627
  if (node.nodeType === Node2.ATTRIBUTE_NODE) {
@@ -16447,8 +16447,8 @@ var require_Window = __commonJS({
16447
16447
  var Location = require_Location();
16448
16448
  var utils = require_utils();
16449
16449
  module.exports = Window;
16450
- function Window(document) {
16451
- this.document = document || new DOMImplementation(null).createHTMLDocument("");
16450
+ function Window(document2) {
16451
+ this.document = document2 || new DOMImplementation(null).createHTMLDocument("");
16452
16452
  this.document._scripting_enabled = true;
16453
16453
  this.document.defaultView = this;
16454
16454
  this.location = new Location(this, this.document._address || "about:blank");
@@ -16578,11 +16578,11 @@ var require_lib = __commonJS({
16578
16578
  };
16579
16579
  };
16580
16580
  exports$1.createWindow = function(html, address) {
16581
- var document = exports$1.createDocument(html);
16581
+ var document2 = exports$1.createDocument(html);
16582
16582
  if (address !== void 0) {
16583
- document._address = address;
16583
+ document2._address = address;
16584
16584
  }
16585
- return new impl.Window(document);
16585
+ return new impl.Window(document2);
16586
16586
  };
16587
16587
  exports$1.impl = impl;
16588
16588
  }
@@ -16598,6 +16598,8 @@ var searchSocketConfigSchema = z.object({
16598
16598
  envVar: z.string().min(1).optional(),
16599
16599
  sanitize: z.boolean().optional()
16600
16600
  }).optional(),
16601
+ exclude: z.array(z.string()).optional(),
16602
+ respectRobotsTxt: z.boolean().optional(),
16601
16603
  source: z.object({
16602
16604
  mode: z.enum(["static-output", "crawl", "content-files", "build"]).optional(),
16603
16605
  staticOutputDir: z.string().min(1).optional(),
@@ -16645,29 +16647,18 @@ var searchSocketConfigSchema = z.object({
16645
16647
  prependTitle: z.boolean().optional(),
16646
16648
  pageSummaryChunk: z.boolean().optional()
16647
16649
  }).optional(),
16648
- embeddings: z.object({
16649
- provider: z.literal("jina").optional(),
16650
- model: z.string().min(1).optional(),
16651
- apiKey: z.string().min(1).optional(),
16652
- apiKeyEnv: z.string().min(1).optional(),
16653
- batchSize: z.number().int().positive().optional(),
16654
- concurrency: z.number().int().positive().optional(),
16655
- pricePer1kTokens: z.number().positive().optional()
16650
+ upstash: z.object({
16651
+ url: z.string().url().optional(),
16652
+ token: z.string().min(1).optional(),
16653
+ urlEnv: z.string().min(1).optional(),
16654
+ tokenEnv: z.string().min(1).optional()
16656
16655
  }).optional(),
16657
- vector: z.object({
16658
- dimension: z.number().int().positive().optional(),
16659
- turso: z.object({
16660
- url: z.string().url().optional(),
16661
- authToken: z.string().min(1).optional(),
16662
- urlEnv: z.string().optional(),
16663
- authTokenEnv: z.string().optional(),
16664
- localPath: z.string().optional()
16665
- }).optional()
16666
- }).optional(),
16667
- rerank: z.object({
16668
- enabled: z.boolean().optional(),
16669
- topN: z.number().int().positive().optional(),
16670
- model: z.string().optional()
16656
+ search: z.object({
16657
+ semanticWeight: z.number().min(0).max(1).optional(),
16658
+ inputEnrichment: z.boolean().optional(),
16659
+ reranking: z.boolean().optional(),
16660
+ dualSearch: z.boolean().optional(),
16661
+ pageSearchWeight: z.number().min(0).max(1).optional()
16671
16662
  }).optional(),
16672
16663
  ranking: z.object({
16673
16664
  enableIncomingLinkBoost: z.boolean().optional(),
@@ -16677,11 +16668,12 @@ var searchSocketConfigSchema = z.object({
16677
16668
  aggregationDecay: z.number().min(0).max(1).optional(),
16678
16669
  minChunkScoreRatio: z.number().min(0).max(1).optional(),
16679
16670
  minScore: z.number().min(0).max(1).optional(),
16671
+ scoreGapThreshold: z.number().min(0).max(1).optional(),
16680
16672
  weights: z.object({
16681
16673
  incomingLinks: z.number().optional(),
16682
16674
  depth: z.number().optional(),
16683
- rerank: z.number().optional(),
16684
- aggregation: z.number().optional()
16675
+ aggregation: z.number().optional(),
16676
+ titleMatch: z.number().optional()
16685
16677
  }).optional()
16686
16678
  }).optional(),
16687
16679
  api: z.object({
@@ -16703,8 +16695,7 @@ var searchSocketConfigSchema = z.object({
16703
16695
  }).optional()
16704
16696
  }).optional(),
16705
16697
  state: z.object({
16706
- dir: z.string().optional(),
16707
- writeMirror: z.boolean().optional()
16698
+ dir: z.string().optional()
16708
16699
  }).optional()
16709
16700
  });
16710
16701
 
@@ -16728,6 +16719,8 @@ function createDefaultConfig(projectId) {
16728
16719
  envVar: "SEARCHSOCKET_SCOPE",
16729
16720
  sanitize: true
16730
16721
  },
16722
+ exclude: [],
16723
+ respectRobotsTxt: true,
16731
16724
  source: {
16732
16725
  mode: "static-output",
16733
16726
  staticOutputDir: "build",
@@ -16756,24 +16749,16 @@ function createDefaultConfig(projectId) {
16756
16749
  prependTitle: true,
16757
16750
  pageSummaryChunk: true
16758
16751
  },
16759
- embeddings: {
16760
- provider: "jina",
16761
- model: "jina-embeddings-v3",
16762
- apiKeyEnv: "JINA_API_KEY",
16763
- batchSize: 64,
16764
- concurrency: 4
16765
- },
16766
- vector: {
16767
- turso: {
16768
- urlEnv: "TURSO_DATABASE_URL",
16769
- authTokenEnv: "TURSO_AUTH_TOKEN",
16770
- localPath: ".searchsocket/vectors.db"
16771
- }
16752
+ upstash: {
16753
+ urlEnv: "UPSTASH_SEARCH_REST_URL",
16754
+ tokenEnv: "UPSTASH_SEARCH_REST_TOKEN"
16772
16755
  },
16773
- rerank: {
16774
- enabled: false,
16775
- topN: 20,
16776
- model: "jina-reranker-v2-base-multilingual"
16756
+ search: {
16757
+ semanticWeight: 0.75,
16758
+ inputEnrichment: true,
16759
+ reranking: true,
16760
+ dualSearch: true,
16761
+ pageSearchWeight: 0.3
16777
16762
  },
16778
16763
  ranking: {
16779
16764
  enableIncomingLinkBoost: true,
@@ -16782,12 +16767,13 @@ function createDefaultConfig(projectId) {
16782
16767
  aggregationCap: 5,
16783
16768
  aggregationDecay: 0.5,
16784
16769
  minChunkScoreRatio: 0.5,
16785
- minScore: 0,
16770
+ minScore: 0.3,
16771
+ scoreGapThreshold: 0.4,
16786
16772
  weights: {
16787
16773
  incomingLinks: 0.05,
16788
16774
  depth: 0.03,
16789
- rerank: 1,
16790
- aggregation: 0.1
16775
+ aggregation: 0.1,
16776
+ titleMatch: 0.15
16791
16777
  }
16792
16778
  },
16793
16779
  api: {
@@ -16805,8 +16791,7 @@ function createDefaultConfig(projectId) {
16805
16791
  }
16806
16792
  },
16807
16793
  state: {
16808
- dir: ".searchsocket",
16809
- writeMirror: false
16794
+ dir: ".searchsocket"
16810
16795
  }
16811
16796
  };
16812
16797
  }
@@ -16892,6 +16877,8 @@ ${issues}`
16892
16877
  ...defaults.scope,
16893
16878
  ...parsed.scope
16894
16879
  },
16880
+ exclude: parsed.exclude ?? defaults.exclude,
16881
+ respectRobotsTxt: parsed.respectRobotsTxt ?? defaults.respectRobotsTxt,
16895
16882
  source: {
16896
16883
  ...defaults.source,
16897
16884
  ...parsed.source,
@@ -16928,21 +16915,13 @@ ${issues}`
16928
16915
  ...defaults.chunking,
16929
16916
  ...parsed.chunking
16930
16917
  },
16931
- embeddings: {
16932
- ...defaults.embeddings,
16933
- ...parsed.embeddings
16918
+ upstash: {
16919
+ ...defaults.upstash,
16920
+ ...parsed.upstash
16934
16921
  },
16935
- vector: {
16936
- ...defaults.vector,
16937
- ...parsed.vector,
16938
- turso: {
16939
- ...defaults.vector.turso,
16940
- ...parsed.vector?.turso
16941
- }
16942
- },
16943
- rerank: {
16944
- ...defaults.rerank,
16945
- ...parsed.rerank
16922
+ search: {
16923
+ ...defaults.search,
16924
+ ...parsed.search
16946
16925
  },
16947
16926
  ranking: {
16948
16927
  ...defaults.ranking,
@@ -17033,128 +17012,6 @@ async function loadConfig(options = {}) {
17033
17012
  function isServerless() {
17034
17013
  return !!(process.env.VERCEL || process.env.NETLIFY || process.env.AWS_LAMBDA_FUNCTION_NAME || process.env.FUNCTIONS_WORKER || process.env.CF_PAGES);
17035
17014
  }
17036
- function sleep(ms) {
17037
- return new Promise((resolve) => {
17038
- setTimeout(resolve, ms);
17039
- });
17040
- }
17041
- var JinaEmbeddingsProvider = class {
17042
- apiKey;
17043
- batchSize;
17044
- concurrency;
17045
- defaultTask;
17046
- constructor(options) {
17047
- if (!Number.isInteger(options.batchSize) || options.batchSize <= 0) {
17048
- throw new Error(`Invalid batchSize: ${options.batchSize}. batchSize must be a positive integer.`);
17049
- }
17050
- if (!Number.isInteger(options.concurrency) || options.concurrency <= 0) {
17051
- throw new Error(`Invalid concurrency: ${options.concurrency}. concurrency must be a positive integer.`);
17052
- }
17053
- this.apiKey = options.apiKey;
17054
- this.batchSize = options.batchSize;
17055
- this.concurrency = options.concurrency;
17056
- this.defaultTask = options.task ?? "retrieval.passage";
17057
- }
17058
- estimateTokens(text) {
17059
- const normalized = text.trim();
17060
- if (!normalized) {
17061
- return 0;
17062
- }
17063
- const wordCount = normalized.match(/[A-Za-z0-9_]+/g)?.length ?? 0;
17064
- const punctuationCount = normalized.match(/[^\s\w]/g)?.length ?? 0;
17065
- const cjkCount = normalized.match(/[\u3400-\u9fff]/g)?.length ?? 0;
17066
- const charEstimate = Math.ceil(normalized.length / 4);
17067
- const lexicalEstimate = Math.ceil(wordCount * 1.25 + punctuationCount * 0.45 + cjkCount * 1.6);
17068
- return Math.max(1, Math.max(charEstimate, lexicalEstimate));
17069
- }
17070
- async embedTexts(texts, modelId, task) {
17071
- if (texts.length === 0) {
17072
- return [];
17073
- }
17074
- const batches = [];
17075
- for (let i = 0; i < texts.length; i += this.batchSize) {
17076
- batches.push({
17077
- index: i,
17078
- values: texts.slice(i, i + this.batchSize)
17079
- });
17080
- }
17081
- const outputs = new Array(batches.length);
17082
- const limit = pLimit2(this.concurrency);
17083
- await Promise.all(
17084
- batches.map(
17085
- (batch, position) => limit(async () => {
17086
- outputs[position] = await this.embedWithRetry(batch.values, modelId, task ?? this.defaultTask);
17087
- })
17088
- )
17089
- );
17090
- return outputs.flat();
17091
- }
17092
- async embedWithRetry(texts, modelId, task) {
17093
- const maxAttempts = 5;
17094
- let attempt = 0;
17095
- while (attempt < maxAttempts) {
17096
- attempt += 1;
17097
- let response;
17098
- try {
17099
- response = await fetch("https://api.jina.ai/v1/embeddings", {
17100
- method: "POST",
17101
- headers: {
17102
- "content-type": "application/json",
17103
- authorization: `Bearer ${this.apiKey}`
17104
- },
17105
- body: JSON.stringify({
17106
- model: modelId,
17107
- input: texts,
17108
- task
17109
- })
17110
- });
17111
- } catch (error) {
17112
- if (attempt >= maxAttempts) {
17113
- throw error;
17114
- }
17115
- await sleep(Math.min(2 ** attempt * 300, 5e3));
17116
- continue;
17117
- }
17118
- if (!response.ok) {
17119
- const retryable = response.status === 429 || response.status >= 500;
17120
- if (!retryable || attempt >= maxAttempts) {
17121
- const errorBody = await response.text();
17122
- throw new Error(`Jina embeddings failed (${response.status}): ${errorBody}`);
17123
- }
17124
- await sleep(Math.min(2 ** attempt * 300, 5e3));
17125
- continue;
17126
- }
17127
- const payload = await response.json();
17128
- if (!payload.data || !Array.isArray(payload.data)) {
17129
- throw new Error("Invalid Jina embeddings response format");
17130
- }
17131
- return payload.data.map((entry) => entry.embedding);
17132
- }
17133
- throw new Error("Unreachable retry state");
17134
- }
17135
- };
17136
-
17137
- // src/embeddings/factory.ts
17138
- function createEmbeddingsProvider(config) {
17139
- if (config.embeddings.provider !== "jina") {
17140
- throw new SearchSocketError(
17141
- "CONFIG_MISSING",
17142
- `Unsupported embeddings provider ${config.embeddings.provider}`
17143
- );
17144
- }
17145
- const apiKey = config.embeddings.apiKey ?? process.env[config.embeddings.apiKeyEnv];
17146
- if (!apiKey) {
17147
- throw new SearchSocketError(
17148
- "CONFIG_MISSING",
17149
- `Missing embeddings API key: provide embeddings.apiKey or set env var ${config.embeddings.apiKeyEnv}`
17150
- );
17151
- }
17152
- return new JinaEmbeddingsProvider({
17153
- apiKey,
17154
- batchSize: config.embeddings.batchSize,
17155
- concurrency: config.embeddings.concurrency
17156
- });
17157
- }
17158
17015
 
17159
17016
  // src/utils/text.ts
17160
17017
  function normalizeText(input) {
@@ -17229,103 +17086,6 @@ function resolveScope(config, override) {
17229
17086
  };
17230
17087
  }
17231
17088
 
17232
- // src/rerank/jina.ts
17233
- function sleep2(ms) {
17234
- return new Promise((resolve) => {
17235
- setTimeout(resolve, ms);
17236
- });
17237
- }
17238
- var JinaReranker = class {
17239
- apiKey;
17240
- model;
17241
- maxRetries;
17242
- constructor(options) {
17243
- this.apiKey = options.apiKey;
17244
- this.model = options.model;
17245
- this.maxRetries = options.maxRetries ?? 2;
17246
- }
17247
- async rerank(query, candidates, topN) {
17248
- if (candidates.length === 0) {
17249
- return [];
17250
- }
17251
- const body = {
17252
- model: this.model,
17253
- query,
17254
- documents: candidates.map((candidate) => candidate.text),
17255
- top_n: topN ?? candidates.length,
17256
- return_documents: false
17257
- };
17258
- let attempt = 0;
17259
- while (attempt <= this.maxRetries) {
17260
- attempt += 1;
17261
- let response;
17262
- try {
17263
- response = await fetch("https://api.jina.ai/v1/rerank", {
17264
- method: "POST",
17265
- headers: {
17266
- "content-type": "application/json",
17267
- authorization: `Bearer ${this.apiKey}`
17268
- },
17269
- body: JSON.stringify(body)
17270
- });
17271
- } catch (error) {
17272
- if (attempt <= this.maxRetries) {
17273
- await sleep2(Math.min(300 * 2 ** attempt, 4e3));
17274
- continue;
17275
- }
17276
- throw error;
17277
- }
17278
- if (!response.ok) {
17279
- const retryable = response.status === 429 || response.status >= 500;
17280
- if (retryable && attempt <= this.maxRetries) {
17281
- await sleep2(Math.min(300 * 2 ** attempt, 4e3));
17282
- continue;
17283
- }
17284
- const errorBody = await response.text();
17285
- throw new Error(`Jina rerank failed (${response.status}): ${errorBody}`);
17286
- }
17287
- const payload = await response.json();
17288
- const rawResults = payload.results ?? payload.data ?? [];
17289
- if (!Array.isArray(rawResults)) {
17290
- throw new Error("Invalid Jina rerank response format");
17291
- }
17292
- return rawResults.flatMap((item) => {
17293
- const index = item.index;
17294
- if (typeof index !== "number" || index < 0 || index >= candidates.length) {
17295
- return [];
17296
- }
17297
- const candidate = candidates[index];
17298
- if (!candidate) {
17299
- return [];
17300
- }
17301
- const score = typeof item.relevance_score === "number" ? item.relevance_score : item.score ?? 0;
17302
- return [
17303
- {
17304
- id: candidate.id,
17305
- score
17306
- }
17307
- ];
17308
- }).sort((a, b) => b.score - a.score);
17309
- }
17310
- throw new Error("Jina rerank request failed after retries");
17311
- }
17312
- };
17313
-
17314
- // src/rerank/factory.ts
17315
- function createReranker(config) {
17316
- if (!config.rerank.enabled) {
17317
- return null;
17318
- }
17319
- const apiKey = config.embeddings.apiKey ?? process.env[config.embeddings.apiKeyEnv];
17320
- if (!apiKey) {
17321
- return null;
17322
- }
17323
- return new JinaReranker({
17324
- apiKey,
17325
- model: config.rerank.model
17326
- });
17327
- }
17328
-
17329
17089
  // src/utils/time.ts
17330
17090
  function nowIso() {
17331
17091
  return (/* @__PURE__ */ new Date()).toISOString();
@@ -17344,13 +17104,6 @@ function normalizeUrlPath(rawPath) {
17344
17104
  }
17345
17105
  return out;
17346
17106
  }
17347
- function urlPathToMirrorRelative(urlPath) {
17348
- const normalized = normalizeUrlPath(urlPath);
17349
- if (normalized === "/") {
17350
- return "index.md";
17351
- }
17352
- return `${normalized.slice(1)}.md`;
17353
- }
17354
17107
  function staticHtmlFileToUrl(filePath, rootDir) {
17355
17108
  const relative = path.relative(rootDir, filePath).replace(/\\/g, "/");
17356
17109
  if (relative === "index.html") {
@@ -17384,434 +17137,239 @@ function joinUrl(baseUrl, route) {
17384
17137
  return `${base}${routePart}`;
17385
17138
  }
17386
17139
 
17387
- // src/vector/turso.ts
17388
- var TursoVectorStore = class {
17140
+ // src/vector/upstash.ts
17141
+ function chunkIndexName(scope) {
17142
+ return `${scope.projectId}--${scope.scopeName}`;
17143
+ }
17144
+ function pageIndexName(scope) {
17145
+ return `${scope.projectId}--${scope.scopeName}--pages`;
17146
+ }
17147
+ var UpstashSearchStore = class {
17389
17148
  client;
17390
- dimension;
17391
- chunksReady = false;
17392
- registryReady = false;
17393
- pagesReady = false;
17394
17149
  constructor(opts) {
17395
17150
  this.client = opts.client;
17396
- this.dimension = opts.dimension;
17397
- }
17398
- async ensureRegistry() {
17399
- if (this.registryReady) return;
17400
- await this.client.execute(`
17401
- CREATE TABLE IF NOT EXISTS registry (
17402
- scope_key TEXT PRIMARY KEY,
17403
- project_id TEXT NOT NULL,
17404
- scope_name TEXT NOT NULL,
17405
- model_id TEXT NOT NULL,
17406
- last_indexed_at TEXT NOT NULL,
17407
- vector_count INTEGER,
17408
- last_estimate_tokens INTEGER,
17409
- last_estimate_cost_usd REAL,
17410
- last_estimate_changed_chunks INTEGER
17411
- )
17412
- `);
17413
- const estimateCols = [
17414
- { name: "last_estimate_tokens", def: "INTEGER" },
17415
- { name: "last_estimate_cost_usd", def: "REAL" },
17416
- { name: "last_estimate_changed_chunks", def: "INTEGER" }
17417
- ];
17418
- for (const col of estimateCols) {
17419
- try {
17420
- await this.client.execute(`ALTER TABLE registry ADD COLUMN ${col.name} ${col.def}`);
17421
- } catch (error) {
17422
- if (error instanceof Error && !error.message.includes("duplicate column")) {
17423
- throw error;
17424
- }
17425
- }
17426
- }
17427
- this.registryReady = true;
17428
- }
17429
- async ensureChunks(dim) {
17430
- if (this.chunksReady) return;
17431
- const exists = await this.chunksTableExists();
17432
- if (exists) {
17433
- const currentDim = await this.getChunksDimension();
17434
- if (currentDim !== null && currentDim !== dim) {
17435
- await this.client.batch([
17436
- "DROP INDEX IF EXISTS idx",
17437
- "DROP TABLE IF EXISTS chunks"
17438
- ]);
17439
- }
17440
- }
17441
- await this.client.batch([
17442
- `CREATE TABLE IF NOT EXISTS chunks (
17443
- id TEXT PRIMARY KEY,
17444
- project_id TEXT NOT NULL,
17445
- scope_name TEXT NOT NULL,
17446
- url TEXT NOT NULL,
17447
- path TEXT NOT NULL,
17448
- title TEXT NOT NULL,
17449
- section_title TEXT NOT NULL DEFAULT '',
17450
- heading_path TEXT NOT NULL DEFAULT '[]',
17451
- snippet TEXT NOT NULL DEFAULT '',
17452
- chunk_text TEXT NOT NULL DEFAULT '',
17453
- ordinal INTEGER NOT NULL DEFAULT 0,
17454
- content_hash TEXT NOT NULL DEFAULT '',
17455
- model_id TEXT NOT NULL DEFAULT '',
17456
- depth INTEGER NOT NULL DEFAULT 0,
17457
- incoming_links INTEGER NOT NULL DEFAULT 0,
17458
- route_file TEXT NOT NULL DEFAULT '',
17459
- tags TEXT NOT NULL DEFAULT '[]',
17460
- description TEXT NOT NULL DEFAULT '',
17461
- keywords TEXT NOT NULL DEFAULT '[]',
17462
- embedding F32_BLOB(${dim})
17463
- )`,
17464
- `CREATE INDEX IF NOT EXISTS idx ON chunks (libsql_vector_idx(embedding, 'metric=cosine'))`
17465
- ]);
17466
- this.chunksReady = true;
17467
- }
17468
- async ensurePages() {
17469
- if (this.pagesReady) return;
17470
- await this.client.execute(`
17471
- CREATE TABLE IF NOT EXISTS pages (
17472
- project_id TEXT NOT NULL,
17473
- scope_name TEXT NOT NULL,
17474
- url TEXT NOT NULL,
17475
- title TEXT NOT NULL,
17476
- markdown TEXT NOT NULL,
17477
- route_file TEXT NOT NULL DEFAULT '',
17478
- route_resolution TEXT NOT NULL DEFAULT 'exact',
17479
- incoming_links INTEGER NOT NULL DEFAULT 0,
17480
- outgoing_links INTEGER NOT NULL DEFAULT 0,
17481
- depth INTEGER NOT NULL DEFAULT 0,
17482
- tags TEXT NOT NULL DEFAULT '[]',
17483
- indexed_at TEXT NOT NULL,
17484
- PRIMARY KEY (project_id, scope_name, url)
17485
- )
17486
- `);
17487
- this.pagesReady = true;
17488
17151
  }
17489
- async chunksTableExists() {
17490
- try {
17491
- await this.client.execute("SELECT 1 FROM chunks LIMIT 0");
17492
- return true;
17493
- } catch (error) {
17494
- if (error instanceof Error && error.message.includes("no such table")) {
17495
- return false;
17496
- }
17497
- throw error;
17498
- }
17152
+ chunkIndex(scope) {
17153
+ return this.client.index(chunkIndexName(scope));
17499
17154
  }
17500
- /**
17501
- * Read the current F32_BLOB dimension from the chunks table schema.
17502
- * Returns null if the table doesn't exist or the dimension can't be parsed.
17503
- */
17504
- async getChunksDimension() {
17505
- try {
17506
- const rs = await this.client.execute(
17507
- "SELECT sql FROM sqlite_master WHERE type='table' AND name='chunks'"
17508
- );
17509
- if (rs.rows.length === 0) return null;
17510
- const sql = rs.rows[0].sql;
17511
- const match = sql.match(/F32_BLOB\((\d+)\)/i);
17512
- return match ? parseInt(match[1], 10) : null;
17513
- } catch {
17514
- return null;
17515
- }
17155
+ pageIndex(scope) {
17156
+ return this.client.index(pageIndexName(scope));
17516
17157
  }
17517
- /**
17518
- * Drop all SearchSocket tables (chunks, registry, pages) and their indexes.
17519
- * Used by `clean --remote` for a full reset.
17520
- */
17521
- async dropAllTables() {
17522
- await this.client.batch([
17523
- "DROP INDEX IF EXISTS idx",
17524
- "DROP TABLE IF EXISTS chunks",
17525
- "DROP TABLE IF EXISTS registry",
17526
- "DROP TABLE IF EXISTS pages"
17527
- ]);
17528
- this.chunksReady = false;
17529
- this.registryReady = false;
17530
- this.pagesReady = false;
17531
- }
17532
- async upsert(records, _scope) {
17533
- if (records.length === 0) return;
17534
- const dim = this.dimension ?? records[0].vector.length;
17535
- await this.ensureChunks(dim);
17158
+ async upsertChunks(chunks, scope) {
17159
+ if (chunks.length === 0) return;
17160
+ const index = this.chunkIndex(scope);
17536
17161
  const BATCH_SIZE = 100;
17537
- for (let i = 0; i < records.length; i += BATCH_SIZE) {
17538
- const batch = records.slice(i, i + BATCH_SIZE);
17539
- const stmts = batch.map((r) => ({
17540
- sql: `INSERT OR REPLACE INTO chunks
17541
- (id, project_id, scope_name, url, path, title, section_title,
17542
- heading_path, snippet, chunk_text, ordinal, content_hash, model_id, depth,
17543
- incoming_links, route_file, tags, description, keywords, embedding)
17544
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, vector(?))`,
17545
- args: [
17546
- r.id,
17547
- r.metadata.projectId,
17548
- r.metadata.scopeName,
17549
- r.metadata.url,
17550
- r.metadata.path,
17551
- r.metadata.title,
17552
- r.metadata.sectionTitle,
17553
- JSON.stringify(r.metadata.headingPath),
17554
- r.metadata.snippet,
17555
- r.metadata.chunkText,
17556
- r.metadata.ordinal,
17557
- r.metadata.contentHash,
17558
- r.metadata.modelId,
17559
- r.metadata.depth,
17560
- r.metadata.incomingLinks,
17561
- r.metadata.routeFile,
17562
- JSON.stringify(r.metadata.tags),
17563
- r.metadata.description ?? "",
17564
- JSON.stringify(r.metadata.keywords ?? []),
17565
- JSON.stringify(r.vector)
17566
- ]
17567
- }));
17568
- await this.client.batch(stmts);
17162
+ for (let i = 0; i < chunks.length; i += BATCH_SIZE) {
17163
+ const batch = chunks.slice(i, i + BATCH_SIZE);
17164
+ await index.upsert(batch);
17569
17165
  }
17570
17166
  }
17571
- async query(queryVector, opts, scope) {
17572
- const dim = this.dimension ?? queryVector.length;
17573
- await this.ensureChunks(dim);
17574
- const queryJson = JSON.stringify(queryVector);
17575
- const rs = await this.client.execute({
17576
- sql: `SELECT c.id, c.project_id, c.scope_name, c.url, c.path, c.title,
17577
- c.section_title, c.heading_path, c.snippet, c.chunk_text,
17578
- c.ordinal, c.content_hash,
17579
- c.model_id, c.depth, c.incoming_links, c.route_file, c.tags,
17580
- c.description, c.keywords,
17581
- vector_distance_cos(c.embedding, vector(?)) AS distance
17582
- FROM vector_top_k('idx', vector(?), ?) AS v
17583
- JOIN chunks AS c ON c.rowid = v.id`,
17584
- args: [queryJson, queryJson, opts.topK]
17167
+ async search(query, opts, scope) {
17168
+ const index = this.chunkIndex(scope);
17169
+ const results = await index.search({
17170
+ query,
17171
+ limit: opts.limit,
17172
+ semanticWeight: opts.semanticWeight,
17173
+ inputEnrichment: opts.inputEnrichment,
17174
+ reranking: opts.reranking,
17175
+ filter: opts.filter
17585
17176
  });
17586
- let hits = [];
17587
- for (const row of rs.rows) {
17588
- const projectId = row.project_id;
17589
- const scopeName = row.scope_name;
17590
- if (projectId !== scope.projectId || scopeName !== scope.scopeName) {
17591
- continue;
17177
+ return results.map((doc) => ({
17178
+ id: doc.id,
17179
+ score: doc.score,
17180
+ metadata: {
17181
+ projectId: doc.metadata?.projectId ?? "",
17182
+ scopeName: doc.metadata?.scopeName ?? "",
17183
+ url: doc.content.url,
17184
+ path: doc.metadata?.path ?? "",
17185
+ title: doc.content.title,
17186
+ sectionTitle: doc.content.sectionTitle,
17187
+ headingPath: doc.content.headingPath ? doc.content.headingPath.split(" > ").filter(Boolean) : [],
17188
+ snippet: doc.metadata?.snippet ?? "",
17189
+ chunkText: doc.content.text,
17190
+ ordinal: doc.metadata?.ordinal ?? 0,
17191
+ contentHash: doc.metadata?.contentHash ?? "",
17192
+ depth: doc.metadata?.depth ?? 0,
17193
+ incomingLinks: doc.metadata?.incomingLinks ?? 0,
17194
+ routeFile: doc.metadata?.routeFile ?? "",
17195
+ tags: doc.content.tags ? doc.content.tags.split(",").filter(Boolean) : [],
17196
+ description: doc.metadata?.description || void 0,
17197
+ keywords: doc.metadata?.keywords ? doc.metadata.keywords.split(",").filter(Boolean) : void 0
17592
17198
  }
17593
- const rowPath = row.path;
17594
- if (opts.pathPrefix) {
17595
- const rawPrefix = opts.pathPrefix.startsWith("/") ? opts.pathPrefix : `/${opts.pathPrefix}`;
17596
- const prefix = rawPrefix.endsWith("/") ? rawPrefix : `${rawPrefix}/`;
17597
- const normalizedPath = rowPath.replace(/\/$/, "");
17598
- const normalizedPrefix = rawPrefix.replace(/\/$/, "");
17599
- if (normalizedPath !== normalizedPrefix && !rowPath.startsWith(prefix)) {
17600
- continue;
17601
- }
17602
- }
17603
- const tags = JSON.parse(row.tags || "[]");
17604
- if (opts.tags && opts.tags.length > 0) {
17605
- if (!opts.tags.every((t) => tags.includes(t))) {
17606
- continue;
17607
- }
17608
- }
17609
- const distance = row.distance;
17610
- const score = 1 - distance;
17611
- const description = row.description || void 0;
17612
- const keywords = (() => {
17613
- const raw = row.keywords || "[]";
17614
- const parsed = JSON.parse(raw);
17615
- return parsed.length > 0 ? parsed : void 0;
17616
- })();
17617
- hits.push({
17618
- id: row.id,
17619
- score,
17620
- metadata: {
17621
- projectId,
17622
- scopeName,
17623
- url: row.url,
17624
- path: rowPath,
17625
- title: row.title,
17626
- sectionTitle: row.section_title,
17627
- headingPath: JSON.parse(row.heading_path || "[]"),
17628
- snippet: row.snippet,
17629
- chunkText: row.chunk_text || "",
17630
- ordinal: row.ordinal || 0,
17631
- contentHash: row.content_hash,
17632
- modelId: row.model_id,
17633
- depth: row.depth,
17634
- incomingLinks: row.incoming_links,
17635
- routeFile: row.route_file,
17636
- tags,
17637
- description,
17638
- keywords
17639
- }
17199
+ }));
17200
+ }
17201
+ async searchPages(query, opts, scope) {
17202
+ const index = this.pageIndex(scope);
17203
+ let results;
17204
+ try {
17205
+ results = await index.search({
17206
+ query,
17207
+ limit: opts.limit,
17208
+ semanticWeight: opts.semanticWeight,
17209
+ inputEnrichment: opts.inputEnrichment,
17210
+ reranking: true,
17211
+ filter: opts.filter
17640
17212
  });
17213
+ } catch {
17214
+ return [];
17641
17215
  }
17642
- hits.sort((a, b) => b.score - a.score);
17643
- return hits;
17216
+ return results.map((doc) => ({
17217
+ id: doc.id,
17218
+ score: doc.score,
17219
+ title: doc.content.title,
17220
+ url: doc.content.url,
17221
+ description: doc.content.description ?? "",
17222
+ tags: doc.content.tags ? doc.content.tags.split(",").filter(Boolean) : [],
17223
+ depth: doc.metadata?.depth ?? 0,
17224
+ incomingLinks: doc.metadata?.incomingLinks ?? 0,
17225
+ routeFile: doc.metadata?.routeFile ?? ""
17226
+ }));
17644
17227
  }
17645
17228
  async deleteByIds(ids, scope) {
17646
17229
  if (ids.length === 0) return;
17230
+ const index = this.chunkIndex(scope);
17647
17231
  const BATCH_SIZE = 500;
17648
17232
  for (let i = 0; i < ids.length; i += BATCH_SIZE) {
17649
17233
  const batch = ids.slice(i, i + BATCH_SIZE);
17650
- const placeholders = batch.map(() => "?").join(", ");
17651
- await this.client.execute({
17652
- sql: `DELETE FROM chunks WHERE project_id = ? AND scope_name = ? AND id IN (${placeholders})`,
17653
- args: [scope.projectId, scope.scopeName, ...batch]
17654
- });
17234
+ await index.delete(batch);
17655
17235
  }
17656
17236
  }
17657
17237
  async deleteScope(scope) {
17658
- await this.ensureRegistry();
17659
17238
  try {
17660
- await this.client.execute({
17661
- sql: `DELETE FROM chunks WHERE project_id = ? AND scope_name = ?`,
17662
- args: [scope.projectId, scope.scopeName]
17663
- });
17664
- } catch (error) {
17665
- if (error instanceof Error && !error.message.includes("no such table")) {
17666
- throw error;
17667
- }
17239
+ const chunkIdx = this.chunkIndex(scope);
17240
+ await chunkIdx.deleteIndex();
17241
+ } catch {
17668
17242
  }
17669
17243
  try {
17670
- await this.client.execute({
17671
- sql: `DELETE FROM pages WHERE project_id = ? AND scope_name = ?`,
17672
- args: [scope.projectId, scope.scopeName]
17673
- });
17674
- } catch (error) {
17675
- if (error instanceof Error && !error.message.includes("no such table")) {
17676
- throw error;
17677
- }
17244
+ const pageIdx = this.pageIndex(scope);
17245
+ await pageIdx.deleteIndex();
17246
+ } catch {
17678
17247
  }
17679
- await this.client.execute({
17680
- sql: `DELETE FROM registry WHERE project_id = ? AND scope_name = ?`,
17681
- args: [scope.projectId, scope.scopeName]
17682
- });
17683
17248
  }
17684
- async listScopes(scopeProjectId) {
17685
- await this.ensureRegistry();
17686
- const rs = await this.client.execute({
17687
- sql: `SELECT project_id, scope_name, model_id, last_indexed_at, vector_count,
17688
- last_estimate_tokens, last_estimate_cost_usd, last_estimate_changed_chunks
17689
- FROM registry WHERE project_id = ?`,
17690
- args: [scopeProjectId]
17691
- });
17692
- return rs.rows.map((row) => ({
17693
- projectId: row.project_id,
17694
- scopeName: row.scope_name,
17695
- modelId: row.model_id,
17696
- lastIndexedAt: row.last_indexed_at,
17697
- vectorCount: row.vector_count,
17698
- lastEstimateTokens: row.last_estimate_tokens,
17699
- lastEstimateCostUSD: row.last_estimate_cost_usd,
17700
- lastEstimateChangedChunks: row.last_estimate_changed_chunks
17701
- }));
17702
- }
17703
- async recordScope(info) {
17704
- await this.ensureRegistry();
17705
- const key = `${info.projectId}:${info.scopeName}`;
17706
- await this.client.execute({
17707
- sql: `INSERT OR REPLACE INTO registry
17708
- (scope_key, project_id, scope_name, model_id, last_indexed_at, vector_count,
17709
- last_estimate_tokens, last_estimate_cost_usd, last_estimate_changed_chunks)
17710
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
17711
- args: [
17712
- key,
17713
- info.projectId,
17714
- info.scopeName,
17715
- info.modelId,
17716
- info.lastIndexedAt,
17717
- info.vectorCount ?? null,
17718
- info.lastEstimateTokens ?? null,
17719
- info.lastEstimateCostUSD ?? null,
17720
- info.lastEstimateChangedChunks ?? null
17721
- ]
17722
- });
17249
+ async listScopes(projectId) {
17250
+ const allIndexes = await this.client.listIndexes();
17251
+ const prefix = `${projectId}--`;
17252
+ const scopeNames = /* @__PURE__ */ new Set();
17253
+ for (const name of allIndexes) {
17254
+ if (name.startsWith(prefix) && !name.endsWith("--pages")) {
17255
+ const scopeName = name.slice(prefix.length);
17256
+ scopeNames.add(scopeName);
17257
+ }
17258
+ }
17259
+ const scopes = [];
17260
+ for (const scopeName of scopeNames) {
17261
+ const scope = {
17262
+ projectId,
17263
+ scopeName,
17264
+ scopeId: `${projectId}:${scopeName}`
17265
+ };
17266
+ try {
17267
+ const info = await this.chunkIndex(scope).info();
17268
+ scopes.push({
17269
+ projectId,
17270
+ scopeName,
17271
+ lastIndexedAt: (/* @__PURE__ */ new Date()).toISOString(),
17272
+ documentCount: info.documentCount
17273
+ });
17274
+ } catch {
17275
+ scopes.push({
17276
+ projectId,
17277
+ scopeName,
17278
+ lastIndexedAt: "unknown",
17279
+ documentCount: 0
17280
+ });
17281
+ }
17282
+ }
17283
+ return scopes;
17723
17284
  }
17724
17285
  async getContentHashes(scope) {
17725
- const exists = await this.chunksTableExists();
17726
- if (!exists) return /* @__PURE__ */ new Map();
17727
- const rs = await this.client.execute({
17728
- sql: `SELECT id, content_hash FROM chunks WHERE project_id = ? AND scope_name = ?`,
17729
- args: [scope.projectId, scope.scopeName]
17730
- });
17731
17286
  const map = /* @__PURE__ */ new Map();
17732
- for (const row of rs.rows) {
17733
- map.set(row.id, row.content_hash);
17287
+ const index = this.chunkIndex(scope);
17288
+ let cursor = "0";
17289
+ try {
17290
+ for (; ; ) {
17291
+ const result = await index.range({ cursor, limit: 100 });
17292
+ for (const doc of result.documents) {
17293
+ if (doc.metadata?.contentHash) {
17294
+ map.set(doc.id, doc.metadata.contentHash);
17295
+ }
17296
+ }
17297
+ if (!result.nextCursor || result.nextCursor === "0") break;
17298
+ cursor = result.nextCursor;
17299
+ }
17300
+ } catch {
17734
17301
  }
17735
17302
  return map;
17736
17303
  }
17737
17304
  async upsertPages(pages, scope) {
17738
17305
  if (pages.length === 0) return;
17739
- await this.ensurePages();
17740
- for (const page of pages) {
17741
- if (page.projectId !== scope.projectId || page.scopeName !== scope.scopeName) {
17742
- throw new Error(
17743
- `Page scope mismatch: page has ${page.projectId}:${page.scopeName} but scope is ${scope.projectId}:${scope.scopeName}`
17744
- );
17745
- }
17746
- }
17747
- const BATCH_SIZE = 100;
17306
+ const index = this.pageIndex(scope);
17307
+ const BATCH_SIZE = 50;
17748
17308
  for (let i = 0; i < pages.length; i += BATCH_SIZE) {
17749
17309
  const batch = pages.slice(i, i + BATCH_SIZE);
17750
- const stmts = batch.map((p) => ({
17751
- sql: `INSERT OR REPLACE INTO pages
17752
- (project_id, scope_name, url, title, markdown, route_file,
17753
- route_resolution, incoming_links, outgoing_links, depth, tags, indexed_at)
17754
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
17755
- args: [
17756
- p.projectId,
17757
- p.scopeName,
17758
- p.url,
17759
- p.title,
17760
- p.markdown,
17761
- p.routeFile,
17762
- p.routeResolution,
17763
- p.incomingLinks,
17764
- p.outgoingLinks,
17765
- p.depth,
17766
- JSON.stringify(p.tags),
17767
- p.indexedAt
17768
- ]
17310
+ const docs = batch.map((p) => ({
17311
+ id: p.url,
17312
+ content: {
17313
+ title: p.title,
17314
+ url: p.url,
17315
+ type: "page",
17316
+ description: p.description ?? "",
17317
+ keywords: (p.keywords ?? []).join(","),
17318
+ summary: p.summary ?? "",
17319
+ tags: p.tags.join(",")
17320
+ },
17321
+ metadata: {
17322
+ markdown: p.markdown,
17323
+ projectId: p.projectId,
17324
+ scopeName: p.scopeName,
17325
+ routeFile: p.routeFile,
17326
+ routeResolution: p.routeResolution,
17327
+ incomingLinks: p.incomingLinks,
17328
+ outgoingLinks: p.outgoingLinks,
17329
+ depth: p.depth,
17330
+ indexedAt: p.indexedAt
17331
+ }
17769
17332
  }));
17770
- await this.client.batch(stmts);
17333
+ await index.upsert(docs);
17771
17334
  }
17772
17335
  }
17773
17336
  async getPage(url, scope) {
17774
- await this.ensurePages();
17775
- const rs = await this.client.execute({
17776
- sql: `SELECT * FROM pages WHERE project_id = ? AND scope_name = ? AND url = ?`,
17777
- args: [scope.projectId, scope.scopeName, url]
17778
- });
17779
- if (rs.rows.length === 0) return null;
17780
- const row = rs.rows[0];
17781
- return {
17782
- url: row.url,
17783
- title: row.title,
17784
- markdown: row.markdown,
17785
- projectId: row.project_id,
17786
- scopeName: row.scope_name,
17787
- routeFile: row.route_file,
17788
- routeResolution: row.route_resolution,
17789
- incomingLinks: row.incoming_links,
17790
- outgoingLinks: row.outgoing_links,
17791
- depth: row.depth,
17792
- tags: JSON.parse(row.tags || "[]"),
17793
- indexedAt: row.indexed_at
17794
- };
17337
+ const index = this.pageIndex(scope);
17338
+ try {
17339
+ const results = await index.fetch([url]);
17340
+ const doc = results[0];
17341
+ if (!doc) return null;
17342
+ return {
17343
+ url: doc.content.url,
17344
+ title: doc.content.title,
17345
+ markdown: doc.metadata.markdown,
17346
+ projectId: doc.metadata.projectId,
17347
+ scopeName: doc.metadata.scopeName,
17348
+ routeFile: doc.metadata.routeFile,
17349
+ routeResolution: doc.metadata.routeResolution,
17350
+ incomingLinks: doc.metadata.incomingLinks,
17351
+ outgoingLinks: doc.metadata.outgoingLinks,
17352
+ depth: doc.metadata.depth,
17353
+ tags: doc.content.tags ? doc.content.tags.split(",").filter(Boolean) : [],
17354
+ indexedAt: doc.metadata.indexedAt,
17355
+ summary: doc.content.summary || void 0,
17356
+ description: doc.content.description || void 0,
17357
+ keywords: doc.content.keywords ? doc.content.keywords.split(",").filter(Boolean) : void 0
17358
+ };
17359
+ } catch {
17360
+ return null;
17361
+ }
17795
17362
  }
17796
17363
  async deletePages(scope) {
17797
- await this.ensurePages();
17798
- await this.client.execute({
17799
- sql: `DELETE FROM pages WHERE project_id = ? AND scope_name = ?`,
17800
- args: [scope.projectId, scope.scopeName]
17801
- });
17802
- }
17803
- async getScopeModelId(scope) {
17804
- await this.ensureRegistry();
17805
- const rs = await this.client.execute({
17806
- sql: `SELECT model_id FROM registry WHERE project_id = ? AND scope_name = ?`,
17807
- args: [scope.projectId, scope.scopeName]
17808
- });
17809
- if (rs.rows.length === 0) return null;
17810
- return rs.rows[0].model_id;
17364
+ try {
17365
+ const index = this.pageIndex(scope);
17366
+ await index.reset();
17367
+ } catch {
17368
+ }
17811
17369
  }
17812
17370
  async health() {
17813
17371
  try {
17814
- await this.client.execute("SELECT 1");
17372
+ await this.client.info();
17815
17373
  return { ok: true };
17816
17374
  } catch (error) {
17817
17375
  return {
@@ -17820,40 +17378,64 @@ var TursoVectorStore = class {
17820
17378
  };
17821
17379
  }
17822
17380
  }
17381
+ async dropAllIndexes(projectId) {
17382
+ const allIndexes = await this.client.listIndexes();
17383
+ const prefix = `${projectId}--`;
17384
+ for (const name of allIndexes) {
17385
+ if (name.startsWith(prefix)) {
17386
+ try {
17387
+ const index = this.client.index(name);
17388
+ await index.deleteIndex();
17389
+ } catch {
17390
+ }
17391
+ }
17392
+ }
17393
+ }
17823
17394
  };
17824
17395
 
17825
17396
  // src/vector/factory.ts
17826
- async function createVectorStore(config, cwd) {
17827
- const turso = config.vector.turso;
17828
- const remoteUrl = turso.url ?? process.env[turso.urlEnv];
17829
- if (remoteUrl) {
17830
- const { createClient: createClient2 } = await import('@libsql/client/http');
17831
- const authToken = turso.authToken ?? process.env[turso.authTokenEnv];
17832
- const client2 = createClient2({
17833
- url: remoteUrl,
17834
- authToken
17835
- });
17836
- return new TursoVectorStore({
17837
- client: client2,
17838
- dimension: config.vector.dimension
17839
- });
17840
- }
17841
- if (isServerless()) {
17397
+ async function createUpstashStore(config) {
17398
+ const url = config.upstash.url ?? process.env[config.upstash.urlEnv];
17399
+ const token = config.upstash.token ?? process.env[config.upstash.tokenEnv];
17400
+ if (!url || !token) {
17842
17401
  throw new SearchSocketError(
17843
17402
  "VECTOR_BACKEND_UNAVAILABLE",
17844
- `No remote vector database URL found (checked vector.turso.url and env var "${turso.urlEnv}"). Local SQLite storage is not available in serverless environments. Set ${turso.urlEnv} or pass vector.turso.url directly.`
17403
+ `Missing Upstash Search credentials. Set ${config.upstash.urlEnv} and ${config.upstash.tokenEnv} environment variables, or pass upstash.url and upstash.token in your config.`
17845
17404
  );
17846
17405
  }
17847
- const { createClient } = await import('@libsql/client');
17848
- const localPath = path.resolve(cwd, turso.localPath);
17849
- fs.mkdirSync(path.dirname(localPath), { recursive: true });
17850
- const client = createClient({
17851
- url: `file:${localPath}`
17852
- });
17853
- return new TursoVectorStore({
17854
- client,
17855
- dimension: config.vector.dimension
17856
- });
17406
+ const { Search } = await import('@upstash/search');
17407
+ const client = new Search({ url, token });
17408
+ return new UpstashSearchStore({ client });
17409
+ }
17410
+
17411
+ // src/utils/pattern.ts
17412
+ function matchUrlPattern(url, pattern) {
17413
+ const norm = (p) => p !== "/" && p.endsWith("/") ? p.slice(0, -1) : p;
17414
+ const normalizedUrl = norm(url);
17415
+ const normalizedPattern = norm(pattern);
17416
+ if (normalizedPattern.endsWith("/**")) {
17417
+ const prefix = normalizedPattern.slice(0, -3);
17418
+ if (prefix === "") {
17419
+ return true;
17420
+ }
17421
+ return normalizedUrl === prefix || normalizedUrl.startsWith(prefix + "/");
17422
+ }
17423
+ if (normalizedPattern.endsWith("/*")) {
17424
+ const prefix = normalizedPattern.slice(0, -2);
17425
+ if (prefix === "") {
17426
+ return normalizedUrl !== "/" && !normalizedUrl.slice(1).includes("/");
17427
+ }
17428
+ if (!normalizedUrl.startsWith(prefix + "/")) return false;
17429
+ const rest = normalizedUrl.slice(prefix.length + 1);
17430
+ return rest.length > 0 && !rest.includes("/");
17431
+ }
17432
+ return normalizedUrl === normalizedPattern;
17433
+ }
17434
+ function matchUrlPatterns(url, patterns) {
17435
+ for (const pattern of patterns) {
17436
+ if (matchUrlPattern(url, pattern)) return true;
17437
+ }
17438
+ return false;
17857
17439
  }
17858
17440
 
17859
17441
  // src/search/ranking.ts
@@ -17863,7 +17445,12 @@ function nonNegativeOrZero(value) {
17863
17445
  }
17864
17446
  return Math.max(0, value);
17865
17447
  }
17866
- function rankHits(hits, config) {
17448
+ function normalizeForTitleMatch(text) {
17449
+ return text.toLowerCase().replace(/[^a-z0-9\s]/g, "").replace(/\s+/g, " ").trim();
17450
+ }
17451
+ function rankHits(hits, config, query) {
17452
+ const normalizedQuery = query ? normalizeForTitleMatch(query) : "";
17453
+ const titleMatchWeight = config.ranking.weights.titleMatch;
17867
17454
  return hits.map((hit) => {
17868
17455
  let score = Number.isFinite(hit.score) ? hit.score : Number.NEGATIVE_INFINITY;
17869
17456
  if (config.ranking.enableIncomingLinkBoost) {
@@ -17874,6 +17461,12 @@ function rankHits(hits, config) {
17874
17461
  const depthBoost = 1 / (1 + nonNegativeOrZero(hit.metadata.depth));
17875
17462
  score += depthBoost * config.ranking.weights.depth;
17876
17463
  }
17464
+ if (normalizedQuery && titleMatchWeight > 0) {
17465
+ const normalizedTitle = normalizeForTitleMatch(hit.metadata.title);
17466
+ if (normalizedQuery.length > 0 && normalizedTitle.length > 0 && (normalizedTitle.includes(normalizedQuery) || normalizedQuery.includes(normalizedTitle))) {
17467
+ score += titleMatchWeight;
17468
+ }
17469
+ }
17877
17470
  return {
17878
17471
  hit,
17879
17472
  finalScore: Number.isFinite(score) ? score : Number.NEGATIVE_INFINITY
@@ -17883,22 +17476,36 @@ function rankHits(hits, config) {
17883
17476
  return Number.isNaN(delta) ? 0 : delta;
17884
17477
  });
17885
17478
  }
17886
- function findPageWeight(url, pageWeights) {
17887
- const norm = (p) => p !== "/" && p.endsWith("/") ? p.slice(0, -1) : p;
17888
- const normalizedUrl = norm(url);
17889
- for (const [pattern, weight] of Object.entries(pageWeights)) {
17890
- if (norm(pattern) === normalizedUrl) {
17891
- return weight;
17479
+ function trimByScoreGap(results, config) {
17480
+ if (results.length === 0) return results;
17481
+ const threshold = config.ranking.scoreGapThreshold;
17482
+ const minScore = config.ranking.minScore;
17483
+ if (minScore > 0 && results.length > 0) {
17484
+ const sortedScores = results.map((r) => r.pageScore).sort((a, b) => a - b);
17485
+ const mid = Math.floor(sortedScores.length / 2);
17486
+ const median = sortedScores.length % 2 === 0 ? (sortedScores[mid - 1] + sortedScores[mid]) / 2 : sortedScores[mid];
17487
+ if (median < minScore) return [];
17488
+ }
17489
+ if (threshold > 0 && results.length > 1) {
17490
+ for (let i = 1; i < results.length; i++) {
17491
+ const prev = results[i - 1].pageScore;
17492
+ const current = results[i].pageScore;
17493
+ if (prev > 0) {
17494
+ const gap = (prev - current) / prev;
17495
+ if (gap >= threshold) {
17496
+ return results.slice(0, i);
17497
+ }
17498
+ }
17892
17499
  }
17893
17500
  }
17894
- let bestPrefix = "";
17501
+ return results;
17502
+ }
17503
+ function findPageWeight(url, pageWeights) {
17504
+ let bestPattern = "";
17895
17505
  let bestWeight = 1;
17896
17506
  for (const [pattern, weight] of Object.entries(pageWeights)) {
17897
- const normalizedPattern = norm(pattern);
17898
- if (normalizedPattern === "/") continue;
17899
- const prefix = `${normalizedPattern}/`;
17900
- if (normalizedUrl.startsWith(prefix) && prefix.length > bestPrefix.length) {
17901
- bestPrefix = prefix;
17507
+ if (matchUrlPattern(url, pattern) && pattern.length > bestPattern.length) {
17508
+ bestPattern = pattern;
17902
17509
  bestWeight = weight;
17903
17510
  }
17904
17511
  }
@@ -17947,6 +17554,61 @@ function aggregateByPage(ranked, config) {
17947
17554
  return Number.isNaN(delta) ? 0 : delta;
17948
17555
  });
17949
17556
  }
17557
+ function mergePageAndChunkResults(pageHits, rankedChunks, config) {
17558
+ if (pageHits.length === 0) return rankedChunks;
17559
+ const w = config.search.pageSearchWeight;
17560
+ const pageScoreMap = /* @__PURE__ */ new Map();
17561
+ for (const ph of pageHits) {
17562
+ pageScoreMap.set(ph.url, ph);
17563
+ }
17564
+ const pagesWithChunks = /* @__PURE__ */ new Set();
17565
+ const merged = rankedChunks.map((ranked) => {
17566
+ const url = ranked.hit.metadata.url;
17567
+ const pageHit = pageScoreMap.get(url);
17568
+ if (pageHit) {
17569
+ pagesWithChunks.add(url);
17570
+ const blended = (1 - w) * ranked.finalScore + w * pageHit.score;
17571
+ return {
17572
+ hit: ranked.hit,
17573
+ finalScore: Number.isFinite(blended) ? blended : ranked.finalScore
17574
+ };
17575
+ }
17576
+ return ranked;
17577
+ });
17578
+ for (const [url, pageHit] of pageScoreMap) {
17579
+ if (pagesWithChunks.has(url)) continue;
17580
+ const syntheticScore = pageHit.score * w;
17581
+ const syntheticHit = {
17582
+ id: `page:${url}`,
17583
+ score: pageHit.score,
17584
+ metadata: {
17585
+ projectId: "",
17586
+ scopeName: "",
17587
+ url: pageHit.url,
17588
+ path: pageHit.url,
17589
+ title: pageHit.title,
17590
+ sectionTitle: "",
17591
+ headingPath: [],
17592
+ snippet: pageHit.description || pageHit.title,
17593
+ chunkText: pageHit.description || pageHit.title,
17594
+ ordinal: 0,
17595
+ contentHash: "",
17596
+ depth: pageHit.depth,
17597
+ incomingLinks: pageHit.incomingLinks,
17598
+ routeFile: pageHit.routeFile,
17599
+ tags: pageHit.tags
17600
+ }
17601
+ };
17602
+ merged.push({
17603
+ hit: syntheticHit,
17604
+ finalScore: Number.isFinite(syntheticScore) ? syntheticScore : 0
17605
+ });
17606
+ }
17607
+ return merged.sort((a, b) => {
17608
+ const delta = b.finalScore - a.finalScore;
17609
+ return Number.isNaN(delta) ? 0 : delta;
17610
+ });
17611
+ }
17950
17612
 
17951
17613
  // src/search/engine.ts
17952
17614
  var requestSchema = z.object({
@@ -17955,34 +17617,25 @@ var requestSchema = z.object({
17955
17617
  scope: z.string().optional(),
17956
17618
  pathPrefix: z.string().optional(),
17957
17619
  tags: z.array(z.string()).optional(),
17958
- rerank: z.boolean().optional(),
17959
17620
  groupBy: z.enum(["page", "chunk"]).optional()
17960
17621
  });
17961
17622
  var SearchEngine = class _SearchEngine {
17962
17623
  cwd;
17963
17624
  config;
17964
- embeddings;
17965
- vectorStore;
17966
- reranker;
17625
+ store;
17967
17626
  constructor(options) {
17968
17627
  this.cwd = options.cwd;
17969
17628
  this.config = options.config;
17970
- this.embeddings = options.embeddings;
17971
- this.vectorStore = options.vectorStore;
17972
- this.reranker = options.reranker;
17629
+ this.store = options.store;
17973
17630
  }
17974
17631
  static async create(options = {}) {
17975
17632
  const cwd = path.resolve(options.cwd ?? process.cwd());
17976
17633
  const config = options.config ?? await loadConfig({ cwd, configPath: options.configPath });
17977
- const embeddings = options.embeddingsProvider ?? createEmbeddingsProvider(config);
17978
- const vectorStore = options.vectorStore ?? await createVectorStore(config, cwd);
17979
- const reranker = options.reranker === void 0 ? createReranker(config) : options.reranker;
17634
+ const store = options.store ?? await createUpstashStore(config);
17980
17635
  return new _SearchEngine({
17981
17636
  cwd,
17982
17637
  config,
17983
- embeddings,
17984
- vectorStore,
17985
- reranker
17638
+ store
17986
17639
  });
17987
17640
  }
17988
17641
  getConfig() {
@@ -17996,99 +17649,130 @@ var SearchEngine = class _SearchEngine {
17996
17649
  const input = parsed.data;
17997
17650
  const totalStart = process.hrtime.bigint();
17998
17651
  const resolvedScope = resolveScope(this.config, input.scope);
17999
- await this.assertModelCompatibility(resolvedScope);
18000
17652
  const topK = input.topK ?? 10;
18001
- const wantsRerank = Boolean(input.rerank);
18002
17653
  const groupByPage = (input.groupBy ?? "page") === "page";
18003
17654
  const candidateK = groupByPage ? Math.max(topK * 10, 50) : Math.max(50, topK);
18004
- const embedStart = process.hrtime.bigint();
18005
- const queryEmbeddings = await this.embeddings.embedTexts([input.q], this.config.embeddings.model, "retrieval.query");
18006
- const queryVector = queryEmbeddings[0];
18007
- if (!queryVector || queryVector.length === 0 || queryVector.some((value) => !Number.isFinite(value))) {
18008
- throw new SearchSocketError("VECTOR_BACKEND_UNAVAILABLE", "Unable to create query embedding.");
17655
+ const filterParts = [];
17656
+ if (input.pathPrefix) {
17657
+ const prefix = input.pathPrefix.startsWith("/") ? input.pathPrefix : `/${input.pathPrefix}`;
17658
+ filterParts.push(`url GLOB '${prefix}*'`);
18009
17659
  }
18010
- const embedMs = hrTimeMs(embedStart);
18011
- const vectorStart = process.hrtime.bigint();
18012
- const hits = await this.vectorStore.query(
18013
- queryVector,
18014
- {
18015
- topK: candidateK,
18016
- pathPrefix: input.pathPrefix,
18017
- tags: input.tags
18018
- },
18019
- resolvedScope
18020
- );
18021
- const vectorMs = hrTimeMs(vectorStart);
18022
- const ranked = rankHits(hits, this.config);
18023
- let usedRerank = false;
18024
- let rerankMs = 0;
18025
- let ordered = ranked;
18026
- if (wantsRerank) {
18027
- const rerankStart = process.hrtime.bigint();
18028
- ordered = await this.rerankHits(input.q, ranked, topK);
18029
- rerankMs = hrTimeMs(rerankStart);
18030
- usedRerank = true;
17660
+ if (input.tags && input.tags.length > 0) {
17661
+ for (const tag of input.tags) {
17662
+ filterParts.push(`tags GLOB '*${tag}*'`);
17663
+ }
18031
17664
  }
18032
- let results;
18033
- const minScore = this.config.ranking.minScore;
17665
+ const filter = filterParts.length > 0 ? filterParts.join(" AND ") : void 0;
17666
+ const useDualSearch = this.config.search.dualSearch && groupByPage;
17667
+ const searchStart = process.hrtime.bigint();
17668
+ let ranked;
17669
+ if (useDualSearch) {
17670
+ const chunkLimit = Math.max(topK * 10, 100);
17671
+ const pageLimit = 20;
17672
+ const [pageHits, chunkHits] = await Promise.all([
17673
+ this.store.searchPages(
17674
+ input.q,
17675
+ {
17676
+ limit: pageLimit,
17677
+ semanticWeight: this.config.search.semanticWeight,
17678
+ inputEnrichment: this.config.search.inputEnrichment,
17679
+ filter
17680
+ },
17681
+ resolvedScope
17682
+ ),
17683
+ this.store.search(
17684
+ input.q,
17685
+ {
17686
+ limit: chunkLimit,
17687
+ semanticWeight: this.config.search.semanticWeight,
17688
+ inputEnrichment: this.config.search.inputEnrichment,
17689
+ reranking: false,
17690
+ filter
17691
+ },
17692
+ resolvedScope
17693
+ )
17694
+ ]);
17695
+ const rankedChunks = rankHits(chunkHits, this.config, input.q);
17696
+ ranked = mergePageAndChunkResults(pageHits, rankedChunks, this.config);
17697
+ } else {
17698
+ const hits = await this.store.search(
17699
+ input.q,
17700
+ {
17701
+ limit: candidateK,
17702
+ semanticWeight: this.config.search.semanticWeight,
17703
+ inputEnrichment: this.config.search.inputEnrichment,
17704
+ reranking: this.config.search.reranking,
17705
+ filter
17706
+ },
17707
+ resolvedScope
17708
+ );
17709
+ ranked = rankHits(hits, this.config, input.q);
17710
+ }
17711
+ const searchMs = hrTimeMs(searchStart);
17712
+ const results = this.buildResults(ranked, topK, groupByPage, input.q);
17713
+ return {
17714
+ q: input.q,
17715
+ scope: resolvedScope.scopeName,
17716
+ results,
17717
+ meta: {
17718
+ timingsMs: {
17719
+ search: Math.round(searchMs),
17720
+ total: Math.round(hrTimeMs(totalStart))
17721
+ }
17722
+ }
17723
+ };
17724
+ }
17725
+ ensureSnippet(hit) {
17726
+ const snippet = hit.hit.metadata.snippet;
17727
+ if (snippet && snippet.length >= 30) return snippet;
17728
+ const chunkText = hit.hit.metadata.chunkText;
17729
+ if (chunkText) return toSnippet(chunkText);
17730
+ return snippet || "";
17731
+ }
17732
+ buildResults(ordered, topK, groupByPage, _query) {
18034
17733
  if (groupByPage) {
18035
17734
  let pages = aggregateByPage(ordered, this.config);
18036
- if (minScore > 0) {
18037
- pages = pages.filter((p) => p.pageScore >= minScore);
18038
- }
17735
+ pages = trimByScoreGap(pages, this.config);
18039
17736
  const minRatio = this.config.ranking.minChunkScoreRatio;
18040
- results = pages.slice(0, topK).map((page) => {
17737
+ return pages.slice(0, topK).map((page) => {
18041
17738
  const bestScore = page.bestChunk.finalScore;
18042
- const minScore2 = Number.isFinite(bestScore) ? bestScore * minRatio : Number.NEGATIVE_INFINITY;
18043
- const meaningful = page.matchingChunks.filter((c) => c.finalScore >= minScore2).slice(0, 5);
17739
+ const minChunkScore = Number.isFinite(bestScore) ? bestScore * minRatio : Number.NEGATIVE_INFINITY;
17740
+ const meaningful = page.matchingChunks.filter((c) => c.finalScore >= minChunkScore).slice(0, 5);
18044
17741
  return {
18045
17742
  url: page.url,
18046
17743
  title: page.title,
18047
17744
  sectionTitle: page.bestChunk.hit.metadata.sectionTitle || void 0,
18048
- snippet: page.bestChunk.hit.metadata.snippet,
17745
+ snippet: this.ensureSnippet(page.bestChunk),
18049
17746
  score: Number(page.pageScore.toFixed(6)),
18050
17747
  routeFile: page.routeFile,
18051
17748
  chunks: meaningful.length > 1 ? meaningful.map((c) => ({
18052
17749
  sectionTitle: c.hit.metadata.sectionTitle || void 0,
18053
- snippet: c.hit.metadata.snippet,
17750
+ snippet: this.ensureSnippet(c),
18054
17751
  headingPath: c.hit.metadata.headingPath,
18055
17752
  score: Number(c.finalScore.toFixed(6))
18056
17753
  })) : void 0
18057
17754
  };
18058
17755
  });
18059
17756
  } else {
17757
+ let filtered = ordered;
17758
+ const minScore = this.config.ranking.minScore;
18060
17759
  if (minScore > 0) {
18061
- ordered = ordered.filter((entry) => entry.finalScore >= minScore);
17760
+ filtered = ordered.filter((entry) => entry.finalScore >= minScore);
18062
17761
  }
18063
- results = ordered.slice(0, topK).map(({ hit, finalScore }) => ({
17762
+ return filtered.slice(0, topK).map(({ hit, finalScore }) => ({
18064
17763
  url: hit.metadata.url,
18065
17764
  title: hit.metadata.title,
18066
17765
  sectionTitle: hit.metadata.sectionTitle || void 0,
18067
- snippet: hit.metadata.snippet,
17766
+ snippet: this.ensureSnippet({ hit, finalScore }),
18068
17767
  score: Number(finalScore.toFixed(6)),
18069
17768
  routeFile: hit.metadata.routeFile
18070
17769
  }));
18071
17770
  }
18072
- return {
18073
- q: input.q,
18074
- scope: resolvedScope.scopeName,
18075
- results,
18076
- meta: {
18077
- timingsMs: {
18078
- embed: Math.round(embedMs),
18079
- vector: Math.round(vectorMs),
18080
- rerank: Math.round(rerankMs),
18081
- total: Math.round(hrTimeMs(totalStart))
18082
- },
18083
- usedRerank,
18084
- modelId: this.config.embeddings.model
18085
- }
18086
- };
18087
17771
  }
18088
17772
  async getPage(pathOrUrl, scope) {
18089
17773
  const resolvedScope = resolveScope(this.config, scope);
18090
17774
  const urlPath = this.resolveInputPath(pathOrUrl);
18091
- const page = await this.vectorStore.getPage(urlPath, resolvedScope);
17775
+ const page = await this.store.getPage(urlPath, resolvedScope);
18092
17776
  if (!page) {
18093
17777
  throw new SearchSocketError("INVALID_REQUEST", `Indexed page not found for ${urlPath}`, 404);
18094
17778
  }
@@ -18109,7 +17793,7 @@ var SearchEngine = class _SearchEngine {
18109
17793
  };
18110
17794
  }
18111
17795
  async health() {
18112
- return this.vectorStore.health();
17796
+ return this.store.health();
18113
17797
  }
18114
17798
  resolveInputPath(pathOrUrl) {
18115
17799
  try {
@@ -18121,90 +17805,6 @@ var SearchEngine = class _SearchEngine {
18121
17805
  const withoutQueryOrHash = pathOrUrl.split(/[?#]/)[0] ?? pathOrUrl;
18122
17806
  return normalizeUrlPath(withoutQueryOrHash);
18123
17807
  }
18124
- async assertModelCompatibility(scope) {
18125
- const modelId = await this.vectorStore.getScopeModelId(scope);
18126
- if (modelId && modelId !== this.config.embeddings.model) {
18127
- throw new SearchSocketError(
18128
- "EMBEDDING_MODEL_MISMATCH",
18129
- `Scope ${scope.scopeName} was indexed with ${modelId}. Current config uses ${this.config.embeddings.model}. Re-index with --force.`
18130
- );
18131
- }
18132
- }
18133
- async rerankHits(query, ranked, topK) {
18134
- if (!this.config.rerank.enabled) {
18135
- throw new SearchSocketError(
18136
- "INVALID_REQUEST",
18137
- "rerank=true requested but rerank.enabled is not set to true.",
18138
- 400
18139
- );
18140
- }
18141
- if (!this.reranker) {
18142
- throw new SearchSocketError(
18143
- "CONFIG_MISSING",
18144
- `rerank=true requested but ${this.config.embeddings.apiKeyEnv} is not set.`,
18145
- 400
18146
- );
18147
- }
18148
- const pageGroups = /* @__PURE__ */ new Map();
18149
- for (const entry of ranked) {
18150
- const url = entry.hit.metadata.url;
18151
- const group = pageGroups.get(url);
18152
- if (group) group.push(entry);
18153
- else pageGroups.set(url, [entry]);
18154
- }
18155
- const MAX_CHUNKS_PER_PAGE = 5;
18156
- const MIN_CHUNKS_PER_PAGE = 1;
18157
- const MIN_CHUNK_SCORE_RATIO = 0.5;
18158
- const MAX_DOC_CHARS = 2e3;
18159
- const pageCandidates = [];
18160
- for (const [url, chunks] of pageGroups) {
18161
- const byScore = [...chunks].sort((a, b) => b.finalScore - a.finalScore);
18162
- const bestScore = byScore[0].finalScore;
18163
- const scoreFloor = Number.isFinite(bestScore) ? bestScore * MIN_CHUNK_SCORE_RATIO : Number.NEGATIVE_INFINITY;
18164
- const selected = byScore.filter(
18165
- (c, i) => i < MIN_CHUNKS_PER_PAGE || c.finalScore >= scoreFloor
18166
- ).slice(0, MAX_CHUNKS_PER_PAGE);
18167
- selected.sort((a, b) => (a.hit.metadata.ordinal ?? 0) - (b.hit.metadata.ordinal ?? 0));
18168
- const first = selected[0].hit.metadata;
18169
- const parts = [first.title];
18170
- if (first.description) {
18171
- parts.push(first.description);
18172
- }
18173
- if (first.keywords && first.keywords.length > 0) {
18174
- parts.push(first.keywords.join(", "));
18175
- }
18176
- const body = selected.map((c) => c.hit.metadata.chunkText || c.hit.metadata.snippet).join("\n\n");
18177
- parts.push(body);
18178
- let text = parts.join("\n\n");
18179
- if (text.length > MAX_DOC_CHARS) {
18180
- text = text.slice(0, MAX_DOC_CHARS);
18181
- }
18182
- pageCandidates.push({ id: url, text });
18183
- }
18184
- const maxCandidates = Math.max(topK, this.config.rerank.topN);
18185
- const cappedCandidates = pageCandidates.slice(0, maxCandidates);
18186
- const reranked = await this.reranker.rerank(
18187
- query,
18188
- cappedCandidates,
18189
- maxCandidates
18190
- );
18191
- const scoreByUrl = new Map(reranked.map((e) => [e.id, e.score]));
18192
- return ranked.map((entry) => {
18193
- const pageScore = scoreByUrl.get(entry.hit.metadata.url);
18194
- const base = Number.isFinite(entry.finalScore) ? entry.finalScore : Number.NEGATIVE_INFINITY;
18195
- if (pageScore === void 0 || !Number.isFinite(pageScore)) {
18196
- return { ...entry, finalScore: base };
18197
- }
18198
- const combined = pageScore * this.config.ranking.weights.rerank + base * 1e-3;
18199
- return {
18200
- ...entry,
18201
- finalScore: Number.isFinite(combined) ? combined : base
18202
- };
18203
- }).sort((a, b) => {
18204
- const delta = b.finalScore - a.finalScore;
18205
- return Number.isNaN(delta) ? 0 : delta;
18206
- });
18207
- }
18208
17808
  };
18209
17809
 
18210
17810
  // src/sveltekit/handle.ts
@@ -18358,7 +17958,8 @@ function searchsocketHandle(options = {}) {
18358
17958
  throw new SearchSocketError("INVALID_REQUEST", "Malformed JSON request body", 400);
18359
17959
  }
18360
17960
  const engine = await getEngine();
18361
- const result = await engine.search(body);
17961
+ const searchRequest = body;
17962
+ const result = await engine.search(searchRequest);
18362
17963
  return withCors(
18363
17964
  new Response(JSON.stringify(result), {
18364
17965
  status: 200,
@@ -18418,9 +18019,8 @@ function withCors(response, request, config) {
18418
18019
  }
18419
18020
  function ensureStateDirs(cwd, stateDir, scope) {
18420
18021
  const statePath = path.resolve(cwd, stateDir);
18421
- const pagesPath = path.join(statePath, "pages", scope.scopeName);
18422
- fs.mkdirSync(pagesPath, { recursive: true });
18423
- return { statePath, pagesPath };
18022
+ fs.mkdirSync(statePath, { recursive: true });
18023
+ return { statePath };
18424
18024
  }
18425
18025
  function sha1(input) {
18426
18026
  return createHash("sha1").update(input).digest("hex");
@@ -18670,7 +18270,7 @@ function buildEmbeddingText(chunk, prependTitle) {
18670
18270
 
18671
18271
  ${chunk.chunkText}`;
18672
18272
  }
18673
- function chunkMirrorPage(page, config, scope) {
18273
+ function chunkPage(page, config, scope) {
18674
18274
  const sections = parseHeadingSections(page.markdown, config.chunking.headingPathDepth);
18675
18275
  const rawChunks = sections.flatMap((section) => splitSection(section, config.chunking));
18676
18276
  const chunks = [];
@@ -19587,6 +19187,17 @@ function extractFromHtml(url, html, config) {
19587
19187
  if ($(`[${config.extract.noindexAttr}]`).length > 0) {
19588
19188
  return null;
19589
19189
  }
19190
+ const weightRaw = $("meta[name='searchsocket-weight']").attr("content")?.trim();
19191
+ let weight;
19192
+ if (weightRaw !== void 0) {
19193
+ const parsed = Number(weightRaw);
19194
+ if (Number.isFinite(parsed) && parsed >= 0) {
19195
+ weight = parsed;
19196
+ }
19197
+ }
19198
+ if (weight === 0) {
19199
+ return null;
19200
+ }
19590
19201
  const description = $("meta[name='description']").attr("content")?.trim() || $("meta[property='og:description']").attr("content")?.trim() || void 0;
19591
19202
  const keywordsRaw = $("meta[name='keywords']").attr("content")?.trim();
19592
19203
  const keywords = keywordsRaw ? keywordsRaw.split(",").map((k) => k.trim()).filter(Boolean) : void 0;
@@ -19642,7 +19253,8 @@ function extractFromHtml(url, html, config) {
19642
19253
  noindex: false,
19643
19254
  tags,
19644
19255
  description,
19645
- keywords
19256
+ keywords,
19257
+ weight
19646
19258
  };
19647
19259
  }
19648
19260
  function extractFromMarkdown(url, markdown, title) {
@@ -19655,6 +19267,14 @@ function extractFromMarkdown(url, markdown, title) {
19655
19267
  if (frontmatter.noindex === true || searchsocketMeta?.noindex === true) {
19656
19268
  return null;
19657
19269
  }
19270
+ let mdWeight;
19271
+ const rawWeight = searchsocketMeta?.weight ?? frontmatter.searchsocketWeight;
19272
+ if (typeof rawWeight === "number" && Number.isFinite(rawWeight) && rawWeight >= 0) {
19273
+ mdWeight = rawWeight;
19274
+ }
19275
+ if (mdWeight === 0) {
19276
+ return null;
19277
+ }
19658
19278
  const content = parsed.content;
19659
19279
  const normalized = normalizeMarkdown(content);
19660
19280
  if (!normalizeText(normalized)) {
@@ -19677,56 +19297,10 @@ function extractFromMarkdown(url, markdown, title) {
19677
19297
  noindex: false,
19678
19298
  tags: normalizeUrlPath(url).split("/").filter(Boolean).slice(0, 1),
19679
19299
  description: fmDescription,
19680
- keywords: fmKeywords
19300
+ keywords: fmKeywords,
19301
+ weight: mdWeight
19681
19302
  };
19682
19303
  }
19683
- function yamlString(value) {
19684
- return JSON.stringify(value);
19685
- }
19686
- function yamlArray(values) {
19687
- return `[${values.map((v) => JSON.stringify(v)).join(", ")}]`;
19688
- }
19689
- function buildMirrorMarkdown(page) {
19690
- const frontmatterLines = [
19691
- "---",
19692
- `url: ${yamlString(page.url)}`,
19693
- `title: ${yamlString(page.title)}`,
19694
- `scope: ${yamlString(page.scope)}`,
19695
- `routeFile: ${yamlString(page.routeFile)}`,
19696
- `routeResolution: ${yamlString(page.routeResolution)}`,
19697
- `generatedAt: ${yamlString(page.generatedAt)}`,
19698
- `incomingLinks: ${page.incomingLinks}`,
19699
- `outgoingLinks: ${page.outgoingLinks}`,
19700
- `depth: ${page.depth}`,
19701
- `tags: ${yamlArray(page.tags)}`,
19702
- "---",
19703
- ""
19704
- ];
19705
- return `${frontmatterLines.join("\n")}${normalizeMarkdown(page.markdown)}`;
19706
- }
19707
- function stripGeneratedAt(content) {
19708
- return content.replace(/^generatedAt: .*$/m, "");
19709
- }
19710
- async function writeMirrorPage(statePath, scope, page) {
19711
- const relative = urlPathToMirrorRelative(page.url);
19712
- const outputPath = path.join(statePath, "pages", scope.scopeName, relative);
19713
- await fs4.mkdir(path.dirname(outputPath), { recursive: true });
19714
- const newContent = buildMirrorMarkdown(page);
19715
- try {
19716
- const existing = await fs4.readFile(outputPath, "utf8");
19717
- if (stripGeneratedAt(existing) === stripGeneratedAt(newContent)) {
19718
- return outputPath;
19719
- }
19720
- } catch {
19721
- }
19722
- await fs4.writeFile(outputPath, newContent, "utf8");
19723
- return outputPath;
19724
- }
19725
- async function cleanMirrorForScope(statePath, scope) {
19726
- const target = path.join(statePath, "pages", scope.scopeName);
19727
- await fs4.rm(target, { recursive: true, force: true });
19728
- await fs4.mkdir(target, { recursive: true });
19729
- }
19730
19304
  function segmentToRegex(segment) {
19731
19305
  if (segment.startsWith("(") && segment.endsWith(")")) {
19732
19306
  return { regex: "", score: 0 };
@@ -19887,7 +19461,7 @@ async function parseManifest(cwd, outputDir) {
19887
19461
  const manifestPath = path.resolve(cwd, outputDir, "server", "manifest-full.js");
19888
19462
  let content;
19889
19463
  try {
19890
- content = await fs4.readFile(manifestPath, "utf8");
19464
+ content = await fs3.readFile(manifestPath, "utf8");
19891
19465
  } catch {
19892
19466
  throw new SearchSocketError(
19893
19467
  "BUILD_MANIFEST_NOT_FOUND",
@@ -19946,15 +19520,7 @@ function expandDynamicUrl(url, value) {
19946
19520
  return url.replace(/\[\[?\.\.\.[^\]]+\]?\]|\[\[[^\]]+\]\]|\[[^\]]+\]/g, value);
19947
19521
  }
19948
19522
  function isExcluded(url, patterns) {
19949
- for (const pattern of patterns) {
19950
- if (pattern.endsWith("/*")) {
19951
- const prefix = pattern.slice(0, -1);
19952
- if (url.startsWith(prefix) || url === prefix.slice(0, -1)) return true;
19953
- } else if (url === pattern) {
19954
- return true;
19955
- }
19956
- }
19957
- return false;
19523
+ return matchUrlPatterns(url, patterns);
19958
19524
  }
19959
19525
  function findFreePort() {
19960
19526
  return new Promise((resolve, reject) => {
@@ -20068,7 +19634,7 @@ async function discoverPages(server, buildConfig, pipelineMaxPages) {
20068
19634
  const visited = /* @__PURE__ */ new Set();
20069
19635
  const pages = [];
20070
19636
  const queue = [];
20071
- const limit = pLimit2(8);
19637
+ const limit = pLimit(8);
20072
19638
  for (const seed of seedUrls) {
20073
19639
  const normalized = normalizeUrlPath(seed);
20074
19640
  if (!visited.has(normalized) && !isExcluded(normalized, exclude)) {
@@ -20150,7 +19716,7 @@ async function loadBuildPages(cwd, config, maxPages) {
20150
19716
  const selected = typeof maxCount === "number" ? expanded.slice(0, maxCount) : expanded;
20151
19717
  const server = await startPreviewServer(cwd, { previewTimeout: buildConfig.previewTimeout }, logger);
20152
19718
  try {
20153
- const concurrencyLimit = pLimit2(8);
19719
+ const concurrencyLimit = pLimit(8);
20154
19720
  const results = await Promise.allSettled(
20155
19721
  selected.map(
20156
19722
  (route) => concurrencyLimit(async () => {
@@ -20224,7 +19790,7 @@ async function loadContentFilesPages(cwd, config, maxPages) {
20224
19790
  const selected = typeof limit === "number" ? files.slice(0, limit) : files;
20225
19791
  const pages = [];
20226
19792
  for (const filePath of selected) {
20227
- const raw = await fs4.readFile(filePath, "utf8");
19793
+ const raw = await fs3.readFile(filePath, "utf8");
20228
19794
  const markdown = filePath.endsWith(".md") ? raw : normalizeSvelteToMarkdown(raw);
20229
19795
  pages.push({
20230
19796
  url: filePathToUrl(filePath, baseDir),
@@ -20319,7 +19885,7 @@ async function loadCrawledPages(config, maxPages) {
20319
19885
  const routes = await resolveRoutes(config);
20320
19886
  const maxCount = typeof maxPages === "number" ? Math.max(0, Math.floor(maxPages)) : void 0;
20321
19887
  const selected = typeof maxCount === "number" ? routes.slice(0, maxCount) : routes;
20322
- const concurrencyLimit = pLimit2(8);
19888
+ const concurrencyLimit = pLimit(8);
20323
19889
  const results = await Promise.allSettled(
20324
19890
  selected.map(
20325
19891
  (route) => concurrencyLimit(async () => {
@@ -20360,7 +19926,7 @@ async function loadStaticOutputPages(cwd, config, maxPages) {
20360
19926
  const selected = typeof limit === "number" ? htmlFiles.slice(0, limit) : htmlFiles;
20361
19927
  const pages = [];
20362
19928
  for (const filePath of selected) {
20363
- const html = await fs4.readFile(filePath, "utf8");
19929
+ const html = await fs3.readFile(filePath, "utf8");
20364
19930
  pages.push({
20365
19931
  url: staticHtmlFileToUrl(filePath, outputDir),
20366
19932
  html,
@@ -20370,35 +19936,113 @@ async function loadStaticOutputPages(cwd, config, maxPages) {
20370
19936
  }
20371
19937
  return pages;
20372
19938
  }
19939
+ function parseRobotsTxt(content, userAgent = "Searchsocket") {
19940
+ const lines = content.split(/\r?\n/);
19941
+ const agentGroups = /* @__PURE__ */ new Map();
19942
+ let currentAgents = [];
19943
+ for (const rawLine of lines) {
19944
+ const line = rawLine.replace(/#.*$/, "").trim();
19945
+ if (!line) continue;
19946
+ const colonIdx = line.indexOf(":");
19947
+ if (colonIdx === -1) continue;
19948
+ const directive = line.slice(0, colonIdx).trim().toLowerCase();
19949
+ const value = line.slice(colonIdx + 1).trim();
19950
+ if (directive === "user-agent") {
19951
+ const agentName = value.toLowerCase();
19952
+ currentAgents.push(agentName);
19953
+ if (!agentGroups.has(agentName)) {
19954
+ agentGroups.set(agentName, { disallow: [], allow: [] });
19955
+ }
19956
+ } else if (directive === "disallow" && value && currentAgents.length > 0) {
19957
+ for (const agent of currentAgents) {
19958
+ agentGroups.get(agent).disallow.push(value);
19959
+ }
19960
+ } else if (directive === "allow" && value && currentAgents.length > 0) {
19961
+ for (const agent of currentAgents) {
19962
+ agentGroups.get(agent).allow.push(value);
19963
+ }
19964
+ } else if (directive !== "disallow" && directive !== "allow") {
19965
+ currentAgents = [];
19966
+ }
19967
+ }
19968
+ const specific = agentGroups.get(userAgent.toLowerCase());
19969
+ if (specific && (specific.disallow.length > 0 || specific.allow.length > 0)) {
19970
+ return specific;
19971
+ }
19972
+ return agentGroups.get("*") ?? { disallow: [], allow: [] };
19973
+ }
19974
+ function isBlockedByRobots(urlPath, rules3) {
19975
+ let longestDisallow = "";
19976
+ for (const pattern of rules3.disallow) {
19977
+ if (urlPath.startsWith(pattern) && pattern.length > longestDisallow.length) {
19978
+ longestDisallow = pattern;
19979
+ }
19980
+ }
19981
+ if (!longestDisallow) return false;
19982
+ let longestAllow = "";
19983
+ for (const pattern of rules3.allow) {
19984
+ if (urlPath.startsWith(pattern) && pattern.length > longestAllow.length) {
19985
+ longestAllow = pattern;
19986
+ }
19987
+ }
19988
+ return longestAllow.length < longestDisallow.length;
19989
+ }
19990
+ async function loadRobotsTxtFromDir(dir) {
19991
+ try {
19992
+ const content = await fs3.readFile(path.join(dir, "robots.txt"), "utf8");
19993
+ return parseRobotsTxt(content);
19994
+ } catch {
19995
+ return null;
19996
+ }
19997
+ }
19998
+ async function fetchRobotsTxt(baseUrl) {
19999
+ try {
20000
+ const url = new URL("/robots.txt", baseUrl).href;
20001
+ const response = await fetch(url);
20002
+ if (!response.ok) return null;
20003
+ const content = await response.text();
20004
+ return parseRobotsTxt(content);
20005
+ } catch {
20006
+ return null;
20007
+ }
20008
+ }
20373
20009
 
20374
20010
  // src/indexing/pipeline.ts
20375
- var EMBEDDING_PRICE_PER_1K_TOKENS_USD = {
20376
- "jina-embeddings-v3": 2e-5
20377
- };
20378
- var DEFAULT_EMBEDDING_PRICE_PER_1K = 2e-5;
20011
+ function buildPageSummary(page, maxChars = 3500) {
20012
+ const parts = [page.title];
20013
+ if (page.description) {
20014
+ parts.push(page.description);
20015
+ }
20016
+ if (page.keywords && page.keywords.length > 0) {
20017
+ parts.push(page.keywords.join(", "));
20018
+ }
20019
+ const plainBody = page.markdown.replace(/```[\s\S]*?```/g, " ").replace(/`([^`]+)`/g, "$1").replace(/!?\[([^\]]*)\]\([^)]*\)/g, "$1").replace(/^#{1,6}\s+/gm, "").replace(/[>*_|~\-]/g, " ").replace(/\s+/g, " ").trim();
20020
+ if (plainBody) {
20021
+ parts.push(plainBody);
20022
+ }
20023
+ const joined = parts.join("\n\n");
20024
+ if (joined.length <= maxChars) return joined;
20025
+ return joined.slice(0, maxChars).trim();
20026
+ }
20379
20027
  var IndexPipeline = class _IndexPipeline {
20380
20028
  cwd;
20381
20029
  config;
20382
- embeddings;
20383
- vectorStore;
20030
+ store;
20384
20031
  logger;
20385
20032
  constructor(options) {
20386
20033
  this.cwd = options.cwd;
20387
20034
  this.config = options.config;
20388
- this.embeddings = options.embeddings;
20389
- this.vectorStore = options.vectorStore;
20035
+ this.store = options.store;
20390
20036
  this.logger = options.logger;
20391
20037
  }
20392
20038
  static async create(options = {}) {
20393
20039
  const cwd = path.resolve(options.cwd ?? process.cwd());
20394
20040
  const config = options.config ?? await loadConfig({ cwd, configPath: options.configPath });
20395
- const embeddings = options.embeddingsProvider ?? createEmbeddingsProvider(config);
20396
- const vectorStore = options.vectorStore ?? await createVectorStore(config, cwd);
20041
+ const store = options.store ?? await createUpstashStore(config);
20397
20042
  return new _IndexPipeline({
20398
20043
  cwd,
20399
20044
  config,
20400
- embeddings,
20401
- vectorStore,
20045
+ store,
20402
20046
  logger: options.logger ?? new Logger()
20403
20047
  });
20404
20048
  }
@@ -20418,25 +20062,17 @@ var IndexPipeline = class _IndexPipeline {
20418
20062
  stageTimingsMs[name] = Math.round(hrTimeMs(start));
20419
20063
  };
20420
20064
  const scope = resolveScope(this.config, options.scopeOverride);
20421
- const { statePath } = ensureStateDirs(this.cwd, this.config.state.dir, scope);
20065
+ ensureStateDirs(this.cwd, this.config.state.dir);
20422
20066
  const sourceMode = options.sourceOverride ?? this.config.source.mode;
20423
- this.logger.info(`Indexing scope "${scope.scopeName}" (source: ${sourceMode}, model: ${this.config.embeddings.model})`);
20067
+ this.logger.info(`Indexing scope "${scope.scopeName}" (source: ${sourceMode}, backend: upstash-search)`);
20424
20068
  if (options.force) {
20425
20069
  this.logger.info("Force mode enabled \u2014 full rebuild");
20426
- await cleanMirrorForScope(statePath, scope);
20427
20070
  }
20428
20071
  if (options.dryRun) {
20429
20072
  this.logger.info("Dry run \u2014 no writes will be performed");
20430
20073
  }
20431
20074
  const manifestStart = stageStart();
20432
- const existingHashes = await this.vectorStore.getContentHashes(scope);
20433
- const existingModelId = await this.vectorStore.getScopeModelId(scope);
20434
- if (existingModelId && existingModelId !== this.config.embeddings.model && !options.force) {
20435
- throw new SearchSocketError(
20436
- "EMBEDDING_MODEL_MISMATCH",
20437
- `Scope ${scope.scopeName} uses model ${existingModelId}. Re-run with --force to migrate.`
20438
- );
20439
- }
20075
+ const existingHashes = options.force ? /* @__PURE__ */ new Map() : await this.store.getContentHashes(scope);
20440
20076
  stageEnd("manifest", manifestStart);
20441
20077
  this.logger.debug(`Manifest: ${existingHashes.size} existing chunk hashes loaded`);
20442
20078
  const sourceStart = stageStart();
@@ -20453,6 +20089,53 @@ var IndexPipeline = class _IndexPipeline {
20453
20089
  }
20454
20090
  stageEnd("source", sourceStart);
20455
20091
  this.logger.info(`Loaded ${sourcePages.length} page${sourcePages.length === 1 ? "" : "s"} (${stageTimingsMs["source"]}ms)`);
20092
+ const filterStart = stageStart();
20093
+ let filteredSourcePages = sourcePages;
20094
+ if (this.config.exclude.length > 0) {
20095
+ const beforeExclude = filteredSourcePages.length;
20096
+ filteredSourcePages = filteredSourcePages.filter((p) => {
20097
+ const url = normalizeUrlPath(p.url);
20098
+ if (matchUrlPatterns(url, this.config.exclude)) {
20099
+ this.logger.debug(`Excluding ${url} (matched exclude pattern)`);
20100
+ return false;
20101
+ }
20102
+ return true;
20103
+ });
20104
+ const excludedCount = beforeExclude - filteredSourcePages.length;
20105
+ if (excludedCount > 0) {
20106
+ this.logger.info(`Excluded ${excludedCount} page${excludedCount === 1 ? "" : "s"} by config exclude patterns`);
20107
+ }
20108
+ }
20109
+ if (this.config.respectRobotsTxt) {
20110
+ let robotsRules = null;
20111
+ if (sourceMode === "static-output") {
20112
+ robotsRules = await loadRobotsTxtFromDir(
20113
+ path.resolve(this.cwd, this.config.source.staticOutputDir)
20114
+ );
20115
+ } else if (sourceMode === "build" && this.config.source.build) {
20116
+ robotsRules = await loadRobotsTxtFromDir(
20117
+ path.resolve(this.cwd, this.config.source.build.outputDir)
20118
+ );
20119
+ } else if (sourceMode === "crawl" && this.config.source.crawl) {
20120
+ robotsRules = await fetchRobotsTxt(this.config.source.crawl.baseUrl);
20121
+ }
20122
+ if (robotsRules) {
20123
+ const beforeRobots = filteredSourcePages.length;
20124
+ filteredSourcePages = filteredSourcePages.filter((p) => {
20125
+ const url = normalizeUrlPath(p.url);
20126
+ if (isBlockedByRobots(url, robotsRules)) {
20127
+ this.logger.debug(`Excluding ${url} (blocked by robots.txt)`);
20128
+ return false;
20129
+ }
20130
+ return true;
20131
+ });
20132
+ const robotsExcluded = beforeRobots - filteredSourcePages.length;
20133
+ if (robotsExcluded > 0) {
20134
+ this.logger.info(`Excluded ${robotsExcluded} page${robotsExcluded === 1 ? "" : "s"} by robots.txt`);
20135
+ }
20136
+ }
20137
+ }
20138
+ stageEnd("filter", filterStart);
20456
20139
  const routeStart = stageStart();
20457
20140
  const routePatterns = await buildRoutePatterns(this.cwd);
20458
20141
  stageEnd("route_map", routeStart);
@@ -20460,7 +20143,7 @@ var IndexPipeline = class _IndexPipeline {
20460
20143
  const extractStart = stageStart();
20461
20144
  this.logger.info("Extracting content...");
20462
20145
  const extractedPages = [];
20463
- for (const sourcePage of sourcePages) {
20146
+ for (const sourcePage of filteredSourcePages) {
20464
20147
  const extracted = sourcePage.html ? extractFromHtml(sourcePage.url, sourcePage.html, this.config) : extractFromMarkdown(sourcePage.url, sourcePage.markdown ?? "", sourcePage.title);
20465
20148
  if (!extracted) {
20466
20149
  this.logger.warn(
@@ -20486,16 +20169,29 @@ var IndexPipeline = class _IndexPipeline {
20486
20169
  seenUrls.add(page.url);
20487
20170
  uniquePages.push(page);
20488
20171
  }
20172
+ const indexablePages = [];
20173
+ for (const page of uniquePages) {
20174
+ const effectiveWeight = page.weight ?? findPageWeight(page.url, this.config.ranking.pageWeights);
20175
+ if (effectiveWeight === 0) {
20176
+ this.logger.debug(`Excluding ${page.url} (zero weight)`);
20177
+ continue;
20178
+ }
20179
+ indexablePages.push(page);
20180
+ }
20181
+ const zeroWeightCount = uniquePages.length - indexablePages.length;
20182
+ if (zeroWeightCount > 0) {
20183
+ this.logger.info(`Excluded ${zeroWeightCount} page${zeroWeightCount === 1 ? "" : "s"} with zero weight`);
20184
+ }
20489
20185
  stageEnd("extract", extractStart);
20490
- const skippedPages = sourcePages.length - uniquePages.length;
20491
- this.logger.info(`Extracted ${uniquePages.length} page${uniquePages.length === 1 ? "" : "s"}${skippedPages > 0 ? ` (${skippedPages} skipped)` : ""} (${stageTimingsMs["extract"]}ms)`);
20186
+ const skippedPages = filteredSourcePages.length - indexablePages.length;
20187
+ this.logger.info(`Extracted ${indexablePages.length} page${indexablePages.length === 1 ? "" : "s"}${skippedPages > 0 ? ` (${skippedPages} skipped)` : ""} (${stageTimingsMs["extract"]}ms)`);
20492
20188
  const linkStart = stageStart();
20493
- const pageSet = new Set(uniquePages.map((page) => normalizeUrlPath(page.url)));
20189
+ const pageSet = new Set(indexablePages.map((page) => normalizeUrlPath(page.url)));
20494
20190
  const incomingLinkCount = /* @__PURE__ */ new Map();
20495
- for (const page of uniquePages) {
20191
+ for (const page of indexablePages) {
20496
20192
  incomingLinkCount.set(page.url, incomingLinkCount.get(page.url) ?? 0);
20497
20193
  }
20498
- for (const page of uniquePages) {
20194
+ for (const page of indexablePages) {
20499
20195
  for (const outgoing of page.outgoingLinks) {
20500
20196
  if (!pageSet.has(outgoing)) {
20501
20197
  continue;
@@ -20505,9 +20201,9 @@ var IndexPipeline = class _IndexPipeline {
20505
20201
  }
20506
20202
  stageEnd("links", linkStart);
20507
20203
  this.logger.debug(`Link analysis: computed incoming links for ${incomingLinkCount.size} pages (${stageTimingsMs["links"]}ms)`);
20508
- const mirrorStart = stageStart();
20509
- this.logger.info("Writing mirror pages...");
20510
- const mirrorPages = [];
20204
+ const pagesStart = stageStart();
20205
+ this.logger.info("Building indexed pages...");
20206
+ const pages = [];
20511
20207
  let routeExact = 0;
20512
20208
  let routeBestEffort = 0;
20513
20209
  const precomputedRoutes = /* @__PURE__ */ new Map();
@@ -20519,7 +20215,7 @@ var IndexPipeline = class _IndexPipeline {
20519
20215
  });
20520
20216
  }
20521
20217
  }
20522
- for (const page of uniquePages) {
20218
+ for (const page of indexablePages) {
20523
20219
  const routeMatch = precomputedRoutes.get(normalizeUrlPath(page.url)) ?? mapUrlToRoute(page.url, routePatterns);
20524
20220
  if (routeMatch.routeResolution === "best-effort") {
20525
20221
  if (this.config.source.strictRouteMapping) {
@@ -20536,7 +20232,7 @@ var IndexPipeline = class _IndexPipeline {
20536
20232
  } else {
20537
20233
  routeExact += 1;
20538
20234
  }
20539
- const mirror = {
20235
+ const indexedPage = {
20540
20236
  url: page.url,
20541
20237
  title: page.title,
20542
20238
  scope: scope.scopeName,
@@ -20551,35 +20247,38 @@ var IndexPipeline = class _IndexPipeline {
20551
20247
  description: page.description,
20552
20248
  keywords: page.keywords
20553
20249
  };
20554
- mirrorPages.push(mirror);
20555
- if (this.config.state.writeMirror) {
20556
- await writeMirrorPage(statePath, scope, mirror);
20557
- }
20558
- this.logger.event("markdown_written", { url: page.url });
20250
+ pages.push(indexedPage);
20251
+ this.logger.event("page_indexed", { url: page.url });
20559
20252
  }
20560
20253
  if (!options.dryRun) {
20561
- const pageRecords = mirrorPages.map((mp) => ({
20562
- url: mp.url,
20563
- title: mp.title,
20564
- markdown: mp.markdown,
20565
- projectId: scope.projectId,
20566
- scopeName: scope.scopeName,
20567
- routeFile: mp.routeFile,
20568
- routeResolution: mp.routeResolution,
20569
- incomingLinks: mp.incomingLinks,
20570
- outgoingLinks: mp.outgoingLinks,
20571
- depth: mp.depth,
20572
- tags: mp.tags,
20573
- indexedAt: mp.generatedAt
20574
- }));
20575
- await this.vectorStore.deletePages(scope);
20576
- await this.vectorStore.upsertPages(pageRecords, scope);
20254
+ const pageRecords = pages.map((p) => {
20255
+ const summary = buildPageSummary(p);
20256
+ return {
20257
+ url: p.url,
20258
+ title: p.title,
20259
+ markdown: p.markdown,
20260
+ projectId: scope.projectId,
20261
+ scopeName: scope.scopeName,
20262
+ routeFile: p.routeFile,
20263
+ routeResolution: p.routeResolution,
20264
+ incomingLinks: p.incomingLinks,
20265
+ outgoingLinks: p.outgoingLinks,
20266
+ depth: p.depth,
20267
+ tags: p.tags,
20268
+ indexedAt: p.generatedAt,
20269
+ summary,
20270
+ description: p.description,
20271
+ keywords: p.keywords
20272
+ };
20273
+ });
20274
+ await this.store.deletePages(scope);
20275
+ await this.store.upsertPages(pageRecords, scope);
20577
20276
  }
20578
- stageEnd("mirror", mirrorStart);
20579
- this.logger.info(`Mirrored ${mirrorPages.length} page${mirrorPages.length === 1 ? "" : "s"} (${routeExact} exact, ${routeBestEffort} best-effort) (${stageTimingsMs["mirror"]}ms)`);
20277
+ stageEnd("pages", pagesStart);
20278
+ this.logger.info(`Indexed ${pages.length} page${pages.length === 1 ? "" : "s"} (${routeExact} exact, ${routeBestEffort} best-effort) (${stageTimingsMs["pages"]}ms)`);
20580
20279
  const chunkStart = stageStart();
20581
20280
  this.logger.info("Chunking pages...");
20582
- let chunks = mirrorPages.flatMap((page) => chunkMirrorPage(page, this.config, scope));
20281
+ let chunks = pages.flatMap((page) => chunkPage(page, this.config, scope));
20583
20282
  const maxChunks = typeof options.maxChunks === "number" ? Math.max(0, Math.floor(options.maxChunks)) : void 0;
20584
20283
  if (typeof maxChunks === "number") {
20585
20284
  chunks = chunks.slice(0, maxChunks);
@@ -20611,125 +20310,59 @@ var IndexPipeline = class _IndexPipeline {
20611
20310
  });
20612
20311
  const deletes = [...existingHashes.keys()].filter((chunkKey) => !currentChunkMap.has(chunkKey));
20613
20312
  this.logger.info(`Changes detected: ${changedChunks.length} changed, ${deletes.length} deleted, ${chunks.length - changedChunks.length} unchanged`);
20614
- const embedStart = stageStart();
20615
- const chunkTokenEstimates = /* @__PURE__ */ new Map();
20616
- for (const chunk of changedChunks) {
20617
- chunkTokenEstimates.set(chunk.chunkKey, this.embeddings.estimateTokens(buildEmbeddingText(chunk, this.config.chunking.prependTitle)));
20618
- }
20619
- const estimatedTokens = changedChunks.reduce(
20620
- (sum, chunk) => sum + (chunkTokenEstimates.get(chunk.chunkKey) ?? 0),
20621
- 0
20622
- );
20623
- const pricePer1k = this.config.embeddings.pricePer1kTokens ?? EMBEDDING_PRICE_PER_1K_TOKENS_USD[this.config.embeddings.model] ?? DEFAULT_EMBEDDING_PRICE_PER_1K;
20624
- const estimatedCostUSD = estimatedTokens / 1e3 * pricePer1k;
20625
- let newEmbeddings = 0;
20626
- const vectorsByChunk = /* @__PURE__ */ new Map();
20313
+ const upsertStart = stageStart();
20314
+ let documentsUpserted = 0;
20627
20315
  if (!options.dryRun && changedChunks.length > 0) {
20628
- this.logger.info(`Embedding ${changedChunks.length} chunk${changedChunks.length === 1 ? "" : "s"} (~${estimatedTokens.toLocaleString()} tokens, ~$${estimatedCostUSD.toFixed(6)})...`);
20629
- const embeddings = await this.embeddings.embedTexts(
20630
- changedChunks.map((chunk) => buildEmbeddingText(chunk, this.config.chunking.prependTitle)),
20631
- this.config.embeddings.model,
20632
- "retrieval.passage"
20633
- );
20634
- if (embeddings.length !== changedChunks.length) {
20635
- throw new SearchSocketError(
20636
- "VECTOR_BACKEND_UNAVAILABLE",
20637
- `Embedding provider returned ${embeddings.length} vectors for ${changedChunks.length} chunks.`
20638
- );
20639
- }
20640
- for (let i = 0; i < changedChunks.length; i += 1) {
20641
- const chunk = changedChunks[i];
20642
- const embedding = embeddings[i];
20643
- if (!chunk || !embedding || embedding.length === 0 || embedding.some((value) => !Number.isFinite(value))) {
20644
- throw new SearchSocketError(
20645
- "VECTOR_BACKEND_UNAVAILABLE",
20646
- `Embedding provider returned an invalid vector for chunk index ${i}.`
20647
- );
20648
- }
20649
- vectorsByChunk.set(chunk.chunkKey, embedding);
20650
- newEmbeddings += 1;
20651
- this.logger.event("embedded_new", { chunkKey: chunk.chunkKey });
20652
- }
20653
- }
20654
- stageEnd("embedding", embedStart);
20655
- if (changedChunks.length > 0) {
20656
- this.logger.info(`Embedded ${newEmbeddings} chunk${newEmbeddings === 1 ? "" : "s"} (${stageTimingsMs["embedding"]}ms)`);
20657
- } else {
20658
- this.logger.info("No chunks to embed \u2014 all up to date");
20659
- }
20660
- const syncStart = stageStart();
20661
- if (!options.dryRun) {
20662
- this.logger.info("Syncing vectors...");
20663
- const upserts = [];
20664
- for (const chunk of changedChunks) {
20665
- const vector = vectorsByChunk.get(chunk.chunkKey);
20666
- if (!vector) {
20667
- continue;
20668
- }
20669
- upserts.push({
20316
+ this.logger.info(`Upserting ${changedChunks.length} chunk${changedChunks.length === 1 ? "" : "s"} to Upstash Search...`);
20317
+ const UPSTASH_CONTENT_LIMIT = 4096;
20318
+ const docs = changedChunks.map((chunk) => {
20319
+ const title = chunk.title;
20320
+ const sectionTitle = chunk.sectionTitle ?? "";
20321
+ const url = chunk.url;
20322
+ const tags = chunk.tags.join(",");
20323
+ const headingPath = chunk.headingPath.join(" > ");
20324
+ const otherFieldsLen = title.length + sectionTitle.length + url.length + tags.length + headingPath.length;
20325
+ const textBudget = Math.max(500, UPSTASH_CONTENT_LIMIT - otherFieldsLen - 50);
20326
+ const text = buildEmbeddingText(chunk, this.config.chunking.prependTitle).slice(0, textBudget);
20327
+ return {
20670
20328
  id: chunk.chunkKey,
20671
- vector,
20329
+ content: { title, sectionTitle, text, url, tags, headingPath },
20672
20330
  metadata: {
20673
20331
  projectId: scope.projectId,
20674
20332
  scopeName: scope.scopeName,
20675
- url: chunk.url,
20676
20333
  path: chunk.path,
20677
- title: chunk.title,
20678
- sectionTitle: chunk.sectionTitle ?? "",
20679
- headingPath: chunk.headingPath,
20680
20334
  snippet: chunk.snippet,
20681
- chunkText: chunk.chunkText.slice(0, 4e3),
20682
20335
  ordinal: chunk.ordinal,
20683
20336
  contentHash: chunk.contentHash,
20684
- modelId: this.config.embeddings.model,
20685
20337
  depth: chunk.depth,
20686
20338
  incomingLinks: chunk.incomingLinks,
20687
20339
  routeFile: chunk.routeFile,
20688
- tags: chunk.tags,
20689
- description: chunk.description,
20690
- keywords: chunk.keywords
20340
+ description: chunk.description ?? "",
20341
+ keywords: (chunk.keywords ?? []).join(",")
20691
20342
  }
20692
- });
20693
- }
20694
- if (upserts.length > 0) {
20695
- await this.vectorStore.upsert(upserts, scope);
20696
- this.logger.event("upserted", { count: upserts.length });
20697
- }
20698
- if (deletes.length > 0) {
20699
- await this.vectorStore.deleteByIds(deletes, scope);
20700
- this.logger.event("deleted", { count: deletes.length });
20701
- }
20702
- }
20703
- stageEnd("sync", syncStart);
20704
- this.logger.debug(`Sync complete (${stageTimingsMs["sync"]}ms)`);
20705
- const finalizeStart = stageStart();
20706
- if (!options.dryRun) {
20707
- const scopeInfo = {
20708
- projectId: scope.projectId,
20709
- scopeName: scope.scopeName,
20710
- modelId: this.config.embeddings.model,
20711
- lastIndexedAt: nowIso(),
20712
- vectorCount: chunks.length,
20713
- lastEstimateTokens: estimatedTokens,
20714
- lastEstimateCostUSD: Number(estimatedCostUSD.toFixed(8)),
20715
- lastEstimateChangedChunks: changedChunks.length
20716
- };
20717
- await this.vectorStore.recordScope(scopeInfo);
20718
- this.logger.event("registry_updated", {
20719
- scope: scope.scopeName,
20720
- vectorCount: chunks.length
20343
+ };
20721
20344
  });
20345
+ await this.store.upsertChunks(docs, scope);
20346
+ documentsUpserted = docs.length;
20347
+ this.logger.event("upserted", { count: docs.length });
20348
+ }
20349
+ if (!options.dryRun && deletes.length > 0) {
20350
+ await this.store.deleteByIds(deletes, scope);
20351
+ this.logger.event("deleted", { count: deletes.length });
20352
+ }
20353
+ stageEnd("upsert", upsertStart);
20354
+ if (changedChunks.length > 0) {
20355
+ this.logger.info(`Upserted ${documentsUpserted} document${documentsUpserted === 1 ? "" : "s"} (${stageTimingsMs["upsert"]}ms)`);
20356
+ } else {
20357
+ this.logger.info("No chunks to upsert \u2014 all up to date");
20722
20358
  }
20723
- stageEnd("finalize", finalizeStart);
20724
20359
  this.logger.info("Done.");
20725
20360
  return {
20726
- pagesProcessed: mirrorPages.length,
20361
+ pagesProcessed: pages.length,
20727
20362
  chunksTotal: chunks.length,
20728
20363
  chunksChanged: changedChunks.length,
20729
- newEmbeddings,
20364
+ documentsUpserted,
20730
20365
  deletes: deletes.length,
20731
- estimatedTokens,
20732
- estimatedCostUSD: Number(estimatedCostUSD.toFixed(8)),
20733
20366
  routeExact,
20734
20367
  routeBestEffort,
20735
20368
  stageTimingsMs
@@ -20760,30 +20393,11 @@ function shouldRunAutoIndex(options) {
20760
20393
  }
20761
20394
  return false;
20762
20395
  }
20763
- function searchsocketViteConfig() {
20764
- return {
20765
- name: "searchsocket:config",
20766
- config() {
20767
- return {
20768
- ssr: {
20769
- external: ["@libsql/client", "libsql"]
20770
- }
20771
- };
20772
- }
20773
- };
20774
- }
20775
20396
  function searchsocketVitePlugin(options = {}) {
20776
20397
  let executed = false;
20777
20398
  let running = false;
20778
20399
  return {
20779
20400
  name: "searchsocket:auto-index",
20780
- config() {
20781
- return {
20782
- ssr: {
20783
- external: ["@libsql/client", "libsql"]
20784
- }
20785
- };
20786
- },
20787
20401
  async closeBundle() {
20788
20402
  if (executed || running) {
20789
20403
  return;
@@ -20805,15 +20419,14 @@ function searchsocketVitePlugin(options = {}) {
20805
20419
  });
20806
20420
  const stats = await pipeline.run({
20807
20421
  changedOnly: options.changedOnly ?? true,
20808
- force: options.force ?? false,
20422
+ force: (options.force ?? false) || /^(1|true|yes)$/i.test(process.env.SEARCHSOCKET_FORCE_REINDEX ?? ""),
20809
20423
  dryRun: options.dryRun ?? false,
20810
20424
  scopeOverride: options.scope,
20811
20425
  verbose: options.verbose
20812
20426
  });
20813
20427
  logger3.info(
20814
- `[searchsocket] indexed pages=${stats.pagesProcessed} chunks=${stats.chunksTotal} changed=${stats.chunksChanged} embedded=${stats.newEmbeddings}`
20428
+ `[searchsocket] indexed pages=${stats.pagesProcessed} chunks=${stats.chunksTotal} changed=${stats.chunksChanged} upserted=${stats.documentsUpserted}`
20815
20429
  );
20816
- logger3.info("[searchsocket] markdown mirror written under .searchsocket/pages/<scope> (safe to commit for content workflows).");
20817
20430
  executed = true;
20818
20431
  } finally {
20819
20432
  running = false;
@@ -20821,6 +20434,186 @@ function searchsocketVitePlugin(options = {}) {
20821
20434
  }
20822
20435
  };
20823
20436
  }
20437
+
20438
+ // src/sveltekit/scroll-to-text.ts
20439
+ var HIGHLIGHT_CLASS = "ssk-highlight";
20440
+ var HIGHLIGHT_DURATION = 2e3;
20441
+ var HIGHLIGHT_MARKER_ATTR = "data-ssk-highlight-marker";
20442
+ var HIGHLIGHT_NAME = "ssk-search-match";
20443
+ var styleInjected = false;
20444
+ function ensureHighlightStyle() {
20445
+ if (styleInjected || typeof document === "undefined") return;
20446
+ styleInjected = true;
20447
+ const style = document.createElement("style");
20448
+ style.textContent = `
20449
+ @keyframes ssk-highlight-fade {
20450
+ 0% { background-color: rgba(16, 185, 129, 0.18); }
20451
+ 100% { background-color: transparent; }
20452
+ }
20453
+ .${HIGHLIGHT_CLASS} {
20454
+ animation: ssk-highlight-fade ${HIGHLIGHT_DURATION}ms ease-out forwards;
20455
+ border-radius: 4px;
20456
+ }
20457
+ ::highlight(${HIGHLIGHT_NAME}) {
20458
+ background-color: rgba(16, 185, 129, 0.18);
20459
+ }
20460
+ `;
20461
+ document.head.appendChild(style);
20462
+ }
20463
+ var IGNORED_TAGS = /* @__PURE__ */ new Set(["SCRIPT", "STYLE", "NOSCRIPT", "TEMPLATE"]);
20464
+ function buildTextMap(root2) {
20465
+ const walker = document.createTreeWalker(root2, NodeFilter.SHOW_TEXT, {
20466
+ acceptNode(node) {
20467
+ const parent = node.parentElement;
20468
+ if (!parent || IGNORED_TAGS.has(parent.tagName)) return NodeFilter.FILTER_REJECT;
20469
+ return NodeFilter.FILTER_ACCEPT;
20470
+ }
20471
+ });
20472
+ const chunks = [];
20473
+ let text = "";
20474
+ let current;
20475
+ while (current = walker.nextNode()) {
20476
+ const value = current.nodeValue ?? "";
20477
+ if (!value) continue;
20478
+ chunks.push({ node: current, start: text.length, end: text.length + value.length });
20479
+ text += value;
20480
+ }
20481
+ return { text, chunks };
20482
+ }
20483
+ function normalize(text) {
20484
+ return text.toLowerCase().replace(/\s+/g, " ").trim();
20485
+ }
20486
+ function escapeRegExp(value) {
20487
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
20488
+ }
20489
+ function buildNeedleRegex(needle) {
20490
+ const tokenParts = needle.split(/[^\p{L}\p{N}]+/u).filter(Boolean);
20491
+ if (tokenParts.length > 1) {
20492
+ const pattern = tokenParts.map(escapeRegExp).join("[^\\p{L}\\p{N}]+");
20493
+ return new RegExp(pattern, "iu");
20494
+ }
20495
+ if (tokenParts.length === 1) {
20496
+ return new RegExp(escapeRegExp(tokenParts[0]), "iu");
20497
+ }
20498
+ if (!needle) return null;
20499
+ return new RegExp(escapeRegExp(needle).replace(/\s+/g, "\\s+"), "i");
20500
+ }
20501
+ function buildLenientRegex(needle) {
20502
+ const tokenParts = needle.split(/[^\p{L}\p{N}]+/u).filter(Boolean);
20503
+ if (tokenParts.length <= 1) return null;
20504
+ const pattern = tokenParts.map(escapeRegExp).join("[^\\p{L}\\p{N}]*");
20505
+ return new RegExp(pattern, "iu");
20506
+ }
20507
+ function findMatch(fullText, needle) {
20508
+ const regex = buildNeedleRegex(needle);
20509
+ if (regex) {
20510
+ const m = regex.exec(fullText);
20511
+ if (m && typeof m.index === "number") {
20512
+ return { start: m.index, end: m.index + m[0].length };
20513
+ }
20514
+ }
20515
+ const lenient = buildLenientRegex(needle);
20516
+ if (lenient) {
20517
+ const m = lenient.exec(fullText);
20518
+ if (m && typeof m.index === "number") {
20519
+ return { start: m.index, end: m.index + m[0].length };
20520
+ }
20521
+ }
20522
+ return null;
20523
+ }
20524
+ function resolveRange(map, offsets) {
20525
+ let startChunk;
20526
+ let endChunk;
20527
+ for (const chunk of map.chunks) {
20528
+ if (!startChunk && offsets.start >= chunk.start && offsets.start < chunk.end) {
20529
+ startChunk = chunk;
20530
+ }
20531
+ if (offsets.end > chunk.start && offsets.end <= chunk.end) {
20532
+ endChunk = chunk;
20533
+ }
20534
+ if (startChunk && endChunk) break;
20535
+ }
20536
+ if (!startChunk || !endChunk) return null;
20537
+ const range = document.createRange();
20538
+ range.setStart(startChunk.node, offsets.start - startChunk.start);
20539
+ range.setEnd(endChunk.node, offsets.end - endChunk.start);
20540
+ return range;
20541
+ }
20542
+ function hasCustomHighlightAPI() {
20543
+ return typeof CSS !== "undefined" && typeof CSS.highlights !== "undefined";
20544
+ }
20545
+ var highlightTimer = null;
20546
+ function highlightWithCSS(range) {
20547
+ ensureHighlightStyle();
20548
+ const hl = new globalThis.Highlight(range);
20549
+ CSS.highlights.set(HIGHLIGHT_NAME, hl);
20550
+ if (highlightTimer) clearTimeout(highlightTimer);
20551
+ highlightTimer = setTimeout(() => {
20552
+ CSS.highlights.delete(HIGHLIGHT_NAME);
20553
+ highlightTimer = null;
20554
+ }, HIGHLIGHT_DURATION);
20555
+ }
20556
+ function unwrapMarker(marker) {
20557
+ if (!marker.isConnected) return;
20558
+ const parent = marker.parentNode;
20559
+ if (!parent) return;
20560
+ while (marker.firstChild) parent.insertBefore(marker.firstChild, marker);
20561
+ parent.removeChild(marker);
20562
+ if (parent instanceof Element) parent.normalize();
20563
+ }
20564
+ function highlightWithDOM(range) {
20565
+ ensureHighlightStyle();
20566
+ try {
20567
+ const marker = document.createElement("span");
20568
+ marker.classList.add(HIGHLIGHT_CLASS);
20569
+ marker.setAttribute(HIGHLIGHT_MARKER_ATTR, "true");
20570
+ range.surroundContents(marker);
20571
+ setTimeout(() => unwrapMarker(marker), HIGHLIGHT_DURATION);
20572
+ return marker;
20573
+ } catch {
20574
+ const ancestor = range.commonAncestorContainer;
20575
+ const el = ancestor instanceof Element ? ancestor : ancestor.parentElement;
20576
+ if (el) {
20577
+ el.classList.add(HIGHLIGHT_CLASS);
20578
+ setTimeout(() => el.classList.remove(HIGHLIGHT_CLASS), HIGHLIGHT_DURATION);
20579
+ return el;
20580
+ }
20581
+ return document.body;
20582
+ }
20583
+ }
20584
+ function scrollToRange(range) {
20585
+ const rect = range.getBoundingClientRect();
20586
+ window.scrollTo({
20587
+ top: window.scrollY + rect.top - window.innerHeight / 3,
20588
+ behavior: "smooth"
20589
+ });
20590
+ }
20591
+ function scrollIntoViewIfPossible(el) {
20592
+ if (typeof el.scrollIntoView === "function") {
20593
+ el.scrollIntoView({ behavior: "smooth", block: "start" });
20594
+ }
20595
+ }
20596
+ function searchsocketScrollToText(navigation) {
20597
+ if (typeof document === "undefined") return;
20598
+ const params = navigation.to?.url.searchParams;
20599
+ const raw = params?.get("_sskt") ?? params?.get("_ssk");
20600
+ if (!raw) return;
20601
+ const needle = normalize(raw);
20602
+ if (!needle) return;
20603
+ const map = buildTextMap(document.body);
20604
+ const offsets = findMatch(map.text, needle);
20605
+ if (!offsets) return;
20606
+ const range = resolveRange(map, offsets);
20607
+ if (!range) return;
20608
+ if (hasCustomHighlightAPI()) {
20609
+ highlightWithCSS(range);
20610
+ scrollToRange(range);
20611
+ } else {
20612
+ const marker = highlightWithDOM(range);
20613
+ const target = typeof marker.scrollIntoView === "function" ? marker : marker.parentElement;
20614
+ if (target) scrollIntoViewIfPossible(target);
20615
+ }
20616
+ }
20824
20617
  /*! Bundled license information:
20825
20618
 
20826
20619
  @mixmark-io/domino/lib/style_parser.js:
@@ -20833,6 +20626,6 @@ function searchsocketVitePlugin(options = {}) {
20833
20626
  *)
20834
20627
  */
20835
20628
 
20836
- export { searchsocketHandle, searchsocketViteConfig, searchsocketVitePlugin };
20629
+ export { searchsocketHandle, searchsocketScrollToText, searchsocketVitePlugin };
20837
20630
  //# sourceMappingURL=sveltekit.js.map
20838
20631
  //# sourceMappingURL=sveltekit.js.map