@living-architecture/riviere-builder 0.5.27 → 0.6.0

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 (91) hide show
  1. package/dist/domain/builder-facade.d.ts +171 -0
  2. package/dist/domain/builder-facade.d.ts.map +1 -0
  3. package/dist/domain/builder-facade.js +211 -0
  4. package/dist/domain/builder-graph.d.ts +10 -0
  5. package/dist/domain/builder-graph.d.ts.map +1 -0
  6. package/dist/domain/construction/builder-assertions.d.ts.map +1 -0
  7. package/dist/{builder-assertions.js → domain/construction/builder-assertions.js} +1 -1
  8. package/dist/domain/construction/builder-internals.d.ts.map +1 -0
  9. package/dist/{builder-internals.js → domain/construction/builder-internals.js} +1 -1
  10. package/dist/{errors.d.ts → domain/construction/construction-errors.d.ts} +1 -10
  11. package/dist/domain/construction/construction-errors.d.ts.map +1 -0
  12. package/dist/{errors.js → domain/construction/construction-errors.js} +0 -18
  13. package/dist/{types.d.ts → domain/construction/construction-types.d.ts} +2 -63
  14. package/dist/domain/construction/construction-types.d.ts.map +1 -0
  15. package/dist/domain/construction/construction-types.js +1 -0
  16. package/dist/domain/construction/graph-construction.d.ts +19 -0
  17. package/dist/domain/construction/graph-construction.d.ts.map +1 -0
  18. package/dist/domain/construction/graph-construction.js +154 -0
  19. package/dist/{deduplicate.d.ts → domain/enrichment/deduplicate-transitions.d.ts} +1 -2
  20. package/dist/domain/enrichment/deduplicate-transitions.d.ts.map +1 -0
  21. package/dist/{deduplicate.js → domain/enrichment/deduplicate-transitions.js} +0 -4
  22. package/dist/domain/enrichment/enrichment-errors.d.ts +6 -0
  23. package/dist/domain/enrichment/enrichment-errors.d.ts.map +1 -0
  24. package/dist/domain/enrichment/enrichment-errors.js +10 -0
  25. package/dist/domain/enrichment/enrichment-types.d.ts +9 -0
  26. package/dist/domain/enrichment/enrichment-types.d.ts.map +1 -0
  27. package/dist/domain/enrichment/enrichment-types.js +1 -0
  28. package/dist/domain/enrichment/graph-enrichment.d.ts +8 -0
  29. package/dist/domain/enrichment/graph-enrichment.d.ts.map +1 -0
  30. package/dist/domain/enrichment/graph-enrichment.js +39 -0
  31. package/dist/domain/enrichment/merge-behavior.d.ts.map +1 -0
  32. package/dist/{merge-behavior.js → domain/enrichment/merge-behavior.js} +1 -1
  33. package/dist/{component-suggestion.d.ts → domain/error-recovery/component-suggestion.d.ts} +2 -2
  34. package/dist/domain/error-recovery/component-suggestion.d.ts.map +1 -0
  35. package/dist/{component-suggestion.js → domain/error-recovery/component-suggestion.js} +2 -2
  36. package/dist/domain/error-recovery/match-types.d.ts +21 -0
  37. package/dist/domain/error-recovery/match-types.d.ts.map +1 -0
  38. package/dist/domain/error-recovery/match-types.js +1 -0
  39. package/dist/domain/error-recovery/near-match.d.ts +8 -0
  40. package/dist/domain/error-recovery/near-match.d.ts.map +1 -0
  41. package/dist/domain/error-recovery/near-match.js +10 -0
  42. package/dist/domain/index.d.ts +6 -0
  43. package/dist/domain/index.d.ts.map +1 -0
  44. package/dist/domain/index.js +5 -0
  45. package/dist/domain/inspection/graph-inspection.d.ts +14 -0
  46. package/dist/domain/inspection/graph-inspection.d.ts.map +1 -0
  47. package/dist/domain/inspection/graph-inspection.js +23 -0
  48. package/dist/{inspection.d.ts → domain/inspection/inspection-functions.d.ts} +2 -2
  49. package/dist/domain/inspection/inspection-functions.d.ts.map +1 -0
  50. package/dist/domain/inspection/inspection-types.d.ts +23 -0
  51. package/dist/domain/inspection/inspection-types.d.ts.map +1 -0
  52. package/dist/domain/inspection/inspection-types.js +1 -0
  53. package/dist/domain/linking/graph-linking.d.ts +10 -0
  54. package/dist/domain/linking/graph-linking.d.ts.map +1 -0
  55. package/dist/domain/linking/graph-linking.js +35 -0
  56. package/dist/domain/linking/linking-types.d.ts +15 -0
  57. package/dist/domain/linking/linking-types.d.ts.map +1 -0
  58. package/dist/domain/linking/linking-types.js +1 -0
  59. package/dist/domain/riviere-builder.d.ts +21 -0
  60. package/dist/domain/riviere-builder.d.ts.map +1 -0
  61. package/dist/domain/riviere-builder.js +73 -0
  62. package/dist/platform/domain/collection-utils/deduplicate-strings.d.ts +2 -0
  63. package/dist/platform/domain/collection-utils/deduplicate-strings.d.ts.map +1 -0
  64. package/dist/platform/domain/collection-utils/deduplicate-strings.js +4 -0
  65. package/dist/platform/domain/text-similarity/string-similarity.d.ts.map +1 -0
  66. package/package.json +10 -10
  67. package/dist/builder-assertions.d.ts.map +0 -1
  68. package/dist/builder-internals.d.ts.map +0 -1
  69. package/dist/builder-test-fixtures.d.ts +0 -7
  70. package/dist/builder-test-fixtures.d.ts.map +0 -1
  71. package/dist/builder-test-fixtures.js +0 -26
  72. package/dist/builder.d.ts +0 -479
  73. package/dist/builder.d.ts.map +0 -1
  74. package/dist/builder.js +0 -734
  75. package/dist/component-suggestion.d.ts.map +0 -1
  76. package/dist/deduplicate.d.ts.map +0 -1
  77. package/dist/errors.d.ts.map +0 -1
  78. package/dist/index.d.ts +0 -5
  79. package/dist/index.d.ts.map +0 -1
  80. package/dist/index.js +0 -4
  81. package/dist/inspection.d.ts.map +0 -1
  82. package/dist/merge-behavior.d.ts.map +0 -1
  83. package/dist/string-similarity.d.ts.map +0 -1
  84. package/dist/types.d.ts.map +0 -1
  85. /package/dist/{types.js → domain/builder-graph.js} +0 -0
  86. /package/dist/{builder-assertions.d.ts → domain/construction/builder-assertions.d.ts} +0 -0
  87. /package/dist/{builder-internals.d.ts → domain/construction/builder-internals.d.ts} +0 -0
  88. /package/dist/{merge-behavior.d.ts → domain/enrichment/merge-behavior.d.ts} +0 -0
  89. /package/dist/{inspection.js → domain/inspection/inspection-functions.js} +0 -0
  90. /package/dist/{string-similarity.d.ts → platform/domain/text-similarity/string-similarity.d.ts} +0 -0
  91. /package/dist/{string-similarity.js → platform/domain/text-similarity/string-similarity.js} +0 -0
