@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,36 @@
1
+ /**
2
+ * YAML parsing utility for loading configuration files
3
+ *
4
+ * Provides error handling and validation for YAML configuration files.
5
+ * Supports both .yaml and .yml file extensions.
6
+ */
7
+ import { PathBasedGraphConfiguration } from '../types/path-based-config';
8
+ export interface YamlParseResult {
9
+ success: boolean;
10
+ data?: PathBasedGraphConfiguration;
11
+ error?: string;
12
+ }
13
+ /**
14
+ * Parse YAML content into a configuration object
15
+ *
16
+ * @param content - Raw YAML string content
17
+ * @param filename - Optional filename for error messages
18
+ * @returns Parse result with configuration or error
19
+ */
20
+ export declare function parseYaml(content: string, filename?: string): YamlParseResult;
21
+ /**
22
+ * Check if a filename has a valid YAML extension
23
+ *
24
+ * @param filename - Filename to check
25
+ * @returns True if filename ends with .yaml or .yml
26
+ */
27
+ export declare function isYamlFile(filename: string): boolean;
28
+ /**
29
+ * Extract configuration name from filename
30
+ * Removes the .yaml or .yml extension
31
+ *
32
+ * @param filename - Filename to process
33
+ * @returns Configuration name without extension
34
+ */
35
+ export declare function getConfigNameFromFilename(filename: string): string;
36
+ //# sourceMappingURL=YamlParser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"YamlParser.d.ts","sourceRoot":"","sources":["../../src/utils/YamlParser.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,2BAA2B,EAAE,MAAM,4BAA4B,CAAC;AAEzE,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,2BAA2B,CAAC;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,eAAe,CA8B7E;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAEpD;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAElE"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * YAML parsing utility for loading configuration files
3
+ *
4
+ * Provides error handling and validation for YAML configuration files.
5
+ * Supports both .yaml and .yml file extensions.
6
+ */
7
+ import * as yaml from 'js-yaml';
8
+ /**
9
+ * Parse YAML content into a configuration object
10
+ *
11
+ * @param content - Raw YAML string content
12
+ * @param filename - Optional filename for error messages
13
+ * @returns Parse result with configuration or error
14
+ */
15
+ export function parseYaml(content, filename) {
16
+ try {
17
+ const data = yaml.load(content);
18
+ if (!data) {
19
+ return {
20
+ success: false,
21
+ error: `Empty YAML file${filename ? `: ${filename}` : ''}`,
22
+ };
23
+ }
24
+ // Basic validation - ensure it has the required structure
25
+ if (!data.metadata || !data.nodeTypes || !data.edgeTypes) {
26
+ return {
27
+ success: false,
28
+ error: `Invalid configuration structure${filename ? ` in ${filename}` : ''}: missing required fields (metadata, nodeTypes, or edgeTypes)`,
29
+ };
30
+ }
31
+ return {
32
+ success: true,
33
+ data,
34
+ };
35
+ }
36
+ catch (error) {
37
+ const errorMessage = error instanceof Error ? error.message : String(error);
38
+ return {
39
+ success: false,
40
+ error: `YAML parse error${filename ? ` in ${filename}` : ''}: ${errorMessage}`,
41
+ };
42
+ }
43
+ }
44
+ /**
45
+ * Check if a filename has a valid YAML extension
46
+ *
47
+ * @param filename - Filename to check
48
+ * @returns True if filename ends with .yaml or .yml
49
+ */
50
+ export function isYamlFile(filename) {
51
+ return filename.endsWith('.yaml') || filename.endsWith('.yml');
52
+ }
53
+ /**
54
+ * Extract configuration name from filename
55
+ * Removes the .yaml or .yml extension
56
+ *
57
+ * @param filename - Filename to process
58
+ * @returns Configuration name without extension
59
+ */
60
+ export function getConfigNameFromFilename(filename) {
61
+ return filename.replace(/\.(yaml|yml)$/, '');
62
+ }
63
+ //# sourceMappingURL=YamlParser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"YamlParser.js","sourceRoot":"","sources":["../../src/utils/YamlParser.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,IAAI,MAAM,SAAS,CAAC;AAShC;;;;;;GAMG;AACH,MAAM,UAAU,SAAS,CAAC,OAAe,EAAE,QAAiB;IAC1D,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAgC,CAAC;QAE/D,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,OAAO;gBACL,OAAO,EAAE,KAAK;gBACd,KAAK,EAAE,kBAAkB,QAAQ,CAAC,CAAC,CAAC,KAAK,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE;aAC3D,CAAC;QACJ,CAAC;QAED,0DAA0D;QAC1D,IAAI,CAAC,IAAI,CAAC,QAAQ,IAAI,CAAC,IAAI,CAAC,SAAS,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;YACzD,OAAO;gBACL,OAAO,EAAE,KAAK;gBACd,KAAK,EAAE,kCAAkC,QAAQ,CAAC,CAAC,CAAC,OAAO,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,+DAA+D;aAC1I,CAAC;QACJ,CAAC;QAED,OAAO;YACL,OAAO,EAAE,IAAI;YACb,IAAI;SACL,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,YAAY,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC5E,OAAO;YACL,OAAO,EAAE,KAAK;YACd,KAAK,EAAE,mBAAmB,QAAQ,CAAC,CAAC,CAAC,OAAO,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,YAAY,EAAE;SAC/E,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CAAC,QAAgB;IACzC,OAAO,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;AACjE,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,yBAAyB,CAAC,QAAgB;IACxD,OAAO,QAAQ,CAAC,OAAO,CAAC,eAAe,EAAE,EAAE,CAAC,CAAC;AAC/C,CAAC"}
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@principal-ai/principal-view-core",
3
+ "version": "0.5.6",
4
+ "description": "Core logic and types for graph-based principal view framework",
5
+ "main": "./dist/index.js",
6
+ "types": "./dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "default": "./dist/index.js"
11
+ }
12
+ },
13
+ "files": ["dist", "src", "README.md"],
14
+ "scripts": {
15
+ "build": "bun run build:clean && bun run build:ts",
16
+ "build:clean": "rm -rf dist",
17
+ "build:ts": "tsc",
18
+ "dev": "tsc --watch",
19
+ "test": "bun test",
20
+ "test:watch": "bun test --watch",
21
+ "test:coverage": "bun test --coverage"
22
+ },
23
+ "dependencies": {
24
+ "zod": "^3.22.0",
25
+ "date-fns": "^3.0.0",
26
+ "@principal-ai/repository-abstraction": "^0.2.5",
27
+ "js-yaml": "^4.1.0"
28
+ },
29
+ "devDependencies": {
30
+ "@types/jest": "^30.0.0",
31
+ "@types/js-yaml": "^4.0.9",
32
+ "jest": "^30.1.3",
33
+ "typescript": "^5.0.4"
34
+ },
35
+ "keywords": [
36
+ "validation",
37
+ "graph",
38
+ "visualization",
39
+ "testing",
40
+ "event-driven"
41
+ ],
42
+ "license": "MIT",
43
+ "publishConfig": {
44
+ "access": "public",
45
+ "registry": "https://registry.npmjs.org/"
46
+ }
47
+ }
@@ -0,0 +1,490 @@
1
+ import { describe, expect, test, beforeEach } from 'bun:test';
2
+ import { ConfigurationLoader } from './ConfigurationLoader';
3
+ import type { ConfigurationFile, ConfigurationLoadResult } from './ConfigurationLoader';
4
+ import { InMemoryFileSystemAdapter } from '@principal-ai/repository-abstraction';
5
+ import type { PathBasedGraphConfiguration } from './types/path-based-config';
6
+
7
+ describe('ConfigurationLoader', () => {
8
+ let fsAdapter: InMemoryFileSystemAdapter;
9
+ let loader: ConfigurationLoader;
10
+
11
+ const validConfig: PathBasedGraphConfiguration = {
12
+ metadata: {
13
+ name: 'Test Config',
14
+ version: '1.0.0',
15
+ description: 'A test configuration',
16
+ },
17
+ nodeTypes: {
18
+ service: {
19
+ shape: 'rectangle',
20
+ color: '#4A90E2',
21
+ dataSchema: {
22
+ name: { type: 'string', required: true },
23
+ },
24
+ },
25
+ },
26
+ edgeTypes: {
27
+ call: {
28
+ style: 'solid',
29
+ color: '#999',
30
+ },
31
+ },
32
+ allowedConnections: [
33
+ {
34
+ from: 'service',
35
+ to: 'service',
36
+ via: 'call',
37
+ },
38
+ ],
39
+ };
40
+
41
+ const architectureConfig: PathBasedGraphConfiguration = {
42
+ metadata: {
43
+ name: 'Architecture',
44
+ version: '1.0.0',
45
+ },
46
+ nodeTypes: {
47
+ component: {
48
+ shape: 'hexagon',
49
+ color: '#FF6B6B',
50
+ dataSchema: {
51
+ name: { type: 'string', required: true },
52
+ },
53
+ sources: ['src/**/*.ts'],
54
+ },
55
+ },
56
+ edgeTypes: {
57
+ dependency: {
58
+ style: 'dashed',
59
+ color: '#888',
60
+ },
61
+ },
62
+ allowedConnections: [
63
+ {
64
+ from: 'component',
65
+ to: 'component',
66
+ via: 'dependency',
67
+ },
68
+ ],
69
+ };
70
+
71
+ beforeEach(() => {
72
+ fsAdapter = new InMemoryFileSystemAdapter();
73
+ loader = new ConfigurationLoader(fsAdapter);
74
+ });
75
+
76
+ describe('hasConfigDirectory', () => {
77
+ test('returns true when .vgc directory exists', () => {
78
+ fsAdapter.createDir('/project/.vgc');
79
+
80
+ expect(loader.hasConfigDirectory('/project')).toBe(true);
81
+ });
82
+
83
+ test('returns false when .vgc directory does not exist', () => {
84
+ expect(loader.hasConfigDirectory('/project')).toBe(false);
85
+ });
86
+
87
+ test('returns false when .vgc exists but is not a directory', () => {
88
+ fsAdapter.writeFile('/project/.vgc', 'not a directory');
89
+
90
+ expect(loader.hasConfigDirectory('/project')).toBe(false);
91
+ });
92
+ });
93
+
94
+ describe('listConfigurations', () => {
95
+ test('returns empty array when .vgc does not exist', () => {
96
+ const configs = loader.listConfigurations('/project');
97
+
98
+ expect(configs).toEqual([]);
99
+ });
100
+
101
+ test('returns list of configuration names', () => {
102
+ fsAdapter.createDir('/project/.vgc');
103
+ fsAdapter.writeFile('/project/.vgc/architecture.yaml', JSON.stringify(validConfig));
104
+ fsAdapter.writeFile('/project/.vgc/data-flow.yaml', JSON.stringify(validConfig));
105
+ fsAdapter.writeFile('/project/.vgc/deployment.yml', JSON.stringify(validConfig));
106
+
107
+ const configs = loader.listConfigurations('/project');
108
+
109
+ expect(configs).toEqual(['architecture', 'data-flow', 'deployment']);
110
+ });
111
+
112
+ test('filters out non-YAML files', () => {
113
+ fsAdapter.createDir('/project/.vgc');
114
+ fsAdapter.writeFile('/project/.vgc/config1.yaml', JSON.stringify(validConfig));
115
+ fsAdapter.writeFile('/project/.vgc/README.md', '# README');
116
+ fsAdapter.writeFile('/project/.vgc/config.json', '{}');
117
+
118
+ const configs = loader.listConfigurations('/project');
119
+
120
+ expect(configs).toEqual(['config1']);
121
+ });
122
+
123
+ test('returns sorted configuration names', () => {
124
+ fsAdapter.createDir('/project/.vgc');
125
+ fsAdapter.writeFile('/project/.vgc/zebra.yaml', JSON.stringify(validConfig));
126
+ fsAdapter.writeFile('/project/.vgc/alpha.yaml', JSON.stringify(validConfig));
127
+ fsAdapter.writeFile('/project/.vgc/middle.yaml', JSON.stringify(validConfig));
128
+
129
+ const configs = loader.listConfigurations('/project');
130
+
131
+ expect(configs).toEqual(['alpha', 'middle', 'zebra']);
132
+ });
133
+ });
134
+
135
+ describe('loadByName', () => {
136
+ beforeEach(() => {
137
+ fsAdapter.createDir('/project/.vgc');
138
+ });
139
+
140
+ test('loads configuration by name with .yaml extension', () => {
141
+ const yamlContent = `
142
+ metadata:
143
+ name: Test Config
144
+ version: 1.0.0
145
+ nodeTypes:
146
+ service:
147
+ shape: rectangle
148
+ color: '#4A90E2'
149
+ dataSchema:
150
+ name:
151
+ type: string
152
+ required: true
153
+ edgeTypes:
154
+ call:
155
+ style: solid
156
+ color: '#999'
157
+ allowedConnections:
158
+ - from: service
159
+ to: service
160
+ via: call
161
+ `;
162
+ fsAdapter.writeFile('/project/.vgc/simple.yaml', yamlContent);
163
+
164
+ const result = loader.loadByName('simple', '/project');
165
+
166
+ expect(result).not.toBeNull();
167
+ expect(result?.name).toBe('simple');
168
+ expect(result?.config.metadata.name).toBe('Test Config');
169
+ });
170
+
171
+ test('loads configuration by name with .yml extension', () => {
172
+ const yamlContent = `
173
+ metadata:
174
+ name: Alternative Config
175
+ version: 2.0.0
176
+ nodeTypes:
177
+ component:
178
+ shape: hexagon
179
+ color: '#FF6B6B'
180
+ dataSchema:
181
+ id:
182
+ type: string
183
+ required: true
184
+ edgeTypes:
185
+ link:
186
+ style: dashed
187
+ color: '#888'
188
+ allowedConnections:
189
+ - from: component
190
+ to: component
191
+ via: link
192
+ `;
193
+ fsAdapter.writeFile('/project/.vgc/alt.yml', yamlContent);
194
+
195
+ const result = loader.loadByName('alt', '/project');
196
+
197
+ expect(result).not.toBeNull();
198
+ expect(result?.name).toBe('alt');
199
+ expect(result?.config.metadata.name).toBe('Alternative Config');
200
+ });
201
+
202
+ test('returns null when configuration does not exist', () => {
203
+ const result = loader.loadByName('nonexistent', '/project');
204
+
205
+ expect(result).toBeNull();
206
+ });
207
+
208
+ test('returns null when .vgc directory does not exist', () => {
209
+ const result = loader.loadByName('any', '/no-project');
210
+
211
+ expect(result).toBeNull();
212
+ });
213
+
214
+ test('returns null when YAML is invalid', () => {
215
+ fsAdapter.writeFile('/project/.vgc/invalid.yaml', 'invalid: yaml: content:');
216
+
217
+ const result = loader.loadByName('invalid', '/project');
218
+
219
+ expect(result).toBeNull();
220
+ });
221
+
222
+ test('returns null when configuration structure is invalid', () => {
223
+ const yamlContent = `
224
+ metadata:
225
+ name: Incomplete
226
+ nodeTypes: {}
227
+ # Missing edgeTypes and allowedConnections
228
+ `;
229
+ fsAdapter.writeFile('/project/.vgc/incomplete.yaml', yamlContent);
230
+
231
+ const result = loader.loadByName('incomplete', '/project');
232
+
233
+ expect(result).toBeNull();
234
+ });
235
+
236
+ test('prefers .yaml over .yml when both exist', () => {
237
+ const yamlContent = `
238
+ metadata:
239
+ name: YAML Version
240
+ version: 1.0.0
241
+ nodeTypes:
242
+ service:
243
+ shape: rectangle
244
+ dataSchema:
245
+ name:
246
+ type: string
247
+ edgeTypes:
248
+ call:
249
+ style: solid
250
+ allowedConnections:
251
+ - from: service
252
+ to: service
253
+ via: call
254
+ `;
255
+ const ymlContent = `
256
+ metadata:
257
+ name: YML Version
258
+ version: 1.0.0
259
+ nodeTypes:
260
+ service:
261
+ shape: rectangle
262
+ dataSchema:
263
+ name:
264
+ type: string
265
+ edgeTypes:
266
+ call:
267
+ style: solid
268
+ allowedConnections:
269
+ - from: service
270
+ to: service
271
+ via: call
272
+ `;
273
+ fsAdapter.writeFile('/project/.vgc/both.yaml', yamlContent);
274
+ fsAdapter.writeFile('/project/.vgc/both.yml', ymlContent);
275
+
276
+ const result = loader.loadByName('both', '/project');
277
+
278
+ expect(result).not.toBeNull();
279
+ expect(result?.config.metadata.name).toBe('YAML Version');
280
+ });
281
+ });
282
+
283
+ describe('loadAll', () => {
284
+ test('returns error when .vgc directory does not exist', () => {
285
+ const result = loader.loadAll('/project');
286
+
287
+ expect(result.configs).toEqual([]);
288
+ expect(result.errors).toHaveLength(1);
289
+ expect(result.errors[0].file).toBe('.vgc');
290
+ expect(result.errors[0].error).toContain('not found');
291
+ });
292
+
293
+ test('loads all valid configurations', () => {
294
+ fsAdapter.createDir('/project/.vgc');
295
+
296
+ const config1 = `
297
+ metadata:
298
+ name: Config 1
299
+ version: 1.0.0
300
+ nodeTypes:
301
+ service:
302
+ shape: rectangle
303
+ dataSchema:
304
+ name:
305
+ type: string
306
+ edgeTypes:
307
+ call:
308
+ style: solid
309
+ allowedConnections:
310
+ - from: service
311
+ to: service
312
+ via: call
313
+ `;
314
+ const config2 = `
315
+ metadata:
316
+ name: Config 2
317
+ version: 2.0.0
318
+ nodeTypes:
319
+ component:
320
+ shape: hexagon
321
+ dataSchema:
322
+ id:
323
+ type: string
324
+ edgeTypes:
325
+ link:
326
+ style: dashed
327
+ allowedConnections:
328
+ - from: component
329
+ to: component
330
+ via: link
331
+ `;
332
+
333
+ fsAdapter.writeFile('/project/.vgc/first.yaml', config1);
334
+ fsAdapter.writeFile('/project/.vgc/second.yml', config2);
335
+
336
+ const result = loader.loadAll('/project');
337
+
338
+ expect(result.configs).toHaveLength(2);
339
+ expect(result.errors).toEqual([]);
340
+ expect(result.configs[0].name).toBe('first');
341
+ expect(result.configs[1].name).toBe('second');
342
+ });
343
+
344
+ test('skips non-YAML files', () => {
345
+ fsAdapter.createDir('/project/.vgc');
346
+
347
+ const validYaml = `
348
+ metadata:
349
+ name: Valid
350
+ version: 1.0.0
351
+ nodeTypes:
352
+ service:
353
+ shape: rectangle
354
+ dataSchema:
355
+ name:
356
+ type: string
357
+ edgeTypes:
358
+ call:
359
+ style: solid
360
+ allowedConnections:
361
+ - from: service
362
+ to: service
363
+ via: call
364
+ `;
365
+
366
+ fsAdapter.writeFile('/project/.vgc/valid.yaml', validYaml);
367
+ fsAdapter.writeFile('/project/.vgc/README.md', '# README');
368
+ fsAdapter.writeFile('/project/.vgc/config.json', '{}');
369
+
370
+ const result = loader.loadAll('/project');
371
+
372
+ expect(result.configs).toHaveLength(1);
373
+ expect(result.configs[0].name).toBe('valid');
374
+ expect(result.errors).toEqual([]);
375
+ });
376
+
377
+ test('collects errors for invalid files', () => {
378
+ fsAdapter.createDir('/project/.vgc');
379
+
380
+ const validYaml = `
381
+ metadata:
382
+ name: Valid
383
+ version: 1.0.0
384
+ nodeTypes:
385
+ service:
386
+ shape: rectangle
387
+ dataSchema:
388
+ name:
389
+ type: string
390
+ edgeTypes:
391
+ call:
392
+ style: solid
393
+ allowedConnections:
394
+ - from: service
395
+ to: service
396
+ via: call
397
+ `;
398
+
399
+ fsAdapter.writeFile('/project/.vgc/valid.yaml', validYaml);
400
+ fsAdapter.writeFile('/project/.vgc/invalid-yaml.yaml', 'invalid: yaml: content:');
401
+ fsAdapter.writeFile('/project/.vgc/incomplete.yaml', 'metadata:\n name: Incomplete\nnodeTypes: {}');
402
+
403
+ const result = loader.loadAll('/project');
404
+
405
+ expect(result.configs).toHaveLength(1);
406
+ expect(result.configs[0].name).toBe('valid');
407
+ expect(result.errors.length).toBeGreaterThan(0);
408
+ });
409
+
410
+ test('returns configs sorted by name', () => {
411
+ fsAdapter.createDir('/project/.vgc');
412
+
413
+ const configTemplate = (name: string) => `
414
+ metadata:
415
+ name: ${name}
416
+ version: 1.0.0
417
+ nodeTypes:
418
+ service:
419
+ shape: rectangle
420
+ dataSchema:
421
+ name:
422
+ type: string
423
+ edgeTypes:
424
+ call:
425
+ style: solid
426
+ allowedConnections:
427
+ - from: service
428
+ to: service
429
+ via: call
430
+ `;
431
+
432
+ fsAdapter.writeFile('/project/.vgc/zebra.yaml', configTemplate('Zebra'));
433
+ fsAdapter.writeFile('/project/.vgc/alpha.yaml', configTemplate('Alpha'));
434
+ fsAdapter.writeFile('/project/.vgc/middle.yaml', configTemplate('Middle'));
435
+
436
+ const result = loader.loadAll('/project');
437
+
438
+ expect(result.configs).toHaveLength(3);
439
+ expect(result.configs[0].name).toBe('alpha');
440
+ expect(result.configs[1].name).toBe('middle');
441
+ expect(result.configs[2].name).toBe('zebra');
442
+ });
443
+
444
+ test('handles mixed .yaml and .yml extensions', () => {
445
+ fsAdapter.createDir('/project/.vgc');
446
+
447
+ const configTemplate = (name: string) => `
448
+ metadata:
449
+ name: ${name}
450
+ version: 1.0.0
451
+ nodeTypes:
452
+ service:
453
+ shape: rectangle
454
+ dataSchema:
455
+ name:
456
+ type: string
457
+ edgeTypes:
458
+ call:
459
+ style: solid
460
+ allowedConnections:
461
+ - from: service
462
+ to: service
463
+ via: call
464
+ `;
465
+
466
+ fsAdapter.writeFile('/project/.vgc/config1.yaml', configTemplate('Config 1'));
467
+ fsAdapter.writeFile('/project/.vgc/config2.yml', configTemplate('Config 2'));
468
+
469
+ const result = loader.loadAll('/project');
470
+
471
+ expect(result.configs).toHaveLength(2);
472
+ expect(result.errors).toEqual([]);
473
+ });
474
+ });
475
+
476
+ describe('getConfigDirectoryPath', () => {
477
+ test('returns correct .vgc path', () => {
478
+ const path = loader.getConfigDirectoryPath('/my/project');
479
+
480
+ expect(path).toBe('/my/project/.vgc');
481
+ });
482
+
483
+ test('handles trailing slashes', () => {
484
+ const path = loader.getConfigDirectoryPath('/my/project/');
485
+
486
+ // The path should be consistent regardless of trailing slash
487
+ expect(path).toContain('.vgc');
488
+ });
489
+ });
490
+ });