3.5 KiB
3.5 KiB
AnalyticsPlugin
(no submodules)
Purpose
AnalyticsPlugin tracks per-session counters and per-event activity for a
@suaveplan/game-server engine. It subscribes to a GameEventEmitter at
install time and routes structured AnalyticsEvent payloads through an
injectable sink so callers can ship them anywhere — OTel, a queue, an HTTP
endpoint, or stdout in tests.
The plugin keeps the sink decoupled from any concrete observability stack: the engine emits domain events; the plugin shapes them into a stable analytics envelope; the sink decides what to do next. That separation means the same plugin works in unit tests, local development, and production without a behaviour fork.
Features
- Subscribes to
session:created,session:destroyed,game:started,game:ended,player:joined,player:left, andmove:processed. - Per-session row with
startTime,endTime,playerIds,moveCount,completed. - Injectable
Clock(defaults toDate.now) for deterministic timestamps. - Injectable
AnalyticsSink— receivesAnalyticsEventpayloads. - Aggregate
GameMetricssnapshot (getMetrics) including average session duration, abandonment count, and total player count. - Per-session snapshot (
getSession,exportSessions). - Zod-validated
abandonAfterMsoption with a 10-minute default. - Lifecycle-safe: unsubscribes from the emitter on uninstall.
Usage: Basic
import { GameEventEmitter } from "@suaveplan/game-server";
import { AnalyticsPlugin } from "@suaveplan/game-server/plugins/analytics-plugin";
const emitter = new GameEventEmitter();
const sink = (event) => console.log(event);
const plugin = new AnalyticsPlugin({ emitter, sink });
Usage: Advanced
import { GameEventEmitter } from "@suaveplan/game-server";
import { AnalyticsPlugin } from "@suaveplan/game-server/plugins/analytics-plugin";
const emitter = new GameEventEmitter();
const events: unknown[] = [];
const plugin = new AnalyticsPlugin({
emitter,
sink: (e) => events.push(e),
clock: () => 1_700_000_000_000,
abandonAfterMs: 300_000,
});
await plugin.onInstall(ctx);
emitter.emit("game:started", { sessionId: "s1" });
emitter.emit("player:joined", { playerId: "p1", sessionId: "s1" });
emitter.emit("move:processed", { playerId: "p1", move: {}, newState: {} });
emitter.emit("game:ended", {
sessionId: "s1",
result: { winnerId: "p1", scores: {} },
});
const metrics = plugin.getMetrics();
API Reference
class AnalyticsPlugin extends BasePluginconstructor(options: AnalyticsPluginOptions)getMetrics(): GameMetricsgetSession(sessionId): SessionAnalyticsSnapshot | undefinedexportSessions(): readonly SessionAnalyticsSnapshot[]reset(): void
Implementation Notes
session:createdfrom the engine surfaces aSessionNode; the plugin reads onlysession.idand never retains a reference to the node.game:startedis treated as session-create as well; back-to-back emissions of the same session ID are idempotent.- A session counts as abandoned once
now - startTime > abandonAfterMsAND it is still active (not completed). The default 10-minute window matches the upstream behaviour but can be tightened viaabandonAfterMs. - The plugin unsubscribes from every event on
onUninstall, so a re-installable engine cannot accumulate dangling listeners. - The sink call is synchronous; callers that need async fan-out should wrap their async logic in their own queue. This keeps the engine's hot event path off the microtask queue.