package/dist/builder.js DELETED
@@ -1,734 +0,0 @@
1
- import { promises as fs } from 'node:fs';
2
- import { dirname } from 'node:path';
3
- import { RiviereQuery } from '@living-architecture/riviere-query';
4
- import { calculateStats, findOrphans, findWarnings, toRiviereGraph, validateGraph, } from './inspection';
5
- import { findNearMatches } from './component-suggestion';
6
- import { createComponentNotFoundError, generateComponentId, validateCustomType, validateDomainExists, validateRequiredProperties, } from './builder-internals';
7
- import { mergeBehavior } from './merge-behavior';
8
- import { deduplicateStrings, deduplicateStateTransitions } from './deduplicate';
9
- import { BuildValidationError, CustomTypeAlreadyDefinedError, DirectoryNotFoundError, DuplicateComponentError, DuplicateDomainError, InvalidEnrichmentTargetError, InvalidGraphError, MissingDomainsError, MissingSourcesError, } from './errors';
10
- /**
11
- * Programmatically construct Rivière architecture graphs.
12
- *
13
- * RiviereBuilder provides a fluent API for creating graphs, adding components,
14
- * linking them together, and exporting valid JSON conforming to the Rivière schema.
15
- *
16
- * @example
17
- * ```typescript
18
- * import { RiviereBuilder } from '@living-architecture/riviere-builder'
19
- *
20
- * const builder = RiviereBuilder.new({
21
- * sources: [{ type: 'git', url: 'https://github.com/your-org/your-repo' }],
22
- * domains: { orders: { description: 'Order management' } }
23
- * })
24
- *
25
- * const api = builder.addApi({
26
- * name: 'Create Order',
27
- * domain: 'orders',
28
- * module: 'checkout',
29
- * apiType: 'REST',
30
- * sourceLocation: { file: 'src/api/orders.ts', line: 10 }
31
- * })
32
- *
33
- * const graph = builder.build()
34
- * ```
35
- */
36
- export class RiviereBuilder {
37
- graph;
38
- constructor(graph) {
39
- this.graph = graph;
40
- }
41
- /**
42
- * Restores a builder from a previously serialized graph.
43
- *
44
- * Use this to continue building a graph that was saved mid-construction,
45
- * or to modify an existing graph.
46
- *
47
- * @param graph - A valid RiviereGraph object to resume from
48
- * @returns A new RiviereBuilder instance with the graph state restored
49
- * @throws If the graph is missing required sources
50
- *
51
- * @example
52
- * ```typescript
53
- * const json = await fs.readFile('draft.json', 'utf-8')
54
- * const graph = JSON.parse(json)
55
- * const builder = RiviereBuilder.resume(graph)
56
- * builder.addApi({ ... })
57
- * ```
58
- */
59
- static resume(graph) {
60
- if (!graph.metadata.sources || graph.metadata.sources.length === 0) {
61
- throw new InvalidGraphError('missing sources');
62
- }
63
- const builderGraph = {
64
- version: graph.version,
65
- metadata: {
66
- ...graph.metadata,
67
- sources: graph.metadata.sources,
68
- customTypes: graph.metadata.customTypes ?? {},
69
- },
70
- components: graph.components,
71
- links: graph.links,
72
- externalLinks: graph.externalLinks ?? [],
73
- };
74
- return new RiviereBuilder(builderGraph);
75
- }
76
- /**
77
- * Creates a new builder with initial configuration.
78
- *
79
- * @param options - Configuration including sources and domains
80
- * @returns A new RiviereBuilder instance
81
- * @throws If sources array is empty
82
- * @throws If domains object is empty
83
- *
84
- * @example
85
- * ```typescript
86
- * const builder = RiviereBuilder.new({
87
- * name: 'My System',
88
- * sources: [{ type: 'git', url: 'https://github.com/your-org/your-repo' }],
89
- * domains: {
90
- * orders: { description: 'Order management' },
91
- * users: { description: 'User accounts' }
92
- * }
93
- * })
94
- * ```
95
- */
96
- static new(options) {
97
- if (options.sources.length === 0) {
98
- throw new MissingSourcesError();
99
- }
100
- if (Object.keys(options.domains).length === 0) {
101
- throw new MissingDomainsError();
102
- }
103
- const graph = {
104
- version: '1.0',
105
- metadata: {
106
- ...(options.name !== undefined && { name: options.name }),
107
- ...(options.description !== undefined && { description: options.description }),
108
- sources: options.sources,
109
- domains: options.domains,
110
- customTypes: {},
111
- },
112
- components: [],
113
- links: [],
114
- externalLinks: [],
115
- };
116
- return new RiviereBuilder(graph);
117
- }
118
- /**
119
- * Adds an additional source repository to the graph.
120
- *
121
- * @param source - Source repository information
122
- *
123
- * @example
124
- * ```typescript
125
- * builder.addSource({
126
- * type: 'git',
127
- * url: 'https://github.com/your-org/another-repo'
128
- * })
129
- * ```
130
- */
131
- addSource(source) {
132
- this.graph.metadata.sources.push(source);
133
- }
134
- /**
135
- * Adds a new domain to the graph.
136
- *
137
- * @param input - Domain name and description
138
- * @throws If domain with same name already exists
139
- *
140
- * @example
141
- * ```typescript
142
- * builder.addDomain({
143
- * name: 'payments',
144
- * description: 'Payment processing'
145
- * })
146
- * ```
147
- */
148
- addDomain(input) {
149
- if (this.graph.metadata.domains[input.name]) {
150
- throw new DuplicateDomainError(input.name);
151
- }
152
- this.graph.metadata.domains[input.name] = {
153
- description: input.description,
154
- systemType: input.systemType,
155
- };
156
- }
157
- /**
158
- * Adds a UI component to the graph.
159
- *
160
- * @param input - UI component properties including route and source location
161
- * @returns The created UI component with generated ID
162
- * @throws If the specified domain does not exist
163
- *
164
- * @example
165
- * ```typescript
166
- * const ui = builder.addUI({
167
- * name: 'Order List',
168
- * domain: 'orders',
169
- * module: 'dashboard',
170
- * route: '/orders',
171
- * sourceLocation: { file: 'src/pages/OrderList.tsx', line: 15 }
172
- * })
173
- * ```
174
- */
175
- addUI(input) {
176
- validateDomainExists(this.graph.metadata.domains, input.domain);
177
- const id = generateComponentId(input.domain, input.module, 'ui', input.name);
178
- const component = {
179
- id,
180
- type: 'UI',
181
- name: input.name,
182
- domain: input.domain,
183
- module: input.module,
184
- route: input.route,
185
- sourceLocation: input.sourceLocation,
186
- ...(input.description !== undefined && { description: input.description }),
187
- };
188
- return this.registerComponent(component);
189
- }
190
- /**
191
- * Adds an API component to the graph.
192
- *
193
- * @param input - API component properties including type, method, and path
194
- * @returns The created API component with generated ID
195
- * @throws If the specified domain does not exist
196
- *
197
- * @example
198
- * ```typescript
199
- * const api = builder.addApi({
200
- * name: 'Create Order',
201
- * domain: 'orders',
202
- * module: 'checkout',
203
- * apiType: 'REST',
204
- * httpMethod: 'POST',
205
- * path: '/api/orders',
206
- * sourceLocation: { file: 'src/api/orders.ts', line: 25 }
207
- * })
208
- * ```
209
- */
210
- addApi(input) {
211
- validateDomainExists(this.graph.metadata.domains, input.domain);
212
- const id = generateComponentId(input.domain, input.module, 'api', input.name);
213
- const component = {
214
- id,
215
- type: 'API',
216
- name: input.name,
217
- domain: input.domain,
218
- module: input.module,
219
- apiType: input.apiType,
220
- sourceLocation: input.sourceLocation,
221
- ...(input.httpMethod !== undefined && { httpMethod: input.httpMethod }),
222
- ...(input.path !== undefined && { path: input.path }),
223
- ...(input.operationName !== undefined && { operationName: input.operationName }),
224
- ...(input.description !== undefined && { description: input.description }),
225
- };
226
- return this.registerComponent(component);
227
- }
228
- /**
229
- * Adds a UseCase component to the graph.
230
- *
231
- * @param input - UseCase component properties
232
- * @returns The created UseCase component with generated ID
233
- * @throws If the specified domain does not exist
234
- *
235
- * @example
236
- * ```typescript
237
- * const useCase = builder.addUseCase({
238
- * name: 'Place Order',
239
- * domain: 'orders',
240
- * module: 'checkout',
241
- * sourceLocation: { file: 'src/usecases/PlaceOrder.ts', line: 10 }
242
- * })
243
- * ```
244
- */
245
- addUseCase(input) {
246
- validateDomainExists(this.graph.metadata.domains, input.domain);
247
- const id = generateComponentId(input.domain, input.module, 'usecase', input.name);
248
- const component = {
249
- id,
250
- type: 'UseCase',
251
- name: input.name,
252
- domain: input.domain,
253
- module: input.module,
254
- sourceLocation: input.sourceLocation,
255
- ...(input.description !== undefined && { description: input.description }),
256
- };
257
- return this.registerComponent(component);
258
- }
259
- /**
260
- * Adds a DomainOp component to the graph.
261
- *
262
- * DomainOp represents domain operations that change entity state.
263
- * Can be enriched later with state changes and business rules.
264
- *
265
- * @param input - DomainOp component properties including operation name
266
- * @returns The created DomainOp component with generated ID
267
- * @throws If the specified domain does not exist
268
- *
269
- * @example
270
- * ```typescript
271
- * const domainOp = builder.addDomainOp({
272
- * name: 'Confirm Order',
273
- * domain: 'orders',
274
- * module: 'fulfillment',
275
- * operationName: 'confirmOrder',
276
- * entity: 'Order',
277
- * sourceLocation: { file: 'src/domain/Order.ts', line: 45 }
278
- * })
279
- * ```
280
- */
281
- addDomainOp(input) {
282
- validateDomainExists(this.graph.metadata.domains, input.domain);
283
- const id = generateComponentId(input.domain, input.module, 'domainop', input.name);
284
- const component = {
285
- id,
286
- type: 'DomainOp',
287
- name: input.name,
288
- domain: input.domain,
289
- module: input.module,
290
- operationName: input.operationName,
291
- sourceLocation: input.sourceLocation,
292
- ...(input.entity !== undefined && { entity: input.entity }),
293
- ...(input.signature !== undefined && { signature: input.signature }),
294
- ...(input.behavior !== undefined && { behavior: input.behavior }),
295
- ...(input.stateChanges !== undefined && { stateChanges: input.stateChanges }),
296
- ...(input.businessRules !== undefined && { businessRules: input.businessRules }),
297
- ...(input.description !== undefined && { description: input.description }),
298
- };
299
- return this.registerComponent(component);
300
- }
301
- /**
302
- * Adds an Event component to the graph.
303
- *
304
- * @param input - Event component properties including event name
305
- * @returns The created Event component with generated ID
306
- * @throws If the specified domain does not exist
307
- *
308
- * @example
309
- * ```typescript
310
- * const event = builder.addEvent({
311
- * name: 'Order Placed',
312
- * domain: 'orders',
313
- * module: 'checkout',
314
- * eventName: 'OrderPlaced',
315
- * sourceLocation: { file: 'src/events/OrderPlaced.ts', line: 5 }
316
- * })
317
- * ```
318
- */
319
- addEvent(input) {
320
- validateDomainExists(this.graph.metadata.domains, input.domain);
321
- const id = generateComponentId(input.domain, input.module, 'event', input.name);
322
- const component = {
323
- id,
324
- type: 'Event',
325
- name: input.name,
326
- domain: input.domain,
327
- module: input.module,
328
- eventName: input.eventName,
329
- sourceLocation: input.sourceLocation,
330
- ...(input.eventSchema !== undefined && { eventSchema: input.eventSchema }),
331
- ...(input.description !== undefined && { description: input.description }),
332
- };
333
- return this.registerComponent(component);
334
- }
335
- /**
336
- * Adds an EventHandler component to the graph.
337
- *
338
- * @param input - EventHandler component properties including subscribed events
339
- * @returns The created EventHandler component with generated ID
340
- * @throws If the specified domain does not exist
341
- *
342
- * @example
343
- * ```typescript
344
- * const handler = builder.addEventHandler({
345
- * name: 'Send Confirmation Email',
346
- * domain: 'notifications',
347
- * module: 'email',
348
- * subscribedEvents: ['OrderPlaced'],
349
- * sourceLocation: { file: 'src/handlers/OrderConfirmation.ts', line: 10 }
350
- * })
351
- * ```
352
- */
353
- addEventHandler(input) {
354
- validateDomainExists(this.graph.metadata.domains, input.domain);
355
- const id = generateComponentId(input.domain, input.module, 'eventhandler', input.name);
356
- const component = {
357
- id,
358
- type: 'EventHandler',
359
- name: input.name,
360
- domain: input.domain,
361
- module: input.module,
362
- subscribedEvents: input.subscribedEvents,
363
- sourceLocation: input.sourceLocation,
364
- ...(input.description !== undefined && { description: input.description }),
365
- };
366
- return this.registerComponent(component);
367
- }
368
- /**
369
- * Defines a custom component type for the graph.
370
- *
371
- * Custom types allow extending the schema with domain-specific component kinds.
372
- * Must be defined before adding custom components of that type.
373
- *
374
- * @param input - Custom type definition with required and optional properties
375
- * @throws If a custom type with the same name already exists
376
- *
377
- * @example
378
- * ```typescript
379
- * builder.defineCustomType({
380
- * name: 'MessageQueue',
381
- * description: 'Async message queue',
382
- * requiredProperties: {
383
- * queueName: { type: 'string', description: 'Queue identifier' }
384
- * }
385
- * })
386
- * ```
387
- */
388
- defineCustomType(input) {
389
- const customTypes = this.graph.metadata.customTypes;
390
- if (customTypes[input.name]) {
391
- throw new CustomTypeAlreadyDefinedError(input.name);
392
- }
393
- customTypes[input.name] = {
394
- ...(input.requiredProperties !== undefined && { requiredProperties: input.requiredProperties, }),
395
- ...(input.optionalProperties !== undefined && { optionalProperties: input.optionalProperties, }),
396
- ...(input.description !== undefined && { description: input.description }),
397
- };
398
- }
399
- /**
400
- * Adds a Custom component to the graph.
401
- *
402
- * Custom components use types defined via defineCustomType().
403
- * Validates that the custom type exists and required properties are provided.
404
- *
405
- * @param input - Custom component properties including type name and metadata
406
- * @returns The created Custom component with generated ID
407
- * @throws If the specified domain does not exist
408
- * @throws If the custom type has not been defined
409
- * @throws If required properties for the custom type are missing
410
- *
411
- * @example
412
- * ```typescript
413
- * const queue = builder.addCustom({
414
- * customTypeName: 'MessageQueue',
415
- * name: 'Order Events Queue',
416
- * domain: 'orders',
417
- * module: 'messaging',
418
- * sourceLocation: { file: 'src/queues/orders.ts', line: 5 },
419
- * metadata: { queueName: 'order-events' }
420
- * })
421
- * ```
422
- */
423
- addCustom(input) {
424
- validateDomainExists(this.graph.metadata.domains, input.domain);
425
- validateCustomType(this.graph.metadata.customTypes, input.customTypeName);
426
- validateRequiredProperties(this.graph.metadata.customTypes, input.customTypeName, input.metadata);
427
- const id = generateComponentId(input.domain, input.module, 'custom', input.name);
428
- const component = {
429
- id,
430
- type: 'Custom',
431
- customTypeName: input.customTypeName,
432
- name: input.name,
433
- domain: input.domain,
434
- module: input.module,
435
- sourceLocation: input.sourceLocation,
436
- ...(input.description !== undefined && { description: input.description }),
437
- ...input.metadata,
438
- };
439
- return this.registerComponent(component);
440
- }
441
- /**
442
- * Enriches a DomainOp component with additional domain details.
443
- *
444
- * Adds state changes and business rules to an existing DomainOp.
445
- * Multiple enrichments accumulate rather than replace.
446
- *
447
- * @param id - The component ID to enrich
448
- * @param enrichment - State changes and business rules to add
449
- * @throws If the component does not exist
450
- * @throws If the component is not a DomainOp type
451
- *
452
- * @example
453
- * ```typescript
454
- * builder.enrichComponent('orders:fulfillment:domainop:confirm-order', {
455
- * entity: 'Order',
456
- * stateChanges: [{ entity: 'Order', from: 'pending', to: 'confirmed' }],
457
- * businessRules: ['Order must have valid payment']
458
- * })
459
- * ```
460
- */
461
- enrichComponent(id, enrichment) {
462
- const component = this.graph.components.find((c) => c.id === id);
463
- if (!component) {
464
- throw createComponentNotFoundError(this.graph.components, id);
465
- }
466
- if (component.type !== 'DomainOp') {
467
- throw new InvalidEnrichmentTargetError(id, component.type);
468
- }
469
- if (enrichment.entity !== undefined) {
470
- component.entity = enrichment.entity;
471
- }
472
- if (enrichment.stateChanges !== undefined) {
473
- const existing = component.stateChanges ?? [];
474
- const newItems = deduplicateStateTransitions(existing, enrichment.stateChanges);
475
- component.stateChanges = [...existing, ...newItems];
476
- }
477
- if (enrichment.businessRules !== undefined) {
478
- const existing = component.businessRules ?? [];
479
- const newItems = deduplicateStrings(existing, enrichment.businessRules);
480
- component.businessRules = [...existing, ...newItems];
481
- }
482
- if (enrichment.behavior !== undefined) {
483
- component.behavior = mergeBehavior(component.behavior, enrichment.behavior);
484
- }
485
- if (enrichment.signature !== undefined) {
486
- component.signature = enrichment.signature;
487
- }
488
- }
489
- registerComponent(component) {
490
- if (this.graph.components.some((c) => c.id === component.id)) {
491
- throw new DuplicateComponentError(component.id);
492
- }
493
- this.graph.components.push(component);
494
- return component;
495
- }
496
- /**
497
- * Finds components similar to a query for error recovery.
498
- *
499
- * Returns fuzzy matches when an exact component lookup fails,
500
- * enabling actionable error messages with "Did you mean...?" suggestions.
501
- *
502
- * @param query - Search criteria including partial ID, name, type, or domain
503
- * @param options - Optional matching thresholds and limits
504
- * @returns Array of similar components with similarity scores
505
- *
506
- * @example
507
- * ```typescript
508
- * const matches = builder.nearMatches({ name: 'Place Ordr' })
509
- * // [{ component: {...}, score: 0.9, mismatches: [...] }]
510
- * ```
511
- */
512
- nearMatches(query, options) {
513
- return findNearMatches(this.graph.components, query, options);
514
- }
515
- /**
516
- * Creates a link between two components in the graph.
517
- *
518
- * Source component must exist; target validation is deferred to build().
519
- * Use linkExternal() for connections to external systems.
520
- *
521
- * @param input - Link properties including source, target, and type
522
- * @returns The created link
523
- * @throws If the source component does not exist
524
- *
525
- * @example
526
- * ```typescript
527
- * const link = builder.link({
528
- * from: 'orders:checkout:api:create-order',
529
- * to: 'orders:checkout:usecase:place-order',
530
- * type: 'sync'
531
- * })
532
- * ```
533
- */
534
- link(input) {
535
- const sourceExists = this.graph.components.some((c) => c.id === input.from);
536
- if (!sourceExists) {
537
- throw createComponentNotFoundError(this.graph.components, input.from);
538
- }
539
- const link = {
540
- source: input.from,
541
- target: input.to,
542
- ...(input.type !== undefined && { type: input.type }),
543
- };
544
- this.graph.links.push(link);
545
- return link;
546
- }
547
- /**
548
- * Creates a link from a component to an external system.
549
- *
550
- * Use this for connections to systems outside the graph,
551
- * such as third-party APIs or external databases.
552
- *
553
- * @param input - External link properties including target system info
554
- * @returns The created external link
555
- * @throws If the source component does not exist
556
- *
557
- * @example
558
- * ```typescript
559
- * const link = builder.linkExternal({
560
- * from: 'orders:payments:usecase:process-payment',
561
- * target: { name: 'Stripe API', domain: 'payments' },
562
- * type: 'sync'
563
- * })
564
- * ```
565
- */
566
- linkExternal(input) {
567
- const sourceExists = this.graph.components.some((c) => c.id === input.from);
568
- if (!sourceExists) {
569
- throw createComponentNotFoundError(this.graph.components, input.from);
570
- }
571
- const externalLink = {
572
- source: input.from,
573
- target: input.target,
574
- ...(input.type !== undefined && { type: input.type }),
575
- ...(input.description !== undefined && { description: input.description }),
576
- ...(input.sourceLocation !== undefined && { sourceLocation: input.sourceLocation }),
577
- };
578
- this.graph.externalLinks.push(externalLink);
579
- return externalLink;
580
- }
581
- /**
582
- * Returns non-fatal issues found in the graph.
583
- *
584
- * Warnings indicate potential problems that don't prevent building,
585
- * such as orphaned components or unused domains.
586
- *
587
- * @returns Array of warning objects with type and message
588
- *
589
- * @example
590
- * ```typescript
591
- * const warnings = builder.warnings()
592
- * for (const w of warnings) {
593
- * console.log(`${w.type}: ${w.message}`)
594
- * }
595
- * ```
596
- */
597
- warnings() {
598
- return findWarnings(this.graph);
599
- }
600
- /**
601
- * Returns statistics about the current graph state.
602
- *
603
- * @returns Counts of components by type, domains, and links
604
- *
605
- * @example
606
- * ```typescript
607
- * const stats = builder.stats()
608
- * console.log(`Components: ${stats.componentCount}`)
609
- * console.log(`Links: ${stats.linkCount}`)
610
- * ```
611
- */
612
- stats() {
613
- return calculateStats(this.graph);
614
- }
615
- /**
616
- * Runs full validation on the graph.
617
- *
618
- * Checks for dangling references, orphans, and schema compliance.
619
- * Called automatically by build().
620
- *
621
- * @returns Validation result with valid flag and error details
622
- *
623
- * @example
624
- * ```typescript
625
- * const result = builder.validate()
626
- * if (!result.valid) {
627
- * for (const error of result.errors) {
628
- * console.error(error.message)
629
- * }
630
- * }
631
- * ```
632
- */
633
- validate() {
634
- return validateGraph(this.graph);
635
- }
636
- /**
637
- * Returns IDs of components with no incoming or outgoing links.
638
- *
639
- * @returns Array of orphaned component IDs
640
- *
641
- * @example
642
- * ```typescript
643
- * const orphans = builder.orphans()
644
- * if (orphans.length > 0) {
645
- * console.warn('Orphaned components:', orphans)
646
- * }
647
- * ```
648
- */
649
- orphans() {
650
- return findOrphans(this.graph);
651
- }
652
- /**
653
- * Returns a RiviereQuery instance for the current graph state.
654
- *
655
- * Enables querying mid-construction without affecting builder state.
656
- *
657
- * @returns RiviereQuery instance for the current graph
658
- *
659
- * @example
660
- * ```typescript
661
- * const query = builder.query()
662
- * const apis = query.componentsByType('API')
663
- * ```
664
- */
665
- query() {
666
- return new RiviereQuery(toRiviereGraph(this.graph));
667
- }
668
- /**
669
- * Serializes the current graph state as a JSON string.
670
- *
671
- * Does not validate. Use for saving drafts mid-construction
672
- * that can be resumed later with RiviereBuilder.resume().
673
- *
674
- * @returns JSON string representation of the graph
675
- *
676
- * @example
677
- * ```typescript
678
- * const json = builder.serialize()
679
- * await fs.writeFile('draft.json', json)
680
- * ```
681
- */
682
- serialize() {
683
- return JSON.stringify(this.graph, null, 2);
684
- }
685
- /**
686
- * Validates and returns the completed graph.
687
- *
688
- * @returns Valid RiviereGraph object
689
- * @throws If validation fails with error details
690
- *
691
- * @example
692
- * ```typescript
693
- * try {
694
- * const graph = builder.build()
695
- * console.log('Graph built successfully')
696
- * } catch (error) {
697
- * console.error('Build failed:', error.message)
698
- * }
699
- * ```
700
- */
701
- build() {
702
- const result = this.validate();
703
- if (!result.valid) {
704
- const messages = result.errors.map((e) => e.message);
705
- throw new BuildValidationError(messages);
706
- }
707
- return toRiviereGraph(this.graph);
708
- }
709
- /**
710
- * Validates the graph and writes it to a file.
711
- *
712
- * @param path - Absolute or relative file path to write
713
- * @throws If validation fails
714
- * @throws If the directory does not exist
715
- * @throws If write fails
716
- *
717
- * @example
718
- * ```typescript
719
- * await builder.save('./output/architecture.json')
720
- * ```
721
- */
722
- async save(path) {
723
- const graph = this.build();
724
- const dir = dirname(path);
725
- try {
726
- await fs.access(dir);
727
- }
728
- catch {
729
- throw new DirectoryNotFoundError(dir);
730
- }
731
- const json = JSON.stringify(graph, null, 2);
732
- await fs.writeFile(path, json, 'utf-8');
733
- }
734
- }