@principal-ai/principal-view-core 0.5.6

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.
Files changed (204) hide show
  1. package/README.md +126 -0
  2. package/dist/ConfigurationLoader.d.ts +76 -0
  3. package/dist/ConfigurationLoader.d.ts.map +1 -0
  4. package/dist/ConfigurationLoader.js +144 -0
  5. package/dist/ConfigurationLoader.js.map +1 -0
  6. package/dist/ConfigurationValidator.d.ts +31 -0
  7. package/dist/ConfigurationValidator.d.ts.map +1 -0
  8. package/dist/ConfigurationValidator.js +242 -0
  9. package/dist/ConfigurationValidator.js.map +1 -0
  10. package/dist/EventProcessor.d.ts +49 -0
  11. package/dist/EventProcessor.d.ts.map +1 -0
  12. package/dist/EventProcessor.js +215 -0
  13. package/dist/EventProcessor.js.map +1 -0
  14. package/dist/EventRecorderService.d.ts +305 -0
  15. package/dist/EventRecorderService.d.ts.map +1 -0
  16. package/dist/EventRecorderService.js +463 -0
  17. package/dist/EventRecorderService.js.map +1 -0
  18. package/dist/LibraryLoader.d.ts +63 -0
  19. package/dist/LibraryLoader.d.ts.map +1 -0
  20. package/dist/LibraryLoader.js +188 -0
  21. package/dist/LibraryLoader.js.map +1 -0
  22. package/dist/PathBasedEventProcessor.d.ts +90 -0
  23. package/dist/PathBasedEventProcessor.d.ts.map +1 -0
  24. package/dist/PathBasedEventProcessor.js +239 -0
  25. package/dist/PathBasedEventProcessor.js.map +1 -0
  26. package/dist/SessionManager.d.ts +194 -0
  27. package/dist/SessionManager.d.ts.map +1 -0
  28. package/dist/SessionManager.js +299 -0
  29. package/dist/SessionManager.js.map +1 -0
  30. package/dist/ValidationEngine.d.ts +31 -0
  31. package/dist/ValidationEngine.d.ts.map +1 -0
  32. package/dist/ValidationEngine.js +158 -0
  33. package/dist/ValidationEngine.js.map +1 -0
  34. package/dist/helpers/GraphInstrumentationHelper.d.ts +93 -0
  35. package/dist/helpers/GraphInstrumentationHelper.d.ts.map +1 -0
  36. package/dist/helpers/GraphInstrumentationHelper.js +248 -0
  37. package/dist/helpers/GraphInstrumentationHelper.js.map +1 -0
  38. package/dist/index.d.ts +33 -0
  39. package/dist/index.d.ts.map +1 -0
  40. package/dist/index.js +34 -0
  41. package/dist/index.js.map +1 -0
  42. package/dist/rules/config.d.ts +57 -0
  43. package/dist/rules/config.d.ts.map +1 -0
  44. package/dist/rules/config.js +382 -0
  45. package/dist/rules/config.js.map +1 -0
  46. package/dist/rules/engine.d.ts +70 -0
  47. package/dist/rules/engine.d.ts.map +1 -0
  48. package/dist/rules/engine.js +252 -0
  49. package/dist/rules/engine.js.map +1 -0
  50. package/dist/rules/implementations/connection-type-references.d.ts +7 -0
  51. package/dist/rules/implementations/connection-type-references.d.ts.map +1 -0
  52. package/dist/rules/implementations/connection-type-references.js +104 -0
  53. package/dist/rules/implementations/connection-type-references.js.map +1 -0
  54. package/dist/rules/implementations/dead-end-states.d.ts +17 -0
  55. package/dist/rules/implementations/dead-end-states.d.ts.map +1 -0
  56. package/dist/rules/implementations/dead-end-states.js +72 -0
  57. package/dist/rules/implementations/dead-end-states.js.map +1 -0
  58. package/dist/rules/implementations/index.d.ts +24 -0
  59. package/dist/rules/implementations/index.d.ts.map +1 -0
  60. package/dist/rules/implementations/index.js +62 -0
  61. package/dist/rules/implementations/index.js.map +1 -0
  62. package/dist/rules/implementations/library-node-type-match.d.ts +17 -0
  63. package/dist/rules/implementations/library-node-type-match.d.ts.map +1 -0
  64. package/dist/rules/implementations/library-node-type-match.js +123 -0
  65. package/dist/rules/implementations/library-node-type-match.js.map +1 -0
  66. package/dist/rules/implementations/minimum-node-sources.d.ts +22 -0
  67. package/dist/rules/implementations/minimum-node-sources.d.ts.map +1 -0
  68. package/dist/rules/implementations/minimum-node-sources.js +54 -0
  69. package/dist/rules/implementations/minimum-node-sources.js.map +1 -0
  70. package/dist/rules/implementations/no-unknown-fields.d.ts +7 -0
  71. package/dist/rules/implementations/no-unknown-fields.d.ts.map +1 -0
  72. package/dist/rules/implementations/no-unknown-fields.js +211 -0
  73. package/dist/rules/implementations/no-unknown-fields.js.map +1 -0
  74. package/dist/rules/implementations/orphaned-edge-types.d.ts +7 -0
  75. package/dist/rules/implementations/orphaned-edge-types.d.ts.map +1 -0
  76. package/dist/rules/implementations/orphaned-edge-types.js +47 -0
  77. package/dist/rules/implementations/orphaned-edge-types.js.map +1 -0
  78. package/dist/rules/implementations/orphaned-node-types.d.ts +7 -0
  79. package/dist/rules/implementations/orphaned-node-types.d.ts.map +1 -0
  80. package/dist/rules/implementations/orphaned-node-types.js +50 -0
  81. package/dist/rules/implementations/orphaned-node-types.js.map +1 -0
  82. package/dist/rules/implementations/required-metadata.d.ts +7 -0
  83. package/dist/rules/implementations/required-metadata.d.ts.map +1 -0
  84. package/dist/rules/implementations/required-metadata.js +57 -0
  85. package/dist/rules/implementations/required-metadata.js.map +1 -0
  86. package/dist/rules/implementations/state-transition-references.d.ts +7 -0
  87. package/dist/rules/implementations/state-transition-references.d.ts.map +1 -0
  88. package/dist/rules/implementations/state-transition-references.js +135 -0
  89. package/dist/rules/implementations/state-transition-references.js.map +1 -0
  90. package/dist/rules/implementations/unreachable-states.d.ts +7 -0
  91. package/dist/rules/implementations/unreachable-states.d.ts.map +1 -0
  92. package/dist/rules/implementations/unreachable-states.js +80 -0
  93. package/dist/rules/implementations/unreachable-states.js.map +1 -0
  94. package/dist/rules/implementations/valid-action-patterns.d.ts +17 -0
  95. package/dist/rules/implementations/valid-action-patterns.d.ts.map +1 -0
  96. package/dist/rules/implementations/valid-action-patterns.js +109 -0
  97. package/dist/rules/implementations/valid-action-patterns.js.map +1 -0
  98. package/dist/rules/implementations/valid-color-format.d.ts +7 -0
  99. package/dist/rules/implementations/valid-color-format.d.ts.map +1 -0
  100. package/dist/rules/implementations/valid-color-format.js +91 -0
  101. package/dist/rules/implementations/valid-color-format.js.map +1 -0
  102. package/dist/rules/implementations/valid-edge-types.d.ts +7 -0
  103. package/dist/rules/implementations/valid-edge-types.d.ts.map +1 -0
  104. package/dist/rules/implementations/valid-edge-types.js +244 -0
  105. package/dist/rules/implementations/valid-edge-types.js.map +1 -0
  106. package/dist/rules/implementations/valid-node-types.d.ts +7 -0
  107. package/dist/rules/implementations/valid-node-types.d.ts.map +1 -0
  108. package/dist/rules/implementations/valid-node-types.js +175 -0
  109. package/dist/rules/implementations/valid-node-types.js.map +1 -0
  110. package/dist/rules/index.d.ts +28 -0
  111. package/dist/rules/index.d.ts.map +1 -0
  112. package/dist/rules/index.js +45 -0
  113. package/dist/rules/index.js.map +1 -0
  114. package/dist/rules/types.d.ts +309 -0
  115. package/dist/rules/types.d.ts.map +1 -0
  116. package/dist/rules/types.js +35 -0
  117. package/dist/rules/types.js.map +1 -0
  118. package/dist/types/canvas.d.ts +409 -0
  119. package/dist/types/canvas.d.ts.map +1 -0
  120. package/dist/types/canvas.js +70 -0
  121. package/dist/types/canvas.js.map +1 -0
  122. package/dist/types/index.d.ts +311 -0
  123. package/dist/types/index.d.ts.map +1 -0
  124. package/dist/types/index.js +13 -0
  125. package/dist/types/index.js.map +1 -0
  126. package/dist/types/library.d.ts +185 -0
  127. package/dist/types/library.d.ts.map +1 -0
  128. package/dist/types/library.js +15 -0
  129. package/dist/types/library.js.map +1 -0
  130. package/dist/types/path-based-config.d.ts +230 -0
  131. package/dist/types/path-based-config.d.ts.map +1 -0
  132. package/dist/types/path-based-config.js +9 -0
  133. package/dist/types/path-based-config.js.map +1 -0
  134. package/dist/utils/CanvasConverter.d.ts +118 -0
  135. package/dist/utils/CanvasConverter.d.ts.map +1 -0
  136. package/dist/utils/CanvasConverter.js +315 -0
  137. package/dist/utils/CanvasConverter.js.map +1 -0
  138. package/dist/utils/GraphConverter.d.ts +18 -0
  139. package/dist/utils/GraphConverter.d.ts.map +1 -0
  140. package/dist/utils/GraphConverter.js +61 -0
  141. package/dist/utils/GraphConverter.js.map +1 -0
  142. package/dist/utils/LibraryConverter.d.ts +113 -0
  143. package/dist/utils/LibraryConverter.d.ts.map +1 -0
  144. package/dist/utils/LibraryConverter.js +166 -0
  145. package/dist/utils/LibraryConverter.js.map +1 -0
  146. package/dist/utils/PathMatcher.d.ts +55 -0
  147. package/dist/utils/PathMatcher.d.ts.map +1 -0
  148. package/dist/utils/PathMatcher.js +172 -0
  149. package/dist/utils/PathMatcher.js.map +1 -0
  150. package/dist/utils/YamlParser.d.ts +36 -0
  151. package/dist/utils/YamlParser.d.ts.map +1 -0
  152. package/dist/utils/YamlParser.js +63 -0
  153. package/dist/utils/YamlParser.js.map +1 -0
  154. package/package.json +47 -0
  155. package/src/ConfigurationLoader.test.ts +490 -0
  156. package/src/ConfigurationLoader.ts +185 -0
  157. package/src/ConfigurationValidator.test.ts +200 -0
  158. package/src/ConfigurationValidator.ts +283 -0
  159. package/src/EventProcessor.test.ts +405 -0
  160. package/src/EventProcessor.ts +250 -0
  161. package/src/EventRecorderService.test.ts +541 -0
  162. package/src/EventRecorderService.ts +744 -0
  163. package/src/LibraryLoader.ts +215 -0
  164. package/src/PathBasedEventProcessor.test.ts +567 -0
  165. package/src/PathBasedEventProcessor.ts +332 -0
  166. package/src/SessionManager.test.ts +424 -0
  167. package/src/SessionManager.ts +470 -0
  168. package/src/ValidationEngine.test.ts +371 -0
  169. package/src/ValidationEngine.ts +196 -0
  170. package/src/helpers/GraphInstrumentationHelper.test.ts +340 -0
  171. package/src/helpers/GraphInstrumentationHelper.ts +326 -0
  172. package/src/index.ts +85 -0
  173. package/src/rules/config.test.ts +278 -0
  174. package/src/rules/config.ts +459 -0
  175. package/src/rules/engine.test.ts +332 -0
  176. package/src/rules/engine.ts +318 -0
  177. package/src/rules/implementations/connection-type-references.ts +117 -0
  178. package/src/rules/implementations/dead-end-states.ts +101 -0
  179. package/src/rules/implementations/index.ts +73 -0
  180. package/src/rules/implementations/library-node-type-match.ts +148 -0
  181. package/src/rules/implementations/minimum-node-sources.ts +82 -0
  182. package/src/rules/implementations/no-unknown-fields.ts +342 -0
  183. package/src/rules/implementations/orphaned-edge-types.ts +55 -0
  184. package/src/rules/implementations/orphaned-node-types.ts +58 -0
  185. package/src/rules/implementations/required-metadata.ts +64 -0
  186. package/src/rules/implementations/state-transition-references.ts +151 -0
  187. package/src/rules/implementations/unreachable-states.ts +94 -0
  188. package/src/rules/implementations/valid-action-patterns.ts +136 -0
  189. package/src/rules/implementations/valid-color-format.ts +140 -0
  190. package/src/rules/implementations/valid-edge-types.ts +258 -0
  191. package/src/rules/implementations/valid-node-types.ts +189 -0
  192. package/src/rules/index.ts +95 -0
  193. package/src/rules/types.ts +426 -0
  194. package/src/types/canvas.ts +496 -0
  195. package/src/types/index.ts +382 -0
  196. package/src/types/library.ts +233 -0
  197. package/src/types/path-based-config.ts +281 -0
  198. package/src/utils/CanvasConverter.ts +431 -0
  199. package/src/utils/GraphConverter.test.ts +195 -0
  200. package/src/utils/GraphConverter.ts +71 -0
  201. package/src/utils/LibraryConverter.ts +245 -0
  202. package/src/utils/PathMatcher.test.ts +148 -0
  203. package/src/utils/PathMatcher.ts +183 -0
  204. package/src/utils/YamlParser.ts +75 -0
