Graph Structure
A Riviere graph consists of metadata, components, and links.
See the real thing: Load example graphs in Eclair to explore interactively. The structure below matches the Riviere JSON Schema.
Top-Level Structure
interface RiviereGraph {
version: string
metadata: {
name?: string
description?: string
generated: string
sources: SourceMetadata[]
domains: Record<string, DomainMetadata>
}
components: Component[]
links: Link[]
}Components
Components represent architectural elements. All components share a base structure:
interface BaseComponent {
id: string
type: ComponentType
name: string
domain: string
module: string
sourceLocation: SourceLocation
description?: string
metadata?: Record<string, unknown>
}Component Types
| Type | Purpose | Type-Specific Fields |
|---|---|---|
UI | User interface entry points | route (required) |
API | HTTP endpoints | apiType, httpMethod, path or operationName |
UseCase | Application layer orchestration | — |
DomainOp | Domain logic operations | operationName, entity, stateChanges, businessRules |
Event | Published domain events | eventName, eventSchema (optional) |
EventHandler | Event subscribers | subscribedEvents |
Custom | Extension point | Defined by custom type registration |
UI Component
builder.addUI({
domain: 'frontend',
module: 'checkout',
name: 'checkout-page',
route: '/checkout',
sourceLocation: {
repository: 'your-repo/frontend',
filePath: 'src/pages/checkout.tsx',
lineNumber: 1
}
})API Component
REST API:
builder.addApi({
domain: 'orders',
module: 'api',
httpMethod: 'POST',
path: '/orders',
apiType: 'REST',
sourceLocation: {
repository: 'your-repo/orders',
filePath: 'src/api/orders.ts',
lineNumber: 10
}
})GraphQL API:
builder.addApi({
domain: 'orders',
module: 'api',
apiType: 'GraphQL',
operationName: 'placeOrder',
sourceLocation: {
repository: 'your-repo/orders',
filePath: 'src/graphql/place-order.ts',
lineNumber: 5
}
})UseCase Component
builder.addUseCase({
domain: 'orders',
module: 'checkout',
name: 'place-order',
sourceLocation: {
repository: 'your-repo/orders',
filePath: 'src/use-cases/place-order.ts',
lineNumber: 15
}
})DomainOp Component
With entity and state changes (method on aggregate):
builder.addDomainOp({
domain: 'orders',
module: 'core',
entity: 'Order',
operationName: 'begin',
stateChanges: [
{ from: 'none', to: 'pending' }
],
businessRules: [
'Order must have at least one item',
'Total must be positive'
],
sourceLocation: {
repository: 'your-repo/orders',
filePath: 'src/domain/order.ts',
lineNumber: 42,
methodName: 'begin'
}
})Without entity (domain service):
builder.addDomainOp({
domain: 'orders',
module: 'services',
operationName: 'calculateTax',
sourceLocation: {
repository: 'your-repo/orders',
filePath: 'src/domain/tax.ts',
lineNumber: 12,
methodName: 'calculateTax'
}
})Event Component
builder.addEvent({
domain: 'orders',
module: 'events',
eventName: 'order-placed',
eventSchema: '{ orderId: string, items: Item[] }',
sourceLocation: {
repository: 'your-repo/orders',
filePath: 'src/events/order-placed.ts',
lineNumber: 5
}
})EventHandler Component
builder.addEventHandler({
domain: 'shipping',
module: 'handlers',
name: 'on-order-placed',
subscribedEvents: ['order-placed'],
sourceLocation: {
repository: 'your-repo/shipping',
filePath: 'src/handlers/on-order-placed.ts',
lineNumber: 10
}
})Custom Component
First register the type, then use it:
builder.defineCustomType('Queue', {
requiredFields: ['queueName'],
optionalFields: ['dlqEnabled']
})
builder.addCustom({
domain: 'messaging',
module: 'queues',
customType: 'Queue',
name: 'order-events',
queueName: 'order-events-queue',
dlqEnabled: true,
sourceLocation: {
repository: 'your-repo/messaging',
filePath: 'src/queues/order-events.ts',
lineNumber: 1
}
})Links
Links connect components to show flow:
interface Link {
id: string
source: string
target: string
type: 'sync' | 'async'
sourceLocation?: SourceLocation
}Link Types
| Type | Meaning | Example |
|---|---|---|
sync | Synchronous call, waits for response | API → UseCase |
async | Asynchronous, fire-and-forget | Event → EventHandler |
Creating Links
builder.link(sourceComponent, targetComponent, 'sync')
builder.link(api, useCase, 'sync', {
repository: 'your-repo/service',
filePath: 'src/api/handler.ts',
lineNumber: 55
})Domains
Domains represent system boundaries (an ownership or deployment area you want to group components under):
interface DomainMetadata {
description: string
systemType: 'domain' | 'bff' | 'ui' | 'other'
}Domains are declared in the builder configuration:
const builder = new RiviereBuilder({
sources: [{ repository: '...' }],
domains: {
orders: {
description: 'Order management and checkout',
systemType: 'domain',
},
frontend: {
description: 'Customer-facing UI',
systemType: 'ui'
}
}
})Entity information (state machines, business rules) is captured on DomainOp components via stateChanges and businessRules fields.
Source Location
Every component requires a source location:
interface SourceLocation {
repository: string
filePath: string
lineNumber?: number
endLineNumber?: number
methodName?: string
url?: string
}Example:
sourceLocation: {
repository: 'your-repo/my-service',
filePath: 'src/domain/order.ts',
lineNumber: 42,
methodName: 'begin'
}See Also
- Riviere JSON Schema —
/schema/riviere.schema.json - Resources — Complete list of references