@neat.is/core 0.4.30 → 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.
package/dist/cli.js CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  resolveNeatVersion,
10
10
  validateConnectorEntry,
11
11
  writeDaemonRecord
12
- } from "./chunk-2AJHEZIB.js";
12
+ } from "./chunk-5MGHWP6R.js";
13
13
  import {
14
14
  buildSearchIndex
15
15
  } from "./chunk-BIY46Q6U.js";
@@ -68,7 +68,7 @@ import {
68
68
  startPersistLoop,
69
69
  startStalenessLoop,
70
70
  upsertConnectorEntry
71
- } from "./chunk-4M2YMGUM.js";
71
+ } from "./chunk-QQDZSIY3.js";
72
72
  import {
73
73
  startOtelGrpcReceiver
74
74
  } from "./chunk-I72HTUOG.js";
package/dist/index.cjs CHANGED
@@ -1277,7 +1277,7 @@ function databaseRootCauseShape(graph, origin, walk3) {
1277
1277
  for (const id of walk3.path) {
1278
1278
  const owner = resolveOwningService(graph, id);
1279
1279
  if (!owner) continue;
1280
- const { id: serviceId5, svc } = owner;
1280
+ const { id: serviceId6, svc } = owner;
1281
1281
  const deps = svc.dependencies ?? {};
1282
1282
  for (const pair of candidatePairs) {
1283
1283
  const declared = deps[pair.driver];
@@ -1290,7 +1290,7 @@ function databaseRootCauseShape(graph, origin, walk3) {
1290
1290
  );
1291
1291
  if (!result.compatible) {
1292
1292
  return {
1293
- rootCauseNode: serviceId5,
1293
+ rootCauseNode: serviceId6,
1294
1294
  rootCauseReason: result.reason ?? "incompatible driver",
1295
1295
  ...result.minDriverVersion ? {
1296
1296
  fixRecommendation: `Upgrade ${svc.name} ${pair.driver} driver to >= ${result.minDriverVersion}`
@@ -1305,7 +1305,7 @@ function serviceRootCauseShape(graph, _origin, walk3) {
1305
1305
  for (const id of walk3.path) {
1306
1306
  const owner = resolveOwningService(graph, id);
1307
1307
  if (!owner) continue;
1308
- const { id: serviceId5, svc } = owner;
1308
+ const { id: serviceId6, svc } = owner;
1309
1309
  const deps = svc.dependencies ?? {};
1310
1310
  const serviceNodeEngine = svc.nodeEngine;
1311
1311
  for (const constraint of nodeEngineConstraints()) {
@@ -1314,7 +1314,7 @@ function serviceRootCauseShape(graph, _origin, walk3) {
1314
1314
  const result = checkNodeEngineConstraint(constraint, declared, serviceNodeEngine);
1315
1315
  if (!result.compatible && result.reason) {
1316
1316
  return {
1317
- rootCauseNode: serviceId5,
1317
+ rootCauseNode: serviceId6,
1318
1318
  rootCauseReason: result.reason,
1319
1319
  ...result.requiredNodeVersion ? {
1320
1320
  fixRecommendation: `Bump ${svc.name}'s engines.node to >= ${result.requiredNodeVersion}`
@@ -1329,7 +1329,7 @@ function serviceRootCauseShape(graph, _origin, walk3) {
1329
1329
  const result = checkPackageConflict(conflict, declared, requiredDeclared);
1330
1330
  if (!result.compatible && result.reason) {
1331
1331
  return {
1332
- rootCauseNode: serviceId5,
1332
+ rootCauseNode: serviceId6,
1333
1333
  rootCauseReason: result.reason,
1334
1334
  fixRecommendation: `Upgrade ${svc.name}'s ${conflict.requires.name} to >= ${conflict.requires.minVersion}`
1335
1335
  };
@@ -1420,9 +1420,9 @@ function rootCauseFromIncidents(nodeId, incidents, errorEvent) {
1420
1420
  function isFailingCallEdge(e) {
1421
1421
  return e.type === import_types.EdgeType.CALLS && (e.signal?.errorCount ?? 0) > 0;
1422
1422
  }
1423
- function callSourcesForService(graph, serviceId5) {
1424
- const ids = [serviceId5];
1425
- for (const edgeId of graph.outboundEdges(serviceId5)) {
1423
+ function callSourcesForService(graph, serviceId6) {
1424
+ const ids = [serviceId6];
1425
+ for (const edgeId of graph.outboundEdges(serviceId6)) {
1426
1426
  const e = graph.getEdgeAttributes(edgeId);
1427
1427
  if (e.type !== import_types.EdgeType.CONTAINS) continue;
1428
1428
  const tgt = graph.getNodeAttributes(e.target);
@@ -1439,9 +1439,9 @@ function failingCallDominates(e, id, curEdge, curId) {
1439
1439
  }
1440
1440
  return id < curId;
1441
1441
  }
1442
- function dominantFailingCall(graph, serviceId5, visited) {
1442
+ function dominantFailingCall(graph, serviceId6, visited) {
1443
1443
  let best = null;
1444
- for (const src of callSourcesForService(graph, serviceId5)) {
1444
+ for (const src of callSourcesForService(graph, serviceId6)) {
1445
1445
  for (const edgeId of graph.outboundEdges(src)) {
1446
1446
  const e = graph.getEdgeAttributes(edgeId);
1447
1447
  if (!isFailingCallEdge(e)) continue;
@@ -2623,6 +2623,27 @@ function ensureLocalDatabaseNode(graph, serviceName, name, engine) {
2623
2623
  graph.addNode(id, node);
2624
2624
  return id;
2625
2625
  }
2626
+ function findDeclaredDatabaseForService(graph, serviceNodeId, engine) {
2627
+ if (!graph.hasNode(serviceNodeId)) return null;
2628
+ const sources = [serviceNodeId];
2629
+ for (const edgeId of graph.outboundEdges(serviceNodeId)) {
2630
+ const e = graph.getEdgeAttributes(edgeId);
2631
+ if (e.type === import_types3.EdgeType.CONTAINS) sources.push(e.target);
2632
+ }
2633
+ const matches = /* @__PURE__ */ new Set();
2634
+ for (const src of sources) {
2635
+ if (!graph.hasNode(src)) continue;
2636
+ for (const edgeId of graph.outboundEdges(src)) {
2637
+ const edge = graph.getEdgeAttributes(edgeId);
2638
+ if (edge.type !== import_types3.EdgeType.CONNECTS_TO || edge.provenance !== import_types3.Provenance.EXTRACTED) continue;
2639
+ if (!graph.hasNode(edge.target)) continue;
2640
+ const target = graph.getNodeAttributes(edge.target);
2641
+ if (target.type !== import_types3.NodeType.DatabaseNode || target.engine !== engine) continue;
2642
+ matches.add(edge.target);
2643
+ }
2644
+ }
2645
+ return matches.size === 1 ? [...matches][0] : null;
2646
+ }
2626
2647
  function ensureFrontierNode(graph, host, ts) {
2627
2648
  const id = frontierIdFor(host);
2628
2649
  if (graph.hasNode(id)) {
@@ -2881,13 +2902,18 @@ async function handleSpan(ctx, span) {
2881
2902
  ensureDatabaseNode(ctx.graph, host, span.dbSystem);
2882
2903
  targetId = (0, import_types3.databaseId)(host);
2883
2904
  } else {
2884
- const localName = span.dbName ?? span.dbSystem;
2885
- targetId = ensureLocalDatabaseNode(
2886
- ctx.graph,
2887
- span.service,
2888
- localName,
2889
- span.dbSystem
2890
- );
2905
+ const declared = findDeclaredDatabaseForService(ctx.graph, sourceId, span.dbSystem);
2906
+ if (declared) {
2907
+ targetId = declared;
2908
+ } else {
2909
+ const localName = span.dbName ?? span.dbSystem;
2910
+ targetId = ensureLocalDatabaseNode(
2911
+ ctx.graph,
2912
+ span.service,
2913
+ localName,
2914
+ span.dbSystem
2915
+ );
2916
+ }
2891
2917
  }
2892
2918
  const result = upsertObservedEdge(
2893
2919
  ctx.graph,
@@ -3071,29 +3097,29 @@ function promoteFrontierNodes(graph, opts = {}) {
3071
3097
  toPromote.push({ frontierId: id, serviceId: target });
3072
3098
  });
3073
3099
  let promoted = 0;
3074
- for (const { frontierId: frontierId2, serviceId: serviceId5 } of toPromote) {
3100
+ for (const { frontierId: frontierId2, serviceId: serviceId6 } of toPromote) {
3075
3101
  if (opts.policies && opts.policies.length > 0 && opts.policyCtx) {
3076
3102
  const gate = canPromoteFrontier(graph, frontierId2, opts.policies, opts.policyCtx);
3077
3103
  if (!gate.allowed) {
3078
3104
  continue;
3079
3105
  }
3080
3106
  }
3081
- rewireFrontierEdges(graph, frontierId2, serviceId5);
3107
+ rewireFrontierEdges(graph, frontierId2, serviceId6);
3082
3108
  graph.dropNode(frontierId2);
3083
3109
  promoted++;
3084
3110
  }
3085
3111
  return promoted;
3086
3112
  }
3087
- function rewireFrontierEdges(graph, frontierId2, serviceId5) {
3113
+ function rewireFrontierEdges(graph, frontierId2, serviceId6) {
3088
3114
  const inbound = [...graph.inboundEdges(frontierId2)];
3089
3115
  const outbound = [...graph.outboundEdges(frontierId2)];
3090
3116
  for (const edgeId of inbound) {
3091
3117
  const edge = graph.getEdgeAttributes(edgeId);
3092
- rebuildEdge(graph, edge, edge.source, serviceId5, edgeId);
3118
+ rebuildEdge(graph, edge, edge.source, serviceId6, edgeId);
3093
3119
  }
3094
3120
  for (const edgeId of outbound) {
3095
3121
  const edge = graph.getEdgeAttributes(edgeId);
3096
- rebuildEdge(graph, edge, serviceId5, edge.target, edgeId);
3122
+ rebuildEdge(graph, edge, serviceId6, edge.target, edgeId);
3097
3123
  }
3098
3124
  }
3099
3125
  function rebuildEdge(graph, edge, newSource, newTarget, oldEdgeId) {
@@ -3889,9 +3915,9 @@ var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
3889
3915
  "StatefulSet",
3890
3916
  "DaemonSet"
3891
3917
  ]);
3892
- function addAliases(graph, serviceId5, candidates) {
3893
- if (!graph.hasNode(serviceId5)) return;
3894
- const node = graph.getNodeAttributes(serviceId5);
3918
+ function addAliases(graph, serviceId6, candidates) {
3919
+ if (!graph.hasNode(serviceId6)) return;
3920
+ const node = graph.getNodeAttributes(serviceId6);
3895
3921
  if (node.type !== import_types6.NodeType.ServiceNode) return;
3896
3922
  const set = new Set(node.aliases ?? []);
3897
3923
  for (const c of candidates) {
@@ -3901,7 +3927,7 @@ function addAliases(graph, serviceId5, candidates) {
3901
3927
  }
3902
3928
  if (set.size === 0) return;
3903
3929
  const updated = { ...node, aliases: [...set].sort() };
3904
- graph.replaceNodeAttributes(serviceId5, updated);
3930
+ graph.replaceNodeAttributes(serviceId6, updated);
3905
3931
  }
3906
3932
  function indexServicesByName(services) {
3907
3933
  const map = /* @__PURE__ */ new Map();
@@ -3934,12 +3960,12 @@ async function collectComposeAliases(graph, scanPath, serviceIndex) {
3934
3960
  }
3935
3961
  if (!compose?.services) return;
3936
3962
  for (const [composeName, svc] of Object.entries(compose.services)) {
3937
- const serviceId5 = serviceIndex.get(composeName);
3938
- if (!serviceId5) continue;
3963
+ const serviceId6 = serviceIndex.get(composeName);
3964
+ if (!serviceId6) continue;
3939
3965
  const aliases = /* @__PURE__ */ new Set([composeName]);
3940
3966
  if (svc.container_name) aliases.add(svc.container_name);
3941
3967
  if (svc.hostname) aliases.add(svc.hostname);
3942
- addAliases(graph, serviceId5, aliases);
3968
+ addAliases(graph, serviceId6, aliases);
3943
3969
  }
3944
3970
  }
3945
3971
  var LABEL_KEYS = /* @__PURE__ */ new Set([
@@ -4589,6 +4615,29 @@ async function readIfExists(filePath) {
4589
4615
  return null;
4590
4616
  }
4591
4617
  }
4618
+ async function resolveEnvVar(serviceDir, name) {
4619
+ const entries = await import_node_fs12.promises.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
4620
+ const envNames = entries.filter((e) => e.isFile() && (e.name === ".env" || e.name.startsWith(".env."))).map((e) => e.name);
4621
+ const rank = (n) => n === ".env.local" ? 0 : n === ".env" ? 1 : 2;
4622
+ envNames.sort((a, b) => rank(a) - rank(b) || a.localeCompare(b));
4623
+ for (const fileName of envNames) {
4624
+ const content = await readIfExists(import_node_path14.default.join(serviceDir, fileName));
4625
+ if (!content) continue;
4626
+ for (const line of content.split("\n")) {
4627
+ const trimmed = line.trim();
4628
+ if (!trimmed || trimmed.startsWith("#")) continue;
4629
+ const eq = trimmed.indexOf("=");
4630
+ if (eq < 0) continue;
4631
+ if (trimmed.slice(0, eq).trim() !== name) continue;
4632
+ let value = trimmed.slice(eq + 1).trim();
4633
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
4634
+ value = value.slice(1, -1);
4635
+ }
4636
+ return value || null;
4637
+ }
4638
+ }
4639
+ return null;
4640
+ }
4592
4641
  async function findFirst(serviceDir, candidates) {
4593
4642
  for (const rel of candidates) {
4594
4643
  const abs = import_node_path14.default.join(serviceDir, rel);
@@ -4686,6 +4735,14 @@ async function parse3(serviceDir) {
4686
4735
  const config = parseConnectionString(urlMatch[1]);
4687
4736
  if (config) return [{ ...config, sourceFile: schemaPath }];
4688
4737
  }
4738
+ const envMatch = body.match(/url\s*=\s*env\(\s*"([^"]+)"\s*\)/);
4739
+ if (envMatch) {
4740
+ const resolved = await resolveEnvVar(serviceDir, envMatch[1]);
4741
+ if (resolved) {
4742
+ const config = parseConnectionString(resolved);
4743
+ if (config) return [{ ...config, sourceFile: schemaPath }];
4744
+ }
4745
+ }
4689
4746
  return [
4690
4747
  {
4691
4748
  host: `${engine}-prisma`,
@@ -7870,14 +7927,23 @@ var import_types31 = require("@neat.is/types");
7870
7927
  function bucketKey(source, target, type) {
7871
7928
  return `${type}|${source}|${target}`;
7872
7929
  }
7930
+ function bucketSourceFor(graph, edge) {
7931
+ if (edge.type !== import_types31.EdgeType.CONNECTS_TO) return edge.source;
7932
+ const parsed = (0, import_types31.parseFileId)(edge.source);
7933
+ if (!parsed || !graph.hasNode(edge.target)) return edge.source;
7934
+ const target = graph.getNodeAttributes(edge.target);
7935
+ if (target.type !== import_types31.NodeType.DatabaseNode) return edge.source;
7936
+ return (0, import_types31.serviceId)(parsed.service);
7937
+ }
7873
7938
  function bucketEdges(graph) {
7874
7939
  const buckets2 = /* @__PURE__ */ new Map();
7875
7940
  graph.forEachEdge((id, attrs) => {
7876
7941
  const e = attrs;
7877
7942
  const parsed = (0, import_types31.parseEdgeId)(id);
7878
7943
  const provenance = parsed?.provenance ?? e.provenance;
7879
- const key = bucketKey(e.source, e.target, e.type);
7880
- const cur = buckets2.get(key) ?? { source: e.source, target: e.target, type: e.type };
7944
+ const source = bucketSourceFor(graph, e);
7945
+ const key = bucketKey(source, e.target, e.type);
7946
+ const cur = buckets2.get(key) ?? { source, target: e.target, type: e.type };
7881
7947
  switch (provenance) {
7882
7948
  case import_types31.Provenance.EXTRACTED:
7883
7949
  cur.extracted = e;