@shumoku/core 0.2.4 → 0.2.5

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.
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Hierarchical sheet generation utilities
3
+ * Shared logic for building child sheets with export connectors
4
+ */
5
+ import type { LayoutResult, NetworkGraph } from './models/types.js';
6
+ export interface SheetData {
7
+ graph: NetworkGraph;
8
+ layout: LayoutResult;
9
+ }
10
+ export interface LayoutEngine {
11
+ layoutAsync(graph: NetworkGraph): Promise<LayoutResult>;
12
+ }
13
+ /**
14
+ * Check if a node is a virtual export connector
15
+ */
16
+ export declare function isExportNode(nodeId: string): boolean;
17
+ /**
18
+ * Check if a link is a virtual export connector link
19
+ */
20
+ export declare function isExportLink(linkId: string): boolean;
21
+ /**
22
+ * Build hierarchical sheets from a root graph
23
+ *
24
+ * Creates child sheets for each subgraph with:
25
+ * - Filtered nodes (only those belonging to the subgraph)
26
+ * - Internal links (both endpoints in subgraph)
27
+ * - Export connector nodes/links for boundary connections
28
+ *
29
+ * @param graph - Root network graph with subgraphs
30
+ * @param rootLayout - Layout result for the root graph
31
+ * @param layoutEngine - Engine to layout child sheets
32
+ * @returns Map of sheet ID to SheetData (includes 'root')
33
+ */
34
+ export declare function buildHierarchicalSheets(graph: NetworkGraph, rootLayout: LayoutResult, layoutEngine: LayoutEngine): Promise<Map<string, SheetData>>;
35
+ //# sourceMappingURL=hierarchical.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hierarchical.d.ts","sourceRoot":"","sources":["../src/hierarchical.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAQ,YAAY,EAAkB,MAAM,mBAAmB,CAAA;AAazF,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,YAAY,CAAA;IACnB,MAAM,EAAE,YAAY,CAAA;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,WAAW,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,CAAA;CACxD;AAgBD;;GAEG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAEpD;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAEpD;AAMD;;;;;;;;;;;;GAYG;AACH,wBAAsB,uBAAuB,CAC3C,KAAK,EAAE,YAAY,EACnB,UAAU,EAAE,YAAY,EACxB,YAAY,EAAE,YAAY,GACzB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,CAsBjC"}
@@ -0,0 +1,176 @@
1
+ /**
2
+ * Hierarchical sheet generation utilities
3
+ * Shared logic for building child sheets with export connectors
4
+ */
5
+ // ============================================
6
+ // Constants
7
+ // ============================================
8
+ const EXPORT_NODE_PREFIX = '__export_';
9
+ const EXPORT_LINK_PREFIX = '__export_link_';
10
+ // ============================================
11
+ // Type Guards
12
+ // ============================================
13
+ /**
14
+ * Check if a node is a virtual export connector
15
+ */
16
+ export function isExportNode(nodeId) {
17
+ return nodeId.startsWith(EXPORT_NODE_PREFIX);
18
+ }
19
+ /**
20
+ * Check if a link is a virtual export connector link
21
+ */
22
+ export function isExportLink(linkId) {
23
+ return linkId.startsWith(EXPORT_LINK_PREFIX);
24
+ }
25
+ // ============================================
26
+ // Main Function
27
+ // ============================================
28
+ /**
29
+ * Build hierarchical sheets from a root graph
30
+ *
31
+ * Creates child sheets for each subgraph with:
32
+ * - Filtered nodes (only those belonging to the subgraph)
33
+ * - Internal links (both endpoints in subgraph)
34
+ * - Export connector nodes/links for boundary connections
35
+ *
36
+ * @param graph - Root network graph with subgraphs
37
+ * @param rootLayout - Layout result for the root graph
38
+ * @param layoutEngine - Engine to layout child sheets
39
+ * @returns Map of sheet ID to SheetData (includes 'root')
40
+ */
41
+ export async function buildHierarchicalSheets(graph, rootLayout, layoutEngine) {
42
+ const sheets = new Map();
43
+ // Add root sheet
44
+ sheets.set('root', { graph, layout: rootLayout });
45
+ if (!graph.subgraphs || graph.subgraphs.length === 0) {
46
+ return sheets;
47
+ }
48
+ // Mark subgraphs as clickable
49
+ for (const sg of graph.subgraphs) {
50
+ sg.file = sg.id;
51
+ }
52
+ // Build child sheets
53
+ for (const sg of graph.subgraphs) {
54
+ const childSheet = await buildChildSheet(graph, sg, layoutEngine);
55
+ sheets.set(sg.id, childSheet);
56
+ }
57
+ return sheets;
58
+ }
59
+ // ============================================
60
+ // Internal Functions
61
+ // ============================================
62
+ async function buildChildSheet(rootGraph, subgraph, layoutEngine) {
63
+ // Get nodes belonging to this subgraph
64
+ const childNodes = rootGraph.nodes.filter((n) => n.parent === subgraph.id);
65
+ const childNodeIds = new Set(childNodes.map((n) => n.id));
66
+ // Get internal links (both endpoints in subgraph)
67
+ const childLinks = rootGraph.links.filter((l) => {
68
+ const fromNode = typeof l.from === 'string' ? l.from : l.from.node;
69
+ const toNode = typeof l.to === 'string' ? l.to : l.to.node;
70
+ return childNodeIds.has(fromNode) && childNodeIds.has(toNode);
71
+ });
72
+ // Generate export connectors for boundary connections
73
+ const { exportNodes, exportLinks } = generateExportConnectors(rootGraph, subgraph.id, childNodeIds);
74
+ // Build child graph
75
+ const childGraph = {
76
+ ...rootGraph,
77
+ name: subgraph.label,
78
+ nodes: [
79
+ ...childNodes.map((n) => ({ ...n, parent: undefined })),
80
+ ...exportNodes,
81
+ ],
82
+ links: [...childLinks, ...exportLinks],
83
+ subgraphs: undefined,
84
+ };
85
+ // Layout child sheet
86
+ const childLayout = await layoutEngine.layoutAsync(childGraph);
87
+ return { graph: childGraph, layout: childLayout };
88
+ }
89
+ function generateExportConnectors(rootGraph, subgraphId, childNodeIds) {
90
+ const exportNodes = [];
91
+ const exportLinks = [];
92
+ const exportPoints = new Map();
93
+ // Find boundary links
94
+ for (const link of rootGraph.links) {
95
+ const fromNode = typeof link.from === 'string' ? link.from : link.from.node;
96
+ const toNode = typeof link.to === 'string' ? link.to : link.to.node;
97
+ const fromPort = typeof link.from === 'object' ? link.from.port : undefined;
98
+ const toPort = typeof link.to === 'object' ? link.to.port : undefined;
99
+ const fromInside = childNodeIds.has(fromNode);
100
+ const toInside = childNodeIds.has(toNode);
101
+ if (fromInside && !toInside) {
102
+ // Outgoing connection
103
+ const key = `${subgraphId}:${fromNode}:${fromPort || ''}`;
104
+ if (!exportPoints.has(key)) {
105
+ const destSubgraph = findNodeSubgraph(rootGraph, toNode);
106
+ exportPoints.set(key, {
107
+ subgraphId,
108
+ device: fromNode,
109
+ port: fromPort,
110
+ destSubgraphLabel: destSubgraph?.label || toNode,
111
+ destDevice: toNode,
112
+ destPort: toPort,
113
+ isSource: true,
114
+ });
115
+ }
116
+ }
117
+ else if (!fromInside && toInside) {
118
+ // Incoming connection
119
+ const key = `${subgraphId}:${toNode}:${toPort || ''}`;
120
+ if (!exportPoints.has(key)) {
121
+ const destSubgraph = findNodeSubgraph(rootGraph, fromNode);
122
+ exportPoints.set(key, {
123
+ subgraphId,
124
+ device: toNode,
125
+ port: toPort,
126
+ destSubgraphLabel: destSubgraph?.label || fromNode,
127
+ destDevice: fromNode,
128
+ destPort: fromPort,
129
+ isSource: false,
130
+ });
131
+ }
132
+ }
133
+ }
134
+ // Create export nodes and links
135
+ for (const [key, exportPoint] of exportPoints) {
136
+ const exportId = key.replace(/:/g, '_');
137
+ // Export node
138
+ exportNodes.push({
139
+ id: `${EXPORT_NODE_PREFIX}${exportId}`,
140
+ label: exportPoint.destSubgraphLabel,
141
+ shape: 'stadium',
142
+ metadata: {
143
+ _isExport: true,
144
+ _destSubgraph: exportPoint.destSubgraphLabel,
145
+ _destDevice: exportPoint.destDevice,
146
+ _destPort: exportPoint.destPort,
147
+ _isSource: exportPoint.isSource,
148
+ },
149
+ });
150
+ // Export link
151
+ const exportNodeId = `${EXPORT_NODE_PREFIX}${exportId}`;
152
+ const deviceEndpoint = exportPoint.port
153
+ ? { node: exportPoint.device, port: exportPoint.port }
154
+ : exportPoint.device;
155
+ exportLinks.push({
156
+ id: `${EXPORT_LINK_PREFIX}${exportId}`,
157
+ from: exportPoint.isSource ? deviceEndpoint : exportNodeId,
158
+ to: exportPoint.isSource ? exportNodeId : deviceEndpoint,
159
+ type: 'dashed',
160
+ arrow: 'forward',
161
+ metadata: {
162
+ _destSubgraphLabel: exportPoint.destSubgraphLabel,
163
+ _destDevice: exportPoint.destDevice,
164
+ _destPort: exportPoint.destPort,
165
+ },
166
+ });
167
+ }
168
+ return { exportNodes, exportLinks };
169
+ }
170
+ function findNodeSubgraph(graph, nodeId) {
171
+ const node = graph.nodes.find((n) => n.id === nodeId);
172
+ if (!node?.parent)
173
+ return undefined;
174
+ return graph.subgraphs?.find((s) => s.id === node.parent);
175
+ }
176
+ //# sourceMappingURL=hierarchical.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hierarchical.js","sourceRoot":"","sources":["../src/hierarchical.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -2,6 +2,7 @@
2
2
  * @shumoku/core - Network topology visualization core library
