@neat.is/core 0.7.7 → 0.7.9

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/neatd.js CHANGED
@@ -2,15 +2,15 @@
2
2
  import {
3
3
  reconcileDaemonRecordSync,
4
4
  startDaemon
5
- } from "./chunk-YVZRBT4C.js";
5
+ } from "./chunk-ILG3SMD5.js";
6
6
  import {
7
7
  listProjects,
8
8
  registryPath
9
- } from "./chunk-UI3AFJAF.js";
9
+ } from "./chunk-N5TPODCX.js";
10
10
  import {
11
11
  BindAuthorityError,
12
12
  __require
13
- } from "./chunk-Y43UCVZS.js";
13
+ } from "./chunk-UUYCTH2E.js";
14
14
 
15
15
  // src/neatd.ts
16
16
  import { promises as fs } from "fs";
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  reshapeGrpcRequest,
3
3
  startOtelGrpcReceiver
4
- } from "./chunk-4GCV46AP.js";
5
- import "./chunk-Y43UCVZS.js";
4
+ } from "./chunk-FCO5Z3RW.js";
5
+ import "./chunk-UUYCTH2E.js";
6
6
  export {
7
7
  reshapeGrpcRequest,
8
8
  startOtelGrpcReceiver
9
9
  };
10
- //# sourceMappingURL=otel-grpc-DPSAGRUU.js.map
10
+ //# sourceMappingURL=otel-grpc-CO47JRIN.js.map
package/dist/server.cjs CHANGED
@@ -502,13 +502,18 @@ function loadProtobufResponseEncoder() {
502
502
  );
503
503
  return exportTraceServiceResponseType;
504
504
  }
505
- function encodeProtobufResponseBody() {
506
- if (cachedProtobufResponseBody) return cachedProtobufResponseBody;
505
+ function encodeProtobufResponseBody(rejected, message) {
507
506
  const Type = loadProtobufResponseEncoder();
508
- const msg = Type.create({});
509
- const encoded = Type.encode(msg).finish();
510
- cachedProtobufResponseBody = Buffer.from(encoded);
511
- return cachedProtobufResponseBody;
507
+ if (!rejected) {
508
+ if (cachedProtobufResponseBody) return cachedProtobufResponseBody;
509
+ const msg2 = Type.create({});
510
+ cachedProtobufResponseBody = Buffer.from(Type.encode(msg2).finish());
511
+ return cachedProtobufResponseBody;
512
+ }
513
+ const msg = Type.fromObject({
514
+ partial_success: { rejected_spans: rejected, error_message: message ?? "" }
515
+ });
516
+ return Buffer.from(Type.encode(msg).finish());
512
517
  }
513
518
  async function decodeProtobufBody(buf) {
514
519
  const Type = loadProtobufDecoder();
@@ -623,6 +628,13 @@ async function buildOtelReceiver(opts) {
623
628
  }
624
629
  return reply.code(200).header("content-type", "application/json").send({ partialSuccess: {} });
625
630
  }
631
+ function sendOtlpPartial(reply, flavor, rejected, message) {
632
+ if (flavor === "protobuf") {
633
+ const buf = encodeProtobufResponseBody(rejected, message);
634
+ return reply.code(200).header("content-type", "application/x-protobuf").send(buf);
635
+ }
636
+ return reply.code(200).header("content-type", "application/json").send({ partialSuccess: { rejectedSpans: rejected, errorMessage: message } });
637
+ }
626
638
  app.addContentTypeParser(
627
639
  "application/x-protobuf",
628
640
  { parseAs: "buffer", bodyLimit: opts.bodyLimit ?? 16 * 1024 * 1024 },
@@ -650,6 +662,10 @@ async function buildOtelReceiver(opts) {
650
662
  }
651
663
  }
652
664
  enqueue(spans);
665
+ if (opts.classifyBareRoutability) {
666
+ const { rejected, message } = opts.classifyBareRoutability(spans);
667
+ if (rejected > 0) return sendOtlpPartial(reply, result.flavor, rejected, message);
668
+ }
653
669
  return sendOtlpSuccess(reply, result.flavor);
654
670
  });
