npm.io
0.2.1 • Published 2d ago

@mygen/traffic-tree

Licence
MIT
Version
0.2.1
Deps
0
Size
104 kB
Vulns
0
Weekly
0

@mygen/traffic-tree

Turn a privacy-safe aggregate navigation graph into a bounded forest ready for a visual renderer. Traffic Tree contains no tracker, database client, authentication, consent UI, or Astro dependency.

Install

pnpm add @mygen/traffic-tree

Use

import { buildForest, type TrafficDataSource } from '@mygen/traffic-tree';

const source: TrafficDataSource = {
  async getSnapshot(query) {
    return fetch(
      `/api/admin/traffic?from=${query.window.from}&to=${query.window.to}`,
    ).then((r) => r.json());
  },
};

const snapshot = await source.getSnapshot({
  window: { from: '2026-07-01T00:00:00.000Z', to: '2026-07-08T00:00:00.000Z' },
});
const forest = buildForest(snapshot);

The default policy renders only branches with at least 10 transitions, stops at depth 5, and shows at most four child branches per route. Suppressed transitions are reported as each node's canopy, so a renderer can show the long tail without exposing individual routes. Hosts can tune that visible fan-out with maxChildren; Local95 uses a deliberately compact value of two.

Nodes may include an optional host-owned group string. The botanical renderer uses it only to keep related shoots adjacent; the package does not interpret the group vocabulary.

Privacy boundary

The host application is responsible for consent, access control, collection, retention, and aggregation. Only supply stable, deliberately small route IDs and safe human labels. Do not send raw paths, query strings, referrers, IP addresses, identifiers, or other personal data to this package.

See the contract for terminal and incomplete-graph behavior.

Browser client

The framework-free SVG renderer is exported from @mygen/traffic-tree/web. It displays snapshots supplied by the host; it does not collect navigation data.

import { mountTrafficTree } from '@mygen/traffic-tree/web';

mountTrafficTree(document.querySelector('#traffic-tree')!, {
  layout: 'botanical',
  loadSnapshot: async (query) => {
    const response = await fetch('/admin/traffic/snapshot', {
      method: 'POST',
      credentials: 'same-origin',
      cache: 'no-store',
      headers: { 'content-type': 'application/json' },
      body: JSON.stringify(query),
    });
    if (!response.ok) throw new Error('snapshot unavailable');
    return response.json();
  },
  forest: { primaryRoot: { id: 'home' } },
});

The browser client includes 24-hour, 7-day, and 30-day controls and polls its loader every 30 seconds while the page is visible. Disable polling with refreshIntervalMs: false. Background refreshes preserve the viewer's scroll position and focused node; the grove re-centres only on first paint and on a range change.

Hosts backed by coarser aggregates can expose only accurate controls while preserving their preferred order:

mountTrafficTree(target, {
  ranges: ['7d', '30d'],
  initialRange: '7d',
  loadSnapshot,
});

Omitting ranges preserves the default 24-hour, 7-day, and 30-day controls.

The default botanical layout chooses the strongest incoming transition as each route's stem and renders secondary transitions as lighter grafts. Node height is not tied to route depth: each limb's length follows the strength of the flow it carries, so strong paths stretch into tall stems while trickles stay short twigs, and every tree earns its own silhouette. Small horizontal sways and branch bends come from a deterministic hash of the route ID — the same snapshot always renders the same grove. Set layout: 'layered' to use the compact strict-depth general-DAG fallback.

Snapshots may also include an optional daily array of { day, navigations } aggregates. The renderer draws it as a small activity line along the grove's ground. These are event totals, not unique-person counts. The renderer inherits the host's --paper, --paper-raised, --ink, --ink-soft, and --accent CSS tokens when available, so it follows the host's light and dark modes.

The renderer merges repeated route IDs at the same visual depth. Multiple incoming branches therefore converge on one route node; the same route may still appear at another depth where it represents a distinct navigation step.

Keywords