3
3
  */
4
4
  export * from './constants.js';
5
+ export * from './hierarchical.js';
5
6
  export * from './icons/index.js';
6
7
  export * from './layout/index.js';
7
8
  export * from './models/index.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,cAAc,gBAAgB,CAAA;AAE9B,cAAc,kBAAkB,CAAA;AAEhC,cAAc,mBAAmB,CAAA;AAEjC,cAAc,mBAAmB,CAAA;AAEjC,cAAc,mBAAmB,CAAA;AAGjC,eAAO,MAAM,OAAO,UAAU,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,cAAc,gBAAgB,CAAA;AAE9B,cAAc,mBAAmB,CAAA;AAEjC,cAAc,kBAAkB,CAAA;AAEhC,cAAc,mBAAmB,CAAA;AAEjC,cAAc,mBAAmB,CAAA;AAEjC,cAAc,mBAAmB,CAAA;AAGjC,eAAO,MAAM,OAAO,UAAU,CAAA"}
package/dist/index.js CHANGED
@@ -3,6 +3,8 @@
3
3
  */
4
4
  // Constants
5
5
  export * from './constants.js';
6
+ // Hierarchical
7
+ export * from './hierarchical.js';
6
8
  // Icons
7
9
  export * from './icons/index.js';
8
10
  // Layout
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,YAAY;AACZ,cAAc,gBAAgB,CAAA;AAC9B,QAAQ;AACR,cAAc,kBAAkB,CAAA;AAChC,SAAS;AACT,cAAc,mBAAmB,CAAA;AACjC,SAAS;AACT,cAAc,mBAAmB,CAAA;AACjC,SAAS;AACT,cAAc,mBAAmB,CAAA;AAEjC,UAAU;AACV,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,YAAY;AACZ,cAAc,gBAAgB,CAAA;AAC9B,eAAe;AACf,cAAc,mBAAmB,CAAA;AACjC,QAAQ;AACR,cAAc,kBAAkB,CAAA;AAChC,SAAS;AACT,cAAc,mBAAmB,CAAA;AACjC,SAAS;AACT,cAAc,mBAAmB,CAAA;AACjC,SAAS;AACT,cAAc,mBAAmB,CAAA;AAEjC,UAAU;AACV,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shumoku/core",
3
- "version": "0.2.4",
3
+ "version": "0.2.5",
4
4
  "description": "Core library for shumoku network topology visualization",
