@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.
- package/README.md +126 -0
- package/dist/ConfigurationLoader.d.ts +76 -0
- package/dist/ConfigurationLoader.d.ts.map +1 -0
- package/dist/ConfigurationLoader.js +144 -0
- package/dist/ConfigurationLoader.js.map +1 -0
- package/dist/ConfigurationValidator.d.ts +31 -0
- package/dist/ConfigurationValidator.d.ts.map +1 -0
- package/dist/ConfigurationValidator.js +242 -0
- package/dist/ConfigurationValidator.js.map +1 -0
- package/dist/EventProcessor.d.ts +49 -0
- package/dist/EventProcessor.d.ts.map +1 -0
- package/dist/EventProcessor.js +215 -0
- package/dist/EventProcessor.js.map +1 -0
- package/dist/EventRecorderService.d.ts +305 -0
- package/dist/EventRecorderService.d.ts.map +1 -0
- package/dist/EventRecorderService.js +463 -0
- package/dist/EventRecorderService.js.map +1 -0
- package/dist/LibraryLoader.d.ts +63 -0
- package/dist/LibraryLoader.d.ts.map +1 -0
- package/dist/LibraryLoader.js +188 -0
- package/dist/LibraryLoader.js.map +1 -0
- package/dist/PathBasedEventProcessor.d.ts +90 -0
- package/dist/PathBasedEventProcessor.d.ts.map +1 -0
- package/dist/PathBasedEventProcessor.js +239 -0
- package/dist/PathBasedEventProcessor.js.map +1 -0
- package/dist/SessionManager.d.ts +194 -0
- package/dist/SessionManager.d.ts.map +1 -0
- package/dist/SessionManager.js +299 -0
- package/dist/SessionManager.js.map +1 -0
- package/dist/ValidationEngine.d.ts +31 -0
- package/dist/ValidationEngine.d.ts.map +1 -0
- package/dist/ValidationEngine.js +158 -0
- package/dist/ValidationEngine.js.map +1 -0
- package/dist/helpers/GraphInstrumentationHelper.d.ts +93 -0
- package/dist/helpers/GraphInstrumentationHelper.d.ts.map +1 -0
- package/dist/helpers/GraphInstrumentationHelper.js +248 -0
- package/dist/helpers/GraphInstrumentationHelper.js.map +1 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +34 -0
- package/dist/index.js.map +1 -0
- package/dist/rules/config.d.ts +57 -0
- package/dist/rules/config.d.ts.map +1 -0
- package/dist/rules/config.js +382 -0
- package/dist/rules/config.js.map +1 -0
- package/dist/rules/engine.d.ts +70 -0
- package/dist/rules/engine.d.ts.map +1 -0
- package/dist/rules/engine.js +252 -0
- package/dist/rules/engine.js.map +1 -0
- package/dist/rules/implementations/connection-type-references.d.ts +7 -0
- package/dist/rules/implementations/connection-type-references.d.ts.map +1 -0
- package/dist/rules/implementations/connection-type-references.js +104 -0
- package/dist/rules/implementations/connection-type-references.js.map +1 -0
- package/dist/rules/implementations/dead-end-states.d.ts +17 -0
- package/dist/rules/implementations/dead-end-states.d.ts.map +1 -0
- package/dist/rules/implementations/dead-end-states.js +72 -0
- package/dist/rules/implementations/dead-end-states.js.map +1 -0
- package/dist/rules/implementations/index.d.ts +24 -0
- package/dist/rules/implementations/index.d.ts.map +1 -0
- package/dist/rules/implementations/index.js +62 -0
- package/dist/rules/implementations/index.js.map +1 -0
- package/dist/rules/implementations/library-node-type-match.d.ts +17 -0
- package/dist/rules/implementations/library-node-type-match.d.ts.map +1 -0
- package/dist/rules/implementations/library-node-type-match.js +123 -0
- package/dist/rules/implementations/library-node-type-match.js.map +1 -0
- package/dist/rules/implementations/minimum-node-sources.d.ts +22 -0
- package/dist/rules/implementations/minimum-node-sources.d.ts.map +1 -0
- package/dist/rules/implementations/minimum-node-sources.js +54 -0
- package/dist/rules/implementations/minimum-node-sources.js.map +1 -0
- package/dist/rules/implementations/no-unknown-fields.d.ts +7 -0
- package/dist/rules/implementations/no-unknown-fields.d.ts.map +1 -0
- package/dist/rules/implementations/no-unknown-fields.js +211 -0
- package/dist/rules/implementations/no-unknown-fields.js.map +1 -0
- package/dist/rules/implementations/orphaned-edge-types.d.ts +7 -0
- package/dist/rules/implementations/orphaned-edge-types.d.ts.map +1 -0
- package/dist/rules/implementations/orphaned-edge-types.js +47 -0
- package/dist/rules/implementations/orphaned-edge-types.js.map +1 -0
- package/dist/rules/implementations/orphaned-node-types.d.ts +7 -0
- package/dist/rules/implementations/orphaned-node-types.d.ts.map +1 -0
- package/dist/rules/implementations/orphaned-node-types.js +50 -0
- package/dist/rules/implementations/orphaned-node-types.js.map +1 -0
- package/dist/rules/implementations/required-metadata.d.ts +7 -0
- package/dist/rules/implementations/required-metadata.d.ts.map +1 -0
- package/dist/rules/implementations/required-metadata.js +57 -0
- package/dist/rules/implementations/required-metadata.js.map +1 -0
- package/dist/rules/implementations/state-transition-references.d.ts +7 -0
- package/dist/rules/implementations/state-transition-references.d.ts.map +1 -0
- package/dist/rules/implementations/state-transition-references.js +135 -0
- package/dist/rules/implementations/state-transition-references.js.map +1 -0
- package/dist/rules/implementations/unreachable-states.d.ts +7 -0
- package/dist/rules/implementations/unreachable-states.d.ts.map +1 -0
- package/dist/rules/implementations/unreachable-states.js +80 -0
- package/dist/rules/implementations/unreachable-states.js.map +1 -0
- package/dist/rules/implementations/valid-action-patterns.d.ts +17 -0
- package/dist/rules/implementations/valid-action-patterns.d.ts.map +1 -0
- package/dist/rules/implementations/valid-action-patterns.js +109 -0
- package/dist/rules/implementations/valid-action-patterns.js.map +1 -0
- package/dist/rules/implementations/valid-color-format.d.ts +7 -0
- package/dist/rules/implementations/valid-color-format.d.ts.map +1 -0
- package/dist/rules/implementations/valid-color-format.js +91 -0
- package/dist/rules/implementations/valid-color-format.js.map +1 -0
- package/dist/rules/implementations/valid-edge-types.d.ts +7 -0
- package/dist/rules/implementations/valid-edge-types.d.ts.map +1 -0
- package/dist/rules/implementations/valid-edge-types.js +244 -0
- package/dist/rules/implementations/valid-edge-types.js.map +1 -0
- package/dist/rules/implementations/valid-node-types.d.ts +7 -0
- package/dist/rules/implementations/valid-node-types.d.ts.map +1 -0
- package/dist/rules/implementations/valid-node-types.js +175 -0
- package/dist/rules/implementations/valid-node-types.js.map +1 -0
- package/dist/rules/index.d.ts +28 -0
- package/dist/rules/index.d.ts.map +1 -0
- package/dist/rules/index.js +45 -0
- package/dist/rules/index.js.map +1 -0
- package/dist/rules/types.d.ts +309 -0
- package/dist/rules/types.d.ts.map +1 -0
- package/dist/rules/types.js +35 -0
- package/dist/rules/types.js.map +1 -0
- package/dist/types/canvas.d.ts +409 -0
- package/dist/types/canvas.d.ts.map +1 -0
- package/dist/types/canvas.js +70 -0
- package/dist/types/canvas.js.map +1 -0
- package/dist/types/index.d.ts +311 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +13 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/library.d.ts +185 -0
- package/dist/types/library.d.ts.map +1 -0
- package/dist/types/library.js +15 -0
- package/dist/types/library.js.map +1 -0
- package/dist/types/path-based-config.d.ts +230 -0
- package/dist/types/path-based-config.d.ts.map +1 -0
- package/dist/types/path-based-config.js +9 -0
- package/dist/types/path-based-config.js.map +1 -0
- package/dist/utils/CanvasConverter.d.ts +118 -0
- package/dist/utils/CanvasConverter.d.ts.map +1 -0
- package/dist/utils/CanvasConverter.js +315 -0
- package/dist/utils/CanvasConverter.js.map +1 -0
- package/dist/utils/GraphConverter.d.ts +18 -0
- package/dist/utils/GraphConverter.d.ts.map +1 -0
- package/dist/utils/GraphConverter.js +61 -0
- package/dist/utils/GraphConverter.js.map +1 -0
- package/dist/utils/LibraryConverter.d.ts +113 -0
- package/dist/utils/LibraryConverter.d.ts.map +1 -0
- package/dist/utils/LibraryConverter.js +166 -0
- package/dist/utils/LibraryConverter.js.map +1 -0
- package/dist/utils/PathMatcher.d.ts +55 -0
- package/dist/utils/PathMatcher.d.ts.map +1 -0
- package/dist/utils/PathMatcher.js +172 -0
- package/dist/utils/PathMatcher.js.map +1 -0
- package/dist/utils/YamlParser.d.ts +36 -0
- package/dist/utils/YamlParser.d.ts.map +1 -0
- package/dist/utils/YamlParser.js +63 -0
- package/dist/utils/YamlParser.js.map +1 -0
- package/package.json +47 -0
- package/src/ConfigurationLoader.test.ts +490 -0
- package/src/ConfigurationLoader.ts +185 -0
- package/src/ConfigurationValidator.test.ts +200 -0
- package/src/ConfigurationValidator.ts +283 -0
- package/src/EventProcessor.test.ts +405 -0
- package/src/EventProcessor.ts +250 -0
- package/src/EventRecorderService.test.ts +541 -0
- package/src/EventRecorderService.ts +744 -0
- package/src/LibraryLoader.ts +215 -0
- package/src/PathBasedEventProcessor.test.ts +567 -0
- package/src/PathBasedEventProcessor.ts +332 -0
- package/src/SessionManager.test.ts +424 -0
- package/src/SessionManager.ts +470 -0
- package/src/ValidationEngine.test.ts +371 -0
- package/src/ValidationEngine.ts +196 -0
- package/src/helpers/GraphInstrumentationHelper.test.ts +340 -0
- package/src/helpers/GraphInstrumentationHelper.ts +326 -0
- package/src/index.ts +85 -0
- package/src/rules/config.test.ts +278 -0
- package/src/rules/config.ts +459 -0
- package/src/rules/engine.test.ts +332 -0
- package/src/rules/engine.ts +318 -0
- package/src/rules/implementations/connection-type-references.ts +117 -0
- package/src/rules/implementations/dead-end-states.ts +101 -0
- package/src/rules/implementations/index.ts +73 -0
- package/src/rules/implementations/library-node-type-match.ts +148 -0
- package/src/rules/implementations/minimum-node-sources.ts +82 -0
- package/src/rules/implementations/no-unknown-fields.ts +342 -0
- package/src/rules/implementations/orphaned-edge-types.ts +55 -0
- package/src/rules/implementations/orphaned-node-types.ts +58 -0
- package/src/rules/implementations/required-metadata.ts +64 -0
- package/src/rules/implementations/state-transition-references.ts +151 -0
- package/src/rules/implementations/unreachable-states.ts +94 -0
- package/src/rules/implementations/valid-action-patterns.ts +136 -0
- package/src/rules/implementations/valid-color-format.ts +140 -0
- package/src/rules/implementations/valid-edge-types.ts +258 -0
- package/src/rules/implementations/valid-node-types.ts +189 -0
- package/src/rules/index.ts +95 -0
- package/src/rules/types.ts +426 -0
- package/src/types/canvas.ts +496 -0
- package/src/types/index.ts +382 -0
- package/src/types/library.ts +233 -0
- package/src/types/path-based-config.ts +281 -0
- package/src/utils/CanvasConverter.ts +431 -0
- package/src/utils/GraphConverter.test.ts +195 -0
- package/src/utils/GraphConverter.ts +71 -0
- package/src/utils/LibraryConverter.ts +245 -0
- package/src/utils/PathMatcher.test.ts +148 -0
- package/src/utils/PathMatcher.ts +183 -0
- 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
|
+
});
|