Tools Guide
Overview
Tools are the core of NitroStack - they're functions that AI models can call. In v3.0, tools are defined using the @Tool decorator.
Basic Tool
import { ToolDecorator as Tool, z, ExecutionContext } from 'nitrostack';
export class WeatherTools {
  @Tool({
    name: 'get_weather',
    description: 'Get current weather for a location',
    inputSchema: z.object({
      city: z.string().describe('City name'),
      units: z.enum(['celsius', 'fahrenheit']).optional()
    })
  })
  async getWeather(input: any, context: ExecutionContext) {
    context.logger.info(`Fetching weather for ${input.city}`);
    
    // Your logic here
    return {
      city: input.city,
      temperature: 72,
      conditions: 'Sunny'
    };
  }
}
@Tool Decorator
Options
interface ToolOptions {
  name: string;                    // Tool identifier (required)
  description: string;              // What the tool does (required)
  inputSchema?: ZodObject;         // Input validation schema
  examples?: {                      // Example request/response
    request?: any;
    response?: any;
  };
}
Example with All Options
@Tool({
  name: 'create_user',
  description: 'Create a new user account',
  inputSchema: z.object({
    email: z.string().email().describe('User email'),
    name: z.string().min(2).describe('Full name'),
    age: z.number().min(13).optional().describe('User age')
  }),
  examples: {
    request: {
      email: 'john@example.com',
      name: 'John Doe',
      age: 25
    },
    response: {
      id: 'user-123',
      email: 'john@example.com',
      name: 'John Doe',
      created_at: '2024-01-15T10:30:00Z'
    }
  }
})
async createUser(input: any, ctx: ExecutionContext) {
  // Implementation
}
Input Validation
Zod Schemas
NitroStack uses Zod for input validation:
import { z } from 'nitrostack';
// String validation
z.string()
z.string().min(3).max(50)
z.string().email()
z.string().url()
z.string().regex(/^[A-Z]+$/)
// Number validation
z.number()
z.number().min(0).max(100)
z.number().int()
z.number().positive()
// Boolean
z.boolean()
// Enum
z.enum(['small', 'medium', 'large'])
// Array
z.array(z.string())
z.array(z.number()).min(1).max(10)
// Object
z.object({
  name: z.string(),
  age: z.number()
})
// Optional/Required
z.string().optional()
z.string().nullable()
z.string().default('default value')
Complex Schema Example
@Tool({
  name: 'create_order',
  inputSchema: z.object({
    customer: z.object({
      name: z.string(),
      email: z.string().email()
    }),
    items: z.array(z.object({
      productId: z.string(),
      quantity: z.number().int().positive()
    })).min(1),
    shipping: z.object({
      address: z.string(),
      city: z.string(),
      zip: z.string().regex(/^\d{5}$/)
    }),
    paymentMethod: z.enum(['card', 'paypal', 'apple_pay'])
  })
})
async createOrder(input: any, ctx: ExecutionContext) {
  // Input is validated before this runs
}
Execution Context
Every tool receives an ExecutionContext:
@Tool({ name: 'my_tool' })
async myTool(input: any, ctx: ExecutionContext) {
  // Authentication info
  const userId = ctx.auth?.subject;
  const token = ctx.auth?.token;
  
  // Logging
  ctx.logger.info('Tool started');
  ctx.logger.error('Something went wrong');
  ctx.logger.debug('Debug info');
  
  // Emit events
  ctx.emit('tool.executed', { input, result });
  
  // Tool name
  console.log(ctx.toolName); // 'my_tool'
  
  return { success: true };
}
Widgets
Attach UI Components
@Tool({
  name: 'get_product',
  description: 'Get product details',
  inputSchema: z.object({
    product_id: z.string()
  }),
  examples: {
    response: {
      id: 'prod-1',
      name: 'Awesome Product',
      price: 99.99
    }
  }
})
@Widget('product-card')  // ← Attach widget
async getProduct(input: any, ctx: ExecutionContext) {
  return {
    id: input.product_id,
    name: 'Awesome Product',
    price: 99.99
  };
}
The widget at src/widgets/app/product-card/page.tsx will render the response.
Guards
Require Authentication
import { UseGuards } from 'nitrostack';
import { JWTGuard } from '../../guards/jwt.guard.js';
@Tool({ name: 'delete_user' })
@UseGuards(JWTGuard)  // ← Requires JWT auth
async deleteUser(input: any, ctx: ExecutionContext) {
  const requesterId = ctx.auth?.subject;
  // Only authenticated users can call this
}
Multiple Guards
@Tool({ name: 'admin_action' })
@UseGuards(JWTGuard, AdminGuard)  // ← Both required
async adminAction(input: any, ctx: ExecutionContext) {
  // Only authenticated admins
}
Middleware
Apply Middleware
import { UseMiddleware } from 'nitrostack';
import { LoggingMiddleware } from '../../middleware/logging.middleware.js';
@Tool({ name: 'important_operation' })
@UseMiddleware(LoggingMiddleware)
async importantOperation(input: any, ctx: ExecutionContext) {
  // Logging middleware runs before and after
}
Interceptors
Transform Responses
import { UseInterceptors } from 'nitrostack';
import { TransformInterceptor } from '../../interceptors/transform.interceptor.js';
@Tool({ name: 'get_data' })
@UseInterceptors(TransformInterceptor)
async getData(input: any, ctx: ExecutionContext) {
  return { value: 42 };
  // Interceptor transforms to: { success: true, data: { value: 42 } }
}
Caching
Cache Responses
import { Cache } from 'nitrostack';
@Tool({ name: 'get_product' })
@Cache({ 
  ttl: 300,  // 5 minutes
  key: (input) => `product:${input.product_id}` 
})
async getProduct(input: any, ctx: ExecutionContext) {
  // Cached for 5 minutes per product
  return await this.productService.findById(input.product_id);
}
Rate Limiting
Limit Request Rate
import { RateLimit } from 'nitrostack';
@Tool({ name: 'send_email' })
@RateLimit({ 
  requests: 10, 
  window: '1m',
  key: (ctx) => ctx.auth?.subject || 'anonymous'
})
async sendEmail(input: any, ctx: ExecutionContext) {
  // Max 10 emails per minute per user
}
Dependency Injection
Inject Services
import { Injectable } from 'nitrostack';
@Injectable()
export class ProductService {
  async findById(id: string) {
    // Database logic
  }
}
export class ProductTools {
  constructor(private productService: ProductService) {}  // ← Injected
  
