# Plugin Services

Plugins are wiring; services are the meat. This page is about which kind of service holds the meat.

There are three tiers, escalating by which plugin_kit features the behavior actually needs.

| Tier | Use when |
|---|---|
| Plain Dart class (no plugin_kit base class) | the behavior does not need settings injection, lifecycle, or event subscriptions |
| `PluginService` | the service should read injected `RuntimeSettings.services` config and have registry-stamped identity |
| `StatefulPluginService<Ctx>` | the service needs `attach()` / `detach()`, a bound plugin context, auto-tracked subscriptions, and a place for context-bound state |

If your plugin class is getting fat, that is almost always a hint that the work belongs in one of these three tiers, not on the plugin. Plain classes register as factories (fresh instance per `context.resolve<T>()`) or as singletons when sharing the instance is safe. `PluginService` supports factory, singleton, and lazy-singleton registration. `StatefulPluginService` rejects factories and must register as a singleton or lazy singleton so the runtime can track lifecycle.

The plugin class stays focused on wiring, services carry settings, state, and long-lived behavior.

## Plugin Services

`PluginService` gives you three things.

- Injected settings, accessible as a typed `ConfigNode` via `config`.
- The raw settings map as `settings`, for when you need to pass it through.
- Authoritative identity via `pluginId` and `serviceId`.

```dart
extractRegion(pluginServicesSnippets, 'plugin-service-basic')
```
**Identity is stamped on resolve, not on construct:** `pluginId` and `serviceId` are set by the registry when the service is resolved.
They are not constructor arguments, and they are not valid until resolution through 
the registry has happened. Do not read them from the constructor.

They are `late` fields, they will throw a `LateInitializationError` if
accessed via the constructor.

## Stateful Plugin Service

`StatefulPluginService` extends `PluginService` with plugin lifecycle. It adds `attach()`, `detach()`, a bound `context`, and automatic subscription tracking. Use `SessionStatefulPluginService` or `GlobalStatefulPluginService` for the common scope aliases.

```dart
extractRegion(pluginServicesSnippets, 'stateful-plugin-service-basic')
```

`attach()`, `detach()`, and `onSettingsInjected()` are pure user hooks: the framework binds and unbinds `this.context` around them, cancels every tracked subscription and binding after `detach()` returns, and runs settings bookkeeping before `onSettingsInjected()` fires. Helpers (`on`, `onRequest`, `bind`, `emit`, `resolve`, etc.) read `this.context` implicitly, so subscriptions inside `attach()` are always bucketed correctly.

## Reacting to settings changes

Override `onSettingsInjected()` to react when settings change. By the time it runs, `config` and `settings` already hold the new values.

```dart
extractRegion(pluginServicesSnippets, 'stateful-plugin-service-inject-settings')
```

Singletons and lazy singletons skip redundant re-injections via a `settingsHash` check; factory wrappers re-inject on every resolve.

## Subscription tracking

After importing `plugin_kit`, extension helpers appear on `StatefulPluginService`. Use those instead
of reaching straight for `context.bus` unless you have a reason not to.

The three that track their own subscriptions:

- `on<T>()`
- `onRequest<Req, Res>()`
- `onRequestSync<Req, Res>()`

When `detach()` runs, every tracked subscription is cancelled for you. This is most of the point of
using a stateful service in the first place: the runtime can tear it down predictably
without you writing explicit cleanup code.

The extension also adds shorthand for the non-subscription actions you need day
to day: `emit(...)`, `resolve(...)`, `maybeResolve(...)`, and `resolveAfter(...)`. Those are covered
in the next section. To resolve a namespaced slot, build 
the `ServiceId` via `Namespace.call(...)` and pass it to `resolve(...)` or `maybeResolve(...)`.

## Resolving and emitting

Stateful services also get short helpers for common runtime actions, so you do not
have to go through `context` for the normal cases.

```dart
extractRegion(pluginServicesSnippets, 'stateful-plugin-service-resolve-emit')
```

The service-level helpers are thin wrappers over the bound plugin context:

- `resolve(...)`
- `maybeResolve(...)`
- `resolveAfter(...)`
- `emit(...)`

For namespaced slots, build a `ServiceId` via `Namespace.call(...)` and pass it to `resolve(...)` or `maybeResolve(...)`.

The convenience `emit(...)` mirrors the underlying `EventBus.emit` signature: pass `(event, {identifier})` and 
await the returned `EventEnvelope<T>` to read the `stopped` flag, the identifier, or
the possibly-mutated payload.

[Events](https://plugin-kit.saad-ardati.dev/concepts/events/) and [Event Bus](https://plugin-kit.saad-ardati.dev/concepts/event-bus/) cover what the envelope carries and when it matters.

## How settings arrive

Settings are injected by the `ServiceRegistry` at resolution time, so:

- the service has to be resolved through the registry
- the service has to extend `PluginService`
- the settings key has to match the service's slot

```dart
extractRegion(pluginServicesSnippets, 'plugin-service-settings-runtime')
```

```dart
extractRegion(pluginServicesSnippets, 'plugin-service-basic')
```

Singletons and lazy singletons avoid redundant reinjection when the effective settings hash has not changed.

## The case for stateful services

The runtime runs the full plugin lifecycle on a toggle: `attach` re-runs on enable, `detach` re-runs on disable. Subscriptions registered directly from `SessionPlugin.attach(context)` via the auto-tracked helpers (`on(context, ...)`, `bind(context, ...)`) are torn down for you on disable. So plugin-level subscriptions can stay correct without ceremony.

Stateful services pay for themselves when the work is more than wiring:

- The `on / onRequest / bind` helpers track subscriptions and bindings automatically.
- The framework cancels every tracked subscription after `detach()` returns. You never maintain a `_subs` list.
- It separates wiring (the plugin class) from state (the service), which scales as the plugin grows.

That makes stateful services the natural home for anything with long-lived state:

- long-lived bus subscriptions
- timers
- context-bound caches
- mutable context state

If you find yourself writing `final _subs = <EventSubscription>[];` on a `SessionPlugin`, that is usually the signal to promote the state to a stateful service.

## Registration rule

Register stateful services as singletons or lazy singletons.

- `registerSingleton(...)` or
- `registerLazySingleton(...)`

Factories are rejected. A factory would produce a fresh instance every resolve, and there
is no way for the runtime to track or detach something it never got to see again.

## Thin plugin, fat service

A clean default structure:

- Plugin class: wiring, ids, registration.
- `PluginService`: configurable services.
- `StatefulPluginService`: context-bound behavior and subscriptions.

That keeps the plugin class small and makes lifecycle obvious.

## Related reading

[Adding a Plugin](https://plugin-kit.saad-ardati.dev/guides/adding-a-plugin/)
  [Event Patterns](https://plugin-kit.saad-ardati.dev/concepts/events/)
  [Configuration](https://plugin-kit.saad-ardati.dev/concepts/configuration/)
  [Settings and Overrides](https://plugin-kit.saad-ardati.dev/guides/settings/)