@neat.is/core 0.4.31-dev.20260718 → 0.5.0

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.
@@ -31,7 +31,7 @@ import {
31
31
  touchLastSeen,
32
32
  upsertObservedEdge,
33
33
  writeAtomically
34
- } from "./chunk-4M2YMGUM.js";
34
+ } from "./chunk-QQDZSIY3.js";
35
35
  import {
36
36
  assertBindAuthority,
37
37
  buildOtelReceiver,
@@ -2461,4 +2461,4 @@ export {
2461
2461
  resolveHost,
2462
2462
  startDaemon
2463
2463
  };
2464
- //# sourceMappingURL=chunk-2AJHEZIB.js.map
2464
+ //# sourceMappingURL=chunk-5MGHWP6R.js.map
@@ -483,7 +483,7 @@ function databaseRootCauseShape(graph, origin, walk3) {
483
483
  for (const id of walk3.path) {
484
484
  const owner = resolveOwningService(graph, id);
485
485
  if (!owner) continue;
486
- const { id: serviceId4, svc } = owner;
486
+ const { id: serviceId5, svc } = owner;
487
487
  const deps = svc.dependencies ?? {};
488
488
  for (const pair of candidatePairs) {
489
489
  const declared = deps[pair.driver];
@@ -496,7 +496,7 @@ function databaseRootCauseShape(graph, origin, walk3) {
496
496
  );
497
497
  if (!result.compatible) {
498
498
  return {
499
- rootCauseNode: serviceId4,
499
+ rootCauseNode: serviceId5,
500
500
  rootCauseReason: result.reason ?? "incompatible driver",
501
501
  ...result.minDriverVersion ? {
502
502
  fixRecommendation: `Upgrade ${svc.name} ${pair.driver} driver to >= ${result.minDriverVersion}`
@@ -511,7 +511,7 @@ function serviceRootCauseShape(graph, _origin, walk3) {
511
511
  for (const id of walk3.path) {
512
512
  const owner = resolveOwningService(graph, id);
513
513
  if (!owner) continue;
514
- const { id: serviceId4, svc } = owner;
514
+ const { id: serviceId5, svc } = owner;
515
515
  const deps = svc.dependencies ?? {};
516
516
  const serviceNodeEngine = svc.nodeEngine;
517
517
  for (const constraint of nodeEngineConstraints()) {
@@ -520,7 +520,7 @@ function serviceRootCauseShape(graph, _origin, walk3) {
520
520
  const result = checkNodeEngineConstraint(constraint, declared, serviceNodeEngine);
521
521
  if (!result.compatible && result.reason) {
522
522
  return {
523
- rootCauseNode: serviceId4,
523
+ rootCauseNode: serviceId5,
524
524
  rootCauseReason: result.reason,
525
525
  ...result.requiredNodeVersion ? {
526
526
  fixRecommendation: `Bump ${svc.name}'s engines.node to >= ${result.requiredNodeVersion}`
@@ -535,7 +535,7 @@ function serviceRootCauseShape(graph, _origin, walk3) {
535
535
  const result = checkPackageConflict(conflict, declared, requiredDeclared);
536
536
  if (!result.compatible && result.reason) {
537
537
  return {
538
- rootCauseNode: serviceId4,
538
+ rootCauseNode: serviceId5,
539
539
  rootCauseReason: result.reason,
540
540
  fixRecommendation: `Upgrade ${svc.name}'s ${conflict.requires.name} to >= ${conflict.requires.minVersion}`
541
541
  };
@@ -626,9 +626,9 @@ function rootCauseFromIncidents(nodeId, incidents, errorEvent) {
626
626
  function isFailingCallEdge(e) {
627
627
  return e.type === EdgeType.CALLS && (e.signal?.errorCount ?? 0) > 0;
628
628
  }
629
- function callSourcesForService(graph, serviceId4) {
630
- const ids = [serviceId4];
631
- for (const edgeId of graph.outboundEdges(serviceId4)) {
629
+ function callSourcesForService(graph, serviceId5) {
630
+ const ids = [serviceId5];
631
+ for (const edgeId of graph.outboundEdges(serviceId5)) {
632
632
  const e = graph.getEdgeAttributes(edgeId);
633
633
  if (e.type !== EdgeType.CONTAINS) continue;
634
634
  const tgt = graph.getNodeAttributes(e.target);
@@ -645,9 +645,9 @@ function failingCallDominates(e, id, curEdge, curId) {
645
645
  }
646
646
  return id < curId;
647
647
  }
648
- function dominantFailingCall(graph, serviceId4, visited) {
648
+ function dominantFailingCall(graph, serviceId5, visited) {
649
649
  let best = null;
650
- for (const src of callSourcesForService(graph, serviceId4)) {
650
+ for (const src of callSourcesForService(graph, serviceId5)) {
651
651
  for (const edgeId of graph.outboundEdges(src)) {
652
652
  const e = graph.getEdgeAttributes(edgeId);
653
653
  if (!isFailingCallEdge(e)) continue;
@@ -1923,6 +1923,27 @@ function ensureLocalDatabaseNode(graph, serviceName, name, engine) {
1923
1923
  graph.addNode(id, node);
1924
1924
  return id;
1925
1925
  }
1926
+ function findDeclaredDatabaseForService(graph, serviceNodeId, engine) {
1927
+ if (!graph.hasNode(serviceNodeId)) return null;
1928
+ const sources = [serviceNodeId];
1929
+ for (const edgeId of graph.outboundEdges(serviceNodeId)) {
1930
+ const e = graph.getEdgeAttributes(edgeId);
1931
+ if (e.type === EdgeType3.CONTAINS) sources.push(e.target);
1932
+ }
1933
+ const matches = /* @__PURE__ */ new Set();
1934
+ for (const src of sources) {
1935
+ if (!graph.hasNode(src)) continue;
1936
+ for (const edgeId of graph.outboundEdges(src)) {
1937
+ const edge = graph.getEdgeAttributes(edgeId);
1938
+ if (edge.type !== EdgeType3.CONNECTS_TO || edge.provenance !== Provenance2.EXTRACTED) continue;
1939
+ if (!graph.hasNode(edge.target)) continue;
1940
+ const target = graph.getNodeAttributes(edge.target);
1941
+ if (target.type !== NodeType3.DatabaseNode || target.engine !== engine) continue;
1942
+ matches.add(edge.target);
1943
+ }
1944
+ }
1945
+ return matches.size === 1 ? [...matches][0] : null;
1946
+ }
1926
1947
  function ensureFrontierNode(graph, host, ts) {
1927
1948
  const id = frontierIdFor(host);
1928
1949
  if (graph.hasNode(id)) {
@@ -2181,13 +2202,18 @@ async function handleSpan(ctx, span) {
2181
2202
  ensureDatabaseNode(ctx.graph, host, span.dbSystem);
2182
2203
  targetId = databaseId(host);
2183
2204
  } else {
2184
- const localName = span.dbName ?? span.dbSystem;
2185
- targetId = ensureLocalDatabaseNode(
2186
- ctx.graph,
2187
- span.service,
2188
- localName,
2189
- span.dbSystem
2190
- );
2205
+ const declared = findDeclaredDatabaseForService(ctx.graph, sourceId, span.dbSystem);
2206
+ if (declared) {
2207
+ targetId = declared;
2208
+ } else {
2209
+ const localName = span.dbName ?? span.dbSystem;
2210
+ targetId = ensureLocalDatabaseNode(
2211
+ ctx.graph,
2212
+ span.service,
2213
+ localName,
2214
+ span.dbSystem
2215
+ );
2216
+ }
2191
2217
  }
2192
2218
  const result = upsertObservedEdge(
2193
2219
  ctx.graph,
@@ -2371,29 +2397,29 @@ function promoteFrontierNodes(graph, opts = {}) {
2371
2397
  toPromote.push({ frontierId: id, serviceId: target });
2372
2398
  });
2373
2399
  let promoted = 0;
2374
- for (const { frontierId: frontierId2, serviceId: serviceId4 } of toPromote) {
2400
+ for (const { frontierId: frontierId2, serviceId: serviceId5 } of toPromote) {
2375
2401
  if (opts.policies && opts.policies.length > 0 && opts.policyCtx) {
2376
2402
  const gate = canPromoteFrontier(graph, frontierId2, opts.policies, opts.policyCtx);
2377
2403
  if (!gate.allowed) {
2378
2404
  continue;
2379
2405
  }
2380
2406
  }
2381
- rewireFrontierEdges(graph, frontierId2, serviceId4);
2407
+ rewireFrontierEdges(graph, frontierId2, serviceId5);
2382
2408
  graph.dropNode(frontierId2);
2383
2409
  promoted++;
2384
2410
  }
2385
2411
  return promoted;
2386
2412
  }
2387
- function rewireFrontierEdges(graph, frontierId2, serviceId4) {
2413
+ function rewireFrontierEdges(graph, frontierId2, serviceId5) {
2388
2414
  const inbound = [...graph.inboundEdges(frontierId2)];
2389
2415
  const outbound = [...graph.outboundEdges(frontierId2)];
2390
2416
  for (const edgeId of inbound) {
2391
2417
  const edge = graph.getEdgeAttributes(edgeId);
2392
- rebuildEdge(graph, edge, edge.source, serviceId4, edgeId);
2418
+ rebuildEdge(graph, edge, edge.source, serviceId5, edgeId);
2393
2419
  }
2394
2420
  for (const edgeId of outbound) {
2395
2421
  const edge = graph.getEdgeAttributes(edgeId);
2396
- rebuildEdge(graph, edge, serviceId4, edge.target, edgeId);
2422
+ rebuildEdge(graph, edge, serviceId5, edge.target, edgeId);
2397
2423
  }
2398
2424
  }
2399
2425
  function rebuildEdge(graph, edge, newSource, newTarget, oldEdgeId) {
@@ -3195,9 +3221,9 @@ var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
3195
3221
  "StatefulSet",
3196
3222
  "DaemonSet"
3197
3223
  ]);
3198
- function addAliases(graph, serviceId4, candidates) {
3199
- if (!graph.hasNode(serviceId4)) return;
3200
- const node = graph.getNodeAttributes(serviceId4);
3224
+ function addAliases(graph, serviceId5, candidates) {
3225
+ if (!graph.hasNode(serviceId5)) return;
3226
+ const node = graph.getNodeAttributes(serviceId5);
3201
3227
  if (node.type !== NodeType5.ServiceNode) return;
3202
3228
  const set = new Set(node.aliases ?? []);
3203
3229
  for (const c of candidates) {
@@ -3207,7 +3233,7 @@ function addAliases(graph, serviceId4, candidates) {
3207
3233
  }
3208
3234
  if (set.size === 0) return;
3209
3235
  const updated = { ...node, aliases: [...set].sort() };
3210
- graph.replaceNodeAttributes(serviceId4, updated);
3236
+ graph.replaceNodeAttributes(serviceId5, updated);
3211
3237
  }
3212
3238
  function indexServicesByName(services) {
3213
3239
  const map = /* @__PURE__ */ new Map();
@@ -3240,12 +3266,12 @@ async function collectComposeAliases(graph, scanPath, serviceIndex) {
3240
3266
  }
3241
3267
  if (!compose?.services) return;
3242
3268
  for (const [composeName, svc] of Object.entries(compose.services)) {
3243
- const serviceId4 = serviceIndex.get(composeName);
3244
- if (!serviceId4) continue;
3269
+ const serviceId5 = serviceIndex.get(composeName);
3270
+ if (!serviceId5) continue;
3245
3271
  const aliases = /* @__PURE__ */ new Set([composeName]);
3246
3272
  if (svc.container_name) aliases.add(svc.container_name);
3247
3273
  if (svc.hostname) aliases.add(svc.hostname);
3248
- addAliases(graph, serviceId4, aliases);
3274
+ addAliases(graph, serviceId5, aliases);
3249
3275
  }
3250
3276
  }
3251
3277
  var LABEL_KEYS = /* @__PURE__ */ new Set([
@@ -3906,6 +3932,29 @@ async function readIfExists(filePath) {
3906
3932
  return null;
3907
3933
  }
3908
3934
  }
3935
+ async function resolveEnvVar(serviceDir, name) {
3936
+ const entries = await fs12.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
3937
+ const envNames = entries.filter((e) => e.isFile() && (e.name === ".env" || e.name.startsWith(".env."))).map((e) => e.name);
3938
+ const rank = (n) => n === ".env.local" ? 0 : n === ".env" ? 1 : 2;
3939
+ envNames.sort((a, b) => rank(a) - rank(b) || a.localeCompare(b));
3940
+ for (const fileName of envNames) {
3941
+ const content = await readIfExists(path14.join(serviceDir, fileName));
3942
+ if (!content) continue;
3943
+ for (const line of content.split("\n")) {
3944
+ const trimmed = line.trim();
3945
+ if (!trimmed || trimmed.startsWith("#")) continue;
3946
+ const eq = trimmed.indexOf("=");
3947
+ if (eq < 0) continue;
3948
+ if (trimmed.slice(0, eq).trim() !== name) continue;
3949
+ let value = trimmed.slice(eq + 1).trim();
3950
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
3951
+ value = value.slice(1, -1);
3952
+ }
3953
+ return value || null;
3954
+ }
3955
+ }
3956
+ return null;
3957
+ }
3909
3958
  async function findFirst(serviceDir, candidates) {
3910
3959
  for (const rel of candidates) {
3911
3960
  const abs = path14.join(serviceDir, rel);
@@ -4002,6 +4051,14 @@ async function parse3(serviceDir) {
4002
4051
  const config = parseConnectionString(urlMatch[1]);
4003
4052
  if (config) return [{ ...config, sourceFile: schemaPath }];
4004
4053
  }
4054
+ const envMatch = body.match(/url\s*=\s*env\(\s*"([^"]+)"\s*\)/);
4055
+ if (envMatch) {
4056
+ const resolved = await resolveEnvVar(serviceDir, envMatch[1]);
4057
+ if (resolved) {
4058
+ const config = parseConnectionString(resolved);
4059
+ if (config) return [{ ...config, sourceFile: schemaPath }];
4060
+ }
4061
+ }
4005
4062
  return [
4006
4063
  {
4007
4064
  host: `${engine}-prisma`,
@@ -6768,19 +6825,30 @@ import {
6768
6825
  EdgeType as EdgeType20,
6769
6826
  NodeType as NodeType15,
6770
6827
  parseEdgeId,
6771
- Provenance as Provenance18
6828
+ parseFileId,
6829
+ Provenance as Provenance18,
6830
+ serviceId as serviceId4
6772
6831
  } from "@neat.is/types";
6773
6832
  function bucketKey(source, target, type) {
6774
6833
  return `${type}|${source}|${target}`;
6775
6834
  }
6835
+ function bucketSourceFor(graph, edge) {
6836
+ if (edge.type !== EdgeType20.CONNECTS_TO) return edge.source;
6837
+ const parsed = parseFileId(edge.source);
6838
+ if (!parsed || !graph.hasNode(edge.target)) return edge.source;
6839
+ const target = graph.getNodeAttributes(edge.target);
6840
+ if (target.type !== NodeType15.DatabaseNode) return edge.source;
6841
+ return serviceId4(parsed.service);
6842
+ }
6776
6843
  function bucketEdges(graph) {
6777
6844
  const buckets = /* @__PURE__ */ new Map();
6778
6845
  graph.forEachEdge((id, attrs) => {
6779
6846
  const e = attrs;
6780
6847
  const parsed = parseEdgeId(id);
6781
6848
  const provenance = parsed?.provenance ?? e.provenance;
6782
- const key = bucketKey(e.source, e.target, e.type);
6783
- const cur = buckets.get(key) ?? { source: e.source, target: e.target, type: e.type };
6849
+ const source = bucketSourceFor(graph, e);
6850
+ const key = bucketKey(source, e.target, e.type);
6851
+ const cur = buckets.get(key) ?? { source, target: e.target, type: e.type };
6784
6852
  switch (provenance) {
6785
6853
  case Provenance18.EXTRACTED:
6786
6854
  cur.extracted = e;
@@ -9233,4 +9301,4 @@ export {
9233
9301
  recordConnectorPoll,
9234
9302
  buildApi
9235
9303
  };
9236
- //# sourceMappingURL=chunk-4M2YMGUM.js.map
9304
+ //# sourceMappingURL=chunk-QQDZSIY3.js.map