@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/index.cjs CHANGED
@@ -503,13 +503,18 @@ function loadProtobufResponseEncoder() {
503
503
  );
504
504
  return exportTraceServiceResponseType;
505
505
  }
506
- function encodeProtobufResponseBody() {
507
- if (cachedProtobufResponseBody) return cachedProtobufResponseBody;
506
+ function encodeProtobufResponseBody(rejected, message) {
508
507
  const Type = loadProtobufResponseEncoder();
509
- const msg = Type.create({});
510
- const encoded = Type.encode(msg).finish();
511
- cachedProtobufResponseBody = Buffer.from(encoded);
512
- return cachedProtobufResponseBody;
508
+ if (!rejected) {
509
+ if (cachedProtobufResponseBody) return cachedProtobufResponseBody;
510
+ const msg2 = Type.create({});
511
+ cachedProtobufResponseBody = Buffer.from(Type.encode(msg2).finish());
512
+ return cachedProtobufResponseBody;
513
+ }
514
+ const msg = Type.fromObject({
515
+ partial_success: { rejected_spans: rejected, error_message: message ?? "" }
516
+ });
517
+ return Buffer.from(Type.encode(msg).finish());
513
518
  }
514
519
  async function decodeProtobufBody(buf) {
515
520
  const Type = loadProtobufDecoder();
@@ -624,6 +629,13 @@ async function buildOtelReceiver(opts) {
624
629
  }
625
630
  return reply.code(200).header("content-type", "application/json").send({ partialSuccess: {} });
626
631
  }
632
+ function sendOtlpPartial(reply, flavor, rejected, message) {
633
+ if (flavor === "protobuf") {
634
+ const buf = encodeProtobufResponseBody(rejected, message);
635
+ return reply.code(200).header("content-type", "application/x-protobuf").send(buf);
636
+ }
637
+ return reply.code(200).header("content-type", "application/json").send({ partialSuccess: { rejectedSpans: rejected, errorMessage: message } });
638
+ }
627
639
  app.addContentTypeParser(
628
640
  "application/x-protobuf",
629
641
  { parseAs: "buffer", bodyLimit: opts.bodyLimit ?? 16 * 1024 * 1024 },
@@ -651,6 +663,10 @@ async function buildOtelReceiver(opts) {
651
663
  }
652
664
  }
653
665
  enqueue(spans);
666
+ if (opts.classifyBareRoutability) {
667
+ const { rejected, message } = opts.classifyBareRoutability(spans);
668
+ if (rejected > 0) return sendOtlpPartial(reply, result.flavor, rejected, message);
669
+ }
654
670
  return sendOtlpSuccess(reply, result.flavor);
655
671
  });
