Nitrocloud LogoNitroStack
/sdk
/typescript
/resources

Resources Guide

Overview

Resources expose data that AI models can read. In v3.0, resources are defined using the @Resource decorator.

Basic Resource

import { ResourceDecorator as Resource, ExecutionContext } from 'nitrostack';

export class ProductResources {
  @Resource({
    uri: 'product://{id}',
    name: 'Product Details',
    description: 'Get detailed product information',
    mimeType: 'application/json'
  })
  async getProduct(uri: string, context: ExecutionContext) {
    // Extract ID from URI
    const id = uri.split('://')[1];
    
    const product = await this.productService.findById(id);
    
    return {
      contents: [{
        uri,
        mimeType: 'application/json',
        text: JSON.stringify(product, null, 2)
      }]
    };
  }
}

@Resource Decorator

Options

interface ResourceOptions {
  uri: string;                // URI template (required)
  name: string;               // Resource name (required)
  description: string;        // What the resource provides (required)
  mimeType?: string;          // Content type (default: 'text/plain')
  examples?: {                // Example response
    response?: any;
  };
}

Complete Example

@Resource({
  uri: 'user://{userId}/profile',
  name: 'User Profile',
  description: 'Get complete user profile with preferences and settings',
  mimeType: 'application/json',
  examples: {
    response: {
      id: 'user-123',
      name: 'John Doe',
      email: 'john@example.com',
      preferences: {
        theme: 'dark',
        language: 'en'
      }
    }
  }
})
async getUserProfile(uri: string, ctx: ExecutionContext) {
  const userId = uri.match(/user:\/\/([^\/]+)/)[1];
  const profile = await this.userService.getProfile(userId);
  
  return {
    contents: [{
      uri,
      mimeType: 'application/json',
      text: JSON.stringify(profile, null, 2)
    }]
  };
}

URI Templates

Simple URIs

@Resource({
  uri: 'config://app',
  name: 'App Configuration'
})
async getConfig(uri: string) {
  return {
    contents: [{
      uri,
      text: JSON.stringify({ version: '1.0.0' })
    }]
  };
}

Parameterized URIs

// Single parameter
@Resource({ uri: 'product://{id}' })

// Multiple parameters
@Resource({ uri: 'order://{orderId}/item/{itemId}' })

// Path parameters
@Resource({ uri: 'file:///{path}' })

Extracting Parameters

@Resource({ uri: 'order://{orderId}/item/{itemId}' })
async getOrderItem(uri: string) {
  // Parse URI
  const match = uri.match(/order:\/\/([^\/]+)\/item\/([^\/]+)/);
  const orderId = match[1];
  const itemId = match[2];
  
  const item = await this.orderService.getItem(orderId, itemId);
  
  return {
    contents: [{
      uri,
      mimeType: 'application/json',
      text: JSON.stringify(item)
    }]
  };
}

Response Format

Contents Array

return {
  contents: [
    {
      uri: 'product://123',
      mimeType: 'application/json',
      text: '{"id": "123", "name": "Product"}'
    }
  ]
};

Multiple Contents

@Resource({ uri: 'report://{id}' })
async getReport(uri: string) {
  const id = uri.split('://')[1];
  const report = await this.reportService.generate(id);
  
  return {
    contents: [
      {
        uri: `${uri}/summary`,
        mimeType: 'text/plain',
        text: report.summary
      },
      {
        uri: `${uri}/data`,
        mimeType: 'application/json',
        text: JSON.stringify(report.data)
      },
      {
        uri: `${uri}/chart`,
        mimeType: 'image/png',
        blob: report.chartImage  // Binary data
      }
    ]
  };
}

MIME Types

Common Types

// JSON data
mimeType: 'application/json'

// Plain text
mimeType: 'text/plain'

// HTML
mimeType: 'text/html'

// Markdown
mimeType: 'text/markdown'

// Images
mimeType: 'image/png'
mimeType: 'image/jpeg'