5
5
  "license": "MIT",
6
6
  "author": "konoe-akitoshi",
@@ -0,0 +1,243 @@
1
+ /**
2
+ * Hierarchical sheet generation utilities
3
+ * Shared logic for building child sheets with export connectors
4
+ */
5
+
6
+ import type { LayoutResult, Link, NetworkGraph, Node, Subgraph } from './models/types.js'
7
+
8
+ // ============================================
9
+ // Constants
10
+ // ============================================
11
+
12
+ const EXPORT_NODE_PREFIX = '__export_'
13
+ const EXPORT_LINK_PREFIX = '__export_link_'
14
+
15
+ // ============================================
16
+ // Types
17
+ // ============================================
18
+
19
+ export interface SheetData {
20
+ graph: NetworkGraph
21
+ layout: LayoutResult
22
+ }
23
+
24
+ export interface LayoutEngine {
25
+ layoutAsync(graph: NetworkGraph): Promise<LayoutResult>
26
+ }
27
+
28
+ interface ExportPoint {
29
+ subgraphId: string
30
+ device: string
31
+ port?: string
32
+ destSubgraphLabel: string
33
+ destDevice: string
34
+ destPort?: string
35
+ isSource: boolean
36
+ }
37
+
38
+ // ============================================
39
+ // Type Guards
40
+ // ============================================
41
+
42
+ /**
43
+ * Check if a node is a virtual export connector
44
+ */
45
+ export function isExportNode(nodeId: string): boolean {
46
+ return nodeId.startsWith(EXPORT_NODE_PREFIX)
47
+ }
48
+
49
+ /**
50
+ * Check if a link is a virtual export connector link
51
+ */
52
+ export function isExportLink(linkId: string): boolean {
53
+ return linkId.startsWith(EXPORT_LINK_PREFIX)
54
+ }
55
+
56
+ // ============================================
57
+ // Main Function
58
+ // ============================================
59
+
60
+ /**
61
+ * Build hierarchical sheets from a root graph
62
+ *
63
+ * Creates child sheets for each subgraph with:
64
+ * - Filtered nodes (only those belonging to the subgraph)
65
+ * - Internal links (both endpoints in subgraph)
66
+ * - Export connector nodes/links for boundary connections
67
+ *
68
+ * @param graph - Root network graph with subgraphs
69
+ * @param rootLayout - Layout result for the root graph
70
+ * @param layoutEngine - Engine to layout child sheets
71
+ * @returns Map of sheet ID to SheetData (includes 'root')
72
+ */
73
+ export async function buildHierarchicalSheets(
74
+ graph: NetworkGraph,
75
+ rootLayout: LayoutResult,
76
+ layoutEngine: LayoutEngine,
77
+ ): Promise<Map<string, SheetData>> {
78
+ const sheets = new Map<string, SheetData>()
79
+
80
+ // Add root sheet
81
+ sheets.set('root', { graph, layout: rootLayout })
82
+
83
+ if (!graph.subgraphs || graph.subgraphs.length === 0) {
84
+ return sheets
85
+ }
86
+
87
+ // Mark subgraphs as clickable
88
+ for (const sg of graph.subgraphs) {
89
+ sg.file = sg.id
90
+ }
91
+
92
+ // Build child sheets
93
+ for (const sg of graph.subgraphs) {
94
+ const childSheet = await buildChildSheet(graph, sg, layoutEngine)
95
+ sheets.set(sg.id, childSheet)
96
+ }
97
+
98
+ return sheets
99
+ }
100
+
101
+ // ============================================
102
+ // Internal Functions
103
+ // ============================================
104
+
105
+ async function buildChildSheet(
106
+ rootGraph: NetworkGraph,
107
+ subgraph: Subgraph,
108
+ layoutEngine: LayoutEngine,
109
+ ): Promise<SheetData> {
110
+ // Get nodes belonging to this subgraph
111
+ const childNodes = rootGraph.nodes.filter((n) => n.parent === subgraph.id)
112
+ const childNodeIds = new Set(childNodes.map((n) => n.id))
113
+
114
+ // Get internal links (both endpoints in subgraph)
115
+ const childLinks = rootGraph.links.filter((l) => {
116
+ const fromNode = typeof l.from === 'string' ? l.from : l.from.node
117
+ const toNode = typeof l.to === 'string' ? l.to : l.to.node
118
+ return childNodeIds.has(fromNode) && childNodeIds.has(toNode)
119
+ })
120
+
121
+ // Generate export connectors for boundary connections
122
+ const { exportNodes, exportLinks } = generateExportConnectors(
123
+ rootGraph,
124
+ subgraph.id,
125
+ childNodeIds,
126
+ )
127
+
128
+ // Build child graph
129
+ const childGraph: NetworkGraph = {
130
+ ...rootGraph,
131
+ name: subgraph.label,
132
+ nodes: [
133
+ ...childNodes.map((n) => ({ ...n, parent: undefined })),
134
+ ...exportNodes,
135
+ ],
136
+ links: [...childLinks, ...exportLinks],
137
+ subgraphs: undefined,
138
+ }
139
+
140
+ // Layout child sheet
141
+ const childLayout = await layoutEngine.layoutAsync(childGraph)
142
+
143
+ return { graph: childGraph, layout: childLayout }
144
+ }
145
+
146
+ function generateExportConnectors(
147
+ rootGraph: NetworkGraph,
148
+ subgraphId: string,
149
+ childNodeIds: Set<string>,
150
+ ): { exportNodes: Node[]; exportLinks: Link[] } {
151
+ const exportNodes: Node[] = []
152
+ const exportLinks: Link[] = []
153
+ const exportPoints = new Map<string, ExportPoint>()
154
+
155
+ // Find boundary links
156
+ for (const link of rootGraph.links) {
157
+ const fromNode = typeof link.from === 'string' ? link.from : link.from.node
158
+ const toNode = typeof link.to === 'string' ? link.to : link.to.node
159
+ const fromPort = typeof link.from === 'object' ? link.from.port : undefined
160
+ const toPort = typeof link.to === 'object' ? link.to.port : undefined
161
+
162
+ const fromInside = childNodeIds.has(fromNode)
163
+ const toInside = childNodeIds.has(toNode)
164
+
165
+ if (fromInside && !toInside) {
166
+ // Outgoing connection
167
+ const key = `${subgraphId}:${fromNode}:${fromPort || ''}`
168
+ if (!exportPoints.has(key)) {
169
+ const destSubgraph = findNodeSubgraph(rootGraph, toNode)
170
+ exportPoints.set(key, {
171
+ subgraphId,
172
+ device: fromNode,
173
+ port: fromPort,
174
+ destSubgraphLabel: destSubgraph?.label || toNode,
175
+ destDevice: toNode,
176
+ destPort: toPort,
177
+ isSource: true,
178
+ })
179
+ }
180
+ } else if (!fromInside && toInside) {
181
+ // Incoming connection
182
+ const key = `${subgraphId}:${toNode}:${toPort || ''}`
183
+ if (!exportPoints.has(key)) {
184
+ const destSubgraph = findNodeSubgraph(rootGraph, fromNode)
185
+ exportPoints.set(key, {
186
+ subgraphId,
187
+ device: toNode,
188
+ port: toPort,
189
+ destSubgraphLabel: destSubgraph?.label || fromNode,
190
+ destDevice: fromNode,
191
+ destPort: fromPort,
192
+ isSource: false,
193
+ })
194
+ }
195
+ }
196
+ }
197
+
198
+ // Create export nodes and links
199
+ for (const [key, exportPoint] of exportPoints) {
200
+ const exportId = key.replace(/:/g, '_')
201
+
202
+ // Export node
203
+ exportNodes.push({
204
+ id: `${EXPORT_NODE_PREFIX}${exportId}`,
205
+ label: exportPoint.destSubgraphLabel,
206
+ shape: 'stadium',
207
+ metadata: {
208
+ _isExport: true,
209
+ _destSubgraph: exportPoint.destSubgraphLabel,
210
+ _destDevice: exportPoint.destDevice,
211
+ _destPort: exportPoint.destPort,
212
+ _isSource: exportPoint.isSource,
213
+ },
214
+ })
215
+
216
+ // Export link
217
+ const exportNodeId = `${EXPORT_NODE_PREFIX}${exportId}`
218
+ const deviceEndpoint = exportPoint.port
219
+ ? { node: exportPoint.device, port: exportPoint.port }
220
+ : exportPoint.device
221
+
222
+ exportLinks.push({
223
+ id: `${EXPORT_LINK_PREFIX}${exportId}`,
224
+ from: exportPoint.isSource ? deviceEndpoint : exportNodeId,
225
+ to: exportPoint.isSource ? exportNodeId : deviceEndpoint,
226
+ type: 'dashed',
227
+ arrow: 'forward',
228
+ metadata: {
229
+ _destSubgraphLabel: exportPoint.destSubgraphLabel,
230
+ _destDevice: exportPoint.destDevice,
231
+ _destPort: exportPoint.destPort,
232
+ },
233
+ })
234
+ }
235
+
236
+ return { exportNodes, exportLinks }
237
+ }
238
+
239
+ function findNodeSubgraph(graph: NetworkGraph, nodeId: string): Subgraph | undefined {
240
+ const node = graph.nodes.find((n) => n.id === nodeId)
241
+ if (!node?.parent) return undefined
242
+ return graph.subgraphs?.find((s) => s.id === node.parent)
243
+ }
package/src/index.ts CHANGED
@@ -4,6 +4,8 @@
4
4
 
5
5
  // Constants
6
6
  export * from './constants.js'
7
+ // Hierarchical
8
+ export * from './hierarchical.js'
7
9
  // Icons
8
10
  export * from './icons/index.js'
9
11
  // Layout