Customization Guide
AGraph was designed for extension. This guide shows how to plug in custom vertex/edge classes, comparators, events, and emitters to tailor the library to your domain.
Extend AGraph
ts
import { AGraph, Vertex, Edge, type GraphEvents, type GraphProperties } from '@avensio/graph'
type TaskData = { status: 'todo' | 'doing' | 'done' }
class TaskVertex extends Vertex<TaskData> {}
class DependencyEdge extends Edge<TaskData> {}
interface TaskEvents extends GraphEvents<TaskData> {
'task:completed': [TaskVertex]
}
const defaultComparator = (a: TaskVertex, b: TaskVertex) =>
a.uuid === b.uuid ? 0 : (a.data?.status === 'done' ? 1 : 0) - (b.data?.status === 'done' ? 1 : 0)
class TaskGraph extends AGraph<TaskData, TaskVertex, DependencyEdge, TaskEvents> {
constructor(props: GraphProperties<TaskData> = {}, comparator = defaultComparator) {
super(props, comparator, TaskVertex, DependencyEdge)
}
markDone(vertex: TaskVertex) {
vertex.data = { status: 'done' }
this.emit('task:completed', vertex)
}
}Custom comparators
Comparators influence traversal order and how the internal List/Stack utilities behave.
ts
import { Vertex } from '@avensio/graph'
import { createComparator, type Comparator } from '@avensio/shared'
const priorityComparator: Comparator<Vertex<{ priority: number }>>
= createComparator(vertex => vertex.data?.priority)Pass the comparator into new Graph(props, priorityComparator) or set graph.comparator = priorityComparator before running algorithms.
Domain-specific events
- Extend
GraphEventsto declare additional channels. - Emit them from custom methods (see
markDoneabove) or listeners. - Consumers can
graph.on('task:completed', handler)with full type safety.
Emitters & views
Create renderer stacks tailored to your UI:
ts
import { VertexEmitter } from '@avensio/graph'
const renderer = new VertexEmitter<{ status: string }, { id: string, color: string }>()
renderer.register({
match: () => true,
render: vertex => ({ id: vertex.uuid, color: vertex.data?.status === 'done' ? 'green' : 'gray' })
})Combine this output with GraphView derivatives to standardize the data you send to charts, dashboards, or storage APIs.
Serialization shortcuts
If you frequently serialize into the same format, register a serializer once during app bootstrap:
ts
const graph = new TaskGraph()
graph.registerSerializer('task-json', {
render: g => ({
tasks: [...g.vertices].map(v => ({ id: v.uuid, status: v.data?.status })),
deps: [...g.edges].map(e => ({ from: e.from.uuid, to: e.to.uuid, kind: e.data?.kind }))
}),
parse(data, g) {
g.clear()
// rebuild from domain snapshot
}
})Then call graph.render('task-json') or graph.fromFormat('task-json', snapshot) anywhere in your app.
Testing customizations
- Reuse the existing Vitest suite as inspiration and extend it with domain-specific cases.
- Add fixtures under
test/factories/to model your graph shapes. - Use
pnpm test -- --runInBandif you rely on global state during custom graph initialization.