Creating a Plugin
Creating a Plugin
A plugin is created by implementing the PhestusPlugin interface and defining its manifest, modules, providers, and optional lifecycle hooks.
A minimal plugin looks like this:
import type {
PhestusPlugin,
} from "@phestus/sdk";
export const examplePlugin: PhestusPlugin = {
manifest: {
slug: "example",
name: "Example Plugin",
version: "0.1.0",
provides: [],
},
};
The plugin can then be registered with Phestus:
const phestus = new Phestus({
plugins: [
examplePlugin,
],
service,
logger,
eventBus,
});
1. Create the Manifest
Every plugin must define a manifest.
manifest: {
slug: "example",
name: "Example Plugin",
version: "0.1.0",
provides: [],
}
The manifest contains:
| Property | Description |
| -------------- | ------------------------------------------------- |
| slug | Unique identifier for the plugin |
| name | Human-readable plugin name |
| version | Plugin version |
| provides | Capabilities implemented by the plugin |
| dependencies | Optional plugin, module, or provider dependencies |
The slug, name, and version are required.
2. Add a Provider
Most plugins exist to package a provider for an existing module.
For example, suppose the event module defines event functionality and a provider implements it.
The provider can be included in the plugin:
import type {
PhestusPlugin,
} from "@phestus/sdk";
const examplePlugin: PhestusPlugin = {
manifest: {
slug: "example-events",
name: "Example Events",
version: "0.1.0",
provides: [
{
moduleSlug: "event",
providerSlug: "example",
},
],
},
providers: [
exampleProvider,
],
};
The important part is the relationship between the module and provider:
{
moduleSlug: "event",
providerSlug: "example",
}
This tells Phestus that the example provider implements the event module.
3. Add a Module
A plugin can also provide modules.
const examplePlugin: PhestusPlugin = {
manifest: {
slug: "example",
name: "Example Plugin",
version: "0.1.0",
provides: [
{
moduleSlug: "example",
providerSlug: "example",
},
],
},
modules: [
exampleModule,
],
providers: [
exampleProvider,
],
};
This pattern is useful when the plugin introduces a completely new capability.
The module defines the capability:
example module
while the provider implements it:
example provider
The plugin packages both together.
4. Declare Capabilities
Every plugin must define a provides array.
provides: [
{
moduleSlug: "event",
providerSlug: "redis",
},
],
A capability connects exactly one module to one provider.
interface PluginCapability {
moduleSlug: string;
providerSlug: string;
}
The module must exist either:
- In the plugin's
modulesarray - Or as a module already registered with Phestus
The provider must be included by the plugin.
For example:
provides: [
{
moduleSlug: "event",
providerSlug: "redis",
},
],
requires a provider with the corresponding redis slug and an event module.
5. Add Dependencies
Plugins can declare dependencies on other plugins, modules, or providers.
dependencies: [
{
type: "module",
slug: "event",
version: "0.1.0",
},
],
A dependency contains:
{
type: "module",
slug: "event",
version: "0.1.0",
}
The supported dependency types are:
"plugin"
"module"
"provider"
Dependencies allow a plugin to explicitly describe the components it requires.
For example, a provider plugin may depend on the module whose capability it implements:
const examplePlugin: PhestusPlugin = {
manifest: {
slug: "example-events",
name: "Example Events",
version: "0.1.0",
dependencies: [
{
type: "module",
slug: "event",
version: "0.1.0",
},
],
provides: [
{
moduleSlug: "event",
providerSlug: "example",
},
],
},
providers: [
exampleProvider,
],
};
6. Add Lifecycle Hooks
Plugins can execute code when Phestus initializes and shuts down.
const examplePlugin: PhestusPlugin = {
manifest: {
slug: "example",
name: "Example Plugin",
version: "0.1.0",
provides: [],
},
async initialize(context) {
context.logger.info(
"Example plugin initialized.",
);
},
async shutdown(context) {
context.logger.info(
"Example plugin shut down.",
);
},
};
The lifecycle context provides access to the shared Phestus runtime context.
interface PhestusContext {
service?: unknown;
logger: Logger;
eventBus: EventBus;
}
Lifecycle hooks should be used for plugin-level startup and shutdown behavior.
7. Register the Plugin
Once the plugin is complete, pass it to the Phestus configuration:
const phestus = new Phestus({
plugins: [
examplePlugin,
],
service,
logger,
eventBus,
});
Phestus registers the plugin and then registers the modules and providers supplied by that plugin.
This means the application does not need to separately register the plugin's components:
// Not required for components owned by the plugin.
modules: [
exampleModule,
],
providers: [
exampleProvider,
],
Instead, the plugin acts as the registration boundary:
plugins: [
examplePlugin,
],
Complete Example
The following example combines the pieces:
import type {
PhestusPlugin,
} from "@phestus/sdk";
import {
exampleModule,
} from "./example-module";
import {
exampleProvider,
} from "./example-provider";
export const examplePlugin: PhestusPlugin = {
manifest: {
slug: "example",
name: "Example Plugin",
version: "0.1.0",
dependencies: [
{
type: "module",
slug: "event",
version: "0.1.0",
},
],
provides: [
{
moduleSlug: "example",
providerSlug: "example",
},
],
},
modules: [
exampleModule,
],
providers: [
exampleProvider,
],
async initialize(context) {
context.logger.info(
"Example plugin initialized.",
);
},
async shutdown(context) {
context.logger.info(
"Example plugin shut down.",
);
},
};
Register it with Phestus:
const phestus = new Phestus({
plugins: [
examplePlugin,
],
service,
logger,
eventBus,
});
The resulting structure is:
Phestus
└── Example Plugin
├── Example Module
├── Example Provider
├── Dependencies
└── Lifecycle
The plugin is now responsible for composing and distributing those components as a single unit.