@motioneffector/spatial

Documentation

Events API

Subscribe to graph changes for real-time updates.


on()

Subscribes to a graph event.

Signature:

on(event: SpatialGraphEvent, callback: EventCallback): () => void

Parameters:

Name Type Required Description
event SpatialGraphEvent Yes Event type to listen for
callback EventCallback Yes Function called when event fires

Returns: () => void — Unsubscribe function

Example:

// Subscribe
const unsubscribe = graph.on('nodeCreated', (nodeId, nodeData) => {
  console.log(`New node: ${nodeId}`)
})

graph.createNode('test')  // Logs: "New node: test"

// Unsubscribe
unsubscribe()
graph.createNode('test2')  // No log

Event Types

nodeCreated

Fired when a node is added to the graph.

Callback Signature:

(nodeId: string, nodeData: NodeData) => void

Example:

graph.on('nodeCreated', (nodeId, nodeData) => {
  console.log('Created:', nodeId)
  console.log('Name:', nodeData.metadata.name)
  console.log('Layer:', nodeData.layer)
})

nodeRemoved

Fired when a node is removed from the graph.

Callback Signature:

(nodeId: string) => void

Example:

graph.on('nodeRemoved', (nodeId) => {
  console.log('Removed:', nodeId)
  minimap.removeRoom(nodeId)
})

connectionCreated

Fired when a connection is created between nodes.

Callback Signature:

(from: string, direction: Direction, to: string) => void

Example:

graph.on('connectionCreated', (from, direction, to) => {
  console.log(`${from} --${direction}--> ${to}`)
  minimap.addPath(from, to)
})

Note: For bidirectional connections, this fires twice (once for each direction).


connectionRemoved

Fired when a connection is removed.

Callback Signature:

(from: string, direction: Direction) => void

Example:

graph.on('connectionRemoved', (from, direction) => {
  console.log(`Disconnected: ${from} ${direction}`)
})

gateUpdated

Fired when a gate is added, updated, or removed.

Callback Signature:

(from: string, direction: Direction, gate: Gate | null) => void

Example:

graph.on('gateUpdated', (from, direction, gate) => {
  if (gate === null) {
    console.log('Gate removed')
    audio.play('door-open.mp3')
  } else if (gate.locked === false) {
    console.log('Gate unlocked')
    audio.play('unlock.mp3')
  } else {
    console.log('Gate set:', gate.id)
  }
})

Types

SpatialGraphEvent

Union of all event type strings.

type SpatialGraphEvent =
  | 'nodeCreated'
  | 'nodeRemoved'
  | 'connectionCreated'
  | 'connectionRemoved'
  | 'gateUpdated'

EventCallback

Union of all callback signatures.

type NodeCreatedCallback = (nodeId: string, data: NodeData) => void
type NodeRemovedCallback = (nodeId: string) => void
type ConnectionCreatedCallback = (from: string, direction: Direction, to: string) => void
type ConnectionRemovedCallback = (from: string, direction: Direction) => void
type GateUpdatedCallback = (from: string, direction: Direction, gate: Gate | null) => void

type EventCallback =
  | NodeCreatedCallback
  | NodeRemovedCallback
  | ConnectionCreatedCallback
  | ConnectionRemovedCallback
  | GateUpdatedCallback

Patterns

Multiple Listeners

Register multiple callbacks for the same event:

graph.on('nodeCreated', (id) => minimap.add(id))
graph.on('nodeCreated', (id) => analytics.track('room', id))
graph.on('nodeCreated', (id) => console.log('Created:', id))

Cleanup on Unmount

Store unsubscribe functions for cleanup:

// React example
useEffect(() => {
  const unsubs = [
    graph.on('nodeCreated', handleNodeCreated),
    graph.on('nodeRemoved', handleNodeRemoved),
    graph.on('gateUpdated', handleGateUpdated),
  ]

  return () => unsubs.forEach(unsub => unsub())
}, [])

Conditional Handling

Filter events in the callback:

graph.on('gateUpdated', (from, direction, gate) => {
  // Only care about treasury door
  if (from === 'hallway' && gate?.id === 'treasury-door') {
    if (!gate.locked) {
      achievements.unlock('opened-treasury')
    }
  }
})