Files
skills/docs/gaming/games/game-server/src/plugins/base-plugin/base-plugin.md
2026-05-29 08:20:56 +01:00

3.7 KiB

BasePlugin

(no submodules)

Purpose

BasePlugin is the abstract base class every concrete @suaveplan/game-server plugin extends. It implements the EnginePlugin contract that the PluginManager consumes (onInstall, onUninstall, onEnable, onDisable) and forwards each call to a protected template-method hook (onInstallHook etc.) that subclasses override.

The base also owns a small lifecycle state machine (created → installed → enabled → disabled → destroyed) and refuses to run any hook once the plugin has reached destroyed. This catches the common "resurrect a torn-down plugin" bug at the boundary instead of letting it corrupt downstream state.

Features

  • Implements EnginePlugin so the PluginManager can install/enable/disable any subclass without per-plugin glue.
  • Template-method hooks (onInstallHook, onUninstallHook, onEnableHook, onDisableHook) — subclasses override only what they need.
  • Injectable Clock for deterministic timestamping; defaults to Date.now.
  • Injectable LoggerLike (defaults to no-op until installed; install promotes the manager-supplied logger).
  • Public state getter and snapshot() accessor for observability.
  • assertLive(op) helper subclasses call from public methods to fail fast on use-after-destroy with GAME_SERVER.PLUGIN_BASE_DESTROYED.

Usage: Basic

import { BasePlugin } from "@suaveplan/game-server/plugins/base-plugin";

class HelloPlugin extends BasePlugin {
    public readonly name = "hello";
    public readonly version = "1.0.0";

    protected async onInstallHook(): Promise<void> {
        this.logger.info("hello plugin installed");
    }
}

Usage: Advanced

import { BasePlugin } from "@suaveplan/game-server/plugins/base-plugin";
import type { EnginePluginContext } from "@suaveplan/game-server/plugins/base-plugin";

class CountingPlugin extends BasePlugin {
    public readonly name = "counter";
    public readonly version = "1.0.0";
    public readonly dependencies = ["hello"] as const;

    private installs = 0;

    public constructor() {
        super({ clock: () => 1_700_000_000_000 });
    }

    protected async onInstallHook(_ctx: EnginePluginContext): Promise<void> {
        this.installs += 1;
    }

    public count(): number {
        this.assertLive("count");
        return this.installs;
    }
}

API Reference

  • class BasePlugin implements EnginePlugin
  • abstract readonly name: string
  • abstract readonly version: string
  • readonly dependencies?: readonly string[]
  • get state(): BasePluginState
  • snapshot(): BasePluginSnapshot
  • onInstall(ctx): Promise<void>
  • onUninstall(ctx): Promise<void>
  • onEnable(ctx): Promise<void>
  • onDisable(ctx): Promise<void>
  • protected onInstallHook(ctx): Promise<void> (override)
  • protected onUninstallHook(ctx): Promise<void> (override)
  • protected onEnableHook(ctx): Promise<void> (override)
  • protected onDisableHook(ctx): Promise<void> (override)
  • protected assertLive(op: string): void

Implementation Notes

  • The state machine is deliberately minimal: every hook except onUninstall throws PLUGIN_BASE_DESTROYED when the plugin has already been torn down. onUninstall itself is also rejected after destroy because the PluginManager removes the entry from its map on the first uninstall.
  • The logger field starts as a no-op so subclass constructors can log defensively without crashing tests that omit a logger; once onInstall runs, the context logger from PluginManager is adopted unless the caller supplied an explicit override via BasePluginOptions.
  • The clock is captured at construction time so callers can substitute a FakeClock from @suaveplan/testing for deterministic snapshots.