@living-architecture/riviere-builder 0.2.1
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 +11 -0
- package/dist/builder-assertions.d.ts +5 -0
- package/dist/builder-assertions.d.ts.map +1 -0
- package/dist/builder-assertions.js +24 -0
- package/dist/builder-test-fixtures.d.ts +7 -0
- package/dist/builder-test-fixtures.d.ts.map +1 -0
- package/dist/builder-test-fixtures.js +12 -0
- package/dist/builder.d.ts +485 -0
- package/dist/builder.d.ts.map +1 -0
- package/dist/builder.js +749 -0
- package/dist/component-suggestion.d.ts +35 -0
- package/dist/component-suggestion.d.ts.map +1 -0
- package/dist/component-suggestion.js +66 -0
- package/dist/errors.d.ts +28 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +59 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/inspection.d.ts +90 -0
- package/dist/inspection.d.ts.map +1 -0
- package/dist/inspection.js +137 -0
- package/dist/string-similarity.d.ts +32 -0
- package/dist/string-similarity.d.ts.map +1 -0
- package/dist/string-similarity.js +61 -0
- package/dist/types.d.ts +149 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/package.json +29 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Component, ComponentId } from '@living-architecture/riviere-schema';
|
|
2
|
+
import { ComponentNotFoundError } from './errors';
|
|
3
|
+
import type { NearMatchOptions, NearMatchQuery, NearMatchResult } from './types';
|
|
4
|
+
/**
|
|
5
|
+
* Finds components similar to a query using fuzzy matching.
|
|
6
|
+
*
|
|
7
|
+
* Used for error recovery to suggest alternatives when exact matches fail.
|
|
8
|
+
*
|
|
9
|
+
* @param components - Array of components to search
|
|
10
|
+
* @param query - Search criteria with name and optional type/domain filters
|
|
11
|
+
* @param options - Optional threshold and limit settings
|
|
12
|
+
* @returns Array of matching components with similarity scores
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* const matches = findNearMatches(components, { name: 'Create Ordr' })
|
|
17
|
+
* // [{ component: {...}, score: 0.9, mismatch: undefined }]
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export declare function findNearMatches(components: Component[], query: NearMatchQuery, options?: NearMatchOptions): NearMatchResult[];
|
|
21
|
+
/**
|
|
22
|
+
* Creates a typed error with suggestions for a missing source component.
|
|
23
|
+
*
|
|
24
|
+
* @param components - Array of existing components to search for suggestions
|
|
25
|
+
* @param id - The ComponentId that was not found
|
|
26
|
+
* @returns ComponentNotFoundError with suggestions array for programmatic access
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```typescript
|
|
30
|
+
* const error = createSourceNotFoundError(components, ComponentId.parse('orders:checkout:api:create-ordr'))
|
|
31
|
+
* // ComponentNotFoundError with suggestions: ['orders:checkout:api:create-order']
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export declare function createSourceNotFoundError(components: Component[], id: ComponentId): ComponentNotFoundError;
|
|
35
|
+
//# sourceMappingURL=component-suggestion.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"component-suggestion.d.ts","sourceRoot":"","sources":["../src/component-suggestion.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,qCAAqC,CAAA;AACjF,OAAO,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAA;AAEjD,OAAO,KAAK,EAAqB,gBAAgB,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAoBnG;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,eAAe,CAC7B,UAAU,EAAE,SAAS,EAAE,EACvB,KAAK,EAAE,cAAc,EACrB,OAAO,CAAC,EAAE,gBAAgB,GACzB,eAAe,EAAE,CAmBnB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,yBAAyB,CAAC,UAAU,EAAE,SAAS,EAAE,EAAE,EAAE,EAAE,WAAW,GAAG,sBAAsB,CAI1G"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { ComponentNotFoundError } from './errors';
|
|
2
|
+
import { similarityScore } from './string-similarity';
|
|
3
|
+
function detectMismatch(query, component) {
|
|
4
|
+
const nameMatches = query.name.toLowerCase() === component.name.toLowerCase();
|
|
5
|
+
if (!nameMatches) {
|
|
6
|
+
return undefined;
|
|
7
|
+
}
|
|
8
|
+
if (query.type !== undefined && query.type !== component.type) {
|
|
9
|
+
return { field: 'type', expected: query.type, actual: component.type };
|
|
10
|
+
}
|
|
11
|
+
if (query.domain !== undefined && query.domain !== component.domain) {
|
|
12
|
+
return { field: 'domain', expected: query.domain, actual: component.domain };
|
|
13
|
+
}
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Finds components similar to a query using fuzzy matching.
|
|
18
|
+
*
|
|
19
|
+
* Used for error recovery to suggest alternatives when exact matches fail.
|
|
20
|
+
*
|
|
21
|
+
* @param components - Array of components to search
|
|
22
|
+
* @param query - Search criteria with name and optional type/domain filters
|
|
23
|
+
* @param options - Optional threshold and limit settings
|
|
24
|
+
* @returns Array of matching components with similarity scores
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```typescript
|
|
28
|
+
* const matches = findNearMatches(components, { name: 'Create Ordr' })
|
|
29
|
+
* // [{ component: {...}, score: 0.9, mismatch: undefined }]
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export function findNearMatches(components, query, options) {
|
|
33
|
+
if (query.name === '') {
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
const threshold = options?.threshold ?? 0.6;
|
|
37
|
+
const limit = options?.limit ?? 10;
|
|
38
|
+
const results = components
|
|
39
|
+
.map((component) => {
|
|
40
|
+
const score = similarityScore(query.name, component.name);
|
|
41
|
+
const mismatch = detectMismatch(query, component);
|
|
42
|
+
return { component, score, mismatch };
|
|
43
|
+
})
|
|
44
|
+
.filter((result) => result.score >= threshold || result.mismatch !== undefined)
|
|
45
|
+
.sort((a, b) => b.score - a.score)
|
|
46
|
+
.slice(0, limit);
|
|
47
|
+
return results;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Creates a typed error with suggestions for a missing source component.
|
|
51
|
+
*
|
|
52
|
+
* @param components - Array of existing components to search for suggestions
|
|
53
|
+
* @param id - The ComponentId that was not found
|
|
54
|
+
* @returns ComponentNotFoundError with suggestions array for programmatic access
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```typescript
|
|
58
|
+
* const error = createSourceNotFoundError(components, ComponentId.parse('orders:checkout:api:create-ordr'))
|
|
59
|
+
* // ComponentNotFoundError with suggestions: ['orders:checkout:api:create-order']
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
export function createSourceNotFoundError(components, id) {
|
|
63
|
+
const matches = findNearMatches(components, { name: id.name() }, { limit: 3 });
|
|
64
|
+
const suggestions = matches.map((s) => s.component.id);
|
|
65
|
+
return new ComponentNotFoundError(id.toString(), suggestions);
|
|
66
|
+
}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export declare class DuplicateDomainError extends Error {
|
|
2
|
+
readonly domainName: string;
|
|
3
|
+
constructor(domainName: string);
|
|
4
|
+
}
|
|
5
|
+
export declare class DomainNotFoundError extends Error {
|
|
6
|
+
readonly domainName: string;
|
|
7
|
+
constructor(domainName: string);
|
|
8
|
+
}
|
|
9
|
+
export declare class CustomTypeNotFoundError extends Error {
|
|
10
|
+
readonly customTypeName: string;
|
|
11
|
+
readonly definedTypes: string[];
|
|
12
|
+
constructor(customTypeName: string, definedTypes: string[]);
|
|
13
|
+
}
|
|
14
|
+
export declare class DuplicateComponentError extends Error {
|
|
15
|
+
readonly componentId: string;
|
|
16
|
+
constructor(componentId: string);
|
|
17
|
+
}
|
|
18
|
+
export declare class ComponentNotFoundError extends Error {
|
|
19
|
+
readonly componentId: string;
|
|
20
|
+
readonly suggestions: string[];
|
|
21
|
+
constructor(componentId: string, suggestions?: string[]);
|
|
22
|
+
}
|
|
23
|
+
export declare class InvalidEnrichmentTargetError extends Error {
|
|
24
|
+
readonly componentId: string;
|
|
25
|
+
readonly componentType: string;
|
|
26
|
+
constructor(componentId: string, componentType: string);
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;gBAEf,UAAU,EAAE,MAAM;CAK/B;AAED,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;gBAEf,UAAU,EAAE,MAAM;CAK/B;AAED,qBAAa,uBAAwB,SAAQ,KAAK;IAChD,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAA;IAC/B,QAAQ,CAAC,YAAY,EAAE,MAAM,EAAE,CAAA;gBAEnB,cAAc,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE;CAU3D;AAED,qBAAa,uBAAwB,SAAQ,KAAK;IAChD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;gBAEhB,WAAW,EAAE,MAAM;CAKhC;AAED,qBAAa,sBAAuB,SAAQ,KAAK;IAC/C,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,WAAW,EAAE,MAAM,EAAE,CAAA;gBAElB,WAAW,EAAE,MAAM,EAAE,WAAW,GAAE,MAAM,EAAO;CAS5D;AAED,qBAAa,4BAA6B,SAAQ,KAAK;IACrD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;gBAElB,WAAW,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM;CAMvD"}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export class DuplicateDomainError extends Error {
|
|
2
|
+
domainName;
|
|
3
|
+
constructor(domainName) {
|
|
4
|
+
super(`Domain '${domainName}' already exists`);
|
|
5
|
+
this.name = 'DuplicateDomainError';
|
|
6
|
+
this.domainName = domainName;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export class DomainNotFoundError extends Error {
|
|
10
|
+
domainName;
|
|
11
|
+
constructor(domainName) {
|
|
12
|
+
super(`Domain '${domainName}' does not exist`);
|
|
13
|
+
this.name = 'DomainNotFoundError';
|
|
14
|
+
this.domainName = domainName;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export class CustomTypeNotFoundError extends Error {
|
|
18
|
+
customTypeName;
|
|
19
|
+
definedTypes;
|
|
20
|
+
constructor(customTypeName, definedTypes) {
|
|
21
|
+
const suffix = definedTypes.length === 0
|
|
22
|
+
? 'No custom types have been defined.'
|
|
23
|
+
: `Defined types: ${definedTypes.join(', ')}`;
|
|
24
|
+
super(`Custom type '${customTypeName}' not defined. ${suffix}`);
|
|
25
|
+
this.name = 'CustomTypeNotFoundError';
|
|
26
|
+
this.customTypeName = customTypeName;
|
|
27
|
+
this.definedTypes = definedTypes;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export class DuplicateComponentError extends Error {
|
|
31
|
+
componentId;
|
|
32
|
+
constructor(componentId) {
|
|
33
|
+
super(`Component with ID '${componentId}' already exists`);
|
|
34
|
+
this.name = 'DuplicateComponentError';
|
|
35
|
+
this.componentId = componentId;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export class ComponentNotFoundError extends Error {
|
|
39
|
+
componentId;
|
|
40
|
+
suggestions;
|
|
41
|
+
constructor(componentId, suggestions = []) {
|
|
42
|
+
const baseMessage = `Source component '${componentId}' not found`;
|
|
43
|
+
const message = suggestions.length > 0 ? `${baseMessage}. Did you mean: ${suggestions.join(', ')}?` : baseMessage;
|
|
44
|
+
super(message);
|
|
45
|
+
this.name = 'ComponentNotFoundError';
|
|
46
|
+
this.componentId = componentId;
|
|
47
|
+
this.suggestions = suggestions;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
export class InvalidEnrichmentTargetError extends Error {
|
|
51
|
+
componentId;
|
|
52
|
+
componentType;
|
|
53
|
+
constructor(componentId, componentType) {
|
|
54
|
+
super(`Only DomainOp components can be enriched. '${componentId}' is type '${componentType}'`);
|
|
55
|
+
this.name = 'InvalidEnrichmentTargetError';
|
|
56
|
+
this.componentId = componentId;
|
|
57
|
+
this.componentType = componentType;
|
|
58
|
+
}
|
|
59
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC;AAC1B,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,qCAAqC,CAAC;AACzF,cAAc,UAAU,CAAC;AACzB,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { Component, CustomTypeDefinition, DomainMetadata, ExternalLink, Link, RiviereGraph, SourceInfo } from '@living-architecture/riviere-schema';
|
|
2
|
+
import { type ValidationResult } from '@living-architecture/riviere-query';
|
|
3
|
+
import type { BuilderStats, BuilderWarning } from './types';
|
|
4
|
+
interface InspectionGraph {
|
|
5
|
+
version: string;
|
|
6
|
+
metadata: {
|
|
7
|
+
name?: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
generated?: string;
|
|
10
|
+
sources: SourceInfo[];
|
|
11
|
+
domains: Record<string, DomainMetadata>;
|
|
12
|
+
customTypes: Record<string, CustomTypeDefinition>;
|
|
13
|
+
};
|
|
14
|
+
components: Component[];
|
|
15
|
+
links: Link[];
|
|
16
|
+
externalLinks: ExternalLink[];
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Finds components with no incoming or outgoing links.
|
|
20
|
+
*
|
|
21
|
+
* @param graph - The graph to inspect
|
|
22
|
+
* @returns Array of orphaned component IDs
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```typescript
|
|
26
|
+
* const orphans = findOrphans(graph)
|
|
27
|
+
* // ['orders:checkout:api:unused-endpoint']
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export declare function findOrphans(graph: InspectionGraph): string[];
|
|
31
|
+
/**
|
|
32
|
+
* Calculates statistics about the graph.
|
|
33
|
+
*
|
|
34
|
+
* @param graph - The graph to analyze
|
|
35
|
+
* @returns Object with component counts, link counts, and domain count
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```typescript
|
|
39
|
+
* const stats = calculateStats(graph)
|
|
40
|
+
* // { componentCount: 10, linkCount: 8, domainCount: 2, ... }
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export declare function calculateStats(graph: InspectionGraph): BuilderStats;
|
|
44
|
+
/**
|
|
45
|
+
* Finds non-fatal issues in the graph.
|
|
46
|
+
*
|
|
47
|
+
* Detects orphaned components and unused domains.
|
|
48
|
+
*
|
|
49
|
+
* @param graph - The graph to inspect
|
|
50
|
+
* @returns Array of warning objects
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```typescript
|
|
54
|
+
* const warnings = findWarnings(graph)
|
|
55
|
+
* // [{ code: 'ORPHAN_COMPONENT', message: '...', componentId: '...' }]
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
export declare function findWarnings(graph: InspectionGraph): BuilderWarning[];
|
|
59
|
+
/**
|
|
60
|
+
* Converts builder internal graph to schema-compliant RiviereGraph.
|
|
61
|
+
*
|
|
62
|
+
* Removes undefined optional fields and ensures proper structure.
|
|
63
|
+
*
|
|
64
|
+
* @param graph - The internal builder graph
|
|
65
|
+
* @returns Schema-compliant RiviereGraph
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* ```typescript
|
|
69
|
+
* const output = toRiviereGraph(builderGraph)
|
|
70
|
+
* JSON.stringify(output) // Valid Rivière JSON
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
export declare function toRiviereGraph(graph: InspectionGraph): RiviereGraph;
|
|
74
|
+
/**
|
|
75
|
+
* Validates the graph against the Rivière schema.
|
|
76
|
+
*
|
|
77
|
+
* @param graph - The graph to validate
|
|
78
|
+
* @returns Validation result with valid flag and any errors
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* ```typescript
|
|
82
|
+
* const result = validateGraph(graph)
|
|
83
|
+
* if (!result.valid) {
|
|
84
|
+
* console.error(result.errors)
|
|
85
|
+
* }
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
export declare function validateGraph(graph: InspectionGraph): ValidationResult;
|
|
89
|
+
export {};
|
|
90
|
+
//# sourceMappingURL=inspection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inspection.d.ts","sourceRoot":"","sources":["../src/inspection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,SAAS,EACT,oBAAoB,EACpB,cAAc,EACd,YAAY,EACZ,IAAI,EACJ,YAAY,EACZ,UAAU,EACX,MAAM,qCAAqC,CAAA;AAC5C,OAAO,EAAgB,KAAK,gBAAgB,EAAE,MAAM,oCAAoC,CAAA;AACxF,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAE3D,UAAU,eAAe;IACvB,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE;QACR,IAAI,CAAC,EAAE,MAAM,CAAA;QACb,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,SAAS,CAAC,EAAE,MAAM,CAAA;QAClB,OAAO,EAAE,UAAU,EAAE,CAAA;QACrB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAA;QACvC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAA;KAClD,CAAA;IACD,UAAU,EAAE,SAAS,EAAE,CAAA;IACvB,KAAK,EAAE,IAAI,EAAE,CAAA;IACb,aAAa,EAAE,YAAY,EAAE,CAAA;CAC9B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,eAAe,GAAG,MAAM,EAAE,CAa5D;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,eAAe,GAAG,YAAY,CAiBnE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,eAAe,GAAG,cAAc,EAAE,CAuBrE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,eAAe,GAAG,YAAY,CAiBnE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,eAAe,GAAG,gBAAgB,CAEtE"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { RiviereQuery } from '@living-architecture/riviere-query';
|
|
2
|
+
/**
|
|
3
|
+
* Finds components with no incoming or outgoing links.
|
|
4
|
+
*
|
|
5
|
+
* @param graph - The graph to inspect
|
|
6
|
+
* @returns Array of orphaned component IDs
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* const orphans = findOrphans(graph)
|
|
11
|
+
* // ['orders:checkout:api:unused-endpoint']
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
export function findOrphans(graph) {
|
|
15
|
+
const connectedIds = new Set();
|
|
16
|
+
for (const link of graph.links) {
|
|
17
|
+
connectedIds.add(link.source);
|
|
18
|
+
connectedIds.add(link.target);
|
|
19
|
+
}
|
|
20
|
+
for (const externalLink of graph.externalLinks) {
|
|
21
|
+
connectedIds.add(externalLink.source);
|
|
22
|
+
}
|
|
23
|
+
return graph.components.filter((c) => !connectedIds.has(c.id)).map((c) => c.id);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Calculates statistics about the graph.
|
|
27
|
+
*
|
|
28
|
+
* @param graph - The graph to analyze
|
|
29
|
+
* @returns Object with component counts, link counts, and domain count
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```typescript
|
|
33
|
+
* const stats = calculateStats(graph)
|
|
34
|
+
* // { componentCount: 10, linkCount: 8, domainCount: 2, ... }
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export function calculateStats(graph) {
|
|
38
|
+
const components = graph.components;
|
|
39
|
+
return {
|
|
40
|
+
componentCount: components.length,
|
|
41
|
+
componentsByType: {
|
|
42
|
+
UI: components.filter((c) => c.type === 'UI').length,
|
|
43
|
+
API: components.filter((c) => c.type === 'API').length,
|
|
44
|
+
UseCase: components.filter((c) => c.type === 'UseCase').length,
|
|
45
|
+
DomainOp: components.filter((c) => c.type === 'DomainOp').length,
|
|
46
|
+
Event: components.filter((c) => c.type === 'Event').length,
|
|
47
|
+
EventHandler: components.filter((c) => c.type === 'EventHandler').length,
|
|
48
|
+
Custom: components.filter((c) => c.type === 'Custom').length,
|
|
49
|
+
},
|
|
50
|
+
linkCount: graph.links.length,
|
|
51
|
+
externalLinkCount: graph.externalLinks.length,
|
|
52
|
+
domainCount: Object.keys(graph.metadata.domains).length,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Finds non-fatal issues in the graph.
|
|
57
|
+
*
|
|
58
|
+
* Detects orphaned components and unused domains.
|
|
59
|
+
*
|
|
60
|
+
* @param graph - The graph to inspect
|
|
61
|
+
* @returns Array of warning objects
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```typescript
|
|
65
|
+
* const warnings = findWarnings(graph)
|
|
66
|
+
* // [{ code: 'ORPHAN_COMPONENT', message: '...', componentId: '...' }]
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
export function findWarnings(graph) {
|
|
70
|
+
const warnings = [];
|
|
71
|
+
for (const id of findOrphans(graph)) {
|
|
72
|
+
warnings.push({
|
|
73
|
+
code: 'ORPHAN_COMPONENT',
|
|
74
|
+
message: `Component '${id}' has no incoming or outgoing links`,
|
|
75
|
+
componentId: id,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
const usedDomains = new Set(graph.components.map((c) => c.domain));
|
|
79
|
+
for (const domain of Object.keys(graph.metadata.domains)) {
|
|
80
|
+
if (!usedDomains.has(domain)) {
|
|
81
|
+
warnings.push({
|
|
82
|
+
code: 'UNUSED_DOMAIN',
|
|
83
|
+
message: `Domain '${domain}' is declared but has no components`,
|
|
84
|
+
domainName: domain,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return warnings;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Converts builder internal graph to schema-compliant RiviereGraph.
|
|
92
|
+
*
|
|
93
|
+
* Removes undefined optional fields and ensures proper structure.
|
|
94
|
+
*
|
|
95
|
+
* @param graph - The internal builder graph
|
|
96
|
+
* @returns Schema-compliant RiviereGraph
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* ```typescript
|
|
100
|
+
* const output = toRiviereGraph(builderGraph)
|
|
101
|
+
* JSON.stringify(output) // Valid Rivière JSON
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
export function toRiviereGraph(graph) {
|
|
105
|
+
const hasCustomTypes = Object.keys(graph.metadata.customTypes).length > 0;
|
|
106
|
+
const hasExternalLinks = graph.externalLinks.length > 0;
|
|
107
|
+
return {
|
|
108
|
+
version: graph.version,
|
|
109
|
+
metadata: {
|
|
110
|
+
...(graph.metadata.name !== undefined && { name: graph.metadata.name }),
|
|
111
|
+
...(graph.metadata.description !== undefined && { description: graph.metadata.description }),
|
|
112
|
+
sources: graph.metadata.sources,
|
|
113
|
+
domains: graph.metadata.domains,
|
|
114
|
+
...(hasCustomTypes && { customTypes: graph.metadata.customTypes }),
|
|
115
|
+
},
|
|
116
|
+
components: graph.components,
|
|
117
|
+
links: graph.links,
|
|
118
|
+
...(hasExternalLinks && { externalLinks: graph.externalLinks }),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Validates the graph against the Rivière schema.
|
|
123
|
+
*
|
|
124
|
+
* @param graph - The graph to validate
|
|
125
|
+
* @returns Validation result with valid flag and any errors
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* ```typescript
|
|
129
|
+
* const result = validateGraph(graph)
|
|
130
|
+
* if (!result.valid) {
|
|
131
|
+
* console.error(result.errors)
|
|
132
|
+
* }
|
|
133
|
+
* ```
|
|
134
|
+
*/
|
|
135
|
+
export function validateGraph(graph) {
|
|
136
|
+
return new RiviereQuery(toRiviereGraph(graph)).validate();
|
|
137
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calculates the Levenshtein edit distance between two strings.
|
|
3
|
+
*
|
|
4
|
+
* @param a - First string
|
|
5
|
+
* @param b - Second string
|
|
6
|
+
* @returns Number of single-character edits needed to transform a into b
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* levenshteinDistance('kitten', 'sitting') // 3
|
|
11
|
+
* levenshteinDistance('hello', 'hello') // 0
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
export declare function levenshteinDistance(a: string, b: string): number;
|
|
15
|
+
/**
|
|
16
|
+
* Calculates normalized similarity score between two strings.
|
|
17
|
+
*
|
|
18
|
+
* Returns 1.0 for identical strings, 0.0 for completely different strings.
|
|
19
|
+
* Case-insensitive comparison.
|
|
20
|
+
*
|
|
21
|
+
* @param a - First string
|
|
22
|
+
* @param b - Second string
|
|
23
|
+
* @returns Similarity score from 0.0 to 1.0
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```typescript
|
|
27
|
+
* similarityScore('Order', 'order') // 1.0
|
|
28
|
+
* similarityScore('Order', 'Ordr') // 0.8
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export declare function similarityScore(a: string, b: string): number;
|
|
32
|
+
//# sourceMappingURL=string-similarity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"string-similarity.d.ts","sourceRoot":"","sources":["../src/string-similarity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAqBhE;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAW5D"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calculates the Levenshtein edit distance between two strings.
|
|
3
|
+
*
|
|
4
|
+
* @param a - First string
|
|
5
|
+
* @param b - Second string
|
|
6
|
+
* @returns Number of single-character edits needed to transform a into b
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* levenshteinDistance('kitten', 'sitting') // 3
|
|
11
|
+
* levenshteinDistance('hello', 'hello') // 0
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
export function levenshteinDistance(a, b) {
|
|
15
|
+
if (a.length === 0)
|
|
16
|
+
return b.length;
|
|
17
|
+
if (b.length === 0)
|
|
18
|
+
return a.length;
|
|
19
|
+
const previousRow = Array.from({ length: b.length + 1 }, (_, j) => j);
|
|
20
|
+
const finalRow = [...a].reduce((currentRow, aChar, i) => {
|
|
21
|
+
const nextRow = [i + 1];
|
|
22
|
+
return [...b].reduce((row, bChar, j) => {
|
|
23
|
+
const cost = aChar === bChar ? 0 : 1;
|
|
24
|
+
const deletion = currentRow[j + 1];
|
|
25
|
+
const insertion = row[j];
|
|
26
|
+
const substitution = currentRow[j];
|
|
27
|
+
/* v8 ignore next -- @preserve */
|
|
28
|
+
const value = Math.min((insertion ?? 0) + 1, (deletion ?? 0) + 1, (substitution ?? 0) + cost);
|
|
29
|
+
return [...row, value];
|
|
30
|
+
}, nextRow);
|
|
31
|
+
}, previousRow);
|
|
32
|
+
/* v8 ignore next -- @preserve */
|
|
33
|
+
return finalRow[b.length] ?? 0;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Calculates normalized similarity score between two strings.
|
|
37
|
+
*
|
|
38
|
+
* Returns 1.0 for identical strings, 0.0 for completely different strings.
|
|
39
|
+
* Case-insensitive comparison.
|
|
40
|
+
*
|
|
41
|
+
* @param a - First string
|
|
42
|
+
* @param b - Second string
|
|
43
|
+
* @returns Similarity score from 0.0 to 1.0
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```typescript
|
|
47
|
+
* similarityScore('Order', 'order') // 1.0
|
|
48
|
+
* similarityScore('Order', 'Ordr') // 0.8
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
export function similarityScore(a, b) {
|
|
52
|
+
const aLower = a.toLowerCase();
|
|
53
|
+
const bLower = b.toLowerCase();
|
|
54
|
+
if (aLower.length === 0 && bLower.length === 0)
|
|
55
|
+
return 1.0;
|
|
56
|
+
if (aLower.length === 0 || bLower.length === 0)
|
|
57
|
+
return 0.0;
|
|
58
|
+
const distance = levenshteinDistance(aLower, bLower);
|
|
59
|
+
const maxLength = Math.max(aLower.length, bLower.length);
|
|
60
|
+
return 1 - distance / maxLength;
|
|
61
|
+
}
|