@neat.is/core 0.7.8 → 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.d.cts CHANGED
@@ -302,6 +302,10 @@ interface BuildOtelReceiverOptions {
302
302
  onProjectSpan?: ProjectSpanHandler;
303
303
  onProjectErrorSpanSync?: (project: string, span: ParsedSpan) => Promise<void>;
304
304
  isProjectRegistered?: (project: string) => boolean;
305
+ classifyBareRoutability?: (spans: ParsedSpan[]) => {
306
+ rejected: number;
307
+ message?: string;
308
+ };
305
309
  bodyLimit?: number;
306
310
  authToken?: string;
307
311
  trustProxy?: boolean;
package/dist/index.d.ts CHANGED
@@ -302,6 +302,10 @@ interface BuildOtelReceiverOptions {
302
302
  onProjectSpan?: ProjectSpanHandler;
303
303
  onProjectErrorSpanSync?: (project: string, span: ParsedSpan) => Promise<void>;
304
304
  isProjectRegistered?: (project: string) => boolean;
305
+ classifyBareRoutability?: (spans: ParsedSpan[]) => {
306
+ rejected: number;
307
+ message?: string;
308
+ };
305
309
  bodyLimit?: number;
306
310
  authToken?: string;
307
311
  trustProxy?: boolean;
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  routeSpanToProject,
3
3
  startDaemon
4
- } from "./chunk-OVRVDSUO.js";
4
+ } from "./chunk-ILG3SMD5.js";
5
5
  import {
6
6
  ProjectNameCollisionError,
7
7
  addProject,
@@ -37,15 +37,15 @@ import {
37
37
  thresholdForEdgeType,
38
38
  touchLastSeen,
39
39
  writeAtomically
40
- } from "./chunk-LN75Z624.js";
40
+ } from "./chunk-N5TPODCX.js";
41
41
  import {
42
42
  startOtelGrpcReceiver
43
- } from "./chunk-4GCV46AP.js";
43
+ } from "./chunk-FCO5Z3RW.js";
44
44
  import {
45
45
  buildOtelReceiver,
46
46
  logSpanHandler,
47
47
  parseOtlpRequest
48
- } from "./chunk-Y43UCVZS.js";
48
+ } from "./chunk-UUYCTH2E.js";
49
49
  export {
50
50
  ProjectNameCollisionError,
51
51
  addProject,
package/dist/neatd.cjs CHANGED
@@ -504,13 +504,18 @@ function loadProtobufResponseEncoder() {
504
504
  );
505
505
  return exportTraceServiceResponseType;
506
506
  }
507
- function encodeProtobufResponseBody() {
508
- if (cachedProtobufResponseBody) return cachedProtobufResponseBody;
507
+ function encodeProtobufResponseBody(rejected, message) {
509
508
  const Type = loadProtobufResponseEncoder();
510
- const msg = Type.create({});
511
- const encoded = Type.encode(msg).finish();
512
- cachedProtobufResponseBody = Buffer.from(encoded);
513
- return cachedProtobufResponseBody;
509
+ if (!rejected) {
510
+ if (cachedProtobufResponseBody) return cachedProtobufResponseBody;
511
+ const msg2 = Type.create({});
512
+ cachedProtobufResponseBody = Buffer.from(Type.encode(msg2).finish());
513
+ return cachedProtobufResponseBody;
514
+ }
515
+ const msg = Type.fromObject({
516
+ partial_success: { rejected_spans: rejected, error_message: message ?? "" }
517
+ });
518
+ return Buffer.from(Type.encode(msg).finish());
514
519
  }
515
520
  async function decodeProtobufBody(buf) {
516
521
  const Type = loadProtobufDecoder();
@@ -625,6 +630,13 @@ async function buildOtelReceiver(opts) {
625
630
  }
626
631
  return reply.code(200).header("content-type", "application/json").send({ partialSuccess: {} });
627
632
  }
633
+ function sendOtlpPartial(reply, flavor, rejected, message) {
634
+ if (flavor === "protobuf") {
635
+ const buf = encodeProtobufResponseBody(rejected, message);
636
+ return reply.code(200).header("content-type", "application/x-protobuf").send(buf);
637
+ }
638
+ return reply.code(200).header("content-type", "application/json").send({ partialSuccess: { rejectedSpans: rejected, errorMessage: message } });
639
+ }
628
640
  app.addContentTypeParser(
629
641
  "application/x-protobuf",
630
642
  { parseAs: "buffer", bodyLimit: opts.bodyLimit ?? 16 * 1024 * 1024 },
@@ -652,6 +664,10 @@ async function buildOtelReceiver(opts) {
652
664
  }
653
665
  }
654
666
  enqueue(spans);
667
+ if (opts.classifyBareRoutability) {
668
+ const { rejected, message } = opts.classifyBareRoutability(spans);
669
+ if (rejected > 0) return sendOtlpPartial(reply, result.flavor, rejected, message);
670
+ }
655
671
  return sendOtlpSuccess(reply, result.flavor);
656
672
  });
