Skip to main content

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.

Key Features
  • 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:

HookDescription
onInitializeRegisters custom decorators in the Apinni registry.
onAfterDecoratorsProcessedRuns after all decorators are processed for validation or preparation.
onProvideSharedContextShares plugin-specific context data with dependent plugins (shareable only).
onConsumeDependencyContextsConsumes shared contexts from dependencies (for plugins with dependencies).
onRegisterMetadataRegisters custom metadata in the generation context.
onGenerateTypesGenerates final type definitions or performs custom generation tasks.
tip

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:

PropertyTypeDescription
namestringUnique identifier for the plugin.
config{ shareable?: boolean }Optional settings; set shareable: true to allow use as a dependency.
hooksObject with lifecycle hooksFunctions for each lifecycle stage (see above).
dependenciesDependency[]Array of dependent plugins with optional allowContextManipulation option.

Dependency Options

When a plugin depends on another, it can specify:

PropertyTypeDescription
pluginShareablePluginThe dependent plugin (must have shareable: true).
options.allowContextManipulationbooleanIf true, allows the dependency to manipulate the global context.
warning

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.

src/plugins/log-paths-plugin.ts
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.

src/plugins/auth-context-plugin.ts
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.

src/plugins/secure-endpoints-plugin.ts
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}`);
};
},
},
};
example
  • Plugin Name: secure-endpoints
  • Dependency: auth-context (shareable plugin)
  • Behavior: Prepends /secure to endpoint paths using the shared authPrefix.
  • 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:

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
warning

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 run apinni

The pipeline executes the following steps:

  1. Preparation: Initializes plugins and caches files.
  2. Core Processing: Scans for decorators, compiles files, and processes decorators.
  3. Finalization: Executes plugin hooks (onAfterDecoratorsProcessed, onConsumeDependencyContexts, onRegisterMetadata, onGenerateTypes) and generates types.

Usage Notes

  • Shareable Plugins: Set shareable: true in the config to allow a plugin to be used as a dependency. Only shareable plugins can provide shared context via onProvideSharedContext.
  • Dependency Restrictions: By default, dependencies cannot manipulate the global context (onRegisterMetadata, onGenerateTypes) unless allowContextManipulation: true is specified.
  • Error Handling: Ensure unique plugin names and valid dependency configurations to avoid errors like circular dependencies or missing shareable plugins.
  • Context Manipulation: Use onRegisterMetadata to modify class or method metadata, but be cautious to avoid conflicts with other plugins.
Try It Out

Experiment with Apinni plugins in a live TypeScript environment: CodeSandbox Link.

Troubleshooting

IssueSolution
Plugin not loadedVerify the plugin path in apinni.config.json or --plugins flag.
Circular dependency errorCheck plugin dependencies for cycles and reorder or remove conflicting dependencies.
Shared context not foundEnsure dependency plugins have shareable: true and implement onProvideSharedContext.
Metadata not registeredConfirm allowContextManipulation: true for dependencies if they need to modify context.

Use the --verbose flag for detailed logs:

npx apinni --verbose
info

For advanced use cases, check the Apinni API Reference or contact the Apinni community.