@neat.is/core 0.7.6 → 0.7.7

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.
@@ -3,7 +3,7 @@ import {
3
3
  mountBearerAuth,
4
4
  readAuthEnv,
5
5
  tableFromSqlStatement
6
- } from "./chunk-P2ZEKJ35.js";
6
+ } from "./chunk-Y43UCVZS.js";
7
7
 
8
8
  // src/graph.ts
9
9
  import GraphDefault from "graphology";
@@ -1247,14 +1247,14 @@ function buildServiceHostIndex(services) {
1247
1247
  }
1248
1248
  async function walkSourceFiles(dir) {
1249
1249
  const out = [];
1250
- async function walk8(current) {
1250
+ async function walk9(current) {
1251
1251
  const entries = await fs5.readdir(current, { withFileTypes: true }).catch(() => []);
1252
1252
  for (const entry of entries) {
1253
1253
  const full = path5.join(current, entry.name);
1254
1254
  if (entry.isDirectory()) {
1255
1255
  if (IGNORED_DIRS.has(entry.name)) continue;
1256
1256
  if (await isPythonVenvDir(full)) continue;
1257
- await walk8(full);
1257
+ await walk9(full);
1258
1258
  } else if (entry.isFile() && SERVICE_FILE_EXTENSIONS.has(path5.extname(entry.name)) && // Skip NEAT's own generated `otel-init.*` bootstrap — extracting it
1259
1259
  // would attribute our instrumentation imports to the user's service.
1260
1260
  !isNeatAuthoredSourceFile(entry.name)) {
@@ -1262,7 +1262,7 @@ async function walkSourceFiles(dir) {
1262
1262
  }
1263
1263
  }
1264
1264
  }
1265
- await walk8(dir);
1265
+ await walk9(dir);
1266
1266
  return out;
1267
1267
  }
1268
1268
  async function loadSourceFiles(dir) {
@@ -1781,7 +1781,7 @@ var ROUTER_METHODS = /* @__PURE__ */ new Set([
1781
1781
  ]);
1782
1782
  var NEXT_APP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
1783
1783
  var JS_ROUTE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx"]);
1784
- function ginRoutesFromSource(source, parser) {
1784
+ function goRouterRoutesFromSource(source, parser, framework) {
1785
1785
  const tree = parseSource2(parser, source);
1786
1786
  const prefixes = /* @__PURE__ */ new Map();
1787
1787
  const out = [];
@@ -1791,10 +1791,12 @@ function ginRoutesFromSource(source, parser) {
1791
1791
  const value = node.childForFieldName("right")?.namedChild(0) ?? node.childForFieldName("value");
1792
1792
  if (name && value?.type === "call_expression") {
1793
1793
  const fn2 = value.childForFieldName("function");
1794
- const field = fn2?.childForFieldName("field")?.text;
1795
- const first2 = value.childForFieldName("arguments")?.namedChild(0);
1796
- if (field === "Group" && first2?.type === "interpreted_string_literal") {
1797
- prefixes.set(name, first2.text.slice(1, -1));
1794
+ if (fn2?.childForFieldName("field")?.text === "Group") {
1795
+ const leaf2 = goStringLiteral(value.childForFieldName("arguments")?.namedChild(0));
1796
+ if (leaf2 !== null) {
1797
+ const parent = fn2.childForFieldName("operand")?.text ?? "";
1798
+ prefixes.set(name, (prefixes.get(parent) ?? "") + leaf2);
1799
+ }
1798
1800
  }
1799
1801
  }
1800
1802
  return;
@@ -1805,18 +1807,32 @@ function ginRoutesFromSource(source, parser) {
1805
1807
  const method = fn.childForFieldName("field")?.text?.toUpperCase();
1806
1808
  if (!method || !ROUTER_METHODS.has(method.toLowerCase())) return;
1807
1809
  const receiver = fn.childForFieldName("operand")?.text ?? "";
1808
- const first = node.childForFieldName("arguments")?.namedChild(0);
1809
- if (first?.type !== "interpreted_string_literal") return;
1810
- const leaf = first.text.slice(1, -1);
1810
+ const leaf = goStringLiteral(node.childForFieldName("arguments")?.namedChild(0));
1811
+ if (leaf === null) return;
1811
1812
  out.push({
1812
- method: method === "ALL" ? "ALL" : method,
1813
+ method,
1813
1814
  pathTemplate: canonicalizeTemplate((prefixes.get(receiver) ?? "") + leaf),
1814
1815
  line: node.startPosition.row + 1,
1815
- framework: "gin"
1816
+ framework
1816
1817
  });
1817
1818
  });
1818
1819
  return out;
1819
1820
  }
1821
+ function goStringLiteral(node) {
1822
+ if (node?.type === "interpreted_string_literal" || node?.type === "raw_string_literal") {
1823
+ return node.text.slice(1, -1);
1824
+ }
1825
+ return null;
1826
+ }
1827
+ function ginRoutesFromSource(source, parser) {
1828
+ return goRouterRoutesFromSource(source, parser, "gin");
1829
+ }
1830
+ function echoRoutesFromSource(source, parser) {
1831
+ return goRouterRoutesFromSource(source, parser, "echo");
1832
+ }
1833
+ function fiberRoutesFromSource(source, parser) {
1834
+ return goRouterRoutesFromSource(source, parser, "fiber");
1835
+ }
1820
1836
  var FASTAPI_METHODS = /* @__PURE__ */ new Set(["get", "post", "put", "patch", "delete", "options", "head", "trace"]);
1821
1837
  var NESTJS_METHODS = /* @__PURE__ */ new Map([
1822
1838
  ["Get", "GET"],
@@ -2401,9 +2417,9 @@ function rubyRocketRoute(args) {
2401
2417
  if (!pair || pair.type !== "pair") continue;
2402
2418
  const k = pair.childForFieldName("key");
2403
2419
  if (k?.type !== "string") continue;
2404
- const path63 = rubyLiteral(k);
2405
- if (path63 === null) continue;
2406
- return { path: path63, target: rubyLiteral(pair.childForFieldName("value")) };
2420
+ const path64 = rubyLiteral(k);
2421
+ if (path64 === null) continue;
2422
+ return { path: path64, target: rubyLiteral(pair.childForFieldName("value")) };
2407
2423
  }
2408
2424
  return null;
2409
2425
  }
@@ -3080,9 +3096,11 @@ async function addRoutes(graph, services) {
3080
3096
  const hasFlask = deps["flask"] !== void 0;
3081
3097
  const hasDjango = deps["django"] !== void 0;
3082
3098
  const hasGin = deps["github.com/gin-gonic/gin"] !== void 0;
3099
+ const hasEcho = deps["github.com/labstack/echo/v4"] !== void 0 || deps["github.com/labstack/echo"] !== void 0;
3100
+ const hasFiber = deps["github.com/gofiber/fiber/v2"] !== void 0 || deps["github.com/gofiber/fiber/v3"] !== void 0;
3083
3101
  const hasRails = deps["rails"] !== void 0;
3084
3102
  const hasLaravel = deps["laravel/framework"] !== void 0;
3085
- if (!hasExpress && !hasFastify && !hasHono && !hasNext && !hasNestjs && !hasFastapi && !hasFlask && !hasDjango && !hasGin && !hasRails && !hasLaravel)
3103
+ if (!hasExpress && !hasFastify && !hasHono && !hasNext && !hasNestjs && !hasFastapi && !hasFlask && !hasDjango && !hasGin && !hasEcho && !hasFiber && !hasRails && !hasLaravel)
3086
3104
  continue;
3087
3105
  const files = await loadSourceFiles(service.dir);
3088
3106
  const mountPrefixes = hasExpress ? await expressMountPrefixes(files, service.dir, await loadTsPathConfig(service.dir)) : /* @__PURE__ */ new Map();
@@ -3106,7 +3124,10 @@ async function addRoutes(graph, services) {
3106
3124
  } else if (isRb) {
3107
3125
  routes = hasRails && relFile === "config/routes.rb" ? railsRoutesFromSource(file.content, rubyParser) : [];
3108
3126
  } else if (isGo) {
3109
- routes = hasGin ? ginRoutesFromSource(file.content, goParser) : [];
3127
+ if (hasGin) routes = ginRoutesFromSource(file.content, goParser);
3128
+ else if (hasEcho) routes = echoRoutesFromSource(file.content, goParser);
3129
+ else if (hasFiber) routes = fiberRoutesFromSource(file.content, goParser);
3130
+ else routes = [];
3110
3131
  } else if (isPy) {
3111
3132
  routes = hasFastapi || hasFlask ? pythonRoutesFromSource(file.content, pyParser, hasFastapi ? "fastapi" : "flask") : [];
3112
3133
  if (hasDjango) routes = routes.concat(djangoRoutesFromSource(file.content, pyParser));
@@ -4740,19 +4761,19 @@ function confidenceFromMix(edges, now = Date.now()) {
4740
4761
  function longestIncomingWalk(graph, start, maxDepth) {
4741
4762
  let best = { path: [start], edges: [] };
4742
4763
  const visited = /* @__PURE__ */ new Set([start]);
4743
- function step(node, path63, edges) {
4744
- if (path63.length > best.path.length) {
4745
- best = { path: [...path63], edges: [...edges] };
4764
+ function step(node, path64, edges) {
4765
+ if (path64.length > best.path.length) {
4766
+ best = { path: [...path64], edges: [...edges] };
4746
4767
  }
4747
- if (path63.length - 1 >= maxDepth) return;
4768
+ if (path64.length - 1 >= maxDepth) return;
4748
4769
  const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
4749
4770
  for (const [srcId, edge] of incoming) {
4750
4771
  if (visited.has(srcId)) continue;
4751
4772
  visited.add(srcId);
4752
- path63.push(srcId);
4773
+ path64.push(srcId);
4753
4774
  edges.push(edge);
4754
- step(srcId, path63, edges);
4755
- path63.pop();
4775
+ step(srcId, path64, edges);
4776
+ path64.pop();
4756
4777
  edges.pop();
4757
4778
  visited.delete(srcId);
4758
4779
  }
@@ -4760,11 +4781,11 @@ function longestIncomingWalk(graph, start, maxDepth) {
4760
4781
  step(start, [start], []);
4761
4782
  return best;
4762
4783
  }
4763
- function databaseRootCauseShape(graph, origin, walk8) {
4784
+ function databaseRootCauseShape(graph, origin, walk9) {
4764
4785
  const targetDb = origin;
4765
4786
  const candidatePairs = compatPairs().filter((p) => p.engine === targetDb.engine);
4766
4787
  if (candidatePairs.length === 0) return null;
4767
- for (const id of walk8.path) {
4788
+ for (const id of walk9.path) {
4768
4789
  const owner = resolveOwningService(graph, id);
4769
4790
  if (!owner) continue;
4770
4791
  const { id: serviceId9, svc } = owner;
@@ -4791,8 +4812,8 @@ function databaseRootCauseShape(graph, origin, walk8) {
4791
4812
  }
4792
4813
  return null;
4793
4814
  }
4794
- function serviceRootCauseShape(graph, _origin, walk8) {
4795
- for (const id of walk8.path) {
4815
+ function serviceRootCauseShape(graph, _origin, walk9) {
4816
+ for (const id of walk9.path) {
4796
4817
  const owner = resolveOwningService(graph, id);
4797
4818
  if (!owner) continue;
4798
4819
  const { id: serviceId9, svc } = owner;
@@ -4828,15 +4849,15 @@ function serviceRootCauseShape(graph, _origin, walk8) {
4828
4849
  }
4829
4850
  return null;
4830
4851
  }
4831
- function fileRootCauseShape(graph, origin, walk8) {
4852
+ function fileRootCauseShape(graph, origin, walk9) {
4832
4853
  const owner = resolveOwningService(graph, origin.id);
4833
4854
  if (!owner) return null;
4834
- return serviceRootCauseShape(graph, owner.svc, walk8);
4855
+ return serviceRootCauseShape(graph, owner.svc, walk9);
4835
4856
  }
4836
- function symbolRootCauseShape(graph, origin, walk8) {
4857
+ function symbolRootCauseShape(graph, origin, walk9) {
4837
4858
  const owner = resolveOwningService(graph, origin.id);
4838
4859
  if (!owner) return null;
4839
- return serviceRootCauseShape(graph, owner.svc, walk8);
4860
+ return serviceRootCauseShape(graph, owner.svc, walk9);
4840
4861
  }
4841
4862
  var rootCauseShapes = {
4842
4863
  [NodeType5.DatabaseNode]: databaseRootCauseShape,
@@ -4849,16 +4870,16 @@ function getRootCause(graph, errorNodeId, errorEvent, incidents) {
4849
4870
  const origin = graph.getNodeAttributes(errorNodeId);
4850
4871
  const shape = rootCauseShapes[origin.type];
4851
4872
  if (shape) {
4852
- const walk8 = longestIncomingWalk(graph, errorNodeId, ROOT_CAUSE_MAX_DEPTH);
4853
- const match = shape(graph, origin, walk8);
4873
+ const walk9 = longestIncomingWalk(graph, errorNodeId, ROOT_CAUSE_MAX_DEPTH);
4874
+ const match = shape(graph, origin, walk9);
4854
4875
  if (match) {
4855
4876
  const reason = errorEvent ? `${match.rootCauseReason} (observed error: ${errorEvent.errorMessage})` : match.rootCauseReason;
4856
4877
  return RootCauseResultSchema.parse({
4857
4878
  rootCauseNode: match.rootCauseNode,
4858
4879
  rootCauseReason: reason,
4859
- traversalPath: walk8.path,
4860
- edgeProvenances: walk8.edges.map((e) => e.provenance),
4861
- confidence: confidenceFromMix(walk8.edges),
4880
+ traversalPath: walk9.path,
4881
+ edgeProvenances: walk9.edges.map((e) => e.provenance),
4882
+ confidence: confidenceFromMix(walk9.edges),
4862
4883
  fixRecommendation: match.fixRecommendation
4863
4884
  });
4864
4885
  }
@@ -4959,26 +4980,26 @@ function dominantFailingCall(graph, serviceId9, visited) {
4959
4980
  return best;
4960
4981
  }
4961
4982
  function followFailingCallChain(graph, originServiceId, maxDepth) {
4962
- const path63 = [originServiceId];
4983
+ const path64 = [originServiceId];
4963
4984
  const edges = [];
4964
4985
  const visited = /* @__PURE__ */ new Set([originServiceId]);
4965
4986
  let current = originServiceId;
4966
4987
  for (let depth = 0; depth < maxDepth; depth++) {
4967
4988
  const hop = dominantFailingCall(graph, current, visited);
4968
4989
  if (!hop) break;
4969
- path63.push(hop.nextService);
4990
+ path64.push(hop.nextService);
4970
4991
  edges.push(hop.edge);
4971
4992
  visited.add(hop.nextService);
4972
4993
  current = hop.nextService;
4973
4994
  }
4974
4995
  if (edges.length === 0) return null;
4975
- return { path: path63, edges, culprit: current };
4996
+ return { path: path64, edges, culprit: current };
4976
4997
  }
4977
4998
  function crossServiceRootCause(graph, originId, incidents, errorEvent) {
4978
4999
  const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
4979
5000
  if (!chain) return null;
4980
5001
  const culprit = chain.culprit;
4981
- const path63 = [...chain.path];
5002
+ const path64 = [...chain.path];
4982
5003
  const edgeProvenances = chain.edges.map((e) => e.provenance);
4983
5004
  const baseConfidence = confidenceFromMix(chain.edges);
4984
5005
  const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
@@ -4986,14 +5007,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
4986
5007
  if (loc) {
4987
5008
  let rootCauseNode = culprit;
4988
5009
  if (loc.fileNode) {
4989
- path63.push(loc.fileNode);
5010
+ path64.push(loc.fileNode);
4990
5011
  edgeProvenances.push(Provenance6.OBSERVED);
4991
5012
  rootCauseNode = loc.fileNode;
4992
5013
  }
4993
5014
  return RootCauseResultSchema.parse({
4994
5015
  rootCauseNode,
4995
5016
  rootCauseReason: loc.rootCauseReason,
4996
- traversalPath: path63,
5017
+ traversalPath: path64,
4997
5018
  edgeProvenances,
4998
5019
  confidence,
4999
5020
  ...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
@@ -5005,7 +5026,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
5005
5026
  return RootCauseResultSchema.parse({
5006
5027
  rootCauseNode: culprit,
5007
5028
  rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
5008
- traversalPath: path63,
5029
+ traversalPath: path64,
5009
5030
  edgeProvenances,
5010
5031
  confidence,
5011
5032
  fixRecommendation: `Inspect ${culpritName}'s failing handler`
@@ -5243,6 +5264,12 @@ function parseGoMod(source) {
5243
5264
  }
5244
5265
  return { module, ...goVersion ? { goVersion } : {}, dependencies };
5245
5266
  }
5267
+ function goFramework(deps) {
5268
+ if (deps["github.com/gin-gonic/gin"]) return "gin";
5269
+ if (deps["github.com/labstack/echo/v4"] || deps["github.com/labstack/echo"]) return "echo";
5270
+ if (deps["github.com/gofiber/fiber/v2"] || deps["github.com/gofiber/fiber/v3"]) return "fiber";
5271
+ return void 0;
5272
+ }
5246
5273
  async function discoverGoService(scanPath, dir) {
5247
5274
  let raw;
5248
5275
  try {
@@ -5254,6 +5281,7 @@ async function discoverGoService(scanPath, dir) {
5254
5281
  if (!mod) return null;
5255
5282
  const name = mod.module.split("/").filter(Boolean).pop() ?? mod.module;
5256
5283
  const pkg = { name, dependencies: mod.dependencies };
5284
+ const framework = goFramework(mod.dependencies);
5257
5285
  const node = {
5258
5286
  id: serviceId2(name),
5259
5287
  type: NodeType6.ServiceNode,
@@ -5261,7 +5289,7 @@ async function discoverGoService(scanPath, dir) {
5261
5289
  language: "go",
5262
5290
  dependencies: mod.dependencies,
5263
5291
  repoPath: path10.relative(scanPath, dir),
5264
- ...mod.dependencies["github.com/gin-gonic/gin"] ? { framework: "gin" } : {}
5292
+ ...framework ? { framework } : {}
5265
5293
  };
5266
5294
  return { pkg, dir, node };
5267
5295
  }
@@ -6168,7 +6196,7 @@ async function addSymbolEdges(graph, services) {
6168
6196
  return best;
6169
6197
  };
6170
6198
  const requests = [];
6171
- const walk8 = (node) => {
6199
+ const walk9 = (node) => {
6172
6200
  if (node.type === "class_declaration" || node.type === "abstract_class_declaration" || node.type === "class") {
6173
6201
  const self = localBySpan.get(`${node.startPosition.row + 1}:${node.endPosition.row + 1}`);
6174
6202
  if (self && self.kind === "class") {
@@ -6214,10 +6242,10 @@ async function addSymbolEdges(graph, services) {
6214
6242
  }
6215
6243
  for (let i = 0; i < node.namedChildCount; i++) {
6216
6244
  const child = node.namedChild(i);
6217
- if (child) walk8(child);
6245
+ if (child) walk9(child);
6218
6246
  }
6219
6247
  };
6220
- walk8(root);
6248
+ walk9(root);
6221
6249
  for (const req of requests) {
6222
6250
  const targetSid = resolveTarget(req.targetName, req.wantKind);
6223
6251
  if (!targetSid) continue;
@@ -7237,20 +7265,20 @@ import {
7237
7265
  } from "@neat.is/types";
7238
7266
  async function walkConfigFiles(dir) {
7239
7267
  const out = [];
7240
- async function walk8(current) {
7268
+ async function walk9(current) {
7241
7269
  const entries = await fs16.readdir(current, { withFileTypes: true });
7242
7270
  for (const entry of entries) {
7243
7271
  const full = path28.join(current, entry.name);
7244
7272
  if (entry.isDirectory()) {
7245
7273
  if (IGNORED_DIRS.has(entry.name)) continue;
7246
7274
  if (await isPythonVenvDir(full)) continue;
7247
- await walk8(full);
7275
+ await walk9(full);
7248
7276
  } else if (entry.isFile() && isConfigFile(entry.name).match) {
7249
7277
  out.push(full);
7250
7278
  }
7251
7279
  }
7252
7280
  }
7253
- await walk8(dir);
7281
+ await walk9(dir);
7254
7282
  return out;
7255
7283
  }
7256
7284
  async function addConfigNodes(graph, services, scanPath) {
@@ -7346,20 +7374,20 @@ function grpcMethodsFromProto(content, fqPackage) {
7346
7374
  }
7347
7375
  async function walkProtoFiles(dir) {
7348
7376
  const out = [];
7349
- async function walk8(current) {
7377
+ async function walk9(current) {
7350
7378
  const entries = await fs17.readdir(current, { withFileTypes: true }).catch(() => []);
7351
7379
  for (const entry of entries) {
7352
7380
  const full = path29.join(current, entry.name);
7353
7381
  if (entry.isDirectory()) {
7354
7382
  if (IGNORED_DIRS.has(entry.name)) continue;
7355
7383
  if (await isPythonVenvDir(full)) continue;
7356
- await walk8(full);
7384
+ await walk9(full);
7357
7385
  } else if (entry.isFile() && path29.extname(entry.name) === PROTO_EXTENSION) {
7358
7386
  out.push(full);
7359
7387
  }
7360
7388
  }
7361
7389
  }
7362
- await walk8(dir);
7390
+ await walk9(dir);
7363
7391
  return out;
7364
7392
  }
7365
7393
  async function addGrpcMethods(graph, services) {
@@ -8190,7 +8218,7 @@ function isFirestoreClientFactory(node) {
8190
8218
  }
8191
8219
  function firestoreClientVars(root) {
8192
8220
  const vars = /* @__PURE__ */ new Set();
8193
- const walk8 = (node) => {
8221
+ const walk9 = (node) => {
8194
8222
  if (node.type === "variable_declarator") {
8195
8223
  const name = node.childForFieldName("name");
8196
8224
  let value = node.childForFieldName("value");
@@ -8199,9 +8227,9 @@ function firestoreClientVars(root) {
8199
8227
  vars.add(name.text);
8200
8228
  }
8201
8229
  }
8202
- for (const c of namedChildren(node)) walk8(c);
8230
+ for (const c of namedChildren(node)) walk9(c);
8203
8231
  };
8204
- walk8(root);
8232
+ walk9(root);
8205
8233
  return vars;
8206
8234
  }
8207
8235
  function isClientExpr(node, clientVars) {
@@ -8356,7 +8384,7 @@ function firestoreEndpointsFromFile(file, serviceDir) {
8356
8384
  }
8357
8385
  s.add(field);
8358
8386
  };
8359
- const walk8 = (node) => {
8387
+ const walk9 = (node) => {
8360
8388
  if (node.type === "call_expression") {
8361
8389
  const fn = node.childForFieldName("function");
8362
8390
  const line = node.startPosition.row + 1;
@@ -8396,9 +8424,9 @@ function firestoreEndpointsFromFile(file, serviceDir) {
8396
8424
  }
8397
8425
  }
8398
8426
  }
8399
- for (const c of namedChildren(node)) walk8(c);
8427
+ for (const c of namedChildren(node)) walk9(c);
8400
8428
  };
8401
- walk8(tree.rootNode);
8429
+ walk9(tree.rootNode);
8402
8430
  const out = [];
8403
8431
  for (const [collPath, line] of collLine) {
8404
8432
  const byField = writes.get(collPath);
@@ -9189,7 +9217,7 @@ function drizzleEndpointsFromFile(file, serviceDir) {
9189
9217
  const tree = parseSource3(parserForExt2(path41.extname(file.path)), file.content);
9190
9218
  const out = [];
9191
9219
  const seen = /* @__PURE__ */ new Set();
9192
- const walk8 = (node) => {
9220
+ const walk9 = (node) => {
9193
9221
  if (node.type === "call_expression") {
9194
9222
  const fn = node.childForFieldName("function");
9195
9223
  if (fn?.type === "identifier" && TABLE_BUILDERS.has(fn.text)) {
@@ -9217,9 +9245,9 @@ function drizzleEndpointsFromFile(file, serviceDir) {
9217
9245
  }
9218
9246
  }
9219
9247
  }
9220
- for (const c of namedChildren4(node)) walk8(c);
9248
+ for (const c of namedChildren4(node)) walk9(c);
9221
9249
  };
9222
- walk8(tree.rootNode);
9250
+ walk9(tree.rootNode);
9223
9251
  return out;
9224
9252
  }
9225
9253
  function enclosingVarName(call) {
@@ -9241,7 +9269,7 @@ function enclosingVarName(call) {
9241
9269
  function collectDrizzleTables(root) {
9242
9270
  const tables = [];
9243
9271
  const varToTable = /* @__PURE__ */ new Map();
9244
- const walk8 = (node) => {
9272
+ const walk9 = (node) => {
9245
9273
  if (node.type === "call_expression") {
9246
9274
  const fn = node.childForFieldName("function");
9247
9275
  if (fn?.type === "identifier" && TABLE_BUILDERS.has(fn.text)) {
@@ -9256,9 +9284,9 @@ function collectDrizzleTables(root) {
9256
9284
  }
9257
9285
  }
9258
9286
  }
9259
- for (const c of namedChildren4(node)) walk8(c);
9287
+ for (const c of namedChildren4(node)) walk9(c);
9260
9288
  };
9261
- walk8(root);
9289
+ walk9(root);
9262
9290
  return { tables, varToTable };
9263
9291
  }
9264
9292
  function referencesTargetVar(call) {
@@ -9281,7 +9309,7 @@ function drizzleForeignKeys(file, serviceDir) {
9281
9309
  const seen = /* @__PURE__ */ new Set();
9282
9310
  for (const table of tables) {
9283
9311
  if (!table.object) continue;
9284
- const walk8 = (node) => {
9312
+ const walk9 = (node) => {
9285
9313
  if (node.type === "call_expression") {
9286
9314
  const targetVar = referencesTargetVar(node);
9287
9315
  const parentTable = targetVar ? varToTable.get(targetVar) : void 0;
@@ -9302,9 +9330,9 @@ function drizzleForeignKeys(file, serviceDir) {
9302
9330
  }
9303
9331
  }
9304
9332
  }
9305
- for (const c of namedChildren4(node)) walk8(c);
9333
+ for (const c of namedChildren4(node)) walk9(c);
9306
9334
  };
9307
- walk8(table.object);
9335
+ walk9(table.object);
9308
9336
  }
9309
9337
  return out;
9310
9338
  }
@@ -10379,6 +10407,521 @@ function goSqlEndpointsFromFile(file, serviceDir) {
10379
10407
  return out;
10380
10408
  }
10381
10409
 
10410
+ // src/extract/calls/gorm.ts
10411
+ import path46 from "path";
10412
+ import Parser15 from "tree-sitter";
10413
+ import Go4 from "tree-sitter-go";
10414
+ import { infraId as infraId16 } from "@neat.is/types";
10415
+ var GORM_IMPORT_RE = /gorm\.io\/gorm/;
10416
+ var PARSE_CHUNK11 = 16384;
10417
+ function makeGoParser3() {
10418
+ const p = new Parser15();
10419
+ p.setLanguage(Go4);
10420
+ return p;
10421
+ }
10422
+ function parseSource10(parser, source) {
10423
+ return parser.parse(
10424
+ (index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK11)
10425
+ );
10426
+ }
10427
+ function walk8(node, visit) {
10428
+ visit(node);
10429
+ for (let i = 0; i < node.namedChildCount; i++) {
10430
+ const c = node.namedChild(i);
10431
+ if (c) walk8(c, visit);
10432
+ }
10433
+ }
10434
+ var COMMON_INITIALISMS = [
10435
+ "ASCII",
10436
+ "HTTPS",
10437
+ "UTF8",
10438
+ "XSRF",
10439
+ "HTML",
10440
+ "HTTP",
10441
+ "JSON",
10442
+ "UUID",
10443
+ "XMPP",
10444
+ "ACL",
10445
+ "API",
10446
+ "CPU",
10447
+ "CSS",
10448
+ "DNS",
10449
+ "EOF",
10450
+ "GUID",
10451
+ "LHS",
10452
+ "QPS",
10453
+ "RAM",
10454
+ "RHS",
10455
+ "RPC",
10456
+ "SLA",
10457
+ "SQL",
10458
+ "SSH",
10459
+ "TCP",
10460
+ "TLS",
10461
+ "TTL",
10462
+ "UDP",
10463
+ "UID",
10464
+ "URI",
10465
+ "URL",
10466
+ "UID",
10467
+ "XSS",
10468
+ "ID",
10469
+ "IP",
10470
+ "UI",
10471
+ "VM",
10472
+ "XML"
10473
+ ].sort((a, b) => b.length - a.length);
10474
+ function titleCase(word) {
10475
+ return word.charAt(0) + word.slice(1).toLowerCase();
10476
+ }
10477
+ function replaceInitialisms(name) {
10478
+ let out = "";
10479
+ let i = 0;
10480
+ while (i < name.length) {
10481
+ let matched = false;
10482
+ for (const init of COMMON_INITIALISMS) {
10483
+ if (name.startsWith(init, i)) {
10484
+ out += titleCase(init);
10485
+ i += init.length;
10486
+ matched = true;
10487
+ break;
10488
+ }
10489
+ }
10490
+ if (!matched) {
10491
+ out += name[i];
10492
+ i++;
10493
+ }
10494
+ }
10495
+ return out;
10496
+ }
10497
+ var isUpper = (c) => c >= "A" && c <= "Z";
10498
+ var isDigit = (c) => c >= "0" && c <= "9";
10499
+ function toDBName(name) {
10500
+ if (name === "") return "";
10501
+ const value = replaceInitialisms(name);
10502
+ if (value.length === 1) return value.toLowerCase();
10503
+ let buf = "";
10504
+ let lastCase = false;
10505
+ let curCase = isUpper(value[0]);
10506
+ for (let i = 0; i < value.length - 1; i++) {
10507
+ const v = value[i];
10508
+ const nextCase = isUpper(value[i + 1]);
10509
+ const nextNumber = isDigit(value[i + 1]);
10510
+ if (curCase) {
10511
+ if (lastCase && (nextCase || nextNumber)) {
10512
+ buf += v.toLowerCase();
10513
+ } else {
10514
+ if (i > 0 && value[i - 1] !== "_" && lastCase !== curCase) buf += "_";
10515
+ buf += v.toLowerCase();
10516
+ }
10517
+ } else {
10518
+ buf += v;
10519
+ }
10520
+ lastCase = curCase;
10521
+ curCase = nextCase;
10522
+ }
10523
+ const last = value[value.length - 1];
10524
+ if (curCase) {
10525
+ if (!lastCase && value.length > 1) buf += "_";
10526
+ buf += last.toLowerCase();
10527
+ } else {
10528
+ buf += last;
10529
+ }
10530
+ return buf;
10531
+ }
10532
+ var UNCOUNTABLE = /* @__PURE__ */ new Set([
10533
+ "equipment",
10534
+ "information",
10535
+ "rice",
10536
+ "money",
10537
+ "species",
10538
+ "series",
10539
+ "fish",
10540
+ "sheep",
10541
+ "jeans",
10542
+ "police"
10543
+ ]);
10544
+ var IRREGULAR = [
10545
+ ["person", "people"],
10546
+ ["man", "men"],
10547
+ ["child", "children"],
10548
+ ["sex", "sexes"],
10549
+ ["move", "moves"]
10550
+ ];
10551
+ var PLURAL_RULES = [
10552
+ [/(quiz)$/i, "$1zes"],
10553
+ [/^(ox)$/i, "$1en"],
10554
+ [/([ml])ouse$/i, "$1ice"],
10555
+ [/(matr|vert|ind)(?:ix|ex)$/i, "$1ices"],
10556
+ [/(x|ch|ss|sh)$/i, "$1es"],
10557
+ [/([^aeiouy]|qu)y$/i, "$1ies"],
10558
+ [/(hive)$/i, "$1s"],
10559
+ [/(?:([^f])fe|([lr])f)$/i, "$1$2ves"],
10560
+ [/sis$/i, "ses"],
10561
+ [/([ti])um$/i, "$1a"],
10562
+ [/([ti])a$/i, "$1a"],
10563
+ [/(buffal|tomat)o$/i, "$1oes"],
10564
+ [/(bu)s$/i, "$1ses"],
10565
+ [/(alias|status)$/i, "$1es"],
10566
+ [/(octop|vir)i$/i, "$1i"],
10567
+ [/(octop|vir)us$/i, "$1i"],
10568
+ [/(ax|test)is$/i, "$1es"],
10569
+ [/s$/i, "s"]
10570
+ ];
10571
+ function pluralize3(word) {
10572
+ if (word === "") return word;
10573
+ const lower = word.toLowerCase();
10574
+ for (const u of UNCOUNTABLE) {
10575
+ if (lower === u || lower.endsWith("_" + u)) return word;
10576
+ }
10577
+ for (const [sing, plur] of IRREGULAR) {
10578
+ const re = new RegExp(sing + "$", "i");
10579
+ if (re.test(word)) return word.replace(re, plur);
10580
+ }
10581
+ for (const [re, rep] of PLURAL_RULES) {
10582
+ if (re.test(word)) return word.replace(re, rep);
10583
+ }
10584
+ return word + "s";
10585
+ }
10586
+ function deriveTableName(structName) {
10587
+ return pluralize3(toDBName(structName));
10588
+ }
10589
+ function stringLiteralValue(node) {
10590
+ if (!node) return null;
10591
+ if (node.type === "interpreted_string_literal" || node.type === "raw_string_literal") {
10592
+ const t = node.text;
10593
+ return t.length >= 2 ? t.slice(1, -1) : "";
10594
+ }
10595
+ return null;
10596
+ }
10597
+ function parseGormTag(tagNode) {
10598
+ const tag = {};
10599
+ if (!tagNode) return tag;
10600
+ let inner = tagNode.text;
10601
+ if (inner.length >= 2) inner = inner.slice(1, -1);
10602
+ if (tagNode.type === "interpreted_string_literal") inner = inner.replace(/\\"/g, '"');
10603
+ const m = inner.match(/gorm:"([^"]*)"/);
10604
+ if (!m) return tag;
10605
+ for (const part of m[1].split(";")) {
10606
+ if (part === "") continue;
10607
+ const idx = part.indexOf(":");
10608
+ const key = (idx >= 0 ? part.slice(0, idx) : part).trim().toLowerCase();
10609
+ const value = idx >= 0 ? part.slice(idx + 1).trim() : "";
10610
+ if (key === "-") tag.skip = true;
10611
+ else if (key === "column") tag.column = value;
10612
+ else if (key === "primarykey" || key === "primary_key") tag.primaryKey = true;
10613
+ else if (key === "foreignkey") tag.foreignKey = value;
10614
+ else if (key === "many2many") tag.many2many = value;
10615
+ else if (key === "embedded") tag.embedded = true;
10616
+ else if (key === "embeddedprefix") tag.embeddedPrefix = value;
10617
+ }
10618
+ return tag;
10619
+ }
10620
+ function unwrapType(typeNode) {
10621
+ let isSlice = false;
10622
+ let isPointer = false;
10623
+ let n = typeNode;
10624
+ while (n && (n.type === "slice_type" || n.type === "array_type" || n.type === "pointer_type")) {
10625
+ if (n.type === "slice_type" || n.type === "array_type") isSlice = true;
10626
+ if (n.type === "pointer_type") isPointer = true;
10627
+ n = n.childForFieldName("element") ?? n.namedChild(n.namedChildCount - 1);
10628
+ }
10629
+ if (!n) return { name: null, qualifier: null, isSlice, isPointer, isQualified: false };
10630
+ if (n.type === "type_identifier") {
10631
+ return { name: n.text, qualifier: null, isSlice, isPointer, isQualified: false };
10632
+ }
10633
+ if (n.type === "qualified_type") {
10634
+ const pkg = n.childForFieldName("package")?.text ?? n.namedChild(0)?.text ?? null;
10635
+ const nm = n.childForFieldName("name")?.text ?? n.namedChild(1)?.text ?? null;
10636
+ return { name: nm, qualifier: pkg, isSlice, isPointer, isQualified: true };
10637
+ }
10638
+ return { name: null, qualifier: null, isSlice, isPointer, isQualified: false };
10639
+ }
10640
+ function readField(fieldDecl) {
10641
+ const names = [];
10642
+ let tagNode = null;
10643
+ for (let i = 0; i < fieldDecl.namedChildCount; i++) {
10644
+ const c = fieldDecl.namedChild(i);
10645
+ if (!c) continue;
10646
+ if (c.type === "field_identifier") names.push(c.text);
10647
+ else if (c.type === "raw_string_literal" || c.type === "interpreted_string_literal") tagNode = c;
10648
+ }
10649
+ const typeNode = fieldDecl.childForFieldName("type");
10650
+ const t = unwrapType(typeNode);
10651
+ return {
10652
+ names,
10653
+ typeName: t.name,
10654
+ qualifier: t.qualifier,
10655
+ isSlice: t.isSlice,
10656
+ isPointer: t.isPointer,
10657
+ isQualified: t.isQualified,
10658
+ tag: parseGormTag(tagNode),
10659
+ line: fieldDecl.startPosition.row + 1
10660
+ };
10661
+ }
10662
+ function collectStructs(tree) {
10663
+ const structs = /* @__PURE__ */ new Map();
10664
+ walk8(tree.rootNode, (node) => {
10665
+ if (node.type !== "type_spec") return;
10666
+ const nameNode = node.childForFieldName("name");
10667
+ const typeNode = node.childForFieldName("type");
10668
+ if (!nameNode || typeNode?.type !== "struct_type") return;
10669
+ const list = typeNode.childForFieldName("body") ?? typeNode.namedChild(0);
10670
+ const fields = [];
10671
+ if (list && list.type === "field_declaration_list") {
10672
+ for (let i = 0; i < list.namedChildCount; i++) {
10673
+ const fd = list.namedChild(i);
10674
+ if (fd?.type === "field_declaration") fields.push(readField(fd));
10675
+ }
10676
+ }
10677
+ structs.set(nameNode.text, {
10678
+ name: nameNode.text,
10679
+ fields,
10680
+ line: node.startPosition.row + 1
10681
+ });
10682
+ });
10683
+ return structs;
10684
+ }
10685
+ var GORM_MODEL_METHODS = /* @__PURE__ */ new Set([
10686
+ "AutoMigrate",
10687
+ "Model",
10688
+ "Create",
10689
+ "Find",
10690
+ "First",
10691
+ "Take",
10692
+ "Last",
10693
+ "Save",
10694
+ "Delete",
10695
+ "Where",
10696
+ "FirstOrCreate",
10697
+ "FirstOrInit"
10698
+ ]);
10699
+ function compositeStructName(arg) {
10700
+ let n = arg;
10701
+ if (n.type === "unary_expression") n = n.childForFieldName("operand") ?? n.namedChild(0);
10702
+ if (!n || n.type !== "composite_literal") return null;
10703
+ const typeNode = n.childForFieldName("type");
10704
+ if (!typeNode) return null;
10705
+ if (typeNode.type === "type_identifier") return typeNode.text;
10706
+ if (typeNode.type === "qualified_type") {
10707
+ return typeNode.childForFieldName("name")?.text ?? typeNode.namedChild(1)?.text ?? null;
10708
+ }
10709
+ return null;
10710
+ }
10711
+ function collectCallModels(tree) {
10712
+ const models = /* @__PURE__ */ new Set();
10713
+ walk8(tree.rootNode, (node) => {
10714
+ if (node.type !== "call_expression") return;
10715
+ const fn = node.childForFieldName("function");
10716
+ if (fn?.type !== "selector_expression") return;
10717
+ const method = fn.childForFieldName("field")?.text;
10718
+ if (!method || !GORM_MODEL_METHODS.has(method)) return;
10719
+ const args = node.childForFieldName("arguments");
10720
+ if (!args) return;
10721
+ for (let i = 0; i < args.namedChildCount; i++) {
10722
+ const arg = args.namedChild(i);
10723
+ if (!arg) continue;
10724
+ const name = compositeStructName(arg);
10725
+ if (name) models.add(name);
10726
+ }
10727
+ });
10728
+ return models;
10729
+ }
10730
+ function collectTableNameOverrides(tree) {
10731
+ const overrides = /* @__PURE__ */ new Map();
10732
+ const declarers = /* @__PURE__ */ new Set();
10733
+ walk8(tree.rootNode, (node) => {
10734
+ if (node.type !== "method_declaration") return;
10735
+ if (node.childForFieldName("name")?.text !== "TableName") return;
10736
+ const receiver = node.childForFieldName("receiver");
10737
+ if (!receiver) return;
10738
+ let recvType = null;
10739
+ for (let i = 0; i < receiver.namedChildCount; i++) {
10740
+ const pd = receiver.namedChild(i);
10741
+ if (pd?.type !== "parameter_declaration") continue;
10742
+ const t = unwrapType(pd.childForFieldName("type"));
10743
+ recvType = t.name;
10744
+ }
10745
+ if (!recvType) return;
10746
+ declarers.add(recvType);
10747
+ const body = node.childForFieldName("body");
10748
+ if (!body) return;
10749
+ let literal = null;
10750
+ walk8(body, (n) => {
10751
+ if (literal !== null) return;
10752
+ if (n.type !== "return_statement") return;
10753
+ const exprList = n.namedChild(0);
10754
+ const first = exprList?.namedChild(0) ?? exprList;
10755
+ const v = stringLiteralValue(first);
10756
+ if (v) literal = v;
10757
+ });
10758
+ if (literal !== null) overrides.set(recvType, literal);
10759
+ });
10760
+ return { overrides, declarers };
10761
+ }
10762
+ function isRelationField(field, structs) {
10763
+ if (field.names.length === 0) return false;
10764
+ if (field.isQualified) return false;
10765
+ if (!field.typeName) return false;
10766
+ return structs.has(field.typeName);
10767
+ }
10768
+ function isGormModelEmbed(field) {
10769
+ return field.names.length === 0 && field.qualifier === "gorm" && field.typeName === "Model";
10770
+ }
10771
+ function analyze(tree) {
10772
+ const structs = collectStructs(tree);
10773
+ const { overrides, declarers } = collectTableNameOverrides(tree);
10774
+ const callModels = collectCallModels(tree);
10775
+ const models = /* @__PURE__ */ new Set();
10776
+ for (const [name, info] of structs) {
10777
+ if (info.fields.some(isGormModelEmbed)) models.add(name);
10778
+ }
10779
+ for (const name of callModels) if (structs.has(name)) models.add(name);
10780
+ for (const name of declarers) if (structs.has(name)) models.add(name);
10781
+ let grew = true;
10782
+ while (grew) {
10783
+ grew = false;
10784
+ for (const name of Array.from(models)) {
10785
+ const info = structs.get(name);
10786
+ if (!info) continue;
10787
+ for (const field of info.fields) {
10788
+ if (!isRelationField(field, structs)) continue;
10789
+ const target = field.typeName;
10790
+ if (!models.has(target) && structs.has(target)) {
10791
+ models.add(target);
10792
+ grew = true;
10793
+ }
10794
+ }
10795
+ }
10796
+ }
10797
+ const tableFor = (structName) => overrides.get(structName) ?? deriveTableName(structName);
10798
+ return { structs, models, tableFor };
10799
+ }
10800
+ function collectColumns(struct, structs, seen, prefix, out, emitted) {
10801
+ if (seen.has(struct.name)) return;
10802
+ seen.add(struct.name);
10803
+ const add = (col) => {
10804
+ const full = prefix + col;
10805
+ if (!emitted.has(full)) {
10806
+ emitted.add(full);
10807
+ out.push(full);
10808
+ }
10809
+ };
10810
+ for (const field of struct.fields) {
10811
+ if (field.tag.skip) continue;
10812
+ if (field.names.length === 0) {
10813
+ if (isGormModelEmbed(field)) {
10814
+ add("id");
10815
+ add("created_at");
10816
+ add("updated_at");
10817
+ add("deleted_at");
10818
+ } else if (!field.isQualified && field.typeName && structs.has(field.typeName)) {
10819
+ collectColumns(structs.get(field.typeName), structs, seen, prefix, out, emitted);
10820
+ }
10821
+ continue;
10822
+ }
10823
+ if (field.tag.embedded && !field.isQualified && field.typeName && structs.has(field.typeName)) {
10824
+ collectColumns(
10825
+ structs.get(field.typeName),
10826
+ structs,
10827
+ seen,
10828
+ prefix + (field.tag.embeddedPrefix ?? ""),
10829
+ out,
10830
+ emitted
10831
+ );
10832
+ continue;
10833
+ }
10834
+ if (isRelationField(field, structs)) continue;
10835
+ if (field.names.length === 1 && field.tag.column) {
10836
+ add(field.tag.column);
10837
+ } else {
10838
+ for (const n of field.names) add(toDBName(n));
10839
+ }
10840
+ }
10841
+ seen.delete(struct.name);
10842
+ }
10843
+ function gormEndpointsFromFile(file, serviceDir) {
10844
+ if (path46.extname(file.path) !== ".go") return [];
10845
+ if (!GORM_IMPORT_RE.test(file.content)) return [];
10846
+ const tree = parseSource10(makeGoParser3(), file.content);
10847
+ const { structs, models, tableFor } = analyze(tree);
10848
+ const out = [];
10849
+ const seenTables = /* @__PURE__ */ new Set();
10850
+ for (const name of models) {
10851
+ const struct = structs.get(name);
10852
+ if (!struct) continue;
10853
+ const table = tableFor(name);
10854
+ if (seenTables.has(table)) continue;
10855
+ seenTables.add(table);
10856
+ const columns = [];
10857
+ collectColumns(struct, structs, /* @__PURE__ */ new Set(), "", columns, /* @__PURE__ */ new Set());
10858
+ out.push({
10859
+ infraId: infraId16("sql-table", table),
10860
+ name: table,
10861
+ kind: "sql-table",
10862
+ edgeType: "CALLS",
10863
+ confidenceKind: "structural",
10864
+ ...columns.length > 0 ? { columns } : {},
10865
+ evidence: {
10866
+ file: toPosix(path46.relative(serviceDir, file.path)),
10867
+ line: struct.line,
10868
+ snippet: snippet(file.content, struct.line)
10869
+ }
10870
+ });
10871
+ }
10872
+ return out;
10873
+ }
10874
+ function gormForeignKeys(file, serviceDir) {
10875
+ if (path46.extname(file.path) !== ".go") return [];
10876
+ if (!GORM_IMPORT_RE.test(file.content)) return [];
10877
+ const tree = parseSource10(makeGoParser3(), file.content);
10878
+ const { structs, models, tableFor } = analyze(tree);
10879
+ const out = [];
10880
+ const seen = /* @__PURE__ */ new Set();
10881
+ const emit = (childTable, parentTable, line) => {
10882
+ if (!childTable || !parentTable || childTable === parentTable) return;
10883
+ const key = `${childTable}->${parentTable}`;
10884
+ if (seen.has(key)) return;
10885
+ seen.add(key);
10886
+ out.push({
10887
+ childTable,
10888
+ parentTable,
10889
+ evidence: {
10890
+ file: toPosix(path46.relative(serviceDir, file.path)),
10891
+ line,
10892
+ snippet: snippet(file.content, line)
10893
+ }
10894
+ });
10895
+ };
10896
+ for (const name of models) {
10897
+ const struct = structs.get(name);
10898
+ if (!struct) continue;
10899
+ const thisTable = tableFor(name);
10900
+ const scalarNames = new Set(
10901
+ struct.fields.filter((f) => f.names.length > 0 && !isRelationField(f, structs)).flatMap((f) => f.names)
10902
+ );
10903
+ for (const field of struct.fields) {
10904
+ if (field.tag.skip) continue;
10905
+ if (!isRelationField(field, structs)) continue;
10906
+ const relTable = tableFor(field.typeName);
10907
+ if (field.tag.many2many) {
10908
+ emit(field.tag.many2many, thisTable, field.line);
10909
+ emit(field.tag.many2many, relTable, field.line);
10910
+ continue;
10911
+ }
10912
+ if (field.isSlice) {
10913
+ emit(relTable, thisTable, field.line);
10914
+ continue;
10915
+ }
10916
+ const convFk = field.names[0] + "ID";
10917
+ const belongsTo = scalarNames.has(convFk) || (field.tag.foreignKey ? scalarNames.has(field.tag.foreignKey) : false);
10918
+ if (belongsTo) emit(thisTable, relTable, field.line);
10919
+ else emit(relTable, thisTable, field.line);
10920
+ }
10921
+ }
10922
+ return out;
10923
+ }
10924
+
10382
10925
  // src/extract/calls/index.ts
10383
10926
  function edgeTypeFromEndpoint(ep) {
10384
10927
  switch (ep.edgeType) {
@@ -10420,6 +10963,11 @@ async function addExternalEndpointEdges(graph, services) {
10420
10963
  } catch (err) {
10421
10964
  recordExtractionError("go SQL call extraction", file.path, err);
10422
10965
  }
10966
+ try {
10967
+ endpoints.push(...gormEndpointsFromFile(file, service.dir));
10968
+ } catch (err) {
10969
+ recordExtractionError("gorm data-axis extraction", file.path, err);
10970
+ }
10423
10971
  try {
10424
10972
  endpoints.push(...railsSchemaEndpointsFromFile(file, service.dir));
10425
10973
  endpoints.push(...railsModelEndpointsFromFile(file, service.dir));
@@ -10535,7 +11083,7 @@ import {
10535
11083
  Provenance as Provenance16,
10536
11084
  confidenceForExtracted as confidenceForExtracted13,
10537
11085
  extractedEdgeId as extractedEdgeId10,
10538
- infraId as infraId16
11086
+ infraId as infraId17
10539
11087
  } from "@neat.is/types";
10540
11088
  async function addTableEdges(graph, services) {
10541
11089
  let nodesAdded = 0;
@@ -10550,6 +11098,7 @@ async function addTableEdges(graph, services) {
10550
11098
  refs.push(...sqlalchemyForeignKeys(file, service.dir));
10551
11099
  refs.push(...railsSchemaForeignKeys(file, service.dir));
10552
11100
  refs.push(...laravelMigrationForeignKeys(file, service.dir));
11101
+ refs.push(...gormForeignKeys(file, service.dir));
10553
11102
  modelRefs.push(...railsModelForeignKeys(file, service.dir));
10554
11103
  modelRefs.push(...laravelModelForeignKeys(file, service.dir));
10555
11104
  } catch (err) {
@@ -10563,8 +11112,8 @@ async function addTableEdges(graph, services) {
10563
11112
  }
10564
11113
  refs.push(...modelRefs);
10565
11114
  for (const ref of refs) {
10566
- const childId = infraId16("sql-table", ref.childTable);
10567
- const parentId = infraId16("sql-table", ref.parentTable);
11115
+ const childId = infraId17("sql-table", ref.childTable);
11116
+ const parentId = infraId17("sql-table", ref.parentTable);
10568
11117
  if (childId === parentId) continue;
10569
11118
  nodesAdded += ensureTableNode(graph, childId, ref.childTable);
10570
11119
  nodesAdded += ensureTableNode(graph, parentId, ref.parentTable);
@@ -10599,14 +11148,14 @@ function ensureTableNode(graph, id, name) {
10599
11148
  }
10600
11149
 
10601
11150
  // src/extract/infra/docker-compose.ts
10602
- import path46 from "path";
11151
+ import path47 from "path";
10603
11152
  import { EdgeType as EdgeType17, Provenance as Provenance18, confidenceForExtracted as confidenceForExtracted15 } from "@neat.is/types";
10604
11153
 
10605
11154
  // src/extract/infra/shared.ts
10606
- import { NodeType as NodeType20, Provenance as Provenance17, confidenceForExtracted as confidenceForExtracted14, infraId as infraId17 } from "@neat.is/types";
11155
+ import { NodeType as NodeType20, Provenance as Provenance17, confidenceForExtracted as confidenceForExtracted14, infraId as infraId18 } from "@neat.is/types";
10607
11156
  function makeInfraNode(kind, name, provider = "self", extras) {
10608
11157
  return {
10609
- id: infraId17(kind, name),
11158
+ id: infraId18(kind, name),
10610
11159
  type: NodeType20.InfraNode,
10611
11160
  name,
10612
11161
  provider,
@@ -10669,7 +11218,7 @@ function dependsOnList(value) {
10669
11218
  }
10670
11219
  function serviceNameToServiceNode(name, services) {
10671
11220
  for (const s of services) {
10672
- if (s.node.name === name || path46.basename(s.dir) === name) return s.node.id;
11221
+ if (s.node.name === name || path47.basename(s.dir) === name) return s.node.id;
10673
11222
  }
10674
11223
  return null;
10675
11224
  }
@@ -10678,7 +11227,7 @@ async function addComposeInfra(graph, scanPath, services) {
10678
11227
  let edgesAdded = 0;
10679
11228
  let composePath = null;
10680
11229
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
10681
- const abs = path46.join(scanPath, name);
11230
+ const abs = path47.join(scanPath, name);
10682
11231
  if (await exists(abs)) {
10683
11232
  composePath = abs;
10684
11233
  break;
@@ -10691,13 +11240,13 @@ async function addComposeInfra(graph, scanPath, services) {
10691
11240
  } catch (err) {
10692
11241
  recordExtractionError(
10693
11242
  "infra docker-compose",
10694
- path46.relative(scanPath, composePath),
11243
+ path47.relative(scanPath, composePath),
10695
11244
  err
10696
11245
  );
10697
11246
  return { nodesAdded, edgesAdded };
10698
11247
  }
10699
11248
  if (!compose?.services) return { nodesAdded, edgesAdded };
10700
- const evidenceFile = path46.relative(scanPath, composePath).split(path46.sep).join("/");
11249
+ const evidenceFile = path47.relative(scanPath, composePath).split(path47.sep).join("/");
10701
11250
  const composeNameToNodeId = /* @__PURE__ */ new Map();
10702
11251
  for (const [composeName, svc] of Object.entries(compose.services)) {
10703
11252
  const matchedServiceId = serviceNameToServiceNode(composeName, services);
@@ -10738,7 +11287,7 @@ async function addComposeInfra(graph, scanPath, services) {
10738
11287
  }
10739
11288
 
10740
11289
  // src/extract/infra/dockerfile.ts
10741
- import path47 from "path";
11290
+ import path48 from "path";
10742
11291
  import { promises as fs18 } from "fs";
10743
11292
  import { EdgeType as EdgeType18, Provenance as Provenance19, confidenceForExtracted as confidenceForExtracted16 } from "@neat.is/types";
10744
11293
  function readDockerfile(content) {
@@ -10769,7 +11318,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
10769
11318
  let nodesAdded = 0;
10770
11319
  let edgesAdded = 0;
10771
11320
  for (const service of services) {
10772
- const dockerfilePath = path47.join(service.dir, "Dockerfile");
11321
+ const dockerfilePath = path48.join(service.dir, "Dockerfile");
10773
11322
  if (!await exists(dockerfilePath)) continue;
10774
11323
  let content;
10775
11324
  try {
@@ -10777,7 +11326,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
10777
11326
  } catch (err) {
10778
11327
  recordExtractionError(
10779
11328
  "infra dockerfile",
10780
- path47.relative(scanPath, dockerfilePath),
11329
+ path48.relative(scanPath, dockerfilePath),
10781
11330
  err
10782
11331
  );
10783
11332
  continue;
@@ -10789,8 +11338,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
10789
11338
  graph.addNode(node.id, node);
10790
11339
  nodesAdded++;
10791
11340
  }
10792
- const relDockerfile = toPosix(path47.relative(service.dir, dockerfilePath));
10793
- const evidenceFile = toPosix(path47.relative(scanPath, dockerfilePath));
11341
+ const relDockerfile = toPosix(path48.relative(service.dir, dockerfilePath));
11342
+ const evidenceFile = toPosix(path48.relative(scanPath, dockerfilePath));
10794
11343
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
10795
11344
  graph,
10796
11345
  service.pkg.name,
@@ -10842,7 +11391,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
10842
11391
 
10843
11392
  // src/extract/infra/terraform.ts
10844
11393
  import { promises as fs19 } from "fs";
10845
- import path48 from "path";
11394
+ import path49 from "path";
10846
11395
  import { EdgeType as EdgeType19, Provenance as Provenance20, confidenceForExtracted as confidenceForExtracted17 } from "@neat.is/types";
10847
11396
  var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
10848
11397
  var REFERENCE_RE = /(?<![\w.])(aws_[A-Za-z0-9_]+)\.([A-Za-z0-9_-]+)/g;
@@ -10853,11 +11402,11 @@ async function walkTfFiles(start, depth = 0, max = 5) {
10853
11402
  for (const entry of entries) {
10854
11403
  if (entry.isDirectory()) {
10855
11404
  if (IGNORED_DIRS.has(entry.name) || entry.name === ".terraform") continue;
10856
- const child = path48.join(start, entry.name);
11405
+ const child = path49.join(start, entry.name);
10857
11406
  if (await isPythonVenvDir(child)) continue;
10858
11407
  out.push(...await walkTfFiles(child, depth + 1, max));
10859
11408
  } else if (entry.isFile() && entry.name.endsWith(".tf")) {
10860
- out.push(path48.join(start, entry.name));
11409
+ out.push(path49.join(start, entry.name));
10861
11410
  }
10862
11411
  }
10863
11412
  return out;
@@ -10889,7 +11438,7 @@ async function addTerraformResources(graph, scanPath) {
10889
11438
  const files = await walkTfFiles(scanPath);
10890
11439
  for (const file of files) {
10891
11440
  const content = await fs19.readFile(file, "utf8");
10892
- const evidenceFile = toPosix(path48.relative(scanPath, file));
11441
+ const evidenceFile = toPosix(path49.relative(scanPath, file));
10893
11442
  const resources = [];
10894
11443
  const byKey = /* @__PURE__ */ new Map();
10895
11444
  RESOURCE_RE.lastIndex = 0;
@@ -10946,7 +11495,7 @@ async function addTerraformResources(graph, scanPath) {
10946
11495
 
10947
11496
  // src/extract/infra/k8s.ts
10948
11497
  import { promises as fs20 } from "fs";
10949
- import path49 from "path";
11498
+ import path50 from "path";
10950
11499
  import { parseAllDocuments as parseAllDocuments2 } from "yaml";
10951
11500
  var K8S_KIND_TO_INFRA_KIND = {
10952
11501
  Service: "k8s-service",
@@ -10964,11 +11513,11 @@ async function walkYamlFiles2(start, depth = 0, max = 5) {
10964
11513
  for (const entry of entries) {
10965
11514
  if (entry.isDirectory()) {
10966
11515
  if (IGNORED_DIRS.has(entry.name)) continue;
10967
- const child = path49.join(start, entry.name);
11516
+ const child = path50.join(start, entry.name);
10968
11517
  if (await isPythonVenvDir(child)) continue;
10969
11518
  out.push(...await walkYamlFiles2(child, depth + 1, max));
10970
- } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(path49.extname(entry.name))) {
10971
- out.push(path49.join(start, entry.name));
11519
+ } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(path50.extname(entry.name))) {
11520
+ out.push(path50.join(start, entry.name));
10972
11521
  }
10973
11522
  }
10974
11523
  return out;
@@ -11001,13 +11550,13 @@ async function addK8sResources(graph, scanPath) {
11001
11550
 
11002
11551
  // src/extract/infra/cloudflare.ts
11003
11552
  import { promises as fs21 } from "fs";
11004
- import path50 from "path";
11553
+ import path51 from "path";
11005
11554
  import { parse as parseToml2 } from "smol-toml";
11006
11555
  import { EdgeType as EdgeType20, Provenance as Provenance21, confidenceForExtracted as confidenceForExtracted18 } from "@neat.is/types";
11007
11556
  var WRANGLER_FILENAMES = ["wrangler.toml", "wrangler.jsonc", "wrangler.json"];
11008
11557
  async function readWranglerConfig(dir) {
11009
11558
  for (const filename of WRANGLER_FILENAMES) {
11010
- const abs = path50.join(dir, filename);
11559
+ const abs = path51.join(dir, filename);
11011
11560
  if (!await exists(abs)) continue;
11012
11561
  const raw = await fs21.readFile(abs, "utf8");
11013
11562
  const config = filename === "wrangler.toml" ? parseToml2(raw) : JSON.parse(maskCommentsInSource(raw));
@@ -11070,11 +11619,11 @@ async function addCloudflareWorkers(graph, services, scanPath) {
11070
11619
  try {
11071
11620
  read = await readWranglerConfig(service.dir);
11072
11621
  } catch (err) {
11073
- recordExtractionError("infra cloudflare", path50.relative(scanPath, service.dir), err);
11622
+ recordExtractionError("infra cloudflare", path51.relative(scanPath, service.dir), err);
11074
11623
  continue;
11075
11624
  }
11076
11625
  if (!read || !read.config.name) continue;
11077
- const evidenceFile = toPosix(path50.relative(scanPath, path50.join(service.dir, read.relFile)));
11626
+ const evidenceFile = toPosix(path51.relative(scanPath, path51.join(service.dir, read.relFile)));
11078
11627
  discovered.push({ service, config: read.config, relFile: read.relFile, raw: read.raw, evidenceFile });
11079
11628
  }
11080
11629
  for (const worker of discovered) {
@@ -11086,7 +11635,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
11086
11635
  }
11087
11636
  let anchorId = service.node.id;
11088
11637
  if (config.main) {
11089
- const entryRelPath = toPosix(path50.normalize(config.main));
11638
+ const entryRelPath = toPosix(path51.normalize(config.main));
11090
11639
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
11091
11640
  graph,
11092
11641
  service.pkg.name,
@@ -11233,12 +11782,12 @@ async function addCloudflareWorkers(graph, services, scanPath) {
11233
11782
 
11234
11783
  // src/extract/infra/vercel.ts
11235
11784
  import { promises as fs22 } from "fs";
11236
- import path51 from "path";
11785
+ import path52 from "path";
11237
11786
  import { EdgeType as EdgeType21 } from "@neat.is/types";
11238
11787
  var VERCEL_CONFIG_FILENAMES = ["vercel.json", "vercel.jsonc"];
11239
11788
  async function readVercelConfig(dir) {
11240
11789
  for (const filename of VERCEL_CONFIG_FILENAMES) {
11241
- const abs = path51.join(dir, filename);
11790
+ const abs = path52.join(dir, filename);
11242
11791
  if (!await exists(abs)) continue;
11243
11792
  const raw = await fs22.readFile(abs, "utf8");
11244
11793
  const config = JSON.parse(maskCommentsInSource(raw));
@@ -11247,7 +11796,7 @@ async function readVercelConfig(dir) {
11247
11796
  return null;
11248
11797
  }
11249
11798
  async function readLinkedProjectName(dir) {
11250
- const abs = path51.join(dir, ".vercel", "project.json");
11799
+ const abs = path52.join(dir, ".vercel", "project.json");
11251
11800
  if (!await exists(abs)) return void 0;
11252
11801
  const parsed = JSON.parse(await fs22.readFile(abs, "utf8"));
11253
11802
  return typeof parsed.projectName === "string" ? parsed.projectName : void 0;
@@ -11265,7 +11814,7 @@ async function addVercelServices(graph, services, scanPath) {
11265
11814
  read = await readVercelConfig(service.dir);
11266
11815
  projectName = await readLinkedProjectName(service.dir);
11267
11816
  } catch (err) {
11268
- recordExtractionError("infra vercel", path51.relative(scanPath, service.dir), err);
11817
+ recordExtractionError("infra vercel", path52.relative(scanPath, service.dir), err);
11269
11818
  continue;
11270
11819
  }
11271
11820
  if (!read && !projectName) continue;
@@ -11281,7 +11830,7 @@ async function addVercelServices(graph, services, scanPath) {
11281
11830
  const anchorId = service.node.id;
11282
11831
  if (!read) continue;
11283
11832
  const { config, relFile, raw } = read;
11284
- const evidenceFile = toPosix(path51.relative(scanPath, path51.join(service.dir, relFile)));
11833
+ const evidenceFile = toPosix(path52.relative(scanPath, path52.join(service.dir, relFile)));
11285
11834
  const add = (edgeType, kind, name) => {
11286
11835
  if (!name) return;
11287
11836
  const result = emitPlatformResourceEdge(
@@ -11310,13 +11859,13 @@ async function addVercelServices(graph, services, scanPath) {
11310
11859
 
11311
11860
  // src/extract/infra/railway.ts
11312
11861
  import { promises as fs23 } from "fs";
11313
- import path52 from "path";
11862
+ import path53 from "path";
11314
11863
  import { parse as parseToml3 } from "smol-toml";
11315
11864
  import { EdgeType as EdgeType22 } from "@neat.is/types";
11316
11865
  var RAILWAY_FILENAMES = ["railway.toml", "railway.json", "railway.jsonc"];
11317
11866
  async function readRailwayConfig(dir) {
11318
11867
  for (const filename of RAILWAY_FILENAMES) {
11319
- const abs = path52.join(dir, filename);
11868
+ const abs = path53.join(dir, filename);
11320
11869
  if (!await exists(abs)) continue;
11321
11870
  const raw = await fs23.readFile(abs, "utf8");
11322
11871
  const config = filename === "railway.toml" ? parseToml3(raw) : JSON.parse(maskCommentsInSource(raw));
@@ -11332,7 +11881,7 @@ async function addRailwayServices(graph, services, scanPath) {
11332
11881
  try {
11333
11882
  read = await readRailwayConfig(service.dir);
11334
11883
  } catch (err) {
11335
- recordExtractionError("infra railway", path52.relative(scanPath, service.dir), err);
11884
+ recordExtractionError("infra railway", path53.relative(scanPath, service.dir), err);
11336
11885
  continue;
11337
11886
  }
11338
11887
  if (!read) continue;
@@ -11342,7 +11891,7 @@ async function addRailwayServices(graph, services, scanPath) {
11342
11891
  }
11343
11892
  const anchorId = service.node.id;
11344
11893
  const { config, relFile, raw } = read;
11345
- const evidenceFile = toPosix(path52.relative(scanPath, path52.join(service.dir, relFile)));
11894
+ const evidenceFile = toPosix(path53.relative(scanPath, path53.join(service.dir, relFile)));
11346
11895
  const add = (edgeType, kind, name) => {
11347
11896
  if (!name) return;
11348
11897
  const result = emitPlatformResourceEdge(
@@ -11367,12 +11916,12 @@ async function addRailwayServices(graph, services, scanPath) {
11367
11916
 
11368
11917
  // src/extract/infra/supabase.ts
11369
11918
  import { promises as fs24 } from "fs";
11370
- import path53 from "path";
11919
+ import path54 from "path";
11371
11920
  import { parse as parseToml4 } from "smol-toml";
11372
11921
  import { EdgeType as EdgeType23 } from "@neat.is/types";
11373
11922
  async function readSupabaseConfig(dir) {
11374
- const relFile = path53.join("supabase", "config.toml");
11375
- const abs = path53.join(dir, relFile);
11923
+ const relFile = path54.join("supabase", "config.toml");
11924
+ const abs = path54.join(dir, relFile);
11376
11925
  if (!await exists(abs)) return null;
11377
11926
  const raw = await fs24.readFile(abs, "utf8");
11378
11927
  const config = parseToml4(raw);
@@ -11386,7 +11935,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
11386
11935
  try {
11387
11936
  read = await readSupabaseConfig(service.dir);
11388
11937
  } catch (err) {
11389
- recordExtractionError("infra supabase", path53.relative(scanPath, service.dir), err);
11938
+ recordExtractionError("infra supabase", path54.relative(scanPath, service.dir), err);
11390
11939
  continue;
11391
11940
  }
11392
11941
  if (!read) continue;
@@ -11401,7 +11950,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
11401
11950
  });
11402
11951
  }
11403
11952
  const anchorId = service.node.id;
11404
- const evidenceFile = toPosix(path53.relative(scanPath, path53.join(service.dir, relFile)));
11953
+ const evidenceFile = toPosix(path54.relative(scanPath, path54.join(service.dir, relFile)));
11405
11954
  const add = (edgeType, kind, name) => {
11406
11955
  if (!name) return;
11407
11956
  const result = emitPlatformResourceEdge(
@@ -11442,8 +11991,8 @@ async function addInfra(graph, scanPath, services) {
11442
11991
  }
11443
11992
 
11444
11993
  // src/extract/zod-shapes.ts
11445
- import path54 from "path";
11446
- import Parser15 from "tree-sitter";
11994
+ import path55 from "path";
11995
+ import Parser16 from "tree-sitter";
11447
11996
  import JavaScript8 from "tree-sitter-javascript";
11448
11997
  import {
11449
11998
  EdgeType as EdgeType24,
@@ -11451,12 +12000,12 @@ import {
11451
12000
  Provenance as Provenance22,
11452
12001
  confidenceForExtracted as confidenceForExtracted19,
11453
12002
  extractedEdgeId as extractedEdgeId11,
11454
- infraId as infraId18
12003
+ infraId as infraId19
11455
12004
  } from "@neat.is/types";
11456
12005
  var ZOD_IMPORT_RE = /\bzod\b/;
11457
12006
  var ZOD_OBJECTS = /* @__PURE__ */ new Set(["z", "zod"]);
11458
12007
  function parserForExt3(ext) {
11459
- const p = new Parser15();
12008
+ const p = new Parser16();
11460
12009
  p.setLanguage(GRAMMAR_BY_EXT[ext] ?? JavaScript8);
11461
12010
  return p;
11462
12011
  }
@@ -11544,7 +12093,7 @@ function topLevelSchemas(root) {
11544
12093
  }
11545
12094
  function zodShapesFromFile(file, serviceDir) {
11546
12095
  if (!ZOD_IMPORT_RE.test(file.content)) return [];
11547
- const tree = parseSource3(parserForExt3(path54.extname(file.path)), file.content);
12096
+ const tree = parseSource3(parserForExt3(path55.extname(file.path)), file.content);
11548
12097
  const out = [];
11549
12098
  const seen = /* @__PURE__ */ new Set();
11550
12099
  for (const { name, call } of topLevelSchemas(tree.rootNode)) {
@@ -11558,11 +12107,11 @@ function zodShapesFromFile(file, serviceDir) {
11558
12107
  seen.add(name);
11559
12108
  const line = call.startPosition.row + 1;
11560
12109
  out.push({
11561
- infraId: infraId18("zod-schema", name),
12110
+ infraId: infraId19("zod-schema", name),
11562
12111
  name,
11563
12112
  fields,
11564
12113
  evidence: {
11565
- file: path54.relative(serviceDir, file.path),
12114
+ file: path55.relative(serviceDir, file.path),
11566
12115
  line,
11567
12116
  snippet: snippet(file.content, line)
11568
12117
  }
@@ -11798,11 +12347,11 @@ async function addFirestoreRules(graph, services) {
11798
12347
  }
11799
12348
 
11800
12349
  // src/extract/index.ts
11801
- import path56 from "path";
12350
+ import path57 from "path";
11802
12351
 
11803
12352
  // src/extract/retire.ts
11804
12353
  import { existsSync as existsSync2 } from "fs";
11805
- import path55 from "path";
12354
+ import path56 from "path";
11806
12355
  import { NodeType as NodeType23, Provenance as Provenance23 } from "@neat.is/types";
11807
12356
  function dropOrphanedFileNodes(graph) {
11808
12357
  const orphans = [];
@@ -11836,11 +12385,11 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
11836
12385
  if (edge.provenance !== Provenance23.EXTRACTED) return;
11837
12386
  const evidenceFile = edge.evidence?.file;
11838
12387
  if (!evidenceFile) return;
11839
- if (path55.isAbsolute(evidenceFile)) {
12388
+ if (path56.isAbsolute(evidenceFile)) {
11840
12389
  if (!existsSync2(evidenceFile)) toDrop.push(id);
11841
12390
  return;
11842
12391
  }
11843
- const found = bases.some((base) => existsSync2(path55.join(base, evidenceFile)));
12392
+ const found = bases.some((base) => existsSync2(path56.join(base, evidenceFile)));
11844
12393
  if (!found) toDrop.push(id);
11845
12394
  });
11846
12395
  for (const id of toDrop) graph.dropEdge(id);
@@ -11897,7 +12446,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
11897
12446
  }
11898
12447
  const droppedEntries = drainDroppedExtracted();
11899
12448
  if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
11900
- const rejectedPath = path56.join(path56.dirname(opts.errorsPath), "rejected.ndjson");
12449
+ const rejectedPath = path57.join(path57.dirname(opts.errorsPath), "rejected.ndjson");
11901
12450
  try {
11902
12451
  await writeRejectedExtracted(droppedEntries, rejectedPath);
11903
12452
  } catch (err) {
@@ -12272,7 +12821,7 @@ function computeDivergences(graph, opts = {}) {
12272
12821
 
12273
12822
  // src/persist.ts
12274
12823
  import { promises as fs25 } from "fs";
12275
- import path57 from "path";
12824
+ import path58 from "path";
12276
12825
  import { NodeType as NodeType25, Provenance as Provenance25, observedEdgeId as observedEdgeId2 } from "@neat.is/types";
12277
12826
  var SCHEMA_VERSION = 7;
12278
12827
  function migrateV1ToV2(payload) {
@@ -12327,7 +12876,7 @@ function migrateV2ToV3(payload) {
12327
12876
  return { ...payload, schemaVersion: 3 };
12328
12877
  }
12329
12878
  async function ensureDir(filePath) {
12330
- await fs25.mkdir(path57.dirname(filePath), { recursive: true });
12879
+ await fs25.mkdir(path58.dirname(filePath), { recursive: true });
12331
12880
  }
12332
12881
  async function saveGraphToDisk(graph, outPath) {
12333
12882
  await ensureDir(outPath);
@@ -12492,23 +13041,23 @@ function canonicalJson(value) {
12492
13041
  }
12493
13042
 
12494
13043
  // src/projects.ts
12495
- import path58 from "path";
13044
+ import path59 from "path";
12496
13045
  function pathsForProject(project, baseDir) {
12497
13046
  if (project === DEFAULT_PROJECT) {
12498
13047
  return {
12499
- snapshotPath: path58.join(baseDir, "graph.json"),
12500
- errorsPath: path58.join(baseDir, "errors.ndjson"),
12501
- staleEventsPath: path58.join(baseDir, "stale-events.ndjson"),
12502
- embeddingsCachePath: path58.join(baseDir, "embeddings.json"),
12503
- policyViolationsPath: path58.join(baseDir, "policy-violations.ndjson")
13048
+ snapshotPath: path59.join(baseDir, "graph.json"),
13049
+ errorsPath: path59.join(baseDir, "errors.ndjson"),
13050
+ staleEventsPath: path59.join(baseDir, "stale-events.ndjson"),
13051
+ embeddingsCachePath: path59.join(baseDir, "embeddings.json"),
13052
+ policyViolationsPath: path59.join(baseDir, "policy-violations.ndjson")
12504
13053
  };
12505
13054
  }
12506
13055
  return {
12507
- snapshotPath: path58.join(baseDir, `${project}.json`),
12508
- errorsPath: path58.join(baseDir, `errors.${project}.ndjson`),
12509
- staleEventsPath: path58.join(baseDir, `stale-events.${project}.ndjson`),
12510
- embeddingsCachePath: path58.join(baseDir, `embeddings.${project}.json`),
12511
- policyViolationsPath: path58.join(baseDir, `policy-violations.${project}.ndjson`)
13056
+ snapshotPath: path59.join(baseDir, `${project}.json`),
13057
+ errorsPath: path59.join(baseDir, `errors.${project}.ndjson`),
13058
+ staleEventsPath: path59.join(baseDir, `stale-events.${project}.ndjson`),
13059
+ embeddingsCachePath: path59.join(baseDir, `embeddings.${project}.json`),
13060
+ policyViolationsPath: path59.join(baseDir, `policy-violations.${project}.ndjson`)
12512
13061
  };
12513
13062
  }
12514
13063
  var Projects = class {
@@ -12549,7 +13098,7 @@ function parseExtraProjects(raw) {
12549
13098
  // src/registry.ts
12550
13099
  import { promises as fs27 } from "fs";
12551
13100
  import os2 from "os";
12552
- import path59 from "path";
13101
+ import path60 from "path";
12553
13102
  import {
12554
13103
  RegistryFileSchema
12555
13104
  } from "@neat.is/types";
@@ -12557,20 +13106,20 @@ var LOCK_TIMEOUT_MS = 5e3;
12557
13106
  var LOCK_RETRY_MS = 50;
12558
13107
  function neatHome() {
12559
13108
  const override = process.env.NEAT_HOME;
12560
- if (override && override.length > 0) return path59.resolve(override);
12561
- return path59.join(os2.homedir(), ".neat");
13109
+ if (override && override.length > 0) return path60.resolve(override);
13110
+ return path60.join(os2.homedir(), ".neat");
12562
13111
  }
12563
13112
  function registryPath() {
12564
- return path59.join(neatHome(), "projects.json");
13113
+ return path60.join(neatHome(), "projects.json");
12565
13114
  }
12566
13115
  function registryLockPath() {
12567
- return path59.join(neatHome(), "projects.json.lock");
13116
+ return path60.join(neatHome(), "projects.json.lock");
12568
13117
  }
12569
13118
  function daemonPidPath() {
12570
- return path59.join(neatHome(), "neatd.pid");
13119
+ return path60.join(neatHome(), "neatd.pid");
12571
13120
  }
12572
13121
  function daemonsDir() {
12573
- return path59.join(neatHome(), "daemons");
13122
+ return path60.join(neatHome(), "daemons");
12574
13123
  }
12575
13124
  function isFiniteInt(v) {
12576
13125
  return typeof v === "number" && Number.isFinite(v);
@@ -12611,7 +13160,7 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
12611
13160
  const out = [];
12612
13161
  for (const name of names) {
12613
13162
  if (!name.endsWith(".json")) continue;
12614
- const file = path59.join(dir, name);
13163
+ const file = path60.join(dir, name);
12615
13164
  let raw;
12616
13165
  try {
12617
13166
  raw = await fs27.readFile(file, "utf8");
@@ -12732,7 +13281,7 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
12732
13281
  }
12733
13282
  }
12734
13283
  async function normalizeProjectPath(input) {
12735
- const resolved = path59.resolve(input);
13284
+ const resolved = path60.resolve(input);
12736
13285
  try {
12737
13286
  return await fs27.realpath(resolved);
12738
13287
  } catch {
@@ -12740,7 +13289,7 @@ async function normalizeProjectPath(input) {
12740
13289
  }
12741
13290
  }
12742
13291
  async function writeAtomically(target, contents) {
12743
- await fs27.mkdir(path59.dirname(target), { recursive: true });
13292
+ await fs27.mkdir(path60.dirname(target), { recursive: true });
12744
13293
  const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
12745
13294
  const fd = await fs27.open(tmp, "w");
12746
13295
  try {
@@ -12753,7 +13302,7 @@ async function writeAtomically(target, contents) {
12753
13302
  }
12754
13303
  async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
12755
13304
  const deadline = Date.now() + timeoutMs;
12756
- await fs27.mkdir(path59.dirname(lockPath), { recursive: true });
13305
+ await fs27.mkdir(path60.dirname(lockPath), { recursive: true });
12757
13306
  let probedHolder = false;
12758
13307
  while (true) {
12759
13308
  try {
@@ -12949,13 +13498,13 @@ import { DivergenceTypeSchema, PoliciesCheckBodySchema, PolicySeveritySchema } f
12949
13498
 
12950
13499
  // src/extend/index.ts
12951
13500
  import { promises as fs29 } from "fs";
12952
- import path61 from "path";
13501
+ import path62 from "path";
12953
13502
  import os3 from "os";
12954
13503
  import { resolve as registryResolve, list as registryList } from "@neat.is/instrumentation-registry";
12955
13504
 
12956
13505
  // src/installers/package-manager.ts
12957
13506
  import { promises as fs28 } from "fs";
12958
- import path60 from "path";
13507
+ import path61 from "path";
12959
13508
  import { spawn } from "child_process";
12960
13509
  var LOCKFILE_PRIORITY = [
12961
13510
  { lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
@@ -12977,22 +13526,22 @@ async function exists2(p) {
12977
13526
  }
12978
13527
  }
12979
13528
  async function detectPackageManager(serviceDir) {
12980
- let dir = path60.resolve(serviceDir);
13529
+ let dir = path61.resolve(serviceDir);
12981
13530
  const stops = /* @__PURE__ */ new Set();
12982
13531
  for (let i = 0; i < 64; i++) {
12983
13532
  if (stops.has(dir)) break;
12984
13533
  stops.add(dir);
12985
13534
  for (const candidate of LOCKFILE_PRIORITY) {
12986
- const lockPath = path60.join(dir, candidate.lockfile);
13535
+ const lockPath = path61.join(dir, candidate.lockfile);
12987
13536
  if (await exists2(lockPath)) {
12988
13537
  return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
12989
13538
  }
12990
13539
  }
12991
- const parent = path60.dirname(dir);
13540
+ const parent = path61.dirname(dir);
12992
13541
  if (parent === dir) break;
12993
13542
  dir = parent;
12994
13543
  }
12995
- return { pm: "npm", cwd: path60.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
13544
+ return { pm: "npm", cwd: path61.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
12996
13545
  }
12997
13546
  async function runPackageManagerInstall(cmd) {
12998
13547
  return new Promise((resolve) => {
@@ -13041,7 +13590,7 @@ async function fileExists2(p) {
13041
13590
  }
13042
13591
  }
13043
13592
  async function readPackageJson(scanPath) {
13044
- const pkgPath = path61.join(scanPath, "package.json");
13593
+ const pkgPath = path62.join(scanPath, "package.json");
13045
13594
  const raw = await fs29.readFile(pkgPath, "utf8");
13046
13595
  return JSON.parse(raw);
13047
13596
  }
@@ -13055,27 +13604,27 @@ var HOOK_WALK_SKIP_DIRS = /* @__PURE__ */ new Set([
13055
13604
  ]);
13056
13605
  async function findHookFiles(scanPath) {
13057
13606
  const found = [];
13058
- const walk8 = async (dir) => {
13607
+ const walk9 = async (dir) => {
13059
13608
  const entries = await fs29.readdir(dir, { withFileTypes: true }).catch(() => []);
13060
13609
  for (const entry of entries) {
13061
13610
  if (entry.isDirectory()) {
13062
13611
  if (entry.name.startsWith(".") || HOOK_WALK_SKIP_DIRS.has(entry.name)) continue;
13063
- await walk8(path61.join(dir, entry.name));
13612
+ await walk9(path62.join(dir, entry.name));
13064
13613
  } else if (entry.isFile()) {
13065
13614
  if ((entry.name.startsWith("instrumentation") || entry.name.startsWith("otel-init")) && /\.(ts|js|cjs|mjs)$/.test(entry.name)) {
13066
- const rel = path61.relative(scanPath, path61.join(dir, entry.name));
13067
- found.push(rel.split(path61.sep).join("/"));
13615
+ const rel = path62.relative(scanPath, path62.join(dir, entry.name));
13616
+ found.push(rel.split(path62.sep).join("/"));
13068
13617
  }
13069
13618
  }
13070
13619
  }
13071
13620
  };
13072
- await walk8(scanPath);
13621
+ await walk9(scanPath);
13073
13622
  return found.sort();
13074
13623
  }
13075
13624
  async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
13076
13625
  let fallback = null;
13077
13626
  for (const file of hookFiles) {
13078
- const content = await fs29.readFile(path61.join(scanPath, file), "utf8");
13627
+ const content = await fs29.readFile(path62.join(scanPath, file), "utf8");
13079
13628
  const patched = splicedContent(content, snippet2);
13080
13629
  if (patched !== null) return { file, content, patched };
13081
13630
  if (fallback === null) fallback = { file, content };
@@ -13083,11 +13632,11 @@ async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
13083
13632
  return { file: fallback.file, content: fallback.content, patched: null };
13084
13633
  }
13085
13634
  function extendLogPath() {
13086
- return process.env.NEAT_EXTEND_LOG ?? path61.join(os3.homedir(), ".neat", "extend-log.ndjson");
13635
+ return process.env.NEAT_EXTEND_LOG ?? path62.join(os3.homedir(), ".neat", "extend-log.ndjson");
13087
13636
  }
13088
13637
  async function appendExtendLog(entry) {
13089
13638
  const logPath = extendLogPath();
13090
- await fs29.mkdir(path61.dirname(logPath), { recursive: true });
13639
+ await fs29.mkdir(path62.dirname(logPath), { recursive: true });
13091
13640
  await fs29.appendFile(logPath, JSON.stringify(entry) + "\n", "utf8");
13092
13641
  }
13093
13642
  function splicedContent(fileContent, snippet2) {
@@ -13146,7 +13695,7 @@ function lookupInstrumentation(library, installedVersion) {
13146
13695
  }
13147
13696
  async function describeProjectInstrumentation(ctx) {
13148
13697
  const hookFiles = await findHookFiles(ctx.scanPath);
13149
- const envNeat = await fileExists2(path61.join(ctx.scanPath, ".env.neat"));
13698
+ const envNeat = await fileExists2(path62.join(ctx.scanPath, ".env.neat"));
13150
13699
  const registryInstrPackages = new Set(
13151
13700
  registryList().map((e) => e.instrumentation_package).filter((p) => !!p)
13152
13701
  );
@@ -13168,7 +13717,7 @@ async function applyExtension(ctx, args, options) {
13168
13717
  );
13169
13718
  }
13170
13719
  for (const file of hookFiles) {
13171
- const content = await fs29.readFile(path61.join(ctx.scanPath, file), "utf8");
13720
+ const content = await fs29.readFile(path62.join(ctx.scanPath, file), "utf8");
13172
13721
  if (content.includes(args.registration_snippet)) {
13173
13722
  return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
13174
13723
  }
@@ -13180,10 +13729,10 @@ async function applyExtension(ctx, args, options) {
13180
13729
  );
13181
13730
  }
13182
13731
  const primaryFile = primary.file;
13183
- const primaryPath = path61.join(ctx.scanPath, primaryFile);
13732
+ const primaryPath = path62.join(ctx.scanPath, primaryFile);
13184
13733
  const filesTouched = [];
13185
13734
  const depsAdded = [];
13186
- const pkgPath = path61.join(ctx.scanPath, "package.json");
13735
+ const pkgPath = path62.join(ctx.scanPath, "package.json");
13187
13736
  const pkg = await readPackageJson(ctx.scanPath);
13188
13737
  if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
13189
13738
  pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
@@ -13222,7 +13771,7 @@ async function dryRunExtension(ctx, args) {
13222
13771
  };
13223
13772
  }
13224
13773
  for (const file of hookFiles) {
13225
- const content = await fs29.readFile(path61.join(ctx.scanPath, file), "utf8");
13774
+ const content = await fs29.readFile(path62.join(ctx.scanPath, file), "utf8");
13226
13775
  if (content.includes(args.registration_snippet)) {
13227
13776
  return {
13228
13777
  library: args.library,
@@ -13263,7 +13812,7 @@ async function rollbackExtension(ctx, args) {
13263
13812
  if (!match) {
13264
13813
  return { undone: false, message: "no apply found for library" };
13265
13814
  }
13266
- const pkgPath = path61.join(ctx.scanPath, "package.json");
13815
+ const pkgPath = path62.join(ctx.scanPath, "package.json");
13267
13816
  if (await fileExists2(pkgPath)) {
13268
13817
  const pkg = await readPackageJson(ctx.scanPath);
13269
13818
  if (pkg.dependencies?.[match.instrumentation_package]) {
@@ -13274,7 +13823,7 @@ async function rollbackExtension(ctx, args) {
13274
13823
  }
13275
13824
  const hookFiles = await findHookFiles(ctx.scanPath);
13276
13825
  for (const file of hookFiles) {
13277
- const filePath = path61.join(ctx.scanPath, file);
13826
+ const filePath = path62.join(ctx.scanPath, file);
13278
13827
  const content = await fs29.readFile(filePath, "utf8");
13279
13828
  if (content.includes(match.registration_snippet)) {
13280
13829
  const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
@@ -13389,7 +13938,7 @@ data: ${JSON.stringify(envelope.payload)}
13389
13938
 
13390
13939
  // src/connectors-config.ts
13391
13940
  import os4 from "os";
13392
- import path62 from "path";
13941
+ import path63 from "path";
13393
13942
  import { promises as fs30 } from "fs";
13394
13943
  var CONNECTORS_CONFIG_VERSION = 1;
13395
13944
  var EnvRefUnsetError = class extends Error {
@@ -13404,11 +13953,11 @@ var EnvRefUnsetError = class extends Error {
13404
13953
  };
13405
13954
  function neatHome2() {
13406
13955
  const override = process.env.NEAT_HOME;
13407
- if (override && override.length > 0) return path62.resolve(override);
13408
- return path62.join(os4.homedir(), ".neat");
13956
+ if (override && override.length > 0) return path63.resolve(override);
13957
+ return path63.join(os4.homedir(), ".neat");
13409
13958
  }
13410
13959
  function connectorsConfigPath(home = neatHome2()) {
13411
- return path62.join(home, "connectors.json");
13960
+ return path63.join(home, "connectors.json");
13412
13961
  }
13413
13962
  var MODE_MASK_LOOSER_THAN_0600 = 63;
13414
13963
  async function warnIfModeLooserThan0600(file) {
@@ -13539,7 +14088,7 @@ function connectorMatchesProject(entry, project) {
13539
14088
  var CONNECTORS_LOCK_TIMEOUT_MS = 5e3;
13540
14089
  var CONNECTORS_LOCK_RETRY_MS = 50;
13541
14090
  function connectorsConfigLockPath(home = neatHome2()) {
13542
- return path62.join(home, "connectors.json.lock");
14091
+ return path63.join(home, "connectors.json.lock");
13543
14092
  }
13544
14093
  function isEnvRef(value) {
13545
14094
  return value.length > 1 && value.startsWith("$");
@@ -13552,7 +14101,7 @@ function redactCredentialRef(ref) {
13552
14101
  return out;
13553
14102
  }
13554
14103
  async function writeConfigAtomically0600(file, contents) {
13555
- await fs30.mkdir(path62.dirname(file), { recursive: true });
14104
+ await fs30.mkdir(path63.dirname(file), { recursive: true });
13556
14105
  const tmp = `${file}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
13557
14106
  const fd = await fs30.open(tmp, "w", 384);
13558
14107
  try {
@@ -13566,7 +14115,7 @@ async function writeConfigAtomically0600(file, contents) {
13566
14115
  }
13567
14116
  async function acquireConnectorsLock(lockPath, timeoutMs = CONNECTORS_LOCK_TIMEOUT_MS) {
13568
14117
  const deadline = Date.now() + timeoutMs;
13569
- await fs30.mkdir(path62.dirname(lockPath), { recursive: true });
14118
+ await fs30.mkdir(path63.dirname(lockPath), { recursive: true });
13570
14119
  for (; ; ) {
13571
14120
  try {
13572
14121
  const fd = await fs30.open(lockPath, "wx");
@@ -14201,10 +14750,10 @@ var SUPABASE_RPC_TARGET_KIND = "supabase-rpc";
14201
14750
  // src/connectors/supabase/map.ts
14202
14751
  var REST_RPC_PATH_RE = /^\/rest\/v1\/rpc\/([^/?]+)/;
14203
14752
  var REST_TABLE_PATH_RE = /^\/rest\/v1\/([^/?]+)/;
14204
- function targetFromRestPath(path63) {
14205
- const rpcMatch = REST_RPC_PATH_RE.exec(path63);
14753
+ function targetFromRestPath(path64) {
14754
+ const rpcMatch = REST_RPC_PATH_RE.exec(path64);
14206
14755
  if (rpcMatch) return { targetKind: SUPABASE_RPC_TARGET_KIND, name: rpcMatch[1] };
14207
- const tableMatch = REST_TABLE_PATH_RE.exec(path63);
14756
+ const tableMatch = REST_TABLE_PATH_RE.exec(path64);
14208
14757
  if (tableMatch) return { targetKind: SUPABASE_TABLE_TARGET_KIND, name: tableMatch[1] };
14209
14758
  return null;
14210
14759
  }
@@ -14313,21 +14862,21 @@ async function fetchPgStatStatements(connectionString, limit = DEFAULT_STATEMENT
14313
14862
  }
14314
14863
 
14315
14864
  // src/connectors/supabase/resolve.ts
14316
- import { EdgeType as EdgeType26, infraId as infraId19 } from "@neat.is/types";
14865
+ import { EdgeType as EdgeType26, infraId as infraId20 } from "@neat.is/types";
14317
14866
  function createSupabaseResolveTarget(graph, config) {
14318
14867
  return (signal, _ctx) => {
14319
14868
  if (signal.targetKind !== SUPABASE_TABLE_TARGET_KIND && signal.targetKind !== SUPABASE_RPC_TARGET_KIND) {
14320
14869
  return null;
14321
14870
  }
14322
- const subResourceId = infraId19(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
14871
+ const subResourceId = infraId20(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
14323
14872
  if (graph.hasNode(subResourceId)) {
14324
14873
  return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: EdgeType26.CALLS };
14325
14874
  }
14326
- const bareResourceId = infraId19(signal.targetKind, signal.targetName);
14875
+ const bareResourceId = infraId20(signal.targetKind, signal.targetName);
14327
14876
  if (graph.hasNode(bareResourceId)) {
14328
14877
  return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: EdgeType26.CALLS };
14329
14878
  }
14330
- const projectLevelId = infraId19("supabase", config.nodeRef);
14879
+ const projectLevelId = infraId20("supabase", config.nodeRef);
14331
14880
  if (graph.hasNode(projectLevelId)) {
14332
14881
  return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: EdgeType26.CALLS };
14333
14882
  }
@@ -14799,9 +15348,9 @@ function parseFirebaseTargetName(targetName) {
14799
15348
  const secondSep = rest.indexOf(FIELD_SEP);
14800
15349
  if (secondSep === -1) return null;
14801
15350
  const method = rest.slice(0, secondSep);
14802
- const path63 = rest.slice(secondSep + 1);
14803
- if (!resourceName || !method || !path63) return null;
14804
- return { resourceName, method, path: path63 };
15351
+ const path64 = rest.slice(secondSep + 1);
15352
+ if (!resourceName || !method || !path64) return null;
15353
+ return { resourceName, method, path: path64 };
14805
15354
  }
14806
15355
  function resourceNameFor(type, labels) {
14807
15356
  if (!labels) return null;
@@ -14839,14 +15388,14 @@ function mapLogEntryToSignal(entry) {
14839
15388
  if (!req) return null;
14840
15389
  if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
14841
15390
  const method = req.requestMethod.toUpperCase();
14842
- const path63 = pathFromRequestUrl(req.requestUrl);
14843
- if (path63 === null) return null;
15391
+ const path64 = pathFromRequestUrl(req.requestUrl);
15392
+ if (path64 === null) return null;
14844
15393
  const timestamp = entry.timestamp;
14845
15394
  if (typeof timestamp !== "string" || timestamp.length === 0) return null;
14846
15395
  const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD2;
14847
15396
  return {
14848
15397
  targetKind: resourceType,
14849
- targetName: packFirebaseTargetName({ resourceName, method, path: path63 }),
15398
+ targetName: packFirebaseTargetName({ resourceName, method, path: path64 }),
14850
15399
  callCount: 1,
14851
15400
  errorCount: isError ? 1 : 0,
14852
15401
  lastObservedIso: timestamp
@@ -14932,7 +15481,7 @@ function createFirebaseConnector(graph, serviceMap) {
14932
15481
  }
14933
15482
 
14934
15483
  // src/connectors/cloudflare/connector.ts
14935
- import { EdgeType as EdgeType29, NodeType as NodeType29, fileId as fileId4, infraId as infraId20 } from "@neat.is/types";
15484
+ import { EdgeType as EdgeType29, NodeType as NodeType29, fileId as fileId4, infraId as infraId21 } from "@neat.is/types";
14936
15485
 
14937
15486
  // src/connectors/cloudflare/client.ts
14938
15487
  import { randomUUID } from "crypto";
@@ -15043,7 +15592,7 @@ function mapEventToSignal(event) {
15043
15592
  if (Number.isNaN(observedAt.getTime())) return null;
15044
15593
  const statusCode = metadata?.statusCode;
15045
15594
  const isError = typeof statusCode === "number" && statusCode >= ERROR_STATUS_THRESHOLD3;
15046
- const path63 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
15595
+ const path64 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
15047
15596
  return {
15048
15597
  targetKind: CLOUDFLARE_TARGET_KIND,
15049
15598
  targetName: scriptName,
@@ -15051,7 +15600,7 @@ function mapEventToSignal(event) {
15051
15600
  errorCount: isError ? 1 : 0,
15052
15601
  lastObservedIso: observedAt.toISOString(),
15053
15602
  method,
15054
- ...path63 ? { path: path63 } : {},
15603
+ ...path64 ? { path: path64 } : {},
15055
15604
  ...typeof statusCode === "number" ? { statusCode } : {},
15056
15605
  ...typeof metadata?.duration === "number" ? { duration: metadata.duration } : {}
15057
15606
  };
@@ -15097,8 +15646,8 @@ function findTaggedWorkerFileNode(graph, workerName) {
15097
15646
  });
15098
15647
  return found;
15099
15648
  }
15100
- function findMatchingRouteNode(graph, serviceName, method, path63) {
15101
- const normalizedPath = normalizePathTemplate(path63);
15649
+ function findMatchingRouteNode(graph, serviceName, method, path64) {
15650
+ const normalizedPath = normalizePathTemplate(path64);
15102
15651
  let found = null;
15103
15652
  graph.forEachNode((id, attrs) => {
15104
15653
  if (found) return;
@@ -15115,10 +15664,10 @@ function createCloudflareResolveTarget(config, graph) {
15115
15664
  return (signal) => {
15116
15665
  if (signal.targetKind !== CLOUDFLARE_TARGET_KIND) return null;
15117
15666
  const scriptName = signal.targetName;
15118
- const { method, path: path63 } = signal;
15667
+ const { method, path: path64 } = signal;
15119
15668
  const resolveRouteGrain = (serviceName, wholeFileId) => {
15120
- if (!method || !path63) return wholeFileId;
15121
- return findMatchingRouteNode(graph, serviceName, method, path63) ?? wholeFileId;
15669
+ if (!method || !path64) return wholeFileId;
15670
+ return findMatchingRouteNode(graph, serviceName, method, path64) ?? wholeFileId;
15122
15671
  };
15123
15672
  const mapping = config.workers?.[scriptName];
15124
15673
  if (mapping) {
@@ -15139,7 +15688,7 @@ function createCloudflareResolveTarget(config, graph) {
15139
15688
  };
15140
15689
  }
15141
15690
  return {
15142
- targetNodeId: infraId20("cloudflare-worker", scriptName),
15691
+ targetNodeId: infraId21("cloudflare-worker", scriptName),
15143
15692
  serviceName: scriptName,
15144
15693
  edgeType: EdgeType29.CALLS,
15145
15694
  ensureInfraNode: { kind: "cloudflare-worker", name: scriptName, provider: "cloudflare" }
@@ -15323,12 +15872,12 @@ function diffNeonStatementsToSignals(rows, previous, observedAtIso) {
15323
15872
  }
15324
15873
 
15325
15874
  // src/connectors/neon/resolve.ts
15326
- import { EdgeType as EdgeType30, infraId as infraId21 } from "@neat.is/types";
15875
+ import { EdgeType as EdgeType30, infraId as infraId22 } from "@neat.is/types";
15327
15876
  function createNeonResolveTarget(config) {
15328
15877
  return (signal) => {
15329
15878
  if (signal.targetKind !== NEON_SQL_TABLE_TARGET_KIND || !signal.targetName) return null;
15330
15879
  return {
15331
- targetNodeId: infraId21("sql-table", signal.targetName),
15880
+ targetNodeId: infraId22("sql-table", signal.targetName),
15332
15881
  serviceName: config.serviceName,
15333
15882
  edgeType: EdgeType30.CALLS,
15334
15883
  ensureInfraNode: { kind: "sql-table", name: signal.targetName, provider: "neon" }
@@ -15448,9 +15997,9 @@ function parseCloudRunTargetName(targetName) {
15448
15997
  const secondSep = rest.indexOf(FIELD_SEP2);
15449
15998
  if (secondSep === -1) return null;
15450
15999
  const method = rest.slice(0, secondSep);
15451
- const path63 = rest.slice(secondSep + 1);
15452
- if (!serviceName || !method || !path63) return null;
15453
- return { serviceName, method, path: path63 };
16000
+ const path64 = rest.slice(secondSep + 1);
16001
+ if (!serviceName || !method || !path64) return null;
16002
+ return { serviceName, method, path: path64 };
15454
16003
  }
15455
16004
 
15456
16005
  // src/connectors/cloud-run/map.ts
@@ -15479,14 +16028,14 @@ function mapLogEntryToSignal2(entry) {
15479
16028
  if (!req) return null;
15480
16029
  if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
15481
16030
  const method = req.requestMethod.toUpperCase();
15482
- const path63 = pathFromRequestUrl2(req.requestUrl);
15483
- if (path63 === null) return null;
16031
+ const path64 = pathFromRequestUrl2(req.requestUrl);
16032
+ if (path64 === null) return null;
15484
16033
  const timestamp = entry.timestamp;
15485
16034
  if (typeof timestamp !== "string" || timestamp.length === 0) return null;
15486
16035
  const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD4;
15487
16036
  return {
15488
16037
  targetKind: CLOUD_RUN_TARGET_KIND,
15489
- targetName: packCloudRunTargetName({ serviceName, method, path: path63 }),
16038
+ targetName: packCloudRunTargetName({ serviceName, method, path: path64 }),
15490
16039
  callCount: 1,
15491
16040
  errorCount: isError ? 1 : 0,
15492
16041
  lastObservedIso: timestamp
@@ -15502,7 +16051,7 @@ function mapLogEntriesToSignals2(entries) {
15502
16051
  }
15503
16052
 
15504
16053
  // src/connectors/cloud-run/resolve.ts
15505
- import { EdgeType as EdgeType31, NodeType as NodeType30, infraId as infraId22 } from "@neat.is/types";
16054
+ import { EdgeType as EdgeType31, NodeType as NodeType30, infraId as infraId23 } from "@neat.is/types";
15506
16055
  var CLOUD_RUN_SERVICE_INFRA_KIND = "cloud-run-service";
15507
16056
  function findMatchingRouteNode2(graph, serviceName, method, normalizedPath) {
15508
16057
  let found = null;
@@ -15524,21 +16073,21 @@ function createCloudRunResolveTarget(graph, config) {
15524
16073
  if (signal.targetKind !== CLOUD_RUN_TARGET_KIND) return null;
15525
16074
  const identity = parseCloudRunTargetName(signal.targetName);
15526
16075
  if (!identity) return null;
15527
- const { serviceName: gcpServiceName, method, path: path63 } = identity;
16076
+ const { serviceName: gcpServiceName, method, path: path64 } = identity;
15528
16077
  const mappedService = config.serviceMap?.[gcpServiceName];
15529
16078
  if (mappedService) {
15530
16079
  const routeNodeId = findMatchingRouteNode2(
15531
16080
  graph,
15532
16081
  mappedService,
15533
16082
  method,
15534
- normalizePathTemplate(path63)
16083
+ normalizePathTemplate(path64)
15535
16084
  );
15536
16085
  if (routeNodeId) {
15537
16086
  return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: EdgeType31.CALLS };
15538
16087
  }
15539
16088
  }
15540
16089
  return {
15541
- targetNodeId: infraId22(CLOUD_RUN_SERVICE_INFRA_KIND, gcpServiceName),
16090
+ targetNodeId: infraId23(CLOUD_RUN_SERVICE_INFRA_KIND, gcpServiceName),
15542
16091
  serviceName: mappedService ?? gcpServiceName,
15543
16092
  edgeType: EdgeType31.CALLS,
15544
16093
  ensureInfraNode: {
@@ -15870,17 +16419,17 @@ function mapInsightsToSignals(rows, observedAtIso) {
15870
16419
  }
15871
16420
 
15872
16421
  // src/connectors/planetscale/resolve.ts
15873
- import { EdgeType as EdgeType33, infraId as infraId23 } from "@neat.is/types";
16422
+ import { EdgeType as EdgeType33, infraId as infraId24 } from "@neat.is/types";
15874
16423
  var PLANETSCALE_DATABASE_KIND = "planetscale-database";
15875
16424
  function createPlanetscaleResolveTarget(graph, config) {
15876
16425
  const databaseName = `${config.organization}/${config.database}`;
15877
16426
  return (signal, _ctx) => {
15878
16427
  if (signal.targetKind !== PLANETSCALE_SQL_TABLE_TARGET_KIND || !signal.targetName) return null;
15879
- const tableId = infraId23("sql-table", signal.targetName);
16428
+ const tableId = infraId24("sql-table", signal.targetName);
15880
16429
  if (graph.hasNode(tableId)) {
15881
16430
  return { targetNodeId: tableId, serviceName: config.serviceName, edgeType: EdgeType33.CALLS };
15882
16431
  }
15883
- const providerId = infraId23(PLANETSCALE_DATABASE_KIND, databaseName);
16432
+ const providerId = infraId24(PLANETSCALE_DATABASE_KIND, databaseName);
15884
16433
  return {
15885
16434
  targetNodeId: providerId,
15886
16435
  serviceName: config.serviceName,
@@ -17385,4 +17934,4 @@ export {
17385
17934
  deprovisionConnector,
17386
17935
  buildApi
17387
17936
  };
17388
- //# sourceMappingURL=chunk-XT4NNFH6.js.map
17937
+ //# sourceMappingURL=chunk-UI3AFJAF.js.map