  @Tool({ name: 'get_product' })
  async getProduct(input: any, ctx: ExecutionContext) {
    return this.productService.findById(input.product_id);
  }
}
Error Handling
Throw Errors
@Tool({ name: 'get_user' })
async getUser(input: any, ctx: ExecutionContext) {
  const user = await this.userService.findById(input.user_id);
  
  if (!user) {
    throw new Error('User not found');
  }
  
  return user;
}
Custom Errors
class UserNotFoundError extends Error {
  constructor(userId: string) {
    super(`User ${userId} not found`);
    this.name = 'UserNotFoundError';
  }
}
@Tool({ name: 'get_user' })
async getUser(input: any, ctx: ExecutionContext) {
  const user = await this.userService.findById(input.user_id);
  
  if (!user) {
    throw new UserNotFoundError(input.user_id);
  }
  
  return user;
}
Exception Filters
import { UseFilters } from 'nitrostack';
import { HttpExceptionFilter } from '../../filters/http-exception.filter.js';
@Tool({ name: 'risky_operation' })
@UseFilters(HttpExceptionFilter)
async riskyOperation(input: any, ctx: ExecutionContext) {
  // Errors handled by filter
}
Events
Emit Events
@Tool({ name: 'create_order' })
async createOrder(input: any, ctx: ExecutionContext) {
  const order = await this.orderService.create(input);
  
  // Emit event
  ctx.emit('order.created', {
    orderId: order.id,
    userId: ctx.auth?.subject,
    total: order.total
  });
  
  return order;
}
Examples
CRUD Operations
export class UserTools {
  constructor(private userService: UserService) {}
  
  @Tool({
    name: 'create_user',
    description: 'Create a new user',
    inputSchema: z.object({
      email: z.string().email(),
      name: z.string()
    })
  })
  @Widget('user-created')
  async createUser(input: any, ctx: ExecutionContext) {
    const user = await this.userService.create(input);
    ctx.emit('user.created', user);
    return user;
  }
  