657
673
  app.post("/projects/:project/v1/traces", async (req2, reply) => {
@@ -4828,10 +4844,15 @@ function resolveDistToSrc(absFilepath, line) {
4828
4844
  }
4829
4845
  if (!entry2) return null;
4830
4846
  try {
4831
- const pos = entry2.consumer.originalPositionFor({
4832
- line: line !== void 0 && Number.isFinite(line) ? line : 1,
4833
- column: 0
4834
- });
4847
+ const queryLine = line !== void 0 && Number.isFinite(line) ? line : 1;
4848
+ let pos = entry2.consumer.originalPositionFor({ line: queryLine, column: 0 });
4849
+ if (!pos || !pos.source) {
4850
+ pos = entry2.consumer.originalPositionFor({
4851
+ line: queryLine,
4852
+ column: 0,
4853
+ bias: sourceMapJs.SourceMapConsumer.LEAST_UPPER_BOUND
4854
+ });
4855
+ }
4835
4856
  if (!pos || !pos.source) return null;
4836
4857
  const root = entry2.consumer.sourceRoot ?? "";
4837
4858
  const resolved = import_node_path8.default.resolve(entry2.dir, root, pos.source);
@@ -4840,6 +4861,9 @@ function resolveDistToSrc(absFilepath, line) {
4840
4861
  return null;
4841
4862
  }
4842
4863
  }
4864
+ function hasAdjacentSourceMap(absFilepath) {
4865
+ return sourceMapCache.get(absFilepath) != null;
4866
+ }
4843
4867
  function callSiteFromSpan(span, serviceNode, scanPath) {
4844
4868
  const filepath = codeFilepathOf(span.attributes);
4845
4869
  if (filepath === void 0) return null;
@@ -4855,7 +4879,7 @@ function callSiteFromSpan(span, serviceNode, scanPath) {
4855
4879
  }
4856
4880
  const relPath = relPathForRuntimeFile(effectivePath, serviceNode, scanPath);
4857
4881
  if (!relPath) return null;
4858
- if (!resolved && abs.endsWith(".js") && relPath.startsWith("dist/") && serviceNode?.name) {
4882
+ if (!resolved && abs.endsWith(".js") && relPath.startsWith("dist/") && !hasAdjacentSourceMap(abs) && serviceNode?.name) {
4859
4883
  warnNoSourceMaps(serviceNode.name);
4860
4884
  }
4861
4885
  const fn = codeFunctionOf(span.attributes);
@@ -5131,7 +5155,7 @@ function resolveServiceId(graph, host, env) {
5131
5155
  function frontierIdFor(host) {
5132
5156
  return (0, import_types8.frontierId)(host);
5133
5157
  }
5134
- function ensureServiceNode(graph, serviceName, env) {
5158
+ function resolveFusedServiceId(graph, serviceName, env) {
5135
5159
  const id = (0, import_types8.serviceId)(serviceName, env);
5136
5160
  if (graph.hasNode(id)) return id;
5137
5161
  const wanted = serviceName.toLowerCase();
@@ -5141,17 +5165,21 @@ function ensureServiceNode(graph, serviceName, env) {
5141
5165
  if (svc.discoveredVia === "otel") return false;
5142
5166
  return typeof svc.name === "string" && svc.name.toLowerCase() === wanted;
5143
5167
  });
5144
- if (extractedId) return extractedId;
5168
+ return extractedId ?? id;
5169
+ }
5170
+ function ensureServiceNode(graph, serviceName, env) {
5171
+ const resolved = resolveFusedServiceId(graph, serviceName, env);
5172
+ if (graph.hasNode(resolved)) return resolved;
5145
5173
  const node = {
5146
- id,
5174
+ id: resolved,
5147
5175
  type: import_types8.NodeType.ServiceNode,
5148
5176
  name: serviceName,
5149
5177
  language: "unknown",
5150
5178
  discoveredVia: "otel",
5151
5179
  ...env !== "unknown" ? { env } : {}
5152
5180
  };
5153
- graph.addNode(id, node);
5154
- return id;
5181
+ graph.addNode(resolved, node);
5182
+ return resolved;
5155
5183
  }
5156
5184
  function ensureInfraNode(graph, kind, name, provider) {
5157
5185
  const id = (0, import_types8.infraId)(kind, name);
@@ -5345,7 +5373,7 @@ async function appendErrorEvent(ctx, ev) {
5345
5373
  await import_node_fs7.promises.appendFile(ctx.errorsPath, JSON.stringify(ev) + "\n", "utf8");
5346
5374
  }
5347
5375
  function incidentAffectedNode(span, graph, scanPath) {
5348
- const sid = (0, import_types8.serviceId)(span.service, span.env);
5376
+ const sid = graph ? resolveFusedServiceId(graph, span.service, span.env) : (0, import_types8.serviceId)(span.service, span.env);
5349
5377
  const serviceNode = graph && graph.hasNode(sid) ? graph.getNodeAttributes(sid) : void 0;
5350
5378
  const callSite = callSiteFromSpan(span, serviceNode, scanPath);
5351
5379
  if (callSite) {
@@ -6744,7 +6772,7 @@ function disambiguate(defs) {
6744
6772
  }
6745
6773
  async function addSymbols(graph, services) {
6746
6774
  const parsers = /* @__PURE__ */ new Map();
6747
- const parserForExt4 = (ext) => {
6775
+ const parserForExt5 = (ext) => {
6748
6776
  const grammar = GRAMMAR_BY_EXT[ext];
6749
6777
  if (!grammar) return null;
6750
6778
  let parser = parsers.get(ext);
@@ -6760,7 +6788,7 @@ async function addSymbols(graph, services) {
6760
6788
  for (const service of services) {
6761
6789
  const files = await loadSourceFiles(service.dir);
6762
6790
  for (const file of files) {
6763
- const parser = parserForExt4(import_node_path17.default.extname(file.path));
6791
+ const parser = parserForExt5(import_node_path17.default.extname(file.path));
6764
6792
  if (!parser) continue;
6765
6793
  const relPath = toPosix(import_node_path17.default.relative(service.dir, file.path));
6766
6794
  let defs;
@@ -6912,7 +6940,7 @@ function stringInner(node) {
6912
6940
  }
6913
6941
  async function addSymbolEdges(graph, services) {
6914
6942
  const parsers = /* @__PURE__ */ new Map();
6915
- const parserForExt4 = (ext) => {
6943
+ const parserForExt5 = (ext) => {
6916
6944
  const grammar = GRAMMAR_BY_EXT[ext];
6917
6945
  if (!grammar) return null;
6918
6946
  let parser = parsers.get(ext);
@@ -6929,7 +6957,7 @@ async function addSymbolEdges(graph, services) {
6929
6957
  const tsPaths = await loadTsPathConfig(service.dir);
6930
6958
  const files = await loadSourceFiles(service.dir);
6931
6959
  for (const file of files) {
6932
- const parser = parserForExt4(import_node_path18.default.extname(file.path));
6960
+ const parser = parserForExt5(import_node_path18.default.extname(file.path));
6933
6961
  if (!parser) continue;
6934
6962
  const relPath = toPosix(import_node_path18.default.relative(service.dir, file.path));
6935
6963
  const fileDir = import_node_path18.default.dirname(file.path);
@@ -7191,7 +7219,7 @@ function firstReferenceLines(root, wanted) {
7191
7219
  }
7192
7220
  async function addServerActions(graph, services) {
7193
7221
  const parsers = /* @__PURE__ */ new Map();
7194
- const parserForExt4 = (ext) => {
7222
+ const parserForExt5 = (ext) => {
7195
7223
  const grammar = GRAMMAR_BY_EXT[ext];
7196
7224
  if (!grammar) return null;
7197
7225
  let parser = parsers.get(ext);
@@ -7214,7 +7242,7 @@ async function addServerActions(graph, services) {
7214
7242
  const files = await loadSourceFiles(service.dir);
7215
7243
  for (const file of files) {
7216
7244
  if (isTestPath(file.path)) continue;
7217
- const parser = parserForExt4(import_node_path19.default.extname(file.path));
7245
+ const parser = parserForExt5(import_node_path19.default.extname(file.path));
7218
7246
  if (!parser) continue;
7219
7247
  const relPath = toPosix(import_node_path19.default.relative(service.dir, file.path));
7220
7248
  let root;
@@ -7277,7 +7305,7 @@ async function addServerActions(graph, services) {
7277
7305
  }
7278
7306
  for (const file of files) {
7279
7307
  if (isTestPath(file.path)) continue;
7280
- const parser = parserForExt4(import_node_path19.default.extname(file.path));
7308
+ const parser = parserForExt5(import_node_path19.default.extname(file.path));
7281
7309
  if (!parser) continue;
7282
7310
  const relPath = toPosix(import_node_path19.default.relative(service.dir, file.path));
7283
7311
  const fileDir = import_node_path19.default.dirname(file.path);
@@ -8241,6 +8269,7 @@ init_cjs_shims();
8241
8269
  var import_node_path30 = __toESM(require("path"), 1);
8242
8270
  var import_tree_sitter6 = __toESM(require("tree-sitter"), 1);
8243
8271
  var import_tree_sitter_javascript4 = __toESM(require("tree-sitter-javascript"), 1);
8272
+ var import_tree_sitter_typescript2 = __toESM(require("tree-sitter-typescript"), 1);
8244
8273
  var import_tree_sitter_python3 = __toESM(require("tree-sitter-python"), 1);
8245
8274
  var import_types20 = require("@neat.is/types");
8246
8275
  var STRING_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set(["string_fragment", "string_content"]);
@@ -8291,19 +8320,27 @@ function callsFromSource(source, parser, knownHosts) {
8291
8320
  }
8292
8321
  return out;
8293
8322
  }
8294
- function makeJsParser3() {
8295
- const p = new import_tree_sitter6.default();
8296
- p.setLanguage(import_tree_sitter_javascript4.default);
8297
- return p;
8298
- }
8299
- function makePyParser3() {
8300
- const p = new import_tree_sitter6.default();
8301
- p.setLanguage(import_tree_sitter_python3.default);
8302
- return p;
8323
+ var GRAMMAR_BY_EXT2 = {
8324
+ ".ts": import_tree_sitter_typescript2.default.typescript,
8325
+ ".tsx": import_tree_sitter_typescript2.default.tsx,
8326
+ ".js": import_tree_sitter_javascript4.default,
8327
+ ".jsx": import_tree_sitter_javascript4.default,
8328
+ ".mjs": import_tree_sitter_javascript4.default,
8329
+ ".cjs": import_tree_sitter_javascript4.default,
8330
+ ".py": import_tree_sitter_python3.default
8331
+ };
8332
+ function parserForExt(ext, cache) {
8333
+ const grammar = GRAMMAR_BY_EXT2[ext] ?? import_tree_sitter_javascript4.default;
8334
+ let parser = cache.get(grammar);
8335
+ if (!parser) {
8336
+ parser = new import_tree_sitter6.default();
8337
+ parser.setLanguage(grammar);
8338
+ cache.set(grammar, parser);
8339
+ }
8340
+ return parser;
8303
8341
  }
8304
8342
  async function addHttpCallEdges(graph, services) {
8305
- const jsParser = makeJsParser3();
8306
- const pyParser = makePyParser3();
8343
+ const parserCache = /* @__PURE__ */ new Map();
8307
8344
  const { knownHosts, hostToNodeId } = buildServiceHostIndex(services);
8308
8345
  let nodesAdded = 0;
8309
8346
  let edgesAdded = 0;
@@ -8312,7 +8349,7 @@ async function addHttpCallEdges(graph, services) {
8312
8349
  const seen = /* @__PURE__ */ new Set();
8313
8350
  for (const file of files) {
8314
8351
  if (isTestPath(file.path)) continue;
8315
- const parser = import_node_path30.default.extname(file.path) === ".py" ? pyParser : jsParser;
8352
+ const parser = parserForExt(import_node_path30.default.extname(file.path), parserCache);
8316
8353
  let sites;
8317
8354
  try {
8318
8355
  sites = callsFromSource(file.content, parser, knownHosts);
@@ -8385,7 +8422,7 @@ function parseSource5(parser, source) {
8385
8422
  (index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK5)
8386
8423
  );
8387
8424
  }
8388
- function makeJsParser4() {
8425
+ function makeJsParser3() {
8389
8426
  const p = new import_tree_sitter7.default();
8390
8427
  p.setLanguage(import_tree_sitter_javascript5.default);
8391
8428
  return p;
@@ -8563,7 +8600,7 @@ function findRoute(entries, method, normalizedPath) {
8563
8600
  );
8564
8601
  }
8565
8602
  async function addRouteCallEdges(graph, services) {
8566
- const jsParser = makeJsParser4();
8603
+ const jsParser = makeJsParser3();
8567
8604
  const { knownHosts, hostToNodeId } = buildServiceHostIndex(services);
8568
8605
  const routeIndex = buildRouteIndex(graph);
8569
8606
  if (routeIndex.size === 0) return { nodesAdded: 0, edgesAdded: 0 };
@@ -8955,7 +8992,7 @@ var import_tree_sitter_javascript6 = __toESM(require("tree-sitter-javascript"),
8955
8992
  var import_types27 = require("@neat.is/types");
8956
8993
  var FIRESTORE_CLIENT_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])firebase\/firestore['"`]/;
8957
8994
  var FIRESTORE_ADMIN_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])firebase-admin(?:\/firestore)?['"`]/;
8958
- function parserForExt(ext) {
8995
+ function parserForExt2(ext) {
8959
8996
  const p = new import_tree_sitter8.default();
8960
8997
  p.setLanguage(GRAMMAR_BY_EXT[ext] ?? import_tree_sitter_javascript6.default);
8961
8998
  return p;
@@ -9120,7 +9157,7 @@ function firestoreEndpointsFromFile(file, serviceDir) {
9120
9157
  const hasAdmin = FIRESTORE_ADMIN_IMPORT_RE.test(file.content);
9121
9158
  if (!hasClient && !hasAdmin) return [];
9122
9159
  const fileSdk = hasClient && !hasAdmin ? "client" : hasAdmin && !hasClient ? "admin" : null;
9123
- const tree = parseSource3(parserForExt(import_node_path37.default.extname(file.path)), file.content);
9160
+ const tree = parseSource3(parserForExt2(import_node_path37.default.extname(file.path)), file.content);
9124
9161
  const clientVars = firestoreClientVars(tree.rootNode);
9125
9162
  const collLine = /* @__PURE__ */ new Map();
9126
9163
  const writes = /* @__PURE__ */ new Map();
@@ -9537,7 +9574,7 @@ var import_tree_sitter_python4 = __toESM(require("tree-sitter-python"), 1);
9537
9574
  var import_types29 = require("@neat.is/types");
9538
9575
  var SQLALCHEMY_IMPORT_RE = /(?:from|import)\s+(?:flask_sqlalchemy|sqlalchemy)\b/;
9539
9576
  var PARSE_CHUNK6 = 16384;
9540
- function makePyParser4() {
9577
+ function makePyParser3() {
9541
9578
  const p = new import_tree_sitter9.default();
9542
9579
  p.setLanguage(import_tree_sitter_python4.default);
9543
9580
  return p;
@@ -9644,7 +9681,7 @@ function foreignKeyParentTable(call) {
9644
9681
  }
9645
9682
  function sqlalchemyForeignKeys(file, serviceDir) {
9646
9683
  if (!SQLALCHEMY_IMPORT_RE.test(file.content)) return [];
9647
- const tree = parseSource6(makePyParser4(), file.content);
9684
+ const tree = parseSource6(makePyParser3(), file.content);
9648
9685
  const out = [];
9649
9686
  const seen = /* @__PURE__ */ new Set();
9650
9687
  walk3(tree.rootNode, (node) => {
@@ -9681,7 +9718,7 @@ function sqlalchemyForeignKeys(file, serviceDir) {
9681
9718
  }
9682
9719
  function sqlalchemyEndpointsFromFile(file, serviceDir) {
9683
9720
  if (!SQLALCHEMY_IMPORT_RE.test(file.content)) return [];
9684
- const tree = parseSource6(makePyParser4(), file.content);
9721
+ const tree = parseSource6(makePyParser3(), file.content);
9685
9722
  const out = [];
9686
9723
  const seen = /* @__PURE__ */ new Set();
9687
9724
  const push = (name, line, columns) => {
@@ -9733,7 +9770,7 @@ function sqlalchemyEndpointsFromFile(file, serviceDir) {
9733
9770
  function buildSqlalchemyModelRegistry(files) {
9734
9771
  const table = /* @__PURE__ */ new Map();
9735
9772
  const ambiguous = /* @__PURE__ */ new Set();
9736
- const parser = makePyParser4();
9773
+ const parser = makePyParser3();
9737
9774
  for (const file of files) {
9738
9775
  if (!SQLALCHEMY_IMPORT_RE.test(file.content)) continue;
9739
9776
  const tree = parseSource6(parser, file.content);
@@ -9782,7 +9819,7 @@ function importsModelName(content, name) {
9782
9819
  function pythonOrmCrossFileEndpoints(files, serviceDir) {
9783
9820
  const registry = buildSqlalchemyModelRegistry(files);
9784
9821
  if (registry.size === 0) return [];
9785
- const parser = makePyParser4();
9822
+ const parser = makePyParser3();
9786
9823
  const out = [];
9787
9824
  const seen = /* @__PURE__ */ new Set();
9788
9825
  for (const file of files) {
@@ -9820,7 +9857,7 @@ var import_tree_sitter_python5 = __toESM(require("tree-sitter-python"), 1);
9820
9857
  var import_types30 = require("@neat.is/types");
9821
9858
  var DJANGO_IMPORT_RE = /(?:from|import)\s+django\b/;
9822
9859
  var PARSE_CHUNK7 = 16384;
9823
- function makePyParser5() {
9860
+ function makePyParser4() {
9824
9861
  const p = new import_tree_sitter10.default();
9825
9862
  p.setLanguage(import_tree_sitter_python5.default);
9826
9863
  return p;
@@ -9881,7 +9918,7 @@ function readMeta(body) {
9881
9918
  }
9882
9919
  function djangoOrmEndpointsFromFile(file, serviceDir) {
9883
9920
  if (!DJANGO_IMPORT_RE.test(file.content)) return [];
9884
- const tree = parseSource7(makePyParser5(), file.content);
9921
+ const tree = parseSource7(makePyParser4(), file.content);
9885
9922
  const out = [];
9886
9923
  const seen = /* @__PURE__ */ new Set();
9887
9924
  const defaultAppLabel = import_node_path40.default.basename(import_node_path40.default.dirname(file.path));
@@ -9916,7 +9953,7 @@ var import_tree_sitter_javascript7 = __toESM(require("tree-sitter-javascript"),
9916
9953
  var import_types31 = require("@neat.is/types");
9917
9954
  var DRIZZLE_IMPORT_RE = /drizzle-orm/;
9918
9955
  var TABLE_BUILDERS = /* @__PURE__ */ new Set(["pgTable", "mysqlTable", "sqliteTable"]);
9919
- function parserForExt2(ext) {
9956
+ function parserForExt3(ext) {
9920
9957
  const p = new import_tree_sitter11.default();
9921
9958
  p.setLanguage(GRAMMAR_BY_EXT[ext] ?? import_tree_sitter_javascript7.default);
9922
9959
  return p;
@@ -9988,7 +10025,7 @@ function columnsFromObject(obj) {
9988
10025
  }
9989
10026
  function drizzleEndpointsFromFile(file, serviceDir) {
9990
10027
  if (!DRIZZLE_IMPORT_RE.test(file.content)) return [];
9991
- const tree = parseSource3(parserForExt2(import_node_path41.default.extname(file.path)), file.content);
10028
+ const tree = parseSource3(parserForExt3(import_node_path41.default.extname(file.path)), file.content);
9992
10029
  const out = [];
9993
10030
  const seen = /* @__PURE__ */ new Set();
9994
10031
  const walk9 = (node) => {
@@ -10077,7 +10114,7 @@ function referencesTargetVar(call) {
10077
10114
  }
10078
10115
  function drizzleForeignKeys(file, serviceDir) {
10079
10116
  if (!DRIZZLE_IMPORT_RE.test(file.content)) return [];
10080
- const tree = parseSource3(parserForExt2(import_node_path41.default.extname(file.path)), file.content);
10117
+ const tree = parseSource3(parserForExt3(import_node_path41.default.extname(file.path)), file.content);
10081
10118
  const { tables, varToTable } = collectDrizzleTables(tree.rootNode);
10082
10119
  const out = [];
10083
10120
  const seen = /* @__PURE__ */ new Set();
@@ -12784,7 +12821,7 @@ var import_tree_sitter_javascript8 = __toESM(require("tree-sitter-javascript"),
12784
12821
  var import_types47 = require("@neat.is/types");
12785
12822
  var ZOD_IMPORT_RE = /\bzod\b/;
12786
12823
  var ZOD_OBJECTS = /* @__PURE__ */ new Set(["z", "zod"]);
12787
- function parserForExt3(ext) {
12824
+ function parserForExt4(ext) {
12788
12825
  const p = new import_tree_sitter16.default();
12789
12826
  p.setLanguage(GRAMMAR_BY_EXT[ext] ?? import_tree_sitter_javascript8.default);
12790
12827
  return p;
@@ -12873,7 +12910,7 @@ function topLevelSchemas(root) {
12873
12910
  }
12874
12911
  function zodShapesFromFile(file, serviceDir) {
12875
12912
  if (!ZOD_IMPORT_RE.test(file.content)) return [];
12876
- const tree = parseSource3(parserForExt3(import_node_path57.default.extname(file.path)), file.content);
12913
+ const tree = parseSource3(parserForExt4(import_node_path57.default.extname(file.path)), file.content);
12877
12914
  const out = [];
12878
12915
  const seen = /* @__PURE__ */ new Set();
12879
12916
  for (const { name, call } of topLevelSchemas(tree.rootNode)) {
@@ -18839,6 +18876,19 @@ async function startDaemon(opts = {}) {
18839
18876
  let otlpAddress = "";
18840
18877
  let daemonRecord = null;
18841
18878
  if (bind) {
18879
+ let bareSpanIsRoutable2 = function(serviceName) {
18880
+ if (singleProject) {
18881
+ const slot = slots.get(singleProject);
18882
+ if (!slot) {
18883
+ return !serviceName || serviceNameMatchesProject(serviceName, singleProject);
18884
+ }
18885
+ return spanBelongsToSingleProject(slot.graph, singleProject, serviceName);
18886
+ }
18887
+ const entries = [...slots.values()].map((s) => s.entry);
18888
+ const target = routeSpanToProject(serviceName, entries);
18889
+ return slots.has(target) || slots.has(DEFAULT_PROJECT);
18890
+ };
18891
+ var bareSpanIsRoutable = bareSpanIsRoutable2;
18842
18892
  const auth = readAuthEnv();
18843
18893
  const host = resolveHost(opts, Boolean(auth.authToken));
18844
18894
  const restPort = resolveRestPort(opts);
@@ -18936,6 +18986,20 @@ async function startDaemon(opts = {}) {
18936
18986
  const liveEntries = await listProjects().catch(() => []);
18937
18987
  let slot = slots.get(project);
18938
18988
  if (!slot) {
18989
+ if (singleProject && project === singleProject) {
18990
+ slot = await tryRecoverSlot({
18991
+ name: singleProject,
18992
+ path: singleProjectPath,
18993
+ registeredAt: (/* @__PURE__ */ new Date()).toISOString(),
18994
+ languages: [],
18995
+ status: "active"
18996
+ });
18997
+ if (!slot || slot.status !== "active") {
18998
+ warnDroppedSpan(singleProject, slot?.errorReason ?? "unknown");
18999
+ return null;
19000
+ }
19001
+ return slot;
19002
+ }
18939
19003
  await recordUnroutedSpan(serviceName, traceId);
18940
19004
  return null;
18941
19005
  }
@@ -19002,7 +19066,30 @@ async function startDaemon(opts = {}) {
19002
19066
  // host, rather than accepting it and dropping the batch. `slots` covers
19003
19067
  // active/recovering projects, `bootstrapStatus` the ones still
19004
19068
  // extracting; a foreign or wrong-cased project name matches neither.
19005
- isProjectRegistered: (project) => slots.has(project) || bootstrapStatus.has(project)
19069
+ // A single-project daemon owns exactly one project by definition, so its
19070
+ // own name always counts as registered even before loadAll populates the
19071
+ // slot — otherwise a scoped span arriving during cold-start would 404 and
19072
+ // be lost (OTLP does not retry 4xx). resolveSlotByName then builds it (#879).
19073
+ isProjectRegistered: (project) => slots.has(project) || bootstrapStatus.has(project) || singleProject !== void 0 && project === singleProject,
19074
+ // #881 — the bare `/v1/traces` route replies before the span is routed
19075
+ // (off the queue), so tell the receiver, per batch, how many spans will
19076
+ // land on no project. It keeps the 200 but reports those as
19077
+ // partialSuccess.rejectedSpans instead of an empty partialSuccess that an
19078
+ // exporter reads as full acceptance. Pure — the drop + unrouted-ledger
19079
+ // write still happen on the async onSpan path.
19080
+ classifyBareRoutability: (spans) => {
19081
+ let rejected = 0;
19082
+ for (const span of spans) {
19083
+ if (!bareSpanIsRoutable2(span.service)) rejected++;
19084
+ }
19085
+ if (rejected === 0) return { rejected: 0 };
19086
+ const noun = rejected === 1 ? "span" : "spans";
19087
+ const verb = rejected === 1 ? "was" : "were";
19088
+ return {
19089
+ rejected,
19090
+ 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.`
19091
+ };
19092
+ }
19006
19093
  });
19007
19094
  otlpAddress = await listenSteppingOtlp(otlpApp, otlpPort, host);
19008
19095
  console.log(`neatd: OTLP listening on ${otlpAddress}/v1/traces`);