// CSV
mimeType: 'text/csv'

// XML
mimeType: 'application/xml'

Example with Different Types

export class DocumentResources {
  @Resource({
    uri: 'document://{id}',
    name: 'Document',
    mimeType: 'text/markdown'
  })
  async getDocument(uri: string) {
    const id = uri.split('://')[1];
    const doc = await this.docService.findById(id);
    
    return {
      contents: [{
        uri,
        mimeType: 'text/markdown',
        text: doc.content
      }]
    };
  }
  
  @Resource({
    uri: 'document://{id}/html',
    name: 'Document HTML',
    mimeType: 'text/html'
  })
  async getDocumentHtml(uri: string) {
    const id = uri.match(/document:\/\/([^\/]+)/)[1];
    const html = await this.docService.toHtml(id);
    
    return {
      contents: [{
        uri,
        mimeType: 'text/html',
        text: html
      }]
    };
  }
}

UI Widgets

Attach Widgets to Resources

@Resource({
  uri: 'product://{id}',
  name: 'Product Details',
  examples: {
    response: {
      id: 'prod-1',
      name: 'Awesome Product',
      price: 99.99
    }
  }
})
@Widget('product-card')  // ← Attach widget
async getProduct(uri: string) {
  const id = uri.split('://')[1];
  const product = await this.productService.findById(id);
  
  return {
    contents: [{
      uri,
      mimeType: 'application/json',
      text: JSON.stringify(product)
    }]
  };
}

Guards

Protected Resources

@Resource({ uri: 'user://{id}/private' })
@UseGuards(JWTGuard)  // ← Auth required
async getPrivateData(uri: string, ctx: ExecutionContext) {
  const requesterId = ctx.auth?.subject;
  const userId = uri.match(/user:\/\/([^\/]+)/)[1];
  
  // Only user can access their own data
  if (requesterId !== userId) {
    throw new Error('Unauthorized');
  }
  
  const data = await this.userService.getPrivateData(userId);
  
  return {
    contents: [{
      uri,
      mimeType: 'application/json',
      text: JSON.stringify(data)
    }]
  };
}

Caching

Cache Resource Responses

@Resource({ uri: 'config://app' })
@Cache({ ttl: 3600 })  // Cache for 1 hour
async getConfig(uri: string) {
  const config = await this.configService.load();
  
  return {
    contents: [{
      uri,
      mimeType: 'application/json',
      text: JSON.stringify(config)
    }]
  };
}

Dependency Injection

Use Services

@Injectable()
export class ProductService {
  async findById(id: string) {
    // Database logic
  }
}

export class ProductResources {
  constructor(private productService: ProductService) {}  // ← Injected
  
  @Resource({ uri: 'product://{id}' })
  async getProduct(uri: string) {
    const id = uri.split('://')[1];
    return {
      contents: [{
        uri,
        text: JSON.stringify(await this.productService.findById(id))
      }]
    };
  }
}

Examples

Configuration Resource

@Resource({
  uri: 'config://app',
  name: 'Application Configuration',
  description: 'Current application settings and preferences',
  mimeType: 'application/json'
})
@Cache({ ttl: 300 })
async getAppConfig(uri: string, ctx: ExecutionContext) {
  const config = {
    version: process.env.APP_VERSION || '1.0.0',
    environment: process.env.NODE_ENV || 'development',
    features: {
      authentication: true,
      widgets: true,
      caching: true
    },
    limits: {
      maxUploadSize: 10 * 1024 * 1024,  // 10MB
      rateLimit: 100
    }
  };
  
  return {
    contents: [{
      uri,
      mimeType: 'application/json',
      text: JSON.stringify(config, null, 2)
    }]
  };
}

Database Resource