@@ -0,0 +1,195 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+ import { GraphConverter } from './GraphConverter';
3
+ import type { PathBasedGraphConfiguration } from '../types/path-based-config';
4
+
5
+ describe('GraphConverter', () => {
6
+ test('should convert simple config to nodes and edges', () => {
7
+ const config: PathBasedGraphConfiguration = {
8
+ metadata: {
9
+ name: 'Test Config',
10
+ version: '1.0.0',
11
+ },
12
+ nodeTypes: {
13
+ 'node-a': {
14
+ shape: 'circle',
15
+ icon: 'user',
16
+ color: '#3b82f6',
17
+ dataSchema: {},
18
+ },
19
+ 'node-b': {
20
+ shape: 'rectangle',
21
+ icon: 'server',
22
+ color: '#8b5cf6',
23
+ dataSchema: {},
24
+ },
25
+ },
26
+ edgeTypes: {
27
+ 'connection': {
28
+ style: 'solid',
29
+ color: '#64748b',
30
+ width: 2,
31
+ directed: true,
32
+ },
33
+ },
34
+ allowedConnections: [
35
+ {
36
+ from: 'node-a',
37
+ to: 'node-b',
38
+ via: 'connection',
39
+ },
40
+ ],
41
+ };
42
+
43
+ const result = GraphConverter.configToGraph(config);
44
+
45
+ expect(result.nodes).toHaveLength(2);
46
+ expect(result.edges).toHaveLength(1);
47
+
48
+ // Check nodes
49
+ expect(result.nodes[0].id).toBe('node-a');
50
+ expect(result.nodes[0].type).toBe('node-a');
51
+ expect(result.nodes[0].data.shape).toBe('circle');
52
+ expect(result.nodes[0].data.icon).toBe('user');
53
+
54
+ expect(result.nodes[1].id).toBe('node-b');
55
+ expect(result.nodes[1].type).toBe('node-b');
56
+ expect(result.nodes[1].data.shape).toBe('rectangle');
57
+
58
+ // Check edges
59
+ expect(result.edges[0].from).toBe('node-a');
60
+ expect(result.edges[0].to).toBe('node-b');
61
+ expect(result.edges[0].type).toBe('connection');
62
+ expect(result.edges[0].data.style).toBe('solid');
63
+ });
64
+
65
+ test('should extract manual positions from node types', () => {
66
+ const config: PathBasedGraphConfiguration = {
67
+ metadata: {
68
+ name: 'Test Config',
69
+ version: '1.0.0',
70
+ },
71
+ nodeTypes: {
72
+ 'node-a': {
73
+ shape: 'circle',
74
+ color: '#3b82f6',
75
+ dataSchema: {},
76
+ position: { x: 100, y: 200 },
77
+ },
78
+ 'node-b': {
79
+ shape: 'rectangle',
80
+ color: '#8b5cf6',
81
+ dataSchema: {},
82
+ position: { x: 300, y: 400 },
83
+ },
84
+ },
85
+ edgeTypes: {},
86
+ allowedConnections: [],
87
+ };
88
+
89
+ const result = GraphConverter.configToGraph(config);
90
+
91
+ expect(result.nodes[0].position).toEqual({ x: 100, y: 200 });
92
+ expect(result.nodes[1].position).toEqual({ x: 300, y: 400 });
93
+ });
94
+
95
+ test('should handle nodes without positions', () => {
96
+ const config: PathBasedGraphConfiguration = {
97
+ metadata: {
98
+ name: 'Test Config',
99
+ version: '1.0.0',
100
+ },
101
+ nodeTypes: {
102
+ 'node-a': {
103
+ shape: 'circle',
104
+ color: '#3b82f6',
105
+ dataSchema: {},
106
+ },
107
+ },
108
+ edgeTypes: {},
109
+ allowedConnections: [],
110
+ };
111
+
112
+ const result = GraphConverter.configToGraph(config);
113
+
114
+ expect(result.nodes[0].position).toBeUndefined();
115
+ });
116
+
117
+ test('should include source paths and actions in node data', () => {
118
+ const config: PathBasedGraphConfiguration = {
119
+ metadata: {
120
+ name: 'Test Config',
121
+ version: '1.0.0',
122
+ },
123
+ nodeTypes: {
124
+ 'node-a': {
125
+ shape: 'circle',
126
+ color: '#3b82f6',
127
+ dataSchema: {},
128
+ sources: ['src/api/**/*.ts'],
129
+ actions: [
130
+ {
131
+ pattern: 'Lock acquired: (?<lockId>.*)',
132
+ event: 'lock-acquired',
133
+ state: 'locked',
134
+ },
135
+ ],
136
+ },
137
+ },
138
+ edgeTypes: {},
139
+ allowedConnections: [],
140
+ };
141
+
142
+ const result = GraphConverter.configToGraph(config);
143
+
144
+ expect(result.nodes[0].data.sources).toEqual(['src/api/**/*.ts']);
145
+ expect(result.nodes[0].data.actions).toHaveLength(1);
146
+ expect(result.nodes[0].data.actions[0].event).toBe('lock-acquired');
147
+ });
148
+
149
+ test('should handle edge animation config', () => {
150
+ const config: PathBasedGraphConfiguration = {
151
+ metadata: {
152
+ name: 'Test Config',
153
+ version: '1.0.0',
154
+ },
155
+ nodeTypes: {
156
+ 'node-a': {
157
+ shape: 'circle',
158
+ color: '#3b82f6',
159
+ dataSchema: {},
160
+ },
161
+ 'node-b': {
162
+ shape: 'rectangle',
163
+ color: '#8b5cf6',
164
+ dataSchema: {},
165
+ },
166
+ },
167
+ edgeTypes: {
168
+ 'animated-flow': {
169
+ style: 'solid',
170
+ color: '#3b82f6',
171
+ width: 3,
172
+ animation: {
173
+ type: 'flow',
174
+ duration: 1500,
175
+ color: '#60a5fa',
176
+ },
177
+ },
178
+ },
179
+ allowedConnections: [
180
+ {
181
+ from: 'node-a',
182
+ to: 'node-b',
183
+ via: 'animated-flow',
184
+ },
185
+ ],
186
+ };
187
+
188
+ const result = GraphConverter.configToGraph(config);
189
+
190
+ expect(result.edges[0].data.animation).toBeDefined();
191
+ expect(result.edges[0].data.animation?.type).toBe('flow');
192
+ expect(result.edges[0].data.animation?.duration).toBe(1500);
193
+ expect(result.edges[0].data.animation?.color).toBe('#60a5fa');
194
+ });
195
+ });
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Utility for converting PathBasedGraphConfiguration to nodes and edges
3
+ */
4
+
5
+ import type { PathBasedGraphConfiguration } from '../types/path-based-config';
6
+ import type { NodeState, EdgeState } from '../types';
7
+
8
+ /**
9
+ * Converts PathBasedGraphConfiguration to graph state (nodes and edges)
10
+ */
11
+ export class GraphConverter {
12
+ /**
13
+ * Convert configuration to nodes and edges
14
+ */
15
+ static configToGraph(config: PathBasedGraphConfiguration): {
16
+ nodes: NodeState[];
17
+ edges: EdgeState[];
18
+ } {
19
+ const nodes: NodeState[] = [];
20
+ const edges: EdgeState[] = [];
21
+ const now = Date.now();
22
+
23
+ // Create nodes from nodeTypes
24
+ Object.entries(config.nodeTypes).forEach(([id, nodeType]) => {
25
+ nodes.push({
26
+ id,
27
+ type: id,
28
+ data: {
29
+ label: id,
30
+ shape: nodeType.shape,
31
+ icon: nodeType.icon,
32
+ color: nodeType.color,
33
+ size: nodeType.size,
34
+ sources: nodeType.sources || [],
35
+ actions: nodeType.actions || [],
36
+ ...nodeType.dataSchema,
37
+ },
38
+ // Extract position if provided in manual layout mode
39
+ position: nodeType.position,
40
+ state: 'idle',
41
+ createdAt: now,
42
+ updatedAt: now,
43
+ });
44
+ });
45
+
46
+ // Create edges from allowedConnections
47
+ if (config.allowedConnections) {
48
+ config.allowedConnections.forEach((connection, index) => {
49
+ const edgeType = config.edgeTypes?.[connection.via];
50
+
51
+ edges.push({
52
+ id: `${connection.from}-${connection.to}-${index}`,
53
+ type: connection.via,
54
+ from: connection.from,
55
+ to: connection.to,
56
+ data: {
57
+ label: connection.via,
58
+ style: edgeType?.style || 'solid',
59
+ color: edgeType?.color,
60
+ width: edgeType?.width,
61
+ animation: edgeType?.animation,
62
+ },
63
+ createdAt: now,
64
+ updatedAt: now,
65
+ });
66
+ });
67
+ }
68
+
69
+ return { nodes, edges };
70
+ }
71
+ }
@@ -0,0 +1,245 @@
1
+ /**
2
+ * Library Converter
3
+ *
4
+ * Utilities for converting library components to canvas nodes/edges.
5
+ * Used when adding components from the library to a canvas.
6
+ */
7
+
8
+ import type { LibraryNodeComponent, LibraryEdgeComponent, ComponentLibrary } from '../types/library';
9
+ import type {
10
+ ExtendedCanvasTextNode,
11
+ ExtendedCanvasEdge,
12
+ PVNodeExtension,
13
+ PVEdgeExtension,
14
+ PVEdgeTypeDefinition,
15
+ } from '../types/canvas';
16
+
17
+ /**
18
+ * Options for creating a canvas node from a library component
19
+ */
20
+ export interface CreateNodeOptions {
21
+ /** Unique ID for the node */
22
+ id: string;
23
+
24
+ /** X position in pixels */
25
+ x: number;
26
+
27
+ /** Y position in pixels */
28
+ y: number;
29
+
30
+ /** Optional label override (defaults to component's defaultLabel or the component key) */
31
+ label?: string;
32
+
33
+ /** Optional initial data values */
34
+ data?: Record<string, unknown>;
35
+ }
36
+
37
+ /**
38
+ * Options for creating a canvas edge from a library component
39
+ */
40
+ export interface CreateEdgeOptions {
41
+ /** Unique ID for the edge */
42
+ id: string;
43
+
44
+ /** Source node ID */
45
+ fromNode: string;
46
+
47
+ /** Target node ID */
48
+ toNode: string;
49
+
50
+ /** Optional label */
51
+ label?: string;
52
+
53
+ /** Optional side of source node */
54
+ fromSide?: 'top' | 'right' | 'bottom' | 'left';
55
+
56
+ /** Optional side of target node */
57
+ toSide?: 'top' | 'right' | 'bottom' | 'left';
58
+ }
59
+
60
+ /**
61
+ * Library Converter utility class
62
+ */
63
+ export class LibraryConverter {
64
+ /**
65
+ * Create a canvas node from a library node component
66
+ *
67
+ * @param componentKey - The key of the component in the library
68
+ * @param component - The library node component definition
69
+ * @param options - Node creation options (id, position, etc.)
70
+ * @returns Extended canvas text node ready to add to a canvas
71
+ */
72
+ static createCanvasNode(
73
+ componentKey: string,
74
+ component: LibraryNodeComponent,
75
+ options: CreateNodeOptions
76
+ ): ExtendedCanvasTextNode {
77
+ const { id, x, y, label, data } = options;
78
+
79
+ // Build the PV extension from the library component
80
+ const pv: PVNodeExtension = {
81
+ nodeType: componentKey,
82
+ shape: component.shape,
83
+ icon: component.icon,
84
+ states: component.states,
85
+ sources: component.sources,
86
+ actions: component.actions,
87
+ dataSchema: component.dataSchema,
88
+ layout: component.layout,
89
+ };
90
+
91
+ // Build the canvas node
92
+ const node: ExtendedCanvasTextNode = {
93
+ id,
94
+ type: 'text',
95
+ text: label || component.defaultLabel || componentKey,
96
+ x,
97
+ y,
98
+ width: component.size?.width || 120,
99
+ height: component.size?.height || 60,
100
+ color: component.color,
101
+ pv,
102
+ };
103
+
104
+ // If there's initial data, merge it with the text
105
+ if (data) {
106
+ // Store additional data in the pv extension
107
+ // The data will be accessible via the node's data field when converted to React Flow
108
+ (pv as PVNodeExtension & { initialData?: Record<string, unknown> }).initialData = data;
109
+ }
110
+
111
+ return node;
112
+ }
113
+
114
+ /**
115
+ * Create a canvas edge from a library edge component
116
+ *
117
+ * @param componentKey - The key of the component in the library
118
+ * @param component - The library edge component definition
119
+ * @param options - Edge creation options
120
+ * @returns Extended canvas edge ready to add to a canvas
121
+ */
122
+ static createCanvasEdge(
123
+ componentKey: string,
124
+ component: LibraryEdgeComponent,
125
+ options: CreateEdgeOptions
126
+ ): ExtendedCanvasEdge {
127
+ const { id, fromNode, toNode, label, fromSide, toSide } = options;
128
+
129
+ // Build the PV extension
130
+ const pv: PVEdgeExtension = {
131
+ edgeType: componentKey,
132
+ style: component.style,
133
+ width: component.width,
134
+ animation: component.animation,
135
+ };
136
+
137
+ // Build the canvas edge
138
+ const edge: ExtendedCanvasEdge = {
139
+ id,
140
+ fromNode,
141
+ toNode,
142
+ label,
143
+ fromSide,
144
+ toSide,
145
+ color: component.color,
146
+ toEnd: component.directed !== false ? 'arrow' : 'none',
147
+ pv,
148
+ };
149
+
150
+ return edge;
151
+ }
152
+
153
+ /**
154
+ * Convert library edge components to canvas-level edge type definitions
155
+ *
156
+ * This is useful when initializing a new canvas from a library.
157
+ *
158
+ * @param edgeComponents - Library edge components
159
+ * @returns Record of edge type definitions for the canvas pv.edgeTypes field
160
+ */
161
+ static createEdgeTypeDefinitions(
162
+ edgeComponents: Record<string, LibraryEdgeComponent>
163
+ ): Record<string, PVEdgeTypeDefinition> {
164
+ const edgeTypes: Record<string, PVEdgeTypeDefinition> = {};
165
+
166
+ for (const [key, component] of Object.entries(edgeComponents)) {
167
+ edgeTypes[key] = {
168
+ style: component.style,
169
+ color: component.color,
170
+ width: component.width,
171
+ directed: component.directed,
172
+ animation: component.animation,
173
+ labelConfig: component.label,
174
+ };
175
+ }
176
+
177
+ return edgeTypes;
178
+ }
179
+
180
+ /**
181
+ * Generate a unique ID for a new node
182
+ *
183
+ * @param prefix - Optional prefix (defaults to 'node')
184
+ * @returns Unique ID string
185
+ */
186
+ static generateNodeId(prefix = 'node'): string {
187
+ return `${prefix}-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
188
+ }
189
+
190
+ /**
191
+ * Generate a unique ID for a new edge
192
+ *
193
+ * @param fromNode - Source node ID
194
+ * @param toNode - Target node ID
195
+ * @returns Unique ID string
196
+ */
197
+ static generateEdgeId(fromNode: string, toNode: string): string {
198
+ return `edge-${fromNode}-${toNode}-${Math.random().toString(36).substring(2, 9)}`;
199
+ }
200
+
201
+ /**
202
+ * Get all node component keys that match the given tags
203
+ *
204
+ * @param library - Component library
205
+ * @param tags - Tags to filter by (matches if component has ANY of these tags)
206
+ * @returns Array of component keys
207
+ */
208
+ static filterNodesByTags(library: ComponentLibrary, tags: string[]): string[] {
209
+ return Object.entries(library.nodeComponents)
210
+ .filter(([, component]) => component.tags?.some((tag) => tags.includes(tag)))
211
+ .map(([key]) => key);
212
+ }
213
+
214
+ /**
215
+ * Get all edge component keys that match the given tags
216
+ *
217
+ * @param library - Component library
218
+ * @param tags - Tags to filter by (matches if component has ANY of these tags)
219
+ * @returns Array of component keys
220
+ */
221
+ static filterEdgesByTags(library: ComponentLibrary, tags: string[]): string[] {
222
+ return Object.entries(library.edgeComponents)
223
+ .filter(([, component]) => component.tags?.some((tag) => tags.includes(tag)))
224
+ .map(([key]) => key);
225
+ }
226
+
227
+ /**
228
+ * Get allowed edge types between two node types based on connection rules
229
+ *
230
+ * @param library - Component library
231
+ * @param fromNodeType - Source node type key
232
+ * @param toNodeType - Target node type key
233
+ * @returns Array of allowed edge type keys
234
+ */
235
+ static getAllowedEdgeTypes(library: ComponentLibrary, fromNodeType: string, toNodeType: string): string[] {
236
+ if (!library.connectionRules) {
237
+ // If no rules defined, allow any edge type
238
+ return Object.keys(library.edgeComponents);
239
+ }
240
+
241
+ return library.connectionRules
242
+ .filter((rule) => rule.from === fromNodeType && rule.to === toNodeType)
243
+ .map((rule) => rule.via);
244
+ }
245
+ }
@@ -0,0 +1,148 @@
1
+ import { describe, it, expect } from 'bun:test';
2
+ import { PathMatcher } from './PathMatcher';
3
+
4
+ describe('PathMatcher', () => {
5
+ describe('exact matches', () => {
6
+ it('should match exact paths', () => {
7
+ expect(PathMatcher.matches('lib/lock-manager.ts', 'lib/lock-manager.ts')).toBe(true);
8
+ expect(PathMatcher.matches('lib/lock-manager.ts', 'lib/github-api.ts')).toBe(false);
9
+ });
10
+
11
+ it('should normalize path separators', () => {
12
+ expect(PathMatcher.matches('lib\\lock-manager.ts', 'lib/lock-manager.ts')).toBe(true);
13
+ expect(PathMatcher.matches('lib/lock-manager.ts', 'lib\\lock-manager.ts')).toBe(true);
14
+ });
15
+ });
16
+
17
+ describe('wildcard patterns', () => {
18
+ it('should match * (single segment wildcard)', () => {
19
+ expect(PathMatcher.matches('lib/lock-manager.ts', 'lib/*.ts')).toBe(true);
20
+ expect(PathMatcher.matches('lib/github-api.ts', 'lib/*.ts')).toBe(true);
21
+ expect(PathMatcher.matches('lib/foo/bar.ts', 'lib/*.ts')).toBe(false); // * doesn't cross directories
22
+ });
23
+
24
+ it('should match ** (recursive wildcard)', () => {
25
+ expect(PathMatcher.matches('lib/lock-manager.ts', 'lib/**/*.ts')).toBe(true);
26
+ expect(PathMatcher.matches('lib/foo/bar.ts', 'lib/**/*.ts')).toBe(true);
27
+ expect(PathMatcher.matches('lib/foo/baz/qux.ts', 'lib/**/*.ts')).toBe(true);
28
+ expect(PathMatcher.matches('src/index.ts', 'lib/**/*.ts')).toBe(false);
29
+ });
30
+
31
+ it('should match ? (single character)', () => {
32
+ expect(PathMatcher.matches('lib/a.ts', 'lib/?.ts')).toBe(true);
33
+ expect(PathMatcher.matches('lib/b.ts', 'lib/?.ts')).toBe(true);
34
+ expect(PathMatcher.matches('lib/ab.ts', 'lib/?.ts')).toBe(false);
35
+ });
36
+ });
37
+
38
+ describe('character sets', () => {
39
+ it('should match [abc] patterns', () => {
40
+ expect(PathMatcher.matches('lib/a.ts', 'lib/[abc].ts')).toBe(true);
41
+ expect(PathMatcher.matches('lib/b.ts', 'lib/[abc].ts')).toBe(true);
42
+ expect(PathMatcher.matches('lib/c.ts', 'lib/[abc].ts')).toBe(true);
43
+ expect(PathMatcher.matches('lib/d.ts', 'lib/[abc].ts')).toBe(false);
44
+ });
45
+
46
+ it('should match [a-z] range patterns', () => {
47
+ expect(PathMatcher.matches('lib/a.ts', 'lib/[a-z].ts')).toBe(true);
48
+ expect(PathMatcher.matches('lib/m.ts', 'lib/[a-z].ts')).toBe(true);
49
+ expect(PathMatcher.matches('lib/z.ts', 'lib/[a-z].ts')).toBe(true);
50
+ expect(PathMatcher.matches('lib/A.ts', 'lib/[a-z].ts')).toBe(false);
51
+ });
52
+ });
53
+
54
+ describe('alternatives', () => {
55
+ it('should match {a,b,c} alternatives', () => {
56
+ expect(PathMatcher.matches('lib/foo.ts', 'lib/{foo,bar,baz}.ts')).toBe(true);
57
+ expect(PathMatcher.matches('lib/bar.ts', 'lib/{foo,bar,baz}.ts')).toBe(true);
58
+ expect(PathMatcher.matches('lib/baz.ts', 'lib/{foo,bar,baz}.ts')).toBe(true);
59
+ expect(PathMatcher.matches('lib/qux.ts', 'lib/{foo,bar,baz}.ts')).toBe(false);
60
+ });
61
+
62
+ it('should match nested alternatives', () => {
63
+ expect(PathMatcher.matches('lib/services/api.ts', '{lib,src}/**/*.ts')).toBe(true);
64
+ expect(PathMatcher.matches('src/components/App.tsx', '{lib,src}/**/*.{ts,tsx}')).toBe(true);
65
+ });
66
+ });
67
+
68
+ describe('complex patterns', () => {
69
+ it('should match combined patterns', () => {
70
+ const pattern = 'lib/{services,utils}/**/*.{ts,js}';
71
+ expect(PathMatcher.matches('lib/services/api.ts', pattern)).toBe(true);
72
+ expect(PathMatcher.matches('lib/utils/helper.js', pattern)).toBe(true);
73
+ expect(PathMatcher.matches('lib/models/user.ts', pattern)).toBe(false);
74
+ });
75
+ });
76
+
77
+ describe('findMatches', () => {
78
+ it('should return all matching patterns', () => {
79
+ const patterns = [
80
+ 'lib/lock-manager.ts',
81
+ 'lib/*.ts',
82
+ 'lib/**/*.ts',
83
+ 'src/**/*.ts'
84
+ ];
85
+
86
+ const matches = PathMatcher.findMatches('lib/lock-manager.ts', patterns);
87
+ expect(matches).toEqual([
88
+ 'lib/lock-manager.ts',
89
+ 'lib/*.ts',
90
+ 'lib/**/*.ts'
91
+ ]);
92
+ });
93
+
94
+ it('should return empty array when no patterns match', () => {
95
+ const patterns = ['src/**/*.ts', 'test/**/*.ts'];
96
+ const matches = PathMatcher.findMatches('lib/lock-manager.ts', patterns);
97
+ expect(matches).toEqual([]);
98
+ });
99
+ });
100
+
101
+ describe('isGlob', () => {
102
+ it('should detect glob patterns', () => {
103
+ expect(PathMatcher.isGlob('lib/*.ts')).toBe(true);
104
+ expect(PathMatcher.isGlob('lib/**/*.ts')).toBe(true);
105
+ expect(PathMatcher.isGlob('lib/?.ts')).toBe(true);
106
+ expect(PathMatcher.isGlob('lib/[abc].ts')).toBe(true);
107
+ expect(PathMatcher.isGlob('lib/{a,b}.ts')).toBe(true);
108
+ });
109
+
110
+ it('should not detect non-glob patterns', () => {
111
+ expect(PathMatcher.isGlob('lib/lock-manager.ts')).toBe(false);
112
+ expect(PathMatcher.isGlob('lib/services/api.ts')).toBe(false);
113
+ });
114
+ });
115
+
116
+ describe('getBaseDir', () => {
117
+ it('should extract base directory from glob patterns', () => {
118
+ expect(PathMatcher.getBaseDir('lib/**/*.ts')).toBe('lib');
119
+ expect(PathMatcher.getBaseDir('lib/services/**/*.ts')).toBe('lib/services');
120
+ expect(PathMatcher.getBaseDir('lib/*.ts')).toBe('lib');
121
+ expect(PathMatcher.getBaseDir('**/*.ts')).toBe('');
122
+ });
123
+
124
+ it('should handle non-glob patterns', () => {
125
+ expect(PathMatcher.getBaseDir('lib/lock-manager.ts')).toBe('lib');
126
+ expect(PathMatcher.getBaseDir('lib/services/api.ts')).toBe('lib/services');
127
+ });
128
+ });
129
+
130
+ describe('edge cases', () => {
131
+ it('should handle empty paths', () => {
132
+ expect(PathMatcher.matches('', '')).toBe(true);
133
+ expect(PathMatcher.matches('lib/foo.ts', '')).toBe(false);
134
+ expect(PathMatcher.matches('', '**')).toBe(true);
135
+ });
136
+
137
+ it('should handle special characters in paths', () => {
138
+ expect(PathMatcher.matches('lib/foo-bar.ts', 'lib/*.ts')).toBe(true);
139
+ expect(PathMatcher.matches('lib/foo_bar.ts', 'lib/*.ts')).toBe(true);
140
+ expect(PathMatcher.matches('lib/foo.bar.ts', 'lib/*.ts')).toBe(true);
141
+ });
142
+
143
+ it('should not match across directory boundaries with *', () => {
144
+ expect(PathMatcher.matches('lib/services/api.ts', 'lib/*.ts')).toBe(false);
145
+ expect(PathMatcher.matches('lib/services/api.ts', 'lib/**/*.ts')).toBe(true);
146
+ });
147
+ });
148
+ });