Apinni Plugins
Apinni’s plugin system allows developers to extend its type generation capabilities by defining custom logic for processing decorators, metadata, and type generation. Plugins integrate with the Apinni pipeline, leveraging lifecycle hooks to customize behavior, and support dependencies for modular and reusable functionality. This guide covers creating plugins, configuring them in your project, and managing dependencies, complementing the core decorators like @ApinniController, @ApinniEndpoint, @ApinniDomain, and @ApinniDisabled.
- Extend Apinni with custom decorators, metadata, and type generation logic.
- Support for shareable plugins that can be reused as dependencies.
- Lifecycle hooks for initializing, processing, and generating types.
- Dependency management with context sharing for modular plugins.
Plugin Architecture
A Apinni plugin is defined by the ApinniPlugin interface, which specifies a unique name, optional config, lifecycle hooks, and optional dependencies. Plugins can be shareable (usable as dependencies by other plugins) and can manipulate the global context (class and method metadata) or share custom context data with dependent plugins.
Plugin Lifecycle Hooks
Plugins execute the following hooks in order during the Apinni pipeline:
| Hook | Description |
|---|---|
onInitialize | Registers custom decorators in the Apinni registry. |
onAfterDecoratorsProcessed | Runs after all decorators are processed for validation or preparation. |
onProvideSharedContext | Shares plugin-specific context data with dependent plugins (shareable only). |
onConsumeDependencyContexts | Consumes shared contexts from dependencies (for plugins with dependencies). |
onRegisterMetadata | Registers custom metadata in the generation context. |
onGenerateTypes | Generates final type definitions or performs custom generation tasks. |
Use onInitialize to register custom decorators and onRegisterMetadata to add plugin-specific metadata, ensuring seamless integration with Apinni’s type generation.
Plugin Configuration
Define a plugin using the ApinniPlugin interface. The configuration includes:
| Property | Type | Description |
|---|---|---|
name | string | Unique identifier for the plugin. |
config | { shareable?: boolean } | Optional settings; set shareable: true to allow use as a dependency. |
hooks | Object with lifecycle hooks | Functions for each lifecycle stage (see above). |
dependencies | Dependency[] | Array of dependent plugins with optional allowContextManipulation option. |
Dependency Options
When a plugin depends on another, it can specify:
| Property | Type | Description |
|---|---|---|
plugin | ShareablePlugin | The dependent plugin (must have shareable: true). |
options.allowContextManipulation | boolean | If true, allows the dependency to manipulate the global context. |
Dependencies must be shareable plugins. Non-shareable dependencies will throw an error during plugin resolution.
Creating a Plugin
Example 1: Basic Plugin
Create a simple plugin that logs endpoint paths during type generation.
import { ApinniPlugin, ShareableRegistry, ShareableContext } from '@apinni/client-ts';
export const logPathsPlugin: ApinniPlugin = {
name: 'log-paths',
hooks: {
onInitialize: (registry: ShareableRegistry) => {
console.log('Initializing log-paths plugin');
},
onRegisterMetadata: (context: ShareableContext) => {
context.registerMethodMetadata = (target, propertyKey, metadata) => {
console.log(`Registering endpoint: ${metadata.path}`);
};
},
onGenerateTypes: async ({ methodMetadata }) => {
console.log('Generated endpoint paths:');
methodMetadata.forEach(meta => {
console.log(`- ${meta.path}`);
});
},
},
};
Example 2: Shareable Plugin with Custom Context
Create a shareable plugin that provides custom context data to dependent plugins.
import { ShareablePlugin, ShareableRegistry, ShareableContext } from '@apinni/client-ts';
export const authContextPlugin: ShareablePlugin = {
name: 'auth-context',
config: { shareable: true },
hooks: {
onInitialize: (registry: ShareableRegistry) => {
console.log('Initializing auth-context plugin');
},
onProvideSharedContext: () => ({
data: { authPrefix: '/secure' },
}),
onGenerateTypes: async ({ methodMetadata }) => {
console.log('Adding auth prefix to endpoints:');
methodMetadata.forEach(meta => {
console.log(`- ${meta.path}`);
});
},
},
};
Example 3: Plugin with Dependencies
Create a plugin that depends on the auth-context plugin and uses its shared context.
- Dependent Plugin
import { ApinniPlugin, ShareableContext } from '@apinni/client-ts';
import { authContextPlugin } from './auth-context-plugin';
export const secureEndpointsPlugin: ApinniPlugin = {
name: 'secure-endpoints',
dependencies: [
{ plugin: authContextPlugin, options: { allowContextManipulation: true } },
],
hooks: {
onInitialize: (registry: ShareableRegistry) => {
console.log('Initializing secure-endpoints plugin');
},
onConsumeDependencyContexts: (contexts: [{ data: { authPrefix: string } }]) => {
const authContext = contexts[0].data;
console.log(`Using auth prefix: ${authContext.authPrefix}`);
},
onRegisterMetadata: (context: ShareableContext) => {
context.registerMethodMetadata = (target, propertyKey, metadata) => {
metadata.path = `/secure${metadata.path}`;
console.log(`Secured endpoint: ${metadata.path}`);
};
},
},
};
- Plugin Name:
secure-endpoints - Dependency:
auth-context(shareable plugin) - Behavior: Prepends
/secureto endpoint paths using the sharedauthPrefix. - Generated Types: Reflects modified paths in type definitions.
Configuring Plugins
Add plugins to your Apinni configuration in apinni.config.json or directly in the apinni command.
Option 1: Configuration File
Include plugins in apinni.config.json:
{
"includePatterns": ["src/controllers/**/*.ts"],
"outputPath": "./types",
"plugins": [
{ "name": "log-paths", "path": "./src/plugins/log-paths-plugin.ts" },
{ "name": "auth-context", "path": "./src/plugins/auth-context-plugin.ts" },
{ "name": "secure-endpoints", "path": "./src/plugins/secure-endpoints-plugin.ts" }
]
}
Option 2: Command-Line
Pass plugins via the --plugins flag:
npx apinni --plugins src/plugins/log-paths-plugin.ts,src/plugins/auth-context-plugin.ts,src/plugins/secure-endpoints-plugin.ts
Ensure plugins are loaded in the correct order to resolve dependencies. Apinni automatically handles topological sorting to prevent circular dependencies.
Running Plugins
Run the apinni command to execute the plugin lifecycle:
- npm
- Yarn
- pnpm
npm run apinni
yarn apinni
pnpm run apinni
The pipeline executes the following steps:
- Preparation: Initializes plugins and caches files.
- Core Processing: Scans for decorators, compiles files, and processes decorators.
- Finalization: Executes plugin hooks (
onAfterDecoratorsProcessed,onConsumeDependencyContexts,onRegisterMetadata,onGenerateTypes) and generates types.
Usage Notes
- Shareable Plugins: Set
shareable: truein theconfigto allow a plugin to be used as a dependency. Only shareable plugins can provide shared context viaonProvideSharedContext. - Dependency Restrictions: By default, dependencies cannot manipulate the global context (
onRegisterMetadata,onGenerateTypes) unlessallowContextManipulation: trueis specified. - Error Handling: Ensure unique plugin names and valid dependency configurations to avoid errors like circular dependencies or missing shareable plugins.
- Context Manipulation: Use
onRegisterMetadatato modify class or method metadata, but be cautious to avoid conflicts with other plugins.
Experiment with Apinni plugins in a live TypeScript environment: CodeSandbox Link.
Troubleshooting
| Issue | Solution |
|---|---|
| Plugin not loaded | Verify the plugin path in apinni.config.json or --plugins flag. |
| Circular dependency error | Check plugin dependencies for cycles and reorder or remove conflicting dependencies. |
| Shared context not found | Ensure dependency plugins have shareable: true and implement onProvideSharedContext. |
| Metadata not registered | Confirm allowContextManipulation: true for dependencies if they need to modify context. |
Use the --verbose flag for detailed logs:
npx apinni --verbose
Related Topics
- Installation – Install and set up Apinni.
- ApinniController – Define controllers with base paths.
- ApinniEndpoint – Define individual endpoints.
- ApinniDomain – Group endpoints into domains.
- ApinniDisabled – Exclude classes or methods from type generation.
For advanced use cases, check the Apinni API Reference or contact the Apinni community.