655
671
  app.post("/projects/:project/v1/traces", async (req, reply) => {
@@ -2796,6 +2812,7 @@ var ROUTER_METHODS = /* @__PURE__ */ new Set([
2796
2812
  "all"
2797
2813
  ]);
2798
2814
  var NEXT_APP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
2815
+ var NET_HTTP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
2799
2816
  var JS_ROUTE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx"]);
2800
2817
  function goRouterRoutesFromSource(source, parser, framework) {
2801
2818
  const tree = parseSource2(parser, source);
@@ -2849,6 +2866,101 @@ function echoRoutesFromSource(source, parser) {
2849
2866
  function fiberRoutesFromSource(source, parser) {
2850
2867
  return goRouterRoutesFromSource(source, parser, "fiber");
2851
2868
  }
2869
+ function chiRoutesFromSource(source, parser) {
2870
+ const tree = parseSource2(parser, source);
2871
+ const out = [];
2872
+ chiWalk(tree.rootNode, "", out);
2873
+ return out;
2874
+ }
2875
+ function stripChiRegex(path68) {
2876
+ return path68.replace(/\{([^{}:]+):[^{}]*\}/g, "{$1}");
2877
+ }
2878
+ function chiWalk(node, prefix, out) {
2879
+ for (let i = 0; i < node.namedChildCount; i++) {
2880
+ const child = node.namedChild(i);
2881
+ if (child) chiHandle(child, prefix, out);
2882
+ }
2883
+ }
2884
+ function chiHandle(node, prefix, out) {
2885
+ if (node.type === "call_expression") {
2886
+ const fn = node.childForFieldName("function");
2887
+ if (fn?.type === "selector_expression") {
2888
+ const field = fn.childForFieldName("field")?.text;
2889
+ const args = node.childForFieldName("arguments");
2890
+ if (field === "Route") {
2891
+ const leaf = goStringLiteral(args?.namedChild(0));
2892
+ const closure = args?.namedChild(1);
2893
+ if (leaf !== null && closure?.type === "func_literal") {
2894
+ const body = closure.childForFieldName("body");
2895
+ if (body) chiWalk(body, prefix + leaf, out);
2896
+ }
2897
+ return;
2898
+ }
2899
+ if (field === "Group") {
2900
+ const closure = args?.namedChild(0);
2901
+ if (closure?.type === "func_literal") {
2902
+ const body = closure.childForFieldName("body");
2903
+ if (body) chiWalk(body, prefix, out);
2904
+ }
2905
+ return;
2906
+ }
2907
+ if (field === "Mount") {
2908
+ return;
2909
+ }
2910
+ if (field && ROUTER_METHODS.has(field.toLowerCase())) {
2911
+ const leaf = goStringLiteral(args?.namedChild(0));
2912
+ if (leaf !== null) {
2913
+ out.push({
2914
+ method: field.toUpperCase(),
2915
+ pathTemplate: canonicalizeTemplate(stripChiRegex(prefix + leaf)),
2916
+ line: node.startPosition.row + 1,
2917
+ framework: "chi"
2918
+ });
2919
+ }
2920
+ return;
2921
+ }
2922
+ }
2923
+ }
2924
+ chiWalk(node, prefix, out);
2925
+ }
2926
+ function netHttpRoutesFromSource(source, parser) {
2927
+ const tree = parseSource2(parser, source);
2928
+ if (!goImportsNetHttp(tree.rootNode)) return [];
2929
+ const out = [];
2930
+ walk(tree.rootNode, (node) => {
2931
+ if (node.type !== "call_expression") return;
2932
+ const fn = node.childForFieldName("function");
2933
+ if (fn?.type !== "selector_expression") return;
2934
+ const field = fn.childForFieldName("field")?.text;
2935
+ if (field !== "HandleFunc" && field !== "Handle") return;
2936
+ const leaf = goStringLiteral(node.childForFieldName("arguments")?.namedChild(0));
2937
+ if (leaf === null) return;
2938
+ const sp = leaf.indexOf(" ");
2939
+ if (sp < 0) return;
2940
+ const method = leaf.slice(0, sp);
2941
+ const rest = leaf.slice(sp + 1);
2942
+ if (!NET_HTTP_METHODS.has(method)) return;
2943
+ if (!rest.startsWith("/")) return;
2944
+ out.push({
2945
+ method,
2946
+ pathTemplate: canonicalizeTemplate(rest),
2947
+ line: node.startPosition.row + 1,
2948
+ framework: "net/http"
2949
+ });
2950
+ });
2951
+ return out;
2952
+ }
2953
+ function goImportsNetHttp(root) {
2954
+ let found = false;
2955
+ walk(root, (node) => {
2956
+ if (found || node.type !== "import_spec") return;
2957
+ for (let i = 0; i < node.namedChildCount; i++) {
2958
+ const child = node.namedChild(i);
2959
+ if (goStringLiteral(child) === "net/http") found = true;
2960
+ }
2961
+ });
2962
+ return found;
2963
+ }
2852
2964
  var FASTAPI_METHODS = /* @__PURE__ */ new Set(["get", "post", "put", "patch", "delete", "options", "head", "trace"]);
2853
2965
  var NESTJS_METHODS = /* @__PURE__ */ new Map([
2854
2966
  ["Get", "GET"],
@@ -4114,9 +4226,11 @@ async function addRoutes(graph, services) {
4114
4226
  const hasGin = deps["github.com/gin-gonic/gin"] !== void 0;
4115
4227
  const hasEcho = deps["github.com/labstack/echo/v4"] !== void 0 || deps["github.com/labstack/echo"] !== void 0;
4116
4228
  const hasFiber = deps["github.com/gofiber/fiber/v2"] !== void 0 || deps["github.com/gofiber/fiber/v3"] !== void 0;
4229
+ const hasChi = deps["github.com/go-chi/chi/v5"] !== void 0 || deps["github.com/go-chi/chi"] !== void 0;
4230
+ const isGoService = service.node.language === "go";
4117
4231
  const hasRails = deps["rails"] !== void 0;
4118
4232
  const hasLaravel = deps["laravel/framework"] !== void 0;
4119
- if (!hasExpress && !hasFastify && !hasHono && !hasNext && !hasNestjs && !hasFastapi && !hasFlask && !hasDjango && !hasGin && !hasEcho && !hasFiber && !hasRails && !hasLaravel)
4233
+ if (!hasExpress && !hasFastify && !hasHono && !hasNext && !hasNestjs && !hasFastapi && !hasFlask && !hasDjango && !hasGin && !hasEcho && !hasFiber && !hasChi && !isGoService && !hasRails && !hasLaravel)
4120
4234
  continue;
4121
4235
  const files = await loadSourceFiles(service.dir);
4122
4236
  const mountPrefixes = hasExpress ? await expressMountPrefixes(files, service.dir, await loadTsPathConfig(service.dir)) : /* @__PURE__ */ new Map();
@@ -4143,7 +4257,9 @@ async function addRoutes(graph, services) {
4143
4257
  if (hasGin) routes = ginRoutesFromSource(file.content, goParser);
4144
4258
  else if (hasEcho) routes = echoRoutesFromSource(file.content, goParser);
4145
4259
  else if (hasFiber) routes = fiberRoutesFromSource(file.content, goParser);
4260
+ else if (hasChi) routes = chiRoutesFromSource(file.content, goParser);
4146
4261
  else routes = [];
4262
+ routes = routes.concat(netHttpRoutesFromSource(file.content, goParser));
4147
4263
  } else if (isPy) {
4148
4264
  routes = hasFastapi || hasFlask ? pythonRoutesFromSource(file.content, pyParser, hasFastapi ? "fastapi" : "flask") : [];
4149
4265
  if (hasDjango) routes = routes.concat(djangoRoutesFromSource(file.content, pyParser));
@@ -4465,10 +4581,15 @@ function resolveDistToSrc(absFilepath, line) {
4465
4581
  }
4466
4582
  if (!entry) return null;
4467
4583
  try {
4468
- const pos = entry.consumer.originalPositionFor({
4469
- line: line !== void 0 && Number.isFinite(line) ? line : 1,
4470
- column: 0
4471
- });
4584
+ const queryLine = line !== void 0 && Number.isFinite(line) ? line : 1;
4585
+ let pos = entry.consumer.originalPositionFor({ line: queryLine, column: 0 });
4586
+ if (!pos || !pos.source) {
4587
+ pos = entry.consumer.originalPositionFor({
4588
+ line: queryLine,
4589
+ column: 0,
4590
+ bias: sourceMapJs.SourceMapConsumer.LEAST_UPPER_BOUND
4591
+ });
4592
+ }
4472
4593
  if (!pos || !pos.source) return null;
4473
4594
  const root = entry.consumer.sourceRoot ?? "";
4474
4595
  const resolved = import_node_path10.default.resolve(entry.dir, root, pos.source);
@@ -4477,6 +4598,9 @@ function resolveDistToSrc(absFilepath, line) {
4477
4598
  return null;
4478
4599
  }
4479
4600
  }
4601
+ function hasAdjacentSourceMap(absFilepath) {
4602
+ return sourceMapCache.get(absFilepath) != null;
4603
+ }
4480
4604
  function callSiteFromSpan(span, serviceNode, scanPath) {
4481
4605
  const filepath = codeFilepathOf(span.attributes);
4482
4606
  if (filepath === void 0) return null;
@@ -4492,7 +4616,7 @@ function callSiteFromSpan(span, serviceNode, scanPath) {
4492
4616
  }
4493
4617
  const relPath = relPathForRuntimeFile(effectivePath, serviceNode, scanPath);
4494
4618
  if (!relPath) return null;
4495
- if (!resolved && abs.endsWith(".js") && relPath.startsWith("dist/") && serviceNode?.name) {
4619
+ if (!resolved && abs.endsWith(".js") && relPath.startsWith("dist/") && !hasAdjacentSourceMap(abs) && serviceNode?.name) {
4496
4620
  warnNoSourceMaps(serviceNode.name);
4497
4621
  }
4498
4622
  const fn = codeFunctionOf(span.attributes);
@@ -4768,7 +4892,7 @@ function resolveServiceId(graph, host, env) {
4768
4892
  function frontierIdFor(host) {
4769
4893
  return (0, import_types7.frontierId)(host);
4770
4894
  }
4771
- function ensureServiceNode(graph, serviceName, env) {
4895
+ function resolveFusedServiceId(graph, serviceName, env) {
4772
4896
  const id = (0, import_types7.serviceId)(serviceName, env);
4773
4897
  if (graph.hasNode(id)) return id;
4774
4898
  const wanted = serviceName.toLowerCase();
@@ -4778,17 +4902,21 @@ function ensureServiceNode(graph, serviceName, env) {
4778
4902
  if (svc.discoveredVia === "otel") return false;
4779
4903
  return typeof svc.name === "string" && svc.name.toLowerCase() === wanted;
4780
4904
  });
4781
- if (extractedId) return extractedId;
4905
+ return extractedId ?? id;
4906
+ }
4907
+ function ensureServiceNode(graph, serviceName, env) {
4908
+ const resolved = resolveFusedServiceId(graph, serviceName, env);
4909
+ if (graph.hasNode(resolved)) return resolved;
4782
4910
  const node = {
4783
- id,
4911
+ id: resolved,
4784
4912
  type: import_types7.NodeType.ServiceNode,
4785
4913
  name: serviceName,
4786
4914
  language: "unknown",
4787
4915
  discoveredVia: "otel",
4788
4916
  ...env !== "unknown" ? { env } : {}
4789
4917
  };
4790
- graph.addNode(id, node);
4791
- return id;
4918
+ graph.addNode(resolved, node);
4919
+ return resolved;
4792
4920
  }
4793
4921
  function ensureInfraNode(graph, kind, name, provider) {
4794
4922
  const id = (0, import_types7.infraId)(kind, name);
@@ -4982,7 +5110,7 @@ async function appendErrorEvent(ctx, ev) {
4982
5110
  await import_node_fs9.promises.appendFile(ctx.errorsPath, JSON.stringify(ev) + "\n", "utf8");
4983
5111
  }
4984
5112
  function incidentAffectedNode(span, graph, scanPath) {
4985
- const sid = (0, import_types7.serviceId)(span.service, span.env);
5113
+ const sid = graph ? resolveFusedServiceId(graph, span.service, span.env) : (0, import_types7.serviceId)(span.service, span.env);
4986
5114
  const serviceNode = graph && graph.hasNode(sid) ? graph.getNodeAttributes(sid) : void 0;
4987
5115
  const callSite = callSiteFromSpan(span, serviceNode, scanPath);
4988
5116
  if (callSite) {
@@ -6585,6 +6713,7 @@ function goFramework(deps) {
6585
6713
  if (deps["github.com/gin-gonic/gin"]) return "gin";
6586
6714
  if (deps["github.com/labstack/echo/v4"] || deps["github.com/labstack/echo"]) return "echo";
6587
6715
  if (deps["github.com/gofiber/fiber/v2"] || deps["github.com/gofiber/fiber/v3"]) return "fiber";
6716
+ if (deps["github.com/go-chi/chi/v5"] || deps["github.com/go-chi/chi"]) return "chi";
6588
6717
  return void 0;
6589
6718
  }
6590
6719
  async function discoverGoService(scanPath, dir) {
@@ -7262,7 +7391,7 @@ function disambiguate(defs) {
7262
7391
  }
7263
7392
  async function addSymbols(graph, services) {
7264
7393
  const parsers = /* @__PURE__ */ new Map();
7265
- const parserForExt4 = (ext) => {
7394
+ const parserForExt5 = (ext) => {
7266
7395
  const grammar = GRAMMAR_BY_EXT[ext];
7267
7396
  if (!grammar) return null;
7268
7397
  let parser = parsers.get(ext);
@@ -7278,7 +7407,7 @@ async function addSymbols(graph, services) {
7278
7407
  for (const service of services) {
7279
7408
  const files = await loadSourceFiles(service.dir);
7280
7409
  for (const file of files) {
7281
- const parser = parserForExt4(import_node_path19.default.extname(file.path));
7410
+ const parser = parserForExt5(import_node_path19.default.extname(file.path));
7282
7411
  if (!parser) continue;
7283
7412
  const relPath = toPosix(import_node_path19.default.relative(service.dir, file.path));
7284
7413
  let defs;
@@ -7430,7 +7559,7 @@ function stringInner(node) {
7430
7559
  }
7431
7560
  async function addSymbolEdges(graph, services) {
7432
7561
  const parsers = /* @__PURE__ */ new Map();
7433
- const parserForExt4 = (ext) => {
7562
+ const parserForExt5 = (ext) => {
7434
7563
  const grammar = GRAMMAR_BY_EXT[ext];
7435
7564
  if (!grammar) return null;
7436
7565
  let parser = parsers.get(ext);
@@ -7447,7 +7576,7 @@ async function addSymbolEdges(graph, services) {
7447
7576
  const tsPaths = await loadTsPathConfig(service.dir);
7448
7577
  const files = await loadSourceFiles(service.dir);
7449
7578
  for (const file of files) {
7450
- const parser = parserForExt4(import_node_path20.default.extname(file.path));
7579
+ const parser = parserForExt5(import_node_path20.default.extname(file.path));
7451
7580
  if (!parser) continue;
7452
7581
  const relPath = toPosix(import_node_path20.default.relative(service.dir, file.path));
7453
7582
  const fileDir = import_node_path20.default.dirname(file.path);
@@ -7709,7 +7838,7 @@ function firstReferenceLines(root, wanted) {
7709
7838
  }
7710
7839
  async function addServerActions(graph, services) {
7711
7840
  const parsers = /* @__PURE__ */ new Map();
7712
- const parserForExt4 = (ext) => {
7841
+ const parserForExt5 = (ext) => {
7713
7842
  const grammar = GRAMMAR_BY_EXT[ext];
7714
7843
  if (!grammar) return null;
7715
7844
  let parser = parsers.get(ext);
@@ -7732,7 +7861,7 @@ async function addServerActions(graph, services) {
7732
7861
  const files = await loadSourceFiles(service.dir);
7733
7862
  for (const file of files) {
7734
7863
  if (isTestPath(file.path)) continue;
7735
- const parser = parserForExt4(import_node_path21.default.extname(file.path));
7864
+ const parser = parserForExt5(import_node_path21.default.extname(file.path));
7736
7865
  if (!parser) continue;
7737
7866
  const relPath = toPosix(import_node_path21.default.relative(service.dir, file.path));
7738
7867
  let root;
@@ -7795,7 +7924,7 @@ async function addServerActions(graph, services) {
7795
7924
  }
7796
7925
  for (const file of files) {
7797
7926
  if (isTestPath(file.path)) continue;
7798
- const parser = parserForExt4(import_node_path21.default.extname(file.path));
7927
+ const parser = parserForExt5(import_node_path21.default.extname(file.path));
7799
7928
  if (!parser) continue;
7800
7929
  const relPath = toPosix(import_node_path21.default.relative(service.dir, file.path));
7801
7930
  const fileDir = import_node_path21.default.dirname(file.path);
@@ -8759,6 +8888,7 @@ init_cjs_shims();
8759
8888
  var import_node_path32 = __toESM(require("path"), 1);
8760
8889
  var import_tree_sitter6 = __toESM(require("tree-sitter"), 1);
8761
8890
  var import_tree_sitter_javascript4 = __toESM(require("tree-sitter-javascript"), 1);
8891
+ var import_tree_sitter_typescript2 = __toESM(require("tree-sitter-typescript"), 1);
8762
8892
  var import_tree_sitter_python3 = __toESM(require("tree-sitter-python"), 1);
8763
8893
  var import_types21 = require("@neat.is/types");
8764
8894
  var STRING_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set(["string_fragment", "string_content"]);
@@ -8809,19 +8939,27 @@ function callsFromSource(source, parser, knownHosts) {
8809
8939
  }
8810
8940
  return out;
8811
8941
  }
8812
- function makeJsParser3() {
8813
- const p = new import_tree_sitter6.default();
8814
- p.setLanguage(import_tree_sitter_javascript4.default);
8815
- return p;
8816
- }
8817
- function makePyParser3() {
8818
- const p = new import_tree_sitter6.default();
8819
- p.setLanguage(import_tree_sitter_python3.default);
8820
- return p;
8942
+ var GRAMMAR_BY_EXT2 = {
8943
+ ".ts": import_tree_sitter_typescript2.default.typescript,
8944
+ ".tsx": import_tree_sitter_typescript2.default.tsx,
8945
+ ".js": import_tree_sitter_javascript4.default,
8946
+ ".jsx": import_tree_sitter_javascript4.default,
8947
+ ".mjs": import_tree_sitter_javascript4.default,
8948
+ ".cjs": import_tree_sitter_javascript4.default,
8949
+ ".py": import_tree_sitter_python3.default
8950
+ };
8951
+ function parserForExt(ext, cache) {
8952
+ const grammar = GRAMMAR_BY_EXT2[ext] ?? import_tree_sitter_javascript4.default;
8953
+ let parser = cache.get(grammar);
8954
+ if (!parser) {
8955
+ parser = new import_tree_sitter6.default();
8956
+ parser.setLanguage(grammar);
8957
+ cache.set(grammar, parser);
8958
+ }
8959
+ return parser;
8821
8960
  }
8822
8961
  async function addHttpCallEdges(graph, services) {
8823
- const jsParser = makeJsParser3();
8824
- const pyParser = makePyParser3();
8962
+ const parserCache = /* @__PURE__ */ new Map();
8825
8963
  const { knownHosts, hostToNodeId } = buildServiceHostIndex(services);
8826
8964
  let nodesAdded = 0;
8827
8965
  let edgesAdded = 0;
@@ -8830,7 +8968,7 @@ async function addHttpCallEdges(graph, services) {
8830
8968
  const seen = /* @__PURE__ */ new Set();
8831
8969
  for (const file of files) {
8832
8970
  if (isTestPath(file.path)) continue;
8833
- const parser = import_node_path32.default.extname(file.path) === ".py" ? pyParser : jsParser;
8971
+ const parser = parserForExt(import_node_path32.default.extname(file.path), parserCache);
8834
8972
  let sites;
8835
8973
  try {
8836
8974
  sites = callsFromSource(file.content, parser, knownHosts);
@@ -8903,7 +9041,7 @@ function parseSource5(parser, source) {
8903
9041
  (index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK5)
8904
9042
  );
8905
9043
  }
8906
- function makeJsParser4() {
9044
+ function makeJsParser3() {
8907
9045
  const p = new import_tree_sitter7.default();
8908
9046
  p.setLanguage(import_tree_sitter_javascript5.default);
8909
9047
  return p;
@@ -9081,7 +9219,7 @@ function findRoute(entries, method, normalizedPath) {
9081
9219
  );
9082
9220
  }
9083
9221
  async function addRouteCallEdges(graph, services) {
9084
- const jsParser = makeJsParser4();
9222
+ const jsParser = makeJsParser3();
9085
9223
  const { knownHosts, hostToNodeId } = buildServiceHostIndex(services);
9086
9224
  const routeIndex = buildRouteIndex(graph);
9087
9225
  if (routeIndex.size === 0) return { nodesAdded: 0, edgesAdded: 0 };
@@ -9473,7 +9611,7 @@ var import_tree_sitter_javascript6 = __toESM(require("tree-sitter-javascript"),
9473
9611
  var import_types28 = require("@neat.is/types");
9474
9612
  var FIRESTORE_CLIENT_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])firebase\/firestore['"`]/;
9475
9613
  var FIRESTORE_ADMIN_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])firebase-admin(?:\/firestore)?['"`]/;
9476
- function parserForExt(ext) {
9614
+ function parserForExt2(ext) {
9477
9615
  const p = new import_tree_sitter8.default();
9478
9616
  p.setLanguage(GRAMMAR_BY_EXT[ext] ?? import_tree_sitter_javascript6.default);
9479
9617
  return p;
@@ -9638,7 +9776,7 @@ function firestoreEndpointsFromFile(file, serviceDir) {
9638
9776
  const hasAdmin = FIRESTORE_ADMIN_IMPORT_RE.test(file.content);
9639
9777
  if (!hasClient && !hasAdmin) return [];
9640
9778
  const fileSdk = hasClient && !hasAdmin ? "client" : hasAdmin && !hasClient ? "admin" : null;
9641
- const tree = parseSource3(parserForExt(import_node_path39.default.extname(file.path)), file.content);
9779
+ const tree = parseSource3(parserForExt2(import_node_path39.default.extname(file.path)), file.content);
9642
9780
  const clientVars = firestoreClientVars(tree.rootNode);
9643
9781
  const collLine = /* @__PURE__ */ new Map();
9644
9782
  const writes = /* @__PURE__ */ new Map();
@@ -10055,7 +10193,7 @@ var import_tree_sitter_python4 = __toESM(require("tree-sitter-python"), 1);
10055
10193
  var import_types30 = require("@neat.is/types");
10056
10194
  var SQLALCHEMY_IMPORT_RE = /(?:from|import)\s+(?:flask_sqlalchemy|sqlalchemy)\b/;
10057
10195
  var PARSE_CHUNK6 = 16384;
10058
- function makePyParser4() {
10196
+ function makePyParser3() {
10059
10197
  const p = new import_tree_sitter9.default();
10060
10198
  p.setLanguage(import_tree_sitter_python4.default);
10061
10199
  return p;
@@ -10162,7 +10300,7 @@ function foreignKeyParentTable(call) {
10162
10300
  }
10163
10301
  function sqlalchemyForeignKeys(file, serviceDir) {
10164
10302
  if (!SQLALCHEMY_IMPORT_RE.test(file.content)) return [];
10165
- const tree = parseSource6(makePyParser4(), file.content);
10303
+ const tree = parseSource6(makePyParser3(), file.content);
10166
10304
  const out = [];
10167
10305
  const seen = /* @__PURE__ */ new Set();
10168
10306
  walk3(tree.rootNode, (node) => {
@@ -10199,7 +10337,7 @@ function sqlalchemyForeignKeys(file, serviceDir) {
10199
10337
  }
10200
10338
  function sqlalchemyEndpointsFromFile(file, serviceDir) {
10201
10339
  if (!SQLALCHEMY_IMPORT_RE.test(file.content)) return [];
10202
- const tree = parseSource6(makePyParser4(), file.content);
10340
+ const tree = parseSource6(makePyParser3(), file.content);
10203
10341
  const out = [];
10204
10342
  const seen = /* @__PURE__ */ new Set();
10205
10343
  const push = (name, line, columns) => {
@@ -10251,7 +10389,7 @@ function sqlalchemyEndpointsFromFile(file, serviceDir) {
10251
10389
  function buildSqlalchemyModelRegistry(files) {
10252
10390
  const table = /* @__PURE__ */ new Map();
10253
10391
  const ambiguous = /* @__PURE__ */ new Set();
10254
- const parser = makePyParser4();
10392
+ const parser = makePyParser3();
10255
10393
  for (const file of files) {
10256
10394
  if (!SQLALCHEMY_IMPORT_RE.test(file.content)) continue;
10257
10395
  const tree = parseSource6(parser, file.content);
@@ -10300,7 +10438,7 @@ function importsModelName(content, name) {
10300
10438
  function pythonOrmCrossFileEndpoints(files, serviceDir) {
10301
10439
  const registry = buildSqlalchemyModelRegistry(files);
10302
10440
  if (registry.size === 0) return [];
10303
- const parser = makePyParser4();
10441
+ const parser = makePyParser3();
10304
10442
  const out = [];
10305
10443
  const seen = /* @__PURE__ */ new Set();
10306
10444
  for (const file of files) {
@@ -10338,7 +10476,7 @@ var import_tree_sitter_python5 = __toESM(require("tree-sitter-python"), 1);
10338
10476
  var import_types31 = require("@neat.is/types");
10339
10477
  var DJANGO_IMPORT_RE = /(?:from|import)\s+django\b/;
10340
10478
  var PARSE_CHUNK7 = 16384;
10341
- function makePyParser5() {
10479
+ function makePyParser4() {
10342
10480
  const p = new import_tree_sitter10.default();
10343
10481
  p.setLanguage(import_tree_sitter_python5.default);
10344
10482
  return p;
@@ -10399,7 +10537,7 @@ function readMeta(body) {
10399
10537
  }
10400
10538
  function djangoOrmEndpointsFromFile(file, serviceDir) {
10401
10539
  if (!DJANGO_IMPORT_RE.test(file.content)) return [];
10402
- const tree = parseSource7(makePyParser5(), file.content);
10540
+ const tree = parseSource7(makePyParser4(), file.content);
10403
10541
  const out = [];
10404
10542
  const seen = /* @__PURE__ */ new Set();
10405
10543
  const defaultAppLabel = import_node_path42.default.basename(import_node_path42.default.dirname(file.path));
@@ -10434,7 +10572,7 @@ var import_tree_sitter_javascript7 = __toESM(require("tree-sitter-javascript"),
10434
10572
  var import_types32 = require("@neat.is/types");
10435
10573
  var DRIZZLE_IMPORT_RE = /drizzle-orm/;
10436
10574
  var TABLE_BUILDERS = /* @__PURE__ */ new Set(["pgTable", "mysqlTable", "sqliteTable"]);
10437
- function parserForExt2(ext) {
10575
+ function parserForExt3(ext) {
10438
10576
  const p = new import_tree_sitter11.default();
10439
10577
  p.setLanguage(GRAMMAR_BY_EXT[ext] ?? import_tree_sitter_javascript7.default);
10440
10578
  return p;
@@ -10506,7 +10644,7 @@ function columnsFromObject(obj) {
10506
10644
  }
10507
10645
  function drizzleEndpointsFromFile(file, serviceDir) {
10508
10646
  if (!DRIZZLE_IMPORT_RE.test(file.content)) return [];
10509
- const tree = parseSource3(parserForExt2(import_node_path43.default.extname(file.path)), file.content);
10647
+ const tree = parseSource3(parserForExt3(import_node_path43.default.extname(file.path)), file.content);
10510
10648
  const out = [];
10511
10649
  const seen = /* @__PURE__ */ new Set();
10512
10650
  const walk9 = (node) => {
@@ -10595,7 +10733,7 @@ function referencesTargetVar(call) {
10595
10733
  }
10596
10734
  function drizzleForeignKeys(file, serviceDir) {
10597
10735
  if (!DRIZZLE_IMPORT_RE.test(file.content)) return [];
10598
- const tree = parseSource3(parserForExt2(import_node_path43.default.extname(file.path)), file.content);
10736
+ const tree = parseSource3(parserForExt3(import_node_path43.default.extname(file.path)), file.content);
10599
10737
  const { tables, varToTable } = collectDrizzleTables(tree.rootNode);
10600
10738
  const out = [];
10601
10739
  const seen = /* @__PURE__ */ new Set();
@@ -13302,7 +13440,7 @@ var import_tree_sitter_javascript8 = __toESM(require("tree-sitter-javascript"),
13302
13440
  var import_types48 = require("@neat.is/types");
13303
13441
  var ZOD_IMPORT_RE = /\bzod\b/;
13304
13442
  var ZOD_OBJECTS = /* @__PURE__ */ new Set(["z", "zod"]);
13305
- function parserForExt3(ext) {
13443
+ function parserForExt4(ext) {
13306
13444
  const p = new import_tree_sitter16.default();
13307
13445
  p.setLanguage(GRAMMAR_BY_EXT[ext] ?? import_tree_sitter_javascript8.default);
13308
13446
  return p;
@@ -13391,7 +13529,7 @@ function topLevelSchemas(root) {
13391
13529
  }
13392
13530
  function zodShapesFromFile(file, serviceDir) {
13393
13531
  if (!ZOD_IMPORT_RE.test(file.content)) return [];
13394
- const tree = parseSource3(parserForExt3(import_node_path59.default.extname(file.path)), file.content);
13532
+ const tree = parseSource3(parserForExt4(import_node_path59.default.extname(file.path)), file.content);
13395
13533
  const out = [];
13396
13534
  const seen = /* @__PURE__ */ new Set();
13397
13535
  for (const { name, call } of topLevelSchemas(tree.rootNode)) {
@@ -18198,12 +18336,46 @@ var SubstringIndex = class {
18198
18336
  this.graph = graph;
18199
18337
  }
18200
18338
  };
18339
+ var DEFAULT_SEARCH_INIT_TIMEOUT_MS = 3e4;
18340
+ function searchInitTimeoutMs() {
18341
+ const env = process.env.NEAT_SEARCH_INIT_TIMEOUT_MS;
18342
+ if (env !== void 0 && env.length > 0) {
18343
+ const n = Number.parseInt(env, 10);
18344
+ if (Number.isFinite(n) && n >= 0) return n;
18345
+ }
18346
+ return DEFAULT_SEARCH_INIT_TIMEOUT_MS;
18347
+ }
18348
+ async function resolveEmbedderBounded(factory, timeoutMs) {
18349
+ if (timeoutMs <= 0) return factory();
18350
+ let timer;
18351
+ const TIMED_OUT = /* @__PURE__ */ Symbol("embedder-init-timeout");
18352
+ const timeout = new Promise((resolve) => {
18353
+ timer = setTimeout(() => resolve(TIMED_OUT), timeoutMs);
18354
+ timer.unref?.();
18355
+ });
18356
+ try {
18357
+ const result = await Promise.race([factory(), timeout]);
18358
+ if (result === TIMED_OUT) {
18359
+ console.warn(
18360
+ `semantic_search: embedder init exceeded ${timeoutMs}ms; falling back to substring search. Set NEAT_SEARCH_INIT_TIMEOUT_MS to raise the bound (or 0 to wait indefinitely).`
18361
+ );
18362
+ return null;
18363
+ }
18364
+ return result;
18365
+ } finally {
18366
+ if (timer) clearTimeout(timer);
18367
+ }
18368
+ }
18201
18369
  async function buildSearchIndex(graph, options = {}) {
18202
18370
  let embedder = null;
18203
18371
  if (options.embedder) {
18204
18372
  embedder = options.embedder;
18205
18373
  } else if (options.forceProvider !== "substring") {
18206
- embedder = await pickEmbedder();
18374
+ const factory = options.embedderFactory ?? pickEmbedder;
18375
+ embedder = await resolveEmbedderBounded(
18376
+ factory,
18377
+ options.initTimeoutMs ?? searchInitTimeoutMs()
18378
+ );
18207
18379
  if (options.forceProvider === "ollama" && embedder?.provider !== "ollama") {
18208
18380
  embedder = null;
18209
18381
  }