@neat.is/core 0.7.6 → 0.7.8

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) {
@@ -1780,8 +1780,9 @@ var ROUTER_METHODS = /* @__PURE__ */ new Set([
1780
1780
  "all"
1781
1781
  ]);
1782
1782
  var NEXT_APP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
1783
+ var NET_HTTP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
1783
1784
  var JS_ROUTE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx"]);
1784
- function ginRoutesFromSource(source, parser) {
1785
+ function goRouterRoutesFromSource(source, parser, framework) {
1785
1786
  const tree = parseSource2(parser, source);
1786
1787
  const prefixes = /* @__PURE__ */ new Map();
1787
1788
  const out = [];
@@ -1791,10 +1792,12 @@ function ginRoutesFromSource(source, parser) {
1791
1792
  const value = node.childForFieldName("right")?.namedChild(0) ?? node.childForFieldName("value");
1792
1793
  if (name && value?.type === "call_expression") {
1793
1794
  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));
1795
+ if (fn2?.childForFieldName("field")?.text === "Group") {
1796
+ const leaf2 = goStringLiteral(value.childForFieldName("arguments")?.namedChild(0));
1797
+ if (leaf2 !== null) {
1798
+ const parent = fn2.childForFieldName("operand")?.text ?? "";
1799
+ prefixes.set(name, (prefixes.get(parent) ?? "") + leaf2);
1800
+ }
1798
1801
  }
1799
1802
  }
1800
1803
  return;
@@ -1805,18 +1808,127 @@ function ginRoutesFromSource(source, parser) {
1805
1808
  const method = fn.childForFieldName("field")?.text?.toUpperCase();
1806
1809
  if (!method || !ROUTER_METHODS.has(method.toLowerCase())) return;
1807
1810
  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);
1811
+ const leaf = goStringLiteral(node.childForFieldName("arguments")?.namedChild(0));
1812
+ if (leaf === null) return;
1811
1813
  out.push({
1812
- method: method === "ALL" ? "ALL" : method,
1814
+ method,
1813
1815
  pathTemplate: canonicalizeTemplate((prefixes.get(receiver) ?? "") + leaf),
1814
1816
  line: node.startPosition.row + 1,
1815
- framework: "gin"
1817
+ framework
1818
+ });
1819
+ });
1820
+ return out;
1821
+ }
1822
+ function goStringLiteral(node) {
1823
+ if (node?.type === "interpreted_string_literal" || node?.type === "raw_string_literal") {
1824
+ return node.text.slice(1, -1);
1825
+ }
1826
+ return null;
1827
+ }
1828
+ function ginRoutesFromSource(source, parser) {
1829
+ return goRouterRoutesFromSource(source, parser, "gin");
1830
+ }
1831
+ function echoRoutesFromSource(source, parser) {
1832
+ return goRouterRoutesFromSource(source, parser, "echo");
1833
+ }
1834
+ function fiberRoutesFromSource(source, parser) {
1835
+ return goRouterRoutesFromSource(source, parser, "fiber");
1836
+ }
1837
+ function chiRoutesFromSource(source, parser) {
1838
+ const tree = parseSource2(parser, source);
1839
+ const out = [];
1840
+ chiWalk(tree.rootNode, "", out);
1841
+ return out;
1842
+ }
1843
+ function stripChiRegex(path64) {
1844
+ return path64.replace(/\{([^{}:]+):[^{}]*\}/g, "{$1}");
1845
+ }
1846
+ function chiWalk(node, prefix, out) {
1847
+ for (let i = 0; i < node.namedChildCount; i++) {
1848
+ const child = node.namedChild(i);
1849
+ if (child) chiHandle(child, prefix, out);
1850
+ }
1851
+ }
1852
+ function chiHandle(node, prefix, out) {
1853
+ if (node.type === "call_expression") {
1854
+ const fn = node.childForFieldName("function");
1855
+ if (fn?.type === "selector_expression") {
1856
+ const field = fn.childForFieldName("field")?.text;
1857
+ const args = node.childForFieldName("arguments");
1858
+ if (field === "Route") {
1859
+ const leaf = goStringLiteral(args?.namedChild(0));
1860
+ const closure = args?.namedChild(1);
1861
+ if (leaf !== null && closure?.type === "func_literal") {
1862
+ const body = closure.childForFieldName("body");
1863
+ if (body) chiWalk(body, prefix + leaf, out);
1864
+ }
1865
+ return;
1866
+ }
1867
+ if (field === "Group") {
1868
+ const closure = args?.namedChild(0);
1869
+ if (closure?.type === "func_literal") {
1870
+ const body = closure.childForFieldName("body");
1871
+ if (body) chiWalk(body, prefix, out);
1872
+ }
1873
+ return;
1874
+ }
1875
+ if (field === "Mount") {
1876
+ return;
1877
+ }
1878
+ if (field && ROUTER_METHODS.has(field.toLowerCase())) {
1879
+ const leaf = goStringLiteral(args?.namedChild(0));
1880
+ if (leaf !== null) {
1881
+ out.push({
1882
+ method: field.toUpperCase(),
1883
+ pathTemplate: canonicalizeTemplate(stripChiRegex(prefix + leaf)),
1884
+ line: node.startPosition.row + 1,
1885
+ framework: "chi"
1886
+ });
1887
+ }
1888
+ return;
1889
+ }
1890
+ }
1891
+ }
1892
+ chiWalk(node, prefix, out);
1893
+ }
1894
+ function netHttpRoutesFromSource(source, parser) {
1895
+ const tree = parseSource2(parser, source);
1896
+ if (!goImportsNetHttp(tree.rootNode)) return [];
1897
+ const out = [];
1898
+ walk(tree.rootNode, (node) => {
1899
+ if (node.type !== "call_expression") return;
1900
+ const fn = node.childForFieldName("function");
1901
+ if (fn?.type !== "selector_expression") return;
1902
+ const field = fn.childForFieldName("field")?.text;
1903
+ if (field !== "HandleFunc" && field !== "Handle") return;
1904
+ const leaf = goStringLiteral(node.childForFieldName("arguments")?.namedChild(0));
1905
+ if (leaf === null) return;
1906
+ const sp = leaf.indexOf(" ");
1907
+ if (sp < 0) return;
1908
+ const method = leaf.slice(0, sp);
1909
+ const rest = leaf.slice(sp + 1);
1910
+ if (!NET_HTTP_METHODS.has(method)) return;
1911
+ if (!rest.startsWith("/")) return;
1912
+ out.push({
1913
+ method,
1914
+ pathTemplate: canonicalizeTemplate(rest),
1915
+ line: node.startPosition.row + 1,
1916
+ framework: "net/http"
1816
1917
  });
1817
1918
  });
1818
1919
  return out;
1819
1920
  }
1921
+ function goImportsNetHttp(root) {
1922
+ let found = false;
1923
+ walk(root, (node) => {
1924
+ if (found || node.type !== "import_spec") return;
1925
+ for (let i = 0; i < node.namedChildCount; i++) {
1926
+ const child = node.namedChild(i);
1927
+ if (goStringLiteral(child) === "net/http") found = true;
1928
+ }
1929
+ });
1930
+ return found;
1931
+ }
1820
1932
  var FASTAPI_METHODS = /* @__PURE__ */ new Set(["get", "post", "put", "patch", "delete", "options", "head", "trace"]);
