first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,106 @@
# BasePlugin
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## 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
```ts
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
```ts
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.