@living-architecture/riviere-query 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/RiviereQuery.d.ts +486 -0
- package/dist/RiviereQuery.d.ts.map +1 -0
- package/dist/RiviereQuery.js +553 -0
- package/dist/component-queries.d.ts +8 -0
- package/dist/component-queries.d.ts.map +1 -0
- package/dist/component-queries.js +24 -0
- package/dist/cross-domain-queries.d.ts +5 -0
- package/dist/cross-domain-queries.d.ts.map +1 -0
- package/dist/cross-domain-queries.js +92 -0
- package/dist/depth-queries.d.ts +4 -0
- package/dist/depth-queries.d.ts.map +1 -0
- package/dist/depth-queries.js +54 -0
- package/dist/domain-queries.d.ts +10 -0
- package/dist/domain-queries.d.ts.map +1 -0
- package/dist/domain-queries.js +101 -0
- package/dist/domain-types.d.ts +307 -0
- package/dist/domain-types.d.ts.map +1 -0
- package/dist/domain-types.js +111 -0
- package/dist/errors.d.ts +6 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +10 -0
- package/dist/event-queries.d.ts +5 -0
- package/dist/event-queries.d.ts.map +1 -0
- package/dist/event-queries.js +32 -0
- package/dist/event-types.d.ts +88 -0
- package/dist/event-types.d.ts.map +1 -0
- package/dist/event-types.js +1 -0
- package/dist/external-system-queries.d.ts +13 -0
- package/dist/external-system-queries.d.ts.map +1 -0
- package/dist/external-system-queries.js +54 -0
- package/dist/flow-queries.d.ts +17 -0
- package/dist/flow-queries.d.ts.map +1 -0
- package/dist/flow-queries.js +127 -0
- package/dist/graph-diff.d.ts +4 -0
- package/dist/graph-diff.d.ts.map +1 -0
- package/dist/graph-diff.js +53 -0
- package/dist/graph-validation.d.ts +5 -0
- package/dist/graph-validation.d.ts.map +1 -0
- package/dist/graph-validation.js +72 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/riviere-graph-fixtures.d.ts +37 -0
- package/dist/riviere-graph-fixtures.d.ts.map +1 -0
- package/dist/riviere-graph-fixtures.js +73 -0
- package/dist/stats-queries.d.ts +4 -0
- package/dist/stats-queries.d.ts.map +1 -0
- package/dist/stats-queries.js +14 -0
- package/package.json +29 -0
|
@@ -0,0 +1,553 @@
|
|
|
1
|
+
import { parseRiviereGraph } from '@living-architecture/riviere-schema';
|
|
2
|
+
import { findComponent, findAllComponents, componentById as lookupComponentById, searchComponents, componentsInDomain as filterByDomain, componentsByType as filterByType } from './component-queries';
|
|
3
|
+
import { queryDomains, operationsForEntity, queryEntities, businessRulesForEntity, transitionsForEntity, statesForEntity } from './domain-queries';
|
|
4
|
+
import { queryExternalDomains } from './external-system-queries';
|
|
5
|
+
import { findEntryPoints, traceFlowFrom, queryFlows, searchWithFlowContext } from './flow-queries';
|
|
6
|
+
import { queryCrossDomainLinks, queryDomainConnections } from './cross-domain-queries';
|
|
7
|
+
import { queryPublishedEvents, queryEventHandlers } from './event-queries';
|
|
8
|
+
import { validateGraph, detectOrphanComponents } from './graph-validation';
|
|
9
|
+
import { diffGraphs } from './graph-diff';
|
|
10
|
+
import { queryStats } from './stats-queries';
|
|
11
|
+
import { queryNodeDepths } from './depth-queries';
|
|
12
|
+
export { parseComponentId } from './domain-types';
|
|
13
|
+
export { ComponentNotFoundError } from './errors';
|
|
14
|
+
function assertValidGraph(graph) {
|
|
15
|
+
parseRiviereGraph(graph);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Query and analyze Riviere architecture graphs.
|
|
19
|
+
*
|
|
20
|
+
* RiviereQuery provides methods to explore components, trace execution flows,
|
|
21
|
+
* analyze domain models, and compare graph versions.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```typescript
|
|
25
|
+
* import { RiviereQuery } from '@living-architecture/riviere-query'
|
|
26
|
+
*
|
|
27
|
+
* // From JSON
|
|
28
|
+
* const query = RiviereQuery.fromJSON(graphData)
|
|
29
|
+
*
|
|
30
|
+
* // Query components
|
|
31
|
+
* const apis = query.componentsByType('API')
|
|
32
|
+
* const orderDomain = query.componentsInDomain('orders')
|
|
33
|
+
*
|
|
34
|
+
* // Trace flows
|
|
35
|
+
* const flow = query.traceFlow('orders:checkout:api:post-orders')
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export class RiviereQuery {
|
|
39
|
+
graph;
|
|
40
|
+
/**
|
|
41
|
+
* Creates a new RiviereQuery instance.
|
|
42
|
+
*
|
|
43
|
+
* @param graph - A valid RiviereGraph object
|
|
44
|
+
* @throws If the graph fails schema validation
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```typescript
|
|
48
|
+
* const graph: RiviereGraph = JSON.parse(jsonString)
|
|
49
|
+
* const query = new RiviereQuery(graph)
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
constructor(graph) {
|
|
53
|
+
assertValidGraph(graph);
|
|
54
|
+
this.graph = graph;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Creates a RiviereQuery from raw JSON data.
|
|
58
|
+
*
|
|
59
|
+
* @param json - Raw JSON data to parse as a RiviereGraph
|
|
60
|
+
* @returns A new RiviereQuery instance
|
|
61
|
+
* @throws If the JSON fails schema validation
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```typescript
|
|
65
|
+
* const jsonData = await fetch('/graph.json').then(r => r.json())
|
|
66
|
+
* const query = RiviereQuery.fromJSON(jsonData)
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
static fromJSON(json) {
|
|
70
|
+
assertValidGraph(json);
|
|
71
|
+
return new RiviereQuery(json);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Returns all components in the graph.
|
|
75
|
+
*
|
|
76
|
+
* @returns Array of all components
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* ```typescript
|
|
80
|
+
* const allComponents = query.components()
|
|
81
|
+
* console.log(`Total: ${allComponents.length}`)
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
components() {
|
|
85
|
+
return this.graph.components;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Returns all links in the graph.
|
|
89
|
+
*
|
|
90
|
+
* @returns Array of all links
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* ```typescript
|
|
94
|
+
* const allLinks = query.links()
|
|
95
|
+
* console.log(`Total links: ${allLinks.length}`)
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
98
|
+
links() {
|
|
99
|
+
return this.graph.links;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Validates the graph structure beyond schema validation.
|
|
103
|
+
*
|
|
104
|
+
* Checks for structural issues like invalid link references.
|
|
105
|
+
*
|
|
106
|
+
* @returns Validation result with any errors found
|
|
107
|
+
*
|
|
108
|
+
* @example
|
|
109
|
+
* ```typescript
|
|
110
|
+
* const result = query.validate()
|
|
111
|
+
* if (!result.valid) {
|
|
112
|
+
* console.error('Validation errors:', result.errors)
|
|
113
|
+
* }
|
|
114
|
+
* ```
|
|
115
|
+
*/
|
|
116
|
+
validate() {
|
|
117
|
+
return validateGraph(this.graph);
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Detects orphan components with no incoming or outgoing links.
|
|
121
|
+
*
|
|
122
|
+
* @returns Array of component IDs that are disconnected from the graph
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* ```typescript
|
|
126
|
+
* const orphanIds = query.detectOrphans()
|
|
127
|
+
* if (orphanIds.length > 0) {
|
|
128
|
+
* console.warn(`Found ${orphanIds.length} orphan nodes`)
|
|
129
|
+
* }
|
|
130
|
+
* ```
|
|
131
|
+
*/
|
|
132
|
+
detectOrphans() {
|
|
133
|
+
return detectOrphanComponents(this.graph);
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Finds the first component matching a predicate.
|
|
137
|
+
*
|
|
138
|
+
* @param predicate - Function that returns true for matching components
|
|
139
|
+
* @returns The first matching component, or undefined if none found
|
|
140
|
+
*
|
|
141
|
+
* @example
|
|
142
|
+
* ```typescript
|
|
143
|
+
* const checkout = query.find(c => c.name.includes('checkout'))
|
|
144
|
+
* ```
|
|
145
|
+
*/
|
|
146
|
+
find(predicate) {
|
|
147
|
+
return findComponent(this.graph, predicate);
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Finds all components matching a predicate.
|
|
151
|
+
*
|
|
152
|
+
* @param predicate - Function that returns true for matching components
|
|
153
|
+
* @returns Array of all matching components
|
|
154
|
+
*
|
|
155
|
+
* @example
|
|
156
|
+
* ```typescript
|
|
157
|
+
* const orderHandlers = query.findAll(c =>
|
|
158
|
+
* c.type === 'EventHandler' && c.domain === 'orders'
|
|
159
|
+
* )
|
|
160
|
+
* ```
|
|
161
|
+
*/
|
|
162
|
+
findAll(predicate) {
|
|
163
|
+
return findAllComponents(this.graph, predicate);
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Finds a component by its ID.
|
|
167
|
+
*
|
|
168
|
+
* @param id - The component ID to look up
|
|
169
|
+
* @returns The component, or undefined if not found
|
|
170
|
+
*
|
|
171
|
+
* @example
|
|
172
|
+
* ```typescript
|
|
173
|
+
* const component = query.componentById('orders:checkout:api:post-orders')
|
|
174
|
+
* ```
|
|
175
|
+
*/
|
|
176
|
+
componentById(id) {
|
|
177
|
+
return lookupComponentById(this.graph, id);
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Searches components by name, domain, or type.
|
|
181
|
+
*
|
|
182
|
+
* Case-insensitive search across component name, domain, and type fields.
|
|
183
|
+
*
|
|
184
|
+
* @param query - Search term
|
|
185
|
+
* @returns Array of matching components
|
|
186
|
+
*
|
|
187
|
+
* @example
|
|
188
|
+
* ```typescript
|
|
189
|
+
* const results = query.search('order')
|
|
190
|
+
* // Matches: "PlaceOrder", "orders" domain, etc.
|
|
191
|
+
* ```
|
|
192
|
+
*/
|
|
193
|
+
search(query) {
|
|
194
|
+
return searchComponents(this.graph, query);
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Returns all components in a specific domain.
|
|
198
|
+
*
|
|
199
|
+
* @param domainName - The domain name to filter by
|
|
200
|
+
* @returns Array of components in the domain
|
|
201
|
+
*
|
|
202
|
+
* @example
|
|
203
|
+
* ```typescript
|
|
204
|
+
* const orderComponents = query.componentsInDomain('orders')
|
|
205
|
+
* ```
|
|
206
|
+
*/
|
|
207
|
+
componentsInDomain(domainName) {
|
|
208
|
+
return filterByDomain(this.graph, domainName);
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Returns all components of a specific type.
|
|
212
|
+
*
|
|
213
|
+
* @param type - The component type to filter by
|
|
214
|
+
* @returns Array of components of that type
|
|
215
|
+
*
|
|
216
|
+
* @example
|
|
217
|
+
* ```typescript
|
|
218
|
+
* const apis = query.componentsByType('API')
|
|
219
|
+
* const events = query.componentsByType('Event')
|
|
220
|
+
* ```
|
|
221
|
+
*/
|
|
222
|
+
componentsByType(type) {
|
|
223
|
+
return filterByType(this.graph, type);
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Returns domain information with component counts.
|
|
227
|
+
*
|
|
228
|
+
* @returns Array of Domain objects sorted by name
|
|
229
|
+
*
|
|
230
|
+
* @example
|
|
231
|
+
* ```typescript
|
|
232
|
+
* const domains = query.domains()
|
|
233
|
+
* for (const domain of domains) {
|
|
234
|
+
* console.log(`${domain.name}: ${domain.componentCounts.total} components`)
|
|
235
|
+
* }
|
|
236
|
+
* ```
|
|
237
|
+
*/
|
|
238
|
+
domains() {
|
|
239
|
+
return queryDomains(this.graph);
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Returns all domain operations for a specific entity.
|
|
243
|
+
*
|
|
244
|
+
* @param entityName - The entity name to get operations for
|
|
245
|
+
* @returns Array of DomainOp components targeting the entity
|
|
246
|
+
*
|
|
247
|
+
* @example
|
|
248
|
+
* ```typescript
|
|
249
|
+
* const orderOps = query.operationsFor('Order')
|
|
250
|
+
* ```
|
|
251
|
+
*/
|
|
252
|
+
operationsFor(entityName) {
|
|
253
|
+
return operationsForEntity(this.graph, entityName);
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Returns entities with their domain operations.
|
|
257
|
+
*
|
|
258
|
+
* @param domainName - Optional domain to filter by
|
|
259
|
+
* @returns Array of Entity objects with their operations
|
|
260
|
+
*
|
|
261
|
+
* @example
|
|
262
|
+
* ```typescript
|
|
263
|
+
* const allEntities = query.entities()
|
|
264
|
+
* const orderEntities = query.entities('orders')
|
|
265
|
+
*
|
|
266
|
+
* for (const entity of orderEntities) {
|
|
267
|
+
* console.log(`${entity.name} has ${entity.operations.length} operations`)
|
|
268
|
+
* }
|
|
269
|
+
* ```
|
|
270
|
+
*/
|
|
271
|
+
entities(domainName) {
|
|
272
|
+
return queryEntities(this.graph, domainName);
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Returns all business rules for an entity's operations.
|
|
276
|
+
*
|
|
277
|
+
* @param entityName - The entity name to get rules for
|
|
278
|
+
* @returns Array of business rule strings
|
|
279
|
+
*
|
|
280
|
+
* @example
|
|
281
|
+
* ```typescript
|
|
282
|
+
* const rules = query.businessRulesFor('Order')
|
|
283
|
+
* ```
|
|
284
|
+
*/
|
|
285
|
+
businessRulesFor(entityName) {
|
|
286
|
+
return businessRulesForEntity(this.graph, entityName);
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Returns state transitions for an entity.
|
|
290
|
+
*
|
|
291
|
+
* @param entityName - The entity name to get transitions for
|
|
292
|
+
* @returns Array of EntityTransition objects
|
|
293
|
+
*
|
|
294
|
+
* @example
|
|
295
|
+
* ```typescript
|
|
296
|
+
* const transitions = query.transitionsFor('Order')
|
|
297
|
+
* ```
|
|
298
|
+
*/
|
|
299
|
+
transitionsFor(entityName) {
|
|
300
|
+
return transitionsForEntity(this.graph, entityName);
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Returns ordered states for an entity based on transitions.
|
|
304
|
+
*
|
|
305
|
+
* States are ordered by transition flow from initial to final states.
|
|
306
|
+
*
|
|
307
|
+
* @param entityName - The entity name to get states for
|
|
308
|
+
* @returns Array of state names in transition order
|
|
309
|
+
*
|
|
310
|
+
* @example
|
|
311
|
+
* ```typescript
|
|
312
|
+
* const orderStates = query.statesFor('Order')
|
|
313
|
+
* // ['pending', 'confirmed', 'shipped', 'delivered']
|
|
314
|
+
* ```
|
|
315
|
+
*/
|
|
316
|
+
statesFor(entityName) {
|
|
317
|
+
return statesForEntity(this.graph, entityName);
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Returns components that are entry points to the system.
|
|
321
|
+
*
|
|
322
|
+
* Entry points are UI, API, EventHandler, or Custom components
|
|
323
|
+
* with no incoming links.
|
|
324
|
+
*
|
|
325
|
+
* @returns Array of entry point components
|
|
326
|
+
*
|
|
327
|
+
* @example
|
|
328
|
+
* ```typescript
|
|
329
|
+
* const entryPoints = query.entryPoints()
|
|
330
|
+
* ```
|
|
331
|
+
*/
|
|
332
|
+
entryPoints() {
|
|
333
|
+
return findEntryPoints(this.graph);
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Traces the complete flow bidirectionally from a starting component.
|
|
337
|
+
*
|
|
338
|
+
* Returns all nodes and links connected to the starting point,
|
|
339
|
+
* following links in both directions.
|
|
340
|
+
*
|
|
341
|
+
* @param startComponentId - ID of the component to start tracing from
|
|
342
|
+
* @returns Object with componentIds and linkIds in the flow
|
|
343
|
+
*
|
|
344
|
+
* @example
|
|
345
|
+
* ```typescript
|
|
346
|
+
* const flow = query.traceFlow('orders:checkout:api:post-orders')
|
|
347
|
+
* console.log(`Flow includes ${flow.componentIds.length} nodes`)
|
|
348
|
+
* ```
|
|
349
|
+
*/
|
|
350
|
+
traceFlow(startComponentId) {
|
|
351
|
+
return traceFlowFrom(this.graph, startComponentId);
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Compares this graph with another and returns the differences.
|
|
355
|
+
*
|
|
356
|
+
* @param other - The graph to compare against
|
|
357
|
+
* @returns GraphDiff with added, removed, and modified items
|
|
358
|
+
*
|
|
359
|
+
* @example
|
|
360
|
+
* ```typescript
|
|
361
|
+
* const oldGraph = RiviereQuery.fromJSON(oldData)
|
|
362
|
+
* const newGraph = RiviereQuery.fromJSON(newData)
|
|
363
|
+
* const diff = newGraph.diff(oldGraph.graph)
|
|
364
|
+
*
|
|
365
|
+
* console.log(`Added: ${diff.stats.componentsAdded}`)
|
|
366
|
+
* console.log(`Removed: ${diff.stats.componentsRemoved}`)
|
|
367
|
+
* ```
|
|
368
|
+
*/
|
|
369
|
+
diff(other) {
|
|
370
|
+
return diffGraphs(this.graph, other);
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Returns published events with their handlers.
|
|
374
|
+
*
|
|
375
|
+
* @param domainName - Optional domain to filter by
|
|
376
|
+
* @returns Array of PublishedEvent objects sorted by event name
|
|
377
|
+
*
|
|
378
|
+
* @example
|
|
379
|
+
* ```typescript
|
|
380
|
+
* const allEvents = query.publishedEvents()
|
|
381
|
+
* const orderEvents = query.publishedEvents('orders')
|
|
382
|
+
*
|
|
383
|
+
* for (const event of orderEvents) {
|
|
384
|
+
* console.log(`${event.eventName} has ${event.handlers.length} handlers`)
|
|
385
|
+
* }
|
|
386
|
+
* ```
|
|
387
|
+
*/
|
|
388
|
+
publishedEvents(domainName) {
|
|
389
|
+
return queryPublishedEvents(this.graph, domainName);
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Returns event handlers with their subscriptions.
|
|
393
|
+
*
|
|
394
|
+
* @param eventName - Optional event name to filter handlers by
|
|
395
|
+
* @returns Array of EventHandlerInfo objects sorted by handler name
|
|
396
|
+
*
|
|
397
|
+
* @example
|
|
398
|
+
* ```typescript
|
|
399
|
+
* const allHandlers = query.eventHandlers()
|
|
400
|
+
* const orderPlacedHandlers = query.eventHandlers('order-placed')
|
|
401
|
+
* ```
|
|
402
|
+
*/
|
|
403
|
+
eventHandlers(eventName) {
|
|
404
|
+
return queryEventHandlers(this.graph, eventName);
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Returns all flows in the graph.
|
|
408
|
+
*
|
|
409
|
+
* Each flow starts from an entry point (UI, API, or Custom with no
|
|
410
|
+
* incoming links) and traces forward through the graph.
|
|
411
|
+
*
|
|
412
|
+
* @returns Array of Flow objects with entry point and steps
|
|
413
|
+
*
|
|
414
|
+
* @example
|
|
415
|
+
* ```typescript
|
|
416
|
+
* const flows = query.flows()
|
|
417
|
+
*
|
|
418
|
+
* for (const flow of flows) {
|
|
419
|
+
* console.log(`Flow: ${flow.entryPoint.name}`)
|
|
420
|
+
* for (const step of flow.steps) {
|
|
421
|
+
* console.log(` ${step.component.name} (depth: ${step.depth})`)
|
|
422
|
+
* }
|
|
423
|
+
* }
|
|
424
|
+
* ```
|
|
425
|
+
*/
|
|
426
|
+
flows() {
|
|
427
|
+
return queryFlows(this.graph);
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Searches for components and returns their flow context.
|
|
431
|
+
*
|
|
432
|
+
* Returns both matching component IDs and all visible IDs in their flows.
|
|
433
|
+
*
|
|
434
|
+
* @param query - Search term
|
|
435
|
+
* @param options - Search options including returnAllOnEmptyQuery
|
|
436
|
+
* @returns Object with matchingIds and visibleIds arrays
|
|
437
|
+
*
|
|
438
|
+
* @example
|
|
439
|
+
* ```typescript
|
|
440
|
+
* const result = query.searchWithFlow('checkout', { returnAllOnEmptyQuery: true })
|
|
441
|
+
* console.log(`Found ${result.matchingIds.length} matches`)
|
|
442
|
+
* console.log(`Showing ${result.visibleIds.length} nodes in context`)
|
|
443
|
+
* ```
|
|
444
|
+
*/
|
|
445
|
+
searchWithFlow(query, options) {
|
|
446
|
+
return searchWithFlowContext(this.graph, query, options);
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* Returns links from a domain to other domains.
|
|
450
|
+
*
|
|
451
|
+
* @param domainName - The source domain name
|
|
452
|
+
* @returns Array of CrossDomainLink objects (deduplicated by target domain and type)
|
|
453
|
+
*
|
|
454
|
+
* @example
|
|
455
|
+
* ```typescript
|
|
456
|
+
* const outgoing = query.crossDomainLinks('orders')
|
|
457
|
+
* ```
|
|
458
|
+
*/
|
|
459
|
+
crossDomainLinks(domainName) {
|
|
460
|
+
return queryCrossDomainLinks(this.graph, domainName);
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Returns cross-domain connections with API and event counts.
|
|
464
|
+
*
|
|
465
|
+
* Shows both incoming and outgoing connections for a domain.
|
|
466
|
+
*
|
|
467
|
+
* @param domainName - The domain to analyze
|
|
468
|
+
* @returns Array of DomainConnection objects
|
|
469
|
+
*
|
|
470
|
+
* @example
|
|
471
|
+
* ```typescript
|
|
472
|
+
* const connections = query.domainConnections('orders')
|
|
473
|
+
* for (const conn of connections) {
|
|
474
|
+
* console.log(`${conn.direction} to ${conn.targetDomain}: ${conn.apiCount} API, ${conn.eventCount} event`)
|
|
475
|
+
* }
|
|
476
|
+
* ```
|
|
477
|
+
*/
|
|
478
|
+
domainConnections(domainName) {
|
|
479
|
+
return queryDomainConnections(this.graph, domainName);
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* Returns aggregate statistics about the graph.
|
|
483
|
+
*
|
|
484
|
+
* @returns GraphStats with counts for components, links, domains, APIs, entities, and events
|
|
485
|
+
*
|
|
486
|
+
* @example
|
|
487
|
+
* ```typescript
|
|
488
|
+
* const stats = query.stats()
|
|
489
|
+
* console.log(`Components: ${stats.componentCount}`)
|
|
490
|
+
* console.log(`Links: ${stats.linkCount}`)
|
|
491
|
+
* console.log(`Domains: ${stats.domainCount}`)
|
|
492
|
+
* ```
|
|
493
|
+
*/
|
|
494
|
+
stats() {
|
|
495
|
+
return queryStats(this.graph);
|
|
496
|
+
}
|
|
497
|
+
/**
|
|
498
|
+
* Calculates depth from entry points for each component.
|
|
499
|
+
*
|
|
500
|
+
* Components unreachable from entry points will not be in the map.
|
|
501
|
+
*
|
|
502
|
+
* @returns Map of component ID to depth (0 = entry point)
|
|
503
|
+
*
|
|
504
|
+
* @example
|
|
505
|
+
* ```typescript
|
|
506
|
+
* const depths = query.nodeDepths()
|
|
507
|
+
* for (const [id, depth] of depths) {
|
|
508
|
+
* console.log(`${id}: depth ${depth}`)
|
|
509
|
+
* }
|
|
510
|
+
* ```
|
|
511
|
+
*/
|
|
512
|
+
nodeDepths() {
|
|
513
|
+
return queryNodeDepths(this.graph);
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* Returns all external links in the graph.
|
|
517
|
+
*
|
|
518
|
+
* External links represent connections from components to external
|
|
519
|
+
* systems that are not part of the graph (e.g., third-party APIs).
|
|
520
|
+
*
|
|
521
|
+
* @returns Array of all external links, or empty array if none exist
|
|
522
|
+
*
|
|
523
|
+
* @example
|
|
524
|
+
* ```typescript
|
|
525
|
+
* const externalLinks = query.externalLinks()
|
|
526
|
+
* for (const link of externalLinks) {
|
|
527
|
+
* console.log(`${link.source} -> ${link.target.name}`)
|
|
528
|
+
* }
|
|
529
|
+
* ```
|
|
530
|
+
*/
|
|
531
|
+
externalLinks() {
|
|
532
|
+
return this.graph.externalLinks ?? [];
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* Returns external domains that components connect to.
|
|
536
|
+
*
|
|
537
|
+
* Each unique external target is returned as a separate ExternalDomain,
|
|
538
|
+
* with aggregated source domains and connection counts.
|
|
539
|
+
*
|
|
540
|
+
* @returns Array of ExternalDomain objects, sorted alphabetically by name
|
|
541
|
+
*
|
|
542
|
+
* @example
|
|
543
|
+
* ```typescript
|
|
544
|
+
* const externals = query.externalDomains()
|
|
545
|
+
* for (const ext of externals) {
|
|
546
|
+
* console.log(`${ext.name}: ${ext.connectionCount} connections from ${ext.sourceDomains.join(', ')}`)
|
|
547
|
+
* }
|
|
548
|
+
* ```
|
|
549
|
+
*/
|
|
550
|
+
externalDomains() {
|
|
551
|
+
return queryExternalDomains(this.graph);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { RiviereGraph, Component, ComponentType } from '@living-architecture/riviere-schema';
|
|
2
|
+
export declare function findComponent(graph: RiviereGraph, predicate: (component: Component) => boolean): Component | undefined;
|
|
3
|
+
export declare function findAllComponents(graph: RiviereGraph, predicate: (component: Component) => boolean): Component[];
|
|
4
|
+
export declare function componentById(graph: RiviereGraph, id: string): Component | undefined;
|
|
5
|
+
export declare function searchComponents(graph: RiviereGraph, query: string): Component[];
|
|
6
|
+
export declare function componentsInDomain(graph: RiviereGraph, domainName: string): Component[];
|
|
7
|
+
export declare function componentsByType(graph: RiviereGraph, type: ComponentType): Component[];
|
|
8
|
+
//# sourceMappingURL=component-queries.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"component-queries.d.ts","sourceRoot":"","sources":["../src/component-queries.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,qCAAqC,CAAA;AAEjG,wBAAgB,aAAa,CAAC,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,CAAC,SAAS,EAAE,SAAS,KAAK,OAAO,GAAG,SAAS,GAAG,SAAS,CAEtH;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,CAAC,SAAS,EAAE,SAAS,KAAK,OAAO,GAAG,SAAS,EAAE,CAEhH;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,YAAY,EAAE,EAAE,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,CAEpF;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,CAUhF;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,GAAG,SAAS,EAAE,CAEvF;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,aAAa,GAAG,SAAS,EAAE,CAEtF"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export function findComponent(graph, predicate) {
|
|
2
|
+
return graph.components.find(predicate);
|
|
3
|
+
}
|
|
4
|
+
export function findAllComponents(graph, predicate) {
|
|
5
|
+
return graph.components.filter(predicate);
|
|
6
|
+
}
|
|
7
|
+
export function componentById(graph, id) {
|
|
8
|
+
return findComponent(graph, (c) => c.id === id);
|
|
9
|
+
}
|
|
10
|
+
export function searchComponents(graph, query) {
|
|
11
|
+
if (query === '') {
|
|
12
|
+
return [];
|
|
13
|
+
}
|
|
14
|
+
const lowerQuery = query.toLowerCase();
|
|
15
|
+
return findAllComponents(graph, (c) => c.name.toLowerCase().includes(lowerQuery) ||
|
|
16
|
+
c.domain.toLowerCase().includes(lowerQuery) ||
|
|
17
|
+
c.type.toLowerCase().includes(lowerQuery));
|
|
18
|
+
}
|
|
19
|
+
export function componentsInDomain(graph, domainName) {
|
|
20
|
+
return findAllComponents(graph, (c) => c.domain === domainName);
|
|
21
|
+
}
|
|
22
|
+
export function componentsByType(graph, type) {
|
|
23
|
+
return findAllComponents(graph, (c) => c.type === type);
|
|
24
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { RiviereGraph } from '@living-architecture/riviere-schema';
|
|
2
|
+
import type { CrossDomainLink, DomainConnection } from './domain-types';
|
|
3
|
+
export declare function queryCrossDomainLinks(graph: RiviereGraph, domainName: string): CrossDomainLink[];
|
|
4
|
+
export declare function queryDomainConnections(graph: RiviereGraph, domainName: string): DomainConnection[];
|
|
5
|
+
//# sourceMappingURL=cross-domain-queries.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cross-domain-queries.d.ts","sourceRoot":"","sources":["../src/cross-domain-queries.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qCAAqC,CAAA;AACvE,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AAOvE,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,GAAG,eAAe,EAAE,CA+BhG;AA8ED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,GAAG,gBAAgB,EAAE,CAOlG"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { parseDomainName } from './domain-types';
|
|
2
|
+
function buildNodeIdToDomain(graph) {
|
|
3
|
+
return new Map(graph.components.map((c) => [c.id, c.domain]));
|
|
4
|
+
}
|
|
5
|
+
export function queryCrossDomainLinks(graph, domainName) {
|
|
6
|
+
const nodeIdToDomain = buildNodeIdToDomain(graph);
|
|
7
|
+
const seen = new Set();
|
|
8
|
+
const results = [];
|
|
9
|
+
for (const link of graph.links) {
|
|
10
|
+
const sourceDomain = nodeIdToDomain.get(link.source);
|
|
11
|
+
const targetDomain = nodeIdToDomain.get(link.target);
|
|
12
|
+
if (sourceDomain !== domainName || targetDomain === domainName) {
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
if (targetDomain === undefined) {
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
const linkTypeKey = link.type === undefined ? 'UNDEFINED_LINK_TYPE' : link.type;
|
|
19
|
+
const key = `${targetDomain}:${linkTypeKey}`;
|
|
20
|
+
if (seen.has(key)) {
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
seen.add(key);
|
|
24
|
+
results.push({
|
|
25
|
+
targetDomain: parseDomainName(targetDomain),
|
|
26
|
+
linkType: link.type,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
return results.sort(compareCrossDomainLinks);
|
|
30
|
+
}
|
|
31
|
+
const UNDEFINED_LINK_TYPE_SORT_KEY = '';
|
|
32
|
+
function linkTypeForSort(linkType) {
|
|
33
|
+
if (linkType === undefined)
|
|
34
|
+
return UNDEFINED_LINK_TYPE_SORT_KEY;
|
|
35
|
+
return linkType;
|
|
36
|
+
}
|
|
37
|
+
function compareCrossDomainLinks(a, b) {
|
|
38
|
+
const domainCompare = a.targetDomain.localeCompare(b.targetDomain);
|
|
39
|
+
if (domainCompare !== 0)
|
|
40
|
+
return domainCompare;
|
|
41
|
+
return linkTypeForSort(a.linkType).localeCompare(linkTypeForSort(b.linkType));
|
|
42
|
+
}
|
|
43
|
+
function initializeConnectionCounts() {
|
|
44
|
+
return { apiCount: 0, eventCount: 0 };
|
|
45
|
+
}
|
|
46
|
+
function getOrInitializeConnectionCounts(map, domain) {
|
|
47
|
+
const existing = map.get(domain);
|
|
48
|
+
if (existing !== undefined) {
|
|
49
|
+
return existing;
|
|
50
|
+
}
|
|
51
|
+
const counts = initializeConnectionCounts();
|
|
52
|
+
map.set(domain, counts);
|
|
53
|
+
return counts;
|
|
54
|
+
}
|
|
55
|
+
function incrementConnectionCount(map, domain, targetType) {
|
|
56
|
+
const counts = getOrInitializeConnectionCounts(map, domain);
|
|
57
|
+
if (targetType === 'API')
|
|
58
|
+
counts.apiCount++;
|
|
59
|
+
if (targetType === 'EventHandler')
|
|
60
|
+
counts.eventCount++;
|
|
61
|
+
}
|
|
62
|
+
function collectConnections(graph, domainName, nodeIdToDomain, nodeById) {
|
|
63
|
+
const outgoing = new Map();
|
|
64
|
+
const incoming = new Map();
|
|
65
|
+
for (const link of graph.links) {
|
|
66
|
+
const sourceDomain = nodeIdToDomain.get(link.source);
|
|
67
|
+
const targetDomain = nodeIdToDomain.get(link.target);
|
|
68
|
+
const targetType = nodeById.get(link.target)?.type;
|
|
69
|
+
if (sourceDomain === domainName && targetDomain !== undefined && targetDomain !== domainName) {
|
|
70
|
+
incrementConnectionCount(outgoing, targetDomain, targetType);
|
|
71
|
+
}
|
|
72
|
+
if (targetDomain === domainName && sourceDomain !== undefined && sourceDomain !== domainName) {
|
|
73
|
+
incrementConnectionCount(incoming, sourceDomain, targetType);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return { outgoing, incoming };
|
|
77
|
+
}
|
|
78
|
+
function toConnectionResults(connections, direction) {
|
|
79
|
+
return Array.from(connections.entries()).map(([domain, counts]) => ({
|
|
80
|
+
targetDomain: parseDomainName(domain),
|
|
81
|
+
direction,
|
|
82
|
+
apiCount: counts.apiCount,
|
|
83
|
+
eventCount: counts.eventCount,
|
|
84
|
+
}));
|
|
85
|
+
}
|
|
86
|
+
export function queryDomainConnections(graph, domainName) {
|
|
87
|
+
const nodeIdToDomain = buildNodeIdToDomain(graph);
|
|
88
|
+
const nodeById = new Map(graph.components.map((c) => [c.id, { type: c.type }]));
|
|
89
|
+
const { outgoing, incoming } = collectConnections(graph, domainName, nodeIdToDomain, nodeById);
|
|
90
|
+
const results = [...toConnectionResults(outgoing, 'outgoing'), ...toConnectionResults(incoming, 'incoming')];
|
|
91
|
+
return results.sort((a, b) => a.targetDomain.localeCompare(b.targetDomain));
|
|
92
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"depth-queries.d.ts","sourceRoot":"","sources":["../src/depth-queries.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAiB,MAAM,qCAAqC,CAAA;AACtF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAQjD,wBAAgB,eAAe,CAAC,KAAK,EAAE,YAAY,GAAG,GAAG,CAAC,WAAW,EAAE,MAAM,CAAC,CAc7E"}
|