@Resource({
  uri: 'database://stats',
  name: 'Database Statistics',
  description: 'Current database size and performance metrics'
})
@UseGuards(AdminGuard)
async getDatabaseStats(uri: string) {
  const stats = await this.db.getStats();
  
  return {
    contents: [{
      uri,
      mimeType: 'application/json',
      text: JSON.stringify({
        size: stats.size,
        tables: stats.tableCount,
        connections: stats.activeConnections,
        queryTime: stats.avgQueryTime
      }, null, 2)
    }]
  };
}

File Resource

@Resource({
  uri: 'file:///{path}',
  name: 'File Content',
  description: 'Read file contents from the project',
  mimeType: 'text/plain'
})
async getFile(uri: string) {
  const path = uri.replace('file:///', '');
  
  // Security: Validate path
  if (path.includes('..') || path.startsWith('/')) {
    throw new Error('Invalid path');
  }
  
  const content = await fs.readFile(path, 'utf-8');
  
  return {
    contents: [{
      uri,
      mimeType: 'text/plain',
      text: content
    }]
  };
}

API Resource

@Resource({
  uri: 'api://weather/{city}',
  name: 'Weather Data',
  description: 'Current weather from external API'
})
@Cache({ ttl: 600 })  // Cache for 10 minutes
async getWeather(uri: string) {
  const city = uri.match(/weather\/([^\/]+)/)[1];
  
  const response = await fetch(
    `https://api.weather.com/v1/current?city=${city}&key=${process.env.WEATHER_API_KEY}`
  );
  
  const data = await response.json();
  
  return {
    contents: [{
      uri,
      mimeType: 'application/json',
      text: JSON.stringify(data)
    }]
  };
}

Best Practices

1. Use Descriptive URIs

// ✅ Good
'product://{id}'
'user://{userId}/profile'
'order://{orderId}/invoice'

// ❌ Avoid
'resource://{id}'
'data://{id}'

2. Set Correct MIME Types

// ✅ Good
mimeType: 'application/json'  // For JSON
mimeType: 'text/markdown'     // For Markdown
mimeType: 'text/html'         // For HTML

// ❌ Avoid
mimeType: 'text/plain'  // For everything

3. Cache When Appropriate

// ✅ Good - Static data
@Resource({ uri: 'config://app' })
@Cache({ ttl: 3600 })

// ✅ Good - Slow external API
@Resource({ uri: 'api://weather/{city}' })
@Cache({ ttl: 600 })

// ❌ Avoid - Real-time data
@Resource({ uri: 'stock://price/{symbol}' })
@Cache({ ttl: 3600 })  // Too long for stock prices

4. Validate URI Parameters

// ✅ Good
@Resource({ uri: 'user://{id}' })
async getUser(uri: string) {
  const id = uri.split('://')[1];
  
  if (!id || id.length < 3) {
    throw new Error('Invalid user ID');
  }
  
  return {
    contents: [{
      uri,
      text: JSON.stringify(await this.userService.findById(id))
    }]
  };
}

// ❌ Avoid - No validation
@Resource({ uri: 'user://{id}' })
async getUser(uri: string) {
  const id = uri.split('://')[1];
  return {
    contents: [{
      uri,
      text: JSON.stringify(await this.userService.findById(id))
    }]
  };
}

5. Use Services

// ✅ Good - Reusable logic
export class UserResources {
  constructor(private userService: UserService) {}
  
  @Resource({ uri: 'user://{id}' })
  async getUser(uri: string) {
    const id = uri.split('://')[1];
    return {
      contents: [{
        uri,
        text: JSON.stringify(await this.userService.findById(id))
      }]
    };
  }
}

// ❌ Avoid - Logic in resource
export class UserResources {
  @Resource({ uri: 'user://{id}' })
  async getUser(uri: string) {
    const id = uri.split('://')[1];
    const db = getDatabase();
    const user = db.query('SELECT * FROM users WHERE id = ?', [id]);
    return {
      contents: [{ uri, text: JSON.stringify(user) }]
    };
  }
}

Next Steps


Pro Tip: Resources are great for exposing configuration, documentation, and API data that AI models can reference during conversations!