next-arch-map 0.1.27 → 0.1.28

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.
@@ -1,6 +1,6 @@
1
1
  import path from "node:path";
2
2
  import ts from "typescript";
3
- import { buildActionNode, buildEdgeKey, buildEndpointNode, buildPageNode, collectStringConstants, ensureNode, getExistingDirectories, getPageRouteFromFile, getSourceFile, getStringLiteralValue, isIgnoredSourceFile, isPageFile, resolveLocalModulePath, resolveProjectRoot, walkDirectory, } from "../utils.js";
3
+ import { buildActionNode, buildEdgeKey, buildEndpointNode, buildPageNode, buildServiceNode, collectStringConstants, ensureNode, getExistingDirectories, getPageRouteFromFile, getSourceFile, getStringLiteralValue, isIgnoredSourceFile, isPageFile, resolveLocalModulePath, resolveProjectRoot, walkDirectory, } from "../utils.js";
4
4
  const DEFAULT_APP_DIRS = ["app", "src/app"];
5
5
  const DEFAULT_EXTRA_SCAN_DIRS = ["src/features", "src/services", "src/lib", "src/hooks"];
6
6
  const DEFAULT_HTTP_CLIENT_IDENTIFIERS = ["fetch", "axios", "apiClient"];
@@ -48,11 +48,26 @@ export async function analyzePagesToEndpoints(options) {
48
48
  const route = getPageRouteFromFile(appDir, filePath);
49
49
  ensureNode(nodes, nodeIds, buildPageNode(route, filePath));
50
50
  for (const call of httpCalls) {
51
+ const pageNode = ensureNode(nodes, nodeIds, buildPageNode(route, filePath));
52
+ // SDK calls create a single service node per client (e.g. "supabase")
53
+ if (call.sdkClient) {
54
+ const serviceNode = ensureNode(nodes, nodeIds, buildServiceNode(call.sdkClient, filePath));
55
+ const edgeKey = buildEdgeKey(pageNode.id, serviceNode.id, "page-service");
56
+ if (!edgeKeys.has(edgeKey)) {
57
+ edgeKeys.add(edgeKey);
58
+ edges.push({
59
+ from: pageNode.id,
60
+ to: serviceNode.id,
61
+ kind: "page-service",
62
+ });
63
+ }
64
+ continue;
65
+ }
66
+ // HTTP calls create endpoint nodes with action intermediaries
51
67
  const nextCallIndex = (callIndexByRoute.get(route) ?? 0) + 1;
52
68
  callIndexByRoute.set(route, nextCallIndex);
53
69
  const actionContext = inferActionContext(call.node, sourceFile, nextCallIndex);
54
70
  const actionId = allocateActionId(route, actionContext.id, actionIdCountByRoute);
55
- const pageNode = ensureNode(nodes, nodeIds, buildPageNode(route, filePath));
56
71
  const actionNode = ensureNode(nodes, nodeIds, buildActionNode(route, actionId, filePath, actionContext.meta));
57
72
  const endpointNode = ensureNode(nodes, nodeIds, buildEndpointNode(call.endpoint, filePath));
58
73
  const pageActionKey = buildEdgeKey(pageNode.id, actionNode.id, "page-action");
@@ -177,7 +192,7 @@ function collectHttpCalls(sourceFile, httpClientIdentifiers, httpClientMethods,
177
192
  if (sdkCall) {
178
193
  calls.push({
179
194
  endpoint: sdkCall.endpoint,
180
- method: sdkCall.method,
195
+ sdkClient: sdkCall.sdkClient,
181
196
  node,
182
197
  });
183
198
  }
@@ -233,8 +248,7 @@ function parseSdkCall(node, sdkClientIdentifiers) {
233
248
  if (segments.length < 2) {
234
249
  return null;
235
250
  }
236
- const endpoint = `${current.text}.${segments.join(".")}`;
237
- return { endpoint };
251
+ return { endpoint: current.text, sdkClient: current.text };
238
252
  }
239
253
  function getEndpointArgument(expression, constMap) {
240
254
  if (!expression) {
package/dist/model.d.ts CHANGED
@@ -1,11 +1,11 @@
1
- export type NodeType = "page" | "endpoint" | "db" | "handler" | "action";
1
+ export type NodeType = "page" | "endpoint" | "db" | "handler" | "action" | "service";
2
2
  export type Node = {
3
3
  id: string;
4
4
  type: NodeType;
5
5
  label: string;
6
6
  meta?: Record<string, any>;
7
7
  };
8
- export type EdgeKind = "page-endpoint" | "endpoint-db" | "endpoint-handler" | "page-action" | "action-endpoint" | "db-relation";
8
+ export type EdgeKind = "page-endpoint" | "endpoint-db" | "endpoint-handler" | "page-action" | "action-endpoint" | "db-relation" | "page-service";
9
9
  export type Edge = {
10
10
  from: string;
11
11
  to: string;
package/dist/utils.d.ts CHANGED
@@ -71,3 +71,12 @@ export declare function buildDbNode(modelName: string, filePath: string): {
71
71
  model: string;
72
72
  };
73
73
  };
74
+ export declare function buildServiceNode(serviceName: string, filePath: string): {
75
+ id: string;
76
+ type: "service";
77
+ label: string;
78
+ meta: {
79
+ filePath: string;
80
+ service: string;
81
+ };
82
+ };
package/dist/utils.js CHANGED
@@ -292,3 +292,14 @@ export function buildDbNode(modelName, filePath) {
292
292
  },
293
293
  };
294
294
  }
295
+ export function buildServiceNode(serviceName, filePath) {
296
+ return {
297
+ id: `service:${serviceName}`,
298
+ type: "service",
299
+ label: serviceName,
300
+ meta: {
301
+ filePath,
302
+ service: serviceName,
303
+ },
304
+ };
305
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "next-arch-map",
3
- "version": "0.1.27",
3
+ "version": "0.1.28",
4
4
  "description": "Static analyzer that builds a multi-layer architecture graph for Next.js-style apps.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -10,9 +10,11 @@ const ALL_NODE_TYPES: NodeType[] = [
10
10
  "endpoint",
11
11
  "handler",
12
12
  "db",
13
+ "service",
13
14
  ];
14
15
  const ALL_EDGE_KINDS: EdgeKind[] = [
15
16
  "page-endpoint",
17
+ "page-service",
16
18
  "endpoint-db",
17
19
  "endpoint-handler",
18
20
  "db-relation",
@@ -16,6 +16,7 @@ const NODE_TYPE_COLORS: Record<NodeType, string> = {
16
16
  endpoint: "bg-emerald-600",
17
17
  handler: "bg-teal-500",
18
18
  db: "bg-red-600",
19
+ service: "bg-violet-600",
19
20
  };
20
21
 
21
22
  export function Filters(props: FiltersProps) {
@@ -7,7 +7,6 @@ import {
7
7
  MiniMap,
8
8
  Position,
9
9
  ReactFlow,
10
- useReactFlow,
11
10
  type Edge as FlowEdge,
12
11
  type Node as FlowNode,
13
12
  type NodeMouseHandler,
@@ -31,13 +30,7 @@ const NODE_COLOR: Record<NodeType, string> = {
31
30
  endpoint: "#059669",
32
31
  db: "#dc2626",
33
32
  handler: "#14b8a6",
34
- };
35
-
36
- const NODE_BORDER: Record<NodeType, string> = {
37
- page: "#2563eb",
38
- endpoint: "#047857",
39
- db: "#b91c1c",
40
- handler: "#0d9488",
33
+ service: "#7c3aed",
41
34
  };
42
35
 
43
36
  const EDGE_COLOR: Record<EdgeKind, string> = {
@@ -47,19 +40,18 @@ const EDGE_COLOR: Record<EdgeKind, string> = {
47
40
  "page-action": "#8b5cf6",
48
41
  "action-endpoint": "#a855f7",
49
42
  "db-relation": "#94a3b8",
43
+ "page-service": "#a78bfa",
50
44
  };
51
45
 
52
46
  const DIFF_BORDER_COLOR: Record<DiffStatus, string> = {
53
47
  added: "#22c55e",
54
48
  removed: "#ef4444",
55
- modified: "#f59e0b",
56
49
  unchanged: "rgba(15, 23, 42, 0.12)",
57
50
  };
58
51
 
59
52
  const DIFF_EDGE_COLOR: Record<DiffStatus, string> = {
60
53
  added: "#22c55e",
61
54
  removed: "#ef4444",
62
- modified: "#f59e0b",
63
55
  unchanged: "#000000",
64
56
  };
65
57
 
@@ -337,7 +329,7 @@ export function GraphView(props: GraphViewProps) {
337
329
 
338
330
  // Compute layout without hover state — this is the expensive part
339
331
  const { flowNodes: baseFlowNodes, flowEdges: baseFlowEdges } = useMemo(() => {
340
- const typeOrder: NodeType[] = ["page", "endpoint", "handler", "db"];
332
+ const typeOrder: NodeType[] = ["page", "endpoint", "handler", "db", "service"];
341
333
  const visibleNodes = graph.nodes.filter((node) => visibleNodeTypes.has(node.type));
342
334
  const visibleNodeIds = new Set(visibleNodes.map((node) => node.id));
343
335
  const nodesByType = new Map<NodeType, typeof visibleNodes>(
@@ -8,9 +8,8 @@ const TYPE_BADGE_COLORS: Record<NodeType, string> = {
8
8
  page: "bg-blue-100 text-blue-700",
9
9
  endpoint: "bg-emerald-100 text-emerald-700",
10
10
  handler: "bg-teal-100 text-teal-700",
11
- action: "bg-amber-100 text-amber-700",
12
11
  db: "bg-red-100 text-red-700",
13
- ui: "bg-orange-100 text-orange-700",
12
+ service: "bg-violet-100 text-violet-700",
14
13
  };
15
14
 
16
15
  export function NodeDetails({ node }: NodeDetailsProps) {
@@ -46,11 +45,11 @@ export function NodeDetails({ node }: NodeDetailsProps) {
46
45
  {node.id}
47
46
  </div>
48
47
 
49
- {(node.meta?.descriptionLong || node.meta?.description) && (
48
+ {(node.meta?.descriptionLong ?? node.meta?.description) ? (
50
49
  <p className="text-xs text-slate-600 leading-relaxed">
51
50
  {String(node.meta.descriptionLong ?? node.meta.description)}
52
51
  </p>
53
- )}
52
+ ) : null}
54
53
 
55
54
  {filePath !== undefined && filePath !== null && (
56
55
  <div className="text-[11px] text-slate-500">
@@ -59,7 +58,7 @@ export function NodeDetails({ node }: NodeDetailsProps) {
59
58
  </div>
60
59
  )}
61
60
 
62
- {node.meta?.screenshot && (
61
+ {node.meta?.screenshot ? (
63
62
  <div className="mt-2">
64
63
  <img
65
64
  src={String(node.meta.screenshot)}
@@ -67,7 +66,7 @@ export function NodeDetails({ node }: NodeDetailsProps) {
67
66
  className="w-full rounded-md border border-slate-200"
68
67
  />
69
68
  </div>
70
- )}
69
+ ) : null}
71
70
 
72
71
  {node.meta && (
73
72
  <pre className="mt-2 p-2.5 rounded-md bg-slate-50 border border-slate-100 text-[11px] font-mono text-slate-600 max-h-40 overflow-auto whitespace-pre-wrap break-all">
@@ -2,7 +2,8 @@ export type NodeType =
2
2
  | "page"
3
3
  | "endpoint"
4
4
  | "db"
5
- | "handler";
5
+ | "handler"
6
+ | "service";
6
7
 
7
8
  export type Node = {
8
9
  id: string;
@@ -17,7 +18,8 @@ export type EdgeKind =
17
18
  | "endpoint-handler"
18
19
  | "page-action"
19
20
  | "action-endpoint"
20
- | "db-relation";
21
+ | "db-relation"
22
+ | "page-service";
21
23
 
22
24
  export type Edge = {
23
25
  from: string;