Skip to content

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 GraphEvents to declare additional channels.
  • Emit them from custom methods (see markDone above) 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 -- --runInBand if you rely on global state during custom graph initialization.

Built with VitePress – Released under the MIT License.