Layer System
Layers let a single script represent multiple architectural views simultaneously. Instead of maintaining separate scripts for “MVP” and “highload”, or “v1” and “v3”, you tag elements with layer names and toggle them interactively with pill buttons.
How it works
Any node, group, edge, or animation scenario can carry a layer property. When a layer is inactive, all elements tagged with that layer name are hidden from the canvas and from the AnimationPlayer scenario list. When a layer is active, they appear.
Elements with no layer property are always visible, regardless of which pills are active.
// Always visible
group.addProcess({ id: 'api', label: 'API Server', state: 'running' });
// Only visible when 'v2' layer is active
group.addProcess({ id: 'cache', label: 'Redis Cache', shape: 'cylinder', layer: 'v2', state: 'running' });
// Only visible when 'v3' layer is active
group.addProcess({ id: 'kafka', label: 'Kafka Broker', shape: 'cylinder', layer: 'v3', state: 'running' });Layer pill UI
The layer pills appear in the top-right corner of the canvas automatically as soon as the script defines at least one element with a layer property. Each unique layer name becomes one pill button.
Clicking a pill toggles that layer: active layers show their elements, inactive layers hide them. Multiple layers can be active simultaneously.
The pills do not require any configuration — they are generated from the layer names found in the script.
Applying layers
Nodes
group.addProcess({
id: 'search',
label: 'Elasticsearch',
shape: 'cylinder',
layer: 'v2',
state: 'running',
});Groups
const monitoringGroup = topology.createGroup('monitoring', {
label: 'Observability Stack',
layer: 'v3',
});Edges
topology.connect('api', 'search', {
protocol: 'http',
label: 'full-text search',
layer: 'v2',
});Animation scenarios
flow.scenario('v2-cache-hit', 'Cache Hit (v2)', 'Redis returns cached result', { layer: 'v2' });When a scenario has a layer and that layer is inactive, the scenario does not appear in the AnimationPlayer dropdown.
Naming conventions
Choose a convention that matches your use case and apply it consistently throughout the script.
Architectural evolution
// v1 = initial MVP
// v2 = caching layer added
// v3 = full event-driven architecture
layer: 'v1'
layer: 'v2'
layer: 'v3'Scalability tiers
// mvp = single-server setup
// highload = horizontally scaled production
layer: 'mvp'
layer: 'highload'Environment comparison
// prod = production-only components
// staging = staging-only components
layer: 'prod'
layer: 'staging'Failure mode overlay
// failure = components and connections relevant to failure scenarios
layer: 'failure'Multi-layer example
const topology = new TopologyBuilder(true);
const backendGroup = topology.createGroup('backend', { label: 'Backend' });
// v1: basic API + database
backendGroup
.addProcess({ id: 'api', label: 'API Server', state: 'running' })
.addProcess({ id: 'pg', label: 'PostgreSQL', shape: 'cylinder', state: 'running' });
// v2: add Redis cache
backendGroup
.addProcess({ id: 'redis', label: 'Redis', shape: 'cylinder', layer: 'v2', state: 'running' });
backendGroup.autoResize();
// v3: add Kafka and async workers (separate group)
const streamGroup = topology.createGroup('streaming', {
label: 'Event Streaming',
layer: 'v3',
});
streamGroup
.addProcess({ id: 'kafka', label: 'Kafka', shape: 'cylinder', state: 'running' })
.addProcess({ id: 'worker', label: 'Async Worker', state: 'running' })
.autoResize();
// Connections
topology.connect('api', 'pg', { protocol: 'postgresql', label: 'SQL' });
topology.connect('api', 'redis', { protocol: 'redis', label: 'cache', layer: 'v2' });
topology.connect('api', 'kafka', { protocol: 'kafka', label: 'publish', layer: 'v3' });
topology.connect('kafka', 'worker', { protocol: 'kafka', label: 'consume', layer: 'v3', type: 'pulse' });
await topology.apply();
// --- Scenarios per layer ---
const flow = new FlowBuilder();
// Always available (no layer filter)
flow.scenario('basic', 'Basic Request', 'API queries Postgres directly');
flow
.from('api').to('pg')
.showMessage('[QUERY] SELECT ...')
.from('pg').to('api')
.showMessage('[ROWS] Result set returned');
// Only available when v2 is active
flow.scenario('cached', 'Cache Hit', 'Redis serves from cache', { layer: 'v2' });
flow
.from('api').to('redis')
.showMessage('[HIT] Cache hit — skip Postgres')
.from('redis').to('api')
.showMessage('[CACHED] Returning cached result');
// Only available when v3 is active
flow.scenario('async', 'Async Processing', 'Kafka offloads heavy work', { layer: 'v3' });
flow
.from('api').to('kafka')
.showMessage('[PUBLISH] Event queued for background processing')
.from('kafka').to('worker')
.showMessage('[CONSUME] Worker processing event');
return flow;The base (no-layer) elements are always shown, providing a stable foundation. Layer-tagged elements extend the base view progressively. This makes the diagram readable at any layer combination.