  @Tool({
    name: 'get_user',
    description: 'Get user by ID',
    inputSchema: z.object({
      user_id: z.string()
    })
  })
  @Cache({ ttl: 60 })
  @Widget('user-card')
  async getUser(input: any, ctx: ExecutionContext) {
    return this.userService.findById(input.user_id);
  }
  
  @Tool({
    name: 'update_user',
    description: 'Update user details',
    inputSchema: z.object({
      user_id: z.string(),
      name: z.string().optional(),
      email: z.string().email().optional()
    })
  })
  @UseGuards(JWTGuard)
  async updateUser(input: any, ctx: ExecutionContext) {
    return this.userService.update(input.user_id, input);
  }
  
  @Tool({
    name: 'delete_user',
    description: 'Delete a user',
    inputSchema: z.object({
      user_id: z.string()
    })
  })
  @UseGuards(JWTGuard, AdminGuard)
  async deleteUser(input: any, ctx: ExecutionContext) {
    await this.userService.delete(input.user_id);
    return { success: true };
  }
}
Complex Tool
@Tool({
  name: 'process_payment',
  description: 'Process a payment transaction',
  inputSchema: z.object({
    order_id: z.string(),
    payment_method: z.enum(['card', 'paypal']),
    amount: z.number().positive(),
    card: z.object({
      number: z.string().regex(/^\d{16}$/),
      cvv: z.string().regex(/^\d{3,4}$/),
      expiry: z.string().regex(/^\d{2}\/\d{2}$/)
    }).optional()
  }),
  examples: {
    request: {
      order_id: 'order-123',
      payment_method: 'card',
      amount: 99.99,
      card: {
        number: '4111111111111111',
        cvv: '123',
        expiry: '12/25'
      }
    },
    response: {
      transaction_id: 'txn-456',
      status: 'completed',
      amount: 99.99,
      timestamp: '2024-01-15T10:30:00Z'
    }
  }
})
@UseGuards(JWTGuard)
@UseMiddleware(LoggingMiddleware)
@UseInterceptors(TransformInterceptor)
@RateLimit({ requests: 5, window: '1m' })
@Widget('payment-confirmation')
async processPayment(input: any, ctx: ExecutionContext) {
  const userId = ctx.auth?.subject;
  
  // Validate order
  const order = await this.orderService.findById(input.order_id);
  if (!order) {
    throw new Error('Order not found');
  }
  
  // Process payment
  const transaction = await this.paymentService.process({
    orderId: input.order_id,
    userId,
    method: input.payment_method,
    amount: input.amount,
    card: input.card
  });
  
  // Emit events
  ctx.emit('payment.processed', transaction);
  
  return transaction;
}
Best Practices
1. Clear Descriptions
// ✅ Good
@Tool({
  name: 'search_products',
  description: 'Search products by name, category, or price range with pagination'
})
// ❌ Avoid
@Tool({
  name: 'search_products',
  description: 'Search'
})
2. Use describe() in Schemas
// ✅ Good
inputSchema: z.object({
  query: z.string().describe('Search query (product name or SKU)'),
  min_price: z.number().optional().describe('Minimum price filter in USD')
})
// ❌ Avoid
inputSchema: z.object({
  query: z.string(),
  min_price: z.number().optional()
})
3. Provide Examples
// ✅ Good - AI understands better
@Tool({
  name: 'get_product',
  examples: {
    request: { product_id: 'prod-123' },
    response: { id: 'prod-123', name: 'Product', price: 99.99 }
  }
})
// ❌ Avoid - No examples
@Tool({ name: 'get_product' })
4. Use Services
// ✅ Good - Testable, reusable
export class ProductTools {
  constructor(private productService: ProductService) {}
  
  @Tool({ name: 'get_product' })
  async getProduct(input: any) {
    return this.productService.findById(input.id);
  }
}
// ❌ Avoid - Logic in tool
export class ProductTools {
  @Tool({ name: 'get_product' })
  async getProduct(input: any) {
    const db = getDatabase();
    return db.query('SELECT * FROM products WHERE id = ?', [input.id]);
  }
}
5. Consistent Naming
// ✅ Good - snake_case, verb_noun
'get_product'
'create_user'
'update_order'
'delete_item'
'search_products'
// ❌ Avoid
'getProduct'  // camelCase
'Product'     // No verb
'DoStuff'     // Unclear
Next Steps
Pro Tip: Use nitrostack generate types after defining tools to get TypeScript types for your widgets!