# Service Registry

The `ServiceRegistry` lets multiple plugins compete for the same slot. Plugins register services
under a `ServiceId`. When someone asks for that id, the highest-priority registration wins. The loser
stays reachable if the winner wants to defer to it.

Most of the interesting behavior in Plugin Kit follows from that shape: override without patching,
wrap without subclassing, discover without instantiating.

## Mental model: slots

Think of the registry as a table of slots.

- A slot has a `ServiceId` and a type.
- One or more plugins can register that slot.
- The highest-priority registration wins resolution.
- The winner can still delegate to the next registration with `resolveAfter`.

A slot is not a class. It is a contract the registry recognizes, and multiple implementations can satisfy it.

## Three registration modes

| Method                  | Behavior                                  | Use when                                    |
|-------------------------|-------------------------------------------|---------------------------------------------|
| `registerFactory`       | creates a fresh instance on every resolve | service is cheap and stateless              |
| `registerLazySingleton` | creates on first resolve, then caches     | setup is expensive but shared state is fine |
| `registerSingleton`     | runs the factory immediately, then caches | you want eager creation and shared state     |

```dart
extractRegion(serviceRegistrySnippets, 'service-registry-register-all-three')
```
**Stateful services cannot be factories:** `StatefulPluginService`s must be singletons or lazy singletons. Factory registration is
    rejected outright because the runtime cannot manage the lifecycle of instances it never gets to see again.

If the same plugin registers the same slot twice, the second call replaces the first unless the existing registration is an attached `StatefulPluginService`, in which case registration throws `ArgumentError`.

## Resolution

Resolve from a `PluginContext`, a `PluginSession`, or the registry directly. They all forward to the same underlying lookup.

```dart
extractRegion(serviceRegistrySnippets, 'service-registry-resolve-basic')
```

| Method | Behavior |
|---|---|
| `resolve<T>(serviceId)` | return the winning registration, throw if none |
| `maybeResolve<T>(serviceId)` | return the winner or `null` |
| `resolveAfter<T>(pluginId, serviceId)` | skip one registration and return the next |
| `resolveRaw<T>(serviceId)` | return the wrapper, do not instantiate |

## Priority

If multiple plugins register the same `serviceId`, the registry sorts them in descending priority order. Higher number wins. The default is `Priority.normal` (500).

```dart
extractRegion(serviceRegistrySnippets, 'service-registry-priority-competing')
```

`resolve(const ServiceId('code_formatter'))` now returns `PrettierFormatter`. `DefaultFormatter` is still in the registry. It is just not the winner.

Priority can also be overridden from outside by `RuntimeSettings.services`. The override is applied at registration time, which
means runtime settings can hand a slot to a different plugin without anyone changing source code.

![Plugin Kit Dialog Advanced tab showing competing service registrations with priority order and the current winner highlighted](https://plugin-kit.saad-ardati.dev/images/dialog/advanced_tab_dark.png)

*The Advanced tab makes the competition visible. Every slot, every registrant, every priority, with the
current winner picked out. This is the same data the runtime uses to resolve.*

## Delegation with `resolveAfter`

`resolveAfter` is what turns "override" into "layer." A plugin that wins a slot can still ask for the previous implementation and defer to it selectively.

Say your plugin provides a smart formatter for `.dart` files, but the user just opened a `.txt` file that the previous formatter already handled well.

```dart
extractRegion(serviceRegistrySnippets, 'service-registry-resolve-after')
```

The wrapper plugin wins the slot completely in the session but quietly defers to the runner-up winner implementation
for anything it does not want to touch.

This is the clean way to extend behavior without forking the plugin you are extending.

<Aside type="caution" title="If you're using `resolveAfter` a lot">
    That is a code smell. It means you are trying to build a chain of responsibility, which the system has more elegant
    solutions for. Consider refactoring your plugin to depend on event-driven decision points instead of trying to
    wrap an entire service end-to-end.

    See [Events](https://plugin-kit.saad-ardati.dev/concepts/events/).
</Aside>

## Namespaces

For related families of slots, the registry supports namespaces. Under the hood, a namespace is just a prefix: `namespace.serviceId`.
The registry itself only knows about `ServiceId`; `Namespace` is a tiny helper for composing dotted ids.

```dart
extractRegion(serviceRegistrySnippets, 'service-registry-namespace-panel')
```

Namespaces are only useful for semantic grouping. Entirely stylistic. You can also register `const ServiceId('panel.console')` directly as a flat id and nothing breaks.

To ask "is this service id under this namespace?", use the typed predicate `namespace.has(serviceId)`. It matches direct children and nested descendants alike (so `Namespace('panel').has(ServiceId('panel.console'))` and `Namespace('panel').has(ServiceId('panel.editor.terminal'))` are both true).

## Settings injection

If the resolved instance extends `PluginService`, the registry injects its settings automatically during resolution. The service reads
them through a typed `config` wrapper instead of parsing raw maps.

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

For singletons and lazy singletons, the registry hashes the effective settings and only re-injects when they actually change.
Reading from `config` repeatedly does not trigger re-parsing.

The registry also stamps two identity fields on every resolved `PluginService`: `pluginId` (which plugin owns the registration) and `serviceId` (the full registry key).
These are only authoritative after resolution through the registry, so do not rely on them inside the constructor of your PluginService class.
**How wildcard overrides land:** Settings support wildcard service ids like `*:formatter` that apply to whoever wins that slot. The rule to remember:
    the winner is decided first,
    then wildcard settings are materialized onto the winner, then normal resolution and injection runs with the winner's
    effective settings.
    Wildcard settings are not "always beaten by plugin-specific settings." They are applied to the winner before the
    plugin's own injection happens.

## Raw wrappers

The registry stores `RegistrationWrapper`s, not service instances directly. `resolveRaw` gives you the wrapper without instantiating anything.

```dart
extractRegion(serviceRegistrySnippets, 'service-registry-resolve-raw')
```

That matters when you want discovery instead of execution. Maybe you are building a settings UI that lists every configurable service without paying the cost of building them all. Maybe you want to show the user which plugin currently wins a slot. `resolveRaw` is how you do that without side effects.

## Capabilities

Capabilities are metadata tags attached to the registration wrapper, not to the instance.
This allows for introspection and discovery without instantiation. The registry does not care about the content of capabilities,
only that they exist on the wrapper.

```dart
extractRegion(capabilitiesSnippets, 'capability-register-multiple')
```

Capabilities answer questions you would otherwise have to build the service to ask: does this support Dart? Is this part of a suite?
You can define your own capability types. The system does not care about the set of tags, only that they exist on the wrapper.

Capabilities live on the slot registration, not inside the service instance, so you can inspect them without constructing anything.
[Capabilities](https://plugin-kit.saad-ardati.dev/concepts/capabilities/) goes deeper.

## Related reading

[Plugin Services](https://plugin-kit.saad-ardati.dev/concepts/plugin-services/)
    [Capabilities](https://plugin-kit.saad-ardati.dev/concepts/capabilities/)
    [Settings and Overrides](https://plugin-kit.saad-ardati.dev/guides/settings/)