1821
1933
  var NESTJS_METHODS = /* @__PURE__ */ new Map([
1822
1934
  ["Get", "GET"],
@@ -2401,9 +2513,9 @@ function rubyRocketRoute(args) {
2401
2513
  if (!pair || pair.type !== "pair") continue;
2402
2514
  const k = pair.childForFieldName("key");
2403
2515
  if (k?.type !== "string") continue;
2404
- const path63 = rubyLiteral(k);
2405
- if (path63 === null) continue;
2406
- return { path: path63, target: rubyLiteral(pair.childForFieldName("value")) };
2516
+ const path64 = rubyLiteral(k);
2517
+ if (path64 === null) continue;
2518
+ return { path: path64, target: rubyLiteral(pair.childForFieldName("value")) };
2407
2519
  }
2408
2520
  return null;
2409
2521
  }
@@ -3080,9 +3192,13 @@ async function addRoutes(graph, services) {
3080
3192
  const hasFlask = deps["flask"] !== void 0;
3081
3193
  const hasDjango = deps["django"] !== void 0;
3082
3194
  const hasGin = deps["github.com/gin-gonic/gin"] !== void 0;
3195
+ const hasEcho = deps["github.com/labstack/echo/v4"] !== void 0 || deps["github.com/labstack/echo"] !== void 0;
3196
+ const hasFiber = deps["github.com/gofiber/fiber/v2"] !== void 0 || deps["github.com/gofiber/fiber/v3"] !== void 0;
3197
+ const hasChi = deps["github.com/go-chi/chi/v5"] !== void 0 || deps["github.com/go-chi/chi"] !== void 0;
3198
+ const isGoService = service.node.language === "go";
3083
3199
  const hasRails = deps["rails"] !== void 0;
3084
3200
  const hasLaravel = deps["laravel/framework"] !== void 0;
3085
- if (!hasExpress && !hasFastify && !hasHono && !hasNext && !hasNestjs && !hasFastapi && !hasFlask && !hasDjango && !hasGin && !hasRails && !hasLaravel)
3201
+ if (!hasExpress && !hasFastify && !hasHono && !hasNext && !hasNestjs && !hasFastapi && !hasFlask && !hasDjango && !hasGin && !hasEcho && !hasFiber && !hasChi && !isGoService && !hasRails && !hasLaravel)
3086
3202
  continue;
3087
3203
  const files = await loadSourceFiles(service.dir);
3088
3204
  const mountPrefixes = hasExpress ? await expressMountPrefixes(files, service.dir, await loadTsPathConfig(service.dir)) : /* @__PURE__ */ new Map();
@@ -3106,7 +3222,12 @@ async function addRoutes(graph, services) {
3106
3222
  } else if (isRb) {
3107
3223
  routes = hasRails && relFile === "config/routes.rb" ? railsRoutesFromSource(file.content, rubyParser) : [];
3108
3224
  } else if (isGo) {
3109
- routes = hasGin ? ginRoutesFromSource(file.content, goParser) : [];
3225
+ if (hasGin) routes = ginRoutesFromSource(file.content, goParser);
3226
+ else if (hasEcho) routes = echoRoutesFromSource(file.content, goParser);
3227
+ else if (hasFiber) routes = fiberRoutesFromSource(file.content, goParser);
3228
+ else if (hasChi) routes = chiRoutesFromSource(file.content, goParser);
3229
+ else routes = [];
3230
+ routes = routes.concat(netHttpRoutesFromSource(file.content, goParser));
3110
3231
  } else if (isPy) {
3111
3232
  routes = hasFastapi || hasFlask ? pythonRoutesFromSource(file.content, pyParser, hasFastapi ? "fastapi" : "flask") : [];
3112
3233
  if (hasDjango) routes = routes.concat(djangoRoutesFromSource(file.content, pyParser));
@@ -4740,19 +4861,19 @@ function confidenceFromMix(edges, now = Date.now()) {
4740
4861
  function longestIncomingWalk(graph, start, maxDepth) {
4741
4862
  let best = { path: [start], edges: [] };
4742
4863
  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] };
4864
+ function step(node, path64, edges) {
4865
+ if (path64.length > best.path.length) {
4866
+ best = { path: [...path64], edges: [...edges] };
4746
4867
  }
4747
- if (path63.length - 1 >= maxDepth) return;
4868
+ if (path64.length - 1 >= maxDepth) return;
4748
4869
  const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
4749
4870
  for (const [srcId, edge] of incoming) {
4750
4871
  if (visited.has(srcId)) continue;
4751
4872
  visited.add(srcId);
4752
- path63.push(srcId);
4873
+ path64.push(srcId);
4753
4874
  edges.push(edge);
4754
- step(srcId, path63, edges);
4755
- path63.pop();
4875
+ step(srcId, path64, edges);
4876
+ path64.pop();
4756
4877
  edges.pop();
4757
4878
  visited.delete(srcId);
4758
4879
  }
@@ -4760,11 +4881,11 @@ function longestIncomingWalk(graph, start, maxDepth) {
4760
4881
  step(start, [start], []);
4761
4882
  return best;
4762
4883
  }
4763
- function databaseRootCauseShape(graph, origin, walk8) {
4884
+ function databaseRootCauseShape(graph, origin, walk9) {
4764
4885
  const targetDb = origin;
4765
4886
  const candidatePairs = compatPairs().filter((p) => p.engine === targetDb.engine);
4766
4887
  if (candidatePairs.length === 0) return null;
4767
- for (const id of walk8.path) {
4888
+ for (const id of walk9.path) {
4768
4889
  const owner = resolveOwningService(graph, id);
4769
4890
  if (!owner) continue;
4770
4891
  const { id: serviceId9, svc } = owner;
@@ -4791,8 +4912,8 @@ function databaseRootCauseShape(graph, origin, walk8) {
4791
4912
  }
4792
4913
  return null;
4793
4914
  }
4794
- function serviceRootCauseShape(graph, _origin, walk8) {
4795
- for (const id of walk8.path) {
4915
+ function serviceRootCauseShape(graph, _origin, walk9) {
4916
+ for (const id of walk9.path) {
4796
4917
  const owner = resolveOwningService(graph, id);
4797
4918
  if (!owner) continue;
4798
4919
  const { id: serviceId9, svc } = owner;
@@ -4828,15 +4949,15 @@ function serviceRootCauseShape(graph, _origin, walk8) {
4828
4949
  }
4829
4950
  return null;
4830
4951
  }
4831
- function fileRootCauseShape(graph, origin, walk8) {
4952
+ function fileRootCauseShape(graph, origin, walk9) {
4832
4953
  const owner = resolveOwningService(graph, origin.id);
4833
4954
  if (!owner) return null;
4834
- return serviceRootCauseShape(graph, owner.svc, walk8);
4955
+ return serviceRootCauseShape(graph, owner.svc, walk9);
4835
4956
  }
4836
- function symbolRootCauseShape(graph, origin, walk8) {
4957
+ function symbolRootCauseShape(graph, origin, walk9) {
4837
4958
  const owner = resolveOwningService(graph, origin.id);
4838
4959
  if (!owner) return null;
4839
- return serviceRootCauseShape(graph, owner.svc, walk8);
4960
+ return serviceRootCauseShape(graph, owner.svc, walk9);
4840
4961
  }
4841
4962
  var rootCauseShapes = {
4842
4963
  [NodeType5.DatabaseNode]: databaseRootCauseShape,
@@ -4849,16 +4970,16 @@ function getRootCause(graph, errorNodeId, errorEvent, incidents) {
4849
4970
  const origin = graph.getNodeAttributes(errorNodeId);
4850
4971
  const shape = rootCauseShapes[origin.type];
4851
4972
  if (shape) {
4852
- const walk8 = longestIncomingWalk(graph, errorNodeId, ROOT_CAUSE_MAX_DEPTH);
4853
- const match = shape(graph, origin, walk8);
4973
+ const walk9 = longestIncomingWalk(graph, errorNodeId, ROOT_CAUSE_MAX_DEPTH);
4974
+ const match = shape(graph, origin, walk9);
4854
4975
  if (match) {
4855
4976
  const reason = errorEvent ? `${match.rootCauseReason} (observed error: ${errorEvent.errorMessage})` : match.rootCauseReason;
4856
4977
  return RootCauseResultSchema.parse({
4857
4978
  rootCauseNode: match.rootCauseNode,
4858
4979
  rootCauseReason: reason,
4859
- traversalPath: walk8.path,
4860
- edgeProvenances: walk8.edges.map((e) => e.provenance),
4861
- confidence: confidenceFromMix(walk8.edges),
4980
+ traversalPath: walk9.path,
4981
+ edgeProvenances: walk9.edges.map((e) => e.provenance),
4982
+ confidence: confidenceFromMix(walk9.edges),
4862
4983
  fixRecommendation: match.fixRecommendation
4863
4984
  });
4864
4985
  }
@@ -4959,26 +5080,26 @@ function dominantFailingCall(graph, serviceId9, visited) {
4959
5080
  return best;
4960
5081
  }
4961
5082
  function followFailingCallChain(graph, originServiceId, maxDepth) {
4962
- const path63 = [originServiceId];
5083
+ const path64 = [originServiceId];
4963
5084
  const edges = [];
4964
5085
  const visited = /* @__PURE__ */ new Set([originServiceId]);
4965
5086
  let current = originServiceId;
4966
5087
  for (let depth = 0; depth < maxDepth; depth++) {
4967
5088
  const hop = dominantFailingCall(graph, current, visited);
4968
5089
  if (!hop) break;
4969
- path63.push(hop.nextService);
5090
+ path64.push(hop.nextService);
4970
5091
  edges.push(hop.edge);
4971
5092
  visited.add(hop.nextService);
4972
5093
  current = hop.nextService;
4973
5094
  }
4974
5095
  if (edges.length === 0) return null;
4975
- return { path: path63, edges, culprit: current };
5096
+ return { path: path64, edges, culprit: current };
4976
5097
  }
4977
5098
  function crossServiceRootCause(graph, originId, incidents, errorEvent) {
4978
5099
  const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
4979
5100
  if (!chain) return null;
4980
5101
  const culprit = chain.culprit;
4981
- const path63 = [...chain.path];
5102
+ const path64 = [...chain.path];
4982
5103
  const edgeProvenances = chain.edges.map((e) => e.provenance);
4983
5104
  const baseConfidence = confidenceFromMix(chain.edges);
4984
5105
  const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
@@ -4986,14 +5107,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
4986
5107
  if (loc) {
4987
5108
  let rootCauseNode = culprit;
4988
5109
  if (loc.fileNode) {
4989
- path63.push(loc.fileNode);
5110
+ path64.push(loc.fileNode);
4990
5111
  edgeProvenances.push(Provenance6.OBSERVED);
4991
5112
  rootCauseNode = loc.fileNode;
4992
5113
  }
4993
5114
  return RootCauseResultSchema.parse({
4994
5115
  rootCauseNode,
4995
5116
  rootCauseReason: loc.rootCauseReason,
4996
- traversalPath: path63,
5117
+ traversalPath: path64,
4997
5118
  edgeProvenances,
4998
5119
  confidence,
4999
5120
  ...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
@@ -5005,7 +5126,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
5005
5126
  return RootCauseResultSchema.parse({
5006
5127
  rootCauseNode: culprit,
5007
5128
  rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
5008
- traversalPath: path63,
5129
+ traversalPath: path64,
5009
5130
  edgeProvenances,
5010
5131
  confidence,
5011
5132
  fixRecommendation: `Inspect ${culpritName}'s failing handler`
@@ -5243,6 +5364,13 @@ function parseGoMod(source) {
5243
5364
  }
5244
5365
  return { module, ...goVersion ? { goVersion } : {}, dependencies };
5245
5366
  }
5367
+ function goFramework(deps) {
5368
+ if (deps["github.com/gin-gonic/gin"]) return "gin";
5369
+ if (deps["github.com/labstack/echo/v4"] || deps["github.com/labstack/echo"]) return "echo";
5370
+ if (deps["github.com/gofiber/fiber/v2"] || deps["github.com/gofiber/fiber/v3"]) return "fiber";
5371
+ if (deps["github.com/go-chi/chi/v5"] || deps["github.com/go-chi/chi"]) return "chi";
5372
+ return void 0;
5373
+ }
5246
5374
  async function discoverGoService(scanPath, dir) {
5247
5375
  let raw;
5248
5376
  try {
@@ -5254,6 +5382,7 @@ async function discoverGoService(scanPath, dir) {
5254
5382
  if (!mod) return null;
5255
5383
  const name = mod.module.split("/").filter(Boolean).pop() ?? mod.module;
5256
5384
  const pkg = { name, dependencies: mod.dependencies };
5385
+ const framework = goFramework(mod.dependencies);
5257
5386
  const node = {
5258
5387
  id: serviceId2(name),
5259
5388
  type: NodeType6.ServiceNode,
@@ -5261,7 +5390,7 @@ async function discoverGoService(scanPath, dir) {
5261
5390
  language: "go",
5262
5391
  dependencies: mod.dependencies,
5263
5392
  repoPath: path10.relative(scanPath, dir),
5264
- ...mod.dependencies["github.com/gin-gonic/gin"] ? { framework: "gin" } : {}
5393
+ ...framework ? { framework } : {}
5265
5394
  };
5266
5395
  return { pkg, dir, node };
5267
5396
  }
@@ -6168,7 +6297,7 @@ async function addSymbolEdges(graph, services) {
6168
6297
  return best;
6169
6298
  };
6170
6299
  const requests = [];
6171
- const walk8 = (node) => {
6300
+ const walk9 = (node) => {
6172
6301
  if (node.type === "class_declaration" || node.type === "abstract_class_declaration" || node.type === "class") {
6173
6302
  const self = localBySpan.get(`${node.startPosition.row + 1}:${node.endPosition.row + 1}`);
6174
6303
  if (self && self.kind === "class") {
@@ -6214,10 +6343,10 @@ async function addSymbolEdges(graph, services) {
6214
6343
  }
6215
6344
  for (let i = 0; i < node.namedChildCount; i++) {
6216
6345
  const child = node.namedChild(i);
6217
- if (child) walk8(child);
6346
+ if (child) walk9(child);
6218
6347
  }
6219
6348
  };
6220
- walk8(root);
6349
+ walk9(root);
6221
6350
  for (const req of requests) {
6222
6351
  const targetSid = resolveTarget(req.targetName, req.wantKind);
6223
6352
  if (!targetSid) continue;
@@ -7237,20 +7366,20 @@ import {
7237
7366
  } from "@neat.is/types";
7238
7367
  async function walkConfigFiles(dir) {
7239
7368
  const out = [];
7240
- async function walk8(current) {
7369
+ async function walk9(current) {
7241
7370
  const entries = await fs16.readdir(current, { withFileTypes: true });
7242
7371
  for (const entry of entries) {
7243
7372
  const full = path28.join(current, entry.name);
7244
7373
  if (entry.isDirectory()) {
7245
7374
  if (IGNORED_DIRS.has(entry.name)) continue;
7246
7375
  if (await isPythonVenvDir(full)) continue;
7247
- await walk8(full);
7376
+ await walk9(full);
7248
7377
  } else if (entry.isFile() && isConfigFile(entry.name).match) {
7249
7378
  out.push(full);
7250
7379
  }
7251
7380
  }
7252
7381
  }
7253
- await walk8(dir);
7382
+ await walk9(dir);
7254
7383
  return out;
7255
7384
  }
7256
7385
  async function addConfigNodes(graph, services, scanPath) {
@@ -7346,20 +7475,20 @@ function grpcMethodsFromProto(content, fqPackage) {
7346
7475
  }
7347
7476
  async function walkProtoFiles(dir) {
7348
7477
  const out = [];
7349
- async function walk8(current) {
7478
+ async function walk9(current) {
7350
7479
  const entries = await fs17.readdir(current, { withFileTypes: true }).catch(() => []);
7351
7480
  for (const entry of entries) {
7352
7481
  const full = path29.join(current, entry.name);
7353
7482
  if (entry.isDirectory()) {
7354
7483
  if (IGNORED_DIRS.has(entry.name)) continue;
7355
7484
  if (await isPythonVenvDir(full)) continue;
7356
- await walk8(full);
7485
+ await walk9(full);
7357
7486
  } else if (entry.isFile() && path29.extname(entry.name) === PROTO_EXTENSION) {
7358
7487
  out.push(full);
7359
7488
  }
7360
7489
  }
7361
7490
  }
7362
- await walk8(dir);
7491
+ await walk9(dir);
7363
7492
  return out;
7364
7493
  }
7365
7494
  async function addGrpcMethods(graph, services) {
@@ -8190,7 +8319,7 @@ function isFirestoreClientFactory(node) {
8190
8319
  }
8191
8320
  function firestoreClientVars(root) {
8192
8321
  const vars = /* @__PURE__ */ new Set();
8193
- const walk8 = (node) => {
8322
+ const walk9 = (node) => {
8194
8323
  if (node.type === "variable_declarator") {
8195
8324
  const name = node.childForFieldName("name");
8196
8325
  let value = node.childForFieldName("value");
@@ -8199,9 +8328,9 @@ function firestoreClientVars(root) {
8199
8328
  vars.add(name.text);
8200
8329
  }
8201
8330
  }
8202
- for (const c of namedChildren(node)) walk8(c);
8331
+ for (const c of namedChildren(node)) walk9(c);
8203
8332
  };
8204
- walk8(root);
8333
+ walk9(root);
8205
8334
  return vars;
8206
8335
  }
8207
8336
  function isClientExpr(node, clientVars) {
@@ -8356,7 +8485,7 @@ function firestoreEndpointsFromFile(file, serviceDir) {
8356
8485
  }
8357
8486
  s.add(field);
8358
8487
  };
8359
- const walk8 = (node) => {
8488
+ const walk9 = (node) => {
8360
8489
  if (node.type === "call_expression") {
8361
8490
  const fn = node.childForFieldName("function");
8362
8491
  const line = node.startPosition.row + 1;
@@ -8396,9 +8525,9 @@ function firestoreEndpointsFromFile(file, serviceDir) {
8396
8525
  }
8397
8526
  }
8398
8527
  }
8399
- for (const c of namedChildren(node)) walk8(c);
8528
+ for (const c of namedChildren(node)) walk9(c);
8400
8529
  };
8401
- walk8(tree.rootNode);
8530
+ walk9(tree.rootNode);
8402
8531
  const out = [];
8403
8532
  for (const [collPath, line] of collLine) {
8404
8533
  const byField = writes.get(collPath);
@@ -9189,7 +9318,7 @@ function drizzleEndpointsFromFile(file, serviceDir) {
9189
9318
  const tree = parseSource3(parserForExt2(path41.extname(file.path)), file.content);
9190
9319
  const out = [];
9191
9320
  const seen = /* @__PURE__ */ new Set();
9192
- const walk8 = (node) => {
9321
+ const walk9 = (node) => {
9193
9322
  if (node.type === "call_expression") {
9194
9323
  const fn = node.childForFieldName("function");
9195
9324
  if (fn?.type === "identifier" && TABLE_BUILDERS.has(fn.text)) {
@@ -9217,9 +9346,9 @@ function drizzleEndpointsFromFile(file, serviceDir) {
9217
9346
  }
9218
9347
  }
9219
9348
  }
9220
- for (const c of namedChildren4(node)) walk8(c);
9349
+ for (const c of namedChildren4(node)) walk9(c);
9221
9350
  };
9222
- walk8(tree.rootNode);
9351
+ walk9(tree.rootNode);
9223
9352
  return out;
9224
9353
  }
9225
9354
  function enclosingVarName(call) {
@@ -9241,7 +9370,7 @@ function enclosingVarName(call) {
9241
9370
  function collectDrizzleTables(root) {
9242
9371
  const tables = [];
9243
9372
  const varToTable = /* @__PURE__ */ new Map();
9244
- const walk8 = (node) => {
9373
+ const walk9 = (node) => {
9245
9374
  if (node.type === "call_expression") {
9246
9375
  const fn = node.childForFieldName("function");
9247
9376
  if (fn?.type === "identifier" && TABLE_BUILDERS.has(fn.text)) {
@@ -9256,9 +9385,9 @@ function collectDrizzleTables(root) {
9256
9385
  }
9257
9386
  }
9258
9387
  }
9259
- for (const c of namedChildren4(node)) walk8(c);
9388
+ for (const c of namedChildren4(node)) walk9(c);
9260
9389
  };
9261
- walk8(root);
9390
+ walk9(root);
9262
9391
  return { tables, varToTable };
9263
9392
  }
9264
9393
  function referencesTargetVar(call) {
@@ -9281,7 +9410,7 @@ function drizzleForeignKeys(file, serviceDir) {
9281
9410
  const seen = /* @__PURE__ */ new Set();
9282
9411
  for (const table of tables) {
9283
9412
  if (!table.object) continue;
9284
- const walk8 = (node) => {
9413
+ const walk9 = (node) => {
9285
9414
  if (node.type === "call_expression") {
9286
9415
  const targetVar = referencesTargetVar(node);
9287
9416
  const parentTable = targetVar ? varToTable.get(targetVar) : void 0;
@@ -9302,9 +9431,9 @@ function drizzleForeignKeys(file, serviceDir) {
9302
9431
  }
9303
9432
  }
9304
9433
  }
9305
- for (const c of namedChildren4(node)) walk8(c);
9434
+ for (const c of namedChildren4(node)) walk9(c);
9306
9435
  };
9307
- walk8(table.object);
9436
+ walk9(table.object);
9308
9437
  }
9309
9438
  return out;
9310
9439
  }
@@ -10379,6 +10508,521 @@ function goSqlEndpointsFromFile(file, serviceDir) {
10379
10508
  return out;
10380
10509
  }
10381
10510
 
10511
+ // src/extract/calls/gorm.ts
10512
+ import path46 from "path";
10513
+ import Parser15 from "tree-sitter";
10514
+ import Go4 from "tree-sitter-go";
10515
+ import { infraId as infraId16 } from "@neat.is/types";
10516
+ var GORM_IMPORT_RE = /gorm\.io\/gorm/;
10517
+ var PARSE_CHUNK11 = 16384;
10518
+ function makeGoParser3() {
10519
+ const p = new Parser15();
10520
+ p.setLanguage(Go4);
10521
+ return p;
10522
+ }
10523
+ function parseSource10(parser, source) {
10524
+ return parser.parse(
10525
+ (index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK11)
10526
+ );
10527
+ }
10528
+ function walk8(node, visit) {
10529
+ visit(node);
10530
+ for (let i = 0; i < node.namedChildCount; i++) {
10531
+ const c = node.namedChild(i);
10532
+ if (c) walk8(c, visit);
10533
+ }
10534
+ }
10535
+ var COMMON_INITIALISMS = [
10536
+ "ASCII",
10537
+ "HTTPS",
10538
+ "UTF8",
10539
+ "XSRF",
10540
+ "HTML",
10541
+ "HTTP",
10542
+ "JSON",
10543
+ "UUID",
10544
+ "XMPP",
10545
+ "ACL",
10546
+ "API",
10547
+ "CPU",
10548
+ "CSS",
10549
+ "DNS",
10550
+ "EOF",
10551
+ "GUID",
10552
+ "LHS",
10553
+ "QPS",
10554
+ "RAM",
10555
+ "RHS",
10556
+ "RPC",
10557
+ "SLA",
10558
+ "SQL",
10559
+ "SSH",
10560
+ "TCP",
10561
+ "TLS",
10562
+ "TTL",
10563
+ "UDP",
10564
+ "UID",
10565
+ "URI",
10566
+ "URL",
10567
+ "UID",
10568
+ "XSS",
10569
+ "ID",
10570
+ "IP",
10571
+ "UI",
10572
+ "VM",
10573
+ "XML"
10574
+ ].sort((a, b) => b.length - a.length);
10575
+ function titleCase(word) {
10576
+ return word.charAt(0) + word.slice(1).toLowerCase();
10577
+ }
10578
+ function replaceInitialisms(name) {
10579
+ let out = "";
10580
+ let i = 0;
10581
+ while (i < name.length) {
10582
+ let matched = false;
10583
+ for (const init of COMMON_INITIALISMS) {
10584
+ if (name.startsWith(init, i)) {
10585
+ out += titleCase(init);
10586
+ i += init.length;
10587
+ matched = true;
10588
+ break;
10589
+ }
10590
+ }
10591
+ if (!matched) {
10592
+ out += name[i];
10593
+ i++;
10594
+ }
10595
+ }
10596
+ return out;
10597
+ }
10598
+ var isUpper = (c) => c >= "A" && c <= "Z";
10599
+ var isDigit = (c) => c >= "0" && c <= "9";
10600
+ function toDBName(name) {
10601
+ if (name === "") return "";
10602
+ const value = replaceInitialisms(name);
10603
+ if (value.length === 1) return value.toLowerCase();
10604
+ let buf = "";
10605
+ let lastCase = false;
10606
+ let curCase = isUpper(value[0]);
10607
+ for (let i = 0; i < value.length - 1; i++) {
10608
+ const v = value[i];
10609
+ const nextCase = isUpper(value[i + 1]);
10610
+ const nextNumber = isDigit(value[i + 1]);
10611
+ if (curCase) {
10612
+ if (lastCase && (nextCase || nextNumber)) {
10613
+ buf += v.toLowerCase();
10614
+ } else {
10615
+ if (i > 0 && value[i - 1] !== "_" && lastCase !== curCase) buf += "_";
10616
+ buf += v.toLowerCase();
10617
+ }
10618
+ } else {
10619
+ buf += v;
10620
+ }
10621
+ lastCase = curCase;
10622
+ curCase = nextCase;
10623
+ }
10624
+ const last = value[value.length - 1];
10625
+ if (curCase) {
10626
+ if (!lastCase && value.length > 1) buf += "_";
10627
+ buf += last.toLowerCase();
10628
+ } else {
10629
+ buf += last;
10630
+ }
10631
+ return buf;
10632
+ }
10633
+ var UNCOUNTABLE = /* @__PURE__ */ new Set([
10634
+ "equipment",
10635
+ "information",
10636
+ "rice",
10637
+ "money",
10638
+ "species",
10639
+ "series",
10640
+ "fish",
10641
+ "sheep",
10642
+ "jeans",
10643
+ "police"
10644
+ ]);
10645
+ var IRREGULAR = [
10646
+ ["person", "people"],
10647
+ ["man", "men"],
10648
+ ["child", "children"],
10649
+ ["sex", "sexes"],
10650
+ ["move", "moves"]
10651
+ ];
10652
+ var PLURAL_RULES = [
10653
+ [/(quiz)$/i, "$1zes"],
10654
+ [/^(ox)$/i, "$1en"],
10655
+ [/([ml])ouse$/i, "$1ice"],
10656
+ [/(matr|vert|ind)(?:ix|ex)$/i, "$1ices"],
10657
+ [/(x|ch|ss|sh)$/i, "$1es"],
10658
+ [/([^aeiouy]|qu)y$/i, "$1ies"],
10659
+ [/(hive)$/i, "$1s"],
10660
+ [/(?:([^f])fe|([lr])f)$/i, "$1$2ves"],
10661
+ [/sis$/i, "ses"],
10662
+ [/([ti])um$/i, "$1a"],
10663
+ [/([ti])a$/i, "$1a"],
10664
+ [/(buffal|tomat)o$/i, "$1oes"],
10665
+ [/(bu)s$/i, "$1ses"],
10666
+ [/(alias|status)$/i, "$1es"],
10667
+ [/(octop|vir)i$/i, "$1i"],
10668
+ [/(octop|vir)us$/i, "$1i"],
10669
+ [/(ax|test)is$/i, "$1es"],
10670
+ [/s$/i, "s"]
10671
+ ];
10672
+ function pluralize3(word) {
10673
+ if (word === "") return word;
10674
+ const lower = word.toLowerCase();
10675
+ for (const u of UNCOUNTABLE) {
10676
+ if (lower === u || lower.endsWith("_" + u)) return word;
10677
+ }
10678
+ for (const [sing, plur] of IRREGULAR) {
10679
+ const re = new RegExp(sing + "$", "i");
10680
+ if (re.test(word)) return word.replace(re, plur);
10681
+ }
10682
+ for (const [re, rep] of PLURAL_RULES) {
10683
+ if (re.test(word)) return word.replace(re, rep);
10684
+ }
10685
+ return word + "s";
10686
+ }
10687
+ function deriveTableName(structName) {
10688
+ return pluralize3(toDBName(structName));
10689
+ }
10690
+ function stringLiteralValue(node) {
10691
+ if (!node) return null;
10692
+ if (node.type === "interpreted_string_literal" || node.type === "raw_string_literal") {
10693
+ const t = node.text;
10694
+ return t.length >= 2 ? t.slice(1, -1) : "";
10695
+ }
10696
+ return null;
10697
+ }
10698
+ function parseGormTag(tagNode) {
10699
+ const tag = {};
10700
+ if (!tagNode) return tag;
10701
+ let inner = tagNode.text;
10702
+ if (inner.length >= 2) inner = inner.slice(1, -1);
10703
+ if (tagNode.type === "interpreted_string_literal") inner = inner.replace(/\\"/g, '"');
10704
+ const m = inner.match(/gorm:"([^"]*)"/);
10705
+ if (!m) return tag;
10706
+ for (const part of m[1].split(";")) {
10707
+ if (part === "") continue;
10708
+ const idx = part.indexOf(":");
10709
+ const key = (idx >= 0 ? part.slice(0, idx) : part).trim().toLowerCase();
10710
+ const value = idx >= 0 ? part.slice(idx + 1).trim() : "";
10711
+ if (key === "-") tag.skip = true;
10712
+ else if (key === "column") tag.column = value;
10713
+ else if (key === "primarykey" || key === "primary_key") tag.primaryKey = true;
10714
+ else if (key === "foreignkey") tag.foreignKey = value;
10715
+ else if (key === "many2many") tag.many2many = value;
10716
+ else if (key === "embedded") tag.embedded = true;
10717
+ else if (key === "embeddedprefix") tag.embeddedPrefix = value;
10718
+ }
10719
+ return tag;
10720
+ }
10721
+ function unwrapType(typeNode) {
10722
+ let isSlice = false;
10723
+ let isPointer = false;
10724
+ let n = typeNode;
10725
+ while (n && (n.type === "slice_type" || n.type === "array_type" || n.type === "pointer_type")) {
10726
+ if (n.type === "slice_type" || n.type === "array_type") isSlice = true;
10727
+ if (n.type === "pointer_type") isPointer = true;
10728
+ n = n.childForFieldName("element") ?? n.namedChild(n.namedChildCount - 1);
10729
+ }
10730
+ if (!n) return { name: null, qualifier: null, isSlice, isPointer, isQualified: false };
10731
+ if (n.type === "type_identifier") {
10732
+ return { name: n.text, qualifier: null, isSlice, isPointer, isQualified: false };
10733
+ }
10734
+ if (n.type === "qualified_type") {
10735
+ const pkg = n.childForFieldName("package")?.text ?? n.namedChild(0)?.text ?? null;
10736
+ const nm = n.childForFieldName("name")?.text ?? n.namedChild(1)?.text ?? null;
10737
+ return { name: nm, qualifier: pkg, isSlice, isPointer, isQualified: true };
10738
+ }
10739
+ return { name: null, qualifier: null, isSlice, isPointer, isQualified: false };
10740
+ }
10741
+ function readField(fieldDecl) {
10742
+ const names = [];
10743
+ let tagNode = null;
10744
+ for (let i = 0; i < fieldDecl.namedChildCount; i++) {
10745
+ const c = fieldDecl.namedChild(i);
10746
+ if (!c) continue;
10747
+ if (c.type === "field_identifier") names.push(c.text);
10748
+ else if (c.type === "raw_string_literal" || c.type === "interpreted_string_literal") tagNode = c;
10749
+ }
10750
+ const typeNode = fieldDecl.childForFieldName("type");
10751
+ const t = unwrapType(typeNode);
10752
+ return {
10753
+ names,
10754
+ typeName: t.name,
10755
+ qualifier: t.qualifier,
10756
+ isSlice: t.isSlice,
10757
+ isPointer: t.isPointer,
10758
+ isQualified: t.isQualified,
10759
+ tag: parseGormTag(tagNode),
10760
+ line: fieldDecl.startPosition.row + 1
10761
+ };
10762
+ }
10763
+ function collectStructs(tree) {
10764
+ const structs = /* @__PURE__ */ new Map();
10765
+ walk8(tree.rootNode, (node) => {
10766
+ if (node.type !== "type_spec") return;
10767
+ const nameNode = node.childForFieldName("name");
10768
+ const typeNode = node.childForFieldName("type");
10769
+ if (!nameNode || typeNode?.type !== "struct_type") return;
10770
+ const list = typeNode.childForFieldName("body") ?? typeNode.namedChild(0);
10771
+ const fields = [];
10772
+ if (list && list.type === "field_declaration_list") {
10773
+ for (let i = 0; i < list.namedChildCount; i++) {
10774
+ const fd = list.namedChild(i);
10775
+ if (fd?.type === "field_declaration") fields.push(readField(fd));
10776
+ }
10777
+ }
10778
+ structs.set(nameNode.text, {
10779
+ name: nameNode.text,
10780
+ fields,
10781
+ line: node.startPosition.row + 1
10782
+ });
10783
+ });
10784
+ return structs;
10785
+ }
10786
+ var GORM_MODEL_METHODS = /* @__PURE__ */ new Set([
10787
+ "AutoMigrate",
10788
+ "Model",
10789
+ "Create",
10790
+ "Find",
10791
+ "First",
10792
+ "Take",
10793
+ "Last",
10794
+ "Save",
10795
+ "Delete",
10796
+ "Where",
10797
+ "FirstOrCreate",
10798
+ "FirstOrInit"
10799
+ ]);
10800
+ function compositeStructName(arg) {
10801
+ let n = arg;
10802
+ if (n.type === "unary_expression") n = n.childForFieldName("operand") ?? n.namedChild(0);
10803
+ if (!n || n.type !== "composite_literal") return null;
10804
+ const typeNode = n.childForFieldName("type");
10805
+ if (!typeNode) return null;
10806
+ if (typeNode.type === "type_identifier") return typeNode.text;
10807
+ if (typeNode.type === "qualified_type") {
10808
+ return typeNode.childForFieldName("name")?.text ?? typeNode.namedChild(1)?.text ?? null;
10809
+ }
10810
+ return null;
10811
+ }
10812
+ function collectCallModels(tree) {
10813
+ const models = /* @__PURE__ */ new Set();
10814
+ walk8(tree.rootNode, (node) => {
10815
+ if (node.type !== "call_expression") return;
10816
+ const fn = node.childForFieldName("function");
10817
+ if (fn?.type !== "selector_expression") return;
10818
+ const method = fn.childForFieldName("field")?.text;
10819
+ if (!method || !GORM_MODEL_METHODS.has(method)) return;
10820
+ const args = node.childForFieldName("arguments");
10821
+ if (!args) return;
10822
+ for (let i = 0; i < args.namedChildCount; i++) {
10823
+ const arg = args.namedChild(i);
10824
+ if (!arg) continue;
10825
+ const name = compositeStructName(arg);
10826
+ if (name) models.add(name);
10827
+ }
10828
+ });
10829
+ return models;
10830
+ }
10831
+ function collectTableNameOverrides(tree) {
10832
+ const overrides = /* @__PURE__ */ new Map();
10833
+ const declarers = /* @__PURE__ */ new Set();
10834
+ walk8(tree.rootNode, (node) => {
10835
+ if (node.type !== "method_declaration") return;
10836
+ if (node.childForFieldName("name")?.text !== "TableName") return;
10837
+ const receiver = node.childForFieldName("receiver");
10838
+ if (!receiver) return;
10839
+ let recvType = null;
10840
+ for (let i = 0; i < receiver.namedChildCount; i++) {
10841
+ const pd = receiver.namedChild(i);
10842
+ if (pd?.type !== "parameter_declaration") continue;
10843
+ const t = unwrapType(pd.childForFieldName("type"));
10844
+ recvType = t.name;
10845
+ }
10846
+ if (!recvType) return;
10847
+ declarers.add(recvType);
10848
+ const body = node.childForFieldName("body");
10849
+ if (!body) return;
10850
+ let literal = null;
10851
+ walk8(body, (n) => {
10852
+ if (literal !== null) return;
10853
+ if (n.type !== "return_statement") return;
10854
+ const exprList = n.namedChild(0);
10855
+ const first = exprList?.namedChild(0) ?? exprList;
10856
+ const v = stringLiteralValue(first);
10857
+ if (v) literal = v;
10858
+ });
10859
+ if (literal !== null) overrides.set(recvType, literal);
10860
+ });
10861
+ return { overrides, declarers };
10862
+ }
10863
+ function isRelationField(field, structs) {
10864
+ if (field.names.length === 0) return false;
10865
+ if (field.isQualified) return false;
10866
+ if (!field.typeName) return false;
10867
+ return structs.has(field.typeName);
10868
+ }
10869
+ function isGormModelEmbed(field) {
10870
+ return field.names.length === 0 && field.qualifier === "gorm" && field.typeName === "Model";
10871
+ }
10872
+ function analyze(tree) {
10873
+ const structs = collectStructs(tree);
10874
+ const { overrides, declarers } = collectTableNameOverrides(tree);
10875
+ const callModels = collectCallModels(tree);
10876
+ const models = /* @__PURE__ */ new Set();
10877
+ for (const [name, info] of structs) {
10878
+ if (info.fields.some(isGormModelEmbed)) models.add(name);
10879
+ }
10880
+ for (const name of callModels) if (structs.has(name)) models.add(name);
10881
+ for (const name of declarers) if (structs.has(name)) models.add(name);
10882
+ let grew = true;
10883
+ while (grew) {
10884
+ grew = false;
10885
+ for (const name of Array.from(models)) {
10886
+ const info = structs.get(name);
10887
+ if (!info) continue;
10888
+ for (const field of info.fields) {
10889
+ if (!isRelationField(field, structs)) continue;
10890
+ const target = field.typeName;
10891
+ if (!models.has(target) && structs.has(target)) {
10892
+ models.add(target);
10893
+ grew = true;
10894
+ }
10895
+ }
10896
+ }
10897
+ }
10898
+ const tableFor = (structName) => overrides.get(structName) ?? deriveTableName(structName);
10899
+ return { structs, models, tableFor };
10900
+ }
10901
+ function collectColumns(struct, structs, seen, prefix, out, emitted) {
10902
+ if (seen.has(struct.name)) return;
10903
+ seen.add(struct.name);
10904
+ const add = (col) => {
10905
+ const full = prefix + col;
10906
+ if (!emitted.has(full)) {
10907
+ emitted.add(full);
10908
+ out.push(full);
10909
+ }
10910
+ };
10911
+ for (const field of struct.fields) {
10912
+ if (field.tag.skip) continue;
10913
+ if (field.names.length === 0) {
10914
+ if (isGormModelEmbed(field)) {
10915
+ add("id");
10916
+ add("created_at");
10917
+ add("updated_at");
10918
+ add("deleted_at");
10919
+ } else if (!field.isQualified && field.typeName && structs.has(field.typeName)) {
10920
+ collectColumns(structs.get(field.typeName), structs, seen, prefix, out, emitted);
10921
+ }
10922
+ continue;
10923
+ }
10924
+ if (field.tag.embedded && !field.isQualified && field.typeName && structs.has(field.typeName)) {
10925
+ collectColumns(
10926
+ structs.get(field.typeName),
10927
+ structs,
10928
+ seen,
10929
+ prefix + (field.tag.embeddedPrefix ?? ""),
10930
+ out,
10931
+ emitted
10932
+ );
10933
+ continue;
10934
+ }
10935
+ if (isRelationField(field, structs)) continue;
10936
+ if (field.names.length === 1 && field.tag.column) {
10937
+ add(field.tag.column);
10938
+ } else {
10939
+ for (const n of field.names) add(toDBName(n));
10940
+ }
10941
+ }
10942
+ seen.delete(struct.name);
10943
+ }
10944
+ function gormEndpointsFromFile(file, serviceDir) {
10945
+ if (path46.extname(file.path) !== ".go") return [];
10946
+ if (!GORM_IMPORT_RE.test(file.content)) return [];
10947
+ const tree = parseSource10(makeGoParser3(), file.content);
10948
+ const { structs, models, tableFor } = analyze(tree);
10949
+ const out = [];
10950
+ const seenTables = /* @__PURE__ */ new Set();
10951
+ for (const name of models) {
10952
+ const struct = structs.get(name);
10953
+ if (!struct) continue;
10954
+ const table = tableFor(name);
10955
+ if (seenTables.has(table)) continue;
10956
+ seenTables.add(table);
10957
+ const columns = [];
10958
+ collectColumns(struct, structs, /* @__PURE__ */ new Set(), "", columns, /* @__PURE__ */ new Set());
10959
+ out.push({
10960
+ infraId: infraId16("sql-table", table),
10961
+ name: table,
10962
+ kind: "sql-table",
10963
+ edgeType: "CALLS",
10964
+ confidenceKind: "structural",
10965
+ ...columns.length > 0 ? { columns } : {},
10966
+ evidence: {
10967
+ file: toPosix(path46.relative(serviceDir, file.path)),
10968
+ line: struct.line,
10969
+ snippet: snippet(file.content, struct.line)
10970
+ }
10971
+ });
10972
+ }
10973
+ return out;
10974
+ }
10975
+ function gormForeignKeys(file, serviceDir) {
10976
+ if (path46.extname(file.path) !== ".go") return [];
10977
+ if (!GORM_IMPORT_RE.test(file.content)) return [];
10978
+ const tree = parseSource10(makeGoParser3(), file.content);
10979
+ const { structs, models, tableFor } = analyze(tree);
10980
+ const out = [];
10981
+ const seen = /* @__PURE__ */ new Set();
10982
+ const emit = (childTable, parentTable, line) => {
10983
+ if (!childTable || !parentTable || childTable === parentTable) return;
10984
+ const key = `${childTable}->${parentTable}`;
10985
+ if (seen.has(key)) return;
10986
+ seen.add(key);
10987
+ out.push({
10988
+ childTable,
10989
+ parentTable,
10990
+ evidence: {
10991
+ file: toPosix(path46.relative(serviceDir, file.path)),
10992
+ line,
10993
+ snippet: snippet(file.content, line)
10994
+ }
10995
+ });
10996
+ };
10997
+ for (const name of models) {
10998
+ const struct = structs.get(name);
10999
+ if (!struct) continue;
11000
+ const thisTable = tableFor(name);
11001
+ const scalarNames = new Set(
11002
+ struct.fields.filter((f) => f.names.length > 0 && !isRelationField(f, structs)).flatMap((f) => f.names)
11003
+ );
11004
+ for (const field of struct.fields) {
11005
+ if (field.tag.skip) continue;
11006
+ if (!isRelationField(field, structs)) continue;
11007
+ const relTable = tableFor(field.typeName);
11008
+ if (field.tag.many2many) {
11009
+ emit(field.tag.many2many, thisTable, field.line);
11010
+ emit(field.tag.many2many, relTable, field.line);
11011
+ continue;
11012
+ }
11013
+ if (field.isSlice) {
11014
+ emit(relTable, thisTable, field.line);
11015
+ continue;
11016
+ }
11017
+ const convFk = field.names[0] + "ID";
11018
+ const belongsTo = scalarNames.has(convFk) || (field.tag.foreignKey ? scalarNames.has(field.tag.foreignKey) : false);
11019
+ if (belongsTo) emit(thisTable, relTable, field.line);
11020
+ else emit(relTable, thisTable, field.line);
11021
+ }
11022
+ }
11023
+ return out;
11024
+ }
11025
+
10382
11026
  // src/extract/calls/index.ts
10383
11027
  function edgeTypeFromEndpoint(ep) {
10384
11028
  switch (ep.edgeType) {
@@ -10420,6 +11064,11 @@ async function addExternalEndpointEdges(graph, services) {
10420
11064
  } catch (err) {
10421
11065
  recordExtractionError("go SQL call extraction", file.path, err);
10422
11066
  }
11067
+ try {
11068
+ endpoints.push(...gormEndpointsFromFile(file, service.dir));
11069
+ } catch (err) {
11070
+ recordExtractionError("gorm data-axis extraction", file.path, err);
11071
+ }
10423
11072
  try {
10424
11073
  endpoints.push(...railsSchemaEndpointsFromFile(file, service.dir));
10425
11074
  endpoints.push(...railsModelEndpointsFromFile(file, service.dir));
@@ -10535,7 +11184,7 @@ import {
10535
11184
  Provenance as Provenance16,
10536
11185
  confidenceForExtracted as confidenceForExtracted13,
10537
11186
  extractedEdgeId as extractedEdgeId10,
10538
- infraId as infraId16
11187
+ infraId as infraId17
10539
11188
  } from "@neat.is/types";
10540
11189
  async function addTableEdges(graph, services) {
10541
11190
  let nodesAdded = 0;
@@ -10550,6 +11199,7 @@ async function addTableEdges(graph, services) {
10550
11199
  refs.push(...sqlalchemyForeignKeys(file, service.dir));
10551
11200
  refs.push(...railsSchemaForeignKeys(file, service.dir));
10552
11201
  refs.push(...laravelMigrationForeignKeys(file, service.dir));
11202
+ refs.push(...gormForeignKeys(file, service.dir));
10553
11203
  modelRefs.push(...railsModelForeignKeys(file, service.dir));
10554
11204
  modelRefs.push(...laravelModelForeignKeys(file, service.dir));
10555
11205
  } catch (err) {
@@ -10563,8 +11213,8 @@ async function addTableEdges(graph, services) {
10563
11213
  }
10564
11214
  refs.push(...modelRefs);
10565
11215
  for (const ref of refs) {
10566
- const childId = infraId16("sql-table", ref.childTable);
10567
- const parentId = infraId16("sql-table", ref.parentTable);
11216
+ const childId = infraId17("sql-table", ref.childTable);
11217
+ const parentId = infraId17("sql-table", ref.parentTable);
10568
11218
  if (childId === parentId) continue;
10569
11219
  nodesAdded += ensureTableNode(graph, childId, ref.childTable);
10570
11220
  nodesAdded += ensureTableNode(graph, parentId, ref.parentTable);
@@ -10599,14 +11249,14 @@ function ensureTableNode(graph, id, name) {
10599
11249
  }
10600
11250
 
10601
11251
  // src/extract/infra/docker-compose.ts
10602
- import path46 from "path";
11252
+ import path47 from "path";
10603
11253
  import { EdgeType as EdgeType17, Provenance as Provenance18, confidenceForExtracted as confidenceForExtracted15 } from "@neat.is/types";
10604
11254
 
10605
11255
  // src/extract/infra/shared.ts
10606
- import { NodeType as NodeType20, Provenance as Provenance17, confidenceForExtracted as confidenceForExtracted14, infraId as infraId17 } from "@neat.is/types";
11256
+ import { NodeType as NodeType20, Provenance as Provenance17, confidenceForExtracted as confidenceForExtracted14, infraId as infraId18 } from "@neat.is/types";
10607
11257
  function makeInfraNode(kind, name, provider = "self", extras) {
10608
11258
  return {
10609
- id: infraId17(kind, name),
11259
+ id: infraId18(kind, name),
10610
11260
  type: NodeType20.InfraNode,
10611
11261
  name,
10612
11262
  provider,
@@ -10669,7 +11319,7 @@ function dependsOnList(value) {
10669
11319
  }
10670
11320
  function serviceNameToServiceNode(name, services) {
10671
11321
  for (const s of services) {
10672
- if (s.node.name === name || path46.basename(s.dir) === name) return s.node.id;
11322
+ if (s.node.name === name || path47.basename(s.dir) === name) return s.node.id;
10673
11323
  }
10674
11324
  return null;
10675
11325
  }
@@ -10678,7 +11328,7 @@ async function addComposeInfra(graph, scanPath, services) {
10678
11328
  let edgesAdded = 0;
10679
11329
  let composePath = null;
10680
11330
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
10681
- const abs = path46.join(scanPath, name);
11331
+ const abs = path47.join(scanPath, name);
10682
11332
  if (await exists(abs)) {
10683
11333
  composePath = abs;
10684
11334
  break;
@@ -10691,13 +11341,13 @@ async function addComposeInfra(graph, scanPath, services) {
10691
11341
  } catch (err) {
10692
11342
  recordExtractionError(
10693
11343
  "infra docker-compose",
10694
- path46.relative(scanPath, composePath),
11344
+ path47.relative(scanPath, composePath),
10695
11345
  err
10696
11346
  );
10697
11347
  return { nodesAdded, edgesAdded };
10698
11348
  }
10699
11349
  if (!compose?.services) return { nodesAdded, edgesAdded };
10700
- const evidenceFile = path46.relative(scanPath, composePath).split(path46.sep).join("/");
11350
+ const evidenceFile = path47.relative(scanPath, composePath).split(path47.sep).join("/");
10701
11351
  const composeNameToNodeId = /* @__PURE__ */ new Map();
10702
11352
  for (const [composeName, svc] of Object.entries(compose.services)) {
10703
11353
  const matchedServiceId = serviceNameToServiceNode(composeName, services);
@@ -10738,7 +11388,7 @@ async function addComposeInfra(graph, scanPath, services) {
10738
11388
  }
10739
11389
 
10740
11390
  // src/extract/infra/dockerfile.ts
10741
- import path47 from "path";
11391
+ import path48 from "path";
10742
11392
  import { promises as fs18 } from "fs";
10743
11393
  import { EdgeType as EdgeType18, Provenance as Provenance19, confidenceForExtracted as confidenceForExtracted16 } from "@neat.is/types";
10744
11394
  function readDockerfile(content) {
@@ -10769,7 +11419,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
10769
11419
  let nodesAdded = 0;
10770
11420
  let edgesAdded = 0;
10771
11421
  for (const service of services) {
10772
- const dockerfilePath = path47.join(service.dir, "Dockerfile");
11422
+ const dockerfilePath = path48.join(service.dir, "Dockerfile");
10773
11423
  if (!await exists(dockerfilePath)) continue;
10774
11424
  let content;
10775
11425
  try {
@@ -10777,7 +11427,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
10777
11427
  } catch (err) {
10778
11428
  recordExtractionError(
10779
11429
  "infra dockerfile",
10780
- path47.relative(scanPath, dockerfilePath),
11430
+ path48.relative(scanPath, dockerfilePath),
10781
11431
  err
10782
11432
  );
10783
11433
  continue;
@@ -10789,8 +11439,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
10789
11439
  graph.addNode(node.id, node);
10790
11440
  nodesAdded++;
10791
11441
  }
10792
- const relDockerfile = toPosix(path47.relative(service.dir, dockerfilePath));
10793
- const evidenceFile = toPosix(path47.relative(scanPath, dockerfilePath));
11442
+ const relDockerfile = toPosix(path48.relative(service.dir, dockerfilePath));
11443
+ const evidenceFile = toPosix(path48.relative(scanPath, dockerfilePath));
10794
11444
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
10795
11445
  graph,
10796
11446
  service.pkg.name,
@@ -10842,7 +11492,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
10842
11492
 
10843
11493
  // src/extract/infra/terraform.ts
10844
11494
  import { promises as fs19 } from "fs";
10845
- import path48 from "path";
11495
+ import path49 from "path";
10846
11496
  import { EdgeType as EdgeType19, Provenance as Provenance20, confidenceForExtracted as confidenceForExtracted17 } from "@neat.is/types";
10847
11497
  var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
10848
11498
  var REFERENCE_RE = /(?<![\w.])(aws_[A-Za-z0-9_]+)\.([A-Za-z0-9_-]+)/g;
@@ -10853,11 +11503,11 @@ async function walkTfFiles(start, depth = 0, max = 5) {
10853
11503
  for (const entry of entries) {
10854
11504
  if (entry.isDirectory()) {
10855
11505
  if (IGNORED_DIRS.has(entry.name) || entry.name === ".terraform") continue;
10856
- const child = path48.join(start, entry.name);
11506
+ const child = path49.join(start, entry.name);
10857
11507
  if (await isPythonVenvDir(child)) continue;
10858
11508
  out.push(...await walkTfFiles(child, depth + 1, max));
10859
11509
  } else if (entry.isFile() && entry.name.endsWith(".tf")) {
10860
- out.push(path48.join(start, entry.name));
11510
+ out.push(path49.join(start, entry.name));
10861
11511
  }
10862
11512
  }
10863
11513
  return out;
@@ -10889,7 +11539,7 @@ async function addTerraformResources(graph, scanPath) {
10889
11539
  const files = await walkTfFiles(scanPath);
10890
11540
  for (const file of files) {
10891
11541
  const content = await fs19.readFile(file, "utf8");
10892
- const evidenceFile = toPosix(path48.relative(scanPath, file));
11542
+ const evidenceFile = toPosix(path49.relative(scanPath, file));
10893
11543
  const resources = [];
10894
11544
  const byKey = /* @__PURE__ */ new Map();
10895
11545
  RESOURCE_RE.lastIndex = 0;
@@ -10946,7 +11596,7 @@ async function addTerraformResources(graph, scanPath) {
10946
11596
 
10947
11597
  // src/extract/infra/k8s.ts
10948
11598
  import { promises as fs20 } from "fs";
10949
- import path49 from "path";
11599
+ import path50 from "path";
10950
11600
  import { parseAllDocuments as parseAllDocuments2 } from "yaml";
10951
11601
  var K8S_KIND_TO_INFRA_KIND = {
10952
11602
  Service: "k8s-service",
@@ -10964,11 +11614,11 @@ async function walkYamlFiles2(start, depth = 0, max = 5) {
10964
11614
  for (const entry of entries) {
10965
11615
  if (entry.isDirectory()) {
10966
11616
  if (IGNORED_DIRS.has(entry.name)) continue;
10967
- const child = path49.join(start, entry.name);
11617
+ const child = path50.join(start, entry.name);
10968
11618
  if (await isPythonVenvDir(child)) continue;
10969
11619
  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));
11620
+ } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(path50.extname(entry.name))) {
11621
+ out.push(path50.join(start, entry.name));
10972
11622
  }
10973
11623
  }
10974
11624
  return out;
@@ -11001,13 +11651,13 @@ async function addK8sResources(graph, scanPath) {
11001
11651
 
11002
11652
  // src/extract/infra/cloudflare.ts
11003
11653
  import { promises as fs21 } from "fs";
11004
- import path50 from "path";
11654
+ import path51 from "path";
11005
11655
  import { parse as parseToml2 } from "smol-toml";
11006
11656
  import { EdgeType as EdgeType20, Provenance as Provenance21, confidenceForExtracted as confidenceForExtracted18 } from "@neat.is/types";
11007
11657
  var WRANGLER_FILENAMES = ["wrangler.toml", "wrangler.jsonc", "wrangler.json"];
11008
11658
  async function readWranglerConfig(dir) {
11009
11659
  for (const filename of WRANGLER_FILENAMES) {
11010
- const abs = path50.join(dir, filename);
11660
+ const abs = path51.join(dir, filename);
11011
11661
  if (!await exists(abs)) continue;
11012
11662
  const raw = await fs21.readFile(abs, "utf8");
11013
11663
  const config = filename === "wrangler.toml" ? parseToml2(raw) : JSON.parse(maskCommentsInSource(raw));
@@ -11070,11 +11720,11 @@ async function addCloudflareWorkers(graph, services, scanPath) {
11070
11720
  try {
11071
11721
  read = await readWranglerConfig(service.dir);
11072
11722
  } catch (err) {
11073
- recordExtractionError("infra cloudflare", path50.relative(scanPath, service.dir), err);
11723
+ recordExtractionError("infra cloudflare", path51.relative(scanPath, service.dir), err);
11074
11724
  continue;
11075
11725
  }
11076
11726
  if (!read || !read.config.name) continue;
11077
- const evidenceFile = toPosix(path50.relative(scanPath, path50.join(service.dir, read.relFile)));
11727
+ const evidenceFile = toPosix(path51.relative(scanPath, path51.join(service.dir, read.relFile)));
11078
11728
  discovered.push({ service, config: read.config, relFile: read.relFile, raw: read.raw, evidenceFile });
11079
11729
  }
11080
11730
  for (const worker of discovered) {
@@ -11086,7 +11736,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
11086
11736
  }
11087
11737
  let anchorId = service.node.id;
11088
11738
  if (config.main) {
11089
- const entryRelPath = toPosix(path50.normalize(config.main));
11739
+ const entryRelPath = toPosix(path51.normalize(config.main));
11090
11740
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
11091
11741
  graph,
11092
11742
  service.pkg.name,
@@ -11233,12 +11883,12 @@ async function addCloudflareWorkers(graph, services, scanPath) {
11233
11883
 
11234
11884
  // src/extract/infra/vercel.ts
11235
11885
  import { promises as fs22 } from "fs";
11236
- import path51 from "path";
11886
+ import path52 from "path";
11237
11887
  import { EdgeType as EdgeType21 } from "@neat.is/types";
11238
11888
  var VERCEL_CONFIG_FILENAMES = ["vercel.json", "vercel.jsonc"];
11239
11889
  async function readVercelConfig(dir) {
11240
11890
  for (const filename of VERCEL_CONFIG_FILENAMES) {
11241
- const abs = path51.join(dir, filename);
11891
+ const abs = path52.join(dir, filename);
11242
11892
  if (!await exists(abs)) continue;
11243
11893
  const raw = await fs22.readFile(abs, "utf8");
11244
11894
  const config = JSON.parse(maskCommentsInSource(raw));
@@ -11247,7 +11897,7 @@ async function readVercelConfig(dir) {
11247
11897
  return null;
11248
11898
  }
11249
11899
  async function readLinkedProjectName(dir) {
11250
- const abs = path51.join(dir, ".vercel", "project.json");
11900
+ const abs = path52.join(dir, ".vercel", "project.json");
11251
11901
  if (!await exists(abs)) return void 0;
11252
11902
  const parsed = JSON.parse(await fs22.readFile(abs, "utf8"));
11253
11903
  return typeof parsed.projectName === "string" ? parsed.projectName : void 0;
@@ -11265,7 +11915,7 @@ async function addVercelServices(graph, services, scanPath) {
11265
11915
  read = await readVercelConfig(service.dir);
11266
11916
  projectName = await readLinkedProjectName(service.dir);
11267
11917
  } catch (err) {
11268
- recordExtractionError("infra vercel", path51.relative(scanPath, service.dir), err);
11918
+ recordExtractionError("infra vercel", path52.relative(scanPath, service.dir), err);
11269
11919
  continue;
11270
11920
  }
11271
11921
  if (!read && !projectName) continue;
@@ -11281,7 +11931,7 @@ async function addVercelServices(graph, services, scanPath) {
11281
11931
  const anchorId = service.node.id;
11282
11932
  if (!read) continue;
11283
11933
  const { config, relFile, raw } = read;
11284
- const evidenceFile = toPosix(path51.relative(scanPath, path51.join(service.dir, relFile)));
11934
+ const evidenceFile = toPosix(path52.relative(scanPath, path52.join(service.dir, relFile)));
11285
11935
  const add = (edgeType, kind, name) => {
11286
11936
  if (!name) return;
11287
11937
  const result = emitPlatformResourceEdge(
@@ -11310,13 +11960,13 @@ async function addVercelServices(graph, services, scanPath) {
11310
11960
 
11311
11961
  // src/extract/infra/railway.ts
11312
11962
  import { promises as fs23 } from "fs";
11313
- import path52 from "path";
11963
+ import path53 from "path";
11314
11964
  import { parse as parseToml3 } from "smol-toml";
11315
11965
  import { EdgeType as EdgeType22 } from "@neat.is/types";
11316
11966
  var RAILWAY_FILENAMES = ["railway.toml", "railway.json", "railway.jsonc"];
11317
11967
  async function readRailwayConfig(dir) {
11318
11968
  for (const filename of RAILWAY_FILENAMES) {
11319
- const abs = path52.join(dir, filename);
11969
+ const abs = path53.join(dir, filename);
11320
11970
  if (!await exists(abs)) continue;
11321
11971
  const raw = await fs23.readFile(abs, "utf8");
11322
11972
  const config = filename === "railway.toml" ? parseToml3(raw) : JSON.parse(maskCommentsInSource(raw));
@@ -11332,7 +11982,7 @@ async function addRailwayServices(graph, services, scanPath) {
11332
11982
  try {
11333
11983
  read = await readRailwayConfig(service.dir);
11334
11984
  } catch (err) {
11335
- recordExtractionError("infra railway", path52.relative(scanPath, service.dir), err);
11985
+ recordExtractionError("infra railway", path53.relative(scanPath, service.dir), err);
11336
11986
  continue;
11337
11987
  }
11338
11988
  if (!read) continue;
@@ -11342,7 +11992,7 @@ async function addRailwayServices(graph, services, scanPath) {
11342
11992
  }
11343
11993
  const anchorId = service.node.id;
11344
11994
  const { config, relFile, raw } = read;
11345
- const evidenceFile = toPosix(path52.relative(scanPath, path52.join(service.dir, relFile)));
11995
+ const evidenceFile = toPosix(path53.relative(scanPath, path53.join(service.dir, relFile)));
11346
11996
  const add = (edgeType, kind, name) => {
11347
11997
  if (!name) return;
11348
11998
  const result = emitPlatformResourceEdge(
@@ -11367,12 +12017,12 @@ async function addRailwayServices(graph, services, scanPath) {
11367
12017
 
11368
12018
  // src/extract/infra/supabase.ts
11369
12019
  import { promises as fs24 } from "fs";
11370
- import path53 from "path";
12020
+ import path54 from "path";
11371
12021
  import { parse as parseToml4 } from "smol-toml";
11372
12022
  import { EdgeType as EdgeType23 } from "@neat.is/types";
11373
12023
  async function readSupabaseConfig(dir) {
11374
- const relFile = path53.join("supabase", "config.toml");
11375
- const abs = path53.join(dir, relFile);
12024
+ const relFile = path54.join("supabase", "config.toml");
12025
+ const abs = path54.join(dir, relFile);
11376
12026
  if (!await exists(abs)) return null;
11377
12027
  const raw = await fs24.readFile(abs, "utf8");
11378
12028
  const config = parseToml4(raw);
@@ -11386,7 +12036,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
11386
12036
  try {
11387
12037
  read = await readSupabaseConfig(service.dir);
11388
12038
  } catch (err) {
11389
- recordExtractionError("infra supabase", path53.relative(scanPath, service.dir), err);
12039
+ recordExtractionError("infra supabase", path54.relative(scanPath, service.dir), err);
11390
12040
  continue;
11391
12041
  }
11392
12042
  if (!read) continue;
@@ -11401,7 +12051,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
11401
12051
  });
11402
12052
  }
11403
12053
  const anchorId = service.node.id;
11404
- const evidenceFile = toPosix(path53.relative(scanPath, path53.join(service.dir, relFile)));
12054
+ const evidenceFile = toPosix(path54.relative(scanPath, path54.join(service.dir, relFile)));
11405
12055
  const add = (edgeType, kind, name) => {
11406
12056
  if (!name) return;
11407
12057
  const result = emitPlatformResourceEdge(
@@ -11442,8 +12092,8 @@ async function addInfra(graph, scanPath, services) {
11442
12092
  }
11443
12093
 
11444
12094
  // src/extract/zod-shapes.ts
11445
- import path54 from "path";
11446
- import Parser15 from "tree-sitter";
12095
+ import path55 from "path";
12096
+ import Parser16 from "tree-sitter";
11447
12097
  import JavaScript8 from "tree-sitter-javascript";
11448
12098
  import {
11449
12099
  EdgeType as EdgeType24,
@@ -11451,12 +12101,12 @@ import {
11451
12101
  Provenance as Provenance22,
11452
12102
  confidenceForExtracted as confidenceForExtracted19,
11453
12103
  extractedEdgeId as extractedEdgeId11,
11454
- infraId as infraId18
12104
+ infraId as infraId19
11455
12105
  } from "@neat.is/types";
11456
12106
  var ZOD_IMPORT_RE = /\bzod\b/;
11457
12107
  var ZOD_OBJECTS = /* @__PURE__ */ new Set(["z", "zod"]);
11458
12108
  function parserForExt3(ext) {
11459
- const p = new Parser15();
12109
+ const p = new Parser16();
11460
12110
  p.setLanguage(GRAMMAR_BY_EXT[ext] ?? JavaScript8);
11461
12111
  return p;
11462
12112
  }
@@ -11544,7 +12194,7 @@ function topLevelSchemas(root) {
11544
12194
  }
11545
12195
  function zodShapesFromFile(file, serviceDir) {
11546
12196
  if (!ZOD_IMPORT_RE.test(file.content)) return [];
11547
- const tree = parseSource3(parserForExt3(path54.extname(file.path)), file.content);
12197
+ const tree = parseSource3(parserForExt3(path55.extname(file.path)), file.content);
11548
12198
  const out = [];
11549
12199
  const seen = /* @__PURE__ */ new Set();
11550
12200
  for (const { name, call } of topLevelSchemas(tree.rootNode)) {
@@ -11558,11 +12208,11 @@ function zodShapesFromFile(file, serviceDir) {
11558
12208
  seen.add(name);
11559
12209
  const line = call.startPosition.row + 1;
11560
12210
  out.push({
11561
- infraId: infraId18("zod-schema", name),
12211
+ infraId: infraId19("zod-schema", name),
11562
12212
  name,
11563
12213
  fields,
11564
12214
  evidence: {
11565
- file: path54.relative(serviceDir, file.path),
12215
+ file: path55.relative(serviceDir, file.path),
11566
12216
  line,
11567
12217
  snippet: snippet(file.content, line)
11568
12218
  }
@@ -11798,11 +12448,11 @@ async function addFirestoreRules(graph, services) {
11798
12448
  }
11799
12449
 
11800
12450
  // src/extract/index.ts
11801
- import path56 from "path";
12451
+ import path57 from "path";
11802
12452
 
11803
12453
  // src/extract/retire.ts
11804
12454
  import { existsSync as existsSync2 } from "fs";
11805
- import path55 from "path";
12455
+ import path56 from "path";
11806
12456
  import { NodeType as NodeType23, Provenance as Provenance23 } from "@neat.is/types";
11807
12457
  function dropOrphanedFileNodes(graph) {
11808
12458
  const orphans = [];
@@ -11836,11 +12486,11 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
11836
12486
  if (edge.provenance !== Provenance23.EXTRACTED) return;
11837
12487
  const evidenceFile = edge.evidence?.file;
11838
12488
  if (!evidenceFile) return;
11839
- if (path55.isAbsolute(evidenceFile)) {
12489
+ if (path56.isAbsolute(evidenceFile)) {
11840
12490
  if (!existsSync2(evidenceFile)) toDrop.push(id);
11841
12491
  return;
11842
12492
  }
11843
- const found = bases.some((base) => existsSync2(path55.join(base, evidenceFile)));
12493
+ const found = bases.some((base) => existsSync2(path56.join(base, evidenceFile)));
11844
12494
  if (!found) toDrop.push(id);
11845
12495
  });
11846
12496
  for (const id of toDrop) graph.dropEdge(id);
@@ -11897,7 +12547,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
11897
12547
  }
11898
12548
  const droppedEntries = drainDroppedExtracted();
11899
12549
  if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
11900
- const rejectedPath = path56.join(path56.dirname(opts.errorsPath), "rejected.ndjson");
12550
+ const rejectedPath = path57.join(path57.dirname(opts.errorsPath), "rejected.ndjson");
11901
12551
  try {
11902
12552
  await writeRejectedExtracted(droppedEntries, rejectedPath);
11903
12553
  } catch (err) {
@@ -12272,7 +12922,7 @@ function computeDivergences(graph, opts = {}) {
12272
12922
 
12273
12923
  // src/persist.ts
12274
12924
  import { promises as fs25 } from "fs";
12275
- import path57 from "path";
12925
+ import path58 from "path";
12276
12926
  import { NodeType as NodeType25, Provenance as Provenance25, observedEdgeId as observedEdgeId2 } from "@neat.is/types";
12277
12927
  var SCHEMA_VERSION = 7;
12278
12928
  function migrateV1ToV2(payload) {
@@ -12327,7 +12977,7 @@ function migrateV2ToV3(payload) {
12327
12977
  return { ...payload, schemaVersion: 3 };
12328
12978
  }
12329
12979
  async function ensureDir(filePath) {
12330
- await fs25.mkdir(path57.dirname(filePath), { recursive: true });
12980
+ await fs25.mkdir(path58.dirname(filePath), { recursive: true });
12331
12981
  }
12332
12982
  async function saveGraphToDisk(graph, outPath) {
12333
12983
  await ensureDir(outPath);
@@ -12492,23 +13142,23 @@ function canonicalJson(value) {
12492
13142
  }
12493
13143
 
12494
13144
  // src/projects.ts
12495
- import path58 from "path";
13145
+ import path59 from "path";
12496
13146
  function pathsForProject(project, baseDir) {
12497
13147
  if (project === DEFAULT_PROJECT) {
12498
13148
  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")
13149
+ snapshotPath: path59.join(baseDir, "graph.json"),
13150
+ errorsPath: path59.join(baseDir, "errors.ndjson"),
13151
+ staleEventsPath: path59.join(baseDir, "stale-events.ndjson"),
13152
+ embeddingsCachePath: path59.join(baseDir, "embeddings.json"),
13153
+ policyViolationsPath: path59.join(baseDir, "policy-violations.ndjson")
12504
13154
  };
12505
13155
  }
12506
13156
  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`)
13157
+ snapshotPath: path59.join(baseDir, `${project}.json`),
13158
+ errorsPath: path59.join(baseDir, `errors.${project}.ndjson`),
13159
+ staleEventsPath: path59.join(baseDir, `stale-events.${project}.ndjson`),
13160
+ embeddingsCachePath: path59.join(baseDir, `embeddings.${project}.json`),
13161
+ policyViolationsPath: path59.join(baseDir, `policy-violations.${project}.ndjson`)
12512
13162
  };
12513
13163
  }
12514
13164
  var Projects = class {
@@ -12549,7 +13199,7 @@ function parseExtraProjects(raw) {
12549
13199
  // src/registry.ts
12550
13200
  import { promises as fs27 } from "fs";
12551
13201
  import os2 from "os";
12552
- import path59 from "path";
13202
+ import path60 from "path";
12553
13203
  import {
12554
13204
  RegistryFileSchema
12555
13205
  } from "@neat.is/types";
@@ -12557,20 +13207,20 @@ var LOCK_TIMEOUT_MS = 5e3;
12557
13207
  var LOCK_RETRY_MS = 50;
12558
13208
  function neatHome() {
12559
13209
  const override = process.env.NEAT_HOME;
12560
- if (override && override.length > 0) return path59.resolve(override);
12561
- return path59.join(os2.homedir(), ".neat");
13210
+ if (override && override.length > 0) return path60.resolve(override);
13211
+ return path60.join(os2.homedir(), ".neat");
12562
13212
  }
12563
13213
  function registryPath() {
12564
- return path59.join(neatHome(), "projects.json");
13214
+ return path60.join(neatHome(), "projects.json");
12565
13215
  }
12566
13216
  function registryLockPath() {
12567
- return path59.join(neatHome(), "projects.json.lock");
13217
+ return path60.join(neatHome(), "projects.json.lock");
12568
13218
  }
12569
13219
  function daemonPidPath() {
12570
- return path59.join(neatHome(), "neatd.pid");
13220
+ return path60.join(neatHome(), "neatd.pid");
12571
13221
  }
12572
13222
  function daemonsDir() {
12573
- return path59.join(neatHome(), "daemons");
13223
+ return path60.join(neatHome(), "daemons");
12574
13224
  }
12575
13225
  function isFiniteInt(v) {
12576
13226
  return typeof v === "number" && Number.isFinite(v);
@@ -12611,7 +13261,7 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
12611
13261
  const out = [];
12612
13262
  for (const name of names) {
12613
13263
  if (!name.endsWith(".json")) continue;
12614
- const file = path59.join(dir, name);
13264
+ const file = path60.join(dir, name);
12615
13265
  let raw;
12616
13266
  try {
12617
13267
  raw = await fs27.readFile(file, "utf8");
@@ -12732,7 +13382,7 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
12732
13382
  }
12733
13383
  }
12734
13384
  async function normalizeProjectPath(input) {
12735
- const resolved = path59.resolve(input);
13385
+ const resolved = path60.resolve(input);
12736
13386
  try {
12737
13387
  return await fs27.realpath(resolved);
12738
13388
  } catch {
@@ -12740,7 +13390,7 @@ async function normalizeProjectPath(input) {
12740
13390
  }
12741
13391
  }
12742
13392
  async function writeAtomically(target, contents) {
12743
- await fs27.mkdir(path59.dirname(target), { recursive: true });
13393
+ await fs27.mkdir(path60.dirname(target), { recursive: true });
12744
13394
  const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
12745
13395
  const fd = await fs27.open(tmp, "w");
12746
13396
  try {
@@ -12753,7 +13403,7 @@ async function writeAtomically(target, contents) {
12753
13403
  }
12754
13404
  async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
12755
13405
  const deadline = Date.now() + timeoutMs;
12756
- await fs27.mkdir(path59.dirname(lockPath), { recursive: true });
13406
+ await fs27.mkdir(path60.dirname(lockPath), { recursive: true });
12757
13407
  let probedHolder = false;
12758
13408
  while (true) {
12759
13409
  try {
@@ -12949,13 +13599,13 @@ import { DivergenceTypeSchema, PoliciesCheckBodySchema, PolicySeveritySchema } f
12949
13599
 
12950
13600
  // src/extend/index.ts
12951
13601
  import { promises as fs29 } from "fs";
12952
- import path61 from "path";
13602
+ import path62 from "path";
12953
13603
  import os3 from "os";
12954
13604
  import { resolve as registryResolve, list as registryList } from "@neat.is/instrumentation-registry";
12955
13605
 
12956
13606
  // src/installers/package-manager.ts
12957
13607
  import { promises as fs28 } from "fs";
12958
- import path60 from "path";
13608
+ import path61 from "path";
12959
13609
  import { spawn } from "child_process";
12960
13610
  var LOCKFILE_PRIORITY = [
12961
13611
  { lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
@@ -12977,22 +13627,22 @@ async function exists2(p) {
12977
13627
  }
12978
13628
  }
12979
13629
  async function detectPackageManager(serviceDir) {
12980
- let dir = path60.resolve(serviceDir);
13630
+ let dir = path61.resolve(serviceDir);
12981
13631
  const stops = /* @__PURE__ */ new Set();
12982
13632
  for (let i = 0; i < 64; i++) {
12983
13633
  if (stops.has(dir)) break;
12984
13634
  stops.add(dir);
12985
13635
  for (const candidate of LOCKFILE_PRIORITY) {
12986
- const lockPath = path60.join(dir, candidate.lockfile);
13636
+ const lockPath = path61.join(dir, candidate.lockfile);
12987
13637
  if (await exists2(lockPath)) {
12988
13638
  return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
12989
13639
  }
12990
13640
  }
12991
- const parent = path60.dirname(dir);
13641
+ const parent = path61.dirname(dir);
12992
13642
  if (parent === dir) break;
12993
13643
  dir = parent;
12994
13644
  }
12995
- return { pm: "npm", cwd: path60.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
13645
+ return { pm: "npm", cwd: path61.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
12996
13646
  }
12997
13647
  async function runPackageManagerInstall(cmd) {
12998
13648
  return new Promise((resolve) => {
@@ -13041,7 +13691,7 @@ async function fileExists2(p) {
13041
13691
  }
13042
13692
  }
13043
13693
  async function readPackageJson(scanPath) {
13044
- const pkgPath = path61.join(scanPath, "package.json");
13694
+ const pkgPath = path62.join(scanPath, "package.json");
13045
13695
  const raw = await fs29.readFile(pkgPath, "utf8");
13046
13696
  return JSON.parse(raw);
13047
13697
  }
@@ -13055,27 +13705,27 @@ var HOOK_WALK_SKIP_DIRS = /* @__PURE__ */ new Set([
13055
13705
  ]);
13056
13706
  async function findHookFiles(scanPath) {
13057
13707
  const found = [];
13058
- const walk8 = async (dir) => {
13708
+ const walk9 = async (dir) => {
13059
13709
  const entries = await fs29.readdir(dir, { withFileTypes: true }).catch(() => []);
13060
13710
  for (const entry of entries) {
13061
13711
  if (entry.isDirectory()) {
13062
13712
  if (entry.name.startsWith(".") || HOOK_WALK_SKIP_DIRS.has(entry.name)) continue;
13063
- await walk8(path61.join(dir, entry.name));
13713
+ await walk9(path62.join(dir, entry.name));
13064
13714
  } else if (entry.isFile()) {
13065
13715
  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("/"));
13716
+ const rel = path62.relative(scanPath, path62.join(dir, entry.name));
13717
+ found.push(rel.split(path62.sep).join("/"));
13068
13718
  }
13069
13719
  }
13070
13720
  }
13071
13721
  };
13072
- await walk8(scanPath);
13722
+ await walk9(scanPath);
13073
13723
  return found.sort();
13074
13724
  }
13075
13725
  async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
13076
13726
  let fallback = null;
13077
13727
  for (const file of hookFiles) {
13078
- const content = await fs29.readFile(path61.join(scanPath, file), "utf8");
13728
+ const content = await fs29.readFile(path62.join(scanPath, file), "utf8");
13079
13729
  const patched = splicedContent(content, snippet2);
13080
13730
  if (patched !== null) return { file, content, patched };
13081
13731
  if (fallback === null) fallback = { file, content };
@@ -13083,11 +13733,11 @@ async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
13083
13733
  return { file: fallback.file, content: fallback.content, patched: null };
13084
13734
  }
13085
13735
  function extendLogPath() {
13086
- return process.env.NEAT_EXTEND_LOG ?? path61.join(os3.homedir(), ".neat", "extend-log.ndjson");
13736
+ return process.env.NEAT_EXTEND_LOG ?? path62.join(os3.homedir(), ".neat", "extend-log.ndjson");
13087
13737
  }
13088
13738
  async function appendExtendLog(entry) {
13089
13739
  const logPath = extendLogPath();
13090
- await fs29.mkdir(path61.dirname(logPath), { recursive: true });
13740
+ await fs29.mkdir(path62.dirname(logPath), { recursive: true });
13091
13741
  await fs29.appendFile(logPath, JSON.stringify(entry) + "\n", "utf8");
13092
13742
  }
13093
13743
  function splicedContent(fileContent, snippet2) {
@@ -13146,7 +13796,7 @@ function lookupInstrumentation(library, installedVersion) {
13146
13796
  }
13147
13797
  async function describeProjectInstrumentation(ctx) {
13148
13798
  const hookFiles = await findHookFiles(ctx.scanPath);
13149
- const envNeat = await fileExists2(path61.join(ctx.scanPath, ".env.neat"));
13799
+ const envNeat = await fileExists2(path62.join(ctx.scanPath, ".env.neat"));
13150
13800
  const registryInstrPackages = new Set(
13151
13801
  registryList().map((e) => e.instrumentation_package).filter((p) => !!p)
13152
13802
  );
@@ -13168,7 +13818,7 @@ async function applyExtension(ctx, args, options) {
13168
13818
  );
13169
13819
  }
13170
13820
  for (const file of hookFiles) {
13171
- const content = await fs29.readFile(path61.join(ctx.scanPath, file), "utf8");
13821
+ const content = await fs29.readFile(path62.join(ctx.scanPath, file), "utf8");
13172
13822
  if (content.includes(args.registration_snippet)) {
13173
13823
  return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
13174
13824
  }
@@ -13180,10 +13830,10 @@ async function applyExtension(ctx, args, options) {
13180
13830
  );
13181
13831
  }
13182
13832
  const primaryFile = primary.file;
13183
- const primaryPath = path61.join(ctx.scanPath, primaryFile);
13833
+ const primaryPath = path62.join(ctx.scanPath, primaryFile);
13184
13834
  const filesTouched = [];
13185
13835
  const depsAdded = [];
13186
- const pkgPath = path61.join(ctx.scanPath, "package.json");
13836
+ const pkgPath = path62.join(ctx.scanPath, "package.json");
13187
13837
  const pkg = await readPackageJson(ctx.scanPath);
13188
13838
  if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
13189
13839
  pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
@@ -13222,7 +13872,7 @@ async function dryRunExtension(ctx, args) {
13222
13872
  };
13223
13873
  }
13224
13874
  for (const file of hookFiles) {
13225
- const content = await fs29.readFile(path61.join(ctx.scanPath, file), "utf8");
13875
+ const content = await fs29.readFile(path62.join(ctx.scanPath, file), "utf8");
13226
13876
  if (content.includes(args.registration_snippet)) {
13227
13877
  return {
13228
13878
  library: args.library,
@@ -13263,7 +13913,7 @@ async function rollbackExtension(ctx, args) {
13263
13913
  if (!match) {
13264
13914
  return { undone: false, message: "no apply found for library" };
13265
13915
  }
13266
- const pkgPath = path61.join(ctx.scanPath, "package.json");
13916
+ const pkgPath = path62.join(ctx.scanPath, "package.json");
13267
13917
  if (await fileExists2(pkgPath)) {
13268
13918
  const pkg = await readPackageJson(ctx.scanPath);
13269
13919
  if (pkg.dependencies?.[match.instrumentation_package]) {
@@ -13274,7 +13924,7 @@ async function rollbackExtension(ctx, args) {
13274
13924
  }
13275
13925
  const hookFiles = await findHookFiles(ctx.scanPath);
13276
13926
  for (const file of hookFiles) {
13277
- const filePath = path61.join(ctx.scanPath, file);
13927
+ const filePath = path62.join(ctx.scanPath, file);
13278
13928
  const content = await fs29.readFile(filePath, "utf8");
13279
13929
  if (content.includes(match.registration_snippet)) {
13280
13930
  const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
@@ -13389,7 +14039,7 @@ data: ${JSON.stringify(envelope.payload)}
13389
14039
 
13390
14040
  // src/connectors-config.ts
13391
14041
  import os4 from "os";
13392
- import path62 from "path";
14042
+ import path63 from "path";
13393
14043
  import { promises as fs30 } from "fs";
13394
14044
  var CONNECTORS_CONFIG_VERSION = 1;
13395
14045
  var EnvRefUnsetError = class extends Error {
@@ -13404,11 +14054,11 @@ var EnvRefUnsetError = class extends Error {
13404
14054
  };
13405
14055
  function neatHome2() {
13406
14056
  const override = process.env.NEAT_HOME;
13407
- if (override && override.length > 0) return path62.resolve(override);
13408
- return path62.join(os4.homedir(), ".neat");
14057
+ if (override && override.length > 0) return path63.resolve(override);
14058
+ return path63.join(os4.homedir(), ".neat");
13409
14059
  }
13410
14060
  function connectorsConfigPath(home = neatHome2()) {
13411
- return path62.join(home, "connectors.json");
14061
+ return path63.join(home, "connectors.json");
13412
14062
  }
13413
14063
  var MODE_MASK_LOOSER_THAN_0600 = 63;
13414
14064
  async function warnIfModeLooserThan0600(file) {
@@ -13539,7 +14189,7 @@ function connectorMatchesProject(entry, project) {
13539
14189
  var CONNECTORS_LOCK_TIMEOUT_MS = 5e3;
13540
14190
  var CONNECTORS_LOCK_RETRY_MS = 50;
13541
14191
  function connectorsConfigLockPath(home = neatHome2()) {
13542
- return path62.join(home, "connectors.json.lock");
14192
+ return path63.join(home, "connectors.json.lock");
13543
14193
  }
13544
14194
  function isEnvRef(value) {
13545
14195
  return value.length > 1 && value.startsWith("$");
@@ -13552,7 +14202,7 @@ function redactCredentialRef(ref) {
13552
14202
  return out;
13553
14203
  }
13554
14204
  async function writeConfigAtomically0600(file, contents) {
13555
- await fs30.mkdir(path62.dirname(file), { recursive: true });
14205
+ await fs30.mkdir(path63.dirname(file), { recursive: true });
13556
14206
  const tmp = `${file}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
13557
14207
  const fd = await fs30.open(tmp, "w", 384);
13558
14208
  try {
@@ -13566,7 +14216,7 @@ async function writeConfigAtomically0600(file, contents) {
13566
14216
  }
13567
14217
  async function acquireConnectorsLock(lockPath, timeoutMs = CONNECTORS_LOCK_TIMEOUT_MS) {
13568
14218
  const deadline = Date.now() + timeoutMs;
13569
- await fs30.mkdir(path62.dirname(lockPath), { recursive: true });
14219
+ await fs30.mkdir(path63.dirname(lockPath), { recursive: true });
13570
14220
  for (; ; ) {
13571
14221
  try {
13572
14222
  const fd = await fs30.open(lockPath, "wx");
@@ -14201,10 +14851,10 @@ var SUPABASE_RPC_TARGET_KIND = "supabase-rpc";
14201
14851
  // src/connectors/supabase/map.ts
14202
14852
  var REST_RPC_PATH_RE = /^\/rest\/v1\/rpc\/([^/?]+)/;
14203
14853
  var REST_TABLE_PATH_RE = /^\/rest\/v1\/([^/?]+)/;
14204
- function targetFromRestPath(path63) {
14205
- const rpcMatch = REST_RPC_PATH_RE.exec(path63);
14854
+ function targetFromRestPath(path64) {
14855
+ const rpcMatch = REST_RPC_PATH_RE.exec(path64);
14206
14856
  if (rpcMatch) return { targetKind: SUPABASE_RPC_TARGET_KIND, name: rpcMatch[1] };
14207
- const tableMatch = REST_TABLE_PATH_RE.exec(path63);
14857
+ const tableMatch = REST_TABLE_PATH_RE.exec(path64);
14208
14858
  if (tableMatch) return { targetKind: SUPABASE_TABLE_TARGET_KIND, name: tableMatch[1] };
14209
14859
  return null;
14210
14860
  }
@@ -14313,21 +14963,21 @@ async function fetchPgStatStatements(connectionString, limit = DEFAULT_STATEMENT
14313
14963
  }
14314
14964
 
14315
14965
  // src/connectors/supabase/resolve.ts
14316
- import { EdgeType as EdgeType26, infraId as infraId19 } from "@neat.is/types";
14966
+ import { EdgeType as EdgeType26, infraId as infraId20 } from "@neat.is/types";
14317
14967
  function createSupabaseResolveTarget(graph, config) {
14318
14968
  return (signal, _ctx) => {
14319
14969
  if (signal.targetKind !== SUPABASE_TABLE_TARGET_KIND && signal.targetKind !== SUPABASE_RPC_TARGET_KIND) {
14320
14970
  return null;
14321
14971
  }
14322
- const subResourceId = infraId19(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
14972
+ const subResourceId = infraId20(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
14323
14973
  if (graph.hasNode(subResourceId)) {
14324
14974
  return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: EdgeType26.CALLS };
14325
14975
  }
14326
- const bareResourceId = infraId19(signal.targetKind, signal.targetName);
14976
+ const bareResourceId = infraId20(signal.targetKind, signal.targetName);
14327
14977
  if (graph.hasNode(bareResourceId)) {
14328
14978
  return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: EdgeType26.CALLS };
14329
14979
  }
14330
- const projectLevelId = infraId19("supabase", config.nodeRef);
14980
+ const projectLevelId = infraId20("supabase", config.nodeRef);
14331
14981
  if (graph.hasNode(projectLevelId)) {
14332
14982
  return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: EdgeType26.CALLS };
14333
14983
  }
@@ -14799,9 +15449,9 @@ function parseFirebaseTargetName(targetName) {
14799
15449
  const secondSep = rest.indexOf(FIELD_SEP);
14800
15450
  if (secondSep === -1) return null;
14801
15451
  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 };
15452
+ const path64 = rest.slice(secondSep + 1);
15453
+ if (!resourceName || !method || !path64) return null;
15454
+ return { resourceName, method, path: path64 };
14805
15455
  }
14806
15456
  function resourceNameFor(type, labels) {
14807
15457
  if (!labels) return null;
@@ -14839,14 +15489,14 @@ function mapLogEntryToSignal(entry) {
14839
15489
  if (!req) return null;
14840
15490
  if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
14841
15491
  const method = req.requestMethod.toUpperCase();
14842
- const path63 = pathFromRequestUrl(req.requestUrl);
14843
- if (path63 === null) return null;
15492
+ const path64 = pathFromRequestUrl(req.requestUrl);
15493
+ if (path64 === null) return null;
14844
15494
  const timestamp = entry.timestamp;
14845
15495
  if (typeof timestamp !== "string" || timestamp.length === 0) return null;
14846
15496
  const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD2;
14847
15497
  return {
14848
15498
  targetKind: resourceType,
14849
- targetName: packFirebaseTargetName({ resourceName, method, path: path63 }),
15499
+ targetName: packFirebaseTargetName({ resourceName, method, path: path64 }),
14850
15500
  callCount: 1,
14851
15501
  errorCount: isError ? 1 : 0,
14852
15502
  lastObservedIso: timestamp
@@ -14932,7 +15582,7 @@ function createFirebaseConnector(graph, serviceMap) {
14932
15582
  }
14933
15583
 
14934
15584
  // src/connectors/cloudflare/connector.ts
14935
- import { EdgeType as EdgeType29, NodeType as NodeType29, fileId as fileId4, infraId as infraId20 } from "@neat.is/types";
15585
+ import { EdgeType as EdgeType29, NodeType as NodeType29, fileId as fileId4, infraId as infraId21 } from "@neat.is/types";
14936
15586
 
14937
15587
  // src/connectors/cloudflare/client.ts
14938
15588
  import { randomUUID } from "crypto";
@@ -15043,7 +15693,7 @@ function mapEventToSignal(event) {
15043
15693
  if (Number.isNaN(observedAt.getTime())) return null;
15044
15694
  const statusCode = metadata?.statusCode;
15045
15695
  const isError = typeof statusCode === "number" && statusCode >= ERROR_STATUS_THRESHOLD3;
15046
- const path63 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
15696
+ const path64 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
15047
15697
  return {
15048
15698
  targetKind: CLOUDFLARE_TARGET_KIND,
15049
15699
  targetName: scriptName,
@@ -15051,7 +15701,7 @@ function mapEventToSignal(event) {
15051
15701
  errorCount: isError ? 1 : 0,
15052
15702
  lastObservedIso: observedAt.toISOString(),
15053
15703
  method,
15054
- ...path63 ? { path: path63 } : {},
15704
+ ...path64 ? { path: path64 } : {},
15055
15705
  ...typeof statusCode === "number" ? { statusCode } : {},
15056
15706
  ...typeof metadata?.duration === "number" ? { duration: metadata.duration } : {}
15057
15707
  };
@@ -15097,8 +15747,8 @@ function findTaggedWorkerFileNode(graph, workerName) {
15097
15747
  });
15098
15748
  return found;
15099
15749
  }
15100
- function findMatchingRouteNode(graph, serviceName, method, path63) {
15101
- const normalizedPath = normalizePathTemplate(path63);
15750
+ function findMatchingRouteNode(graph, serviceName, method, path64) {
15751
+ const normalizedPath = normalizePathTemplate(path64);
15102
15752
  let found = null;
15103
15753
  graph.forEachNode((id, attrs) => {
15104
15754
  if (found) return;
@@ -15115,10 +15765,10 @@ function createCloudflareResolveTarget(config, graph) {
15115
15765
  return (signal) => {
15116
15766
  if (signal.targetKind !== CLOUDFLARE_TARGET_KIND) return null;
15117
15767
  const scriptName = signal.targetName;
15118
- const { method, path: path63 } = signal;
15768
+ const { method, path: path64 } = signal;
15119
15769
  const resolveRouteGrain = (serviceName, wholeFileId) => {
15120
- if (!method || !path63) return wholeFileId;
15121
- return findMatchingRouteNode(graph, serviceName, method, path63) ?? wholeFileId;
15770
+ if (!method || !path64) return wholeFileId;
15771
+ return findMatchingRouteNode(graph, serviceName, method, path64) ?? wholeFileId;
15122
15772
  };
15123
15773
  const mapping = config.workers?.[scriptName];
15124
15774
  if (mapping) {
@@ -15139,7 +15789,7 @@ function createCloudflareResolveTarget(config, graph) {
15139
15789
  };
15140
15790
  }
15141
15791
  return {
15142
- targetNodeId: infraId20("cloudflare-worker", scriptName),
15792
+ targetNodeId: infraId21("cloudflare-worker", scriptName),
15143
15793
  serviceName: scriptName,
15144
15794
  edgeType: EdgeType29.CALLS,
15145
15795
  ensureInfraNode: { kind: "cloudflare-worker", name: scriptName, provider: "cloudflare" }
@@ -15323,12 +15973,12 @@ function diffNeonStatementsToSignals(rows, previous, observedAtIso) {
15323
15973
  }
15324
15974
 
15325
15975
  // src/connectors/neon/resolve.ts
15326
- import { EdgeType as EdgeType30, infraId as infraId21 } from "@neat.is/types";
15976
+ import { EdgeType as EdgeType30, infraId as infraId22 } from "@neat.is/types";
15327
15977
  function createNeonResolveTarget(config) {
15328
15978
  return (signal) => {
15329
15979
  if (signal.targetKind !== NEON_SQL_TABLE_TARGET_KIND || !signal.targetName) return null;
15330
15980
  return {
15331
- targetNodeId: infraId21("sql-table", signal.targetName),
15981
+ targetNodeId: infraId22("sql-table", signal.targetName),
15332
15982
  serviceName: config.serviceName,
15333
15983
  edgeType: EdgeType30.CALLS,
15334
15984
  ensureInfraNode: { kind: "sql-table", name: signal.targetName, provider: "neon" }
@@ -15448,9 +16098,9 @@ function parseCloudRunTargetName(targetName) {
15448
16098
  const secondSep = rest.indexOf(FIELD_SEP2);
15449
16099
  if (secondSep === -1) return null;
15450
16100
  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 };
16101
+ const path64 = rest.slice(secondSep + 1);
16102
+ if (!serviceName || !method || !path64) return null;
16103
+ return { serviceName, method, path: path64 };
15454
16104
  }
15455
16105
 
15456
16106
  // src/connectors/cloud-run/map.ts
@@ -15479,14 +16129,14 @@ function mapLogEntryToSignal2(entry) {
15479
16129
  if (!req) return null;
15480
16130
  if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
15481
16131
  const method = req.requestMethod.toUpperCase();
15482
- const path63 = pathFromRequestUrl2(req.requestUrl);
15483
- if (path63 === null) return null;
16132
+ const path64 = pathFromRequestUrl2(req.requestUrl);
16133
+ if (path64 === null) return null;
15484
16134
  const timestamp = entry.timestamp;
15485
16135
  if (typeof timestamp !== "string" || timestamp.length === 0) return null;
15486
16136
  const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD4;
15487
16137
  return {
15488
16138
  targetKind: CLOUD_RUN_TARGET_KIND,
15489
- targetName: packCloudRunTargetName({ serviceName, method, path: path63 }),
16139
+ targetName: packCloudRunTargetName({ serviceName, method, path: path64 }),
15490
16140
  callCount: 1,
15491
16141
  errorCount: isError ? 1 : 0,
15492
16142
  lastObservedIso: timestamp
@@ -15502,7 +16152,7 @@ function mapLogEntriesToSignals2(entries) {
15502
16152
  }
15503
16153
 
15504
16154
  // src/connectors/cloud-run/resolve.ts
15505
- import { EdgeType as EdgeType31, NodeType as NodeType30, infraId as infraId22 } from "@neat.is/types";
16155
+ import { EdgeType as EdgeType31, NodeType as NodeType30, infraId as infraId23 } from "@neat.is/types";
15506
16156
  var CLOUD_RUN_SERVICE_INFRA_KIND = "cloud-run-service";
15507
16157
  function findMatchingRouteNode2(graph, serviceName, method, normalizedPath) {
15508
16158
  let found = null;
@@ -15524,21 +16174,21 @@ function createCloudRunResolveTarget(graph, config) {
15524
16174
  if (signal.targetKind !== CLOUD_RUN_TARGET_KIND) return null;
15525
16175
  const identity = parseCloudRunTargetName(signal.targetName);
15526
16176
  if (!identity) return null;
15527
- const { serviceName: gcpServiceName, method, path: path63 } = identity;
16177
+ const { serviceName: gcpServiceName, method, path: path64 } = identity;
15528
16178
  const mappedService = config.serviceMap?.[gcpServiceName];
15529
16179
  if (mappedService) {
15530
16180
  const routeNodeId = findMatchingRouteNode2(
15531
16181
  graph,
15532
16182
  mappedService,
15533
16183
  method,
15534
- normalizePathTemplate(path63)
16184
+ normalizePathTemplate(path64)
15535
16185
  );
15536
16186
  if (routeNodeId) {
15537
16187
  return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: EdgeType31.CALLS };
15538
16188
  }
15539
16189
  }
15540
16190
  return {
15541
- targetNodeId: infraId22(CLOUD_RUN_SERVICE_INFRA_KIND, gcpServiceName),
16191
+ targetNodeId: infraId23(CLOUD_RUN_SERVICE_INFRA_KIND, gcpServiceName),
15542
16192
  serviceName: mappedService ?? gcpServiceName,
15543
16193
  edgeType: EdgeType31.CALLS,
15544
16194
  ensureInfraNode: {
@@ -15870,17 +16520,17 @@ function mapInsightsToSignals(rows, observedAtIso) {
15870
16520
  }
15871
16521
 
15872
16522
  // src/connectors/planetscale/resolve.ts
15873
- import { EdgeType as EdgeType33, infraId as infraId23 } from "@neat.is/types";
16523
+ import { EdgeType as EdgeType33, infraId as infraId24 } from "@neat.is/types";
15874
16524
  var PLANETSCALE_DATABASE_KIND = "planetscale-database";
15875
16525
  function createPlanetscaleResolveTarget(graph, config) {
15876
16526
  const databaseName = `${config.organization}/${config.database}`;
15877
16527
  return (signal, _ctx) => {
15878
16528
  if (signal.targetKind !== PLANETSCALE_SQL_TABLE_TARGET_KIND || !signal.targetName) return null;
15879
- const tableId = infraId23("sql-table", signal.targetName);
16529
+ const tableId = infraId24("sql-table", signal.targetName);
15880
16530
  if (graph.hasNode(tableId)) {
15881
16531
  return { targetNodeId: tableId, serviceName: config.serviceName, edgeType: EdgeType33.CALLS };
15882
16532
  }
15883
- const providerId = infraId23(PLANETSCALE_DATABASE_KIND, databaseName);
16533
+ const providerId = infraId24(PLANETSCALE_DATABASE_KIND, databaseName);
15884
16534
  return {
15885
16535
  targetNodeId: providerId,
15886
16536
  serviceName: config.serviceName,
@@ -17385,4 +18035,4 @@ export {
17385
18035
  deprovisionConnector,
17386
18036
  buildApi
17387
18037
  };
17388
- //# sourceMappingURL=chunk-XT4NNFH6.js.map
18038
+ //# sourceMappingURL=chunk-LN75Z624.js.map