Plugin Development Guide
This guide provides detailed instructions for developing TSDIAPI plugins, including configuration, file structure, and best practices.
🚀 Getting Started
Creating a New Plugin
To create a new empty plugin, use the following command:
tsdiapi dev plugin <name>
This will:
- Create a new plugin directory with the specified name
- Set up the basic file structure
- Initialize the necessary configuration files
- Create a basic plugin implementation
Example:
tsdiapi dev plugin my-plugin
The command will create a plugin with the following structure:
📂 tsdiapi-my-plugin/
├── 📜 package.json
├── 📜 tsdiapi.config.json
├── 📜 README.md
├── 📂 src/
│ ├── 📜 index.ts
│ ├── 📜 provider.ts
📦 Plugin Basics
A TSDIAPI plugin extends server functionality by adding controllers, services, middleware, or integrations.
Each plugin follows a modular architecture:
- Entry point (
index.ts
) → Registers the plugin in TSDIAPI - Configuration (
tsdiapi.config.json
) → Defines installation settings - Provider (
provider.ts
) → Implements business logic - Files & Generators (
files/
,generators/
) → Handles additional structures
📁 Plugin File Structure
A typical TSDIAPI plugin follows this structure:
📂 tsdiapi-myplugin/
├── 📜 package.json
├── 📜 tsdiapi.config.json
├── 📜 README.md
├── 📂 src/
│ ├── 📜 index.ts
│ ├── 📜 provider.ts
│ ├── 📂 features/
│ │ ├── 📜 myfeature.controller.ts
│ │ ├── 📜 myfeature.service.ts
│ │ ├── 📜 myfeature.dto.ts
├── 📂 files/
├── 📂 generators/
🔧 Plugin Components
1. Entry Point (index.ts
)
import type { AppContext, AppPlugin } from "@tsdiapi/server";
import { MyPluginProvider } from "./provider";
export type PluginOptions = {
apiKey?: string;
};
const defaultConfig: PluginOptions = {
apiKey: "",
};
class MyPlugin implements AppPlugin {
name = "tsdiapi-myplugin";
config: PluginOptions;
context: AppContext;
provider: MyPluginProvider;
constructor(config?: PluginOptions) {
this.config = { ...defaultConfig, ...config };
this.provider = new MyPluginProvider();
}
async onInit(ctx: AppContext) {
this.context = ctx;
const appConfig = ctx.config.appConfig || {};
this.config.apiKey = this.config.apiKey || appConfig["MYPLUGIN_API_KEY"];
if (!this.config.apiKey) {
throw new Error("Missing API key");
}
this.provider.init(this.config, ctx.fastify.log);
}
}
export default function createPlugin(config?: PluginOptions) {
return new MyPlugin(config);
}
2. Provider (provider.ts
)
import type { AppContext } from "@tsdiapi/server";
type Logger = AppContext["fastify"]["log"];
export class MyPluginProvider {
private config: { apiKey: string };
private logger: Logger;
init(config: { apiKey: string }, logger: Logger) {
this.config = config;
this.logger = logger;
}
async fetchData(query: string): Promise<string> {
this.logger.info(`Fetching data with query: ${query}`);
return `Response for: ${query}`;
}
}
3. Configuration (tsdiapi.config.json
)
{
"name": "@tsdiapi/myplugin",
"description": "A TSDIAPI plugin to fetch external data",
"variables": [
{
"name": "MYPLUGIN_API_KEY",
"type": "string",
"default": "",
"configurable": true,
"description": "API Key for authentication",
"inquirer": {
"type": "input",
"message": "Enter your API key:"
}
}
],
"generators": [
{
"name": "feature",
"description": "Generate a feature for MyPlugin",
"args": [
{
"name": "featureName",
"description": "Feature name",
"inquirer": {
"type": "input",
"message": "Enter the feature name:"
}
}
],
"files": [
{
"source": "generators/feature/*.ts",
"destination": "src/features/{{featureName}}/",
"isHandlebarsTemplate": true
}
]
}
]
}
4. Prisma Integration
TSDIAPI plugins can extend Prisma in two ways:
-
Root Level Prisma Extension (
tsdiapi.config.json
root)- Executed during plugin installation
- Used for core models and enums
- Example:
{
"prisma": {
"required": true,
"scripts": [
{
"command": "ADD ENUM MediaType ({IMAGE|VIDEO|DOCUMENT|OTHER});",
"description": "Add MediaType enum",
"when": "MEDIA_ENABLED === 'true'"
}
]
}
}when
condition uses plugin variables directly- Example variables:
{
"variables": [
{
"name": "MEDIA_ENABLED",
"type": "string",
"default": "false",
"configurable": true,
"description": "Enable media functionality",
"inquirer": {
"type": "input",
"message": "Enable media functionality? (true/false):"
}
},
{
"name": "MEDIA_PREVIEW_SIZE",
"type": "number",
"default": 512,
"configurable": true,
"description": "The size of the media preview image in pixels",
"inquirer": {
"type": "input",
"message": "Enter the width of the media preview image in pixels:"
}
}
]
} -
Generator Level Prisma Extension (inside generators)
- Executed during feature generation
- Used for feature-specific models
- Example:
{
"generators": [
{
"name": "feature",
"args": [
{
"name": "userModelName",
"description": "Prisma model name for users",
"inquirer": {
"type": "input",
"message": "Enter the Prisma model name for users:",
"default": "User"
}
},
{
"name": "withMedia",
"type": "boolean",
"default": false,
"description": "Include media functionality",
"inquirer": {
"type": "confirm",
"message": "Include media functionality?"
}
}
],
"prismaScripts": [
{
"command": "ADD MODEL Media ({id String @id @default(cuid())});",
"description": "Add Media model",
"when": "withMedia === true"
}
]
}
]
}when
condition uses generator arguments directly
Prisma scripts support:
- Adding models
- Adding enums
- Adding relations
- Modifying existing models
- Conditional execution using
when
expressions
The when
condition:
- Uses JEXL expressions
- Evaluates to boolean
- In root scripts: uses plugin variables directly
- In generator scripts: uses generator arguments directly
- If not specified, script always executes
5. Advanced Configuration Examples
Variable Configuration
{
"variables": [
{
"name": "MYPLUGIN_SECRET",
"type": "string",
"default": "",
"configurable": true,
"description": "Secret key for the plugin",
"inquirer": {
"type": "input",
"message": "Enter your secret key:"
},
"validate": {
"type": "string",
"minLength": 10,
"errorMessage": "Secret key must be at least 10 characters long."
}
},
{
"name": "MYPLUGIN_TIMEOUT",
"type": "number",
"default": 60,
"configurable": true,
"description": "Timeout in seconds",
"inquirer": {
"type": "number",
"message": "Enter timeout in seconds:"
},
"validate": {
"type": "integer",
"minimum": 1,
"errorMessage": "Timeout must be a positive number."
},
"transform": "x * 1000"
}
]
}
Generator with Prisma Relations
{
"generators": [
{
"name": "feature_with_relations",
"description": "Generate a feature with Prisma relations",
"dependencies": [
"@tsdiapi/prisma"
],
"files": [
{
"source": "generators/feature_with_relations/*.*",
"destination": "{{name}}",
"overwrite": false,
"isHandlebarsTemplate": true
}
],
"args": [
{
"name": "parentModelName",
"description": "Parent model name",
"inquirer": {
"type": "input",
"message": "Enter the parent model name:",
"default": "Parent"
}
},
{
"name": "childModelName",
"description": "Child model name",
"inquirer": {
"type": "input",
"message": "Enter the child model name:",
"default": "Child"
}
}
],
"prismaScripts": [
{
"command": "ADD MODEL {{pascalCase parentModelName}} ({id String @id @default(cuid()) | name String });",
"description": "Add parent model to Prisma schema"
},
{
"command": "ADD MODEL {{pascalCase childModelName}} ({id String @id @default(cuid()) | name String | parentId String });",
"description": "Add child model to Prisma schema"
},
{
"command": "ADD RELATION {{pascalCase parentModelName}} AND {{pascalCase childModelName}} (fkHolder={{pascalCase childModelName}}, type=1:M);",
"description": "Add relation between models"
}
]
}
]
}
📝 Best Practices
-
Naming Conventions
- Use lowercase with hyphens for plugin names
- Follow TSDIAPI naming patterns
-
Project Structure
- Maintain consistent file organization
- Include all required files
- Document your plugin
-
Configuration
- Use environment variables for sensitive data
- Provide sensible defaults
- Document all configuration options
-
Error Handling
- Implement proper error handling
- Provide meaningful error messages
- Log errors appropriately
-
Testing
- Write unit tests for your plugin
- Test with different configurations
- Verify integration with TSDIAPI
-
Prisma Integration
- Define clear model relationships
- Use appropriate field types
- Add necessary indexes
- Document model relationships
- Handle migrations carefully
-
Configuration Validation
- Use strong validation rules
- Provide clear error messages
- Transform values when needed
- Set appropriate defaults
- Document all configuration options