656
672
  app.post("/projects/:project/v1/traces", async (req, reply) => {
@@ -3045,6 +3061,7 @@ var ROUTER_METHODS = /* @__PURE__ */ new Set([
3045
3061
  "all"
3046
3062
  ]);
3047
3063
  var NEXT_APP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
3064
+ var NET_HTTP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
3048
3065
  var JS_ROUTE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx"]);
3049
3066
  function goRouterRoutesFromSource(source, parser, framework) {
3050
3067
  const tree = parseSource2(parser, source);
@@ -3098,6 +3115,101 @@ function echoRoutesFromSource(source, parser) {
3098
3115
  function fiberRoutesFromSource(source, parser) {
3099
3116
  return goRouterRoutesFromSource(source, parser, "fiber");
3100
3117
  }
3118
+ function chiRoutesFromSource(source, parser) {
3119
+ const tree = parseSource2(parser, source);
3120
+ const out = [];
3121
+ chiWalk(tree.rootNode, "", out);
3122
+ return out;
3123
+ }
3124
+ function stripChiRegex(path68) {
3125
+ return path68.replace(/\{([^{}:]+):[^{}]*\}/g, "{$1}");
3126
+ }
3127
+ function chiWalk(node, prefix, out) {
3128
+ for (let i = 0; i < node.namedChildCount; i++) {
3129
+ const child = node.namedChild(i);
3130
+ if (child) chiHandle(child, prefix, out);
3131
+ }
3132
+ }
3133
+ function chiHandle(node, prefix, out) {
3134
+ if (node.type === "call_expression") {
3135
+ const fn = node.childForFieldName("function");
3136
+ if (fn?.type === "selector_expression") {
3137
+ const field = fn.childForFieldName("field")?.text;
3138
+ const args = node.childForFieldName("arguments");
3139
+ if (field === "Route") {
3140
+ const leaf = goStringLiteral(args?.namedChild(0));
3141
+ const closure = args?.namedChild(1);
3142
+ if (leaf !== null && closure?.type === "func_literal") {
3143
+ const body = closure.childForFieldName("body");
3144
+ if (body) chiWalk(body, prefix + leaf, out);
3145
+ }
3146
+ return;
3147
+ }
3148
+ if (field === "Group") {
3149
+ const closure = args?.namedChild(0);
3150
+ if (closure?.type === "func_literal") {
3151
+ const body = closure.childForFieldName("body");
3152
+ if (body) chiWalk(body, prefix, out);
3153
+ }
3154
+ return;
3155
+ }
3156
+ if (field === "Mount") {
3157
+ return;
3158
+ }
3159
+ if (field && ROUTER_METHODS.has(field.toLowerCase())) {
3160
+ const leaf = goStringLiteral(args?.namedChild(0));
3161
+ if (leaf !== null) {
3162
+ out.push({
3163
+ method: field.toUpperCase(),
3164
+ pathTemplate: canonicalizeTemplate(stripChiRegex(prefix + leaf)),
3165
+ line: node.startPosition.row + 1,
3166
+ framework: "chi"
3167
+ });
3168
+ }
3169
+ return;
3170
+ }
3171
+ }
3172
+ }
3173
+ chiWalk(node, prefix, out);
3174
+ }
3175
+ function netHttpRoutesFromSource(source, parser) {
3176
+ const tree = parseSource2(parser, source);
3177
+ if (!goImportsNetHttp(tree.rootNode)) return [];
3178
+ const out = [];
3179
+ walk(tree.rootNode, (node) => {
3180
+ if (node.type !== "call_expression") return;
3181
+ const fn = node.childForFieldName("function");
3182
+ if (fn?.type !== "selector_expression") return;
3183
+ const field = fn.childForFieldName("field")?.text;
3184
+ if (field !== "HandleFunc" && field !== "Handle") return;
3185
+ const leaf = goStringLiteral(node.childForFieldName("arguments")?.namedChild(0));
3186
+ if (leaf === null) return;
3187
+ const sp = leaf.indexOf(" ");
3188
+ if (sp < 0) return;
3189
+ const method = leaf.slice(0, sp);
3190
+ const rest = leaf.slice(sp + 1);
3191
+ if (!NET_HTTP_METHODS.has(method)) return;
3192
+ if (!rest.startsWith("/")) return;
3193
+ out.push({
3194
+ method,
3195
+ pathTemplate: canonicalizeTemplate(rest),
3196
+ line: node.startPosition.row + 1,
3197
+ framework: "net/http"
3198
+ });
3199
+ });
3200
+ return out;
3201
+ }
3202
+ function goImportsNetHttp(root) {
3203
+ let found = false;
3204
+ walk(root, (node) => {
3205
+ if (found || node.type !== "import_spec") return;
3206
+ for (let i = 0; i < node.namedChildCount; i++) {
3207
+ const child = node.namedChild(i);
3208
+ if (goStringLiteral(child) === "net/http") found = true;
3209
+ }
3210
+ });
3211
+ return found;
3212
+ }
3101
3213
  var FASTAPI_METHODS = /* @__PURE__ */ new Set(["get", "post", "put", "patch", "delete", "options", "head", "trace"]);
3102
3214
  var NESTJS_METHODS = /* @__PURE__ */ new Map([
3103
3215
  ["Get", "GET"],
@@ -4363,9 +4475,11 @@ async function addRoutes(graph, services) {
4363
4475
  const hasGin = deps["github.com/gin-gonic/gin"] !== void 0;
4364
4476
  const hasEcho = deps["github.com/labstack/echo/v4"] !== void 0 || deps["github.com/labstack/echo"] !== void 0;
4365
4477
  const hasFiber = deps["github.com/gofiber/fiber/v2"] !== void 0 || deps["github.com/gofiber/fiber/v3"] !== void 0;
4478
+ const hasChi = deps["github.com/go-chi/chi/v5"] !== void 0 || deps["github.com/go-chi/chi"] !== void 0;
4479
+ const isGoService = service.node.language === "go";
4366
4480
  const hasRails = deps["rails"] !== void 0;
4367
4481
  const hasLaravel = deps["laravel/framework"] !== void 0;
4368
- if (!hasExpress && !hasFastify && !hasHono && !hasNext && !hasNestjs && !hasFastapi && !hasFlask && !hasDjango && !hasGin && !hasEcho && !hasFiber && !hasRails && !hasLaravel)
4482
+ if (!hasExpress && !hasFastify && !hasHono && !hasNext && !hasNestjs && !hasFastapi && !hasFlask && !hasDjango && !hasGin && !hasEcho && !hasFiber && !hasChi && !isGoService && !hasRails && !hasLaravel)
4369
4483
  continue;
4370
4484
  const files = await loadSourceFiles(service.dir);
4371
4485
  const mountPrefixes = hasExpress ? await expressMountPrefixes(files, service.dir, await loadTsPathConfig(service.dir)) : /* @__PURE__ */ new Map();
@@ -4392,7 +4506,9 @@ async function addRoutes(graph, services) {
4392
4506
  if (hasGin) routes = ginRoutesFromSource(file.content, goParser);
4393
4507
  else if (hasEcho) routes = echoRoutesFromSource(file.content, goParser);
4394
4508
  else if (hasFiber) routes = fiberRoutesFromSource(file.content, goParser);
4509
+ else if (hasChi) routes = chiRoutesFromSource(file.content, goParser);
4395
4510
  else routes = [];
4511
+ routes = routes.concat(netHttpRoutesFromSource(file.content, goParser));
4396
4512
  } else if (isPy) {
4397
4513
  routes = hasFastapi || hasFlask ? pythonRoutesFromSource(file.content, pyParser, hasFastapi ? "fastapi" : "flask") : [];
4398
4514
  if (hasDjango) routes = routes.concat(djangoRoutesFromSource(file.content, pyParser));
@@ -4765,10 +4881,15 @@ function resolveDistToSrc(absFilepath, line) {
4765
4881
  }
4766
4882
  if (!entry) return null;
4767
4883
  try {
4768
- const pos = entry.consumer.originalPositionFor({
4769
- line: line !== void 0 && Number.isFinite(line) ? line : 1,
4770
- column: 0
4771
- });
4884
+ const queryLine = line !== void 0 && Number.isFinite(line) ? line : 1;
4885
+ let pos = entry.consumer.originalPositionFor({ line: queryLine, column: 0 });
4886
+ if (!pos || !pos.source) {
4887
+ pos = entry.consumer.originalPositionFor({
4888
+ line: queryLine,
4889
+ column: 0,
4890
+ bias: sourceMapJs.SourceMapConsumer.LEAST_UPPER_BOUND
4891
+ });
4892
+ }
4772
4893
  if (!pos || !pos.source) return null;
4773
4894
  const root = entry.consumer.sourceRoot ?? "";
4774
4895
  const resolved = import_node_path8.default.resolve(entry.dir, root, pos.source);
@@ -4777,6 +4898,9 @@ function resolveDistToSrc(absFilepath, line) {
4777
4898
  return null;
4778
4899
  }
4779
4900
  }
4901
+ function hasAdjacentSourceMap(absFilepath) {
4902
+ return sourceMapCache.get(absFilepath) != null;
4903
+ }
4780
4904
  function callSiteFromSpan(span, serviceNode, scanPath) {
4781
4905
  const filepath = codeFilepathOf(span.attributes);
4782
4906
  if (filepath === void 0) return null;
@@ -4792,7 +4916,7 @@ function callSiteFromSpan(span, serviceNode, scanPath) {
4792
4916
  }
4793
4917
  const relPath = relPathForRuntimeFile(effectivePath, serviceNode, scanPath);
4794
4918
  if (!relPath) return null;
4795
- if (!resolved && abs.endsWith(".js") && relPath.startsWith("dist/") && serviceNode?.name) {
4919
+ if (!resolved && abs.endsWith(".js") && relPath.startsWith("dist/") && !hasAdjacentSourceMap(abs) && serviceNode?.name) {
4796
4920
  warnNoSourceMaps(serviceNode.name);
4797
4921
  }
4798
4922
  const fn = codeFunctionOf(span.attributes);
@@ -5068,7 +5192,7 @@ function resolveServiceId(graph, host, env) {
5068
5192
  function frontierIdFor(host) {
5069
5193
  return (0, import_types8.frontierId)(host);
5070
5194
  }
5071
- function ensureServiceNode(graph, serviceName, env) {
5195
+ function resolveFusedServiceId(graph, serviceName, env) {
5072
5196
  const id = (0, import_types8.serviceId)(serviceName, env);
5073
5197
  if (graph.hasNode(id)) return id;
5074
5198
  const wanted = serviceName.toLowerCase();
@@ -5078,17 +5202,21 @@ function ensureServiceNode(graph, serviceName, env) {
5078
5202
  if (svc.discoveredVia === "otel") return false;
5079
5203
  return typeof svc.name === "string" && svc.name.toLowerCase() === wanted;
5080
5204
  });
5081
- if (extractedId) return extractedId;
5205
+ return extractedId ?? id;
5206
+ }
5207
+ function ensureServiceNode(graph, serviceName, env) {
5208
+ const resolved = resolveFusedServiceId(graph, serviceName, env);
5209
+ if (graph.hasNode(resolved)) return resolved;
5082
5210
  const node = {
5083
- id,
5211
+ id: resolved,
5084
5212
  type: import_types8.NodeType.ServiceNode,
5085
5213
  name: serviceName,
5086
5214
  language: "unknown",
5087
5215
  discoveredVia: "otel",
5088
5216
  ...env !== "unknown" ? { env } : {}
5089
5217
  };
5090
- graph.addNode(id, node);
5091
- return id;
5218
+ graph.addNode(resolved, node);
5219
+ return resolved;
5092
5220
  }
5093
5221
  function ensureInfraNode(graph, kind, name, provider) {
5094
5222
  const id = (0, import_types8.infraId)(kind, name);
@@ -5282,7 +5410,7 @@ async function appendErrorEvent(ctx, ev) {
5282
5410
  await import_node_fs7.promises.appendFile(ctx.errorsPath, JSON.stringify(ev) + "\n", "utf8");
5283
5411
  }
5284
5412
  function incidentAffectedNode(span, graph, scanPath) {
5285
- const sid = (0, import_types8.serviceId)(span.service, span.env);
5413
+ const sid = graph ? resolveFusedServiceId(graph, span.service, span.env) : (0, import_types8.serviceId)(span.service, span.env);
5286
5414
  const serviceNode = graph && graph.hasNode(sid) ? graph.getNodeAttributes(sid) : void 0;
5287
5415
  const callSite = callSiteFromSpan(span, serviceNode, scanPath);
5288
5416
  if (callSite) {
@@ -6006,6 +6134,7 @@ function goFramework(deps) {
6006
6134
  if (deps["github.com/gin-gonic/gin"]) return "gin";
6007
6135
  if (deps["github.com/labstack/echo/v4"] || deps["github.com/labstack/echo"]) return "echo";
6008
6136
  if (deps["github.com/gofiber/fiber/v2"] || deps["github.com/gofiber/fiber/v3"]) return "fiber";
6137
+ if (deps["github.com/go-chi/chi/v5"] || deps["github.com/go-chi/chi"]) return "chi";
6009
6138
  return void 0;
6010
6139
  }
6011
6140
  async function discoverGoService(scanPath, dir) {
@@ -6683,7 +6812,7 @@ function disambiguate(defs) {
6683
6812
  }
6684
6813
  async function addSymbols(graph, services) {
6685
6814
  const parsers = /* @__PURE__ */ new Map();
6686
- const parserForExt4 = (ext) => {
6815
+ const parserForExt5 = (ext) => {
6687
6816
  const grammar = GRAMMAR_BY_EXT[ext];
6688
6817
  if (!grammar) return null;
6689
6818
  let parser = parsers.get(ext);
@@ -6699,7 +6828,7 @@ async function addSymbols(graph, services) {
6699
6828
  for (const service of services) {
6700
6829
  const files = await loadSourceFiles(service.dir);
6701
6830
  for (const file of files) {
6702
- const parser = parserForExt4(import_node_path17.default.extname(file.path));
6831
+ const parser = parserForExt5(import_node_path17.default.extname(file.path));
6703
6832
  if (!parser) continue;
6704
6833
  const relPath = toPosix(import_node_path17.default.relative(service.dir, file.path));
6705
6834
  let defs;
@@ -6851,7 +6980,7 @@ function stringInner(node) {
6851
6980
  }
6852
6981
  async function addSymbolEdges(graph, services) {
6853
6982
  const parsers = /* @__PURE__ */ new Map();
6854
- const parserForExt4 = (ext) => {
6983
+ const parserForExt5 = (ext) => {
6855
6984
  const grammar = GRAMMAR_BY_EXT[ext];
6856
6985
  if (!grammar) return null;
6857
6986
  let parser = parsers.get(ext);
@@ -6868,7 +6997,7 @@ async function addSymbolEdges(graph, services) {
6868
6997
  const tsPaths = await loadTsPathConfig(service.dir);
6869
6998
  const files = await loadSourceFiles(service.dir);
6870
6999
  for (const file of files) {
6871
- const parser = parserForExt4(import_node_path18.default.extname(file.path));
7000
+ const parser = parserForExt5(import_node_path18.default.extname(file.path));
6872
7001
  if (!parser) continue;
6873
7002
  const relPath = toPosix(import_node_path18.default.relative(service.dir, file.path));
6874
7003
  const fileDir = import_node_path18.default.dirname(file.path);
@@ -7130,7 +7259,7 @@ function firstReferenceLines(root, wanted) {
7130
7259
  }
7131
7260
  async function addServerActions(graph, services) {
7132
7261
  const parsers = /* @__PURE__ */ new Map();
7133
- const parserForExt4 = (ext) => {
7262
+ const parserForExt5 = (ext) => {
7134
7263
  const grammar = GRAMMAR_BY_EXT[ext];
7135
7264
  if (!grammar) return null;
7136
7265
  let parser = parsers.get(ext);
@@ -7153,7 +7282,7 @@ async function addServerActions(graph, services) {
7153
7282
  const files = await loadSourceFiles(service.dir);
7154
7283
  for (const file of files) {
7155
7284
  if (isTestPath(file.path)) continue;
7156
- const parser = parserForExt4(import_node_path19.default.extname(file.path));
7285
+ const parser = parserForExt5(import_node_path19.default.extname(file.path));
7157
7286
  if (!parser) continue;
7158
7287
  const relPath = toPosix(import_node_path19.default.relative(service.dir, file.path));
7159
7288
  let root;
@@ -7216,7 +7345,7 @@ async function addServerActions(graph, services) {
7216
7345
  }
7217
7346
  for (const file of files) {
7218
7347
  if (isTestPath(file.path)) continue;
7219
- const parser = parserForExt4(import_node_path19.default.extname(file.path));
7348
+ const parser = parserForExt5(import_node_path19.default.extname(file.path));
7220
7349
  if (!parser) continue;
7221
7350
  const relPath = toPosix(import_node_path19.default.relative(service.dir, file.path));
7222
7351
  const fileDir = import_node_path19.default.dirname(file.path);
@@ -8180,6 +8309,7 @@ init_cjs_shims();
8180
8309
  var import_node_path30 = __toESM(require("path"), 1);
8181
8310
  var import_tree_sitter6 = __toESM(require("tree-sitter"), 1);
8182
8311
  var import_tree_sitter_javascript4 = __toESM(require("tree-sitter-javascript"), 1);
8312
+ var import_tree_sitter_typescript2 = __toESM(require("tree-sitter-typescript"), 1);
8183
8313
  var import_tree_sitter_python3 = __toESM(require("tree-sitter-python"), 1);
8184
8314
  var import_types20 = require("@neat.is/types");
8185
8315
  var STRING_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set(["string_fragment", "string_content"]);
@@ -8230,19 +8360,27 @@ function callsFromSource(source, parser, knownHosts) {
8230
8360
  }
8231
8361
  return out;
8232
8362
  }
8233
- function makeJsParser3() {
8234
- const p = new import_tree_sitter6.default();
8235
- p.setLanguage(import_tree_sitter_javascript4.default);
8236
- return p;
8237
- }
8238
- function makePyParser3() {
8239
- const p = new import_tree_sitter6.default();
8240
- p.setLanguage(import_tree_sitter_python3.default);
8241
- return p;
8363
+ var GRAMMAR_BY_EXT2 = {
8364
+ ".ts": import_tree_sitter_typescript2.default.typescript,
8365
+ ".tsx": import_tree_sitter_typescript2.default.tsx,
8366
+ ".js": import_tree_sitter_javascript4.default,
8367
+ ".jsx": import_tree_sitter_javascript4.default,
8368
+ ".mjs": import_tree_sitter_javascript4.default,
8369
+ ".cjs": import_tree_sitter_javascript4.default,
8370
+ ".py": import_tree_sitter_python3.default
8371
+ };
8372
+ function parserForExt(ext, cache) {
8373
+ const grammar = GRAMMAR_BY_EXT2[ext] ?? import_tree_sitter_javascript4.default;
8374
+ let parser = cache.get(grammar);
8375
+ if (!parser) {
8376
+ parser = new import_tree_sitter6.default();
8377
+ parser.setLanguage(grammar);
8378
+ cache.set(grammar, parser);
8379
+ }
8380
+ return parser;
8242
8381
  }
8243
8382
  async function addHttpCallEdges(graph, services) {
8244
- const jsParser = makeJsParser3();
8245
- const pyParser = makePyParser3();
8383
+ const parserCache = /* @__PURE__ */ new Map();
8246
8384
  const { knownHosts, hostToNodeId } = buildServiceHostIndex(services);
8247
8385
  let nodesAdded = 0;
8248
8386
  let edgesAdded = 0;
@@ -8251,7 +8389,7 @@ async function addHttpCallEdges(graph, services) {
8251
8389
  const seen = /* @__PURE__ */ new Set();
8252
8390
  for (const file of files) {
8253
8391
  if (isTestPath(file.path)) continue;
8254
- const parser = import_node_path30.default.extname(file.path) === ".py" ? pyParser : jsParser;
8392
+ const parser = parserForExt(import_node_path30.default.extname(file.path), parserCache);
8255
8393
  let sites;
8256
8394
  try {
8257
8395
  sites = callsFromSource(file.content, parser, knownHosts);
@@ -8324,7 +8462,7 @@ function parseSource5(parser, source) {
8324
8462
  (index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK5)
8325
8463
  );
8326
8464
  }
8327
- function makeJsParser4() {
8465
+ function makeJsParser3() {
8328
8466
  const p = new import_tree_sitter7.default();
8329
8467
  p.setLanguage(import_tree_sitter_javascript5.default);
8330
8468
  return p;
@@ -8502,7 +8640,7 @@ function findRoute(entries, method, normalizedPath) {
8502
8640
  );
8503
8641
  }
8504
8642
  async function addRouteCallEdges(graph, services) {
8505
- const jsParser = makeJsParser4();
8643
+ const jsParser = makeJsParser3();
8506
8644
  const { knownHosts, hostToNodeId } = buildServiceHostIndex(services);
8507
8645
  const routeIndex = buildRouteIndex(graph);
8508
8646
  if (routeIndex.size === 0) return { nodesAdded: 0, edgesAdded: 0 };
@@ -8894,7 +9032,7 @@ var import_tree_sitter_javascript6 = __toESM(require("tree-sitter-javascript"),
8894
9032
  var import_types27 = require("@neat.is/types");
8895
9033
  var FIRESTORE_CLIENT_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])firebase\/firestore['"`]/;
8896
9034
  var FIRESTORE_ADMIN_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])firebase-admin(?:\/firestore)?['"`]/;
8897
- function parserForExt(ext) {
9035
+ function parserForExt2(ext) {
8898
9036
  const p = new import_tree_sitter8.default();
8899
9037
  p.setLanguage(GRAMMAR_BY_EXT[ext] ?? import_tree_sitter_javascript6.default);
8900
9038
  return p;
@@ -9059,7 +9197,7 @@ function firestoreEndpointsFromFile(file, serviceDir) {
9059
9197
  const hasAdmin = FIRESTORE_ADMIN_IMPORT_RE.test(file.content);
9060
9198
  if (!hasClient && !hasAdmin) return [];
9061
9199
  const fileSdk = hasClient && !hasAdmin ? "client" : hasAdmin && !hasClient ? "admin" : null;
9062
- const tree = parseSource3(parserForExt(import_node_path37.default.extname(file.path)), file.content);
9200
+ const tree = parseSource3(parserForExt2(import_node_path37.default.extname(file.path)), file.content);
9063
9201
  const clientVars = firestoreClientVars(tree.rootNode);
9064
9202
  const collLine = /* @__PURE__ */ new Map();
9065
9203
  const writes = /* @__PURE__ */ new Map();
@@ -9476,7 +9614,7 @@ var import_tree_sitter_python4 = __toESM(require("tree-sitter-python"), 1);
9476
9614
  var import_types29 = require("@neat.is/types");
9477
9615
  var SQLALCHEMY_IMPORT_RE = /(?:from|import)\s+(?:flask_sqlalchemy|sqlalchemy)\b/;
9478
9616
  var PARSE_CHUNK6 = 16384;
9479
- function makePyParser4() {
9617
+ function makePyParser3() {
9480
9618
  const p = new import_tree_sitter9.default();
9481
9619
  p.setLanguage(import_tree_sitter_python4.default);
9482
9620
  return p;
@@ -9583,7 +9721,7 @@ function foreignKeyParentTable(call) {
9583
9721
  }
9584
9722
  function sqlalchemyForeignKeys(file, serviceDir) {
9585
9723
  if (!SQLALCHEMY_IMPORT_RE.test(file.content)) return [];
9586
- const tree = parseSource6(makePyParser4(), file.content);
9724
+ const tree = parseSource6(makePyParser3(), file.content);
9587
9725
  const out = [];
9588
9726
  const seen = /* @__PURE__ */ new Set();
9589
9727
  walk3(tree.rootNode, (node) => {
@@ -9620,7 +9758,7 @@ function sqlalchemyForeignKeys(file, serviceDir) {
9620
9758
  }
9621
9759
  function sqlalchemyEndpointsFromFile(file, serviceDir) {
9622
9760
  if (!SQLALCHEMY_IMPORT_RE.test(file.content)) return [];
9623
- const tree = parseSource6(makePyParser4(), file.content);
9761
+ const tree = parseSource6(makePyParser3(), file.content);
9624
9762
  const out = [];
9625
9763
  const seen = /* @__PURE__ */ new Set();
9626
9764
  const push = (name, line, columns) => {
@@ -9672,7 +9810,7 @@ function sqlalchemyEndpointsFromFile(file, serviceDir) {
9672
9810
  function buildSqlalchemyModelRegistry(files) {
9673
9811
  const table = /* @__PURE__ */ new Map();
9674
9812
  const ambiguous = /* @__PURE__ */ new Set();
9675
- const parser = makePyParser4();
9813
+ const parser = makePyParser3();
9676
9814
  for (const file of files) {
9677
9815
  if (!SQLALCHEMY_IMPORT_RE.test(file.content)) continue;
9678
9816
  const tree = parseSource6(parser, file.content);
@@ -9721,7 +9859,7 @@ function importsModelName(content, name) {
9721
9859
  function pythonOrmCrossFileEndpoints(files, serviceDir) {
9722
9860
  const registry = buildSqlalchemyModelRegistry(files);
9723
9861
  if (registry.size === 0) return [];
9724
- const parser = makePyParser4();
9862
+ const parser = makePyParser3();
9725
9863
  const out = [];
9726
9864
  const seen = /* @__PURE__ */ new Set();
9727
9865
  for (const file of files) {
@@ -9759,7 +9897,7 @@ var import_tree_sitter_python5 = __toESM(require("tree-sitter-python"), 1);
9759
9897
  var import_types30 = require("@neat.is/types");
9760
9898
  var DJANGO_IMPORT_RE = /(?:from|import)\s+django\b/;
9761
9899
  var PARSE_CHUNK7 = 16384;
9762
- function makePyParser5() {
9900
+ function makePyParser4() {
9763
9901
  const p = new import_tree_sitter10.default();
9764
9902
  p.setLanguage(import_tree_sitter_python5.default);
9765
9903
  return p;
@@ -9820,7 +9958,7 @@ function readMeta(body) {
9820
9958
  }
9821
9959
  function djangoOrmEndpointsFromFile(file, serviceDir) {
9822
9960
  if (!DJANGO_IMPORT_RE.test(file.content)) return [];
9823
- const tree = parseSource7(makePyParser5(), file.content);
9961
+ const tree = parseSource7(makePyParser4(), file.content);
9824
9962
  const out = [];
9825
9963
  const seen = /* @__PURE__ */ new Set();
9826
9964
  const defaultAppLabel = import_node_path40.default.basename(import_node_path40.default.dirname(file.path));
@@ -9855,7 +9993,7 @@ var import_tree_sitter_javascript7 = __toESM(require("tree-sitter-javascript"),
9855
9993
  var import_types31 = require("@neat.is/types");
9856
9994
  var DRIZZLE_IMPORT_RE = /drizzle-orm/;
9857
9995
  var TABLE_BUILDERS = /* @__PURE__ */ new Set(["pgTable", "mysqlTable", "sqliteTable"]);
9858
- function parserForExt2(ext) {
9996
+ function parserForExt3(ext) {
9859
9997
  const p = new import_tree_sitter11.default();
9860
9998
  p.setLanguage(GRAMMAR_BY_EXT[ext] ?? import_tree_sitter_javascript7.default);
9861
9999
  return p;
@@ -9927,7 +10065,7 @@ function columnsFromObject(obj) {
9927
10065
  }
9928
10066
  function drizzleEndpointsFromFile(file, serviceDir) {
9929
10067
  if (!DRIZZLE_IMPORT_RE.test(file.content)) return [];
9930
- const tree = parseSource3(parserForExt2(import_node_path41.default.extname(file.path)), file.content);
10068
+ const tree = parseSource3(parserForExt3(import_node_path41.default.extname(file.path)), file.content);
9931
10069
  const out = [];
9932
10070
  const seen = /* @__PURE__ */ new Set();
9933
10071
  const walk9 = (node) => {
@@ -10016,7 +10154,7 @@ function referencesTargetVar(call) {
10016
10154
  }
10017
10155
  function drizzleForeignKeys(file, serviceDir) {
10018
10156
  if (!DRIZZLE_IMPORT_RE.test(file.content)) return [];
10019
- const tree = parseSource3(parserForExt2(import_node_path41.default.extname(file.path)), file.content);
10157
+ const tree = parseSource3(parserForExt3(import_node_path41.default.extname(file.path)), file.content);
10020
10158
  const { tables, varToTable } = collectDrizzleTables(tree.rootNode);
10021
10159
  const out = [];
10022
10160
  const seen = /* @__PURE__ */ new Set();
@@ -12723,7 +12861,7 @@ var import_tree_sitter_javascript8 = __toESM(require("tree-sitter-javascript"),
12723
12861
  var import_types47 = require("@neat.is/types");
12724
12862
  var ZOD_IMPORT_RE = /\bzod\b/;
12725
12863
  var ZOD_OBJECTS = /* @__PURE__ */ new Set(["z", "zod"]);
12726
- function parserForExt3(ext) {
12864
+ function parserForExt4(ext) {
12727
12865
  const p = new import_tree_sitter16.default();
12728
12866
  p.setLanguage(GRAMMAR_BY_EXT[ext] ?? import_tree_sitter_javascript8.default);
12729
12867
  return p;
@@ -12812,7 +12950,7 @@ function topLevelSchemas(root) {
12812
12950
  }
12813
12951
  function zodShapesFromFile(file, serviceDir) {
12814
12952
  if (!ZOD_IMPORT_RE.test(file.content)) return [];
12815
- const tree = parseSource3(parserForExt3(import_node_path57.default.extname(file.path)), file.content);
12953
+ const tree = parseSource3(parserForExt4(import_node_path57.default.extname(file.path)), file.content);
12816
12954
  const out = [];
12817
12955
  const seen = /* @__PURE__ */ new Set();
12818
12956
  for (const { name, call } of topLevelSchemas(tree.rootNode)) {
@@ -18830,6 +18968,19 @@ async function startDaemon(opts = {}) {
18830
18968
  let otlpAddress = "";
18831
18969
  let daemonRecord = null;
18832
18970
  if (bind) {
18971
+ let bareSpanIsRoutable2 = function(serviceName) {
18972
+ if (singleProject) {
18973
+ const slot = slots.get(singleProject);
18974
+ if (!slot) {
18975
+ return !serviceName || serviceNameMatchesProject(serviceName, singleProject);
18976
+ }
18977
+ return spanBelongsToSingleProject(slot.graph, singleProject, serviceName);
18978
+ }
18979
+ const entries = [...slots.values()].map((s) => s.entry);
18980
+ const target = routeSpanToProject(serviceName, entries);
18981
+ return slots.has(target) || slots.has(DEFAULT_PROJECT);
18982
+ };
18983
+ var bareSpanIsRoutable = bareSpanIsRoutable2;
18833
18984
  const auth = readAuthEnv();
18834
18985
  const host = resolveHost(opts, Boolean(auth.authToken));
18835
18986
  const restPort = resolveRestPort(opts);
@@ -18927,6 +19078,20 @@ async function startDaemon(opts = {}) {
18927
19078
  const liveEntries = await listProjects().catch(() => []);
18928
19079
  let slot = slots.get(project);
18929
19080
  if (!slot) {
19081
+ if (singleProject && project === singleProject) {
19082
+ slot = await tryRecoverSlot({
19083
+ name: singleProject,
19084
+ path: singleProjectPath,
19085
+ registeredAt: (/* @__PURE__ */ new Date()).toISOString(),
19086
+ languages: [],
19087
+ status: "active"
19088
+ });
19089
+ if (!slot || slot.status !== "active") {
19090
+ warnDroppedSpan(singleProject, slot?.errorReason ?? "unknown");
19091
+ return null;
19092
+ }
19093
+ return slot;
19094
+ }
18930
19095
  await recordUnroutedSpan(serviceName, traceId);
18931
19096
  return null;
18932
19097
  }
@@ -18993,7 +19158,30 @@ async function startDaemon(opts = {}) {
18993
19158
  // host, rather than accepting it and dropping the batch. `slots` covers
18994
19159
  // active/recovering projects, `bootstrapStatus` the ones still
18995
19160
  // extracting; a foreign or wrong-cased project name matches neither.
18996
- isProjectRegistered: (project) => slots.has(project) || bootstrapStatus.has(project)
19161
+ // A single-project daemon owns exactly one project by definition, so its
19162
+ // own name always counts as registered even before loadAll populates the
19163
+ // slot — otherwise a scoped span arriving during cold-start would 404 and
19164
+ // be lost (OTLP does not retry 4xx). resolveSlotByName then builds it (#879).
19165
+ isProjectRegistered: (project) => slots.has(project) || bootstrapStatus.has(project) || singleProject !== void 0 && project === singleProject,
19166
+ // #881 — the bare `/v1/traces` route replies before the span is routed
19167
+ // (off the queue), so tell the receiver, per batch, how many spans will
19168
+ // land on no project. It keeps the 200 but reports those as
19169
+ // partialSuccess.rejectedSpans instead of an empty partialSuccess that an
19170
+ // exporter reads as full acceptance. Pure — the drop + unrouted-ledger
19171
+ // write still happen on the async onSpan path.
19172
+ classifyBareRoutability: (spans) => {
19173
+ let rejected = 0;
19174
+ for (const span of spans) {
19175
+ if (!bareSpanIsRoutable2(span.service)) rejected++;
19176
+ }
19177
+ if (rejected === 0) return { rejected: 0 };
19178
+ const noun = rejected === 1 ? "span" : "spans";
19179
+ const verb = rejected === 1 ? "was" : "were";
19180
+ return {
19181
+ rejected,
19182
+ message: `${rejected} ${noun} matched no project on this daemon and ${verb} dropped. Export to /projects/<project>/v1/traces, or check the exporter's service.name.`
19183
+ };
19184
+ }
18997
19185
  });
18998
19186
  otlpAddress = await listenSteppingOtlp(otlpApp, otlpPort, host);
18999
19187
  console.log(`neatd: OTLP listening on ${otlpAddress}/v1/traces`);